🗃️ Removed the .claude/settings.local.json 🗃️
Some checks failed
apprise / release-apprise (push) Has been cancelled

CLAUDE.md
Dockerfile
.env.scripts
.gitattributes
.gitea/workflows/docker.yaml
.gitignore
LICENSE.md
PLAN.md
README.md
rootfs/root/docker/setup/04-users.sh
rootfs/root/docker/setup/05-custom.sh
rootfs/tmp/
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/bin/pkmgr
rootfs/usr/local/etc/docker/bin/
rootfs/usr/local/etc/docker/init.d/99-apprise.sh
rootfs/usr/local/etc/docker/init.d/zz-default.sh
This commit is contained in:
casjay
2026-05-12 20:05:26 -04:00
parent ede71f9df1
commit 2c82e90b6f
19 changed files with 1720 additions and 887 deletions

View File

@@ -1,4 +1,4 @@
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202509161146-git ##@Version : 202509161146-git
# @@Author : CasjaysDev # @@Author : CasjaysDev
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro> # @@Contact : CasjaysDev <docker-admin@casjaysdev.pro>
@@ -7,54 +7,77 @@
# @@Created : Tue Sep 16 11:46:15 AM EDT 2025 # @@Created : Tue Sep 16 11:46:15 AM EDT 2025
# @@File : .env.scripts # @@File : .env.scripts
# @@Description : Variables for gen-dockerfile and buildx 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 # entrypoint Settings
DOCKER_ENTYPOINT_PORTS_WEB="${DOCKER_ENTYPOINT_PORTS_WEB}" DOCKER_ENTYPOINT_PORTS_WEB="${DOCKER_ENTYPOINT_PORTS_WEB}"
DOCKER_ENTYPOINT_PORTS_SRV="${DOCKER_ENTYPOINT_PORTS_SRV}" DOCKER_ENTYPOINT_PORTS_SRV="${DOCKER_ENTYPOINT_PORTS_SRV}"
DOCKER_ENTYPOINT_HEALTH_APPS="$DOCKER_ENTYPOINT_HEALTH_APPS" DOCKER_ENTYPOINT_HEALTH_APPS="$DOCKER_ENTYPOINT_HEALTH_APPS"
DOCKER_ENTYPOINT_HEALTH_ENDPOINTS="$DOCKER_ENTYPOINT_HEALTH_ENDPOINTS" DOCKER_ENTYPOINT_HEALTH_ENDPOINTS="$DOCKER_ENTYPOINT_HEALTH_ENDPOINTS"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Dockerfile info # Dockerfile info
ENV_DOCKERFILE="Dockerfile" ENV_DOCKERFILE="Dockerfile"
ENV_IMAGE_NAME="apprise" # ENV_REGISTRY_REPO: Registry repository/image name
ENV_REGISTRY_REPO="apprise"
ENV_USE_TEMPLATE="alpine" ENV_USE_TEMPLATE="alpine"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Maintainer info # Maintainer info
ENV_ORG_NAME="casjaysdevdocker" ENV_REGISTRY_ORG="casjaysdevdocker"
ENV_VENDOR="CasjaysDev" ENV_VENDOR="CasjaysDev"
ENV_AUTHOR="CasjaysDev" ENV_AUTHOR="CasjaysDev"
ENV_MAINTAINER="CasjaysDev <docker-admin@casjaysdev.pro>" ENV_MAINTAINER="CasjaysDev <docker-admin@casjaysdev.pro>"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# REPO info # Repository URLs (Full URLs)
# ENV_GIT_REPO_URL: Complete Git repository URL for source code
ENV_GIT_REPO_URL="https://github.com/casjaysdevdocker/apprise" ENV_GIT_REPO_URL="https://github.com/casjaysdevdocker/apprise"
ENV_REGISTRY_URL="docker.io" # ENV_REGISTRY_URL: Registry provider base URL (for example https://docker.io)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ENV_REGISTRY_URL="https://docker.io"
# Push image info # - - - - - - - - - - - - - - - - - - - - - - - - -
ENV_IMAGE_PUSH="casjaysdevdocker/apprise" # Push Configuration
# ENV_REGISTRY_PUSH: Complete push destination derived from registry/org/repo
ENV_REGISTRY_PUSH="casjaysdevdocker/apprise"
# ENV_IMAGE_TAG: Default tag for the image
ENV_IMAGE_TAG="latest" ENV_IMAGE_TAG="latest"
# ENV_ADD_TAGS: Additional tags, comma-separated (USE_DATE = auto date tag)
ENV_ADD_TAGS="" ENV_ADD_TAGS=""
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Additional push destinations (if needed)
ENV_ADD_IMAGE_PUSH="" ENV_ADD_IMAGE_PUSH=""
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Pull image info # Pull Configuration
# ENV_PULL_URL: Source image to pull from (base image)
ENV_PULL_URL="casjaysdev/alpine" ENV_PULL_URL="casjaysdev/alpine"
# ENV_DISTRO_TAG: Tag for the pull source image
ENV_DISTRO_TAG="${IMAGE_VERSION}" ENV_DISTRO_TAG="${IMAGE_VERSION}"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Env # Env
SERVICE_PORT="80" SERVICE_PORT="8000"
EXPOSE_PORTS="80" EXPOSE_PORTS=""
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# IF using a lanuage such as go, php, rust, ruby, etc set the version here.
LANG_VERSION=""
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Versions # Versions
PHP_VERSION="system" PHP_VERSION="none"
NODE_VERSION="system" NODE_VERSION="system"
NODE_MANAGER="system" NODE_MANAGER="system"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Default directories # Default directories
WWW_ROOT_DIR="/usr/local/share/httpd/default" WWW_ROOT_DIR="/usr/local/share/apprise-api/webapp"
DEFAULT_FILE_DIR="/usr/local/share/template-files" DEFAULT_FILE_DIR="/usr/local/share/template-files"
DEFAULT_DATA_DIR="/usr/local/share/template-files/data" DEFAULT_DATA_DIR="/usr/local/share/template-files/data"
DEFAULT_CONF_DIR="/usr/local/share/template-files/config" DEFAULT_CONF_DIR="/usr/local/share/template-files/config"
DEFAULT_TEMPLATE_DIR="/usr/local/share/template-files/defaults" DEFAULT_TEMPLATE_DIR="/usr/local/share/template-files/defaults"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
ENV_PACKAGES="" ENV_PACKAGES="bash tini curl wget git tar gzip tzdata ca-certificates pwgen nginx python3 py3-pip py3-setuptools py3-wheel py3-django py3-gunicorn py3-gevent py3-cryptography py3-requests py3-yaml py3-paho-mqtt py3-aiodns py3-prometheus-client py3-charset-normalizer py3-markdown py3-six py3-django-prometheus py3-zope-event py3-zope-interface"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# ex: ts=2 sw=2 et filetype=sh
# - - - - - - - - - - - - - - - - - - - - - - - - -

2
.gitattributes vendored
View File

@@ -1,4 +1,4 @@
# Template generated on Thu Sep 4 10:41:50 PM EDT 2025 from https://github.com/alexkaratarakis/gitattributes" # 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 # Common settings that generally should always be used with your language specific settings
# Auto detect text files and perform LF normalization # Auto detect text files and perform LF normalization
* text=auto * text=auto

View File

@@ -1,9 +1,9 @@
name: release-tag name: apprise
on: push on: push
jobs: jobs:
release-image: release-apprise:
runs-on: act_runner runs-on: act_runner
container: container:
image: catthehacker/ubuntu:act-latest image: catthehacker/ubuntu:act-latest
@@ -19,13 +19,20 @@ jobs:
- name: Get Meta - name: Get Meta
id: meta id: meta
run: | run: |
echo DATE_TAG=$(date +'%y%m') >> $GITHUB_OUTPUT repo_version="$(git describe --tags --always)"
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT repo_version="${repo_version#v}"
echo DOCKER_ORG=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $1}') >> $GITHUB_OUTPUT docker_org="${GITHUB_REPOSITORY%%/*}"
echo DOCKER_TAG=$([ -n "$DOCKER_TAG" ] && echo ${DOCKER_TAG} || echo "latest") >> $GITHUB_OUTPUT repo_name="${GITHUB_REPOSITORY#*/}"
echo DOCKER_HUB=$([ -n "$DOCKER_HUB" ] && echo ${DOCKER_HUB} || echo "docker.io") >> $GITHUB_OUTPUT repo_name="${repo_name#docker-}"
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}' | sed 's|^docker-||g') >> $GITHUB_OUTPUT docker_tag="${DOCKER_TAG:-latest}"
echo "$DOCKER_HUB/$DOCKER_ORG/$REPO_NAME:$DOCKER_TAG" 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 - name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
@@ -46,7 +53,16 @@ jobs:
linux/amd64 linux/amd64
linux/arm64 linux/arm64
push: true push: true
tags: | # replace it with your local IP and tags 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.DATE_TAG }}
${{ steps.meta.outputs.DOCKER_HUB }}/${{ steps.meta.outputs.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.DOCKER_TAG }} ${{ steps.meta.outputs.DOCKER_HUB }}/${{ steps.meta.outputs.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.DOCKER_TAG }}

20
.gitignore vendored
View File

@@ -1,7 +1,10 @@
# gitignore created on 05/22/25 at 21:00 # gitignore created on 05/05/26 at 14:38
# Disable reminder in prompt # Disable reminder in prompt
ignoredirmessage ignoredirmessage
# ignore .build_failed files
**/.build_failed*
# OS generated files # OS generated files
### Linux ### ### Linux ###
*~ *~
@@ -99,17 +102,4 @@ $RECYCLE.BIN/
**/*.rewrite.sh **/*.rewrite.sh
**/*.refactor.sh **/*.refactor.sh
# ignore dotenv files rootfs/tmp/apprise-src/
.env
# Ignore the file: app.env
app.env
# Ignore the file: compose.default.yaml
compose.default.yaml
# ignore the default dotenv file
default.env
# Exclude compose.yaml just in case it has sensitive data
compose.yaml

395
CLAUDE.md Normal file
View File

