Reload Caddy when the vhost fragment changes, and 404 unmatched paths
Caddy glob-imports Caddyfile.d/ once, at startup, and its own unit does not change when a fragment does — so NixOS left it running and the vhost simply did not exist. No listener, no certificate, and nothing in the log to say why, because Caddy had never been told to look. The first deploy of this module hit exactly that: it had to be reloaded by hand before ACME would even try. reloadTriggers makes the fragment's content part of what decides whether Caddy is reloaded, so this cannot recur the next time the domain or the port moves. Also answer unmatched paths with 404. Caddy's default for a request that matched no route is an empty 200, which made every stray path — /config.yml among them — look like an empty file that exists. Nothing was ever served either way, but 404 is the honest answer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1b2eba9535
commit
affbb703be
1 changed files with 56 additions and 32 deletions
88
module.nix
88
module.nix
|
|
@ -98,6 +98,52 @@ let
|
||||||
'map(.["trigger-rule"].match.secret = $s)' \
|
'map(.["trigger-rule"].match.secret = $s)' \
|
||||||
${hooksTemplate} > /run/fdroid-webhook/hooks.json
|
${hooksTemplate} > /run/fdroid-webhook/hooks.json
|
||||||
'';
|
'';
|
||||||
|
# Served as a Caddyfile.d fragment rather than a virtualHosts entry
|
||||||
|
# because bucur's Caddyfile is a hand-written file, not the NixOS
|
||||||
|
# module's attrset. Hoisted into a binding so reloadTriggers below can
|
||||||
|
# depend on its content.
|
||||||
|
caddyFragment = ''
|
||||||
|
${cfg.domain} {
|
||||||
|
handle /hooks/* {
|
||||||
|
reverse_proxy localhost:${toString cfg.webhookPort}
|
||||||
|
}
|
||||||
|
|
||||||
|
# F-Droid's convention is <host>/fdroid/repo, and the fingerprint
|
||||||
|
# clients pin is tied to that exact address via the signed index,
|
||||||
|
# so it is not a path that can be tidied up later.
|
||||||
|
#
|
||||||
|
# Rooted at repo/ rather than at the state directory with /fdroid
|
||||||
|
# stripped, which would have served the whole fdroidserver working
|
||||||
|
# tree: config.yml and metadata/ are not secret, but they are not
|
||||||
|
# part of the repo and nothing should be able to fetch them.
|
||||||
|
redir /fdroid/repo /fdroid/repo/ permanent
|
||||||
|
handle_path /fdroid/repo/* {
|
||||||
|
root * ${cfg.stateDir}/repo
|
||||||
|
# No `browse`: repo/index.html is the landing page, and a
|
||||||
|
# directory listing for everything else only advertises paths
|
||||||
|
# that no client needs to discover.
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
# `fdroid update` writes repo/index.html: the repo's own landing
|
||||||
|
# page, listing every app and carrying the QR code that adds the
|
||||||
|
# repo to a phone. Better than anything hand-written here, and it
|
||||||
|
# can never fall out of step with the index.
|
||||||
|
redir / /fdroid/repo/ permanent
|
||||||
|
|
||||||
|
# Caddy answers a request that matched no route with an empty 200, so
|
||||||
|
# without this every stray path — /config.yml included — looks like a
|
||||||
|
# file that exists and happens to be empty. Nothing is served either
|
||||||
|
# way, but "not found" is the honest answer and the one a client can
|
||||||
|
# act on.
|
||||||
|
handle {
|
||||||
|
respond 404
|
||||||
|
}
|
||||||
|
|
||||||
|
encode zstd gzip
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.services.fdroidRepo = {
|
options.services.fdroidRepo = {
|
||||||
|
|
@ -272,38 +318,16 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
environment.etc = lib.mkIf cfg.serveCaddy {
|
environment.etc = lib.mkIf cfg.serveCaddy {
|
||||||
"caddy/Caddyfile.d/fdroid.caddyfile".text = ''
|
"caddy/Caddyfile.d/fdroid.caddyfile".text = caddyFragment;
|
||||||
${cfg.domain} {
|
|
||||||
handle /hooks/* {
|
|
||||||
reverse_proxy localhost:${toString cfg.webhookPort}
|
|
||||||
}
|
|
||||||
|
|
||||||
# F-Droid's convention is <host>/fdroid/repo, and the fingerprint
|
|
||||||
# clients pin is tied to that exact address via the signed index,
|
|
||||||
# so it is not a path that can be tidied up later.
|
|
||||||
#
|
|
||||||
# Rooted at repo/ rather than at the state directory with /fdroid
|
|
||||||
# stripped, which would have served the whole fdroidserver working
|
|
||||||
# tree: config.yml and metadata/ are not secret, but they are not
|
|
||||||
# part of the repo and nothing should be able to fetch them.
|
|
||||||
redir /fdroid/repo /fdroid/repo/ permanent
|
|
||||||
handle_path /fdroid/repo/* {
|
|
||||||
root * ${cfg.stateDir}/repo
|
|
||||||
# No `browse`: repo/index.html is the landing page, and a
|
|
||||||
# directory listing for everything else only advertises paths
|
|
||||||
# that no client needs to discover.
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
|
|
||||||
# `fdroid update` writes repo/index.html: the repo's own landing
|
|
||||||
# page, listing every app and carrying the QR code that adds the
|
|
||||||
# repo to a phone. Better than anything hand-written here, and it
|
|
||||||
# can never fall out of step with the index.
|
|
||||||
redir / /fdroid/repo/ permanent
|
|
||||||
|
|
||||||
encode zstd gzip
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Caddy glob-imports Caddyfile.d/ once, at startup. Its own unit does not
|
||||||
|
# change when a fragment does, so NixOS leaves it running and the vhost
|
||||||
|
# silently does not exist — no certificate, no listener, nothing in the log
|
||||||
|
# to say why, until something unrelated happens to restart it. Which is
|
||||||
|
# exactly what happened on the first deploy of this module.
|
||||||
|
systemd.services.caddy.reloadTriggers =
|
||||||
|
lib.mkIf cfg.serveCaddy [ caddyFragment ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue