From 5d5b69a6296af1bd5ed2d2de88a491c93b41dbda Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sun, 30 Apr 2023 22:04:12 -0500 Subject: [PATCH] firefly-iii: Deploy Firefly III [Firefly III][0] is a free and open source, web-based personal finance management application. It features a double-entry bookkeeping system for tracking transactions, plus other classification options like budgets, categories, and tags. It has a rule engine that can automatically manipulate transactions, plus several other really useful features. The application itself is mostly standard browser-based GUI written in PHP. There is an official container image, though it is not particularly well designed and must be run as root (it does drop privileges before launching the actual application, thankfully). I may decide to create a better image later. Along with the main application, there is a separate tool for importing transactions from a CSV file. Its design is rather interesting: though it is a web-based application, it does not have any authentication or user management, but uses a user API key to access the main Firefly III application. This effectively requires us to have one instance of the importer per user. While not ideal, it isn't particularly problematic since there are only two of us (and Tabitha may not even end up using it; she seems to like YNAB). [0]: https://www.firefly-iii.org/ --- authelia/configuration.yml | 10 ++ firefly-iii/.gitignore | 4 + firefly-iii/firefly-iii-importer.env | 3 + firefly-iii/firefly-iii.env | 30 ++++++ firefly-iii/firefly-iii.yaml | 149 +++++++++++++++++++++++++++ firefly-iii/importer-ingress.yaml | 72 +++++++++++++ firefly-iii/importer.yaml | 124 ++++++++++++++++++++++ firefly-iii/ingress.yaml | 33 ++++++ firefly-iii/kustomization.yaml | 64 ++++++++++++ firefly-iii/redis.yaml | 91 ++++++++++++++++ postgresql/default-cluster.yaml | 3 + 11 files changed, 583 insertions(+) create mode 100644 firefly-iii/.gitignore create mode 100644 firefly-iii/firefly-iii-importer.env create mode 100644 firefly-iii/firefly-iii.env create mode 100644 firefly-iii/firefly-iii.yaml create mode 100644 firefly-iii/importer-ingress.yaml create mode 100644 firefly-iii/importer.yaml create mode 100644 firefly-iii/ingress.yaml create mode 100644 firefly-iii/kustomization.yaml create mode 100644 firefly-iii/redis.yaml diff --git a/authelia/configuration.yml b/authelia/configuration.yml index f1cc044..176102b 100644 --- a/authelia/configuration.yml +++ b/authelia/configuration.yml @@ -8,6 +8,12 @@ access_control: rules: - domain: paperless.pyrocufflink.blue policy: two_factor + - domain: firefly.pyrocufflink.blue + resources: + - '^/api/' + policy: bypass + - domain: firefly.pyrocufflink.blue + policy: two_factor - domain: scan.pyrocufflink.blue networks: - internal @@ -69,6 +75,10 @@ session: expiration: 1d inactivity: 4h +server: + buffers: + read: 16384 + storage: local: path: /var/lib/authelia/db.sqlite3 diff --git a/firefly-iii/.gitignore b/firefly-iii/.gitignore new file mode 100644 index 0000000..c09b02c --- /dev/null +++ b/firefly-iii/.gitignore @@ -0,0 +1,4 @@ +*.access-token +app.key +cron.token +db.password diff --git a/firefly-iii/firefly-iii-importer.env b/firefly-iii/firefly-iii-importer.env new file mode 100644 index 0000000..e37427e --- /dev/null +++ b/firefly-iii/firefly-iii-importer.env @@ -0,0 +1,3 @@ +TZ=America/Chicago + +TRUSTED_PROXIES=172.30.0.160/28 diff --git a/firefly-iii/firefly-iii.env b/firefly-iii/firefly-iii.env new file mode 100644 index 0000000..efc5836 --- /dev/null +++ b/firefly-iii/firefly-iii.env @@ -0,0 +1,30 @@ +APP_ENV=local + +SITE_OWNER=dustin@hatch.name + +TZ=America/Chicago + +TRUSTED_PROXIES=172.30.0.160/28 + +DB_CONNECTION=pgsql +DB_HOST=default.postgresql +DB_PORT=5432 +DB_USERNAME=firefly-iii.firefly +DB_DATABASE=firefly + +CACHE_DRIVER=redis +SESSION_DRIVER=redis + +REDIS_SCHEME=tcp +REDIS_HOST=redis +REDIS_PORT=6379 + +AUTHENTICATION_GUARD=remote_user_guard +AUTHENTICATION_GUARD_HEADER=Remote-User +AUTHENTICATION_GUARD_EMAIL=Remote-Email + +MAIL_MAILER=smtp +MAIL_HOST=mail.pyrocufflink.blue +MAIL_PORT=25 +MAIL_ENCRYPTION=null +MAIL_FROM=firefly-iii@pyrocufflink.net diff --git a/firefly-iii/firefly-iii.yaml b/firefly-iii/firefly-iii.yaml new file mode 100644 index 0000000..ba743aa --- /dev/null +++ b/firefly-iii/firefly-iii.yaml @@ -0,0 +1,149 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: firefly-iii + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app.kubernetes.io/component: firefly-iii + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/name: firefly-iii + app.kubernetes.io/part-of: firefly-iii + name: firefly-iii +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 2Gi + +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: firefly-iii + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/name: firefly-iii + app.kubernetes.io/part-of: firefly-iii + name: firefly-iii +spec: + ports: + - port: 8080 + name: http + selector: + app.kubernetes.io/component: firefly-iii + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/name: firefly-iii + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: firefly-iii + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/name: firefly-iii + app.kubernetes.io/part-of: firefly-iii + name: firefly-iii +spec: + selector: + matchLabels: + app.kubernetes.io/component: firefly-iii + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/name: firefly-iii + template: + metadata: + labels: + app.kubernetes.io/component: firefly-iii + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/name: firefly-iii + app.kubernetes.io/part-of: firefly-iii + spec: + containers: + - name: firefly-iii + image: docker.io/fireflyiii/core:version-6.0.8 + envFrom: + - configMapRef: + name: firefly-iii + optional: true + env: + - name: APP_KEY_FILE + value: /run/secrets/firefly-iii/app.key + - name: DB_PASSWORD_FILE + value: /run/secrets/firefly-iii/db.password + - name: STATIC_CRON_TOKEN_FILE + value: /run/secrets/firefly-iii/cron.token + ports: + - containerPort: 8080 + name: http + readinessProbe: + httpGet: + port: 8080 + path: /health + failureThreshold: 3 + periodSeconds: 60 + successThreshold: 1 + timeoutSeconds: 1 + startupProbe: + httpGet: + port: 8080 + path: /health + failureThreshold: 30 + periodSeconds: 3 + initialDelaySeconds: 3 + successThreshold: 1 + timeoutSeconds: 1 + volumeMounts: + - name: firefly-iii-secrets + mountPath: /run/secrets/firefly-iii + readOnly: true + - name: firefly-iii-data + mountPath: /var/www/html/storage/upload + subPath: upload + securityContext: + fsGroup: 33 + volumes: + - name: firefly-iii-secrets + secret: + secretName: firefly-iii + defaultMode: 0440 + - name: firefly-iii-data + persistentVolumeClaim: + claimName: firefly-iii + +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: firefly-iii +spec: + timeZone: America/Chicago + schedule: '0 3 * * *' + jobTemplate: + spec: + template: + spec: + containers: + - image: docker.io/library/busybox + name: wget + command: + - wget + args: + - http://$(FIREFLY_III_SERVICE_HOST):$(FIREFLY_III_SERVICE_PORT)/api/v1/cron/$(STATIC_CRON_TOKEN) + - -O + - /dev/null + env: + - name: STATIC_CRON_TOKEN + valueFrom: + secretKeyRef: + name: firefly-iii + key: cron.token + securityContext: + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + restartPolicy: Never diff --git a/firefly-iii/importer-ingress.yaml b/firefly-iii/importer-ingress.yaml new file mode 100644 index 0000000..640d9cc --- /dev/null +++ b/firefly-iii/importer-ingress.yaml @@ -0,0 +1,72 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: firefly-iii-importer-dustin + labels: + app.kubernetes.io/name: firefly-iii-importer-dustin + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: dustin + app.kubernetes.io/part-of: firefly-iii-importer + annotations: + cert-manager.io/cluster-issuer: zerossl + nginx.ingress.kubernetes.io/proxy-buffer-size: "8k" + nginx.ingress.kubernetes.io/auth-method: GET + nginx.ingress.kubernetes.io/auth-url: http://authelia.authelia.svc.cluster.local:9091/api/verify + nginx.ingress.kubernetes.io/auth-signin: https://auth.pyrocufflink.blue/?rm=$request_method + nginx.ingress.kubernetes.io/auth-response-headers: Remote-User,Remote-Name,Remote-Groups,Remote-Email + nginx.ingress.kubernetes.io/auth-snippet: | + proxy_set_header X-Forwarded-Method $request_method; +spec: + ingressClassName: nginx + tls: + - hosts: + - '*.import.firefly.pyrocufflink.blue' + secretName: firefly-import-cert + rules: + - host: dustin.import.firefly.pyrocufflink.blue + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: firefly-iii-importer-dustin + port: + name: http + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: firefly-iii-importer-tabitha + labels: + app.kubernetes.io/name: firefly-iii-importer-tabitha + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: tabitha + app.kubernetes.io/part-of: firefly-iii-importer + annotations: + nginx.ingress.kubernetes.io/proxy-buffer-size: "8k" + nginx.ingress.kubernetes.io/auth-method: GET + nginx.ingress.kubernetes.io/auth-url: http://authelia.authelia.svc.cluster.local:9091/api/verify + nginx.ingress.kubernetes.io/auth-signin: https://auth.pyrocufflink.blue/?rm=$request_method + nginx.ingress.kubernetes.io/auth-response-headers: Remote-User,Remote-Name,Remote-Groups,Remote-Email + nginx.ingress.kubernetes.io/auth-snippet: | + proxy_set_header X-Forwarded-Method $request_method; +spec: + ingressClassName: nginx + tls: + - hosts: + - '*.import.firefly.pyrocufflink.blue' + secretName: firefly-import-cert + rules: + - host: tabitha.import.firefly.pyrocufflink.blue + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: firefly-iii-importer-tabitha + port: + name: http + diff --git a/firefly-iii/importer.yaml b/firefly-iii/importer.yaml new file mode 100644 index 0000000..830da03 --- /dev/null +++ b/firefly-iii/importer.yaml @@ -0,0 +1,124 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: dustin + app.kubernetes.io/name: firefly-iii-importer-dustin + app.kubernetes.io/part-of: firefly-iii + name: firefly-iii-importer-dustin +spec: + ports: + - port: 8080 + name: http + selector: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: dustin + app.kubernetes.io/name: firefly-iii-importer-dustin + +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: tabitha + app.kubernetes.io/name: firefly-iii-importer-tabitha + app.kubernetes.io/part-of: firefly-iii + name: firefly-iii-importer-tabitha +spec: + ports: + - port: 8080 + name: http + selector: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: tabitha + app.kubernetes.io/name: firefly-iii-importer-tabitha + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: dustin + app.kubernetes.io/name: firefly-iii-importer-dustin + app.kubernetes.io/part-of: firefly-iii + name: firefly-iii-importer-dustin +spec: + selector: + matchLabels: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: dustin + app.kubernetes.io/name: firefly-iii-importer-dustin + template: + metadata: + labels: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: dustin + app.kubernetes.io/name: firefly-iii-importer-dustin + app.kubernetes.io/part-of: firefly-iii-importer + spec: + containers: + - name: firefly-iii + image: docker.io/fireflyiii/data-importer:version-1.2.2 + envFrom: + - configMapRef: + name: firefly-iii-importer + optional: true + env: + - name: FIREFLY_III_URL + value: http://firefly-iii:8080 + - name: FIREFLY_III_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: firefly-iii-importer + key: dustin.access-token + ports: + - containerPort: 8080 + name: http + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: tabitha + app.kubernetes.io/name: firefly-iii-importer-tabitha + app.kubernetes.io/part-of: firefly-iii + name: firefly-iii-importer-tabitha +spec: + selector: + matchLabels: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: tabitha + app.kubernetes.io/name: firefly-iii-importer-tabitha + template: + metadata: + labels: + app.kubernetes.io/component: firefly-iii-importer + app.kubernetes.io/instance: tabitha + app.kubernetes.io/name: firefly-iii-importer-tabitha + app.kubernetes.io/part-of: firefly-iii-importer + spec: + containers: + - name: firefly-iii + image: docker.io/fireflyiii/data-importer:version-1.2.2 + envFrom: + - configMapRef: + name: firefly-iii-importer + optional: true + env: + - name: FIREFLY_III_URL + value: http://firefly-iii:8080 + - name: FIREFLY_III_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: firefly-iii-importer + key: tabitha.access-token + ports: + - containerPort: 8080 + name: http + diff --git a/firefly-iii/ingress.yaml b/firefly-iii/ingress.yaml new file mode 100644 index 0000000..5b0f32b --- /dev/null +++ b/firefly-iii/ingress.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: firefly-iii + labels: + app.kubernetes.io/name: firefly-iii + app.kubernetes.io/component: firefly-iii + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/part-of: firefly-iii + annotations: + nginx.ingress.kubernetes.io/auth-method: GET + nginx.ingress.kubernetes.io/auth-url: http://authelia.authelia.svc.cluster.local:9091/api/verify + nginx.ingress.kubernetes.io/auth-signin: https://auth.pyrocufflink.blue/?rm=$request_method + nginx.ingress.kubernetes.io/auth-response-headers: Remote-User,Remote-Name,Remote-Groups,Remote-Email + nginx.ingress.kubernetes.io/auth-snippet: | + proxy_set_header X-Forwarded-Method $request_method; +spec: + ingressClassName: nginx + tls: + - hosts: + - firefly.pyrocufflink.blue + rules: + - host: firefly.pyrocufflink.blue + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: firefly-iii + port: + name: http diff --git a/firefly-iii/kustomization.yaml b/firefly-iii/kustomization.yaml new file mode 100644 index 0000000..b0c1e1a --- /dev/null +++ b/firefly-iii/kustomization.yaml @@ -0,0 +1,64 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: firefly-iii + +resources: +- redis.yaml +- firefly-iii.yaml +- ingress.yaml +- importer.yaml +- importer-ingress.yaml + +configMapGenerator: +- name: firefly-iii + envs: + - firefly-iii.env + options: + disableNameSuffixHash: true +- name: firefly-iii-importer + envs: + - firefly-iii-importer.env + options: + disableNameSuffixHash: true + +secretGenerator: +- name: firefly-iii + files: + - app.key + - cron.token + options: + disableNameSuffixHash: true +- name: firefly-iii-importer + files: + - dustin.access-token + - tabitha.access-token + options: + disableNameSuffixHash: true + +patches: +# This patch changes the source secret for the PostgreSQL database +# password from the default (`db.password` inside `firefly-iii`) to +# a secret managed by the postgres operator. +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: firefly-iii + spec: + template: + spec: + containers: + - name: firefly-iii + env: + - name: DB_PASSWORD_FILE + value: /run/secrets/postgresql/password + volumeMounts: + - name: db-secret + mountPath: /run/secrets/postgresql + readOnly: true + volumes: + - name: db-secret + secret: + secretName: firefly-iii.firefly.default.credentials.postgresql.acid.zalan.do + defaultMode: 0440 diff --git a/firefly-iii/redis.yaml b/firefly-iii/redis.yaml new file mode 100644 index 0000000..4084754 --- /dev/null +++ b/firefly-iii/redis.yaml @@ -0,0 +1,91 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis + namespace: firefly-iii + labels: + app.kubernetes.io/name: redis + app.kubernetes.io/component: redis + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/part-of: firefly-iii +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: redis + app.kubernetes.io/component: redis + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/part-of: firefly-iii + name: redis + namespace: firefly-iii +spec: + ports: + - name: redis + port: 6379 + selector: + app.kubernetes.io/name: redis + app.kubernetes.io/component: redis + app.kubernetes.io/instance: firefly-iii + type: ClusterIP + +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis + namespace: firefly-iii + labels: + app.kubernetes.io/name: redis + app.kubernetes.io/component: redis + app.kubernetes.io/instance: firefly-iii + app.kubernetes.io/part-of: firefly-iii +spec: + serviceName: redis + selector: + matchLabels: + app.kubernetes.io/name: redis + app.kubernetes.io/component: redis + app.kubernetes.io/instance: firefly-iii + template: + metadata: + labels: + app.kubernetes.io/name: redis + app.kubernetes.io/component: redis + app.kubernetes.io/instance: firefly-iii + spec: + containers: + - name: redis + image: docker.io/library/redis:7 + imagePullPolicy: IfNotPresent + ports: + - name: redis + containerPort: 6379 + securityContext: + runAsNonRoot: true + readOnlyRootFilesystem: true + runAsUser: 1000 + runAsGroup: 1000 + volumeMounts: + - name: redisdata + mountPath: /data + subPath: data + - name: tmp + mountPath: /tmp + securityContext: + fsGroup: 1000 + volumes: + - name: redisdata + persistentVolumeClaim: + claimName: redis + - name: tmp + emptyDir: + diff --git a/postgresql/default-cluster.yaml b/postgresql/default-cluster.yaml index 652b41f..b30a7f1 100644 --- a/postgresql/default-cluster.yaml +++ b/postgresql/default-cluster.yaml @@ -14,5 +14,8 @@ spec: dustin: - superuser - createdb + firefly-iii.firefly: + - login databases: dustin: dustin + firefly: firefly-iii.firefly