feat: switch from actual -> firefly-iii

This commit is contained in:
oddlama 2025-04-26 10:28:33 +02:00
parent 4adf28a442
commit ced72fb7bb
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
34 changed files with 193 additions and 128 deletions

View file

@ -46,7 +46,7 @@ I've included the major components in the lists below.
| ~~~~~~~~~~~~ | Service | Source | Description
---|---|---|---
💸 Budgeting | Actual Budget | [Link](./hosts/sire/guests/actual.nix) | Budgeting application to track income and expenses
💸 Budgeting | Firefly III \& Firefly Pico | [Link](./hosts/ward/guests/firefly.nix) | Budgeting application to track income and expenses
🛡️ Adblock | AdGuard Home | [Link](./hosts/ward/guests/adguardhome.nix) | DNS level adblocker
🔒 SSO | Kanidm | [Link](./hosts/ward/guests/kanidm.nix) | Identity provider for Single-Sign-On on my hosted services, with provisioning.
🐙 Git | Forgejo | [Link](./hosts/ward/guests/forgejo.nix) | Forgejo with SSO

View file

@ -38,10 +38,11 @@
# 973
gamemode = uidGid 972;
plausible = uidGid 971;
actual = uidGid 970;
# actual = uidGid 970;
# flatpak = uidGid 969;
unifi = uidGid 968;
plugdev.gid = 967;
tss = uidGid 966;
firefly-iii = uidGid 965;
};
}

View file

