🦈🏠🐜 Initial Commit 🐜🦈🏠

This commit is contained in:
casjay
2026-09-03 21:08:19 -04:00
commit c8f7ff8d22
37 changed files with 9238 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{"sessionId":"a4c0164e-35a4-4118-a2fb-1ec39431392f","pid":618193,"procStart":"112807435","acquiredAt":1788427111850}
+17
View File
@@ -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/**
+80
View File
@@ -0,0 +1,80 @@
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202606051822-git
# @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
# @@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 <docker-admin@casjaysdev.pro>"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# 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
# - - - - - - - - - - - - - - - - - - - - - - - - -
+86
View File
@@ -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
+68
View File
@@ -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 }}
+104
View File
@@ -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
+829
View File
@@ -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/<base>`** — never pull an upstream distro image directly;
the base repos exist so every app shares one patched, multi-arch foundation.
9. **Only `root/`, `tmp/`, and `usr/` may exist at `rootfs/` top level** (PART 1).
10. **Maintenance runs through the `dockersrc-bootstrap` agent** — do not improvise the
update procedure from memory.
---
# PART 1: REPOSITORY MODEL & STRUCTURE
## What an app image repo is
A `casjaysdevdocker/{name}` repo containerizes one application on top of a
`casjaysdev/*` base. It publishes a single image (`casjaysdevdocker/{name}:latest` plus a
date tag) — no per-version Dockerfile variants. The application itself is installed in
`05-custom.sh` and started by one or more init.d service scripts.
## Standard tree
```
{name}/
├── AI.md # This specification (permanent)
├── Dockerfile # [generated] single build file
├── .dockerignore # [generated]
├── .env.scripts # [generated] build config
├── .gitattributes # [generated]
├── .gitea/workflows/
│ └── build.yml # [generated] gen-dockerfile actions
├── .gitignore # [generated]
├── LICENSE.md # License (WTFPL / app's own license)
├── README.md # [generated*] standard layout (PART 6)
└── rootfs/ # Container filesystem overlay
├── root/docker/setup/ # [generated*] build-time setup scripts 0007
├── tmp/ # staged files installed at build time (optional)
└── usr/local/
├── bin/ # [generated*] entrypoint.sh, pkmgr, symlink, copy,
│ # healthcheck + [hand-crafted] app-specific scripts
└── etc/docker/
├── env/ # [hand-crafted] build/runtime env fragments (optional)
├── functions/
│ └── entrypoint.sh # [generated] entrypoint function library
└── init.d/ # [hand-crafted] runtime init scripts (one per service)
```
`[generated]` — safe to regenerate; local edits will be lost.
`[generated*]` — regenerated from the template, EXCEPT files carrying repo-specific
content (`05-custom.sh` with a real body, extra bin scripts, a hand-crafted README) —
those follow the hand-crafted rules in PARTs 5 and 6.
`[hand-crafted]` — never overwritten by the template system.
App repos may additionally carry project files (`IDEA.md`, `CLAUDE.md`, `TODO.AI.md`)
per the global project conventions — they are repo-owned and never touched by tooling.
## rootfs top-level policy
The only valid directories at the `rootfs/` root are `root/`, `tmp/`, and `usr/`.
Anything else is a leftover from old patterns. Migration map:
| Old rootfs path | Correct rootfs path |
|-----------------|---------------------|
| `rootfs/etc/{path}` | `rootfs/tmp/etc/{path}` |
| `rootfs/config/{path}` | `rootfs/tmp/etc/{path}` |
| `rootfs/data/{path}` | `rootfs/tmp/var/{path}` |
| `rootfs/var/{path}` | `rootfs/tmp/var/{path}` |
| `rootfs/opt/{path}` | `rootfs/tmp/opt/{path}` |
| `rootfs/share/{path}` | `rootfs/usr/local/share/{path}` |
`rootfs/usr/local/share/template-files/` is retired — the `DEFAULT_TEMPLATE_DIR`,
`DEFAULT_FILE_DIR`, `DEFAULT_DATA_DIR`, and `DEFAULT_CONF_DIR` variables were removed
from the template system; the entrypoint installs staged files from `rootfs/tmp/etc/`
at container start instead.
## Repo type detection
A repo is an **app** repo when no `Dockerfile.*` variant files exist:
```bash
if find . -maxdepth 1 -name 'Dockerfile.*' -type f | grep -q -- .; then
REPO_TYPE="base"
else
REPO_TYPE="app"
fi
```
---
# PART 2: TEMPLATE SYSTEM REFERENCE
Templates ship with `gen-dockerfile`, installed at
`/usr/local/share/CasjaysDev/scripts/templates/dockerfiles/`
(`$CASJAYSDEVDIR/templates/dockerfiles/` in a dev checkout). To inspect what the current
templates produce, generate a fresh reference tree in a temp dir:
```bash
gen-dockerfile /tmp/gen-dockerfile/{org}/{repo} {distro}
```
See `gen-dockerfile --help` for supported distros/types. Keep this PART in sync whenever
the templates change.
## Template inventory
The template name selects the base OS family; for an app repo the resulting pull URL is
always the matching `casjaysdev/*` image:
| Template | Final stage | Init / PID 1 | App pulls FROM |
|----------|-------------|--------------|----------------|
| `alpine.template` | `scratch.template` | tini | `casjaysdev/alpine` |
| `debian.template` | `scratch.template` | tini | `casjaysdev/debian` |
| `ubuntu.template` | `scratch.template` | tini | `casjaysdev/ubuntu` |
| `rhel.template` | `scratch.template` | tini | `casjaysdev/almalinux` |
| `archlinux.template` | `scratch.template` | tini | `casjaysdev/archlinux` (multi-arch manifest) |
| `web.template` | `systemd.template` | `/sbin/init` | `casjaysdev/web` |
| `xorg.template` | `systemd.template` | `/sbin/init` | `casjaysdev/xorg` |
Default template for app repos is `alpine` unless the application needs systemd, a GUI
stack, or a distro-specific package.
## Final-stage templates
`scratch.template` — all non-GUI templates.
- `ENTRYPOINT [ "tini", "-p", "SIGTERM","--", "/usr/local/bin/entrypoint.sh" ]`
- `STOPSIGNAL SIGRTMIN+3`
`systemd.template``web` and `xorg` (systemd is PID 1; tini is redundant).
- `ENTRYPOINT [ "/sbin/init" ]`
- `STOPSIGNAL SIGRTMIN+3`
- No `tini_provider` stage, no `COPY --from=tini_provider` line.
Both are identical apart from `ENTRYPOINT`. OCI labels, `ENV HOSTNAME`, and
`VOLUME`/`EXPOSE`/`HEALTHCHECK` are the same in both.
## OCI label standard
Both final-stage templates emit these labels (no others):
```
LABEL maintainer="${GEN_DOCKERFILE_MAINTAINER}"
LABEL org.opencontainers.image.vendor="${GEN_DOCKERFILE_VENDOR:-CasjaysDev}"
LABEL org.opencontainers.image.authors="${GEN_DOCKERFILE_AUTHOR:-CasjaysDev}"
LABEL org.opencontainers.image.licenses="${LICENSE}"
LABEL org.opencontainers.image.title="${IMAGE_NAME}"
LABEL org.opencontainers.image.description="Containerized version of ${IMAGE_NAME}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"
LABEL org.opencontainers.image.version="${BUILD_VERSION}"
LABEL org.opencontainers.image.revision="${GIT_COMMIT}"
LABEL org.opencontainers.image.url="${GEN_DOCKERFILE_HUB_REPO}"
LABEL org.opencontainers.image.source="${GEN_DOCKERFILE_GIT_REPO}"
LABEL org.opencontainers.image.documentation="${GEN_DOCKERFILE_GIT_REPO}"
LABEL org.opencontainers.image.vcs-type="Git"
LABEL com.github.containers.toolbox="false"
```
Shell-expanded values (no `\`) are evaluated at template-render time by `gen-dockerfile`.
Dollar-escaped values (`\${...}`) become literal Docker `ARG`/`ENV` references in the
generated `Dockerfile`.
Resolved values for a `casjaysdevdocker` repo pushing to Docker Hub:
| Label | Value |
|-------|-------|
| `url` | `https://hub.docker.com/r/casjaysdevdocker/{name}` — browsable Hub page; `gen-dockerfile` derives it from the registry host (`docker.io``hub.docker.com/r/`) |
| `source` | `https://github.com/casjaysdevdocker/{name}` |
| `documentation` | `https://github.com/casjaysdevdocker/{name}` |
Older app repos may still carry `url="https://docker.io/casjaysdevdocker/{name}"` — that
is the stale form; regeneration corrects it. Removed labels (never re-add):
- `org.opencontainers.image.base.name` — belongs on the base image, not this image
- `org.opencontainers.image.schema-version` — non-spec; redundant with `version`
- Any duplicate `authors` or `source` entries
## HOSTNAME convention
All templates set `ENV HOSTNAME="casjaysdevdocker-${IMAGE_NAME}"` in every stage that
declares it. The prefix is always `casjaysdevdocker-`, never `casjaysdev-`.
## `GEN_DOCKERFILE_APP_DIR` and pull URL logic
`GEN_DOCKERFILE_APP_DIR` is auto-detected by `gen-dockerfile` from the parent directory
of `$PWD` (the org the checkout lives in):
```bash
GEN_DOCKERFILE_APP_DIR="${GEN_DOCKERFILE_APP_DIR:-$(basename -- "$(dirname -- "$PWD")")}"
```
It selects the `GEN_DOCKER_SPECIFY_IMAGE_SOURCE_*` defaults:
- `casjaysdevdocker/*` repos → `FROM casjaysdev/<distro>:latest` (pre-built, multi-arch)
- `dockersrc/*` and all other orgs → `FROM <distro>:latest` (upstream official images)
App repos must resolve to the `casjaysdev/*` branch — a checkout outside
`~/Projects/*/casjaysdevdocker/` needs `GEN_DOCKERFILE_APP_DIR="casjaysdevdocker"`
exported before calling `gen-dockerfile`, or the regenerated Dockerfile silently reverts
to upstream distro pulls (rule 8 violation).
## Arch Linux apps
`casjaysdev/archlinux` is a multi-arch manifest (`linux/amd64` + `linux/arm64`), so app
repos use a single `FROM ${PULL_URL}:${DISTRO_VERSION} AS build` — the three-stage
`base-${TARGETARCH}` FROM block belongs to the base repo only.
## `web.template` / `xorg.template` notes
`web` apps inherit the systemd + noVNC stack (`SERVICE_PORT="5800"`,
`EXPOSE_PORTS="5800 5900"` defaults); `xorg` apps inherit the systemd + Xorg stack. App
packages go in `ENV_PACKAGES` / `02-packages.sh`, never by editing the template's stack
list.
## `debian.template` / `ubuntu.template` — RUN continuation
The first `RUN` block must have `; \` after the `echo` line so
`export DEBIAN_FRONTEND=noninteractive` executes before `apt-get`:
```dockerfile
RUN set -e; \
echo "Updating the system"; \
export DEBIAN_FRONTEND=noninteractive; \
apt-get update && apt-get upgrade -yy && apt-get dist-upgrade -yy
```
Without the `; \` the export is a no-op and `apt-get` may prompt interactively.
## Template resolution order
1. `$GEN_DOCKERFILE_CONFIG_DIR/templates/<name>.template` (user override)
2. `/usr/local/share/CasjaysDev/scripts/templates/dockerfiles/<name>.template`
(installed; `$CASJAYSDEVDIR/templates/dockerfiles/` in a dev checkout)
`template_options.source` is sourced after `__set_variables`, allowing template-specific
variable overrides.
---
# PART 3: TOOLING — gen-dockerfile & gen-script
## `gen-dockerfile`
```
Usage: gen-dockerfile [options] [dir] [template] [repo-name] [git-repo-url]
```
| Flag | Meaning |
|------|---------|
| `--update` | Rewrite `.env.scripts` (add/drop vars against the current template) and update ARG/LABEL lines in the `Dockerfile`. Touches no other file. |
| `--nogit` | Do not init or commit a git repo — required inside an existing repo. |
| `--dir PATH` | Operate on / write output to PATH instead of `$PWD`. |
| `--template NAME` | Template to use (`alpine`, `debian`, `ubuntu`, `rhel`, `archlinux`, `scratch`, `web`, `xorg`). Defaults to `alpine`. |
| `--repo NAME` | Registry repo name (image basename). Defaults to the directory name. |
| `--org NAME` | Registry owner / GitHub org (`--user` is an alias). Prefix `git:` or `reg:` to scope to one system; bare value sets both. For app repos both are `casjaysdevdocker`. |
| `--registry URL` | Registry provider URL (e.g. `https://docker.io`). |
| `--tag VERSION` | Image version tag (default `latest`). |
| `--add-tags TAGS` | Comma-separated additional tags (`USE_DATE` = auto date tag). |
| `--distro-name IMG` | Base image pull URL (overrides `ENV_PULL_URL`). |
| `--distro-version T` | Base image tag (overrides `ENV_DISTRO_TAG`). |
| `--startup FILE` | Generate an init.d service script at `rootfs/usr/local/etc/docker/init.d/FILE` via `gen-script other/start-service`. |
| `--dockerfile` | Regenerate the Dockerfile only. |
| `--force` | Overwrite existing files without prompting. |
Resolution order when a value is not given by a flag: flags → git remote → project dirs →
defaults.
Special subcommand — `gen-dockerfile actions` writes `.gitea/workflows/build.yml` from
the existing `Dockerfile` (PART 7). App repos have no versioned `build.{ver}.yml` files.
## `gen-script`
```
Usage: gen-script [options] [template] [filename]
```
| Flag / env var | Meaning |
|----------------|---------|
| `--dir PATH` | Write the generated file to `PATH/filename`. |
| `-n` / `--name VALUE` | Service name substituted into the template — fills `REPLACE_SERVICE_NAME` in `other/start-service`, pre-populating `SERVICE_NAME=` without a sed step. |
| `GEN_SCRIPT_OVERWRITE="Y"` | Overwrite the output without prompting (default `"A"` = ask). Required when the target exists, even with `GEN_SCRIPT_EDITFILE="N"`. |
| `GEN_SCRIPT_EDITFILE="N"` | Suppress the interactive editor after generation. `-e`/`--no` sets BOTH this AND `GEN_SCRIPT_OVERWRITE="Y"`; the env var alone does not. |
| `other/start-service` | Template path — positional arg 1, slash-joined words, matching the `@@Template` header. |
| `filename` | Output basename — positional arg 2, combined with `--dir`. |
Other flags: `-k`/`--keep` (never overwrite), `--replace` (new header replaces old),
`-d`/`--desc` (header description), `-p`/`--prev` (copy header metadata from a file).
---
# PART 4: `.env.scripts` REFERENCE
Generated at the repo root; sourced by `gen-dockerfile` and by CI at build time. App
repos carry exactly one. It is a pure `KEY="value"` file — no logic.
## Variables
| Variable | Purpose |
|----------|---------|
| `ENV_DOCKERFILE` | Dockerfile to build (`Dockerfile`) |
| `ENV_REGISTRY_REPO` | Image name in the registry (`{name}`) |
| `ENV_REGISTRY_ORG` | Registry namespace — `casjaysdevdocker` for app repos |
| `ENV_REGISTRY_URL` | Registry base URL (`https://docker.io`) — pull/push host, never a label URL |
| `ENV_REGISTRY_PUSH` | Full push path `org/repo` (`casjaysdevdocker/{name}`) |
| `ENV_ADD_IMAGE_PUSH` | Extra push destinations |
| `ENV_GIT_REPO_URL` | Full Git repo URL — `https://github.com/casjaysdevdocker/{name}`; feeds the `source`/`documentation` labels, so a wrong value here regresses labels on regeneration |
| `ENV_USE_TEMPLATE` | Template name (`alpine`, `debian`, …) — the authoritative record of which base family the app builds on |
| `ENV_PULL_URL` | Base image to pull FROM (`casjaysdev/<base>`) |
| `ENV_DISTRO_TAG` | Tag for the pull image (`latest`) |
| `ENV_IMAGE_TAG` | Default image tag (`latest`) |
| `ENV_ADD_TAGS` | Additional comma-separated tags; `USE_DATE` auto-generates a date tag |
| `ENV_PACKAGES` | Space-separated package list |
| `ENV_VENDOR` / `ENV_AUTHOR` / `ENV_MAINTAINER` | Label metadata |
| `SERVICE_PORT` | Primary exposed port — apps normally set this |
| `EXPOSE_PORTS` | Additional exposed ports |
| `PHP_VERSION` / `NODE_VERSION` / `NODE_MANAGER` | Runtime versions (`system` default) |
| `WWW_ROOT_DIR` | Web root (`/usr/local/share/httpd/default`) |
| `DOCKER_ENTYPOINT_PORTS_WEB` / `DOCKER_ENTYPOINT_PORTS_SRV` | Ports passed to the entrypoint |
| `DOCKER_ENTYPOINT_HEALTH_APPS` / `DOCKER_ENTYPOINT_HEALTH_ENDPOINTS` | Healthcheck targets |
## Legacy variable auto-migration
`gen-dockerfile` calls `__migrate_env_script` on every run, renaming old variables:
| Old name | Current name |
|----------|-------------|
| `ENV_IMAGE_NAME` | `ENV_REGISTRY_REPO` |
| `ENV_IMAGE_PUSH` | `ENV_REGISTRY_PUSH` |
| `ENV_HUB_BASE` | `ENV_REGISTRY_URL` |
| `ENV_ORG_NAME` | `ENV_REGISTRY_ORG` |
Never use the old names in new files. Retired variables that must not reappear anywhere:
`DEFAULT_TEMPLATE_DIR`, `DEFAULT_FILE_DIR`, `DEFAULT_DATA_DIR`, `DEFAULT_CONF_DIR`.
---
# PART 5: RUNTIME SYSTEM — SETUP SCRIPTS, ENTRYPOINT, INIT.D
## Build-time setup scripts (`rootfs/root/docker/setup/`)
Run in order inside the build stage:
| Script | Role |
|--------|------|
| `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).
+3
View File
@@ -0,0 +1,3 @@
# gitea
Read `AI.md` and `IDEA.md` before acting on this project.
+245
View File
@@ -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 <docker-admin@casjaysdev.pro>"
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" ]
+16
View File
@@ -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.
+13
View File
@@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2026 casjay <git-admin@casjaysdev.pro>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
1. You just DO WHAT THE FUCK YOU WANT TO.
+294
View File
@@ -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@<FORGEJO_SERVER>` | 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 <your-api-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 <registration-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)
+307
View File
@@ -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 <ip>: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/<pid>` 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/<pid>/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 <any image> ...` — 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.
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605241158-git
# @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
# @@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
# - - - - - - - - - - - - - - - - - - - - - - - - -
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605241158-git
# @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
# @@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
# - - - - - - - - - - - - - - - - - - - - - - - - -
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605241158-git
# @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
# @@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
# - - - - - - - - - - - - - - - - - - - - - - - - -
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202606041215-git
# @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
# @@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
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605241158-git
# @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
# @@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
# - - - - - - - - - - - - - - - - - - - - - - - - -
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202609030601-git
# @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
# @@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
# - - - - - - - - - - - - - - - - - - - - - - - - -
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605241158-git
# @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
# @@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
# - - - - - - - - - - - - - - - - - - - - - - - - -
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605241158-git
# @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
# @@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
# - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -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'
@@ -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'
+15
View File
@@ -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"]
}
+383
View File
@@ -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 <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
+35
View File
@@ -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
+78
View File
@@ -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
+702
View File
@@ -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 <<EOF 2>/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
+249
View File
@@ -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 <<EOF
$APPNAME $VERSION — Docker container healthcheck
Usage: $APPNAME [options]
At least one check must be configured (via env var or flag), or the script
exits 1. All configured checks must pass for the container to be healthy.
Options:
--url LIST HTTP(S) URL(s) to GET, comma-separated; ALL must
return an accepted status
(e.g. "http://localhost/health,http://localhost/ready")
--status PREFIXES Accepted status code prefixes, comma-separated
(default: "2,3" — any 2xx or 3xx; e.g. "200,204,301")
--host HOST Host for TCP port check (default: 127.0.0.1)
--port LIST TCP port(s) that must be accepting connections,
comma-separated; ALL must be reachable
(e.g. "80,443,3306")
--process LIST Process name(s) that must be running (matches the
executable name via pgrep). Comma-separated for
multiple — ALL must be present
(e.g. "tini,nginx,postfix,mariadb")
--file LIST File path(s) that must exist, comma-separated; ALL
must exist (and pass --file-max-age, if set)
--file-max-age SECONDS Each file's mtime must be within this many seconds
--timeout SECONDS Network check timeout (default: 5)
-v, --verbose Print check progress to stderr
-h, --help Show this help and exit 0
Environment variables (overridden by flags):
HEALTHCHECK_URL, HEALTHCHECK_HTTP_STATUS, HEALTHCHECK_HOST,
HEALTHCHECK_PORT, HEALTHCHECK_PROCESS, HEALTHCHECK_FILE,
HEALTHCHECK_FILE_MAX_AGE, HEALTHCHECK_TIMEOUT, HEALTHCHECK_VERBOSE
Exit codes:
0 all configured checks passed
1 at least one check failed, or no checks were configured
EOF
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Parse CLI flags (override env vars)
while [ $# -gt 0 ]; do
case "$1" in
--url) HEALTHCHECK_URL="$2"; shift 2 ;;
--url=*) HEALTHCHECK_URL="${1#*=}"; shift ;;
--status) HEALTHCHECK_HTTP_STATUS="$2"; shift 2 ;;
--status=*) HEALTHCHECK_HTTP_STATUS="${1#*=}"; shift ;;
--host) HEALTHCHECK_HOST="$2"; shift 2 ;;
--host=*) HEALTHCHECK_HOST="${1#*=}"; shift ;;
--port) HEALTHCHECK_PORT="$2"; shift 2 ;;
--port=*) HEALTHCHECK_PORT="${1#*=}"; shift ;;
--process) HEALTHCHECK_PROCESS="$2"; shift 2 ;;
--process=*) HEALTHCHECK_PROCESS="${1#*=}"; shift ;;
--file) HEALTHCHECK_FILE="$2"; shift 2 ;;
--file=*) HEALTHCHECK_FILE="${1#*=}"; shift ;;
--file-max-age) HEALTHCHECK_FILE_MAX_AGE="$2"; shift 2 ;;
--file-max-age=*) HEALTHCHECK_FILE_MAX_AGE="${1#*=}"; shift ;;
--timeout) HEALTHCHECK_TIMEOUT="$2"; shift 2 ;;
--timeout=*) HEALTHCHECK_TIMEOUT="${1#*=}"; shift ;;
-v|--verbose) HEALTHCHECK_VERBOSE=1; shift ;;
-h|--help) __usage; exit 0 ;;
--) shift; break ;;
-*) printf 'Unknown option: %s\n' "$1" >&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
+143
View File
@@ -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 <<EEOF >"/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
+174
View File
@@ -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 <<EOF >"$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
+77
View File
@@ -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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+983
View File
@@ -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 <<EOF | tee -p "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" &>/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
File diff suppressed because it is too large Load Diff
+6
View File
@@ -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