Make /opt/stack a Forgejo git checkout instead of rsync-only

Prefer stack_deploy_method=git: init/fetch on the server, register an
SSH deploy key with Forgejo, and leave local secrets untracked. Document
git pull for day-to-day updates; keep rsync as an opt-in fallback.
This commit is contained in:
tim 2026-07-22 11:12:07 -07:00
parent 6c9e085cbd
commit 90f3cf62c5
4 changed files with 159 additions and 10 deletions

View file

@ -47,14 +47,142 @@
loop: "{{ media_subdirs }}"
tags: [stack, storage]
- name: Deploy stack files from control machine
# ---------------------------------------------------------------------------
# Code deploy: git (preferred) or rsync
# Local-only paths stay untracked: config/, .env, models/*.gguf, wireguard/wg0.conf
# ---------------------------------------------------------------------------
- name: Ensure git is installed on stack host
ansible.builtin.apt:
name: git
state: present
when: deploy_stack_files | bool and stack_deploy_method == "git"
tags: [stack, deploy, git]
- name: Ensure stack owner has an SSH key for Forgejo git
become: true
become_user: "{{ stack_owner }}"
ansible.builtin.user:
name: "{{ stack_owner }}"
generate_ssh_key: true
ssh_key_type: ed25519
ssh_key_file: "/home/{{ stack_owner }}/.ssh/id_ed25519"
ssh_key_comment: "{{ stack_owner }}@{{ inventory_hostname }}-forgejo"
when: deploy_stack_files | bool and stack_deploy_method == "git"
tags: [stack, deploy, git]
- name: Read stack owner public key
ansible.builtin.slurp:
src: "/home/{{ stack_owner }}/.ssh/id_ed25519.pub"
register: stack_owner_pubkey
when: deploy_stack_files | bool and stack_deploy_method == "git"
tags: [stack, deploy, git]
- name: Ensure Forgejo knows the stack host deploy key (idempotent via API best-effort)
ansible.builtin.shell: |
set -e
PUB='{{ stack_owner_pubkey.content | b64decode | trim }}'
# Create a short-lived admin token, add key if fingerprint not present, drop token
TOKEN=$(docker exec -u 1000 forgejo gitea admin user generate-access-token \
--username "{{ stack_owner }}" \
--token-name "ansible-deploy-$(date +%s)" \
--scopes all 2>/dev/null | awk '/Access token was successfully created:/{print $NF}')
if [ -z "$TOKEN" ]; then
echo "Could not create Forgejo token (is forgejo up?). Continuing — key may already exist."
exit 0
fi
# List keys; add if title missing
if curl -fsS -H "Authorization: token $TOKEN" http://127.0.0.1:3002/api/v1/user/keys \
| grep -q "ansible-ser5\|{{ inventory_hostname }}-forgejo"; then
echo "SSH key already registered"
else
curl -fsS -X POST http://127.0.0.1:3002/api/v1/user/keys \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"title\":\"ansible-{{ inventory_hostname }}\",\"key\":\"$PUB\"}" || true
fi
args:
executable: /bin/bash
when:
- deploy_stack_files | bool
- stack_deploy_method == "git"
changed_when: false
failed_when: false
tags: [stack, deploy, git]
- name: Initialize git repo in stack root if missing
become: true
become_user: "{{ stack_owner }}"
ansible.builtin.command: git init -b {{ stack_git_branch }}
args:
chdir: "{{ stack_root }}"
creates: "{{ stack_root }}/.git"
when: deploy_stack_files | bool and stack_deploy_method == "git"
tags: [stack, deploy, git]
- name: Configure origin remote
become: true
become_user: "{{ stack_owner }}"
ansible.builtin.shell: |
set -e
cd "{{ stack_root }}"
if git remote get-url origin >/dev/null 2>&1; then
git remote set-url origin "{{ stack_git_repo }}"
else
git remote add origin "{{ stack_git_repo }}"
fi
args:
executable: /bin/bash
when: deploy_stack_files | bool and stack_deploy_method == "git"
changed_when: true
tags: [stack, deploy, git]
- name: Mark stack root as safe.directory for git
become: true
become_user: "{{ stack_owner }}"
ansible.builtin.command: git config --global --add safe.directory {{ stack_root }}
changed_when: false
failed_when: false
when: deploy_stack_files | bool and stack_deploy_method == "git"
tags: [stack, deploy, git]
- name: Fetch and hard-reset tracked files to origin (keeps untracked secrets)
become: true
become_user: "{{ stack_owner }}"
ansible.builtin.shell: |
set -euo pipefail
cd "{{ stack_root }}"
export GIT_SSH_COMMAND="ssh -i /home/{{ stack_owner }}/.ssh/id_ed25519 -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new"
git fetch origin "{{ stack_git_branch }}"
# Preserve local-only paths; only reset tracked files
git checkout -B "{{ stack_git_branch }}" "origin/{{ stack_git_branch }}"
args:
executable: /bin/bash
when: deploy_stack_files | bool and stack_deploy_method == "git"
register: stack_git_pull
tags: [stack, deploy, git]
- name: Show git HEAD after pull
become: true
become_user: "{{ stack_owner }}"
ansible.builtin.command: git -C {{ stack_root }} log -1 --oneline
register: stack_git_head
changed_when: false
when: deploy_stack_files | bool and stack_deploy_method == "git"
tags: [stack, deploy, git]
- name: Report stack git revision
ansible.builtin.debug:
msg: "stack @ {{ stack_git_head.stdout | default('n/a') }}"
when: deploy_stack_files | bool and stack_deploy_method == "git"
tags: [stack, deploy, git]
- name: Deploy stack files from control machine (rsync fallback)
ansible.posix.synchronize:
src: "{{ stack_src }}/"
dest: "{{ stack_root }}/"
delete: false
recursive: true
# Non-root rsync cannot chown/chgrp files Docker created as root
# (e.g. searxng). Content sync only; ownership fixed in next task.
archive: true
owner: false
group: false
@ -72,11 +200,11 @@
- "--no-owner"
- "--no-group"
- "--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r"
when: deploy_stack_files | bool
when: deploy_stack_files | bool and stack_deploy_method == "rsync"
become: false
tags: [stack, deploy]
tags: [stack, deploy, rsync]
- name: Fix ownership after rsync
- name: Fix ownership of stack tree
ansible.builtin.file:
path: "{{ stack_root }}"
state: directory
@ -84,7 +212,6 @@
group: "{{ stack_group }}"
recurse: true
when: deploy_stack_files | bool
# become: true from play — can reset root-owned files from containers
tags: [stack, deploy]
- name: Deploy production .env for this host