@ -12,7 +12,7 @@ let
# FIXME: new entry here? make new firezone gateway on ward entry too.
homeDomains = [
globals.services.grafana.domain
globals.services.actual.domain
globals.services.firefly.domain
globals.services.immich.domain
globals.services.influxdb.domain
globals.services.loki.domain

View file

@ -18,6 +18,11 @@
group = "oauth2-proxy";
};
# FIXME: switch to loadcredential + start wrapper.
# TODO: define nixos option to do this for us, it's recurring. like systemd.services.a.secretEnv = {
# ABC = ./path.to.secret.file; # or runtime path.
# };
# Mirror the original oauth2 secret, but prepend OAUTH2_PROXY_CLIENT_SECRET=
# so it can be used as an EnvironmentFile
age.secrets.oauth2-client-secret = {

View file

@ -134,7 +134,6 @@
in
lib.mkIf (!minimal) (
{ }
// mkMicrovm "actual" { }
// mkMicrovm "samba" {
enableStorageDataset = true;
enableBunkerDataset = true;

View file

@ -1,96 +0,0 @@
{
config,
globals,
lib,
pkgs,
nodes,
...
}:
let
actualDomain = "finance.${globals.domains.me}";
# client_id = "actual";
in
{
wireguard.proxy-home = {
client.via = "ward";
firewallRuleForNode.ward-web-proxy.allowedTCPPorts = [ config.services.actual.settings.port ];
};
# Mirror the original oauth2 secret
age.secrets.actual-oauth2-client-secret = {
inherit (nodes.ward-kanidm.config.age.secrets.kanidm-oauth2-actual) rekeyFile;
};
environment.persistence."/persist".directories = [
{
directory = "/var/lib/private/actual";
mode = "0700";
}
];
services.actual = {
enable = true;
settings.trustedProxies = [ nodes.ward-web-proxy.config.wireguard.proxy-home.ipv4 ];
};
# NOTE: state: to enable openid, we need to call their enable-openid script once
# which COPIES this data to the database :( so changing these values later will
# require manual intervention.
systemd.services.actual = {
serviceConfig.ExecStart = lib.mkForce [
(pkgs.writeShellScript "start-actual" ''
export ACTUAL_OPENID_CLIENT_SECRET=$(< "$CREDENTIALS_DIRECTORY"/oauth2-client-secret)
exec ${lib.getExe config.services.actual.package}
'')
];
serviceConfig.LoadCredential = [
"oauth2-client-secret:${config.age.secrets.actual-oauth2-client-secret.path}"
];
# NOTE: openid is disabled for now. too experimental, many rough edges.
# only admins can use sync, every admin can open anyones finances. not good enough yet.
# environment = {
# ACTUAL_OPENID_ENFORCE = "true";
# ACTUAL_TOKEN_EXPIRATION = "openid-provider";
#
# ACTUAL_OPENID_DISCOVERY_URL = "https://${globals.services.kanidm.domain}/oauth2/openid/${client_id}/.well-known/openid-configuration";
# ACTUAL_OPENID_CLIENT_ID = client_id;
# ACTUAL_OPENID_SERVER_HOSTNAME = "https://${actualDomain}";
# };
};
globals.services.actual.domain = actualDomain;
# FIXME: monitor from internal network
# globals.monitoring.http.actual = {
# url = "https://${actualDomain}/";
# expectedBodyRegex = "Actual";
# network = "local-${config.node.name}";
# };
nodes.ward-web-proxy = {
services.nginx = {
upstreams.actual = {
servers."${config.wireguard.proxy-home.ipv4}:${toString config.services.actual.settings.port}" =
{ };
extraConfig = ''
zone actual 64k;
keepalive 2;
'';
monitoring = {
enable = true;
expectedBodyRegex = "Actual";
};
};
virtualHosts.${actualDomain} = {
forceSSL = true;
useACMEWildcardHost = true;
extraConfig = ''
client_max_body_size 256M;
'';
locations."/" = {
proxyPass = "http://actual";
proxyWebsockets = true;
};
};
};
};
}

View file

@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIARJ59yifkMFmcWWM4sAwhQN6u+H4Bv+VVboPBslHqZj

View file

@ -13,7 +13,7 @@ let
# FIXME: new entry here? make new firezone entry too.
homeDomains = [
globals.services.grafana.domain
globals.services.actual.domain
globals.services.firefly.domain
globals.services.immich.domain
globals.services.influxdb.domain
globals.services.loki.domain
@ -135,6 +135,7 @@ in
lib.mkIf (!minimal) (
{ }
// mkMicrovm "adguardhome"
// mkMicrovm "firefly"
// mkMicrovm "forgejo"
// mkMicrovm "kanidm"
// mkMicrovm "radicale"

View file

@ -112,7 +112,7 @@ in
# FIXME: new entry here? make new firezone entry too.
# FIXME: new entry here? make new firezone gateway on ward entry too.
globals.services.grafana.domain
globals.services.actual.domain
globals.services.firefly.domain
globals.services.immich.domain
globals.services.influxdb.domain
globals.services.loki.domain

View file

@ -0,0 +1,87 @@
{
config,
globals,
nodes,
...
}:
let
fireflyDomain = "firefly.${globals.domains.me}";
wardWebProxyCfg = nodes.ward-web-proxy.config;
in
{
wireguard.proxy-home = {
client.via = "ward";
firewallRuleForNode.sausebiene.allowedTCPPorts = [ config.services.firefly.port ];
};
globals.services.firefly.domain = fireflyDomain;
globals.monitoring.http.firefly = {
url = "https://${fireflyDomain}";
expectedBodyRegex = "Firefly-III";
network = "home-lan.vlans.services";
};
age.secrets.firefly-app-key = {
generator.script = _: ''
echo "base64:$(head -c 32 /dev/urandom | base64)"
'';
owner = "firefly-iii";
};
environment.persistence."/persist".directories = [
{
directory = "/var/lib/firefly-iii";
user = "firefly-iii";
}
];
i18n.supportedLocales = [ "all" ];
services.firefly-iii = {
enable = true;
enableNginx = true;
virtualHost = globals.services.firefly.domain;
settings = {
APP_URL = "https://${globals.services.firefly.domain}";
TZ = "Europe/Berlin";
TRUSTED_PROXIES = wardWebProxyCfg.wireguard.proxy-home.ipv4;
SITE_OWNER = "admin@${globals.domains.me}";
APP_KEY_FILE = config.age.secrets.firefly-app-key.path;
AUTHENTICATION_GUARD = "remote_user_guard";
AUTHENTICATION_GUARD_HEADER = "X-User";
AUTHENTICATION_GUARD_EMAIL = "X-Email";
};
};
nodes.ward-web-proxy = {
services.nginx = {
upstreams.firefly = {
servers."${config.wireguard.proxy-home.ipv4}:${toString config.services.firefly.settings.server.http_port}" =
{ };
extraConfig = ''
zone firefly 64k;
keepalive 2;
'';
monitoring = {
enable = true;
expectedBodyRegex = "Firefly";
};
};
virtualHosts.${fireflyDomain} = {
forceSSL = true;
useACMEWildcardHost = true;
locations."/" = {
proxyPass = "http://firefly";
proxyWebsockets = true;
};
extraConfig = ''
allow ${globals.net.home-lan.vlans.home.cidrv4};
allow ${globals.net.home-lan.vlans.home.cidrv6};
# Firezone traffic
allow ${globals.net.home-lan.vlans.services.hosts.ward.ipv4};
allow ${globals.net.home-lan.vlans.services.hosts.ward.ipv6};
deny all;
'';
};
};
};
}

View file

@ -35,7 +35,6 @@ in
age.secrets.kanidm-admin-password = mkRandomSecret;
age.secrets.kanidm-idm-admin-password = mkRandomSecret;
age.secrets.kanidm-oauth2-actual = mkRandomSecret;
age.secrets.kanidm-oauth2-forgejo = mkRandomSecret;
age.secrets.kanidm-oauth2-grafana = mkRandomSecret;
age.secrets.kanidm-oauth2-immich = mkRandomSecret;
@ -137,23 +136,6 @@ in
];
};
# Actual
groups."actual.access" = { };
systems.oauth2.actual = {
displayName = "Actual Budget";
originUrl = "https://${globals.services.actual.domain}/openid/callback";
originLanding = "https://${globals.services.actual.domain}/";
basicSecretFile = config.age.secrets.kanidm-oauth2-actual.path;
preferShortUsername = true;
# XXX: RS256 is used instead of ES256 so additionally we need legacy crypto
enableLegacyCrypto = true;
scopeMaps."actual.access" = [
"openid"
"email"
"profile"
];
};
# Firezone
groups."firezone.access" = { };
systems.oauth2.firezone = {

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDI93VlzePCcQnAF3MmgcvfJPhWrLmT+9uWCzgVl3YV+

View file

@ -0,0 +1,12 @@
age-encryption.org/v1
-> X25519 K0iCGjPk+lIfakWRt19tUebknIoPk7CRKQgJxt6543c
1TTIJMkKIBC4mkV22/+DL6MonwX5bSFnHrI/1UMBxOA
-> piv-p256 xqSe8Q A8TmdnrAPXoyy1s69kIJ4+UDB0ecn5BGj7AOgyT46x+A
3IOxV7OkgkCq4OBN521ONImxkbZ7CA3rXcYixF1T0v0
-> IMt/c-grease
3x3A2IBsky5I8QkGvxAv0Sf5+uuTdrLtRGVIQ7Kx7/PgZJdHrEVp7brTImvGHa7U
7R3tyMLSVAUq0fje5TuY+qt5iovMFvN9Ju9tXq0TTrR8oMQ7AuRTPVQSZCa8Sj4i
46w
--- xqksX/NXpJyr2omtpSxWZT17lzp0JsGVOAwVF3qmS88
+Ty»×W5*gP$ØóG@ƒ°ìR*˜º<ó ne¥"œp
®öê#Nΰ¬ý¾·|¦º¶GæOpÜA8xÿà)‡¶ýàËü½´-&_â

View file

@ -0,0 +1,11 @@
age-encryption.org/v1
-> X25519 HgSF2a9Q3nwcydHrTdp+3OCofuM0icQ17MA20aZd3Qs
psZ9T9mQRBWDK/AEZSZaYaLX9hLaarWo3ih5TdF0KtY
-> piv-p256 xqSe8Q AlVUJIsg77JKUuLZkd3QFdVaJxZl5y7pQAvFu3hS7DBO
3GySsYAQAuR5nYJOKQ49qLBhZy8H5ozQ78dyAZuqdDw
-> -?_-R-grease
TBgbQcVKKpCzoOQ1IJhKN17FQj6sC6g/0ZWWilWPfJbhGRZBocynl1o4H492FE3k
NUjMFQFOY28JlHX1N8yT8T9AMFYdpUS3hQ
--- dFzRUdI8Gc5FA+zFzaGpWNV4s2kQFy1neRt2cyTGiTs
Èy™ízfá®Åö Ü_µT(ýݨë{¸’î j™Ö­Þ!;Dôø³u- ±$äÖ¿•ã‹ 8ŽÚý�Ô©Å
üüg\XdIQ>ü�"òò

View file

@ -0,0 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 1tdZKQ PhTEEKU3VbR4jKOq9yqGfCXI3LgBS9tuTniqQVs+qU0
ZDzt7bQ03RT5W2hoxsZTrQI8PuBBrH8UiYHpVox4YU4
-> GCS:-grease
H0Poh145lWvrCYnOXCFt1VJMpwGK/Ek
--- z76MlfKZZvOBXOhvmWY7Am5nnICI2rhS8fnNpXfj4dA
1»þ³¬´$ µÆ~[>ªØ»ã1>ê u^þ\ñª|KÌÿ÷×^Âcué*ígµW�H¾¿óª•ð7{Îéòúè.Š!:ù)Œs\î¶ù•‹Ûx

View file

@ -0,0 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 iMlJww E5FqXO/vzW9DHo9fCSAMUKWmjR8MFXpU3XDnRa0EJ28
QMY3FFVIMMA7hkzgt4KFCZqnceAt3HX5nBgDMHaOoHI
-> 9eg'-grease VW\g2`l 31z"
NPUdagDQa5101Jc+IJ9q6SC+91YOK+k
--- iBSbWHLrGSanifDuwn2dobAwAlTRGEt3wNWshz96nNA
êþ¶q3æ²êŠá8�ng©®ŸŸ:½“¬ºÇñ¹}üo{­Ž‚â÷ü·Ænm…Rüè™ØÚr+edNÃz'`sÇ[úb¿•(Ϻʭãò5ãÑ

View file

@ -0,0 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 iMlJww Z4Ks4fk4VNuB+NVnmWgFGe7pUdwaOEIRjHhxldrS1Rg
ryrr2czYVoljwgokZ4SueF4K86Rn5ZQ9hryeuxgzD64
-> Sq1*-grease |:Mx~ 5qJ||" " 6!+(,I
7DhAuAnGOTWQ/3IeVR6GFBQ
--- MYFaGCLMR7pf3AOctnWoff2GI+hJ68QKO9Mh6/qPFEY
,A8–ŚĐ>˙ tĹcߎz IíF�Ž*sl�iŠĚ€bŢ „Ȳ"?Ž$ťŞ°87{˙4ˇfţŹwŞźŻě¸cá°#,!YßYĆľ�Ł

View file

@ -0,0 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 iMlJww 2JAtq+eRbq02hjI35LQX8swDM9CHIlk04dSbWJP3zSQ
QEb6tHk4JQPXqqnYY/mY9CUB3IgzfjCW9ovp2sV1TFM
-> @-grease KM$g *!O`
izfolg
--- WILRDX5ScwJzlzULjw9xiiLn4p8wrd3wUw4h2QJCYeg
žö:g[XOÊ]JÔøn}¸òëÊÁ…˜Ò5F×2-íßÕüÐY–¿þ<ÿžÇ¢p¢Ø‹Õn£�Xå±CŸoÐH—ÑU¾]V/~Q•ÂìÁŸ. ­|Ôaï

View file

@ -0,0 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 iMlJww CTRuCRd64uojf1Q0yisBkwEt42tLLqCA0X2Umw45Y3A
UcSTnVKmw7nl9Apg/ZZcGPSBm/i6AIrD82QnPY+UnVQ
-> .$Mz#d-grease f-1BU |@F!=+8 0s`)Z 9&5<J&MM
uEQ6iKSlaBgxbDppOaXU/k0ujx4/FyQuj8PCzw+Erwdoxwxuw8w
--- gOGIExA2G2rschlaM8Tr+BsWQMFOpUDA1ClR/IsUlLI
pÔ÷­Òœs$³ƒ2õÆŠï o³lÑ|�ž}+à1‰¬ ¤Ì®@€6æ’±†J».«�?^¢…äeø˜‹Ü‡E:)ñ/‰ÿÜ*ETÕ–øór�¬Ñ

View file

@ -0,0 +1,8 @@
age-encryption.org/v1
-> ssh-ed25519 iMlJww DFczqyym2mmSZTWehkkvSPaNf7q8biiiEqoRrilbEno
hvzfFRr0rpm0pJU5AFa1LOJ8QFEBgc1sU8LUbu1iPQk
-> h;[-grease @z] EChs4o3 U`,fK;
VX4FqvfERRpfAPCw8F4D2vdVDuhS3FQ0Viw9G/Lp4Kda8/u1/LXxcCRSQ+Mvuj/o
QCOdco48EagX3CVp+a6xuPNgaxgopMgkyQr2nmvXh0W6r1s
--- e0ODGAsNYbD8EWnZgZ776iHi2Y41+zbAL7jDo2InYQM
Io;µ;—Z $È…-\ØÏ<¡¦•×ͼœø¹ûRPœmrÌÖ7ÛJîBÎÕ}å·ä;DlHÚdô¢¸îꚀ2�Üb|Âs^²šD

View file

@ -1,7 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 QciEZQ VcWKLWPg9nAruDvA/KXaDefLu8SF7PbMH/FJRfHteFc
1AvjkdFCx+2nqE9qvQr6/2AqxUuLgm2q9krLZ1FVqA4
-> V]-grease gujG %5pig
jiipvJVY7Td0OMyhH7nTdSf4EBwcKQ
--- eaCRPI5enSnNczltwLy4EPgf1FRgUiBxL8BoA8vekh8
ßÌ–•m“»I½ô[÷"O!0®ô5g‹½�ÆiSêãÌCñZJ*ë ZPÜ*ÛâØÀ¸óSÊ©ñ^б¦úÉ@�`µj öß/?¿

View file

@ -0,0 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 iNceIg wdAnVbYVU+fgSIb4ZiNqfbV5e+Gyt7l2Pr+gqoTzt30
iV0QOUfzJLu061EjjY+hD8SnT4Mx5udoO7tUogRPeA4
-> rv!OK-grease 3@%B3|g
JUDryA
--- mADEX6kUWctEoYX8d/eTbYFdB+kcILfyhbUjiDMNGx0
¤”鯕‘®Ï^�Þ$aHn ×ÖÄ•GLYŽ©ÑU÷bRÓÝ£Afu¼Õªd�àç‡5ù>r�c¥í’ª¬¯&×Ó•0ׇéF:�jÖ

View file

@ -0,0 +1,10 @@
age-encryption.org/v1
-> X25519 uvlRP7EwYe9edPcApANRaV0Eqwx/CY6ElDJ8zPS6NTc
SGcubLfhsAQxjjxUfoczcKT7acC+o9YLDjAbaZxIXj8
-> piv-p256 xqSe8Q A7hFGedcJqbSCtfnBTi52Vm3lwCojBBJg2KZoHBHzFeb
9UPcvDAwU1Kl6nRE3eakB4dPyyjeKlSVK0/MeUzMVvs
-> id2?G-grease J=PF j
3PdNbIHnMNBtH6OPbMXyMtpt2HVSW+D0BCg3qg3V3p3DDd7FEQzr7lEfsjooZwG8
fC2jno79z1r7t7lg5VgP5s1yA2WO6sfaMvZ25iQLlg
--- V6na3Vl3HiCI97qZbIwhvJnrN9St1VaU4wLxvVaJY8k
a-˘ť°‘¤XĂčń = ­¶ĐU˘Ň€Ť[šmHkt~ěF…J1ÝW)lÓíéá(�΂s�/ŤOĎíü<vű qĚYzN–�?Ĺ

View file

@ -0,0 +1 @@
MRJ0x/6qO8xo5rJus+L99qmSZu6gB/KBxvwqk0T2EyE=

View file

@ -0,0 +1,9 @@
age-encryption.org/v1
-> X25519 u+GOb4it1FIuM2xQLit+Qj8FnHxog7mrgqzYB2cAF1Q
MiVwE4nNwJo7aIg+H8/1vCPsHbKIRw58xPprat9E7xk
-> piv-p256 xqSe8Q AqY1gjvBbu3vDCrksaKE9BExREcAp00pMIVyAZxvr/aw
CubJmGfOCZyLXxWgoZ+fnQu7BCs1arzt5iKZjdSIVM0
-> ??~tD-grease i2nkSM_{ iogOoT}>
wWFu3rir4mB4RUy9
--- K1zBn3duHmWgsphLbNn1ujFQ/X08tpW4dkmKB7S8eM4
¯å¦—�ƒõðÛ‚JÓ²·Ü/"mç×=À¯Ëf®s$ßå½@Äf~‰a7 3î4‡ÞŠ}�”ñ™ßÝk"ÀÀ¿±ƒ¶!W1«U‚é6q