@@ -0,0 +1,395 @@
# casjaysdevdocker repo template spec
This file is the **canonical spec** for what a properly-set-up `casjaysdevdocker/<repo>` container repo looks like. It is the source of truth for migrations, audits, and new repos.
- Untracked: this file lives at the repos root, not in any individual repo's git tree.
- Per-repo: each repo gets `<repo>/CLAUDE.md` (a copy of this file) and `<repo>/PLAN.md` (the concrete plan for that specific service stack), both committed to the repo.
- Authority: `~/.claude/CLAUDE.md` (52 numbered global rules) is the base. This file extends it for the docker-image work. Existing repo files are *input*, never authoritative — the repos are mostly unfinished.
---
## 1. What a repo is
Each repo builds **one self-contained Docker image** for the service named after the repo. The image:
- Targets `linux/amd64` and `linux/arm64` via `buildx`.
- Boots via `tini``entrypoint.sh` → init.d service scripts → long-running service.
- Stores user-modifiable config under `/config/<svc>` (volume).
- Stores runtime data, logs, DB files under `/data` (volume).
- Ships a single, **highly-optimized** config in `rootfs/tmp/etc/<svc>/` that replaces the distro defaults at build time.
- Provides sane defaults: first-run with no env vars works for the common case.
App-style repos (ampache, wordpress, navidrome, …) ship the **whole stack** the app needs: the app itself at `/usr/local/share/<app>`, a webserver (apache or nginx) to serve it, php-fpm if PHP-based, and a database (mariadb/postgres) if the app needs one. DB-server repos (mariadb, postgres, mongodb, …) ship the DB **plus** apache + php-fpm + the canonical web admin UI (phpmyadmin for mariadb/mysql; pgAdmin/phpPgAdmin for postgres; mongo-express for mongodb; etc.).
Inferred intent from repo name examples:
| Repo | What it is |
|-----------------|-------------------------------------------------------------------------------------------------------------------------|
| `nginx` | nginx webserver; PHP-FPM upstream on `:9000`; CGI/Lua/etc via `nginx-mod-*` packages |
| `apache`/`httpd`| apache2 webserver + php-fpm; mod_rewrite/proxy/ssl/etc |
| `cherokee` | cherokee webserver with **CGI handlers for Ruby, Perl, Python** + **full PHP support** |
| `lighttpd` | lighttpd webserver + php-fpm/cgi |
| `caddy` | Caddy webserver (reverse-proxy/auto-TLS) |
| `traefik` | Traefik edge router |
| `mariadb` | mariadb-server + apache + php-fpm + **phpmyadmin** at `/usr/local/share/phpmyadmin` |
| `mysql` | mysql + apache + php-fpm + phpmyadmin |
| `postgres` | postgresql + apache + php-fpm + **phpPgAdmin/pgAdmin** at `/usr/local/share/phppgadmin` |
| `mongodb` | mongodb + apache + php-fpm + mongo-express or comparable web UI |
| `couchdb` | couchdb (built-in Fauxton UI) |
| `redis`/`valkey`| key-value store; CLI tools |
| `ampache` | apache + php-fpm + mariadb + ampache app at `/usr/local/share/ampache`; serves the music UI on `:80` |
| `wordpress` | apache + php-fpm + mariadb + wordpress at `/usr/local/share/wordpress` |
| `nextcloud` | apache + php-fpm + mariadb + nextcloud at `/usr/local/share/nextcloud` |
| `gitea`/`forgejo`| the binary, sqlite by default |
| `bind` | bind9 DNS server |
| `tor` | tor daemon |
| `ssl-ca` | a self-signed CA + `openssl`/`certbot` tooling |
**Always cross-check intent and packages against:**
- **Distro docs for file paths**: Alpine docs for `/etc/<svc>` layout (and `pkgs.alpinelinux.org` for available package names); Alma/Rocky docs for `/etc/httpd` (rhel-family path).
- **Project docs for config content**: `nginx.org` for `nginx.conf` directives, `httpd.apache.org` for `httpd.conf`, `mariadb.com` for `my.cnf`, etc.
- **Upstream Docker images**: when available, `docker pull <upstream>/<image>:latest` and inspect (`docker history`, `docker run --rm -it ... sh`) to confirm canonical install paths and config locations. **Always `docker rmi` to clean up after.**
Never invent a package name or a config option. `WebFetch`/`WebSearch`/`docker pull` to verify.
---
## 2. File inventory (every repo)
```
<repo>/
├── CLAUDE.md # copy of TEMPLATE.md, committed; agent reads this when working in this repo
├── PLAN.md # this repo's concrete plan: packages, configs, init.d behavior, success criteria
├── README.md # user-facing install/run docs
├── LICENSE.md # MIT (per global rule 32) or per-repo as appropriate
├── Dockerfile # multi-stage, alpine-based by default (rhel for systemd-only repos)
├── .env.scripts # buildx wrapper config (registry, push, pull, packages list)
├── .dockerignore
├── .gitattributes
├── .gitignore
├── .gitea/workflows/docker.yaml # gitea CI workflow
├── Jenkinsfile # optional, if the repo had one
└── rootfs/ # everything under here is COPYed to / in the build
├── usr/local/bin/
│ ├── entrypoint.sh # SHARED template, only the description + CONTAINER_NAME line are repo-specific
│ └── pkmgr # SHARED template (apt/dnf/apk auto-detect wrapper)
├── usr/local/etc/docker/
│ ├── functions/entrypoint.sh # framework functions sourced by entrypoint.sh and 99-<svc>.sh
│ └── init.d/
│ └── 99-<svc>.sh # PER-REPO; defines SERVICE_NAME, ETC_DIR, CONF_DIR, EXEC_CMD_BIN/ARGS, hooks
├── usr/local/share/
│ ├── <app>/ # for app-style repos: the actual app code (e.g., ampache, wordpress)
│ ├── phpmyadmin/ # for mariadb/mysql repos
│ ├── phppgadmin/ # for postgres repos
│ ├── httpd/default/ # default webroot (used when no app)
│ └── template-files/
│ ├── config/ # templates copied to /config/<svc>/ on first run by __initialize_config_dir
│ ├── data/ # templates copied to /data/<svc>/ on first run
│ └── defaults/ # default fallbacks
├── tmp/etc/<svc>/ # PER-REPO optimized configs; copied to /etc/<svc>/ at build time (see §4)
├── tmp/bin/ # optional; auto-installed to /usr/local/bin/ at build time
├── tmp/var/ # optional; auto-installed to /var/ at build time
└── root/docker/setup/ # PER-REPO build-time scripts (see §3)
├── 00-init.sh
├── 01-system.sh
├── 02-packages.sh
├── 03-files.sh
├── 04-users.sh
├── 05-custom.sh # ← service-specific config wipe-and-replace lives here
├── 06-post.sh
└── 07-cleanup.sh
```
**SHARED vs PER-REPO** (load-bearing distinction — getting this wrong breaks repos):
- **SHARED** (safe to overwrite from the upstream template): `rootfs/usr/local/bin/entrypoint.sh` (only the description + `CONTAINER_NAME="<svc>"` change per repo), `rootfs/usr/local/bin/pkmgr`, `rootfs/usr/local/etc/docker/functions/entrypoint.sh`.
- **PER-REPO** (never overwrite from a template — read first; preserve service-specific logic): every script under `rootfs/root/docker/setup/`, `rootfs/usr/local/etc/docker/init.d/*`, everything under `rootfs/tmp/`, everything under `rootfs/usr/local/share/<app>/`.
---
## 3. Build-time setup script flow
`Dockerfile` runs `00-init.sh` → … → `07-cleanup.sh` interleaved with package install. Order and contract:
| Script | When it runs | What it does |
|------------------|-----------------------------------------------|----------------------------------------------------------------------------------------------------|
| `00-init.sh` | Right after `mkdir`s before anything else | Sanity setup; usually empty |
| `01-system.sh` | After apk repos are configured | System-level tweaks (extra repos, timezone, non-package OS config) |
| `02-packages.sh` | After `pkmgr install $PACK_LIST` | Per-service post-install tweaks (e.g., compile a module, install a pip/npm package, fetch the app) |
| `03-files.sh` | After packages are installed | **Auto-installs `rootfs/tmp/{bin,var,etc,data}/*`** into `/usr/local/bin/`, `/var/`, `/etc/`, and the `template-files/` staging dirs. Most repos use the canonical version verbatim. |
| `04-users.sh` | After files are placed | Create system users/groups the service needs (e.g., `nginx`, `mysql`, `apache`) |
| `05-custom.sh` | After users | **Service-specific config wipe-and-replace** (see §4). Also: clone wwwroot templates, fetch app source, etc. |
| `06-post.sh` | After custom | Late tweaks (permissions, symlinks) |
| `07-cleanup.sh` | Last | Per-service cache cleanup beyond the Dockerfile's generic cleanup |
The Dockerfile's own RUN steps already do generic cleanup (`pkmgr clean`, `rm -Rf /usr/share/doc/*`, etc.); the per-script `07-cleanup.sh` only handles service-specific files.
---
## 4. The wipe-and-replace config flow
The most important pattern in this template. Goal: the running container's `/etc/<svc>/` contains **only** our optimized config, never distro defaults.
**Build time:**
1. Service package install (e.g., `apk add nginx`) creates distro defaults under `/etc/<svc>/` (e.g., `/etc/nginx/{nginx.conf, conf.d/, modules-enabled/, http.d/, mime.types, …}`).
2. `03-files.sh` copies `rootfs/tmp/etc/<svc>/*``/etc/<svc>/*` (overlay only) **and** stages a copy at `/usr/local/share/template-files/config/<svc>/` for runtime seeding.
3. `05-custom.sh` performs the **wipe-and-replace** (the canonical idiom):
```sh
if [ -d "/tmp/etc/<svc>" ]; then
# preserve distro-shipped files we need (e.g., mime.types when not in tmp/etc/)
# then wipe defaults
rm -Rf "/etc/<svc>"/*
cp -Rf "/tmp/etc/<svc>/." "/etc/<svc>/"
fi
```
For services that auto-discover sub-confs, our `<svc>.conf` ends with an **optional** include like `include /config/<svc>/vhosts.d/*.conf;` (nginx) or `IncludeOptional /config/<svc>/conf.d/*.conf` (apache) so an empty include dir doesn't crash startup.
**Runtime (entrypoint + 99-<svc>.sh):**
1. `entrypoint.sh` calls `__initialize_default_templates` / `__initialize_config_dir` / `__initialize_data_dir` which copy `template-files/{defaults,config,data}/<svc>/*``/config/<svc>/` and `/data/<svc>/` **only when those target dirs are not already initialized** (via `/config/.docker_has_run` and `/data/.docker_has_run` markers).
2. `99-<svc>.sh` calls `__initialize_system_etc "$CONF_DIR"` which symlinks/copies the user-editable `/config/<svc>/<file>` → the service's expected runtime path (`/etc/<svc>/<file>` for system services; `/usr/local/share/<app>/config/<file>` for app-stack repos).
3. `99-<svc>.sh` also ensures runtime dirs exist: `vhosts.d/`, `conf.d/`, `ssl/`, `secure/auth/`, log dirs under `/data/logs/<svc>/`.
4. Service starts pointing at `/etc/<svc>/<svc>.conf` (or equivalent), which transitively reads from `/config/<svc>/`.
Net effect: end users edit files under `/config/<svc>/` (volume); the service picks them up; rebuilds and restarts don't trample user changes.
**Anti-patterns:**
- Letting distro defaults survive into the running image (didn't wipe `/etc/<svc>/*`).
- Hardcoding paths in our config that point inside `/etc/<svc>/` instead of `/config/<svc>/` for things users should customize.
- Copying `template-files/config/<svc>/*` into `/config/<svc>/` unconditionally on every container start (clobbers user edits) — always gate with the init markers.
- Using a non-optional `include` for `vhosts.d/` (kills the service when the dir is empty on first run).
---
## 5. Dockerfile structure
Multi-stage. Build stage installs packages and runs setup scripts; final stage is `FROM scratch` and `COPY --from=build /. /` for a minimal final image. Exception: containers needing systemd as PID 1 (e.g., blueonyx) are single-stage with `CMD ["/sbin/init"]`.
Required ARGs in the **header** (preserve per-repo values during migration):
```dockerfile
ARG IMAGE_NAME="<repo>"
ARG PHP_SERVER="<repo>" # often same as IMAGE_NAME
ARG BUILD_DATE="<YYYYMMDDHHMM>" # auto-bumped by gen-dockerfile / CI
ARG LANGUAGE="en_US.UTF-8"
ARG TIMEZONE="America/New_York"
ARG WWW_ROOT_DIR="/usr/local/share/httpd/default"
ARG DEFAULT_FILE_DIR="/usr/local/share/template-files"
ARG DEFAULT_DATA_DIR="/usr/local/share/template-files/data"
ARG DEFAULT_CONF_DIR="/usr/local/share/template-files/config"
ARG DEFAULT_TEMPLATE_DIR="/usr/local/share/template-files/defaults"
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="<primary port for healthcheck/EXPOSE>"
ARG EXPOSE_PORTS="<additional space-separated ports>"
ARG PHP_VERSION="<php82|php83|php84|system|none>"
ARG NODE_VERSION="system"
ARG NODE_MANAGER="system"
ARG IMAGE_REPO="<org>/<repo>"
ARG IMAGE_VERSION="latest"
ARG CONTAINER_VERSION="" # "USE_DATE" if buildx should auto-add a date tag
ARG PULL_URL="casjaysdev/alpine" # or "alpine", "almalinux/10-init", etc.
ARG DISTRO_VERSION="${IMAGE_VERSION}"
ARG BUILD_VERSION="${BUILD_DATE}"
```
`PACK_LIST` lives in the build stage and is the **single most repo-specific value** — it must be complete and accurate for the service stack:
```dockerfile
ARG PACK_LIST="<all packages this stack needs, space-separated, trailing space>"
```
Build-stage RUN order (alpine):
1. `COPY ./rootfs/. /` (early, so setup scripts and `/tmp/etc/` are available before package install)
2. `RUN pkmgr update; pkmgr install bash`
3. `RUN` install bash + symlink `/bin/sh``bash` (Alpine ships busybox sh)
4. `ENV SHELL="/bin/bash"; SHELL [ "/bin/bash", "-c" ]`
5. `COPY --from=gosu /usr/local/bin/gosu /usr/local/bin/gosu`
6. `RUN` initialize: `mkdir -p` template dirs + `00-init.sh`
7. `RUN` system: rewrite `/etc/apk/repositories` for the right distro version; `apk update && apk upgrade` + `01-system.sh`
8. `RUN` pre-package commands (usually empty)
9. `RUN` install packages from `${PACK_LIST}` via `pkmgr install`
10. `RUN` `02-packages.sh`
11. `COPY ./Dockerfile /root/docker/Dockerfile` (so the image carries its own build recipe)
12. `RUN` updating system files: timezone, nsswitch, php symlinks, .bashrc, `03-files.sh`
13. `RUN` Custom Settings (usually empty placeholder)
14. `RUN` users + `04-users.sh`
15. `RUN` user init (placeholder)
16. `RUN` OS Settings (placeholder)
17. `RUN` Custom Applications (placeholder)
18. `RUN` `05-custom.sh`
19. `RUN` final commands + `06-post.sh`
20. `RUN` cleanup (generic) + `07-cleanup.sh`
21. `RUN echo "Init done"`
Final stage (`FROM scratch`):
- ARGs re-declared (scratch needs them); ENVs set; standard LABEL block (URLs, vendor, revision = `${GIT_COMMIT}`); `COPY --from=build /. /`; `VOLUME [ "/config","/data" ]`; `EXPOSE ${SERVICE_PORT} ${ENV_PORTS}`; `STOPSIGNAL SIGRTMIN+3`; `ENTRYPOINT [ "tini", "-p", "SIGTERM", "--", "/usr/local/bin/entrypoint.sh" ]`; `HEALTHCHECK ... CMD [ "/usr/local/bin/entrypoint.sh", "healthcheck" ]`.
Distro variants:
- **alpine** (default for ~all repos)
- **rhel/almalinux** (for repos that need RHEL packages or systemd) — generated via `gen-dockerfile --dir <tmp> rhel`. Almost all are still single-stage with `CMD ["/sbin/init"]`.
Generic Dockerfile bug fixes to apply when migrating any repo (these are upstream gen-dockerfile bugs):
- Missing space before `]`: `[ "$SH_CMD" != "/bin/sh"]``[ "$SH_CMD" != "/bin/sh" ]`
- Blank line inside the "Creating and editing system files" RUN block (after `$SHELL_OPTS; \`) — remove the blank line; line continuation is broken otherwise.
- Unindented `echo ""` in the "Custom Settings" and "Custom Applications" RUN blocks — re-indent to ` echo ""`.
---
## 6. .env.scripts fields
The buildx wrapper (`/usr/local/bin/buildx`) reads `.env.scripts`. Required fields (current names — older field names are deprecated):
```sh
ENV_DOCKERFILE="Dockerfile"
ENV_REGISTRY_REPO="<repo>" # was ENV_IMAGE_NAME
ENV_USE_TEMPLATE="alpine" # or "almalinux", "debian", "ubuntu"
ENV_REGISTRY_ORG="<casjaysdevdocker | casjaysdev>" # was ENV_ORG_NAME; must match the org in ENV_REGISTRY_PUSH
ENV_VENDOR="CasjaysDev"
ENV_AUTHOR="CasjaysDev"
ENV_MAINTAINER="CasjaysDev <docker-admin@casjaysdev.pro>"
ENV_GIT_REPO_URL="https://github.com/<org>/<repo>"
ENV_REGISTRY_URL="https://docker.io" # full URL, not bare "docker.io"
ENV_REGISTRY_PUSH="<org>/<repo>" # was ENV_IMAGE_PUSH
ENV_IMAGE_TAG="latest"
ENV_ADD_TAGS="" # "USE_DATE" to auto-add YYMM tag
ENV_ADD_IMAGE_PUSH=""
ENV_PULL_URL="<base image>" # e.g. "casjaysdev/alpine", "alpine", "almalinux/10-init"
ENV_DISTRO_TAG="${IMAGE_VERSION}"
ENV_PLATFORMS="linux/amd64,linux/arm64" # only emit when overriding the default both-archs
SERVICE_PORT="<primary>"
EXPOSE_PORTS="<extra ports space-separated>"
LANG_VERSION=""
PHP_VERSION="<system|php82|php83|php84|none>"
NODE_VERSION="system"
NODE_MANAGER="system"
WWW_ROOT_DIR="/usr/local/share/httpd/default"
DEFAULT_FILE_DIR="/usr/local/share/template-files"
DEFAULT_DATA_DIR="/usr/local/share/template-files/data"
DEFAULT_CONF_DIR="/usr/local/share/template-files/config"
DEFAULT_TEMPLATE_DIR="/usr/local/share/template-files/defaults"
ENV_PACKAGES="<single-space-separated package list — must mirror PACK_LIST in Dockerfile>"
```
`ENV_PACKAGES` and Dockerfile `PACK_LIST` must stay in sync. Single-space separation, no double spaces.
---
## 7. init.d/99-<svc>.sh contract
Each repo's primary init.d script (named `99-<svc>.sh` for late ordering, or `09-<svc>.sh`/etc. when ordering matters relative to other init.d entries — e.g., php-fpm starts before nginx) defines repo-specific state and calls framework functions defined in `rootfs/usr/local/etc/docker/functions/entrypoint.sh`. Required variable assignments at the top (use the nginx 99-nginx.sh as the reference structure):
```sh
SERVICE_NAME="<svc>"
DATA_DIR="/data/<svc>"
CONF_DIR="/config/<svc>"
ETC_DIR="/etc/<svc>" # or /usr/local/share/<app>/config for app-stack repos
TMP_DIR="/tmp/<svc>"
RUN_DIR="/run/<svc>"
LOG_DIR="/data/logs/<svc>"
SERVICE_PORT="<primary>"
SERVICE_USER="<svc>" # the daemon's run-as user
SERVICE_GROUP="<svc>"
EXEC_CMD_BIN='<binary>' # e.g., 'nginx', 'mysqld', 'httpd'
EXEC_CMD_ARGS='<args>' # e.g., '-c $ETC_DIR/nginx.conf'
IS_WEB_SERVER="yes|no"
IS_DATABASE_SERVICE="yes|no"
USES_DATABASE_SERVICE="yes|no"
DATABASE_SERVICE_TYPE="<sqlite|redis|postgres|mariadb|mysql|couchdb|mongodb|supabase|custom>"
ADDITIONAL_CONFIG_DIRS="" # extra /config subdirs to seed/symlink
APPLICATION_FILES="..."
APPLICATION_DIRS="$ETC_DIR $CONF_DIR $LOG_DIR $TMP_DIR $RUN_DIR $VAR_DIR"
```
Repo-customizable hooks (override the `_local` variants — the framework calls them at the right time):
- `__run_precopy_local` — before any /config copy
- `__execute_prerun_local` — pre-execution setup
- `__run_pre_execute_checks_local` — final preflight checks
- `__update_conf_files_local` — token replacement in `/etc/<svc>/*` (use `__replace`/`__find_replace`)
- `__pre_execute_local` — last-mile actions
- `__post_execute_local` — actions in background after service start
- `__pre_message_local` — pre-launch banner
- `__update_ssl_conf_local` — repo-specific SSL handling
---
## 8. Per-repo CLAUDE.md and PLAN.md
When working on a repo, the agent should:
1. `cd <repo>`
2. Read `CLAUDE.md` (this file's content, copied) for the spec.
3. Read `PLAN.md` for repo-specific decisions.
4. Read every existing file in the repo before editing it (rule 8).
5. When uncertain about a package/path/option, `WebFetch` the relevant docs (distro for paths, project for content). Never invent.
`PLAN.md` template (commit this in each repo):
```markdown
# <repo> migration plan
## Service intent
<one paragraph: what this image provides, who runs it, primary user-facing port>
## Service stack
- <component 1>: <package(s)>; canonical config at <path>
- <component 2>: ...
## Packages (PACK_LIST / ENV_PACKAGES)
<list, sourced from pkgs.alpinelinux.org, with one-line justification each>
## Configs to ship in rootfs/tmp/etc/
- /etc/<svc>/<file>: <source: which project's docs>; <key tunings applied>
- ...
## /config/<svc>/ layout (user-editable)
- <file> -> symlinked to <runtime path>
- vhosts.d/ -> include /config/<svc>/vhosts.d/*.conf (optional)
## init.d/99-<svc>.sh
- SERVICE_NAME, EXEC_CMD_BIN, EXEC_CMD_ARGS
- IS_WEB_SERVER / IS_DATABASE_SERVICE / USES_DATABASE_SERVICE
- Repo-specific hooks needed: __update_conf_files_local (replace XYZ), ...
## 05-custom.sh additions
- Wipe /etc/<svc>/* and copy from /tmp/etc/<svc>/.
- Fetch <app source> if not present
- Other service-specific install steps
## Verification (success criteria)
- buildx run Dockerfile succeeds for linux/amd64 + linux/arm64
- docker run -d -p <port>:<port> ... starts cleanly; logs show no errors
- curl -fsS http://localhost:<port>/<healthpath> returns the expected response
- /config/<svc>/ is seeded on first run; editing a file there changes service behavior on restart
- (DB repos) connecting with the right CLI client succeeds
- (optional) compared against upstream image (`docker pull <upstream>:latest && docker history <upstream>:latest`); upstream image deleted (`docker rmi`) after verification
```
---
## 9. Migration workflow per repo
1. Create `<repo>/CLAUDE.md` = copy of this `TEMPLATE.md`.
2. Read existing files; assemble the PLAN.md.
3. **Read each file before changing it** (no batch templating). Apply only the changes that PLAN.md identifies.
4. `cd <repo> && rm -f .build_failed && buildx run Dockerfile` — fix any build error before moving on.
5. Smoke-test: `docker run --rm -d --name test-<svc> -p <port>:<port> <org>/<repo>:latest`; wait for healthcheck; `curl` the health/main endpoint; `docker exec` and inspect `/config/<svc>/`; stop and remove.
6. Commit `CLAUDE.md` and `PLAN.md` to the repo (do NOT commit code changes unless the user has asked — global rule about commits applies).
7. Move to next repo with **no carry-over**: spawn a fresh subagent; do not load prior repo's context.
---
## 10. Anti-patterns (never do)
- Overwriting `rootfs/root/docker/setup/*.sh` from a generic template — these are per-repo.
- Overwriting `rootfs/usr/local/etc/docker/init.d/*.sh` from a template — per-repo.
- Overwriting `rootfs/tmp/etc/<svc>/*` — per-repo.
- Overwriting `rootfs/usr/local/share/<app>/*` — per-repo (the actual application code).
- Inventing package names, config keys, or service paths — verify with distro/project docs.
- Hardcoding secrets, tokens, internal hostnames (rule 39: every repo is public).
- Skipping the wipe-and-replace step (leaves distro defaults active alongside our config).
- Using a non-optional include for vhosts.d / conf.d (empty dir crashes the service).
- Calling a repo "done" because `buildx` was green; "done" requires the smoke-test passing.

View File

@@ -1,10 +1,10 @@
# Docker image for apprise using the alpine template # Docker image for apprise using the alpine template
ARG IMAGE_NAME="apprise" ARG IMAGE_NAME="apprise"
ARG PHP_SERVER="apprise" ARG PHP_SERVER="apprise"
ARG BUILD_DATE="202509161146" ARG BUILD_DATE="202605091200"
ARG LANGUAGE="en_US.UTF-8" ARG LANGUAGE="en_US.UTF-8"
ARG TIMEZONE="America/New_York" ARG TIMEZONE="America/New_York"
ARG WWW_ROOT_DIR="/usr/local/share/httpd/default" ARG WWW_ROOT_DIR="/usr/local/share/apprise-api/webapp"
ARG DEFAULT_FILE_DIR="/usr/local/share/template-files" ARG DEFAULT_FILE_DIR="/usr/local/share/template-files"
ARG DEFAULT_DATA_DIR="/usr/local/share/template-files/data" ARG DEFAULT_DATA_DIR="/usr/local/share/template-files/data"
ARG DEFAULT_CONF_DIR="/usr/local/share/template-files/config" ARG DEFAULT_CONF_DIR="/usr/local/share/template-files/config"
@@ -14,15 +14,15 @@ ARG PATH="/usr/local/etc/docker/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/us
ARG USER="root" ARG USER="root"
ARG SHELL_OPTS="set -e -o pipefail" ARG SHELL_OPTS="set -e -o pipefail"
ARG SERVICE_PORT="80" ARG SERVICE_PORT="8000"
ARG EXPOSE_PORTS="80" ARG EXPOSE_PORTS=""
ARG PHP_VERSION="system" ARG PHP_VERSION="none"
ARG NODE_VERSION="system" ARG NODE_VERSION="system"
ARG NODE_MANAGER="system" ARG NODE_MANAGER="system"
ARG IMAGE_REPO="casjaysdevdocker/apprise" ARG IMAGE_REPO="casjaysdevdocker/apprise"
ARG IMAGE_VERSION="latest" ARG IMAGE_VERSION="latest"
ARG CONTAINER_VERSION="" ARG CONTAINER_VERSION="USE_DATE"
ARG PULL_URL="casjaysdev/alpine" ARG PULL_URL="casjaysdev/alpine"
ARG DISTRO_VERSION="${IMAGE_VERSION}" ARG DISTRO_VERSION="${IMAGE_VERSION}"
@@ -54,7 +54,7 @@ ARG PHP_SERVER
ARG SHELL_OPTS ARG SHELL_OPTS
ARG PATH ARG PATH
ARG PACK_LIST=" " ARG PACK_LIST="bash tini curl wget git tar gzip tzdata ca-certificates pwgen nginx python3 py3-pip py3-setuptools py3-wheel py3-django py3-gunicorn py3-gevent py3-cryptography py3-requests py3-yaml py3-paho-mqtt py3-aiodns py3-prometheus-client py3-charset-normalizer py3-markdown py3-six py3-django-prometheus py3-zope-event py3-zope-interface "
ENV ENV=~/.profile ENV ENV=~/.profile
ENV SHELL="/bin/sh" ENV SHELL="/bin/sh"
@@ -68,7 +68,7 @@ ENV HOSTNAME="casjaysdevdocker-apprise"
USER ${USER} USER ${USER}
WORKDIR /root WORKDIR /root
COPY ./rootfs/usr/local/bin/. /usr/local/bin/ COPY ./rootfs/. /
RUN set -e; \ RUN set -e; \
echo "Updating the system and ensuring bash is installed"; \ echo "Updating the system and ensuring bash is installed"; \
@@ -76,7 +76,13 @@ RUN set -e; \
RUN set -e; \ RUN set -e; \
echo "Setting up prerequisites"; \ echo "Setting up prerequisites"; \
true apk --no-cache add bash; \
SH_CMD="$(which sh 2>/dev/null||command -v sh 2>/dev/null)"; \
BASH_CMD="$(which bash 2>/dev/null||command -v bash 2>/dev/null)"; \
[ -x "$BASH_CMD" ] && symlink "$BASH_CMD" "/bin/sh" || true; \
[ -x "$BASH_CMD" ] && symlink "$BASH_CMD" "/usr/bin/sh" || true; \
[ -x "$BASH_CMD" ] && [ "$SH_CMD" != "/bin/sh" ] && symlink "$BASH_CMD" "$SH_CMD" || true; \
[ -n "$BASH_CMD" ] && sed -i 's|root:x:.*|root:x:0:0:root:/root:'$BASH_CMD'|g' "/etc/passwd" || true
ENV SHELL="/bin/bash" ENV SHELL="/bin/bash"
SHELL [ "/bin/bash", "-c" ] SHELL [ "/bin/bash", "-c" ]
@@ -91,7 +97,12 @@ RUN echo "Initializing the system"; \
RUN echo "Creating and editing system files "; \ RUN echo "Creating and editing system files "; \
$SHELL_OPTS; \ $SHELL_OPTS; \
[ -f "/root/.profile" ] || touch "/root/.profile"; \ rm -Rf "/etc/apk/repositories"; \
[ "$DISTRO_VERSION" = "latest" ] && DISTRO_VERSION="edge";[ "$DISTRO_VERSION" = "edge" ] || DISTRO_VERSION="v${DISTRO_VERSION}"; \
echo "http://dl-cdn.alpinelinux.org/alpine/${DISTRO_VERSION}/main" >>"/etc/apk/repositories"; \
echo "http://dl-cdn.alpinelinux.org/alpine/${DISTRO_VERSION}/community" >>"/etc/apk/repositories"; \
if [ "${DISTRO_VERSION}" = "edge" ]; then echo "http://dl-cdn.alpinelinux.org/alpine/${DISTRO_VERSION}/testing" >>"/etc/apk/repositories";fi; \
apk update; apk upgrade --no-cache; \
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; \ 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 "" echo ""
@@ -109,7 +120,6 @@ RUN echo "Initializing packages before copying files to image"; \
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; \ 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 "" echo ""
COPY ./rootfs/. /
COPY ./Dockerfile /root/docker/Dockerfile COPY ./Dockerfile /root/docker/Dockerfile
RUN echo "Updating system files "; \ RUN echo "Updating system files "; \
@@ -119,7 +129,7 @@ RUN echo "Updating system files "; \
echo 'hosts: files dns' >"/etc/nsswitch.conf"; \ echo 'hosts: files dns' >"/etc/nsswitch.conf"; \
[ "$PHP_VERSION" = "system" ] && PHP_VERSION="php" || true; \ [ "$PHP_VERSION" = "system" ] && PHP_VERSION="php" || true; \
PHP_BIN="$(command -v ${PHP_VERSION} 2>/dev/null || true)"; \ PHP_BIN="$(command -v ${PHP_VERSION} 2>/dev/null || true)"; \
PHP_FPM="$(ls /usr/*bin/php*fpm* 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)"; \ 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="$(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=""; \ [ "$py_version" -gt "310" ] && pip_opts="--break-system-packages " || pip_opts=""; \
@@ -176,7 +186,7 @@ RUN echo "Deleting unneeded files"; \
rm -rf /lib/systemd/system/sockets.target.wants/*udev* || true; \ rm -rf /lib/systemd/system/sockets.target.wants/*udev* || true; \
rm -rf /lib/systemd/system/sockets.target.wants/*initctl* || 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; \ 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" && rm -f $(ls | grep -v systemd-tmpfiles-setup);fi; \ 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; \ 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 "" echo ""
@@ -193,6 +203,7 @@ ARG SERVICE_PORT
ARG EXPOSE_PORTS ARG EXPOSE_PORTS
ARG BUILD_VERSION ARG BUILD_VERSION
ARG IMAGE_VERSION ARG IMAGE_VERSION
ARG GIT_COMMIT
ARG WWW_ROOT_DIR ARG WWW_ROOT_DIR
ARG DEFAULT_FILE_DIR ARG DEFAULT_FILE_DIR
ARG DEFAULT_DATA_DIR ARG DEFAULT_DATA_DIR
@@ -219,10 +230,10 @@ LABEL org.opencontainers.image.authors="${LICENSE}"
LABEL org.opencontainers.image.created="${BUILD_DATE}" LABEL org.opencontainers.image.created="${BUILD_DATE}"
LABEL org.opencontainers.image.version="${BUILD_VERSION}" LABEL org.opencontainers.image.version="${BUILD_VERSION}"
LABEL org.opencontainers.image.schema-version="${BUILD_VERSION}" LABEL org.opencontainers.image.schema-version="${BUILD_VERSION}"
LABEL org.opencontainers.image.url="docker.io" LABEL org.opencontainers.image.url="https://docker.io/casjaysdevdocker/apprise"
LABEL org.opencontainers.image.source="docker.io" LABEL org.opencontainers.image.source="https://docker.io/casjaysdevdocker/apprise"
LABEL org.opencontainers.image.vcs-type="Git" LABEL org.opencontainers.image.vcs-type="Git"
LABEL org.opencontainers.image.revision="${BUILD_VERSION}" LABEL org.opencontainers.image.revision="${GIT_COMMIT}"
LABEL org.opencontainers.image.source="https://github.com/casjaysdevdocker/apprise" LABEL org.opencontainers.image.source="https://github.com/casjaysdevdocker/apprise"
LABEL org.opencontainers.image.documentation="https://github.com/casjaysdevdocker/apprise" LABEL org.opencontainers.image.documentation="https://github.com/casjaysdevdocker/apprise"
LABEL com.github.containers.toolbox="false" LABEL com.github.containers.toolbox="false"
@@ -252,5 +263,8 @@ VOLUME [ "/config","/data" ]
EXPOSE ${SERVICE_PORT} ${ENV_PORTS} EXPOSE ${SERVICE_PORT} ${ENV_PORTS}
ENTRYPOINT [ "tini","--","/usr/local/bin/entrypoint.sh" ] 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" ] HEALTHCHECK --start-period=10m --interval=5m --timeout=15s CMD [ "/usr/local/bin/entrypoint.sh", "healthcheck" ]

View File

@@ -1,7 +1,7 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004 Version 2, December 2004
Copyright (C) 2023 casjay <git-admin@casjaysdev.pro> Copyright (C) 2026 casjay <git-admin@casjaysdev.pro>
Everyone is permitted to copy and distribute verbatim or modified Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long copies of this license document, and changing it is allowed as long

197
PLAN.md Normal file
View File

@@ -0,0 +1,197 @@
# apprise migration plan
## Service intent
A self-hosted REST notification gateway built on the upstream `caronc/apprise-api` Django web app. Runs as a single Alpine-based Docker image bundling **nginx + gunicorn + Django + apprise** so users can POST a notification (with one or more notification URLs) and have it delivered to dozens of services (Discord, Slack, Telegram, Pushover, email, MQTT, etc.). Persistent stateful configs (`/cfg/<key>` style YAML/text profiles) live under `/config/store`. Container exposes **`:8000`**. Volumes: `/config` (apprise YAML/text config files + the canonical store), `/data` (logs, attachments, temp). Optional volumes the upstream supports (mapped under `/config` in our layout instead of separate mounts): `attachments` and `plugin paths`.
## Service stack
- Web frontend: `nginx` (Alpine), main config served from `/etc/nginx/nginx.conf` -> proxies `*` to gunicorn over `unix:/run/apprise/gunicorn.sock`. We base our nginx.conf on the upstream `apprise-api/etc/nginx.conf` (route table for `/`, `/notify`, `/notify/<key>`, `/status`, `/metrics`, `/details`, `/cfg`, `/add`, `/del`, `/get`, `/json/urls/...`, `/_/`, `/s/`, `/favicon.ico`, `/robots.txt`, catch-all). Single `server { listen 8000; }` block.
- Application: `apprise-api` Django app cloned from upstream `github.com/caronc/apprise-api` to `/usr/local/share/apprise-api/webapp/` (matches upstream's `/opt/apprise/webapp` layout but in our `/usr/local/share/<app>/` convention). Run via gunicorn.
- WSGI server: `gunicorn` with `gevent` worker class — invoked as `gunicorn -c /usr/local/share/apprise-api/webapp/gunicorn.conf.py --worker-tmp-dir /dev/shm core.wsgi`. Listens on `unix:/run/apprise/gunicorn.sock`.
- Notification engine: `apprise` (Python lib, `pip install apprise`). Pulls in 80+ notification backends (the rest of the upstream `requirements.txt` such as `paho-mqtt`, `gntp`, `cryptography`, `PGPy`, `slixmpp`, `smpplib` — Alpine has packages for some, the rest come from pip).
- Process supervisor: a small shell script `start-apprise` at `/usr/local/etc/docker/bin/start-apprise` (mirrors ampache's `start-ampache` pattern) that starts gunicorn in the background, waits for the unix socket, then `exec`s nginx in the foreground. The framework's `99-apprise.sh` invokes this single binary as its `EXEC_CMD_BIN` — the framework already handles supervision/restart.
## Packages (PACK_LIST / ENV_PACKAGES)
Verified against `pkgs.alpinelinux.org` for the `edge` branch (community + main). Each entry has a one-line justification.
System glue:
- `bash` — entrypoint and 99-* scripts are bash.
- `tini` — PID 1 init.
- `curl`, `wget` — entrypoint healthcheck + cloning fallback.
- `git` — clone the apprise-api repo at build time.
- `tzdata` — TZ awareness in nginx and Python logging.
- `ca-certificates` — TLS to outbound notification services.
- `pwgen` — random secrets/seed material if needed.
- `tar`, `gzip` — unpack archives if any (defensive).
nginx:
- `nginx` — front HTTP(S) proxy (port 8000 inside the container).
- `nginx-mod-http-headers-more` — not strictly required, dropped to keep image lean. Default `nginx` ships `mod_http_realip` etc., which is enough for our X-Forwarded-* setup.
Python runtime + Alpine-packaged Python deps (saves a lot of pip compile time and avoids needing build toolchain):
- `python3` — Python 3.x runtime.
- `py3-pip` — pip for the leftover deps from upstream `requirements.txt`.
- `py3-setuptools`, `py3-wheel` — needed for pip installs.
- `py3-django` — Django web framework (5.x in Alpine edge; upstream pins to `Django` open).
- `py3-gunicorn` — WSGI HTTP server.
- `py3-gevent` — async worker class for gunicorn (upstream uses `worker_class = "gevent"`).
- `py3-cryptography` — X.509/key handling; required by apprise + PGPy.
- `py3-requests` — HTTP client.
- `py3-yaml` — YAML config parsing (apprise YAML configs).
- `py3-paho-mqtt` — MQTT notification backend.
- `py3-aiodns` — async DNS, used by gevent.
- `py3-prometheus-client``/metrics` endpoint support.
- `py3-charset-normalizer` — requests dep, packaged.
- `py3-markdown` — apprise UI markdown rendering.
- `py3-six` — packaged transitive dep.
- `py3-django-prometheus` — Django prometheus middleware (upstream requirement).
- `py3-zope-event`, `py3-zope-interface` — gevent transitive deps (avoid pip rebuild).
Dropped from a hypothetical kitchen-sink list: `py3-uwsgi`, `uwsgi`, `uwsgi-python3` — upstream switched to gunicorn long ago; we follow upstream. No `apache2/php-fpm/mariadb` either; this is a pure Python web service.
`pip install` (in `02-packages.sh`, with `--break-system-packages`) for the deps Alpine doesn't ship:
- `apprise` (the notification library itself; the apprise-api Django app imports it at runtime).
- `PGPy` (PGP message support; not in Alpine).
- `slixmpp >= 1.10.0` (XMPP; not in Alpine).
- `smpplib` (SMPP; not in Alpine).
- `gntp` (Growl; not in Alpine).
## Configs to ship in rootfs/tmp/etc/
Wipe-and-replace at build time (per template §4). All paths under `rootfs/tmp/etc/`.
- `nginx/nginx.conf` — based on upstream `apprise-api/webapp/etc/nginx.conf`, lifted into our standard structure. Top-level: `daemon off;`, `worker_processes auto;`, `pid /run/apprise/nginx.pid;`, `error_log /data/logs/apprise/nginx-error.log;`. Inside `events { worker_connections 4096; }`. Inside `http { ... }`: include `mime.types`; `client_max_body_size 500M`; `access_log /data/logs/apprise/nginx-access.log;`; rate-limit zone for `/status` and `/metrics`; the **`upstream apprise_upstream { server unix:/run/apprise/gunicorn.sock max_fails=0; keepalive 16; }`**; one `server { listen 8000; listen [::]:8000; ... }` block with the full route table copied from upstream (locations for `/`, `/notify`, `/notify/<key>`, `/status|metrics`, `/details|json/urls/...`, `/cfg`, `/_/`, `/cfg|add|del|get/<key>`, `/s/`, `/favicon.ico`, `/robots.txt`, catch-all). Final line: `include /config/apprise/conf.d/*.conf;` (optional include for user-supplied vhost overrides).
- `nginx/mime.types` — preserved from the Alpine `nginx` package (we copy it back after wiping `/etc/nginx/`).
- `nginx/conf.d/.gitkeep` — empty placeholder.
- `apprise/apprise.yml.sample` — a documented sample apprise YAML config the user can copy into `/config/apprise/store/<key>.yml`. Comments explain TEXT vs YAML formats.
We don't ship a separate `gunicorn.conf.py` — the upstream's lives at `/usr/local/share/apprise-api/webapp/gunicorn.conf.py` and we use it as-is, only overriding the bind path via env (`APPRISE_WORKER_COUNT`, `APPRISE_WORKER_TIMEOUT`) when the user wants to.
## /config/<svc>/ layout (user-editable)
The framework's `__initialize_system_etc` symlinks every file under `/config/<svc>/` back to its `/etc/<svc>/` peer. The user-editable seed mirrors `/etc/`:
- `/config/nginx/nginx.conf` -> `/etc/nginx/nginx.conf`
- `/config/nginx/conf.d/*.conf` -> picked up by the `include /config/apprise/conf.d/*.conf;` line in nginx.conf for user-supplied location overrides
- `/config/apprise/apprise.yml.sample` -> documentation/sample
- `/config/apprise/store/` — apprise-api **persistent config store** (Django writes `<key>.yml` / `<key>.cfg` here when the user POSTs to `/add/<key>`). The `APPRISE_CONFIG_DIR` env points at `/config/apprise/store`.
- `/config/apprise/attach/` — optional attachments dir (`APPRISE_ATTACH_DIR`).
- `/config/apprise/plugin/` — optional custom plugin path (`APPRISE_PLUGIN_PATHS`).
- `/config/secure/auth/{root,user}/apprise_{name,pass}` — generated by the framework if the user opts into HTTP basic auth (none by default; apprise-api itself has no built-in auth).
- `/config/env/apprise.sh` — per-service env overrides (TZ, APPRISE_WORKER_COUNT, etc.).
`ADDITIONAL_CONFIG_DIRS` for apprise will be `/config/nginx /config/apprise` so each one runs through `__initialize_system_etc`.
## init.d/99-apprise.sh
Single init.d script (no separate DB — apprise-api is stateless / file-backed). Based on ampache's `99-ampache.sh` structure, with these knobs:
- `SERVICE_NAME="apprise"`
- `SERVICE_USER="apprise"`, `SERVICE_GROUP="apprise"`, but daemon runs as root by default (Alpine nginx package's user is `nginx`; we keep it simple and use `root` for the start script — gunicorn worker drops privileges if `--user`/`--group` are set, but we follow upstream and keep root).
- `EXEC_CMD_BIN='/usr/local/etc/docker/bin/start-apprise'`
- `EXEC_CMD_ARGS=''`
- `IS_WEB_SERVER="yes"`, `IS_DATABASE_SERVICE="no"`, `USES_DATABASE_SERVICE="no"`
- `WWW_ROOT_DIR="/usr/local/share/apprise-api/webapp"`, `ETC_DIR="/etc/nginx"`, `CONF_DIR="/config/nginx"`
- `ADDITIONAL_CONFIG_DIRS="/config/apprise"`
- `SERVICE_PORT="8000"`
- `__execute_prerun_local`: `mkdir -p /run/apprise /tmp/apprise /config/apprise/store /config/apprise/attach /config/apprise/plugin /data/logs/apprise`; `chmod 1777 /tmp/apprise`; `chown -Rf root:root /run/apprise`; export `APPRISE_CONFIG_DIR=/config/apprise/store`, `APPRISE_ATTACH_DIR=/config/apprise/attach`, `APPRISE_PLUGIN_PATHS=/config/apprise/plugin`.
- `__run_pre_execute_checks_local`: `nginx -t -c /etc/nginx/nginx.conf` to validate config before launch.
- `__update_conf_files_local`: replace `REPLACE_TZ` token in any of our shipped configs with `${TZ:-UTC}`. (Currently only nginx error_log gets one, optional.)
- `PRE_EXEC_MESSAGE="Apprise REST API listening on http://localhost:${SERVICE_PORT:-8000}/"`.
## start-apprise wrapper script
`rootfs/usr/local/etc/docker/bin/start-apprise` — small bash wrapper:
1. `set -e`
2. `mkdir -p /run/apprise /tmp/apprise /data/logs/apprise /config/apprise/store /config/apprise/attach /config/apprise/plugin`
3. `chmod 1777 /tmp/apprise`
4. Export the `APPRISE_*` env vars (defaults if user hasn't set them).
5. Start gunicorn in background: `cd /usr/local/share/apprise-api/webapp && gunicorn -c gunicorn.conf.py --worker-tmp-dir /dev/shm core.wsgi >>/data/logs/apprise/gunicorn.log 2>&1 &`
6. Wait up to 15s for `/run/apprise/gunicorn.sock` to appear.
7. `exec /usr/sbin/nginx -c /etc/nginx/nginx.conf` (nginx.conf has `daemon off;`).
The gunicorn config file sets `bind = ["unix:/run/apprise/gunicorn.sock"]` — we patch this in 05-custom.sh because upstream's path is `/tmp/apprise/gunicorn.sock`.
## 05-custom.sh additions
Replace the placeholder content with:
1. Wipe distro-default `/etc/nginx/*` and copy in our shipped nginx config (preserving `mime.types` and `fastcgi_params` from the package since we don't ship them):
```sh
if [ -d /tmp/etc/nginx ]; then
[ -f /etc/nginx/mime.types ] && cp -f /etc/nginx/mime.types /tmp/nginx-mime.types.preserve
[ -f /etc/nginx/fastcgi_params ] && cp -f /etc/nginx/fastcgi_params /tmp/nginx-fastcgi_params.preserve
rm -Rf /etc/nginx/*
cp -Rf /tmp/etc/nginx/. /etc/nginx/
[ -f /tmp/nginx-mime.types.preserve ] && mv -f /tmp/nginx-mime.types.preserve /etc/nginx/mime.types
[ -f /tmp/nginx-fastcgi_params.preserve ] && mv -f /tmp/nginx-fastcgi_params.preserve /etc/nginx/fastcgi_params
mkdir -p /usr/local/share/template-files/config/nginx
cp -Rf /etc/nginx/. /usr/local/share/template-files/config/nginx/
fi
```
2. Same wipe-and-replace pattern for `/etc/apprise/`.
3. Clone apprise-api at a pinned tag from upstream:
```sh
APPRISE_API_VERSION="${APPRISE_API_VERSION:-master}"
git clone --depth 1 --branch "$APPRISE_API_VERSION" https://github.com/caronc/apprise-api /usr/local/share/apprise-api
```
Layout note: upstream repo's `apprise_api/` contents become `/usr/local/share/apprise-api/`. Inside, the Django app is the `Apprise-API` checkout's root (it already has `manage.py`, `core/`, `api/`, `error/`, `gunicorn.conf.py`). We adopt their `webapp/` symlink convention by creating `/usr/local/share/apprise-api/webapp -> .` so paths in our nginx.conf and start script can reference `/usr/local/share/apprise-api/webapp/` consistently with upstream.
4. Patch `gunicorn.conf.py` to point at our socket path:
```sh
sed -i 's|/tmp/apprise/gunicorn.sock|/run/apprise/gunicorn.sock|g' /usr/local/share/apprise-api/webapp/gunicorn.conf.py
```
5. `pip install --no-cache-dir --break-system-packages apprise PGPy slixmpp smpplib gntp` (the deps Alpine doesn't package).
6. Create runtime dirs:
```sh
mkdir -p /run/apprise /tmp/apprise /var/log/nginx /usr/local/share/template-files/config/apprise
chmod 1777 /tmp/apprise
```
7. Drop a sample apprise YAML (`apprise.yml.sample`) into `/usr/local/share/template-files/config/apprise/` so first-run seeding lands one in `/config/apprise/`.
## 04-users.sh additions
The `nginx` Alpine package creates the `nginx` user automatically. Add a defensive `apprise` system user in case the framework's user creation hasn't run yet (`addgroup -S apprise 2>/dev/null; adduser -S -G apprise -H -h /var/lib/apprise -s /sbin/nologin apprise 2>/dev/null`). Keep it idempotent (the `|| true` pattern).
## 02-packages.sh additions
Empty placeholder is fine — pip installs go in 05-custom.sh next to the upstream clone (needs to run after the clone so we can also install from the repo's own `requirements.txt`). Decision: do `pip install` in `05-custom.sh`.
## Dockerfile changes
- Update `BUILD_DATE` to `202605091200` (today, 2026-05-09).
- Replace `PACK_LIST=" "` with the trimmed list above (single line, trailing space).
- Change `ARG SERVICE_PORT="80"` -> `"8000"` and `ARG EXPOSE_PORTS="80"` -> `"8000"` in the header. (Final-stage labels reuse them.)
- Change `PHP_VERSION="system"` to `"none"` (no PHP at all).
- Add `ARG CONTAINER_VERSION="USE_DATE"` so the YYMM tag is auto-added like ampache.
- Keep everything else (multi-stage, scratch final, ENVs, volumes, healthcheck).
## .env.scripts changes
- Sync `ENV_PACKAGES` to match the new `PACK_LIST` (single space, no doubles).
- `SERVICE_PORT="8000"`, `EXPOSE_PORTS=""`.
- `PHP_VERSION="none"`.
## README updates
Document the first-run workflow:
- visit `http://localhost:8000/` -> the apprise-api welcome UI.
- create a stateful config: `curl -X POST http://localhost:8000/add/mykey -d 'urls=mailto://user:pass@gmail.com'` (or POST a YAML body).
- send a notification: `curl -X POST http://localhost:8000/notify/mykey -d 'body=hello&title=test'`.
- stateless one-shot: `curl -X POST http://localhost:8000/notify -d 'urls=json://localhost&body=hi'`.
- volumes: `/config` (apprise YAML/text profiles + nginx overrides), `/data` (logs, attachments not-mounted-elsewhere).
- env vars: `TZ`, `APPRISE_WORKER_COUNT`, `APPRISE_WORKER_TIMEOUT`, `APPRISE_BASE_URL`, `APPRISE_STATEFUL_MODE`.
## Verification (success criteria)
1. `cd /root/Projects/github/casjaysdevdocker/apprise && rm -f .build_failed && buildx run Dockerfile` succeeds for both `linux/amd64` and `linux/arm64`. Single retry permitted on transient network errors.
2. `docker run -d --rm --name test-apprise -p 18000:8000 docker.io/casjaysdevdocker/apprise:latest` boots; after ~30s `docker logs test-apprise | tail -50` shows nginx + gunicorn started, no fatal errors.
3. `curl -fsS -o /dev/null -w '%{http_code}' http://localhost:18000/` returns 200.
4. `curl -fsS -X POST http://localhost:18000/notify -d 'urls=json://&body=test&title=test'` returns 200 (or a clear 4xx if the URL is rejected — note which).
5. `docker exec test-apprise ls /config/apprise/store /config/nginx/ /usr/local/share/apprise-api/webapp/manage.py` — every path exists.
6. `docker stop test-apprise`.
## Rollback
If anything in this PLAN.md proves wrong, the existing files are recoverable from git (`git checkout -- rootfs/`). New files (init.d/99-apprise.sh, tmp/etc/, start-apprise) can be removed cleanly because they didn't exist before this migration.

101
README.md
View File

@@ -1,57 +1,98 @@
## 👋 Welcome to apprise 🚀 ## Welcome to apprise
apprise README REST API gateway for the [apprise](https://github.com/caronc/apprise)
notification library. POST a notification to `http://<host>:8000/notify` (or
`/notify/<key>` for stateful configs) and apprise fans it out to dozens of
## Install my system scripts notification services (Discord, Slack, Telegram, Pushover, email, MQTT, ...).
This image bundles **nginx + gunicorn + Django + apprise-api** on top of Alpine.
### Ports
| Port | Purpose |
|------|---------|
| 8000 | Apprise REST API + web UI |
### Volumes
| Path | Purpose |
|------|---------|
| `/config` | Apprise YAML/text configs (`/config/apprise/store/<key>.yml`), nginx overrides (`/config/nginx/`), per-service env (`/config/env/apprise.sh`) |
| `/data` | Logs (`/data/logs/apprise/`), runtime state |
## Install my system scripts
```shell ```shell
sudo bash -c "$(curl -q -LSsf "https://github.com/systemmgr/installer/raw/main/install.sh")" sudo bash -c "$(curl -q -LSsf "https://github.com/systemmgr/installer/raw/main/install.sh")"
sudo systemmgr --config && sudo systemmgr install scripts sudo systemmgr --config && sudo systemmgr install scripts
``` ```
## Automatic install/update ## Automatic install/update
```shell ```shell
dockermgr update apprise dockermgr update apprise
``` ```
## Install and run container ## Install and run container
```shell ```shell
mkdir -p "$HOME/.local/share/srv/docker/apprise/volumes" dockerHome="/var/lib/srv/$USER/docker/casjaysdevdocker/apprise/apprise/latest/rootfs"
git clone "https://github.com/dockermgr/apprise" "$HOME/.local/share/CasjaysDev/dockermgr/apprise" mkdir -p "$dockerHome/data" "$dockerHome/config"
cp -Rfva "$HOME/.local/share/CasjaysDev/dockermgr/apprise/rootfs/." "$HOME/.local/share/srv/docker/apprise/volumes/"
docker run -d \ docker run -d \
--restart always \ --restart always \
--privileged \ --name casjaysdevdocker-apprise-latest \
--name casjaysdevdocker-apprise \ --hostname apprise \
--hostname apprise \ -e TZ=${TIMEZONE:-America/New_York} \
-e TZ=${TIMEZONE:-America/New_York} \ -v "$dockerHome/data:/data:z" \
-v "$HOME/.local/share/srv/docker/casjaysdevdocker-apprise/volumes/data:/data:z" \ -v "$dockerHome/config:/config:z" \
-v "$HOME/.local/share/srv/docker/casjaysdevdocker-apprise/volumes/config:/config:z" \ -p 8000:8000 \
-p 80:80 \ casjaysdevdocker/apprise:latest
casjaysdevdocker/apprise:latest
``` ```
## via docker-compose ## via docker-compose
```yaml ```yaml
version: "2" version: "2"
services: services:
ProjectName: apprise:
image: casjaysdevdocker/apprise image: casjaysdevdocker/apprise
container_name: casjaysdevdocker-apprise container_name: casjaysdevdocker-apprise
environment: environment:
- TZ=America/New_York - TZ=America/New_York
- HOSTNAME=apprise - HOSTNAME=apprise
volumes: volumes:
- "$HOME/.local/share/srv/docker/casjaysdevdocker-apprise/volumes/data:/data:z" - "./data:/data:z"
- "$HOME/.local/share/srv/docker/casjaysdevdocker-apprise/volumes/config:/config:z" - "./config:/config:z"
ports: ports:
- 80:80 - 8000:8000
restart: always restart: always
``` ```
## First-run usage
1. Visit `http://localhost:8000/` to see the apprise-api welcome UI.
2. Save a stateful config (a named bundle of notification URLs):
```shell
curl -X POST http://localhost:8000/add/mykey -d 'urls=mailto://user:pass@gmail.com'
```
3. Send a notification through it:
```shell
curl -X POST http://localhost:8000/notify/mykey -d 'body=hello&title=test'
```
4. One-shot stateless notify (no saved config):
```shell
curl -X POST http://localhost:8000/notify -d 'urls=json://localhost&body=hi&title=test'
```
## Useful environment variables
| Variable | Default | Purpose |
|----------|---------|---------|
| `TZ` | `America/New_York` | Timezone for log timestamps |
| `APPRISE_WORKER_COUNT` | `(2*CPUs)+1` | gunicorn worker count |
| `APPRISE_WORKER_TIMEOUT` | `300` | gunicorn worker timeout (seconds) |
| `APPRISE_BASE_URL` | _(none)_ | Mount under a URL prefix, e.g. `/apprise` |
| `APPRISE_STATEFUL_MODE` | `simple` | `simple`, `hash`, or `disabled` |
## Get source files ## Get source files

View File

@@ -28,6 +28,15 @@ exitCode=0
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Predefined actions # Predefined actions
# Defensive: the nginx Alpine package creates the nginx user. Apprise itself
# has no daemon user — we run as root and let gunicorn handle worker-level
# privilege drops via PUID/PGID env if the user wants. Add an apprise system
# user/group for /config/secure ownership.
if command -v addgroup >/dev/null 2>&1 && command -v adduser >/dev/null 2>&1; then
addgroup -S apprise 2>/dev/null || true
adduser -S -G apprise -H -h /var/lib/apprise -s /sbin/nologin apprise 2>/dev/null || true
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Main script # Main script

View File

@@ -1,38 +1,124 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202509161144-git # casjaysdevdocker/apprise - 05-custom.sh
# @@Author : CasjaysDev # 1. Wipe distro defaults under /etc/{nginx,apprise}/* and drop in our
# @@Contact : CasjaysDev <docker-admin@casjaysdev.pro> # optimized configs from /tmp/etc/.
# @@License : MIT # 2. Clone the upstream apprise-api Django app to /usr/local/share/apprise-api.
# @@ReadME : # 3. pip install the Python deps Alpine doesn't ship.
# @@Copyright : Copyright 2023 CasjaysDev # 4. Stage runtime dirs that the init.d scripts will need.
# @@Created : Mon Aug 28 06:48:42 PM EDT 2023
# @@File : 05-custom.sh
# @@Description : script to run custom
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# shellcheck shell=bash set -e -o pipefail
# shellcheck disable=SC2016
# shellcheck disable=SC2031
# shellcheck disable=SC2120
# shellcheck disable=SC2155
# shellcheck disable=SC2199
# shellcheck disable=SC2317
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Set bash options
set -o pipefail
[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS [ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -x$DEBUGGER_OPTIONS
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Set env variables
exitCode=0 exitCode=0
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - APPRISE_API_VERSION="${APPRISE_API_VERSION:-master}"
# Predefined actions APPRISE_API_REPO="${APPRISE_API_REPO:-https://github.com/caronc/apprise-api}"
APPRISE_API_INSTALL_DIR="/usr/local/share/apprise-api"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - echo "Wiping distro defaults and installing optimized configs"
# Main script for svc in nginx apprise; do
src="/tmp/etc/$svc"
dst="/etc/$svc"
[ -d "$src" ] || continue
if [ "$svc" = "nginx" ]; then
# Preserve mime.types + fastcgi_params from the package; we don't ship them.
[ -f "$dst/mime.types" ] && cp -f "$dst/mime.types" "/tmp/${svc}_mime.types.preserve" || true
[ -f "$dst/fastcgi_params" ] && cp -f "$dst/fastcgi_params" "/tmp/${svc}_fastcgi_params.preserve" || true
[ -f "$dst/scgi_params" ] && cp -f "$dst/scgi_params" "/tmp/${svc}_scgi_params.preserve" || true
[ -f "$dst/uwsgi_params" ] && cp -f "$dst/uwsgi_params" "/tmp/${svc}_uwsgi_params.preserve" || true
[ -d "$dst/modules" ] && cp -Rf "$dst/modules" "/tmp/${svc}_modules.preserve" || true
fi
rm -Rf "$dst"/*
mkdir -p "$dst"
cp -Rf "$src/." "$dst/"
if [ "$svc" = "nginx" ]; then
[ -f "/tmp/${svc}_mime.types.preserve" ] && mv -f "/tmp/${svc}_mime.types.preserve" "$dst/mime.types"
[ -f "/tmp/${svc}_fastcgi_params.preserve" ] && mv -f "/tmp/${svc}_fastcgi_params.preserve" "$dst/fastcgi_params"
[ -f "/tmp/${svc}_scgi_params.preserve" ] && mv -f "/tmp/${svc}_scgi_params.preserve" "$dst/scgi_params"
[ -f "/tmp/${svc}_uwsgi_params.preserve" ] && mv -f "/tmp/${svc}_uwsgi_params.preserve" "$dst/uwsgi_params"
[ -d "/tmp/${svc}_modules.preserve" ] && cp -Rf "/tmp/${svc}_modules.preserve" "$dst/modules" && rm -Rf "/tmp/${svc}_modules.preserve"
fi
mkdir -p "/usr/local/share/template-files/config/$svc"
cp -Rf "$dst/." "/usr/local/share/template-files/config/$svc/"
done
echo "Installing apprise-api from prebundled source tarball"
# Tarball is shipped in rootfs/tmp/apprise-src/ (downloaded on host pre-build)
# because the buildx build environment intermittently fails SSL validation
# against github.com. The host download is in .gitignore.
APPRISE_API_TARBALL="/tmp/apprise-src/apprise-api.tar.gz"
if [ ! -f "$APPRISE_API_TARBALL" ]; then
echo "Apprise-api source tarball missing at $APPRISE_API_TARBALL" >&2
echo "Run 'curl -fsSL -o rootfs/tmp/apprise-src/apprise-api.tar.gz \\" >&2
echo " https://github.com/caronc/apprise-api/archive/refs/heads/master.tar.gz' on the host first." >&2
exit 12
fi
mkdir -p "$(dirname "$APPRISE_API_INSTALL_DIR")"
rm -Rf "$APPRISE_API_INSTALL_DIR" /tmp/apprise-api-extract
mkdir -p /tmp/apprise-api-extract
tar -xzf "$APPRISE_API_TARBALL" -C /tmp/apprise-api-extract/
SRC_DIR="$(find /tmp/apprise-api-extract -mindepth 1 -maxdepth 1 -type d -name 'apprise-api-*' | head -1)"
if [ -z "$SRC_DIR" ] || [ ! -d "$SRC_DIR" ]; then
echo "Tarball extracted but no apprise-api-* dir found" >&2
ls -la /tmp/apprise-api-extract >&2
exit 13
fi
mv "$SRC_DIR" "$APPRISE_API_INSTALL_DIR"
rm -Rf /tmp/apprise-api-extract
# Upstream layout (verified at master):
# repo-root/
# manage.py (launcher; adds apprise_api/ to sys.path)
# apprise_api/ <-- the actual Django app: core/, api/, static/, etc/, gunicorn.conf.py
# Upstream Dockerfile does `COPY apprise_api/ webapp` — i.e. the *inner* dir
# becomes "webapp". We mirror that by symlinking apprise_api -> webapp so
# /usr/local/share/apprise-api/webapp/{core,static,gunicorn.conf.py,etc} all
# resolve correctly.
if [ -d "$APPRISE_API_INSTALL_DIR/apprise_api" ] && [ ! -e "$APPRISE_API_INSTALL_DIR/webapp" ]; then
ln -sf "apprise_api" "$APPRISE_API_INSTALL_DIR/webapp"
fi
# Sanity check
if [ ! -f "$APPRISE_API_INSTALL_DIR/webapp/manage.py" ] && [ ! -f "$APPRISE_API_INSTALL_DIR/webapp/gunicorn.conf.py" ]; then
echo "Apprise-API layout unexpected: webapp/gunicorn.conf.py missing after symlink" >&2
ls -la "$APPRISE_API_INSTALL_DIR" "$APPRISE_API_INSTALL_DIR/webapp" 2>&1 | head -40 >&2
exit 11
fi
# Patch gunicorn.conf.py: change socket path to our /run/apprise location, and
# fix the hard-coded pythonpath that upstream points at /opt/apprise/webapp.
GUNICORN_CONF="$APPRISE_API_INSTALL_DIR/webapp/gunicorn.conf.py"
if [ -f "$GUNICORN_CONF" ]; then
sed -i 's|/tmp/apprise/gunicorn.sock|/run/apprise/gunicorn.sock|g' "$GUNICORN_CONF"
sed -i 's|/opt/apprise/webapp|/usr/local/share/apprise-api/webapp|g' "$GUNICORN_CONF"
echo "Patched gunicorn.conf.py: $GUNICORN_CONF"
fi
echo "Installing Python deps not in Alpine"
pip3 install --no-cache-dir --break-system-packages \
apprise PGPy slixmpp smpplib gntp || \
pip3 install --no-cache-dir \
apprise PGPy slixmpp smpplib gntp
# Install the rest of upstream's requirements.txt (defensive; most are already
# satisfied by our Alpine packages or the explicit pip list above).
if [ -f "$APPRISE_API_INSTALL_DIR/requirements.txt" ]; then
pip3 install --no-cache-dir --break-system-packages \
-r "$APPRISE_API_INSTALL_DIR/requirements.txt" 2>/dev/null || \
pip3 install --no-cache-dir \
-r "$APPRISE_API_INSTALL_DIR/requirements.txt" 2>/dev/null || true
fi
echo "Creating runtime dirs"
mkdir -p /run/apprise /tmp/apprise /var/log/nginx \
/usr/local/share/template-files/config/apprise \
/usr/local/share/template-files/config/nginx
chmod 1777 /tmp/apprise
# Drop a sample apprise YAML into the template-files seed dir so first-run lands one.
if [ -f /tmp/etc/apprise/apprise.yml.sample ]; then
cp -f /tmp/etc/apprise/apprise.yml.sample /usr/local/share/template-files/config/apprise/
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Set the exit code
#exitCode=$?
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
exit $exitCode exit $exitCode

View File

@@ -0,0 +1,17 @@
# Sample apprise YAML configuration.
# Copy/rename to /config/apprise/store/<key>.yml then POST to
# http://<container-host>:8000/notify/<key> -d 'body=hello&title=test'
#
# Two equivalent formats are supported:
# - TEXT: one URL per line in /config/apprise/store/<key>.cfg
# - YAML: structured (this file)
#
# See https://github.com/caronc/apprise/wiki for the full URL syntax.
urls:
# - mailto://user:password@gmail.com
# - tgram://botid/chatid
# - discord://webhook_id/webhook_token
# - slack://TokenA/TokenB/TokenC
# - pover://user@token
# - json://localhost

View File

@@ -0,0 +1,2 @@
# Placeholder for distro-style /etc/nginx/conf.d.
# Apprise itself includes /config/apprise/conf.d/*.conf for user overrides.

View File

@@ -0,0 +1,263 @@
# casjaysdevdocker/apprise - nginx.conf
# Based on upstream caronc/apprise-api/webapp/etc/nginx.conf, adapted to our paths.
# Runs in foreground (daemon off) under the start-apprise wrapper.
daemon off;
worker_processes auto;
pid /run/apprise/nginx.pid;
error_log /data/logs/apprise/nginx-error.log error;
events {
worker_connections 4096;
}
http {
# Upstream gunicorn (started by start-apprise)
upstream apprise_upstream {
server unix:/run/apprise/gunicorn.sock max_fails=0;
keepalive 16;
}
# Basic Settings
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
# Upload Restriction
client_max_body_size 500M;
# Logging
access_log /data/logs/apprise/nginx-access.log;
# Centralize tmp paths so a tmpfs mount on /tmp works cleanly
client_body_temp_path /tmp/apprise/nginx_client_temp 1 2;
proxy_temp_path /tmp/apprise/nginx_proxy_temp 1 2;
fastcgi_temp_path /tmp/apprise/nginx_fastcgi_temp 1 2;
uwsgi_temp_path /tmp/apprise/nginx_uwsgi_temp 1 2;
scgi_temp_path /tmp/apprise/nginx_scgi_temp 1 2;
# Gzip
gzip on;
gzip_proxied any;
gzip_vary on;
gzip_min_length 1024;
gzip_types application/json text/plain text/css application/javascript text/xml application/xml;
# Host config
client_body_buffer_size 256k;
client_body_in_file_only off;
# Retry cushions
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
# Rate limits for /status and /metrics
limit_req_zone $binary_remote_addr zone=status:10m rate=5r/s;
server {
listen 8000;
listen [::]:8000;
# Error handling
proxy_intercept_errors on;
error_page 404 = /_/404/;
error_page 500 = /_/50x/;
error_page 502 503 504 /50x.html;
#
# 1. Welcome page
#
location = / {
proxy_pass http://apprise_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120s;
}
#
# 2. Stateless notify
#
location = /notify {
proxy_pass http://apprise_upstream;
proxy_http_version 1.1;
proxy_request_buffering off;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Expect $http_expect;
client_max_body_size 500M;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
#
# 3. Stateful notify with key
#
location ~ "^/notify/[\w_-]{1,128}/?$" {
proxy_pass http://apprise_upstream;
proxy_http_version 1.1;
proxy_request_buffering off;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Expect $http_expect;
client_max_body_size 500M;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
#
# 4. Health check / metrics
#
location ~ "^/(status|metrics)/?$" {
rewrite ^/status/?$ /status/ break;
rewrite ^/metrics/$ /metrics break;
proxy_pass http://apprise_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 1s;
proxy_send_timeout 2s;
proxy_read_timeout 2s;
proxy_buffering off;
access_log off;
limit_req zone=status burst=10 nodelay;
add_header Cache-Control "no-store" always;
}
#
# 5. Service discovery and metadata
#
location ~ "^/(details|json/urls/[\w_-]{1,128})/?$" {
proxy_pass http://apprise_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120s;
}
#
# 6. Config list view: GET /cfg
#
location = /cfg {
proxy_pass http://apprise_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120s;
}
#
# 7. Django Error pages
#
location /_/ {
proxy_intercept_errors off;
proxy_pass http://apprise_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120s;
access_log off;
}
#
# 8. Config management (HTML UI + API)
#
location ~ "^/(cfg|add|del|get)/[\w_-]{1,128}/?$" {
proxy_pass http://apprise_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 300s;
}
#
# 9. Static content (apprise-api ships static files under webapp/static/)
# Serve /s/ from the bundled static dir.
#
location /s/ {
alias /usr/local/share/apprise-api/webapp/static/;
index index.html;
}
#
# 10. favicon
#
location = /favicon.ico {
alias /usr/local/share/apprise-api/webapp/static/favicon.ico;
access_log off;
log_not_found off;
}
#
# 11. robots.txt
#
location = /robots.txt {
access_log off;
log_not_found off;
default_type text/plain;
return 200 "User-agent: *\nDisallow: /\n";
}
#
# 12. Catch-all
#
location / {
proxy_pass http://apprise_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
proxy_set_header Transfer-Encoding "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# Optional user-supplied location overrides
include /config/apprise/conf.d/*.conf;
}
}

View File

@@ -1,13 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# shellcheck shell=bash # shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202511301623-git ##@Version : 202602061352-git
# @@Author : Jason Hempstead # @@Author : Jason Hempstead
# @@Contact : jason@casjaysdev.pro # @@Contact : jason@casjaysdev.pro
# @@License : WTFPL # @@License : WTFPL
# @@ReadME : entrypoint.sh --help # @@ReadME : entrypoint.sh --help
# @@Copyright : Copyright: (c) 2025 Jason Hempstead, Casjays Developments # @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments
# @@Created : Sunday, Nov 30, 2025 16:23 EST # @@Created : Tuesday, May 05, 2026 14:38 EDT
# @@File : entrypoint.sh # @@File : entrypoint.sh
# @@Description : Entrypoint file for apprise # @@Description : Entrypoint file for apprise
# @@Changelog : New script # @@Changelog : New script
@@ -32,7 +32,7 @@ PATH="/usr/local/etc/docker/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin"
# Set bash options # Set bash options
SCRIPT_FILE="$0" SCRIPT_FILE="$0"
CONTAINER_NAME="apprise" CONTAINER_NAME="apprise"
SCRIPT_NAME="$(basename -- "$SCRIPT_FILE" 2>/dev/null)" SCRIPT_NAME="${SCRIPT_FILE##*/}"
CONTAINER_NAME="${ENV_CONTAINER_NAME:-$CONTAINER_NAME}" CONTAINER_NAME="${ENV_CONTAINER_NAME:-$CONTAINER_NAME}"
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# remove whitespaces from beginning argument # remove whitespaces from beginning argument
@@ -73,30 +73,38 @@ done
unset set_env unset set_env
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# User to use to launch service - IE: postgres # User to use to launch service - IE: postgres
RUNAS_USER="root" # normally root # normally root
RUNAS_USER="root"
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Set user and group from env # Set user and group from env
SERVICE_USER="${PUID:-$SERVICE_USER}" SERVICE_USER="${PUID:-$SERVICE_USER}"
SERVICE_GROUP="${PGID:-$SERVICE_GROUP}" SERVICE_GROUP="${PGID:-$SERVICE_GROUP}"
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Set user and group ID # Set user and group ID
SERVICE_UID="${SERVICE_UID:-0}" # set the user id # set the user id
SERVICE_GID="${SERVICE_GID:-0}" # set the group 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 # User and group in which the service switches to - IE: nginx,apache,mysql,postgres
#SERVICE_USER="${SERVICE_USER:-apprise}" # execute command as another user #SERVICE_USER="${SERVICE_USER:-apprise}" # execute command as another user
#SERVICE_GROUP="${SERVICE_GROUP:-apprise}" # Set the service group #SERVICE_GROUP="${SERVICE_GROUP:-apprise}" # Set the service group
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Secondary ports # Secondary ports
SERVER_PORTS="" # specifiy other ports # specifiy other ports
SERVER_PORTS=""
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Primary server port- will be added to server ports # Primary server port- will be added to server ports
WEB_SERVER_PORT="" # port : 80,443 # port : 80,443
WEB_SERVER_PORT=""
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Healthcheck variables # Healthcheck variables
HEALTH_ENABLED="yes" # enable healthcheck [yes/no] # enable healthcheck [yes/no]
SERVICES_LIST="tini" # comma separated list of processes for the healthcheck HEALTH_ENABLED="yes"
HEALTH_ENDPOINTS="" # url endpoints: [http://localhost/health,http://localhost/test] # comma separated list of processes for the healthcheck
SERVICES_LIST="tini"
# url endpoints: [http://localhost/health,http://localhost/test]
HEALTH_ENDPOINTS=""
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Update path var # Update path var
export PATH RUNAS_USER SERVICE_USER SERVICE_GROUP SERVICE_UID SERVICE_GID WWW_ROOT_DIR DATABASE_DIR export PATH RUNAS_USER SERVICE_USER SERVICE_GROUP SERVICE_UID SERVICE_GID WWW_ROOT_DIR DATABASE_DIR
@@ -162,28 +170,40 @@ export ENTRYPOINT_DATA_INIT_FILE="${ENTRYPOINT_DATA_INIT_FILE:-/data/.docker_has
export ENTRYPOINT_CONFIG_INIT_FILE="${ENTRYPOINT_CONFIG_INIT_FILE:-/config/.docker_has_run}" export ENTRYPOINT_CONFIG_INIT_FILE="${ENTRYPOINT_CONFIG_INIT_FILE:-/config/.docker_has_run}"
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -n "$CONTAINER_WEB_SERVER_WWW_REPO" ]; then if [ -n "$CONTAINER_WEB_SERVER_WWW_REPO" ]; then
www_temp_dir="/tmp/git/$(basename -- "$CONTAINER_WEB_SERVER_WWW_REPO")" www_temp_dir="/tmp/git/${CONTAINER_WEB_SERVER_WWW_REPO##*/}"
rm -Rf "${WWW_ROOT_DIR:?}"/* "${www_temp_dir:?}"/* rm -Rf "${WWW_ROOT_DIR:?}"/* "${www_temp_dir:?}"/* 2>/dev/null || true
mkdir -p "$WWW_ROOT_DIR" "$www_temp_dir" 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 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* rm -Rf "$www_temp_dir/.git" "$www_temp_dir"/.git* 2>/dev/null || true
rsync -ra "$www_temp_dir/" "$WWW_ROOT_DIR" --delete >/dev/null 2>&1 rsync -ra "$www_temp_dir/" "$WWW_ROOT_DIR" --delete 2>/dev/null || true
rm -Rf "$www_temp_dir" rm -Rf "$www_temp_dir" 2>/dev/null || true
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# variables based on env/files # variables based on env/files
[ -f "/config/enable/ssl" ] && SSL_ENABLED="yes" if [ -f "/config/enable/ssl" ]; then SSL_ENABLED="yes"; fi
[ -f "/config/enable/ssh" ] && SSH_ENABLED="yes" if [ -f "/config/enable/ssh" ]; then SSH_ENABLED="yes"; fi
[ "$WEB_SERVER_PORT" = "443" ] && SSL_ENABLED="yes" if [ "$WEB_SERVER_PORT" = "443" ]; then SSL_ENABLED="yes"; fi
[ "$CONTAINER_WEB_SERVER_PROTOCOL" = "https" ] && SSL_ENABLED="yes" if [ "$CONTAINER_WEB_SERVER_PROTOCOL" = "https" ]; then SSL_ENABLED="yes"; fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# export variables # export variables
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# is already Initialized # is already Initialized
[ -f "$ENTRYPOINT_DATA_INIT_FILE" ] && DATA_DIR_INITIALIZED="yes" || DATA_DIR_INITIALIZED="no" if [ -f "$ENTRYPOINT_DATA_INIT_FILE" ]; then
[ -f "$ENTRYPOINT_CONFIG_INIT_FILE" ] && CONFIG_DIR_INITIALIZED="yes" || CONFIG_DIR_INITIALIZED="no" DATA_DIR_INITIALIZED="yes"
{ [ -f "$ENTRYPOINT_PID_FILE" ] || [ -f "$ENTRYPOINT_INIT_FILE" ]; } && ENTRYPOINT_FIRST_RUN="no" || ENTRYPOINT_FIRST_RUN="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 # clean ENV_PORTS variables
ENV_PORTS="${ENV_PORTS//,/ }" # ENV_PORTS="${ENV_PORTS//,/ }" #
@@ -207,168 +227,236 @@ ENV_PORTS="$(__format_variables "$SERVER_PORTS" "$WEB_SERVER_PORTS" "$ENV_PORTS"
HEALTH_ENDPOINTS="${HEALTH_ENDPOINTS//,/ }" HEALTH_ENDPOINTS="${HEALTH_ENDPOINTS//,/ }"
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# create required directories # create required directories
mkdir -p "/run" mkdir -p "/run" 2>/dev/null || true
mkdir -p "/tmp" mkdir -p "/tmp" 2>/dev/null || true
mkdir -p "/root" mkdir -p "/root" 2>/dev/null || true
mkdir -p "/var/run" mkdir -p "/var/run" 2>/dev/null || true
mkdir -p "/var/tmp" mkdir -p "/var/tmp" 2>/dev/null || true
mkdir -p "/run/cron" mkdir -p "/run/cron" 2>/dev/null || true
mkdir -p "/data/logs" mkdir -p "/data/logs" 2>/dev/null || true
mkdir -p "/run/init.d" mkdir -p "/run/init.d" 2>/dev/null || true
mkdir -p "/config/enable" mkdir -p "/config/enable" 2>/dev/null || true
mkdir -p "/config/secure" mkdir -p "/config/secure" 2>/dev/null || true
mkdir -p "/usr/local/etc/docker/exec" mkdir -p "/usr/local/etc/docker/exec" 2>/dev/null || true
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# create required files # create required files
touch "/data/logs/start.log" touch "/data/logs/start.log" 2>/dev/null || true
touch "/data/logs/entrypoint.log" touch "/data/logs/entrypoint.log" 2>/dev/null || true
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# fix permissions # fix permissions
chmod -f 777 "/run" chmod -f 777 "/run" 2>/dev/null || true
chmod -f 777 "/tmp" chmod -f 777 "/tmp" 2>/dev/null || true
chmod -f 700 "/root" chmod -f 700 "/root" 2>/dev/null || true
chmod -f 777 "/var/run" chmod -f 777 "/var/run" 2>/dev/null || true
chmod -f 777 "/var/tmp" chmod -f 777 "/var/tmp" 2>/dev/null || true
chmod -f 777 "/run/cron" chmod -f 777 "/run/cron" 2>/dev/null || true
chmod -f 777 "/data/logs" chmod -f 777 "/data/logs" 2>/dev/null || true
chmod -f 777 "/run/init.d" chmod -f 777 "/run/init.d" 2>/dev/null || true
chmod -f 777 "/config/enable" chmod -f 777 "/config/enable" 2>/dev/null || true
chmod -f 777 "/config/secure" chmod -f 777 "/config/secure" 2>/dev/null || true
chmod -f 777 "/data/logs/entrypoint.log" chmod -f 777 "/data/logs/entrypoint.log" 2>/dev/null || true
chmod -f 777 "/usr/local/etc/docker/exec" chmod -f 777 "/usr/local/etc/docker/exec" 2>/dev/null || true
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# lets ensure everyone can write to std* # lets ensure everyone can write to std*
[ -f "/dev/stdin" ] && chmod -f 777 "/dev/stdin" if [ -f "/dev/stdin" ]; then
[ -f "/dev/stderr" ] && chmod -f 777 "/dev/stderr" chmod -f 777 "/dev/stdin" 2>/dev/null || true
[ -f "/dev/stdout" ] && chmod -f 777 "/dev/stdout" 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 | tee /etc/profile.d/locales.shadow /etc/profile.d/locales.sh >/dev/null 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 LANG="\${LANG:-C.UTF-8}"
export LC_ALL="\${LANG:-C.UTF-8}" export LC_ALL="\${LANG:-C.UTF-8}"
export TZ="\${TZ:-\${TIMEZONE:-America/New_York}}" export TZ="\${TZ:-\${TIMEZONE:-America/New_York}}"
EOF EOF
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Create the backup dir # Create the backup dir
[ -n "$BACKUP_DIR" ] && { [ -d "$BACKUP_DIR" ] || mkdir -p "$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 if [ -f "$ENTRYPOINT_INIT_FILE" ]; then
ENTRYPOINT_MESSAGE="no" ENTRYPOINT_FIRST_RUN="no" ENTRYPOINT_MESSAGE="no" ENTRYPOINT_FIRST_RUN="no"
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
if [ "$ENTRYPOINT_FIRST_RUN" != "no" ]; then if [ "$ENTRYPOINT_FIRST_RUN" != "no" ]; then
# Show start message
if [ "$CONFIG_DIR_INITIALIZED" = "no" ] || [ "$DATA_DIR_INITIALIZED" = "no" ]; then if [ "$CONFIG_DIR_INITIALIZED" = "no" ] || [ "$DATA_DIR_INITIALIZED" = "no" ]; then
[ "$ENTRYPOINT_MESSAGE" = "yes" ] && echo "Executing entrypoint script for apprise" if [ "$ENTRYPOINT_MESSAGE" = "yes" ]; then
echo "Executing entrypoint script for apprise"
fi
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Set reusable variables # Set reusable variables
{ { [ -w "/etc" ] && [ ! -f "/etc/hosts" ]; } || [ -w "/etc/hosts" ]; } && UPDATE_FILE_HOSTS="yes" && touch "/etc/hosts" if [ -w "/etc" ] && [ ! -f "/etc/hosts" ]; then
{ { [ -w "/etc" ] && [ ! -f "/etc/timezone" ]; } || [ -w "/etc/timezone" ]; } && UPDATE_FILE_TZ="yes" && touch "/etc/timezone" UPDATE_FILE_HOSTS="yes"
{ { [ -w "/etc" ] && [ ! -f "/etc/resolv.conf" ]; } || [ -w "/etc/resolv.conf" ]; } && UPDATE_FILE_RESOLV="yes" && touch "/etc/resolv.conf" 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 # Set timezone
[ -n "$TZ" ] && [ "$UPDATE_FILE_TZ" = "yes" ] && echo "$TZ" >"/etc/timezone" if [ -n "$TZ" ] && [ "$UPDATE_FILE_TZ" = "yes" ]; then
[ -f "/usr/share/zoneinfo/$TZ" ] && [ "$UPDATE_FILE_TZ" = "yes" ] && ln -sf "/usr/share/zoneinfo/$TZ" "/etc/localtime" 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 ipv6 add it to /etc/hosts
if [ "$UPDATE_FILE_HOSTS" = "yes" ]; then if [ "$UPDATE_FILE_HOSTS" = "yes" ]; then
echo "# known hostname mappings" >"/etc/hosts" 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 if [ -n "$(ip a 2>/dev/null | grep 'inet6.*::' || ifconfig 2>/dev/null | grep 'inet6.*::')" ]; then
__printf_space "40" "::1" "localhost" >>"/etc/hosts" __printf_space "40" "::1" "localhost" >>"/etc/hosts" 2>/dev/null || true
__printf_space "40" "127.0.0.1" "localhost" >>"/etc/hosts" __printf_space "40" "127.0.0.1" "localhost" >>"/etc/hosts" 2>/dev/null || true
else else
__printf_space "40" "127.0.0.1" "localhost" >>"/etc/hosts" __printf_space "40" "127.0.0.1" "localhost" >>"/etc/hosts" 2>/dev/null || true
fi fi
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# add .internal domain # add .internal domain
if [ "$UPDATE_FILE_HOSTS" = "yes" ] && [ -n "$HOSTNAME" ]; then if [ "$UPDATE_FILE_HOSTS" = "yes" ] && [ -n "$HOSTNAME" ]; then
__grep_test " $HOSTNAME" "/etc/hosts" || __printf_space "40" "${CONTAINER_IP4_ADDRESS:-127.0.0.1}" "$HOSTNAME" >>"/etc/hosts" if ! __grep_test " $HOSTNAME" "/etc/hosts"; then
__grep_test " ${HOSTNAME%%.*}.internal" "/etc/hosts" || __printf_space "40" "${CONTAINER_IP4_ADDRESS:-127.0.0.1}" "${HOSTNAME%%.*}.internal" >>"/etc/hosts" __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 fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# add domainname # add domainname
if [ "$UPDATE_FILE_HOSTS" = "yes" ] && [ "$DOMAINNAME" != "internal" ] && [ -n "$DOMAINNAME" ] && [ "$HOSTNAME.$DOMAINNAME" != "$DOMAINNAME" ]; then if [ "$UPDATE_FILE_HOSTS" = "yes" ] && [ "$DOMAINNAME" != "internal" ] && [ -n "$DOMAINNAME" ] && [ "$HOSTNAME.$DOMAINNAME" != "$DOMAINNAME" ]; then
__grep_test " ${HOSTNAME%%.*}.$DOMAINNAME" "/etc/hosts" || __printf_space "40" "${CONTAINER_IP4_ADDRESS:-127.0.0.1}" "${HOSTNAME%%.*}.$DOMAINNAME" >>"/etc/hosts" 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 fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Set containers hostname # Set containers hostname
[ -n "$HOSTNAME" ] && [ "$UPDATE_FILE_HOSTS" = "yes" ] && echo "$HOSTNAME" >"/etc/hostname" if [ -n "$HOSTNAME" ] && [ "$UPDATE_FILE_HOSTS" = "yes" ]; then
echo "$HOSTNAME" >"/etc/hostname" 2>/dev/null || true
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -f "/etc/hostname" ]; then if [ -f "/etc/hostname" ]; then
[ -n "$(type -P hostname)" ] && hostname -F "/etc/hostname" &>/dev/null || HOSTNAME="$(<"/etc/hostname")" if [ -n "$(type -P hostname 2>/dev/null)" ]; then
hostname -F "/etc/hostname" 2>/dev/null || true
else
HOSTNAME="$(<"/etc/hostname")" 2>/dev/null || true
fi
export HOSTNAME export HOSTNAME
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# import hosts file into container # import hosts file into container
[ -f "/usr/local/etc/hosts" ] && [ "$UPDATE_FILE_HOSTS" = "yes" ] && cat "/usr/local/etc/hosts" | grep -vF "$HOSTNAME" >>"/etc/hosts" if [ -f "/usr/local/etc/hosts" ] && [ "$UPDATE_FILE_HOSTS" = "yes" ]; then
grep -vF "$HOSTNAME" "/usr/local/etc/hosts" 2>/dev/null >>"/etc/hosts" 2>/dev/null || true
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# import resolv.conf file into container # import resolv.conf file into container
[ "$CUSTOM_DNS" != "yes" ] && [ -f "/usr/local/etc/resolv.conf" ] && [ "$UPDATE_FILE_RESOLV" = "yes" ] && cat "/usr/local/etc/resolv.conf" >"/etc/resolv.conf" if [ "$CUSTOM_DNS" != "yes" ] && [ -f "/usr/local/etc/resolv.conf" ] && [ "$UPDATE_FILE_RESOLV" = "yes" ]; then
cat "/usr/local/etc/resolv.conf" >"/etc/resolv.conf" 2>/dev/null || true
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -n "$HOME" ] && [ -d "/usr/local/etc/skel" ]; then if [ -n "$HOME" ] && [ -d "/usr/local/etc/skel" ]; then
[ -d "$HOME" ] && cp -Rf "/usr/local/etc/skel/." "$HOME/" if [ -d "$HOME" ]; then
cp -Rf "/usr/local/etc/skel/." "$HOME/" 2>/dev/null || true
fi
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Delete any .gitkeep files # Delete any .gitkeep files
[ -d "/data" ] && rm -Rf "/data/.gitkeep" "/data"/*/*.gitkeep
[ -d "/config" ] && rm -Rf "/config/.gitkeep" "/config"/*/*.gitkeep
[ -f "/usr/local/bin/.gitkeep" ] && rm -Rf "/usr/local/bin/.gitkeep"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Setup bin directory - /config/bin > /usr/local/bin
__initialize_custom_bin_dir
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Copy default system configs - /usr/local/share/template-files/defaults > /config/
__initialize_default_templates
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Copy custom config files - /usr/local/share/template-files/config > /config/
__initialize_config_dir
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Copy custom data files - /usr/local/share/template-files/data > /data/
__initialize_data_dir
# - - - - - - - - - - - - - - - - - - - - - - - - -
__initialize_ssl_certs
# - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -f "$ENTRYPOINT_INIT_FILE" ]; then
ENTRYPOINT_FIRST_RUN="no"
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -d "/config" ]; then
echo "Initialized on: $INIT_DATE" >"$ENTRYPOINT_INIT_FILE"
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Check if this is a new container
if [ -f "$ENTRYPOINT_DATA_INIT_FILE" ]; then
DATA_DIR_INITIALIZED="yes"
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -d "/data" ]; then if [ -d "/data" ]; then
echo "Initialized on: $INIT_DATE" >"$ENTRYPOINT_DATA_INIT_FILE" rm -Rf "/data/.gitkeep" "/data"/*/*.gitkeep 2>/dev/null || true
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -f "$ENTRYPOINT_CONFIG_INIT_FILE" ]; then
CONFIG_DIR_INITIALIZED="yes"
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -d "/config" ]; then if [ -d "/config" ]; then
echo "Initialized on: $INIT_DATE" >"$ENTRYPOINT_CONFIG_INIT_FILE" 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 fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
if [ "$ENTRYPOINT_FIRST_RUN" != "no" ]; then # 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
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Copy default system configs - /usr/local/share/template-files/defaults > /config/
if [ "$CONFIG_DIR_INITIALIZED" = "no" ]; then
__initialize_default_templates
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Copy custom config files - /usr/local/share/template-files/config > /config/
if [ "$CONFIG_DIR_INITIALIZED" = "no" ]; then
__initialize_config_dir
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Copy custom data files - /usr/local/share/template-files/data > /data/
if [ "$DATA_DIR_INITIALIZED" = "no" ]; then
__initialize_data_dir
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
# 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 the smtp server
__setup_mta __setup_mta
# - - - - - - - - - - - - - - - - - - - - - - - - -
ENTRYPOINT_FIRST_RUN="no"
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# if no pid assume container restart - clean stale files on restart # if no pid assume container restart - clean stale files on restart
if [ -f "$ENTRYPOINT_PID_FILE" ]; then if [ -f "$ENTRYPOINT_PID_FILE" ]; then
START_SERVICES="no" # Check if the PID in the file is still running
touch "$ENTRYPOINT_PID_FILE" entrypoint_pid=$(cat "$ENTRYPOINT_PID_FILE" 2>/dev/null || echo "")
if [ -n "$entrypoint_pid" ] && kill -0 "$entrypoint_pid" 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 else
START_SERVICES=yes START_SERVICES=yes
# Clean any stale PID files on first run # 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 rm -f /run/__start_init_scripts.pid /run/init.d/*.pid /run/*.pid 2>/dev/null || true
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
[ "$ENTRYPOINT_MESSAGE" = "yes" ] && __printf_space "40" "The containers ip address is:" "$CONTAINER_IP4_ADDRESS" [ "$ENTRYPOINT_MESSAGE" = "yes" ] && __printf_space "40" "The containers ip address is:" "$CONTAINER_IP4_ADDRESS"
@@ -411,6 +499,9 @@ if [ "$START_SERVICES" = "yes" ] || [ -z "$1" ]; then
echo "$$" >"$ENTRYPOINT_PID_FILE" echo "$$" >"$ENTRYPOINT_PID_FILE"
__start_init_scripts "/usr/local/etc/docker/init.d" __start_init_scripts "/usr/local/etc/docker/init.d"
CONTAINER_INIT="${CONTAINER_INIT:-no}" CONTAINER_INIT="${CONTAINER_INIT:-no}"
# Services started successfully - enter monitoring mode
__no_exit
exit $?
fi fi
START_SERVICES="no" START_SERVICES="no"
fi fi
@@ -420,7 +511,7 @@ export START_SERVICES CONTAINER_INIT ENTRYPOINT_PID_FILE
case "$1" in case "$1" in
init) init)
shift 1 shift 1
echo "Container has been Initialized" __log_info "Container has been initialized"
exit 0 exit 0
;; ;;
tail) tail)
@@ -451,7 +542,7 @@ logs)
clean) clean)
log_files="$(find "/data/logs" -type f)" log_files="$(find "/data/logs" -type f)"
for log in "${log_files[@]}"; do for log in "${log_files[@]}"; do
echo "clearing $log" __log_info "Clearing log file: $log"
printf '' >$log printf '' >$log
done done
;; ;;
@@ -464,7 +555,7 @@ logs)
cron) cron)
shift 1 shift 1
__cron "$@" & __cron "$@" &
echo "cron script is running with pid: $!" __log_info "Cron script is running with PID: $!"
exit exit
;; ;;
# backup data and config dirs # backup data and config dirs
@@ -492,7 +583,7 @@ healthcheck)
[ "$healthEnabled" = "yes" ] || exit 0 [ "$healthEnabled" = "yes" ] || exit 0
if [ -d "/run/healthcheck" ] && [ "$(ls -A "/run/healthcheck" | wc -l)" -ne 0 ]; then if [ -d "/run/healthcheck" ] && [ "$(ls -A "/run/healthcheck" | wc -l)" -ne 0 ]; then
for service in /run/healthcheck/*; do for service in /run/healthcheck/*; do
name=$(basename -- $service) name="${service##*/}"
services+="$name " services+="$name "
done done
fi fi

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# shellcheck shell=sh # shellcheck shell=sh
# shellcheck disable=SC2016 # shellcheck disable=SC2016
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
USER_UID="$(id -u)" USER_UID="$(id -u)"
USER_GID="$(id -g)" USER_GID="$(id -g)"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -x "$(command -v apt 2>/dev/null)" ]; then if [ -x "$(command -v apt 2>/dev/null)" ]; then
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
pkmgr_cmd="apt" pkmgr_cmd="apt"
@@ -58,7 +58,7 @@ else
pkmgr_update_cmd="$pkmgr_cmd" pkmgr_update_cmd="$pkmgr_cmd"
pkmgr_install_cmd="$pkmgr_cmd" pkmgr_install_cmd="$pkmgr_cmd"
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
if [ -f "/config/pkmgr/settings.conf" ]; then if [ -f "/config/pkmgr/settings.conf" ]; then
. "/config/pkmgr/settings.conf" . "/config/pkmgr/settings.conf"
elif [ -f "/etc/pkmgr/settings.conf" ]; then elif [ -f "/etc/pkmgr/settings.conf" ]; then
@@ -73,9 +73,9 @@ pkmgr_install_cmd="$pkmgr_install_cmd"
pkmgr_mkcache_cmd="$pkmgr_mkcache_cmd" pkmgr_mkcache_cmd="$pkmgr_mkcache_cmd"
EEOF EEOF
fi fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
[ -n "$pkmgr_cmd" ] || { echo "Can not determine the package manager" && exit 1; } [ -n "$pkmgr_cmd" ] || { echo "Can not determine the package manager" && exit 1; }
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
case "$1" in case "$1" in
pip) pip)
shift 1 shift 1
@@ -103,7 +103,7 @@ install)
[ -n "$1" ] || exit 0 [ -n "$1" ] || exit 0
[ "$USER_UID" -eq 0 ] || [ "$USER" = "root" ] || pkmgr_install_cmd="sudo $pkmgr_install_cmd" [ "$USER_UID" -eq 0 ] || [ "$USER" = "root" ] || pkmgr_install_cmd="sudo $pkmgr_install_cmd"
if [ -f "$1" ]; then if [ -f "$1" ]; then
install_list="$(cat "$1")" install_list="$(tr '\n' ' ' < "$1")"
else else
install_list="$*" install_list="$*"
fi fi
@@ -138,5 +138,6 @@ clean)
exit $? exit $?
;; ;;
esac esac
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# end # end

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# casjaysdevdocker/apprise - launches gunicorn (background) then nginx (foreground).
#
# Single command for the framework's EXEC_CMD_BIN slot.
set -e
# Runtime dirs
mkdir -p /run/apprise /tmp/apprise /data/logs/apprise \
/config/apprise/store /config/apprise/attach /config/apprise/plugin
chmod 1777 /tmp/apprise
# Apprise-API env knobs (defaults; overridable via /config/env/apprise.sh)
export APPRISE_CONFIG_DIR="${APPRISE_CONFIG_DIR:-/config/apprise/store}"
export APPRISE_ATTACH_DIR="${APPRISE_ATTACH_DIR:-/config/apprise/attach}"
export APPRISE_PLUGIN_PATHS="${APPRISE_PLUGIN_PATHS:-/config/apprise/plugin}"
export PYTHONPATH="${PYTHONPATH:-/usr/local/share/apprise-api/webapp}"
export DJANGO_SETTINGS_MODULE="${DJANGO_SETTINGS_MODULE:-core.settings}"
# Resolve the gunicorn binary; Alpine's py3-gunicorn installs to /usr/bin/gunicorn
GUNICORN_BIN="$(command -v gunicorn || true)"
if [ -z "$GUNICORN_BIN" ]; then
echo "gunicorn not found in PATH" >&2
exit 2
fi
# Start gunicorn in background (only if not already running)
if ! pgrep -f 'gunicorn.*core.wsgi' >/dev/null 2>&1; then
cd /usr/local/share/apprise-api/webapp
"$GUNICORN_BIN" \
--bind unix:/run/apprise/gunicorn.sock \
--workers "${GUNICORN_WORKERS:-4}" \
--timeout "${GUNICORN_TIMEOUT:-120}" \
--worker-tmp-dir /dev/shm \
--access-logfile /data/logs/apprise/gunicorn-access.log \
--error-logfile /data/logs/apprise/gunicorn-err.log \
core.wsgi:application \
>>/data/logs/apprise/gunicorn.log 2>>/data/logs/apprise/gunicorn-err.log &
# wait up to 30s for the unix socket
i=0
while [ ! -S /run/apprise/gunicorn.sock ] && [ $i -lt 30 ]; do
sleep 1
i=$((i + 1))
done
if [ ! -S /run/apprise/gunicorn.sock ]; then
echo "gunicorn failed to create socket /run/apprise/gunicorn.sock after 30s" >&2
cat /data/logs/apprise/gunicorn-err.log >&2 || true
exit 3
fi
# Allow nginx (running as 'nginx' user) to connect to the gunicorn socket
# (gunicorn creates it owned by the launching user, default mode 660).
chmod 666 /run/apprise/gunicorn.sock
fi
# nginx in foreground (nginx.conf has daemon off;)
exec /usr/sbin/nginx -c /etc/nginx/nginx.conf

