diff --git a/docs/architecture.md b/docs/architecture.md
index 038c39a..4810044 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -8,44 +8,22 @@ The setup is entirely self-hosted (with the exception of Hetzner Cloud VPSs and
## Network Topology
-```
- ┌──────────────┐
- │ Cloudflare │
- │ DNS + CDN │
- │ *.pez.sh │
- └──────┬───────┘
- │
- │ HTTPS
- │
- ┌────────────▼────────────┐
- │ helsinki-a │
- │ Hetzner Cloud VPS │
- │ │
- │ Caddy (reverse proxy) │
- │ Authelia (SSO) │
- │ Bitwarden │
- │ LLDAP │
- └────────────┬────────────┘
- │
- ┌───────────────┼───────────────┐
- │ Tailscale Mesh │
- │ (WireGuard-based VPN) │
- └───┬───────┬───────┬───────┬───┘
- │ │ │ │
- ┌────────▼──┐ ┌──▼────────┐ ┌────▼───────┐ ┌──▼──────────┐
- │ london-b │ │ london-a │ │nuremberg-a │ │copenhagen-a │
- │ │ │ │ │ │ │ │
- │ Storage │ │ Monitoring│ │ Mail │ │ Gaming │
- │ Media │ │ Prometheus│ │ poste.io │ │ Minecraft │
- │ Docker │ │ Grafana │ │ │ │ WoW/MaNGOS │
- │ services │ │ │ │ │ │ │
- │ (46T ZFS) │ │ (FreeBSD) │ │ (Alpine) │ │ (Ubuntu) │
- └───────────┘ └───────────┘ └────────────┘ └─────────────┘
+```mermaid
+graph TD
+ CF["Cloudflare
DNS + CDN
*.pez.sh"]
+ CF -->|HTTPS| HEL
- ┌─────────────┐
- │copenhagen-c │
- │ (idle) │
- └─────────────┘
+ HEL["helsinki-a
Hetzner Cloud VPS
Caddy (reverse proxy)
Authelia (SSO)
Bitwarden
LLDAP"]
+
+ HEL --> TS["Tailscale Mesh
WireGuard-based VPN"]
+
+ TS --> LB["london-b
Storage / Media
Docker services
(46T ZFS)"]
+ TS --> LA["london-a
Monitoring
Prometheus / Grafana
(FreeBSD)"]
+ TS --> NA["nuremberg-a
Mail
poste.io
(Alpine)"]
+ TS --> CA["copenhagen-a
Gaming
Minecraft / WoW/MaNGOS
(Ubuntu)"]
+ TS --> CC["copenhagen-c
(idle)"]
+
+ style CC stroke-dasharray: 5 5
```
## Traffic Flow
@@ -62,39 +40,27 @@ User → Cloudflare (DNS + TLS) → helsinki-a (Caddy) → Backend (over Tailsca
4. For protected services, Caddy calls Authelia first (`forward_auth`)
5. If authenticated (or no auth required), traffic is proxied over Tailscale to the backend
-```
- ┌─────────────────────────────────────────────┐
- │ helsinki-a (Caddy) │
- │ │
- radarr.pez.sh ──► │ forward_auth → Authelia ──► london-b:7878 │
- │ │
- jellyfin.pez.sh ─►│ (no auth) ───────────────► london-b:8096 │
- │ │
- grafana.pez.sh ──►│ forward_auth → Authelia ──► london-a:3000 │
- │ │
- auth.pez.sh ─────►│ (local) ────────────────► localhost:9091 │
- └─────────────────────────────────────────────┘
+```mermaid
+graph LR
+ subgraph "helsinki-a (Caddy)"
+ A1["forward_auth → Authelia"]
+ A2["(no auth)"]
+ A3["forward_auth → Authelia"]
+ A4["(local)"]
+ end
+
+ R["radarr.pez.sh"] --> A1 --> LB1["london-b:7878"]
+ J["jellyfin.pez.sh"] --> A2 --> LB2["london-b:8096"]
+ G["grafana.pez.sh"] --> A3 --> LA["london-a:3000"]
+ AU["auth.pez.sh"] --> A4 --> LO["localhost:9091"]
```
## Auth Architecture
-```
- ┌──────────┐
- │ Caddy │
- │ │
- │ forward_ │
- │ auth │
- └────┬─────┘
- │
- ┌────▼─────┐
- │ Authelia │ auth.pez.sh
- │ (SSO) │
- └────┬─────┘
- │
- ┌────▼─────┐
- │ LLDAP │ User directory
- │ │
- └──────────┘
+```mermaid
+graph TD
+ Caddy["Caddy
forward_auth"] --> Authelia["Authelia
SSO
auth.pez.sh"]
+ Authelia --> LLDAP["LLDAP
User directory"]
```
Authelia authenticates against LLDAP (both on helsinki-a). One place to manage users — add or remove someone in LDAP and it propagates to all protected services.
diff --git a/terraform/cloudflare_dns.tf b/terraform/cloudflare_dns.tf
index 36d541e..44c1da2 100644
--- a/terraform/cloudflare_dns.tf
+++ b/terraform/cloudflare_dns.tf
@@ -13,7 +13,7 @@ resource "cloudflare_dns_record" "alertmanager" {
zone_id = cloudflare_zone.pez-sh.id
name = "alertmanager"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -22,7 +22,7 @@ resource "cloudflare_dns_record" "apps" {
zone_id = cloudflare_zone.pez-sh.id
name = "apps"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -31,7 +31,7 @@ resource "cloudflare_dns_record" "auth" {
zone_id = cloudflare_zone.pez-sh.id
name = "auth"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -40,7 +40,7 @@ resource "cloudflare_dns_record" "bitwarden" {
zone_id = cloudflare_zone.pez-sh.id
name = "bitwarden"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -49,7 +49,7 @@ resource "cloudflare_dns_record" "cloud" {
zone_id = cloudflare_zone.pez-sh.id
name = "cloud"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -58,7 +58,7 @@ resource "cloudflare_dns_record" "download" {
zone_id = cloudflare_zone.pez-sh.id
name = "download"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -67,7 +67,7 @@ resource "cloudflare_dns_record" "git" {
zone_id = cloudflare_zone.pez-sh.id
name = "git"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -76,7 +76,7 @@ resource "cloudflare_dns_record" "grafana" {
zone_id = cloudflare_zone.pez-sh.id
name = "grafana"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -85,7 +85,7 @@ resource "cloudflare_dns_record" "helsinki-a" {
zone_id = cloudflare_zone.pez-sh.id
name = "helsinki-a"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -94,7 +94,7 @@ resource "cloudflare_dns_record" "jellyfin" {
zone_id = cloudflare_zone.pez-sh.id
name = "jellyfin"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -103,7 +103,7 @@ resource "cloudflare_dns_record" "jellyfin-requests" {
zone_id = cloudflare_zone.pez-sh.id
name = "jellyfin-requests"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -112,7 +112,7 @@ resource "cloudflare_dns_record" "ldap" {
zone_id = cloudflare_zone.pez-sh.id
name = "ldap"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -121,7 +121,7 @@ resource "cloudflare_dns_record" "lidarr" {
zone_id = cloudflare_zone.pez-sh.id
name = "lidarr"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -148,7 +148,7 @@ resource "cloudflare_dns_record" "music" {
zone_id = cloudflare_zone.pez-sh.id
name = "music"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -157,7 +157,7 @@ resource "cloudflare_dns_record" "naveen" {
zone_id = cloudflare_zone.pez-sh.id
name = "naveen"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -166,7 +166,7 @@ resource "cloudflare_dns_record" "root" {
zone_id = cloudflare_zone.pez-sh.id
name = "@"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -175,7 +175,7 @@ resource "cloudflare_dns_record" "plex" {
zone_id = cloudflare_zone.pez-sh.id
name = "plex"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -184,7 +184,7 @@ resource "cloudflare_dns_record" "prometheus" {
zone_id = cloudflare_zone.pez-sh.id
name = "prometheus"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -193,7 +193,7 @@ resource "cloudflare_dns_record" "prowlarr" {
zone_id = cloudflare_zone.pez-sh.id
name = "prowlarr"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -202,7 +202,7 @@ resource "cloudflare_dns_record" "radarr" {
zone_id = cloudflare_zone.pez-sh.id
name = "radarr"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -211,7 +211,7 @@ resource "cloudflare_dns_record" "readarr" {
zone_id = cloudflare_zone.pez-sh.id
name = "readarr"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -220,7 +220,7 @@ resource "cloudflare_dns_record" "request" {
zone_id = cloudflare_zone.pez-sh.id
name = "request"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -229,7 +229,7 @@ resource "cloudflare_dns_record" "rss" {
zone_id = cloudflare_zone.pez-sh.id
name = "rss"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = true
ttl = 1
}
@@ -238,7 +238,7 @@ resource "cloudflare_dns_record" "sonarr" {
zone_id = cloudflare_zone.pez-sh.id
name = "sonarr"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -247,7 +247,7 @@ resource "cloudflare_dns_record" "soulseek" {
zone_id = cloudflare_zone.pez-sh.id
name = "soulseek"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
@@ -256,7 +256,7 @@ resource "cloudflare_dns_record" "status" {
zone_id = cloudflare_zone.pez-sh.id
name = "status"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = true
ttl = 1
}
@@ -265,7 +265,7 @@ resource "cloudflare_dns_record" "thiswebsitedoesnotexist" {
zone_id = cloudflare_zone.pez-sh.id
name = "thiswebsitedoesnotexist"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = true
ttl = 1
}
@@ -274,7 +274,7 @@ resource "cloudflare_dns_record" "webdav" {
zone_id = cloudflare_zone.pez-sh.id
name = "webdav"
type = "A"
- content = "65.108.48.44"
+ content = hcloud_server.helsinki-a.ipv4_address
proxied = false
ttl = 1
}
diff --git a/terraform/hetzner_compute.tf b/terraform/hetzner_compute.tf
new file mode 100644
index 0000000..781de4d
--- /dev/null
+++ b/terraform/hetzner_compute.tf
@@ -0,0 +1,39 @@
+resource "hcloud_server" "nuremberg-a" {
+ name = "nuremberg-a"
+ image = "debian-13"
+ server_type = "cx23"
+
+ location = "nbg1"
+ delete_protection = true
+ rebuild_protection = true
+ keep_disk = true
+
+ labels = {
+ "role" = "mail"
+ }
+
+ public_net {
+ ipv4_enabled = true
+ ipv6_enabled = true
+ }
+}
+
+resource "hcloud_server" "helsinki-a" {
+ name = "helsinki-a"
+ image = "debian-13"
+ server_type = "cax11"
+
+ location = "hel1"
+ delete_protection = true
+ rebuild_protection = true
+ keep_disk = true
+
+ labels = {
+ "role" = "ingress"
+ }
+
+ public_net {
+ ipv4_enabled = true
+ ipv6_enabled = true
+ }
+}
\ No newline at end of file
diff --git a/terraform/hetzner_firewall.tf b/terraform/hetzner_firewall.tf
new file mode 100644
index 0000000..4b492a8
--- /dev/null
+++ b/terraform/hetzner_firewall.tf
@@ -0,0 +1,192 @@
+resource "hcloud_firewall" "nuremberg-a" {
+ name = "nuremberg-a"
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "22"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ # poste.io mail server ports
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "25"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "80"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "110"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "143"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "443"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "465"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "587"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "993"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "995"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "out"
+ protocol = "tcp"
+ port = "any"
+ destination_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "out"
+ protocol = "udp"
+ port = "any"
+ destination_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+}
+
+resource "hcloud_firewall_attachment" "nuremberg-a" {
+ firewall_id = hcloud_firewall.nuremberg-a.id
+ server_ids = [
+ hcloud_server.nuremberg-a.id
+ ]
+}
+
+resource "hcloud_firewall" "helsinki-a" {
+ name = "helsinki-a"
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "22"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "80"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "in"
+ protocol = "tcp"
+ port = "443"
+ source_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "out"
+ protocol = "tcp"
+ port = "any"
+ destination_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+
+ rule {
+ direction = "out"
+ protocol = "udp"
+ port = "any"
+ destination_ips = [
+ "0.0.0.0/0",
+ "::/0"
+ ]
+ }
+}
+
+resource "hcloud_firewall_attachment" "helsinki-a" {
+ firewall_id = hcloud_firewall.helsinki-a.id
+ server_ids = [
+ hcloud_server.helsinki-a.id
+ ]
+}
\ No newline at end of file
diff --git a/terraform/providers.tf b/terraform/providers.tf
index 3d6ac60..a8bbfdf 100644
--- a/terraform/providers.tf
+++ b/terraform/providers.tf
@@ -5,8 +5,14 @@ terraform {
cloudflare = {
source = "cloudflare/cloudflare"
}
+
+ hcloud = {
+ source = "hetznercloud/hcloud"
+ version = "~> 1.45"
+ }
}
+
backend "s3" {
bucket = "pez-infra-tfstate"
key = "tfstate/terraform.tfstate"
@@ -22,3 +28,8 @@ provider "cloudflare" {
email = local.secrets["cloudflare_email"]
api_token = local.secrets["cloudflare_api_key"]
}
+
+provider "hcloud" {
+ token = local.secrets["hetzner_token"]
+}
+
diff --git a/terraform/secrets.enc.yaml b/terraform/secrets.enc.yaml
index 9b65c39..884c7cc 100644
--- a/terraform/secrets.enc.yaml
+++ b/terraform/secrets.enc.yaml
@@ -1,20 +1,21 @@
-cloudflare_email: ENC[AES256_GCM,data:IOxyqjzQbw+9zg==,iv:bvMQ3JncMf2suPpshwsgtRm5h1UlQ6kAEm7cB/ExM3w=,tag:R9ZcED/RaW16wnqG99ym8A==,type:str]
-cloudflare_api_key: ENC[AES256_GCM,data:z1NWHsh4jJ+QAGILfJuKgkrBjjGKoEh2mlSER3LL8vnG8gMDbVsm9O3hkuMfsMxPsY+zbXs=,iv:sw1+gfPIf8auqdDZO3VTtSOhoi0XNsSca0EbEFWZJuI=,tag:hT9Wjls99sE2jdNVSNQtkQ==,type:str]
-backblaze_keyID: ENC[AES256_GCM,data:YneBYL27E8lmSULI9w/HLtizqMrk5nDu2Q==,iv:/gNeG2yy4Em/SIjh7i2tGV+8+KYk/d4/UHceDBM6II8=,tag:pfN0ghvcUDQxYKZdIrWUfQ==,type:str]
-backblaze_keyName: ENC[AES256_GCM,data:9tKnmmQWDTO3FHZ3D01Isvo=,iv:wLdbiPj5rgIn9Yeu5w+tOnJ2PdRtCFQLP4rncZHxN6w=,tag:ADwSi5oz613meQjPa3kshw==,type:str]
-backblaze_applicationKey: ENC[AES256_GCM,data:veIMwboFDx414vVp+kKw2uYRraayZ1DUswTKQMjfsg==,iv:dYdDd71uNPURiPGuieastA4/TtskVNq6uwsDM6Dl1JQ=,tag:jMnV0ydgTrq3zl6F6V5PPQ==,type:str]
+cloudflare_email: ENC[AES256_GCM,data:kzVXRWRT7/RUBg==,iv:g9r2gP1BxrBoAighKUIKgO1ZVgfATywSe8I5CX/SJ3A=,tag:TmWfgAfIuQVoz7ddc/7ykQ==,type:str]
+cloudflare_api_key: ENC[AES256_GCM,data:E5ZjsAQ0toXauqGkkQDR2/OqOKNaObkTlK8tnGS2nXYX4gQZaDrRhi5ufklxxO0yzZD9qHE=,iv:5JwQOIuhx1cK1jns2eIR+N1tkc4m7Ydeiya4DRoYRVg=,tag:9ojmEiG8Dlxe1EuNiv1A2w==,type:str]
+backblaze_keyID: ENC[AES256_GCM,data:mwAeG2OuxSZ95jZZ5qhJGjePtNbo5wUa2w==,iv:uRSZQsMA6sUCvaQOnRZxgdQWS/TpyjFC8nBksOH2yQE=,tag:yhjjiivBkJkhb42nfPju1A==,type:str]
+backblaze_keyName: ENC[AES256_GCM,data:HIxN7kPJPnJDp/pR/yWdayU=,iv:fk9lrFJmlZTnb1lk4AdERS+YPics1XXDOq3McBMhSGU=,tag:Sa3Z+qFs8yBmGA5FLRC/xA==,type:str]
+backblaze_applicationKey: ENC[AES256_GCM,data:0J/NTaQe+uvJXc9FgGLN4xl4EHKOxKeSjXya+wC0pA==,iv:f8w7Ir+pVs/0yD/5FFLTnlYFrw95aq73Q+r1eBZedho=,tag:cz9aMPiHWE8iIKBEA3G6xw==,type:str]
+hetzner_token: ENC[AES256_GCM,data:kUi0EJlK8xuILT7dp8ql2VQCT/t2DJCtQoXrnC52sr2y73uH4QlSGbYwrJbE+0ZgAeB2l43i8cSvW6MWUt/lrA==,iv:zrshjeeb1oQV6OHhLdXQwwhW8ssN0yHvjbjPxgYgOJk=,tag:hOy8bJuDjNJkQ0URfVwoQA==,type:str]
sops:
age:
- recipient: age1r8uh2w2qad2z5sgq9q7l73962q2sp8zz9hdnh6sjuvanxl565vmswn8squ
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyOHlsaHZKRzJLUjhTamha
- cmtFN0J3eEFNaERDNDFlbUd0dWIxV25tMVRBClJIZU55N1lLTFYxblRXd3dma0pX
- UnZzeGoyMHR0UWxkM3RaNmloUTBFUHMKLS0tIHB5TmdIWEY4dWJUQWNZcVUwV1or
- ekhtYkVLZ1hBbEZEakhXeUh0UW94QTgKdEY6mwWVQpMtaAYn+tnXFUvBk9QvzFX4
- ai91WDaO/iRtHluOSp5HxRVh2BNO4uH4opXQEthUIkQzLGtDTUN1uw==
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtWTFiajV2cThSN240YVEr
+ SlpOZUV1WVZkeXdOUXJJNnRpOXlOVnNCRGg4Cklxam1uaFgwMy9UU01STlBBSFhT
+ ZXNQSU1jQXJUZW5HWDEvVWdEUnhzS2MKLS0tIHBYMWJFYStyZVpMMXQ5MUowMy80
+ ZTdhWjkzTzRDZy8rM2J4TzhmRFFnaUkKt50w9Oq2O5qdo2NMlWo9S8V4m3X6MQG6
+ Jx/Oit+4DOCFHpL7yxggdD83NJw+0c6kMSB968J/M0EmRAzoYHqFBw==
-----END AGE ENCRYPTED FILE-----
- lastmodified: "2026-03-22T21:04:06Z"
- mac: ENC[AES256_GCM,data:6nWRb9Ne7YlcgAiJQAPx7zO51Fb2qAIup5qUG72b3s+XHbutTO5KGefWEx4/flmQx+ctbQ8fRWPOxBHECnB2xVkU0OgehGWAxKXpalnDSMp3cSjXE/Zjisd6H3U5gm8ilRysfCQE1SL8RvZCWWsKI3v89acP+ADYcU9NNOHswbc=,iv:qiWX7JFgsNgwjRPTYNNORDRUj96HRaVopN69qTAD+pM=,tag:qHw27PIvY2hhcYTLY4VPnQ==,type:str]
+ lastmodified: "2026-03-29T18:58:01Z"
+ mac: ENC[AES256_GCM,data:q9lEwaxcWAquQP+Dzg1J5WqM2cwcync9EUSVHxtc0peGAxJzg4afHlJi35mC5PZbzv/4wOpdxFR89r9jF3isvvZ6icHcRKmWmlNEl2YCI7VAKIZXZHPx56xXZoj1pOQwNNmEZgAwcreskAINjNIkP6+eIzUDCZ2QRMEK3ok9cHE=,iv:LxtYfXnwfrLmH5w7N36GGRvy1+MpgcoEzm8+KA+QjjI=,tag:/2fIIlNmJcBAXJOyZuotug==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.2