Commit Graph
8 Commits
Author SHA1 Message Date
awkawbandClaude Sonnet 5 6f16735747 Auto-expire public/backup exports via a configurable tmpfiles age
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>
2026-09-28 13:54:07 -04:00
awkawbandClaude Sonnet 5 8ec061bede Point cron jobs at the app's own loopback address, not its public appUrl
Cron previously curled appUrl (the externally-reachable domain, with
cfg.port appended when set). That meant every cron tick depended on
the host being able to route back to its own public domain (hairpin
NAT, not guaranteed), and whenever cfg.port was set to an
externally-forwarded port nginx/caddy don't themselves listen on (e.g.
the QEMU test VM's hostfwd), cron would curl a port nothing is
listening on at all and fail outright.

curl --resolve now pins the connection to 127.0.0.1 on the port
nginx/caddy actually listen on (80, or 443 under proxy.ssl), while
keeping the Host header/SNI as cfg.domain so vhost routing and cert
validation still work normally. Also fixes the cron services'
"after" dependency, which was hardcoded to nginx.service even when
proxy.server = "caddy".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 13:21:27 -04:00
awkawbandClaude Sonnet 5 3f5a120338 Assert against proxy.ssl with the default domain "localhost"
proxy.ssl = true (the default) requests a real ACME certificate for
services.hortusfox-web.domain. Left at its own default of "localhost",
that request can never succeed -- Let's Encrypt won't issue for it --
but previously this only surfaced as an ACME failure at activation
time. Catch it at eval time instead with a clear assertion message.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 13:20:06 -04:00
awkawbandClaude Sonnet 5 e2ac57d9c6 Escape adminAccount.username/email before interpolating into the seed INSERT
Both fields were spliced directly into a single-quoted SQL string. An
ordinary value containing an apostrophe (e.g. adminAccount.username =
"O'Brien") broke the INSERT's SQL syntax outright, failing
hortusfox-setup on first boot under set -euo pipefail; a crafted value
could inject arbitrary SQL. Escape backslashes and quotes to match
MySQL/MariaDB's default string literal rules.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 13:19:24 -04:00
awkawbandClaude Sonnet 5 d88f6f312d Add adminAccount.username, decoupled from the seeded admin's hardcoded name
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>
2026-09-28 12:52:33 -04:00
awkawbandClaude Sonnet 5 150d307020 Add adminAccount.email, decoupled from the seeded admin's assumed domain email
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>
2026-09-27 17:14:22 -04:00
awkawbandClaude Sonnet 5 c7888c3f9d Fix asset/URL port mismatch when proxy.port differs from nginx/caddy's own listen port
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>
2026-09-27 17:14:22 -04:00
awkawbandClaude Sonnet 5 38ec3c76f7 Initial import of HortusFox Nix flake packaging
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>
2026-09-27 17:14:22 -04:00