View File

@@ -0,0 +1,263 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# casjaysdevdocker/apprise - nginx + gunicorn + apprise-api init.d
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# shellcheck disable=SC1003,SC2016,SC2031,SC2120,SC2155,SC2199,SC2317
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
trap 'retVal=$?;[ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ] && rm -Rf "$SERVICE_PID_FILE";exit $retVal' SIGINT SIGTERM EXIT
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[ -f "/config/.debug" ] && [ -z "$DEBUGGER_OPTIONS" ] && export DEBUGGER_OPTIONS="$(<"/config/.debug")" || DEBUGGER_OPTIONS="${DEBUGGER_OPTIONS:-}"
{ [ "$DEBUGGER" = "on" ] || [ -f "/config/.debug" ]; } && echo "Enabling debugging" && set -xo pipefail -x$DEBUGGER_OPTIONS && export DEBUGGER="on" || set -o pipefail
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
export PATH="/usr/local/etc/docker/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin"
SCRIPT_FILE="$0"
SERVICE_NAME="apprise"
__script_exit() {
local exit_code="${1:-0}"
if [ "${BASH_SOURCE[0]}" != "${0}" ]; then return "$exit_code"; else exit "$exit_code"; fi
}
SCRIPT_NAME="$(basename -- "$SCRIPT_FILE" 2>/dev/null)"
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
if [ -f "/usr/local/etc/docker/functions/entrypoint.sh" ]; then
. "/usr/local/etc/docker/functions/entrypoint.sh"
fi
for set_env in "/root/env.sh" "/usr/local/etc/docker/env"/*.sh "/config/env"/*.sh; do
[ -f "$set_env" ] && . "$set_env"
done
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
START_SCRIPT="/usr/local/etc/docker/exec/$SERVICE_NAME"
RESET_ENV="no"
WWW_ROOT_DIR="/usr/local/share/apprise-api/webapp"
DATA_DIR="/data/apprise"
CONF_DIR="/config/nginx"
ETC_DIR="/etc/nginx"
VAR_DIR=""
TMP_DIR="/tmp/apprise"
RUN_DIR="/run/apprise"
LOG_DIR="/data/logs/apprise"
WORK_DIR=""
SERVICE_PORT="8000"
RUNAS_USER="root"
SERVICE_USER="root"
SERVICE_GROUP="root"
RANDOM_PASS_USER=""
RANDOM_PASS_ROOT=""
SERVICE_UID="0"
SERVICE_GID="0"
EXEC_CMD_BIN='/usr/local/etc/docker/bin/start-apprise'
EXEC_CMD_ARGS=''
EXEC_PRE_SCRIPT=''
IS_WEB_SERVER="yes"
IS_DATABASE_SERVICE="no"
USES_DATABASE_SERVICE="no"
DATABASE_SERVICE_TYPE=""
PRE_EXEC_MESSAGE="Apprise REST API listening on http://localhost:${SERVICE_PORT:-8000}/"
POST_EXECUTE_WAIT_TIME="1"
PATH="$PATH:."
IP4_ADDRESS="$(__get_ip4)"
IP6_ADDRESS="$(__get_ip6)"
ROOT_FILE_PREFIX="/config/secure/auth/root"
USER_FILE_PREFIX="/config/secure/auth/user"
root_user_name="${APPRISE_ROOT_USER_NAME:-}"
root_user_pass="${APPRISE_ROOT_PASS_WORD:-}"
user_name="${APPRISE_USER_NAME:-}"
user_pass="${APPRISE_USER_PASS_WORD:-}"
[ -f "/config/env/apprise.script.sh" ] && . "/config/env/apprise.script.sh"
[ -f "/config/env/apprise.sh" ] && . "/config/env/apprise.sh"
ADD_APPLICATION_FILES=""
ADD_APPLICATION_DIRS="/usr/local/share/apprise-api /run/apprise /tmp/apprise /config/apprise/store /config/apprise/attach /config/apprise/plugin"
APPLICATION_FILES="$LOG_DIR/$SERVICE_NAME.log"
APPLICATION_DIRS="$ETC_DIR $CONF_DIR $LOG_DIR $TMP_DIR $RUN_DIR $VAR_DIR"
ADDITIONAL_CONFIG_DIRS="/config/apprise"
CMD_ENV="APPRISE_CONFIG_DIR=/config/apprise/store,APPRISE_ATTACH_DIR=/config/apprise/attach,APPRISE_PLUGIN_PATHS=/config/apprise/plugin"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__run_precopy() {
local hostname=${HOSTNAME}
if builtin type -t __run_precopy_local | grep -q 'function'; then __run_precopy_local; fi
}
__execute_prerun() {
local hostname=${HOSTNAME}
mkdir -p /run/apprise /tmp/apprise /data/logs/apprise \
/config/apprise/store /config/apprise/attach /config/apprise/plugin \
/config/apprise/conf.d
chmod 1777 /tmp/apprise 2>/dev/null || true
if builtin type -t __execute_prerun_local | grep -q 'function'; then __execute_prerun_local; fi
}
__run_pre_execute_checks() {
local exitStatus=0
__banner "Running preexecute check for $SERVICE_NAME"
# Validate nginx config syntax
nginx -t -c /etc/nginx/nginx.conf 2>&1 | head -20 || exitStatus=$?
__banner "Finished preexecute check for $SERVICE_NAME: Status $exitStatus"
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
if builtin type -t __run_pre_execute_checks_local | grep -q 'function'; then __run_pre_execute_checks_local; fi
return $exitStatus
}
__update_conf_files() {
local exitCode=0
local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}"
__replace "REPLACE_TZ" "${TZ:-UTC}" "/etc/nginx/nginx.conf" 2>/dev/null || true
if builtin type -t __update_conf_files_local | grep -q 'function'; then __update_conf_files_local; fi
return $exitCode
}
__pre_execute() {
local exitCode=0
sleep 2
if builtin type -t __pre_execute_local | grep -q 'function'; then __pre_execute_local; fi
return $exitCode
}
__post_execute() {
local pid=""
local retVal=0
local ctime=${POST_EXECUTE_WAIT_TIME:-1}
local waitTime=$((ctime * 60))
sleep $waitTime
(
__banner "Running post commands for $SERVICE_NAME"
# Drop the sample yaml into /config/apprise/ if not already present
if [ -f /usr/local/share/template-files/config/apprise/apprise.yml.sample ] && \
[ ! -f /config/apprise/apprise.yml.sample ]; then
cp -f /usr/local/share/template-files/config/apprise/apprise.yml.sample /config/apprise/ 2>/dev/null || true
fi
__banner "Finished post commands for $SERVICE_NAME: Status $retVal"
) 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" &
pid=$!
if builtin type -t __post_execute_local | grep -q 'function'; then __post_execute_local; fi
return $retVal
}
__pre_message() {
local exitCode=0
[ -n "$PRE_EXEC_MESSAGE" ] && eval echo "$PRE_EXEC_MESSAGE"
if builtin type -t __pre_message_local | grep -q 'function'; then __pre_message_local; fi
return $exitCode
}
__update_ssl_conf() {
local exitCode=0
if builtin type -t __update_ssl_conf_local | grep -q 'function'; then __update_ssl_conf_local; fi
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
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Generated by 99-apprise.sh - edit to override defaults
#APPRISE_WORKER_COUNT=""
#APPRISE_WORKER_TIMEOUT="300"
#APPRISE_BASE_URL=""
#APPRISE_STATEFUL_MODE="simple"
EOF
fi
if [ ! -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" ]; then
__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; }
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
}
__run_start_script() {
local runExitCode=0
local cmd="$(eval echo "${EXEC_CMD_BIN:-}")"
local args="$(eval echo "${EXEC_CMD_ARGS:-}")"
local name="$(eval echo "${EXEC_CMD_NAME:-}")"
local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}"
[ -f "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" ] && . "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh"
if [ ! -x "$cmd" ]; then echo "$cmd is not executable" >&2; return 2; fi
if __proc_check "nginx"; then echo "nginx already running" >&2; return 0; fi
echo "Starting $cmd $args" | tee -a -p "/data/logs/init.txt"
su_cmd touch "$SERVICE_PID_FILE"
if [ ! -f "$START_SCRIPT" ]; then
cat <<EOF >"$START_SCRIPT"
#!/usr/bin/env bash
trap 'exitCode=\$?;if [ \$exitCode -ne 0 ] && [ -f "\$SERVICE_PID_FILE" ]; then rm -Rf "\$SERVICE_PID_FILE"; fi; exit \$exitCode' EXIT
set -Eeo pipefail
retVal=10
cmd="$cmd"
SERVICE_NAME="$SERVICE_NAME"
SERVICE_PID_FILE="$SERVICE_PID_FILE"
$cmd $args 2>>"/dev/stderr" >>"$LOG_DIR/$SERVICE_NAME.log" &
execPid=\$!
sleep 3
checkPID="\$(ps ax | awk '{print \$1}' | grep -v grep | grep "\$execPid$" || false)"
[ -n "\$execPid" ] && [ -n "\$checkPID" ] && echo "\$execPid" >"\$SERVICE_PID_FILE" && retVal=0 || retVal=10
[ "\$retVal" = 0 ] && echo "\$cmd has been started" || echo "Failed to start $cmd $args" >&2
exit \$retVal
EOF
fi
[ -x "$START_SCRIPT" ] || chmod 755 -Rf "$START_SCRIPT"
[ "$CONTAINER_INIT" = "yes" ] || eval sh -c "$START_SCRIPT"
runExitCode=$?
return $runExitCode
}
__run_secure_function() {
local filesperms
for filesperms in "${USER_FILE_PREFIX}"/* "${ROOT_FILE_PREFIX}"/*; do
[ -e "$filesperms" ] && { chmod -Rf 600 "$filesperms"; chown -Rf $SERVICE_USER:$SERVICE_USER "$filesperms" 2>/dev/null; }
done 2>/dev/null
unset filesperms
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__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"
SERVICE_EXIT_CODE=0
EXEC_CMD_NAME="$(basename -- "$EXEC_CMD_BIN")"
SERVICE_PID_FILE="/run/init.d/$EXEC_CMD_NAME.pid"
SERVICE_PID_NUMBER="$(__pgrep)"
__check_service "$1" && SERVICE_IS_RUNNING=yes
[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR"
[ -d "$RUN_DIR" ] || mkdir -p "$RUN_DIR"
[ -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="8000"
[ -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ] && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh"
sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}"
__create_service_env
__init_config_etc
__execute_prerun
__create_service_user "$SERVICE_USER" "$SERVICE_GROUP" "${WORK_DIR:-/home/$SERVICE_USER}" "${SERVICE_UID:-}" "${SERVICE_GID:-}"
__set_user_group_id $SERVICE_USER ${SERVICE_UID:-} ${SERVICE_GID:-}
__setup_directories
__switch_to_user
__init_working_dir
__pre_message
__update_ssl_conf
__update_ssl_certs
__run_secure_function
__run_precopy
for config_2_etc in $CONF_DIR $ADDITIONAL_CONFIG_DIRS; do
__initialize_system_etc "$config_2_etc" 2>/dev/stderr | tee -p -a "/data/logs/init.txt"
done
__initialize_replace_variables "$ETC_DIR" "$CONF_DIR" "$ADDITIONAL_CONFIG_DIRS" "$WWW_ROOT_DIR"
__update_conf_files
__pre_execute
__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=$?
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}"; [ -s "$SERVICE_PID_FILE" ] || rm -Rf "$SERVICE_PID_FILE"; fi
SERVICE_EXIT_CODE=0
fi
__post_execute 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" &
__banner "Initializing of $SERVICE_NAME has completed with statusCode: $SERVICE_EXIT_CODE" | tee -p -a "/data/logs/entrypoint.log" "/data/logs/init.txt"
__script_exit $SERVICE_EXIT_CODE

View File

@@ -1,631 +0,0 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202308281848-git
# @@Author : Jason Hempstead
# @@Contact : jason@casjaysdev.pro
# @@License : WTFPL
# @@ReadME : zz-default.sh --help
# @@Copyright : Copyright: (c) 2023 Jason Hempstead, Casjays Developments
# @@Created : Monday, Aug 28, 2023 18:48 EDT
# @@File : zz-default.sh
# @@Description :
# @@Changelog : New script
# @@TODO : Better documentation
# @@Other :
# @@Resource :
# @@Terminal App : no
# @@sudo/root : no
# @@Template : other/start-service
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# shellcheck disable=SC2016
# shellcheck disable=SC2031
# shellcheck disable=SC2120
# shellcheck disable=SC2155
# shellcheck disable=SC2199
# shellcheck disable=SC2317
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
[ "$DEBUGGER" = "on" ] && echo "Enabling debugging" && set -o pipefail -x$DEBUGGER_OPTIONS || set -o pipefail
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
printf '%s\n' "# - - - Initializing apprise - - - #"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SERVICE_NAME="apprise"
# 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
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SCRIPT_NAME="$(basename "$0" 2>/dev/null)"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
export PATH="/usr/local/etc/docker/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# run trap command on exit
trap 'retVal=$?;[ "$SERVICE_IS_RUNNING" != "true" ] && [ -f "$SERVICE_PID_FILE" ] && rm -Rf "$SERVICE_PID_FILE";exit $retVal' SIGINT SIGTERM EXIT
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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
[ -f "$set_env" ] && . "$set_env"
done
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Run any pre-execution checks
__run_pre_execute_checks() {
local exitStatus=0
true
exitStatus=$?
if [ $exitStatus -ne 0 ]; then
echo "The pre-execution check has failed"
exit ${exitStatus:-20}
fi
return $exitStatus
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Custom functions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Reset environment before executing service
RESET_ENV="yes"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Show message before execute
PRE_EXEC_MESSAGE=""
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# set the database directory
DATABASE_DIR="${DATABASE_DIR_APPRISE:-/data/db/sqlite}"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Set webroot
WWW_ROOT_DIR="/usr/local/share/httpd/default"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Default predefined variables
DATA_DIR="/data/apprise" # set data directory
CONF_DIR="/config/apprise" # set config directory
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# set the containers etc directory
ETC_DIR="/etc/apprise"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TMP_DIR="/tmp/apprise"
RUN_DIR="/run/apprise" # set scripts pid dir
LOG_DIR="/data/logs/apprise" # set log directory
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Set the working dir
WORK_DIR="" # set working directory
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Where to save passwords to
ROOT_FILE_PREFIX="/config/secure/auth/root" # directory to save username/password for root user
USER_FILE_PREFIX="/config/secure/auth/user" # directory to save username/password for normal user
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# root/admin user info password/random]
root_user_name="${APPRISE_ROOT_USER_NAME:-}" # root user name
root_user_pass="${APPRISE_ROOT_PASS_WORD:-}" # root user password
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Normal user info [password/random]
user_name="${APPRISE_USER_NAME:-}" # normal user name
user_pass="${APPRISE_USER_PASS_WORD:-}" # normal user password
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Overwrite variables from files
__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")"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# port which service is listening on
SERVICE_PORT=""
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# User to use to launch service - IE: postgres
RUNAS_USER="root" # normally root
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# User and group in which the service switches to - IE: nginx,apache,mysql,postgres
SERVICE_USER="apprise" # execute command as another user
SERVICE_GROUP="apprise" # Set the service group
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Set user and group ID
SERVICE_UID="0" # set the user id
SERVICE_GID="0" # set the group id
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# execute command variables - keep single quotes variables will be expanded later
EXEC_CMD_BIN='apprise' # command to execute
EXEC_CMD_ARGS='' # command arguments
EXEC_PRE_SCRIPT='' # execute script before
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Is this service a web server
IS_WEB_SERVER="no"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Is this service a database server
IS_DATABASE_SERVICE="no"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Load variables from config
[ -f "$CONF_DIR/env/apprise.sh" ] && . "$CONF_DIR/env/apprise.sh"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Additional predefined variables
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Additional variables
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Specifiy custom directories to be created
ADD_APPLICATION_FILES=""
ADD_APPLICATION_DIRS=""
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
APPLICATION_FILES="$LOG_DIR/apprise.log"
APPLICATION_DIRS="$RUN_DIR $ETC_DIR $CONF_DIR $LOG_DIR $TMP_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
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# use this function to update config files - IE: change port
__update_conf_files() {
local exitCode=0 # default exit code
local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" # set hostname
# CD into temp to bybass any permission errors
cd /tmp || false # lets keep shellcheck happy by adding false
# delete files
#__rm ""
# Initialize templates
if [ -e "$DEFAULT_DATA_DIR/$SERVICE_NAME" ]; then
if [ -d "$DEFAULT_DATA_DIR/$SERVICE_NAME" ]; then
mkdir -p "$DATA_DIR"
__copy_templates "$DEFAULT_DATA_DIR/$SERVICE_NAME/." "$DATA_DIR/"
else
__copy_templates "$DEFAULT_DATA_DIR/$SERVICE_NAME" "$DATA_DIR"
fi
fi
if [ -e "$DEFAULT_CONF_DIR/$SERVICE_NAME" ]; then
if [ -d "$DEFAULT_CONF_DIR/$SERVICE_NAME" ]; then
mkdir -p "$CONF_DIR"
__copy_templates "$DEFAULT_CONF_DIR/$SERVICE_NAME/." "$CONF_DIR/"
else
__copy_templates "$DEFAULT_CONF_DIR/$SERVICE_NAME" "$CONF_DIR"
fi
elif [ -e "$ETC_DIR" ]; then
if [ -d "$ETC_DIR" ]; then
mkdir -p "$CONF_DIR"
__copy_templates "$ETC_DIR/." "$CONF_DIR/"
else
__copy_templates "$ETC_DIR" "$CONF_DIR"
fi
fi
# define actions
# create default directories
for filedirs in $ADD_APPLICATION_DIRS $APPLICATION_DIRS; do
if [ -n "$filedirs" ] && [ ! -d "$filedirs" ]; then
(
echo "Creating directory $filedirs with permissions 777"
mkdir -p "$filedirs" && chmod -Rf 777 "$filedirs"
) |& tee -a "$LOG_DIR/init.txt" &>/dev/null
fi
done
# create default files
for application_files in $ADD_APPLICATION_FILES $APPLICATION_FILES; do
if [ -n "$application_files" ] && [ ! -e "$application_files" ]; then
(
echo "Creating file $application_files with permissions 777"
touch "$application_files" && chmod -Rf 777 "$application_files"
) |& tee -a "$LOG_DIR/init.txt" &>/dev/null
fi
done
# create directories if variable is yes"
if [ "$IS_WEB_SERVER" = "yes" ]; then
APPLICATION_DIRS="$APPLICATION_DIRS $WWW_ROOT_DIR"
if __is_dir_empty "$WWW_ROOT_DIR" || [ ! -d "$WWW_ROOT_DIR" ]; then
(echo "Creating directory $WWW_ROOT_DIR with permissions 777" && mkdir -p "$WWW_ROOT_DIR" && chmod -f 777 "$WWW_ROOT_DIR") |& tee -a "$LOG_DIR/init.txt" &>/dev/null
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Copy html files
__initialize_www_root
__initialize_web_health "$WWW_ROOT_DIR"
fi
if [ "$IS_DATABASE_SERVICE" = "yes" ]; then
APPLICATION_DIRS="$APPLICATION_DIRS $DATABASE_DIR"
if __is_dir_empty "$DATABASE_DIR" || [ ! -d "$DATABASE_DIR" ]; then
(echo "Creating directory $DATABASE_DIR with permissions 777" && mkdir -p "$DATABASE_DIR" && chmod -f 777 "$DATABASE_DIR") |& tee -a "$LOG_DIR/init.txt" &>/dev/null
fi
fi
# replace variables
# __replace "" "" "$CONF_DIR/apprise.conf"
# replace variables recursively
# __find_replace "" "" "$CONF_DIR"
# execute if directory is empty
#__is_dir_empty "" && true || false
# custom commands
# unset unneeded variables
unset application_files filedirs
return $exitCode
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# function to run before executing
__pre_execute() {
local exitCode=0 # default exit code
local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" # set hostname
# define commands
# execute if directories is empty
#__is_dir_empty "" && true || false
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# create user if needed
__create_service_user "$SERVICE_USER" "$SERVICE_GROUP" "${WORK_DIR:-/home/$SERVICE_USER}" "${SERVICE_UID:-3000}" "${SERVICE_GID:-3000}"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Modify user if needed
__set_user_group_id $SERVICE_USER ${SERVICE_UID:-3000} ${SERVICE_GID:-3000}
# Run Custom command
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Copy /config to /etc
for config_2_etc in $CONF_DIR $ADDITIONAL_CONFIG_DIRS; do
__initialize_system_etc "$config_2_etc" |& tee -a "$LOG_DIR/init.txt" &>/dev/null |& tee -a "$LOG_DIR/init.txt" &>/dev/null
done
unset config_2_etc ADDITIONAL_CONFIG_DIRS
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# set user on files/folders
if [ -n "$SERVICE_USER" ] && [ "$SERVICE_USER" != "root" ]; then
if grep -sq "^$SERVICE_USER:" "/etc/passwd"; then
for permissions in $ADD_APPLICATION_DIRS $APPLICATION_DIRS; do
if [ -n "$permissions" ] && [ -e "$permissions" ]; then
(chown -Rf $SERVICE_USER:${SERVICE_GROUP:-$SERVICE_USER} "$permissions" && echo "changed ownership on $permissions to user:$SERVICE_USER and group:${SERVICE_GROUP:-$SERVICE_USER}") |& tee -a "$LOG_DIR/init.txt" &>/dev/null
fi
done
fi
fi
if [ -n "$SERVICE_GROUP" ] && [ "$SERVICE_GROUP" != "root" ]; then
if grep -sq "^$SERVICE_GROUP:" "/etc/group"; then
for permissions in $ADD_APPLICATION_DIRS $APPLICATION_DIRS; do
if [ -n "$permissions" ] && [ -e "$permissions" ]; then
(chgrp -Rf $SERVICE_GROUP "$permissions" && echo "changed group ownership on $permissions to group $SERVICE_GROUP") |& tee -a "$LOG_DIR/init.txt" &>/dev/null
fi
done
fi
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Replace the applications user and group
__find_replace "REPLACE_WWW_USER" "${SERVICE_USER:-root}" "$ETC_DIR"
__find_replace "REPLACE_WWW_GROUP" "${SERVICE_GROUP:-${SERVICE_USER:-root}}" "$ETC_DIR"
__find_replace "REPLACE_APP_USER" "${SERVICE_USER:-root}" "$ETC_DIR"
__find_replace "REPLACE_APP_GROUP" "${SERVICE_GROUP:-${SERVICE_USER:-root}}" "$ETC_DIR"
# Replace variables
__initialize_replace_variables "$ETC_DIR"
__initialize_replace_variables "$CONF_DIR"
__initialize_replace_variables "$WWW_ROOT_DIR"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Run checks
__run_pre_execute_checks
# unset unneeded variables
unset filesperms filename
# Lets wait a few seconds before continuing
sleep 10
return $exitCode
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# function to run after executing
__post_execute() {
local exitCode=0 # default exit code
local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" # set hostname
sleep 60 # how long to wait before executing
echo "Running post commands" # message
# execute commands
(
sleep 20
true
) |& tee -a "$LOG_DIR/init.txt" &>/dev/null &
return $exitCode
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# use this function to update config files - IE: change port
__pre_message() {
local exitCode=0
[ -n "$user_name" ] && echo "username: $user_name" && echo "$user_name" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_name"
[ -n "$user_pass" ] && __printf_space "40" "password:" "saved to ${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" && echo "$user_pass" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_pass"
[ -n "$root_user_name" ] && echo "root username: $root_user_name" && echo "$root_user_name" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name"
[ -n "$root_user_pass" ] && __printf_space "40" "root password:" "saved to ${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" && echo "$root_user_pass" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass"
return $exitCode
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# use this function to setup ssl support
__update_ssl_conf() {
local exitCode=0
local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" # set hostname
return $exitCode
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__create_service_env() {
cat <<EOF | tee "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" &>/dev/null
#ENV_SERVICE_UID="${ENV_UID:-${ENV_SERVICE_UID:-$SERVICE_UID}}" # Set UID
#ENV_SERVICE_GID="${ENV_GID:-${ENV_SERVICE_GID:-$SERVICE_GID}}" # Set GID
#ENV_RUNAS_USER="${ENV_RUNAS_USER:-$RUNAS_USER}" # normally root
#ENV_WORKDIR="${ENV_WORK_DIR:-$WORK_DIR}" # change to directory
#ENV_WWW_DIR="${ENV_WWW_ROOT_DIR:-$WWW_ROOT_DIR}" # set default web dir
#ENV_ETC_DIR="${ENV_ETC_DIR:-$ETC_DIR}" # set default etc dir
#ENV_DATA_DIR="${ENV_DATA_DIR:-$DATA_DIR}" # set default data dir
#ENV_CONF_DIR="${ENV_CONF_DIR:-$CONF_DIR}" # set default config dir
#ENV_DATABASE_DIR="${ENV_DATABASE_DIR:-$DATABASE_DIR}" # set database dir
#ENV_SERVICE_USER="${ENV_SERVICE_USER:-$SERVICE_USER}" # execute command as another user
#ENV_SERVICE_PORT="${ENV_SERVICE_PORT:-$SERVICE_PORT}" # port which service is listening on
#ENV_EXEC_PRE_SCRIPT="${ENV_EXEC_PRE_SCRIPT:-$EXEC_PRE_SCRIPT}" # execute before commands
#ENV_EXEC_CMD_BIN="${ENV_EXEC_CMD_BIN:-$EXEC_CMD_BIN}" # command to execute
#ENV_EXEC_CMD_ARGS="${ENV_EXEC_CMD_ARGS:-$EXEC_CMD_ARGS}" # command arguments
#ENV_EXEC_CMD_NAME="$(basename "$EXEC_CMD_BIN")" # set the binary name
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# root/admin user info [password/random]
#ENV_ROOT_USER_NAME="${ENV_ROOT_USER_NAME:-$APPRISE_ROOT_USER_NAME}" # root user name
#ENV_ROOT_USER_PASS="${ENV_ROOT_USER_NAME:-$APPRISE_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:-$APPRISE_USER_NAME}" #
#ENV_USER_PASS="${ENV_USER_PASS:-$APPRISE_USER_PASS_WORD}" #
#user_name="${ENV_USER_NAME:-$user_name}" # normal user name
#user_pass="${ENV_USER_PASS:-$user_pass}" # normal user password
EOF
__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" || return 1
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# script to start server
__run_start_script() {
local runExitCode=0
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 "")" # expand variables
local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" # set hostname
[ -f "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" ] && . "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh"
if [ -z "$cmd" ]; then
__post_execute 2>"/dev/stderr" |& tee -a "$LOG_DIR/init.txt" &>/dev/null
echo "Initializing $SCRIPT_NAME has completed"
else
# ensure the command exists
if [ ! -x "$cmd" ]; then
echo "$name is not a valid executable"
exit 2
fi
# set working directories
[ -z "$home" ] && home="${workdir:-/tmp/docker}"
[ "$home" = "/root" ] && home="/tmp/docker"
[ "$home" = "$workdir" ] && workdir=""
# create needed directories
[ -n "$home" ] && { [ -d "$home" ] || { mkdir -p "$home" && chown -Rf $SERVICE_USER:$SERVICE_GROUP "$home"; }; }
[ -n "$workdir" ] && { [ -d "$workdir" ] || { mkdir -p "$workdir" && chown -Rf $SERVICE_USER:$SERVICE_GROUP "$workdir"; }; }
[ "$user" != "root " ] && [ -d "$home" ] && chmod -f 777 "$home"
[ "$user" != "root " ] && [ -d "$workdir" ] && chmod -f 777 "$workdir"
# check and exit if already running
if __proc_check "$name" || __proc_check "$cmd"; then
echo "$name is already running" >&2
exit 0
else
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# show message if env exists
if [ -n "$cmd_exec" ]; then
[ -n "$SERVICE_USER" ] && echo "Setting up $cmd_exec to run as $SERVICE_USER" || SERVICE_USER="root"
[ -n "$SERVICE_PORT" ] && echo "$name will be running on $SERVICE_PORT" || SERVICE_PORT=""
fi
if [ -n "$pre" ] && [ -n "$(command -v "$pre" 2>/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
__cd "${workdir:-$home}"
echo "$message"
su_cmd touch "$SERVICE_PID_FILE"
__post_execute |& tee -a "$LOG_DIR/init.txt" &>/dev/null &
if [ "$RESET_ENV" = "yes" ]; then
su_cmd env -i HOME="$home" LC_CTYPE="$lc_type" PATH="$path" HOSTNAME="$sysname" USER="${SERVICE_USER:-$RUNAS_USER}" $extra_env sh -c "$cmd_exec" ||
eval env -i HOME="$home" LC_CTYPE="$lc_type" PATH="$path" HOSTNAME="$sysname" USER="${SERVICE_USER:-$RUNAS_USER}" $extra_env sh -c "$cmd_exec" ||
return 10
else
eval "$cmd_exec" || return 10
fi
fi
fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# username and password actions
__run_secure_function() {
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"
fi
done |& tee -a "$LOG_DIR/init.txt" &>/dev/null
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"
fi
done |& tee -a "$LOG_DIR/init.txt" &>/dev/null
fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# simple cd function
__cd() { mkdir -p "$1" && builtin cd "$1" || exit 1; }
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# process check functions
__pcheck() { [ -n "$(type -P pgrep 2>/dev/null)" ] && pgrep -x "$1" &>/dev/null && return 0 || return 10; }
__pgrep() { __pcheck "${1:-$EXEC_CMD_BIN}" || __ps aux 2>/dev/null | grep -Fw " ${1:-$EXEC_CMD_BIN}" | grep -qv ' grep' | grep '^' && return 0 || return 10; }
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# check if process is already running
__proc_check() {
cmd_bin="$(type -P "${1:-$EXEC_CMD_BIN}")"
cmd_name="$(basename "${cmd_bin:-$EXEC_CMD_NAME}")"
if __pgrep "$cmd_bin" || __pgrep "$cmd_name"; then
SERVICE_IS_RUNNING="true"
touch "$SERVICE_PID_FILE"
echo "$cmd_name is already running"
return 0
else
return 1
fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Allow ENV_ variable - Import env file
__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SERVICE_EXIT_CODE=0 # default exit code
SERVICE_USER="${ENV_SERVICE_USER:-$SERVICE_USER}" # execute command as another user
SERVICE_UID="${ENV_UID:-${ENV_SERVICE_UID:-$SERVICE_UID}}" # Set UID
SERVICE_GID="${ENV_GID:-${ENV_SERVICE_GID:-$SERVICE_GID}}" # Set GID
SERVICE_PORT="${ENV_SERVICE_PORT:-$SERVICE_PORT}" # port which service is listening on
RUNAS_USER="${ENV_RUNAS_USER:-$RUNAS_USER}" # normally root
WORK_DIR="${ENV_WORK_DIR:-$WORK_DIR}" # change to directory
WWW_ROOT_DIR="${ENV_WWW_ROOT_DIR:-$WWW_ROOT_DIR}" # set default web dir
ETC_DIR="${ENV_ETC_DIR:-$ETC_DIR}" # set default etc dir
DATA_DIR="${ENV_DATA_DIR:-$DATA_DIR}" # set default data dir
CONF_DIR="${ENV_CONF_DIR:-$CONF_DIR}" # set default config dir
DATABASE_DIR="${ENV_DATABASE_DIR:-$DATABASE_DIR}" # set database dir
PRE_EXEC_MESSAGE="${ENV_PRE_EXEC_MESSAGE:-$PRE_EXEC_MESSAGE}" # Show message before execute
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# application specific
EXEC_PRE_SCRIPT="${ENV_EXEC_PRE_SCRIPT:-$EXEC_PRE_SCRIPT}" # Pre
EXEC_CMD_BIN="${ENV_EXEC_CMD_BIN:-$EXEC_CMD_BIN}" # command to execute
EXEC_CMD_NAME="$(basename "$EXEC_CMD_BIN")" # set the binary name
SERVICE_PID_FILE="/run/init.d/$EXEC_CMD_NAME.pid" # set the pid file location
EXEC_CMD_ARGS="${ENV_EXEC_CMD_ARGS:-$EXEC_CMD_ARGS}" # command arguments
SERVICE_PID_NUMBER="$(__pgrep)" # check if running
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")" # set full path
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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"; }
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[ "$IS_WEB_SERVER" = "yes" ] && RESET_ENV="yes"
[ "$IS_DATABASE_SERVICE" = "yes" ] && RESET_ENV="no"
[ -n "$RUNAS_USER" ] || RUNAS_USER="root"
[ -n "$SERVICE_USER" ] || SERVICE_USER="${RUNAS_USER:-root}"
[ -n "$SERVICE_GROUP" ] || SERVICE_GROUP="${RUNAS_USER:-root}"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Allow per init script usernames and passwords
__file_exists_with_content "$ETC_DIR/auth/user/name" && user_name="$(<"$ETC_DIR/auth/user/name")"
__file_exists_with_content "$ETC_DIR/auth/user/pass" && user_pass="$(<"$ETC_DIR/auth/user/pass")"
__file_exists_with_content "$ETC_DIR/auth/root/name" && root_user_name="$(<"$ETC_DIR/auth/root/name")"
__file_exists_with_content "$ETC_DIR/auth/root/pass" && root_user_pass="$(<"$ETC_DIR/auth/root/pass")"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Allow setting initial users and passwords via environment
user_name="${ENV_USER_NAME:-$user_name}"
user_pass="${ENV_USER_PASS:-$user_pass}"
root_user_name="${ENV_ROOT_USER_NAME:-$root_user_name}"
root_user_pass="${ENV_ROOT_USER_PASS:-$root_user_pass}"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Env vars from dockermgr script
SERVICE_UID="${ENV_PUID:-${PUID:-$SERVICE_UID}}"
SERVICE_GID="${ENV_PGID:-${PGID:-$SERVICE_GID}}"
EMAIL_RELAY="${ENV_EMAIL_RELAY:-$EMAIL_RELAY}"
EMAIL_ADMIN="${ENV_EMAIL_ADMIN:-$EMAIL_ADMIN}"
EMAIL_DOMAIN="${ENV_EMAIL_DOMAIN:-$EMAIL_DOMAIN}"
SERVICE_PROTOCOL="${ENV_CONTAINER_PROTOCOL:-$CONTAINER_PROTOCOL}"
WWW_ROOT_DIR="${CONTAINER_HTML_ENV:-${ENV_WWW_ROOT_DIR:-$WWW_ROOT_DIR}}"
DATABASE_DIR="${ENV_DATABASE_DIR_CUSTOM:-${DATABASE_DIR_CUSTOM:-${DATABASE_DIR_SQLITE:-$DATABASE_DIR}}}"
user_name="${ENV_DATABASE_USER_NORMAL:-${DATABASE_USER_NORMAL:-${CONTAINER_ENV_USER_NAME:-$user_name}}}"
user_pass="${ENV_DATABASE_PASS_NORMAL:-${DATABASE_PASS_NORMAL:-${CONTAINER_ENV_PASS_NAME:-$user_pass}}}"
root_user_name="${ENV_DATABASE_USER_ROOT:-${DATABASE_USER_ROOT:-$root_user_name}}"
root_user_pass="${ENV_DATABASE_PASS_ROOT:-${DATABASE_PASS_ROOT:-$root_user_pass}}"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# set password to random if variable is random
if [ "$user_pass" = "random" ]; then
user_pass="$(__random_password)"
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if [ "$root_user_pass" = "random" ]; then
root_user_pass="$(__random_password)"
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Allow variables via imports - Overwrite existing
[ -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ] && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Only run check
if [ "$1" = "check" ]; then
__proc_check "$EXEC_CMD_NAME" || __proc_check "$EXEC_CMD_BIN"
exit $?
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# set switch user command
if [ "$RUNAS_USER" = "root" ]; then
su_cmd() { eval "$@" || return 1; }
elif [ "$(builtin type -P gosu)" ]; then
su_cmd() { gosu $RUNAS_USER "$@" || return 1; }
elif [ "$(builtin type -P runuser)" ]; then
su_cmd() { runuser -u $RUNAS_USER "$@" || return 1; }
elif [ "$(builtin type -P sudo)" ]; then
su_cmd() { sudo -u $RUNAS_USER "$@" || return 1; }
elif [ "$(builtin type -P su)" ]; then
su_cmd() { su -s /bin/sh - $RUNAS_USER -c "$@" || return 1; }
else
su_cmd() { echo "Can not switch to $RUNAS_USER: attempting to run as root" && eval "$*" || return 1; }
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Change to working directory
[ "$HOME" = "/root" ] && [ "$RUNAS_USER" != "root" ] && __cd "/tmp" && echo "Changed to $PWD"
[ "$HOME" = "/root" ] && [ "$SERVICE_USER" != "root" ] && __cd "/tmp" && echo "Changed to $PWD"
[ -n "$WORK_DIR" ] && [ -n "$EXEC_CMD_BIN" ] && __cd "$WORK_DIR" && echo "Changed to $PWD"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# show init message
__pre_message
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Initialize ssl
__update_ssl_conf
__update_ssl_certs
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Updating config files
__create_service_env
__update_conf_files
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# run the pre execute commands
[ -n "$PRE_EXEC_MESSAGE" ] && eval echo "$PRE_EXEC_MESSAGE"
__pre_execute
__run_secure_function
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__run_start_script "$@" |& tee -a "/data/logs/entrypoint.log" &>/dev/null
if [ "$?" -ne 0 ] && [ -n "$EXEC_CMD_BIN" ]; then
eval echo "Failed to execute: ${cmd_exec:-$EXEC_CMD_BIN $EXEC_CMD_ARGS}" |& tee -a "/data/logs/entrypoint.log" "$LOG_DIR/init.txt"
rm -Rf "$SERVICE_PID_FILE"
SERVICE_EXIT_CODE=10
SERVICE_IS_RUNNING="false"
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__script_exit $SERVICE_EXIT_CODE