[ci skip] Deploy health dashboard service

Apple Health data visualization app (Svelte + FastAPI + Caddy).
Uses shared PostgreSQL via DBaaS, NFS storage for uploads,
accessible at health.viktorbarzin.me.
This commit is contained in:
Viktor Barzin 2026-02-08 01:54:24 +00:00
parent 00943e92fe
commit 43bee50de8
No known key found for this signature in database
GPG key ID: 0EB088298288D958
6 changed files with 162 additions and 7 deletions

6
.terraform.lock.hcl generated
View file

@ -5,6 +5,7 @@ provider "registry.terraform.io/cloudflare/cloudflare" {
version = "4.52.5"
constraints = "~> 4.0"
hashes = [
"h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=",
"h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=",
"zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b",
"zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a",
@ -27,6 +28,7 @@ provider "registry.terraform.io/cloudflare/cloudflare" {
provider "registry.terraform.io/hashicorp/helm" {
version = "3.1.1"
hashes = [
"h1:47CqNwkxctJtL/N/JuEj+8QMg8mRNI/NWeKO5/ydfZU=",
"h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=",
"zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275",
"zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a",
@ -47,6 +49,7 @@ provider "registry.terraform.io/hashicorp/kubernetes" {
version = "3.0.1"
constraints = "3.0.1"
hashes = [
"h1:P0c8knzZnouTNFIRij8IS7+pqd0OKaFDYX0j4GRsiqo=",
"h1:vyHdH0p6bf9xp1NPePObAJkXTJb/I09FQQmmevTzZe0=",
"zh:02d55b0b2238fd17ffa12d5464593864e80f402b90b31f6e1bd02249b9727281",
"zh:20b93a51bfeed82682b3c12f09bac3031f5bdb4977c47c97a042e4df4fb2f9ba",
@ -66,6 +69,7 @@ provider "registry.terraform.io/hashicorp/kubernetes" {
provider "registry.terraform.io/hashicorp/null" {
version = "3.2.4"
hashes = [
"h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=",
"h1:hkf5w5B6q8e2A42ND2CjAvgvSN3puAosDmOJb3zCVQM=",
"zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
@ -86,6 +90,7 @@ provider "registry.terraform.io/hashicorp/random" {
version = "3.8.1"
hashes = [
"h1:Eexl06+6J+s75uD46+WnZtpJZYRVUMB0AiuPBifK6Jc=",
"h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=",
"zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4",
"zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae",
"zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57",
@ -105,6 +110,7 @@ provider "registry.terraform.io/telmate/proxmox" {
version = "3.0.2-rc07"
constraints = "3.0.2-rc07"
hashes = [
"h1:0UpRJ8PFsu9lhD3p2KUdUNVsDPbjZLPR46wYRpt1dxc=",
"h1:zp5hpQJQ4t4zROSLqdltVpBO+Riy9VugtfFbpyTw1aM=",
"zh:2ee860cd0a368b3eaa53f4a9ea46f16dab8a97929e813ea6ef55183f8112c2ca",
"zh:415965fd915bae2040d7f79e45f64d6e3ae61149c10114efeac1b34687d7296c",

19
main.tf
View file

@ -115,6 +115,7 @@ variable "cloudflare_proxied_names" {}
variable "cloudflare_non_proxied_names" {}
variable "cloudflare_tunnel_token" {}
variable "owntracks_credentials" {}
variable "ollama_api_credentials" {}
variable "dawarich_database_password" {}
variable "geoapify_api_key" {}
variable "tandoor_database_password" {}
@ -150,6 +151,8 @@ variable "openrouter_api_key" { type = string }
variable "slack_bot_token" { type = string }
variable "slack_channel" { type = string }
variable "affine_postgresql_password" { type = string }
variable "health_postgresql_password" { type = string }
variable "health_secret_key" { type = string }
variable "kube_config_path" {
type = string
@ -510,12 +513,12 @@ module "kubernetes_cluster" {
immich_postgresql_password = var.immich_postgresql_password
immich_frame_api_key = var.immich_frame_api_key
ingress_crowdsec_api_key = var.ingress_crowdsec_api_key
crowdsec_enroll_key = var.crowdsec_enroll_key
crowdsec_db_password = var.crowdsec_db_password
crowdsec_dash_api_key = var.crowdsec_dash_api_key
crowdsec_dash_machine_id = var.crowdsec_dash_machine_id
crowdsec_dash_machine_password = var.crowdsec_dash_machine_password
ingress_crowdsec_api_key = var.ingress_crowdsec_api_key
crowdsec_enroll_key = var.crowdsec_enroll_key
crowdsec_db_password = var.crowdsec_db_password
crowdsec_dash_api_key = var.crowdsec_dash_api_key
crowdsec_dash_machine_id = var.crowdsec_dash_machine_id
crowdsec_dash_machine_password = var.crowdsec_dash_machine_password
vaultwarden_smtp_password = var.vaultwarden_smtp_password
@ -558,6 +561,8 @@ module "kubernetes_cluster" {
owntracks_credentials = var.owntracks_credentials
ollama_api_credentials = var.ollama_api_credentials
dawarich_database_password = var.dawarich_database_password
geoapify_api_key = var.geoapify_api_key
@ -607,6 +612,8 @@ module "kubernetes_cluster" {
slack_channel = var.slack_channel
affine_postgresql_password = var.affine_postgresql_password
health_postgresql_password = var.health_postgresql_password
health_secret_key = var.health_secret_key
}

View file

@ -0,0 +1,129 @@
variable "tls_secret_name" {}
variable "tier" { type = string }
variable "postgresql_password" {}
variable "secret_key" { type = string }
resource "kubernetes_namespace" "health" {
metadata {
name = "health"
}
}
module "tls_secret" {
source = "../setup_tls_secret"
namespace = kubernetes_namespace.health.metadata[0].name
tls_secret_name = var.tls_secret_name
}
resource "kubernetes_deployment" "health" {
metadata {
name = "health"
namespace = kubernetes_namespace.health.metadata[0].name
labels = {
app = "health"
tier = var.tier
}
}
spec {
replicas = 1
selector {
match_labels = {
app = "health"
}
}
template {
metadata {
labels = {
app = "health"
}
}
spec {
container {
name = "health"
image = "viktorbarzin/health:latest"
port {
container_port = 80
}
env {
name = "DATABASE_URL"
value = "postgresql+asyncpg://health:${var.postgresql_password}@postgresql.dbaas.svc.cluster.local:5432/health"
}
env {
name = "SECRET_KEY"
value = var.secret_key
}
env {
name = "UPLOAD_DIR"
value = "/data/uploads"
}
env {
name = "WEBAUTHN_RP_ID"
value = "health.viktorbarzin.me"
}
env {
name = "WEBAUTHN_ORIGIN"
value = "https://health.viktorbarzin.me"
}
env {
name = "COOKIE_SECURE"
value = "true"
}
volume_mount {
name = "uploads"
mount_path = "/data/uploads"
}
resources {
requests = {
memory = "256Mi"
cpu = "100m"
}
limits = {
memory = "1Gi"
cpu = "1"
}
}
}
volume {
name = "uploads"
nfs {
server = "10.0.10.15"
path = "/mnt/main/health"
}
}
}
}
}
}
resource "kubernetes_service" "health" {
metadata {
name = "health"
namespace = kubernetes_namespace.health.metadata[0].name
labels = {
app = "health"
}
}
spec {
selector = {
app = "health"
}
port {
name = "http"
port = 80
target_port = 80
}
}
}
module "ingress" {
source = "../ingress_factory"
namespace = kubernetes_namespace.health.metadata[0].name
name = "health"
tls_secret_name = var.tls_secret_name
max_body_size = "100m"
}

View file

@ -122,6 +122,8 @@ variable "openrouter_api_key" { type = string }
variable "slack_bot_token" { type = string }
variable "slack_channel" { type = string }
variable "affine_postgresql_password" { type = string }
variable "health_postgresql_password" { type = string }
variable "health_secret_key" { type = string }
variable "defcon_level" {
@ -143,7 +145,7 @@ locals {
], # Activel used services
# Optional services
5 : [
"blog", "descheduler", "drone", "hackmd", "kms", "privatebin", "vault", "reloader", "city-guesser", "echo",
"blog", "descheduler", "drone", "hackmd", "health", "kms", "privatebin", "vault", "reloader", "city-guesser", "echo",
"url", "excalidraw", "travel_blog", "dashy", "send", "ytdlp", "wealthfolio", "rybbit", "stirling-pdf",
"networking-toolbox", "navidrome", "freshrss", "forgejo", "tor-proxy", "real-estate-crawler", "n8n",
"changedetection", "linkwarden", "matrix", "homepage", "meshcentral", "diun", "cyberchef", "ntfy", "ollama",
@ -1089,6 +1091,17 @@ module "plotting-book" {
depends_on = [null_resource.core_services]
}
module "health" {
source = "./health"
for_each = contains(local.active_modules, "health") ? { health = true } : {}
tls_secret_name = var.tls_secret_name
postgresql_password = var.health_postgresql_password
secret_key = var.health_secret_key
tier = local.tiers.aux
depends_on = [null_resource.core_services]
}
module "whisper" {
source = "./whisper"
for_each = contains(local.active_modules, "whisper") ? { whisper = true } : {}

Binary file not shown.

Binary file not shown.