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 0000000..5a2630c Binary files /dev/null and b/secrets/fireflyiii.toml.age differ 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 c259c27..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 ]; @@ -65,6 +66,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;