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

@ -6,3 +6,4 @@
- After any meaningful change set: **commit and push** to `main` with a clear, complete-sentence message (what changed and why).
- Never commit secrets: `.env`, `wireguard/wg0.conf`, `config/`, GGUF models, `ansible/.secrets/`.
- Prefer SSH push (`GIT_SSH_COMMAND` with `~/.ssh/id_ed25519` if needed).
- Server checkout: `/opt/stack` is the same repo (`git pull` on ser5). Ansible uses `stack_deploy_method: git`.

View file

@ -195,9 +195,17 @@ forgejo_root_url: "http://{{ forgejo_domain }}:{{ forgejo_http_port }}/"
# ---------------------------------------------------------------------------
# Stack deployment
# ---------------------------------------------------------------------------
# Sync this repo (parent of ansible/) to stack_root on the server.
# Set false if you manage /opt/stack yourself (git clone, etc.).
deploy_stack_files: true
# Deploy stack *code* to the server.
# git — /opt/stack is a clone of Forgejo (preferred; use `git pull` on ser5)
# rsync — push from the control machine (laptop) tree
stack_deploy_method: git
deploy_stack_files: true # master switch for code deploy tasks
# Forgejo remote used on the server (same host → prefer loopback SSH port)
stack_git_repo: "ssh://git@127.0.0.1:2222/tim/stack.git"
stack_git_branch: main
# Also accept the LAN URL if someone clones from another machine
stack_git_repo_lan: "ssh://git@192.168.8.123:2222/tim/stack.git"
# Paths on the control machine (the machine running ansible-playbook)
# Default: the arr-stack repo that contains this ansible/ directory

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

View file

@ -18,6 +18,19 @@ ansible-playbook site.yml -K # -K only if sudo still asks once; playboo
What it does: OS updates, tools (incl. Microsoft `edit`), Docker, AMD/Vulkan, UFW (LAN only), `/opt/stack` + `/storage`, `.env`, `arr-stack.service`, and **tim** as local admin (passwordless sudo, docker/video/render/…, owns stack + storage, permissive GPU nodes).
**Code on the server:** `/opt/stack` is a **git clone** of Forgejo `tim/stack` (`stack_deploy_method: git`).
Day-to-day updates on ser5:
```bash
ssh tim@192.168.8.123
cd /opt/stack
git pull
docker compose up -d # if compose/images changed
```
Ansible can also refresh code: `ansible-playbook site.yml -K --tags stack,deploy` (runs `git fetch` + checkout).
Local-only (not in git): `.env`, `config/`, `models/**/*.gguf`, `wireguard/wg0.conf`.
**Before first full stack start** (on ser5):
```bash