Compare commits
No commits in common. "62fa6ef371e33d50c38cffc96a20425b416284a5" and "354a803ff44c09c73647fdf7349cf3f39829699c" have entirely different histories.
62fa6ef371
...
354a803ff4
@ -1,30 +0,0 @@
|
|||||||
# clusters/atlas/flux-system/applications/finance/kustomization.yaml
|
|
||||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
|
||||||
kind: Kustomization
|
|
||||||
metadata:
|
|
||||||
name: finance
|
|
||||||
namespace: flux-system
|
|
||||||
spec:
|
|
||||||
interval: 10m
|
|
||||||
path: ./services/finance
|
|
||||||
prune: true
|
|
||||||
sourceRef:
|
|
||||||
kind: GitRepository
|
|
||||||
name: flux-system
|
|
||||||
targetNamespace: finance
|
|
||||||
dependsOn:
|
|
||||||
- name: keycloak
|
|
||||||
- name: postgres
|
|
||||||
- name: traefik
|
|
||||||
- name: vault
|
|
||||||
- name: mailu
|
|
||||||
healthChecks:
|
|
||||||
- apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
name: actual-budget
|
|
||||||
namespace: finance
|
|
||||||
- apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
name: firefly
|
|
||||||
namespace: finance
|
|
||||||
wait: false
|
|
||||||
@ -28,5 +28,4 @@ resources:
|
|||||||
- nextcloud-mail-sync/kustomization.yaml
|
- nextcloud-mail-sync/kustomization.yaml
|
||||||
- outline/kustomization.yaml
|
- outline/kustomization.yaml
|
||||||
- planka/kustomization.yaml
|
- planka/kustomization.yaml
|
||||||
- finance/kustomization.yaml
|
|
||||||
- health/kustomization.yaml
|
- health/kustomization.yaml
|
||||||
|
|||||||
@ -102,12 +102,6 @@ spec:
|
|||||||
value: wger-user-sync
|
value: wger-user-sync
|
||||||
- name: WGER_USER_SYNC_WAIT_TIMEOUT_SEC
|
- name: WGER_USER_SYNC_WAIT_TIMEOUT_SEC
|
||||||
value: "90"
|
value: "90"
|
||||||
- name: FIREFLY_NAMESPACE
|
|
||||||
value: finance
|
|
||||||
- name: FIREFLY_USER_SYNC_CRONJOB
|
|
||||||
value: firefly-user-sync
|
|
||||||
- name: FIREFLY_USER_SYNC_WAIT_TIMEOUT_SEC
|
|
||||||
value: "90"
|
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
# services/finance/actual-budget-data-pvc.yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: actual-budget-data
|
|
||||||
namespace: finance
|
|
||||||
spec:
|
|
||||||
accessModes: ["ReadWriteOnce"]
|
|
||||||
storageClassName: asteria
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 10Gi
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
# services/finance/actual-budget-deployment.yaml
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: actual-budget
|
|
||||||
namespace: finance
|
|
||||||
labels:
|
|
||||||
app: actual-budget
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: actual-budget
|
|
||||||
strategy:
|
|
||||||
type: RollingUpdate
|
|
||||||
rollingUpdate:
|
|
||||||
maxSurge: 0
|
|
||||||
maxUnavailable: 1
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: actual-budget
|
|
||||||
annotations:
|
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
|
||||||
vault.hashicorp.com/agent-pre-populate-only: "true"
|
|
||||||
vault.hashicorp.com/role: "finance"
|
|
||||||
vault.hashicorp.com/agent-inject-secret-actual-env.sh: "kv/data/atlas/finance/actual-oidc"
|
|
||||||
vault.hashicorp.com/agent-inject-template-actual-env.sh: |
|
|
||||||
{{ with secret "kv/data/atlas/finance/actual-oidc" }}
|
|
||||||
export ACTUAL_OPENID_CLIENT_ID="{{ .Data.data.ACTUAL_OPENID_CLIENT_ID }}"
|
|
||||||
export ACTUAL_OPENID_CLIENT_SECRET="{{ .Data.data.ACTUAL_OPENID_CLIENT_SECRET }}"
|
|
||||||
{{ end }}
|
|
||||||
spec:
|
|
||||||
serviceAccountName: finance-vault
|
|
||||||
nodeSelector:
|
|
||||||
kubernetes.io/arch: arm64
|
|
||||||
node-role.kubernetes.io/worker: "true"
|
|
||||||
affinity:
|
|
||||||
nodeAffinity:
|
|
||||||
preferredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
- weight: 100
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi5"]
|
|
||||||
- weight: 70
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi4"]
|
|
||||||
securityContext:
|
|
||||||
runAsUser: 1000
|
|
||||||
runAsGroup: 1000
|
|
||||||
fsGroup: 1000
|
|
||||||
fsGroupChangePolicy: OnRootMismatch
|
|
||||||
initContainers:
|
|
||||||
- name: init-data-permissions
|
|
||||||
image: docker.io/alpine:3.20
|
|
||||||
command: ["/bin/sh", "-c"]
|
|
||||||
args:
|
|
||||||
- |
|
|
||||||
set -e
|
|
||||||
mkdir -p /data
|
|
||||||
chown -R 1000:1000 /data
|
|
||||||
securityContext:
|
|
||||||
runAsUser: 0
|
|
||||||
runAsGroup: 0
|
|
||||||
volumeMounts:
|
|
||||||
- name: actual-data
|
|
||||||
mountPath: /data
|
|
||||||
- name: init-openid
|
|
||||||
image: actualbudget/actual-server:sha-b6452f9-alpine
|
|
||||||
command: ["/bin/sh", "-c"]
|
|
||||||
args:
|
|
||||||
- |
|
|
||||||
set -eu
|
|
||||||
. /vault/secrets/actual-env.sh
|
|
||||||
node /scripts/actual_openid_bootstrap.mjs
|
|
||||||
env:
|
|
||||||
- name: ACTUAL_DATA_DIR
|
|
||||||
value: /data
|
|
||||||
- name: ACTUAL_LOGIN_METHOD
|
|
||||||
value: openid
|
|
||||||
- name: ACTUAL_ALLOWED_LOGIN_METHODS
|
|
||||||
value: openid
|
|
||||||
- name: ACTUAL_MULTIUSER
|
|
||||||
value: "true"
|
|
||||||
- name: ACTUAL_OPENID_DISCOVERY_URL
|
|
||||||
value: https://sso.bstein.dev/realms/atlas
|
|
||||||
- name: ACTUAL_OPENID_SERVER_HOSTNAME
|
|
||||||
value: https://budget.bstein.dev
|
|
||||||
volumeMounts:
|
|
||||||
- name: actual-data
|
|
||||||
mountPath: /data
|
|
||||||
- name: actual-openid-bootstrap-script
|
|
||||||
mountPath: /scripts
|
|
||||||
readOnly: true
|
|
||||||
containers:
|
|
||||||
- name: actual-budget
|
|
||||||
image: actualbudget/actual-server:sha-b6452f9-alpine
|
|
||||||
command: ["/bin/sh", "-c"]
|
|
||||||
args:
|
|
||||||
- |
|
|
||||||
. /vault/secrets/actual-env.sh
|
|
||||||
exec node app
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: 5006
|
|
||||||
env:
|
|
||||||
- name: ACTUAL_DATA_DIR
|
|
||||||
value: /data
|
|
||||||
- name: ACTUAL_LOGIN_METHOD
|
|
||||||
value: openid
|
|
||||||
- name: ACTUAL_ALLOWED_LOGIN_METHODS
|
|
||||||
value: openid
|
|
||||||
- name: ACTUAL_MULTIUSER
|
|
||||||
value: "true"
|
|
||||||
- name: ACTUAL_OPENID_DISCOVERY_URL
|
|
||||||
value: https://sso.bstein.dev/realms/atlas
|
|
||||||
- name: ACTUAL_OPENID_SERVER_HOSTNAME
|
|
||||||
value: https://budget.bstein.dev
|
|
||||||
volumeMounts:
|
|
||||||
- name: actual-data
|
|
||||||
mountPath: /data
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 3
|
|
||||||
failureThreshold: 6
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 20
|
|
||||||
timeoutSeconds: 3
|
|
||||||
failureThreshold: 6
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 200m
|
|
||||||
memory: 512Mi
|
|
||||||
limits:
|
|
||||||
cpu: "1"
|
|
||||||
memory: 1Gi
|
|
||||||
volumes:
|
|
||||||
- name: actual-data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: actual-budget-data
|
|
||||||
- name: actual-openid-bootstrap-script
|
|
||||||
configMap:
|
|
||||||
name: actual-openid-bootstrap-script
|
|
||||||
defaultMode: 0555
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
# services/finance/actual-budget-ingress.yaml
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: actual-budget
|
|
||||||
namespace: finance
|
|
||||||
annotations:
|
|
||||||
kubernetes.io/ingress.class: traefik
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
||||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
|
||||||
cert-manager.io/cluster-issuer: letsencrypt
|
|
||||||
spec:
|
|
||||||
tls:
|
|
||||||
- hosts: ["budget.bstein.dev"]
|
|
||||||
secretName: actual-budget-tls
|
|
||||||
rules:
|
|
||||||
- host: budget.bstein.dev
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: actual-budget
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
# services/finance/actual-budget-service.yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: actual-budget
|
|
||||||
namespace: finance
|
|
||||||
labels:
|
|
||||||
app: actual-budget
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: actual-budget
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
port: 80
|
|
||||||
targetPort: 5006
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
# services/finance/finance-secrets-ensure-job.yaml
|
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: Job
|
|
||||||
metadata:
|
|
||||||
name: finance-secrets-ensure-1
|
|
||||||
namespace: finance
|
|
||||||
spec:
|
|
||||||
backoffLimit: 1
|
|
||||||
ttlSecondsAfterFinished: 3600
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
serviceAccountName: finance-secrets-ensure
|
|
||||||
restartPolicy: Never
|
|
||||||
affinity:
|
|
||||||
nodeAffinity:
|
|
||||||
preferredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
- weight: 100
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi5"]
|
|
||||||
- weight: 70
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi4"]
|
|
||||||
nodeSelector:
|
|
||||||
kubernetes.io/arch: arm64
|
|
||||||
node-role.kubernetes.io/worker: "true"
|
|
||||||
containers:
|
|
||||||
- name: ensure
|
|
||||||
image: alpine:3.20
|
|
||||||
command: ["/scripts/finance_secrets_ensure.sh"]
|
|
||||||
env:
|
|
||||||
- name: VAULT_ROLE
|
|
||||||
value: finance-secrets
|
|
||||||
volumeMounts:
|
|
||||||
- name: finance-secrets-ensure-script
|
|
||||||
mountPath: /scripts
|
|
||||||
readOnly: true
|
|
||||||
- name: firefly-db
|
|
||||||
mountPath: /secrets/firefly-db
|
|
||||||
readOnly: true
|
|
||||||
- name: actualbudget-db
|
|
||||||
mountPath: /secrets/actualbudget-db
|
|
||||||
readOnly: true
|
|
||||||
volumes:
|
|
||||||
- name: finance-secrets-ensure-script
|
|
||||||
configMap:
|
|
||||||
name: finance-secrets-ensure-script
|
|
||||||
defaultMode: 0555
|
|
||||||
- name: firefly-db
|
|
||||||
secret:
|
|
||||||
secretName: firefly-db
|
|
||||||
- name: actualbudget-db
|
|
||||||
secret:
|
|
||||||
secretName: actualbudget-db
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
# services/finance/firefly-cronjob.yaml
|
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: CronJob
|
|
||||||
metadata:
|
|
||||||
name: firefly-cron
|
|
||||||
namespace: finance
|
|
||||||
spec:
|
|
||||||
schedule: "0 3 * * *"
|
|
||||||
concurrencyPolicy: Forbid
|
|
||||||
successfulJobsHistoryLimit: 1
|
|
||||||
failedJobsHistoryLimit: 3
|
|
||||||
jobTemplate:
|
|
||||||
spec:
|
|
||||||
backoffLimit: 1
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
|
||||||
vault.hashicorp.com/agent-pre-populate-only: "true"
|
|
||||||
vault.hashicorp.com/role: "finance"
|
|
||||||
vault.hashicorp.com/agent-inject-secret-firefly-cron-token: "kv/data/atlas/finance/firefly-secrets"
|
|
||||||
vault.hashicorp.com/agent-inject-template-firefly-cron-token: |
|
|
||||||
{{- with secret "kv/data/atlas/finance/firefly-secrets" -}}
|
|
||||||
{{ .Data.data.STATIC_CRON_TOKEN }}
|
|
||||||
{{- end -}}
|
|
||||||
spec:
|
|
||||||
serviceAccountName: finance-vault
|
|
||||||
restartPolicy: Never
|
|
||||||
affinity:
|
|
||||||
nodeAffinity:
|
|
||||||
preferredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
- weight: 100
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi5"]
|
|
||||||
- weight: 70
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi4"]
|
|
||||||
nodeSelector:
|
|
||||||
kubernetes.io/arch: arm64
|
|
||||||
node-role.kubernetes.io/worker: "true"
|
|
||||||
containers:
|
|
||||||
- name: cron
|
|
||||||
image: curlimages/curl:8.5.0
|
|
||||||
command: ["/bin/sh", "-c"]
|
|
||||||
args:
|
|
||||||
- |
|
|
||||||
set -eu
|
|
||||||
token="$(cat /vault/secrets/firefly-cron-token)"
|
|
||||||
curl -fsS "http://firefly.finance.svc.cluster.local/api/v1/cron/${token}"
|
|
||||||
@ -1,166 +0,0 @@
|
|||||||
# services/finance/firefly-deployment.yaml
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: firefly
|
|
||||||
namespace: finance
|
|
||||||
labels:
|
|
||||||
app: firefly
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: firefly
|
|
||||||
strategy:
|
|
||||||
type: RollingUpdate
|
|
||||||
rollingUpdate:
|
|
||||||
maxSurge: 0
|
|
||||||
maxUnavailable: 1
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: firefly
|
|
||||||
annotations:
|
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
|
||||||
vault.hashicorp.com/role: "finance"
|
|
||||||
vault.hashicorp.com/agent-inject-secret-firefly-env.sh: "kv/data/atlas/finance/firefly-db"
|
|
||||||
vault.hashicorp.com/agent-inject-template-firefly-env.sh: |
|
|
||||||
{{ with secret "kv/data/atlas/finance/firefly-db" }}
|
|
||||||
export DB_CONNECTION="pgsql"
|
|
||||||
export DB_HOST="{{ .Data.data.DB_HOST }}"
|
|
||||||
export DB_PORT="{{ .Data.data.DB_PORT }}"
|
|
||||||
export DB_DATABASE="{{ .Data.data.DB_DATABASE }}"
|
|
||||||
export DB_USERNAME="{{ .Data.data.DB_USERNAME }}"
|
|
||||||
export DB_PASSWORD="$(cat /vault/secrets/firefly-db-password)"
|
|
||||||
{{ end }}
|
|
||||||
{{ with secret "kv/data/atlas/finance/firefly-secrets" }}
|
|
||||||
export APP_KEY="$(cat /vault/secrets/firefly-app-key)"
|
|
||||||
export STATIC_CRON_TOKEN="$(cat /vault/secrets/firefly-cron-token)"
|
|
||||||
{{ end }}
|
|
||||||
{{ with secret "kv/data/atlas/shared/postmark-relay" }}
|
|
||||||
export MAIL_USERNAME="{{ index .Data.data "relay-username" }}"
|
|
||||||
export MAIL_PASSWORD="{{ index .Data.data "relay-password" }}"
|
|
||||||
{{ end }}
|
|
||||||
vault.hashicorp.com/agent-inject-secret-firefly-db-password: "kv/data/atlas/finance/firefly-db"
|
|
||||||
vault.hashicorp.com/agent-inject-template-firefly-db-password: |
|
|
||||||
{{- with secret "kv/data/atlas/finance/firefly-db" -}}
|
|
||||||
{{ .Data.data.DB_PASSWORD }}
|
|
||||||
{{- end -}}
|
|
||||||
vault.hashicorp.com/agent-inject-secret-firefly-app-key: "kv/data/atlas/finance/firefly-secrets"
|
|
||||||
vault.hashicorp.com/agent-inject-template-firefly-app-key: |
|
|
||||||
{{- with secret "kv/data/atlas/finance/firefly-secrets" -}}
|
|
||||||
{{ .Data.data.APP_KEY }}
|
|
||||||
{{- end -}}
|
|
||||||
vault.hashicorp.com/agent-inject-secret-firefly-cron-token: "kv/data/atlas/finance/firefly-secrets"
|
|
||||||
vault.hashicorp.com/agent-inject-template-firefly-cron-token: |
|
|
||||||
{{- with secret "kv/data/atlas/finance/firefly-secrets" -}}
|
|
||||||
{{ .Data.data.STATIC_CRON_TOKEN }}
|
|
||||||
{{- end -}}
|
|
||||||
spec:
|
|
||||||
serviceAccountName: finance-vault
|
|
||||||
nodeSelector:
|
|
||||||
kubernetes.io/arch: arm64
|
|
||||||
node-role.kubernetes.io/worker: "true"
|
|
||||||
affinity:
|
|
||||||
nodeAffinity:
|
|
||||||
preferredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
- weight: 100
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi5"]
|
|
||||||
- weight: 70
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi4"]
|
|
||||||
securityContext:
|
|
||||||
runAsUser: 33
|
|
||||||
runAsGroup: 33
|
|
||||||
fsGroup: 33
|
|
||||||
fsGroupChangePolicy: OnRootMismatch
|
|
||||||
initContainers:
|
|
||||||
- name: init-storage-permissions
|
|
||||||
image: docker.io/alpine:3.20
|
|
||||||
command: ["/bin/sh", "-c"]
|
|
||||||
args:
|
|
||||||
- |
|
|
||||||
set -e
|
|
||||||
mkdir -p /var/www/html/storage
|
|
||||||
chown -R 33:33 /var/www/html/storage
|
|
||||||
securityContext:
|
|
||||||
runAsUser: 0
|
|
||||||
runAsGroup: 0
|
|
||||||
volumeMounts:
|
|
||||||
- name: firefly-storage
|
|
||||||
mountPath: /var/www/html/storage
|
|
||||||
containers:
|
|
||||||
- name: firefly
|
|
||||||
image: fireflyiii/core:version-6.4.15
|
|
||||||
args: ["/bin/sh", "-c", ". /vault/secrets/firefly-env.sh && exec /init"]
|
|
||||||
env:
|
|
||||||
- name: APP_ENV
|
|
||||||
value: production
|
|
||||||
- name: APP_DEBUG
|
|
||||||
value: "false"
|
|
||||||
- name: APP_URL
|
|
||||||
value: https://money.bstein.dev
|
|
||||||
- name: SITE_OWNER
|
|
||||||
value: brad@bstein.dev
|
|
||||||
- name: TZ
|
|
||||||
value: Etc/UTC
|
|
||||||
- name: TRUSTED_PROXIES
|
|
||||||
value: "**"
|
|
||||||
- name: AUTHENTICATION_GUARD
|
|
||||||
value: web
|
|
||||||
- name: MAIL_MAILER
|
|
||||||
value: smtp
|
|
||||||
- name: MAIL_HOST
|
|
||||||
value: mail.bstein.dev
|
|
||||||
- name: MAIL_PORT
|
|
||||||
value: "587"
|
|
||||||
- name: MAIL_ENCRYPTION
|
|
||||||
value: tls
|
|
||||||
- name: MAIL_FROM_ADDRESS
|
|
||||||
value: no-reply-firefly@bstein.dev
|
|
||||||
- name: MAIL_FROM_NAME
|
|
||||||
value: Firefly III
|
|
||||||
- name: CACHE_DRIVER
|
|
||||||
value: file
|
|
||||||
- name: SESSION_DRIVER
|
|
||||||
value: file
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: 8080
|
|
||||||
volumeMounts:
|
|
||||||
- name: firefly-storage
|
|
||||||
mountPath: /var/www/html/storage
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: 20
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
failureThreshold: 6
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 20
|
|
||||||
timeoutSeconds: 5
|
|
||||||
failureThreshold: 6
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 200m
|
|
||||||
memory: 512Mi
|
|
||||||
limits:
|
|
||||||
cpu: "1"
|
|
||||||
memory: 1Gi
|
|
||||||
volumes:
|
|
||||||
- name: firefly-storage
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: firefly-storage
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
# services/finance/firefly-ingress.yaml
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: firefly
|
|
||||||
namespace: finance
|
|
||||||
annotations:
|
|
||||||
kubernetes.io/ingress.class: traefik
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
||||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
|
||||||
cert-manager.io/cluster-issuer: letsencrypt
|
|
||||||
spec:
|
|
||||||
tls:
|
|
||||||
- hosts: ["money.bstein.dev"]
|
|
||||||
secretName: firefly-tls
|
|
||||||
rules:
|
|
||||||
- host: money.bstein.dev
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: firefly
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
# services/finance/firefly-service.yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: firefly
|
|
||||||
namespace: finance
|
|
||||||
labels:
|
|
||||||
app: firefly
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: firefly
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
# services/finance/firefly-storage-pvc.yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: firefly-storage
|
|
||||||
namespace: finance
|
|
||||||
spec:
|
|
||||||
accessModes: ["ReadWriteOnce"]
|
|
||||||
storageClassName: asteria
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 10Gi
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
# services/finance/firefly-user-sync-cronjob.yaml
|
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: CronJob
|
|
||||||
metadata:
|
|
||||||
name: firefly-user-sync
|
|
||||||
namespace: finance
|
|
||||||
spec:
|
|
||||||
schedule: "0 6 * * *"
|
|
||||||
suspend: true
|
|
||||||
concurrencyPolicy: Forbid
|
|
||||||
successfulJobsHistoryLimit: 1
|
|
||||||
failedJobsHistoryLimit: 3
|
|
||||||
jobTemplate:
|
|
||||||
spec:
|
|
||||||
backoffLimit: 0
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
|
||||||
vault.hashicorp.com/agent-pre-populate-only: "true"
|
|
||||||
vault.hashicorp.com/role: "finance"
|
|
||||||
vault.hashicorp.com/agent-inject-secret-firefly-env.sh: "kv/data/atlas/finance/firefly-db"
|
|
||||||
vault.hashicorp.com/agent-inject-template-firefly-env.sh: |
|
|
||||||
{{ with secret "kv/data/atlas/finance/firefly-db" }}
|
|
||||||
export DB_CONNECTION="pgsql"
|
|
||||||
export DB_HOST="{{ .Data.data.DB_HOST }}"
|
|
||||||
export DB_PORT="{{ .Data.data.DB_PORT }}"
|
|
||||||
export DB_DATABASE="{{ .Data.data.DB_DATABASE }}"
|
|
||||||
export DB_USERNAME="{{ .Data.data.DB_USERNAME }}"
|
|
||||||
export DB_PASSWORD="$(cat /vault/secrets/firefly-db-password)"
|
|
||||||
{{ end }}
|
|
||||||
{{ with secret "kv/data/atlas/finance/firefly-secrets" }}
|
|
||||||
export APP_KEY="$(cat /vault/secrets/firefly-app-key)"
|
|
||||||
{{ end }}
|
|
||||||
vault.hashicorp.com/agent-inject-secret-firefly-db-password: "kv/data/atlas/finance/firefly-db"
|
|
||||||
vault.hashicorp.com/agent-inject-template-firefly-db-password: |
|
|
||||||
{{- with secret "kv/data/atlas/finance/firefly-db" -}}
|
|
||||||
{{ .Data.data.DB_PASSWORD }}
|
|
||||||
{{- end -}}
|
|
||||||
vault.hashicorp.com/agent-inject-secret-firefly-app-key: "kv/data/atlas/finance/firefly-secrets"
|
|
||||||
vault.hashicorp.com/agent-inject-template-firefly-app-key: |
|
|
||||||
{{- with secret "kv/data/atlas/finance/firefly-secrets" -}}
|
|
||||||
{{ .Data.data.APP_KEY }}
|
|
||||||
{{- end -}}
|
|
||||||
spec:
|
|
||||||
serviceAccountName: finance-vault
|
|
||||||
restartPolicy: Never
|
|
||||||
affinity:
|
|
||||||
nodeAffinity:
|
|
||||||
preferredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
- weight: 100
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi5"]
|
|
||||||
- weight: 70
|
|
||||||
preference:
|
|
||||||
matchExpressions:
|
|
||||||
- key: hardware
|
|
||||||
operator: In
|
|
||||||
values: ["rpi4"]
|
|
||||||
nodeSelector:
|
|
||||||
kubernetes.io/arch: arm64
|
|
||||||
node-role.kubernetes.io/worker: "true"
|
|
||||||
containers:
|
|
||||||
- name: sync
|
|
||||||
image: fireflyiii/core:version-6.4.15
|
|
||||||
command: ["/bin/sh", "-c"]
|
|
||||||
args:
|
|
||||||
- |
|
|
||||||
set -eu
|
|
||||||
. /vault/secrets/firefly-env.sh
|
|
||||||
exec php /scripts/firefly_user_sync.php
|
|
||||||
env:
|
|
||||||
- name: APP_ENV
|
|
||||||
value: production
|
|
||||||
- name: APP_DEBUG
|
|
||||||
value: "false"
|
|
||||||
- name: TZ
|
|
||||||
value: Etc/UTC
|
|
||||||
volumeMounts:
|
|
||||||
- name: firefly-user-sync-script
|
|
||||||
mountPath: /scripts
|
|
||||||
readOnly: true
|
|
||||||
volumes:
|
|
||||||
- name: firefly-user-sync-script
|
|
||||||
configMap:
|
|
||||||
name: firefly-user-sync-script
|
|
||||||
defaultMode: 0555
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# services/finance/kustomization.yaml
|
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
namespace: finance
|
|
||||||
resources:
|
|
||||||
- namespace.yaml
|
|
||||||
- serviceaccount.yaml
|
|
||||||
- portal-rbac.yaml
|
|
||||||
- actual-budget-data-pvc.yaml
|
|
||||||
- firefly-storage-pvc.yaml
|
|
||||||
- finance-secrets-ensure-job.yaml
|
|
||||||
- actual-budget-deployment.yaml
|
|
||||||
- firefly-deployment.yaml
|
|
||||||
- firefly-user-sync-cronjob.yaml
|
|
||||||
- firefly-cronjob.yaml
|
|
||||||
- actual-budget-service.yaml
|
|
||||||
- firefly-service.yaml
|
|
||||||
- actual-budget-ingress.yaml
|
|
||||||
- firefly-ingress.yaml
|
|
||||||
generatorOptions:
|
|
||||||
disableNameSuffixHash: true
|
|
||||||
configMapGenerator:
|
|
||||||
- name: actual-openid-bootstrap-script
|
|
||||||
files:
|
|
||||||
- actual_openid_bootstrap.mjs=scripts/actual_openid_bootstrap.mjs
|
|
||||||
- name: firefly-user-sync-script
|
|
||||||
files:
|
|
||||||
- firefly_user_sync.php=scripts/firefly_user_sync.php
|
|
||||||
- name: finance-secrets-ensure-script
|
|
||||||
files:
|
|
||||||
- finance_secrets_ensure.sh=scripts/finance_secrets_ensure.sh
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
# services/finance/namespace.yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: finance
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# services/finance/portal-rbac.yaml
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: Role
|
|
||||||
metadata:
|
|
||||||
name: bstein-dev-home-firefly-user-sync
|
|
||||||
namespace: finance
|
|
||||||
rules:
|
|
||||||
- apiGroups: ["batch"]
|
|
||||||
resources: ["cronjobs"]
|
|
||||||
verbs: ["get"]
|
|
||||||
resourceNames: ["firefly-user-sync"]
|
|
||||||
- apiGroups: ["batch"]
|
|
||||||
resources: ["jobs"]
|
|
||||||
verbs: ["create", "get", "list", "watch"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["pods"]
|
|
||||||
verbs: ["get", "list"]
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: RoleBinding
|
|
||||||
metadata:
|
|
||||||
name: bstein-dev-home-firefly-user-sync
|
|
||||||
namespace: finance
|
|
||||||
roleRef:
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
kind: Role
|
|
||||||
name: bstein-dev-home-firefly-user-sync
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: bstein-dev-home
|
|
||||||
namespace: bstein-dev-home
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { pathToFileURL } from 'node:url';
|
|
||||||
|
|
||||||
function findRoot() {
|
|
||||||
const candidates = [];
|
|
||||||
if (process.env.ACTUAL_SERVER_ROOT) {
|
|
||||||
candidates.push(process.env.ACTUAL_SERVER_ROOT);
|
|
||||||
}
|
|
||||||
candidates.push('/app');
|
|
||||||
candidates.push('/usr/src/app');
|
|
||||||
candidates.push('/srv/app');
|
|
||||||
candidates.push('/opt/actual-server');
|
|
||||||
|
|
||||||
for (const base of candidates) {
|
|
||||||
if (!base) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const accountDb = path.join(base, 'src', 'account-db.js');
|
|
||||||
if (fs.existsSync(accountDb)) {
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = findRoot();
|
|
||||||
if (!root) {
|
|
||||||
console.error('actual server root not found');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountDbUrl = pathToFileURL(path.join(root, 'src', 'account-db.js')).href;
|
|
||||||
const loadConfigUrl = pathToFileURL(path.join(root, 'src', 'load-config.js')).href;
|
|
||||||
|
|
||||||
const accountDb = await import(accountDbUrl);
|
|
||||||
const { default: finalConfig } = await import(loadConfigUrl);
|
|
||||||
|
|
||||||
const openId = finalConfig?.openId;
|
|
||||||
if (!openId) {
|
|
||||||
console.error('missing openid configuration');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const active = accountDb.getActiveLoginMethod();
|
|
||||||
if (active === 'openid') {
|
|
||||||
console.log('openid already enabled');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (accountDb.needsBootstrap()) {
|
|
||||||
const result = await accountDb.bootstrap({ openId });
|
|
||||||
if (result?.error && result.error !== 'already-bootstrapped') {
|
|
||||||
console.error(`bootstrap failed: ${result.error}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const result = await accountDb.enableOpenID({ openId });
|
|
||||||
if (result?.error) {
|
|
||||||
console.error(`enable openid failed: ${result.error}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('openid bootstrap complete');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('openid bootstrap error:', err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
apk add --no-cache curl jq >/dev/null
|
|
||||||
|
|
||||||
vault_addr="${VAULT_ADDR:-http://vault.vault.svc.cluster.local:8200}"
|
|
||||||
vault_role="${VAULT_ROLE:-finance-secrets}"
|
|
||||||
jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
|
|
||||||
login_payload="$(jq -nc --arg jwt "${jwt}" --arg role "${vault_role}" '{jwt:$jwt, role:$role}')"
|
|
||||||
vault_token="$(curl -sS --request POST --data "${login_payload}" \
|
|
||||||
"${vault_addr}/v1/auth/kubernetes/login" | jq -r '.auth.client_token')"
|
|
||||||
if [ -z "${vault_token}" ] || [ "${vault_token}" = "null" ]; then
|
|
||||||
echo "vault login failed" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
read_secret() {
|
|
||||||
path="$1"
|
|
||||||
if [ -f "${path}" ]; then
|
|
||||||
cat "${path}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
require_value() {
|
|
||||||
label="$1"
|
|
||||||
value="$2"
|
|
||||||
if [ -z "${value}" ]; then
|
|
||||||
echo "missing ${label}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
vault_read() {
|
|
||||||
path="$1"
|
|
||||||
key="$2"
|
|
||||||
curl -sS -H "X-Vault-Token: ${vault_token}" \
|
|
||||||
"${vault_addr}/v1/kv/data/atlas/${path}" 2>/dev/null | \
|
|
||||||
jq -r --arg key "${key}" '.data.data[$key] // empty' 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
vault_write_json() {
|
|
||||||
path="$1"
|
|
||||||
payload="$2"
|
|
||||||
curl -sS -X POST -H "X-Vault-Token: ${vault_token}" \
|
|
||||||
-d "${payload}" "${vault_addr}/v1/kv/data/atlas/${path}" >/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
firefly_db_host="$(read_secret /secrets/firefly-db/DB_HOST)"
|
|
||||||
if [ -z "${firefly_db_host}" ]; then
|
|
||||||
firefly_db_host="$(read_secret /secrets/firefly-db/DB_HOSTNAME)"
|
|
||||||
fi
|
|
||||||
firefly_db_port="$(read_secret /secrets/firefly-db/DB_PORT)"
|
|
||||||
firefly_db_name="$(read_secret /secrets/firefly-db/DB_DATABASE)"
|
|
||||||
if [ -z "${firefly_db_name}" ]; then
|
|
||||||
firefly_db_name="$(read_secret /secrets/firefly-db/DB_NAME)"
|
|
||||||
fi
|
|
||||||
firefly_db_user="$(read_secret /secrets/firefly-db/DB_USERNAME)"
|
|
||||||
if [ -z "${firefly_db_user}" ]; then
|
|
||||||
firefly_db_user="$(read_secret /secrets/firefly-db/DB_USER)"
|
|
||||||
fi
|
|
||||||
firefly_db_pass="$(read_secret /secrets/firefly-db/DB_PASSWORD)"
|
|
||||||
if [ -z "${firefly_db_pass}" ]; then
|
|
||||||
firefly_db_pass="$(read_secret /secrets/firefly-db/DB_PASS)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
require_value "firefly-db/DB_HOST" "${firefly_db_host}"
|
|
||||||
require_value "firefly-db/DB_PORT" "${firefly_db_port}"
|
|
||||||
require_value "firefly-db/DB_DATABASE" "${firefly_db_name}"
|
|
||||||
require_value "firefly-db/DB_USERNAME" "${firefly_db_user}"
|
|
||||||
require_value "firefly-db/DB_PASSWORD" "${firefly_db_pass}"
|
|
||||||
|
|
||||||
firefly_payload="$(jq -nc \
|
|
||||||
--arg host "${firefly_db_host}" \
|
|
||||||
--arg port "${firefly_db_port}" \
|
|
||||||
--arg db "${firefly_db_name}" \
|
|
||||||
--arg user "${firefly_db_user}" \
|
|
||||||
--arg pass "${firefly_db_pass}" \
|
|
||||||
'{data:{DB_HOST:$host, DB_PORT:$port, DB_DATABASE:$db, DB_USERNAME:$user, DB_PASSWORD:$pass}}')"
|
|
||||||
vault_write_json "finance/firefly-db" "${firefly_payload}"
|
|
||||||
|
|
||||||
app_key="$(vault_read "finance/firefly-secrets" "APP_KEY")"
|
|
||||||
if [ -z "${app_key}" ]; then
|
|
||||||
app_key="base64:$(head -c 32 /dev/urandom | base64 | tr -d '\n')"
|
|
||||||
fi
|
|
||||||
cron_token="$(vault_read "finance/firefly-secrets" "STATIC_CRON_TOKEN")"
|
|
||||||
if [ -z "${cron_token}" ]; then
|
|
||||||
cron_token="$(head -c 32 /dev/urandom | base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')"
|
|
||||||
fi
|
|
||||||
firefly_secret_payload="$(jq -nc \
|
|
||||||
--arg app_key "${app_key}" \
|
|
||||||
--arg cron "${cron_token}" \
|
|
||||||
'{data:{APP_KEY:$app_key, STATIC_CRON_TOKEN:$cron}}')"
|
|
||||||
vault_write_json "finance/firefly-secrets" "${firefly_secret_payload}"
|
|
||||||
|
|
||||||
if [ -d /secrets/actualbudget-db ]; then
|
|
||||||
actual_db_host="$(read_secret /secrets/actualbudget-db/DB_HOST)"
|
|
||||||
if [ -z "${actual_db_host}" ]; then
|
|
||||||
actual_db_host="$(read_secret /secrets/actualbudget-db/DB_HOSTNAME)"
|
|
||||||
fi
|
|
||||||
actual_db_port="$(read_secret /secrets/actualbudget-db/DB_PORT)"
|
|
||||||
actual_db_name="$(read_secret /secrets/actualbudget-db/DB_DATABASE)"
|
|
||||||
if [ -z "${actual_db_name}" ]; then
|
|
||||||
actual_db_name="$(read_secret /secrets/actualbudget-db/DB_NAME)"
|
|
||||||
fi
|
|
||||||
actual_db_user="$(read_secret /secrets/actualbudget-db/DB_USERNAME)"
|
|
||||||
if [ -z "${actual_db_user}" ]; then
|
|
||||||
actual_db_user="$(read_secret /secrets/actualbudget-db/DB_USER)"
|
|
||||||
fi
|
|
||||||
actual_db_pass="$(read_secret /secrets/actualbudget-db/DB_PASSWORD)"
|
|
||||||
if [ -z "${actual_db_pass}" ]; then
|
|
||||||
actual_db_pass="$(read_secret /secrets/actualbudget-db/DB_PASS)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${actual_db_host}${actual_db_port}${actual_db_name}${actual_db_user}${actual_db_pass}" ]; then
|
|
||||||
require_value "actualbudget-db/DB_HOST" "${actual_db_host}"
|
|
||||||
require_value "actualbudget-db/DB_PORT" "${actual_db_port}"
|
|
||||||
require_value "actualbudget-db/DB_DATABASE" "${actual_db_name}"
|
|
||||||
require_value "actualbudget-db/DB_USERNAME" "${actual_db_user}"
|
|
||||||
require_value "actualbudget-db/DB_PASSWORD" "${actual_db_pass}"
|
|
||||||
|
|
||||||
actual_payload="$(jq -nc \
|
|
||||||
--arg host "${actual_db_host}" \
|
|
||||||
--arg port "${actual_db_port}" \
|
|
||||||
--arg db "${actual_db_name}" \
|
|
||||||
--arg user "${actual_db_user}" \
|
|
||||||
--arg pass "${actual_db_pass}" \
|
|
||||||
'{data:{DB_HOST:$host, DB_PORT:$port, DB_DATABASE:$db, DB_USERNAME:$user, DB_PASSWORD:$pass}}')"
|
|
||||||
vault_write_json "finance/actual-db" "${actual_payload}"
|
|
||||||
else
|
|
||||||
echo "actualbudget-db secret empty; skipping actual-db vault write" >&2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use FireflyIII\Console\Commands\Correction\CreatesGroupMemberships;
|
|
||||||
use FireflyIII\Models\Role;
|
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
|
||||||
use FireflyIII\User;
|
|
||||||
use Illuminate\Contracts\Console\Kernel as ConsoleKernel;
|
|
||||||
|
|
||||||
function log_line(string $message): void
|
|
||||||
{
|
|
||||||
fwrite(STDOUT, $message . PHP_EOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function error_line(string $message): void
|
|
||||||
{
|
|
||||||
fwrite(STDERR, $message . PHP_EOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function find_app_root(): string
|
|
||||||
{
|
|
||||||
$candidates = [];
|
|
||||||
$env_root = getenv('FIREFLY_APP_DIR') ?: '';
|
|
||||||
if ($env_root !== '') {
|
|
||||||
$candidates[] = $env_root;
|
|
||||||
}
|
|
||||||
$candidates[] = '/var/www/html';
|
|
||||||
$candidates[] = '/var/www/firefly-iii';
|
|
||||||
$candidates[] = '/app';
|
|
||||||
|
|
||||||
foreach ($candidates as $candidate) {
|
|
||||||
if (!is_dir($candidate)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (file_exists($candidate . '/vendor/autoload.php')) {
|
|
||||||
return $candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$email = trim((string) getenv('FIREFLY_USER_EMAIL'));
|
|
||||||
$password = (string) getenv('FIREFLY_USER_PASSWORD');
|
|
||||||
|
|
||||||
if ($email === '' || $password === '') {
|
|
||||||
error_line('missing FIREFLY_USER_EMAIL or FIREFLY_USER_PASSWORD');
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$root = find_app_root();
|
|
||||||
if ($root === '') {
|
|
||||||
error_line('firefly app root not found');
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$autoload = $root . '/vendor/autoload.php';
|
|
||||||
$app_bootstrap = $root . '/bootstrap/app.php';
|
|
||||||
|
|
||||||
if (!file_exists($autoload) || !file_exists($app_bootstrap)) {
|
|
||||||
error_line('firefly bootstrap files missing');
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
require $autoload;
|
|
||||||
$app = require $app_bootstrap;
|
|
||||||
|
|
||||||
$kernel = $app->make(ConsoleKernel::class);
|
|
||||||
$kernel->bootstrap();
|
|
||||||
|
|
||||||
$repository = $app->make(UserRepositoryInterface::class);
|
|
||||||
|
|
||||||
$existing_user = User::where('email', $email)->first();
|
|
||||||
$first_user = User::count() == 0;
|
|
||||||
|
|
||||||
if (!$existing_user) {
|
|
||||||
$existing_user = User::create(
|
|
||||||
[
|
|
||||||
'email' => $email,
|
|
||||||
'password' => bcrypt($password),
|
|
||||||
'blocked' => false,
|
|
||||||
'blocked_code' => null,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($first_user) {
|
|
||||||
$role = Role::where('name', 'owner')->first();
|
|
||||||
if ($role) {
|
|
||||||
$existing_user->roles()->attach($role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log_line(sprintf('created firefly user %s', $email));
|
|
||||||
} else {
|
|
||||||
log_line(sprintf('updating firefly user %s', $email));
|
|
||||||
}
|
|
||||||
|
|
||||||
$existing_user->blocked = false;
|
|
||||||
$existing_user->blocked_code = null;
|
|
||||||
$existing_user->save();
|
|
||||||
|
|
||||||
$repository->changePassword($existing_user, $password);
|
|
||||||
CreatesGroupMemberships::createGroupMembership($existing_user);
|
|
||||||
|
|
||||||
log_line('firefly user sync complete');
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
# services/finance/serviceaccount.yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: finance-vault
|
|
||||||
namespace: finance
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: finance-secrets-ensure
|
|
||||||
namespace: finance
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
# services/keycloak/actual-oidc-secret-ensure-job.yaml
|
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: Job
|
|
||||||
metadata:
|
|
||||||
name: actual-oidc-secret-ensure-1
|
|
||||||
namespace: sso
|
|
||||||
spec:
|
|
||||||
backoffLimit: 0
|
|
||||||
ttlSecondsAfterFinished: 3600
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
|
||||||
vault.hashicorp.com/agent-pre-populate-only: "true"
|
|
||||||
vault.hashicorp.com/role: "sso-secrets"
|
|
||||||
vault.hashicorp.com/agent-inject-secret-keycloak-admin-env.sh: "kv/data/atlas/shared/keycloak-admin"
|
|
||||||
vault.hashicorp.com/agent-inject-template-keycloak-admin-env.sh: |
|
|
||||||
{{ with secret "kv/data/atlas/shared/keycloak-admin" }}
|
|
||||||
export KEYCLOAK_ADMIN="{{ .Data.data.username }}"
|
|
||||||
export KEYCLOAK_ADMIN_USER="{{ .Data.data.username }}"
|
|
||||||
export KEYCLOAK_ADMIN_PASSWORD="{{ .Data.data.password }}"
|
|
||||||
{{ end }}
|
|
||||||
spec:
|
|
||||||
serviceAccountName: mas-secrets-ensure
|
|
||||||
restartPolicy: Never
|
|
||||||
volumes:
|
|
||||||
- name: actual-oidc-secret-ensure-script
|
|
||||||
configMap:
|
|
||||||
name: actual-oidc-secret-ensure-script
|
|
||||||
defaultMode: 0555
|
|
||||||
affinity:
|
|
||||||
nodeAffinity:
|
|
||||||
requiredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
nodeSelectorTerms:
|
|
||||||
- matchExpressions:
|
|
||||||
- key: kubernetes.io/arch
|
|
||||||
operator: In
|
|
||||||
values: ["arm64"]
|
|
||||||
- key: node-role.kubernetes.io/worker
|
|
||||||
operator: Exists
|
|
||||||
containers:
|
|
||||||
- name: apply
|
|
||||||
image: alpine:3.20
|
|
||||||
command: ["/scripts/actual_oidc_secret_ensure.sh"]
|
|
||||||
volumeMounts:
|
|
||||||
- name: actual-oidc-secret-ensure-script
|
|
||||||
mountPath: /scripts
|
|
||||||
readOnly: true
|
|
||||||
@ -24,7 +24,6 @@ resources:
|
|||||||
- logs-oidc-secret-ensure-job.yaml
|
- logs-oidc-secret-ensure-job.yaml
|
||||||
- harbor-oidc-secret-ensure-job.yaml
|
- harbor-oidc-secret-ensure-job.yaml
|
||||||
- vault-oidc-secret-ensure-job.yaml
|
- vault-oidc-secret-ensure-job.yaml
|
||||||
- actual-oidc-secret-ensure-job.yaml
|
|
||||||
- service.yaml
|
- service.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
generatorOptions:
|
generatorOptions:
|
||||||
@ -40,6 +39,3 @@ configMapGenerator:
|
|||||||
- name: vault-oidc-secret-ensure-script
|
- name: vault-oidc-secret-ensure-script
|
||||||
files:
|
files:
|
||||||
- vault_oidc_secret_ensure.sh=scripts/vault_oidc_secret_ensure.sh
|
- vault_oidc_secret_ensure.sh=scripts/vault_oidc_secret_ensure.sh
|
||||||
- name: actual-oidc-secret-ensure-script
|
|
||||||
files:
|
|
||||||
- actual_oidc_secret_ensure.sh=scripts/actual_oidc_secret_ensure.sh
|
|
||||||
|
|||||||
@ -250,22 +250,6 @@ spec:
|
|||||||
"permissions": {"view": ["admin"], "edit": ["admin"]},
|
"permissions": {"view": ["admin"], "edit": ["admin"]},
|
||||||
"validations": {"length": {"max": 64}},
|
"validations": {"length": {"max": 64}},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "firefly_password",
|
|
||||||
"displayName": "Firefly Password",
|
|
||||||
"multivalued": False,
|
|
||||||
"annotations": {"group": "user-metadata"},
|
|
||||||
"permissions": {"view": ["admin"], "edit": ["admin"]},
|
|
||||||
"validations": {"length": {"max": 255}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "firefly_password_updated_at",
|
|
||||||
"displayName": "Firefly Password Updated At",
|
|
||||||
"multivalued": False,
|
|
||||||
"annotations": {"group": "user-metadata"},
|
|
||||||
"permissions": {"view": ["admin"], "edit": ["admin"]},
|
|
||||||
"validations": {"length": {"max": 64}},
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def has_attr(name: str) -> bool:
|
def has_attr(name: str) -> bool:
|
||||||
|
|||||||
@ -1,78 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
apk add --no-cache curl jq >/dev/null
|
|
||||||
|
|
||||||
. /vault/secrets/keycloak-admin-env.sh
|
|
||||||
|
|
||||||
KC_URL="http://keycloak.sso.svc.cluster.local"
|
|
||||||
ACCESS_TOKEN=""
|
|
||||||
for attempt in 1 2 3 4 5; do
|
|
||||||
TOKEN_JSON="$(curl -sS -X POST "$KC_URL/realms/master/protocol/openid-connect/token" \
|
|
||||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
|
||||||
-d "grant_type=password" \
|
|
||||||
-d "client_id=admin-cli" \
|
|
||||||
-d "username=${KEYCLOAK_ADMIN}" \
|
|
||||||
-d "password=${KEYCLOAK_ADMIN_PASSWORD}" || true)"
|
|
||||||
ACCESS_TOKEN="$(echo "$TOKEN_JSON" | jq -r '.access_token' 2>/dev/null || true)"
|
|
||||||
if [ -n "$ACCESS_TOKEN" ] && [ "$ACCESS_TOKEN" != "null" ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "Keycloak token request failed (attempt ${attempt})" >&2
|
|
||||||
sleep $((attempt * 2))
|
|
||||||
done
|
|
||||||
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
|
|
||||||
echo "Failed to fetch Keycloak admin token" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
CLIENT_QUERY="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
|
||||||
"$KC_URL/admin/realms/atlas/clients?clientId=actual-budget" || true)"
|
|
||||||
CLIENT_ID="$(echo "$CLIENT_QUERY" | jq -r '.[0].id' 2>/dev/null || true)"
|
|
||||||
|
|
||||||
if [ -z "$CLIENT_ID" ] || [ "$CLIENT_ID" = "null" ]; then
|
|
||||||
create_payload='{"clientId":"actual-budget","enabled":true,"protocol":"openid-connect","publicClient":false,"standardFlowEnabled":true,"implicitFlowEnabled":false,"directAccessGrantsEnabled":false,"serviceAccountsEnabled":false,"redirectUris":["https://budget.bstein.dev/openid/callback"],"webOrigins":["https://budget.bstein.dev"],"rootUrl":"https://budget.bstein.dev","baseUrl":"/"}'
|
|
||||||
status="$(curl -sS -o /dev/null -w "%{http_code}" -X POST \
|
|
||||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d "${create_payload}" \
|
|
||||||
"$KC_URL/admin/realms/atlas/clients")"
|
|
||||||
if [ "$status" != "201" ] && [ "$status" != "204" ]; then
|
|
||||||
echo "Keycloak client create failed (status ${status})" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
CLIENT_QUERY="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
|
||||||
"$KC_URL/admin/realms/atlas/clients?clientId=actual-budget" || true)"
|
|
||||||
CLIENT_ID="$(echo "$CLIENT_QUERY" | jq -r '.[0].id' 2>/dev/null || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$CLIENT_ID" ] || [ "$CLIENT_ID" = "null" ]; then
|
|
||||||
echo "Keycloak client actual-budget not found" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
CLIENT_SECRET="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
|
||||||
"$KC_URL/admin/realms/atlas/clients/${CLIENT_ID}/client-secret" | jq -r '.value' 2>/dev/null || true)"
|
|
||||||
if [ -z "$CLIENT_SECRET" ] || [ "$CLIENT_SECRET" = "null" ]; then
|
|
||||||
echo "Keycloak client secret not found" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
vault_addr="${VAULT_ADDR:-http://vault.vault.svc.cluster.local:8200}"
|
|
||||||
vault_role="${VAULT_ROLE:-sso-secrets}"
|
|
||||||
jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
|
|
||||||
login_payload="$(jq -nc --arg jwt "${jwt}" --arg role "${vault_role}" '{jwt:$jwt, role:$role}')"
|
|
||||||
vault_token="$(curl -sS --request POST --data "${login_payload}" \
|
|
||||||
"${vault_addr}/v1/auth/kubernetes/login" | jq -r '.auth.client_token')"
|
|
||||||
if [ -z "${vault_token}" ] || [ "${vault_token}" = "null" ]; then
|
|
||||||
echo "vault login failed" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
payload="$(jq -nc \
|
|
||||||
--arg client_id "actual-budget" \
|
|
||||||
--arg client_secret "${CLIENT_SECRET}" \
|
|
||||||
'{data:{ACTUAL_OPENID_CLIENT_ID:$client_id, ACTUAL_OPENID_CLIENT_SECRET:$client_secret}}')"
|
|
||||||
|
|
||||||
curl -sS -X POST -H "X-Vault-Token: ${vault_token}" \
|
|
||||||
-d "${payload}" "${vault_addr}/v1/kv/data/atlas/finance/actual-oidc" >/dev/null
|
|
||||||
@ -214,11 +214,6 @@ write_policy_and_role "crypto" "crypto" "crypto-vault-sync" \
|
|||||||
"crypto/* harbor-pull/crypto" ""
|
"crypto/* harbor-pull/crypto" ""
|
||||||
write_policy_and_role "health" "health" "health-vault-sync" \
|
write_policy_and_role "health" "health" "health-vault-sync" \
|
||||||
"health/*" ""
|
"health/*" ""
|
||||||
write_policy_and_role "finance" "finance" "finance-vault" \
|
|
||||||
"finance/* shared/postmark-relay" ""
|
|
||||||
write_policy_and_role "finance-secrets" "finance" "finance-secrets-ensure" \
|
|
||||||
"" \
|
|
||||||
"finance/*"
|
|
||||||
write_policy_and_role "longhorn" "longhorn-system" "longhorn-vault,longhorn-vault-sync" \
|
write_policy_and_role "longhorn" "longhorn-system" "longhorn-vault,longhorn-vault-sync" \
|
||||||
"longhorn/* harbor-pull/longhorn" ""
|
"longhorn/* harbor-pull/longhorn" ""
|
||||||
write_policy_and_role "postgres" "postgres" "postgres-vault" \
|
write_policy_and_role "postgres" "postgres" "postgres-vault" \
|
||||||
@ -228,7 +223,7 @@ write_policy_and_role "vault" "vault" "vault" \
|
|||||||
|
|
||||||
write_policy_and_role "sso-secrets" "sso" "mas-secrets-ensure" \
|
write_policy_and_role "sso-secrets" "sso" "mas-secrets-ensure" \
|
||||||
"shared/keycloak-admin" \
|
"shared/keycloak-admin" \
|
||||||
"harbor/harbor-oidc vault/vault-oidc-config comms/synapse-oidc logging/oauth2-proxy-logs-oidc finance/actual-oidc"
|
"harbor/harbor-oidc vault/vault-oidc-config comms/synapse-oidc logging/oauth2-proxy-logs-oidc"
|
||||||
write_policy_and_role "crypto-secrets" "crypto" "crypto-secrets-ensure" \
|
write_policy_and_role "crypto-secrets" "crypto" "crypto-secrets-ensure" \
|
||||||
"" \
|
"" \
|
||||||
"crypto/wallet-monero-temp-rpc-auth"
|
"crypto/wallet-monero-temp-rpc-auth"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user