diff --git a/README.md b/README.md index 4594066..5530c99 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ deployments// # one folder per stack | [`headscale`](deployments/headscale/) | Self-hosted Tailscale control server, OIDC login. | pocket-id (OIDC) | | [`webfinger`](deployments/webfinger/) | Serves `/.well-known/webfinger` for OIDC discovery; redirects the rest. | pocket-id (issuer) | | [`simplex`](deployments/simplex/) | SimpleX SMP + XFTP relay with Tor hidden services + encrypted backups. | globals/age-pubkey.txt | +| [`kanrisha`](deployments/kanrisha/) | Custom app on a ScoutFS filesystem; two-phase install (host prep + ScoutFS, then the Docker stack). **Alma/Rocky only.** | ScoutFS (host) | ## Conventions @@ -149,6 +150,10 @@ path, hostname, boot hooks, and the sshguard log source/backend. **simplex** remains **Alpine-targeted** — it depends on `awall` and Tor hidden services with Alpine-specific wiring, so it isn't part of the tri-distro set. +**kanrisha** is the inverse — **Alma/Rocky-targeted**. It installs ScoutFS (a +RHEL-only kernel filesystem) and its host package set via `dnf`, so it runs on +the RHEL family only for now. + ## Host firewall [`scripts/harden-firewall.sh`](scripts/harden-firewall.sh) installs a diff --git a/automations.sh b/automations.sh index 64ca819..dfd1d33 100644 --- a/automations.sh +++ b/automations.sh @@ -48,7 +48,7 @@ fi . "$ROOT/scripts/lib.sh" load_globals -DEPLOYMENTS=(pocket-id beszel headscale webfinger simplex) +DEPLOYMENTS=(pocket-id beszel headscale webfinger simplex kanrisha) SCRIPTS=(setup-host harden-ssh harden-jumphost sshuser auto-update) # ---------------------------------------------------------------------------- @@ -100,6 +100,19 @@ ask_deployment_vars() { ask XFTP_QUOTA "XFTP disk quota" optional ask SSH_PORT "SSH port" optional ask ALLOWED_IP "Your IP to whitelist in sshguard" optional ;; + kanrisha) + # License: reuse globals.env value if present, else prompt for it. + if [[ -z "${SCOUTFS_LICENSE:-}" ]]; then + ask SCOUTFS_LICENSE "ScoutFS license key" + else + ENVS+=("SCOUTFS_LICENSE=$SCOUTFS_LICENSE") + fi + # Blank path -> phase 1 (host prep). A live path -> phase 2 (deploy). + ask SCOUTFS_PATH "ScoutFS mount path (blank = host prep / phase 1)" optional + if [[ -n "${SCOUTFS_PATH:-}" ]]; then + ask KANRISHA_DOMAIN "Public hostname (e.g. kanrisha.example.com)" + ask ACME_EMAIL "Let's Encrypt email" + fi ;; esac } diff --git a/deployments/kanrisha/.env.example b/deployments/kanrisha/.env.example new file mode 100644 index 0000000..899d1fd --- /dev/null +++ b/deployments/kanrisha/.env.example @@ -0,0 +1,23 @@ +# Copy to .env and fill in. docker compose picks .env up automatically. +# deploy.sh (phase 2) seeds .env from this on first run; an existing .env is +# never overwritten. + +# ─── Public hostname ──────────────────────────────────────────────────────── +# Bare hostname (no scheme) where Kanrisha will be reached. DNS must point here +# and 80/443 be reachable before deploy, or the Let's Encrypt request fails. +KANRISHA_DOMAIN=kanrisha.example.com + +# Email for Let's Encrypt registration / expiry notifications. +ACME_EMAIL=admin@example.com + +# ─── ScoutFS ──────────────────────────────────────────────────────────────── +# Absolute path to the mounted ScoutFS filesystem Kanrisha operates on. You +# create + mount this between phase 1 and phase 2 (see README). Bind-mounted +# into the Kanrisha container by docker-compose.yml. +SCOUTFS_PATH=/mnt/kanrisha + +# ─── Image tags ───────────────────────────────────────────────────────────── +CADDY_TAG=2-alpine +# TODO (part two): set the real Kanrisha image + tag once the compose lands. +# KANRISHA_IMAGE= +# KANRISHA_TAG=latest diff --git a/deployments/kanrisha/Caddyfile b/deployments/kanrisha/Caddyfile new file mode 100644 index 0000000..169e8c3 --- /dev/null +++ b/deployments/kanrisha/Caddyfile @@ -0,0 +1,32 @@ +# Caddyfile for the kanrisha stack. +# +# Auto-issues a Let's Encrypt cert for $KANRISHA_DOMAIN and reverse-proxies to +# the Kanrisha app container. +# +# STUB: the upstream below points at the placeholder service on :80. Update the +# reverse_proxy target (service name + port) to match the real Kanrisha service +# in docker-compose.yml when you wire in part two. + +{ + email {$ACME_EMAIL} +} + +{$KANRISHA_DOMAIN} { + encode zstd gzip + + reverse_proxy kanrisha:80 { # TODO: real Kanrisha service + port + header_up X-Real-IP {http.request.remote.host} + } + + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains" + X-Content-Type-Options "nosniff" + Referrer-Policy "strict-origin-when-cross-origin" + -Server + } + + log { + output stdout + format console + } +} diff --git a/deployments/kanrisha/README.md b/deployments/kanrisha/README.md new file mode 100644 index 0000000..53b1c52 --- /dev/null +++ b/deployments/kanrisha/README.md @@ -0,0 +1,70 @@ +# kanrisha + +Kanrisha (custom application) deployed as a Docker Compose stack behind Caddy, +running on a **ScoutFS** filesystem. **Alma / Rocky Linux only** for now — it +installs ScoutFS (a RHEL-only kernel filesystem) and a host package set via +`dnf`. + +> **Status:** the host-prep half (phase 1) is complete; the application stack +> (phase 2) is scaffolded with a **stub** `docker-compose.yml` / `Caddyfile` +> pending the real Kanrisha compose file. See the TODOs in those files. + +## Two-phase deploy + +Kanrisha runs on a ScoutFS filesystem you create by hand (it depends on your +storage layout), so [`deploy.sh`](deploy.sh) is split into two phases and runs +whichever fits when you invoke it — decided by whether `SCOUTFS_PATH` points at +a live ScoutFS mount. + +**Phase 1 — host prep (automated):** +1. `dnf install epel-release` +2. `dnf install git tar sparse mt-st mtx sg3_utils pciutils mbuffer wget curl zstd jq bash-completion sos lsof` +3. Install Docker. +4. Install ScoutFS: `curl -fsSL https://rpm-1.srvno.de/setup-scoutfs.sh | bash -s -- --license ` +5. Print the steps to create your ScoutFS filesystem, then stop. + +**You do (manual):** create + mount a ScoutFS filesystem, e.g. at +`/mnt/kanrisha` (and add it to `/etc/fstab`). + +**Phase 2 — deploy the stack (automated):** lay down the compose + Caddyfile, +seed `.env`, open 80/443, pull images, bring the stack up behind Caddy/TLS. + +## Required values + +| Variable | Phase | Notes | +|----------|-------|-------| +| `SCOUTFS_LICENSE` | 1 | License key for `setup-scoutfs.sh`. Prompted, or set in `globals/globals.env`. | +| `SCOUTFS_PATH` | 2 | Absolute path to the mounted ScoutFS filesystem. Its presence is what triggers phase 2. | +| `KANRISHA_DOMAIN` | 2 | Public hostname (e.g. `kanrisha.example.com`). | +| `ACME_EMAIL` | 2 | Let's Encrypt registration email. | + +See [`.env.example`](.env.example) for image tags. + +## Deploy + +```bash +./automations.sh # Deploy on this host → deploy: kanrisha +``` + +Or build + run the self-contained artifact: + +```bash +./build.sh +scp deploy.sh root@host: +ssh root@host 'bash deploy.sh' # phase 1 (prompts for license) +# ... create + mount your ScoutFS filesystem on the host ... +ssh root@host 'SCOUTFS_PATH=/mnt/kanrisha KANRISHA_DOMAIN=kanrisha.example.com \ + ACME_EMAIL=me@example.com bash deploy.sh' # phase 2 +``` + +Unattended provisioning (phase 1): [`cloud-init.yml`](cloud-init.yml). + +## Notes + +- **Rebuild after editing loose files.** `docker-compose.yml`, `Caddyfile`, and + `.env.example` are embedded into `deploy.sh` by [`build.sh`](build.sh); re-run + it after any edit or the deploy ships the stale copy. +- DNS for `KANRISHA_DOMAIN` must resolve to the host and 80/443 be reachable + before phase 2, or the Let's Encrypt cert request fails. +- The ScoutFS `mkfs`/`mount` recipe in the phase-1 handoff is a placeholder — + fill in your canonical commands. diff --git a/deployments/kanrisha/build.sh b/deployments/kanrisha/build.sh new file mode 100644 index 0000000..4e56901 --- /dev/null +++ b/deployments/kanrisha/build.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# +# build.sh -- (re)embed docker-compose.yml, Caddyfile, .env.example into +# deploy.sh as a base64-encoded tar.gz payload after __ARCHIVE_BELOW__. +# Idempotent: strips any existing payload first. + +set -euo pipefail + +DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +SCRIPT="$DIR/deploy.sh" +MARKER="__ARCHIVE_BELOW__" + +[[ -f "$SCRIPT" ]] || { echo "deploy.sh not found at $SCRIPT" >&2; exit 1; } +for f in docker-compose.yml Caddyfile .env.example; do + [[ -f "$DIR/$f" ]] || { echo "Missing $DIR/$f" >&2; exit 1; } +done + +PAYLOAD=$(tar -czf - -C "$DIR" docker-compose.yml Caddyfile .env.example | base64) + +TMP=$(mktemp) +trap 'rm -f "$TMP"' EXIT + +sed "/^${MARKER}\$/,\$d" "$SCRIPT" > "$TMP" +{ + echo "$MARKER" + echo "$PAYLOAD" +} >> "$TMP" + +mv "$TMP" "$SCRIPT" +chmod +x "$SCRIPT" +trap - EXIT + +size=$(wc -c < "$SCRIPT") +echo "Built $SCRIPT (${size} bytes)" diff --git a/deployments/kanrisha/cloud-init.yml b/deployments/kanrisha/cloud-init.yml new file mode 100644 index 0000000..6c19935 --- /dev/null +++ b/deployments/kanrisha/cloud-init.yml @@ -0,0 +1,44 @@ +#cloud-config +# +# Kanrisha (phase 1) -- harden SSH, then run host prep + ScoutFS install on a +# fresh Alma / Rocky host. +# +# This covers PHASE 1 ONLY. Kanrisha needs a ScoutFS filesystem that you create +# by hand (it depends on your storage layout), so after first boot: +# 1. read the handoff at the end of /var/log/cloud-init-output.log +# 2. create + mount your ScoutFS filesystem (e.g. at /mnt/kanrisha) +# 3. finish with phase 2: +# SCOUTFS_PATH=/mnt/kanrisha KANRISHA_DOMAIN=... ACME_EMAIL=... \ +# SKIP_PROMPTS=1 bash /opt/automations/deployments/kanrisha/deploy.sh +# +# Fill in REPO_URL, SCOUTFS_LICENSE, and the values in the runcmd block, then +# paste this as the instance user-data. + +packages: + - git + +runcmd: + - hostnamectl set-hostname kanrisha || true + - | + set -e + REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git + REPO_BRANCH=main + HARDEN_SSH=1 # harden SSH on this fresh VM (set 0 to skip) + SSH_PORT=22 + ALLOWED_IP= # optional: whitelist your client IP in sshguard + SCOUTFS_LICENSE= # <-- your ScoutFS license key (required) + git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations + cd /opt/automations + + # Harden SSH on this fresh VM: PQ KEX, key-only auth, sshguard. Seeds root + # from globals/authorized_keys (or SSH_KEYS_URL). + if [ "$HARDEN_SSH" = 1 ]; then + SSH_PORT="$SSH_PORT" ALLOWED_IP="$ALLOWED_IP" SKIP_PROMPTS=1 FORCE=1 \ + bash scripts/harden-ssh.sh + fi + + # Phase 1: EPEL + host packages + Docker + ScoutFS. With no SCOUTFS_PATH set + # this stops after install and prints the next steps (create the ScoutFS + # filesystem, then run phase 2). + SCOUTFS_LICENSE="$SCOUTFS_LICENSE" SKIP_PROMPTS=1 \ + bash deployments/kanrisha/deploy.sh diff --git a/deployments/kanrisha/deploy.sh b/deployments/kanrisha/deploy.sh new file mode 100644 index 0000000..542462c --- /dev/null +++ b/deployments/kanrisha/deploy.sh @@ -0,0 +1,374 @@ +#!/usr/bin/env bash +# +# deploy.sh -- deploy Kanrisha (custom app on a ScoutFS filesystem) on +# Alma / Rocky Linux. Two-phase, idempotent, self-contained. +# +# Kanrisha runs on a ScoutFS filesystem that you create by hand (it depends on +# your storage layout), so the deploy splits into two phases and this one +# script runs whichever is appropriate when you invoke it: +# +# PHASE 1 -- host prep (no ScoutFS path yet): +# 1. Installs EPEL + the host package set Kanrisha needs. +# 2. Installs Docker. +# 3. Installs ScoutFS from the vendor repo (setup-scoutfs.sh + license). +# 4. Prints the steps to create your ScoutFS filesystem, then exits. +# +# +# +# PHASE 2 -- deploy the stack (SCOUTFS_PATH points at that mount): +# 5. Lays down docker-compose.yml + Caddyfile + .env in $STACK_DIR. +# 6. Opens 80/443, pulls images, brings the stack up behind Caddy/TLS. +# +# The phase is chosen automatically: if SCOUTFS_PATH is set and is a live +# ScoutFS mount, it deploys; otherwise it preps the host. Re-run any time. +# +# Self-contained: docker-compose.yml, Caddyfile, .env.example are embedded as a +# base64 tar.gz after __ARCHIVE_BELOW__. Rebuild with build.sh after editing the +# loose source files. (The host-prep phase needs none of them.) +# +# Alma / Rocky (RHEL family) only for now -- it installs ScoutFS (a RHEL-only +# kernel filesystem) and host packages via dnf. +# +# Usage: +# bash deploy.sh # phase 1 (host prep) +# SCOUTFS_LICENSE=... bash deploy.sh # phase 1, license preset +# SCOUTFS_PATH=/mnt/kanrisha KANRISHA_DOMAIN=k.example.com \ +# ACME_EMAIL=me@example.com bash deploy.sh # phase 2 (deploy) +# SKIP_PROMPTS=1 ... bash deploy.sh # non-interactive (cloud-init) + +set -euo pipefail + +: "${STACK_DIR:=/srv/kanrisha}" +: "${SKIP_DOCKER_INSTALL:=0}" +: "${FORCE:=0}" +: "${SKIP_PROMPTS:=0}" # non-interactive: require values via env, no prompts +[[ "$SKIP_PROMPTS" == "1" ]] && FORCE=1 +: "${SCOUTFS_LICENSE:=}" +: "${SCOUTFS_PATH:=}" +: "${SCOUTFS_SETUP_URL:=https://rpm-1.srvno.de/setup-scoutfs.sh}" +: "${KANRISHA_DOMAIN:=}" +: "${ACME_EMAIL:=}" + +# Host packages Kanrisha needs in place before ScoutFS + the stack. +HOST_PACKAGES=(git tar sparse mt-st mtx sg3_utils pciutils mbuffer wget curl + zstd jq bash-completion sos lsof) + +log() { printf '\033[1;32m[+]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; } +die() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; exit 1; } + +[[ $EUID -eq 0 ]] || die "Run as root." + +# --------------------------------------------------------------------------- +# OS detection. This deploy.sh is self-contained (scp'd standalone), so the OS +# logic is inlined here instead of sourced from scripts/oslib.sh. +# --------------------------------------------------------------------------- +osfam() { + local id="" like="" + if [[ -r /etc/os-release ]]; then + id="$(. /etc/os-release 2>/dev/null && echo "${ID:-}")" + like="$(. /etc/os-release 2>/dev/null && echo "${ID_LIKE:-}")" + fi + case " $id $like " in + *" alpine "*) echo alpine ;; + *" debian "*|*" ubuntu "*) echo debian ;; + *" rhel "*|*" fedora "*|*" centos "*) echo rhel ;; + *) echo "${id:-unknown}" ;; + esac +} + +require_rhel() { + [[ "$(osfam)" == "rhel" ]] || \ + die "Kanrisha supports Alma / Rocky Linux (RHEL family) only for now (detected: $(osfam))." +} + +install_docker() { + if command -v docker >/dev/null 2>&1; then + log "Docker already installed: $(docker --version)" + return 0 + fi + log "Installing Docker..." + command -v curl >/dev/null 2>&1 || dnf install -y -q curl + curl -fsSL https://get.docker.com | sh + systemctl enable --now docker >/dev/null 2>&1 || systemctl start docker || true +} + +# Is the host already prepared (ScoutFS tooling present)? +scoutfs_installed() { + command -v mkfs.scoutfs >/dev/null 2>&1 +} + +# Is SCOUTFS_PATH a directory sitting on a live ScoutFS mount? +scoutfs_path_ready() { + [[ -n "$SCOUTFS_PATH" && -d "$SCOUTFS_PATH" ]] || return 1 + if command -v findmnt >/dev/null 2>&1; then + [[ "$(findmnt -no FSTYPE --target "$SCOUTFS_PATH" 2>/dev/null)" == "scoutfs" ]] || return 1 + else + mountpoint -q "$SCOUTFS_PATH" 2>/dev/null || return 1 + fi + return 0 +} + +open_web_ports() { + # Register 80/443 for this stack. Prefer the host firewall + # (harden-firewall.sh) when present; else fall back to firewalld/ufw if + # active (no-op when neither is). + # + # NOTE: Caddy publishes 80/443 via Docker, which reaches the host through + # nat/FORWARD and BYPASSES the INPUT firewall -- so this is belt-and-braces + # for any host-bound bind and self-documents the stack ports. + if [[ -d /etc/firewall/ports.d && -x /usr/local/sbin/firewall-apply ]]; then + log "Registering 80,443/tcp with host firewall..." + printf '80/tcp\n443/tcp\n' > /etc/firewall/ports.d/kanrisha.rule + /usr/local/sbin/firewall-apply + elif command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then + log "firewalld active -- allowing http,https..." + firewall-cmd -q --add-service=http --permanent + firewall-cmd -q --add-service=https --permanent + firewall-cmd -q --reload + elif command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then + log "ufw active -- allowing 80,443/tcp..." + ufw allow 80/tcp >/dev/null; ufw allow 443/tcp >/dev/null + fi +} + +prompt() { + local varname="$1" message="$2" + local -n ref="$varname" + if [[ -z "${ref:-}" ]]; then + [[ "$SKIP_PROMPTS" == "1" ]] && die "$varname required (set it in the environment; running with SKIP_PROMPTS=1)." + read -r -p "$message: " ref + [[ -n "$ref" ]] || die "$varname required." + fi +} + +# =========================================================================== +# PHASE 1 -- host preparation + ScoutFS +# =========================================================================== +phase1_prep() { + require_rhel + log "Phase 1/2: preparing host (EPEL, packages, Docker, ScoutFS)." + + log "Installing EPEL release..." + dnf install -y -q epel-release || die "Failed to install epel-release." + + log "Installing ${#HOST_PACKAGES[@]} host packages..." + dnf install -y -q "${HOST_PACKAGES[@]}" || die "Failed to install host packages." + + if [[ "$SKIP_DOCKER_INSTALL" != "1" ]]; then + install_docker + fi + + if scoutfs_installed; then + log "ScoutFS already installed (mkfs.scoutfs present); skipping vendor setup." + else + if [[ -z "$SCOUTFS_LICENSE" ]]; then + [[ "$SKIP_PROMPTS" == "1" ]] && \ + die "SCOUTFS_LICENSE required (set it in the environment or globals.env; running with SKIP_PROMPTS=1)." + read -r -p "ScoutFS license key: " SCOUTFS_LICENSE + [[ -n "$SCOUTFS_LICENSE" ]] || die "SCOUTFS_LICENSE required." + fi + log "Installing ScoutFS from ${SCOUTFS_SETUP_URL}..." + curl -fsSL "$SCOUTFS_SETUP_URL" | bash -s -- --license "$SCOUTFS_LICENSE" \ + || die "ScoutFS setup failed." + scoutfs_installed || \ + warn "setup-scoutfs.sh finished but mkfs.scoutfs is not on PATH yet -- check the vendor output." + fi + + print_handoff +} + +print_handoff() { + cat </dev/null 2>&1 || \ + die "Docker is not installed (run phase 1 first, or unset SKIP_DOCKER_INSTALL)." + + # --- extract embedded archive --- + SCRIPT_DIR=$(mktemp -d -t kanrisha-deploy.XXXXXX) + trap 'rm -rf "$SCRIPT_DIR"' EXIT + if grep -q -a '^__ARCHIVE_BELOW__$' "$0"; then + log "Extracting embedded deployment files..." + grep -a -A 9999999 '^__ARCHIVE_BELOW__$' "$0" | tail -n +2 | base64 -d | tar -xz -C "$SCRIPT_DIR" + else + die "No embedded archive found. Run build.sh to embed deployment files." + fi + for f in docker-compose.yml Caddyfile .env.example; do + [[ -f "$SCRIPT_DIR/$f" ]] || die "Embedded archive missing $f" + done + + open_web_ports + + log "Setting up $STACK_DIR..." + install -d -m 0750 "$STACK_DIR" + install -m 0640 "$SCRIPT_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml" + install -m 0640 "$SCRIPT_DIR/Caddyfile" "$STACK_DIR/Caddyfile" + + ENV_FILE="$STACK_DIR/.env" + if [[ ! -f "$ENV_FILE" ]]; then + log "Seeding $ENV_FILE..." + install -m 0600 "$SCRIPT_DIR/.env.example" "$ENV_FILE" + sed -i \ + -e "s|^KANRISHA_DOMAIN=.*|KANRISHA_DOMAIN=${KANRISHA_DOMAIN}|" \ + -e "s|^ACME_EMAIL=.*|ACME_EMAIL=${ACME_EMAIL}|" \ + -e "s|^SCOUTFS_PATH=.*|SCOUTFS_PATH=${SCOUTFS_PATH}|" \ + "$ENV_FILE" + else + log ".env exists; leaving it alone." + fi + + missing=() + for var in KANRISHA_DOMAIN ACME_EMAIL SCOUTFS_PATH; do + grep -E "^${var}=.+$" "$ENV_FILE" >/dev/null || missing+=("$var") + done + (( ${#missing[@]} == 0 )) || die "Missing values in $ENV_FILE: ${missing[*]}" + + if [[ "$FORCE" != "1" ]]; then + cat </dev/null || true) + unhealthy=$(echo "$status" | awk '$2 != "healthy" && $2 != "" {print $1}') + if [[ -z "$unhealthy" && -n "$status" ]]; then + log "All services healthy." + break + fi + sleep 5 + done + + echo + log "Stack status:" + docker compose ps + echo + cat < caddy:443 -> kanrisha: +# +# Kanrisha operates on the host's ScoutFS filesystem, bind-mounted read-write +# at ${SCOUTFS_PATH} (created by you between phase 1 and phase 2). + +name: kanrisha + +volumes: + caddy-data: + caddy-config: + +services: + caddy: + image: caddy:${CADDY_TAG:-2-alpine} + container_name: caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + - caddy-config:/config + environment: + KANRISHA_DOMAIN: "${KANRISHA_DOMAIN}" + ACME_EMAIL: "${ACME_EMAIL}" + depends_on: + - kanrisha + healthcheck: + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:2019/config/"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + # TODO (part two): replace this stub with the real Kanrisha service. + # - real image + tag (KANRISHA_TAG in .env) + # - the port Caddy should reverse_proxy to (update Caddyfile too) + # - any env / config / healthcheck the app needs + kanrisha: + image: ${KANRISHA_IMAGE:-traefik/whoami}:${KANRISHA_TAG:-latest} # STUB image + container_name: kanrisha + restart: unless-stopped + volumes: + # ScoutFS filesystem the app runs on (host path from .env). + - "${SCOUTFS_PATH}:/data" diff --git a/globals/globals.env.example b/globals/globals.env.example index fdc5fb2..31e5f34 100644 --- a/globals/globals.env.example +++ b/globals/globals.env.example @@ -15,6 +15,11 @@ REPO_BRANCH=main ACME_EMAIL=admin@example.com SSH_PORT=22 +# ─── ScoutFS (kanrisha) ───────────────────────────────────────────────────── +# License key passed to setup-scoutfs.sh by the kanrisha deployment. Leave +# empty to be prompted per host; set it here to reuse one key unattended. +SCOUTFS_LICENSE= + # ─── SSH authorized keys ──────────────────────────────────────────────────── # resolve_ssh_keys() prefers this URL when set (fetched live, always current), # e.g. a GitHub keys endpoint (https://github.com/.keys) or a raw