diff --git a/.github/workflows/build-push-latest.yml b/.github/workflows/build-push-latest.yml new file mode 100644 index 0000000..ca27941 --- /dev/null +++ b/.github/workflows/build-push-latest.yml @@ -0,0 +1,46 @@ +name: build-push-latest + +on: + push: + branches: + - 'main' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + docker: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee4c926 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/test diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6c26aae --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM alpine:3.19 + +RUN set -eux; \ + apk add --no-cache \ + bash \ + openssh \ + git \ + jq + +WORKDIR /srv/ + +COPY bin bin +COPY conf conf + +RUN mkdir -p /srv/keys +RUN echo -n "" > /etc/motd + +ENV EXTERNAL_PORT="2222" +ENV EXTERNAL_HOSTNAME="localhost" + +EXPOSE 22 + +ENTRYPOINT ["bin/entrypoint.sh"] +CMD ["/usr/sbin/sshd", "-D"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..9a79044 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +Originaly inspired by https://github.com/jkarlosb/git-server-docker + +This container allows to run a minimal git server with a basic CLI to manage repositories. +It supports multiple accounts. + +# Minimal configuration + +This container requires 3 volumes in order to correctly persist data : +- /srv/ssh to persist generated server keys +- /srv/git to store repositories +- /srv/keys to setup available users, with allowed public keys + + + +# /srv/keys structure + +``` +user-A:12345 + key_A.pub +user-B:12346 + key_B.pub +shared:12347 + key_A.pub + key_B.pub +``` + +For every account, create a folder `username:uid` in /srv/keys. +Put every public keys allowed to access this account in the created directory. diff --git a/bin/00-setup-ssh.sh b/bin/00-setup-ssh.sh new file mode 100755 index 0000000..820e1a5 --- /dev/null +++ b/bin/00-setup-ssh.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +if [[ -L /etc/ssh ]]; then + echo "SSH already configured, skipping" +else + cp -R /etc/ssh /srv/ssh + rm -fr /etc/ssh + ln -s /srv/ssh /etc/ssh + + cp /srv/conf/sshd_config /srv/ssh/sshd_config + + ssh-keygen -A +fi \ No newline at end of file diff --git a/bin/10-create-users.sh b/bin/10-create-users.sh new file mode 100755 index 0000000..369c597 --- /dev/null +++ b/bin/10-create-users.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +for usercfg in $(ls /srv/keys); do + + params=(${usercfg//:/ }) + user=${params[0]} + uid=${params[1]} + + current_uid=$(id -u "$user" 2>/dev/null || echo "0") + + if [[ "$current_uid" == "0" ]]; then + echo "Creating $user with uid=$uid (current uid=$current_uid)" + adduser -D -s /usr/bin/git-shell -u $uid "$user" + + # sshd does not allow to login if no password is set + random_pwd=$(cat /proc/sys/kernel/random/uuid) + echo "$user":"$random_pwd" | chpasswd + + cp -R /srv/conf/git-shell-commands /home/"$user"/git-shell-commands + chmod -R 755 /home/"$user"/git-shell-commands + elif [[ "$current_uid" != "$uid" ]]; then + echo "Fatal, cannot change UID (from $current_uid to $uid). Please re-create container."; + exit 1 + else + continue + fi +done diff --git a/bin/20-setup-keys.sh b/bin/20-setup-keys.sh new file mode 100755 index 0000000..84fa801 --- /dev/null +++ b/bin/20-setup-keys.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +for usercfg in $(ls /srv/keys); do + params=(${usercfg//:/ }) + user=${params[0]} + + mkdir -p /home/"$user"/.ssh + echo "Loading keys for $user" + if [ "$(ls -A /srv/keys/"$usercfg")" ]; then + cd /home/"$user" || exit + cat /srv/keys/"$usercfg"/*.pub > .ssh/authorized_keys + fi + chown -R "$user":"$user" .ssh + chmod 700 .ssh + chmod -R 600 .ssh/* +done diff --git a/bin/30-setup-repos.sh b/bin/30-setup-repos.sh new file mode 100755 index 0000000..458a596 --- /dev/null +++ b/bin/30-setup-repos.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +for usercfg in $(ls /srv/keys); do + params=(${usercfg//:/ }) + user=${params[0]} + + echo "Setting up repos for $user" + if [ ! -d /srv/git/"$user" ]; then + echo "Warning: missing repo folder, creating it" + mkdir -p /srv/git/"$user" + fi + + chown -R "$user":"$user" /srv/git/"$user" +done diff --git a/bin/40-update-env.sh b/bin/40-update-env.sh new file mode 100755 index 0000000..8bb50e9 --- /dev/null +++ b/bin/40-update-env.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +touch /srv/env + +echo "REPO_ROOT=/srv/git" > /srv/env +echo "EXTERNAL_HOSTNAME=$EXTERNAL_HOSTNAME" >> /srv/env +echo "EXTERNAL_PORT=$EXTERNAL_PORT" >> /srv/env + +chmod 777 /srv/env \ No newline at end of file diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh new file mode 100755 index 0000000..0404f43 --- /dev/null +++ b/bin/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +cd /srv/bin || exit +myself=$(basename "$0") + +for file in * +do + case "$file" in + "$myself"): + ;; + *) + echo "Executing $file"; "./${file}" ;; + esac +done + +exec "$@" \ No newline at end of file diff --git a/conf/git-shell-commands/create b/conf/git-shell-commands/create new file mode 100755 index 0000000..4907c84 --- /dev/null +++ b/conf/git-shell-commands/create @@ -0,0 +1,25 @@ +#!/bin/sh + +. /srv/env + +show_usage() { + echo -e "Usage: create REPO_NAME" +} + +if [ $# -ne 1 ] +then + show_usage + exit 1 +fi + +echo "Creating repo $1" +GIT_REPO="${REPO_ROOT}/$USER/$1" +if [[ -d "$GIT_REPO" ]]; then + echo "Already exists, skipped !" + exit 1 +fi +git init --bare "$GIT_REPO" + + +echo "You can now clone it :" +echo "git clone ssh://${USER}@$EXTERNAL_HOSTNAME:$EXTERNAL_PORT${GIT_REPO}" diff --git a/conf/git-shell-commands/help b/conf/git-shell-commands/help new file mode 100755 index 0000000..a346304 --- /dev/null +++ b/conf/git-shell-commands/help @@ -0,0 +1,9 @@ +#!/bin/sh + +. /srv/env + +echo "Availables commands :" +echo " create REPO_NAME : create a git repo" +echo " show REPO_NAME : get clone url" +echo " list : list availables repos" +echo \ No newline at end of file diff --git a/conf/git-shell-commands/list b/conf/git-shell-commands/list new file mode 100755 index 0000000..2fe9188 --- /dev/null +++ b/conf/git-shell-commands/list @@ -0,0 +1,5 @@ +#!/bin/sh + +. /srv/env + +ls --color=auto $REPO_ROOT/${USER} diff --git a/conf/git-shell-commands/show b/conf/git-shell-commands/show new file mode 100755 index 0000000..d49bcbd --- /dev/null +++ b/conf/git-shell-commands/show @@ -0,0 +1,20 @@ +#!/bin/sh + +. /srv/env + +show_usage() { + echo -e "Usage: show REPO_NAME" +} + +if [ $# -ne 1 ] +then + show_usage + exit 1 +fi + +GIT_REPO="${REPO_ROOT}/$USER/$1" +if [[ -d "$GIT_REPO" ]]; then + echo "git clone ssh://${USER}@$EXTERNAL_HOSTNAME:$EXTERNAL_PORT${GIT_REPO}" +else + echo "Repo does not exists" +fi diff --git a/conf/sshd_config b/conf/sshd_config new file mode 100644 index 0000000..399f2ae --- /dev/null +++ b/conf/sshd_config @@ -0,0 +1,9 @@ +PubkeyAuthentication yes +AuthorizedKeysFile .ssh/authorized_keys +PasswordAuthentication no + +AllowTcpForwarding no +GatewayPorts no +X11Forwarding no + +Subsystem sftp /usr/lib/ssh/sftp-server diff --git a/run_local.sh b/run_local.sh new file mode 100755 index 0000000..b36fd3c --- /dev/null +++ b/run_local.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +mkdir -p test + +docker stop test-minimal-git-server > /dev/null 2>&1 +docker rm test-minimal-git-server > /dev/null 2>&1 + +docker build -t test-minimal-git-server . +docker run -v ${PWD}/test/ssh:/srv/ssh -v ${PWD}/test/git:/srv/git -v ${PWD}/test/keys:/srv/keys \ + --env EXTERNAL_PORT=20222 --name test-minimal-git-server -d -p 20222:22 test-minimal-git-server + +docker logs -f test-minimal-git-server \ No newline at end of file