Setting Up Git Repositories in `/srv/` Directory on Ubuntu Server 24.04.2 LTS
- Ubuntu Server 24.04.2 LTS
- Git
So, you decided to setup a home server on an old laptop. (Or on your VPS). Here is how to setup a Git repositories directory without sharing it with anyone but yourself. But why?
Table of Contents
- Table of Contents
- Story
- Prerequisites
- Short Setup
- Full Setup
- FIN
- Related Documentation Links
- Extras
If you’re looking for a simple, single-user setup, you can skip to the “Full Setup” section or the “Short Setup” if you’re already comfortable with configuring an Ubuntu Server environment. Other sections of this post are more of an output of my personal interest in details.
Story
Let’s start by addressing the “Ok But Why?” question.
I’ve been using source code repository hosting services for a long time, and I will continue to do so. However, in today’s software development landscape, where AI crawlers are increasingly involved, maintaining repositories on these services reminds me of a years-old concern: Privacy! Do I have full control over my content? I haven’t seen a clear option in the settings of these services, and frankly, I don’t believe any of them would truly respect our privacy choices, even if such an option existed.
For now, there isn’t a free solution that satisfies me in the cloud. The internet was supposed to be a free environment, but clearly, it is not the case anymore. Until I find a better alternative, I’ve decided to keep my side projects local.
I’m aware that a few personal projects can add zero to none value to LLMs, especially if the coding is done by me. 😀 But it’s not about the value of the code; it’s about having a choice.
Prerequisites
This post doesn’t cover the initial setup for Ubuntu Server, installing Git, or setting up SSH access on your server. To get the most out of this post, you need to have:
- A server with
Ubuntu Server 24.04.2 LTSinstalled. (Probably other versions will also work. I tested it with the default image on an old laptop and with the24.04.2 LTS (GNU/Linux 6.8.0-1018-raspi aarch64)image on a Raspberry Pi 3. On the Raspberry Pi Imager software it’s code isServer OS for RPi Zero 2W/3/4/400/5.) - A
sudouser on the server. (SSH-accessible by you.) - Git and SSH software installed on both the server and the client. (Ubuntu Server includes both Git and OpenSSH.)
- And a person who feels uncomfortable using the
rootuser for such operations!
Server, as used in this post, is the place where you set up and store your bare repositories. It’s the origin and the single source of truth. Client is any other place where you clone your repositories from the server. Users’ local copies of the origin repositories.
Short Setup
A super short commands summary for the lazy people. If you’re looking for an explained version, visit the Full Setup section.
Assuming you have access to the
uglycatsudouser on the server with a public key.
# ---
# Server Part
# ---
# 1. connect to the server
ssh uglycat@your.server.ip.address
# 2. create parent git directory in `/srv/`
cd /srv/
sudo mkdir git
# 3. change ownership of `/srv/git/` directory
sudo chown uglycat:uglycat /srv/git/
# 4. (optional) setup default git branch
git config --global init.defaultBranch main
# 5. create a directory for your first repository
cd git/
mkdir my-first-repo.git
# 6. initialize a bare repo
cd my-first-repo.git/
git init --bare
# 7. logout from server
logout
# ---
# Client Part
# ---
# 1. create a directory on your computer (wherever you keep your repositories)
cd ~
mkdir Repositories
cd Repositories/
# 2. clone the bare repo you've just created
git clone uglycat@your.server.ip.address:/srv/git/my-first-repo.git/
# 3. verify write access
cd my-first-repo/
touch README.md
git add README.md
git commit -m "Added README file"
git push
# FIN
Full Setup
First, start with verifying your username and the user groups you belong to. After connecting to your server with SSH, run the commands below for verification:
# verify your user name:
whoami
# uglycat
# verify your groups:
groups uglycat
# uglycat : uglycat some other groups sudo ...
So, we are the user uglycat and we belong to the sudo group. Now, let’s continue with the directory we’ll use as the repository directory.
The /srv directory is the proper place to use such content serving purposes in Linux-like systems. (I believe it’s short for “serve”.) We’ll use the /srv directory; however, it’s a root user owned directory by default. Run ls -l in the / directory of the Ubuntu Server and verify it. (Focus on the ... root root ... srv parts of the console output.)
# change to the / directory
cd /
# find the /srv directory in the list and see the owner
ls -l
# PERMISSIONS LINK_COUNT root root SIZE DATE srv
I’ve seen comments and articles that suggest changing permissions on the /srv/ directory itself. That’s not a good idea, I guess. We won’t do that. Instead, we’ll create a directory under the /srv/ directory to keep our repositories. We’ll use /srv/git/, but the name doesn’t matter.
# change to the /srv directory
cd /srv/
# create /srv/git/ directory (WITH sudo)
sudo mkdir git
# check created directory
ls -l
# PERMISSIONS LINK_COUNT root root SIZE DATE git
Above, we used the sudo command to create the git directory because the parent directory /srv belongs to the root user. Now we need to change the ownership of the git directory so we can interact with it as the uglycat user.
# change ownership of /srv/git/ directory (WITH sudo)
sudo chown uglycat:uglycat /srv/git/
# check the directory again
# added `-a` flag to see the current (.) and parent (..) directories are still `root` owned.
ls -al
# PERMISSIONS LINK_COUNT uglycat uglycat SIZE DATE git
A Note on
uglycat:uglycat(user:group) notation:In Ubuntu (and most Linux distros), every user has their own group with the same user name. This is a feature called User Private Groups. Here, we’re using a single user setup, so that’s ok. See the extra content below on multiple users.
And that’s all you need to do to set up the parent directory (git) of your future repositories.
Before creating repositories, I suggest you set the global Git default branch name with:
# setup default git branch
git config --global init.defaultBranch main
git config set --global init.defaultBranch=main # (git version 2.46.0 and above)
git config --list --global
git config list --global # (git version 2.46.0 and above)
Not required to continue, but while we’re here:
# setup default git user
git config --global user.email "uglycat@ugly.cat"
git config --global user.name "Ugly Cat"
git config set --global user.email "uglycat@ugly.cat" # (git version 2.46.0 and above)
git config set --global user.name "Ugly Cat" # (git version 2.46.0 and above)
Check the default Git configuration:
git config --list --global
# init.defaultbranch=main
# user.email=uglycat@ugly.cat
# user.name=Ugly Cat
Now we have our basic global Git configuration. Let’s create the first Git repository under the /srv/git/ directory:
# assuming we're still in the /srv directory
cd git/
# `mkdir` WITHOUT `sudo` since it's already owned by uglycat
mkdir my-first-repo.git
cd my-first-repo.git/
git init --bare
# Initialized empty Git repository in /srv/git/my-first-repo.git/
Adding
.gitat the end of the bare repository name is a common naming convention for bare repositories.
Congratulations! 🎉 You’ve set your first bare repository on your home server. Now let’s see if we have proper access to this repository from outside the server.
First, log out from your server (logout command) and return to your client machine. Set a directory to keep your repositories.
# switch to the client
logout
# assuming you're on the same machine! :)
cd ~
mkdir Repositories
cd Repositories/
# clone your repo under the `~/Repositories/` directory
git clone uglycat@your.server.ip.address:/srv/git/my-first-repo.git/
# Cloning into 'my-first-repo'...
# warning: You appear to have cloned an empty repository.
# see if the default branch is set as `main`
cd my-first-repo/
# my-first-repo git:(main)
git status
# On branch main
# ...
And you have read access if you see the same above. Let’s check if we have write access as well:
# create and empty file
touch README.md
# add & commit
git add README.md
git commit -m "Added README file"
# push to remote
git push
# Enumerating objects: 3, done.
# ...
# To your.server.ip.address:/srv/git/my-first-repo.git
# * [new branch] main -> main
And if you see a successful push message on your terminal, you’re all set!
# optionally, see the commit log
git log
FIN
Contrary to the common belief, there’s a possibility that the future internet will push us more and more local. This could also result in more and more services being placed behind paywalls. This is my temporary plan to keep my work to myself while dreaming of a truly free internet.
Continue with the “Extras” section to dig deeper into different use cases.
Related Documentation Links
- 4.2 Git on the Server - Getting Git on a Server
- 4.4 Git on the Server - Setting Up the Server
- git-shell - Restricted login shell for Git-only SSH access
- sshd — OpenSSH daemon
- ssh — OpenSSH remote login client
- tar - an archiving utility
- Notation of traditional Unix permissions
Extras
Here are a few extras for more use cases.
Extras 1: Putting an Existing Repository as a Bare Repo on the Server
You should have the server setup described in the Full Setup section before continuing with this section. (Or a similar one. Or just be sure of having a read/write accessible server directory.)
# change directory to your existing repository folder
cd my-existing-repo/
# optional but beneficial
# switch to the `main` branch to set the remote branch head to main (you can change it later)
git checkout main
# switch to the top directory (or a different directory)
cd ..
# clone the existing repo as a bare (mind the ".git" part)
git clone --bare ./my-existing-repo/ ./my-existing-repo.git/
# optionally, see the commit log of the newly created bare repository
# switch to cloned bare repo (mind the ".git" part)
cd my-existing-repo.git/
git log
When creating a bare repository, all the
origin/*counterparts of local branches will be created, even if you don’t have them in your current origin. You can delete them later if you don’t want them in theorigin. (Something like a local-only work-in-progress feature branch.)
We created the bare repository on the client. Now we need to push it to the server:
# scp is OpenSSH's secure file copy tool
# recursively (-r) copy the contents of the client copy to the `/srv/git/` directory in the server
scp -r ./my-existing-repo.git/ uglycat@your.server.ip.address:/srv/git/
# wait until copy operation finishes
# ...
# ... (lots of hashed numbers and file names)
# ...
# SSH into the server and see if the repo is copied
ssh uglycat@your.server.ip.address
ls -al /srv/git/
# PERMISSIONS LINK_COUNT uglycat uglycat SIZE DATE my-existing-repo.git
# switch to the client
logout
# you can now delete the old repository and clone the server copy to continue from the local repository
cd ~/Repositories/
git clone uglycat@your.server.ip.address:/srv/git/my-existing-repo.git/
cd my-existing-repo/
git log
Extras 2: Multiple Git Users Setup
If you want to set up a shared repository directory for multiple outside users, here’s a quick and easy way:
Outside Users are users who either have no direct access to the server or are users of the server without
sudoprivileges. Thegit-shareduser account will be created here.
Backup User is an always-available account to access the server with
sudoprivileges to correct things in case of situations like accidentally or knowingly locking yourself out of your user access.
Here are the steps we will follow to create the shared Git account:
- Create a new user on the server with
sudoaccess and name it something likegit-shared, or any other name. - Log out from the current user, copy your public key to
git-shareduser as, well and log back in as the newgit-shareduser. - Create the shared directory under
/srv/git-shared/and change ownership of the directory to thegit-shareduser andgit-sharedgroup as we did in the previous section. - Create a bare repository.
- Copy outside users’ public keys to the
~/.ssh/authorized_keysfile of thegit-shareduser manually.
Now, we can start adding the git-shared user:
# 1. create `git-shared` user
sudo adduser git-shared
sudo usermod -aG sudo git-shared
# 2. logout and login again with the new `git-shared` user (you need to copy your public key to the `git-shared` user as well with `ssh-copy-id` for ssh access)
logout
ssh git-shared@your.server.ip.address
# 3. create the shared repo directory
sudo mkdir /srv/git-shared/
sudo chown git-shared:git-shared /srv/git-shared/
# 4. create a bare repo
cd /srv/git-shared/
mkdir my-shared-repo.git
cd my-shared-repo.git/
git config --global init.defaultBranch main
git init --bare
# 5. copy public keys (you need to ask each outsider to share you with their public key)
echo "outside-user-s-public-key-1" >> ~/.ssh/authorized_keys
echo "outside-user-s-public-key-2" >> ~/.ssh/authorized_keys
echo "outside-user-s-public-key-3" >> ~/.ssh/authorized_keys
# step 5. confused you? that's good, you're reading, thanks
# since we've already copied our very own public key to access the `git-shared` user account on the server confusion is expected
# think your own public key as the administrator role
# the intention here is to have public keys of outside contributors, not yours or administrator's
# outsiders' public keys should be added in the `~/.ssh/authorized_keys` in a restricted way not in an administrative manner, as explained below
# to continue with the tutorial, you may use your own public key, just be sure to have a backup `sudo` account
The only difference from the single-user setup in the first section is a new git-shared user account with multiple public keys.
We assume that outside users won’t have any access to the server other than restricted git interactions. So, you need to copy and paste their public keys manually as the administrator of the
git-shareduser account.
⚠️ This setup is not complete yet! With this setup, outside users will still have full access to the
git-shareduser, who hassudoprivileges. This is not desirable. We’ll restrict it in a bit.
Before moving forward, a helper for the server. Since git-shared account users should not know about the server, add an empty .hushlogin file under the user directory to suppress welcome message of the server:
# add this file to the `git-shared` user's home directory to suppress initial server messages after ssh connection
touch ~/.hushlogin
# this will not suppress the custom welcome messages we'll add in this section later
Another helper below, for the client. If you just want to test SSH connections, you may use the -T flag:
# use -T flag if your intention is to see only the shell access status in a limited shell environment that doesn't need pseudo-terminal allocation
ssh -T git-shared@your.server.ip.address
You don’t need
-Tflag when you need to access the server with the default shell. (It won’t connect anyway.)
No more extra information will be provided with these helpers when logging into the server. Let’s continue.
Thanks to one of the most powerful tools on the internet, “Git,” we have a helper inside the Git software called git-shell. It’s a restricted login shell for Git-only SSH access.
The change we need to complete the setup is simple:
- Add
git-shellto the list of shells allowed by Ubuntu Server. - Then replace the
git-shareduser’s default shell withgit-shell.
Before moving further, make sure you have a backup
sudoaccount to restore things later (Like, re-enabling the default shell for thegit-shareduser or managing thegit-shareduser’s server configurations.)
By default, Ubuntu Server supports multiple shells. To see what shell you’re currently using as the git-shared user, use the command below:
cat /etc/passwd | grep git-shared
# git-shared:...:...:/home/git-shared:/bin/bash
# Bash (/bin/bash) is the default shell for most user accounts
Now add git-shell to the shell list:
# list current shells
cat /etc/shells
# check existence of `git-shell`
which git-shell
# /usr/bin/git-shell
# add the path of `git-shell` (/usr/bin/git-shell) to the valid login shell list (/etc/shells)
# or `sudo nano /etc/shells`
# or `echo "/usr/bin/git-shell" | sudo tee --append /etc/shells`
sudo --edit /etc/shells
# add `/usr/bin/git-shell` to the list
# list current shells again to see if `git-shell` is added into the list
cat /etc/shells
The final step is to change the git-shared user’s default login shell to git-shell:
# change default shell of the `git-shared` user
sudo chsh git-shared --shell $(which git-shell)
# or use hard-coded value `/usr/bin/git-shell` instead of command subsition `$()`
# re-check the login shell
cat /etc/passwd | grep git-shared
# git-shared:...:...:/home/git-shared:/usr/bin/git-shell
Now, you may log out of the git-shared user and try to SSH into the server again using the same user:
⚠️ Instead of logging out, you may also keep the current server shell open. It will still keep the default shell as Bash until you close this terminal session. You can switch to another terminal window to avoid restricted shell lock-in temporarily. New login attempts will be forcing
git-shell.
# logout
logout
# try logging in again
ssh git-shared@your.server.ip.address
# ...
# ...
# ...
# fatal: Interactive git shell is not enabled.
# hint: ~/git-shell-commands should exist and have read and execute access.
# Connection to your.server.ip.address closed.
# you may see the server information here as well if you didn't create the `.hushlogin` file, connection error message above is at the end in that case
Congratulations again! 🎉 Your public key has no direct access to the server anymore. (You’re also locked in, btw, if you’re using your own public key and you’ve listened to me and logged out. 🤣)
Now, you can’t use the default login shell, but you can pull and push repositories from/to the server:
# switch to the client
# change to repositories directory
# clone repo
git clone git-shared@your.server.ip.address:/srv/git-shared/my-shared-repo.git/
# add something and push freely, it is allowed
At this point, users can not SSH into the git-shared user account, and the user account can only accept a limited set of git commands for the authorized public keys. (Including the initial administrator account, your key.)
You may stop here for the basic multiple user setup. But I’ll add 3 more interesting inclusions and then conclude this section:
- Instead of closing the server connection completely, we’ll offer an interactive
git>shell to the users with an auto-runninghelpcommand. (2.1) - We’ll disable the interactive shell completely with a pretty info message. (
2.2) - We’ll apply more restrictions to the outsiders’ public keys for more protection. (
2.3)
Before starting with the first one, here is how to unlock yourself if you’re locked in:
2.0: Resolving Account Lock-In Situations
Remember, we locked ourselves in with git-shell? When you need to update the git-shared user account configuration, you can temporarily switch to the default shell using another sudo account. Here’s the quick tutorial:
- Log in with the initial
uglycatuser. (Or any othersudouser.) - Revert the
git-shareduser’s shell to Bash (default) withsudo chsh git-shared --shell /bin/bash. - Switch to the
git-shareduser’s shell with its own environment usingsu - git-shared. (No need forsudosince we’re switching from asudouser. You’ll still be asked for thegit-shareduser’s password though.) - … Do the administrative changes you need. …
- Switch back to
git-shellwithsudo chsh git-shared --shell $(which git-shell). - Log out and try logging in again to see the changes.
You may also use the
uglycatuser to change files under thegit-shareduser’s home directory, but it might get a bit confusing every time you’re targeting thegit-shareduser’s home directory. Switching the login shell back and forth felt safer for me.
# login with another `sudo` account
ssh uglycat@your.server.ip.address
# you can't switch to `git-shared` user using `su - git-shared` for now
# revert the `git-shared` user's shell to Bash (default)
# (you're using `uglycat` user's sudo privileges)
sudo chsh git-shared --shell /bin/bash
# now you can switch user
su - git-shared
# do administrative stuff
# ...
# ...
# ...
# don't forget to switch back to the `git-shell` when you're finished
sudo chsh git-shared --shell $(which git-shell)
Now we know how to resolve the lock in situation. Let’s enable the interactive git shell.
2.1: Enabling Interactive git> Shell with a help Command
git-shell can have a special directory named git-shell-commands under a user’s home directory. You may extend the command capabilities of git-shell using this directory. Now, as the git-shared user, create a directory right under the user’s home (~) directory. Just create an empty folder, return to the git-shell, and try to log in again to see what changed.
# create the special directory (name is reserved and should be the exact same)
# be sure `git-shared` user has both read and execute access on `~/git-shell-commands` directory as stated in the hint by the server message
mkdir ~/git-shell-commands
# re-enable `git-shell`
sudo chsh git-shared --shell $(which git-shell)
# exit & logout (exit to return back from `su - git-shared` session, logout to leave the server fron `uglycat` account, here the commands are interchangable no worries)
exit
logout
# try logging in again
ssh git-shared@your.server.ip.address
# git>
# you should see a git shell (`git>`) instead of a connection refusal message
At this point, there is almost nothing you can do with this interactive shell. Let’s give some help to the visitors of this shell.
Type help in the git> interactive shell and see the error message unrecognized command 'help'.
helpis another special naming convention for a file, like thegit-shell-commandsdirectory name.helpwill be invoked automatically when users ssh into thegit>shell.
Exit from the git> shell and return back to the server.
# you may use exit command to leave `git>` shell as well
# git>
exit
# do the change user stuff again
#
# login with another sudo user
# change `git-shared` account's shell to Bash,
# switch to `git-shared` user
# create a file named `help` under the `git-shell-commands` directory
touch ~/git-shell-commands/help
# add some content into the help command
sudo nano ~/git-shell-commands/help
# add the simple "printf" command below to the `help` file to say hi right after login (without the initial "# " part of course) (also be careful with emojies in a git shell 😃)
# printf '%s\n' "Hi $USER! 👋"
# mark the file as executable
chmod +x ~/git-shell-commands/help
# re-enable `git-shell` and retry logging in from client
Now, when you try to log in to the server from a client, you should see the content from ~/git-shell-commands/help command:
# after swtiching to the `git-shell` and logging out form the server
# try logging in again
ssh git-shared@your.server.ip.address
# ...
# ...
# ...
# Hi git-shared! 👋
# # git>
And you can run the help command again and again from the interactive git> shell:
# git>
help
# Hi git-shared! 👋
# git>
You may have arbitrary commands in the same way along with the help command. Just create another file ~/git-shell-commands/some-random-command and call it in the same way from the git> shell. (Arbitrary commands will not auto-run; you need to call them manually, or as an argument to the connection command, or as a command in the authorized_keys options.)
2.2: Disabling Interactive git> Shell Completely
If you don’t want an interactive git> shell at all, another special file here to help, ~/git-shell-commands/no-interactive-login. The process is the same as with the help file.
You may use an empty file, or you may use a message here as well:
# do the change user stuff again from another sudo account and switch to the `git-shared` user
touch ~/git-shell-commands/no-interactive-login
sudo nano ~/git-shell-commands/no-interactive-login
# add message content like the one below (exit 128 is just to indicate something went wrong)
# #!/bin/sh
# printf '%s\n' "Hi $USER! 👋 You've successfully authenticated, but this server does not provide shell access."
# exit 128
chmod +x ~/git-shell-commands/no-interactive-login
# re-enable `git-shell` and retry logging in from client
ssh git-shared@your.server.ip.address
# ...
# ...
# ...
# Hi git-shared! 👋 You've successfully authenticated, but this server does not provide shell access.
# Connection to your.server.ip.address closed.
🎉 Tada!
No need to delete the previous
helpcommand withno-interactive-login. But keep in mind that commands are still callable without the interactivegit>shell. So, if you’re revealing sensitive information, delete them as well.
If you forget to make a command file executable, you’ll get
chmod: fatal: cannot exec 'git-shell-commands/no-interactive-login': Permission deniedwhen you try to log in from the client.
2.3: Applying More Restrictions on Outsiders’ Public Keys
This is not a server security article, but one last restriction note before closing.
So far, we’ve managed to block direct access to the server for all users. However, some other access-related features are still available, such as port forwarding, which enables outsiders to access configured server services and applications within the local network setup of the server.
To restrict such access-related features mentioned above, add the restrict option directly before an outside user’s public key in the ~/.ssh/authorized_keys file of the git-shared user.
# login with another sudo user, change `git-shared` account's shell to Bash, switch to `git-shared` user
# edit `authorized_keys` of the `git-shared` user
sudo nano ~/.ssh/authorized_keys
# right before each outside user's public key, add `restrict` keyword (with a space after)
restrict ssh-xxx AAABBBCCC...
The restrict keyword is a shortcut that enables all the related restrictions, such as port, agent, and X11 forwarding, PTY allocation, and execution of ~/.ssh/rc. (You can also re-enable wanted features by adding them after restrict like this: restrict,pty.)
And that’s the wrap for this section.
If you experience troubles when logging into the server after configuration changes, troubleshoot these:
- Check if you’ve switched back to the
git-shellfor thegit-shareduser. - The
-Tflag of thesshcommand and therestrictkeyword preceding a public key disable pseudo-terminal allocation. Try not using them if you need thepty. - Restricting
pty(pseudo-terminal allocation) and not enablinggit-shellwill block the SSH connection onto the server. - Emojis can cause problems. Avoid them if they are not necessary.
git-shellalso disables tab-completion on the client, so you need to know the full repository paths on the server.
And don’t forget that we made all these configurations for Git interactions. So, every time you change a configuration, check if you can still clone, pull, and push from/to the repositories.
The setup is more than enough for a small, trusted list of developers’ access controls. Many extra configurations are available with SSH and Git, but I have to stop here.
Extras 3: Possible Errors With Incorrect Directory Ownership Setup
This section is to prevent unnecessary copy & paste operations from unreliable resources. (Including this blog!)
Let’s say you’d used sudo privileges to create /srv/git/ and /srv/git/my-root-owned-repo.git/ directories on the server and initialized the my-root-owned-repo.git/ directory as a --bare repository in the same way.
When you try to clone such a repository, you’ll get a “dubious ownership” error:
git clone uglycat@your.server.ip.address:/srv/git/my-root-owned-repo.git/
# Cloning into 'my-root-owned-repo'...
# fatal: detected dubious ownership in repository at '/srv/git/my-root-owned-repo.git'
# To add an exception for this directory, call:
# git config --global --add safe.directory /srv/git/my-root-owned-repo.git
# fatal: Could not read from remote repository.
# Please make sure you have the correct access rights
# and the repository exists.
The error message (and the internet lookup) will lead you to add the repository directory to the Git global config variable safe.directory.
# login back to the server and do the recommended change
# WITHOUT using trailing slahes at the end!
git config --global --add safe.directory /srv/git/my-root-owned-repo.git
git config --global --list
This change will allow you to clone the repository, but when you try to push a change from the client to the server, you’ll get an error:
# logout and run the clone command again
logout
git clone uglycat@your.server.ip.address:/srv/git/my-root-owned-repo.git/
# create a file and commit it in the client
touch README.md
git add .
git commit -m "Initial commit"
# try to push the change
git push
# ...
# ...
# Writing objects: 100% (3/3), 221 bytes | 221.00 KiB/s, done.
# ...
# error: remote unpack failed: unable to create temporary object directory
# To your.server.ip.address:/srv/git/my-root-owned-repo.git
# ! [remote rejected] main -> main (unpacker error)
# error: failed to push some refs to 'your.server.ip.address:/srv/git/my-root-owned-repo.git'
Using the --shared flag with git init --bare --shared doesn’t matter in this case because the problem is still directory permissions on the server.
If you find yourself in such a situation, your solution is the same directory ownership change, this time providing the --recursive (or -R) flag with the already initialized repository.
# login back to the server
# in the server run `chown` command
sudo chown --recursive uglycat:uglycat /srv/git/my-root-owned-repo.git/
# logout and retry pushing in the client
logout
# then git push and you're good to go
You might want to cover all repositories under the /srv/git/ directory. In this case, run the same command for the parent directory:
# in the server run `chown` command
sudo chown --recursive uglycat:uglycat /srv/git/
The purpose of this section was to prevent you from doing crazy stuff offered by the internet. Just don’t copy and paste commands blindly, even from this blog. Ugly and old-looking documentation about these kinds of things is usually the most reliable. Read them. Again and again, …
Extras 4: Manual Backups
Having Git repositories on the cloud gives us the comfort of availability, … like forever. In a local setup like the one explained here, you need to back up files manually, either with a daemon or a manual user interaction. I’m using a manual workflow for now:
- Log in to the server.
- Archive the
/srv/git/directory or a specific repository with the built-intarutility. (Optionally password protect it.) - Log out and copy the archive to the client.
- Upload the archive file to a secure environment or store them in a local storage.
Bare repositories are smaller in size since they don’t have .gitignore ignored files or dependencies such as node_modules. On the server, use the tar archiving utility to archive the my-backup-repo.git/ directory.
cd ~
tar -cavf backup.tar.gz --directory=/srv/git/ ./my-backup-repo.git
# this will create an arhive in the current directory with the name `backup.tar.gz`
# it will include a single target repository into the archive
# -c,--create: creates a new archive
# -a,--auto-compress: uses user provided suffix (`.tar.gz` here) to decide the compression algorithm
# -v,--verbose: enables verbose logging
# -f,--file: sets archive name to user provided value (backup.tar.gz here)
# switch to the client
logout
# after creating the archive, run the command below on the client to get the archived content
scp uglycat@your.server.ip.address:~/backup.tar.gz ~
# this command will copy the `backup.tar.gz` archive from server's ~ to client's ~ directory
# then you can store the backup in a secure location
You may also want to archive the entire /srv/git/ directory. In that case, use:
cd ~
tar -cavf backup.tar.gz --directory=/srv/ ./git
Extras 5: Quick Reminders on Useful Ubuntu Commands
Copying your public key from your client to a server:
# run this command on your client
ssh-copy-id uglycat@your.server.ip.address
Adding new users and giving them sudo privileges:
sudo adduser some_user_name
sudo usermod -aG sudo some_user_name
Deleting a user with all artifacts including the home directory:
sudo deluser --remove-all-files some_user_name
Switching user with the target user’s environment:
su - some_user_name
# no need for `sudo` if you're switching from a sudo user
Listing groups of a user:
groups some_user_name
Listing users and groups:
cat /etc/passwd
cat /etc/group
Getting the current user name and details:
whoami
id
Logging out from the server:
logout
Getting SSH and Git versions:
ssh -V # capital "V"
git -v
Unsetting a global git configuration:
# single config item
git config --global --unset user.name
git config unset --global user.name # (git version 2.46.0 and above)
# multiple config items
git config --global --unset-all safe.directory
git config unset --global --all safe.directory # (git version 2.46.0 and above)