Compare commits
3
Commits
d88f6f312d
...
8ec061bede
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ec061bede | ||
|
|
3f5a120338 | ||
|
|
e2ac57d9c6 |
@@ -41,6 +41,15 @@ let
|
|||||||
in if builtins.length parts > 1 then lib.concatStringsSep "/" (lib.init parts) else null)
|
in if builtins.length parts > 1 then lib.concatStringsSep "/" (lib.init parts) else null)
|
||||||
writableDirs));
|
writableDirs));
|
||||||
|
|
||||||
|
# Escape a value for interpolation into a single-quoted SQL string literal
|
||||||
|
# (used for the admin-seed INSERT below). Doubling backslashes first, then
|
||||||
|
# quotes, matches MySQL/MariaDB's default (backslash-escapes-active)
|
||||||
|
# string literal rules -- without this, an ordinary value like "O'Brien"
|
||||||
|
# breaks the INSERT's SQL syntax outright (failing hortusfox-setup on
|
||||||
|
# first boot, since the script runs under `set -euo pipefail`), and a
|
||||||
|
# crafted value could inject arbitrary SQL.
|
||||||
|
sqlEscape = s: lib.replaceStrings [ "\\" "'" ] [ "\\\\" "''" ] s;
|
||||||
|
|
||||||
# Render one KEY=VALUE .env line. Booleans/ints/floats are left bare so
|
# Render one KEY=VALUE .env line. Booleans/ints/floats are left bare so
|
||||||
# Asatru's datatype auto-detection picks them up as such rather than string.
|
# Asatru's datatype auto-detection picks them up as such rather than string.
|
||||||
mkEnvLine = name: value:
|
mkEnvLine = name: value:
|
||||||
@@ -112,6 +121,20 @@ let
|
|||||||
lib.concatStringsSep "\n" (lib.mapAttrsToList mkEnvLine envVars) + "\n"
|
lib.concatStringsSep "\n" (lib.mapAttrsToList mkEnvLine envVars) + "\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# Cron jobs curl the app's own loopback address rather than its
|
||||||
|
# externally-reachable appUrl, on the port nginx/caddy actually listen on
|
||||||
|
# (80, or 443 when proxy.ssl -- never cfg.port, which per its own option
|
||||||
|
# doc doesn't change that). Otherwise cron would depend on the host being
|
||||||
|
# able to route back to its own public domain (hairpin NAT, not
|
||||||
|
# guaranteed) and, whenever cfg.port is set to an externally-forwarded
|
||||||
|
# port nginx/caddy don't themselves listen on (e.g. the QEMU test VM's
|
||||||
|
# hostfwd), would curl a port nothing is listening on at all. curl's
|
||||||
|
# --resolve pins the connection to 127.0.0.1 while keeping the
|
||||||
|
# Host header/SNI as cfg.domain, so vhost routing and cert validation
|
||||||
|
# still work normally.
|
||||||
|
cronPort = if cfg.proxy.ssl then 443 else 80;
|
||||||
|
cronUrl = path: "${scheme}://${cfg.domain}:${toString cronPort}${path}";
|
||||||
|
|
||||||
# curl'd cron endpoints -> (name, path, calendar spec)
|
# curl'd cron endpoints -> (name, path, calendar spec)
|
||||||
cronjobs = {
|
cronjobs = {
|
||||||
"tasks-overdue" = { path = "/cronjob/tasks/overdue"; when = "*-*-* 06,18:00:00"; };
|
"tasks-overdue" = { path = "/cronjob/tasks/overdue"; when = "*-*-* 06,18:00:00"; };
|
||||||
@@ -264,6 +287,20 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = !(cfg.proxy.ssl && cfg.domain == "localhost");
|
||||||
|
message = ''
|
||||||
|
services.hortusfox-web: proxy.ssl = true (the default) requests an
|
||||||
|
ACME certificate for services.hortusfox-web.domain, but domain is
|
||||||
|
left at its own default of "localhost" -- Let's Encrypt can never
|
||||||
|
issue a certificate for that, so this would only fail at runtime.
|
||||||
|
Set domain to a real, publicly resolvable FQDN, or set
|
||||||
|
proxy.ssl = false for a local/test deployment.
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
users.users.${cfg.user} = {
|
users.users.${cfg.user} = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
group = cfg.group;
|
group = cfg.group;
|
||||||
@@ -384,7 +421,7 @@ in {
|
|||||||
# this point.
|
# this point.
|
||||||
${lib.optionalString (cfg.adminAccount.passwordFile != null) ''
|
${lib.optionalString (cfg.adminAccount.passwordFile != null) ''
|
||||||
admin_hash="$(php -r 'echo password_hash($argv[1], PASSWORD_BCRYPT);' "$(cat "${cfg.adminAccount.passwordFile}")")"
|
admin_hash="$(php -r 'echo password_hash($argv[1], PASSWORD_BCRYPT);' "$(cat "${cfg.adminAccount.passwordFile}")")"
|
||||||
mariadb "${cfg.database.name}" -e "INSERT INTO UserModel (name, email, password, admin) VALUES ('${cfg.adminAccount.username}', '${cfg.adminAccount.email}', '$admin_hash', 1);"
|
mariadb "${cfg.database.name}" -e "INSERT INTO UserModel (name, email, password, admin) VALUES ('${sqlEscape cfg.adminAccount.username}', '${sqlEscape cfg.adminAccount.email}', '$admin_hash', 1);"
|
||||||
''}
|
''}
|
||||||
|
|
||||||
touch "${stateDir}/.installed"
|
touch "${stateDir}/.installed"
|
||||||
@@ -401,12 +438,13 @@ in {
|
|||||||
} // (lib.mapAttrs' (name: job:
|
} // (lib.mapAttrs' (name: job:
|
||||||
lib.nameValuePair "hortusfox-cron-${name}" {
|
lib.nameValuePair "hortusfox-cron-${name}" {
|
||||||
description = "HortusFox cronjob: ${name}";
|
description = "HortusFox cronjob: ${name}";
|
||||||
after = [ "nginx.service" ];
|
after = [ "${cfg.proxy.server}.service" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
User = cfg.user;
|
User = cfg.user;
|
||||||
ExecStart = "${pkgs.curl}/bin/curl -fsS --max-time 300 "
|
ExecStart = "${pkgs.curl}/bin/curl -fsS --max-time 300 "
|
||||||
+ "${appUrl}${job.path}";
|
+ "--resolve ${cfg.domain}:${toString cronPort}:127.0.0.1 "
|
||||||
|
+ "${cronUrl job.path}";
|
||||||
};
|
};
|
||||||
}) cronjobs);
|
}) cronjobs);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user