commit c8f7ff8d22d4f4bacd89ccb27f585ddc63c9db70 Author: casjay Date: Thu Sep 3 21:08:19 2026 -0400 πŸ¦ˆπŸ πŸœβ— Initial Commit β—πŸœπŸ¦ˆπŸ  diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 0000000..d466a1b --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"a4c0164e-35a4-4118-a2fb-1ec39431392f","pid":618193,"procStart":"112807435","acquiredAt":1788427111850} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..71c00b7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Files to ignore +# Tell docker to ignore .gitkeep +.gitkeep +# Tell docker to ignore .gitignore +.gitignore +# Tell docker to ignore node_modules/** +node_modules/** +# Tell docker to ignore .node_modules/** +.node_modules/** +# Tell docker to ignore **/.gitkeep +**/.gitkeep +# Tell docker to ignore **/.gitignore +**/.gitignore +# Tell docker to ignore **/node_modules/** +**/node_modules/** +# Tell docker to ignore **/.node_modules/** +**/.node_modules/** diff --git a/.env.scripts b/.env.scripts new file mode 100644 index 0000000..83419d4 --- /dev/null +++ b/.env.scripts @@ -0,0 +1,80 @@ +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202606051822-git +# @@Author : CasjaysDev +# @@Contact : CasjaysDev +# @@License : WTFPL +# @@Copyright : Copyright 2026 CasjaysDev +# @@Created : Fri Jun 5 06:22:56 PM EDT 2026 +# @@File : .env.scripts +# @@Description : Variables for gen-dockerfile and buildx scripts +# @@Changelog : newScript +# @@TODO : Refactor code +# @@Other : N/A +# @@Resource : N/A +# @@Terminal App : yes +# @@sudo/root : yes +# @@Template : templates/dockerfiles/dotenv.template +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# entrypoint Settings +DOCKER_ENTYPOINT_PORTS_WEB="${DOCKER_ENTYPOINT_PORTS_WEB}" +DOCKER_ENTYPOINT_PORTS_SRV="${DOCKER_ENTYPOINT_PORTS_SRV}" +DOCKER_ENTYPOINT_HEALTH_APPS="$DOCKER_ENTYPOINT_HEALTH_APPS" +DOCKER_ENTYPOINT_HEALTH_ENDPOINTS="$DOCKER_ENTYPOINT_HEALTH_ENDPOINTS" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Dockerfile info +ENV_DOCKERFILE="Dockerfile" +# ENV_REGISTRY_REPO: Registry repository/image name +ENV_REGISTRY_REPO="forgejo" +ENV_USE_TEMPLATE="alpine" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Maintainer info +ENV_REGISTRY_ORG="casjaysdevdocker" +ENV_VENDOR="CasjaysDev" +ENV_AUTHOR="CasjaysDev" +ENV_MAINTAINER="CasjaysDev " +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Repository URLs (Full URLs) +# ENV_GIT_REPO_URL: Complete Git repository URL for source code +ENV_GIT_REPO_URL="https://github.com/casjaysdevdocker/forgejo" +# ENV_REGISTRY_URL: Registry provider base URL (for example https://docker.io) +ENV_REGISTRY_URL="https://docker.io" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Push Configuration +# ENV_REGISTRY_PUSH: Complete push destination derived from registry/org/repo +ENV_REGISTRY_PUSH="casjaysdevdocker/forgejo" +# ENV_IMAGE_TAG: Default tag for the image +ENV_IMAGE_TAG="latest" +# ENV_ADD_TAGS: Additional tags, comma-separated (USE_DATE = auto date tag) +ENV_ADD_TAGS="USE_DATE" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional push destinations (if needed) +ENV_ADD_IMAGE_PUSH="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Pull Configuration +# ENV_PULL_URL: Source image to pull from (base image) +ENV_PULL_URL="casjaysdev/alpine" +# ENV_DISTRO_TAG: Tag for the pull source image +ENV_DISTRO_TAG="${IMAGE_VERSION}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Env +SERVICE_PORT="80" +EXPOSE_PORTS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# IF using a lanuage such as go, php, rust, ruby, etc set the version here. +LANG_VERSION="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Versions +PHP_VERSION="system" +NODE_VERSION="system" +NODE_MANAGER="system" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Default directories +WWW_ROOT_DIR="/usr/local/share/httpd/default" +# - - - - - - - - - - - - - - - - - - - - - - - - - +ENV_PACKAGES="cgroup-tools fuse-overlayfs btrfs-progs xfsprogs e2fsprogs e2fsprogs-extra zfs git ip6tables iptables openssl pigz shadow-uidmap xz docker openssh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..07f3b95 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,86 @@ +# Template generated on Sat Nov 29 11:57:12 AM EST 2025 from https://github.com/alexkaratarakis/gitattributes" +# Common settings that generally should always be used with your language specific settings +# Auto detect text files and perform LF normalization +* text=auto +# The above will handle all files NOT found below +# Documents +*.bibtex text diff=bibtex +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain +*.md text diff=markdown +*.mdx text diff=markdown +*.tex text diff=tex +*.adoc text +*.textile text +*.mustache text +*.csv text eol=crlf +*.tab text +*.tsv text +*.txt text +*.sql text +*.epub diff=astextplain +# Graphics +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.tif binary +*.tiff binary +*.ico binary +# SVG treated as text by default. +*.svg text +# If you want to treat it as binary, +# use the following line instead. +# *.svg binary +*.eps binary +# Scripts +*.bash text eol=lf +*.fish text eol=lf +*.ksh text eol=lf +*.sh text eol=lf +*.zsh text eol=lf +# These are explicitly windows files and should use crlf +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf +# Serialisation +*.json text +*.toml text +*.xml text +*.yaml text +*.yml text +# Archives +*.7z binary +*.bz binary +*.bz2 binary +*.bzip2 binary +*.gz binary +*.lz binary +*.lzma binary +*.rar binary +*.tar binary +*.taz binary +*.tbz binary +*.tbz2 binary +*.tgz binary +*.tlz binary +*.txz binary +*.xz binary +*.Z binary +*.zip binary +*.zst binary +# Text files where line endings should be preserved +*.patch -text +# Exclude files from exporting +.gitattributes export-ignore +.gitignore export-ignore +.gitkeep export-ignore + diff --git a/.gitea/workflows/docker.yaml b/.gitea/workflows/docker.yaml new file mode 100644 index 0000000..71ed7ea --- /dev/null +++ b/.gitea/workflows/docker.yaml @@ -0,0 +1,68 @@ +name: gitea + +on: push + +jobs: + release-gitea: + runs-on: act_runner + container: + image: catthehacker/ubuntu:act-latest + env: + RUNNER_TOOL_CACHE: /toolcache + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Get Meta + id: meta + run: | + repo_version="$(git describe --tags --always)" + repo_version="${repo_version#v}" + docker_org="${GITHUB_REPOSITORY%%/*}" + repo_name="${GITHUB_REPOSITORY#*/}" + repo_name="${repo_name#docker-}" + docker_tag="${DOCKER_TAG:-latest}" + docker_hub="${DOCKER_HUB:-docker.io}" + printf 'DATE_TAG=%s\n' "$(date +'%y%m')" >> "$GITHUB_OUTPUT" + printf 'REPO_VERSION=%s\n' "$repo_version" >> "$GITHUB_OUTPUT" + printf 'DOCKER_ORG=%s\n' "$docker_org" >> "$GITHUB_OUTPUT" + printf 'DOCKER_TAG=%s\n' "$docker_tag" >> "$GITHUB_OUTPUT" + printf 'DOCKER_HUB=%s\n' "$docker_hub" >> "$GITHUB_OUTPUT" + printf 'REPO_NAME=%s\n' "$repo_name" >> "$GITHUB_OUTPUT" + printf '%s\n' "$docker_hub/$docker_org/$repo_name:$docker_tag" + + - name: Set up Docker BuildX + uses: docker/setup-buildx-action@v2 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + password: ${{ secrets.DOCKER_TOKEN }} + username: ${{ secrets.DOCKER_USERNAME }} + registry: ${{ steps.meta.outputs.DOCKER_HUB }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + file: ./Dockerfile + platforms: | + linux/amd64 + linux/arm64 + push: true + build-args: | + IMAGE_NAME=${{ steps.meta.outputs.REPO_NAME }} + BUILD_DATE=$(date -u +'%Y%m%d%H%M') + BUILD_VERSION=$(date -u +'%Y%m%d%H%M') + GIT_COMMIT=${{ github.sha }} + TIMEZONE=America/New_York + LANGUAGE=en_US.UTF-8 + LICENSE=WTFPL + TZ=America/New_York + tags: | + ${{ steps.meta.outputs.DOCKER_HUB }}/${{ steps.meta.outputs.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.DATE_TAG }} + ${{ steps.meta.outputs.DOCKER_HUB }}/${{ steps.meta.outputs.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.DOCKER_TAG }} + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bbc636 --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# gitignore created on 05/05/26 at 14:38 +# Disable reminder in prompt +ignoredirmessage + +# ignore .build_failed files +**/.build_failed* + +# OS generated files +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store? +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# misc +!*/README* +!inc/main.bash + +# Windows shortcuts +*.lnk + +# ignore commit message +**/.gitcommit + +# ignore .build_failed files +**/.build_failed* + +# ignore .bak files +**/*.bak + +# ignore .no_push files +**/.no_push + +# ignore .no_git files +**/.no_git + +# ignore .installed files +**/.installed + +# ignore work in progress files +**/*.rewrite.sh +**/*.refactor.sh + diff --git a/AI.md b/AI.md new file mode 100644 index 0000000..a8bab1c --- /dev/null +++ b/AI.md @@ -0,0 +1,829 @@ +# CasjaysDev Docker Application Image Specification (casjaysdevdocker) + +**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. + +--- + +# 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) | `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/`** β€” 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/:latest` (pre-built, multi-arch) +- `dockersrc/*` and all other orgs β†’ `FROM :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/.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. + +--- + +# 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/`) | +| `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 | +|--------|------| +| `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 +``` + +## 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. + +## 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 `casjaysdevdocker` 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 β€” App-install `05-custom.sh` (from `casjaysdevdocker/gitea`) + +App repos always own a non-stub `05-custom.sh` β€” it is where the application binary +is installed. The gitea repo (100 lines) shows the canonical version-resolution and +download pattern: + +```bash +# Set bash options +set -o pipefail +[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set env variables +exitCode=0 +apk add --no-cache ca-certificates 2>/dev/null || true +update-ca-certificates 2>/dev/null || true +GITEA_VERSION="${GITEA_VERSION:-latest}" +GITEA_BIN_FILE="/usr/local/bin/gitea" +ARCH="$(uname -m | tr '[:upper]' '[:lower]')" +case "$ARCH" in x86_64) ARCH="amd64" ;; aarch64) ARCH="arm64" ;; *) echo "$ARCH is not supported by this script" >&2 && exit 1 ;; esac +# Pinned fallback used when gitea.com is unreachable from the build host +ACT_RUNNER_FALLBACK_VERSION="${ACT_RUNNER_FALLBACK_VERSION:-v1.0.8}" +# Fetch latest version tag from the renamed repo β€” 30s connect timeout +ACT_VERSIONS="$(curl -q --connect-timeout 30 --max-time 45 -LSsf \ + 'https://gitea.com/api/v1/repos/gitea/runner/releases' \ + -H 'accept: application/json' 2>/dev/null | jq -r '.[].tag_name' | sort -Vr | head -n1)" +# Fall back to pinned version if API is unreachable +[ -z "$ACT_VERSIONS" ] && ACT_VERSIONS="$ACT_RUNNER_FALLBACK_VERSION" +if [ -z "$GITEA_VERSION" ] || [ "$GITEA_VERSION" = "latest" ]; then + _latest_url="$(curl -4sfL -o /dev/null -w '%{url_effective}' https://github.com/go-gitea/gitea/releases/latest 2>/dev/null)" + GITEA_VERSION="$(printf '%s\n' "$_latest_url" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" +fi +GITEA_URL="https://github.com/go-gitea/gitea/releases/download/v${GITEA_VERSION}/gitea-${GITEA_VERSION}-linux-${ARCH}" +``` + +Patterns to note: + +- Version defaults to `latest` but resolves to a concrete number at build time via + the upstream API, with a pinned fallback for offline/rate-limited builds +- Arch mapping (`x86_64β†’amd64`, `aarch64β†’arm64`) with a hard error for anything else +- Bounded curl (`--connect-timeout`/`--max-time`), never open-ended +- Downloads land in `/usr/local/bin`, are chmodded, and verified before exit 0 + +## 9.2 β€” init.d service script config block (from `casjaysdevdocker/gitea` `08-gitea.sh`) + +One init.d script per service, generated by `gen-script` and then configured by +editing ONLY the variable block β€” the function bodies below it are template code. +The configured block from `08-gitea.sh`: + +```bash +SERVICE_NAME="gitea" +# set data directory +DATA_DIR="/data/gitea" +# set config directory +CONF_DIR="/config/gitea" +# set the containers etc directory +ETC_DIR="/etc/gitea" +# set the temp dir +TMP_DIR="/tmp/gitea" +# set scripts pid dir +RUN_DIR="/run/gitea" +# set log directory +LOG_DIR="/data/logs/gitea" +# Set the working dir +WORK_DIR="/data/gitea" +# port which service is listening on +SERVICE_PORT="80" +# gitea must run as git user, not root +RUNAS_USER="git" +# execute command as another user +SERVICE_USER="git" +# Set the service group +SERVICE_GROUP="git" +# execute command variables - keep single quotes variables will be expanded later +# command to execute +EXEC_CMD_BIN='gitea' +# command arguments +EXEC_CMD_ARGS='web ' +# command arguments +EXEC_CMD_ARGS+='--port $SERVICE_PORT --config $CONF_DIR/app.ini ' +``` + +Patterns to note: + +- Dir vars all derive from `$SERVICE_NAME` (`/data/{svc}`, `/config/{svc}`, + `/run/{svc}`, `/data/logs/{svc}`) +- `EXEC_CMD_BIN`/`EXEC_CMD_ARGS` stay single-quoted β€” template code expands them + later, after ports and paths are finalized +- Multi-word args are built up with `EXEC_CMD_ARGS+=`, one concern per line + +## 9.3 β€” Customizing behavior via `*_local()` hooks (from `08-gitea.sh`) + +Template functions (`__pre_execute`, `__update_conf_files`, …) each end by calling +an optional `*_local()` hook. Repo-specific behavior goes in the hook, never inside +the template function body: + +```bash +# function to run before executing +__pre_execute() { + local exitCode=0 + ... + # allow custom functions + if builtin type -t __pre_execute_local | grep -q 'function'; then __pre_execute_local; fi + return $exitCode +} +``` + +The stubs (`__pre_execute_local() { true; }` etc.) live near the bottom of the +script β€” replace a stub's body to customize; regeneration then only requires +re-applying the variable block and the non-stub hooks. Multi-service apps ship one +script per service with two-digit ordering (`05-dockerd.sh`, `08-gitea.sh`, +`zz-act_runner.sh` for run-last). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3fb0e24 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# gitea + +Read `AI.md` and `IDEA.md` before acting on this project. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4a2472d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,245 @@ +# Docker image for forgejo using the alpine template +ARG IMAGE_NAME="forgejo" +ARG PHP_SERVER="forgejo" +ARG BUILD_DATE="202606051822" +ARG LANGUAGE="en_US.UTF-8" +ARG TIMEZONE="America/New_York" +ARG WWW_ROOT_DIR="/usr/local/share/httpd/default" +ARG PATH="/usr/local/etc/docker/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +ARG USER="root" +ARG SHELL_OPTS="set -e -o pipefail" + +ARG SERVICE_PORT="80" +ARG EXPOSE_PORTS="" +ARG PHP_VERSION="system" +ARG NODE_VERSION="system" +ARG NODE_MANAGER="system" + +ARG IMAGE_REPO="casjaysdevdocker/forgejo" +ARG IMAGE_VERSION="latest" +ARG CONTAINER_VERSION="USE_DATE" + +ARG PULL_URL="casjaysdev/alpine" +ARG DISTRO_VERSION="${IMAGE_VERSION}" +ARG BUILD_VERSION="${BUILD_DATE}" + +FROM tianon/gosu:latest AS gosu +FROM ${PULL_URL}:${DISTRO_VERSION} AS build +ARG TZ +ARG USER +ARG LICENSE +ARG TIMEZONE +ARG LANGUAGE +ARG IMAGE_NAME +ARG BUILD_DATE +ARG SERVICE_PORT +ARG EXPOSE_PORTS +ARG BUILD_VERSION +ARG IMAGE_VERSION +ARG WWW_ROOT_DIR +ARG DISTRO_VERSION +ARG NODE_VERSION +ARG NODE_MANAGER +ARG PHP_VERSION +ARG PHP_SERVER +ARG SHELL_OPTS +ARG PATH + +ARG PACK_LIST="cgroup-tools fuse-overlayfs btrfs-progs xfsprogs e2fsprogs e2fsprogs-extra zfs git ip6tables iptables openssl pigz shadow-uidmap xz docker openssh " + +ENV ENV=~/.profile +ENV SHELL="/bin/sh" +ENV PATH="${PATH}" +ENV TZ="${TIMEZONE}" +ENV TIMEZONE="${TZ}" +ENV LANG="${LANGUAGE}" +ENV TERM="xterm-256color" +ENV HOSTNAME="casjaysdevdocker-forgejo" + +USER ${USER} +WORKDIR /root + +COPY ./rootfs/. / + +RUN set -e; \ + echo "Updating the system and ensuring bash is installed"; \ + pkmgr update;pkmgr install bash ca-certificates; \ + update-ca-certificates + +RUN set -e; \ + echo "Setting up prerequisites"; \ + true + +ENV SHELL="/bin/bash" +SHELL [ "/bin/bash", "-c" ] + +COPY --from=gosu /usr/local/bin/gosu /usr/local/bin/gosu + +RUN echo "Initializing the system"; \ + $SHELL_OPTS; \ + mkdir -p "/root/docker/setup" "/etc/profile.d"; \ + if [ -f "/root/docker/setup/00-init.sh" ];then echo "Running the init script";/root/docker/setup/00-init.sh||{ echo "Failed to execute /root/docker/setup/00-init.sh" >&2 && exit 10; };echo "Done running the init script";fi; \ + echo "" + +RUN echo "Creating and editing system files "; \ + $SHELL_OPTS; \ + [ -f "/root/.profile" ] || touch "/root/.profile"; \ + if [ -f "/root/docker/setup/01-system.sh" ];then echo "Running the system script";/root/docker/setup/01-system.sh||{ echo "Failed to execute /root/docker/setup/01-system.sh" >&2 && exit 10; };echo "Done running the system script";fi; \ + echo "" + +RUN echo "Running pre-package commands"; \ + $SHELL_OPTS; \ + echo "" + +RUN echo "Setting up and installing packages"; \ + $SHELL_OPTS; \ + if [ -n "${PACK_LIST}" ];then echo "Installing packages: $PACK_LIST";echo "${PACK_LIST}" >/root/docker/setup/packages.txt;pkmgr install ${PACK_LIST};fi; \ + echo "" + +RUN echo "Initializing packages before copying files to image"; \ + $SHELL_OPTS; \ + if [ -f "/root/docker/setup/02-packages.sh" ];then echo "Running the packages script";/root/docker/setup/02-packages.sh||{ echo "Failed to execute /root/docker/setup/02-packages.sh" >&2 && exit 10; };echo "Done running the packages script";fi; \ + echo "" + +COPY ./Dockerfile /root/docker/Dockerfile + +RUN echo "Updating system files "; \ + $SHELL_OPTS; \ + echo "$TIMEZONE" >"/etc/timezone"; \ + touch "/etc/profile" "/root/.profile"; \ + echo 'hosts: files dns' >"/etc/nsswitch.conf"; \ + [ "$PHP_VERSION" = "system" ] && PHP_VERSION="php" || true; \ + PHP_BIN="$(command -v ${PHP_VERSION} 2>/dev/null || true)"; \ + set -- /usr/*bin/php*fpm*; [ -e "$1" ] && PHP_FPM="$1" || PHP_FPM=""; \ + pip_bin="$(command -v python3 2>/dev/null || command -v python2 2>/dev/null || command -v python 2>/dev/null || true)"; \ + py_version="$(command $pip_bin --version | sed 's|[pP]ython ||g' | awk -F '.' '{print $1$2}' | grep '[0-9]' || true)"; \ + [ "$py_version" -gt "310" ] && pip_opts="--break-system-packages " || pip_opts=""; \ + [ -f "/usr/share/zoneinfo/${TZ}" ] && ln -sf "/usr/share/zoneinfo/${TZ}" "/etc/localtime" || true; \ + [ -n "$PHP_BIN" ] && [ -z "$(command -v php 2>/dev/null)" ] && ln -sf "$PHP_BIN" "/usr/bin/php" 2>/dev/null || true; \ + [ -n "$PHP_FPM" ] && [ -z "$(command -v php-fpm 2>/dev/null)" ] && ln -sf "$PHP_FPM" "/usr/bin/php-fpm" 2>/dev/null || true; \ + if [ -f "/etc/profile.d/color_prompt.sh.disabled" ]; then mv -f "/etc/profile.d/color_prompt.sh.disabled" "/etc/profile.d/color_prompt.sh";fi ; \ + { [ -f "/etc/bash/bashrc" ] && cp -Rf "/etc/bash/bashrc" "/root/.bashrc"; } || { [ -f "/etc/bashrc" ] && cp -Rf "/etc/bashrc" "/root/.bashrc"; } || { [ -f "/etc/bash.bashrc" ] && cp -Rf "/etc/bash.bashrc" "/root/.bashrc"; } || true; \ + if [ -z "$(command -v "apt-get" 2>/dev/null)" ];then grep -sh -q 'alias quit' "/root/.bashrc" || printf '# Profile\n\n%s\n%s\n%s\n' '. /etc/profile' '. /root/.profile' "alias quit='exit 0 2>/dev/null'" >>"/root/.bashrc"; fi; \ + if [ "$PHP_VERSION" != "system" ] && [ -e "/etc/php" ] && [ -d "/etc/${PHP_VERSION}" ];then rm -Rf "/etc/php";fi; \ + if [ "$PHP_VERSION" != "system" ] && [ -n "${PHP_VERSION}" ] && [ -d "/etc/${PHP_VERSION}" ];then ln -sf "/etc/${PHP_VERSION}" "/etc/php";fi; \ + if [ -f "/root/docker/setup/03-files.sh" ];then echo "Running the files script";/root/docker/setup/03-files.sh||{ echo "Failed to execute /root/docker/setup/03-files.sh" >&2 && exit 10; };echo "Done running the files script";fi; \ + echo "" + +RUN echo "Custom Settings"; \ + $SHELL_OPTS; \ +echo "" + +RUN echo "Setting up users and scripts "; \ + $SHELL_OPTS; \ + if [ -f "/root/docker/setup/04-users.sh" ];then echo "Running the users script";/root/docker/setup/04-users.sh||{ echo "Failed to execute /root/docker/setup/04-users.sh" >&2 && exit 10; };echo "Done running the users script";fi; \ + echo "" + +RUN echo "Running the user init commands"; \ + $SHELL_OPTS; \ + echo "" + +RUN echo "Setting OS Settings "; \ + $SHELL_OPTS; \ + echo "" + +RUN echo "Custom Applications"; \ + $SHELL_OPTS; \ +echo "" + +RUN echo "Running custom commands"; \ + if [ -f "/root/docker/setup/05-custom.sh" ];then echo "Running the custom script";/root/docker/setup/05-custom.sh||{ echo "Failed to execute /root/docker/setup/05-custom.sh" && exit 10; };echo "Done running the custom script";fi; \ + echo "" + +RUN echo "Running final commands before cleanup"; \ + $SHELL_OPTS; \ + if [ -f "/root/docker/setup/06-post.sh" ];then echo "Running the post script";/root/docker/setup/06-post.sh||{ echo "Failed to execute /root/docker/setup/06-post.sh" >&2 && exit 10; };echo "Done running the post script";fi; \ + echo "" + +RUN echo "Deleting unneeded files"; \ + $SHELL_OPTS; \ + pkmgr clean; \ + rm -Rf "/config" "/data" || true; \ + rm -rf /etc/systemd/system/*.wants/* || true; \ + rm -rf /lib/systemd/system/systemd-update-utmp* || true; \ + rm -rf /lib/systemd/system/anaconda.target.wants/* || true; \ + rm -rf /lib/systemd/system/local-fs.target.wants/* || true; \ + rm -rf /lib/systemd/system/multi-user.target.wants/* || true; \ + rm -rf /lib/systemd/system/sockets.target.wants/*udev* || true; \ + rm -rf /lib/systemd/system/sockets.target.wants/*initctl* || true; \ + rm -Rf /usr/share/doc/* /var/tmp/* /var/cache/*/* /root/.cache/* /usr/share/info/* /tmp/* || true; \ + if [ -d "/lib/systemd/system/sysinit.target.wants" ];then cd "/lib/systemd/system/sysinit.target.wants" && for want_file in *; do [ "$want_file" = "systemd-tmpfiles-setup" ] || rm -f "$want_file"; done; fi; \ + if [ -f "/root/docker/setup/07-cleanup.sh" ];then echo "Running the cleanup script";/root/docker/setup/07-cleanup.sh||{ echo "Failed to execute /root/docker/setup/07-cleanup.sh" >&2 && exit 10; };echo "Done running the cleanup script";fi; \ + echo "" + +RUN echo "Init done" +FROM scratch +ARG TZ +ARG PATH +ARG USER +ARG TIMEZONE +ARG LANGUAGE +ARG IMAGE_NAME +ARG BUILD_DATE +ARG SERVICE_PORT +ARG EXPOSE_PORTS +ARG BUILD_VERSION +ARG IMAGE_VERSION +ARG GIT_COMMIT +ARG WWW_ROOT_DIR +ARG DISTRO_VERSION +ARG NODE_VERSION +ARG NODE_MANAGER +ARG PHP_VERSION +ARG PHP_SERVER +ARG LICENSE="WTFPL" +ARG ENV_PORTS="${EXPOSE_PORTS}" + +USER ${USER} +WORKDIR /root + +LABEL maintainer="CasjaysDev " +LABEL org.opencontainers.image.vendor="CasjaysDev" +LABEL org.opencontainers.image.authors="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="https://docker.io/casjaysdevdocker/forgejo" +LABEL org.opencontainers.image.source="https://github.com/casjaysdevdocker/forgejo" +LABEL org.opencontainers.image.documentation="https://github.com/casjaysdevdocker/forgejo" +LABEL org.opencontainers.image.vcs-type="Git" +LABEL com.github.containers.toolbox="false" + +ENV ENV=~/.bashrc +ENV USER="${USER}" +ENV PATH="${PATH}" +ENV TZ="${TIMEZONE}" +ENV SHELL="/bin/bash" +ENV TIMEZONE="${TZ}" +ENV LANG="${LANGUAGE}" +ENV TERM="xterm-256color" +ENV PORT="${SERVICE_PORT}" +ENV ENV_PORTS="${ENV_PORTS}" +ENV CONTAINER_NAME="${IMAGE_NAME}" +ENV HOSTNAME="casjaysdev-${IMAGE_NAME}" +ENV PHP_SERVER="${PHP_SERVER}" +ENV NODE_VERSION="${NODE_VERSION}" +ENV NODE_MANAGER="${NODE_MANAGER}" +ENV PHP_VERSION="${PHP_VERSION}" +ENV DISTRO_VERSION="${IMAGE_VERSION}" +ENV WWW_ROOT_DIR="${WWW_ROOT_DIR}" + +COPY --from=build /. / + +VOLUME [ "/config","/data" ] + +EXPOSE ${SERVICE_PORT} ${ENV_PORTS} + +STOPSIGNAL SIGRTMIN+3 + +ENTRYPOINT [ "tini", "-p", "SIGTERM","--", "/usr/local/bin/entrypoint.sh" ] +HEALTHCHECK --start-period=10m --interval=5m --timeout=15s CMD [ "/usr/local/bin/entrypoint.sh", "healthcheck" ] + diff --git a/IDEA.md b/IDEA.md new file mode 100644 index 0000000..2952ae4 --- /dev/null +++ b/IDEA.md @@ -0,0 +1,16 @@ +# IDEA.md + +## Project variables + +project_name: forgejo +project_org: casjaysdevdocker +internal_name: forgejo +internal_org: casjaysdevdocker + +## Notes + +Docker image project β€” containerized Forgejo for `casjaysdevdocker/forgejo`. +Generated and maintained via the CasjaysDev `gen-dockerfile`/`gen-script` template +system (`casjay-dotfiles/scripts`). `AI.md` in this repo is not a PART 0-6 +scaffold spec β€” it is a permanent maintenance runbook (Steps 1-9) for re-syncing +generated files whenever the upstream templates change. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..27b62a2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2026 casjay + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a874f10 --- /dev/null +++ b/README.md @@ -0,0 +1,294 @@ +# forgejo + +A self-hosted Docker image for [Forgejo](https://forgejo.org) β€” a lightweight, fast Git hosting service (a hard fork of Gitea) β€” bundled with the Forgejo Actions runner (`act_runner`) and Docker-in-Docker support so CI pipelines run out of the box. + +--- + +## 🐳 Docker + +### Quick start + +```shell +docker run -d \ + --name casjaysdevdocker-forgejo-latest \ + --privileged \ + --restart always \ + --tty \ + --cgroupns private \ + --hostname git.example.com \ + --domainname example.com \ + --network bridge \ + --cap-add CHOWN \ + --cap-add SYS_TIME \ + --cap-add SYS_ADMIN \ + -e TZ=America/New_York \ + -e HOSTNAME=git.example.com \ + -e FORGEJO_PROTO=http \ + -e DATABASE_DIR_SQLITE=/data/db/sqlite \ + -v /srv/docker/forgejo/data:/data:z \ + -v /srv/docker/forgejo/config:/config:z \ + -v /srv/docker/databases/sqlite/forgejo:/data/db/sqlite:z \ + -p 80:80 \ + -p 22:22 \ + casjaysdevdocker/forgejo:latest +``` + +### via docker compose + +```yaml +# nginx proxy address - http://172.17.0.1:80 + +x-logging: &default-logging + driver: json-file + options: + max-size: "5m" + max-file: "1" + +services: + forgejo: + image: casjaysdevdocker/forgejo:latest + pull_policy: always + container_name: casjaysdevdocker-forgejo-latest + hostname: git.example.com + domainname: example.com + privileged: true + tty: true + restart: always + logging: *default-logging + cgroupns_mode: private + cap_add: + - CHOWN + - SYS_TIME + - SYS_ADMIN + environment: + TZ: ${TZ:-America/New_York} + CONTAINER_NAME: casjaysdevdocker-forgejo-latest + HOSTNAME: ${BASE_HOST_NAME:-git.example.com} + FORGEJO_PROTO: http + DATABASE_DIR_SQLITE: /data/db/sqlite + volumes: + - ./volumes/data:/data:z + - ./volumes/config:/config:z + - ./volumes/db/sqlite:/data/db/sqlite:z + ports: + - "172.17.0.1:80:80" + - "172.17.0.1:22:22" + networks: + - forgejo + +networks: + forgejo: + name: forgejo + external: false +``` + +### Environment variables + +**General** + +| Variable | Default | Description | +|----------|---------|-------------| +| `TZ` | `America/New_York` | Timezone | +| `DEBUGGER` | _(empty)_ | Set to `on` to enable shell-level debug tracing | + +**Server / domain** + +| Variable | Default | Description | +|----------|---------|-------------| +| `FORGEJO_SERVER` | `hostname -f` | Public FQDN β€” sets ROOT\_URL, DOMAIN, SSH\_DOMAIN, and all email addresses. **Always set this in production.** | +| `FORGEJO_HOSTNAME` | _(empty)_ | Alias for `FORGEJO_SERVER` | +| `FULL_DOMAIN_NAME` | _(empty)_ | Fallback FQDN used when neither `FORGEJO_SERVER` nor `FORGEJO_HOSTNAME` is set | +| `DOMAIN` | _(empty)_ | Overrides the domain used in email addresses (takes precedence over `FORGEJO_SERVER`) | +| `FORGEJO_PROTO` | `http` | Protocol used in ROOT\_URL (`http` or `https`) | +| `FORGEJO_PORT` | `80` | Internal port Forgejo listens on | +| `FORGEJO_NAME` | `SelfHosted GIT Server` | Site title shown in the UI | +| `FORGEJO_TZ` | `$TZ` | Override timezone for Forgejo specifically | +| `FORGEJO_WORK_DIR` | `/data/forgejo` | Override Forgejo's work path | + +**Users** + +| Variable | Default | Description | +|----------|---------|-------------| +| `FORGEJO_ROOT_USER_NAME` | _(empty)_ | Initial admin account username (created on first run) | +| `FORGEJO_ROOT_PASS_WORD` | _(empty)_ | Initial admin account password | +| `FORGEJO_USER_NAME` | _(empty)_ | Initial normal user username | +| `FORGEJO_USER_PASS_WORD` | _(empty)_ | Initial normal user password | + +**Mail** + +| Variable | Default | Description | +|----------|---------|-------------| +| `FORGEJO_ADMIN` | `administrator@` | Admin contact / mailer FROM address | +| `FORGEJO_EMAIL_RELAY` | `172.17.0.1` | SMTP relay host | +| `FORGEJO_EMAIL_CONFIRM` | `false` | Set to `yes` to require email confirmation and enable the mailer | + +**Database** + +| Variable | Default | Description | +|----------|---------|-------------| +| `FORGEJO_SQL_TYPE` | `sqlite3` | Database type (`sqlite3`, `mysql`, `postgres`) | +| `FORGEJO_SQL_HOST` | `localhost` | Database host (external DB only) | +| `FORGEJO_SQL_DB_HOST` | `$FORGEJO_SQL_HOST` | Alternate database host variable | +| `FORGEJO_SQL_USER` | _(empty)_ | Database user (external DB only) | +| `FORGEJO_SQL_PASS` | _(empty)_ | Database password (external DB only) | +| `FORGEJO_SQL_NAME` | _(empty)_ | Database name (external DB only) | +| `DATABASE_DIR_SQLITE` | `$DATA_DIR/db/sqlite` | Override the SQLite database directory (mount a separate volume here to keep the DB outside `/data`) | + +**act\_runner** + +| Variable | Default | Description | +|----------|---------|-------------| +| `RUNNERS_START` | `5` | Number of act\_runner instances to register | +| `RUNNER_CACHE_PORT` | `44015` | Port for the act\_runner cache server | +| `RUNNER_IP_ADDRESS` | container IP | IP address act\_runner registers with Forgejo | +| `RUNNER_DEFAULT_HOME` | `/config/act_runner/forgejo` | Directory where runner registration state is stored | +| `RUNNER_CONFIG_NAME` | `act_runner.yaml` | Runner config filename inside `RUNNER_DEFAULT_HOME` | +| `ACT_RUNNER_FALLBACK_VERSION` | `v13.1.0` | Pinned act\_runner version used if code.forgejo.org is unreachable during build | + +**Runner labels** are set automatically based on the host architecture. All jobs run inside Docker containers β€” no bare-metal execution. + +| Host arch | Labels registered | +|-----------|------------------| +| `x86_64` | `amd64:docker://ubuntu:latest`, `linux:docker://ubuntu:latest`, `linux/amd64:docker://ubuntu:latest`, + language images | +| `aarch64` | `arm64:docker://ubuntu:latest`, `linux:docker://ubuntu:latest`, `linux/arm64:docker://ubuntu:latest`, + language images | + +Language image labels available on both architectures: `node` (14/16/18/20/22/latest), `perl`, `ruby`, `python`/`python3`, `php`/`php7`/`php8`, `alpine`, `debian`, `ubuntu`, `almalinux`/`rhel`/`redhat`, `ubuntu-latest`. + +### Volumes + +| Path | Purpose | +|------|---------| +| `/data` | Repositories, SQLite database, LFS objects, attachments, indexes | +| `/config` | `app.ini`, SSH host keys, act\_runner config β€” persisted across container restarts | + +### Ports + +| Port | Protocol | Purpose | +|------|----------|---------| +| `80` | TCP | Forgejo web UI and API | +| `22` (internal) / `7833` (default external) | TCP | Git over SSH β€” host port 22 is typically taken by sshd; map container port 22 to an available host port and set `SSH_PORT` to match | + +### Notes + +- **`--privileged` is required** for Docker-in-Docker (act\_runner runs CI jobs inside containers). +- The container ships its own `/etc/resolv.conf` (Cloudflare + Google DNS, no search domain) so DNS resolution inside the container is not affected by the host's search domain configuration. +- `FORGEJO_SERVER` **must be set** for a production deployment β€” without it, `ROOT_URL`, SSH clone URLs, and all system email addresses fall back to the container's short hostname. +- The mailer is **disabled by default**. Set `FORGEJO_EMAIL_CONFIRM=yes` to enable it along with the SMTP relay. +- SQLite is the default database. For external MySQL/Postgres set `FORGEJO_SQL_TYPE`, `FORGEJO_SQL_HOST`, `FORGEJO_SQL_USER`, `FORGEJO_SQL_PASS`, and `FORGEJO_SQL_NAME`. + +--- + +## πŸƒ Adding external runners + +External runners let you add dedicated hardware (e.g. a native ARM64 server) to your Forgejo Actions pool without running the full container. Each runner registers directly against your Forgejo instance and declares its own labels, so matrix workflows can target it by architecture. + +### 1 β€” Get a registration token + +In the Forgejo web UI: **Site Administration β†’ Runners β†’ Create Runner Token** + +Or via API: + +```shell +curl -s -X POST https://git.example.com/api/v1/user/actions/runners/registration-token \ + -H "Authorization: token " +``` + +### 2 β€” Install the act_runner binary + +```shell +# Detect arch +ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') +VER=v13.1.0 + +curl -LSsf "https://code.forgejo.org/forgejo/runner/releases/download/${VER}/forgejo-runner-${VER#v}-linux-${ARCH}" \ + -o /usr/local/bin/act_runner +chmod +x /usr/local/bin/act_runner +``` + +### 3 β€” Register against your Forgejo instance + +```shell +act_runner register \ + --instance https://git.example.com \ + --token \ + --name "arm64-server" \ + --labels "arm64:docker://ubuntu:latest,linux/arm64:docker://ubuntu:latest,alpine:docker://alpine:latest,debian:docker://debian:latest" \ + --no-interactive +``` + +Label format: `name:type:image` β€” all jobs run inside Docker containers, never directly on the host. +- `arm64:docker://ubuntu:latest` β€” dispatched to this runner, job runs in a native arm64 Ubuntu container +- `linux/arm64:docker://ubuntu:latest` β€” OCI-style label for the same runner +- Docker must be installed and running on the host machine + +### 4 β€” Run as a systemd service + +```ini +# /etc/systemd/system/act_runner.service +[Unit] +Description=Forgejo Actions Runner +After=network.target + +[Service] +ExecStart=/usr/local/bin/act_runner daemon +WorkingDirectory=/var/lib/act_runner +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +```shell +mkdir -p /var/lib/act_runner +mv .runner /var/lib/act_runner/ # move registration file to working dir +systemctl daemon-reload +systemctl enable --now act_runner +``` + +### Matrix workflow example + +Once both an amd64 and an arm64 runner are registered: + +```yaml +jobs: + build: + strategy: + matrix: + arch: [amd64, arm64] + runs-on: ${{ matrix.arch }} + steps: + - uses: actions/checkout@v4 + - run: uname -m # confirms native arch +``` + +--- + +## πŸ› οΈ Development + +### Prerequisites + +- Docker with `buildx` +- `bash`, `git` + +### Build from source + +```shell +git clone https://github.com/casjaysdevdocker/forgejo "$HOME/Projects/github/casjaysdevdocker/forgejo" +cd "$HOME/Projects/github/casjaysdevdocker/forgejo" +buildx +``` + +### Install via dockermgr + +```shell +sudo bash -c "$(curl -q -LSsf https://github.com/systemmgr/installer/raw/main/install.sh)" +sudo systemmgr --config && sudo systemmgr install scripts +dockermgr update forgejo +``` + +--- + +## πŸ“„ License + +MIT β€” see [LICENSE.md](LICENSE.md) diff --git a/TODO.AI.md b/TODO.AI.md new file mode 100644 index 0000000..99d4939 --- /dev/null +++ b/TODO.AI.md @@ -0,0 +1,307 @@ +# TODO.AI.md + +## App-breaking bug fixed β€” sshd_config AllowUsers mismatch (giteaβ†’forgejo migration) + +Found incidentally while sweeping for remaining "gitea" references during the forgejo rename. + +- `rootfs/tmp/etc/ssh/sshd_config`: `AllowUsers gitea` referenced a system account that is never + created β€” `08-forgejo.sh` sets `SERVICE_USER="git"`, and no script anywhere creates a `gitea` + user. This bug predates the rename (the same mismatch existed in the gitea version) and would + have silently blocked all git-over-SSH logins. Fixed to `AllowUsers git`. + +## Lint cleanup done β€” UUOC fixed (start-runners) + +Verified clean by `script-lint` agent after fix. + +- `rootfs/usr/local/bin/start-runners`: line 24 UUOC (`echo | grep -q '://'`) replaced with + `[[ "$SERVER_ADDRESS" != *"://"* ]]`; grep call removed entirely so the missing `--` no longer + applies. + +## Lint cleanup done β€” version stamp and grep -- fixed (zz-act_runner.sh) + +Verified clean by `script-lint` agent after fix. + +- `rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh`: added `VERSION="202608031200-git"` + matching the existing `##@Version` header; added `--` before the pattern argument on all 15 grep + invocations in the file (not just the subset originally enumerated); quoted the bare `grep` + pattern at the former line 544 (now `grep -v -- 'grep'`). + +## Lint cleanup done β€” line-length violation fixed (start-runners) + +- `rootfs/usr/local/bin/start-runners`: the 781-char `RUNNER_LABELS="${RUNNER_LABELS:-...}"` + default literal was replaced with a `_default_runner_labels` array joined via `IFS=,`, only + applied when `RUNNER_LABELS` is unset. Verified with `bash -n` and a line-length scan (no line + exceeds 180 chars). + +## App-breaking bug fixed β€” DEBUGGER guard pattern under set -e (functions/entrypoint.sh) + +Needs syncing back to the upstream template in `casjay-dotfiles/scripts` per the Docker Template +Update Runbook in AI.md β€” `functions/entrypoint.sh` is normally regenerated, not hand-edited. + +- 26x occurrences of `[ "$DEBUGGER" = "on" ] && echo/printf/__service_banner "..."` used as a bare + statement: under `set -e`, this aborts the whole script silently whenever `$DEBUGGER` != "on" + (the default). This was the root cause of the container dying immediately after printing only + the startup banner. Fixed by appending `|| true` to all 26 occurrences. + +## App-breaking bug fixed β€” __random_password() SIGPIPE (functions/entrypoint.sh) + +Needs syncing back to the upstream template in `casjay-dotfiles/scripts` per the Docker Template +Update Runbook in AI.md β€” `functions/entrypoint.sh` is normally regenerated, not hand-edited. + +- `__random_password()` (~line 333): `tr | head -c` pipeline died under `set -eo pipefail` on + SIGPIPE. Fixed by wrapping in `{ ... } || true`. + +## App-breaking bug fixed β€” __format_variables() whitespace-only input (functions/entrypoint.sh) + +Needs syncing back to the upstream template per AI.md's runbook. + +- `__format_variables()` (~line 187): `printf '%s\n' $input | sort -Ru | tr '\n' ' '` always + emits at least one line even when `$input` word-splits to zero words (whitespace-only), because + `printf` with a format containing `%s` runs once even with no args. This made `ENV_PORTS` / + `WEB_SERVER_PORTS` resolve to a single space `" "` instead of empty when no port env vars were + set, which made `SERVICE_PORT` in `08-gitea.sh` become `" "` β€” passing the `-n` test but + rendering as an empty `--port` arg to `gitea web`, which broke gitea's CLI argument parsing + entirely (`Command error: unknown command: /config/gitea/app.ini`). Fixed by replacing the + `[ -z "$input" ]` check with `[[ "$input" =~ [^[:space:]] ]] || return 0`. + +## App-breaking bug fixed β€” missing /config/env directory (bin/entrypoint.sh) + +- `/config/env` directory was never explicitly created. It only came into existence as a side + effect of `__create_env_file()` (functions/entrypoint.sh) copying + `/usr/local/etc/docker/env/default.sample` into it β€” but that sample file/dir does not exist in + this image's rootfs, so `__create_env_file()` returns early (line 960) without creating the + directory. `05-dockerd.sh`'s `__create_service_env()` then fails writing + `/config/env/docker.local.sh` directly (`cat <<'EOF' >"/config/env/....local.sh"`, no `tee` + suppression) with `No such file or directory`; `zz-act_runner.sh` hits the same error writing + `/config/env/act_runner.local.sh`. Fixed by adding + `mkdir -p "/config/env" 2>/dev/null || true` alongside the other `/config/*` directory creation + lines (~line 241) in `rootfs/usr/local/bin/entrypoint.sh`. Needs syncing to the upstream + template per AI.md's runbook. + +## OCI label cleanup done β€” forbidden labels removed from Dockerfile + +- Removed `org.opencontainers.image.base.name` (belongs on the base image, not the app image) and + `org.opencontainers.image.schema-version` (non-spec, redundant with `version`). +- Removed the duplicate `org.opencontainers.image.authors="${LICENSE}"` line and duplicate + `org.opencontainers.image.source="https://docker.io/..."` line; the license value now correctly + populates the (previously missing) `org.opencontainers.image.licenses` label per AI.md's OCI + label standard (lines 58-87), and `source` keeps the single github.com URL. + +## App-breaking bug fixed β€” act_runner init.d hang + duplicate runner registration (zz-act_runner.sh) + +Needs syncing back to the upstream template in `casjay-dotfiles/scripts` per the Docker Template +Update Runbook in AI.md β€” the outer hooks (`__post_execute`, `__run_start_script`) are normally +generated, not hand-edited, but this repo's app-specific runner logic already lives inline in +them rather than in the `*_local()` stubs, so the fix was applied in place. + +- Root cause of the hang: because `EXEC_CMD_BIN=''` for this service, `__run_start_script` + (called at the script's tail) internally calls `__post_execute` synchronously through + `__post_execute | tee -p -a "/data/logs/init.txt"`. Inside `__post_execute`'s own backgrounded + subshell, `/usr/local/bin/start-runners &` was launched without redirecting its stdout/stderr, + so it (and the long-running `act_runner daemon` processes it `exec`s, which never exit) + inherited the write end of that `tee` pipe. Since that write end never closed, `tee` never saw + EOF, so it never exited, so `__run_start_script` never returned, so `__start_init_scripts`'s + `( source "$init" )` for this script never completed β€” the init.d loop hung forever after all + services were actually up, and `/data/logs/start.log`'s final `printf` (the completion marker) + was never reached. Fixed by redirecting `start-runners`'s stdout/stderr to + `"$LOG_DIR/runners.log"` at its invocation site instead of inheriting the pipe. +- The script also called `__post_execute` a second time explicitly + (`__post_execute ... | tee ... &` near the end of the script), duplicating the call + `__run_start_script` already makes via its empty-`EXEC_CMD_BIN` branch. Removed the redundant + explicit call. +- The script additionally registered and started a hardcoded, always-on single runner named + `"gitea"` (in `__run_pre_execute_checks` and `__post_execute`) independent of and in addition + to the `RUNNERS_START`-driven runners from `start-runners` β€” so `RUNNERS_START=2` produced 3 + registered runners (`gitea`, `runner-1`, `runner-2`) instead of 2. Removed both the legacy + registration block and its matching daemon-start block; `start-runners` (already idempotent + per-runner via its `.runner`-file check) is now the sole runner registration/startup path. +- Verified with `bash -n` and a live rebuild/retest: the init.d loop now completes and + `/data/logs/start.log` gets its completion marker; only `runner-1`/`runner-2` are registered + (no `gitea` runner). + +## App-breaking bug fixed β€” act_runner registered against wrong/unreachable IP (zz-act_runner.sh) + +Needs syncing back to the upstream template per AI.md's runbook (same file/hooks as above). + +- `RUNNER_IP_ADDRESS` defaulted to `$IP4_ADDRESS` (the container's externally-detected IP, + captured once very early in `bin/entrypoint.sh` via `__get_ip4`). In live testing this value + did not match the container's actual `eth0` address by the time `act_runner daemon` tried to + connect, causing every runner to fail its first RPC with `dial tcp :80: connect: no route + to host` and exit immediately (act_runner does not retry a failed initial connection). Since + act_runner registers against gitea running in the very same container/network namespace, the + detected external IP was never the right thing to use. Fixed by defaulting `RUNNER_IP_ADDRESS` + to `127.0.0.1` instead (still overridable via the `RUNNER_IP_ADDRESS` env var). +- Verified live: after the fix, `curl http://127.0.0.1/` inside the container succeeds + immediately. A first retest still showed runners connecting to `172.17.0.2:80`, but this + was a false alarm caused by stale test volumes: a previous test run's `.runner` + registration files (with the old baked-in address) were still present, and + `__register_runner`'s idempotency check correctly skipped re-registering over them. After + fully removing the test volumes and starting a genuinely fresh container, runner + registration and connectivity succeeded (`Runner registered successfully.` / + `declare successfully` for both `runner-1` and `runner-2`, no connection errors). + +## App-breaking bug fixed β€” cache-server fd leak hangs init.d loop (zz-act_runner.sh) + +Needs syncing back to the upstream template per AI.md's runbook (same file/hooks as above). + +- Same fd-leak class as the `start-runners` hang fixed above, but for + `act_runner cache-server`: `act_runner cache-server --config "$CACHE_CONFIG_FILE" + 2>>/dev/stderr >>"$CACHE_LOG_FILE" &` used `2>>/dev/stderr`, which duplicates the + process's *current* stderr fd β€” at that point in `__post_execute`'s backgrounded + subshell, still the write end of the `tee -p -a "/data/logs/init.txt"` pipe used by the + synchronous `__post_execute` call in `__run_start_script`. Since `cache-server` is a + long-running daemon that never exits, that pipe's write end never closed, so `tee` never + saw EOF, hanging `__run_start_script` (and therefore the whole init.d loop) forever even + though dockerd/gitea/act_runner/cache-server were all actually up and working. Fixed by + redirecting to `"$CACHE_LOG_FILE" 2>&1` instead of `/dev/stderr`. +- The redirect fix alone did not fully resolve the hang: the subshell launching + `start-runners`/`cache-server` (the left side of `__post_execute`'s pipe to + `tee -p -a /data/logs/init.txt`) was still observed blocked in `do_wait` on its + long-running child even with output redirected to a real file, preventing the pipe from + ever seeing EOF. Added an explicit `disown "$!"` immediately after each background launch + (`act_runner cache-server` and `/usr/local/bin/start-runners`) to fully detach them from + the subshell's job table. +- Verified live on a genuinely fresh container (volumes removed first): `/data/logs/start.log` + now gets its completion marker, no orphaned `tee -p -a /data/logs/init.txt` processes + remain, and both `runner-1`/`runner-2` register and declare successfully against gitea + (`runners.log` shows `Runner registered successfully.` and `declare successfully` for + both, no connection errors). + +## App-breaking bug fixed β€” dockerd fails to restart under rapid restart cycling (05-dockerd.sh) + +Found while directly testing the user's "restart container many times" requirement with a rapid +4x-restart loop. + +- `docker restart` reuses the same container filesystem (unlike a fresh `run`), so + `/tmp/docker.pid` from the previous dockerd instance survives the restart. The PID namespace + itself resets on every restart, so the low PID number written into that file (e.g. `461`) can + coincidentally be reused by an unrelated early-boot process in the new namespace. dockerd's own + startup check saw `/proc/` exist and refused to start: `failed to start daemon, ensure + docker is not running or delete /tmp/docker.pid: process with PID 461 is still running`. +- Observed impact: `05-dockerd.sh` logged `❌ Service dockerd failed to start - check logs`, but + gitea and act_runner still reported starting successfully; the entrypoint logged + `⚠️ Warning: 1 critical service(s) reported failures` / `ℹ️ Continuing with 1 failure(s) - + container may still be functional` and kept going in a degraded state, then the whole container + crashed anyway roughly 2 minutes later (`ExitCode=1`). +- Fixed by removing `/tmp/docker.pid` unconditionally at the top of `__run_pre_execute_checks` in + `05-dockerd.sh`, before dockerd is started. This init script is the sole owner of the dockerd + lifecycle (a separate real overlap-guard already exists via `SERVICE_PID_FILE`), so clearing + docker's own pidfile before every start attempt is safe. +- Verified live on a rebuilt image: no `failed to start daemon` / stale-pidfile error appeared in + the logs across either a 3x normally-paced restart loop or a 4x rapid back-to-back restart burst + (see the verification note on the PID-reuse fix below β€” both fixes were tested together in the + same runs). + +## App-breaking bug fixed β€” PID-reuse false positive kills container after restart (entrypoint.sh, functions/entrypoint.sh) + +Found and fixed immediately after the dockerd stale-pidfile fix above, while retesting restart +robustness on the rebuilt image. Same root bug class (a PID recorded in a file that persists +across `docker restart` gets coincidentally reused by an unrelated process once the PID namespace +resets), but hitting two different guards this time, one of which actively took down the whole +container: + +- `functions/entrypoint.sh`'s `__no_exit()`: guarded re-entry with `[ -f /run/.no_exit.pid ] && + kill -0 "$no_exit_pid"`. After a restart, an unrelated early-boot process could reuse that old + PID number, making the check wrongly believe the monitor loop was already running. `__no_exit` + then `return`ed 0 instead of `exec`ing the actual monitor loop, so `bin/entrypoint.sh` fell + straight through to `exit $?` β€” the whole entrypoint process exited cleanly (`ExitCode=0`), + killing the container roughly 2 minutes after a normal, correctly-paced restart, well after + dockerd/gitea/act_runner had all logged successful starts. This is almost certainly the actual + root cause behind earlier restart-loop crashes previously attributed only to the dockerd + stale-pidfile bug. +- `bin/entrypoint.sh`'s `ENTRYPOINT_PID_FILE` check (~line 433) has the identical hazard in the + opposite direction: a false-positive "still alive" match sets `START_SERVICES=no`, which would + silently skip `__start_init_scripts` entirely on a genuine restart (not observed in this test run, + but reachable by the same mechanism). +- Fixed both by requiring the live PID's own `/proc//cmdline` to actually match the expected + process, not just `kill -0` succeeding: `__no_exit`'s exec'd monitor loop now embeds a + `__no_exit_monitor_loop` marker comment in its own `bash -c` command text (visible in its own + cmdline), and the re-check greps for it; `ENTRYPOINT_PID_FILE`'s check greps the candidate PID's + cmdline for `entrypoint.sh`. +- Verified live on a rebuilt image (fresh volumes): a 3x normally-paced restart loop (each + followed by a 90s post-init settle, well past the previously-observed ~2min crash window) and a + 4x rapid back-to-back restart burst (3s apart, no waiting for init between them) both completed + with the container remaining `running`/`ExitCode=0` throughout β€” no clean-exit crash, and no + `failed to start daemon ... still running` dockerd error in the logs. Runner UUIDs for all 5 + registered runners were identical before and after both test sequences (`/config/act_runner/reg/` + still holds exactly 5 directories, no duplicates), confirming the "same id, not new" requirement + holds even under rapid restart cycling. + +## Config cleanup done β€” deprecated `[webhook].ALLOWED_HOST_LIST` moved to `[security]` (rootfs/tmp/etc/gitea/app.ini) + +- Every `gitea admin user create` (and, by extension, every gitea startup) logged: + `[E] Deprecation: config option [webhook].ALLOWED_HOST_LIST present, please use + [security].ALLOWED_HOST_LIST instead because this fallback will be/has been removed in v28.0.0`. +- Root cause: the shipped `app.ini` template set `ALLOWED_HOST_LIST = *` under `[webhook]` only; no + `[security]` equivalent existed, so gitea used the deprecated fallback on every run. +- Fix: added `ALLOWED_HOST_LIST = *` to `[security]` and removed it from `[webhook]`. +- `app.ini` is a staged config file (`rootfs/tmp/etc/gitea/app.ini` β†’ `/config/gitea/app.ini`), not a + `gen-dockerfile`-generated file, so this is a normal in-repo edit, not a template/generation issue. +- Verified live on a rebuilt image: `gitea admin user create` no longer emits the deprecation warning, + and admin login/API access still works normally after the move. +- Re-ran the full functional + restart-stability regression suite against this rebuilt image on fresh + volumes: admin user creation, non-admin user creation, repo creation, fork (`forker/testrepo`), and + GitHub mirror creation + manual `mirror-sync` trigger (`mirror_last_sync_at` moved off epoch) all + passed. A 3x paced restart loop (90s settle each) and a 4x rapid back-to-back restart burst (3s + apart) both left the container `running`/`ExitCode=0` throughout, with dockerd, `gitea web`, and all + 5 `act_runner` daemons (+ cache-server) alive afterward, and all 5 runner UUIDs byte-for-byte + identical before and after (`/config/act_runner/reg/` still exactly 5 directories, no duplicates). + +## App-breaking bug fixed β€” act_runner CI jobs cannot start: nested overlayfs mount failure (05-dockerd.sh) + +- Every Gitea Actions job failed within ~13s with, both via act_runner and via a plain manual + `docker run` inside the container: `failed to mount ... fstype: overlay ... err: invalid argument`. + Reproduced with `docker exec gitea-test docker run --rm ...` β€” 100% failure rate, not + specific to any one image or to act_runner's job-container logic. +- Root cause: the inner (DinD) dockerd defaulted to the `overlayfs` storage driver, same as the outer + host/container's own root filesystem. Nesting an `overlay2`-driver dockerd inside a container whose + own root is itself an overlay filesystem is a well-known Docker-in-Docker failure mode β€” the kernel + rejects the resulting overlay-on-overlay mount with `invalid argument` on this kernel/host + combination, even with `index=off` already set. +- Impact: **the entire act_runner CI subsystem was non-functional** β€” runners registered fine and + showed as online, but every single job would fail immediately at the "Starting job container" step, + regardless of workflow, label, or target image. This would not have been caught by + container-startup/registration testing alone; it required actually running a real workflow to + surface. +- Fix: added `"storage-driver": "fuse-overlayfs"` to both branches of the `/config/docker/daemon.json` + generation in `__run_pre_execute_checks_local` (with-registry and without-registry cases). + `fuse-overlayfs` is already bundled in the image (`/usr/bin/fuse-overlayfs`, `/dev/fuse` present) and + avoids the kernel-level overlay-on-overlay conflict by mounting entirely in userspace via FUSE. The + existing `--privileged`/`SYS_ADMIN` requirement (already documented in README.md as required for + Docker-in-Docker) covers fuse-overlayfs's own requirements β€” no new run-flag requirement introduced. +- Verified live: manually patched `/config/docker/daemon.json` on the running container, restarted the + inner dockerd, and confirmed `docker info` reports `Storage Driver: fuse-overlayfs` and `docker run` + (both `casjaysdev/alpine:latest` and `ubuntu:latest`) now creates and starts containers successfully + with no mount error, where it previously failed 100% of the time. +- **Follow-up root cause found**: the initial `05-dockerd.sh` heredoc fix alone did not take effect on + a fresh container/fresh volumes. Traced to `__run_precopy`'s baked-`/etc`-seeding step: on first run + it copies the whole baked `/etc/docker` directory (staged from `rootfs/tmp/etc/docker/daemon.json` via + `03-files.sh`) into `/config/docker/` BEFORE `__run_pre_execute_checks_local`'s + `[ ! -f "/config/docker/daemon.json" ]` guard ever runs β€” so the guard always saw the file already + present and the heredoc (containing the fix) never executed. Fixed by adding + `"storage-driver": "fuse-overlayfs"` to the actual baked source file, + `rootfs/tmp/etc/docker/daemon.json`, in addition to keeping the `05-dockerd.sh` heredoc fix as + defense-in-depth for any case where the baked `/etc/docker` dir is absent. +- Rebuilt the image and recreated the container on completely fresh volumes: confirmed + `/config/docker/daemon.json` now contains `"storage-driver": "fuse-overlayfs"` and + `docker info` reports `Storage Driver: fuse-overlayfs` via the normal init path (not a manual patch). + Confirmed `docker exec gitea-test docker run --rm ubuntu:latest echo ...` succeeds with no mount + error. Pushed a real `runs-on: alpine` Gitea Actions workflow via the contents API and confirmed the + job reached `"status":"success"` in ~3 seconds via `GET .../actions/tasks` β€” full end-to-end + confirmation, not just container-creation succeeding. **This bug is now fully resolved and verified.** +- Re-ran the full restart-stability + functional regression suite on this fix, on the same fresh + container: 3x paced restarts (90s settle each) + 4x rapid-burst restarts (3s apart) β€” container + stayed `running`/`ExitCode:0`/`healthy` throughout, all 5 runner UUIDs identical before/after (no + duplicate registrations), and `Storage Driver: fuse-overlayfs` + prior admin user/repo state all + survived the restarts intact. Also verified admin user creation, repo creation, fork (into a second + `forker` user account), and mirror+sync (`mirror_updated` timestamp advanced from mirror-registration + time to a fresh sync time) all still work with no regressions from this fix. + +## Non-issue β€” confirmed intentional (`.gitea/workflows/docker.yaml`) + +- Uses a stale/unpinned action pattern (`@v2`-`@v4`, DockerHub-only, `catthehacker/ubuntu:act-latest`). + AI.md PART 7 explicitly documents this as the legacy hand-crafted workflow: "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`." No `build.yml` exists yet in + this repo; generating one is a separate task (running `gen-dockerfile actions`), not a fix to this + file. diff --git a/rootfs/root/docker/setup/00-init.sh b/rootfs/root/docker/setup/00-init.sh new file mode 100755 index 0000000..56de5de --- /dev/null +++ b/rootfs/root/docker/setup/00-init.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202605241158-git +# @@Author : CasjaysDev +# @@Contact : CasjaysDev +# @@License : MIT +# @@Copyright : Copyright 2026 CasjaysDev +# @@Created : Sun May 24 11:58:45 AM EDT 2026 +# @@File : 00-init.sh +# @@Description : script to run init +# @@Changelog : newScript +# @@TODO : Refactor code +# @@Other : N/A +# @@Resource : N/A +# @@Terminal App : yes +# @@sudo/root : yes +# @@Template : templates/dockerfiles/init_scripts/00-init.sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set bash options +set -o pipefail +[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set env variables +exitCode=0 + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Predefined actions +if [ -d "/usr/local/share/template-files/data" ]; then rm -Rf "/usr/local/share/template-files/data"/*; fi +if [ -d "/usr/local/share/template-files/config" ]; then rm -Rf "/usr/local/share/template-files/config"/*; fi +if [ -d "/usr/local/share/template-files/defaults" ]; then rm -Rf "/usr/local/share/template-files/defaults"/*; fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Main script + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the exit code +#exitCode=$? +# - - - - - - - - - - - - - - - - - - - - - - - - - +exit $exitCode +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/rootfs/root/docker/setup/01-system.sh b/rootfs/root/docker/setup/01-system.sh new file mode 100755 index 0000000..494eb3e --- /dev/null +++ b/rootfs/root/docker/setup/01-system.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202605241158-git +# @@Author : CasjaysDev +# @@Contact : CasjaysDev +# @@License : MIT +# @@Copyright : Copyright 2026 CasjaysDev +# @@Created : Sun May 24 11:58:45 AM EDT 2026 +# @@File : 01-system.sh +# @@Description : script to run system +# @@Changelog : newScript +# @@TODO : Refactor code +# @@Other : N/A +# @@Resource : N/A +# @@Terminal App : yes +# @@sudo/root : yes +# @@Template : templates/dockerfiles/init_scripts/01-system.sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set bash options +set -o pipefail +[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set env variables +exitCode=0 + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Predefined actions + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Main script + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the exit code +#exitCode=$? +# - - - - - - - - - - - - - - - - - - - - - - - - - +exit $exitCode +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/rootfs/root/docker/setup/02-packages.sh b/rootfs/root/docker/setup/02-packages.sh new file mode 100755 index 0000000..ab46417 --- /dev/null +++ b/rootfs/root/docker/setup/02-packages.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202605241158-git +# @@Author : CasjaysDev +# @@Contact : CasjaysDev +# @@License : MIT +# @@Copyright : Copyright 2026 CasjaysDev +# @@Created : Sun May 24 11:58:45 AM EDT 2026 +# @@File : 02-packages.sh +# @@Description : script to run packages +# @@Changelog : newScript +# @@TODO : Refactor code +# @@Other : N/A +# @@Resource : N/A +# @@Terminal App : yes +# @@sudo/root : yes +# @@Template : templates/dockerfiles/init_scripts/02-packages.sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set bash options +set -o pipefail +[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set env variables +exitCode=0 + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Predefined actions + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Main script + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the exit code +#exitCode=$? +# - - - - - - - - - - - - - - - - - - - - - - - - - +exit $exitCode +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/rootfs/root/docker/setup/03-files.sh b/rootfs/root/docker/setup/03-files.sh new file mode 100755 index 0000000..d8ed80a --- /dev/null +++ b/rootfs/root/docker/setup/03-files.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202606041215-git +# @@Author : CasjaysDev +# @@Contact : CasjaysDev +# @@License : WTFPL +# @@ReadME : +# @@Copyright : Copyright: (c) 2023 CasjaysDev +# @@Created : Mon Aug 28 06:48:42 PM EDT 2023 +# @@File : 03-files.sh +# @@Description : script to run files +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck shell=bash +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +set -o pipefail +[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +exitCode=0 +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if [ -d "/tmp/bin" ]; then + \mkdir -p "/usr/local/bin" + for bin in "/tmp/bin"/*; do + name="${bin##*/}" + echo "Installing $name to /usr/local/bin/$name" + copy "$bin" "/usr/local/bin/$name" + \chmod -f +x "/usr/local/bin/$name" + done +fi +unset bin +if [ -d "/tmp/var" ]; then + for var in "/tmp/var"/*; do + name="${var##*/}" + echo "Installing $var to /var/$name" + if [ -d "$var" ]; then + \mkdir -p "/var/$name" + copy "$var/." "/var/$name/" + else + copy "$var" "/var/$name" + fi + done +fi +unset var +if [ -d "/tmp/etc" ]; then + for config in "/tmp/etc"/*; do + name="${config##*/}" + echo "Installing $config to /etc/$name" + if [ -d "$config" ]; then + \mkdir -p "/etc/$name" + copy "$config/." "/etc/$name/" + else + copy "$config" "/etc/$name" + fi + done +fi +unset config +if [ -d "/tmp/usr" ]; then + for usrpath in "/tmp/usr"/*; do + name="${usrpath##*/}" + echo "Installing $usrpath to /usr/$name" + if [ -d "$usrpath" ]; then + \mkdir -p "/usr/$name" + copy "$usrpath/." "/usr/$name/" + else + copy "$usrpath" "/usr/$name" + fi + done +fi +unset usrpath +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +exitCode=$? +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +exit $exitCode +# ex: ts=2 sw=2 et filetype=sh diff --git a/rootfs/root/docker/setup/04-users.sh b/rootfs/root/docker/setup/04-users.sh new file mode 100755 index 0000000..434f031 --- /dev/null +++ b/rootfs/root/docker/setup/04-users.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202605241158-git +# @@Author : CasjaysDev +# @@Contact : CasjaysDev +# @@License : MIT +# @@Copyright : Copyright 2026 CasjaysDev +# @@Created : Sun May 24 11:58:45 AM EDT 2026 +# @@File : 04-users.sh +# @@Description : script to run users +# @@Changelog : newScript +# @@TODO : Refactor code +# @@Other : N/A +# @@Resource : N/A +# @@Terminal App : yes +# @@sudo/root : yes +# @@Template : templates/dockerfiles/init_scripts/04-users.sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set bash options +set -o pipefail +[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set env variables +exitCode=0 + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Predefined actions + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Main script + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the exit code +#exitCode=$? +# - - - - - - - - - - - - - - - - - - - - - - - - - +exit $exitCode +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/rootfs/root/docker/setup/05-custom.sh b/rootfs/root/docker/setup/05-custom.sh new file mode 100755 index 0000000..de7d765 --- /dev/null +++ b/rootfs/root/docker/setup/05-custom.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202609030601-git +# @@Author : CasjaysDev +# @@Contact : CasjaysDev +# @@License : MIT +# @@Copyright : Copyright 2026 CasjaysDev +# @@Created : Sun May 24 11:58:45 AM EDT 2026 +# @@File : 05-custom.sh +# @@Description : script to run custom +# @@Changelog : newScript +# @@TODO : Refactor code +# @@Other : N/A +# @@Resource : N/A +# @@Terminal App : yes +# @@sudo/root : yes +# @@Template : templates/dockerfiles/init_scripts/05-custom.sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set bash options +set -o pipefail +[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set env variables +VERSION="202609030601-git" +exitCode=0 +apk add --no-cache ca-certificates 2>/dev/null || true +update-ca-certificates 2>/dev/null || true +FORGEJO_VERSION="${FORGEJO_VERSION:-latest}" +FORGEJO_BIN_FILE="/usr/local/bin/forgejo" +ACT_BIN_FILE="/usr/local/bin/act_runner" +ARCH="$(uname -m | tr '[:upper]' '[:lower]')" +case "$ARCH" in x86_64) ARCH="amd64" ;; aarch64) ARCH="arm64" ;; *) echo "$ARCH is not supported by this script" >&2 && exit 1 ;; esac +# Pinned fallback used when code.forgejo.org is unreachable from the build host +ACT_RUNNER_FALLBACK_VERSION="${ACT_RUNNER_FALLBACK_VERSION:-v13.1.0}" +# Fetch latest version tag from the forgejo-runner repo β€” 30s connect timeout +ACT_VERSIONS="$(curl -q --connect-timeout 30 --max-time 45 -LSsf \ + 'https://code.forgejo.org/api/v1/repos/forgejo/runner/releases' \ + -H 'accept: application/json' 2>/dev/null | jq -r '.[].tag_name' | sort -Vr | head -n1)" +# Fall back to pinned version if API is unreachable +[ -z "$ACT_VERSIONS" ] && ACT_VERSIONS="$ACT_RUNNER_FALLBACK_VERSION" && echo "WARNING: code.forgejo.org unreachable, using act_runner $ACT_VERSIONS" >&2 +# Fetch download URL from API; binary names use the version without leading 'v' +ACT_URL="$(curl -q --connect-timeout 30 --max-time 45 -LSsf \ + "https://code.forgejo.org/api/v1/repos/forgejo/runner/releases/tags/$ACT_VERSIONS" \ + -H 'accept: application/json' 2>/dev/null | jq -rc '.assets|.[]|.browser_download_url' | grep -E -- "linux-${ARCH}$")" +# If API parse yielded nothing, construct the direct download URL from the version +# Tag format: v13.1.0 β†’ filename: forgejo-runner-13.1.0-linux-amd64 (strip leading 'v') +ACT_VER_PLAIN="${ACT_VERSIONS#v}" +[ -z "$ACT_URL" ] && ACT_URL="https://code.forgejo.org/forgejo/runner/releases/download/${ACT_VERSIONS}/forgejo-runner-${ACT_VER_PLAIN}-linux-${ARCH}" +if [ -z "$FORGEJO_VERSION" ] || [ "$FORGEJO_VERSION" = "latest" ] || [ "$FORGEJO_VERSION" = "current" ]; then + _latest_url="$(curl -4sfL -o /dev/null -w '%{url_effective}' https://code.forgejo.org/forgejo/forgejo/releases/latest 2>/dev/null)" + FORGEJO_VERSION="$(printf '%s\n' "$_latest_url" | grep -oE -- '[0-9]+\.[0-9]+\.[0-9]+')" +fi +if [ -z "$FORGEJO_VERSION" ]; then + echo "Failed to resolve forgejo latest version from code.forgejo.org" >&2 + exit 1 +fi +FORGEJO_URL="https://code.forgejo.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-${ARCH}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Predefined actions +echo "Downloading forgejo from $FORGEJO_URL" +if curl -4 -q -LSsf --retry 5 --retry-delay 10 --retry-all-errors "$FORGEJO_URL" -o "/tmp/forgejo.$$"; then + mv -f "/tmp/forgejo.$$" "$FORGEJO_BIN_FILE" + echo "forgejo has been installed to: $FORGEJO_BIN_FILE" + chmod +x "$FORGEJO_BIN_FILE" + if [ -d "/etc/sudoers.d" ]; then + echo "root ALL=(ALL) NOPASSWD: ALL" >"/etc/sudoers.d/root" + echo "git ALL=(ALL) NOPASSWD: ALL" >"/etc/sudoers.d/git" + echo "docker ALL=(ALL) NOPASSWD: ALL" >"/etc/sudoers.d/docker" + fi +else + echo "Failed to download forgejo" >&2 + exitCode=$((exitCode + 1)) +fi +echo "Downloading act_runner from $ACT_URL" +if [ -z "$ACT_URL" ]; then + echo "Failed to resolve act_runner download URL" >&2 + exitCode=$((exitCode + 1)) +elif curl -q -LSsf --retry 5 --retry-delay 10 --retry-all-errors "$ACT_URL" -o "/tmp/act_runner.$$"; then + mv -f "/tmp/act_runner.$$" "$ACT_BIN_FILE" + echo "act_runner has been installed to: $ACT_BIN_FILE" + chmod +x "$ACT_BIN_FILE" +else + echo "Failed to download act_runner" >&2 + exitCode=$((exitCode + 1)) +fi +[ -x "$ACT_BIN_FILE" ] && [ -x "$FORGEJO_BIN_FILE" ] && exitCode=0 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Main script + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the exit code +#exitCode=$? +# - - - - - - - - - - - - - - - - - - - - - - - - - +exit "$exitCode" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rootfs/root/docker/setup/06-post.sh b/rootfs/root/docker/setup/06-post.sh new file mode 100755 index 0000000..eda3c19 --- /dev/null +++ b/rootfs/root/docker/setup/06-post.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202605241158-git +# @@Author : CasjaysDev +# @@Contact : CasjaysDev +# @@License : MIT +# @@Copyright : Copyright 2026 CasjaysDev +# @@Created : Sun May 24 11:58:45 AM EDT 2026 +# @@File : 06-post.sh +# @@Description : script to run post +# @@Changelog : newScript +# @@TODO : Refactor code +# @@Other : N/A +# @@Resource : N/A +# @@Terminal App : yes +# @@sudo/root : yes +# @@Template : templates/dockerfiles/init_scripts/06-post.sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set bash options +set -o pipefail +[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set env variables +exitCode=0 + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Predefined actions + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Main script + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the exit code +#exitCode=$? +# - - - - - - - - - - - - - - - - - - - - - - - - - +exit $exitCode +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/rootfs/root/docker/setup/07-cleanup.sh b/rootfs/root/docker/setup/07-cleanup.sh new file mode 100755 index 0000000..86eb883 --- /dev/null +++ b/rootfs/root/docker/setup/07-cleanup.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202605241158-git +# @@Author : CasjaysDev +# @@Contact : CasjaysDev +# @@License : MIT +# @@Copyright : Copyright 2026 CasjaysDev +# @@Created : Sun May 24 11:58:45 AM EDT 2026 +# @@File : 07-cleanup.sh +# @@Description : script to run cleanup +# @@Changelog : newScript +# @@TODO : Refactor code +# @@Other : N/A +# @@Resource : N/A +# @@Terminal App : yes +# @@sudo/root : yes +# @@Template : templates/dockerfiles/init_scripts/07-cleanup.sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set bash options +set -o pipefail +[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Load functions +__find_and_remove() { [ -z "$1" ] || find "${2:-/etc}" -iname "$1" -exec rm -Rf {} \; 2>/dev/null; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set env variables +exitCode=0 + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Predefined actions +if [ -d "/tmp" ]; then rm -Rf "/tmp"/*; fi +if [ -d "$HOME/.cache" ]; then rm -Rf "$HOME/.cache"; fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Main script + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the exit code +#exitCode=$? +# - - - - - - - - - - - - - - - - - - - - - - - - - +exit $exitCode +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/rootfs/tmp/etc/act_runner/cache_server.yaml b/rootfs/tmp/etc/act_runner/cache_server.yaml new file mode 100644 index 0000000..6373a26 --- /dev/null +++ b/rootfs/tmp/etc/act_runner/cache_server.yaml @@ -0,0 +1,9 @@ +log: + level: warn + +cache: + enabled: true + dir: 'REPLACE_RUNNER_CACHE_DIR' + host: '0.0.0.0' + port: REPLACE_RUNNER_CACHE_PORT + external_secret: 'REPLACE_RUNNER_CACHE_SECRET' diff --git a/rootfs/tmp/etc/act_runner/default_config.yaml b/rootfs/tmp/etc/act_runner/default_config.yaml new file mode 100644 index 0000000..4a1970b --- /dev/null +++ b/rootfs/tmp/etc/act_runner/default_config.yaml @@ -0,0 +1,71 @@ +# Config for act_runner daemon +log: + # The level of logging, can be trace, debug, info, warn, error, fatal + level: warn + +host: + # The parent directory of a job's working directory. + workdir_parent: 'REPLACE_RUNNER_TEMP' + +runner: + # Where to store the registration result. + file: REPLACE_RUNNER_HOME/runners + # Execute how many tasks concurrently at the same time. + capacity: 4 + # Extra environment variables to run jobs. + envs: + A_TEST_ENV_NAME_1: a_test_env_value_1 + # Extra environment variables to run jobs from a file. + env_file: .env + # The timeout for a job to be finished. + timeout: 3h + # Whether skip verifying the TLS certificate of the Forgejo instance. + insecure: true + # The timeout for fetching the job from the Forgejo instance. + fetch_timeout: 5s + # The interval for fetching the job from the Forgejo instance. + fetch_interval: 2s + # The labels of a runner are used to determine which jobs the runner can run, and how to run them. + labels: + #- 'macos:docker:dockurr/macos' + #- 'windows:docker:dockurr/windows' + #- 'linux:docker:casjaysdev/almalinux' + #- 'alma:docker:casjaysdev/almalinux' + #- 'alpine:docker:casjaysdev/alpine' + #- 'debian:docker:casjaysdev/debian' + #- 'arch:docker:casjaysdev/archlinux' + #- 'node:docker://node:latest' + #- 'node14:docker://node:14' + #- 'node16:docker://node:16' + #- 'node18:docker://node:18' + #- 'node20:docker://node:20' + #- 'node20:docker://node:20' + #- 'python3:docker://python:latest' + #- 'php7:docker://php:7-fpm' + #- 'php8:docker://php:8-fpm' + #- 'php:docker://php:8.4-rc-fpm-alpine3.20' + #- 'alpine:docker://casjaysdev/alpine:latest' + #- 'almalinux:docker://casjaysdev/almalinux:latest' + #- 'debian:docker://casjaysdev/debian:latest' + #- 'ubuntu:docker://casjaysdev/ubuntu:latest' + #- 'linux:host,ubuntu-latest:docker://catthehacker/ubuntu:full-latest' + +container: + # Specifies the network to which the container will connect. + network: 'bridge' + # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker). + privileged: true + # And other options to be used when the container is started (eg, --add-host=my.forgejo.url:host-gateway). + options: + # The parent directory of a job's working directory. + workdir_parent: 'REPLACE_RUNNER_TEMP/volumes' + # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob + valid_volumes: + - '**' + # overrides the docker client host with the specified one. + docker_host: '' + +cache: + enabled: true + external_server: 'http://REPLACE_RUNNER_CACHE_HOST:REPLACE_RUNNER_CACHE_PORT/' + external_secret: 'REPLACE_RUNNER_CACHE_SECRET' diff --git a/rootfs/tmp/etc/docker/daemon.json b/rootfs/tmp/etc/docker/daemon.json new file mode 100644 index 0000000..b89966b --- /dev/null +++ b/rootfs/tmp/etc/docker/daemon.json @@ -0,0 +1,15 @@ +{ + "ip": "0.0.0.0", + "iptables": true, + "log-level": "error", + "experimental": true, + "pidfile": "/tmp/docker.pid", + "cgroup-parent": "/docker", + "storage-driver": "fuse-overlayfs", + "default-address-pools": [ + {"base": "172.17.0.0/12", "size": 24}, + {"base": "192.168.0.0/16", "size": 24}, + {"base": "10.0.0.0/8", "size": 24} + ], + "insecure-registries": ["localhost"] +} diff --git a/rootfs/tmp/etc/forgejo/app.ini b/rootfs/tmp/etc/forgejo/app.ini new file mode 100644 index 0000000..65e1655 --- /dev/null +++ b/rootfs/tmp/etc/forgejo/app.ini @@ -0,0 +1,383 @@ +; https://forgejo.org/docs/latest/admin/config-cheat-sheet/#default-configuration-non-appini-configuration +APP_NAME = REPLACE_SERVER_SITE_TITLE +RUN_USER = REPLACE_SERVICE_USER +RUN_MODE = prod + +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[server] +PROTOCOL = http +HTTP_ADDR = 0.0.0.0 +HTTP_PORT = 80 +ROOT_URL = REPLACE_SERVER_PROTO://REPLACE_SERVER_NAME +DOMAIN = REPLACE_SERVER_NAME +APP_DATA_PATH = REPLACE_DATA_DIR +OFFLINE_MODE = false +LFS_START_SERVER = true +LFS_JWT_SECRET = REPLACE_FORGEJO_LFS_JWT_SECRET +LANDING_PAGE = explore +ACME_URL = +ACME_ACCEPTTOS = true +ACME_EMAIL = ssl-admin@REPLACE_SERVER_NAME +ACME_DIRECTORY = REPLACE_DATA_DIR/certs +LETSENCRYPT_ACCEPTTOS = true +DISABLE_SSH = false +START_SSH_SERVER = true +SSH_LISTEN_HOST = 0.0.0.0 +SSH_PORT = 7833 +SSH_LISTEN_PORT = 22 +SSH_CREATE_AUTHORIZED_KEYS_FILE = true +SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true +SSH_AUTHORIZED_KEYS_BACKUP = true +SSH_ROOT_PATH = REPLACE_DATA_DIR/ssh +SSH_DOMAIN = REPLACE_SERVER_NAME +; reverse-proxy passthrough β€” TLS terminated upstream +USE_PROXY_PROTOCOL = false +REDIRECT_OTHER_PORT = false +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[lfs] +STORAGE_TYPE = local +PATH = REPLACE_DATA_DIR/lfs +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[storage.repo-archive] +STORAGE_TYPE = local +PATH = REPLACE_DATA_DIR/repositories/archive +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[repository] +SCRIPT_TYPE = sh +ROOT = REPLACE_DATA_DIR/repositories/root +DEFAULT_BRANCH = main +DEFAULT_PRIVATE = false +MAX_CREATION_LIMIT = -1 +PREFERRED_LICENSES = MIT,Apache-2.0 +DISABLE_HTTP_GIT = false +USE_COMPAT_SSH_URI = false +DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages,repo.actions +DEFAULT_FORK_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages,repo.actions +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[repository.local] +LOCAL_COPY_PATH = REPLACE_DATA_DIR/repositories/local +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[repository.upload] +TEMP_PATH = REPLACE_DATA_DIR/repositories/uploads +FILE_MAX_SIZE = 100 +MAX_FILES = 10 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[repository.pull-request] +DEFAULT_MERGE_STYLE = merge +WORK_IN_PROGRESS_PREFIXES = WIP:,[WIP] +CLOSE_KEYWORDS = close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved +REOPEN_KEYWORDS = reopen,reopens,reopened +DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT = 50 +DEFAULT_MERGE_MESSAGE_SIZE = 5120 +DEFAULT_MERGE_MESSAGE_ALL_AUTHORS = false +DEFAULT_MERGE_MESSAGE_MAX_APPROVERS = 10 +ADD_CO_COMMITTER_TRAILERS = true +TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY = false +RETARGET_CHILDREN_ON_MERGE = true +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[repository.release] +ALLOWED_TYPES = +DEFAULT_PAGING_NUM = 20 +ADD_CO_COMMITTER_TRAILERS = true +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[repository.signing] +DEFAULT_TRUST_MODEL = collaboratorcommitter +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[packages] +ENABLED = true +CHUNKED_UPLOAD_PATH = REPLACE_DATA_DIR/tmp/package-upload +LIMIT_TOTAL_OWNER_COUNT = -1 +LIMIT_TOTAL_OWNER_SIZE = -1 +LIMIT_SIZE_ALPINE = -1 +LIMIT_SIZE_CARGO = -1 +LIMIT_SIZE_CHEF = -1 +LIMIT_SIZE_COMPOSER = -1 +LIMIT_SIZE_CONAN = -1 +LIMIT_SIZE_CONDA = -1 +LIMIT_SIZE_CONTAINER = -1 +LIMIT_SIZE_CRAN = -1 +LIMIT_SIZE_DEBIAN = -1 +LIMIT_SIZE_GENERIC = -1 +LIMIT_SIZE_GO = -1 +LIMIT_SIZE_HELM = -1 +LIMIT_SIZE_MAVEN = -1 +LIMIT_SIZE_NPM = -1 +LIMIT_SIZE_NUGET = -1 +LIMIT_SIZE_PUB = -1 +LIMIT_SIZE_PYPI = -1 +LIMIT_SIZE_RPM = -1 +LIMIT_SIZE_RUBYGEMS = -1 +LIMIT_SIZE_SWIFT = -1 +LIMIT_SIZE_VAGRANT = -1 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[attachment] +PATH = REPLACE_DATA_DIR/attachments +MAX_SIZE = 100 +MAX_FILES = 10 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[indexer] +ISSUE_INDEXER_PATH = REPLACE_DATA_DIR/indexers/issues.bleve +REPO_INDEXER_ENABLED = true +REPO_INDEXER_PATH = REPLACE_DATA_DIR/indexers/repos.bleve +REPO_INDEXER_INCLUDE = +REPO_INDEXER_EXCLUDE = vendor,node_modules +MAX_FILE_SIZE = 1048576 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[session] +PROVIDER = file +PROVIDER_CONFIG = REPLACE_DATA_DIR/sessions +COOKIE_SECURE = false +SAME_SITE = lax +SESSION_LIFE_TIME = 86400 +GC_INTERVAL_TIME = 86400 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[cache] +ADAPTER = memory +INTERVAL = 60 +HOST = +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[queue] +TYPE = channel +CONN_STR = +LENGTH = 100 +BATCH_LENGTH = 20 +WORKERS = 0 +MAX_WORKERS = 10 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[database] +DB_TYPE = REPLACE_SQL_TYPE +HOST = REPLACE_SQL_HOST +USER = REPLACE_SQL_USER +PASSWD = REPLACE_SQL_PASS +NAME = REPLACE_SQL_NAME +LOG_SQL = false +SCHEMA = +SSL_MODE = disable +CHARSET = utf8 +PATH = REPLACE_DATABASE_DIR/forgejo.db +MAX_IDLE_CONNS = 2 +MAX_OPEN_CONNS = 100 +CONN_MAX_LIFETIME = 3s +ITERATE_BUFFER_SIZE = 50 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[git] +MAX_GIT_DIFF_LINES = 1000 +MAX_GIT_DIFF_LINE_CHARACTERS = 5000 +MAX_GIT_DIFF_FILES = 100 +GC_ARGS = +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[git.timeout] +DEFAULT = 360 +MIGRATE = 600 +MIRROR = 300 +CLONE = 300 +PULL = 300 +GC = 60 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[picture] +AVATAR_UPLOAD_PATH = REPLACE_DATA_DIR/avatars/users +REPOSITORY_AVATAR_UPLOAD_PATH = REPLACE_DATA_DIR/avatars/repos +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[log] +MODE = console +LEVEL = warn +ROOT_PATH = REPLACE_LOG_DIR +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[security] +INSTALL_LOCK = true +SECRET_KEY = REPLACE_SECRET_KEY +INTERNAL_TOKEN = REPLACE_FORGEJO_INTERNAL_TOKEN +PASSWORD_HASH_ALGO = argon2 +MIN_PASSWORD_LENGTH = 8 +PASSWORD_COMPLEXITY = on +LOGIN_REMEMBER_DAYS = 1825 +COOKIE_USERNAME = git_REPLACE_RANDOM_COOKIE_KEY +COOKIE_SECURE = false +CSRF_COOKIE_HTTP_ONLY = true +REVERSE_PROXY_LIMIT = 1 +REVERSE_PROXY_TRUSTED_PROXIES = * +REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER +REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL +REVERSE_PROXY_AUTHENTICATION_FULL_NAME = X-WEBAUTH-FULLNAME +X_FRAME_OPTIONS = SAMEORIGIN +ALLOWED_HOST_LIST = * +; explicit: defaults to true starting Gitea 1.23 and deprecates ?token=/?access_token= +; auth entirely in 1.24 β€” pin false so query-token API calls keep authenticating on +; upgrade instead of silently downgrading to anonymous; migrate callers to +; "Authorization: token " when convenient +DISABLE_QUERY_AUTH_TOKEN = false +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[time] +DEFAULT_UI_LOCATION = REPLACE_TZ +FORMAT = RFC1123 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[api] +ENABLE_SWAGGER = true +MAX_RESPONSE_ITEMS = 500 +DEFAULT_PAGING_NUM = 50 +DEFAULT_GIT_TREES_PER_PAGE = 1000 +DEFAULT_MAX_BLOB_SIZE = 10485760 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[migration] +SKIP_TLS_VERIFY = true +ALLOWED_DOMAINS = +BLOCKED_DOMAINS = +ALLOW_LOCALNETWORKS = false +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[git.config] +http.sslVerify = false +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[webhook] +QUEUE_LENGTH = 1000 +DELIVER_TIMEOUT = 30 +SKIP_TLS_VERIFY = true +PAGING_NUM = 10 +PROXY_URL = +PROXY_HOSTS = +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[service] +DISABLE_REGISTRATION = false +REQUIRE_SIGNIN_VIEW = false +REGISTER_EMAIL_CONFIRM = REPLACE_FORGEJO_EMAIL_CONFIRM +ENABLE_NOTIFY_MAIL = REPLACE_FORGEJO_EMAIL_CONFIRM +ALLOW_ONLY_EXTERNAL_REGISTRATION = false +ENABLE_CAPTCHA = true +DEFAULT_KEEP_EMAIL_PRIVATE = true +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +DEFAULT_ENABLE_TIMETRACKING = true +NO_REPLY_ADDRESS = no-reply.REPLACE_SERVER_NAME +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[actions] +ENABLED = true +DEFAULT_ACTIONS_URL = github +ARTIFACT_RETENTION_DAYS = 90 +ZOMBIE_TASK_TIMEOUT = 10m +ENDLESS_TASK_TIMEOUT = 3h +ABANDONED_JOB_TIMEOUT = 24h +SKIP_WORKFLOW_STRINGS = [skip ci],[ci skip],[no ci],[skip actions],[actions skip] +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[actions.artifacts] +ENABLED = true +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[storage.actions_log] +STORAGE_TYPE = local +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[mailer] +ENABLED = REPLACE_FORGEJO_EMAIL_CONFIRM +SMTP_ADDR = REPLACE_EMAIL_RELAY +SMTP_PORT = 25 +FROM = REPLACE_SERVER_ADMIN +USER = +PASSWD = +PROTOCOL = +SEND_AS_PLAIN_TEXT = false +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[email.incoming] +ENABLED = false +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[notify.email] +ENABLED = REPLACE_FORGEJO_EMAIL_CONFIRM +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[openid] +ENABLE_OPENID_SIGNIN = true +ENABLE_OPENID_SIGNUP = true +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[oauth2] +ENABLED = true +JWT_SIGNING_ALGORITHM = RS256 +JWT_SECRET = REPLACE_FORGEJO_LFS_JWT_SECRET +ACCESS_TOKEN_EXPIRATION_TIME = 3600 +REFRESH_TOKEN_EXPIRATION_TIME = 730 +MAX_TOKEN_LENGTH = 32767 +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[oauth2_client] +REGISTER_EMAIL_CONFIRM = REPLACE_FORGEJO_EMAIL_CONFIRM +OPENID_CONNECT_SCOPES = +ENABLE_AUTO_REGISTRATION = true +USERNAME = nickname +UPDATE_AVATAR = false +ACCOUNT_LINKING = login +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[ui] +DEFAULT_THEME = forgejo-dark +SEARCH_REPO_DESCRIPTION = true +EXPLORE_PAGING_NUM = 20 +ISSUE_PAGING_NUM = 20 +FEED_MAX_COMMIT_NUM = 5 +GRAPH_MAX_COMMIT_NUM = 100 +CODE_COMMENT_LINES = 4 +REACTIONS = +1,-1,laugh,hooray,confused,heart,rocket,eyes +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[ui.meta] +AUTHOR = forgejo +DESCRIPTION = REPLACE_SERVER_SITE_TITLE +KEYWORDS = go,git,self-hosted,forgejo +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[ui.notification] +MIN_TIMEOUT = 10s +MAX_TIMEOUT = 60s +TIMEOUT_STEP = 10s +EVENT_SOURCE_UPDATE_TIME = 10s +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[metrics] +ENABLED = false +TOKEN = +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[cors] +ENABLED = true +SCHEME = REPLACE_SERVER_PROTO +ALLOW_DOMAIN = * +ALLOW_SUBDOMAIN = true +METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS +MAX_AGE = 10m +ALLOW_CREDENTIALS = true +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[project] +PROJECT_BOARD_BASIC_KANBAN_TYPE = To Do, In Progress, Done +PROJECT_BOARD_BUG_TRIAGE_TYPE = Needs Triage, High Priority, Low Priority, Closed +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[cron] +ENABLED = true +RUN_AT_START = false +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[cron.archive_cleanup] +ENABLED = true +RUN_AT_START = true +NO_SUCCESS_NOTICE = false +OLDER_THAN = 24h +SCHEDULE = @midnight +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[cron.update_mirrors] +ENABLED = true +RUN_AT_START = true +NO_SUCCESS_NOTICE = true +PULL_LIMIT = 100000 +PUSH_LIMIT = 100000 +SCHEDULE = @every 360m +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[cron.repo_health_check] +ENABLED = true +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +TIMEOUT = 60s +SCHEDULE = @midnight +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[cron.delete_inactive_accounts] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +OLDER_THAN = 168h +SCHEDULE = @annually +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[cron.delete_repo_archives] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +SCHEDULE = @annually +; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[cron.update_checker] +ENABLED = false +RUN_AT_START = false +ENABLE_SUCCESS_NOTICE = true +SCHEDULE = @every 168h +HTTP_ENDPOINT = https://dl.gitea.com/gitea/version.json diff --git a/rootfs/tmp/etc/ssh/sshd_config b/rootfs/tmp/etc/ssh/sshd_config new file mode 100644 index 0000000..403f0b4 --- /dev/null +++ b/rootfs/tmp/etc/ssh/sshd_config @@ -0,0 +1,35 @@ +Port 7833 +Protocol 2 + +AddressFamily any +ListenAddress 0.0.0.0 +ListenAddress :: + +LogLevel INFO + +HostKey REPLACE_SSH_CONF_DIR/ssh_host_ed25519_key +HostKey REPLACE_SSH_CONF_DIR/ssh_host_rsa_key +HostKey REPLACE_SSH_CONF_DIR/ssh_host_ecdsa_key + +AuthorizedKeysFile REPLACE_SSH_DATA_DIR/authorized_keys +AuthorizedPrincipalsFile REPLACE_SSH_DATA_DIR/authorized_principals +TrustedUserCAKeys REPLACE_SSH_DATA_DIR/forgejo-trusted-user-ca-keys.pem +CASignatureAlgorithms ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ecdsa-sha2-nistp256@openssh.com,ssh-ed25519,sk-ssh-ed25519@openssh.com,rsa-sha2-512,rsa-sha2-256,ssh-rsa + +UseDNS no +AllowAgentForwarding no +AllowTcpForwarding no +PrintMotd no + +PermitUserEnvironment yes +PermitRootLogin no +ChallengeResponseAuthentication no +PasswordAuthentication no +PermitEmptyPasswords no + +AllowUsers git + +Banner none +Subsystem sftp /usr/lib/ssh/sftp-server + +AcceptEnv GIT_PROTOCOL diff --git a/rootfs/usr/local/bin/copy b/rootfs/usr/local/bin/copy new file mode 100755 index 0000000..e9878d8 --- /dev/null +++ b/rootfs/usr/local/bin/copy @@ -0,0 +1,78 @@ +#!/usr/bin/env sh +# shellcheck shell=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202605051306-git +# @@Author : Jason Hempstead +# @@Contact : jason@casjaysdev.pro +# @@License : WTFPL +# @@ReadME : copy --help +# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments +# @@Created : Tuesday, May 05, 2026 13:06 EDT +# @@File : copy +# @@Description : copies a file and shows progress +# @@Changelog : Refactored for self-contained operation +# @@TODO : Better documentation +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : shell/sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +APPNAME="$(basename -- "$0" 2>/dev/null)" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# colorization +if [ -n "$NO_COLOR" ]; then + __printf_color() { printf '%b' "$1\n" | tr -d '\t' | sed '/^%b$/d;s,\x1B\[ 0-9;]*[a-zA-Z],,g'; } +else + __printf_color() { { [ -z "$2" ] || DEFAULT_COLOR=$2; } && printf "%b" "$(tput setaf "$DEFAULT_COLOR" 2>/dev/null)" "$1\n" "$(tput sgr0 2>/dev/null)"; } +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +__unlink() { [ -L "$1" ] && rm -f -- "$1" >/dev/null; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +# custom functions +__copy() { + exitCode=0 + if [ -d "$1" ]; then + __printf_color "Copying $1/* to $2/" + __unlink "$2" + mkdir -p "$2" + for f in "$1"/* "$1"/.[!.]* "$1"/..?*; do + [ -e "$f" ] || [ -L "$f" ] || continue + base=$(basename -- "$f") + __copy "$f" "$2/$base" || exitCode=$? + done + elif [ -f "$1" ] || [ -L "$1" ]; then + __printf_color "Copying $1 to $2" + __unlink "$2" + cp -Rf "$1" "$2" + exitCode=$? + fi + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Define variables +DEFAULT_COLOR="254" +COPY_EXIT_STATUS=0 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Main application +if [ $# -ne 2 ]; then + __printf_color "USAGE: $APPNAME to from" "1" >&2 + COPY_EXIT_STATUS=1 +elif [ ! -e "$1" ]; then + __printf_color "$1 does not exist" >&2 + COPY_EXIT_STATUS=2 +else + __printf_color "Copying $1 to $2" "4" + __copy "$1" "$2" >/dev/null + COPY_EXIT_STATUS=$? +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# End application +# - - - - - - - - - - - - - - - - - - - - - - - - - +# lets exit with code +# - - - - - - - - - - - - - - - - - - - - - - - - - +exit $COPY_EXIT_STATUS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh diff --git a/rootfs/usr/local/bin/entrypoint.sh b/rootfs/usr/local/bin/entrypoint.sh new file mode 100755 index 0000000..973800f --- /dev/null +++ b/rootfs/usr/local/bin/entrypoint.sh @@ -0,0 +1,702 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202609030524-git +# @@Author : Jason Hempstead +# @@Contact : jason@casjaysdev.pro +# @@License : WTFPL +# @@ReadME : entrypoint.sh --help +# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments +# @@Created : Friday, Jun 05, 2026 18:14 EDT +# @@File : entrypoint.sh +# @@Description : Entrypoint file for alpine +# @@Changelog : New script +# @@TODO : Better documentation +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : other/docker-entrypoint +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +VERSION="202609030524-git" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# run trap command on exit +trap 'retVal=$?;[ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ] && rm -Rf "$SERVICE_PID_FILE";exit $retVal' INT TERM +trap 'retVal=$?;[ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ] && rm -Rf "$SERVICE_PID_FILE";exit $retVal' SIGPWR 2>/dev/null || true +# - - - - - - - - - - - - - - - - - - - - - - - - - +# setup debugging - https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html +[ -f "/config/.debug" ] && [ -z "$DEBUGGER_OPTIONS" ] && export DEBUGGER_OPTIONS="$(<"/config/.debug")" || DEBUGGER_OPTIONS="${DEBUGGER_OPTIONS:-}" +if [ "$DEBUGGER" = "on" ] || [ -f "/config/.debug" ]; then + echo "Enabling debugging" + set -eo pipefail + [ -n "$DEBUGGER_OPTIONS" ] && set -"$DEBUGGER_OPTIONS" + export DEBUGGER="on" +else + set -eo pipefail +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +PATH="/usr/local/etc/docker/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set bash options +SCRIPT_FILE="$0" +CONTAINER_NAME="alpine" +SCRIPT_NAME="${SCRIPT_FILE##*/}" +CONTAINER_NAME="${ENV_CONTAINER_NAME:-$CONTAINER_NAME}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# remove whitespaces from beginning argument +while :; do [ "$1" = " " ] && shift 1 || break; done +# - - - - - - - - - - - - - - - - - - - - - - - - - +[ "$1" = "$SCRIPT_FILE" ] && shift 1 +[ "$1" = "$SCRIPT_NAME" ] && shift 1 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# import the functions file +if [ -f "/usr/local/etc/docker/functions/entrypoint.sh" ]; then + . "/usr/local/etc/docker/functions/entrypoint.sh" +else + echo "Can not load functions from /usr/local/etc/docker/functions/entrypoint.sh" + exit 1 +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +case "$1" in +# Help message +-h | --help) + shift 1 + echo "Docker container for $CONTAINER_NAME" + echo "Usage: $CONTAINER_NAME [help tail cron exec start init shell procs ports healthcheck backup command]" + echo "" + exit 0 + ;; +-*) + shift + ;; +esac +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Create the default env files +__create_env_file "/config/env/default.sh" "/root/env.sh" &>/dev/null +# - - - - - - - - - - - - - - - - - - - - - - - - - +# import variables from files +for set_env in "/root/env.sh" "/usr/local/etc/docker/env"/*.sh "/config/env"/*.sh; do + [ -f "$set_env" ] && . "$set_env" +done +unset set_env +# - - - - - - - - - - - - - - - - - - - - - - - - - +# User to use to launch service - IE: postgres +# normally root +RUNAS_USER="root" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set user and group from env +SERVICE_USER="${PUID:-$SERVICE_USER}" +SERVICE_GROUP="${PGID:-$SERVICE_GROUP}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set user and group ID +# set the user id +SERVICE_UID="${SERVICE_UID:-0}" +# set the group id +SERVICE_GID="${SERVICE_GID:-0}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# User and group in which the service switches to - IE: nginx,apache,mysql,postgres +#SERVICE_USER="${SERVICE_USER:-alpine}" # execute command as another user +#SERVICE_GROUP="${SERVICE_GROUP:-alpine}" # Set the service group +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Secondary ports +# specifiy other ports +SERVER_PORTS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Primary server port- will be added to server ports +# port : 80,443 +WEB_SERVER_PORT="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Healthcheck variables +# enable healthcheck [yes/no] +HEALTH_ENABLED="yes" +# comma separated list of processes for the healthcheck +SERVICES_LIST="tini" +# url endpoints: [http://localhost/health,http://localhost/test] +HEALTH_ENDPOINTS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Update path var +export PATH RUNAS_USER SERVICE_USER SERVICE_GROUP SERVICE_UID SERVICE_GID WWW_ROOT_DIR DATABASE_DIR +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom variables + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# show message +__run_message() { + + return +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +################## END OF CONFIGURATION ##################### +# Lets get containers ip address +IP4_ADDRESS="$(__get_ip4)" +IP6_ADDRESS="$(__get_ip6)" +CONTAINER_IP4_ADDRESS="${CONTAINER_IP4_ADDRESS:-$IP4_ADDRESS}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Startup variables +export INIT_DATE="${INIT_DATE:-$(date)}" +export CONTAINER_INIT="${CONTAINER_INIT:-no}" +export START_SERVICES="${START_SERVICES:-no}" +export ENTRYPOINT_MESSAGE="${ENTRYPOINT_MESSAGE:-yes}" +export ENTRYPOINT_FIRST_RUN="${ENTRYPOINT_FIRST_RUN:-yes}" +export DATA_DIR_INITIALIZED="${DATA_DIR_INITIALIZED:-no}" +export CONFIG_DIR_INITIALIZED="${CONFIG_DIR_INITIALIZED:-no}" +export CONTAINER_NAME="${ENV_CONTAINER_NAME:-$CONTAINER_NAME}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# System +export LANG="${LANG:-C.UTF-8}" +export LC_ALL="${LANG:-C.UTF-8}" +export TZ="${TZ:-${TIMEZONE:-America/New_York}}" +export HOSTNAME="$(hostname -s)" +export DOMAINNAME="$(hostname -d)" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Default directories +export SSL_DIR="${SSL_DIR:-/config/ssl}" +export SSL_CA="${SSL_CA:-/config/ssl/ca.crt}" +export SSL_KEY="${SSL_KEY:-/config/ssl/localhost.pem}" +export SSL_CERT="${SSL_CERT:-/config/ssl/localhost.crt}" +export LOCAL_BIN_DIR="${LOCAL_BIN_DIR:-/usr/local/bin}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Backup settings +export BACKUP_MAX_DAYS="${BACKUP_MAX_DAYS:-}" +export BACKUP_RUN_CRON="${BACKUP_RUN_CRON:-}" +export BACKUP_DIR="${BACKUP_DIR:-/data/backups}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional +export PHP_INI_DIR="${PHP_INI_DIR:-$(__find_php_ini)}" +export PHP_BIN_DIR="${PHP_BIN_DIR:-$(__find_php_bin)}" +export HTTPD_CONFIG_FILE="${HTTPD_CONFIG_FILE:-$(__find_httpd_conf)}" +export NGINX_CONFIG_FILE="${NGINX_CONFIG_FILE:-$(__find_nginx_conf)}" +export MYSQL_CONFIG_FILE="${MYSQL_CONFIG_FILE:-$(__find_mysql_conf)}" +export PGSQL_CONFIG_FILE="${PGSQL_CONFIG_FILE:-$(__find_pgsql_conf)}" +export MONGODB_CONFIG_FILE="${MONGODB_CONFIG_FILE:-$(__find_mongodb_conf)}" +export ENTRYPOINT_PID_FILE="${ENTRYPOINT_PID_FILE:-/run/.entrypoint.pid}" +export ENTRYPOINT_INIT_FILE="${ENTRYPOINT_INIT_FILE:-/config/.entrypoint.done}" +export ENTRYPOINT_DATA_INIT_FILE="${ENTRYPOINT_DATA_INIT_FILE:-/data/.docker_has_run}" +export ENTRYPOINT_CONFIG_INIT_FILE="${ENTRYPOINT_CONFIG_INIT_FILE:-/config/.docker_has_run}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +if [ -n "$CONTAINER_WEB_SERVER_WWW_REPO" ]; then + www_temp_dir="/tmp/git/${CONTAINER_WEB_SERVER_WWW_REPO##*/}" + rm -Rf "${WWW_ROOT_DIR:?}"/* "${www_temp_dir:?}"/* 2>/dev/null || true + mkdir -p "$WWW_ROOT_DIR" "$www_temp_dir" 2>/dev/null || true + git clone -q "$CONTAINER_WEB_SERVER_WWW_REPO" "$www_temp_dir" 2>/dev/null || true + rm -Rf "$www_temp_dir/.git" "$www_temp_dir"/.git* 2>/dev/null || true + rsync -ra "$www_temp_dir/" "$WWW_ROOT_DIR" --delete 2>/dev/null || true + rm -Rf "$www_temp_dir" 2>/dev/null || true +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# variables based on env/files +if [ -f "/config/enable/ssl" ]; then SSL_ENABLED="yes"; fi +if [ -f "/config/enable/ssh" ]; then SSH_ENABLED="yes"; fi +if [ "$WEB_SERVER_PORT" = "443" ]; then SSL_ENABLED="yes"; fi +if [ "$CONTAINER_WEB_SERVER_PROTOCOL" = "https" ]; then SSL_ENABLED="yes"; fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# export variables + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# is already Initialized +if [ -f "$ENTRYPOINT_DATA_INIT_FILE" ]; then + DATA_DIR_INITIALIZED="yes" +else + DATA_DIR_INITIALIZED="no" +fi +if [ -f "$ENTRYPOINT_CONFIG_INIT_FILE" ]; then + CONFIG_DIR_INITIALIZED="yes" +else + CONFIG_DIR_INITIALIZED="no" +fi +if [ -f "$ENTRYPOINT_PID_FILE" ] || [ -f "$ENTRYPOINT_INIT_FILE" ]; then + ENTRYPOINT_FIRST_RUN="no" +else + ENTRYPOINT_FIRST_RUN="yes" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# clean ENV_PORTS variables +ENV_PORTS="${ENV_PORTS//,/ }" +ENV_PORTS="${ENV_PORTS//\/*/}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# clean SERVER_PORTS variables +SERVER_PORTS="${SERVER_PORTS//,/ }" +SERVER_PORTS="${SERVER_PORTS//\/*/}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# clean WEB_SERVER_PORTS variables +WEB_SERVER_PORTS="${WEB_SERVER_PORT//,/ } ${ENV_WEB_SERVER_PORTS//,/ }" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# rewrite and merge variables +ENV_PORTS="$(__format_variables "$ENV_PORTS" || false)" +WEB_SERVER_PORTS="$(__format_variables "$WEB_SERVER_PORTS" || false)" +ENV_PORTS="$(__format_variables "$SERVER_PORTS" "$WEB_SERVER_PORTS" "$ENV_PORTS" "$SERVER_PORTS" || false)" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove the commas from env +HEALTH_ENDPOINTS="${HEALTH_ENDPOINTS//,/ }" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create required directories +mkdir -p "/run" 2>/dev/null || true +mkdir -p "/tmp" 2>/dev/null || true +mkdir -p "/root" 2>/dev/null || true +mkdir -p "/var/run" 2>/dev/null || true +mkdir -p "/var/tmp" 2>/dev/null || true +mkdir -p "/run/cron" 2>/dev/null || true +mkdir -p "/data/logs" 2>/dev/null || true +mkdir -p "/run/init.d" 2>/dev/null || true +mkdir -p "/config/enable" 2>/dev/null || true +mkdir -p "/config/secure" 2>/dev/null || true +mkdir -p "/config/env" 2>/dev/null || true +mkdir -p "/usr/local/etc/docker/exec" 2>/dev/null || true +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create required files +touch "/data/logs/start.log" 2>/dev/null || true +touch "/data/logs/entrypoint.log" 2>/dev/null || true +# - - - - - - - - - - - - - - - - - - - - - - - - - +# fix permissions +chmod -f 777 "/run" 2>/dev/null || true +chmod -f 777 "/tmp" 2>/dev/null || true +chmod -f 700 "/root" 2>/dev/null || true +chmod -f 777 "/var/run" 2>/dev/null || true +chmod -f 777 "/var/tmp" 2>/dev/null || true +chmod -f 777 "/run/cron" 2>/dev/null || true +chmod -f 777 "/data/logs" 2>/dev/null || true +chmod -f 777 "/run/init.d" 2>/dev/null || true +chmod -f 777 "/config/enable" 2>/dev/null || true +chmod -f 777 "/config/secure" 2>/dev/null || true +chmod -f 777 "/data/logs/entrypoint.log" 2>/dev/null || true +chmod -f 777 "/usr/local/etc/docker/exec" 2>/dev/null || true +# - - - - - - - - - - - - - - - - - - - - - - - - - +# lets ensure everyone can write to std* +if [ -f "/dev/stdin" ]; then + chmod -f 777 "/dev/stdin" 2>/dev/null || true +fi +if [ -f "/dev/stderr" ]; then + chmod -f 777 "/dev/stderr" 2>/dev/null || true +fi +if [ -f "/dev/stdout" ]; then + chmod -f 777 "/dev/stdout" 2>/dev/null || true +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +cat </dev/null | tee /etc/profile.d/locales.shadow /etc/profile.d/locales.sh >/dev/null 2>&1 || true +export LANG="\${LANG:-C.UTF-8}" +export LC_ALL="\${LANG:-C.UTF-8}" +export TZ="\${TZ:-\${TIMEZONE:-America/New_York}}" +EOF +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Create the backup dir +if [ -n "$BACKUP_DIR" ]; then + if [ ! -d "$BACKUP_DIR" ]; then + mkdir -p "$BACKUP_DIR" 2>/dev/null || true + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +if [ -f "$ENTRYPOINT_INIT_FILE" ]; then + ENTRYPOINT_MESSAGE="no" ENTRYPOINT_FIRST_RUN="no" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +if [ "$ENTRYPOINT_FIRST_RUN" != "no" ]; then + if [ "$CONFIG_DIR_INITIALIZED" = "no" ] || [ "$DATA_DIR_INITIALIZED" = "no" ]; then + if [ "$ENTRYPOINT_MESSAGE" = "yes" ]; then + echo "Executing entrypoint script for alpine" + fi + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # Set reusable variables + if [ -w "/etc" ] && [ ! -f "/etc/hosts" ]; then + UPDATE_FILE_HOSTS="yes" + touch "/etc/hosts" + elif [ -w "/etc/hosts" ]; then + UPDATE_FILE_HOSTS="yes" + touch "/etc/hosts" + fi + if [ -w "/etc" ] && [ ! -f "/etc/timezone" ]; then + UPDATE_FILE_TZ="yes" + touch "/etc/timezone" + elif [ -w "/etc/timezone" ]; then + UPDATE_FILE_TZ="yes" + touch "/etc/timezone" + fi + if [ -w "/etc" ] && [ ! -f "/etc/resolv.conf" ]; then + UPDATE_FILE_RESOLV="yes" + touch "/etc/resolv.conf" + elif [ -w "/etc/resolv.conf" ]; then + UPDATE_FILE_RESOLV="yes" + touch "/etc/resolv.conf" + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # Set timezone + if [ -n "$TZ" ] && [ "$UPDATE_FILE_TZ" = "yes" ]; then + echo "$TZ" >"/etc/timezone" 2>/dev/null || true + fi + if [ -f "/usr/share/zoneinfo/$TZ" ] && [ "$UPDATE_FILE_TZ" = "yes" ]; then + ln -sf "/usr/share/zoneinfo/$TZ" "/etc/localtime" 2>/dev/null || true + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # if ipv6 add it to /etc/hosts + if [ "$UPDATE_FILE_HOSTS" = "yes" ]; then + echo "# known hostname mappings" >"/etc/hosts" 2>/dev/null || true + if [ -n "$(ip a 2>/dev/null | grep -- 'inet6.*::' || ifconfig 2>/dev/null | grep -- 'inet6.*::')" ]; then + __printf_space "40" "::1" "localhost" >>"/etc/hosts" 2>/dev/null || true + __printf_space "40" "127.0.0.1" "localhost" >>"/etc/hosts" 2>/dev/null || true + else + __printf_space "40" "127.0.0.1" "localhost" >>"/etc/hosts" 2>/dev/null || true + fi + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # add .internal domain + if [ "$UPDATE_FILE_HOSTS" = "yes" ] && [ -n "$HOSTNAME" ]; then + if ! __grep_test " $HOSTNAME" "/etc/hosts"; then + __printf_space "40" "${CONTAINER_IP4_ADDRESS:-127.0.0.1}" "$HOSTNAME" >>"/etc/hosts" 2>/dev/null || true + fi + if ! __grep_test " ${HOSTNAME%%.*}.internal" "/etc/hosts"; then + __printf_space "40" "${CONTAINER_IP4_ADDRESS:-127.0.0.1}" "${HOSTNAME%%.*}.internal" >>"/etc/hosts" 2>/dev/null || true + fi + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # add domainname + if [ "$UPDATE_FILE_HOSTS" = "yes" ] && [ "$DOMAINNAME" != "internal" ] && [ -n "$DOMAINNAME" ] && [ "$HOSTNAME.$DOMAINNAME" != "$DOMAINNAME" ]; then + if ! __grep_test " ${HOSTNAME%%.*}.$DOMAINNAME" "/etc/hosts"; then + __printf_space "40" "${CONTAINER_IP4_ADDRESS:-127.0.0.1}" "${HOSTNAME%%.*}.$DOMAINNAME" >>"/etc/hosts" 2>/dev/null || true + fi + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # Set containers hostname + if [ -n "$HOSTNAME" ] && [ "$UPDATE_FILE_HOSTS" = "yes" ]; then + echo "$HOSTNAME" >"/etc/hostname" 2>/dev/null || true + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + if [ -f "/etc/hostname" ]; then + if command -v hostname &>/dev/null; then + hostname -F "/etc/hostname" 2>/dev/null || true + else + HOSTNAME="$(<"/etc/hostname")" 2>/dev/null || true + fi + export HOSTNAME + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # import hosts file into container + if [ -f "/usr/local/etc/hosts" ] && [ "$UPDATE_FILE_HOSTS" = "yes" ]; then + grep -vF -- "$HOSTNAME" "/usr/local/etc/hosts" 2>/dev/null >>"/etc/hosts" || true + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # import resolv.conf file into container + if [ "$CUSTOM_DNS" != "yes" ] && [ -f "/usr/local/etc/resolv.conf" ] && [ "$UPDATE_FILE_RESOLV" = "yes" ]; then + cp -f "/usr/local/etc/resolv.conf" "/etc/resolv.conf" 2>/dev/null || true + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + if [ -n "$HOME" ] && [ -d "/usr/local/etc/skel" ]; then + if [ -d "$HOME" ]; then + cp -Rf "/usr/local/etc/skel/." "$HOME/" 2>/dev/null || true + fi + fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Delete any .gitkeep files (bash * does not match dotfiles by default, +# so the explicit /.gitkeep path is required at each depth) +if [ -d "/data" ]; then + rm -Rf "/data/.gitkeep" "/data"/*/.gitkeep 2>/dev/null || true +fi +if [ -d "/config" ]; then + rm -Rf "/config/.gitkeep" "/config"/*/.gitkeep 2>/dev/null || true +fi +if [ -f "/usr/local/bin/.gitkeep" ]; then + rm -Rf "/usr/local/bin/.gitkeep" 2>/dev/null || true +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Only run initialization on first run or when directories are not initialized +if [ "$ENTRYPOINT_FIRST_RUN" != "no" ] || [ "$CONFIG_DIR_INITIALIZED" = "no" ] || [ "$DATA_DIR_INITIALIZED" = "no" ]; then + # - - - - - - - - - - - - - - - - - - - - - - - - - + # Setup bin directory - /config/bin > /usr/local/bin + __initialize_custom_bin_dir + # - - - - - - - - - - - - - - - - - - - - - - - - - + # Initialize SSL certificates + __initialize_ssl_certs + # - - - - - - - - - - - - - - - - - - - - - - - - - + # Mark directories as initialized (only write if not already initialized) + if [ -d "/config" ] && [ "$CONFIG_DIR_INITIALIZED" = "no" ]; then + echo "Initialized on: $INIT_DATE" >"$ENTRYPOINT_CONFIG_INIT_FILE" 2>/dev/null || true + CONFIG_DIR_INITIALIZED="yes" + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + if [ -d "/data" ] && [ "$DATA_DIR_INITIALIZED" = "no" ]; then + echo "Initialized on: $INIT_DATE" >"$ENTRYPOINT_DATA_INIT_FILE" 2>/dev/null || true + DATA_DIR_INITIALIZED="yes" + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + if [ -d "/config" ] && [ ! -f "$ENTRYPOINT_INIT_FILE" ]; then + echo "Initialized on: $INIT_DATE" >"$ENTRYPOINT_INIT_FILE" 2>/dev/null || true + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # setup the smtp server + __setup_mta + # - - - - - - - - - - - - - - - - - - - - - - - - - + ENTRYPOINT_FIRST_RUN="no" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# if no pid assume container restart - clean stale files on restart +if [ -f "$ENTRYPOINT_PID_FILE" ]; then + # Check if the PID in the file is still running. /run persists across + # `docker restart` (same container filesystem), but the PID namespace + # resets every restart, so a recorded PID can coincidentally be reused by + # an unrelated early-boot process in the new namespace. A bare `kill -0` + # would then wrongly treat this as "entrypoint already running" and skip + # __start_init_scripts entirely on a genuine restart, so also require the + # live process's own cmdline to actually be this entrypoint script. + entrypoint_pid=$(<"$ENTRYPOINT_PID_FILE") 2>/dev/null + if [ -n "$entrypoint_pid" ] && kill -0 "$entrypoint_pid" 2>/dev/null \ + && grep -q -- "entrypoint.sh" "/proc/$entrypoint_pid/cmdline" 2>/dev/null; then + # Process is still running, don't restart services + START_SERVICES="no" + touch "$ENTRYPOINT_PID_FILE" + else + # PID file exists but process is dead - this is a restart + START_SERVICES="yes" + # Clean any stale PID files on restart + rm -f /run/.start_init_scripts.pid /run/init.d/*.pid /run/*.pid 2>/dev/null || true + fi +else + START_SERVICES=yes + # Clean any stale PID files on first run + rm -f /run/.start_init_scripts.pid /run/init.d/*.pid /run/*.pid 2>/dev/null || true +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +[ "$ENTRYPOINT_MESSAGE" = "yes" ] && __printf_space "40" "The containers ip address is:" "$CONTAINER_IP4_ADDRESS" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Show configured listing processes +if [ "$ENTRYPOINT_MESSAGE" = "yes" ] && [ -n "$ENV_PORTS" ]; then + show_port="" + for port in $ENV_PORTS; do [ -n "$port" ] && show_port+="$(printf '%s ' "${port// /}") "; done + __printf_space "40" "The following ports are open:" "$show_port" + unset port show_port +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# execute init script +if [ -f "/tmp/init" ]; then sh "/tmp/init"; fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create user if needed +__create_service_user "$SERVICE_USER" "$SERVICE_GROUP" "${WORK_DIR:-/home/$SERVICE_USER}" "${SERVICE_UID:-}" "${SERVICE_GID:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Modify user if needed +__set_user_group_id $SERVICE_USER ${SERVICE_UID:-} ${SERVICE_GID:-} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Show message +__run_message +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Just start services +START_SERVICES="${START_SERVICES:-yes}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Determine if we should start services based on command +# Only skip service start for the 'init' command +SKIP_SERVICE_START="no" +[ "$1" = "init" ] && SKIP_SERVICE_START="yes" && CONTAINER_INIT="yes" +[ "$2" = "init" ] && SKIP_SERVICE_START="yes" && CONTAINER_INIT="yes" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Start all services if no pidfile and not skipping +# Start all services only when no command was given at all β€” an explicit +# command (exec, shell, tail, or an arbitrary program) must reach the case +# statement below instead of being swallowed into daemon/monitor mode, even +# on a first run where START_SERVICES is force-set to "yes" +if [ -z "$1" ]; then + if [ "$SKIP_SERVICE_START" = "no" ]; then + [ "$1" = "start" ] && shift 1 + [ "$1" = "all" ] && shift 1 + rm -Rf "/run"/*/*pid 2>/dev/null || true + echo "$$" >"$ENTRYPOINT_PID_FILE" + __start_init_scripts "/usr/local/etc/docker/init.d" + CONTAINER_INIT="${CONTAINER_INIT:-no}" + # Services started successfully - enter monitoring mode + __no_exit + exit $? + fi + START_SERVICES="no" +fi +export START_SERVICES CONTAINER_INIT ENTRYPOINT_PID_FILE +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Begin options +case "$1" in +init) + shift 1 + __log_info "Container has been initialized" + exit 0 + ;; +tail) + shift 1 + case "$1" in + null) + shift $# + tail -F "/dev/null" + ;; + app) + shift $# + tail -F /data/logs/*/*.log + ;; + -*) + tail "$@" + ;; + *) + tail -F "${@:-/dev/null}" + ;; + esac + ;; +logs) + shift 1 + case "$1" in + follow) + tail -Fq /data/logs/*/* + ;; + clean) + mapfile -t log_files < <(find "/data/logs" -type f 2>/dev/null) + for log in "${log_files[@]}"; do + __log_info "Clearing log file: $log" + printf '' >"$log" + done + ;; + *) + echo "Usage: logs [follow,clean]" + exit 0 + ;; + esac + ;; +cron) + shift 1 + __cron "$@" & + __log_info "Cron script is running with PID: $!" + exit 0 + ;; +# backup data and config dirs +backup) + shift 1 + __backup $BACKUP_MAX_DAYS $1 + exit $? + ;; +# Docker healthcheck +healthcheck) + shift 1 + case "$1" in + init | test) + exit 0 + ;; + *) + arguments="$*" + healthStatus=0 + healthEnabled="${HEALTH_ENABLED:-}" + healthPorts="${WEB_SERVER_PORTS:-}" + healthEndPoints="${HEALTH_ENDPOINTS:-}" + SERVICES_LIST="${arguments:-$SERVICES_LIST}" + services="${SERVICES_LIST//,/ }" + healthMessage="Everything seems to be running" + [ "$healthEnabled" = "yes" ] || exit 0 + if [ -d "/run/healthcheck" ] && ! __is_dir_empty "/run/healthcheck"; then + for service in /run/healthcheck/*; do + name="${service##*/}" + services+="$name " + done + fi + services="$(printf '%s\n' $services | sort -u | grep -v -- '^$')" + for proc in $services; do + if [ -n "$proc" ]; then + if ! __pgrep "$proc"; then + echo "$proc is not running" >&2 + healthStatus=$((healthStatus + 1)) + fi + fi + done + for port in $healthPorts; do + if command -v netstat &>/dev/null && [ -n "$port" ]; then + if ! netstat -taupln | grep -q -- ":$port "; then + echo "$port isn't open" >&2 + healthStatus=$((healthStatus + 1)) + fi + fi + done + for endpoint in $healthEndPoints; do + if [ -n "$endpoint" ]; then + if ! __curl "$endpoint"; then + echo "Can not connect to $endpoint" >&2 + healthStatus=$((healthStatus + 1)) + fi + fi + done + [ "$healthStatus" -eq 0 ] || healthMessage="Errors reported see: docker logs --follow $CONTAINER_NAME" + [ -n "$healthMessage" ] && echo "$healthMessage" + exit $healthStatus + ;; + esac + ;; + # show open ports +ports) + shift 1 + ports="$(__netstat -taupln 2>/dev/null | awk '{ split($4, a, ":"); if (a[2] != "") print a[2] }' | sort -uV)" + [ -n "$ports" ] && printf '%s\n%s\n' "The following are servers:" "$ports" | tr '\n' ' ' + exit $? + ;; + # show running processes +procs) + shift 1 + ps="$(__ps axco command 2>/dev/null | grep -vE -- '^(COMMAND|grep|ps)$' | sort -u)" + [ -n "$ps" ] && printf '%s\n%s\n' "Found the following processes" "$ps" | tr '\n' ' ' + exit $? + ;; +# Launch shell +# Launch shell β€” do not shift here: "sh -c 'cmd'" / "bash -c 'cmd'" needs the +# interpreter name kept as argv[0] for __exec_command's `exec "$@"` to work; +# shifting it away turned "sh -c 'cmd'" into `exec -c cmd` (command not found) +*/bin/sh | */bin/bash | bash | sh) + __exec_command "${@:-/bin/bash -l}" + exit $? + ;; +# "shell" is a keyword, not a real interpreter β€” it must be shifted away, and +# any remaining args need "sh" prepended so __exec_command's `exec "$@"` gets +# a real interpreter instead of trying to exec "-c" as a program +shell) + shift 1 + if [ $# -eq 0 ]; then + __exec_command + else + __exec_command sh "$@" + fi + exit $? + ;; +# execute commands +exec) + shift 1 + __exec_command "${@:-echo "No commands given"}" + exit $? + ;; +# show/start init scripts +start) + shift 1 + export PATH="/usr/local/etc/docker/init.d:$PATH" + if [ $# -eq 0 ]; then + scripts="$(ls -A "/usr/local/etc/docker/init.d")" + [ -n "$scripts" ] && echo "$scripts" || echo "No scripts found in: /usr/local/etc/docker/init.d" + exit 0 + elif [ "$1" = "all" ]; then + shift $# + if [ "$START_SERVICES" = "yes" ]; then + echo "$$" >"$ENTRYPOINT_PID_FILE" + __start_init_scripts "/usr/local/etc/docker/init.d" + __no_exit + elif [ -f "/usr/local/etc/docker/init.d/$1" ]; then + eval "/usr/local/etc/docker/init.d/$1" & + __no_exit + fi + fi + ;; +# Execute primary command +*) + if [ $# -eq 0 ]; then + if [ ! -f "$ENTRYPOINT_PID_FILE" ]; then + echo "$$" >"$ENTRYPOINT_PID_FILE" + [ "$START_SERVICES" = "no" ] && [ "$CONTAINER_INIT" = "yes" ] || __start_init_scripts "/usr/local/etc/docker/init.d" + fi + __no_exit + else + __exec_command "$@" + fi + exit $? + ;; +esac +# - - - - - - - - - - - - - - - - - - - - - - - - - +# end of entrypoint +# - - - - - - - - - - - - - - - - - - - - - - - - - + +# ex: ts=2 sw=2 et filetype=sh diff --git a/rootfs/usr/local/bin/healthcheck b/rootfs/usr/local/bin/healthcheck new file mode 100755 index 0000000..8dab380 --- /dev/null +++ b/rootfs/usr/local/bin/healthcheck @@ -0,0 +1,249 @@ +#!/usr/bin/env sh +# shellcheck shell=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202605051654-git +# @@Author : Jason Hempstead +# @@Contact : jason@casjaysdev.pro +# @@License : WTFPL +# @@ReadME : healthcheck --help +# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments +# @@Created : Tuesday, May 05, 2026 16:54 EDT +# @@File : healthcheck +# @@Description : Docker container healthcheck β€” HTTP/TCP/process/file checks +# @@Changelog : Rewrote as a real Docker HEALTHCHECK probe +# @@TODO : Better documentation +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : shell/sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +APPNAME="$(basename -- "$0" 2>/dev/null)" +VERSION="202605051654-git" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Defaults (env vars override built-ins, CLI flags override env vars) +HEALTHCHECK_URL="${HEALTHCHECK_URL:-}" +HEALTHCHECK_HTTP_STATUS="${HEALTHCHECK_HTTP_STATUS:-2,3}" +HEALTHCHECK_HOST="${HEALTHCHECK_HOST:-127.0.0.1}" +HEALTHCHECK_PORT="${HEALTHCHECK_PORT:-}" +HEALTHCHECK_PROCESS="${HEALTHCHECK_PROCESS:-}" +HEALTHCHECK_FILE="${HEALTHCHECK_FILE:-}" +HEALTHCHECK_FILE_MAX_AGE="${HEALTHCHECK_FILE_MAX_AGE:-}" +HEALTHCHECK_TIMEOUT="${HEALTHCHECK_TIMEOUT:-5}" +HEALTHCHECK_VERBOSE="${HEALTHCHECK_VERBOSE:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +__cmd_exists() { command -v "$1" >/dev/null 2>&1; } +__log() { [ -n "$HEALTHCHECK_VERBOSE" ] && printf '%s\n' "$*" >&2; return 0; } +__fail() { printf 'UNHEALTHY: %s\n' "$*" >&2; exit 1; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__usage() { + cat <&2; __usage >&2; exit 1 ;; + *) printf 'Unexpected argument: %s\n' "$1" >&2; exit 1 ;; + esac +done +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Individual checks β€” each prints why it failed and exits 1 on failure +__trim() { printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'; } + +__check_one_http() { + url="$1"; accepted="$2"; timeout="$3" + if __cmd_exists curl; then + code="$(curl -ksSL -o /dev/null -w '%{http_code}' --max-time "$timeout" "$url" 2>/dev/null)" \ + || __fail "HTTP request to $url failed (curl error)" + elif __cmd_exists wget; then + code="$(wget -q -S --spider --timeout="$timeout" --tries=1 "$url" 2>&1 \ + | awk '/^ HTTP\// {c=$2} END {print c+0}')" + [ "$code" -gt 0 ] 2>/dev/null || __fail "HTTP request to $url failed (wget error)" + else + __fail "HTTP check requires curl or wget" + fi + IFS=',' + for prefix in $accepted; do + case "$code" in + "$prefix"*) unset IFS; __log "HTTP ok: $url -> $code"; return 0 ;; + esac + done + unset IFS + __fail "HTTP $url returned $code (expected prefix in: $accepted)" +} + +__check_http() { + urls="$1"; accepted="$2"; timeout="$3" + __log "HTTP: urls=$urls (timeout=${timeout}s, accept=${accepted})" + IFS=',' + for u in $urls; do + unset IFS + u="$(__trim "$u")" + [ -n "$u" ] || { IFS=','; continue; } + __check_one_http "$u" "$accepted" "$timeout" + IFS=',' + done + unset IFS + return 0 +} + +__check_one_tcp() { + host="$1"; port="$2"; timeout="$3" + if __cmd_exists nc; then + nc -z -w "$timeout" "$host" "$port" >/dev/null 2>&1 && { __log "TCP ok: $host:$port"; return 0; } + fi + if __cmd_exists ncat; then + ncat -z -w "${timeout}s" "$host" "$port" >/dev/null 2>&1 && { __log "TCP ok (ncat): $host:$port"; return 0; } + fi + # Last resort: bash /dev/tcp (only if bash is available; sh-only systems skip) + if __cmd_exists bash; then + bash -c "exec 3<>/dev/tcp/$host/$port" >/dev/null 2>&1 && { __log "TCP ok (bash): $host:$port"; return 0; } + fi + return 1 +} + +__check_tcp() { + host="$1"; ports="$2"; timeout="$3" + __log "TCP: host=$host ports=$ports (timeout=${timeout}s)" + IFS=',' + for p in $ports; do + unset IFS + p="$(__trim "$p")" + [ -n "$p" ] || { IFS=','; continue; } + __check_one_tcp "$host" "$p" "$timeout" || __fail "TCP $host:$p not reachable" + IFS=',' + done + unset IFS + return 0 +} + +__check_one_process() { + pattern="$1" + if __cmd_exists pgrep; then + # Match against process name (not full cmdline) so our own argv doesn't self-match + pgrep -- "$pattern" >/dev/null 2>&1 && return 0 + else + # Portable fallback: ps -o comm= prints just the command name + ps -e -o comm= 2>/dev/null | grep -v -e "^grep$" -e "^$APPNAME$" | grep -q -- "$pattern" && return 0 + fi + return 1 +} + +__check_process() { + patterns="$1" + __log "Process: patterns=$patterns" + IFS=',' + for p in $patterns; do + unset IFS + p="$(__trim "$p")" + [ -n "$p" ] || { IFS=','; continue; } + __check_one_process "$p" || __fail "Process not running: $p" + __log "Process ok: $p" + IFS=',' + done + unset IFS + return 0 +} + +__check_one_file() { + path="$1"; max_age="$2" + [ -e "$path" ] || __fail "File not found: $path" + if [ -n "$max_age" ]; then + now="$(date +%s)" + mtime="$(stat -c %Y "$path" 2>/dev/null || stat -f %m "$path" 2>/dev/null \ + || perl -e 'print((stat(shift))[9])' "$path" 2>/dev/null)" + [ -n "$mtime" ] || __fail "Cannot determine mtime of $path" + age=$(( now - mtime )) + [ "$age" -le "$max_age" ] || __fail "File $path is stale (age=${age}s, max=${max_age}s)" + fi + __log "File ok: $path" + return 0 +} + +__check_file() { + paths="$1"; max_age="$2" + __log "File: paths=$paths max_age=${max_age:-none}" + IFS=',' + for f in $paths; do + unset IFS + f="$(__trim "$f")" + [ -n "$f" ] || { IFS=','; continue; } + __check_one_file "$f" "$max_age" + IFS=',' + done + unset IFS + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Run checks +ran_any=0 +[ -n "$HEALTHCHECK_URL" ] && { __check_http "$HEALTHCHECK_URL" "$HEALTHCHECK_HTTP_STATUS" "$HEALTHCHECK_TIMEOUT"; ran_any=1; } +[ -n "$HEALTHCHECK_PORT" ] && { __check_tcp "$HEALTHCHECK_HOST" "$HEALTHCHECK_PORT" "$HEALTHCHECK_TIMEOUT"; ran_any=1; } +[ -n "$HEALTHCHECK_PROCESS" ] && { __check_process "$HEALTHCHECK_PROCESS"; ran_any=1; } +[ -n "$HEALTHCHECK_FILE" ] && { __check_file "$HEALTHCHECK_FILE" "$HEALTHCHECK_FILE_MAX_AGE"; ran_any=1; } + +[ "$ran_any" -eq 1 ] || __fail "no checks configured (set HEALTHCHECK_URL/PORT/PROCESS/FILE or pass --url/--port/--process/--file)" + +__log "All checks passed" +exit 0 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh diff --git a/rootfs/usr/local/bin/pkmgr b/rootfs/usr/local/bin/pkmgr new file mode 100755 index 0000000..bebefdb --- /dev/null +++ b/rootfs/usr/local/bin/pkmgr @@ -0,0 +1,143 @@ +#!/usr/bin/env sh +# shellcheck shell=sh +# shellcheck disable=SC2016 +# - - - - - - - - - - - - - - - - - - - - - - - - - +USER_UID="$(id -u)" +USER_GID="$(id -g)" +# - - - - - - - - - - - - - - - - - - - - - - - - - +if [ -x "$(command -v apt 2>/dev/null)" ]; then + export DEBIAN_FRONTEND=noninteractive + pkmgr_cmd="apt" + pkmgr_clean_cmd="$pkmgr_cmd clean" + pkmgr_mkcache_cmd="$pkmgr_cmd update" + pkmgr_update_cmd="$pkmgr_cmd upgrade -yy" + pkmgr_install_cmd="$pkmgr_cmd install -yy $PKMGR_OPTS" + pkmgr_install_post="$pkmgr_cmd --fix-broken install" +elif [ -x "$(command -v apt-get 2>/dev/null)" ]; then + export DEBIAN_FRONTEND=noninteractive + pkmgr_cmd="apt-get" + pkmgr_clean_cmd="$pkmgr_cmd clean" + pkmgr_mkcache_cmd="$pkmgr_cmd update" + pkmgr_update_cmd="$pkmgr_cmd upgrade -yy" + pkmgr_install_cmd="$pkmgr_cmd install -yy $PKMGR_OPTS" + pkmgr_install_post="$pkmgr_cmd --fix-broken install" +elif [ -x "$(command -v dnf 2>/dev/null)" ]; then + pkmgr_cmd="dnf" + pkmgr_clean_cmd="$pkmgr_cmd clean all" + pkmgr_mkcache_cmd="$pkmgr_cmd makecache" + pkmgr_update_cmd="$pkmgr_cmd update -y --skip-broken $PKMGR_OPTS" + pkmgr_install_cmd="$pkmgr_cmd install -y --skip-broken $PKMGR_OPTS" +elif [ -x "$(command -v yum 2>/dev/null)" ]; then + pkmgr_cmd="yum" + pkmgr_clean_cmd="$pkmgr_cmd clean all" + pkmgr_mkcache_cmd="$pkmgr_cmd makecache" + pkmgr_update_cmd="$pkmgr_cmd update -y --skip-broken $PKMGR_OPTS" + pkmgr_install_cmd="$pkmgr_cmd install -y --skip-broken $PKMGR_OPTS" +elif [ -n "$(command -v pacman 2>/dev/null)" ]; then + pkmgr_cmd="pacman" + pkmgr_mkcache_cmd="true" + pkmgr_clean_cmd="$pkmgr_cmd -Scc --noconfirm" + pkmgr_update_cmd="$pkmgr_cmd -Syyu --noconfirm $PKMGR_OPTS" + pkmgr_install_cmd="$pkmgr_cmd -Syy --noconfirm $PKMGR_OPTS" +elif [ -x "$(command -v apk 2>/dev/null)" ]; then + pkmgr_cmd="apk" + pkmgr_mkcache_cmd="true" + pkmgr_clean_cmd="$pkmgr_cmd cache clean" + pkmgr_update_cmd="$pkmgr_cmd -U upgrade --no-cache $PKMGR_OPTS" + pkmgr_install_cmd="$pkmgr_cmd add --no-cache $PKMGR_OPTS" +elif [ -x "$(command -v zypper 2>/dev/null)" ]; then + pkmgr_cmd="zypper" + pkmgr_mkcache_cmd="true" + pkmgr_clean_cmd="$pkmgr_cmd clean --all" + pkmgr_update_cmd="$pkmgr_cmd update -y $PKMGR_OPTS" + pkmgr_install_cmd="$pkmgr_cmd install -y $PKMGR_OPTS" +else + pkmgr_cmd="true" + pkmgr_mkcache_cmd="$pkmgr_cmd" + pkmgr_clean_cmd="$pkmgr_cmd" + pkmgr_update_cmd="$pkmgr_cmd" + pkmgr_install_cmd="$pkmgr_cmd" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +if [ -f "/config/pkmgr/settings.conf" ]; then + . "/config/pkmgr/settings.conf" +elif [ -f "/etc/pkmgr/settings.conf" ]; then + . "/etc/pkmgr/settings.conf" +else + mkdir -p "/config/pkmgr" + cat <"/config/pkmgr/settings.conf" +pkmgr_cmd="$pkmgr_cmd" +pkmgr_clean_cmd="$pkmgr_clean_cmd" +pkmgr_update_cmd="$pkmgr_update_cmd" +pkmgr_install_cmd="$pkmgr_install_cmd" +pkmgr_mkcache_cmd="$pkmgr_mkcache_cmd" +EEOF +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +[ -n "$pkmgr_cmd" ] || { echo "Can not determine the package manager" && exit 1; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +case "$1" in +pip) + shift 1 + pip_bin="$(command -v python3 2>/dev/null || command -v python2 2>/dev/null || command -v python 2>/dev/null || echo "")" + py_version="$($pip_bin --version | sed 's|[pP]ython ||g' | awk -F '.' '{print $1$2}' | grep '[0-9]' || echo "0")" + [ "$py_version" -gt "310" ] && pip_opts="--break-system-packages " || pip_opts="" + case "$1" in + install) + shift 1 + pkg_list="$*" + for pkg in $pkg_list; do + $pip_bin -m pip $pip_opts "$pkg" + done + ;; + *) + for pkg in "$@"; do + $pip_bin -m pip "$pkg" + done + ;; + esac + exit + ;; +install) + shift 1 + [ -n "$1" ] || exit 0 + [ "$USER_UID" -eq 0 ] || [ "$USER" = "root" ] || pkmgr_install_cmd="sudo $pkmgr_install_cmd" + if [ -f "$1" ]; then + install_list="$(tr '\n' ' ' < "$1")" + else + install_list="$*" + fi + for pkg in $install_list;do + echo "installing packages command: $pkmgr_install_cmd $pkg" + $pkmgr_install_cmd $pkg + if [ -n "$pkmgr_install_post" ]; then eval $pkmgr_install_post; fi + done + exit + ;; +update | upgrade) + shift $# + [ "$USER_UID" -eq 0 ] || [ "$USER" = "root" ] || pkmgr_update_cmd="sudo $pkmgr_install_cmd" + echo "Updating packages command: $pkmgr_update_cmd" + $pkmgr_mkcache_cmd + $pkmgr_update_cmd + exit $? + ;; +clean) + shift $# + [ -n "$1" ] || exit 0 + [ "$USER_UID" -eq 0 ] || [ "$USER" = "root" ] || pkmgr_clean_cmd="sudo $pkmgr_clean_cmd" + echo "Cleaning package cache: $pkmgr_clean_cmd" + $pkmgr_clean_cmd + exit $? + ;; +*) + [ -n "$1" ] || exit 0 + [ "$USER_UID" -eq 0 ] || [ "$USER" = "root" ] || pkmgr_cmd="sudo $pkmgr_cmd" + echo "executing packages command: $pkmgr_cmd $*" + $pkmgr_cmd "$@" + exit $? + ;; +esac +# - - - - - - - - - - - - - - - - - - - - - - - - - +# end + diff --git a/rootfs/usr/local/bin/start-runners b/rootfs/usr/local/bin/start-runners new file mode 100755 index 0000000..ee5569a --- /dev/null +++ b/rootfs/usr/local/bin/start-runners @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202609030524-git +# @@Author : Jason Hempstead +# @@Contact : jason@casjaysdev.pro +# @@License : LICENSE.md +# @@ReadME : start-runners --help +# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments +# @@Created : Friday, Jun 05, 2026 18:14 EDT +# @@File : start-runners +# @@Description : Start act runners +# @@Changelog : New script +# @@TODO : Better documentation +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : shell/bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +VERSION="202609030524-git" +set -e +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +trap 'retVal=$?; emoji=$( [ -z "$NO_COLOR" ] && echo "❌ " || echo "" ); \ +echo "${emoji}Fatal error occurred: Exit code $retVal at line $LINENO in command: $BASH_COMMAND"; \ +kill -TERM 1' ERR +trap 'retVal=$?;if [ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ]; then rm -Rf "$SERVICE_PID_FILE"; fi;exit $retVal' SIGINT SIGTERM SIGPWR +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Function to __log messages with timestamp +__log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >>"$RUNNERS_LOG_DIR/runners" 2>&1; } +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Function to cleanup child processes on exit +__cleanup() { + __log "Shutting down runners..." + kill $(jobs -p) 2>/dev/null || true + wait + __log "All runners stopped" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Set up signal handling +trap __cleanup SIGTERM SIGINT +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Validate required environment variables +if [ -n "$SERVER_ADDRESS" ]; then + if [[ "$SERVER_ADDRESS" != *"://"* ]]; then + SERVER_ADDRESS="http://$SERVER_ADDRESS" + fi +else + SERVER_ADDRESS=http://$HOSTNAME +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if [ -z "$SERVER_TOKEN" ]; then + __log "ERROR: SERVER_TOKEN environment variable is required" + exit 1 +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if [ -z "$RUNNER_LABELS" ]; then + _default_runner_labels=( + "linux:host" + "node14:docker://node:14" + "node16:docker://node:16" + "node18:docker://node:18" + "node20:docker://node:20" + "node22:docker://node:22" + "node:docker://node:latest" + "perl:docker://perl:latest" + "ruby:docker://ruby:latest" + "python:docker://python:latest" + "python3:docker://python:latest" + "php7:docker://casjaysdevdocker/php:7" + "php8:docker://casjaysdevdocker/php:8" + "php:docker://casjaysdevdocker/php:latest" + "alpine:docker://casjaysdev/alpine:latest" + "debian:docker://casjaysdev/debian:latest" + "ubuntu:docker://casjaysdev/ubuntu:latest" + "rhel:docker://casjaysdev/almalinux:latest" + "redhat:docker://casjaysdev/almalinux:latest" + "almalinux:docker://casjaysdev/almalinux:latest" + "act_runner:docker://catthehacker/ubuntu:full-latest" + "ubuntu-latest:docker://catthehacker/ubuntu:full-latest" + ) + RUNNER_LABELS="$( + IFS=, + echo "${_default_runner_labels[*]}" + )" + unset _default_runner_labels +fi +# Determine number of runners to start +RUNNERS_START=${RUNNERS_START:-1} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Validate RUNNERS_START is a positive integer +if ! [[ "$RUNNERS_START" =~ ^[0-9]+$ ]] || [ "$RUNNERS_START" -lt 1 ]; then + __log "WARNING: Invalid RUNNERS_START value '$RUNNERS_START', defaulting to 1" + RUNNERS_START=1 +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +__log "Starting $RUNNERS_START act_runner instance(s)" +__log "Server Address: $SERVER_ADDRESS" +__log "Runner Name Prefix: ${RUNNER_NAME_PREFIX:-runner}" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Shared cache config so runner-N instances use the same cache-server +# started by zz-act_runner.sh instead of an unshared per-process cache. +RUNNERS_CACHE_CONFIG="" +if [ -n "$RUNNER_CACHE_HOST" ] && [ -n "$RUNNER_CACHE_PORT" ] && [ -n "$RUNNER_CACHE_SECRET" ]; then + RUNNERS_CACHE_CONFIG="/config/act_runner/runners-cache.yaml" + cat <"$RUNNERS_CACHE_CONFIG" +cache: + enabled: true + external_server: 'http://${RUNNER_CACHE_HOST}:${RUNNER_CACHE_PORT}/' + external_secret: '${RUNNER_CACHE_SECRET}' +EOF +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Register a single runner synchronously (no daemon start) +__register_runner() { + local runner_id=$1 + local runner_name="${RUNNER_NAME_PREFIX:-runner}-${runner_id}" + local runner_dir="/config/act_runner/reg/${runner_name}" + local config_args=() + + mkdir -p "$runner_dir" + [ -d "$runner_dir" ] && cd "$runner_dir" || return 1 + [ -n "$RUNNERS_CACHE_CONFIG" ] && config_args=(--config "$RUNNERS_CACHE_CONFIG") + + if [ ! -f "$runner_dir/.runner" ]; then + __log "Registering runner: $runner_name (ID: $runner_id)" + act_runner register "${config_args[@]}" --instance "$SERVER_ADDRESS" --token "$SERVER_TOKEN" --name "$runner_name" --labels "$RUNNER_LABELS" --no-interactive + if [ $? -ne 0 ]; then + __log "ERROR: Failed to register runner $runner_name" + return 1 + fi + fi + __log "Runner $runner_name registered" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Start daemon for a single runner (called in background after all are registered) +__start_runner_daemon() { + local runner_id=$1 + local runner_name="${RUNNER_NAME_PREFIX:-runner}-${runner_id}" + local runner_dir="/config/act_runner/reg/${runner_name}" + local config_args=() + + [ -d "$runner_dir" ] && cd "$runner_dir" || return 1 + [ -n "$RUNNERS_CACHE_CONFIG" ] && config_args=(--config "$RUNNERS_CACHE_CONFIG") + __log "Starting daemon for runner: $runner_name" + exec act_runner daemon "${config_args[@]}" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Phase 1: register all runners sequentially so IDs are assigned in order +for i in $(seq 1 $RUNNERS_START); do + __register_runner "$i" || { __log "Aborting: registration failed for runner-$i"; exit 1; } +done +unset i +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Phase 2: start all daemons in parallel +for i in $(seq 1 $RUNNERS_START); do + (__start_runner_daemon "$i") & +done +unset i +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +RUNNERS_ID="$(jobs -p | tr '\n' ' ')" +if [ -n "$RUNNERS_ID" ]; then + __log "All $RUNNERS_START runners started successfully" + __log "Process IDs: $RUNNERS_ID" + printf 'All %s runners started successfully: %s\n' "$RUNNERS_START" "$RUNNERS_ID" +else + __log "The runners have failed to start" + printf '%s\n' "The runners have failed to start" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Wait for all background processes +wait diff --git a/rootfs/usr/local/bin/symlink b/rootfs/usr/local/bin/symlink new file mode 100755 index 0000000..ac2db15 --- /dev/null +++ b/rootfs/usr/local/bin/symlink @@ -0,0 +1,77 @@ +#!/usr/bin/env sh +# shellcheck shell=sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202605051306-git +# @@Author : Jason Hempstead +# @@Contact : jason@casjaysdev.pro +# @@License : WTFPL +# @@ReadME : symlink --help +# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments +# @@Created : Tuesday, May 05, 2026 13:06 EDT +# @@File : symlink +# @@Description : +# @@Changelog : New script +# @@TODO : Better documentation +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : shell/sh +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +APPNAME="$(basename -- "$0" 2>/dev/null)" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# colorization +if [ -n "$NO_COLOR" ]; then + __printf_color() { printf '%b' "$1\n" | tr -d '\t' | sed '/^%b$/d;s,\x1B\[ 0-9;]*[a-zA-Z],,g'; } +else + __printf_color() { { [ -z "$2" ] || DEFAULT_COLOR=$2; } && printf "%b" "$(tput setaf "$DEFAULT_COLOR" 2>/dev/null)" "$1\n" "$(tput sgr0 2>/dev/null)"; } +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +__unlink() { [ -L "$1" ] && rm -f -- "$1" >/dev/null; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +# custom functions +__ln_sf() { + exitCode=0 + if [ -d "$1" ] && [ ! -L "$1" ]; then + __printf_color "symlinking contents of $1 into $2/" "4" + __unlink "$2" + mkdir -p "$2" + for f in "$1"/* "$1"/.[!.]* "$1"/..?*; do + [ -e "$f" ] || [ -L "$f" ] || continue + base=$(basename -- "$f") + __ln_sf "$f" "$2/$base" || exitCode=$? + done + else + __printf_color "symlinking $2 to $1" "4" + __unlink "$2" + ln -sf "$1" "$2" + exitCode=$? + fi + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Define variables +DEFAULT_COLOR="254" +SYMLINK_EXIT_STATUS=0 +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Main application +if [ $# -ne 2 ]; then + __printf_color "USAGE: $APPNAME from to" "2" >&2 + SYMLINK_EXIT_STATUS=1 +elif [ ! -e "$1" ]; then + __printf_color "$1 does not exist" >&2 + SYMLINK_EXIT_STATUS=2 +else + __ln_sf "$1" "$2" >/dev/null + SYMLINK_EXIT_STATUS=$? +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# End application +# - - - - - - - - - - - - - - - - - - - - - - - - - +# lets exit with code +# - - - - - - - - - - - - - - - - - - - - - - - - - +exit $SYMLINK_EXIT_STATUS +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ex: ts=2 sw=2 et filetype=sh diff --git a/rootfs/usr/local/etc/docker/functions/entrypoint.sh b/rootfs/usr/local/etc/docker/functions/entrypoint.sh new file mode 100644 index 0000000..21d1602 --- /dev/null +++ b/rootfs/usr/local/etc/docker/functions/entrypoint.sh @@ -0,0 +1,1713 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202609030524-git +# @@Author : Jason Hempstead +# @@Contact : git-admin@casjaysdev.pro +# @@License : LICENSE.md +# @@ReadME : docker-entrypoint --help +# @@Copyright : Copyright: (c) 2023 Jason Hempstead, Casjays Developments +# @@Created : Sunday, Sep 03, 2023 01:40 EDT +# @@File : docker-entrypoint +# @@Description : functions for my docker containers +# @@Changelog : fix __setup_mta calling __symlink with from/to swapped, which no-op'd the symlink and broke msmtp/ssmtp/postfix config on container start +# @@TODO : Refactor code +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : functions/docker-entrypoint +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +VERSION="202609030524-git" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# setup debugging - https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html +if [ -f "/config/.debug" ] && [ -z "$DEBUGGER_OPTIONS" ]; then + export DEBUGGER_OPTIONS="$(<"/config/.debug")" +fi +if [ "$DEBUGGER" = "on" ] || [ -f "/config/.debug" ]; then + set -eo pipefail + [ -n "$DEBUGGER_OPTIONS" ] && set -"$DEBUGGER_OPTIONS" + export DEBUGGER="on" +else + set -eo pipefail +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +__remove_extra_spaces() { sed -E 's/ +/ /g; s|^ ||'; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__log_debug() { + [ "$DEBUGGER" = "on" ] && echo "[DEBUG] $*" >&2 || true +} +__log_info() { + echo "[INFO] $*" +} +__log_warn() { + echo "[WARN] $*" >&2 +} +__log_error() { + echo "[ERROR] $*" >&2 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__printf_space() { + local pad=$(printf '%0.1s' " "{1..60}) + local padlength=$1 + local string1="$2" + local string2="$3" + local message + message+="$(printf '%s' "$string1") " + message+="$(printf '%*.*s' 0 $((padlength - ${#string1} - ${#string2})) "$pad") " + message+="$(printf '%s\n' "$string2") " + printf '%s\n' "$message" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__mkdir() { + if [ -n "$1" ]; then + if ! mkdir -p "$@" 2>/dev/null; then + [ "$DEBUGGER" = "on" ] && echo "Warning: Failed to create directory: $*" >&2 || true + return 1 + fi + fi + return 0 +} +__rm() { + if [ -n "$1" ] && [ -e "$1" ]; then + if ! rm -Rf "${1:?}" 2>/dev/null; then + [ "$DEBUGGER" = "on" ] && echo "Warning: Failed to remove: $1" >&2 || true + return 1 + fi + fi + return 0 +} +__grep_test() { grep -sh -- "$1" "$2" 2>/dev/null | grep -qwF -- "${3:-$1}"; } +__netstat() { + command -v netstat &>/dev/null || { + [ "$DEBUGGER" = "on" ] && echo "Warning: netstat command not found" >&2 || true + return 10 + } + netstat "$@" 2>/dev/null +} +__cd() { + [ -d "$1" ] || mkdir -p "$1" 2>/dev/null || return 1 + builtin cd "$1" || return 1 +} +__is_in_file() { [ -e "$2" ] && grep -Rsq -- "$1" "$2" 2>/dev/null; } +__curl() { curl -q -sfI --max-time 3 -k -o /dev/null "$@" 2>/dev/null || return 10; } +__find() { + local result + if [ -n "$2" ]; then + result=$(find "$1" -mindepth 1 -type "$2" 2>/dev/null) + else + result=$(find "$1" -mindepth 1 \( -type f -o -type d \) 2>/dev/null) + fi + [ -n "$result" ] || return 10 + printf '%s\n' "$result" +} +__pcheck() { + command -v pgrep &>/dev/null && pgrep -x "$1" &>/dev/null || return 10 +} +__file_exists_with_content() { [ -n "$1" ] && [ -f "$1" ] && [ -s "$1" ] || return 2; } +__sed() { sed -i "s|$1|$2|g" "$3" 2>/dev/null || return 1; } +__ps() { + command -v ps &>/dev/null || return 10 + ps "$@" 2>/dev/null | sed 's|:||g' | grep -Fw -- " ${1:-$SERVICE_NAME}$" || return 10 +} +__is_dir_empty() { + [ -n "$1" ] && [ -d "$1" ] || return 1 + [ -z "$(ls -A "$1" 2>/dev/null)" ] +} +__get_ip6() { + ip a 2>/dev/null | awk '/^[[:space:]]*inet6 / { + split($2, a, "/"); ip = a[1] + if (ip !~ /^::1$/ && ip !~ /^fe/) { print ip; exit } + }' +} +__get_ip4() { + local ip4 + ip4=$(ip a 2>/dev/null | awk '/^[[:space:]]*inet / { + split($2, a, "/"); ip = a[1] + if (ip !~ /^127\.0\.0/) { print ip; exit } + }') + echo "${ip4:-127.0.0.1}" +} +__find_and_remove() { + find "${2:-/etc}" -iname "$1" -exec rm -Rfv {} \; 2>/dev/null || true +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__pgrep() { + local srvc="$1" count=3 + [ -z "$srvc" ] && return 10 + while [ $count -ge 0 ]; do + pgrep -x "$srvc" &>/dev/null && return 0 + pgrep -f "$srvc" &>/dev/null && return 0 + ps -eo comm 2>/dev/null | grep -qxF -- "$srvc" && return 0 + [ $count -gt 0 ] && sleep 1 + count=$((count - 1)) + done + return 10 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__find_file_relative() { + [ -e "$1" ] || return 0 + find "$1"/* -not -path '*env/*' -not -path '*/.git/*' -not -name '.git' -type f 2>/dev/null \ + | sort -u \ + | sed "s|^$1/||" || true +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__find_directory_relative() { + [ -d "$1" ] || return 0 + find "$1"/* -not -path '*env/*' -not -path '*/.git/*' -not -name '.git' -type d 2>/dev/null \ + | sort -u \ + | sed "s|^$1/||" || true +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__pid_exists() { [ -n "$1" ] && [ -d "/proc/$1" ]; } +__is_running() { + local pat="$1" + [ -n "$2" ] && pat="$pat.*$2" + if command -v pgrep &>/dev/null; then + pgrep -f "$pat" &>/dev/null + else + ps -eo args 2>/dev/null | grep -v -- grep | grep -Eq -- "$pat" + fi +} +__get_pid() { + if [ -z "$1" ]; then + [ "$DEBUGGER" = "on" ] && echo "Warning: __get_pid called without process name" >&2 || true + return 1 + fi + local pid + pid=$(pgrep -x -n "$1" 2>/dev/null) + if [ -n "$pid" ]; then + echo "$pid" + return 0 + fi + [ "$DEBUGGER" = "on" ] && echo "Debug: No PID found for process: $1" >&2 || true + return 1 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__format_variables() { + local input="${*//,/ }" + [[ "$input" =~ [^[:space:]] ]] || return 0 + printf '%s\n' $input | sort -Ru | tr '\n' ' ' +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__clean_variables() { + local var="$*" + var="${var#"${var%%[![:space:]]*}"}" + var="${var%"${var##*[![:space:]]}"}" + while [[ $var == *" "* ]]; do var="${var// / }"; done + [ -n "$var" ] && printf '%s' "$var" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__no_exit() { + local monitor_interval="${SERVICE_MONITOR_INTERVAL:-60}" + local failure_threshold="${SERVICE_FAILURE_THRESHOLD:-3}" + local monitor_services="${SERVICES_LIST:-tini}" + local failed_services="" + local failure_count=0 + + # only return early if the recorded PID is still alive AND it is actually + # this same monitor loop (not just some unrelated process that happens to + # have been assigned the same number). /run persists across `docker + # restart` (same container filesystem), but the PID namespace resets on + # every restart, so a low PID recorded before the restart can coincidentally + # be reused by an unrelated early-boot process within the new namespace. A + # bare `kill -0` on that number alone would then wrongly report the old + # monitor as still running and `return 0` here without ever exec'ing the + # replacement monitor loop below β€” silently leaving the container with no + # supervisor loop, so it exits as soon as the caller's own script reaches + # its end. Guarding on the "__no_exit_monitor_loop" marker (embedded in the + # exec'd bash -c command below, so it shows up in that PID's own cmdline) + # confirms the live process is actually this monitor, not a coincidental + # PID-reuse false positive. + if [ -f "/run/.no_exit.pid" ]; then + local no_exit_pid + no_exit_pid=$(<"/run/.no_exit.pid") 2>/dev/null + if [ -n "$no_exit_pid" ] && kill -0 "$no_exit_pid" 2>/dev/null \ + && grep -q -- "__no_exit_monitor_loop" "/proc/$no_exit_pid/cmdline" 2>/dev/null; then + return 0 + fi + rm -f /run/.no_exit.pid 2>/dev/null || true + fi + + exec bash -c " + # __no_exit_monitor_loop marker: identifies this process's cmdline as the + # genuine monitor loop, so a future __no_exit call can tell it apart from + # an unrelated process that coincidentally reused this PID after a restart + trap 'echo \"Container shutdown requested\"; rm -f /run/.no_exit.pid /run/*.pid; exit 0' TERM INT + echo \$\$ > /run/.no_exit.pid + failed_services=\"\" + failure_count=0 + + while true; do + if [ -n \"$monitor_services\" ] && [ \"$monitor_services\" != \"tini\" ]; then + for service in \$(echo \"$monitor_services\" | tr ',' ' '); do + if [ \"\$service\" != \"tini\" ] && ! pgrep -x \"\$service\" >/dev/null 2>&1; then + echo \"WARNING: Service \$service is not running\" >&2 + failed_services=\"\$failed_services \$service\" + failure_count=\$((failure_count + 1)) + fi + done + + if [ \$failure_count -ge $failure_threshold ]; then + echo \"ERROR: Too many service failures (\$failure_count), exiting container\" >&2 + exit 1 + fi + + if [ -n \"\$failed_services\" ]; then + echo \"WARNING: Failed services:\$failed_services\" >&2 + failed_services=\"\" + failure_count=0 + fi + fi + + sleep $monitor_interval & wait \$! + done + " +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__trim() { + local var="${*//;/ }" + var="${var#"${var%%[![:space:]]*}"}" + var="${var%"${var##*[![:space:]]}"}" + while [[ $var == *" "* ]]; do var="${var// / }"; done + [ -n "$var" ] && printf '%s' "$var" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__banner() { + local message="$*" + local total_width=80 + # Account for "# - - - " and " - - - #" + local content_width=$((total_width - 14)) + printf '# - - - %-*s - - - #\n' "$content_width" "$message" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__service_banner() { + local icon="${1:-}" + [ -z "$icon" ] && icon="$( [ -z "$NO_COLOR" ] && echo "πŸ”§" || echo "" )" + local message="${2:-Processing}" + local service="${3:-service}" + local full_message="$message $service" + local total_width=80 + # Account for "# - - - " and " - - - #" + local content_width=$((total_width - 14)) + # Most emojis are 2 chars wide + local icon_width=2 + # Account for both icons and spaces + local text_width=$((content_width - icon_width * 2 - 2)) + printf '# - - - %s %-*s %s - - - #\n' "$icon" "$text_width" "$full_message" "$icon" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__find_php_bin() { + command -v php-fpm &>/dev/null || command -v php &>/dev/null || return 0 + find -L '/usr'/*bin -maxdepth 4 -name 'php-fpm*' 2>/dev/null | head -n1 +} +__find_php_ini() { + command -v php &>/dev/null || return 0 + local f + f=$(find -L '/etc' -maxdepth 4 -name 'php.ini' 2>/dev/null | head -n1) + [ -n "$f" ] && printf '%s\n' "${f%/php.ini}" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__find_nginx_conf() { + command -v nginx &>/dev/null || return 0 + find -L '/etc' -maxdepth 4 -name 'nginx.conf' 2>/dev/null | head -n1 +} +__find_caddy_conf() { + command -v caddy &>/dev/null || return 0 + find -L '/etc' -maxdepth 4 -type f -iname 'caddy.conf' 2>/dev/null | head -n1 +} +__find_lighttpd_conf() { + command -v lighttpd &>/dev/null || return 0 + find -L '/etc' -maxdepth 4 -type f -iname 'lighttpd.conf' 2>/dev/null | head -n1 +} +__find_cherokee_conf() { + command -v cherokee &>/dev/null || command -v cherokee-admin &>/dev/null || return 0 + find -L '/etc' -maxdepth 4 -type f -iname 'cherokee.conf' 2>/dev/null | head -n1 +} +__find_httpd_conf() { + command -v httpd &>/dev/null || command -v apache2 &>/dev/null || return 0 + find -L '/etc' -maxdepth 4 -type f \( -iname 'httpd.conf' -o -iname 'apache2.conf' \) 2>/dev/null | head -n1 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__find_mysql_conf() { + command -v mysqld &>/dev/null || command -v mariadbd &>/dev/null || command -v mysql &>/dev/null || return 0 + find -L '/etc' -maxdepth 4 -type f -name 'my.cnf' 2>/dev/null | head -n1 +} +__find_pgsql_conf() { + command -v postgres &>/dev/null || command -v pg_ctl &>/dev/null || return 0 + find -L '/var/lib' '/etc' -maxdepth 8 -type f -name 'postgresql.conf' 2>/dev/null | head -n1 +} +__find_couchdb_conf() { + command -v couchdb &>/dev/null || return 0 + find -L '/opt/couchdb/etc' '/etc/couchdb' -maxdepth 4 -type f \( -name 'local.ini' -o -name 'default.ini' \) 2>/dev/null | head -n1 +} +__find_mongodb_conf() { + command -v mongod &>/dev/null || return 0 + find -L '/etc/mongodb' '/etc' -maxdepth 4 -type f \( -name 'mongod.conf' -o -name 'mongodb.conf' \) 2>/dev/null | head -n1 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__random_password() { { tr -dc '0-9a-zA-Z' < /dev/urandom | head -c"${1:-16}"; } || true; echo ""; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_working_dir() { + # get service name + local service_name="$SERVICE_NAME" + # expand variables + local workdir="$(eval echo "${WORK_DIR:-}")" + # expand variables + local home="$(eval echo "${workdir//\/root/\/tmp\/docker}")" + # set working directories + [ "$home" = "$workdir" ] && workdir="" + [ "$home" = "/root" ] && home="/tmp/$service_name" + [ -z "$home" ] && home="${workdir:-/tmp/$service_name}" + # Change to working directory + [ -n "$WORK_DIR" ] && [ -n "$EXEC_CMD_BIN" ] && workdir="$WORK_DIR" + [ -z "$WORK_DIR" ] && [ "$HOME" = "/root" ] && [ "$RUNAS_USER" != "root" ] && [ "$PWD" != "/tmp" ] && home="${workdir:-$home}" + [ -z "$WORK_DIR" ] && [ "$HOME" = "/root" ] && [ "$SERVICE_USER" != "root" ] && [ "$PWD" != "/tmp" ] && home="${workdir:-$home}" + # create needed directories + if [ -n "$home" ]; then + if [ ! -d "$home" ]; then + mkdir -p "$home" + fi + fi + if [ -n "$workdir" ]; then + if [ ! -d "$workdir" ]; then + mkdir -p "$workdir" + fi + fi + if [ "$SERVICE_USER" != "root" ] && [ -d "$home" ]; then + chmod -f 777 "$home" + fi + if [ "$SERVICE_USER" != "root" ] && [ -d "$workdir" ]; then + chmod -f 777 "$workdir" + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - + # cd to dir + __cd "${workdir:-$home}" + # - - - - - - - - - - - - - - - - - - - - - - - - - + [ "$DEBUGGER" = "on" ] && echo "Setting the working directory to: $PWD" || true + # - - - - - - - - - - - - - - - - - - - - - - - - - + export WORK_DIR="$workdir" HOME="$home" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__exec_service() { + local count=6 + local bgpid + [ "$DEBUGGER" = "on" ] && echo "Starting $1" || true + eval "$@" & + bgpid=$! + while [ $count -ne 0 ]; do + sleep 3 + if ! kill -0 "$bgpid" 2>/dev/null; then + wait "$bgpid" + return $? + fi + if __pgrep "$1"; then + touch "/run/init.d/$1.pid" + return 0 + fi + count=$((count - 1)) + done + return 1 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__update_ssl_certs() { + [ -f "/config/env/ssl.sh" ] && . "/config/env/ssl.sh" + if [ -f "$SSL_CERT" ] && [ -f "$SSL_KEY" ]; then + mkdir -p /etc/ssl + [ -f "$SSL_CA" ] && cp -Rf "$SSL_CA" "/etc/ssl/$SSL_CA" + [ -f "$SSL_KEY" ] && cp -Rf "$SSL_KEY" "/etc/ssl/$SSL_KEY" + [ -f "$SSL_CERT" ] && cp -Rf "$SSL_CERT" "/etc/ssl/$SSL_CERT" + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__display_user_info() { + if [ -n "$user_name" ] || [ -n "$user_pass" ] || [ -n "$root_user_name" ] || [ -n "$root_user_pass" ]; then + __banner "User info" + [ -n "$user_name" ] && __printf_space "40" "username:" "$user_name" + if [ -n "$user_pass" ]; then + if [ "${SHOW_PASSWORDS:-no}" = "yes" ]; then + __printf_space "40" "password:" "$user_pass" + else + __printf_space "40" "password:" "saved to ${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" + fi + fi + [ -n "$root_user_name" ] && __printf_space "40" "root username:" "$root_user_name" + if [ -n "$root_user_pass" ]; then + if [ "${SHOW_PASSWORDS:-no}" = "yes" ]; then + __printf_space "40" "root password:" "$root_user_pass" + else + __printf_space "40" "root password:" "saved to ${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" + fi + fi + __banner "" + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_config_etc() { + local copy="no" + local name="$SERVICE_NAME" + local etc_dir="${ETC_DIR:-/etc/$name}" + local conf_dir="${CONF_DIR:-/config/$name}" + __is_dir_empty "$conf_dir" && copy=yes + if [ "$copy" = "yes" ]; then + if [ -d "$etc_dir" ]; then + mkdir -p "$conf_dir" + __copy_templates "$etc_dir/." "$conf_dir/" + elif [ -f "$etc_dir" ]; then + __copy_templates "$etc_dir" "$conf_dir" + fi + fi + # - - - - - - - - - - - - - - - - - - - - - - - - - +} +__create_ssl_cert() { + local SSL_DIR="${SSL_DIR:-/etc/ssl}" + [ -f "/config/env/ssl.sh" ] && . "/config/env/ssl.sh" + if [ -z "$SSL_DIR" ]; then + echo "SSL_DIR is unset" >&2 + return 1 + fi + [ -d "$SSL_DIR" ] || mkdir -p "$SSL_DIR" + if [ -n "$FORCE_SSL" ] || [ ! -f "$SSL_CERT" ] || [ ! -f "$SSL_KEY" ]; then + echo "Setting Country to $COUNTRY and Setting State/Province to $STATE and Setting City to $CITY" + echo "Setting OU to $UNIT and Setting ORG to $ORG and Setting server to $CN" + echo "All variables can be overwritten by creating a /config/.ssl.env and setting the variables there" + echo "Creating ssl key and certificate in $SSL_DIR and will be valid for $((VALID_FOR / 365)) year[s]" + openssl req \ + -new \ + -newkey rsa:$RSA \ + -days $VALID_FOR \ + -nodes \ + -x509 \ + -subj "/C=${COUNTRY// /\\ }/ST=${STATE// /\\ }/L=${CITY// /\\ }/O=${ORG// /\\ }/OU=${UNIT// /\\ }/CN=${CN// /\\ }" \ + -keyout "$SSL_KEY" \ + -out "$SSL_CERT" + fi + if [ -f "$SSL_CERT" ] && [ -f "$SSL_KEY" ]; then + __update_ssl_certs + return 0 + else + return 2 + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_service_conf() { + # Seed /config/$svc/ from build-time baked /etc sources on first container start. + # Copy only β€” no symlinks. Symlinking /etc back to /config/ is the service's own + # responsibility, done inside __update_conf_files in each init.d/*.sh script so + # each service controls its exact paths and variable substitution order. + # + # Usage: __init_service_conf [extra_etc_path ...] + # + # primary_etc_dir directory β†’ contents copied into conf_dir/ when conf_dir is empty + # extra_etc_path directory β†’ copied into conf_dir// when that subdir is empty + # extra_etc_path file β†’ copied to conf_dir/ when absent + local conf_dir="$1" + local primary_etc="$2" + shift 2 + local src name + mkdir -p "$conf_dir" + if [ -d "$primary_etc" ] && __is_dir_empty "$conf_dir"; then + __copy_templates "$primary_etc/." "$conf_dir/" + fi + for src in "$@"; do + [ -e "$src" ] || continue + name="${src##*/}" + if [ -d "$src" ] && __is_dir_empty "$conf_dir/$name"; then + mkdir -p "$conf_dir/$name" + __copy_templates "$src/." "$conf_dir/$name/" + elif [ -f "$src" ] && [ ! -f "$conf_dir/$name" ]; then + cp -f "$src" "$conf_dir/$name" + fi + done +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_apache() { + command -v httpd &>/dev/null || command -v apache2 &>/dev/null || return 0 + local svc="${1:-apache2}" + __init_service_conf "/config/$svc" "/etc/$svc" + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_nginx() { + command -v nginx &>/dev/null || return 0 + local svc="${1:-nginx}" + __init_service_conf "/config/$svc" "/etc/$svc" + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_php() { + command -v php &>/dev/null || return 0 + local php_etc="${PHP_INI_DIR:-$(__find_php_ini)}" + __init_service_conf "/config/php" "${php_etc:-/etc/php}" \ + "/etc/php.ini" "/etc/php-fpm" "/etc/php-fpm.conf" + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_mysql() { + command -v mysqld &>/dev/null || command -v mariadbd &>/dev/null || return 0 + local svc="${1:-mysql}" + __init_service_conf "/config/$svc" "/etc/$svc" "/etc/my.ini" "/etc/my.cnf" + [ -d "${DATABASE_DIR:-/data/db/$svc}" ] || mkdir -p "${DATABASE_DIR:-/data/db/$svc}" + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_mongodb() { + command -v mongod &>/dev/null || return 0 + __init_service_conf "/config/mongodb" "/etc/mongodb" "/etc/mongod.conf" + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_postgres() { + command -v postgres &>/dev/null || command -v pg_ctl &>/dev/null || return 0 + local pg_etc + pg_etc="${PGSQL_CONFIG_FILE:+${PGSQL_CONFIG_FILE%/*}}" + [ -n "$pg_etc" ] || pg_etc="$(__find_pgsql_conf)" + [ -n "$pg_etc" ] && pg_etc="${pg_etc%/*}" + [ -n "$pg_etc" ] && __init_service_conf "/config/postgres" "$pg_etc" + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__init_couchdb() { + command -v couchdb &>/dev/null || return 0 + __init_service_conf "/config/couchdb" "/etc/couchdb" + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Show available init functions +__init_help() { + echo ' +Config seeding (copy /etc β†’ /config, no symlinks): + __init_service_conf [extra_etc_path ...] + __init_apache [svc] seeds /config/apache2 from /etc/apache2 + __init_nginx [svc] seeds /config/nginx from /etc/nginx + __init_php seeds /config/php from /etc/php* + /etc/php.ini + /etc/php-fpm + __init_mysql [svc] seeds /config/mysql from /etc/mysql + /etc/my.{ini,cnf} + __init_mongodb seeds /config/mongodb from /etc/mongodb + /etc/mongod.conf + __init_postgres seeds /config/postgres from pg data dir + __init_couchdb seeds /config/couchdb from /etc/couchdb + +SSL: + __update_ssl_certs + __create_ssl_cert +' + return +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_once() { + if [ "$CONFIG_DIR_INITIALIZED" = "no" ] || [ "$DATA_DIR_INITIALIZED" = "no" ] || [ ! -f "/config/.docker_has_run" ]; then + return 0 + else + return 1 + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# run program ever n minutes +__cron() { + local bin="" + if [ "$1" = "--pid" ]; then + pid="$2" + shift 2 + else + pid="$$" + fi + if test -n "$1" && test -z "${1//[0-9]/}"; then + interval=$(($1 * 60)) + shift 1 + else + interval="300" + fi + [ $# -eq 0 ] && echo "Usage: cron [interval] [command]" && exit 1 + local command="$*" + bin="${CRON_NAME:-$1}"; bin="${bin##*/}" + trap 'retVal=$?;[ -f "/run/cron/$bin.run" ] && rm -Rf "/run/cron/$bin.run";[ -f "/run/cron/$bin.pid" ] && rm -Rf "/run/cron/$bin.pid";exit ${retVal:-0}' SIGINT ERR EXIT + [ -d "/run/cron" ] || mkdir -p "/run/cron" + echo "$pid" >"/run/cron/$bin.pid" + echo "$command" >"/run/cron/$bin.run" + [ "$DEBUGGER" = "on" ] && echo "Log is saved to /data/logs/cron.log" || true + # eval is intentional: $command is operator-controlled input from this container's init + while :; do + eval "$command" + sleep $interval + [ -f "/run/cron/$bin.run" ] || break + done 2>/dev/stderr >>"/data/logs/cron.log" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__replace() { + local search="$1" replace="$2" file="${3:-$2}" + [ -e "$file" ] || return 1 + __sed "$search" "$replace" "$file" || return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__find_replace() { + local search="$1" replace="$2" file="${3:-$2}" + [ -e "$file" ] || return 1 + find "$file" -type f -not -path '*/.git/*' -not -name '.git' -exec sed -i "s|$search|$replace|g" {} + 2>/dev/null +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# /config > /etc +__copy_templates() { + local from="$1" to="$2" is_link="" + [ -L "$to" ] && is_link="$(readlink "$to")" + [ "$from" != "$is_link" ] || return 0 + if [ -e "$from" ] && (! [ -d "$to" ] || __is_dir_empty "$to"); then + __file_copy "$from" "$to" + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# /config/file > /etc/file +__symlink() { + local from="$1" to="$2" + [ -e "$to" ] || return 0 + [ "$from" = "$to" ] && return 0 + __rm "$from" + [ -d "${from%/*}" ] || mkdir -p "${from%/*}" 2>/dev/null + ln -sf "$to" "$from" && [ "$DEBUGGER" = "on" ] && echo "Created symlink: $from -> $to" || true +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__file_copy() { + local from="$1" + local dest="$2" + local is_link="" + [ -L "$dest" ] && is_link="$(readlink "$dest")" + if [ "$from" != "$is_link" ]; then + if [ -n "$from" ] && [ -e "$from" ] && [ -n "$dest" ]; then + if [ -d "$from" ]; then + if cp -Rf "$from/." "$dest/" &>/dev/null; then + [ "$DEBUGGER" = "on" ] && printf '%s\n' "Copied: $from > $dest" || true + return 0 + else + printf '%s\n' "Copy failed: $from < $dest" >&2 + return 1 + fi + else + if cp -Rf "$from" "$dest" &>/dev/null; then + [ "$DEBUGGER" = "on" ] && printf '%s\n' "Copied: $from > $dest" || true + return 0 + else + printf '%s\n' "Copy failed: $from < $dest" >&2 + return 1 + fi + fi + else + printf '%s\n' "$from does not exist" >&2 + return 2 + fi + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__generate_random_uids() { + local set_random_uid=$((100 + RANDOM % 900)) + while :; do + if grep -shq -- "x:.*:$set_random_uid:" "/etc/group" && ! grep -shq -- "x:$set_random_uid:.*:" "/etc/passwd"; then + set_random_uid=$((set_random_uid + 1)) + else + echo "$set_random_uid" + break + fi + done +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__setup_directories() { + APPLICATION_DIRS="${APPLICATION_DIRS//,/ }" + APPLICATION_FILES="${APPLICATION_FILES//,/ }" + ADD_APPLICATION_DIRS="${ADD_APPLICATION_DIRS//,/ }" + ADD_APPLICATION_FILES="${ADD_APPLICATION_FILES//,/ }" + [ -n "$ENV_WWW_ROOT_DIR" ] && export WWW_ROOT_DIR="$ENV_WWW_ROOT_DIR" + # Setup WWW_ROOT_DIR + if [ "$IS_WEB_SERVER" = "yes" ]; then + APPLICATION_DIRS="$APPLICATION_DIRS $WWW_ROOT_DIR" + __initialize_www_root + mkdir -p "$WWW_ROOT_DIR" 2>/dev/null + find "$WWW_ROOT_DIR" -type d -exec chmod -f 777 {} \; 2>/dev/null + [ "$DEBUGGER" = "on" ] && echo "Created directory $WWW_ROOT_DIR" || true + fi + # Setup DATABASE_DIR + if [ "$IS_DATABASE_SERVICE" = "yes" ] || [ "$USES_DATABASE_SERVICE" = "yes" ]; then + APPLICATION_DIRS="$APPLICATION_DIRS $DATABASE_DIR" + if __is_dir_empty "$DATABASE_DIR" || [ ! -d "$DATABASE_DIR" ]; then + mkdir -p "$DATABASE_DIR" 2>/dev/null + chmod -f 777 "$DATABASE_DIR" 2>/dev/null + [ "$DEBUGGER" = "on" ] && echo "Created directory $DATABASE_DIR" || true + fi + fi + # create default directories + for filedirs in $ADD_APPLICATION_DIRS $APPLICATION_DIRS; do + if [ -n "$filedirs" ] && [ ! -d "$filedirs" ]; then + mkdir -p "$filedirs" 2>/dev/null + chmod -f 777 "$filedirs" 2>/dev/null + [ "$DEBUGGER" = "on" ] && echo "Created directory $filedirs" || true + fi + done + # create default files + for application_files in $ADD_APPLICATION_FILES $APPLICATION_FILES; do + if [ -n "$application_files" ] && [ ! -e "$application_files" ]; then + touch "$application_files" 2>/dev/null + chmod -Rf 777 "$application_files" 2>/dev/null + [ "$DEBUGGER" = "on" ] && echo "Created file $application_files" || true + fi + done +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set user on files/folders +__fix_permissions() { + change_user="${1:-${SERVICE_USER:-root}}" + change_group="${2:-${SERVICE_GROUP:-$change_user}}" + [ -n "$RUNAS_USER" ] && [ "$RUNAS_USER" != "root" ] && change_user="$RUNAS_USER" && change_group="$change_user" + if [ -n "$change_user" ]; then + if grep -shq -- "^$change_user:" "/etc/passwd"; then + for permissions in $ADD_APPLICATION_DIRS $APPLICATION_DIRS; do + if [ -n "$permissions" ] && [ -e "$permissions" ]; then + chown -Rf "$change_user" "$permissions" 2>/dev/null + [ "$DEBUGGER" = "on" ] && echo "Changed ownership of $permissions to $change_user" || true + fi + done + fi + fi + if [ -n "$change_group" ]; then + if grep -shq -- "^$change_group:" "/etc/group"; then + for permissions in $ADD_APPLICATION_DIRS $APPLICATION_DIRS; do + if [ -n "$permissions" ] && [ -e "$permissions" ]; then + chgrp -Rf "$change_group" "$permissions" 2>/dev/null + [ "$DEBUGGER" = "on" ] && echo "Changed group of $permissions to $change_group" || true + fi + done + fi + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__get_gid() { awk -F: -v n="$1" '$1==n {print $3; found=1; exit} END {exit !found}' /etc/group 2>/dev/null; } +__get_uid() { awk -F: -v n="$1" '$1==n {print $3; found=1; exit} END {exit !found}' /etc/passwd 2>/dev/null; } +__check_for_uid() { awk -F: -v n="$1" '$3==n {found=1; exit} END {exit !found}' /etc/passwd 2>/dev/null; } +__check_for_guid() { awk -F: -v n="$1" '$3==n {found=1; exit} END {exit !found}' /etc/group 2>/dev/null; } +__check_for_user() { awk -F: -v n="$1" '$1==n {found=1; exit} END {exit !found}' /etc/passwd 2>/dev/null; } +__check_for_group() { awk -F: -v n="$1" '$1==n {found=1; exit} END {exit !found}' /etc/group 2>/dev/null; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +# check if process is already running +__proc_check() { + # Skip process check for one-shot/configuration services + if [ "$SERVICE_USES_PID" = "no" ]; then + return 1 + fi + local cmd_bin cmd_name check_result + cmd_bin="$(type -P "${1:-$EXEC_CMD_BIN}" 2>/dev/null || echo "${1:-$EXEC_CMD_BIN}")" + cmd_name="${cmd_bin:-${1:-$EXEC_CMD_NAME}}"; cmd_name="${cmd_name##*/}" + if [ -z "$cmd_name" ] || [ "$cmd_name" = "." ]; then + return 1 + fi + check_result=1 + if [ -n "$cmd_bin" ] && __pgrep "$cmd_bin" 2>/dev/null; then + check_result=0 + elif [ -n "$cmd_name" ] && __pgrep "$cmd_name" 2>/dev/null; then + check_result=0 + elif [ -f "$SERVICE_PID_FILE" ]; then + local pid_from_file + pid_from_file=$(<"$SERVICE_PID_FILE") 2>/dev/null + if [ -n "$pid_from_file" ] && kill -0 "$pid_from_file" 2>/dev/null; then + check_result=0 + fi + fi + if [ $check_result -eq 0 ]; then + SERVICE_IS_RUNNING="yes" + pgrep -x -n "$cmd_name" >"$SERVICE_PID_FILE" 2>/dev/null || true + return 0 + else + return 1 + fi +} + +# - - - - - - - - - - - - - - - - - - - - - - - - - +__set_user_group_id() { + local set_user="${1:-$SERVICE_USER}" + local set_uid="${2:-${SERVICE_UID:-1000}}" + local set_gid="${3:-${SERVICE_GID:-1000}}" + # Nothing to do for root or unset + if [ -z "$set_user" ] || [ "$set_user" = "root" ]; then + return 0 + fi + # Nothing to do if the user does not exist yet + if ! grep -shq -- "^$set_user:" "/etc/passwd" "/etc/group"; then + return 0 + fi + set_uid="$(__get_uid "$set_user" || echo "$set_uid")" + set_gid="$(__get_gid "$set_user" || echo "$set_gid")" + if __check_for_guid "$set_gid"; then + groupmod -g "${set_gid}" "$set_user" 2>/dev/stderr | tee -a "/data/logs/init.txt" >/dev/null + fi + if __check_for_uid "$set_uid"; then + usermod -u "${set_uid}" -g "${set_gid}" "$set_user" 2>/dev/stderr | tee -a "/data/logs/init.txt" >/dev/null + fi + export SERVICE_UID="$set_uid" + export SERVICE_GID="$set_gid" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__create_service_user() { + local exitStatus=0 + local max_attempts=100 + local attempt=0 + local create_user="${1:-$SERVICE_USER}" + local create_group="${2:-${SERVICE_GROUP:-$create_user}}" + local create_home_dir="${3:-$WORK_DIR}" + local create_uid="${4:-${SERVICE_UID:-$USER_UID}}" + local create_gid="${5:-${SERVICE_GID:-$USER_GID}}" + local random_id="$(__generate_random_uids)" + local log_file="/data/logs/init.txt" + # Nothing to do for unset or root user β€” return silently + create_home_dir="${create_home_dir:-/home/$create_user}" + if [ -z "$create_user" ] || [ "$create_user" = "root" ]; then + return 0 + fi + # Ensure log directory exists + [ -d "$(dirname "$log_file")" ] || mkdir -p "$(dirname "$log_file")" 2>/dev/null + # Validate user/group name format (alphanumeric, underscore, hyphen; must start with letter or underscore) + if [ -n "$create_user" ] && [[ ! "$create_user" =~ ^[a-z_][a-z0-9_-]*$ ]]; then + echo "Error: Invalid username format '$create_user' - must start with letter/underscore, contain only lowercase alphanumeric, underscore, or hyphen" >&2 + return 1 + fi + if [ -n "$create_group" ] && [[ ! "$create_group" =~ ^[a-z_][a-z0-9_-]*$ ]]; then + echo "Error: Invalid group name format '$create_group' - must start with letter/underscore, contain only lowercase alphanumeric, underscore, or hyphen" >&2 + return 1 + fi + # Check if user and group already exist + if grep -shq -- "^$create_user:" "/etc/passwd" && grep -shq -- "^$create_group:" "/etc/group"; then + return 0 + fi + # Override with RUNAS_USER if specified and not root + if [ -n "$RUNAS_USER" ] && [ "$RUNAS_USER" != "root" ]; then + create_user="$RUNAS_USER" + create_group="$RUNAS_USER" + create_uid="${create_uid:-1000}" + create_gid="${create_gid:-1000}" + fi + # Get existing UID/GID or use provided values + create_uid="$(__get_uid "$create_user" 2>/dev/null || echo "$create_uid")" + create_gid="$(__get_gid "$create_user" 2>/dev/null || echo "$create_gid")" + # Ensure we have valid non-root UID/GID + if [ -z "$create_uid" ] || [ "$create_uid" = "0" ]; then + create_uid="$random_id" + fi + if [ -z "$create_gid" ] || [ "$create_gid" = "0" ]; then + create_gid="$random_id" + fi + # Validate UID/GID are numeric and within valid range + if [[ ! "$create_uid" =~ ^[0-9]+$ ]] || [ "$create_uid" -lt 1 ] || [ "$create_uid" -gt 65534 ]; then + echo "Error: Invalid UID '$create_uid' - must be a number between 1 and 65534" >&2 + return 1 + fi + if [[ ! "$create_gid" =~ ^[0-9]+$ ]] || [ "$create_gid" -lt 1 ] || [ "$create_gid" -gt 65534 ]; then + echo "Error: Invalid GID '$create_gid' - must be a number between 1 and 65534" >&2 + return 1 + fi + # Find available UID/GID if current ones are taken (with loop protection) + while __check_for_uid "$create_uid" || __check_for_guid "$create_gid"; do + attempt=$((attempt + 1)) + if [ $attempt -ge $max_attempts ]; then + echo "Error: Could not find available UID/GID after $max_attempts attempts" >&2 + return 1 + fi + random_id=$((random_id + 1)) + create_uid="$random_id" + create_gid="$random_id" + done + # Create group if needed + if [ -n "$create_group" ] && ! __check_for_group "$create_group"; then + echo "Creating system group '$create_group' with GID $create_gid" + if ! groupadd --force --system -g "$create_gid" "$create_group" 2>&1 | tee -a "$log_file"; then + echo "Error: Failed to create group '$create_group'" >&2 + exitStatus=$((exitStatus + 1)) + elif ! grep -shq -- "^$create_group:" "/etc/group"; then + echo "Error: Group '$create_group' not found in /etc/group after creation" >&2 + exitStatus=$((exitStatus + 1)) + fi + fi + # Create user if needed (only if group creation succeeded) + if [ $exitStatus -eq 0 ] && [ -n "$create_user" ] && ! __check_for_user "$create_user"; then + echo "Creating system user '$create_user' with UID $create_uid" + if ! useradd --system --uid "$create_uid" --gid "$create_group" \ + --comment "Account for $create_user" --home-dir "$create_home_dir" \ + --shell /bin/false "$create_user" 2>&1 | tee -a "$log_file"; then + echo "Error: Failed to create user '$create_user'" >&2 + exitStatus=$((exitStatus + 1)) + elif ! grep -shq -- "^$create_user:" "/etc/passwd"; then + echo "Error: User '$create_user' not found in /etc/passwd after creation" >&2 + exitStatus=$((exitStatus + 1)) + fi + fi + # Setup user environment if creation succeeded + if [ $exitStatus -eq 0 ] && [ -n "$create_group" ] && [ -n "$create_user" ]; then + export WORK_DIR="${create_home_dir:-}" + if [ -n "$WORK_DIR" ]; then + if [ ! -d "$WORK_DIR" ]; then + if ! mkdir -p "$WORK_DIR" 2>/dev/null; then + echo "Warning: Failed to create home directory '$WORK_DIR'" >&2 + fi + fi + if [ -d "/etc/.skel" ] && [ -d "$WORK_DIR" ]; then + cp -Rf /etc/.skel/. "$WORK_DIR/" 2>/dev/null || echo "Warning: Failed to copy skeleton files to '$WORK_DIR'" >&2 + fi + fi + # Setup sudo access + if [ -d "/etc/sudoers.d" ]; then + if [ ! -f "/etc/sudoers.d/$create_user" ]; then + echo "$create_user ALL=(ALL) NOPASSWD: ALL" >"/etc/sudoers.d/$create_user" 2>/dev/null || echo "Warning: Failed to create sudoers file for '$create_user'" >&2 + chmod 0440 "/etc/sudoers.d/$create_user" 2>/dev/null + fi + elif [ -f "/etc/sudoers" ] && ! grep -qs -- "^$create_user " "/etc/sudoers"; then + echo "$create_user ALL=(ALL) NOPASSWD: ALL" >>"/etc/sudoers" 2>/dev/null || echo "Warning: Failed to add '$create_user' to sudoers" >&2 + fi + SERVICE_UID="$create_uid" + SERVICE_GID="$create_gid" + SERVICE_USER="$create_user" + SERVICE_GROUP="$create_group" + else + echo "Warning: Falling back to root user due to creation errors" >&2 + SERVICE_UID=0 + SERVICE_GID=0 + SERVICE_USER=root + SERVICE_GROUP=root + exitStatus=2 + fi + export SERVICE_UID SERVICE_GID SERVICE_USER SERVICE_GROUP + return $exitStatus +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__create_env_file() { + local dir="" + local envStatus=0 + local envFile=("${@:-}") + local sample_file="/usr/local/etc/docker/env/default.sample" + [ -f "$sample_file" ] || return 0 + for create_env in "/usr/local/etc/docker/env/default.sh" "${envFile[@]}"; do + dir="$(dirname "$create_env")" + [ -d "$dir" ] || mkdir -p "$dir" + if [ -n "$create_env" ] && [ ! -f "$create_env" ]; then + cp -f "$sample_file" "$create_env" + fi + [ -f "$create_env" ] || envStatus=$((1 + envStatus)) + done + [ "$envStatus" -eq 0 ] && rm -f "$sample_file" + return $envStatus +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__exec_command() { + if [ $# -eq 0 ]; then + exec bash -l + fi + exec "$@" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Setup the server init scripts +__start_init_scripts() { + [ "$1" = " " ] && shift 1 + if [ "$DEBUGGER" = "on" ]; then + echo "Enabling debugging" + set -eo pipefail + [ -n "$DEBUGGER_OPTIONS" ] && set -"$DEBUGGER_OPTIONS" + else + set -eo pipefail + fi + local retPID="" + local basename="" + local init_pids="" + local retstatus="0" + local initStatus="0" + local critical_failures="0" + local pidFile="/run/.start_init_scripts.pid" + local init_dir="${1:-/usr/local/etc/docker/init.d}" + local init_files=("$init_dir"/*.sh) + local init_count=0 + [ -e "${init_files[0]}" ] && init_count=${#init_files[@]} + local exit_on_failure="${EXIT_ON_SERVICE_FAILURE:-true}" + + # Clean stale PID files from previous runs + if [ ! -f "/run/.start_init_scripts.pid" ]; then + [ "$DEBUGGER" = "on" ] && echo "Cleaning stale PID files from previous container run" || true + rm -f /run/*.pid /run/init.d/*.pid 2>/dev/null || true + fi + + touch "$pidFile" + + if [ "$init_count" -eq 0 ] || [ ! -d "$init_dir" ]; then + mkdir -p "/data/logs/init" + while :; do echo "Running: $(date)" >"/data/logs/init/keep_alive" && sleep 3600; done & + else + if [ -d "$init_dir" ]; then + local sample + for sample in "$init_dir"/*.sample; do + [ -e "$sample" ] && __rm "$sample" + done + (shopt -s nullglob; chmod -Rf 755 "$init_dir"/*.sh 2>/dev/null || true) + + if [ "$DEBUGGER" = "on" ]; then + echo "Starting container services initialization" + echo "Init directory: $init_dir β€” $init_count service scripts to execute" + echo "" + fi + + for init in "$init_dir"/*.sh; do + if [ -x "$init" ]; then + touch "$pidFile" + name="${init##*/}" + service="${name#*-}"; service="${service%.sh}" + [ "$DEBUGGER" = "on" ] && __service_banner "$( [ -z "$NO_COLOR" ] && echo "πŸ”§" || echo "" )" "Executing service script:" "${init##*/}" || true + # Execute the init script and capture the exit code (subshell isolates exit calls) + if ( source "$init" ); then + # Check if service was disabled first + if [ -n "$SERVICE_DISABLED" ]; then + initStatus="0" + __service_banner "🚫" "Service $service is disabled -" "skipping" + unset SERVICE_DISABLED + # Continue to next service + elif [ "$CONTAINER_INIT" = "yes" ]; then + initStatus="0" + __service_banner "βœ…" "Service $service completed successfully -" "configuration service" + else + # Allow some time for service to initialize + sleep 1 + # Check for service success indicators + local expected_pid_file="/run/init.d/$service.pid" + set +e + # Check if this is a configuration service (no daemon process expected) + if [ "$SERVICE_USES_PID" = "no" ]; then + # Configuration service - no daemon process expected + initStatus="0" + __service_banner "βœ…" "Service $service completed successfully -" "configuration service" + else + # Service uses PID tracking - verify actual running processes + retPID="" + local found_process="" + # Try multiple name variants to find the process + for name_variant in "$service" "${service}d" "${service//-/}"; do + if [ -z "$retPID" ]; then + retPID=$(__get_pid "$name_variant" 2>/dev/null || echo "") + if [ -n "$retPID" ] && [ "$retPID" != "0" ]; then + found_process="$name_variant" + break + fi + fi + done + if [ -n "$retPID" ] && [ "$retPID" != "0" ]; then + # Found actual running process + initStatus="0" + __service_banner "βœ…" "Service $service started successfully -" "PID: ${retPID} ($found_process)" + elif [ -f "$expected_pid_file" ]; then + # No running process but PID file exists - verify PID is valid + file_pid=$(cat "$expected_pid_file" 2>/dev/null) + if [ -n "$file_pid" ] && kill -0 "$file_pid" 2>/dev/null; then + initStatus="0" + __service_banner "βœ…" "Service $service started successfully -" "PID: $file_pid (from file)" + else + # PID file exists but process isn't running - treat as warning, not failure + initStatus="0" + __service_banner "⚠️" "Service $service may not be running -" "no process found (non-critical)" + fi + else + # No process and no PID file - likely a configuration-only service + initStatus="0" + __service_banner "βœ…" "Service $service completed successfully -" "configuration service" + fi + fi + set -e + fi + else + initStatus="1" + critical_failures=$((critical_failures + 1)) + __service_banner "$( [ -z "$NO_COLOR" ] && echo "❌" || echo "" )" "Service $service failed to start -" "check logs" + fi + echo "" + fi + retstatus=$((retstatus + initStatus)) + done + + # Summary + echo "" + if [ $critical_failures -gt 0 ]; then + if [ -z "$NO_COLOR" ]; then + echo "⚠️ Warning: $critical_failures critical service(s) reported failures" + else + echo "Warning: $critical_failures critical service(s) reported failures" + fi + if [ "$exit_on_failure" = "true" ] && [ $critical_failures -ge 2 ]; then + if [ -z "$NO_COLOR" ]; then + echo "❌ Exiting due to multiple critical service failures (threshold: 2)" + else + echo "Exiting due to multiple critical service failures (threshold: 2)" + fi + return 1 + else + if [ -z "$NO_COLOR" ]; then + echo "ℹ️ Continuing with $critical_failures failure(s) - container may still be functional" + else + echo "Continuing with $critical_failures failure(s) - container may still be functional" + fi + fi + else + echo "βœ… All service initializations completed successfully" + fi + echo "" + fi + fi + + printf '%s\n' "$SERVICE_NAME started on $(date)" >"/data/logs/start.log" + return $retstatus +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__setup_mta() { + # Check for an installed MTA binary β€” directories alone cannot be trusted because + # /etc/postfix ships in some base images (casjaysdev/alpine) even without postfix. + command -v msmtp &>/dev/null || command -v ssmtp &>/dev/null || command -v postfix &>/dev/null || return 0 + local exitCode=0 + # Extract server and port only when EMAIL_RELAY contains a colon + local relay_server="" relay_port="" + if [[ "$EMAIL_RELAY" == *:* ]]; then + relay_server="${EMAIL_RELAY%%:*}" + relay_port="${EMAIL_RELAY##*:}" + else + relay_server="$EMAIL_RELAY" + fi + local local_hostname="${FULL_DOMAIN_NAME:-}" + local account_user="${SERVER_ADMIN//@*/}" + local account_domain="${EMAIL_DOMAIN//*@/}" + # Default to port 25 β€” plain SMTP, no cert validation required + relay_port="${relay_port:-25}" + # Autodetect Docker host gateway as SMTP relay when EMAIL_RELAY is unset + if [ -z "$EMAIL_RELAY" ] && timeout 2 bash -c 'echo >/dev/tcp/172.17.0.1/25' 2>/dev/null; then + relay_server="172.17.0.1" + relay_port="25" + fi + # Port 25 is plain SMTP β€” no TLS; anything else defaults to TLS on + local relay_use_tls="Yes" + local relay_smtp_tls="yes" + [ "$relay_port" = "25" ] && relay_use_tls="No" && relay_smtp_tls="no" + ################# msmtp relay setup + if command -v msmtp &>/dev/null; then + [ -d "/config/msmtp" ] || mkdir -p "/config/msmtp" + [ -f "/etc/msmtprc" ] && __rm "/etc/msmtprc" + if [ ! -f "/config/msmtp/msmtprc" ]; then + cat >"/config/msmtp/msmtprc" </dev/null || true + # sendmail compat symlink + if [ ! -e "/usr/sbin/sendmail" ] && command -v msmtp &>/dev/null; then + __symlink "/usr/sbin/sendmail" "$(command -v msmtp)" + fi + [ "$DEBUGGER" = "on" ] && echo "Done setting up msmtp" || true + fi + + ################# sSMTP relay setup + elif command -v ssmtp &>/dev/null; then + [ -d "/config/ssmtp" ] || mkdir -p "/config/ssmtp" + [ -f "/etc/ssmtp/ssmtp.conf" ] && __rm "/etc/ssmtp/ssmtp.conf" + symlink_files="$(__find_file_relative "/config/ssmtp")" + if [ ! -f "/config/ssmtp/ssmtp.conf" ]; then + cat >"/config/ssmtp/ssmtp.conf" </dev/null; then + [ -d "/etc/postfix" ] || mkdir -p "/etc/postfix" + [ -d "/config/postfix" ] || mkdir -p "/config/postfix" + [ -f "/etc/postfix/main.cf" ] && __rm "/etc/postfix/main.cf" + symlink_files="$(__find_file_relative "/config/postfix")" + if [ ! -f "/config/postfix/main.cf" ]; then + cat >"/config/postfix/main.cf" </dev/null + postmap "/config/postfix/mydomains.pcre" "/config/postfix/mydomains" "/config/postfix/virtual" &>/dev/null + fi + if [ -f "/etc/postfix/main.cf" ] && [ ! -f "/run/init.d/postfix.pid" ]; then + SERVICES_LIST+="postfix " + if [ ! -f "/run/init.d/postfix.pid" ]; then + __exec_service postfix start + fi + [ "$DEBUGGER" = "on" ] && echo "Done setting up postfix" || true + fi + fi + [ -f "/root/dead.letter" ] && __rm "/root/dead.letter" + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__initialize_web_health() { + local www_dir="${1:-${WWW_ROOT_DIR:-/usr/local/share/httpd/default}}" + if [ -d "$www_dir" ]; then + __find_replace "REPLACE_CONTAINER_IP4" "${REPLACE_CONTAINER_IP4:-127.0.0.1}" "/usr/local/share/httpd" + __find_replace "REPLACE_COPYRIGHT_FOOTER" "${COPYRIGHT_FOOTER:-Copyright 1999 - $(date +'%Y')}" "/usr/local/share/httpd" + __find_replace "REPLACE_LAST_UPDATED_ON_MESSAGE" "${LAST_UPDATED_ON_MESSAGE:-$(date +'Last updated on: %Y-%m-%d at %H:%M:%S')}" "/usr/local/share/httpd" + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# file_dir +__initialize_replace_variables() { + local set_dir="" get_dir="$*" + [ $# -ne 0 ] || return 1 + for set_dir in $get_dir; do + __find_replace "REPLACE_SSL_DIR" "${SSL_DIR:-/etc/ssl}" "$set_dir" + __find_replace "REPLACE_RANDOM_ID" "$(__random_password 8)" "$set_dir" + __find_replace "REPLACE_TZ" "${TZ:-${TIMEZONE:-America/New_York}}" "$set_dir" + __find_replace "REPLACE_SERVER_PROTO" "${SERVICE_PROTOCOL:-http}" "$set_dir" + __find_replace "REPLACE_SERVER_SITE_TITLE" "${SERVER_SITE_TITLE:-CasjaysDev - Docker Container}" "$set_dir" + __find_replace "REPLACE_TMP_DIR" "${TMP_DIR:-/tmp/$SERVICE_NAME}" "$set_dir" + __find_replace "REPLACE_RUN_DIR" "${RUN_DIR:-/run/$SERVICE_NAME}" "$set_dir" + __find_replace "REPLACE_LOG_DIR" "${LOG_DIR:-/data/logs/$SERVICE_NAME}" "$set_dir" + __find_replace "REPLACE_ETC_DIR" "${ETC_DIR:-/etc/$SERVICE_NAME}" "$set_dir" + __find_replace "REPLACE_DATA_DIR" "${DATA_DIR:-/data/$SERVICE_NAME}" "$set_dir" + __find_replace "REPLACE_CONFIG_DIR" "${CONF_DIR:-/config/$SERVICE_NAME}" "$set_dir" + __find_replace "REPLACE_EMAIL_RELAY" "${EMAIL_RELAY:-172.17.0.1}" "$set_dir" + __find_replace "REPLACE_SERVER_ADMIN" "${SERVER_ADMIN:-root@${EMAIL_DOMAIN:-${FULL_DOMAIN_NAME:-$HOSTNAME}}}" "$set_dir" + __find_replace "REPLACE_APP_USER" "${SERVICE_USER:-${RUNAS_USER:-root}}" "$set_dir" + __find_replace "REPLACE_WWW_USER" "${SERVICE_USER:-${RUNAS_USER:-root}}" "$set_dir" + __find_replace "REPLACE_APP_GROUP" "${SERVICE_GROUP:-${SERVICE_USER:-${RUNAS_USER:-root}}}" "$set_dir" + __find_replace "REPLACE_WWW_GROUP" "${SERVICE_GROUP:-${SERVICE_USER:-${RUNAS_USER:-root}}}" "$set_dir" + __find_replace "REPLACE_SERVICE_USER" "${SERVICE_USER:-${RUNAS_USER:-root}}" "$set_dir" + __find_replace "REPLACE_SERVICE_GROUP" "${SERVICE_GROUP:-${RUNAS_USER:-root}}" "$set_dir" + __find_replace "REPLACE_SERVER_ADMIN_URL" "$SERVER_ADMIN_URL" "$set_dir" + if [ -n "$VAR_DIR" ]; then + mkdir -p "$VAR_DIR" + __find_replace "REPLACE_VAR_DIR" "$VAR_DIR" "$set_dir" + fi + [ -n "$SERVICE_PORT" ] && __find_replace "REPLACE_SERVER_PORT" "${SERVICE_PORT:-80}" "$set_dir" + [ -n "$HOSTNAME" ] && __find_replace "REPLACE_SERVER_NAME" "${FULL_DOMAIN_NAME:-$HOSTNAME}" "$set_dir" + [ -n "$CONTAINER_NAME" ] && __find_replace "REPLACE_SERVER_SOFTWARE" "${CONTAINER_NAME:-docker}" "$set_dir" + [ -n "$WWW_ROOT_DIR" ] && __find_replace "REPLACE_SERVER_WWW_DIR" "${WWW_ROOT_DIR:-/usr/local/share/httpd/default}" "$set_dir" + done + if [ -n "$WWW_ROOT_DIR" ] && [ "$set_dir" != "$WWW_ROOT_DIR" ] && [ -d "$WWW_ROOT_DIR" ]; then + __find_replace "REPLACE_CONTAINER_IP4" "${REPLACE_CONTAINER_IP4:-127.0.0.1}" "$WWW_ROOT_DIR" + __find_replace "REPLACE_COPYRIGHT_FOOTER" "${COPYRIGHT_FOOTER:-Copyright 1999 - $(date +'%Y')}" "$WWW_ROOT_DIR" + __find_replace "REPLACE_LAST_UPDATED_ON_MESSAGE" "${LAST_UPDATED_ON_MESSAGE:-$(date +'Last updated on: %Y-%m-%d at %H:%M:%S')}" "$WWW_ROOT_DIR" + fi + mkdir -p "${TMP_DIR:-/tmp/$SERVICE_NAME}" "${RUN_DIR:-/run/$SERVICE_NAME}" "${LOG_DIR:-/data/logs/$SERVICE_NAME}" + chmod -f 777 "${TMP_DIR:-/tmp/$SERVICE_NAME}" "${RUN_DIR:-/run/$SERVICE_NAME}" "${LOG_DIR:-/data/logs/$SERVICE_NAME}" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__initialize_database() { + [ "$IS_DATABASE_SERVICE" = "yes" ] || [ "$USES_DATABASE_SERVICE" = "yes" ] || return 0 + local dir="${1:-$ETC_DIR}" + local db_normal_user="${DATABASE_USER_NORMAL:-$user_name}" + local db_normal_pass="${DATABASE_PASS_NORMAL:-$user_pass}" + local db_admin_user="${DATABASE_USER_ROOT:-$root_user_name}" + local db_admin_pass="${DATABASE_PASS_ROOT:-$root_user_pass}" + __find_replace "REPLACE_USER_NAME" "$db_normal_user" "$dir" + __find_replace "REPLACE_USER_PASS" "$db_normal_pass" "$dir" + __find_replace "REPLACE_DATABASE_USER" "$db_normal_user" "$dir" + __find_replace "REPLACE_DATABASE_PASS" "$db_normal_pass" "$dir" + __find_replace "REPLACE_ROOT_ADMIN" "$db_admin_user" "$dir" + __find_replace "REPLACE_ROOT_PASS" "$db_admin_pass" "$dir" + __find_replace "REPLACE_DATABASE_ROOT_USER" "$db_admin_user" "$dir" + __find_replace "REPLACE_DATABASE_ROOT_PASS" "$db_admin_pass" "$dir" + __find_replace "REPLACE_DATABASE_NAME" "$DATABASE_NAME" "$dir" + __find_replace "REPLACE_DATABASE_DIR" "$DATABASE_DIR" "$dir" + if [[ "$dir" == "/etc"* ]]; then + __find_replace "REPLACE_USER_NAME" "$db_normal_user" "/etc" + __find_replace "REPLACE_USER_PASS" "$db_normal_pass" "/etc" + __find_replace "REPLACE_DATABASE_USER" "$db_normal_user" "/etc" + __find_replace "REPLACE_DATABASE_PASS" "$db_normal_pass" "/etc" + __find_replace "REPLACE_ROOT_ADMIN" "$db_admin_user" "/etc" + __find_replace "REPLACE_ROOT_PASS" "$db_admin_pass" "/etc" + __find_replace "REPLACE_DATABASE_ROOT_USER" "$db_admin_user" "/etc" + __find_replace "REPLACE_DATABASE_ROOT_PASS" "$db_admin_pass" "/etc" + __find_replace "REPLACE_DATABASE_NAME" "$DATABASE_NAME" "/etc" + __find_replace "REPLACE_DATABASE_DIR" "$DATABASE_DIR" "/etc" + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__initialize_db_users() { + [ "$IS_DATABASE_SERVICE" = "yes" ] || [ "$USES_DATABASE_SERVICE" = "yes" ] || return 0 + db_normal_user="${DATABASE_USER_NORMAL:-$user_name}" + db_normal_pass="${DATABASE_PASS_NORMAL:-$user_pass}" + db_admin_user="${DATABASE_USER_ROOT:-$root_user_name}" + db_admin_pass="${DATABASE_PASS_ROOT:-$root_user_pass}" + export DATABASE_USER_NORMAL="$db_normal_user" + export DATABASE_PASS_NORMAL="$db_normal_pass" + export DATABASE_USER_ROOT="$db_admin_user" + export DATABASE_PASS_ROOT="$db_admin_pass" + export db_normal_user db_normal_pass db_admin_user db_admin_pass +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__initialize_system_etc() { + local conf_dir="$1" + local dir="" + local files="" + local directories="" + if [ -n "$conf_dir" ] && [ -e "$conf_dir" ]; then + files=$(find "$conf_dir"/* -not -path '*/env/*' -type f 2>/dev/null | sort -u | sed 's|/config/||') + directories=$(find "$conf_dir"/* -not -path '*/env/*' -type d 2>/dev/null | sort -u | sed 's|/config/||') + [ "$DEBUGGER" = "on" ] && echo "Copying config: $conf_dir > /etc/${conf_dir//\/config\//}" || true + if [ -n "$directories" ]; then + for d in $directories; do + dir="/etc/$d" + mkdir -p "$dir" + [ "$DEBUGGER" = "on" ] && echo "Created directory: $dir" || true + done + fi + for f in $files; do + etc_file="/etc/$f" + conf_file="/config/$f" + [ -f "$etc_file" ] && __rm "$etc_file" + __symlink "$etc_file" "$conf_file" + __initialize_replace_variables "$conf_file" "$etc_file" + [ -e "/data/$f" ] && __initialize_replace_variables "/data/$f" + done + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__initialize_custom_bin_dir() { + local SET_USR_BIN="" + [ -d "/data/bin" ] && SET_USR_BIN+="$(__find /data/bin f) " + [ -d "/config/bin" ] && SET_USR_BIN+="$(__find /config/bin f) " + if [ -n "$SET_USR_BIN" ]; then + [ "$DEBUGGER" = "on" ] && echo "Setting up bin: $SET_USR_BIN > $LOCAL_BIN_DIR" || true + for create_bin_template in $SET_USR_BIN; do + if [ -n "$create_bin_template" ]; then + create_bin_name="${create_bin_template##*/}" + if [ -e "$create_bin_template" ]; then + ln -sf "$create_bin_template" "$LOCAL_BIN_DIR/$create_bin_name" + fi + fi + done + unset create_bin_template create_bin_name SET_USR_BIN + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# - - - - - - - - - - - - - - - - - - - - - - - - - +__initialize_www_root() { + local WWW_INIT="" + local WWW_TEMPLATE="" + [ -d "/usr/local/share/httpd/default" ] && WWW_TEMPLATE="/usr/local/share/httpd/default" + [ "$WWW_ROOT_DIR" = "/app" ] && WWW_INIT="${WWW_INIT:-true}" + [ "$WWW_ROOT_DIR" = "/data/htdocs" ] && WWW_INIT="${WWW_INIT:-true}" + if __is_dir_empty "$WWW_ROOT_DIR/"; then + WWW_INIT="true" + else + WWW_INIT="false" + fi + if [ "$WWW_INIT" = "true" ] && [ -d "$WWW_TEMPLATE" ]; then + cp -Rf "$WWW_TEMPLATE/." "$WWW_ROOT_DIR/" 2>/dev/null + fi + __initialize_web_health "$WWW_ROOT_DIR" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__is_htdocs_mounted() { + WWW_ROOT_DIR="${WWW_ROOT_DIR:-/data/htdocs}" + [ -n "$ENV_WWW_ROOT_DIR" ] && WWW_ROOT_DIR="$ENV_WWW_ROOT_DIR" + if [ -n "$IMPORT_FROM_GIT" ]; then + if [[ ! "$IMPORT_FROM_GIT" =~ (https://|http://|git://|ssh://) ]]; then + unset IMPORT_FROM_GIT + fi + fi + if [ -n "$IMPORT_FROM_GIT" ] && command -v git &>/dev/null; then + if __is_dir_empty "$WWW_ROOT_DIR"; then + echo "Importing project from $IMPORT_FROM_GIT to $WWW_ROOT_DIR" + git clone -q "$IMPORT_FROM_GIT" "$WWW_ROOT_DIR" + elif [ -d "$WWW_ROOT_DIR" ]; then + echo "Updating the project in $WWW_ROOT_DIR" + git -C "$WWW_ROOT_DIR" pull -q + fi + elif [ -d "/app" ]; then + WWW_ROOT_DIR="/app" + elif [ -d "/data/htdocs/www" ]; then + WWW_ROOT_DIR="/data/htdocs/www" + elif [ -d "/data/htdocs/root" ]; then + WWW_ROOT_DIR="/data/htdocs/root" + elif [ -d "/data/htdocs" ]; then + WWW_ROOT_DIR="/data/htdocs" + elif [ -d "/data/wwwroot" ]; then + WWW_ROOT_DIR="/data/wwwroot" + fi + [ -d "$WWW_ROOT_DIR" ] || mkdir -p "$WWW_ROOT_DIR" + export WWW_ROOT_DIR="${WWW_ROOT_DIR:-/usr/local/share/httpd/default}" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__initialize_ssl_certs() { + [ -d "$SSL_DIR" ] || mkdir -p "$SSL_DIR" + if [ "$SSL_ENABLED" = "yes" ]; then + if [ -f "$SSL_CERT" ] && [ -f "$SSL_KEY" ]; then + if [ -n "$SSL_CA" ] && [ -f "$SSL_CA" ]; then + mkdir -p "$SSL_DIR/certs" + cat "$SSL_CA" >>"/etc/ssl/certs/ca-certificates.crt" + fi + __update_ssl_certs + else + __create_ssl_cert + fi + fi + type update-ca-certificates &>/dev/null && update-ca-certificates &>/dev/null +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__start_php_dev_server() { + [ "$2" = "yes" ] || return 0 + local docroot="" + # Prefer a custom doc root when it differs from the default share path + if [ -n "$1" ] && [[ "$1" != "/usr/local/share/httpd"* ]]; then + docroot="$1" + elif [ -d "/usr/local/share/httpd" ]; then + docroot="/usr/local/share/httpd" + fi + [ -n "$docroot" ] || return 0 + find "$docroot" -type f -not -path '.git*' -iname '*.php' \ + -exec sed -i 's|[<].*SERVER_ADDR.*[>]|'"${CONTAINER_IP4_ADDRESS:-127.0.0.1}"'|g' {} \; 2>/dev/null + php -S "0.0.0.0:${PHP_DEV_SERVER_PORT}" -t "$docroot" +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__check_service() { + if [ "$1" = "check" ]; then + shift $# + __proc_check "$EXEC_CMD_NAME" || __proc_check "$EXEC_CMD_BIN" + exit $? + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__switch_to_user() { + # Use SERVICE_USER if set, otherwise fall back to RUNAS_USER + local switch_user="${SERVICE_USER:-$RUNAS_USER}" + if [ "$switch_user" = "root" ]; then + su_exec="" + __su_cmd() { eval "$@" || return 1; } + elif command -v gosu &>/dev/null; then + su_exec="gosu $switch_user" + __su_cmd() { $su_exec "$@" || return 1; } + elif command -v runuser &>/dev/null; then + su_exec="runuser -u $switch_user" + __su_cmd() { $su_exec "$@" || return 1; } + elif command -v sudo &>/dev/null; then + su_exec="sudo -u $switch_user" + __su_cmd() { $su_exec "$@" || return 1; } + elif command -v su &>/dev/null; then + su_exec="su -s /bin/sh - $switch_user" + __su_cmd() { $su_exec -c "$@" || return 1; } + else + su_exec="" + __su_cmd() { + echo "Can not switch to $switch_user: attempting to run as root" + if ! eval "$@"; then + return 1 + fi + } + fi + export su_exec +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# usage backup "days" "hours" +__backup() { + local dirs=() backup_dir backup_name backup_exclude runTime cronTime maxDays + if test -n "$1" && test -z "${1//[0-9]/}"; then + maxDays="$1" + shift 1 + else + maxDays="7" + fi + if test -n "$1" && test -z "${1//[0-9]/}"; then + cronTime="$1" + shift 1 + else + cronTime="" + fi + local exitCodeP=0 + local exitStatus=0 + local pidFile="/run/.backup.pid" + local logDir="/data/log/backups" + maxDays="${BACKUP_MAX_DAYS:-$maxDays}" + cronTime="${BACKUP_RUN_CRON:-$cronTime}" + backup_dir="$BACKUP_DIR/$(date +'%y/%m')" + backup_name="$(date +'%d_%H-%M').tar.gz" + backup_exclude="/data/logs $BACKUP_DIR $BACK_EXCLUDE_DIR" + [ -d "/data" ] && dirs+=("/data") + [ -d "/config" ] && dirs+=("/config") + [ -d "$logDir" ] || mkdir -p "$logDir" + [ -d "$backup_dir" ] || mkdir -p "$backup_dir" + [ "${#dirs[@]}" -eq 0 ] && echo "BACKUP_DIR is unset" >&2 && return 1 + [ -f "$pidFile" ] && echo "A backup job is already running" >&2 && return 1 + echo "$$" >"$pidFile" + trap "rm -f '$pidFile'" EXIT INT TERM + echo "Starting backup in $(date)" >>"$logDir/$CONTAINER_NAME" + local tar_excludes=() + for excl in $backup_exclude; do + tar_excludes+=("--exclude=$excl") + done + tar "${tar_excludes[@]}" -cfvz "$backup_dir/$backup_name" "${dirs[@]}" 2>/dev/stderr >>"$logDir/$CONTAINER_NAME" || exitCodeP=1 + if [ $exitCodeP -eq 0 ]; then + echo "Backup has completed and saved to: $backup_dir/$backup_name" + printf '%s\n\n' "Backup has completed on $(date)" >>"$logDir/$CONTAINER_NAME" + else + __rm "${backup_dir:?}/$backup_name" + echo "Backup has failed - log file saved to: $logDir/$CONTAINER_NAME" >&2 + printf '%s\n\n' "Backup has completed on $(date)" >>"$logDir/$CONTAINER_NAME" + exitStatus=1 + fi + [ -f "$pidFile" ] && __rm "$pidFile" + [ -n "$maxDays" ] && find "$BACKUP_DIR" -mtime +"$maxDays" -exec rm -Rf {} \; >/dev/null 2>&1 + if [ -n "$cronTime" ]; then + runTime=$((cronTime * 3600)) + else + return $exitStatus + fi + # Loop instead of recurse β€” recursion adds a stack frame every interval + while true; do + sleep "$runTime" + __backup "$maxDays" + done +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set variables from function calls +export INIT_DATE="${INIT_DATE:-$(date)}" +export START_SERVICES="${START_SERVICES:-yes}" +export ENTRYPOINT_MESSAGE="${ENTRYPOINT_MESSAGE:-yes}" +export ENTRYPOINT_FIRST_RUN="${ENTRYPOINT_FIRST_RUN:-yes}" +export DATA_DIR_INITIALIZED="${DATA_DIR_INITIALIZED:-no}" +export CONFIG_DIR_INITIALIZED="${CONFIG_DIR_INITIALIZED:-no}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# System +export LANG="${LANG:-C.UTF-8}" +export LC_ALL="${LANG:-C.UTF-8}" +export TZ="${TZ:-${TIMEZONE:-America/New_York}}" +export HOSTNAME="${FULL_DOMAIN_NAME:-${SERVER_HOSTNAME:-$HOSTNAME}}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Default directories +export SSL_DIR="${SSL_DIR:-/config/ssl}" +export SSL_CA="${SSL_CA:-/config/ssl/ca.crt}" +export SSL_KEY="${SSL_KEY:-/config/ssl/localhost.pem}" +export SSL_CERT="${SSL_CERT:-/config/ssl/localhost.crt}" +export LOCAL_BIN_DIR="${LOCAL_BIN_DIR:-/usr/local/bin}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Backup settings +export BACKUP_MAX_DAYS="${BACKUP_MAX_DAYS:-}" +export BACKUP_RUN_CRON="${BACKUP_RUN_CRON:-}" +export BACKUP_DIR="${BACKUP_DIR:-/data/backups}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +CONTAINER_IP4_ADDRESS="${CONTAINER_IP4_ADDRESS:-$(__get_ip4)}" +CONTAINER_IP6_ADDRESS="${CONTAINER_IP6_ADDRESS:-$(__get_ip6)}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional +export WORK_DIR="${ENV_WORK_DIR:-$WORK_DIR}" +export SET_RANDOM_PASS="${SET_RANDOM_PASS:-$(__random_password 16)}" +export PHP_INI_DIR="${PHP_INI_DIR:-$(__find_php_ini)}" +export PHP_BIN_DIR="${PHP_BIN_DIR:-$(__find_php_bin)}" +export HTTPD_CONFIG_FILE="${HTTPD_CONFIG_FILE:-$(__find_httpd_conf)}" +export NGINX_CONFIG_FILE="${NGINX_CONFIG_FILE:-$(__find_nginx_conf)}" +export MYSQL_CONFIG_FILE="${MYSQL_CONFIG_FILE:-$(__find_mysql_conf)}" +export PGSQL_CONFIG_FILE="${PGSQL_CONFIG_FILE:-$(__find_pgsql_conf)}" +export LIGHTTPD_CONFIG_FILE="${LIGHTTPD_CONFIG_FILE:-$(__find_lighttpd_conf)}" +export MARIADB_CONFIG_FILE="${MARIADB_CONFIG_FILE:-$(__find_mysql_conf)}" +export POSTGRES_CONFIG_FILE="${POSTGRES_CONFIG_FILE:-$(__find_pgsql_conf)}" +export MONGODB_CONFIG_FILE="${MONGODB_CONFIG_FILE:-$(__find_mongodb_conf)}" +export ENTRYPOINT_PID_FILE="${ENTRYPOINT_PID_FILE:-/run/.entrypoint.pid}" +export ENTRYPOINT_INIT_FILE="${ENTRYPOINT_INIT_FILE:-/config/.entrypoint.done}" +export ENTRYPOINT_DATA_INIT_FILE="${ENTRYPOINT_DATA_INIT_FILE:-/data/.docker_has_run}" +export ENTRYPOINT_CONFIG_INIT_FILE="${ENTRYPOINT_CONFIG_INIT_FILE:-/config/.docker_has_run}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# is already Initialized +if [ -z "$DATA_DIR_INITIALIZED" ]; then + if [ -f "$ENTRYPOINT_DATA_INIT_FILE" ]; then + DATA_DIR_INITIALIZED="yes" + else + DATA_DIR_INITIALIZED="no" + fi +fi +if [ -z "$CONFIG_DIR_INITIALIZED" ]; then + if [ -f "$ENTRYPOINT_CONFIG_INIT_FILE" ]; then + CONFIG_DIR_INITIALIZED="yes" + else + CONFIG_DIR_INITIALIZED="no" + fi +fi +if [ -z "$ENTRYPOINT_FIRST_RUN" ]; then + if [ -f "$ENTRYPOINT_PID_FILE" ] || [ -f "$ENTRYPOINT_INIT_FILE" ]; then + ENTRYPOINT_FIRST_RUN="no" + else + ENTRYPOINT_FIRST_RUN="yes" + fi +fi +export ENTRYPOINT_DATA_INIT_FILE DATA_DIR_INITIALIZED ENTRYPOINT_CONFIG_INIT_FILE CONFIG_DIR_INITIALIZED +export ENTRYPOINT_PID_FILE ENTRYPOINT_INIT_FILE ENTRYPOINT_FIRST_RUN +# - - - - - - - - - - - - - - - - - - - - - - - - - +# export the functions +export -f __get_pid __start_init_scripts __is_running __update_ssl_certs __create_ssl_cert +# - - - - - - - - - - - - - - - - - - - - - - - - - +# end of functions +# vim: set ft=sh ts=4 sw=4 st=4 et : diff --git a/rootfs/usr/local/etc/docker/init.d/05-dockerd.sh b/rootfs/usr/local/etc/docker/init.d/05-dockerd.sh new file mode 100755 index 0000000..b32c3ba --- /dev/null +++ b/rootfs/usr/local/etc/docker/init.d/05-dockerd.sh @@ -0,0 +1,1047 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202609030524-git +# @@Author : Jason Hempstead +# @@Contact : jason@casjaysdev.pro +# @@License : LICENSE.md +# @@ReadME : 05-dockerd.sh --help +# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments +# @@Created : Sunday, May 24, 2026 11:59 EDT +# @@File : 05-dockerd.sh +# @@Description : +# @@Changelog : New script +# @@TODO : Better documentation +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : other/start-service +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +VERSION="202609030524-git" +# - - - - - - - - - - - - - - - - - - - - - - - - - +set -e +# - - - - - - - - - - - - - - - - - - - - - - - - - +# run trap command on exit +trap '__trap_err_handler' ERR +trap 'retVal=$?;if [ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ]; then rm -Rf "$SERVICE_PID_FILE"; fi;exit $retVal' SIGINT SIGTERM +trap 'retVal=$?;if [ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ]; then rm -Rf "$SERVICE_PID_FILE"; fi;exit $retVal' SIGPWR 2>/dev/null || true +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ERR trap handler - smart about critical vs non-critical errors +__trap_err_handler() { + local retVal=$? + local command="$BASH_COMMAND" + # Ignore SIGPIPE and user interrupts + [ $retVal -eq 130 ] || [ $retVal -eq 141 ] && return $retVal + # Non-critical: file operations, text processing, user/group operations + if [[ "$command" =~ (mkdir|touch|chmod|chown|chgrp|ln|cp|mv|rm|echo|printf|cat|tee|sed|awk|grep|find|sort|uniq|adduser|addgroup|usermod|groupmod|id|getent) ]]; then + return 0 + fi + # Non-critical: conditional checks that might fail + if [[ "$command" =~ (test|\[|\[\[|kill -0|pgrep|pidof|ps) ]]; then + return 0 + fi + # Critical error - but only fail if service hasn't started yet + if [ "$SERVICE_IS_RUNNING" != "yes" ]; then + if [ -z "$NO_COLOR" ]; then + echo "❌ Critical error (exit $retVal): $command" >&2 + else + echo "Critical error (exit $retVal): $command" >&2 + fi + kill -TERM 1 2>/dev/null || exit $retVal + fi + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +SCRIPT_FILE="$0" +SERVICE_NAME="docker" +SCRIPT_NAME="${SCRIPT_FILE##*/}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Function to exit appropriately based on context +__script_exit() { + local exit_code="${1:-0}" + if [ "${BASH_SOURCE[0]}" != "${0}" ]; then + # Script is being sourced - use return + return "$exit_code" + else + # Script is being executed - use exit + exit "$exit_code" + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Exit if service is disabled +if [ -n "$DOCKER_APPNAME_ENABLED" ]; then + if [ "$DOCKER_APPNAME_ENABLED" != "yes" ]; then + export SERVICE_DISABLED="$SERVICE_NAME" + __script_exit 0 + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# setup debugging - https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html +[ -f "/config/.debug" ] && [ -z "$DEBUGGER_OPTIONS" ] && export DEBUGGER_OPTIONS="$(<"/config/.debug")" || DEBUGGER_OPTIONS="${DEBUGGER_OPTIONS:-}" +if [ "$DEBUGGER" = "on" ] || [ -f "/config/.debug" ]; then + echo "Enabling debugging" + set -o pipefail + [ -n "$DEBUGGER_OPTIONS" ] && set -"$DEBUGGER_OPTIONS" + export DEBUGGER="on" +else + set -o pipefail +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +export PATH="/usr/local/etc/docker/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# import the functions file +if [ -f "/usr/local/etc/docker/functions/entrypoint.sh" ]; then + . "/usr/local/etc/docker/functions/entrypoint.sh" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# import variables +for set_env in "/root/env.sh" "/usr/local/etc/docker/env"/*.sh "/config/env"/*.sh; do + if [ -f "$set_env" ]; then + . "$set_env" + fi +done +# - - - - - - - - - - - - - - - - - - - - - - - - - +# exit if __start_init_scripts function hasn't been Initialized +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 +# Clean up any stale PID file for this service on startup +if [ -n "$SERVICE_NAME" ] && [ -f "/run/init.d/$SERVICE_NAME.pid" ]; then + old_pid=$(<"/run/init.d/$SERVICE_NAME.pid") 2>/dev/null + if [ -n "$old_pid" ] && ! kill -0 "$old_pid" 2>/dev/null; then + if [ -z "$NO_COLOR" ]; then + echo "🧹 Removing stale PID file for $SERVICE_NAME" + else + echo "Removing stale PID file for $SERVICE_NAME" + fi + rm -f "/run/init.d/$SERVICE_NAME.pid" + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom functions + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Script to execute +START_SCRIPT="/usr/local/etc/docker/exec/$SERVICE_NAME" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Reset environment before executing service +RESET_ENV="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set webroot +WWW_ROOT_DIR="/usr/local/share/httpd/default" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Default predefined variables +# set data directory +DATA_DIR="/data/docker" +# set config directory +CONF_DIR="/config/docker" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set the containers etc directory +ETC_DIR="/etc/docker" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set the var dir +VAR_DIR="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set the temp dir +TMP_DIR="/tmp/docker" +# set scripts pid dir +RUN_DIR="/run/docker" +# set log directory +LOG_DIR="/data/logs/docker" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the working dir +WORK_DIR="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# port which service is listening on +SERVICE_PORT="2375" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# User to use to launch service - IE: postgres +# normally root +RUNAS_USER="root" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# User and group in which the service switches to - IE: nginx,apache,mysql,postgres +# execute command as another user +SERVICE_USER="root" +# Set the service group +SERVICE_GROUP="root" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set password length +RANDOM_PASS_USER="" +RANDOM_PASS_ROOT="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set user and group ID +# set the user id +SERVICE_UID="0" +# set the group id +SERVICE_GID="0" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# execute command variables - keep single quotes variables will be expanded later +# command to execute +EXEC_CMD_BIN='dockerd' +# command arguments +EXEC_CMD_ARGS='-H tcp://0.0.0.0:$SERVICE_PORT ' +# command arguments +EXEC_CMD_ARGS+='-H unix:///var/run/docker.sock ' +# command arguments +EXEC_CMD_ARGS+='-H unix:///tmp/docker.sock --config-file $CONF_DIR/daemon.json' +# execute script before +EXEC_PRE_SCRIPT='' +# Set to no if the service is not running otherwise leave blank +SERVICE_USES_PID='' +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Is this service a web server +IS_WEB_SERVER="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Is this service a database server +IS_DATABASE_SERVICE="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Does this service use a database server +USES_DATABASE_SERVICE="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set defualt type - [custom,sqlite,redis,postgres,mariadb,mysql,couchdb,mongodb,supabase] +DATABASE_SERVICE_TYPE="sqlite" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Show message before execute +PRE_EXEC_MESSAGE="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the wait time to execute __post_execute function - minutes +POST_EXECUTE_WAIT_TIME="1" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Update path var +PATH="$PATH:." +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Lets get containers ip address +IP4_ADDRESS="$(__get_ip4)" +IP6_ADDRESS="$(__get_ip6)" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Where to save passwords to +# directory to save username/password for root user +ROOT_FILE_PREFIX="/config/secure/auth/root" +# directory to save username/password for normal user +USER_FILE_PREFIX="/config/secure/auth/user" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# root/admin user info password/random] +# root user name +root_user_name="${DOCKER_ROOT_USER_NAME:-}" +# root user password +root_user_pass="${DOCKER_ROOT_PASS_WORD:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Normal user info [password/random] +# normal user name +user_name="${DOCKER_USER_NAME:-}" +# normal user password +user_pass="${DOCKER_USER_PASS_WORD:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Load variables from config +# Generated by my dockermgr script +[ -f "/config/env/docker.script.sh" ] && . "/config/env/docker.script.sh" +# Overwrite the variabes +[ -f "/config/env/docker.sh" ] && . "/config/env/docker.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional predefined variables + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional variables + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Specifiy custom directories to be created +ADD_APPLICATION_FILES="" +ADD_APPLICATION_DIRS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +APPLICATION_FILES="$LOG_DIR/$SERVICE_NAME.log" +APPLICATION_DIRS="$ETC_DIR $CONF_DIR $DATA_DIR $LOG_DIR $TMP_DIR $RUN_DIR $VAR_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional config dirs - will be Copied to /etc/$name +ADDITIONAL_CONFIG_DIRS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# define variables that need to be loaded into the service - escape quotes - var=\"value\",other=\"test\" +CMD_ENV="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Overwrite based on file/directory + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Per Application Variables or imports + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom commands to run before copying to /config +__run_precopy() { + # Define environment + local hostname=${HOSTNAME} + [ -d "/run/healthcheck" ] || mkdir -p "/run/healthcheck" + # Seed /config/$SERVICE_NAME from the baked /etc copy if daemon.json is missing, + # then replace the /etc/$SERVICE_NAME directory with a symlink to /config/$SERVICE_NAME + # so both paths always resolve to the same processed config. + if [ -d "$ETC_DIR" ] && ! [ -L "$ETC_DIR" ]; then + if [ ! -f "$CONF_DIR/.initialized" ]; then + mkdir -p "$CONF_DIR" + cp -Rf "$ETC_DIR/." "$CONF_DIR/" 2>/dev/null || true + fi + rm -Rf "$ETC_DIR" + ln -sf "$CONF_DIR" "$ETC_DIR" + fi + # allow custom functions + if builtin type -t __run_precopy_local | grep -q -- 'function'; then __run_precopy_local; fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom prerun functions - IE setup WWW_ROOT_DIR +__execute_prerun() { + # Define environment + local hostname=${HOSTNAME} + # Define actions/commands + + # allow custom functions + if builtin type -t __execute_prerun_local | grep -q -- 'function'; then __execute_prerun_local; fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Run any pre-execution checks +__run_pre_execute_checks() { + # Set variables + local exitStatus=0 + # message to show at start + local pre_execute_checks_MessageST="Running preexecute check for $SERVICE_NAME" + # message to show at completion + local pre_execute_checks_MessageEnd="Finished preexecute check for $SERVICE_NAME" + __banner "$pre_execute_checks_MessageST" + # Put command to execute in parentheses + { + # Setup cgroups v2 for Docker-in-Docker without mounting host cgroups + echo "Setting up cgroups v2 for Docker-in-Docker" + + # Check if cgroup2 filesystem needs to be mounted + if ! mountpoint -q /sys/fs/cgroup 2>/dev/null; then + echo "Mounting cgroup2 filesystem" + mkdir -p /sys/fs/cgroup + mount -t cgroup2 none /sys/fs/cgroup || { + echo "Warning: Failed to mount cgroup2 filesystem" + } + fi + + # Verify we have cgroup v2 (unified hierarchy) + if [ -f "/sys/fs/cgroup/cgroup.controllers" ]; then + echo "Detected cgroup v2 (unified hierarchy)" + + # Get available controllers + CONTROLLERS=$(cat /sys/fs/cgroup/cgroup.controllers 2>/dev/null) + echo "Available controllers: $CONTROLLERS" + + # In cgroups v2, we need to move all processes out of root cgroup + # before we can enable subtree_control (no internal processes rule) + + # Create init cgroup for system processes (including PID 1) + if [ ! -d "/sys/fs/cgroup/init" ]; then + echo "Creating init cgroup for system processes" + mkdir -p /sys/fs/cgroup/init + fi + + # Move all root cgroup processes to init cgroup + # This allows us to enable subtree_control at root level + if [ -f "/sys/fs/cgroup/cgroup.procs" ] && [ -w "/sys/fs/cgroup/init/cgroup.procs" ]; then + echo "Moving processes to init cgroup" + while read -r pid; do + echo "$pid" > /sys/fs/cgroup/init/cgroup.procs 2>/dev/null || true + done < /sys/fs/cgroup/cgroup.procs + fi + + # Now enable controllers at root level (after processes moved out) + if [ -w "/sys/fs/cgroup/cgroup.subtree_control" ]; then + echo "Enabling cgroup v2 controller delegation at root" + for controller in $CONTROLLERS; do + echo "+$controller" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true + done + fi + + # Create docker cgroup - this creates proper cgroup with interface files + if [ ! -d "/sys/fs/cgroup/docker" ]; then + echo "Creating docker cgroup" + mkdir -p /sys/fs/cgroup/docker + fi + + # Enable controllers in docker cgroup for nested containers + if [ -f "/sys/fs/cgroup/docker/cgroup.subtree_control" ]; then + echo "Enabling controllers in docker cgroup" + for controller in $CONTROLLERS; do + echo "+$controller" > /sys/fs/cgroup/docker/cgroup.subtree_control 2>/dev/null || true + done + fi + + # Verify setup + if [ -f "/sys/fs/cgroup/docker/cgroup.subtree_control" ]; then + echo "Docker cgroup setup successful" + echo "Enabled controllers: $(cat /sys/fs/cgroup/docker/cgroup.subtree_control 2>/dev/null)" + else + echo "Warning: Docker cgroup subtree_control not available" + fi + else + echo "Warning: cgroup v2 not available, Docker-in-Docker may have limited functionality" + fi + + # Remove a stale dockerd pidfile before starting + # /tmp persists across `docker restart` (same container filesystem), but the PID + # namespace is reset on every restart, so a low PID number like the one dockerd + # wrote last time can coincidentally be reused by an unrelated process very early + # in the new namespace. dockerd's own startup check then sees /proc/ exists + # and refuses to start with "process with PID is still running", even though + # it is not actually the previous dockerd. This init script is the sole owner of + # the dockerd lifecycle (enforced separately via SERVICE_PID_FILE), so it is always + # safe to clear docker's own pidfile here before each start attempt. + [ -f "/tmp/docker.pid" ] && rm -f "/tmp/docker.pid" + + # Clean up orphaned containers before dockerd starts + # This prevents "failed to load container" errors on restart + if [ -d "/data/docker/containers" ]; then + echo "Checking for orphaned containers" + for container_dir in /data/docker/containers/*/; do + if [ -d "$container_dir" ]; then + if [ ! -f "${container_dir}config.v2.json" ]; then + echo "Removing orphaned container: ${container_dir##*/}" + rm -rf "$container_dir" + fi + fi + done + fi + + [ -L "/config/docker/daemon.json" ] && unlink "/config/docker/daemon.json" + if [ -n "$DOCKER_REGISTRIES" ]; then + local set_reg="" + local get_reg="" + DOCKER_REGISTRIES="${DOCKER_REGISTRIES//,/ }" + for get_reg in $DOCKER_REGISTRIES; do + set_reg+="\"$get_reg\" " + done + registry="$(printf '%s\n' "$set_reg" | tr ' ' '\n' | sort -V | grep -v -- '^$' | tr '\n' ',' | sed 's|,$||g;s| ||g' | grep -E -- '^')" + export registry + else + unset registry + fi + if [ ! -f "$HOME/.docker/config.json" ]; then + if [ -n "$registry" ]; then + cat </dev/null +{ + "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_TOKEN" } }, + "HttpHeaders": { "User-Agent": "Docker-Client/23.0.1 (linux)" }, + "insecure-registries" : [$registry] +} +EOF + else + cat </dev/null +{ + "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_TOKEN" } }, + "HttpHeaders": { "User-Agent": "Docker-Client/23.0.1 (linux)" } +} +EOF + fi + fi + if [ ! -f "/config/docker/daemon.json" ]; then + if [ -n "$registry" ]; then + cat </dev/null +{ + "ip": "0.0.0.0", + "iptables": true, + "log-level": "error", + "experimental": true, + "pidfile": "/tmp/docker.pid", + "cgroup-parent": "/docker", + "storage-driver": "fuse-overlayfs", + "default-address-pools": [ + {"base": "172.17.0.0/12", "size": 24}, + {"base": "192.168.0.0/16", "size": 24}, + {"base": "10.0.0.0/8", "size": 24} + ], + "insecure-registries": [$registry] +} +EOF + else + cat </dev/null +{ + "ip": "0.0.0.0", + "iptables": true, + "log-level": "error", + "experimental": true, + "pidfile": "/tmp/docker.pid", + "cgroup-parent": "/docker", + "storage-driver": "fuse-overlayfs", + "default-address-pools": [ + {"base": "172.17.0.0/12", "size": 24}, + {"base": "192.168.0.0/16", "size": 24}, + {"base": "10.0.0.0/8", "size": 24} + ] +} +EOF + fi + fi + [ -f "$CONF_DIR/daemon.json" ] && sed -i 's|"REPLACE_DOCKER_REGISTRIES"|'$registry'|g' "$CONF_DIR/daemon.json" + } + exitStatus=$? + __banner "$pre_execute_checks_MessageEnd: Status $exitStatus" + + # show exit message + if [ $exitStatus -ne 0 ]; then + echo "The pre-execution check has failed" >&2 + [ -f "$SERVICE_PID_FILE" ] && rm -Rf "$SERVICE_PID_FILE" + __script_exit 1 + fi + # allow custom functions + if builtin type -t __run_pre_execute_checks_local | grep -q -- 'function'; then __run_pre_execute_checks_local; fi + # exit function + return $exitStatus +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to update config files - IE: change port +__update_conf_files() { + # default exit code + local exitCode=0 + # set hostname + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + # - - - - - - - - - - - - - - - - - - - - - - - - - + # delete files + #__rm "" + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # custom commands + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # replace variables + # __replace "" "" "$CONF_DIR/docker.conf" + # replace variables recursively + # __find_replace "" "" "$CONF_DIR" + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # define actions + __symlink "$DATA_DIR" "/var/lib/docker" + chmod 777 "$DATA_DIR" "/var/lib/docker" + # Mark config as fully initialised so __run_precopy skips re-seeding on restart + touch "$CONF_DIR/.initialized" 2>/dev/null || true + # allow custom functions + if builtin type -t __update_conf_files_local | grep -q -- 'function'; then __update_conf_files_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# function to run before executing +__pre_execute() { + # default exit code + local exitCode=0 + # set hostname + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + # execute if directories is empty + # __is_dir_empty "$CONF_DIR" && true + # - - - - - - - - - - - - - - - - - - - - - - - - - + # define actions to run after copying to /config + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # unset unneeded variables + unset sysname + # Lets wait a few seconds before continuing + sleep 2 + # allow custom functions + if builtin type -t __pre_execute_local | grep -q -- 'function'; then __pre_execute_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# function to run after executing +__post_execute() { + # init pid var + local pid="" + # set default exit code + local retVal=0 + # how long to wait before executing + local ctime=${POST_EXECUTE_WAIT_TIME:-1} + # convert minutes to seconds + local waitTime=$((ctime * 60)) + # message to show at start + local postMessageST="Running post commands for $SERVICE_NAME" + # message to show at completion + local postMessageEnd="Finished post commands for $SERVICE_NAME" + # wait + sleep $waitTime + # execute commands after waiting + ( + # show message + __banner "$postMessageST" + # commands to execute + sleep 5 + # show exit message + __banner "$postMessageEnd: Status $retVal" + ) 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" & + # fire-and-forget: backgrounded subshell always succeeds at launch + retVal=0 + # allow custom functions + if builtin type -t __post_execute_local | grep -q -- 'function'; then __post_execute_local; fi + # exit function + return $retVal +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to update config files - IE: change port +__pre_message() { + local exitCode=0 + [ -n "$PRE_EXEC_MESSAGE" ] && eval echo "$PRE_EXEC_MESSAGE" + # execute commands + + # allow custom functions + if builtin type -t __pre_message_local | grep -q -- 'function'; then __pre_message_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to setup ssl support +__update_ssl_conf() { + local exitCode=0 + # set hostname + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + # execute commands + + # allow custom functions + if builtin type -t __update_ssl_conf_local | grep -q -- 'function'; then __update_ssl_conf_local; fi + # set exitCode + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__create_service_env() { + local exitCode=0 + if [ ! -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ]; then + cat </dev/null +# - - - - - - - - - - - - - - - - - - - - - - - - - +# root/admin user info [password/random] +#ENV_ROOT_USER_NAME="${ENV_ROOT_USER_NAME:-$DOCKER_ROOT_USER_NAME}" # root user name +#ENV_ROOT_USER_PASS="${ENV_ROOT_USER_NAME:-$DOCKER_ROOT_PASS_WORD}" # root user password +#root_user_name="${ENV_ROOT_USER_NAME:-$root_user_name}" # +#root_user_pass="${ENV_ROOT_USER_PASS:-$root_user_pass}" # +# - - - - - - - - - - - - - - - - - - - - - - - - - +#Normal user info [password/random] +#ENV_USER_NAME="${ENV_USER_NAME:-$DOCKER_USER_NAME}" # +#ENV_USER_PASS="${ENV_USER_PASS:-$DOCKER_USER_PASS_WORD}" # +#user_name="${ENV_USER_NAME:-$user_name}" # normal user name +#user_pass="${ENV_USER_PASS:-$user_pass}" # normal user password + +EOF + fi + if [ ! -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" ]; then + cat <<'EOF' >"/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Local overrides - sourced after the main env file. +# Redefine any of these functions to customise behaviour. +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_precopy_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__execute_prerun_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_pre_execute_checks_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__update_conf_files_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__pre_execute_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__post_execute_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__pre_message_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__update_ssl_conf_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +EOF + fi + __file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" || exitCode=$((exitCode + 1)) + __file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" || exitCode=$((exitCode + 1)) + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# script to start server +__run_start_script() { + local runExitCode=0 + # expand variables + local workdir="$(eval echo "${WORK_DIR:-}")" + # expand variables + local cmd="$(eval echo "${EXEC_CMD_BIN:-}")" + # expand variables + local args="$(eval echo "${EXEC_CMD_ARGS:-}")" + # expand variables + local name="$(eval echo "${EXEC_CMD_NAME:-}")" + # expand variables + local pre="$(eval echo "${EXEC_PRE_SCRIPT:-}")" + # expand variables + local extra_env="$(eval echo "${CMD_ENV//,/ }")" + # expand variables + local lc_type="$(eval echo "${LANG:-${LC_ALL:-$LC_CTYPE}}")" + # expand variables + local home="$(eval echo "${workdir//\/root/\/tmp\/docker}")" + # expand variables + local path="$(eval echo "$PATH")" + # expand variables + local message="$(eval echo "")" + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + if [ -f "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" ]; then + . "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" + fi + # + if [ -z "$cmd" ]; then + __post_execute 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" + retVal=$? + __log_info "Initialization of $SCRIPT_NAME has completed" + __script_exit $retVal + else + # ensure the command exists + if [ ! -x "$cmd" ]; then + __log_error "$name is not a valid executable" + return 2 + fi + # check and exit if already running (respects SERVICE_USES_PID in __proc_check) + if __proc_check "$name" || __proc_check "$cmd"; then + __log_debug "Service $name is already running" + return 0 + else + # - - - - - - - - - - - - - - - - - - - - - - - - - + # show message if env exists + if [ -n "$cmd" ]; then + if [ -n "$SERVICE_USER" ]; then + __log_info "Setting up $cmd to run as $SERVICE_USER" + else + SERVICE_USER="root" + fi + if [ -n "$SERVICE_PORT" ]; then + __log_info "$name will be running on port $SERVICE_PORT" + else + SERVICE_PORT="" + fi + fi + if [ -n "$pre" ] && command -v "$pre" &>/dev/null; then + export cmd_exec="$pre $cmd $args" + message="Starting service: $name $args through $pre" + else + export cmd_exec="$cmd $args" + message="Starting service: $name $args" + fi + if [ -n "$su_exec" ]; then + __log_debug "Using $su_exec" | tee -a -p "/data/logs/init.txt" + fi + __log_info "$message" | tee -a -p "/data/logs/init.txt" + __su_cmd touch "$SERVICE_PID_FILE" + # W14: invalidate cached START_SCRIPT if key variables changed + local _script_hash_src="$cmd $args $SERVICE_USER $RESET_ENV $su_exec" + local _script_hash + _script_hash=$(printf '%s' "$_script_hash_src" | md5sum 2>/dev/null | cut -c1-8 || true) + if [ -f "${START_SCRIPT}.hash" ] && [ -f "$START_SCRIPT" ]; then + if [ "$(cat "${START_SCRIPT}.hash" 2>/dev/null)" != "$_script_hash" ]; then + rm -f "$START_SCRIPT" "${START_SCRIPT}.hash" + fi + fi + if [ "$RESET_ENV" = "yes" ]; then + # RESET_ENV=yes intentionally strips all inherited vars; only explicit vars are passed + if [ ! -f "$START_SCRIPT" ]; then + # Use printf %q to safely quote each env component for embedding in the script + local _q_home _q_lc _q_path _q_sysname _q_svcuser _q_su _q_cmd _q_args _q_extra + _q_home=$(printf '%q' "$home") + _q_lc=$(printf '%q' "$lc_type") + _q_path=$(printf '%q' "$path") + _q_sysname=$(printf '%q' "$sysname") + _q_svcuser=$(printf '%q' "${SERVICE_USER:-$RUNAS_USER}") + _q_su="${su_exec:+$(printf '%q ' $su_exec)}" + _q_cmd=$(printf '%q' "$cmd") + _q_args=$(printf '%q ' $args) + _q_extra=$(printf '%q ' $extra_env) + { + printf '#!/usr/bin/env bash\n' + printf "trap 'exitCode=\$?;[ \$exitCode -ne 0 ] && [ -f \"\$SERVICE_PID_FILE\" ] && rm -Rf \"\$SERVICE_PID_FILE\";exit \$exitCode' EXIT\n" + printf 'set -Eeo pipefail\n' + printf '# Setting up %s to run as %s with env\n' "$cmd" "${SERVICE_USER:-root}" + printf 'retVal=10\n' + printf 'SERVICE_NAME=%q\n' "$SERVICE_NAME" + printf 'SERVICE_PID_FILE=%q\n' "$SERVICE_PID_FILE" + printf 'LOG_DIR=%q\n' "$LOG_DIR" + printf '%senv -i HOME=%s LC_CTYPE=%s PATH=%s HOSTNAME=%s USER=%s %s %s %s 2>>"/dev/stderr" >>"$LOG_DIR/$SERVICE_NAME.log" &\n' \ + "$_q_su" "$_q_home" "$_q_lc" "$_q_path" "$_q_sysname" "$_q_svcuser" "$_q_extra" "$_q_cmd" "$_q_args" + printf 'execPid=$!\n' + printf 'sleep 1\n' + printf 'if [ -n "$execPid" ] && kill -0 "$execPid" 2>/dev/null; then\n' + printf ' echo "$execPid" >"$SERVICE_PID_FILE"\n' + printf ' retVal=0\n' + printf ' printf '"'"'%%s\n'"'"' "$SERVICE_NAME: $execPid" >"/run/healthcheck/$SERVICE_NAME"\n' + printf 'else\n' + printf ' retVal=10\n' + printf ' echo "Failed to start service %s" >&2\n' "$cmd" + printf 'fi\n' + printf 'exit $retVal\n' + } >"$START_SCRIPT" + printf '%s' "$_script_hash" >"${START_SCRIPT}.hash" + fi + else + if [ ! -f "$START_SCRIPT" ]; then + local _q_su _q_cmd _q_args _q_path _q_home + _q_su="${su_exec:+$(printf '%q ' $su_exec)}" + _q_cmd=$(printf '%q' "$cmd") + _q_args=$(printf '%q ' $args) + _q_path=$(printf '%q' "$path") + _q_home=$(printf '%q' "$home") + { + printf '#!/usr/bin/env bash\n' + printf "trap 'exitCode=\$?;[ \$exitCode -ne 0 ] && [ -f \"\$SERVICE_PID_FILE\" ] && rm -Rf \"\$SERVICE_PID_FILE\";exit \$exitCode' EXIT\n" + printf 'set -Eeo pipefail\n' + printf '# Setting up %s to run as %s\n' "$cmd" "${SERVICE_USER:-root}" + printf 'export PATH=%s\n' "$_q_path" + printf 'export HOME=%s\n' "$_q_home" + printf 'retVal=10\n' + printf 'SERVICE_NAME=%q\n' "$SERVICE_NAME" + printf 'SERVICE_PID_FILE=%q\n' "$SERVICE_PID_FILE" + printf 'LOG_DIR=%q\n' "$LOG_DIR" + printf '%s%s %s 2>>"/dev/stderr" >>"$LOG_DIR/$SERVICE_NAME.log" &\n' \ + "$_q_su" "$_q_cmd" "$_q_args" + printf 'execPid=$!\n' + printf 'sleep 1\n' + printf 'if [ -n "$execPid" ] && kill -0 "$execPid" 2>/dev/null; then\n' + printf ' echo "$execPid" >"$SERVICE_PID_FILE"\n' + printf ' retVal=0\n' + printf 'else\n' + printf ' retVal=10\n' + printf ' echo "Failed to start service %s" >&2\n' "$cmd" + printf 'fi\n' + printf 'exit $retVal\n' + } >"$START_SCRIPT" + printf '%s' "$_script_hash" >"${START_SCRIPT}.hash" + fi + fi + fi + if [ ! -x "$START_SCRIPT" ]; then + chmod 755 -Rf "$START_SCRIPT" + fi + if [ "$CONTAINER_INIT" != "yes" ]; then + # W15: launch as bash, not sh, since the generated script uses bash-specific features + bash "$START_SCRIPT" + runExitCode=$? + fi + fi + return $runExitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# username and password actions +__run_secure_function() { + local filesperms + if [ -n "$user_name" ] || [ -n "$user_pass" ]; then + for filesperms in "${USER_FILE_PREFIX}"/*; do + if [ -e "$filesperms" ]; then + chmod -Rf 600 "$filesperms" + chown -Rf $SERVICE_USER:$SERVICE_USER "$filesperms" 2>/dev/null + fi + done 2>/dev/null | tee -p -a "/data/logs/init.txt" + fi + if [ -n "$root_user_name" ] || [ -n "$root_user_pass" ]; then + for filesperms in "${ROOT_FILE_PREFIX}"/*; do + if [ -e "$filesperms" ]; then + chmod -Rf 600 "$filesperms" + chown -Rf $SERVICE_USER:$SERVICE_USER "$filesperms" 2>/dev/null + fi + done 2>/dev/null | tee -p -a "/data/logs/init.txt" + fi + unset filesperms +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow ENV_ variable - Import env file +__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" +__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# default exit code +SERVICE_EXIT_CODE=0 +# application specific +# set the binary name +EXEC_CMD_NAME="${EXEC_CMD_BIN##*/}" +# set the pid file location +SERVICE_PID_FILE="/run/init.d/$EXEC_CMD_NAME.pid" +# check if running +SERVICE_PID_NUMBER="$(__pgrep "$EXEC_CMD_NAME" || echo '')" +# set full path +EXEC_CMD_BIN="$(type -P "$EXEC_CMD_BIN" || echo "$EXEC_CMD_BIN")" +# set full path +EXEC_PRE_SCRIPT="$(type -P "$EXEC_PRE_SCRIPT" || echo "$EXEC_PRE_SCRIPT")" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Only run check when explicitly requested +if [ "$1" = "check" ] && __check_service "$1"; then + SERVICE_IS_RUNNING=yes +elif [ "$1" = "check" ]; then + SERVICE_IS_RUNNING="no" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ensure needed directories exists +[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR" +[ -d "$RUN_DIR" ] || mkdir -p "$RUN_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create auth directories +[ -n "$USER_FILE_PREFIX" ] && { [ -d "$USER_FILE_PREFIX" ] || mkdir -p "$USER_FILE_PREFIX"; } +[ -n "$ROOT_FILE_PREFIX" ] && { [ -d "$ROOT_FILE_PREFIX" ] || mkdir -p "$ROOT_FILE_PREFIX"; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +[ -n "$RUNAS_USER" ] || RUNAS_USER="root" +[ -n "$SERVICE_USER" ] || SERVICE_USER="$RUNAS_USER" +[ -n "$SERVICE_GROUP" ] || SERVICE_GROUP="${SERVICE_USER:-$RUNAS_USER}" +[ "$IS_WEB_SERVER" = "yes" ] && RESET_ENV="yes" && __is_htdocs_mounted +[ "$IS_WEB_SERVER" = "yes" ] && [ -z "$SERVICE_PORT" ] && SERVICE_PORT="80" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Database env +if [ "$IS_DATABASE_SERVICE" = "yes" ] || [ "$USES_DATABASE_SERVICE" = "yes" ]; then + RESET_ENV="no" + DATABASE_CREATE="${ENV_DATABASE_CREATE:-$DATABASE_CREATE}" + DATABASE_USER_NORMAL="${ENV_DATABASE_USER:-${DATABASE_USER_NORMAL:-$user_name}}" + DATABASE_PASS_NORMAL="${ENV_DATABASE_PASSWORD:-${DATABASE_PASS_NORMAL:-$user_pass}}" + DATABASE_USER_ROOT="${ENV_DATABASE_ROOT_USER:-${DATABASE_USER_ROOT:-$root_user_name}}" + DATABASE_PASS_ROOT="${ENV_DATABASE_ROOT_PASSWORD:-${DATABASE_PASS_ROOT:-$root_user_pass}}" + if [ -n "$DATABASE_PASS_NORMAL" ] && [ ! -f "${USER_FILE_PREFIX}/db_pass_user" ]; then + echo "$DATABASE_PASS_NORMAL" >"${USER_FILE_PREFIX}/db_pass_user" + fi + if [ -n "$DATABASE_PASS_ROOT" ] && [ ! -f "${ROOT_FILE_PREFIX}/db_pass_root" ]; then + echo "$DATABASE_PASS_ROOT" >"${ROOT_FILE_PREFIX}/db_pass_root" + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# [DATABASE_DIR_[SQLITE,REDIS,POSTGRES,MARIADB,COUCHDB,MONGODB,SUPABASE]] +if [ "$DATABASE_SERVICE_TYPE" = "custom" ]; then + DATABASE_DIR="${DATABASE_DIR_CUSTOM:-/data/db/custom}" + DATABASE_BASE_DIR="${DATABASE_DIR_CUSTOM:-/data/db/custom}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_CUSTOM:-/usr/local/share/httpd/admin/databases}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_CUSTOM:-/admin/dbadmin}" +elif [ "$SERVICE_NAME" = "redis" ] || [ "$DATABASE_SERVICE_TYPE" = "redis" ]; then + DATABASE_DIR="${DATABASE_DIR_REDIS:-/data/db/redis}" + DATABASE_BASE_DIR="${DATABASE_DIR_REDIS:-/data/db/redis}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_REDIS:-/usr/local/share/httpd/admin/redis}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_REDIS:-/admin/redis}" +elif [ "$SERVICE_NAME" = "postgres" ] || [ "$DATABASE_SERVICE_TYPE" = "postgres" ]; then + DATABASE_DIR="${DATABASE_DIR_POSTGRES:-/data/db/postgres}" + DATABASE_BASE_DIR="${DATABASE_DIR_POSTGRES:-/data/db/postgres}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_POSTGRES:-/usr/local/share/httpd/admin/postgres}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_POSTGRES:-/admin/postgres}" +elif [ "$SERVICE_NAME" = "mariadb" ] || [ "$DATABASE_SERVICE_TYPE" = "mariadb" ]; then + DATABASE_DIR="${DATABASE_DIR_MARIADB:-/data/db/mariadb}" + DATABASE_BASE_DIR="${DATABASE_DIR_MARIADB:-/data/db/mariadb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MARIADB:-/usr/local/share/httpd/admin/mysql}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MARIADB:-/admin/mysql}" +elif [ "$SERVICE_NAME" = "mysql" ] || [ "$DATABASE_SERVICE_TYPE" = "mysql" ]; then + DATABASE_DIR="${DATABASE_DIR_MYSQL:-/data/db/mysql}" + DATABASE_BASE_DIR="${DATABASE_DIR_MYSQL:-/data/db/mysql}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MYSQL:-/usr/local/share/httpd/admin/mysql}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MYSQL:-/admin/mysql}" +elif [ "$SERVICE_NAME" = "couchdb" ] || [ "$DATABASE_SERVICE_TYPE" = "couchdb" ]; then + DATABASE_DIR="${DATABASE_DIR_COUCHDB:-/data/db/couchdb}" + DATABASE_BASE_DIR="${DATABASE_DIR_COUCHDB:-/data/db/couchdb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_COUCHDB:-/usr/local/share/httpd/admin/couchdb}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_COUCHDB:-/admin/couchdb}" +elif [ "$SERVICE_NAME" = "mongodb" ] || [ "$DATABASE_SERVICE_TYPE" = "mongodb" ]; then + DATABASE_DIR="${DATABASE_DIR_MONGODB:-/data/db/mongodb}" + DATABASE_BASE_DIR="${DATABASE_DIR_MONGODB:-/data/db/mongodb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MONGODB:-/usr/local/share/httpd/admin/mongodb}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MONGODB:-/admin/mongodb}" +elif [ "$SERVICE_NAME" = "supabase" ] || [ "$DATABASE_SERVICE_TYPE" = "supabase" ]; then + DATABASE_DIR="${DATABASE_DIR_SUPABASE:-/data/db/supabase}" + DATABASE_BASE_DIR="${DATABASE_DIR_SUPABASE:-/data/db/supabase}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_SUPABASE:-/usr/local/share/httpd/admin/supabase}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_SUPBASE:-/admin/supabase}" +elif [ "$SERVICE_NAME" = "sqlite" ] || [ "$DATABASE_SERVICE_TYPE" = "sqlite" ]; then + DATABASE_DIR="${DATABASE_DIR_SQLITE:-/data/db/sqlite}/$SERVER_NAME" + DATABASE_BASE_DIR="${DATABASE_DIR_SQLITE:-/data/db/sqlite}/$SERVER_NAME" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_SQLITE:-/usr/local/share/httpd/admin/sqlite}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_SQLITE:-/admin/sqlite}" + [ -d "$DATABASE_DIR" ] || mkdir -p "$DATABASE_DIR" + chmod 777 "$DATABASE_DIR" +fi +[ -n "$DATABASE_ADMIN_WWW_ROOT" ] && { [ ! -d "$DATABASE_ADMIN_WWW_ROOT" ] || mkdir -p "${DATABASE_ADMIN_WWW_ROOT}"; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow variables via imports - Overwrite existing +[ -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ] && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set password to random if variable is random +[ "$user_pass" = "random" ] && user_pass="$(__random_password ${RANDOM_PASS_USER:-16})" +# - - - - - - - - - - - - - - - - - - - - - - - - - +[ "$root_user_pass" = "random" ] && root_user_pass="$(__random_password ${RANDOM_PASS_ROOT:-16})" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow setting initial users and passwords via environment and save to file +[ -n "$user_name" ] && echo "$user_name" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_name" +[ -n "$user_pass" ] && echo "$user_pass" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" +[ -n "$root_user_name" ] && echo "$root_user_name" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name" +[ -n "$root_user_pass" ] && echo "$root_user_pass" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create needed dirs +[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR" +[ -d "$RUN_DIR" ] || mkdir -p "$RUN_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow per init script usernames and passwords +__file_exists_with_content "${USER_FILE_PREFIX}/${SERVICE_NAME}_name" && user_name="$(<"${USER_FILE_PREFIX}/${SERVICE_NAME}_name")" +__file_exists_with_content "${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" && user_pass="$(<"${USER_FILE_PREFIX}/${SERVICE_NAME}_pass")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name" && root_user_name="$(<"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" && root_user_pass="$(<"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass")" +__file_exists_with_content "${USER_FILE_PREFIX}/db_pass_user" && DATABASE_PASS_NORMAL="$(<"${USER_FILE_PREFIX}/db_pass_user")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/db_pass_root" && DATABASE_PASS_ROOT="$(<"${ROOT_FILE_PREFIX}/db_pass_root")" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set hostname for script +sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +__create_service_env +# - - - - - - - - - - - - - - - - - - - - - - - - - +# pre-run function +__execute_prerun +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create user if needed +__create_service_user "$SERVICE_USER" "$SERVICE_GROUP" "${WORK_DIR:-/home/$SERVICE_USER}" "${SERVICE_UID:-}" "${SERVICE_GID:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Modify user if needed +__set_user_group_id $SERVICE_USER ${SERVICE_UID:-} ${SERVICE_GID:-} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Create base directories +__setup_directories +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set switch user command +__switch_to_user +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Initialize the home/working dir +__init_working_dir +# - - - - - - - - - - - - - - - - - - - - - - - - - +# show init message +__pre_message +# - - - - - - - - - - - - - - - - - - - - - - - - - +# +__initialize_db_users +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Initialize ssl +__update_ssl_conf +__update_ssl_certs +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set permissions in ${USER_FILE_PREFIX} and ${ROOT_FILE_PREFIX} +__run_secure_function +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_precopy +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Replace variables +__initialize_replace_variables "$CONF_DIR" "$ADDITIONAL_CONFIG_DIRS" "$WWW_ROOT_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# +__initialize_database +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Updating config files +__update_conf_files +# - - - - - - - - - - - - - - - - - - - - - - - - - +# run the pre execute commands +__pre_execute +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set permissions +__fix_permissions "$SERVICE_USER" "$SERVICE_GROUP" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# +__run_pre_execute_checks 2>/dev/stderr | tee -a -p "/data/logs/entrypoint.log" "/data/logs/init.txt" || return 20 +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_start_script 2>>/dev/stderr | tee -p -a "/data/logs/entrypoint.log" +errorCode=${PIPESTATUS[0]} +if [ -n "$EXEC_CMD_BIN" ]; then + if [ "$errorCode" -eq 0 ]; then + SERVICE_EXIT_CODE=0 + SERVICE_IS_RUNNING="yes" + else + SERVICE_EXIT_CODE=$errorCode + SERVICE_IS_RUNNING="${SERVICE_IS_RUNNING:-no}" + if [ ! -s "$SERVICE_PID_FILE" ]; then + rm -Rf "$SERVICE_PID_FILE" + fi + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# start the post execute function in background +__post_execute 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" & +# - - - - - - - - - - - - - - - - - - - - - - - - - +__script_exit $SERVICE_EXIT_CODE diff --git a/rootfs/usr/local/etc/docker/init.d/08-forgejo.sh b/rootfs/usr/local/etc/docker/init.d/08-forgejo.sh new file mode 100755 index 0000000..51d1f41 --- /dev/null +++ b/rootfs/usr/local/etc/docker/init.d/08-forgejo.sh @@ -0,0 +1,983 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202609030601-git +# @@Author : Jason Hempstead +# @@Contact : jason@casjaysdev.pro +# @@License : LICENSE.md +# @@ReadME : 08-forgejo.sh --help +# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments +# @@Created : Sunday, May 24, 2026 11:59 EDT +# @@File : 08-forgejo.sh +# @@Description : +# @@Changelog : New script +# @@TODO : Better documentation +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : other/start-service +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +VERSION="202609030601-git" +set -e +# - - - - - - - - - - - - - - - - - - - - - - - - - +# run trap command on exit +trap '__trap_err_handler' ERR +trap 'retVal=$?;if [ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ]; then rm -Rf "$SERVICE_PID_FILE"; fi;exit $retVal' SIGINT SIGTERM +trap 'retVal=$?;if [ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ]; then rm -Rf "$SERVICE_PID_FILE"; fi;exit $retVal' SIGPWR 2>/dev/null || true +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ERR trap handler - smart about critical vs non-critical errors +__trap_err_handler() { + local retVal=$? + local command="$BASH_COMMAND" + # Ignore SIGPIPE and user interrupts + [ $retVal -eq 130 ] || [ $retVal -eq 141 ] && return $retVal + # Non-critical: file operations, text processing, user/group operations + if [[ "$command" =~ (mkdir|touch|chmod|chown|chgrp|ln|cp|mv|rm|echo|printf|cat|tee|sed|awk|grep|find|sort|uniq|adduser|addgroup|usermod|groupmod|id|getent) ]]; then + return 0 + fi + # Non-critical: conditional checks that might fail + if [[ "$command" =~ (test|\[|\[\[|kill -0|pgrep|pidof|ps) ]]; then + return 0 + fi + # Critical error - but only fail if service hasn't started yet + if [ "$SERVICE_IS_RUNNING" != "yes" ]; then + if [ -z "$NO_COLOR" ]; then + echo "❌ Critical error (exit $retVal): $command" >&2 + else + echo "Critical error (exit $retVal): $command" >&2 + fi + kill -TERM 1 2>/dev/null || exit $retVal + fi + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +SCRIPT_FILE="$0" +SERVICE_NAME="forgejo" +SCRIPT_NAME="${SCRIPT_FILE##*/}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Function to exit appropriately based on context +__script_exit() { + local exit_code="${1:-0}" + if [ "${BASH_SOURCE[0]}" != "${0}" ]; then + # Script is being sourced - use return + return "$exit_code" + else + # Script is being executed - use exit + exit "$exit_code" + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Exit if service is disabled +if [ -n "$FORGEJO_APPNAME_ENABLED" ]; then + if [ "$FORGEJO_APPNAME_ENABLED" != "yes" ]; then + export SERVICE_DISABLED="$SERVICE_NAME" + __script_exit 0 + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# setup debugging - https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html +[ -f "/config/.debug" ] && [ -z "$DEBUGGER_OPTIONS" ] && export DEBUGGER_OPTIONS="$(<"/config/.debug")" || DEBUGGER_OPTIONS="${DEBUGGER_OPTIONS:-}" +if [ "$DEBUGGER" = "on" ] || [ -f "/config/.debug" ]; then + echo "Enabling debugging" + set -o pipefail + [ -n "$DEBUGGER_OPTIONS" ] && set -"$DEBUGGER_OPTIONS" + export DEBUGGER="on" +else + set -o pipefail +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +export PATH="/usr/local/etc/docker/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# import the functions file +if [ -f "/usr/local/etc/docker/functions/entrypoint.sh" ]; then + . "/usr/local/etc/docker/functions/entrypoint.sh" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# import variables +for set_env in "/root/env.sh" "/usr/local/etc/docker/env"/*.sh "/config/env"/*.sh; do + if [ -f "$set_env" ]; then + . "$set_env" + fi +done +# - - - - - - - - - - - - - - - - - - - - - - - - - +# exit if __start_init_scripts function hasn't been Initialized +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 +# Clean up any stale PID file for this service on startup +if [ -n "$SERVICE_NAME" ] && [ -f "/run/init.d/$SERVICE_NAME.pid" ]; then + old_pid=$(<"/run/init.d/$SERVICE_NAME.pid") 2>/dev/null + if [ -n "$old_pid" ] && ! kill -0 "$old_pid" 2>/dev/null; then + if [ -z "$NO_COLOR" ]; then + echo "🧹 Removing stale PID file for $SERVICE_NAME" + else + echo "Removing stale PID file for $SERVICE_NAME" + fi + rm -f "/run/init.d/$SERVICE_NAME.pid" + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom functions + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Script to execute +START_SCRIPT="/usr/local/etc/docker/exec/$SERVICE_NAME" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Reset environment before executing service +RESET_ENV="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set webroot +WWW_ROOT_DIR="/usr/local/share/httpd/default" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Default predefined variables +# set data directory +DATA_DIR="/data/forgejo" +# set config directory +CONF_DIR="/config/forgejo" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set the containers etc directory +ETC_DIR="/etc/forgejo" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set the var dir +VAR_DIR="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set the temp dir +TMP_DIR="/tmp/forgejo" +# set scripts pid dir +RUN_DIR="/run/forgejo" +# set log directory +LOG_DIR="/data/logs/forgejo" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the working dir +WORK_DIR="/data/forgejo" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# port which service is listening on +SERVICE_PORT="80" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# User to use to launch service - IE: postgres +# forgejo must run as git user, not root +RUNAS_USER="git" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# User and group in which the service switches to - IE: nginx,apache,mysql,postgres +# execute command as another user +SERVICE_USER="git" +# Set the service group +SERVICE_GROUP="git" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set password length +RANDOM_PASS_USER="" +RANDOM_PASS_ROOT="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set user and group ID +# set the user id +SERVICE_UID="0" +# set the group id +SERVICE_GID="0" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# execute command variables - keep single quotes variables will be expanded later +# command to execute +EXEC_CMD_BIN='forgejo' +# command arguments +EXEC_CMD_ARGS='web ' +# command arguments +EXEC_CMD_ARGS+='--port $SERVICE_PORT --config $CONF_DIR/app.ini ' +# command arguments +EXEC_CMD_ARGS+='--custom-path $CONF_DIR/custom --work-path $DATA_DIR ' +# execute script before +EXEC_PRE_SCRIPT='' +# Set to no if the service is not running otherwise leave blank +SERVICE_USES_PID='' +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Is this service a web server +IS_WEB_SERVER="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Is this service a database server +IS_DATABASE_SERVICE="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Does this service use a database server +USES_DATABASE_SERVICE="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set defualt type - [custom,sqlite,redis,postgres,mariadb,mysql,couchdb,mongodb,supabase] +DATABASE_SERVICE_TYPE="sqlite" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Show message before execute +PRE_EXEC_MESSAGE="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the wait time to execute __post_execute function - minutes +POST_EXECUTE_WAIT_TIME="1" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Update path var +PATH="$PATH:." +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Lets get containers ip address +IP4_ADDRESS="$(__get_ip4)" +IP6_ADDRESS="$(__get_ip6)" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Where to save passwords to +# directory to save username/password for root user +ROOT_FILE_PREFIX="/config/secure/auth/root" +# directory to save username/password for normal user +USER_FILE_PREFIX="/config/secure/auth/user" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# root/admin user info password/random] +# root user name +root_user_name="${FORGEJO_ROOT_USER_NAME:-}" +# root user password +root_user_pass="${FORGEJO_ROOT_PASS_WORD:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Normal user info [password/random] +# normal user name +user_name="${FORGEJO_USER_NAME:-}" +# normal user password +user_pass="${FORGEJO_USER_PASS_WORD:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Load variables from config +# Generated by my dockermgr script +[ -f "/config/env/forgejo.script.sh" ] && . "/config/env/forgejo.script.sh" +# Overwrite the variabes +[ -f "/config/env/forgejo.sh" ] && . "/config/env/forgejo.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional predefined variables + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional variables +DATABASE_DIR="${DATABASE_DIR_SQLITE:-$DATA_DIR/db/sqlite}" +FORGEJO_SQL_NAME="${FORGEJO_SQL_NAME:-}" +FORGEJO_SQL_HOST="${FORGEJO_SQL_HOST:-localhost}" +FORGEJO_WORK_DIR="${FORGEJO_WORK_DIR:-$WORK_DIR}" +TZ="${FORGEJO_TZ:-${TZ:-America/New_York}}" +# Map container-generic protocol env vars to SERVICE_PROTOCOL +SERVICE_PROTOCOL="${FORGEJO_PROTO:-${CONTAINER_PROTOCOL:-${CONTAINER_WEB_SERVER_PROTOCOL:-${SERVICE_PROTOCOL:-http}}}}" +# Map container-generic port env vars to SERVICE_PORT +SERVICE_PORT="${WEB_PORT:-${ENV_PORTS:-${SERVICE_PORT:-80}}}" +EMAIL_RELAY="${FORGEJO_EMAIL_RELAY:-${EMAIL_RELAY:-172.17.0.1}}" +SERVER_SITE_TITLE="${FORGEJO_NAME:-${SERVER_SITE_TITLE:-SelfHosted GIT Server}}" +SERVER_ADMIN="${FORGEJO_ADMIN:-${SERVER_ADMIN:-administrator@${HOSTNAME}}}" +FORGEJO_SERVER="${ENV_FORGEJO_SERVER:-$FORGEJO_SERVER}" +FORGEJO_EMAIL_CONFIRM="${FORGEJO_EMAIL_CONFIRM:-false}" +FORGEJO_SQL_DB_HOST="${FORGEJO_SQL_DB_HOST:-localhost}" +FORGEJO_SQL_USER="${ENV_FORGEJO_SQL_USER:-$FORGEJO_SQL_USER}" +FORGEJO_SQL_PASS="${ENV_FORGEJO_SQL_PASS:-$FORGEJO_SQL_PASS}" +# Map CONTAINER_DEFAULT_DATABASE_TYPE to forgejo's DB_TYPE value +if [ -n "$CONTAINER_DEFAULT_DATABASE_TYPE" ]; then + DATABASE_SERVICE_TYPE="$CONTAINER_DEFAULT_DATABASE_TYPE" + case "$CONTAINER_DEFAULT_DATABASE_TYPE" in + sqlite|sqlite3) FORGEJO_SQL_TYPE="${FORGEJO_SQL_TYPE:-sqlite3}" ;; + postgres|postgresql) FORGEJO_SQL_TYPE="${FORGEJO_SQL_TYPE:-postgres}" ;; + mysql|mariadb) FORGEJO_SQL_TYPE="${FORGEJO_SQL_TYPE:-mysql}" ;; + mssql) FORGEJO_SQL_TYPE="${FORGEJO_SQL_TYPE:-mssql}" ;; + esac +fi +FORGEJO_SQL_TYPE="${ENV_FORGEJO_SQL_TYPE:-${FORGEJO_SQL_TYPE:-sqlite3}}" +HOSTNAME="${FORGEJO_SERVER:-${FORGEJO_HOSTNAME:-${FULL_DOMAIN_NAME:-$(hostname -f 2>/dev/null || echo "$HOSTNAME")}}}" +SERVER_NAME="${DOMAIN:-$HOSTNAME}" +SERVER_PROTO="${SERVICE_PROTOCOL:-http}" +# Feed SERVER_NAME back into FULL_DOMAIN_NAME so __initialize_replace_variables +# uses DOMAIN (if set) rather than falling back to the raw HOSTNAME. +export FULL_DOMAIN_NAME="$SERVER_NAME" +FORGEJO_SECRET_KEY="${FORGEJO_SECRET_KEY:-$(__random_password 32)}" +FORGEJO_LFS_JWT_SECRET="${FORGEJO_LFS_JWT_SECRET:-$($EXEC_CMD_BIN generate secret LFS_JWT_SECRET)}" +FORGEJO_INTERNAL_TOKEN="${FORGEJO_INTERNAL_TOKEN:-$($EXEC_CMD_BIN generate secret INTERNAL_TOKEN)}" +FORGEJO_RANDOM_COOKIE_KEY="${FORGEJO_RANDOM_COOKIE_KEY:-$(__random_password 16)}" +[ "$FORGEJO_EMAIL_CONFIRM" = "yes" ] && FORGEJO_EMAIL_CONFIRM="true" +export CUSTOM_PATH="$CONF_DIR/custom" WORK_DIR="${FORGEJO_WORK_DIR:-$DATA_DIR}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Specifiy custom directories to be created +ADD_APPLICATION_FILES="" +ADD_APPLICATION_DIRS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +APPLICATION_FILES="$LOG_DIR/$SERVICE_NAME.log" +APPLICATION_DIRS="$ETC_DIR $CONF_DIR $DATA_DIR $LOG_DIR $TMP_DIR $RUN_DIR $VAR_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional config dirs - will be Copied to /etc/$name +ADDITIONAL_CONFIG_DIRS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# define variables that need to be loaded into the service - escape quotes - var=\"value\",other=\"test\" +CMD_ENV="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Overwrite based on file/directory + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Per Application Variables or imports + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom commands to run before copying to /config +__run_precopy() { + # Define environment + local hostname=${HOSTNAME} + [ -d "/run/healthcheck" ] || mkdir -p "/run/healthcheck" + # Re-apply custom resolv.conf β€” Docker rewrites /etc/resolv.conf asynchronously + # during container startup, after the entrypoint's initial copy. Applying it here + # (in the init.d phase) ensures it takes effect after Docker finishes network setup. + [ -f "/usr/local/etc/resolv.conf" ] && cp -f "/usr/local/etc/resolv.conf" "/etc/resolv.conf" 2>/dev/null || true + # Seed /config/$SERVICE_NAME from the baked /etc copy on first initialisation only. + # The marker file $CONF_DIR/.initialized is written after substitution completes; + # delete it to force a re-seed (e.g. after intentional config reset). + # Then replace the /etc/$SERVICE_NAME directory with a symlink to /config/$SERVICE_NAME + # so both paths always resolve to the same processed config. + if [ -d "$ETC_DIR" ] && ! [ -L "$ETC_DIR" ]; then + if [ ! -f "$CONF_DIR/.initialized" ]; then + mkdir -p "$CONF_DIR" + cp -Rf "$ETC_DIR/." "$CONF_DIR/" 2>/dev/null || true + fi + rm -Rf "$ETC_DIR" + ln -sf "$CONF_DIR" "$ETC_DIR" + fi + # allow custom functions + if builtin type -t __run_precopy_local | grep -q -- 'function'; then __run_precopy_local; fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom prerun functions - IE setup WWW_ROOT_DIR +__execute_prerun() { + # Define environment + local hostname=${HOSTNAME} + # Define actions/commands + + # allow custom functions + if builtin type -t __execute_prerun_local | grep -q -- 'function'; then __execute_prerun_local; fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Run any pre-execution checks +__run_pre_execute_checks() { + # Set variables + local exitStatus=0 + # message to show at start + local pre_execute_checks_MessageST="Running preexecute check for $SERVICE_NAME" + # message to show at completion + local pre_execute_checks_MessageEnd="Finished preexecute check for $SERVICE_NAME" + __banner "$pre_execute_checks_MessageST" + # Put command to execute in parentheses + { + true + } + exitStatus=$? + __banner "$pre_execute_checks_MessageEnd: Status $exitStatus" + + # show exit message + if [ $exitStatus -ne 0 ]; then + echo "The pre-execution check has failed" >&2 + [ -f "$SERVICE_PID_FILE" ] && rm -Rf "$SERVICE_PID_FILE" + __script_exit 1 + fi + # allow custom functions + if builtin type -t __run_pre_execute_checks_local | grep -q -- 'function'; then __run_pre_execute_checks_local; fi + # exit function + return $exitStatus +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to update config files - IE: change port +__update_conf_files() { + # default exit code + local exitCode=0 + # set hostname + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + # - - - - - - - - - - - - - - - - - - - - - - - - - + # delete files + #__rm "" + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # custom commands + __is_dir_empty "/config/ssh" && COPY_SSHD_CONF="yes" + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # replace variables + __replace "REPLACE_SSH_CONF_DIR" "/config/ssh" "/etc/ssh/sshd_config" + __replace "REPLACE_SSH_DATA_DIR" "$DATA_DIR/ssh" "/etc/ssh/sshd_config" + __replace "REPLACE_RANDOM_COOKIE_KEY" "$FORGEJO_RANDOM_COOKIE_KEY" "$CONF_DIR/app.ini" + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # define actions + [ -d "/config/ssh" ] || mkdir -p "/config/ssh" + [ -d "$DATA_DIR/ssh" ] || mkdir -p "$DATA_DIR/ssh" + [ "$COPY_SSHD_CONF" = "yes" ] && copy "/etc/ssh/sshd_config" "/config/ssh/" + if [ ! -f "/config/ssh/ssh_host_ed25519_key" ]; then + echo "Generating /config/ssh/ssh_host_ed25519_key..." + ssh-keygen -t ed25519 -f /config/ssh/ssh_host_ed25519_key -N "" >/dev/null && __symlink "/config/ssh/ssh_host_ed25519_key" "$DATA_DIR/ssh/ssh_host_ed25519_key" + fi + if [ ! -f "/config/ssh/ssh_host_rsa_key" ]; then + echo "Generating /config/ssh/ssh_host_rsa_key..." + ssh-keygen -t rsa -b 3072 -f /config/ssh/ssh_host_rsa_key -N "" >/dev/null && __symlink "/config/ssh/ssh_host_rsa_key" "$DATA_DIR/ssh/ssh_host_rsa_key" + fi + if [ ! -f "/config/ssh/ssh_host_ecdsa_key" ]; then + echo "Generating /config/ssh/ssh_host_ecdsa_key..." + ssh-keygen -t ecdsa -b 256 -f /config/ssh/ssh_host_ecdsa_key -N "" >/dev/null && __symlink "/config/ssh/ssh_host_ecdsa_key" "$DATA_DIR/ssh/ssh_host_ecdsa_key" + fi + chmod 0700 "$DATA_DIR/ssh" /config/ssh + find "$DATA_DIR/ssh" /config/ssh -maxdepth 1 -type f -exec chmod 0600 {} \; + [ -d "$DATA_DIR/ssh" ] || mkdir -p "$DATA_DIR/ssh" + [ -d "$CONF_DIR/custom" ] || mkdir -p "$CONF_DIR/custom" + if [ -n "$CONF_DIR" ] && [ -f "$CONF_DIR/app.ini" ]; then + sed -i "s|REPLACE_SQL_NAME|$FORGEJO_SQL_NAME|g" "$CONF_DIR/app.ini" + sed -i "s|REPLACE_SQL_USER|$FORGEJO_SQL_USER|g" "$CONF_DIR/app.ini" + sed -i "s|REPLACE_SQL_PASS|$FORGEJO_SQL_PASS|g" "$CONF_DIR/app.ini" + sed -i "s|REPLACE_SQL_TYPE|${FORGEJO_SQL_TYPE}|g" "$CONF_DIR/app.ini" + sed -i "s|REPLACE_SQL_HOST|$FORGEJO_SQL_DB_HOST|g" "$CONF_DIR/app.ini" + sed -i "s|REPLACE_DATABASE_DIR|$DATABASE_DIR|g" "$CONF_DIR/app.ini" + sed -i "s|REPLACE_SECRET_KEY|$FORGEJO_SECRET_KEY|g" "$CONF_DIR/app.ini" + sed -i "s|REPLACE_FORGEJO_EMAIL_CONFIRM|$FORGEJO_EMAIL_CONFIRM|g" "$CONF_DIR/app.ini" + sed -i "s|REPLACE_FORGEJO_INTERNAL_TOKEN|$FORGEJO_INTERNAL_TOKEN|g" "$CONF_DIR/app.ini" + sed -i "s|REPLACE_FORGEJO_LFS_JWT_SECRET|$FORGEJO_LFS_JWT_SECRET|g" "$CONF_DIR/app.ini" + fi + # Re-stamp dynamic values and remove deprecated settings on every startup. + for _ini_file in "$CONF_DIR/app.ini"; do + [ -f "$_ini_file" ] || continue + # Sync ROOT_URL, DOMAIN, and SSH_DOMAIN β€” prefer DOMAIN env var over raw hostname + sed -i "s|^ROOT_URL[[:space:]]*=.*|ROOT_URL = ${SERVICE_PROTOCOL:-http}://${SERVER_NAME}|" "$_ini_file" + sed -i "s|^DOMAIN[[:space:]]*=.*|DOMAIN = ${SERVER_NAME}|" "$_ini_file" + sed -i "s|^SSH_DOMAIN[[:space:]]*=.*|SSH_DOMAIN = ${SERVER_NAME}|" "$_ini_file" + # Remove deprecated [cors].X_FRAME_OPTIONS (moved to [security] in Gitea v1.26) + awk 'BEGIN{in_s=0}/^\[/{in_s=0}/^\[cors\]/{in_s=1}in_s&&/^X_FRAME_OPTIONS/{next}{print}' \ + "$_ini_file" > /tmp/_forgejo_conf.ini && mv /tmp/_forgejo_conf.ini "$_ini_file" + # Remove deprecated [picture].DISABLE_GRAVATAR (moved to admin panel in Gitea v1.18) + awk 'BEGIN{in_s=0}/^\[/{in_s=0}/^\[picture\]/{in_s=1}in_s&&/^DISABLE_GRAVATAR/{next}{print}' \ + "$_ini_file" > /tmp/_forgejo_conf.ini && mv /tmp/_forgejo_conf.ini "$_ini_file" + # Remove deprecated [picture].ENABLE_FEDERATED_AVATAR (moved to admin panel in Gitea v1.18) + awk 'BEGIN{in_s=0}/^\[/{in_s=0}/^\[picture\]/{in_s=1}in_s&&/^ENABLE_FEDERATED_AVATAR/{next}{print}' \ + "$_ini_file" > /tmp/_forgejo_conf.ini && mv /tmp/_forgejo_conf.ini "$_ini_file" + done + unset _ini_file + if [ -n "$DATA_DIR" ] && [ -d "$DATA_DIR" ]; then + find "$DATA_DIR" -type d -exec chmod 0777 {} \; + chown -Rf $SERVICE_USER:$SERVICE_GROUP "$DATA_DIR" 2>/dev/null + fi + [ -d "$DATABASE_DIR" ] && chown -Rf $SERVICE_USER:$SERVICE_GROUP "$DATABASE_DIR" 2>/dev/null + # Mark config as fully initialised so __run_precopy skips re-seeding on restart + touch "$CONF_DIR/.initialized" 2>/dev/null || true + + # allow custom functions + if builtin type -t __update_conf_files_local | grep -q -- 'function'; then __update_conf_files_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# function to run before executing +__pre_execute() { + # default exit code + local exitCode=0 + # set hostname + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + # execute if directories is empty + # __is_dir_empty "$CONF_DIR" && true + # - - - - - - - - - - - - - - - - - - - - - - - - - + # define actions to run after copying to /config + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # unset unneeded variables + unset sysname + # Lets wait a few seconds before continuing + sleep 2 + # allow custom functions + if builtin type -t __pre_execute_local | grep -q -- 'function'; then __pre_execute_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# function to run after executing +__post_execute() { + # init pid var + local pid="" + # set default exit code + local retVal=0 + # how long to wait before executing + local ctime=${POST_EXECUTE_WAIT_TIME:-1} + # convert minutes to seconds + local waitTime=$((ctime * 60)) + # message to show at start + local postMessageST="Running post commands for $SERVICE_NAME" + # message to show at completion + local postMessageEnd="Finished post commands for $SERVICE_NAME" + # wait + sleep $waitTime + # execute commands after waiting + ( + # show message + __banner "$postMessageST" + # commands to execute + sleep 5 + # show exit message + __banner "$postMessageEnd: Status $retVal" + ) 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" & + # fire-and-forget: backgrounded subshell always succeeds at launch + retVal=0 + # allow custom functions + if builtin type -t __post_execute_local | grep -q -- 'function'; then __post_execute_local; fi + # exit function + return $retVal +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to update config files - IE: change port +__pre_message() { + local exitCode=0 + [ -n "$PRE_EXEC_MESSAGE" ] && eval echo "$PRE_EXEC_MESSAGE" + # execute commands + + # allow custom functions + if builtin type -t __pre_message_local | grep -q -- 'function'; then __pre_message_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to setup ssl support +__update_ssl_conf() { + local exitCode=0 + # set hostname + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + # execute commands + + # allow custom functions + if builtin type -t __update_ssl_conf_local | grep -q -- 'function'; then __update_ssl_conf_local; fi + # set exitCode + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__create_service_env() { + local exitCode=0 + if [ ! -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ]; then + cat </dev/null +# - - - - - - - - - - - - - - - - - - - - - - - - - +# root/admin user info [password/random] +#ENV_ROOT_USER_NAME="${ENV_ROOT_USER_NAME:-$FORGEJO_ROOT_USER_NAME}" # root user name +#ENV_ROOT_USER_PASS="${ENV_ROOT_USER_NAME:-$FORGEJO_ROOT_PASS_WORD}" # root user password +#root_user_name="${ENV_ROOT_USER_NAME:-$root_user_name}" # +#root_user_pass="${ENV_ROOT_USER_PASS:-$root_user_pass}" # +# - - - - - - - - - - - - - - - - - - - - - - - - - +#Normal user info [password/random] +#ENV_USER_NAME="${ENV_USER_NAME:-$FORGEJO_USER_NAME}" # +#ENV_USER_PASS="${ENV_USER_PASS:-$FORGEJO_USER_PASS_WORD}" # +#user_name="${ENV_USER_NAME:-$user_name}" # normal user name +#user_pass="${ENV_USER_PASS:-$user_pass}" # normal user password + +EOF + fi + if [ ! -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" ]; then + cat <<'EOF' >"/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Local overrides - sourced after the main env file. +# Redefine any of these functions to customise behaviour. +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_precopy_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__execute_prerun_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_pre_execute_checks_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__update_conf_files_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__pre_execute_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__post_execute_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__pre_message_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__update_ssl_conf_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +EOF + fi + __file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" || exitCode=$((exitCode + 1)) + __file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" || exitCode=$((exitCode + 1)) + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# script to start server +__run_start_script() { + local runExitCode=0 + # expand variables + local workdir="$(eval echo "${WORK_DIR:-}")" + # expand variables + local cmd="$(eval echo "${EXEC_CMD_BIN:-}")" + # expand variables + local args="$(eval echo "${EXEC_CMD_ARGS:-}")" + # expand variables + local name="$(eval echo "${EXEC_CMD_NAME:-}")" + # expand variables + local pre="$(eval echo "${EXEC_PRE_SCRIPT:-}")" + # expand variables + local extra_env="$(eval echo "${CMD_ENV//,/ }")" + # expand variables + local lc_type="$(eval echo "${LANG:-${LC_ALL:-$LC_CTYPE}}")" + # expand variables + local home="$(eval echo "${workdir//\/root/\/tmp\/docker}")" + # expand variables + local path="$(eval echo "$PATH")" + # expand variables + local message="$(eval echo "")" + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + if [ -f "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" ]; then + . "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" + fi + # + if [ -z "$cmd" ]; then + __post_execute 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" + retVal=$? + __log_info "Initialization of $SCRIPT_NAME has completed" + __script_exit $retVal + else + # ensure the command exists + if [ ! -x "$cmd" ]; then + __log_error "$name is not a valid executable" + return 2 + fi + # check and exit if already running (respects SERVICE_USES_PID in __proc_check) + if __proc_check "$name" || __proc_check "$cmd"; then + __log_debug "Service $name is already running" + return 0 + else + # - - - - - - - - - - - - - - - - - - - - - - - - - + # show message if env exists + if [ -n "$cmd" ]; then + if [ -n "$SERVICE_USER" ]; then + __log_info "Setting up $cmd to run as $SERVICE_USER" + else + SERVICE_USER="root" + fi + if [ -n "$SERVICE_PORT" ]; then + __log_info "$name will be running on port $SERVICE_PORT" + else + SERVICE_PORT="" + fi + fi + if [ -n "$pre" ] && command -v "$pre" &>/dev/null; then + export cmd_exec="$pre $cmd $args" + message="Starting service: $name $args through $pre" + else + export cmd_exec="$cmd $args" + message="Starting service: $name $args" + fi + if [ -n "$su_exec" ]; then + __log_debug "Using $su_exec" | tee -a -p "/data/logs/init.txt" + fi + __log_info "$message" | tee -a -p "/data/logs/init.txt" + __su_cmd touch "$SERVICE_PID_FILE" + # W14: invalidate cached START_SCRIPT if key variables changed + local _script_hash_src="$cmd $args $SERVICE_USER $RESET_ENV $su_exec" + local _script_hash + _script_hash=$(printf '%s' "$_script_hash_src" | md5sum 2>/dev/null | cut -c1-8 || true) + if [ -f "${START_SCRIPT}.hash" ] && [ -f "$START_SCRIPT" ]; then + if [ "$(cat "${START_SCRIPT}.hash" 2>/dev/null)" != "$_script_hash" ]; then + rm -f "$START_SCRIPT" "${START_SCRIPT}.hash" + fi + fi + if [ "$RESET_ENV" = "yes" ]; then + # RESET_ENV=yes intentionally strips all inherited vars; only explicit vars are passed + if [ ! -f "$START_SCRIPT" ]; then + # Use printf %q to safely quote each env component for embedding in the script + local _q_home _q_lc _q_path _q_sysname _q_svcuser _q_su _q_cmd _q_args _q_extra + _q_home=$(printf '%q' "$home") + _q_lc=$(printf '%q' "$lc_type") + _q_path=$(printf '%q' "$path") + _q_sysname=$(printf '%q' "$sysname") + _q_svcuser=$(printf '%q' "${SERVICE_USER:-$RUNAS_USER}") + _q_su="${su_exec:+$(printf '%q ' $su_exec)}" + _q_cmd=$(printf '%q' "$cmd") + _q_args=$(printf '%q ' $args) + _q_extra=$(printf '%q ' $extra_env) + { + printf '#!/usr/bin/env bash\n' + printf "trap 'exitCode=\$?;[ \$exitCode -ne 0 ] && [ -f \"\$SERVICE_PID_FILE\" ] && rm -Rf \"\$SERVICE_PID_FILE\";exit \$exitCode' EXIT\n" + printf 'set -Eeo pipefail\n' + printf '# Setting up %s to run as %s with env\n' "$cmd" "${SERVICE_USER:-root}" + printf 'retVal=10\n' + printf 'SERVICE_NAME=%q\n' "$SERVICE_NAME" + printf 'SERVICE_PID_FILE=%q\n' "$SERVICE_PID_FILE" + printf 'LOG_DIR=%q\n' "$LOG_DIR" + printf '%senv -i HOME=%s LC_CTYPE=%s PATH=%s HOSTNAME=%s USER=%s %s %s %s 2>>"/dev/stderr" >>"$LOG_DIR/$SERVICE_NAME.log" &\n' \ + "$_q_su" "$_q_home" "$_q_lc" "$_q_path" "$_q_sysname" "$_q_svcuser" "$_q_extra" "$_q_cmd" "$_q_args" + printf 'execPid=$!\n' + printf 'sleep 1\n' + printf 'if [ -n "$execPid" ] && kill -0 "$execPid" 2>/dev/null; then\n' + printf ' echo "$execPid" >"$SERVICE_PID_FILE"\n' + printf ' retVal=0\n' + printf ' printf '"'"'%%s\n'"'"' "$SERVICE_NAME: $execPid" >"/run/healthcheck/$SERVICE_NAME"\n' + printf 'else\n' + printf ' retVal=10\n' + printf ' echo "Failed to start service %s" >&2\n' "$cmd" + printf 'fi\n' + printf 'exit $retVal\n' + } >"$START_SCRIPT" + printf '%s' "$_script_hash" >"${START_SCRIPT}.hash" + fi + else + if [ ! -f "$START_SCRIPT" ]; then + local _q_su _q_cmd _q_args _q_path _q_home + _q_su="${su_exec:+$(printf '%q ' $su_exec)}" + _q_cmd=$(printf '%q' "$cmd") + _q_args=$(printf '%q ' $args) + _q_path=$(printf '%q' "$path") + _q_home=$(printf '%q' "$home") + { + printf '#!/usr/bin/env bash\n' + printf "trap 'exitCode=\$?;[ \$exitCode -ne 0 ] && [ -f \"\$SERVICE_PID_FILE\" ] && rm -Rf \"\$SERVICE_PID_FILE\";exit \$exitCode' EXIT\n" + printf 'set -Eeo pipefail\n' + printf '# Setting up %s to run as %s\n' "$cmd" "${SERVICE_USER:-root}" + printf 'export PATH=%s\n' "$_q_path" + printf 'export HOME=%s\n' "$_q_home" + printf 'retVal=10\n' + printf 'SERVICE_NAME=%q\n' "$SERVICE_NAME" + printf 'SERVICE_PID_FILE=%q\n' "$SERVICE_PID_FILE" + printf 'LOG_DIR=%q\n' "$LOG_DIR" + printf '%s%s %s 2>>"/dev/stderr" >>"$LOG_DIR/$SERVICE_NAME.log" &\n' \ + "$_q_su" "$_q_cmd" "$_q_args" + printf 'execPid=$!\n' + printf 'sleep 1\n' + printf 'if [ -n "$execPid" ] && kill -0 "$execPid" 2>/dev/null; then\n' + printf ' echo "$execPid" >"$SERVICE_PID_FILE"\n' + printf ' retVal=0\n' + printf 'else\n' + printf ' retVal=10\n' + printf ' echo "Failed to start service %s" >&2\n' "$cmd" + printf 'fi\n' + printf 'exit $retVal\n' + } >"$START_SCRIPT" + printf '%s' "$_script_hash" >"${START_SCRIPT}.hash" + fi + fi + fi + if [ ! -x "$START_SCRIPT" ]; then + chmod 755 -Rf "$START_SCRIPT" + fi + if [ "$CONTAINER_INIT" != "yes" ]; then + # W15: launch as bash, not sh, since the generated script uses bash-specific features + bash "$START_SCRIPT" + runExitCode=$? + fi + fi + return $runExitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# username and password actions +__run_secure_function() { + local filesperms + if [ -n "$user_name" ] || [ -n "$user_pass" ]; then + for filesperms in "${USER_FILE_PREFIX}"/*; do + if [ -e "$filesperms" ]; then + chmod -Rf 600 "$filesperms" + chown -Rf $SERVICE_USER:$SERVICE_USER "$filesperms" 2>/dev/null + fi + done 2>/dev/null | tee -p -a "/data/logs/init.txt" + fi + if [ -n "$root_user_name" ] || [ -n "$root_user_pass" ]; then + for filesperms in "${ROOT_FILE_PREFIX}"/*; do + if [ -e "$filesperms" ]; then + chmod -Rf 600 "$filesperms" + chown -Rf $SERVICE_USER:$SERVICE_USER "$filesperms" 2>/dev/null + fi + done 2>/dev/null | tee -p -a "/data/logs/init.txt" + fi + unset filesperms +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow ENV_ variable - Import env file +__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" +__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# default exit code +SERVICE_EXIT_CODE=0 +# application specific +# set the binary name +EXEC_CMD_NAME="${EXEC_CMD_BIN##*/}" +# set the pid file location +SERVICE_PID_FILE="/run/init.d/$EXEC_CMD_NAME.pid" +# check if running +SERVICE_PID_NUMBER="$(__pgrep "$EXEC_CMD_NAME" || echo '')" +# set full path +EXEC_CMD_BIN="$(type -P "$EXEC_CMD_BIN" || echo "$EXEC_CMD_BIN")" +# set full path +EXEC_PRE_SCRIPT="$(type -P "$EXEC_PRE_SCRIPT" || echo "$EXEC_PRE_SCRIPT")" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Only run check when explicitly requested +if [ "$1" = "check" ] && __check_service "$1"; then + SERVICE_IS_RUNNING=yes +elif [ "$1" = "check" ]; then + SERVICE_IS_RUNNING="no" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ensure needed directories exists +[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR" +[ -d "$RUN_DIR" ] || mkdir -p "$RUN_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create auth directories +[ -n "$USER_FILE_PREFIX" ] && { [ -d "$USER_FILE_PREFIX" ] || mkdir -p "$USER_FILE_PREFIX"; } +[ -n "$ROOT_FILE_PREFIX" ] && { [ -d "$ROOT_FILE_PREFIX" ] || mkdir -p "$ROOT_FILE_PREFIX"; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +[ -n "$RUNAS_USER" ] || RUNAS_USER="root" +[ -n "$SERVICE_USER" ] || SERVICE_USER="$RUNAS_USER" +[ -n "$SERVICE_GROUP" ] || SERVICE_GROUP="${SERVICE_USER:-$RUNAS_USER}" +[ "$IS_WEB_SERVER" = "yes" ] && RESET_ENV="yes" && __is_htdocs_mounted +[ "$IS_WEB_SERVER" = "yes" ] && [ -z "$SERVICE_PORT" ] && SERVICE_PORT="80" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Database env +if [ "$IS_DATABASE_SERVICE" = "yes" ] || [ "$USES_DATABASE_SERVICE" = "yes" ]; then + RESET_ENV="no" + DATABASE_CREATE="${ENV_DATABASE_CREATE:-$DATABASE_CREATE}" + DATABASE_USER_NORMAL="${ENV_DATABASE_USER:-${DATABASE_USER_NORMAL:-$user_name}}" + DATABASE_PASS_NORMAL="${ENV_DATABASE_PASSWORD:-${DATABASE_PASS_NORMAL:-$user_pass}}" + DATABASE_USER_ROOT="${ENV_DATABASE_ROOT_USER:-${DATABASE_USER_ROOT:-$root_user_name}}" + DATABASE_PASS_ROOT="${ENV_DATABASE_ROOT_PASSWORD:-${DATABASE_PASS_ROOT:-$root_user_pass}}" + if [ -n "$DATABASE_PASS_NORMAL" ] && [ ! -f "${USER_FILE_PREFIX}/db_pass_user" ]; then + echo "$DATABASE_PASS_NORMAL" >"${USER_FILE_PREFIX}/db_pass_user" + fi + if [ -n "$DATABASE_PASS_ROOT" ] && [ ! -f "${ROOT_FILE_PREFIX}/db_pass_root" ]; then + echo "$DATABASE_PASS_ROOT" >"${ROOT_FILE_PREFIX}/db_pass_root" + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# [DATABASE_DIR_[SQLITE,REDIS,POSTGRES,MARIADB,COUCHDB,MONGODB,SUPABASE]] +if [ "$DATABASE_SERVICE_TYPE" = "custom" ]; then + DATABASE_DIR="${DATABASE_DIR_CUSTOM:-/data/db/custom}" + DATABASE_BASE_DIR="${DATABASE_DIR_CUSTOM:-/data/db/custom}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_CUSTOM:-/usr/local/share/httpd/admin/databases}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_CUSTOM:-/admin/dbadmin}" +elif [ "$SERVICE_NAME" = "redis" ] || [ "$DATABASE_SERVICE_TYPE" = "redis" ]; then + DATABASE_DIR="${DATABASE_DIR_REDIS:-/data/db/redis}" + DATABASE_BASE_DIR="${DATABASE_DIR_REDIS:-/data/db/redis}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_REDIS:-/usr/local/share/httpd/admin/redis}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_REDIS:-/admin/redis}" +elif [ "$SERVICE_NAME" = "postgres" ] || [ "$DATABASE_SERVICE_TYPE" = "postgres" ]; then + DATABASE_DIR="${DATABASE_DIR_POSTGRES:-/data/db/postgres}" + DATABASE_BASE_DIR="${DATABASE_DIR_POSTGRES:-/data/db/postgres}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_POSTGRES:-/usr/local/share/httpd/admin/postgres}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_POSTGRES:-/admin/postgres}" +elif [ "$SERVICE_NAME" = "mariadb" ] || [ "$DATABASE_SERVICE_TYPE" = "mariadb" ]; then + DATABASE_DIR="${DATABASE_DIR_MARIADB:-/data/db/mariadb}" + DATABASE_BASE_DIR="${DATABASE_DIR_MARIADB:-/data/db/mariadb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MARIADB:-/usr/local/share/httpd/admin/mysql}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MARIADB:-/admin/mysql}" +elif [ "$SERVICE_NAME" = "mysql" ] || [ "$DATABASE_SERVICE_TYPE" = "mysql" ]; then + DATABASE_DIR="${DATABASE_DIR_MYSQL:-/data/db/mysql}" + DATABASE_BASE_DIR="${DATABASE_DIR_MYSQL:-/data/db/mysql}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MYSQL:-/usr/local/share/httpd/admin/mysql}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MYSQL:-/admin/mysql}" +elif [ "$SERVICE_NAME" = "couchdb" ] || [ "$DATABASE_SERVICE_TYPE" = "couchdb" ]; then + DATABASE_DIR="${DATABASE_DIR_COUCHDB:-/data/db/couchdb}" + DATABASE_BASE_DIR="${DATABASE_DIR_COUCHDB:-/data/db/couchdb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_COUCHDB:-/usr/local/share/httpd/admin/couchdb}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_COUCHDB:-/admin/couchdb}" +elif [ "$SERVICE_NAME" = "mongodb" ] || [ "$DATABASE_SERVICE_TYPE" = "mongodb" ]; then + DATABASE_DIR="${DATABASE_DIR_MONGODB:-/data/db/mongodb}" + DATABASE_BASE_DIR="${DATABASE_DIR_MONGODB:-/data/db/mongodb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MONGODB:-/usr/local/share/httpd/admin/mongodb}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MONGODB:-/admin/mongodb}" +elif [ "$SERVICE_NAME" = "supabase" ] || [ "$DATABASE_SERVICE_TYPE" = "supabase" ]; then + DATABASE_DIR="${DATABASE_DIR_SUPABASE:-/data/db/supabase}" + DATABASE_BASE_DIR="${DATABASE_DIR_SUPABASE:-/data/db/supabase}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_SUPABASE:-/usr/local/share/httpd/admin/supabase}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_SUPBASE:-/admin/supabase}" +elif [ "$SERVICE_NAME" = "sqlite" ] || [ "$DATABASE_SERVICE_TYPE" = "sqlite" ]; then + DATABASE_DIR="${DATABASE_DIR_SQLITE:-/data/db/sqlite}/$SERVER_NAME" + DATABASE_BASE_DIR="${DATABASE_DIR_SQLITE:-/data/db/sqlite}/$SERVER_NAME" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_SQLITE:-/usr/local/share/httpd/admin/sqlite}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_SQLITE:-/admin/sqlite}" + [ -d "$DATABASE_DIR" ] || mkdir -p "$DATABASE_DIR" + chmod 777 "$DATABASE_DIR" +fi +# Override: pin to a predictable path β€” respect DATABASE_DIR_SQLITE if set, otherwise use DATA_DIR +DATABASE_DIR="${DATABASE_DIR_SQLITE:-$DATA_DIR/db/sqlite}" +[ -d "$DATABASE_DIR" ] || mkdir -p "$DATABASE_DIR" +[ -n "$DATABASE_ADMIN_WWW_ROOT" ] && { [ ! -d "$DATABASE_ADMIN_WWW_ROOT" ] || mkdir -p "${DATABASE_ADMIN_WWW_ROOT}"; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow variables via imports - Overwrite existing +[ -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ] && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set password to random if variable is random +[ "$user_pass" = "random" ] && user_pass="$(__random_password ${RANDOM_PASS_USER:-16})" +# - - - - - - - - - - - - - - - - - - - - - - - - - +[ "$root_user_pass" = "random" ] && root_user_pass="$(__random_password ${RANDOM_PASS_ROOT:-16})" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow setting initial users and passwords via environment and save to file +[ -n "$user_name" ] && echo "$user_name" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_name" +[ -n "$user_pass" ] && echo "$user_pass" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" +[ -n "$root_user_name" ] && echo "$root_user_name" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name" +[ -n "$root_user_pass" ] && echo "$root_user_pass" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create needed dirs +[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR" +[ -d "$RUN_DIR" ] || mkdir -p "$RUN_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow per init script usernames and passwords +__file_exists_with_content "${USER_FILE_PREFIX}/${SERVICE_NAME}_name" && user_name="$(<"${USER_FILE_PREFIX}/${SERVICE_NAME}_name")" +__file_exists_with_content "${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" && user_pass="$(<"${USER_FILE_PREFIX}/${SERVICE_NAME}_pass")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name" && root_user_name="$(<"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" && root_user_pass="$(<"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass")" +__file_exists_with_content "${USER_FILE_PREFIX}/db_pass_user" && DATABASE_PASS_NORMAL="$(<"${USER_FILE_PREFIX}/db_pass_user")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/db_pass_root" && DATABASE_PASS_ROOT="$(<"${ROOT_FILE_PREFIX}/db_pass_root")" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set hostname for script +sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +__create_service_env +# - - - - - - - - - - - - - - - - - - - - - - - - - +# pre-run function +__execute_prerun +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create user if needed +__create_service_user "$SERVICE_USER" "$SERVICE_GROUP" "${WORK_DIR:-/home/$SERVICE_USER}" "${SERVICE_UID:-}" "${SERVICE_GID:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Modify user if needed +__set_user_group_id $SERVICE_USER ${SERVICE_UID:-} ${SERVICE_GID:-} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Create base directories +__setup_directories +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set switch user command +__switch_to_user +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Initialize the home/working dir +__init_working_dir +# - - - - - - - - - - - - - - - - - - - - - - - - - +# show init message +__pre_message +# - - - - - - - - - - - - - - - - - - - - - - - - - +# +__initialize_db_users +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Initialize ssl +__update_ssl_conf +__update_ssl_certs +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set permissions in ${USER_FILE_PREFIX} and ${ROOT_FILE_PREFIX} +__run_secure_function +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_precopy +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Replace variables +__initialize_replace_variables "$CONF_DIR" "$ADDITIONAL_CONFIG_DIRS" "$WWW_ROOT_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# +__initialize_database +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Updating config files +__update_conf_files +# - - - - - - - - - - - - - - - - - - - - - - - - - +# run the pre execute commands +__pre_execute +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set permissions +__fix_permissions "$SERVICE_USER" "$SERVICE_GROUP" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# +__run_pre_execute_checks 2>/dev/stderr | tee -a -p "/data/logs/entrypoint.log" "/data/logs/init.txt" || return 20 +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_start_script 2>>/dev/stderr | tee -p -a "/data/logs/entrypoint.log" +errorCode=${PIPESTATUS[0]} +if [ -n "$EXEC_CMD_BIN" ]; then + if [ "$errorCode" -eq 0 ]; then + SERVICE_EXIT_CODE=0 + SERVICE_IS_RUNNING="yes" + else + SERVICE_EXIT_CODE=$errorCode + SERVICE_IS_RUNNING="${SERVICE_IS_RUNNING:-no}" + if [ ! -s "$SERVICE_PID_FILE" ]; then + rm -Rf "$SERVICE_PID_FILE" + fi + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# start the post execute function in background +__post_execute 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" & +# - - - - - - - - - - - - - - - - - - - - - - - - - +__script_exit $SERVICE_EXIT_CODE diff --git a/rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh b/rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh new file mode 100755 index 0000000..3b1c538 --- /dev/null +++ b/rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh @@ -0,0 +1,1052 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202609030534-git +# @@Author : Jason Hempstead +# @@Contact : jason@casjaysdev.pro +# @@License : LICENSE.md +# @@ReadME : zz-act_runner.sh --help +# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments +# @@Created : Sunday, May 24, 2026 11:59 EDT +# @@File : zz-act_runner.sh +# @@Description : +# @@Changelog : New script +# @@TODO : Better documentation +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : other/start-service +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - +VERSION="202609030534-git" +set -e +# - - - - - - - - - - - - - - - - - - - - - - - - - +# run trap command on exit +trap '__trap_err_handler' ERR +trap 'retVal=$?;if [ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ]; then rm -Rf "$SERVICE_PID_FILE"; fi;exit $retVal' SIGINT SIGTERM +trap 'retVal=$?;if [ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ]; then rm -Rf "$SERVICE_PID_FILE"; fi;exit $retVal' SIGPWR 2>/dev/null || true +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ERR trap handler - smart about critical vs non-critical errors +__trap_err_handler() { + local retVal=$? + local command="$BASH_COMMAND" + # Ignore SIGPIPE and user interrupts + [ $retVal -eq 130 ] || [ $retVal -eq 141 ] && return $retVal + # Non-critical: file operations, text processing, user/group operations + if [[ "$command" =~ (mkdir|touch|chmod|chown|chgrp|ln|cp|mv|rm|echo|printf|cat|tee|sed|awk|grep|find|sort|uniq|adduser|addgroup|usermod|groupmod|id|getent) ]]; then + return 0 + fi + # Non-critical: conditional checks that might fail + if [[ "$command" =~ (test|\[|\[\[|kill -0|pgrep|pidof|ps) ]]; then + return 0 + fi + # Critical error - but only fail if service hasn't started yet + if [ "$SERVICE_IS_RUNNING" != "yes" ]; then + if [ -z "$NO_COLOR" ]; then + echo "❌ Critical error (exit $retVal): $command" >&2 + else + echo "Critical error (exit $retVal): $command" >&2 + fi + kill -TERM 1 2>/dev/null || exit $retVal + fi + return 0 +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +SCRIPT_FILE="$0" +SERVICE_NAME="act_runner" +SCRIPT_NAME="${SCRIPT_FILE##*/}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Function to exit appropriately based on context +__script_exit() { + local exit_code="${1:-0}" + if [ "${BASH_SOURCE[0]}" != "${0}" ]; then + # Script is being sourced - use return + return "$exit_code" + else + # Script is being executed - use exit + exit "$exit_code" + fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Exit if service is disabled +if [ -n "$ACT_RUNNER_APPNAME_ENABLED" ]; then + if [ "$ACT_RUNNER_APPNAME_ENABLED" != "yes" ]; then + export SERVICE_DISABLED="$SERVICE_NAME" + __script_exit 0 + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# setup debugging - https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html +[ -f "/config/.debug" ] && [ -z "$DEBUGGER_OPTIONS" ] && export DEBUGGER_OPTIONS="$(<"/config/.debug")" || DEBUGGER_OPTIONS="${DEBUGGER_OPTIONS:-}" +if [ "$DEBUGGER" = "on" ] || [ -f "/config/.debug" ]; then + echo "Enabling debugging" + set -o pipefail + [ -n "$DEBUGGER_OPTIONS" ] && set -"$DEBUGGER_OPTIONS" + export DEBUGGER="on" +else + set -o pipefail +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +export PATH="/usr/local/etc/docker/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# import the functions file +if [ -f "/usr/local/etc/docker/functions/entrypoint.sh" ]; then + . "/usr/local/etc/docker/functions/entrypoint.sh" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# import variables +for set_env in "/root/env.sh" "/usr/local/etc/docker/env"/*.sh "/config/env"/*.sh; do + if [ -f "$set_env" ]; then + . "$set_env" + fi +done +# - - - - - - - - - - - - - - - - - - - - - - - - - +# exit if __start_init_scripts function hasn't been Initialized +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 +# Clean up any stale PID file for this service on startup +if [ -n "$SERVICE_NAME" ] && [ -f "/run/init.d/$SERVICE_NAME.pid" ]; then + old_pid=$(<"/run/init.d/$SERVICE_NAME.pid") 2>/dev/null + if [ -n "$old_pid" ] && ! kill -0 "$old_pid" 2>/dev/null; then + if [ -z "$NO_COLOR" ]; then + echo "🧹 Removing stale PID file for $SERVICE_NAME" + else + echo "Removing stale PID file for $SERVICE_NAME" + fi + rm -f "/run/init.d/$SERVICE_NAME.pid" + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom functions +__gen_auth_token() { + local user conf_file auth_token token_dir forgejo_bin exitCode + exitCode=1 + user="${FORGEJO_USER:-git}" + token_dir="$CONF_DIR/tokens" + forgejo_bin="$(command -v forgejo)" + mkdir -p "$token_dir" >/dev/null 2>&1 + conf_file="$(find "/config" "/etc" -type f -name '*.ini' 2>/dev/null | grep -E -- 'git/app.ini|forgejo/app.ini|forgejo.ini' | head -n1 | grep -- '^')" + if [ -n "$SYS_AUTH_TOKEN" ]; then + auth_token="$SYS_AUTH_TOKEN" + elif [ -s "$CONF_DIR/tokens/system" ]; then + auth_token="$(<"$CONF_DIR/tokens/system")" + fi + auth_token="$(echo "$auth_token" | grep -vE -- '# |^$')" + if [ -z "$auth_token" ] && [ -n "$forgejo_bin" ] && [ -n "$conf_file" ]; then + # Only attempt token generation if forgejo is fully installed (INSTALL_LOCK = true) + if grep -qiE -- 'INSTALL_LOCK\s*=\s*true' "$conf_file" 2>/dev/null; then + auth_token="$( gosu $user $forgejo_bin --config "$conf_file" \ + --work-path /data/forgejo --custom-path /config/forgejo/custom \ + actions generate-runner-token 2>/dev/null | \ + grep -oE -- '[A-Za-z0-9]{20,}' | tail -n1 )" + fi + fi + if [ -n "$auth_token" ]; then + exitCode=0 + echo "$auth_token" + echo "$auth_token" >"$CONF_DIR/tokens/system" + fi + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# shared secret between the runner(s) and the standalone cache-server +__gen_cache_secret() { + local secret token_dir exitCode + exitCode=1 + token_dir="$CONF_DIR/tokens" + mkdir -p "$token_dir" >/dev/null 2>&1 + if [ -n "$RUNNER_CACHE_SECRET" ]; then + secret="$RUNNER_CACHE_SECRET" + elif [ -s "$token_dir/cache_secret" ]; then + secret="$(<"$token_dir/cache_secret")" + fi + [ -z "$secret" ] && secret="$(__random_password 32)" + if [ -n "$secret" ]; then + exitCode=0 + echo "$secret" + echo "$secret" >"$token_dir/cache_secret" + fi + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Script to execute +START_SCRIPT="/usr/local/etc/docker/exec/$SERVICE_NAME" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Reset environment before executing service +RESET_ENV="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set webroot +WWW_ROOT_DIR="/usr/local/share/httpd/default" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Default predefined variables +# set data directory +DATA_DIR="/data/act_runner" +# set config directory +CONF_DIR="/config/act_runner" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set the containers etc directory +ETC_DIR="/etc/act_runner" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set the var dir +VAR_DIR="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set the temp dir +TMP_DIR="/tmp/act_runner" +# set scripts pid dir +RUN_DIR="/run/act_runner" +# set log directory +LOG_DIR="/data/logs/act_runner" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the working dir +WORK_DIR="/data/act_runner" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# port which service is listening on +SERVICE_PORT="44015" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# User to use to launch service - IE: postgres +# normally root +RUNAS_USER="root" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# User and group in which the service switches to - IE: nginx,apache,mysql,postgres +# execute command as another user +SERVICE_USER="git" +# Set the service group +SERVICE_GROUP="git" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set password length +RANDOM_PASS_USER="" +RANDOM_PASS_ROOT="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set user and group ID +# set the user id +SERVICE_UID="0" +# set the group id +SERVICE_GID="0" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# execute command variables - keep single quotes variables will be expanded later +# command to execute +EXEC_CMD_BIN='' +# command arguments +EXEC_CMD_ARGS='' +# execute script before +EXEC_PRE_SCRIPT='' +# Set to no if the service is not running otherwise leave blank +SERVICE_USES_PID='' +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Is this service a web server +IS_WEB_SERVER="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Is this service a database server +IS_DATABASE_SERVICE="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Does this service use a database server +USES_DATABASE_SERVICE="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set defualt type - [custom,sqlite,redis,postgres,mariadb,mysql,couchdb,mongodb,supabase] +DATABASE_SERVICE_TYPE="sqlite" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Show message before execute +PRE_EXEC_MESSAGE="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the wait time to execute __post_execute function - minutes +POST_EXECUTE_WAIT_TIME="1" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Update path var +PATH="$PATH:." +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Lets get containers ip address +IP4_ADDRESS="$(__get_ip4)" +IP6_ADDRESS="$(__get_ip6)" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Where to save passwords to +# directory to save username/password for root user +ROOT_FILE_PREFIX="/config/secure/auth/root" +# directory to save username/password for normal user +USER_FILE_PREFIX="/config/secure/auth/user" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# root/admin user info password/random] +# root user name +root_user_name="${ACT_RUNNER_ROOT_USER_NAME:-}" +# root user password +root_user_pass="${ACT_RUNNER_ROOT_PASS_WORD:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Normal user info [password/random] +# normal user name +user_name="${ACT_RUNNER_USER_NAME:-}" +# normal user password +user_pass="${ACT_RUNNER_USER_PASS_WORD:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Load variables from config +# Generated by my dockermgr script +[ -f "/config/env/act_runner.script.sh" ] && . "/config/env/act_runner.script.sh" +# Overwrite the variabes +[ -f "/config/env/act_runner.sh" ] && . "/config/env/act_runner.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional predefined variables +FORGEJO_PORT="${FORGEJO_PORT:-80}" +SYS_AUTH_TOKEN="" +FORGEJO_USER="${FORGEJO_USER:-$SERVICE_USER}" +INSTANCE_HOSTNAME="${FORGEJO_HOSTNAME:-$HOSTNAME}" +RUNNERS_START="${RUNNERS_START:-5}" +RUNNER_CACHE_PORT="${RUNNER_CACHE_PORT:-$SERVICE_PORT}" +# Detect host architecture and set arch-specific labels so matrix jobs +# can target native runners: runs-on: amd64 / runs-on: arm64 +_HOST_ARCH="$(uname -m)" +case "$_HOST_ARCH" in + x86_64) _ARCH_LABEL="amd64" ;; + aarch64) _ARCH_LABEL="arm64" ;; + *) _ARCH_LABEL="$_HOST_ARCH" ;; +esac +RUNNER_LABELS="${_ARCH_LABEL}:docker://ubuntu:latest," +RUNNER_LABELS+="linux:docker://ubuntu:latest," +RUNNER_LABELS+="linux/${_ARCH_LABEL}:docker://ubuntu:latest," +RUNNER_LABELS+="node14:docker://node:14," +RUNNER_LABELS+="node16:docker://node:16," +RUNNER_LABELS+="node18:docker://node:18," +RUNNER_LABELS+="node20:docker://node:20," +RUNNER_LABELS+="node22:docker://node:22," +RUNNER_LABELS+="node:docker://node:latest," +RUNNER_LABELS+="perl:docker://perl:latest," +RUNNER_LABELS+="ruby:docker://ruby:latest," +RUNNER_LABELS+="python:docker://python:latest," +RUNNER_LABELS+="python3:docker://python:latest," +RUNNER_LABELS+="php7:docker://casjaysdevdocker/php:7," +RUNNER_LABELS+="php8:docker://casjaysdevdocker/php:8," +RUNNER_LABELS+="php:docker://casjaysdevdocker/php:latest," +RUNNER_LABELS+="alpine:docker://casjaysdev/alpine:latest," +RUNNER_LABELS+="debian:docker://casjaysdev/debian:latest," +RUNNER_LABELS+="ubuntu:docker://casjaysdev/ubuntu:latest," +RUNNER_LABELS+="rhel:docker://casjaysdev/almalinux:latest," +RUNNER_LABELS+="redhat:docker://casjaysdev/almalinux:latest," +RUNNER_LABELS+="almalinux:docker://casjaysdev/almalinux:latest," +RUNNER_LABELS+="act_runner:docker://catthehacker/ubuntu:full-latest," +RUNNER_LABELS+="ubuntu-latest:docker://catthehacker/ubuntu:full-latest" +unset _HOST_ARCH _ARCH_LABEL +# - - - - - - - - - - - - - - - - - - - - - - - - - +# act_runner registers against forgejo in the same container/network namespace, so use +# loopback rather than the detected external IP4_ADDRESS (which can be transient/wrong +# under Docker-in-Docker networking and caused "no route to host" registration failures) +RUNNER_IP_ADDRESS="${RUNNER_IP_ADDRESS:-127.0.0.1}" +RUNNER_CONFIG_DEFAULT="${RUNNER_CONFIG_DEFAULT:-$CONF_DIR/default_config.yaml}" +RUNNER_DEFAULT_HOME="${RUNNER_DEFAULT_HOME:-$CONF_DIR/forgejo}" +RUNNER_CONFIG_NAME="${RUNNER_CONFIG_NAME:-act_runner.yaml}" +RUNNER_LOG_FILE="${RUNNER_LOG_FILE:-$LOG_DIR/register.log}" +RUNNER_DAEMON_LOG="${RUNNER_DAEMON_LOG:-$LOG_DIR/daemon.log}" +RUNNER_CACHE_HOST="${RUNNER_CACHE_HOST:-$IP4_ADDRESS}" +CACHE_CONFIG_FILE="${CACHE_CONFIG_FILE:-$CONF_DIR/cache_server.yaml}" +CACHE_LOG_FILE="${CACHE_LOG_FILE:-$LOG_DIR/cache.log}" +RUNNER_CACHE_SECRET="${RUNNER_CACHE_SECRET:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional variables + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Specifiy custom directories to be created +ADD_APPLICATION_FILES="" +ADD_APPLICATION_DIRS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +APPLICATION_FILES="$LOG_DIR/$SERVICE_NAME.log" +APPLICATION_DIRS="$ETC_DIR $CONF_DIR $DATA_DIR $LOG_DIR $TMP_DIR $RUN_DIR $VAR_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional config dirs - will be Copied to /etc/$name +ADDITIONAL_CONFIG_DIRS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# define variables that need to be loaded into the service - escape quotes - var=\"value\",other=\"test\" +CMD_ENV="" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Overwrite based on file/directory + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Per Application Variables or imports + +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom commands to run before copying to /config +__run_precopy() { + # Define environment + local hostname=${HOSTNAME} + [ -d "/run/healthcheck" ] || mkdir -p "/run/healthcheck" + # Seed /config/$SERVICE_NAME from the baked /etc copy if default_config.yaml is missing, + # then replace the /etc/$SERVICE_NAME directory with a symlink to /config/$SERVICE_NAME + # so both paths always resolve to the same processed config. + if [ -d "$ETC_DIR" ] && ! [ -L "$ETC_DIR" ]; then + if [ ! -f "$CONF_DIR/.initialized" ]; then + mkdir -p "$CONF_DIR" + cp -Rf "$ETC_DIR/." "$CONF_DIR/" 2>/dev/null || true + fi + rm -Rf "$ETC_DIR" + ln -sf "$CONF_DIR" "$ETC_DIR" + fi + # allow custom functions + if builtin type -t __run_precopy_local | grep -q -- 'function'; then __run_precopy_local; fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom prerun functions - IE setup WWW_ROOT_DIR +__execute_prerun() { + # Define environment + local hostname=${HOSTNAME} + local max=30 + local count=0 + local status=0 + # Define actions/commands + while [ $count -lt $max ]; do + status=$(curl -q -LSsf -o /dev/null -w '%{http_code}' --max-time 5 http://localhost 2>/dev/null) + if [ "$status" = "200" ]; then + break + else + sleep 30 + count=$((count + 1)) + fi + done + # allow custom functions + if builtin type -t __execute_prerun_local | grep -q -- 'function'; then __execute_prerun_local; fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Run any pre-execution checks +__run_pre_execute_checks() { + # Set variables + local exitStatus=0 + # message to show at start + local pre_execute_checks_MessageST="Running preexecute check for $SERVICE_NAME" + # message to show at completion + local pre_execute_checks_MessageEnd="Finished preexecute check for $SERVICE_NAME" + __banner "$pre_execute_checks_MessageST" + # Put command to execute in parentheses + { + [ -d "$CONF_DIR/reg" ] || mkdir -p "$CONF_DIR/reg" + [ -d "$DATA_DIR/cache" ] || mkdir -p "$DATA_DIR/cache" + [ -d "$CONF_DIR/tokens" ] || mkdir -p "$CONF_DIR/tokens" + SYS_AUTH_TOKEN="${SYS_AUTH_TOKEN:-$(__gen_auth_token)}" + RUNNER_CACHE_SECRET="${RUNNER_CACHE_SECRET:-$(__gen_cache_secret)}" + if [ -f "$RUNNER_CONFIG_DEFAULT" ]; then + mkdir -p "$RUNNER_DEFAULT_HOME" "$TMP_DIR/runners/forgejo" + [ -f "$RUNNER_DEFAULT_HOME/$RUNNER_CONFIG_NAME" ] || copy "$RUNNER_CONFIG_DEFAULT" "$RUNNER_DEFAULT_HOME/$RUNNER_CONFIG_NAME" + # Always substitute tokens immediately after copy, regardless of registration state + __replace "REPLACE_RUNNER_TEMP" "$TMP_DIR/runners/forgejo" "$RUNNER_DEFAULT_HOME/$RUNNER_CONFIG_NAME" + __replace "REPLACE_RUNNER_HOME" "$RUNNER_DEFAULT_HOME" "$RUNNER_DEFAULT_HOME/$RUNNER_CONFIG_NAME" + __replace "REPLACE_RUNNER_CACHE_HOST" "$RUNNER_CACHE_HOST" "$RUNNER_DEFAULT_HOME/$RUNNER_CONFIG_NAME" + __replace "REPLACE_RUNNER_CACHE_PORT" "$RUNNER_CACHE_PORT" "$RUNNER_DEFAULT_HOME/$RUNNER_CONFIG_NAME" + __replace "REPLACE_RUNNER_CACHE_SECRET" "$RUNNER_CACHE_SECRET" "$RUNNER_DEFAULT_HOME/$RUNNER_CONFIG_NAME" + # Legacy single "forgejo"-named runner registration removed: start-runners + # (invoked from __post_execute) now owns all runner registration/count via + # RUNNERS_START, and registering a second runner here duplicated it + fi + exitStatus="${exitStatus:-0}" + chown -Rf "$SERVICE_USER":"$SERVICE_GROUP" "$CONF_DIR" "$ETC_DIR" "$DATA_DIR" 2>/dev/null + return $exitStatus + } + exitStatus=$? + __banner "$pre_execute_checks_MessageEnd: Status $exitStatus" + + # show exit message + if [ $exitStatus -ne 0 ]; then + echo "The pre-execution check has failed" >&2 + [ -f "$SERVICE_PID_FILE" ] && rm -Rf "$SERVICE_PID_FILE" + __script_exit 1 + fi + # allow custom functions + if builtin type -t __run_pre_execute_checks_local | grep -q -- 'function'; then __run_pre_execute_checks_local; fi + # exit function + return $exitStatus +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to update config files - IE: change port +__update_conf_files() { + # default exit code + local exitCode=0 + # set hostname + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + # - - - - - - - - - - - - - - - - - - - - - - - - - + # delete files + #__rm "" + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # custom commands + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # replace variables + # __replace "" "" "$CONF_DIR/act_runner.conf" + # replace variables recursively + # __find_replace "" "" "$CONF_DIR" + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # define actions + + # Mark config as fully initialised so __run_precopy skips re-seeding on restart + touch "$CONF_DIR/.initialized" 2>/dev/null || true + # allow custom functions + if builtin type -t __update_conf_files_local | grep -q -- 'function'; then __update_conf_files_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# function to run before executing +__pre_execute() { + # default exit code + local exitCode=0 + # set hostname + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + # execute if directories is empty + # __is_dir_empty "$CONF_DIR" && true + # - - - - - - - - - - - - - - - - - - - - - - - - - + # define actions to run after copying to /config + + # - - - - - - - - - - - - - - - - - - - - - - - - - + # unset unneeded variables + unset sysname + # Lets wait a few seconds before continuing + sleep 2 + # allow custom functions + if builtin type -t __pre_execute_local | grep -q -- 'function'; then __pre_execute_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# function to run after executing +__post_execute() { + # init pid var + local pid="" + # set default exit code + local retVal=0 + # how long to wait before executing + local ctime=${POST_EXECUTE_WAIT_TIME:-1} + # convert minutes to seconds + local waitTime=$((ctime * 60)) + # message to show at start + local postMessageST="Running post commands for $SERVICE_NAME" + # message to show at completion + local postMessageEnd="Finished post commands for $SERVICE_NAME" + export RUNNERS_START="${RUNNERS_START:-5}" RUNNER_LABELS RUNNERS_LOG_DIR="$LOG_DIR" + export SERVER_ADDRESS="$RUNNER_IP_ADDRESS:$FORGEJO_PORT" SERVER_TOKEN="${RUNNER_AUTH_TOKEN:-$SYS_AUTH_TOKEN}" + export RUNNER_CACHE_HOST RUNNER_CACHE_PORT + export RUNNER_CACHE_SECRET="${RUNNER_CACHE_SECRET:-$(__gen_cache_secret)}" + + # wait + sleep $waitTime + # execute commands after waiting + ( + # show message + __banner "$postMessageST" + # Legacy single "forgejo"-named runner daemon start removed: start-runners + # (below) now owns all runner registration/daemon startup via RUNNERS_START + # + if [ -f "$CACHE_CONFIG_FILE" ]; then + mkdir -p "$DATA_DIR/cache" + __replace "REPLACE_RUNNER_CACHE_DIR" "$DATA_DIR/cache" "$CACHE_CONFIG_FILE" + __replace "REPLACE_RUNNER_CACHE_PORT" "$RUNNER_CACHE_PORT" "$CACHE_CONFIG_FILE" + __replace "REPLACE_RUNNER_CACHE_SECRET" "$RUNNER_CACHE_SECRET" "$CACHE_CONFIG_FILE" + # stdout/stderr are redirected to a real file here (not inherited from the + # __post_execute pipe) because cache-server is a long-running process that + # never exits; if it inherited the pipe's write end, the tee reading it + # would never see EOF and __run_start_script would hang forever (same + # fd-leak class as the start-runners fix below) + act_runner cache-server --config "$CACHE_CONFIG_FILE" >>"$CACHE_LOG_FILE" 2>&1 & + execPid=$! + # disown so this long-running background job is fully detached from this + # subshell's job table; otherwise bash can block waiting on it when this + # subshell (itself the left side of the __post_execute pipe) reaches its end + disown "$execPid" 2>/dev/null || true + sleep 5 + if ps ax | awk '{print $1}' | grep -v -- 'grep' | grep -q -- "$execPid$"; then + echo "Cache server has been started and is listening on $RUNNER_CACHE_PORT" + else + echo "Failed to start the cache server" >&2 + fi + unset pid + fi + # stdout/stderr are redirected to a real file here (not inherited from the + # __post_execute pipe) because start-runners execs long-running act_runner + # daemons that never exit; if they inherited the pipe's write end, the + # tee reading it would never see EOF and __run_start_script would hang forever + if [ -x "/usr/local/bin/start-runners" ]; then + /usr/local/bin/start-runners >>"$LOG_DIR/runners.log" 2>&1 & + # disown so this long-running background job is fully detached from this + # subshell's job table; otherwise bash can block waiting on it when this + # subshell (itself the left side of the __post_execute pipe) reaches its end + disown "$!" 2>/dev/null || true + fi + # show exit message + __banner "$postMessageEnd: Status $retVal" + ) 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" & + # fire-and-forget: backgrounded subshell always succeeds at launch + retVal=0 + # allow custom functions + if builtin type -t __post_execute_local | grep -q -- 'function'; then __post_execute_local; fi + # exit function + return $retVal +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to update config files - IE: change port +__pre_message() { + local exitCode=0 + [ -n "$PRE_EXEC_MESSAGE" ] && eval echo "$PRE_EXEC_MESSAGE" + # execute commands + + # allow custom functions + if builtin type -t __pre_message_local | grep -q -- 'function'; then __pre_message_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to setup ssl support +__update_ssl_conf() { + local exitCode=0 + # set hostname + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + # execute commands + + # allow custom functions + if builtin type -t __update_ssl_conf_local | grep -q -- 'function'; then __update_ssl_conf_local; fi + # set exitCode + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +__create_service_env() { + local exitCode=0 + if [ ! -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ]; then + cat </dev/null +# - - - - - - - - - - - - - - - - - - - - - - - - - +# root/admin user info [password/random] +#ENV_ROOT_USER_NAME="${ENV_ROOT_USER_NAME:-$ACT_RUNNER_ROOT_USER_NAME}" # root user name +#ENV_ROOT_USER_PASS="${ENV_ROOT_USER_NAME:-$ACT_RUNNER_ROOT_PASS_WORD}" # root user password +#root_user_name="${ENV_ROOT_USER_NAME:-$root_user_name}" # +#root_user_pass="${ENV_ROOT_USER_PASS:-$root_user_pass}" # +# - - - - - - - - - - - - - - - - - - - - - - - - - +#Normal user info [password/random] +#ENV_USER_NAME="${ENV_USER_NAME:-$ACT_RUNNER_USER_NAME}" # +#ENV_USER_PASS="${ENV_USER_PASS:-$ACT_RUNNER_USER_PASS_WORD}" # +#user_name="${ENV_USER_NAME:-$user_name}" # normal user name +#user_pass="${ENV_USER_PASS:-$user_pass}" # normal user password + +EOF + fi + if [ ! -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" ]; then + cat <<'EOF' >"/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Local overrides - sourced after the main env file. +# Redefine any of these functions to customise behaviour. +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_precopy_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__execute_prerun_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_pre_execute_checks_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__update_conf_files_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__pre_execute_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__post_execute_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__pre_message_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +__update_ssl_conf_local() { true; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +EOF + fi + __file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" || exitCode=$((exitCode + 1)) + __file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" || exitCode=$((exitCode + 1)) + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# script to start server +__run_start_script() { + local runExitCode=0 + # expand variables + local workdir="$(eval echo "${WORK_DIR:-}")" + # expand variables + local cmd="$(eval echo "${EXEC_CMD_BIN:-}")" + # expand variables + local args="$(eval echo "${EXEC_CMD_ARGS:-}")" + # expand variables + local name="$(eval echo "${EXEC_CMD_NAME:-}")" + # expand variables + local pre="$(eval echo "${EXEC_PRE_SCRIPT:-}")" + # expand variables + local extra_env="$(eval echo "${CMD_ENV//,/ }")" + # expand variables + local lc_type="$(eval echo "${LANG:-${LC_ALL:-$LC_CTYPE}}")" + # expand variables + local home="$(eval echo "${workdir//\/root/\/tmp\/docker}")" + # expand variables + local path="$(eval echo "$PATH")" + # expand variables + local message="$(eval echo "")" + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" + if [ -f "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" ]; then + . "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" + fi + # + if [ -z "$cmd" ]; then + __post_execute 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" + retVal=$? + __log_info "Initialization of $SCRIPT_NAME has completed" + __script_exit $retVal + else + # ensure the command exists + if [ ! -x "$cmd" ]; then + __log_error "$name is not a valid executable" + return 2 + fi + # check and exit if already running (respects SERVICE_USES_PID in __proc_check) + if __proc_check "$name" || __proc_check "$cmd"; then + __log_debug "Service $name is already running" + return 0 + else + # - - - - - - - - - - - - - - - - - - - - - - - - - + # show message if env exists + if [ -n "$cmd" ]; then + if [ -n "$SERVICE_USER" ]; then + __log_info "Setting up $cmd to run as $SERVICE_USER" + else + SERVICE_USER="root" + fi + if [ -n "$SERVICE_PORT" ]; then + __log_info "$name will be running on port $SERVICE_PORT" + else + SERVICE_PORT="" + fi + fi + if [ -n "$pre" ] && command -v "$pre" &>/dev/null; then + export cmd_exec="$pre $cmd $args" + message="Starting service: $name $args through $pre" + else + export cmd_exec="$cmd $args" + message="Starting service: $name $args" + fi + if [ -n "$su_exec" ]; then + __log_debug "Using $su_exec" | tee -a -p "/data/logs/init.txt" + fi + __log_info "$message" | tee -a -p "/data/logs/init.txt" + __su_cmd touch "$SERVICE_PID_FILE" + # W14: invalidate cached START_SCRIPT if key variables changed + local _script_hash_src="$cmd $args $SERVICE_USER $RESET_ENV $su_exec" + local _script_hash + _script_hash=$(printf '%s' "$_script_hash_src" | md5sum 2>/dev/null | cut -c1-8 || true) + if [ -f "${START_SCRIPT}.hash" ] && [ -f "$START_SCRIPT" ]; then + if [ "$(cat "${START_SCRIPT}.hash" 2>/dev/null)" != "$_script_hash" ]; then + rm -f "$START_SCRIPT" "${START_SCRIPT}.hash" + fi + fi + if [ "$RESET_ENV" = "yes" ]; then + # RESET_ENV=yes intentionally strips all inherited vars; only explicit vars are passed + if [ ! -f "$START_SCRIPT" ]; then + # Use printf %q to safely quote each env component for embedding in the script + local _q_home _q_lc _q_path _q_sysname _q_svcuser _q_su _q_cmd _q_args _q_extra + _q_home=$(printf '%q' "$home") + _q_lc=$(printf '%q' "$lc_type") + _q_path=$(printf '%q' "$path") + _q_sysname=$(printf '%q' "$sysname") + _q_svcuser=$(printf '%q' "${SERVICE_USER:-$RUNAS_USER}") + _q_su="${su_exec:+$(printf '%q ' $su_exec)}" + _q_cmd=$(printf '%q' "$cmd") + _q_args=$(printf '%q ' $args) + _q_extra=$(printf '%q ' $extra_env) + { + printf '#!/usr/bin/env bash\n' + printf "trap 'exitCode=\$?;[ \$exitCode -ne 0 ] && [ -f \"\$SERVICE_PID_FILE\" ] && rm -Rf \"\$SERVICE_PID_FILE\";exit \$exitCode' EXIT\n" + printf 'set -Eeo pipefail\n' + printf '# Setting up %s to run as %s with env\n' "$cmd" "${SERVICE_USER:-root}" + printf 'retVal=10\n' + printf 'SERVICE_NAME=%q\n' "$SERVICE_NAME" + printf 'SERVICE_PID_FILE=%q\n' "$SERVICE_PID_FILE" + printf 'LOG_DIR=%q\n' "$LOG_DIR" + printf '%senv -i HOME=%s LC_CTYPE=%s PATH=%s HOSTNAME=%s USER=%s %s %s %s 2>>"/dev/stderr" >>"$LOG_DIR/$SERVICE_NAME.log" &\n' \ + "$_q_su" "$_q_home" "$_q_lc" "$_q_path" "$_q_sysname" "$_q_svcuser" "$_q_extra" "$_q_cmd" "$_q_args" + printf 'execPid=$!\n' + printf 'sleep 1\n' + printf 'if [ -n "$execPid" ] && kill -0 "$execPid" 2>/dev/null; then\n' + printf ' echo "$execPid" >"$SERVICE_PID_FILE"\n' + printf ' retVal=0\n' + printf ' printf '"'"'%%s\n'"'"' "$SERVICE_NAME: $execPid" >"/run/healthcheck/$SERVICE_NAME"\n' + printf 'else\n' + printf ' retVal=10\n' + printf ' echo "Failed to start service %s" >&2\n' "$cmd" + printf 'fi\n' + printf 'exit $retVal\n' + } >"$START_SCRIPT" + printf '%s' "$_script_hash" >"${START_SCRIPT}.hash" + fi + else + if [ ! -f "$START_SCRIPT" ]; then + local _q_su _q_cmd _q_args _q_path _q_home + _q_su="${su_exec:+$(printf '%q ' $su_exec)}" + _q_cmd=$(printf '%q' "$cmd") + _q_args=$(printf '%q ' $args) + _q_path=$(printf '%q' "$path") + _q_home=$(printf '%q' "$home") + { + printf '#!/usr/bin/env bash\n' + printf "trap 'exitCode=\$?;[ \$exitCode -ne 0 ] && [ -f \"\$SERVICE_PID_FILE\" ] && rm -Rf \"\$SERVICE_PID_FILE\";exit \$exitCode' EXIT\n" + printf 'set -Eeo pipefail\n' + printf '# Setting up %s to run as %s\n' "$cmd" "${SERVICE_USER:-root}" + printf 'export PATH=%s\n' "$_q_path" + printf 'export HOME=%s\n' "$_q_home" + printf 'retVal=10\n' + printf 'SERVICE_NAME=%q\n' "$SERVICE_NAME" + printf 'SERVICE_PID_FILE=%q\n' "$SERVICE_PID_FILE" + printf 'LOG_DIR=%q\n' "$LOG_DIR" + printf '%s%s %s 2>>"/dev/stderr" >>"$LOG_DIR/$SERVICE_NAME.log" &\n' \ + "$_q_su" "$_q_cmd" "$_q_args" + printf 'execPid=$!\n' + printf 'sleep 1\n' + printf 'if [ -n "$execPid" ] && kill -0 "$execPid" 2>/dev/null; then\n' + printf ' echo "$execPid" >"$SERVICE_PID_FILE"\n' + printf ' retVal=0\n' + printf 'else\n' + printf ' retVal=10\n' + printf ' echo "Failed to start service %s" >&2\n' "$cmd" + printf 'fi\n' + printf 'exit $retVal\n' + } >"$START_SCRIPT" + printf '%s' "$_script_hash" >"${START_SCRIPT}.hash" + fi + fi + fi + if [ ! -x "$START_SCRIPT" ]; then + chmod 755 -Rf "$START_SCRIPT" + fi + if [ "$CONTAINER_INIT" != "yes" ]; then + # W15: launch as bash, not sh, since the generated script uses bash-specific features + bash "$START_SCRIPT" + runExitCode=$? + fi + fi + return $runExitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# username and password actions +__run_secure_function() { + local filesperms + if [ -n "$user_name" ] || [ -n "$user_pass" ]; then + for filesperms in "${USER_FILE_PREFIX}"/*; do + if [ -e "$filesperms" ]; then + chmod -Rf 600 "$filesperms" + chown -Rf $SERVICE_USER:$SERVICE_USER "$filesperms" 2>/dev/null + fi + done 2>/dev/null | tee -p -a "/data/logs/init.txt" + fi + if [ -n "$root_user_name" ] || [ -n "$root_user_pass" ]; then + for filesperms in "${ROOT_FILE_PREFIX}"/*; do + if [ -e "$filesperms" ]; then + chmod -Rf 600 "$filesperms" + chown -Rf $SERVICE_USER:$SERVICE_USER "$filesperms" 2>/dev/null + fi + done 2>/dev/null | tee -p -a "/data/logs/init.txt" + fi + unset filesperms +} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow ENV_ variable - Import env file +__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" +__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# default exit code +SERVICE_EXIT_CODE=0 +# application specific +# set the binary name +EXEC_CMD_NAME="${EXEC_CMD_BIN##*/}" +# set the pid file location +SERVICE_PID_FILE="/run/init.d/$EXEC_CMD_NAME.pid" +# check if running +SERVICE_PID_NUMBER="$(__pgrep "$EXEC_CMD_NAME" || echo '')" +# set full path +EXEC_CMD_BIN="$(type -P "$EXEC_CMD_BIN" || echo "$EXEC_CMD_BIN")" +# set full path +EXEC_PRE_SCRIPT="$(type -P "$EXEC_PRE_SCRIPT" || echo "$EXEC_PRE_SCRIPT")" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Only run check when explicitly requested +if [ "$1" = "check" ] && __check_service "$1"; then + SERVICE_IS_RUNNING=yes +elif [ "$1" = "check" ]; then + SERVICE_IS_RUNNING="no" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# ensure needed directories exists +[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR" +[ -d "$RUN_DIR" ] || mkdir -p "$RUN_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create auth directories +[ -n "$USER_FILE_PREFIX" ] && { [ -d "$USER_FILE_PREFIX" ] || mkdir -p "$USER_FILE_PREFIX"; } +[ -n "$ROOT_FILE_PREFIX" ] && { [ -d "$ROOT_FILE_PREFIX" ] || mkdir -p "$ROOT_FILE_PREFIX"; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +[ -n "$RUNAS_USER" ] || RUNAS_USER="root" +[ -n "$SERVICE_USER" ] || SERVICE_USER="$RUNAS_USER" +[ -n "$SERVICE_GROUP" ] || SERVICE_GROUP="${SERVICE_USER:-$RUNAS_USER}" +[ "$IS_WEB_SERVER" = "yes" ] && RESET_ENV="yes" && __is_htdocs_mounted +[ "$IS_WEB_SERVER" = "yes" ] && [ -z "$SERVICE_PORT" ] && SERVICE_PORT="80" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Database env +if [ "$IS_DATABASE_SERVICE" = "yes" ] || [ "$USES_DATABASE_SERVICE" = "yes" ]; then + RESET_ENV="no" + DATABASE_CREATE="${ENV_DATABASE_CREATE:-$DATABASE_CREATE}" + DATABASE_USER_NORMAL="${ENV_DATABASE_USER:-${DATABASE_USER_NORMAL:-$user_name}}" + DATABASE_PASS_NORMAL="${ENV_DATABASE_PASSWORD:-${DATABASE_PASS_NORMAL:-$user_pass}}" + DATABASE_USER_ROOT="${ENV_DATABASE_ROOT_USER:-${DATABASE_USER_ROOT:-$root_user_name}}" + DATABASE_PASS_ROOT="${ENV_DATABASE_ROOT_PASSWORD:-${DATABASE_PASS_ROOT:-$root_user_pass}}" + if [ -n "$DATABASE_PASS_NORMAL" ] && [ ! -f "${USER_FILE_PREFIX}/db_pass_user" ]; then + echo "$DATABASE_PASS_NORMAL" >"${USER_FILE_PREFIX}/db_pass_user" + fi + if [ -n "$DATABASE_PASS_ROOT" ] && [ ! -f "${ROOT_FILE_PREFIX}/db_pass_root" ]; then + echo "$DATABASE_PASS_ROOT" >"${ROOT_FILE_PREFIX}/db_pass_root" + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# [DATABASE_DIR_[SQLITE,REDIS,POSTGRES,MARIADB,COUCHDB,MONGODB,SUPABASE]] +if [ "$DATABASE_SERVICE_TYPE" = "custom" ]; then + DATABASE_DIR="${DATABASE_DIR_CUSTOM:-/data/db/custom}" + DATABASE_BASE_DIR="${DATABASE_DIR_CUSTOM:-/data/db/custom}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_CUSTOM:-/usr/local/share/httpd/admin/databases}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_CUSTOM:-/admin/dbadmin}" +elif [ "$SERVICE_NAME" = "redis" ] || [ "$DATABASE_SERVICE_TYPE" = "redis" ]; then + DATABASE_DIR="${DATABASE_DIR_REDIS:-/data/db/redis}" + DATABASE_BASE_DIR="${DATABASE_DIR_REDIS:-/data/db/redis}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_REDIS:-/usr/local/share/httpd/admin/redis}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_REDIS:-/admin/redis}" +elif [ "$SERVICE_NAME" = "postgres" ] || [ "$DATABASE_SERVICE_TYPE" = "postgres" ]; then + DATABASE_DIR="${DATABASE_DIR_POSTGRES:-/data/db/postgres}" + DATABASE_BASE_DIR="${DATABASE_DIR_POSTGRES:-/data/db/postgres}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_POSTGRES:-/usr/local/share/httpd/admin/postgres}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_POSTGRES:-/admin/postgres}" +elif [ "$SERVICE_NAME" = "mariadb" ] || [ "$DATABASE_SERVICE_TYPE" = "mariadb" ]; then + DATABASE_DIR="${DATABASE_DIR_MARIADB:-/data/db/mariadb}" + DATABASE_BASE_DIR="${DATABASE_DIR_MARIADB:-/data/db/mariadb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MARIADB:-/usr/local/share/httpd/admin/mysql}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MARIADB:-/admin/mysql}" +elif [ "$SERVICE_NAME" = "mysql" ] || [ "$DATABASE_SERVICE_TYPE" = "mysql" ]; then + DATABASE_DIR="${DATABASE_DIR_MYSQL:-/data/db/mysql}" + DATABASE_BASE_DIR="${DATABASE_DIR_MYSQL:-/data/db/mysql}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MYSQL:-/usr/local/share/httpd/admin/mysql}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MYSQL:-/admin/mysql}" +elif [ "$SERVICE_NAME" = "couchdb" ] || [ "$DATABASE_SERVICE_TYPE" = "couchdb" ]; then + DATABASE_DIR="${DATABASE_DIR_COUCHDB:-/data/db/couchdb}" + DATABASE_BASE_DIR="${DATABASE_DIR_COUCHDB:-/data/db/couchdb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_COUCHDB:-/usr/local/share/httpd/admin/couchdb}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_COUCHDB:-/admin/couchdb}" +elif [ "$SERVICE_NAME" = "mongodb" ] || [ "$DATABASE_SERVICE_TYPE" = "mongodb" ]; then + DATABASE_DIR="${DATABASE_DIR_MONGODB:-/data/db/mongodb}" + DATABASE_BASE_DIR="${DATABASE_DIR_MONGODB:-/data/db/mongodb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MONGODB:-/usr/local/share/httpd/admin/mongodb}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MONGODB:-/admin/mongodb}" +elif [ "$SERVICE_NAME" = "supabase" ] || [ "$DATABASE_SERVICE_TYPE" = "supabase" ]; then + DATABASE_DIR="${DATABASE_DIR_SUPABASE:-/data/db/supabase}" + DATABASE_BASE_DIR="${DATABASE_DIR_SUPABASE:-/data/db/supabase}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_SUPABASE:-/usr/local/share/httpd/admin/supabase}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_SUPBASE:-/admin/supabase}" +elif [ "$SERVICE_NAME" = "sqlite" ] || [ "$DATABASE_SERVICE_TYPE" = "sqlite" ]; then + DATABASE_DIR="${DATABASE_DIR_SQLITE:-/data/db/sqlite}/$SERVER_NAME" + DATABASE_BASE_DIR="${DATABASE_DIR_SQLITE:-/data/db/sqlite}/$SERVER_NAME" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_SQLITE:-/usr/local/share/httpd/admin/sqlite}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_SQLITE:-/admin/sqlite}" + [ -d "$DATABASE_DIR" ] || mkdir -p "$DATABASE_DIR" + chmod 777 "$DATABASE_DIR" +fi +[ -n "$DATABASE_ADMIN_WWW_ROOT" ] && { [ ! -d "$DATABASE_ADMIN_WWW_ROOT" ] || mkdir -p "${DATABASE_ADMIN_WWW_ROOT}"; } +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow variables via imports - Overwrite existing +[ -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ] && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set password to random if variable is random +[ "$user_pass" = "random" ] && user_pass="$(__random_password ${RANDOM_PASS_USER:-16})" +# - - - - - - - - - - - - - - - - - - - - - - - - - +[ "$root_user_pass" = "random" ] && root_user_pass="$(__random_password ${RANDOM_PASS_ROOT:-16})" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow setting initial users and passwords via environment and save to file +[ -n "$user_name" ] && echo "$user_name" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_name" +[ -n "$user_pass" ] && echo "$user_pass" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" +[ -n "$root_user_name" ] && echo "$root_user_name" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name" +[ -n "$root_user_pass" ] && echo "$root_user_pass" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create needed dirs +[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR" +[ -d "$RUN_DIR" ] || mkdir -p "$RUN_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow per init script usernames and passwords +__file_exists_with_content "${USER_FILE_PREFIX}/${SERVICE_NAME}_name" && user_name="$(<"${USER_FILE_PREFIX}/${SERVICE_NAME}_name")" +__file_exists_with_content "${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" && user_pass="$(<"${USER_FILE_PREFIX}/${SERVICE_NAME}_pass")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name" && root_user_name="$(<"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" && root_user_pass="$(<"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass")" +__file_exists_with_content "${USER_FILE_PREFIX}/db_pass_user" && DATABASE_PASS_NORMAL="$(<"${USER_FILE_PREFIX}/db_pass_user")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/db_pass_root" && DATABASE_PASS_ROOT="$(<"${ROOT_FILE_PREFIX}/db_pass_root")" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set hostname for script +sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +__create_service_env +# - - - - - - - - - - - - - - - - - - - - - - - - - +# pre-run function +__execute_prerun +# - - - - - - - - - - - - - - - - - - - - - - - - - +# create user if needed +__create_service_user "$SERVICE_USER" "$SERVICE_GROUP" "${WORK_DIR:-/home/$SERVICE_USER}" "${SERVICE_UID:-}" "${SERVICE_GID:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Modify user if needed +__set_user_group_id $SERVICE_USER ${SERVICE_UID:-} ${SERVICE_GID:-} +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Create base directories +__setup_directories +# - - - - - - - - - - - - - - - - - - - - - - - - - +# set switch user command +__switch_to_user +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Initialize the home/working dir +__init_working_dir +# - - - - - - - - - - - - - - - - - - - - - - - - - +# show init message +__pre_message +# - - - - - - - - - - - - - - - - - - - - - - - - - +# +__initialize_db_users +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Initialize ssl +__update_ssl_conf +__update_ssl_certs +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set permissions in ${USER_FILE_PREFIX} and ${ROOT_FILE_PREFIX} +__run_secure_function +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_precopy +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Replace variables +__initialize_replace_variables "$CONF_DIR" "$ADDITIONAL_CONFIG_DIRS" "$WWW_ROOT_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# +__initialize_database +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Updating config files +__update_conf_files +# - - - - - - - - - - - - - - - - - - - - - - - - - +# run the pre execute commands +__pre_execute +# - - - - - - - - - - - - - - - - - - - - - - - - - +# Set permissions +__fix_permissions "$SERVICE_USER" "$SERVICE_GROUP" +# - - - - - - - - - - - - - - - - - - - - - - - - - +# +__run_pre_execute_checks 2>/dev/stderr | tee -a -p "/data/logs/entrypoint.log" "/data/logs/init.txt" || return 20 +# Token was set inside a subshell (pipe); read it back from the file written by __gen_auth_token +[ -z "$SYS_AUTH_TOKEN" ] && [ -s "$CONF_DIR/tokens/system" ] && SYS_AUTH_TOKEN="$(<"$CONF_DIR/tokens/system")" +# - - - - - - - - - - - - - - - - - - - - - - - - - +__run_start_script 2>>/dev/stderr | tee -p -a "/data/logs/entrypoint.log" +errorCode=${PIPESTATUS[0]} +if [ -n "$EXEC_CMD_BIN" ]; then + if [ "$errorCode" -eq 0 ]; then + SERVICE_EXIT_CODE=0 + SERVICE_IS_RUNNING="yes" + else + SERVICE_EXIT_CODE=$errorCode + SERVICE_IS_RUNNING="${SERVICE_IS_RUNNING:-no}" + if [ ! -s "$SERVICE_PID_FILE" ]; then + rm -Rf "$SERVICE_PID_FILE" + fi + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - +# EXEC_CMD_BIN is empty for this service, so __run_start_script already invoked +# __post_execute internally (its empty-cmd branch); calling it again here would +# duplicate runner registration/daemon startup +# - - - - - - - - - - - - - - - - - - - - - - - - - +__script_exit $SERVICE_EXIT_CODE diff --git a/rootfs/usr/local/etc/resolv.conf b/rootfs/usr/local/etc/resolv.conf new file mode 100644 index 0000000..7b7a70b --- /dev/null +++ b/rootfs/usr/local/etc/resolv.conf @@ -0,0 +1,6 @@ +nameserver 1.1.1.1 +nameserver 1.0.0.1 +nameserver 8.8.8.8 +nameserver 8.8.4.4 +search . +options ndots:0