diff --git a/AI.md b/AI.md
index ddbf359..e972c0a 100644
--- a/AI.md
+++ b/AI.md
@@ -1,63 +1,205 @@
-# AI.md — Docker Template Update Runbook
+# CasjaysDev Docker Application Image Specification (casjaysdevdocker)
-Run this whenever upstream templates in `casjay-dotfiles/scripts` are updated.
-This file is **permanent** — do not delete it. It is the maintenance runbook for this repo.
+**Name**: {name}
+
+**About this file:** This is the complete, authoritative specification for a CasjaysDev
+Docker **application image** repository (`casjaysdevdocker/{name}`). It is a master
+template — copied into an app image repo as that repo's `AI.md`. It is **permanent** —
+never delete it from a repo that carries it.
+
+**Note:** `{name}` in this file is a reference token, not setup-time replacement text. Its
+value is always the repo directory basename (`basename "$PWD"`).
+
+**Maintenance procedure:** The bootstrap/update runbook (regenerating files after upstream
+template changes, creating new repos) is NOT in this file — it lives in the
+`dockersrc-bootstrap` agent (it handles both repo families via `REPO_TYPE` detection).
+This file defines the standards that procedure enforces.
---
-## What This Runbook Does
+# PART INDEX
-The upstream Docker templates in `casjay-dotfiles/scripts` change over time. Generated files that
-are left in place may call removed functions, source removed templates, or reference removed env
-vars — causing runtime failures. This runbook brings every generated file in the repo up to date.
-
-Files updated:
-
-- `.env.scripts` — vars synced to current template (added/removed)
-- `Dockerfile` / `Dockerfile.*` — removed ARG lines dropped, new ones added
-- `rootfs/usr/local/bin/*` — all template-generated bin scripts replaced from temp dir
-- `rootfs/usr/local/etc/docker/functions/entrypoint.sh` — replaced from temp dir
-- `rootfs/usr/local/etc/docker/init.d/*.sh` — regenerated; app-specific values restored
-- `rootfs/root/docker/setup/00-*.sh` through `07-*.sh` — replaced from temp dir
-- `README.md` — rewritten to current standard layout
-- Non-standard rootfs root-level directories — files migrated; stale dirs removed
+| PART | Title |
+|------|-------|
+| 0 | Critical rules |
+| 1 | Repository model & structure |
+| 2 | Template system reference |
+| 3 | Tooling — gen-dockerfile & gen-script |
+| 4 | `.env.scripts` reference |
+| 5 | Runtime system — setup scripts, entrypoint, init.d |
+| 6 | README.md standard layout |
+| 7 | CI/CD workflows |
+| 8 | Verification & commit |
---
-## Template System Reference
+# PART 0: CRITICAL RULES
-This section documents the current state of `templates/dockerfiles/` and key
-`bin/gen-dockerfile` variables. Keep it in sync whenever upstream templates change.
+## Org mapping
-### Template inventory
+| System | Org | Example |
+|--------|-----|---------|
+| GitHub (source) | `casjaysdevdocker` | `https://github.com/casjaysdevdocker/{name}` |
+| Docker Hub (push) | `casjaysdevdocker` | `casjaysdevdocker/{name}` |
-| Template | Final stage | Init / PID 1 | Base OS |
-|----------|-------------|--------------|---------|
-| `alpine.template` | `scratch.template` | tini | Alpine |
-| `debian.template` | `scratch.template` | tini | Debian |
-| `ubuntu.template` | `scratch.template` | tini | Ubuntu |
-| `rhel.template` | `scratch.template` | tini | AlmaLinux |
-| `archlinux.template` | `scratch.template` | tini | Arch Linux (see multi-arch note) |
-| `web.template` | `systemd.template` | `/sbin/init` | Debian |
-| `xorg.template` | `systemd.template` | `/sbin/init` | Debian |
+`casjaysdevdocker` repos are **applications** (gitea, opengist, super-productivity,
+ampache, aria2, …). They always build FROM the pre-built, multi-arch `casjaysdev/*` base
+images — never directly from upstream distro images. The bases themselves live in the
+separate `dockersrc` org (GitHub `dockersrc/{base}` → Docker Hub `casjaysdev/{base}`) —
+see the base specification (`DOCKERSRC.md`).
-### Final-stage templates
+## Non-negotiable rules
-`scratch.template` — used by all non-GUI templates.
+1. **`AI.md` is permanent** — never delete it from the repo.
+2. **Generated files are owned by the template system** — never hand-tune content that
+ `gen-dockerfile` regenerates (see PART 1 ownership table); fix the upstream
+ `gen-dockerfile` template instead, then regenerate.
+3. **Hand-crafted files are owned by the repo** — `gen-dockerfile` must never overwrite
+ app-specific init.d scripts, custom bin scripts, a `05-custom.sh` with real content, or
+ a hand-crafted README (PART 6).
+4. **Removed OCI labels stay removed** (PART 2) — never re-add `base.name`,
+ `schema-version`, or duplicate `authors`/`source` entries.
+5. **`image.url` is a browsable page** — `https://hub.docker.com/r/casjaysdevdocker/{name}`.
+ `docker.io` is only a registry pull host; it is never a label URL.
+6. **`image.source` and `image.documentation` are the GitHub repo** —
+ `https://github.com/casjaysdevdocker/{name}`.
+7. **One Dockerfile, one file set** — app repos build one image (`latest` + date tag);
+ version variants (`Dockerfile.{ver}`) belong to base repos only.
+8. **Always `FROM casjaysdev/`** — never pull an upstream distro image directly;
+ the base repos exist so every app shares one patched, multi-arch foundation.
+9. **Only `root/`, `tmp/`, and `usr/` may exist at `rootfs/` top level** (PART 1).
+10. **Maintenance runs through the `dockersrc-bootstrap` agent** — do not improvise the
+ update procedure from memory.
+
+---
+
+# PART 1: REPOSITORY MODEL & STRUCTURE
+
+## What an app image repo is
+
+A `casjaysdevdocker/{name}` repo containerizes one application on top of a
+`casjaysdev/*` base. It publishes a single image (`casjaysdevdocker/{name}:latest` plus a
+date tag) — no per-version Dockerfile variants. The application itself is installed in
+`05-custom.sh` and started by one or more init.d service scripts.
+
+## Standard tree
+
+```
+{name}/
+├── AI.md # This specification (permanent)
+├── Dockerfile # [generated] single build file
+├── .dockerignore # [generated]
+├── .env.scripts # [generated] build config
+├── .gitattributes # [generated]
+├── .gitea/workflows/
+│ └── build.yml # [generated] gen-dockerfile actions
+├── .gitignore # [generated]
+├── LICENSE.md # License (WTFPL / app's own license)
+├── README.md # [generated*] standard layout (PART 6)
+└── rootfs/ # Container filesystem overlay
+ ├── root/docker/setup/ # [generated*] build-time setup scripts 00–07
+ ├── tmp/ # staged files installed at build time (optional)
+ └── usr/local/
+ ├── bin/ # [generated*] entrypoint.sh, pkmgr, symlink, copy,
+ │ # healthcheck + [hand-crafted] app-specific scripts
+ └── etc/docker/
+ ├── env/ # [hand-crafted] build/runtime env fragments (optional)
+ ├── functions/
+ │ └── entrypoint.sh # [generated] entrypoint function library
+ └── init.d/ # [hand-crafted] runtime init scripts (one per service)
+```
+
+`[generated]` — safe to regenerate; local edits will be lost.
+`[generated*]` — regenerated from the template, EXCEPT files carrying repo-specific
+content (`05-custom.sh` with a real body, extra bin scripts, a hand-crafted README) —
+those follow the hand-crafted rules in PARTs 5 and 6.
+`[hand-crafted]` — never overwritten by the template system.
+
+App repos may additionally carry project files (`IDEA.md`, `CLAUDE.md`, `TODO.AI.md`)
+per the global project conventions — they are repo-owned and never touched by tooling.
+
+## rootfs top-level policy
+
+The only valid directories at the `rootfs/` root are `root/`, `tmp/`, and `usr/`.
+Anything else is a leftover from old patterns. Migration map:
+
+| Old rootfs path | Correct rootfs path |
+|-----------------|---------------------|
+| `rootfs/etc/{path}` | `rootfs/tmp/etc/{path}` |
+| `rootfs/config/{path}` | `rootfs/tmp/etc/{path}` |
+| `rootfs/data/{path}` | `rootfs/tmp/var/{path}` |
+| `rootfs/var/{path}` | `rootfs/tmp/var/{path}` |
+| `rootfs/opt/{path}` | `rootfs/tmp/opt/{path}` |
+| `rootfs/share/{path}` | `rootfs/usr/local/share/{path}` |
+
+`rootfs/usr/local/share/template-files/` is retired — the `DEFAULT_TEMPLATE_DIR`,
+`DEFAULT_FILE_DIR`, `DEFAULT_DATA_DIR`, and `DEFAULT_CONF_DIR` variables were removed
+from the template system; the entrypoint installs staged files from `rootfs/tmp/etc/`
+at container start instead.
+
+## Repo type detection
+
+A repo is an **app** repo when no `Dockerfile.*` variant files exist:
+
+```bash
+if find . -maxdepth 1 -name 'Dockerfile.*' -type f | grep -q -- .; then
+ REPO_TYPE="base"
+else
+ REPO_TYPE="app"
+fi
+```
+
+---
+
+# PART 2: TEMPLATE SYSTEM REFERENCE
+
+Templates ship with `gen-dockerfile`, installed at
+`/usr/local/share/CasjaysDev/scripts/templates/dockerfiles/`
+(`$CASJAYSDEVDIR/templates/dockerfiles/` in a dev checkout). To inspect what the current
+templates produce, generate a fresh reference tree in a temp dir:
+
+```bash
+gen-dockerfile /tmp/gen-dockerfile/{org}/{repo} {distro}
+```
+
+See `gen-dockerfile --help` for supported distros/types. Keep this PART in sync whenever
+the templates change.
+
+## Template inventory
+
+The template name selects the base OS family; for an app repo the resulting pull URL is
+always the matching `casjaysdev/*` image:
+
+| Template | Final stage | Init / PID 1 | App pulls FROM |
+|----------|-------------|--------------|----------------|
+| `alpine.template` | `scratch.template` | tini | `casjaysdev/alpine` |
+| `debian.template` | `scratch.template` | tini | `casjaysdev/debian` |
+| `ubuntu.template` | `scratch.template` | tini | `casjaysdev/ubuntu` |
+| `rhel.template` | `scratch.template` | tini | `casjaysdev/almalinux` |
+| `archlinux.template` | `scratch.template` | tini | `casjaysdev/archlinux` (multi-arch manifest) |
+| `web.template` | `systemd.template` | `/sbin/init` | `casjaysdev/web` |
+| `xorg.template` | `systemd.template` | `/sbin/init` | `casjaysdev/xorg` |
+
+Default template for app repos is `alpine` unless the application needs systemd, a GUI
+stack, or a distro-specific package.
+
+## Final-stage templates
+
+`scratch.template` — all non-GUI templates.
- `ENTRYPOINT [ "tini", "-p", "SIGTERM","--", "/usr/local/bin/entrypoint.sh" ]`
- `STOPSIGNAL SIGRTMIN+3`
-`systemd.template` — used by `web` and `xorg` (systemd runs as PID 1; tini is redundant).
+`systemd.template` — `web` and `xorg` (systemd is PID 1; tini is redundant).
- `ENTRYPOINT [ "/sbin/init" ]`
- `STOPSIGNAL SIGRTMIN+3`
- No `tini_provider` stage, no `COPY --from=tini_provider` line.
-Both templates are identical apart from `ENTRYPOINT`. OCI labels, `ENV HOSTNAME`, and
+Both are identical apart from `ENTRYPOINT`. OCI labels, `ENV HOSTNAME`, and
`VOLUME`/`EXPOSE`/`HEALTHCHECK` are the same in both.
-### OCI label standard
+## OCI label standard
-Both `scratch.template` and `systemd.template` emit these labels (no others):
+Both final-stage templates emit these labels (no others):
```
LABEL maintainer="${GEN_DOCKERFILE_MAINTAINER}"
@@ -76,93 +218,62 @@ LABEL org.opencontainers.image.vcs-type="Git"
LABEL com.github.containers.toolbox="false"
```
-Shell-expanded values (no `\`) are evaluated at template-render time by
-`gen-dockerfile`. Dollar-escaped values (`\${...}`) become literal Docker
-`ARG`/`ENV` references in the generated `Dockerfile`.
+Shell-expanded values (no `\`) are evaluated at template-render time by `gen-dockerfile`.
+Dollar-escaped values (`\${...}`) become literal Docker `ARG`/`ENV` references in the
+generated `Dockerfile`.
-Removed labels (do not re-add):
-- `org.opencontainers.image.base.name` — belongs on the base image, not the app image
+Resolved values for a `casjaysdevdocker` repo pushing to Docker Hub:
+
+| Label | Value |
+|-------|-------|
+| `url` | `https://hub.docker.com/r/casjaysdevdocker/{name}` — browsable Hub page; `gen-dockerfile` derives it from the registry host (`docker.io` → `hub.docker.com/r/`) |
+| `source` | `https://github.com/casjaysdevdocker/{name}` |
+| `documentation` | `https://github.com/casjaysdevdocker/{name}` |
+
+Older app repos may still carry `url="https://docker.io/casjaysdevdocker/{name}"` — that
+is the stale form; regeneration corrects it. Removed labels (never re-add):
+- `org.opencontainers.image.base.name` — belongs on the base image, not this image
- `org.opencontainers.image.schema-version` — non-spec; redundant with `version`
- Any duplicate `authors` or `source` entries
-### HOSTNAME convention
+## HOSTNAME convention
-All templates set `ENV HOSTNAME="casjaysdevdocker-${IMAGE_NAME}"` in every stage
-that declares it. The prefix is always `casjaysdevdocker-`, never `casjaysdev-`.
+All templates set `ENV HOSTNAME="casjaysdevdocker-${IMAGE_NAME}"` in every stage that
+declares it. The prefix is always `casjaysdevdocker-`, never `casjaysdev-`.
-### `GEN_DOCKERFILE_APP_DIR` and pull URL logic
+## `GEN_DOCKERFILE_APP_DIR` and pull URL logic
-`GEN_DOCKERFILE_APP_DIR` is auto-detected in `bin/gen-dockerfile` from the parent
-directory of `$PWD` (i.e. the GitHub org the project lives in):
+`GEN_DOCKERFILE_APP_DIR` is auto-detected by `gen-dockerfile` from the parent directory
+of `$PWD` (the org the checkout lives in):
```bash
GEN_DOCKERFILE_APP_DIR="${GEN_DOCKERFILE_APP_DIR:-$(basename -- "$(dirname -- "$PWD")")}"
```
-It controls which base images `GEN_DOCKER_SPECIFY_IMAGE_SOURCE_*` default to:
+It selects the `GEN_DOCKER_SPECIFY_IMAGE_SOURCE_*` defaults:
-```bash
-if [ "${GEN_DOCKERFILE_APP_DIR}" = "casjaysdevdocker" ]; then
- # Pull from pre-built casjaysdev/* base images on Docker Hub
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_RHEL="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_RHEL:-casjaysdev/almalinux}"
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_ALPINE="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_ALPINE:-casjaysdev/alpine}"
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_DEBIAN="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_DEBIAN:-casjaysdev/debian}"
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_UBUNTU="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_UBUNTU:-casjaysdev/ubuntu}"
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_ARCHLINUX="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_ARCHLINUX:-casjaysdev/archlinux}"
-else
- # Pull from upstream official images (dockersrc/* builds its own base images)
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_RHEL="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_RHEL:-almalinux}"
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_ALPINE="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_ALPINE:-alpine}"
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_DEBIAN="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_DEBIAN:-debian}"
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_UBUNTU="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_UBUNTU:-ubuntu}"
- GEN_DOCKER_SPECIFY_IMAGE_SOURCE_ARCHLINUX="${GEN_DOCKER_SPECIFY_IMAGE_SOURCE_ARCHLINUX:-archlinux}"
-fi
-```
-
-Summary:
-- `casjaysdevdocker/*` repos → `FROM casjaysdev/:latest` (already multi-arch)
+- `casjaysdevdocker/*` repos → `FROM casjaysdev/:latest` (pre-built, multi-arch)
- `dockersrc/*` and all other orgs → `FROM :latest` (upstream official images)
-The Docker Hub push org (`casjaysdev`) is unchanged regardless of `GEN_DOCKERFILE_APP_DIR`.
+App repos must resolve to the `casjaysdev/*` branch — a checkout outside
+`~/Projects/*/casjaysdevdocker/` needs `GEN_DOCKERFILE_APP_DIR="casjaysdevdocker"`
+exported before calling `gen-dockerfile`, or the regenerated Dockerfile silently reverts
+to upstream distro pulls (rule 8 violation).
-### Arch Linux multi-arch (`archlinux.template`)
+## Arch Linux apps
-When `GEN_DOCKERFILE_APP_DIR != "casjaysdevdocker"` (i.e. building a base image in
-`dockersrc/archlinux`), the template emits a three-stage FROM to support both
-`linux/amd64` and `linux/arm64`:
+`casjaysdev/archlinux` is a multi-arch manifest (`linux/amd64` + `linux/arm64`), so app
+repos use a single `FROM ${PULL_URL}:${DISTRO_VERSION} AS build` — the three-stage
+`base-${TARGETARCH}` FROM block belongs to the base repo only.
-```dockerfile
-ARG TARGETARCH
-ARG TARGETPLATFORM
-FROM --platform=${TARGETPLATFORM} archlinux:latest AS base-amd64
-FROM --platform=${TARGETPLATFORM} lopsided/archlinux-arm64v8:latest AS base-arm64
-FROM base-${TARGETARCH} AS build
-```
+## `web.template` / `xorg.template` notes
-When `GEN_DOCKERFILE_APP_DIR = "casjaysdevdocker"`, `casjaysdev/archlinux` is a
-multi-arch manifest so a single `FROM ${PULL_URL}:${DISTRO_VERSION} AS build` suffices.
+`web` apps inherit the systemd + noVNC stack (`SERVICE_PORT="5800"`,
+`EXPOSE_PORTS="5800 5900"` defaults); `xorg` apps inherit the systemd + Xorg stack. App
+packages go in `ENV_PACKAGES` / `02-packages.sh`, never by editing the template's stack
+list.
-### `web.template` packages
-
-The `web` template installs the following systemd + noVNC stack in the build stage:
-
-```
-systemd systemd-sysv dbus dbus-x11 procps
-tigervnc-standalone-server novnc openbox xdotool
-```
-
-Default ports: `SERVICE_PORT="5800"`, `EXPOSE_PORTS="5800 5900"`.
-
-### `xorg.template` packages
-
-The `xorg` template installs the following systemd + Xorg stack in the build stage:
-
-```
-systemd systemd-sysv dbus dbus-x11 procps
-xserver-xorg x11-xserver-utils xinit
-```
-
-### `debian.template` / `ubuntu.template` — RUN continuation fix
+## `debian.template` / `ubuntu.template` — RUN continuation
The first `RUN` block must have `; \` after the `echo` line so
`export DEBIAN_FRONTEND=noninteractive` executes before `apt-get`:
@@ -176,349 +287,254 @@ RUN set -e; \
Without the `; \` the export is a no-op and `apt-get` may prompt interactively.
+## Template resolution order
+
+1. `$GEN_DOCKERFILE_CONFIG_DIR/templates/.template` (user override)
+2. `/usr/local/share/CasjaysDev/scripts/templates/dockerfiles/.template`
+ (installed; `$CASJAYSDEVDIR/templates/dockerfiles/` in a dev checkout)
+
+`template_options.source` is sourced after `__set_variables`, allowing template-specific
+variable overrides.
+
---
-## Tool Reference
+# PART 3: TOOLING — gen-dockerfile & gen-script
-### `gen-dockerfile`
+## `gen-dockerfile`
```
Usage: gen-dockerfile [options] [dir] [template] [repo-name] [git-repo-url]
```
-Flags used in this runbook:
-
| Flag | Meaning |
|------|---------|
-| `--update` | Rewrite `.env.scripts` (add/drop vars against current template) and update ARG/LABEL lines in every `Dockerfile`/`Dockerfile.*`. Does not touch any other file. |
-| `--nogit` | Do not init or commit a git repo — required when running inside an existing repo. |
+| `--update` | Rewrite `.env.scripts` (add/drop vars against the current template) and update ARG/LABEL lines in the `Dockerfile`. Touches no other file. |
+| `--nogit` | Do not init or commit a git repo — required inside an existing repo. |
| `--dir PATH` | Operate on / write output to PATH instead of `$PWD`. |
-| `--template NAME` | Template to use (`alpine`, `debian`, `rhel`, `scratch`, `web`, `xorg`). Defaults to `alpine` if omitted. |
-| `--repo NAME` | Registry repo name (image basename). Defaults to the directory name if omitted. |
-| `--org NAME` | Alias for `--user`. Sets the registry owner / GitHub org. |
+| `--template NAME` | Template to use (`alpine`, `debian`, `ubuntu`, `rhel`, `archlinux`, `scratch`, `web`, `xorg`). Defaults to `alpine`. |
+| `--repo NAME` | Registry repo name (image basename). Defaults to the directory name. |
+| `--org NAME` | Registry owner / GitHub org (`--user` is an alias). Prefix `git:` or `reg:` to scope to one system; bare value sets both. For app repos both are `casjaysdevdocker`. |
+| `--registry URL` | Registry provider URL (e.g. `https://docker.io`). |
+| `--tag VERSION` | Image version tag (default `latest`). |
+| `--add-tags TAGS` | Comma-separated additional tags (`USE_DATE` = auto date tag). |
+| `--distro-name IMG` | Base image pull URL (overrides `ENV_PULL_URL`). |
+| `--distro-version T` | Base image tag (overrides `ENV_DISTRO_TAG`). |
+| `--startup FILE` | Generate an init.d service script at `rootfs/usr/local/etc/docker/init.d/FILE` via `gen-script other/start-service`. |
+| `--dockerfile` | Regenerate the Dockerfile only. |
+| `--force` | Overwrite existing files without prompting. |
-Resolution order when a value is not given by a flag: flags → git remote → project dirs → defaults.
+Resolution order when a value is not given by a flag: flags → git remote → project dirs →
+defaults.
-### `gen-script`
+Special subcommand — `gen-dockerfile actions` writes `.gitea/workflows/build.yml` from
+the existing `Dockerfile` (PART 7). App repos have no versioned `build.{ver}.yml` files.
+
+## `gen-script`
```
Usage: gen-script [options] [template] [filename]
```
-Flags and env vars used in this runbook:
-
| Flag / env var | Meaning |
|----------------|---------|
-| `--dir PATH` | Write the generated file to PATH instead of `$PWD`. The output file is `PATH/filename`. |
-| `-n` / `--name VALUE` | Sets the service name substituted into the generated file. In the `other/start-service` template this fills `REPLACE_SERVICE_NAME` — e.g. `--name nginx` pre-populates `SERVICE_NAME=nginx` in the output without a separate `sed` step. |
-| `GEN_SCRIPT_OVERWRITE="Y"` | Overwrite the output file without prompting. Default is `"A"` (ask). Must be set when the target file already exists or gen-script will prompt even with `GEN_SCRIPT_EDITFILE="N"`. |
-| `GEN_SCRIPT_EDITFILE="N"` | Suppress the interactive editor prompt after generation. Note: `-e`/`--no` sets BOTH this AND `GEN_SCRIPT_OVERWRITE="Y"` in one flag; setting this env var alone does NOT set OVERWRITE. |
-| `other/start-service` | Template path — words joined by `/`, matching the `@@Template` header in the existing script. This arg is positional (first non-flag arg). |
-| `filename` | Output file basename — second positional arg. Combined with `--dir` to form the full output path. |
+| `--dir PATH` | Write the generated file to `PATH/filename`. |
+| `-n` / `--name VALUE` | Service name substituted into the template — fills `REPLACE_SERVICE_NAME` in `other/start-service`, pre-populating `SERVICE_NAME=` without a sed step. |
+| `GEN_SCRIPT_OVERWRITE="Y"` | Overwrite the output without prompting (default `"A"` = ask). Required when the target exists, even with `GEN_SCRIPT_EDITFILE="N"`. |
+| `GEN_SCRIPT_EDITFILE="N"` | Suppress the interactive editor after generation. `-e`/`--no` sets BOTH this AND `GEN_SCRIPT_OVERWRITE="Y"`; the env var alone does not. |
+| `other/start-service` | Template path — positional arg 1, slash-joined words, matching the `@@Template` header. |
+| `filename` | Output basename — positional arg 2, combined with `--dir`. |
-Other available flags (for reference, not used in this runbook):
-
-| Flag | Meaning |
-|------|---------|
-| `-k` / `--keep` | Do not overwrite an existing file. |
-| `--replace` | Import and create a new header to replace an older one. |
-| `-d` / `--desc` | Set the description in the generated file header. |
-| `-p` / `--prev` | Set the header based on an existing file (copies its metadata). |
+Other flags: `-k`/`--keep` (never overwrite), `--replace` (new header replaces old),
+`-d`/`--desc` (header description), `-p`/`--prev` (copy header metadata from a file).
---
-## Session Start
+# PART 4: `.env.scripts` REFERENCE
-```bash
-git status --porcelain
-# If dirty:
-git stash push -m "session-start auto-stash"
-git pull
-# If stashed:
-git stash pop
-# If stash pop conflicts: report the conflicting files and stop — never auto-resolve
-```
-
-If `git pull` fails (no remote, offline, diverged): report it and stop.
-
----
+Generated at the repo root; sourced by `gen-dockerfile` and by CI at build time. App
+repos carry exactly one. It is a pure `KEY="value"` file — no logic.
## Variables
-```bash
-name="$(basename "$PWD")"
-SCRIPTS_DIR="${CASJAYSDEVDIR:-/usr/local/share/CasjaysDev/scripts}"
-TEMPLATE_DIR="$SCRIPTS_DIR/templates"
+| Variable | Purpose |
+|----------|---------|
+| `ENV_DOCKERFILE` | Dockerfile to build (`Dockerfile`) |
+| `ENV_REGISTRY_REPO` | Image name in the registry (`{name}`) |
+| `ENV_REGISTRY_ORG` | Registry namespace — `casjaysdevdocker` for app repos |
+| `ENV_REGISTRY_URL` | Registry base URL (`https://docker.io`) — pull/push host, never a label URL |
+| `ENV_REGISTRY_PUSH` | Full push path `org/repo` (`casjaysdevdocker/{name}`) |
+| `ENV_ADD_IMAGE_PUSH` | Extra push destinations |
+| `ENV_GIT_REPO_URL` | Full Git repo URL — `https://github.com/casjaysdevdocker/{name}`; feeds the `source`/`documentation` labels, so a wrong value here regresses labels on regeneration |
+| `ENV_USE_TEMPLATE` | Template name (`alpine`, `debian`, …) — the authoritative record of which base family the app builds on |
+| `ENV_PULL_URL` | Base image to pull FROM (`casjaysdev/`) |
+| `ENV_DISTRO_TAG` | Tag for the pull image (`latest`) |
+| `ENV_IMAGE_TAG` | Default image tag (`latest`) |
+| `ENV_ADD_TAGS` | Additional comma-separated tags; `USE_DATE` auto-generates a date tag |
+| `ENV_PACKAGES` | Space-separated package list |
+| `ENV_VENDOR` / `ENV_AUTHOR` / `ENV_MAINTAINER` | Label metadata |
+| `SERVICE_PORT` | Primary exposed port — apps normally set this |
+| `EXPOSE_PORTS` | Additional exposed ports |
+| `PHP_VERSION` / `NODE_VERSION` / `NODE_MANAGER` | Runtime versions (`system` default) |
+| `WWW_ROOT_DIR` | Web root (`/usr/local/share/httpd/default`) |
+| `DOCKER_ENTYPOINT_PORTS_WEB` / `DOCKER_ENTYPOINT_PORTS_SRV` | Ports passed to the entrypoint |
+| `DOCKER_ENTYPOINT_HEALTH_APPS` / `DOCKER_ENTYPOINT_HEALTH_ENDPOINTS` | Healthcheck targets |
-# Detect repo type: base repos (dockersrc) have Dockerfile.* variant files
-if find . -maxdepth 1 -name 'Dockerfile.*' -type f | grep -q -- .; then
- REPO_TYPE="base"
- org="dockersrc"
-else
- REPO_TYPE="app"
- org="casjaysdevdocker"
+## Legacy variable auto-migration
+
+`gen-dockerfile` calls `__migrate_env_script` on every run, renaming old variables:
+
+| Old name | Current name |
+|----------|-------------|
+| `ENV_IMAGE_NAME` | `ENV_REGISTRY_REPO` |
+| `ENV_IMAGE_PUSH` | `ENV_REGISTRY_PUSH` |
+| `ENV_HUB_BASE` | `ENV_REGISTRY_URL` |
+| `ENV_ORG_NAME` | `ENV_REGISTRY_ORG` |
+
+Never use the old names in new files. Retired variables that must not reappear anywhere:
+`DEFAULT_TEMPLATE_DIR`, `DEFAULT_FILE_DIR`, `DEFAULT_DATA_DIR`, `DEFAULT_CONF_DIR`.
+
+---
+
+# PART 5: RUNTIME SYSTEM — SETUP SCRIPTS, ENTRYPOINT, INIT.D
+
+## Build-time setup scripts (`rootfs/root/docker/setup/`)
+
+Run in order inside the build stage:
+
+| Script | Role |
+|--------|------|
+| `00-init.sh` | Initialize base directory structure and environment |
+| `01-system.sh` | Repos, locales, timezone, system settings |
+| `02-packages.sh` | App-specific packages, package managers, language runtimes |
+| `03-files.sh` | Install staged files (`rootfs/tmp/etc/*` → `/etc/*`), permissions, symlinks |
+| `04-users.sh` | Create service users/groups |
+| `05-custom.sh` | Application install logic — the heart of an app repo |
+| `06-post.sh` | Post-install configuration |
+| `07-cleanup.sh` | Remove build deps, caches, temp files |
+
+**`05-custom.sh` ownership:** the upstream template ships an empty stub. An app repo's
+`05-custom.sh` carries the application install (download/build, users, default config) —
+that content exists only in the repo's git history, never in the template. On
+regeneration, keep the existing body and pull forward only boilerplate (version-stamp
+header, `set` line, shellcheck-disable line). The same rule applies to any other `0*.sh`
+found to contain real logic beyond the stub.
+
+## Entrypoint flow
+
+```
+tini → /usr/local/bin/entrypoint.sh
+├─ Load /usr/local/etc/docker/functions/entrypoint.sh
+├─ Source env: /root/env.sh, /usr/local/etc/docker/env/*.sh, /config/env/*.sh
+├─ Seed /config and /data on first run
+├─ __start_init_scripts — source every init.d/*.sh in sort order
+├─ Handle `healthcheck` command
+└─ Execute main application
+```
+
+`rootfs/usr/local/bin/` generated set: `entrypoint.sh`, `pkmgr`, `symlink`, `copy`,
+`healthcheck`. `pkmgr` wraps the native package manager (`apk`, `apt-get`, `dnf`,
+`pacman`) behind `pkmgr update|install|remove|clean`.
+
+## App-specific bin scripts
+
+Extra scripts in `rootfs/usr/local/bin/` that `gen-dockerfile` does not generate are
+repo-owned. Their `@@Template` header governs maintenance:
+
+- `@@Template : shell/sh` — boilerplate synced from `$TEMPLATE_DIR/scripts/shell/sh`;
+ `#!/usr/bin/env sh`, `set -e` only (`pipefail` is a bashism — must NOT appear)
+- `@@Template : shell/bash` — synced from `shell/bash`; `set -eo pipefail` required
+- No `@@Template` header — hand-written; never modified by tooling
+
+## init.d scripts — critical rules
+
+**Each service gets its own numbered init.d script. Never merge or remove services.**
+`__start_init_scripts` sources every `*.sh` in sort order — multi-process apps have one
+script per daemon (e.g. gitea: `05-dockerd.sh`, `08-gitea.sh`, `zz-act_runner.sh`).
+
+init.d scripts are **regenerated, never patched in place** — old copies may call functions
+removed from the current `functions/entrypoint.sh`. Generate fresh via
+`gen-script other/start-service` (or `gen-dockerfile --startup`), then restore the
+app-specific values. They are `#!/usr/bin/env bash` with `set -eo pipefail`.
+
+Required variables in every init.d script:
+
+```bash
+SERVICE_NAME="myapp"
+EXEC_CMD_BIN='myapp'
+EXEC_CMD_ARGS=''
+EXEC_PRE_SCRIPT=''
+SERVICE_USES_PID=''
+IS_WEB_SERVER="no"
+IS_DATABASE_SERVICE="no"
+USES_DATABASE_SERVICE="no"
+DATABASE_SERVICE_TYPE="sqlite"
+RUNAS_USER="root"
+```
+
+Directory variables:
+
+```bash
+DATA_DIR="/data/$SERVICE_NAME"
+CONF_DIR="/config/$SERVICE_NAME"
+ETC_DIR="/etc/$SERVICE_NAME"
+LOG_DIR="/data/logs/$SERVICE_NAME"
+TMP_DIR="/tmp/$SERVICE_NAME"
+RUN_DIR="/run/$SERVICE_NAME"
+ROOT_FILE_PREFIX="/config/secure/auth/root"
+USER_FILE_PREFIX="/config/secure/auth/user"
+```
+
+## Hook functions
+
+The `start-service` template generates all outer hooks fully implemented — customise via
+the matching `*_local()` stub, which each outer hook calls automatically if defined:
+
+| Outer hook (do not redefine) | Customise via |
+|------------------------------|---------------|
+| `__run_precopy` | `__run_precopy_local` |
+| `__execute_prerun` | `__execute_prerun_local` |
+| `__run_pre_execute_checks` | `__run_pre_execute_checks_local` |
+| `__update_conf_files` | `__update_conf_files_local` |
+| `__pre_execute` | `__pre_execute_local` |
+| `__post_execute` | `__post_execute_local` |
+| `__pre_message` | `__pre_message_local` |
+| `__update_ssl_conf` | `__update_ssl_conf_local` |
+| `__create_service_env` | — |
+| `__run_start_script` | — |
+| `__run_secure_function` | — |
+
+## PID sentinel guard
+
+Every init.d script must guard on exactly this sentinel — leading dot, no underscores in
+the filename portion; any other form silently skips the guard:
+
+```bash
+if [ ! -f "/run/.start_init_scripts.pid" ]; then
+ echo "__start_init_scripts function hasn't been Initialized" >&2
+ SERVICE_IS_RUNNING="no"
+ __script_exit 1
fi
-
-# GEN_DOCKERFILE_APP_DIR is auto-detected by gen-dockerfile from the parent of $PWD.
-# For repos under casjaysdevdocker/: pull base from casjaysdev/* (pre-built multi-arch).
-# For repos under dockersrc/ or any other org: pull from upstream official images.
-# Override by setting GEN_DOCKERFILE_APP_DIR in the environment before calling gen-dockerfile.
```
+## Volumes
+
+- `/config` — persistent configuration
+- `/data` — persistent application data
+
---
-## Step 1 — Sync `.env.scripts` and Dockerfile ARG lines
+# PART 6: README.md STANDARD LAYOUT
-Run for **all** repos (both app and base):
+App image layout (`casjaysdevdocker/{name}` → `casjaysdevdocker/{name}`). Substitute
+`{name}` and `{port}` (the value of `SERVICE_PORT`); omit all `-p`/`ports:` sections only
+in the rare case `SERVICE_PORT` is empty.
-```bash
-gen-dockerfile --update --nogit --dir .
-```
+**Hand-crafted README exception:** a repo whose README deliberately diverges from this
+layout (full env-var tables, app-specific quick-start flags — e.g. gitea) owns its README.
+Update its facts (image name, org, ports, URLs), never rewrite its structure back to the
+generated layout.
-This rewrites `.env.scripts` against the current dotenv template: adds vars the template now
-includes, drops vars it no longer includes (e.g. `DEFAULT_TEMPLATE_DIR`, `DEFAULT_FILE_DIR`,
-`DEFAULT_DATA_DIR`, `DEFAULT_CONF_DIR`), and preserves all project-specific values
-(`ENV_REGISTRY_REPO`, `ENV_USE_TEMPLATE`, `ENV_PACKAGES`, etc.).
-
-It also updates ARG lines in every Dockerfile:
-- App repos (`REPO_TYPE=app`): updates `Dockerfile` only — `ARG IMAGE_NAME=`, `ARG IMAGE_REPO=`,
- `LABEL org.opencontainers.*`, and any removed ARG lines.
-- Base repos (`REPO_TYPE=base`): same changes applied to `Dockerfile` AND all `Dockerfile.*`
- variant files.
-
-All other file content is untouched.
-
-After running, capture the list of removed vars for use in Step 5:
-
-```bash
-removed_vars="$(git diff .env.scripts | grep -- '^-[A-Z_][A-Z0-9_]*=' | sed 's/^-//' | cut -d= -f1)"
-printf 'Removed vars: %s\n' "$removed_vars"
-```
-
----
-
-## Step 2 — Regenerate all rootfs files from temp dir
-
-Generate a complete fresh tree into a temp dir. Every file produced here is the authoritative
-replacement for its counterpart in this repo — old copies may reference removed functions or
-templates and will cause runtime failures if left in place.
-
-```bash
-tmpdir="$(mktemp -d "/tmp/gen-${name}-XXXXXX")"
-
-if [ "$REPO_TYPE" = "app" ]; then
- template="$(grep -- '^ENV_USE_TEMPLATE=' .env.scripts | cut -d= -f2 | tr -d '"')"
-else
- template="$(grep -- 'using the' Dockerfile | head -1 | sed 's/.*using the \([^ ]*\) template.*/\1/')"
-fi
-
-gen-dockerfile --dir "$tmpdir" --nogit --template "$template" --repo "$name" --org "$org"
-```
-
-Copy every file the temp dir produced that already exists in this repo — skip nothing:
-
-```bash
-find "$tmpdir/rootfs" -type f | while read -r src; do
- rel="${src#"$tmpdir/rootfs/"}"
- dest="rootfs/$rel"
- if [ -f "$dest" ]; then
- cp -f "$src" "$dest"
- fi
-done
-
-rm -rf "$tmpdir"
-```
-
-This covers: `rootfs/usr/local/bin/entrypoint.sh`, `rootfs/usr/local/bin/pkmgr`,
-`rootfs/usr/local/bin/symlink`, `rootfs/usr/local/bin/copy`, `rootfs/usr/local/bin/healthcheck`,
-`rootfs/usr/local/etc/docker/functions/entrypoint.sh`,
-`rootfs/root/docker/setup/00-*.sh` through `07-*.sh`, and every other file gen-dockerfile
-generates. The copy condition (`-f "$dest"`) means files not already in this repo are not
-added — only existing files are updated.
-
----
-
-## Step 3 — Update app-specific bin scripts
-
-Some repos have extra scripts in `rootfs/usr/local/bin/` that gen-dockerfile does not generate —
-they are app-specific (e.g. `check-record`, `get_dns_record`). These were not touched in Step 2.
-
-For each such script, read its `@@Template` header (line beginning `# @@Template`):
-
-**Has `@@Template : shell/sh`**
-Update boilerplate in-place from `$TEMPLATE_DIR/scripts/shell/sh`. Read the template, diff
-against the existing script, apply only the boilerplate changes (version stamp, shellcheck
-disable line, set line, trap lines). These are `#!/usr/bin/env sh` scripts — `set -e` is correct;
-`-o pipefail` is a bashism and must NOT appear. The app-specific logic body is untouched.
-
-**Has `@@Template : shell/bash`** (or another template path)
-Same process, using the matching template file. These are `#!/usr/bin/env bash` scripts —
-`set -eo pipefail` is required.
-
-**No `@@Template` header**
-Hand-written app logic. Do not modify it.
-
-After each edit run the appropriate syntax check:
-
-```bash
-# sh scripts
-sh -n "$script"
-# bash scripts
-bash -n "$script"
-```
-
----
-
-## Step 4 — Regenerate `init.d/*.sh`
-
-`init.d/*.sh` scripts must be regenerated from the current template — never updated in-place.
-Old copies may call functions that have since been removed from `entrypoint.sh`, causing failures.
-Each script also contains app-specific content that must be preserved; extract it before
-regenerating and restore it into the new file.
-
-For each `*.sh` in `rootfs/usr/local/etc/docker/init.d/` with `@@Template : other/start-service`
-in its header:
-
-**1. Read the existing script AND `$TEMPLATE_DIR/scripts/other/start-service`.**
-
-Diff the two. Every line or block present in the existing script but absent from the template is
-app-specific content. Record all of it. It typically includes:
-
-- `SERVICE_NAME=` value
-- `EXEC_CMD_BIN=` value
-- `EXEC_CMD_ARGS=` value
-- `DATA_DIR=`, `CONF_DIR=`, `ETC_DIR=`, `TMP_DIR=`, `RUN_DIR=`, `LOG_DIR=` values
-- `SERVICE_USER=` and `SERVICE_GROUP=` values
-- Extra `export` or variable declarations for this service
-- Service-specific env file sourcing (e.g. `. "/config/env/nginx.sh"`)
-- Custom code inside function bodies (pre-start checks, post-start waits, etc.)
-- App-specific functions defined at the top of the file (e.g. `__rndc_key`, `__tsig_key`)
-
-**2. Regenerate from the template:**
-
-```bash
-init_d_dir="rootfs/usr/local/etc/docker/init.d"
-filename="$(basename "$init_script")"
-svcname="$(grep -- '^SERVICE_NAME=' "$init_script" | cut -d= -f2 | tr -d '"')"
-# GEN_SCRIPT_OVERWRITE="Y" — overwrite the existing file without prompting (default is "A"/ask)
-# GEN_SCRIPT_EDITFILE="N" — suppress the interactive editor after generation
-# --dir — write the output file to init_d_dir/filename
-# --name — pre-fills REPLACE_SERVICE_NAME in the template with the service name,
-# so SERVICE_NAME= is correct in the generated file without a separate sed step
-# other/start-service — template path (positional arg 1, slash-joined words)
-# "$filename" — output file basename (positional arg 2); combined with --dir for full path
-GEN_SCRIPT_OVERWRITE="Y" GEN_SCRIPT_EDITFILE="N" gen-script --dir "$init_d_dir" --name "$svcname" other/start-service "$filename"
-```
-
-The regenerated file is `#!/usr/bin/env bash` — it must use `set -eo pipefail`. If gen-script
-emits `set -e` only, fix it:
-
-```bash
-sed -i 's/^set -e$/set -eo pipefail/' "$init_d_dir/$filename"
-```
-
-**3. Restore all app-specific content.**
-
-`SERVICE_NAME` is already correct — `--name "$svcname"` pre-filled it during generation.
-For all other app-specific `KEY=value` lines recorded in step 1:
-
-```bash
-sed -i "s|^EXEC_CMD_BIN=.*|EXEC_CMD_BIN=\"/usr/sbin/named\"|" "$init_d_dir/$filename"
-sed -i "s|^EXEC_CMD_ARGS=.*|EXEC_CMD_ARGS=\"-f -u named\"|" "$init_d_dir/$filename"
-```
-
-For multi-line function bodies and custom functions, use Edit to splice them into the correct
-location (same function or section they occupied before).
-
-The final script must:
-- Only call functions defined in the current `rootfs/usr/local/etc/docker/functions/entrypoint.sh`
- or defined within the script itself
-- Contain all app-specific variable values and custom logic from the old version
-- Pass `bash -n "$init_d_dir/$filename"` with no errors
-
----
-
-## Step 5 — Audit for dead variable and function references
-
-After regeneration, app-specific code preserved in Steps 3 and 4 may still reference env vars
-removed in Step 1 or functions no longer present in the current `entrypoint.sh`. Find and fix
-every such reference before committing.
-
-### 5a — Dead env var references
-
-Use `$removed_vars` captured in Step 1. For each removed var, search all scripts:
-
-```bash
-for var in $removed_vars; do
- grep -rn -- "\$$var\|\${$var" rootfs/ 2>/dev/null | grep -v -- '\.git'
-done
-```
-
-Fix every hit based on context:
-
-| Removed var | Replacement |
-|-------------|-------------|
-| `DEFAULT_TEMPLATE_DIR` | Remove the code that used it. The `template-files` directory no longer exists. If the code was copying default configs into `/config` or `/etc`, the entrypoint now handles that from `rootfs/tmp/etc/` at container start. |
-| `DEFAULT_FILE_DIR` | Same as above — remove usages. |
-| `DEFAULT_CONF_DIR` | Replace with `${CONF_DIR:-/etc/$SERVICE_NAME}` or the service-specific hardcoded path. |
-| `DEFAULT_DATA_DIR` | Replace with `${DATA_DIR:-/var/$SERVICE_NAME}` or the service-specific path. |
-| Any other removed var | Determine from context whether to remove the block or substitute the correct current var. |
-
-Also search for `__copy_templates` calls — that function copied from `$DEFAULT_TEMPLATE_DIR`
-and is now a no-op since the directory is gone. Remove any call to it in app-specific code:
-
-```bash
-grep -rn -- '__copy_templates' rootfs/usr/local/etc/docker/init.d/ rootfs/usr/local/bin/
-```
-
-### 5b — Dead function calls
-
-The fresh `rootfs/usr/local/etc/docker/functions/entrypoint.sh` from Step 2 is the ground truth
-for what functions are available at container runtime. Extract all defined names:
-
-```bash
-defined_fns="$(grep -oE -- '^__[a-zA-Z_]+' \
- rootfs/usr/local/etc/docker/functions/entrypoint.sh | sort -u)"
-```
-
-For each script NOT fully replaced from the temp dir (init.d scripts, custom bin scripts), find
-calls to functions that are neither in `$defined_fns` nor defined within the script itself:
-
-```bash
-for script in rootfs/usr/local/etc/docker/init.d/*.sh rootfs/usr/local/bin/*; do
- [ -f "$script" ] || continue
- local_fns="$(grep -oE -- '^__[a-zA-Z_]+' "$script" | sort -u)"
- grep -oE -- '__[a-zA-Z_]+' "$script" | sort -u | while read -r fn; do
- if ! printf '%s\n' $defined_fns $local_fns | grep -qx -- "$fn"; then
- printf 'DEAD: %s in %s\n' "$fn" "$script"
- fi
- done
-done
-```
-
-For each dead call found:
-
-- Check whether the function was renamed in the current template (e.g. `__get_ip` → `__get_ip4`
- or `__get_ip6`) and update the call.
-- If the function was removed with no replacement, remove the call and any surrounding block
- that only makes sense with it.
-- When unsure, check `$TEMPLATE_DIR/scripts/` for the current equivalent.
-
-Fix every dead reference before proceeding.
-
----
-
-## Step 6 — Update README.md
-
-Rewrite `README.md` to match the current state. Use the existing file as a base; update any stale
-values (wrong image name, wrong org, wrong ports).
-
-Read `SERVICE_PORT` from `.env.scripts` for the port value (app repos). Omit all `-p` and
-`ports:` sections when `SERVICE_PORT` is empty or unset.
-
-### App container layout (`casjaysdevdocker/{name}`)
-
-```markdown
+````markdown
## 👋 Welcome to {name} 🚀
{name} README
@@ -540,10 +556,10 @@ dockermgr update {name}
## Install and run container
```shell
-dockerHome="/var/lib/srv/$USER/docker/casjaysdevdocker/{name}/{name}/latest/rootfs"
-mkdir -p "/var/lib/srv/$USER/docker/{name}/rootfs"
+dockerHome="/srv/$USER/docker/casjaysdevdocker/{name}/latest/volumes"
+mkdir -p "$dockerHome"
git clone "https://github.com/dockermgr/{name}" "$HOME/.local/share/CasjaysDev/dockermgr/{name}"
-cp -Rfva "$HOME/.local/share/CasjaysDev/dockermgr/{name}/rootfs/." "$dockerHome/"
+cp -Rfva "$HOME/.local/share/CasjaysDev/dockermgr/{name}/volumes/." "$dockerHome/"
docker run -d \
--restart always \
--privileged \
@@ -559,7 +575,6 @@ casjaysdevdocker/{name}:latest
## via docker-compose
```yaml
-version: "2"
services:
ProjectName:
image: casjaysdevdocker/{name}
@@ -568,8 +583,8 @@ services:
- TZ=America/New_York
- HOSTNAME={name}
volumes:
- - "/var/lib/srv/$USER/docker/casjaysdevdocker/{name}/{name}/latest/rootfs/data:/data:z"
- - "/var/lib/srv/$USER/docker/casjaysdevdocker/{name}/{name}/latest/rootfs/config:/config:z"
+ - "/srv/$USER/docker/casjaysdevdocker/{name}/latest/volumes/data:/data:z"
+ - "/srv/$USER/docker/casjaysdevdocker/{name}/latest/volumes/config:/config:z"
ports:
- {port}:{port}
restart: always
@@ -598,198 +613,85 @@ buildx
🤖 casjay: [Github](https://github.com/casjay) 🤖
⛵ casjaysdevdocker: [Github](https://github.com/casjaysdevdocker) [Docker](https://hub.docker.com/u/casjaysdevdocker) ⛵
-```
-
-### Base image layout (`dockersrc/{name}`)
-
-```markdown
-## 👋 Welcome to {name} 🚀
-
-{name} README
-
-
-## Install my system scripts
-
-```shell
- sudo bash -c "$(curl -q -LSsf "https://github.com/systemmgr/installer/raw/main/install.sh")"
- sudo systemmgr --config && sudo systemmgr install scripts
-```
-
-## Automatic install/update
-
-```shell
-dockermgr update os {name}
-```
-
-## Install and run container
-
-```shell
-mkdir -p "/var/lib/srv/root/docker/casjaysdev/{name}/latest"
-git clone "https://github.com/dockermgr/{name}" "$HOME/.local/share/CasjaysDev/dockermgr/{name}"
-cp -Rfva "$HOME/.local/share/CasjaysDev/dockermgr/{name}/rootfs/." "/var/lib/srv/root/docker/casjaysdev/{name}/latest/"
-docker run -d \
---restart always \
---privileged \
---name casjaysdev-{name}-latest \
---hostname {name} \
--e TZ=${TIMEZONE:-America/New_York} \
--v "/var/lib/srv/root/docker/casjaysdev/{name}/latest/data:/data:z" \
--v "/var/lib/srv/root/docker/casjaysdev/{name}/latest/config:/config:z" \
-casjaysdev/{name}:latest
-```
-
-## via docker-compose
-
-```yaml
-version: "2"
-services:
- ProjectName:
- image: casjaysdev/{name}
- container_name: casjaysdev-{name}-latest
- environment:
- - TZ=America/New_York
- - HOSTNAME={name}
- volumes:
- - "/var/lib/srv/root/docker/casjaysdev/{name}/latest/data:/data:z"
- - "/var/lib/srv/root/docker/casjaysdev/{name}/latest/config:/config:z"
- restart: always
-```
-
-## Get source files
-
-```shell
-dockermgr download src os {name}
-```
-
-## Build container
-
-```shell
-git clone "https://github.com/dockersrc/{name}" "$HOME/Projects/github/dockersrc/{name}"
-cd "$HOME/Projects/github/dockersrc/{name}" && buildx all
-```
-
-## Authors
-
-🤖 casjay: [Github](https://github.com/casjay) 🤖
-⛵ casjaysdev: [Github](https://github.com/dockersrc) [Docker](https://hub.docker.com/u/casjaysdev) ⛵
-```
+````
---
-## Step 7 — Clean up non-standard rootfs directories
+# PART 7: CI/CD WORKFLOWS
-The only valid directories at the `rootfs/` root level are `root/`, `tmp/`, and `usr/`. Any other
-directory is a leftover from old patterns and must be cleaned up.
+## Generated workflow (`gen-dockerfile actions`)
-Find non-standard dirs:
+`gen-dockerfile actions` writes `.gitea/workflows/build.yml` from the current
+`Dockerfile`. App repos get the single `build.yml` only — no versioned variants. All
+actions are SHA-pinned — never tag-pinned.
-```bash
-find rootfs -maxdepth 1 -mindepth 1 -type d | grep -vE -- 'rootfs/(root|tmp|usr)$'
-```
+- **Triggers:** `push` to `main`, monthly schedule, `workflow_dispatch`
+- **Registry strategy:** always logs in to the Gitea registry via the auto-provided
+ `GITEA_TOKEN`; conditionally logs in to Docker Hub when `vars.DOCKER_USERNAME` is set
+ (`vars.DOCKER_USERNAME` + `secrets.DOCKER_PASSWORD`; `vars.DOCKER_REGISTRY` overrides
+ the registry, `vars.DOCKER_ORG` the namespace)
+- **Platforms:** `linux/amd64,linux/arm64`
+- **build-args:** only `BUILD_DATE`, `GIT_COMMIT`, `BUILD_VERSION`
+- **Tags pushed:** date tag (`yymm`) + `latest` to both registries
+- **Annotations:** mirror the OCI label standard (PART 2), with `url`/`source`/
+ `documentation` set to the workflow's repository URL
-**If the directory contains only `.gitkeep` (empty placeholder):** remove it directly.
+## Legacy workflow (`docker.yaml`)
-```bash
-rm -rf "rootfs/{dir}"
-```
-
-**If the directory contains actual files:** migrate them to the correct location first, then remove.
-
-Migration path map:
-
-| Old rootfs path | Correct rootfs path |
-|-----------------|---------------------|
-| `rootfs/etc/{path}` | `rootfs/tmp/etc/{path}` |
-| `rootfs/config/{path}` | `rootfs/tmp/etc/{path}` |
-| `rootfs/data/{path}` | `rootfs/tmp/var/{path}` |
-| `rootfs/var/{path}` | `rootfs/tmp/var/{path}` |
-| `rootfs/opt/{path}` | `rootfs/tmp/opt/{path}` |
-| `rootfs/share/{path}` | `rootfs/usr/local/share/{path}` |
-
-Migration pattern (adapt `src_dir` and `dest_dir` per the table above):
-
-```bash
-src_dir="rootfs/etc"
-dest_dir="rootfs/tmp/etc"
-find "$src_dir" -type f | while read -r src; do
- rel="${src#"$src_dir/"}"
- dest="$dest_dir/$rel"
- mkdir -p "$(dirname -- "$dest")"
- mv "$src" "$dest"
-done
-rm -rf "$src_dir"
-```
-
-Also remove `rootfs/usr/local/share/template-files/` if it exists — the `DEFAULT_TEMPLATE_DIR`,
-`DEFAULT_FILE_DIR`, `DEFAULT_DATA_DIR`, and `DEFAULT_CONF_DIR` variables were removed from the
-template and this directory is no longer used at build time:
-
-```bash
-rm -rf rootfs/usr/local/share/template-files
-```
+A hand-crafted `.gitea/workflows/docker.yaml` may exist in older repos — reference copy in
+the org-level `.github` repo. **Never overwrite it, and never use it as a template for new
+work** — it uses tag-pinned actions and retired secret names. All new/updated workflows
+come from `gen-dockerfile actions`.
---
-## Step 8 — Verify
+# PART 8: VERIFICATION & COMMIT
-Run syntax checks on every script that was touched. Fix all failures before committing.
+## Syntax gates
+
+Every touched script must pass before commit:
```bash
-# bin scripts (check shebang to pick the right interpreter)
for f in rootfs/usr/local/bin/*; do
[ -f "$f" ] || continue
case "$(head -1 "$f")" in
- *bash*) bash -n "$f" && printf 'OK: %s\n' "$f" || printf 'FAIL: %s\n' "$f" ;;
- *sh*) sh -n "$f" && printf 'OK: %s\n' "$f" || printf 'FAIL: %s\n' "$f" ;;
+ *bash*) bash -n "$f" || exit 1 ;;
+ *sh*) sh -n "$f" || exit 1 ;;
esac
done
-# entrypoint.sh and setup scripts are bash
bash -n rootfs/usr/local/etc/docker/functions/entrypoint.sh
-for f in rootfs/root/docker/setup/0*.sh; do
+for f in rootfs/root/docker/setup/0*.sh rootfs/usr/local/etc/docker/init.d/*.sh; do
[ -f "$f" ] || continue
- bash -n "$f" && printf 'OK: %s\n' "$f" || printf 'FAIL: %s\n' "$f"
-done
-
-# init.d scripts are bash
-for f in rootfs/usr/local/etc/docker/init.d/*.sh; do
- [ -f "$f" ] || continue
- bash -n "$f" && printf 'OK: %s\n' "$f" || printf 'FAIL: %s\n' "$f"
+ bash -n "$f" || exit 1
done
```
----
+## Dead-reference gates
-## Step 9 — Commit
+After any regeneration:
-Check what actually changed:
+1. No script references an env var removed from `.env.scripts` (diff-driven check).
+2. No script calls a function absent from both the current
+ `functions/entrypoint.sh` and the script itself.
+3. No `__copy_templates` calls remain (retired with `DEFAULT_TEMPLATE_DIR`).
+4. `Dockerfile` still pulls `FROM casjaysdev/*` (rule 8) — an upstream distro pull means
+ `GEN_DOCKERFILE_APP_DIR` resolved wrong during regeneration.
+
+## Commit
```bash
git status --porcelain
git diff --stat
```
-Write `.git/COMMIT_MESS` listing only the files that actually changed per `git diff --stat`.
-Subject line ≤64 chars. Body as `- path: change` bullets. Include only what changed in this run.
-
-Example template (adjust bullets to match actual diff):
-
-```
-✨ Update to latest docker template revision ✨
-
-- .env.scripts: synced vars to current template
-- Dockerfile: removed stale ARG lines, updated IMAGE_NAME/REPO/LABEL
-- rootfs/usr/local/bin/*: regenerated from current template via gen-dockerfile
-- rootfs/usr/local/etc/docker/functions/entrypoint.sh: replaced from template
-- rootfs/usr/local/etc/docker/init.d/*.sh: regenerated; app-specific values restored
-- rootfs/root/docker/setup/: regenerated from current template
-- README.md: updated to current standard layout
-- rootfs/{old-dirs}: files migrated to rootfs/tmp/; stale directories removed
-```
-
-Then commit:
+Write `.git/COMMIT_MESS` from the actual diff — subject ≤64 chars, body as
+`- path: change` bullets covering every changed file. Then:
```bash
gitcommit --dir "$(git rev-parse --show-toplevel)" all
```
+
+`git commit` / `git push` directly are forbidden. Never commit with a failing syntax
+gate.