From 76f5c19d272b7f73198751a65d8ca6a5a4864bb1 Mon Sep 17 00:00:00 2001 From: Jan Sydänviita Date: Thu, 20 Aug 2026 18:39:12 +0300 Subject: Add multi user support to forge --- roles/jails/22_forge/defaults/main.yml | 14 +- roles/jails/22_forge/files/git-mirrors.sh | 6 +- .../jails/22_forge/files/git-shell-commands/delete | 3 + roles/jails/22_forge/files/git-shell-commands/new | 41 +++- .../jails/22_forge/files/git-shell-commands/rename | 13 ++ roles/jails/22_forge/tasks/main.yml | 226 ++++++++++++--------- .../22_forge/templates/etc_ssh_sshd_config.j2 | 9 +- .../22_forge/templates/usr_local_etc_cgitrc.j2 | 2 +- .../templates/usr_local_etc_nginx_nginx.conf.j2 | 2 +- 9 files changed, 206 insertions(+), 110 deletions(-) (limited to 'roles') diff --git a/roles/jails/22_forge/defaults/main.yml b/roles/jails/22_forge/defaults/main.yml index 3065221..3c4f527 100644 --- a/roles/jails/22_forge/defaults/main.yml +++ b/roles/jails/22_forge/defaults/main.yml @@ -2,12 +2,20 @@ userland: "15.1-RELEASE" zfs: - name: forge_repos - owner: "1000" - group: "1000" + mode: "0755" + - name: forge_config + mode: "0755" nullfs: - src: forge_repos - dst: /home/git + dst: /var/db/repos + - src: forge_config + dst: /var/db/forge-config + +forge_users: + - name: jan + uid: 2000 + authorized_keys: "{{ forge_authorized_keys }}" pkg: - git diff --git a/roles/jails/22_forge/files/git-mirrors.sh b/roles/jails/22_forge/files/git-mirrors.sh index c96a000..6de665a 100644 --- a/roles/jails/22_forge/files/git-mirrors.sh +++ b/roles/jails/22_forge/files/git-mirrors.sh @@ -1,9 +1,11 @@ #!/bin/sh # Run hourly via cron. Mirrors all repos that have a 'mirror' remote configured. -REPOS_DIR="/home/git" +REPOS_DIR="/var/db/repos" LOG="/var/log/git-mirrors.log" -export GIT_SSH_COMMAND="ssh -i /home/git/.ssh/forge_deploy -o IdentitiesOnly=yes" +CONFIG_DIR="/var/db/forge-config/$(whoami)" +export GIT_SSH_COMMAND="ssh -i $CONFIG_DIR/.ssh/forge_deploy -F $CONFIG_DIR/.ssh/config -o IdentitiesOnly=yes" +export GIT_CONFIG_GLOBAL="$CONFIG_DIR/.gitconfig" echo "$(date) Starting mirror run" >> "$LOG" diff --git a/roles/jails/22_forge/files/git-shell-commands/delete b/roles/jails/22_forge/files/git-shell-commands/delete index 1c1ce0d..78666ed 100644 --- a/roles/jails/22_forge/files/git-shell-commands/delete +++ b/roles/jails/22_forge/files/git-shell-commands/delete @@ -7,5 +7,8 @@ if [ ! -d "$1.git" ]; then echo "Repository $1 does not exist" exit 1 fi + +GROUP="r_$(echo "$1" | sed 's/^\.//' | sha1 -q | cut -c1-14)" rm -rf "$1.git" +pw groupdel "$GROUP" 2>/dev/null || true echo "Deleted repository $1" diff --git a/roles/jails/22_forge/files/git-shell-commands/new b/roles/jails/22_forge/files/git-shell-commands/new index b8f73a5..331d546 100644 --- a/roles/jails/22_forge/files/git-shell-commands/new +++ b/roles/jails/22_forge/files/git-shell-commands/new @@ -1,17 +1,40 @@ #!/bin/sh if [ -z "$1" ]; then echo "Usage: new [REPO_NAME]" - echo "Tip: prefix with '.' to make private (e.g. new .dotfiles)" + echo "Tip: prefix with '.' to make private (e.g. new .myrepo)" exit 1 fi -if [ -d "$1.git" ]; then - echo "Repository $1 already exists" + +REPO="$1" +REPO_PATH="$HOME/$REPO.git" +GROUP="r_$(echo "$REPO" | sed 's/^\.//' | sha1 -q | cut -c1-14)" + +if [ -d "$REPO_PATH" ]; then + echo "Repository $REPO already exists" exit 1 fi -git init --bare "$1.git" -echo "$1" > "$1.git/description" -echo "Created repository $1" -echo "Clone: git@forge.jan.systems:$1.git" -if [ "${1#.}" = "$1" ]; then - echo "Web: https://forge.jan.systems/$1.git" + +# Create repo group +pw groupadd "$GROUP" 2>/dev/null || true +pw groupmod "$GROUP" -m "$USER" 2>/dev/null || true + +# Init bare repo +git init --bare "$REPO_PATH" +echo "$REPO" > "$REPO_PATH/description" + +# Set ownership and permissions +chown -R "$USER:$GROUP" "$REPO_PATH" + +# Public: rwxr-x--- (group can read, others nothing) +# Private (dot-prefix): rwx------ (only owner) +if [ "${REPO#.}" = "$REPO" ]; then + chmod -R 2755 "$REPO_PATH" +else + chmod -R 2700 "$REPO_PATH" +fi + +echo "Created repository $REPO" +echo "Clone: $USER@forge.jan.systems:$REPO.git" +if [ "${REPO#.}" = "$REPO" ]; then + echo "Web: https://forge.jan.systems/$REPO.git" fi diff --git a/roles/jails/22_forge/files/git-shell-commands/rename b/roles/jails/22_forge/files/git-shell-commands/rename index 2d1f73e..8586991 100644 --- a/roles/jails/22_forge/files/git-shell-commands/rename +++ b/roles/jails/22_forge/files/git-shell-commands/rename @@ -11,5 +11,18 @@ if [ -d "$2.git" ]; then echo "Repository $2 already exists" exit 1 fi + +OLD_GROUP="r_$(echo "$1" | sed 's/^\.//' | sha1 -q | cut -c1-14)" +NEW_GROUP="r_$(echo "$2" | sed 's/^\.//' | sha1 -q | cut -c1-14)" + mv "$1.git" "$2.git" + +# Rename group +pw groupmod "$OLD_GROUP" -l "$NEW_GROUP" 2>/dev/null || { + pw groupadd "$NEW_GROUP" 2>/dev/null || true + pw groupmod "$NEW_GROUP" -m "$USER" 2>/dev/null || true + pw groupdel "$OLD_GROUP" 2>/dev/null || true +} + +chown -R "$USER:$NEW_GROUP" "$2.git" echo "Renamed $1 to $2" diff --git a/roles/jails/22_forge/tasks/main.yml b/roles/jails/22_forge/tasks/main.yml index cf1e9c6..03108df 100644 --- a/roles/jails/22_forge/tasks/main.yml +++ b/roles/jails/22_forge/tasks/main.yml @@ -2,131 +2,148 @@ name: jail tasks_from: jail_setup -- name: Create git group - group: - name: git - gid: 1000 - -- name: Create git user - user: - name: git - uid: 1000 - group: git - home: /home/git - shell: /usr/local/bin/git-shell - comment: "Git Server" - create_home: false +# --- Shared git-shell-commands --- -- name: Deploy gitconfig for git user - copy: - content: | - [init] - defaultBranch = main - dest: /home/git/.gitconfig - owner: git - group: git - mode: "0644" - -- name: Create /usr/local/etc/git directory +- name: Create /usr/local/libexec/git-shell-commands directory file: - path: /usr/local/etc/git + path: /var/db/repos/git-shell-commands state: directory owner: root group: wheel mode: "0755" -- name: Deploy gitconfig for www user (safe.directory for cgit/fcgiwrap) +- name: Deploy git-shell-commands copy: - content: | - [safe] - directory = * - dest: /usr/local/etc/git/gitconfig-www + src: "{{ jail_role_dir }}/files/git-shell-commands/{{ item }}" + dest: "/var/db/repos/git-shell-commands/{{ item }}" owner: root group: wheel - mode: "0644" + mode: "0755" + loop: + - help + - new + - ls + - delete + - rename + - describe + - mirror-to + +# --- Remove legacy git user --- + +- name: Remove legacy git user + user: + name: git + state: absent + remove: false + ignore_errors: true + +- name: Remove legacy git group + group: + name: git + state: absent + ignore_errors: true + +# --- Forge users --- + +- name: Create forge user groups + group: + name: "{{ item.name }}" + gid: "{{ item.uid }}" + loop: "{{ forge_users }}" + loop_control: + label: "{{ item.name }}" + +- name: Create forge users + user: + name: "{{ item.name }}" + uid: "{{ item.uid }}" + group: "{{ item.name }}" + home: /var/db/repos + shell: /usr/local/bin/git-shell + create_home: false + loop: "{{ forge_users }}" + loop_control: + label: "{{ item.name }}" + +- name: Create per-user config directories + file: + path: "/var/db/forge-config/{{ item.name }}" + state: directory + owner: "{{ item.name }}" + group: "{{ item.name }}" + mode: "0700" + loop: "{{ forge_users }}" + loop_control: + label: "{{ item.name }}" -- name: Create git .ssh directory +- name: Create .ssh directories for forge users file: - path: /home/git/.ssh + path: "/var/db/forge-config/{{ item.name }}/.ssh" state: directory - owner: git - group: git + owner: "{{ item.name }}" + group: "{{ item.name }}" mode: "0700" + loop: "{{ forge_users }}" + loop_control: + label: "{{ item.name }}" + +- name: Deploy authorized_keys for forge users + copy: + content: "{{ item.authorized_keys }}\n" + dest: "/var/db/forge-config/{{ item.name }}/.ssh/authorized_keys" + owner: "{{ item.name }}" + group: "{{ item.name }}" + mode: "0600" + loop: "{{ forge_users }}" + loop_control: + label: "{{ item.name }}" + +- name: Deploy gitconfig for primary forge user (jan) + copy: + content: | + [init] + defaultBranch = main + dest: "/var/db/forge-config/{{ forge_users[0].name }}/.gitconfig" + owner: "{{ forge_users[0].name }}" + group: "{{ forge_users[0].name }}" + mode: "0644" - name: Deploy forge deploy private key copy: content: "{{ forge_deploy_key.private }}" - dest: /home/git/.ssh/forge_deploy - owner: git - group: git + dest: "/var/db/forge-config/{{ forge_users[0].name }}/.ssh/forge_deploy" + owner: "{{ forge_users[0].name }}" + group: "{{ forge_users[0].name }}" mode: "0600" - name: Deploy forge deploy public key copy: content: "{{ forge_deploy_key.public }}" - dest: /home/git/.ssh/forge_deploy.pub - owner: git - group: git + dest: "/var/db/forge-config/{{ forge_users[0].name }}/.ssh/forge_deploy.pub" + owner: "{{ forge_users[0].name }}" + group: "{{ forge_users[0].name }}" mode: "0644" -- name: Deploy SSH config for git user +- name: Deploy SSH config for primary forge user copy: content: | Host * - IdentityFile ~/.ssh/forge_deploy + IdentityFile /var/db/forge-config/{{ forge_users[0].name }}/.ssh/forge_deploy IdentitiesOnly yes StrictHostKeyChecking=accept-new - dest: /home/git/.ssh/config - owner: git - group: git - mode: "0600" - -- name: Deploy authorized_keys - copy: - content: "{{ forge_authorized_keys }}\n" - dest: /home/git/.ssh/authorized_keys - owner: git - group: git + dest: "/var/db/forge-config/{{ forge_users[0].name }}/.ssh/config" + owner: "{{ forge_users[0].name }}" + group: "{{ forge_users[0].name }}" mode: "0600" -- name: Create git-shell-commands directory - file: - path: /home/git/git-shell-commands - state: directory - owner: git - group: git - mode: "0755" - -- name: Deploy git-shell-commands - copy: - src: "{{ jail_role_dir }}/files/git-shell-commands/{{ item }}" - dest: "/home/git/git-shell-commands/{{ item }}" - owner: git - group: git - mode: "0755" - loop: - - help - - new - - ls - - delete - - rename - - describe - - mirror-to - -- name: Create /usr/local/etc/cgit directory - file: - path: /usr/local/etc/cgit - state: directory - owner: root - group: wheel - mode: "0755" +# --- Git mirrors --- - name: Create git-mirrors log file file: path: /var/log/git-mirrors.log state: touch - owner: git - group: git + owner: "{{ forge_users[0].name }}" + group: "{{ forge_users[0].name }}" mode: "0644" - name: Deploy git-mirrors script @@ -137,17 +154,45 @@ group: wheel mode: "0755" -- name: Deploy crontab for git user +- name: Deploy crontab for primary forge user copy: content: | SHELL=/bin/sh PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin 0 * * * * /usr/local/bin/git-mirrors - dest: /var/cron/tabs/git + dest: "/var/cron/tabs/{{ forge_users[0].name }}" owner: root group: wheel mode: "0600" +# --- cgit/www config --- + +- name: Create /usr/local/etc/git directory + file: + path: /usr/local/etc/git + state: directory + owner: root + group: wheel + mode: "0755" + +- name: Deploy gitconfig for www user (safe.directory for cgit/fcgiwrap) + copy: + content: | + [safe] + directory = * + dest: /usr/local/etc/git/gitconfig-www + owner: root + group: wheel + mode: "0644" + +- name: Create /usr/local/etc/cgit directory + file: + path: /usr/local/etc/cgit + state: directory + owner: root + group: wheel + mode: "0755" + - name: Deploy cgit head-include copy: src: "{{ jail_role_dir }}/files/head-include.html" @@ -213,7 +258,6 @@ group: wheel mode: "0644" notify: Restart sshd - - import_role: name: jail tasks_from: jail_launch diff --git a/roles/jails/22_forge/templates/etc_ssh_sshd_config.j2 b/roles/jails/22_forge/templates/etc_ssh_sshd_config.j2 index c99143c..11cf4a7 100644 --- a/roles/jails/22_forge/templates/etc_ssh_sshd_config.j2 +++ b/roles/jails/22_forge/templates/etc_ssh_sshd_config.j2 @@ -8,11 +8,14 @@ HostKey /etc/ssh/ssh_host_ed25519_key PermitRootLogin no PubkeyAuthentication yes -AuthorizedKeysFile .ssh/authorized_keys +AuthorizedKeysFile /var/db/forge-config/%u/.ssh/authorized_keys PasswordAuthentication no ChallengeResponseAuthentication no -AllowUsers git - UseDNS no PrintMotd no + +{% for user in forge_users %} +Match User {{ user.name }} + SetEnv GIT_CONFIG_GLOBAL=/var/db/forge-config/{{ user.name }}/.gitconfig +{% endfor %} diff --git a/roles/jails/22_forge/templates/usr_local_etc_cgitrc.j2 b/roles/jails/22_forge/templates/usr_local_etc_cgitrc.j2 index 1fac7f1..7a0e040 100644 --- a/roles/jails/22_forge/templates/usr_local_etc_cgitrc.j2 +++ b/roles/jails/22_forge/templates/usr_local_etc_cgitrc.j2 @@ -27,4 +27,4 @@ readme=:README readme=:README.txt # Scan /home/git for bare repositories -scan-path=/home/git +scan-path=/var/db/repos diff --git a/roles/jails/22_forge/templates/usr_local_etc_nginx_nginx.conf.j2 b/roles/jails/22_forge/templates/usr_local_etc_nginx_nginx.conf.j2 index 7baf367..c154f2c 100644 --- a/roles/jails/22_forge/templates/usr_local_etc_nginx_nginx.conf.j2 +++ b/roles/jails/22_forge/templates/usr_local_etc_nginx_nginx.conf.j2 @@ -22,7 +22,7 @@ http { include fastcgi_params; fastcgi_param SCRIPT_FILENAME /usr/local/bin/git-http-backend; fastcgi_param GIT_HTTP_EXPORT_ALL ""; - fastcgi_param GIT_PROJECT_ROOT /home/git; + fastcgi_param GIT_PROJECT_ROOT /var/db/repos; fastcgi_param GIT_CONFIG_GLOBAL /usr/local/etc/git/gitconfig-www; fastcgi_param PATH_INFO /$1/$2; fastcgi_pass unix:/var/run/fcgiwrap/fcgiwrap.sock; -- cgit v1.3