Files
Varaverk/Deployment/README-Deployment.md
T

212 lines
9.3 KiB
Markdown

# ━━━━━ DEPLOYMENT ━━━━━
The schema layer. `Configurations/*.conf` holds every value the ecosystem runs on — and is
gitignored, because it holds credentials. This folder holds the **templates** those confs are
built from, and the two scripts that keep the confs in step with them.
Two files that are versioned, and two scripts that reconcile the unversioned confs against
them:
```
Deployment/master.conf.template 349 vars ← the versioned schema
Deployment/host.conf.template 140 vars ← per-host schema, HOSTN_-prefixed
Deployment/conf_upgrade.sh ← template → conf, values preserved
Deployment/conf_populate.sh ← running services → conf, empty fields only
```
> **The templates are the only versioned record of what configuration exists.** Nothing else
> in git knows that a variable is supposed to be there.
---
## ━━━ THE PROBLEM THAT BUILT THIS ━━━
**Config Holds Secrets, So Config Cannot Be Committed**
`master.conf` and `host*.conf` contain API keys, passwords, SSH key paths and personal
hostnames. They are gitignored, along with their `.bak` files:
```
.gitignore:4 Configurations/host*.conf
.gitignore:5 Configurations/master.conf
.gitignore:6 Configurations/*.bak
```
That is correct and non-negotiable. But it creates a problem: if the confs are not in git,
then **git has no idea a new setting was ever added.** A script that starts reading
`NEW_THRESHOLD` works on the machine where it was developed and silently fails everywhere
else, because no other node's conf has that key.
**A New Node Would Start With Nothing**
Without a versioned schema there is no way to stand up a second server, or rebuild a wiped
one, except by hand-copying a conf from a machine that already works — which means copying
its credentials too.
**Hand-Editing Confs Across Nodes Does Not Scale**
Two servers, ~490 variables between them. Adding a setting by hand means editing it on every
node, in the right section, with the right default, without disturbing the values already
there. Miss one and the failure surfaces days later as a script behaving differently on one
host.
---
## ━━━ WHAT THIS FOLDER DOES ━━━
### 🔀 Schema Merge — `conf_upgrade.sh`
Merges a template into an existing conf while preserving **every value the user has already
set**. Runs automatically from `git_pull_execute.sh` after every single pull.
```
Key in template only → ADDED placeholder/default, filled in once
Key in conf only → REMOVED deprecated in this version
Key in both → KEPT the conf's value always wins
Comments, blank lines → from the template — structure follows the new version
```
That last rule is what makes it safe to run unattended forever: the template supplies
*structure and new keys*, never settings. Your values cannot be overwritten by a pull.
The live confs currently match their templates exactly — 349 and 140 variables — which is
what a working merge looks like.
### 🔎 Credential Discovery — `conf_populate.sh`
Reads settings out of the services actually running on this host and writes them into the
host conf: arr API keys from each `config.xml`, ports from real docker port bindings, paths
from real volume mounts, SABnzbd/slskd/qBittorrent credentials from their own config files.
Only fills **empty** fields unless `--overwrite`. Manual — it is not scheduled anywhere.
---
## ━━━ HOW A CHANGE REACHES EVERY NODE ━━━
```
You add a variable
└── edit Deployment/master.conf.template ← the versioned schema
git push
└── every node: git_pull_execute.sh
└── conf_upgrade.sh --template ... --target ... --backup
ADDED → new key appears with the template default
KEPT → every existing value untouched
REMOVED → deprecated keys dropped
```
**This is the rule that follows from it, and it is not optional:**
> Any conf variable change — add, remove, or rename — must update
> `Deployment/master.conf.template` and `Deployment/host.conf.template` **in the same pass**
> as the script change that uses it.
A script merged without its template entry works only on the machine it was written on.
Nothing errors; the variable is simply empty everywhere else, and the script takes whatever
branch an empty value leads to.
---
## ━━━ THE `HOSTN_` PLACEHOLDER ━━━
`host.conf.template` is written with a generic prefix — 149 occurrences of `HOSTN_`:
```bash
HOSTN_SONARR_URL=""
HOSTN_SONARR_API_KEY=""
```
`git_pull_execute.sh` substitutes the real identity before merging, so keys match the target:
```bash
sed "s/HOSTN_/${MY_ID}_/g; s/REMOTE_ID/${REMOTE_ID}/g" host.conf.template > "$TMPL_RESOLVED"
```
One template therefore serves every host. HOST1 merges it as `HOST1_*`, HOST2 as `HOST2_*`,
and a third node would work with no template change at all.
---
## ━━━ SCRIPTS IN THIS FOLDER ━━━
| Script | Role | When It Runs |
|--------|------|-------------|
| `conf_upgrade.sh` | Merge template into conf — structure forward, values preserved | Automatically, after every `git pull` |
| `conf_populate.sh` | Detect settings from running services into the host conf | Manually — onboarding, or after a key rotation |
| `migrate_data_layout.sh` | Move everything persisted into the rooted `data/` tree | Once per host, manually. Idempotent. |
### 📦 Data layout migration — `migrate_data_layout.sh`
`conf_upgrade.sh` adds keys the template has and the installation does not; it never rewrites a
value you already have. That is exactly what you want from it, and exactly why it cannot perform
a layout migration — the paths being moved are *existing* keys, so their values would keep
pointing at the old layout forever while the new directory variables sat beside them unused.
So this rewrites those values and moves the files to match. Both halves or neither.
```bash
Deployment/migrate_data_layout.sh --dry-run # always first
Deployment/migrate_data_layout.sh
```
It refuses to run while a job from *this* installation is active — scoped to the installation's
own path, because `pgrep` is system-wide and a box running both a production checkout and a
development clone will otherwise always look busy. `--force` overrides.
Each host runs it itself: `data/` is gitignored, so a restructure travels as code and conf while
the files stay where they are. See `data/README.md` for the resulting layout.
| Template | Role |
|----------|------|
| `master.conf.template` | Shared schema — thresholds, toggles, profiles, orchestrator job lists |
| `host.conf.template` | Per-host schema — credentials, paths, container names. `HOSTN_`-prefixed |
---
## ━━━ SAFEGUARDS WORTH KNOWING ━━━
**The install is atomic.** `conf_upgrade.sh` stages the merged conf beside the target and
installs it with a rename, never a copy. A `cp` truncates the live conf and writes into it —
and every watchdog sources `load_config.sh` on every run, so anything reading during that
window would get a partial conf with empty path variables. The temp file is staged in the
target's own directory deliberately: `/tmp` is rootfs while the confs are on flash, and a
cross-device `mv` degrades to copy-then-unlink, which is the exact torn write being avoided.
**Dry run needs no privilege, writing does.** `--dry-run` prints the full ADDED / REMOVED /
KEPT report and is useful to anyone. Installing over a conf under `/boot` requires root.
**`conf_upgrade.sh` sources nothing — deliberately.** No `load_config.sh`, no `common.sh`.
It is the tool that repairs the conf `load_config.sh` depends on, so it has to work when that
conf is broken, partial, or missing keys. That is also why it uses plain `echo` rather than
`log()`, and why it has no `acquire_lock` — concurrency is handled by the atomic rename
instead, and since the merge is idempotent, last-writer-wins is identical to running once.
**`conf_populate.sh` refuses to guess a container.** An ambiguous name prefix skips the field
rather than picking the first match. Writing the wrong container name is worse than writing
nothing: an empty field is visibly incomplete and gets fixed, a wrong one silently points the
whole stack at the wrong instance. This host has a live example — `authelia` prefix-matches
both `Authelia` (9091) and `Authelia-Secondary` (9092).
---
## ━━━ THE RECOVERY GAP ━━━
Confs are gitignored, **and so are their `.bak` files**. There is no versioned history to
revert to, and the single `.bak` slot is overwritten by whoever writes next:
| file | modified | its `.bak` |
|---|---|---|
| `master.conf` | Jul 28 18:52 | Jul 28 18:52 |
| `host1.conf` | Aug 1 21:00 | **Jul 3 17:46** |
A bad write to `master.conf` currently falls back to a file that may predate weeks of edits.
Worth knowing before hand-editing a conf, and the reason `--backup` exists on `conf_upgrade.sh`
at all.
Moving `Configurations/` into a private repo would make `git diff` and `git revert` the
recovery mechanism and give the history for free. That overlaps the existing GitHub-mirror
TODO, which is blocked on the same question — see `Notes_AI-Design.md`, where it also blocks
AI-assisted conf writes.