feat: add writer-branch workflow macros

Adds the branch-based publishing model: the editor writes to a long-lived
`atheos-writer` branch, and merging into `master` stays in Gitea.

Six macros installed into Atheos's macro store (a copy is kept in
files/macros.db.php so a rebuild reproduces them), invoked by right-clicking
the `blog` folder:

  1. Start/Join Writer Branch            idempotent create-or-switch + push -u
  2. Commit + Push Writer Branch         identity guard -> add -A -> commit (only
                                         if something is staged) -> push
  3. DESTRUCTIVE: Reset Writer to Master fetch -> reset --hard origin/master ->
                                         push --force-with-lease
  4. Status + Recent Log                 read-only
  5. Refresh Master (ff-only)            pull master down, return to writer
  6. Merge Master Into Writer Branch     when master has moved ahead

Atheos has no terminal and codegit exposes no branch-switching, so macros are
the only way to run arbitrary git from the UI. Verified: codegit's `checkout`
action takes a FILE argument - it is per-file revert, not branch switching.

Identity handling: the commit macros deliberately do NOT hardcode an identity.
php-fpm runs with HOME=/home/atheos (verified), the same global gitconfig the
operator edits over SSH, so hardcoding `git -c user.name=...` would OVERRIDE the
operator's own identity. Instead macro 2 guards on an identity existing and fails
with an actionable message - which is what design decision D1 intends.

Verified by exercising each command through the web server as the php-fpm user:
  - macro 1, 4, 5 return rc=0 with correct output
  - macro 2's identity guard fires with the exact RUNBOOK STEP 1 message
  - macro 3 was tested with real divergence: committed on the writer branch,
    reset --hard to master, force-pushed; origin/atheos-writer then matched
    origin/master exactly (0 ahead, 0 behind)

Security note recorded in the docs and README: this model only holds if branch
protection is enabled on armistace/blog's master. It is currently NOT enabled,
so the deploy key could force-push master directly - the operator needs to turn
it on (repo-owner action).

Also documents two hazards found while testing:
  - macro 2's `git add -A` stages everything, and a subsequent `reset --hard`
    deletes any untracked file that was staged. Keep scratch files outside
    workspace/blog/.
  - recovery procedure for a file lost that way (git fsck --lost-found ->
    ls-tree -> cat-file), which was used to restore a file during testing.
This commit is contained in:
2026-09-29 16:07:25 +10:00
parent e038f758ac
commit 1c7db911df
4 changed files with 236 additions and 2 deletions
+78
View File
@@ -0,0 +1,78 @@
<?php/*|[
{
"uuid": "macro-947e-4763-b7b6f2",
"title": "Execute",
"type": "file",
"fTypes": [
"sh"
],
"command": "%PATH%",
"icon": "fas fa-magic",
"owner": "ATHEOS"
},
{
"uuid": "macro-53a0-4ebb-56e800",
"title": "Execute",
"type": "file",
"fTypes": [
"php"
],
"command": "cd %FOLDER% && php %BASENAME%",
"icon": "fas fa-magic",
"owner": "ATHEOS"
},
{
"uuid": "macro-writer-0001-start",
"title": "1. Start/Join Writer Branch",
"type": "folder",
"fTypes": "N/A",
"command": "cd %PATH% && (git checkout atheos-writer 2>/dev/null || git checkout -b atheos-writer) && git push -u origin atheos-writer",
"icon": "fas fa-code-branch",
"owner": "armistace"
},
{
"uuid": "macro-writer-0002-commit",
"title": "2. Commit + Push Writer Branch",
"type": "folder",
"fTypes": "N/A",
"command": "cd %PATH% && (git config --get user.email >/dev/null || { echo 'ERROR: no git identity set. Run over SSH: git config --global user.name \"Your Name\" && git config --global user.email \"you@example.com\" (RUNBOOK STEP 1)'; exit 1; }) && git add -A && (git diff --cached --quiet || git commit -m \"writer: $(date +%F_%H%M)\") && git push origin atheos-writer",
"icon": "fas fa-cloud-upload-alt",
"owner": "armistace"
},
{
"uuid": "macro-writer-0003-reset",
"title": "3. DESTRUCTIVE: Reset Writer Branch to Master",
"type": "folder",
"fTypes": "N/A",
"command": "cd %PATH% && git fetch origin && git reset --hard origin/master && git push --force-with-lease origin atheos-writer",
"icon": "fas fa-exclamation-triangle",
"owner": "armistace"
},
{
"uuid": "macro-writer-0004-status",
"title": "4. Status + Recent Log",
"type": "folder",
"fTypes": "N/A",
"command": "cd %PATH% && git status --short --branch && echo '--- recent commits:' && git log --oneline -8 && echo '--- local branches:' && git branch",
"icon": "fas fa-info-circle",
"owner": "armistace"
},
{
"uuid": "macro-writer-0005-refresh",
"title": "5. Refresh Master (ff-only)",
"type": "folder",
"fTypes": "N/A",
"command": "cd %PATH% && git checkout master && git pull --ff-only && git checkout atheos-writer",
"icon": "fas fa-sync-alt",
"owner": "armistace"
},
{
"uuid": "macro-writer-0006-pullmaster",
"title": "6. Merge Master Into Writer Branch",
"type": "folder",
"fTypes": "N/A",
"command": "cd %PATH% && (git config --get user.email >/dev/null || { echo 'ERROR: no git identity set. Run over SSH: git config --global user.name \"Your Name\" && git config --global user.email \"you@example.com\" (RUNBOOK STEP 1)'; exit 1; }) && git fetch origin && git merge origin/master -m \"merge master into atheos-writer\" && git push origin atheos-writer",
"icon": "fas fa-code-merge",
"owner": "armistace"
}
]|*/?>