Dark Frappé base (#303446) background with the pivoine.art peony
centered and padded. Replaces logo.svg/png, favicon.svg/png, and
apple-touch-icon.png.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Port 0 disabled the web UI but broke the built-in /readyz healthcheck.
Binding to 8025 keeps the UI accessible within the Docker network only
(no Traefik labels) and lets the container report healthy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IONOS rejects relay from VPS IPs when sender doesn't match authenticated
account. Switch gitea and passbolt FROM to the actual IONOS account address.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
stacks.sh now lives in git@dev.pivoine.art:valknar/scripts.git
and resolves STACKS_DIR from env or defaults to ~/stacks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same fix as the update service: gate "not installed" on unit file
existence rather than systemctl exit code, which is non-zero for
failed services too.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
systemctl status exits non-zero for failed services (exit 3), not
only missing ones (exit 4), causing the || warn branch to always fire
after a failed run. Gate the "not installed" warning on whether the
unit file exists instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docker image inspect returns exit code 1 for images not yet pulled.
With set -euo pipefail, this silently killed the script mid-loop.
Add || true so an uncached image yields empty before/after (correct
first-run behaviour: pull fires, before != after, stack gets started).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Docker binds to both [::]:22 and 0.0.0.0:22 without an explicit IP,
conflicting with sshd on [::]:22.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ondemand kills all workers after 10s idle, causing cold Laravel
bootstrap (10-54s) on every request after any quiet period.
dynamic keeps min_spare_servers=1 alive at all times; the other
PM_* values (start_servers=2, min=1, max_spare=3) were already
set but ignored under ondemand.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74.7.243.196 was crawling valknar/home SVG icon tree at 1.8 req/s.
- Denylist the IP in fail2ban dynamic config
- Add rate-limit@file middleware to Gitea's Traefik router to throttle
future scrapers on public repos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.7.1 caused 403 on all routes. v0.9.0 renamed whitelist→allowlist,
changed ip to a comma-separated string, and added enabled/statuscode fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Registers tomMoulard/fail2ban v0.7.1 as an experimental plugin and
applies it globally on the web-secure entrypoint alongside security-headers.
Bans IPs after 5 failures within 10 minutes for 3 hours; whitelists
RFC1918 ranges to protect internal/Docker traffic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Source .env before restic init so RESTIC_REPOSITORY is available
- Remove stale references to deleted _backup/.env and _update/.env
- Update Notifications section to point at root .env
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docker compose images -q reports the image IDs of currently running
containers, which don't change after a pull — so before == after always
and containers were never recreated.
Fix: resolve each service's image tag to its local SHA256 ID via
docker image inspect, which reads the local image store and correctly
reflects the newly pulled image. Falls back from 'config --images'
(compose v2.19+) to parsing 'config' yaml for older versions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Inline update logic (pull → compare digests → up -d → prune → notify)
- Inline backup logic with dynamic Postgres detection: any running
<stack>_db container is dumped using the <stack>/<stack> convention
- Systemd unit files are now generated on `install` from embedded
heredocs pointing at stacks.sh itself — no external scripts needed
- Root .env (WEBHOOK_URL, RESTIC_REPOSITORY, RESTIC_PASSWORD) replaces
the per-service .env files in _backup/ and _update/
- Remove _backup/ and _update/ directories entirely
- Update README accordingly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace manual docker compose / systemctl snippets with stacks.sh
equivalents and add a dedicated section covering all commands.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bakes the current stack list into the generated completion script instead
of using runtime directory discovery. Useful for remote hosts where the
stacks dir path differs from the local repo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bash script for managing Docker Compose stacks with glob filtering,
parallel execution, service management, scaffolding, and shell completion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use image ID diff instead of grepping "Pulled" (which appears even
when images are already up to date). Add timestamp, stack count, and
updated/total ratio to all notification messages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add +x permission to update.sh (was causing systemd 203/EXEC failure)
- Remove $ anchor from grep so trailing space in docker output is matched
- Add .data/ to .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mount templates/custom/ directly to Gitea's default GITEA_CUSTOM templates
path. No entrypoint override or envsubst needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Injects the Umami tracking script into every Gitea page using a custom
header.tmpl generated at startup via envsubst. Script URL and website ID
are externalized to UMAMI_SCRIPT_URL / UMAMI_WEBSITE_ID env vars.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The patched PublicKeyValidationService.php and its volume mount are
no longer needed now that the metadata key exists in the database.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Credentials migrated to Passbolt CE at passbolt.pivoine.art.
Removed stack files and README entry. Data wiped on VPS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The isNotCreatedInTheFutureRule has zero tolerance, causing the
browser extension to fail when generating a metadata key if the
browser clock is even 1 second ahead of the server. Patching
isDateInFuture to allow 300 seconds tolerance and mounting the
file as a read-only volume so the fix survives image updates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Passbolt's entrypoint creates /var/lib/passbolt/.gnupg/pubring.kbx
as root while PHP-FPM runs as www-data. Without a volume this file
is recreated with wrong ownership on every container recreate, breaking
all GPG operations. Mounting the dir as a volume keeps the chown
33:33 fix permanent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>