mirror of
https://github.com/casjaysdevdocker/ampache
synced 2026-08-21 08:01:12 -04:00
📖 Standardize AI.md; move build docs into IDEA.md 📖
ampache / release-ampache (push) Canceled after 0s
ampache / release-ampache (push) Canceled after 0s
AI.md is now the standardized application-image specification from
claudemgr/docker/CASJAYSDEVDOCKER.md (PARTs 0–8: critical rules, repo
model, template system and OCI label canon, tooling, .env.scripts,
runtime system, README layout, CI/CD, verification gates). The former
AI.md's app-specific build documentation ("How this image is built and
wired" — stack, package set, setup wiring) now lives in IDEA.md
alongside the existing "What this image is" description, so nothing
was lost.
- AI.md: replaced with the CASJAYSDEVDOCKER.md master template
- IDEA.md: appended the app-specific build documentation moved out of AI.md
This commit is contained in:
@@ -1,86 +1,697 @@
|
|||||||
# How this image is built and wired
|
# CasjaysDev Docker Application Image Specification (casjaysdevdocker)
|
||||||
|
|
||||||
## Base image and tooling
|
**Name**: {name}
|
||||||
|
|
||||||
Built from `casjaysdev/alpine:latest` via a multi-stage Dockerfile (build stage → `FROM scratch` final). `tini` is the PID 1 supervisor; `gosu` provides privilege dropping. Package management is handled by `pkmgr` (auto-detects `apk`).
|
**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.
|
||||||
|
|
||||||
## Package set
|
**Note:** `{name}` in this file is a reference token, not setup-time replacement text. Its
|
||||||
|
value is always the repo directory basename (`basename "$PWD"`).
|
||||||
|
|
||||||
All packages come from Alpine's `apk` package manager. Key groups:
|
**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.
|
||||||
|
|
||||||
- **Apache 2**: `apache2 apache2-ctl apache2-utils apache2-ssl apache2-proxy apache2-http2 apache2-brotli apache2-icons apache2-error`
|
---
|
||||||
- **PHP 8.4**: `php84 php84-fpm` plus all modules Ampache requires (pdo, pdo_mysql, mysqli, mysqlnd, session, intl, curl, simplexml, xml, xmlreader, xmlwriter, dom, mbstring, iconv, tokenizer, fileinfo, openssl, phar, gd, zip, bz2, gmp, exif, opcache, pecl-redis, ctype)
|
|
||||||
- **MariaDB**: `mariadb mariadb-client mariadb-server-utils`
|
|
||||||
- **Utilities**: `bash tini curl wget unzip tar gzip tzdata ca-certificates pwgen`
|
|
||||||
|
|
||||||
## Build-time setup flow (`rootfs/root/docker/setup/`)
|
# PART INDEX
|
||||||
|
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PART 0: CRITICAL RULES
|
||||||
|
|
||||||
|
## Org mapping
|
||||||
|
|
||||||
|
| System | Org | Example |
|
||||||
|
|--------|-----|---------|
|
||||||
|
| GitHub (source) | `casjaysdevdocker` | `https://github.com/casjaysdevdocker/{name}` |
|
||||||
|
| Docker Hub (push) | `casjaysdevdocker` | `casjaysdevdocker/{name}` |
|
||||||
|
|
||||||
|
`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`).
|
||||||
|
|
||||||
|
## Non-negotiable rules
|
||||||
|
|
||||||
|
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/<base>`** — 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` — `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 are identical apart from `ENTRYPOINT`. OCI labels, `ENV HOSTNAME`, and
|
||||||
|
`VOLUME`/`EXPOSE`/`HEALTHCHECK` are the same in both.
|
||||||
|
|
||||||
|
## OCI label standard
|
||||||
|
|
||||||
|
Both final-stage templates emit these labels (no others):
|
||||||
|
|
||||||
|
```
|
||||||
|
LABEL maintainer="${GEN_DOCKERFILE_MAINTAINER}"
|
||||||
|
LABEL org.opencontainers.image.vendor="${GEN_DOCKERFILE_VENDOR:-CasjaysDev}"
|
||||||
|
LABEL org.opencontainers.image.authors="${GEN_DOCKERFILE_AUTHOR:-CasjaysDev}"
|
||||||
|
LABEL org.opencontainers.image.licenses="${LICENSE}"
|
||||||
|
LABEL org.opencontainers.image.title="${IMAGE_NAME}"
|
||||||
|
LABEL org.opencontainers.image.description="Containerized version of ${IMAGE_NAME}"
|
||||||
|
LABEL org.opencontainers.image.created="${BUILD_DATE}"
|
||||||
|
LABEL org.opencontainers.image.version="${BUILD_VERSION}"
|
||||||
|
LABEL org.opencontainers.image.revision="${GIT_COMMIT}"
|
||||||
|
LABEL org.opencontainers.image.url="${GEN_DOCKERFILE_HUB_REPO}"
|
||||||
|
LABEL org.opencontainers.image.source="${GEN_DOCKERFILE_GIT_REPO}"
|
||||||
|
LABEL org.opencontainers.image.documentation="${GEN_DOCKERFILE_GIT_REPO}"
|
||||||
|
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`.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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` 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 selects the `GEN_DOCKER_SPECIFY_IMAGE_SOURCE_*` defaults:
|
||||||
|
|
||||||
|
- `casjaysdevdocker/*` repos → `FROM casjaysdev/<distro>:latest` (pre-built, multi-arch)
|
||||||
|
- `dockersrc/*` and all other orgs → `FROM <distro>:latest` (upstream official images)
|
||||||
|
|
||||||
|
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 apps
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
## `web.template` / `xorg.template` notes
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
## `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`:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
RUN set -e; \
|
||||||
|
echo "Updating the system"; \
|
||||||
|
export DEBIAN_FRONTEND=noninteractive; \
|
||||||
|
apt-get update && apt-get upgrade -yy && apt-get dist-upgrade -yy
|
||||||
|
```
|
||||||
|
|
||||||
|
Without the `; \` the export is a no-op and `apt-get` may prompt interactively.
|
||||||
|
|
||||||
|
## Template resolution order
|
||||||
|
|
||||||
|
1. `$GEN_DOCKERFILE_CONFIG_DIR/templates/<name>.template` (user override)
|
||||||
|
2. `/usr/local/share/CasjaysDev/scripts/templates/dockerfiles/<name>.template`
|
||||||
|
(installed; `$CASJAYSDEVDIR/templates/dockerfiles/` in a dev checkout)
|
||||||
|
|
||||||
|
`template_options.source` is sourced after `__set_variables`, allowing template-specific
|
||||||
|
variable overrides.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PART 3: TOOLING — gen-dockerfile & gen-script
|
||||||
|
|
||||||
|
## `gen-dockerfile`
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: gen-dockerfile [options] [dir] [template] [repo-name] [git-repo-url]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Meaning |
|
||||||
|
|------|---------|
|
||||||
|
| `--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`, `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.
|
||||||
|
|
||||||
|
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]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag / env var | Meaning |
|
||||||
|
|----------------|---------|
|
||||||
|
| `--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 flags: `-k`/`--keep` (never overwrite), `--replace` (new header replaces old),
|
||||||
|
`-d`/`--desc` (header description), `-p`/`--prev` (copy header metadata from a file).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PART 4: `.env.scripts` REFERENCE
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
| 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/<base>`) |
|
||||||
|
| `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 |
|
||||||
|
|
||||||
|
## 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 |
|
| Script | Role |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| `00-init.sh` | Clears `template-files/{data,config,defaults}` staging dirs |
|
| `00-init.sh` | Initialize base directory structure and environment |
|
||||||
| `01-system.sh` | Stub (Alpine repos are already configured by the base image) |
|
| `01-system.sh` | Repos, locales, timezone, system settings |
|
||||||
| `02-packages.sh` | Stub (no post-install compile steps; prebuilt Ampache zip needs no composer/npm) |
|
| `02-packages.sh` | App-specific packages, package managers, language runtimes |
|
||||||
| `03-files.sh` | Auto-installs `rootfs/tmp/etc/*` → `/etc/*` and stages copies under `template-files/config/` |
|
| `03-files.sh` | Install staged files (`rootfs/tmp/etc/*` → `/etc/*`), permissions, symlinks |
|
||||||
| `04-users.sh` | Defensive creation of `apache:apache` and `mysql:mysql` system accounts |
|
| `04-users.sh` | Create service users/groups |
|
||||||
| `05-custom.sh` | **Core setup**: wipes distro defaults under `/etc/{apache2,php84,my.cnf.d}/*`, copies our optimized configs from `/tmp/etc/`, then downloads and unpacks the Ampache prebuilt zip to `/usr/local/share/ampache/` |
|
| `05-custom.sh` | Application install logic — the heart of an app repo |
|
||||||
| `06-post.sh` | Stub |
|
| `06-post.sh` | Post-install configuration |
|
||||||
| `07-cleanup.sh` | Stub |
|
| `07-cleanup.sh` | Remove build deps, caches, temp files |
|
||||||
|
|
||||||
## Config wipe-and-replace
|
**`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.
|
||||||
|
|
||||||
`05-custom.sh` performs the canonical wipe-and-replace for all three config trees:
|
## Entrypoint flow
|
||||||
|
|
||||||
1. Preserve `apache2/mime.types` and `apache2/magic` (not shipped in our `rootfs/tmp/etc/apache2/`)
|
```
|
||||||
2. `rm -Rf /etc/{apache2,php84,my.cnf.d}/*`
|
tini → /usr/local/bin/entrypoint.sh
|
||||||
3. `cp -Rf /tmp/etc/{apache2,php84,my.cnf.d}/. /etc/{apache2,php84,my.cnf.d}/`
|
├─ Load /usr/local/etc/docker/functions/entrypoint.sh
|
||||||
4. Copy resulting `/etc/` trees to `template-files/config/` for runtime seeding
|
├─ 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
|
||||||
|
```
|
||||||
|
|
||||||
## Runtime init.d scripts
|
`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`.
|
||||||
|
|
||||||
Two init.d scripts run in numeric order:
|
## App-specific bin scripts
|
||||||
|
|
||||||
### `09-mariadb.sh`
|
Extra scripts in `rootfs/usr/local/bin/` that `gen-dockerfile` does not generate are
|
||||||
- `SERVICE_NAME="mariadb"`, `EXEC_CMD_BIN='mariadbd'`
|
repo-owned. Their `@@Template` header governs maintenance:
|
||||||
- Runs as `mysql:mysql`; datadir `/data/db/mariadb`; socket `/run/mysqld/mysqld.sock`
|
|
||||||
- `IS_DATABASE_SERVICE="yes"`, `DATABASE_SERVICE_TYPE="mariadb"`
|
|
||||||
- `__run_pre_execute_checks`: initializes the datadir with `mariadb-install-db` if `ibdata1` is missing
|
|
||||||
- `__post_execute`: waits for the socket, then creates the `ampache` database, `ampache` user (random password), grants privileges, and sets the root password
|
|
||||||
|
|
||||||
### `99-ampache.sh`
|
- `@@Template : shell/sh` — boilerplate synced from `$TEMPLATE_DIR/scripts/shell/sh`;
|
||||||
- `SERVICE_NAME="ampache"`, `EXEC_CMD_BIN='/usr/local/etc/docker/bin/start-ampache'`
|
`#!/usr/bin/env sh`, `set -e` only (`pipefail` is a bashism — must NOT appear)
|
||||||
- `IS_WEB_SERVER="yes"`, `USES_DATABASE_SERVICE="yes"`, `DATABASE_SERVICE_TYPE="mariadb"`
|
- `@@Template : shell/bash` — synced from `shell/bash`; `set -eo pipefail` required
|
||||||
- `SERVICE_USER="apache"`, `SERVICE_GROUP="apache"`
|
- No `@@Template` header — hand-written; never modified by tooling
|
||||||
- `WWW_ROOT_DIR="/usr/local/share/ampache/public"`, `ETC_DIR="/etc/apache2"`, `CONF_DIR="/config/apache2"`
|
|
||||||
- `ADDITIONAL_CONFIG_DIRS="/config/php84 /config/ampache"`
|
|
||||||
- `__execute_prerun`: creates runtime dirs (`/run/apache2 /run/php-fpm /tmp/php-sessions /data/logs/{apache2,php-fpm}`), chowns them to `apache:apache`; symlinks `ampache.cfg.php` between `/usr/local/share/ampache/config/` and `/config/ampache/` once install.php writes it
|
|
||||||
- `__run_pre_execute_checks`: waits up to 30 s for the MariaDB socket; validates `httpd -t` apache config syntax
|
|
||||||
- `__update_conf_files`: replaces the `REPLACE_TZ` token in `/etc/php84/php.ini` with `$TZ`
|
|
||||||
- `__post_execute`: mirrors `ampache.cfg.php` out to `/config/ampache/` and creates the symlink if install.php has written it
|
|
||||||
|
|
||||||
### `start-ampache` wrapper
|
## init.d scripts — critical rules
|
||||||
`EXEC_CMD_BIN` points to `/usr/local/etc/docker/bin/start-ampache`, which:
|
|
||||||
1. Starts `php-fpm84` in the background (waits up to 10 s for `/run/php-fpm/php-fpm.sock`)
|
|
||||||
2. Exec's `httpd -D FOREGROUND -f /etc/apache2/httpd.conf` (becomes the process the framework monitors)
|
|
||||||
|
|
||||||
## Config paths
|
**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`).
|
||||||
|
|
||||||
| Component | Config file | User-editable at |
|
init.d scripts are **regenerated, never patched in place** — old copies may call functions
|
||||||
|-----------|-------------|-----------------|
|
removed from the current `functions/entrypoint.sh`. Generate fresh via
|
||||||
| Apache main | `/etc/apache2/httpd.conf` | `/config/apache2/httpd.conf` |
|
`gen-script other/start-service` (or `gen-dockerfile --startup`), then restore the
|
||||||
| Apache ampache vhost | `/etc/apache2/conf.d/ampache.conf` | `/config/apache2/conf.d/ampache.conf` |
|
app-specific values. They are `#!/usr/bin/env bash` with `set -eo pipefail`.
|
||||||
| Apache MPM tuning | `/etc/apache2/conf.d/mpm.conf` | `/config/apache2/conf.d/mpm.conf` |
|
|
||||||
| User vhosts | `IncludeOptional /config/apache2/vhosts.d/*.conf` | `/config/apache2/vhosts.d/` |
|
|
||||||
| PHP runtime | `/etc/php84/php.ini` | `/config/php84/php.ini` |
|
|
||||||
| PHP-FPM global | `/etc/php84/php-fpm.conf` | `/config/php84/php-fpm.conf` |
|
|
||||||
| PHP-FPM pool | `/etc/php84/php-fpm.d/www.conf` | `/config/php84/php-fpm.d/www.conf` |
|
|
||||||
| MariaDB server | `/etc/my.cnf.d/mariadb-server.cnf` | `/config/my.cnf.d/mariadb-server.cnf` |
|
|
||||||
| Ampache app config | `/usr/local/share/ampache/config/ampache.cfg.php` | `/config/ampache/ampache.cfg.php` (symlinked) |
|
|
||||||
|
|
||||||
## Volume layout
|
Required variables in every init.d script:
|
||||||
|
|
||||||
- `/config` — all user-editable configs (seeded on first run from `template-files/config/`)
|
```bash
|
||||||
- `/data` — MariaDB datadir (`/data/db/mariadb`), logs (`/data/logs/`), media library if mounted
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
## Port
|
Directory variables:
|
||||||
|
|
||||||
- `80` (HTTP); `443` available if SSL is enabled via `/config/enable/ssl`
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Volumes
|
||||||
|
|
||||||
|
- `/config` — persistent configuration
|
||||||
|
- `/data` — persistent application data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PART 6: README.md STANDARD LAYOUT
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
````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 {name}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install and run container
|
||||||
|
|
||||||
|
```shell
|
||||||
|
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}/volumes/." "$dockerHome/"
|
||||||
|
docker run -d \
|
||||||
|
--restart always \
|
||||||
|
--privileged \
|
||||||
|
--name casjaysdevdocker-{name}-latest \
|
||||||
|
--hostname {name} \
|
||||||
|
-e TZ=${TIMEZONE:-America/New_York} \
|
||||||
|
-v "$dockerHome/data:/data:z" \
|
||||||
|
-v "$dockerHome/config:/config:z" \
|
||||||
|
-p {port}:{port} \
|
||||||
|
casjaysdevdocker/{name}:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## via docker-compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
ProjectName:
|
||||||
|
image: casjaysdevdocker/{name}
|
||||||
|
container_name: casjaysdevdocker-{name}
|
||||||
|
environment:
|
||||||
|
- TZ=America/New_York
|
||||||
|
- HOSTNAME={name}
|
||||||
|
volumes:
|
||||||
|
- "/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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get source files
|
||||||
|
|
||||||
|
```shell
|
||||||
|
dockermgr download src casjaysdevdocker/{name}
|
||||||
|
```
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone "https://github.com/casjaysdevdocker/{name}" "$HOME/Projects/github/casjaysdevdocker/{name}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build container
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd "$HOME/Projects/github/casjaysdevdocker/{name}"
|
||||||
|
buildx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
🤖 casjay: [Github](https://github.com/casjay) 🤖
|
||||||
|
⛵ casjaysdevdocker: [Github](https://github.com/casjaysdevdocker) [Docker](https://hub.docker.com/u/casjaysdevdocker) ⛵
|
||||||
|
````
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PART 7: CI/CD WORKFLOWS
|
||||||
|
|
||||||
|
## Generated workflow (`gen-dockerfile actions`)
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
- **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
|
||||||
|
|
||||||
|
## Legacy workflow (`docker.yaml`)
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PART 8: VERIFICATION & COMMIT
|
||||||
|
|
||||||
|
## Syntax gates
|
||||||
|
|
||||||
|
Every touched script must pass before commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for f in rootfs/usr/local/bin/*; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
case "$(head -1 "$f")" in
|
||||||
|
*bash*) bash -n "$f" || exit 1 ;;
|
||||||
|
*sh*) sh -n "$f" || exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
bash -n rootfs/usr/local/etc/docker/functions/entrypoint.sh
|
||||||
|
|
||||||
|
for f in rootfs/root/docker/setup/0*.sh rootfs/usr/local/etc/docker/init.d/*.sh; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
bash -n "$f" || exit 1
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dead-reference gates
|
||||||
|
|
||||||
|
After any regeneration:
|
||||||
|
|
||||||
|
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` 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.
|
||||||
|
|||||||
@@ -1,3 +1,90 @@
|
|||||||
# What this image is
|
# What this image is
|
||||||
|
|
||||||
A self-contained Docker image for [Ampache](https://ampache.org/), the open-source web-based music streaming server. The image bundles the full application stack in a single Alpine Linux container: Apache 2 as the web server, PHP 8.4 via PHP-FPM for application execution, MariaDB as the relational database, and the Ampache PHP application itself pre-installed under `/usr/local/share/ampache`. On first run, navigating to `http://localhost:80/` lands on the Ampache web installer (`install.php`), which provisions the database and creates the initial admin account. After installation, users can catalog and stream their music libraries, manage users and playlists, and connect media players via Ampache's API or DLNA/UPnP capabilities.
|
A self-contained Docker image for [Ampache](https://ampache.org/), the open-source web-based music streaming server. The image bundles the full application stack in a single Alpine Linux container: Apache 2 as the web server, PHP 8.4 via PHP-FPM for application execution, MariaDB as the relational database, and the Ampache PHP application itself pre-installed under `/usr/local/share/ampache`. On first run, navigating to `http://localhost:80/` lands on the Ampache web installer (`install.php`), which provisions the database and creates the initial admin account. After installation, users can catalog and stream their music libraries, manage users and playlists, and connect media players via Ampache's API or DLNA/UPnP capabilities.
|
||||||
|
|
||||||
|
# How this image is built and wired
|
||||||
|
|
||||||
|
## Base image and tooling
|
||||||
|
|
||||||
|
Built from `casjaysdev/alpine:latest` via a multi-stage Dockerfile (build stage → `FROM scratch` final). `tini` is the PID 1 supervisor; `gosu` provides privilege dropping. Package management is handled by `pkmgr` (auto-detects `apk`).
|
||||||
|
|
||||||
|
## Package set
|
||||||
|
|
||||||
|
All packages come from Alpine's `apk` package manager. Key groups:
|
||||||
|
|
||||||
|
- **Apache 2**: `apache2 apache2-ctl apache2-utils apache2-ssl apache2-proxy apache2-http2 apache2-brotli apache2-icons apache2-error`
|
||||||
|
- **PHP 8.4**: `php84 php84-fpm` plus all modules Ampache requires (pdo, pdo_mysql, mysqli, mysqlnd, session, intl, curl, simplexml, xml, xmlreader, xmlwriter, dom, mbstring, iconv, tokenizer, fileinfo, openssl, phar, gd, zip, bz2, gmp, exif, opcache, pecl-redis, ctype)
|
||||||
|
- **MariaDB**: `mariadb mariadb-client mariadb-server-utils`
|
||||||
|
- **Utilities**: `bash tini curl wget unzip tar gzip tzdata ca-certificates pwgen`
|
||||||
|
|
||||||
|
## Build-time setup flow (`rootfs/root/docker/setup/`)
|
||||||
|
|
||||||
|
| Script | Role |
|
||||||
|
|--------|------|
|
||||||
|
| `00-init.sh` | Clears `template-files/{data,config,defaults}` staging dirs |
|
||||||
|
| `01-system.sh` | Stub (Alpine repos are already configured by the base image) |
|
||||||
|
| `02-packages.sh` | Stub (no post-install compile steps; prebuilt Ampache zip needs no composer/npm) |
|
||||||
|
| `03-files.sh` | Auto-installs `rootfs/tmp/etc/*` → `/etc/*` and stages copies under `template-files/config/` |
|
||||||
|
| `04-users.sh` | Defensive creation of `apache:apache` and `mysql:mysql` system accounts |
|
||||||
|
| `05-custom.sh` | **Core setup**: wipes distro defaults under `/etc/{apache2,php84,my.cnf.d}/*`, copies our optimized configs from `/tmp/etc/`, then downloads and unpacks the Ampache prebuilt zip to `/usr/local/share/ampache/` |
|
||||||
|
| `06-post.sh` | Stub |
|
||||||
|
| `07-cleanup.sh` | Stub |
|
||||||
|
|
||||||
|
## Config wipe-and-replace
|
||||||
|
|
||||||
|
`05-custom.sh` performs the canonical wipe-and-replace for all three config trees:
|
||||||
|
|
||||||
|
1. Preserve `apache2/mime.types` and `apache2/magic` (not shipped in our `rootfs/tmp/etc/apache2/`)
|
||||||
|
2. `rm -Rf /etc/{apache2,php84,my.cnf.d}/*`
|
||||||
|
3. `cp -Rf /tmp/etc/{apache2,php84,my.cnf.d}/. /etc/{apache2,php84,my.cnf.d}/`
|
||||||
|
4. Copy resulting `/etc/` trees to `template-files/config/` for runtime seeding
|
||||||
|
|
||||||
|
## Runtime init.d scripts
|
||||||
|
|
||||||
|
Two init.d scripts run in numeric order:
|
||||||
|
|
||||||
|
### `09-mariadb.sh`
|
||||||
|
- `SERVICE_NAME="mariadb"`, `EXEC_CMD_BIN='mariadbd'`
|
||||||
|
- Runs as `mysql:mysql`; datadir `/data/db/mariadb`; socket `/run/mysqld/mysqld.sock`
|
||||||
|
- `IS_DATABASE_SERVICE="yes"`, `DATABASE_SERVICE_TYPE="mariadb"`
|
||||||
|
- `__run_pre_execute_checks`: initializes the datadir with `mariadb-install-db` if `ibdata1` is missing
|
||||||
|
- `__post_execute`: waits for the socket, then creates the `ampache` database, `ampache` user (random password), grants privileges, and sets the root password
|
||||||
|
|
||||||
|
### `99-ampache.sh`
|
||||||
|
- `SERVICE_NAME="ampache"`, `EXEC_CMD_BIN='/usr/local/etc/docker/bin/start-ampache'`
|
||||||
|
- `IS_WEB_SERVER="yes"`, `USES_DATABASE_SERVICE="yes"`, `DATABASE_SERVICE_TYPE="mariadb"`
|
||||||
|
- `SERVICE_USER="apache"`, `SERVICE_GROUP="apache"`
|
||||||
|
- `WWW_ROOT_DIR="/usr/local/share/ampache/public"`, `ETC_DIR="/etc/apache2"`, `CONF_DIR="/config/apache2"`
|
||||||
|
- `ADDITIONAL_CONFIG_DIRS="/config/php84 /config/ampache"`
|
||||||
|
- `__execute_prerun`: creates runtime dirs (`/run/apache2 /run/php-fpm /tmp/php-sessions /data/logs/{apache2,php-fpm}`), chowns them to `apache:apache`; symlinks `ampache.cfg.php` between `/usr/local/share/ampache/config/` and `/config/ampache/` once install.php writes it
|
||||||
|
- `__run_pre_execute_checks`: waits up to 30 s for the MariaDB socket; validates `httpd -t` apache config syntax
|
||||||
|
- `__update_conf_files`: replaces the `REPLACE_TZ` token in `/etc/php84/php.ini` with `$TZ`
|
||||||
|
- `__post_execute`: mirrors `ampache.cfg.php` out to `/config/ampache/` and creates the symlink if install.php has written it
|
||||||
|
|
||||||
|
### `start-ampache` wrapper
|
||||||
|
`EXEC_CMD_BIN` points to `/usr/local/etc/docker/bin/start-ampache`, which:
|
||||||
|
1. Starts `php-fpm84` in the background (waits up to 10 s for `/run/php-fpm/php-fpm.sock`)
|
||||||
|
2. Exec's `httpd -D FOREGROUND -f /etc/apache2/httpd.conf` (becomes the process the framework monitors)
|
||||||
|
|
||||||
|
## Config paths
|
||||||
|
|
||||||
|
| Component | Config file | User-editable at |
|
||||||
|
|-----------|-------------|-----------------|
|
||||||
|
| Apache main | `/etc/apache2/httpd.conf` | `/config/apache2/httpd.conf` |
|
||||||
|
| Apache ampache vhost | `/etc/apache2/conf.d/ampache.conf` | `/config/apache2/conf.d/ampache.conf` |
|
||||||
|
| Apache MPM tuning | `/etc/apache2/conf.d/mpm.conf` | `/config/apache2/conf.d/mpm.conf` |
|
||||||
|
| User vhosts | `IncludeOptional /config/apache2/vhosts.d/*.conf` | `/config/apache2/vhosts.d/` |
|
||||||
|
| PHP runtime | `/etc/php84/php.ini` | `/config/php84/php.ini` |
|
||||||
|
| PHP-FPM global | `/etc/php84/php-fpm.conf` | `/config/php84/php-fpm.conf` |
|
||||||
|
| PHP-FPM pool | `/etc/php84/php-fpm.d/www.conf` | `/config/php84/php-fpm.d/www.conf` |
|
||||||
|
| MariaDB server | `/etc/my.cnf.d/mariadb-server.cnf` | `/config/my.cnf.d/mariadb-server.cnf` |
|
||||||
|
| Ampache app config | `/usr/local/share/ampache/config/ampache.cfg.php` | `/config/ampache/ampache.cfg.php` (symlinked) |
|
||||||
|
|
||||||
|
## Volume layout
|
||||||
|
|
||||||
|
- `/config` — all user-editable configs (seeded on first run from `template-files/config/`)
|
||||||
|
- `/data` — MariaDB datadir (`/data/db/mariadb`), logs (`/data/logs/`), media library if mounted
|
||||||
|
|
||||||
|
## Port
|
||||||
|
|
||||||
|
- `80` (HTTP); `443` available if SSL is enabled via `/config/enable/ssl`
|
||||||
|
|||||||
Reference in New Issue
Block a user