hortusfox-setup and the cron jobs previously ran with no systemd
hardening at all; phpfpm-hortusfox only had whatever services.phpfpm
sets by default (ProtectSystem = "full"). Add a shared conservative
baseline (NoNewPrivileges, Protect*, Restrict*, LockPersonality,
RemoveIPC, RestrictAddressFamilies scoped to AF_UNIX/AF_INET/AF_INET6)
to hortusfox-setup and the cron services, and tighten phpfpm's own
ProtectSystem to "strict" plus an explicit ReadWritePaths for stateDir.
Deliberately no UMask override: a tighter umask silently reintroduces
the "Permission denied" static-asset bug already fixed once, since it
changes the mode cp/mkdir produce for files nginx/caddy (a different
user) need to read directly. Also no MemoryDenyWriteExecute or
SystemCallFilter for now -- broader surface, easy to break something
subtle in migrations/mysql/zip handling without much more testing.
Verified against both vm-tests (full boot + cookie-jar login) for both
proxy backends before landing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
HortusFox writes export/auto-backup zips to public/backup using a
fully predictable hf_backup_<timestamp>.zip name, serves that
directory as real static files (ahead of its own auth-gated routes),
and never deletes them itself unless an external backup_path is
separately configured. That leaves full data exports downloadable by
anyone who knows or guesses the timestamp, indefinitely. This can't be
fixed from packaging alone, but giving public/backup's tmpfiles rule
an age (new backupMaxAge option, default "1d", null to disable) lets
systemd-tmpfiles-clean.timer prune old exports and bound the exposure
window. Every other writableDirs entry is unaffected (age stays "-").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The seeded admin account's UserModel "name" was hardcoded to "admin".
Make it a separate, overridable option (still defaulting to "admin"
for compatibility) -- it's purely a display name, since HortusFox logs
in by email rather than this field.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The seeded admin account's email was hardcoded to admin@<domain>, which
isn't necessarily a mailbox the deployer can actually receive mail at --
notably breaking HortusFox's own "Restore password" flow, which emails
that address. Make it a separate, overridable option (still defaulting
to admin@<domain> for compatibility).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Asatru's own url()/asset() helpers build absolute URLs from
$_SERVER['SERVER_PORT'] rather than APP_URL, and nginx/caddy populate
that from the port they're actually listening on -- not an externally
forwarded one (e.g. the QEMU test VM's hostfwd to 8080). That silently
dropped the port from every generated asset/redirect URL, breaking all
static assets when accessed through a forwarded port. Override
SERVER_PORT at the fastcgi layer to cfg.port when set, for both
backends.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Packages HortusFox from source via php.buildComposerProject and provides
a NixOS module (services.hortusfox-web) to deploy it as a full service
(mariadb + phpfpm + nginx/caddy + ACME + cron timers).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>