📝 Update project spec and documentation 📝

- AI.md
- LICENSE.md
- README.md
This commit is contained in:
casjay
2026-08-31 20:26:31 -04:00
commit d71564dd3e
3 changed files with 942 additions and 0 deletions
+785
View File
@@ -0,0 +1,785 @@
# CasjaysDev Docker Base Image Specification (dockersrc)
**Name**: {name}
**About this file:** This is the complete, authoritative specification for a CasjaysDev
Docker **base image** repository (`dockersrc/{name}`). It is a master template — copied
into a base 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. This file defines the standards that procedure enforces.
---
# 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 |
| 9 | Examples from real repos |
---
# PART 0: CRITICAL RULES
## Org mapping
| System | Org | Example |
|--------|-----|---------|
| GitHub (source) | `dockersrc` | `https://github.com/dockersrc/{name}` |
| Docker Hub (push) | `casjaysdev` | `casjaysdev/{name}` |
`dockersrc` repos are **OS bases and toolchains** (alpine, debian, ubuntu, almalinux,
archlinux, web, xorg, go, rust, android). Application images live in the separate
`casjaysdevdocker` org and pull FROM these images — see the apps specification
(`CASJAYSDEVDOCKER.md`).
The Docker Hub push org is always `casjaysdev` regardless of where the repo is checked out.
## 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, or a `05-custom.sh` with real content.
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/casjaysdev/{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/dockersrc/{name}`.
7. **One variant, one file set** — every published version tag has its own
`Dockerfile.{ver}`, `.env.scripts.{ver}`, and `.gitea/workflows/build.{ver}.yml`.
8. **Only `root/`, `tmp/`, and `usr/` may exist at `rootfs/` top level** (PART 1).
9. **Maintenance runs through the `dockersrc-bootstrap` agent** — do not improvise the
update procedure from memory.
---
# PART 1: REPOSITORY MODEL & STRUCTURE
## What a base image repo is
A `dockersrc/{name}` repo builds one image family from upstream official distro images
(never from `casjaysdev/*` — base repos ARE the `casjaysdev/*` images). OS repos publish
one variant per supported release; toolchain repos (go, rust, android) publish `latest`
plus whatever the toolchain needs.
## Standard tree
```
{name}/
├── AI.md # This specification (permanent)
├── Dockerfile # [generated] latest/default variant
├── Dockerfile.{ver} # [generated] one per version variant (OS repos)
├── .dockerignore # [generated]
├── .env.scripts # [generated] build config for the default variant
├── .env.scripts.{ver} # [generated] one per version variant
├── .gitattributes # [generated]
├── .gitea/workflows/
│ ├── build.yml # [generated] gen-dockerfile actions — default variant
│ └── build.{ver}.yml # [generated] one per version variant
├── .gitignore # [generated]
├── LICENSE.md # License (WTFPL)
├── README.md # [generated] standard layout (PART 6)
└── rootfs/ # Container filesystem overlay
├── root/docker/setup/ # [generated*] build-time setup scripts 0007
├── tmp/ # staged files installed at build time (optional)
└── usr/local/
├── bin/ # [generated*] entrypoint.sh, pkmgr, symlink, copy,
│ # healthcheck + [hand-crafted] repo-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) — those follow the
hand-crafted rules in PART 5.
`[hand-crafted]` — never overwritten by the template system.
## 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.
## Variant detection
A repo is a **base** repo when `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
| 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 (multi-arch note below) |
| `web.template` | `systemd.template` | `/sbin/init` | Debian |
| `xorg.template` | `systemd.template` | `/sbin/init` | Debian |
## 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
`EXPOSE`/`HEALTHCHECK` are the same in both. Neither declares `VOLUME``dockersrc`
images are toolchain/distro base images, not persistent services, so there is no
`/config`/`/data` (or any other) mount point to reserve (see "Volumes" below).
## 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 `dockersrc` repo pushing to Docker Hub:
| Label | Value |
|-------|-------|
| `url` | `https://hub.docker.com/r/casjaysdev/{name}` — browsable Hub page; `gen-dockerfile` derives it from the registry host (`docker.io``hub.docker.com/r/`) |
| `source` | `https://github.com/dockersrc/{name}` |
| `documentation` | `https://github.com/dockersrc/{name}` |
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)
Base repos always pull upstream — a base image never builds FROM itself. Override by
exporting `GEN_DOCKERFILE_APP_DIR` before calling `gen-dockerfile`.
## Arch Linux multi-arch (`archlinux.template`)
When building the base image (`GEN_DOCKERFILE_APP_DIR != "casjaysdevdocker"`), the
template emits a three-stage FROM for `linux/amd64` + `linux/arm64`:
```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
```
App repos pull `casjaysdev/archlinux`, a multi-arch manifest, so a single
`FROM ${PULL_URL}:${DISTRO_VERSION} AS build` suffices there.
## `web.template` packages
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
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
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 every `Dockerfile`/`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. |
| `--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`
(`build.{ver}.yml` for versioned tags) from the existing `Dockerfile` (PART 7).
Base repos update `Dockerfile` AND every `Dockerfile.*` variant on `--update`; the
matching `.env.scripts.{ver}` files carry per-variant values.
## `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. Base
repos carry one per variant (`.env.scripts` + `.env.scripts.{ver}`). It is a pure
`KEY="value"` file — no logic.
## Variables
| Variable | Purpose |
|----------|---------|
| `ENV_DOCKERFILE` | Dockerfile the variant builds (`Dockerfile` or `Dockerfile.{ver}`) |
| `ENV_REGISTRY_REPO` | Image name in the registry (`{name}`) |
| `ENV_REGISTRY_ORG` | Registry namespace — `casjaysdev` for base 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` (`casjaysdev/{name}`) |
| `ENV_ADD_IMAGE_PUSH` | Extra push destinations |
| `ENV_GIT_REPO_URL` | Full Git repo URL — `https://github.com/dockersrc/{name}`; feeds the `source`/`documentation` labels, so a wrong value here regresses labels on regeneration |
| `ENV_USE_TEMPLATE` | Template name (`alpine`, `debian`, …) |
| `ENV_PULL_URL` | Base image to pull FROM |
| `ENV_DISTRO_TAG` | Tag for the pull image |
| `ENV_IMAGE_TAG` | Default image tag (`latest`, or the variant version) |
| `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 (empty for pure base images) |
| `EXPOSE_PORTS` | Additional exposed ports |
| `LANG_VERSION` | Language runtime version (toolchain repos) |
| `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 |
|--------|------|
| `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/toolchain-specific install logic |
| `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. Any repo whose
`05-custom.sh` has a real body (e.g. a toolchain repo's install logic) owns that content —
it 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 repos have one
script per daemon (`01-named.sh`, `02-nginx.sh`, `03-php-fpm.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
```
## Volumes
`dockersrc/{name}` images never declare `VOLUME` — they are toolchain/distro base
images (OS repos, `go`/`rust`/`android` toolchains), not long-running services with
state to persist. No Dockerfile in this repo family ships a `VOLUME` instruction,
including for directories that look stateful (module caches, package caches,
GOPATH-style tool directories) — those are build-time/runtime cache locations
owned by the image, not user data. `/config` and `/data` persistent-volume
mount points belong to `casjaysdevdocker/{name}` app repos only (see
`CASJAYSDEVDOCKER.md` → "Volumes"), which run an actual service `FROM
casjaysdev/{name}`.
---
# PART 6: README.md STANDARD LAYOUT
Base image layout (`dockersrc/{name}``casjaysdev/{name}`). Substitute `{name}`;
this repo family has no `-p` port mappings unless `SERVICE_PORT` is set in
`.env.scripts` — omit all port sections when it is empty.
````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) ⛵
````
---
# PART 7: CI/CD WORKFLOWS
## Generated workflow (`gen-dockerfile actions`)
`gen-dockerfile actions` writes `.gitea/workflows/build.yml` from the current
`Dockerfile`; versioned variants get `build.{ver}.yml` (named `Build and Push {ver}`, no
schedule trigger, fixed version tag only). 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` (or the fixed variant version) 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`).
## 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.
## Project Memory (.claude/memory/)
Durable, repo-specific knowledge discovered during work on this image — a template
quirk, a base-image gotcha, a decision on why something deviates from the generated
default — belongs in `.claude/memory/`, not only in a commit message or chat. Committed
to the repo, not gitignored. One markdown file per topic, YAML frontmatter (`name`,
`description`, `type: project`), indexed by `.claude/memory/MEMORY.md`, read on demand.
Same credential-masking rule as everywhere else — never store secrets. `~/.claude/**`
(global) stays read-only, deployed only via `claudemgr/config`'s `install.sh`;
`.claude/memory/` here is read/write in this repo directly.
---
# PART 9: EXAMPLES FROM REAL REPOS
Real excerpts from live `dockersrc` repos, showing how the conventions look in
practice. Use these as reference patterns — do not copy them verbatim into other
repos; adapt names, paths, and versions.
## 9.1 — Non-stub `05-custom.sh` (toolchain install, from `dockersrc/go`)
A base/toolchain repo owns its `05-custom.sh` when it installs anything beyond
packages. The go repo (221 lines) shows the full pattern: env block, helper
functions, download with retries, verification, cleanup. Key excerpt:
```bash
# Set bash options
set -eo pipefail
[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS
# Force IPv4 for all curl calls in this script — the base image IPv6 routing
# intercepts *.github.com and presents a cert for casjay.in, causing SAN mismatch
printf -- '-4\n' > /root/.curlrc
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Set env variables
VERSION="202607271500-git"
CUSTOM_EXITCODE=0
# Installation root for the Go distribution (not GOPATH)
CUSTOM_GOINSTALL_DIR="/usr/local/go"
# GOPATH: module cache, pkg index, user-installed binaries — build/runtime cache
# owned by the image, not a persistent VOLUME (dockersrc images never declare one)
CUSTOM_GOPATH_DIR="/usr/local/share/go"
# Baked-in tool binaries land here so they are on the default PATH
CUSTOM_GOBIN_DIR="/usr/local/bin"
# Throwaway build cache used only during this image build layer
CUSTOM_GOCACHE_BUILD="/tmp/go-build-cache"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Helpers
# Return the latest release tag from GitHub; retries up to 3 times on transient errors
# (rate-limit 403s are common in parallel multi-platform builds without a token).
# Set GITHUB_TOKEN to raise the authenticated rate limit (5000 req/hr vs 60 req/hr).
__gh_latest() {
local repo="$1"
local filter="${2:-.tag_name}"
...
}
```
Patterns to note:
- Comments sit ABOVE the line they describe, never inline
- All script-local variables carry a `CUSTOM_` prefix so a dead-variable audit can
distinguish them from template-provided vars
- Version resolution queries the upstream API with retries and a pinned fallback —
never a hardcoded latest version
- Everything downloaded is verified (checksum or successful `--version` run) before
the script exits 0
## 9.2 — Stub `05-custom.sh` (from `dockersrc/alpine`)
Pure OS base repos keep the generated 43-line stub untouched — header, bash
options, `exitCode=0`, exit. A stub is template-owned: `gen-dockerfile` may
overwrite it freely. The moment a repo adds real logic to `05-custom.sh` it becomes
repo-owned and must never be regenerated (PART 2 ownership rules).
+13
View File
@@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2026 casjay <git-admin@casjaysdev.pro>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
1. You just DO WHAT THE FUCK YOU WANT TO.
+144
View File
@@ -0,0 +1,144 @@
## 👋 Welcome to android 🚀
Debian-based Android build toolchain image. It is the shared build image
(`casjaysdev/android`) consumed by every CasjaysDev Android app project — see
[APPLICATION.md](https://github.com/claudemgr/android) for the project template
that builds against it. Nothing app-specific (no baked `build.gradle`
dependencies, no project source) lives in this image; only the toolchain that
is identical across projects does.
### What's included
| Component | Version | Notes |
|---|---|---|
| Base OS | `casjaysdev/debian` | via `ENV_PULL_URL` |
| JDK | 17 (Temurin) | installed from the Adoptium apt repo, `JAVA_HOME=/opt/jdk-17` |
| Android SDK cmdline-tools | `15859902` | `$ANDROID_SDK_ROOT/cmdline-tools/latest` |
| Android platforms | `android-34`, `android-35` | via `sdkmanager` |
| Android build-tools | `35.0.0` | via `sdkmanager` |
| Android NDK | `27.3.13750724` (r27d LTS) | via `sdkmanager`, for native/JNI builds |
| CMake | `3.31.6` | via `sdkmanager`, for native/JNI builds |
| Gradle | `8.14.5` | wrapper distribution pre-warmed into `$GRADLE_USER_HOME` so `./gradlew` never re-downloads it |
| GitHub CLI | `gh` | from the official apt repo, for release/CI steps that shell out to `gh` |
`ANDROID_HOME` / `ANDROID_SDK_ROOT` default to `/opt/android-sdk`.
`GRADLE_USER_HOME` defaults to `/root/.gradle`. All of the above are
overridable at build time via `--build-arg` (see `Dockerfile` for the full
`ARG` list and defaults: `ANDROID_CMDLINE_TOOLS_VERSION`,
`ANDROID_PLATFORM_VERSIONS`, `ANDROID_BUILD_TOOLS_VERSION`,
`ANDROID_NDK_VERSION`, `ANDROID_CMAKE_VERSION`, `GRADLE_VERSION`).
### How it's built
- `PACK_LIST` (wired from `ENV_PACKAGES` in `.env.scripts`) installs
`wget unzip git curl gnupg` from the base distro's own repos — the minimum
needed to fetch and unpack everything else. `ca-certificates` is already
installed by the base template before `PACK_LIST` runs.
- `rootfs/root/docker/setup/02-packages.sh` adds the Adoptium Temurin and
GitHub CLI apt repositories (both need a keyring + `sources.list.d` entry
that `pkmgr install` alone can't set up) and then installs
`temurin-17-jdk` and `gh` through `pkmgr install`, same as any other
package-manager install.
- `rootfs/root/docker/setup/05-custom.sh` downloads and unpacks the Android
SDK cmdline-tools, accepts the SDK licenses, installs the pinned
platforms/build-tools/cmake/NDK via `sdkmanager`, and pre-warms the Gradle
wrapper distribution zip so the wrapper never fetches it at build time.
- No project `build.gradle`/dependency pre-warm is baked in here — that step
is project-specific and belongs in each app's own CI, not in the shared
base image.
## 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 android
```
## Install and run container
```shell
mkdir -p "/var/lib/srv/root/docker/casjaysdev/android/latest"
git clone "https://github.com/dockermgr/android" "$HOME/.local/share/CasjaysDev/dockermgr/android"
cp -Rfva "$HOME/.local/share/CasjaysDev/dockermgr/android/rootfs/." "/var/lib/srv/root/docker/casjaysdev/android/latest/"
docker run -d \
--restart always \
--privileged \
--name casjaysdev-android-latest \
--hostname android \
-e TZ=${TIMEZONE:-America/New_York} \
-v "/var/lib/srv/root/docker/casjaysdev/android/latest/data:/data:z" \
-v "/var/lib/srv/root/docker/casjaysdev/android/latest/config:/config:z" \
casjaysdev/android:latest
```
## Build an Android app with this image
This image is a build toolchain, not a long-running service — run it once per
build and let it exit:
```shell
docker run --rm --name {project_name}-$(tr -dc 'a-z0-9' </dev/urandom | head -c8) \
--memory=4g --cpus=2 \
-v "$PWD":/workspace -w /workspace \
-e GRADLE_USER_HOME=/workspace/.gradle \
casjaysdev/android:latest ./gradlew assembleDebug
```
- Source tree → `/workspace`; Gradle cache → `GRADLE_USER_HOME=/workspace/.gradle`
(project-scoped, safe for concurrent builds across app repos).
- Never volume-mount over `/opt/android-sdk` — it overlays the SDK baked into
the image. `ANDROID_HOME`/`ANDROID_SDK_ROOT` are preset and should be left
alone unless the consuming project intentionally overrides the SDK.
- `cmake`/`ndk` versions referenced in `app/build.gradle` must match the
versions baked into this image (see the table above) — the image never
lazily downloads SDK components mid-build, so a mismatch fails the build
instead of silently fetching a different version.
- `gh` is on `PATH` for CI steps (e.g. `gh release delete`, `gh release
upload`) that run inside this image.
## via docker-compose
```yaml
version: "2"
services:
ProjectName:
image: casjaysdev/android
container_name: casjaysdev-android-latest
environment:
- TZ=America/New_York
- HOSTNAME=android
volumes:
- "/var/lib/srv/root/docker/casjaysdev/android/latest/data:/data:z"
- "/var/lib/srv/root/docker/casjaysdev/android/latest/config:/config:z"
restart: always
```
## Get source files
```shell
dockermgr download src os android
```
## Build container
```shell
git clone "https://github.com/dockersrc/android" "$HOME/Projects/github/dockersrc/android"
cd "$HOME/Projects/github/dockersrc/android" && buildx all
```
To override a pinned version at build time:
```shell
docker build --build-arg ANDROID_BUILD_TOOLS_VERSION=35.0.0 -t casjaysdev/android:latest .
```
## Authors
🤖 casjay: [Github](https://github.com/casjay) 🤖
⛵ casjaysdev: [Github](https://github.com/dockersrc) [Docker](https://hub.docker.com/u/casjaysdev) ⛵