Skip to content

Instantly share code, notes, and snippets.

@CristianCantoro
Last active March 19, 2023 19:49
Show Gist options
  • Save CristianCantoro/290c4b55f6e3cc2b0b1a32e124019c55 to your computer and use it in GitHub Desktop.
Save CristianCantoro/290c4b55f6e3cc2b0b1a32e124019c55 to your computer and use it in GitHub Desktop.
Setup git user on a server as on GitHub

Setup a git user on a server to behave as on GitHub

Let's assume that you want to put your repositories in the directory /data/repositories.

Setup

  1. Create a gituser group
# addgroup --system gituser

Adding group `gituser' (GID 101) ...
Done.
  1. Create a git system user.
# adduser --system \
               --shell /usr/bin/git-shell \
               --home /data/repositories \
               --ingroup gituser \
               --disabled-password \
               --disabled-login \
                   git

Adding system user `git' (UID 101) ...
Adding new user `git' (UID 101) with group `gituser' ...
useradd: Warning: missing or non-executable shell '/usr/bin/git-shell'
Creating home directory `/data/repositories' ...
  1. Add the git user to the adm` group.
# usermod -a -G adm git
  1. Create the directory .ssh in the git user home (/data/repositories)
# mkdir /data/repositories/.ssh
  1. Create a authorized_keys in /data/repositories/.ssh using the following format:
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <ssh_public_key>  # <username>


  1. Create a .hushlogin file in /data/repositories/.ssh
# touch /data/repositories/.ssh/.hushlogin
  1. Add the following lines
Match User git
  AllowAgentForwarding no
  X11Forwarding no
  AllowTcpForwarding no

Restart the `ssh service:

# systemctl restart ssh

Effect

You can try to ssh onto the server with the `git user:

$ ssh -T git@<server>
Hi <username>! You've successfully authenticated, but I do not
provide interactive shell access.
#!/bin/bash
# bash strict mode
set -euo pipefail
IFS=$'\n\t'
SSH_LOG='/var/log/auth.log'
AUTHORIZED_KEYS='/data/repositories/.ssh/authorized_keys'
if [ -n "${SSH_CLIENT:-}" ]; then
# shellcheck disable=SC2034
IFS=' ' read -r client_ip client_port server_port <<< "$SSH_CLIENT"
log_message="Accepted publickey for git from ${client_ip} port ${client_port}"
IFS=' ' read -r auth_method key_fingerprint \
<<< "$(grep "$log_message" "$SSH_LOG" | sed -re 's/(.+)ssh2: (.+)/\2/')"
client_user="$(ssh-keygen -lf "$AUTHORIZED_KEYS" | \
grep "$key_fingerprint" | \
sed -re "s/(.+)# (.*)\\($auth_method\\)/\\2/" | \
tr -d '[:space:]')"
printf '%s\n' "Hi $client_user! You've successfully authenticated,"
printf '%s\n' "but I do not provide interactive shell access."
exit 128
else
echo "You do not appear to be connecting over SSH, however the shell for the"
echo "git user (git-shell) as limited capabilities."
echo "You will be logged out now."
exit 128
fi
#!/usr/bin/env bash
#
# set up permissions for a shared git repo.
# shellcheck disable=SC2128
SOURCED=false && [ "$0" = "$BASH_SOURCE" ] || SOURCED=true
if ! $SOURCED; then
set -euo pipefail
IFS=$'\n\t'
fi
#################### helpers
function check_dir() {
local adir="$1"
if [[ ! -d "$adir" ]]; then
(>&2 echo "Error. '$adir' is not a valid directory.")
exit 1
fi
}
function rerun_as_root() {
if [ "$(id -u)" -ne "0" ]; then
(>&2 echo "Warning: This script will be re-run as root.")
# trim leading and trailing whitespaces from string
args="$(echo "$*" | \
tr $'\n' ' ' | \
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' )"
exec sudo -E su 'root' -c "/bin/bash $0 $args"
exit 0
fi
}
#################### end: helpers
#################### help
function short_usage() {
(>&2 echo \
"Usage:
$(basename "$0") [options] <group> <repo>
$(basename "$0") [options] <owner>:<group> <repo>
")
}
function usage() {
(>&2 short_usage )
(>&2 echo \
"
Setup permissions for the git repository <repo> such that all the users
belonging to group <group>. Sets the owner of the repo to <owner>.
This script will ask for root privileges.
Arguments:
<owner> New owner of the repository.
<group> Group owbership for the repository.
<repo> Path to the repo.
Options:
-d Enable debug output.
-h Show this help and exits.
Example:
# setup 'myrepo' so that it can be shared by all users of group 'gituser'
# can read and write to it. Sets the new owner to 'git'.
$(basename "$0") git:gituser myrepo
")
}
debug=false
help_flag=false
while getopts ":dh" opt; do
case $opt in
d)
debug=true
;;
h)
help_flag=true
;;
\?)
(>&2 echo "Error. Invalid option: -$OPTARG")
exit 1
;;
:)
(>&2 echo "Error.Option -$OPTARG requires an argument.")
exit 1
;;
esac
done
if $help_flag; then
usage
exit 0
fi
#################### end: help
#################### utils
if $debug; then
function echodebug() {
(>&2 echo -en "[$(date '+%F %H:%M:%S')][debug]: ")
(>&2 echo "$@" 1>&2)
}
else
function echodebug() { true; }
fi
#################### end: utils
# Shell Script: is mixing getopts with positional parameters possible?
# https://stackoverflow.com/q/11742996/2377454
numopt="$#"
echodebug "numopt: $numopt"
echodebug "OPTIND: $OPTIND"
# parameter not specified
NARGS=2
if (( numopt-OPTIND < NARGS-1 )) ; then
(>&2 echo "Error: parameters <group> (or <owner>:<group>) and <repo> " \
"are required.")
short_usage
exit 1
fi
# unrecognized extra parameters not specified
if (( numopt-OPTIND > NARGS-1 )) ; then
(>&2 echo "Error: unrecognized extra parameters specified.")
short_usage
exit 1
fi
rerun_as_root "$@"
first_arg="${*:$OPTIND:1}"
repo="$(realpath "${*:$OPTIND+1:1}")"
owner=''
group=''
if [[ "$first_arg" =~ ^[^:]+:[^:]+$ ]]; then
# split a string and store each split into variables
# https://stackoverflow.com/a/22459219/2377454
OLDIFS=$IFS
IFS=":" read -r owner group <<< "$first_arg"
IFS=$OLDIFS
else
group="$first_arg"
fi
echodebug "Arguments:"
echodebug " * first_arg: $first_arg"
echodebug " - owner: $owner"
echodebug " - group: $group"
echodebug " * repo: $repo"
check_dir "$repo"
set -x
if [[ -n "$owner" ]]; then
chown -R "$owner":"$group" "$repo"
else
chgrp -R "$group" "$repo"
fi
chmod 2775 "$repo"
(
cd "$repo"
git config core.sharedRepository group
find . -type d -exec chmod -R g+wX {} \;
)
exit 0

Set up permissions for a shared git repo.

Sources:

What we do here is the following:

  1. change ownership or repo to the desired owner and group
  2. set the permissions for this repo to drwxrwxr-x+setgit
  3. set git configuration core.sharedRepository group in the repo
  4. resursively set mode g+wX to the directories in the repo with:
    find . -type d -exec sudo chmod -R g+wX {} \;
    The mode g+wX sets the following permissions for the group:
    • +w: grants write permission to the group.
    • +X: Grants execute permission to the group, but only for directories and files that already have at least one execute bit set (i.e., it will not add execute permission to a file that didn't have it before). Note you may see guides or tutorials suggesting these two commands:
    find . -type f -exec sudo chmod 2775 {} \;
    find . -type f -exec sudo chmod 0664 {} \;
    the first command is roughly equivalent to the following two, but better.

In short, these are the operations:

sudo chown -R <owner>:<group> /path/to/repository.git
sudo chmod 2775 /path/to/repository.git

cd /path/to/repository.git
git config core.sharedRepository group
find . -type d -exec sudo chmod -R g+wX {} \;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment