aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Sydänviita <jan@sydanviita.fi>2026-08-20 18:39:12 +0300
committerJan Sydänviita <jan@sydanviita.fi>2026-08-20 18:51:22 +0300
commit76f5c19d272b7f73198751a65d8ca6a5a4864bb1 (patch)
treefb8071125b587d60a7577c0660fc63a233a8fd2d
parent55a67ef35de8b425b83372667de223c46f3307ea (diff)
Add multi user support to forge
-rw-r--r--roles/jails/22_forge/defaults/main.yml14
-rw-r--r--roles/jails/22_forge/files/git-mirrors.sh6
-rw-r--r--roles/jails/22_forge/files/git-shell-commands/delete3
-rw-r--r--roles/jails/22_forge/files/git-shell-commands/new41
-rw-r--r--roles/jails/22_forge/files/git-shell-commands/rename13
-rw-r--r--roles/jails/22_forge/tasks/main.yml226
-rw-r--r--roles/jails/22_forge/templates/etc_ssh_sshd_config.j29
-rw-r--r--roles/jails/22_forge/templates/usr_local_etc_cgitrc.j22
-rw-r--r--roles/jails/22_forge/templates/usr_local_etc_nginx_nginx.conf.j22
9 files changed, 206 insertions, 110 deletions
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;