Reorg plan: stacks/+data/ layout, drop upstream+overrides, add layout/reorg docs
This commit is contained in:
@@ -9,9 +9,11 @@ Centralized source of truth for the homelab. Per-app stacks live as repos under
|
||||
- [Identity & access](docs/identity.md)
|
||||
- [Strategy](docs/strategy.md)
|
||||
- [Conventions](docs/conventions.md)
|
||||
- [Layout](docs/layout.md)
|
||||
- [Stack inventory](docs/inventory.md)
|
||||
- [Repo plan (`Charlie/*`)](docs/repos.md)
|
||||
- [Migration recipe](docs/migration.md)
|
||||
- [Reorg runbook](docs/reorg.md)
|
||||
- [Roadmap](docs/roadmap.md)
|
||||
|
||||
## Assumptions
|
||||
|
||||
+59
-52
@@ -1,72 +1,79 @@
|
||||
# Stack inventory
|
||||
|
||||
Source: `docker compose ls -a` on each host + `find /mnt/nas -name 'docker-compose.y*ml'`. Compose paths under `/mnt/nas/home/` unless noted.
|
||||
Source: `docker compose ls -a` on each host + `find /mnt/nas/home -maxdepth 2 -type d` (May 3, 2026).
|
||||
|
||||
**Repo type:**
|
||||
- `init` — no git history yet; `git init` in place, push to `Charlie/<name>`.
|
||||
- `rewrite` — existing git repo (typically on github.com); change `origin` to `Charlie/<name>`.
|
||||
- `upstream+overrides` — upstream repo we don't author; track `upstream`, add `origin` = `Charlie/<name>` containing only overrides.
|
||||
- `mirror` — pull-mirror of an upstream repo we use unmodified, kept under `Charlie/<name>` for backup/availability.
|
||||
**Repo type** — see [strategy.md](strategy.md#repo-types).
|
||||
|
||||
## Morgott (owner host)
|
||||
|
||||
| Stack | Path | Status | Repo type | Charlie repo |
|
||||
|-------|------|--------|-----------|--------------|
|
||||
| caddy | `caddy/` | running | init | `Charlie/caddy` |
|
||||
| authentik | `authentik/authentik/` | running | init (overrides only) | `Charlie/authentik` |
|
||||
| open-ldap | `open-ldap/` | running | init | `Charlie/ldap` |
|
||||
| ddclient | `ddclient/` | running | init | `Charlie/ddclient` |
|
||||
| vaultwarden | `vaultwarden/` | running | init | `Charlie/vaultwarden` |
|
||||
| bookstack | `bookstack/` | running | init | `Charlie/bookstack` |
|
||||
| monica | `monica/monica/` | running | init | `Charlie/monica` |
|
||||
| miniflux | `miniflux/` | running | init | `Charlie/miniflux` |
|
||||
| mealie | `mealie/mealie/docker/` | running | upstream+overrides | `Charlie/mealie` |
|
||||
| homarr | `homarr/` | running | init | `Charlie/homarr` |
|
||||
| paperless-ngx | `paperless-ngx/` | running | upstream+overrides | `Charlie/paperless-ngx` |
|
||||
| portainer | `portainer/` | running | init | `Charlie/portainer` |
|
||||
| portainer-agent | `portainer-agent/` | running | init | `Charlie/portainer-agent` |
|
||||
| otelcollector | `otelcollector/` | running | init | `Charlie/otelcollector` |
|
||||
| VictoriaMetrics | `VictoriaMetrics/deployment/docker/` | running (`docker`) | upstream+overrides | `Charlie/victoriametrics` |
|
||||
| watchtower | `watchtower/` | running | init | `Charlie/watchtower` (retire after pipelines) |
|
||||
| Gssh | `Gssh/` | running | rewrite | `Charlie/gssh` |
|
||||
| Old path (`home/`) | New path (`stacks/`) | Charlie repo | Repo type |
|
||||
|---|---|---|---|
|
||||
| `caddy/` | `caddy/` | `caddy` | init |
|
||||
| `authentik/authentik/` | `authentik/` | `authentik` | init |
|
||||
| `open-ldap/` | `ldap/` | `ldap` | init |
|
||||
| `ddclient/` | `ddclient/` | `ddclient` | init |
|
||||
| `vaultwarden/` | `vaultwarden/` | `vaultwarden` | init |
|
||||
| `bookstack/` | `bookstack/` | `bookstack` | init |
|
||||
| `monica/monica/` | `monica/` | `monica` | init |
|
||||
| `miniflux/` | `miniflux/` | `miniflux` | init |
|
||||
| `mealie/mealie/docker/` | `mealie/` | `mealie` | init |
|
||||
| `homarr/` | `homarr/` | `homarr` | init |
|
||||
| `paperless-ngx/` | `paperless-ngx/` | `paperless-ngx` | init |
|
||||
| `portainer/` | `portainer/` | `portainer` | init |
|
||||
| `portainer-agent/` | `portainer-agent/` | `portainer-agent` | init |
|
||||
| `otelcollector/` | `otelcollector/` | `otelcollector` | init |
|
||||
| `VictoriaMetrics/deployment/docker/` | `victoriametrics/` | `victoriametrics` | init |
|
||||
| `watchtower/` | `watchtower/` | `watchtower` | init (retire after Diun) |
|
||||
| `Gssh/` | `gssh/` | `gssh` | rewrite |
|
||||
| `ServerManagementUtils/` | `server-mgmt-utils/` | `server-mgmt-utils` | rewrite |
|
||||
|
||||
## Melina (owner host)
|
||||
|
||||
| Stack | Path | Status | Repo type | Charlie repo |
|
||||
|-------|------|--------|-----------|--------------|
|
||||
| gitea | `gitea/` | running | init | `Charlie/gitea` |
|
||||
| MediaServerCompose | `MediaServerCompose/` | running | rewrite | `Charlie/mediacompose` |
|
||||
| invidious | `invidious/` | running | upstream+overrides | `Charlie/invidious` |
|
||||
| searxng-docker | `searxng-docker/` | running | upstream+overrides | `Charlie/searxng` |
|
||||
| planka | `planka/` | running | init | `Charlie/planka` |
|
||||
| audiobookshelf | `audiobookshelf/` | running | init | `Charlie/audiobookshelf` |
|
||||
| DirectoryAdmin | `DirectoryAdmin/` | running | rewrite | `Charlie/directoryadmin` |
|
||||
| portainer-agent | `portainer-agent/` | running | shared with morgott | `Charlie/portainer-agent` |
|
||||
| otelcollector | `otelcollector/` | running | shared with morgott | `Charlie/otelcollector` |
|
||||
| Old path (`home/`) | New path (`stacks/`) | Charlie repo | Repo type |
|
||||
|---|---|---|---|
|
||||
| `gitea/` | `gitea/` | `gitea` | init |
|
||||
| `MediaServerCompose/` | `mediacompose/` | `mediacompose` | rewrite |
|
||||
| `invidious/` | `invidious/` | `invidious` | init (was upstream-cloned; redo as our compose) |
|
||||
| `searxng-docker/` | `searxng/` | `searxng` | init (same) |
|
||||
| `planka/` | `planka/` | `planka` | init |
|
||||
| `audiobookshelf/` | `audiobookshelf/` | `audiobookshelf` | init |
|
||||
| `DirectoryAdmin/` | `directoryadmin/` | `directoryadmin` | rewrite |
|
||||
|
||||
## Mohg
|
||||
|
||||
TBD — re-run inventory.
|
||||
|
||||
## Non-stack dirs (kept on NAS, not stack repos)
|
||||
## Config-only (not deployed, but version-controlled)
|
||||
|
||||
| Path | Purpose | Action |
|
||||
|------|---------|--------|
|
||||
| `/mnt/nas/ca/` | host CA material (ssh, ldap.lan) | leave; consider `Charlie/ca-tooling` for `sync-ca.sh` only |
|
||||
| `/mnt/nas/ldap/certs/` | LDAP server certs | leave |
|
||||
| `/mnt/nas/media-server/` | bulk media (downloads, movies, tv) | leave; bind target |
|
||||
| `/mnt/nas/portainer/data/` | portainer state | leave |
|
||||
| `/mnt/nas/home/shell/` | shared shell config (`profile.d`, `inputrc`) | `Charlie/shell` (init) |
|
||||
| `/mnt/nas/home/plist/` | unclear | classify before action |
|
||||
| `/mnt/nas/home/ServerManagementUtils/` | filebeat / metricbeat / logstash / orchestrator | rewrite → `Charlie/server-mgmt-utils` |
|
||||
| Old path | New path | Charlie repo |
|
||||
|---|---|---|
|
||||
| `home/shell/` | `stacks/shell/` | `shell` |
|
||||
| `home/first-time-setup.md` | `stacks/first-time-setup/` | `first-time-setup` |
|
||||
| `home/plist/` | TBD — classify | — |
|
||||
|
||||
## Mirror-only (Charlie hosts a backup mirror, no overrides)
|
||||
## Mirrors
|
||||
|
||||
Useful for upstreams we may patch later or want offline copies of:
|
||||
- `invidious`, `searxng-docker`, `VictoriaMetrics`, `mealie`, `paperless-ngx`, `nitter`.
|
||||
Pull-mirror only (Gitea config; no host action). Naming: `Charlie/<name>-mirror`.
|
||||
|
||||
(See [strategy.md](strategy.md) on Gitea pull-mirror config.)
|
||||
| Mirror | Upstream |
|
||||
|---|---|
|
||||
| `invidious-mirror` | https://github.com/iv-org/invidious |
|
||||
| `searxng-docker-mirror` | https://github.com/searxng/searxng-docker |
|
||||
| `victoriametrics-mirror` | https://github.com/VictoriaMetrics/VictoriaMetrics |
|
||||
| `mealie-mirror` | https://github.com/mealie-recipes/mealie |
|
||||
| `paperless-ngx-mirror` | https://github.com/paperless-ngx/paperless-ngx |
|
||||
| `nitter-mirror` | https://github.com/zedeus/nitter |
|
||||
| `caddy-mirror` | https://github.com/caddyserver/caddy |
|
||||
| `authentik-mirror` | https://github.com/goauthentik/authentik |
|
||||
| `gitea-mirror` | https://github.com/go-gitea/gitea |
|
||||
| `act-runner-mirror` | https://gitea.com/gitea/act_runner |
|
||||
|
||||
## Removed since last scan
|
||||
## Non-stack dirs
|
||||
|
||||
`apache-guacamole`, `docker-dnsmasq`, `Docker-Minecraft-Paper`, `elasticsearch/`, `elk/`, `ferdium-server`, `LibreChat`, `localai`, `nitter` (still on disk, not running), `openobserve`, `reranker`, `scraper`, `step-ca`, `vrising` (game data only), `xstarbound`. `nitter` and `vrising` directories remain on disk but no longer have running stacks.
|
||||
| Path | Action |
|
||||
|---|---|
|
||||
| `/mnt/nas/ca/` | leave; out of scope |
|
||||
| `/mnt/nas/ldap/` → `/mnt/nas/ldap-certs/` | rename during reorg |
|
||||
| `/mnt/nas/media-server/` → `/mnt/nas/media/` | rename when `mediacompose` migrates |
|
||||
| `/mnt/nas/portainer/data/` → `/mnt/nas/data/portainer/` | move when `portainer` migrates |
|
||||
| `/mnt/nas/home/` | delete after every stack migrated |
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Layout
|
||||
|
||||
Canonical `/mnt/nas/` shape after the reorg.
|
||||
|
||||
```
|
||||
/mnt/nas/
|
||||
├── stacks/ ← per-stack git working trees. Small, all-in-git.
|
||||
│ ├── caddy/
|
||||
│ ├── ldap/
|
||||
│ ├── ...
|
||||
├── data/ ← per-stack runtime state. Bind-mounted into containers.
|
||||
│ ├── caddy/
|
||||
│ ├── ldap/
|
||||
│ ├── mediacompose/
|
||||
│ │ ├── jellyfin/
|
||||
│ │ ├── sonarr/
|
||||
│ │ └── ...
|
||||
│ ├── ...
|
||||
├── media/ ← bulk media (renamed from media-server/)
|
||||
│ ├── downloads/
|
||||
│ ├── movies/
|
||||
│ └── tv/
|
||||
├── ca/ ← host CA material; out of scope for the reorg
|
||||
├── ldap-certs/ ← LDAP server certs (renamed from ldap/); out of scope
|
||||
└── home/ ← legacy; deleted at end of reorg
|
||||
```
|
||||
|
||||
## Naming
|
||||
|
||||
- All directories under `stacks/` and `data/` are **kebab-case, lowercase**.
|
||||
- Repo names under `Charlie/` match the `stacks/` directory name 1:1.
|
||||
|
||||
| Old | New |
|
||||
|-----|-----|
|
||||
| `home/Gssh/` | `stacks/gssh/` |
|
||||
| `home/MediaServerCompose/` | `stacks/mediacompose/` |
|
||||
| `home/DirectoryAdmin/` | `stacks/directoryadmin/` |
|
||||
| `home/open-ldap/` | `stacks/ldap/` |
|
||||
| `home/VictoriaMetrics/deployment/docker/` | `stacks/victoriametrics/` |
|
||||
| `home/mealie/mealie/docker/` | `stacks/mealie/` |
|
||||
| `home/authentik/authentik/` | `stacks/authentik/` |
|
||||
| `home/searxng-docker/` | `stacks/searxng/` |
|
||||
| `home/paperless-ngx/` | `stacks/paperless-ngx/` |
|
||||
| `home/ServerManagementUtils/` | `stacks/server-mgmt-utils/` |
|
||||
| `home/portainer-agent/` | `stacks/portainer-agent/` |
|
||||
|
||||
## Bind-mount convention
|
||||
|
||||
Every compose file binds **only** `/mnt/nas/data/<stack>/...` for runtime state. No bind into `./` (which is the git working tree).
|
||||
|
||||
```yaml
|
||||
# stacks/ldap/docker-compose.yml
|
||||
services:
|
||||
openldap:
|
||||
volumes:
|
||||
- /mnt/nas/data/ldap/db:/var/lib/ldap
|
||||
- /mnt/nas/data/ldap/config:/etc/ldap/slapd.d
|
||||
```
|
||||
|
||||
Small read-only configs that *should* be version-controlled (Caddyfile, openldap bootstrap LDIFs, etc.) live inside the `stacks/<stack>/` working tree and are bind-mounted as files:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
```
|
||||
|
||||
## Out-of-scope dirs (leave alone for now)
|
||||
|
||||
- `/mnt/nas/ca/` — host CA material. Considered separately later (`Charlie/ca-tooling`).
|
||||
- `/mnt/nas/ldap-certs/` — LDAP server certs (after rename from `ldap/`). Same.
|
||||
- `/mnt/nas/#recycle` — Synology recycle bin.
|
||||
|
||||
## Stacks-only-as-config (no container)
|
||||
|
||||
Some `stacks/<name>/` may not deploy a container — they're just version-controlled config that belongs to the homelab:
|
||||
|
||||
- `stacks/shell/` — shared `profile.d`, `inputrc`. Cloned by hosts to `/usr/local/share/charlie-shell/`, sourced by `/etc/profile`.
|
||||
- `stacks/first-time-setup/` — bootstrap docs / scripts (currently `home/first-time-setup.md`).
|
||||
|
||||
These have no `docker-compose.yml` and no entry in the runner deploy list.
|
||||
+95
-61
@@ -1,85 +1,119 @@
|
||||
# Migration — NAS folder → `Charlie/<stack>` repo
|
||||
# Migration
|
||||
|
||||
For `init`-type stacks. Run on the owner host. Container keeps running throughout.
|
||||
Per-stack recipe to move `home/<old>/` → `stacks/<new>/` + `data/<new>/` and create `Charlie/<new>`.
|
||||
|
||||
## Pre-conditions
|
||||
|
||||
- Owner host SSH session as admin.
|
||||
- Container is **stopped**: `cd /mnt/nas/home/<old> && sudo docker compose down`.
|
||||
- Empty Gitea repo created (Charlie org, private, uninitialized).
|
||||
|
||||
## `init` — no existing git history
|
||||
|
||||
```bash
|
||||
# 0. In Gitea UI: create empty Charlie/<name>, private, uninitialized.
|
||||
OLD=/mnt/nas/home/<old>
|
||||
NEW=/mnt/nas/stacks/<new>
|
||||
DATA=/mnt/nas/data/<new>
|
||||
|
||||
cd /mnt/nas/home/<stack>
|
||||
ls -la && du -sh */ 2>/dev/null # spot the heavy state dirs
|
||||
# 1. Make destinations
|
||||
sudo mkdir -p "$NEW" "$DATA"
|
||||
sudo chown -R alex:alex "$NEW"
|
||||
|
||||
git init -b main
|
||||
# 2. Move runtime state OUT of the old dir, into data/
|
||||
# (Identify state dirs first: data/, db-data/, appdata/, cache/, …)
|
||||
sudo mv "$OLD"/data "$DATA"/ 2>/dev/null || true
|
||||
sudo mv "$OLD"/db-data "$DATA"/ 2>/dev/null || true
|
||||
sudo mv "$OLD"/<other-state> "$DATA"/ 2>/dev/null || true
|
||||
|
||||
# .gitignore BEFORE first add — never stage runtime state
|
||||
$EDITOR .gitignore # see template below
|
||||
# 3. Copy the deployment files into the new working tree
|
||||
cp "$OLD"/docker-compose.yml "$NEW"/
|
||||
cp "$OLD"/docker-compose.override.yml "$NEW"/ 2>/dev/null || true
|
||||
cp "$OLD"/.env "$NEW"/ 2>/dev/null || true
|
||||
cp -r "$OLD"/<small-configs> "$NEW"/ 2>/dev/null || true
|
||||
|
||||
cp .env .env.example
|
||||
$EDITOR .env.example # redact every secret
|
||||
cd "$NEW"
|
||||
|
||||
git add .
|
||||
git status
|
||||
git diff --cached --stat
|
||||
# If state dirs slipped through: fix .gitignore, `git rm --cached -r <dir>`, repeat.
|
||||
# 4. Rewrite the compose so binds reference /mnt/nas/data/<new>/...
|
||||
$EDITOR docker-compose.yml docker-compose.override.yml
|
||||
|
||||
git commit -m "Initial import from /mnt/nas/home/<stack>"
|
||||
git remote add origin https://gitea.alexandru.macocian.me/Charlie/<name>.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## `.gitignore` template
|
||||
|
||||
```gitignore
|
||||
# runtime state
|
||||
data/
|
||||
db-data/
|
||||
appdata/
|
||||
*-data/
|
||||
cache/
|
||||
logs/
|
||||
*.log
|
||||
*.sqlite*
|
||||
|
||||
# secrets
|
||||
# 5. .gitignore + .env.example
|
||||
cat > .gitignore <<'EOF'
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
```
|
||||
EOF
|
||||
cp .env .env.example
|
||||
$EDITOR .env.example # redact secrets
|
||||
|
||||
Tune per stack — e.g. `paperless-ngx` also has `consume/`, `export/`, `media/`, `media-bk/`.
|
||||
|
||||
## `rewrite`-type
|
||||
|
||||
```bash
|
||||
cd /mnt/nas/home/<stack>
|
||||
git remote rename origin github 2>/dev/null || true
|
||||
git remote add origin https://gitea.alexandru.macocian.me/Charlie/<name>.git
|
||||
# 6. Init + first commit
|
||||
git init -b main
|
||||
git add .
|
||||
git status
|
||||
git diff --cached --stat
|
||||
git commit -m "Initial import"
|
||||
git remote add origin https://gitea.alexandru.macocian.me/Charlie/<new>.git
|
||||
git push -u origin main
|
||||
|
||||
# 7. Bring it back up FROM THE NEW LOCATION
|
||||
sudo docker compose up -d
|
||||
sudo docker compose ps
|
||||
sudo docker compose logs -f --tail=50
|
||||
|
||||
# 8. Once verified, retire the old dir (do NOT delete data inside it — already moved)
|
||||
sudo mv "$OLD" "$OLD".retired
|
||||
# Final removal happens at end of full reorg.
|
||||
```
|
||||
|
||||
## `upstream+overrides`
|
||||
## `rewrite` — already a git repo
|
||||
|
||||
For stacks that are clones of github.com repos we own (e.g. `Gssh`, `MediaServerCompose`, `DirectoryAdmin`).
|
||||
|
||||
```bash
|
||||
cd /mnt/nas/home/<stack>
|
||||
git remote rename origin upstream
|
||||
git remote add origin https://gitea.alexandru.macocian.me/Charlie/<name>.git
|
||||
# create our overlay branch
|
||||
git checkout -b charlie
|
||||
# add our files: docker-compose.override.yml, .env.example, custom Dockerfile…
|
||||
git add docker-compose.override.yml .env.example
|
||||
git commit -m "Charlie overrides"
|
||||
git push -u origin charlie
|
||||
OLD=/mnt/nas/home/<old>
|
||||
NEW=/mnt/nas/stacks/<new>
|
||||
DATA=/mnt/nas/data/<new>
|
||||
|
||||
sudo mkdir -p "$DATA"
|
||||
sudo chown -R alex:alex "$DATA"
|
||||
|
||||
# 1. Move state out
|
||||
sudo mv "$OLD"/<state-dir> "$DATA"/ 2>/dev/null || true
|
||||
|
||||
# 2. Move the whole git repo to the new location (preserves .git)
|
||||
sudo mv "$OLD" "$NEW"
|
||||
sudo chown -R alex:alex "$NEW"
|
||||
|
||||
cd "$NEW"
|
||||
|
||||
# 3. Repoint origin to Charlie (keep github as a fallback remote)
|
||||
git remote rename origin github
|
||||
git remote add origin https://gitea.alexandru.macocian.me/Charlie/<new>.git
|
||||
|
||||
# 4. Update compose binds to /mnt/nas/data/<new>/...
|
||||
$EDITOR docker-compose.yml docker-compose.override.yml
|
||||
|
||||
# 5. .gitignore additions for the data dirs we just moved out
|
||||
$EDITOR .gitignore
|
||||
|
||||
# 6. Commit, push to Charlie
|
||||
git add .
|
||||
git commit -m "Move bind paths to /mnt/nas/data/<new>"
|
||||
git push -u origin main
|
||||
|
||||
# 7. Bring back up
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
The pipeline tracks `origin/charlie`, not upstream's default.
|
||||
## `mirror` — pull-mirror only, no host action
|
||||
|
||||
## `mirror`
|
||||
In Gitea: New Migration → Pull mirror → upstream URL → name `Charlie/<name>-mirror` → schedule `24h0m0s`.
|
||||
|
||||
No host-side action. In Gitea: New Migration → **Pull mirror** → upstream URL → schedule (e.g. daily). Repo lives at `Charlie/<name>` and is read-only.
|
||||
No filesystem changes on any host.
|
||||
|
||||
## Post-migration check
|
||||
## Post-stack checklist
|
||||
|
||||
```bash
|
||||
git -C /mnt/nas/home/<stack> status # should be clean
|
||||
git -C /mnt/nas/home/<stack> remote -v # origin → gitea
|
||||
sudo docker compose ps # still up
|
||||
```
|
||||
- [ ] Container is up at the new location.
|
||||
- [ ] `docker inspect <container> --format '{{ range .Mounts }}{{ .Source }}{{"\n"}}{{ end }}'` confirms binds point at `/mnt/nas/data/<new>/...`.
|
||||
- [ ] `git -C /mnt/nas/stacks/<new> status` is clean.
|
||||
- [ ] Old `home/<old>.retired/` exists and is empty of state (only deployment leftovers).
|
||||
- [ ] Service reachable via Caddy as before.
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# Reorg runbook
|
||||
|
||||
The one-time migration from `/mnt/nas/home/` to `/mnt/nas/stacks/` + `/mnt/nas/data/`.
|
||||
|
||||
## Phase 0 — preconditions
|
||||
|
||||
- [ ] Admin SSH on morgott + melina + mohg.
|
||||
- [ ] `Charlie/project-charlie` pushed (done).
|
||||
- [ ] Decided owner host per stack (see [repos.md](repos.md)).
|
||||
- [ ] Snapshot taken on miquella (Synology snapshot of `volume1/ubuntu`).
|
||||
|
||||
## Phase 1 — scaffolding (no downtime)
|
||||
|
||||
```bash
|
||||
# On any one host (NAS is shared).
|
||||
sudo mkdir -p /mnt/nas/stacks /mnt/nas/data
|
||||
sudo chown alex:alex /mnt/nas/stacks /mnt/nas/data
|
||||
```
|
||||
|
||||
Optional rename now (still no downtime — these are bulk dirs nothing reads except by full path):
|
||||
|
||||
```bash
|
||||
# Only if you commit to the new names. Update bind paths in compose files BEFORE renaming.
|
||||
sudo mv /mnt/nas/media-server /mnt/nas/media # if no compose binds it yet at the old path
|
||||
sudo mv /mnt/nas/ldap /mnt/nas/ldap-certs
|
||||
```
|
||||
|
||||
(Actually: don't rename `media-server` until `mediacompose` is migrated — its compose binds the old path. Leave both renames for the per-stack pass.)
|
||||
|
||||
## Phase 2 — create empty Charlie repos in Gitea
|
||||
|
||||
Per [repos.md](repos.md), create empty repos:
|
||||
|
||||
- All `init` and `rewrite` rows.
|
||||
- All `mirror` rows (configured as pull-mirrors, see [migration.md](migration.md#mirror)).
|
||||
|
||||
No host changes yet.
|
||||
|
||||
## Phase 3 — per-stack migration loop
|
||||
|
||||
Order: Tier 1 → 5 from [repos.md](repos.md). For each stack on its owner host:
|
||||
|
||||
1. `cd /mnt/nas/home/<old> && sudo docker compose down`
|
||||
2. Run [migration.md](migration.md) recipe (`init` or `rewrite`).
|
||||
3. Verify checklist at the bottom of [migration.md](migration.md).
|
||||
4. ✅ on to next.
|
||||
|
||||
Pilot: do `ddclient` first end-to-end before touching anything else. If it works, the pattern works.
|
||||
|
||||
## Phase 4 — sweep
|
||||
|
||||
Once **every** stack is migrated and verified:
|
||||
|
||||
```bash
|
||||
# On miquella or any NFS client:
|
||||
ls /mnt/nas/home/ # expect: only *.retired/
|
||||
sudo rm -rf /mnt/nas/home/*.retired
|
||||
sudo rmdir /mnt/nas/home
|
||||
|
||||
# Final layout check
|
||||
ls /mnt/nas/ # expect: ca, data, ldap-certs, media, stacks, #recycle
|
||||
```
|
||||
|
||||
## Phase 5 — runner stack
|
||||
|
||||
Now that everything is git-tracked, design + deploy `Charlie/runners` (separate effort). Pilot redeploy via webhook on `Charlie/ddclient`.
|
||||
|
||||
## Rollback strategy
|
||||
|
||||
Any stack failing to come up at the new location:
|
||||
|
||||
1. `sudo docker compose down` in `/mnt/nas/stacks/<new>/`.
|
||||
2. `sudo mv /mnt/nas/home/<old>.retired /mnt/nas/home/<old>` (state was moved, not copied — restore is just a path rename of the directory shell).
|
||||
3. Move state back: `sudo mv /mnt/nas/data/<new>/* /mnt/nas/home/<old>/`.
|
||||
4. `cd /mnt/nas/home/<old> && sudo docker compose up -d`.
|
||||
5. Investigate, retry.
|
||||
|
||||
Because state was **moved** (not copied), there's only ever one source of truth. No risk of two diverging copies.
|
||||
+40
-39
@@ -1,73 +1,74 @@
|
||||
# Repo plan — `Charlie/*`
|
||||
|
||||
Order of carving. See [inventory.md](inventory.md) for the full list with repo types.
|
||||
Carving order. See [inventory.md](inventory.md) for the full list. Migration recipe: [migration.md](migration.md). Sequenced runbook: [reorg.md](reorg.md).
|
||||
|
||||
## Tier 0 — bootstrap
|
||||
|
||||
| Repo | Owner host | Repo type | Why first |
|
||||
|------|------------|-----------|-----------|
|
||||
| `project-charlie` | — | new | meta-repo (this) |
|
||||
| Repo | Owner | Status |
|
||||
|---|---|---|
|
||||
| `project-charlie` | — | done |
|
||||
|
||||
## Tier 1 — pilot the loop (small, low blast radius)
|
||||
## Tier 1 — pilot
|
||||
|
||||
Goal: prove `git push → webhook → runner → docker compose up -d` end-to-end before touching anything important.
|
||||
Prove the loop end-to-end. Smallest blast radius.
|
||||
|
||||
| Repo | Owner | Source | Repo type |
|
||||
|------|-------|--------|-----------|
|
||||
| Repo | Owner | Source | Type |
|
||||
|---|---|---|---|
|
||||
| `ddclient` | morgott | `home/ddclient/` | init |
|
||||
| `homarr` | morgott | `home/homarr/` | init |
|
||||
| `vaultwarden` | morgott | `home/vaultwarden/` | init |
|
||||
|
||||
## Tier 2 — own image, our code
|
||||
## Tier 2 — our code
|
||||
|
||||
| Repo | Owner | Source | Repo type |
|
||||
|------|-------|--------|-----------|
|
||||
| Repo | Owner | Source | Type |
|
||||
|---|---|---|---|
|
||||
| `gssh` | morgott | `home/Gssh/` | rewrite |
|
||||
| `directoryadmin` | melina | `home/DirectoryAdmin/` | rewrite |
|
||||
| `mediacompose` | melina | `home/MediaServerCompose/` | rewrite |
|
||||
| `server-mgmt-utils` | morgott | `home/ServerManagementUtils/` | rewrite |
|
||||
| `caddy` | morgott | `home/caddy/` (custom Dockerfile) | init |
|
||||
|
||||
## Tier 3 — pinned upstream images, plain init
|
||||
|
||||
| Repo | Owner | Source | Repo type |
|
||||
|------|-------|--------|-----------|
|
||||
| `ldap` | morgott | `home/open-ldap/` | init |
|
||||
| `authentik` | morgott | `home/authentik/authentik/` | init |
|
||||
| `bookstack` | morgott | `home/bookstack/` | init |
|
||||
| `monica` | morgott | `home/monica/monica/` | init |
|
||||
| `miniflux` | morgott | `home/miniflux/` | init |
|
||||
| `planka` | melina | `home/planka/` | init |
|
||||
| `audiobookshelf` | melina | `home/audiobookshelf/` | init |
|
||||
| `gitea` | melina | `home/gitea/` | init (deployed by *morgott* runner — never self) |
|
||||
| Repo | Owner | Source |
|
||||
|---|---|---|
|
||||
| `ldap` | morgott | `home/open-ldap/` |
|
||||
| `authentik` | morgott | `home/authentik/authentik/` |
|
||||
| `bookstack` | morgott | `home/bookstack/` |
|
||||
| `monica` | morgott | `home/monica/monica/` |
|
||||
| `miniflux` | morgott | `home/miniflux/` |
|
||||
| `planka` | melina | `home/planka/` |
|
||||
| `audiobookshelf` | melina | `home/audiobookshelf/` |
|
||||
| `gitea` | melina | `home/gitea/` (deployed by morgott runner — never self) |
|
||||
|
||||
## Tier 4 — upstream + overrides
|
||||
## Tier 4 — was upstream-cloned, now plain init
|
||||
|
||||
Two-remote model. See [strategy.md](strategy.md#upstreamoverrides).
|
||||
These previously lived as upstream-clones with `docker-compose.override.yml`. We're collapsing override → canonical compose. **Hand-merge step required** in [migration.md](migration.md).
|
||||
|
||||
| Repo | Owner | Source | Upstream |
|
||||
|------|-------|--------|----------|
|
||||
| `invidious` | melina | `home/invidious/` | iv-org/invidious |
|
||||
| `searxng` | melina | `home/searxng-docker/` | searxng/searxng-docker |
|
||||
| `victoriametrics` | morgott | `home/VictoriaMetrics/deployment/docker/` | VictoriaMetrics/VictoriaMetrics |
|
||||
| `mealie` | morgott | `home/mealie/mealie/docker/` | mealie-recipes/mealie |
|
||||
| `paperless-ngx` | morgott | `home/paperless-ngx/` | paperless-ngx/paperless-ngx |
|
||||
| Repo | Owner | Source | Image source |
|
||||
|---|---|---|---|
|
||||
| `invidious` | melina | `home/invidious/` | `quay.io/invidious/invidious` |
|
||||
| `searxng` | melina | `home/searxng-docker/` | `searxng/searxng` |
|
||||
| `victoriametrics` | morgott | `home/VictoriaMetrics/deployment/docker/` | `victoriametrics/victoria-metrics` |
|
||||
| `mealie` | morgott | `home/mealie/mealie/docker/` | `ghcr.io/mealie-recipes/mealie` |
|
||||
| `paperless-ngx` | morgott | `home/paperless-ngx/` | `ghcr.io/paperless-ngx/paperless-ngx` |
|
||||
|
||||
## Tier 5 — long tail / utilities
|
||||
|
||||
| Repo | Owner | Source | Repo type |
|
||||
|------|-------|--------|-----------|
|
||||
| Repo | Owner | Source | Type |
|
||||
|---|---|---|---|
|
||||
| `otelcollector` | shared | `home/otelcollector/` | init |
|
||||
| `portainer` | morgott | `home/portainer/` | init |
|
||||
| `portainer-agent` | shared | `home/portainer-agent/` | init |
|
||||
| `server-mgmt-utils` | morgott | `home/ServerManagementUtils/` | rewrite |
|
||||
| `shell` | — | `home/shell/` | init (config-only, not deployed) |
|
||||
| `watchtower` | morgott | `home/watchtower/` | init then **archive after Diun + pipelines land** |
|
||||
| `shell` | — (config-only) | `home/shell/` | init |
|
||||
| `first-time-setup` | — (docs-only) | `home/first-time-setup.md` | init |
|
||||
| `watchtower` | morgott | `home/watchtower/` | init then archive |
|
||||
|
||||
## Mirrors (no overrides, backup only)
|
||||
## Mirrors
|
||||
|
||||
Pull-mirrors under `Charlie/mirrors/` (or as plain `Charlie/<name>-mirror`):
|
||||
- `nitter`, plus any of the Tier 4 upstreams we want a guaranteed offline copy of.
|
||||
See [inventory.md](inventory.md#mirrors). Configured in Gitea UI; no host action.
|
||||
|
||||
## Deferred / dormant
|
||||
## Deferred
|
||||
|
||||
`plist/` — classify before deciding.
|
||||
`nitter/`, `vrising/` — on disk, not running. Leave as-is or archive.
|
||||
|
||||
+11
-11
@@ -1,19 +1,19 @@
|
||||
# Roadmap
|
||||
|
||||
## Phase 1 — CI plumbing
|
||||
## Phase 1 — reorg + CI plumbing
|
||||
|
||||
- [x] Inventory `/mnt/nas/` stacks.
|
||||
- [ ] Push `Charlie/project-charlie` (this repo).
|
||||
- [ ] Set up Gitea pull-mirrors for relevant upstreams (see [strategy.md](strategy.md#mirror)).
|
||||
- [ ] Design runner stack (host docker socket; secrets sourcing TBD — candidate: vaultwarden).
|
||||
- [ ] Stand up runners on morgott + melina, register org-scope to `Charlie`.
|
||||
- [ ] Pilot end-to-end on `Charlie/ddclient`: push → webhook → redeploy.
|
||||
- [x] Push `Charlie/project-charlie`.
|
||||
- [ ] Snapshot `volume1/ubuntu` on miquella.
|
||||
- [ ] Create empty `Charlie/<stack>` repos per [repos.md](repos.md).
|
||||
- [ ] Create Gitea pull-mirrors per [inventory.md](inventory.md#mirrors).
|
||||
- [ ] Walk [reorg.md](reorg.md) Tier 1 (pilot: `ddclient`).
|
||||
- [ ] Walk Tiers 2–5.
|
||||
- [ ] Delete `/mnt/nas/home/`.
|
||||
- [ ] Design + deploy runner stack (`Charlie/runners`).
|
||||
- [ ] First end-to-end pipeline: push to `Charlie/ddclient` → webhook → redeploy on morgott.
|
||||
|
||||
## Phase 2 — Per-app repos
|
||||
|
||||
Walk Tier 1 → Tier 5 in [repos.md](repos.md), one stack at a time. Each migration follows [migration.md](migration.md).
|
||||
|
||||
## Phase 3 — Automation
|
||||
## Phase 2 — Automation
|
||||
|
||||
- Diun watching registries → webhook → `redeploy` workflow with `force-pull`.
|
||||
- Retire watchtower.
|
||||
|
||||
+20
-17
@@ -8,43 +8,46 @@ Pull-based GitOps via Gitea Actions runners on the owner host.
|
||||
dev box ── git push ──► Gitea (Charlie/<stack>) ── webhook ──► runner on owner host
|
||||
│
|
||||
▼
|
||||
git -C /mnt/nas/home/<stack> pull --ff-only
|
||||
git -C /mnt/nas/stacks/<stack> pull --ff-only
|
||||
docker compose pull && docker compose up -d
|
||||
```
|
||||
|
||||
The working tree on the owner host **is** the deployment. `git status` on the host is the source-of-truth diff against `Charlie/<stack>`.
|
||||
The git working tree at `/mnt/nas/stacks/<stack>/` **is** the deployment. Runtime state lives separately under `/mnt/nas/data/<stack>/` (see [layout.md](layout.md)).
|
||||
|
||||
## Rules
|
||||
|
||||
- One owner host per stack. Only that host's runner ever touches the working tree.
|
||||
- No hand-edits on the host once a stack is repo-managed. `git pull --ff-only` will refuse to clobber.
|
||||
- `.gitignore` first, `git add` second. Runtime state (`data/`, `db-data/`, `cache/`, `*-data/`) never enters git.
|
||||
- Runtime state never enters git. Compose files bind into `/mnt/nas/data/<stack>/`, not into the working tree.
|
||||
- `.env` ignored, `.env.example` tracked. Secrets stay out.
|
||||
- Image tags pinned. No `:latest`.
|
||||
- Cross-host self-update: the runner that deploys `Charlie/gitea` lives on morgott; the runner that updates the morgott runner lives on melina. A runner never redeploys itself mid-job.
|
||||
- Cross-host self-update: the runner that deploys `Charlie/gitea` lives on morgott; the runner that updates morgott's runner lives on melina. A runner never redeploys itself mid-job.
|
||||
|
||||
## Repo types
|
||||
|
||||
Three. (We dropped `upstream+overrides` — see [Why no upstream cloning](#why-no-upstream-cloning).)
|
||||
|
||||
### `init`
|
||||
|
||||
Stack is currently just a NAS folder. `git init` in place on the owner host, push to `Charlie/<name>`. Procedure: see [migration.md](migration.md).
|
||||
Stack is currently just a NAS folder. `git init` in the new `/mnt/nas/stacks/<stack>/`, push to `Charlie/<name>`. Recipe: [migration.md](migration.md#init).
|
||||
|
||||
### `rewrite`
|
||||
|
||||
Stack is already a clone (typically of a github.com repo we own). `git remote set-url origin https://gitea.alexandru.macocian.me/Charlie/<name>` (and optionally keep github as `github` remote). Push.
|
||||
|
||||
### `upstream+overrides`
|
||||
|
||||
Stack is a clone of an upstream we don't author (e.g. `invidious`, `searxng-docker`, `VictoriaMetrics`). Two remotes:
|
||||
|
||||
- `upstream` — read-only tracking remote.
|
||||
- `origin` — `Charlie/<name>`, contains *only* our overrides as commits on top: `docker-compose.override.yml`, `.env.example`, custom `Dockerfile`s, optional patches.
|
||||
|
||||
Updating the upstream is `git fetch upstream && git rebase upstream/<branch>` (or `merge`), then push to `origin`. Pipeline runs on push to `origin/main`.
|
||||
Code we author, currently a clone of a github.com repo. Move to `/mnt/nas/stacks/<stack>/`, `git remote set-url origin` to Charlie. Recipe: [migration.md](migration.md#rewrite).
|
||||
|
||||
### `mirror`
|
||||
|
||||
Pure backup. Configured via Gitea's **pull mirror** feature (Repo → Settings → Mirror). Gitea polls the upstream on a schedule and pushes nothing back. Useful for upstreams we want guaranteed offline access to even when we're not customising them.
|
||||
Pull-mirror of an upstream we don't customise (or want as offline backup). Configured via Gitea's pull-mirror feature. Read-only, never deployed from. Naming: `Charlie/<name>-mirror`.
|
||||
|
||||
## Why no upstream cloning
|
||||
|
||||
Previously we considered cloning upstream's source on the host and layering a `docker-compose.override.yml`. Dropped because:
|
||||
|
||||
- For every "upstream image" stack (`invidious`, `searxng`, `victoriametrics`, `mealie`, `paperless-ngx`), we only ever ran the published image. Their source tree is dead weight on the host.
|
||||
- Override-on-template means the upstream's compose can change under us silently.
|
||||
- Two-remote / rebase / force-push dance was the price for dubious value.
|
||||
|
||||
Instead: our `docker-compose.yml` references the published image directly. It is the only compose. Upstream's compose is a *reference document* we read in their mirror repo when we want to see what changed.
|
||||
|
||||
## Image-update flow (Phase 3)
|
||||
|
||||
@@ -60,4 +63,4 @@ Open question. Candidate: vaultwarden as the source, with a runner-side fetch st
|
||||
- **Komodo**: nice, also pull-based, also relocates working trees. Reconsider if we ever want a UI.
|
||||
- **Dockge**: file mgmt only, no git wiring.
|
||||
|
||||
The runner-driven pattern keeps the existing `/mnt/nas/home/<stack>/` layout and adds nothing the team doesn't already understand.
|
||||
The runner-driven pattern keeps us in plain `git` + `docker compose` and adds nothing the team doesn't already understand.
|
||||
|
||||
Reference in New Issue
Block a user