From 38e851620cfc5af308e596ab449cd23e90840268 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:36:42 -0600 Subject: [PATCH 1/2] feat: Add redis database --- systems/linux/mcentire.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/systems/linux/mcentire.nix b/systems/linux/mcentire.nix index c259c27..5d80ada 100644 --- a/systems/linux/mcentire.nix +++ b/systems/linux/mcentire.nix @@ -65,6 +65,8 @@ # Do not "enable" database services, but include the package configuration # so that borgmatic does not freak out about unset variables postgresql.package = pkgs.postgresql_17; + + redis.servers.redis.enable = true; }; virtualisation.quadlet.enable = true; From dcf59faaf6413bb31eef9c7fb3f9324dc6de20cf Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:59:48 -0600 Subject: [PATCH 2/2] feat: Add fireflyiii --- docker-compose.yaml | 136 ++++++++++++++++++++++++++++++ secrets.nix | 2 + secrets/fireflyiii.toml.age | Bin 0 -> 1230 bytes services/fireflyiii.nix | 162 ++++++++++++++++++++++++++++++++++++ systems/linux/mcentire.nix | 1 + 5 files changed, 301 insertions(+) create mode 100644 docker-compose.yaml create mode 100644 secrets/fireflyiii.toml.age create mode 100644 services/fireflyiii.nix diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..39e97af --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,136 @@ +networks: + fireflyiii: + +services: + fireflyiii-db: + image: "mariadb:10" + container_name: "fireflyiii-db" + restart: always + networks: + - fireflyiii + volumes: + - "/var/lib/fireflyiii/mysql:/var/lib/mysql" + healthcheck: + test: "healthcheck.sh --connect" + interval: 30s + timeout: 1s + retries: 10 + environment: + - "MYSQL_ROOT_PASSWORD=${FIREFLY_III_MYSQL_ROOT_PASSWORD}" + - "MYSQL_PASSWORD=${FIREFLY_III_MYSQL_PASSWORD}" + - "MYSQL_DATABASE=${FIREFLY_III_MYSQL_DB}" + - "MYSQL_USER=${FIREFLY_III_MYSQL_USER}" + + fireflyiii: + image: fireflyiii/core:version-6 + container_name: "fireflyiii" + restart: always + networks: + - web + - fireflyiii + - redis + volumes: + - "/var/fireflyiii:/var/www/html/storage/upload" + depends_on: + fireflyiii-db: + condition: service_healthy + redis: + condition: service_healthy + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik_proxy" + - "traefik.http.routers.fireflyiii.rule=Host(`money.millironx.com`)" + - "traefik.http.routers.fireflyiii.tls=true" + - "traefik.http.routers.fireflyiii.tls.certresolver=letsencrypt" + - "traefik.http.services.fireflyiii.loadbalancer.server.port=8080" + environment: + - APP_ENV=local + - SITE_OWNER=${ADMIN_EMAIL} + - APP_KEY=${FIREFLY_III_APP_KEY} + - DEFAULT_LANGUAGE=en_US + - TZ=America/New_York + - TRUSTED_PROXIES=* + - DB_CONNECTION=mysql + - DB_HOST=fireflyiii-db + - DB_PORT=3306 + - DB_DATABASE=${FIREFLY_III_MYSQL_DB} + - DB_USERNAME=${FIREFLY_III_MYSQL_USER} + - DB_PASSWORD=${FIREFLY_III_MYSQL_PASSWORD} + - CACHE_DRIVER=redis + - SESSION_DRIER=redis + - REDIS_SCHEME=tcp + - REDIS_HOST=redis + - REDIS_PORT=6379 + - REDIS_USERNAME='' + - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_DB="0" + - REDIS_CACHE_DB="1" + - MAIL_MAILER=smtp + - MAIL_HOST=${NOREPLY_EMAIL_HOST} + - MAIL_PORT=${NOREPLY_EMAIL_PORT} + - MAIL_FROM=${NOREPLY_EMAIL} + - MAIL_USERNAME=${NOREPLY_EMAIL_USERNAME} + - MAIL_PASSWORD=${NOREPLY_EMAIL_PASSWORD} + - MAIL_ENCRYPTION=ssl + - APP_URL=https://money.millironx.com + - APP_LOG_LEVEL=debug + - STATIC_CRON_TOKEN=${FIREFLY_III_CRON_TOKEN} + + fireflyiii-importer: + image: fireflyiii/data-importer:version-1.5 + container_name: "fireflyiii-importer" + restart: always + networks: + - fireflyiii + - redis + - web + depends_on: + - fireflyiii + - redis + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik_proxy" + - "traefik.http.routers.fireflyiiiimport.rule=Host(`moneyimport.millironx.com`)" + - "traefik.http.routers.fireflyiiiimport.tls=true" + - "traefik.http.services.fireflyiiiimport.loadbalancer.server.port=8080" + environment: + FIREFLY_III_URL: http://fireflyiii:8080 + VANITY_URL: https://money.millironx.com + FIREFLY_III_ACCESS_TOKEN: ${FIREFLY_III_IMPORT_TOKEN} + AUTO_IMPORT_SECRET: ${FIREFLY_III_IMPORT_SECRET} + TRUSTED_PROXIES: "*" + TZ: America/New_York + MAIL_MAILER: smtp + MAIL_HOST: ${NOREPLY_EMAIL_HOST} + MAIL_PORT: ${NOREPLY_EMAIL_PORT} + MAIL_FROM: ${NOREPLY_EMAIL} + MAIL_USERNAME: ${NOREPLY_EMAIL_USERNAME} + MAIL_PASSWORD: ${NOREPLY_EMAIL_PASSWORD} + MAIL_ENCRYPTION: ssl + CACHE_DRIVER: redis + SESSION_DRIER: redis + REDIS_SCHEME: tcp + REDIS_HOST: redis + REDIS_PORT: 6379 + REDIS_USERNAME: "" + REDIS_PASSWORD: ${REDIS_PASSWORD} + REDIS_DB: "0" + REDIS_CACHE_DB: "1" + + fireflyiii-cron: + image: alpine:latest + container_name: "fireflyiii-cron" + restart: always + networks: + - fireflyiii + depends_on: + - fireflyiii + command: sh -c " + apk add tzdata + && ln -s /usr/share/zoneinfo/$${TZ} /etc/localtime + | echo \"0 3 * * * wget -qO- http://fireflyiii:8080/api/v1/cron/$${CRON_TOKEN};echo\" + | crontab - + && crond -f -L /dev/stdout" + environment: + TZ: America/New_York + CRON_TOKEN: ${FIREFLY_III_CRON_TOKEN} diff --git a/secrets.nix b/secrets.nix index ce66958..7f4bd23 100644 --- a/secrets.nix +++ b/secrets.nix @@ -34,6 +34,8 @@ in { ++ [ mcentire-host ]; "secrets/darwin-policies-json.age".publicKeys = system-administrators ++ [ corianne-host ]; + "secrets/fireflyiii.toml.age".publicKeys = system-administrators + ++ [ mcentire-host ]; "secrets/freshrss.toml.age".publicKeys = system-administrators ++ [ mcentire-host ]; "secrets/millironx-books-s3.age".publicKeys = system-administrators diff --git a/secrets/fireflyiii.toml.age b/secrets/fireflyiii.toml.age new file mode 100644 index 0000000000000000000000000000000000000000..5a2630c52e5bfa8900ba88dee01f9c431a7b167d GIT binary patch literal 1230 zcmZY6>u(bU003|mR46KDz?i_a1Hyu5x$E`1_9PJdxa;+CZLg2DH%z40d-ZwkUDubH zP6*?@C;}2doWn5&;}OULhKF%MhhIeCG+a6=momAS zl%6HY0+`pTG;`I8B5F7m*6X#XI+oNW%aoeXYIzU@3Z`rw4#iv|Ti~40BBBTe)+H*u zEavJ{4P}6cLq-*ua?;Ky=Fg(}aIDehErGn*<#AIkt)6Bq&T@coq)Rz%8Lg*>Ygz#; z77FTwCs-c}W+N#NW=0b}Qjc3A!E($AHsp;+BI7qu9BnaL1HL$=N{B@ia+FbzHtRQ< zm=MCEa*UI*xYcV&z^LLt4KRqH;S^2{*I35M5d}5b81_l_B%*IfNi0rUf{c?*7f2M9 z#BfHz&7eKO<%?Dr^&u)4ht0lZBh3U*(U+7kuaStzInGa8>yvm3C?;)2f@9W6tKe=S=)w>+G+d z^-4?Djy+nvX6E_qkN@>b*Cg-n#SaTML$;zbt+^kyGz$U0StW)nbn{BQr-gwapJ(Y{%9; z`F`Y;<*T>%c*pfnul9ESb+zsFZ}#80oxAkKyVrrPrW3Q`om^ktZx=_csQ6=QB@*w3 z$vN8&cbuB>_BCwrggHkaPYWKrTR9!)VgtaRei&)ZIXa%>?7>^S#KWqAdy^dx RxcE=q@05nFk0BmT`44?y;2;11 literal 0 HcmV?d00001 diff --git a/services/fireflyiii.nix b/services/fireflyiii.nix new file mode 100644 index 0000000..704f4fc --- /dev/null +++ b/services/fireflyiii.nix @@ -0,0 +1,162 @@ +{ config, pkgs, home-manager-quadlet-nix, ... }: +let + user = "fireflyiii"; + port = "34733"; + containerPort = "8080"; + authentikPort = "9000"; + stateDirectory = "/var/lib/${user}"; + servicePaths = [ "upload" ]; + databasePaths = [ "database" ]; +in { + age.secrets."fireflyiii.toml" = { + file = ./../secrets/fireflyiii.toml.age; + owner = user; + }; + + millironx.podman-secrets.fireflyiii = { + inherit user; + secrets-files = [ config.age.secrets."fireflyiii.toml".path ]; + }; + + systemd.tmpfiles.rules = + map (d: "d ${stateDirectory}/${d} 1775 ${user} ${user} -") [ "" ] + ++ servicePaths ++ databasePaths; + + services.borgmatic.configurations."${config.networking.hostName}" = { + source_directories = map (d: "${stateDirectory}/${d}") servicePaths; + postgresql_databases = [{ + name = user; + psql_command = + "/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db psql --username=${user}"; + pg_dump_command = + "/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_dump --username=${user}"; + pg_restore_command = + "/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_restore --username=${user}"; + }]; + }; + + services.caddy.virtualHosts."money.millironx.com".extraConfig = '' + reverse_proxy /outpost.goauthentik.io/* http://127.0.0.1:${authentikPort} + forward_auth http://127.0.0.1:${authentikPort} { + uri /outpost.goauthentik.io/auth/caddy + copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version + } + reverse_proxy http://127.0.0.1:${port} + ''; + + users.users."${user}" = { + group = user; + isNormalUser = true; + home = stateDirectory; + createHome = true; + linger = true; + autoSubUidGidRange = true; + }; + users.groups."${user}" = { }; + + home-manager.users."${user}" = { config, osConfig, ... }: { + imports = [ home-manager-quadlet-nix ]; + + home.stateVersion = "25.05"; + + systemd.user = let inherit (config.virtualisation.quadlet) containers; + in { + services."${user}-cron" = { + Unit = { + Description = "Firefly III cron"; + Requires = [ containers."${user}".ref ]; + After = [ containers."${user}".ref ]; + }; + Service.ExecStart = + "${pkgs.podman}/bin/podman exec ${user} /usr/local/bin/php /var/www/html/artisan firefly-iii:cron"; + }; + timers."${user}-cron" = { + Unit.Description = "Firefly III cron"; + Timer.OnCalendar = "daily"; + }; + }; + + virtualisation.quadlet = let + inherit (config.virtualisation.quadlet) containers; + inherit (config.virtualisation.quadlet) networks; + secrets = osConfig.millironx.podman-secrets.freshrss; + in { + autoUpdate.enable = true; + autoEscape = true; + + networks."${user}" = { }; + + containers = { + "${user}-db" = { + autoStart = true; + containerConfig = { + image = "docker.io/library/postgres:16"; + environments = { + POSTGRES_DB = user; + POSTGRES_USER = user; + }; + secrets = [ + "POSTGRES_PASSWORD,type=env" + "POSTGRES_PASSWORD,type=env,target=PGPASSWORD" + ]; + healthCmd = "pg_isready -d $\${POSTGRES_DB} -U $\${POSTGRES_USER}"; + healthInterval = "30s"; + healthRetries = 5; + healthStartPeriod = "20s"; + volumes = + [ "${stateDirectory}/database:/var/lib/postgresql/data:U" ]; + networks = [ networks."${user}".ref ]; + }; + unitConfig.Requires = [ secrets.ref ]; + unitConfig.After = [ secrets.ref ]; + }; + + "${user}" = { + autoStart = true; + containerConfig = { + image = "docker.io/fireflyiii/core:version-6"; + environments = { + APP_ENV = "local"; + DEFAULT_LANGUAGE = "en_US"; + TZ = "America/New_York"; + TRUSTED_PROXIES = "*"; + DB_CONNECTION = "pgsql"; + DB_HOST = "${user}-db"; + DB_PORT = "5432"; + DB_DATABASE = user; + DB_USERNAME = user; + CACHE_DRIVER = "redis"; + SESSION_DRIVER = "redis"; + REDIS_SCHEME = "tcp"; + REDIS_HOST = "host.docker.internal"; + REDIS_PORT = "6379"; + REDIS_DB = "0"; + REDIS_CACHE_DB = "1"; + MAIL_MAILER = "smtp"; + APP_URL = "https://money.millironx.com"; + AUTHENTICATION_GUARD = "remote_user_guard"; + AUTHENTICATION_GUARD_HEADER = "HTTP_X_AUTHENTIK_USERNAME"; + AUTHENTICATION_GUARD_EMAIL = "HTTP_X_AUTHENTIK_EMAIL"; + }; + secrets = map (s: "${s},type=env") [ + "SITE_OWNER" + "APP_KEY" + "DB_PASSWORD" + "MAIL_HOST" + "MAIL_PORT" + "MAIL_FROM" + "MAIL_USERNAME" + "MAIL_PASSWORD" + "MAIL_ENCRYPTION" + ]; + volumes = [ "${stateDirectory}/upload:/var/html/storage/upload:U" ]; + networks = [ networks."${user}".ref ]; + publishPorts = [ "127.0.0.1:${port}:${containerPort}" ]; + }; + unitConfig.Requires = [ secrets.ref containers."${user}".ref ]; + unitConfig.After = [ secrets.ref containers."${user}".ref ]; + }; + }; + }; + }; +} diff --git a/systems/linux/mcentire.nix b/systems/linux/mcentire.nix index 5d80ada..5861114 100644 --- a/systems/linux/mcentire.nix +++ b/systems/linux/mcentire.nix @@ -8,6 +8,7 @@ ./../../services/crowdsec.nix ./../../services/authentik.nix ./../../services/audiobookshelf.nix + ./../../services/fireflyiii.nix ./../../services/freshrss.nix ./../../services/navidrome.nix ];