wip(sgx): firefly-sparda-fetch service + timer (DISABLED)

End-to-end FinTS pipeline against Sparda Südwest is wired up but
disabled — aqbanking 6.8.2's `-P pinfile` flag does not consume the
file content correctly on this build (verified: pinfile bytes match
the manually-typed PIN exactly, yet the bank receives a wrong PIN).
Three rejected attempts locked the access at Sparda; do not re-arm
the timer until the auth path is replaced (likely python-fints).

What works:
- aqbanking config and FinTS dialog (manual PIN entry)
- getaccsepa workaround for HKCAZ "Mussfeld 9160" rejection
- custom CSV profile (decimal amounts + IBAN columns) wired in
- Firefly importer auto-upload settings + sops secret slot
- inbox + profile-symlink tmpfiles

What's broken:
- Headless PIN delivery via aqbanking-cli -P
- Timer left wantedBy=[] so it cannot fire post-deploy

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Harald Hoyer 2026-04-26 20:58:10 +02:00
parent 55bef14076
commit 74af9fd5ac

View file

@ -2,8 +2,24 @@
let let
domain = "firefly.hoyer.world"; domain = "firefly.hoyer.world";
importDomain = "firefly-import.hoyer.world"; importDomain = "firefly-import.hoyer.world";
aqHome = "/var/lib/firefly-aqbanking"; importerHome = "/var/lib/firefly-iii-data-importer";
inbox = "/var/lib/firefly-iii-data-importer/inbox"; inbox = "${importerHome}/inbox";
configFile = "${importerHome}/sparda-config.json";
bankCode = "55090500";
userId = "5987838198";
giroAccountId = "3";
# aqbanking 6.8.2 ships only an "import" profile and a "full" export
# profile that renders amounts as fractions ("-499/100"). Firefly's CSV
# importer needs decimal amounts and benefits from localIban/remoteIban
# columns, so derive a profile that combines "full"'s columns with
# decimal value formatting.
fireflyCsvProfile = pkgs.runCommand "aqbanking-csv-firefly-profile" { } ''
sed 's/name="full"/name="firefly"/; s/valueFormat="rational"/valueFormat="float"/' \
${pkgs.aqbanking}/share/aqbanking/imexporters/csv/profiles/full.conf > $out
'';
vhostBase = { vhostBase = {
enableACME = false; enableACME = false;
useACMEHost = "internal.hoyer.world"; useACMEHost = "internal.hoyer.world";
@ -20,14 +36,97 @@ in
sopsFile = ../../../.secrets/sgx/firefly.yaml; sopsFile = ../../../.secrets/sgx/firefly.yaml;
owner = "firefly-iii-data-importer"; owner = "firefly-iii-data-importer";
}; };
"firefly/auto_import_secret" = {
sopsFile = ../../../.secrets/sgx/firefly.yaml;
owner = "firefly-iii-data-importer";
};
}; };
environment.systemPackages = [ pkgs.aqbanking ]; environment.systemPackages = [ pkgs.aqbanking ];
systemd.tmpfiles.rules = [ systemd = {
"d ${aqHome} 0700 firefly-iii-data-importer firefly-iii-data-importer -" tmpfiles.rules = [
"d ${inbox} 0700 firefly-iii-data-importer firefly-iii-data-importer -" "d ${inbox} 0700 firefly-iii-data-importer nginx -"
]; "d ${importerHome}/.aqbanking/imexporters/csv/profiles 0700 firefly-iii-data-importer nginx -"
"L+ ${importerHome}/.aqbanking/imexporters/csv/profiles/firefly.conf - - - - ${fireflyCsvProfile}"
];
services.firefly-sparda-fetch = {
description = "Fetch Sparda transactions via FinTS and trigger Firefly auto-import";
after = [
"network-online.target"
"phpfpm-firefly-iii-data-importer.service"
];
wants = [ "network-online.target" ];
path = with pkgs; [
aqbanking
curl
coreutils
];
serviceConfig = {
Type = "oneshot";
User = "firefly-iii-data-importer";
Group = "nginx";
RuntimeDirectory = "firefly-sparda-fetch";
LoadCredential = [
"pin:${config.sops.secrets."firefly/sparda_pin".path}"
"secret:${config.sops.secrets."firefly/auto_import_secret".path}"
];
ProtectSystem = "strict";
ReadWritePaths = [ importerHome ];
ProtectHome = true;
PrivateTmp = true;
NoNewPrivileges = true;
TimeoutStartSec = "3min";
};
script = ''
set -euo pipefail
pinfile=$RUNTIME_DIRECTORY/pinfile
umask 077
printf 'PIN_%s_%s = "%s"\n' "${bankCode}" "${userId}" \
"$(<"$CREDENTIALS_DIRECTORY/pin")" >"$pinfile"
ts=$(date +%Y%m%d-%H%M%S)
ctx=$RUNTIME_DIRECTORY/ctx-$ts.aqb
out=${inbox}/sparda-$ts.csv
# Refresh SEPA account list — Atruvia/Sparda rejects HKCAZ
# ("Mussfeld 9160") if this metadata isn't fresh in the dialog.
aqhbci-tool4 -n -A -P "$pinfile" getaccsepa -u ${giroAccountId}
fromdate=$(date --date='35 days ago' +%Y%m%d)
aqbanking-cli -n -A -P "$pinfile" request \
--transactions --fromdate="$fromdate" \
--aid=${giroAccountId} -c "$ctx"
aqbanking-cli export \
--exporter=csv --profile=firefly \
-c "$ctx" -o "$out"
secret=$(<"$CREDENTIALS_DIRECTORY/secret")
curl -fsS -X POST \
"https://${importDomain}/autoupload?secret=$secret" \
-F "json=@${configFile}" \
-F "importable=@$out"
'';
};
# Timer disabled while we work around aqbanking 6.8.2's broken
# `-P pinfile` handling. The fetch service authenticates with a wrong
# PIN against the bank — three runs locked the access at Sparda. Do
# not re-enable until the auth path is replaced (likely python-fints).
timers.firefly-sparda-fetch = {
wantedBy = [ ];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
};
services = { services = {
firefly-iii = { firefly-iii = {
@ -54,6 +153,10 @@ in
FIREFLY_III_URL = "https://${domain}"; FIREFLY_III_URL = "https://${domain}";
VANITY_URL = "https://${importDomain}"; VANITY_URL = "https://${importDomain}";
TZ = "Europe/Berlin"; TZ = "Europe/Berlin";
CAN_POST_FILES = "true";
CAN_POST_AUTOIMPORT = "true";
IMPORT_DIR_ALLOWLIST = inbox;
AUTO_IMPORT_SECRET_FILE = config.sops.secrets."firefly/auto_import_secret".path;
}; };
}; };
@ -62,4 +165,5 @@ in
${importDomain} = vhostBase; ${importDomain} = vhostBase;
}; };
}; };
} }