diff --git a/.github/workflows/build-push-latest.yml b/.github/workflows/build-push-latest.yml new file mode 100644 index 0000000..2b3a95e --- /dev/null +++ b/.github/workflows/build-push-latest.yml @@ -0,0 +1,47 @@ +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: + provenance: false + 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..1437702 --- /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/accounts +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..7b89248 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +Originally 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/accounts` to setup available accounts, with allowed public keys + +``` +docker run -v .../ssh:/srv/ssh -v .../git:/srv/git -v .../accounts:/srv/accounts \ + --env EXTERNAL_PORT=20222 --env EXTERNAL_HOSTNAME=xxxx \ + --name minimal-git-server -d -p 20222:22 ghcr.io/mcarbonne/minimal-git-server:latest +``` + + +# /srv/accounts 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/accounts. +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..78cfbcd --- /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..ddf635a --- /dev/null +++ b/bin/10-create-users.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +for usercfg in /srv/accounts/*; do + IFS=":" read -r -a params <<< "$(basename "$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-accounts.sh b/bin/20-setup-accounts.sh new file mode 100755 index 0000000..3201d2d --- /dev/null +++ b/bin/20-setup-accounts.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +for usercfg in /srv/accounts/*; do + IFS=":" read -r -a params <<< "$(basename "$usercfg")" + user=${params[0]} + + mkdir -p /home/"$user"/.ssh + echo "Loading keys for $user" + if [ "$(ls -A "$usercfg")" ]; then + cd /home/"$user" || exit + cat "$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..a13af42 --- /dev/null +++ b/bin/30-setup-repos.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +for usercfg in /srv/accounts/*; do + IFS=":" read -r -a params <<< "$(basename "$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..8287922 --- /dev/null +++ b/conf/git-shell-commands/create @@ -0,0 +1,26 @@ +#!/bin/sh + +#shellcheck disable=SC1091 +. /srv/env + +show_usage() { + echo "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..90f4d86 --- /dev/null +++ b/conf/git-shell-commands/help @@ -0,0 +1,10 @@ +#!/bin/sh + +#shellcheck disable=SC1091 +. /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..8e31c6b --- /dev/null +++ b/conf/git-shell-commands/list @@ -0,0 +1,6 @@ +#!/bin/sh + +#shellcheck disable=SC1091 +. /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..323b766 --- /dev/null +++ b/conf/git-shell-commands/show @@ -0,0 +1,21 @@ +#!/bin/sh + +#shellcheck disable=SC1091 +. /srv/env + +show_usage() { + echo "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..3494cd4 --- /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/accounts:/srv/accounts \ + --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