refactor(structure): reorganize monorepo with projects, terraform hierarchy, and strict k8s namespace rules

This commit is contained in:
Moritz Graf
2026-10-12 13:49:41 +02:00
parent 4831147087
commit 8f01b75bd6
4766 changed files with 551 additions and 91 deletions
+2
View File
@@ -0,0 +1,2 @@
.terraform
+104
View File
@@ -0,0 +1,104 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/goauthentik/authentik" {
version = "2026.5.1"
constraints = ">= 2024.12.0"
hashes = [
"h1:sa790BNXc5pY8vVXyFn8Y1cHY+cRDbEiHDsr9t0NdiA=",
"zh:05f252734db99792e5d3f52b582a4e0027348ac2614d8c9621bf7e623cff8036",
"zh:0e1cd8041650aed9bb8d4af23301e786223ed5d1a00c2a5150b7ad874ae03917",
"zh:26b1daebd30650df411f903248e061cf963712a063b38e731782fb13a5755ddf",
"zh:362b536ff6bf8866c7c1094c3e2322c22b8c36d8d4caa4db0397cebbcad08561",
"zh:3d94367b853960f5e88efe3ae69d995d02a11dc8d60ec4d7413a37aabf78b379",
"zh:3e840e204677eabe562d17c2ad12afdfa250098725bebac4e91fb8934e233c22",
"zh:4db66139d7a9ec6f7852538573261010d48f0a939e2fe8eeb5c55b01149e3629",
"zh:7b88a830fb26c697f9d240f498f604485454b12e56b7604eda4ca35d10660ecd",
"zh:851bc8bd2d4a16dc71f30da17b6285aa7d85f4bc4175fde7b8319a6a8987ec65",
"zh:8bdfc2c70271d48126eef3b1a30d0d132e868d2118ae7277323ed4c905636e4a",
"zh:e85c03eac43f23f25ad5443a2984cc67977c3abdcb4c4c426d37342c2f25c37e",
"zh:efc296826a700a4e90ee06f8bd3b137617486ce774772aabed3053d4ef8dd906",
"zh:f249033dc242c51600cd3968535efa0780352d5ae7435e8ab1482a7ccc9c2f5e",
]
}
provider "registry.terraform.io/hashicorp/google" {
version = "5.45.2"
constraints = "~> 5.0"
hashes = [
"h1:y2hf6zus1eKA5vpAfoyYkNBKDBQTVqmx4OVh3iRBaRo=",
"zh:0d09c8f20b556305192cdbe0efa6d333ceebba963a8ba91f9f1714b5a20c4b7a",
"zh:117143fc91be407874568df416b938a6896f94cb873f26bba279cedab646a804",
"zh:16ccf77d18dd2c5ef9c0625f9cf546ebdf3213c0a452f432204c69feed55081e",
"zh:3e555cf22a570a4bd247964671f421ed7517970cd9765ceb46f335edc2c6f392",
"zh:688bd5b05a75124da7ae6e885b2b92bd29f4261808b2b78bd5f51f525c1052ca",
"zh:6db3ef37a05010d82900bfffb3261c59a0c247e0692049cb3eb8c2ef16c9d7bf",
"zh:70316fde75f6a15d72749f66d994ccbdde5f5ed4311b6d06b99850f698c9bbf9",
"zh:84b8e583771a4f2bd514e519d98ed7fd28dce5efe0634e973170e1cfb5556fb4",
"zh:9d4b8ef0a9b6677935c604d94495042e68ff5489932cfd1ec41052e094a279d3",
"zh:a2089dd9bd825c107b148dd12d6b286f71aa37dfd4ca9c35157f2dcba7bc19d8",
"zh:f03d795c0fd9721e59839255ee7ba7414173017dc530b4ce566daf3802a0d6dd",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}
provider "registry.terraform.io/hashicorp/local" {
version = "2.9.0"
constraints = "~> 2.0"
hashes = [
"h1:oF4vw1rikPMqQtMMBoUjc/pERp50DlWwO+C2I6/sNI8=",
"zh:0baa4566cf77f1ff52f4293d1c8536202dd23edc197c3196413a28343c3ac3a0",
"zh:16b5559c3c07088ddad11a9bb9e9c0799999363c2958e9a5be2bcbbf2cd9ca64",
"zh:197c79015a10d1cce904a8ea722cbc750c42aeae2da53f44a6a0751d9fd1aa90",
"zh:29d0b03e5343a80677ebfeb2e2c31cbe4b1f65e736e53417454a4277fec2544c",
"zh:4896bfa6cf1d2fd562b47ef2e87f47862ae92a04f8ad5d764380f0c6653473b8",
"zh:531f8529cbca49f681883e57761a05a8398afaef6d1ab0d205d26bf12f4428e8",
"zh:6aaf5011d83161c86d2bfb80c0923ec934e578288758da2f37acb7aec129004b",
"zh:7430275253d3d3c40aa6179e0ec0d63212874dbbc06c5a51b9d07ec590f9756c",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:be17dc611e95e26cdf6cad79dfccf1064f0e32032a2efeb939a9bbe7fb1cbfe9",
"zh:f0e3b0aa644202e1d79d2000dca91f6019425da71e9800fa23f27e51c034f195",
"zh:f62bae4519e4ead49182ddc8afe8cf61e2a4c3ba3973b0fbba967736a2696aa3",
"zh:fcafa360a5b0b96244f26f4e3a6d642b716a376557142c2442ff2fb12d11da18",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.9.0"
hashes = [
"h1:o0s5Mk9NXMP60nlheO1r0LsDGGratFb3oL0t7bD2QnM=",
"zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1",
"zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea",
"zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f",
"zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0",
"zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61",
"zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc",
"zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e",
"zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef",
"zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b",
"zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257",
"zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04",
]
}
provider "registry.terraform.io/ovh/ovh" {
version = "2.19.0"
hashes = [
"h1:SUzZY0kbQQ1Z+T958n8//7u+UlItQgll3vTfxuUbdOM=",
"zh:0ba0eec9b66d3cf3ffbb8cc753d87aebc7ddb73b975c5b2eb372ec1fcf8712fe",
"zh:24773d4959b2fa2674760eebdda421e93a7b0e4cadf1ae135ed66d573e86b040",
"zh:2f24103c6b132cb21f1834ee2b00ebce863118d53dc2e55b85cd5b1d154bdc46",
"zh:3a7492ea842566601323b6ff4ee45a6ced7eb7c08ed888608acc3bc8e490c22a",
"zh:5018841095e9677fc8e5abcdd8336b5ad20086426cc22be35fb657b3cd8c7b9d",
"zh:62ea15a24fb26a79586c14eed2d1b8dfbdb5195288620dcd36d9c71a55b5860c",
"zh:6847b431185ddf4e7349d2397ac9cb1659ad129b01ec1661df2cac60b031e6c4",
"zh:7cdc7f403f10b405ef5e9a10094ce11685d2a739058e5c21c7db268dcf3c8fd8",
"zh:9d9ae5b6afe707487230c46930a805b0bcfc4c95cb779c4dd956695dd67a7a84",
"zh:bcc1bfd1df2546aff09726885703713dc9579f73240395cb9890140165f45d1c",
"zh:c02e93bdd854b5814089d88c2ecf76fc22b83dda1c4785aea6765c1b72ac9897",
"zh:e26191ff7f5ae7750d129136c0e6f13a54167fe332dc6c5f3f3ceab17c27c4df",
"zh:f0de0de40649efc805e63fbe405055ed4c0f8149b0d530bb63d079b62f692de1",
]
}
+819
View File
@@ -0,0 +1,819 @@
# --- Authentik Terraform Configuration ---
variable "authentik_url" {
description = "Base URL of the Authentik instance"
type = string
default = "https://auth.haumdaucher.de"
}
variable "authentik_token" {
description = "Authentik API Token (create via Directory > Tokens & App passwords)"
type = string
sensitive = true
default = null
}
provider "authentik" {
url = var.authentik_url
token = var.authentik_token
}
# --- Branding: Haumdaucher Custom Theme & Assets ---
resource "authentik_brand" "haumdaucher" {
domain = "auth.haumdaucher.de"
default = true
branding_title = "HAUMDAUCHER"
branding_logo = "https://haumdaucher.de/images/logo_classic.png"
branding_favicon = "https://haumdaucher.de/icon-192.png"
branding_custom_css = file("${path.module}/haumdaucher-theme.css")
}
# --- Directory: User Groups ---
resource "authentik_group" "infra_admins" {
name = "infra-admins"
is_superuser = false
}
resource "authentik_group" "users" {
name = "users"
is_superuser = false
}
resource "authentik_group" "verein_mitglieder" {
name = "verein-mitglieder"
is_superuser = false
}
data "authentik_user" "amy" {
username = "Amy"
}
resource "authentik_group" "family" {
name = "family"
is_superuser = false
users = [data.authentik_user.amy.pk]
}
# --- Flows: Authorization, Invalidation & Custom Invitation Flow ---
data "authentik_flow" "default_authorization_flow" {
slug = "default-provider-authorization-implicit-consent"
}
data "authentik_flow" "default_invalidation_flow" {
slug = "default-invalidation-flow"
}
# 1. Prompt Fields for Member Onboarding
resource "authentik_stage_prompt_field" "haumdaucher_enrollment_username" {
name = "haumdaucher-enrollment-username"
field_key = "username"
label = "Benutzername"
type = "username"
required = true
order = 10
}
resource "authentik_stage_prompt_field" "haumdaucher_enrollment_email" {
name = "haumdaucher-enrollment-email"
field_key = "email"
label = "E-Mail-Adresse"
type = "email"
required = true
order = 15
}
resource "authentik_stage_prompt_field" "haumdaucher_enrollment_name" {
name = "haumdaucher-enrollment-name"
field_key = "name"
label = "Vollständiger Name"
type = "text"
required = true
order = 20
}
resource "authentik_stage_prompt_field" "haumdaucher_enrollment_password" {
name = "haumdaucher-enrollment-password"
field_key = "password"
label = "Passwort"
type = "password"
required = true
order = 30
}
resource "authentik_stage_prompt_field" "haumdaucher_enrollment_password_repeat" {
name = "haumdaucher-enrollment-password-repeat"
field_key = "password_repeat"
label = "Passwort wiederholen"
type = "password"
required = true
order = 40
}
# 2. Prompt Stage
resource "authentik_stage_prompt" "haumdaucher_enrollment_prompt" {
name = "haumdaucher-enrollment-prompt"
fields = [
authentik_stage_prompt_field.haumdaucher_enrollment_username.id,
authentik_stage_prompt_field.haumdaucher_enrollment_email.id,
authentik_stage_prompt_field.haumdaucher_enrollment_name.id,
authentik_stage_prompt_field.haumdaucher_enrollment_password.id,
authentik_stage_prompt_field.haumdaucher_enrollment_password_repeat.id,
]
}
# 3. Invitation Stage (gates the flow so only valid ?itoken=... can register)
resource "authentik_stage_invitation" "haumdaucher_enrollment_invitation" {
name = "haumdaucher-enrollment-invitation"
continue_flow_without_invitation = false
}
# 4. User Write Stage (creates user in database and assigns default 'users' group)
resource "authentik_stage_user_write" "haumdaucher_enrollment_write" {
name = "haumdaucher-enrollment-write"
create_users_as_inactive = false
create_users_group = authentik_group.users.id
}
# 5. User Login Stage (starts the user session)
resource "authentik_stage_user_login" "haumdaucher_enrollment_login" {
name = "haumdaucher-enrollment-login"
session_duration = "days=30"
}
# Master Flow: Default User Login Stage (30-day persistent browser session)
import {
to = authentik_stage_user_login.default_authentication_login
id = "49749dd0-3c0d-41e2-9f10-e6c66d719901"
}
resource "authentik_stage_user_login" "default_authentication_login" {
name = "default-authentication-login"
session_duration = "days=30"
}
import {
to = authentik_stage_user_login.default_source_authentication_login
id = "7ff5a23d-6f78-4539-b40f-d7fb24af0ae7"
}
resource "authentik_stage_user_login" "default_source_authentication_login" {
name = "default-source-authentication-login"
session_duration = "days=30"
}
# 6. Redirect Stage (redirects user to haumdaucher.de after successful registration)
resource "authentik_stage_redirect" "haumdaucher_enrollment_redirect" {
name = "haumdaucher-enrollment-redirect"
mode = "static"
target_static = "https://haumdaucher.de"
}
# 7. Flow Definition
resource "authentik_flow" "haumdaucher_invitation_flow" {
name = "Haumdaucher Invitation Flow"
slug = "haumdaucher-invitation-flow"
title = "Haumdaucher Registrierung"
designation = "enrollment"
}
# 8. Flow Stage Bindings (Execution Order)
resource "authentik_flow_stage_binding" "bind_haumdaucher_invitation" {
target = authentik_flow.haumdaucher_invitation_flow.uuid
stage = authentik_stage_invitation.haumdaucher_enrollment_invitation.id
order = 10
}
resource "authentik_flow_stage_binding" "bind_haumdaucher_prompt" {
target = authentik_flow.haumdaucher_invitation_flow.uuid
stage = authentik_stage_prompt.haumdaucher_enrollment_prompt.id
order = 20
}
resource "authentik_flow_stage_binding" "bind_haumdaucher_write" {
target = authentik_flow.haumdaucher_invitation_flow.uuid
stage = authentik_stage_user_write.haumdaucher_enrollment_write.id
order = 30
}
resource "authentik_flow_stage_binding" "bind_haumdaucher_login" {
target = authentik_flow.haumdaucher_invitation_flow.uuid
stage = authentik_stage_user_login.haumdaucher_enrollment_login.id
order = 40
}
resource "authentik_flow_stage_binding" "bind_haumdaucher_redirect" {
target = authentik_flow.haumdaucher_invitation_flow.uuid
stage = authentik_stage_redirect.haumdaucher_enrollment_redirect.id
order = 50
}
# --- ForwardAuth Proxy Providers & Applications ---
# 1. Zigbee2MQTT
resource "authentik_provider_proxy" "zigbee2mqtt" {
name = "Zigbee2MQTT"
external_host = "https://zigbee2mqtt.moritzgraf.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
}
resource "authentik_application" "zigbee2mqtt" {
name = "Zigbee2MQTT"
slug = "zigbee2mqtt"
protocol_provider = authentik_provider_proxy.zigbee2mqtt.id
}
# 2. Prometheus
resource "authentik_provider_proxy" "prometheus" {
name = "Prometheus"
external_host = "https://prometheus.haumdaucher.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
}
resource "authentik_application" "prometheus" {
name = "Prometheus"
slug = "prometheus"
protocol_provider = authentik_provider_proxy.prometheus.id
}
# 3. Alertmanager
resource "authentik_provider_proxy" "alertmanager" {
name = "Alertmanager"
external_host = "https://alertmanager.haumdaucher.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
}
resource "authentik_application" "alertmanager" {
name = "Alertmanager"
slug = "alertmanager"
protocol_provider = authentik_provider_proxy.alertmanager.id
}
# 4. Nextcloud phpMyAdmin
resource "authentik_provider_proxy" "nextcloud_phpmyadmin" {
name = "Nextcloud phpMyAdmin"
external_host = "https://nextcloud.phpmyadmin.haumdaucher.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
}
resource "authentik_application" "nextcloud_phpmyadmin" {
name = "Nextcloud phpMyAdmin"
slug = "nextcloud-phpmyadmin"
protocol_provider = authentik_provider_proxy.nextcloud_phpmyadmin.id
}
# 5. Gitea phpMyAdmin
resource "authentik_provider_proxy" "gitea_phpmyadmin" {
name = "Gitea phpMyAdmin"
external_host = "https://gitea.phpmyadmin.haumdaucher.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
}
resource "authentik_application" "gitea_phpmyadmin" {
name = "Gitea phpMyAdmin"
slug = "gitea-phpmyadmin"
protocol_provider = authentik_provider_proxy.gitea_phpmyadmin.id
}
# 6. Home Assistant VS Code
resource "authentik_provider_proxy" "hass_coder" {
name = "Home Assistant Code"
external_host = "https://hass-coder.moritzgraf.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
}
resource "authentik_application" "hass_coder" {
name = "Home Assistant Code"
slug = "hass-coder"
protocol_provider = authentik_provider_proxy.hass_coder.id
}
# 7. OpenClaw
resource "authentik_provider_proxy" "openclaw" {
name = "OpenClaw"
external_host = "https://openclaw.haumdaucher.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
}
resource "authentik_application" "openclaw" {
name = "OpenClaw"
slug = "openclaw"
protocol_provider = authentik_provider_proxy.openclaw.id
}
# 8. n8n
resource "authentik_provider_proxy" "n8n" {
name = "n8n"
external_host = "https://n8n.moritzgraf.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
}
resource "authentik_application" "n8n" {
name = "n8n"
slug = "n8n"
protocol_provider = authentik_provider_proxy.n8n.id
}
# 9. SilverBullet Wiki
resource "authentik_provider_proxy" "silverbullet" {
name = "SilverBullet"
external_host = "https://wiki.moritzgraf.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
skip_path_regex = "^/service_worker\\.js$|^/\\.client/.*|^/manifest\\.json$"
access_token_validity = "days=7"
}
resource "authentik_application" "silverbullet" {
name = "SilverBullet"
slug = "silverbullet"
protocol_provider = authentik_provider_proxy.silverbullet.id
}
# 10. Hosting Family Dashboards
resource "authentik_provider_proxy" "hosting_family" {
name = "Hosting Family"
external_host = "https://hosting.haumdaucher.de"
mode = "forward_single"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
intercept_header_auth = true
access_token_validity = "days=30"
}
resource "authentik_application" "hosting_family" {
name = "Hosting Family"
slug = "hosting-family"
protocol_provider = authentik_provider_proxy.hosting_family.id
}
# --- Outpost: Embedded Proxy Outpost ---
resource "authentik_outpost" "embedded" {
name = "authentik Embedded Outpost"
type = "proxy"
protocol_providers = [
authentik_provider_proxy.zigbee2mqtt.id,
authentik_provider_proxy.prometheus.id,
authentik_provider_proxy.alertmanager.id,
authentik_provider_proxy.nextcloud_phpmyadmin.id,
authentik_provider_proxy.gitea_phpmyadmin.id,
authentik_provider_proxy.hass_coder.id,
authentik_provider_proxy.openclaw.id,
authentik_provider_proxy.n8n.id,
authentik_provider_proxy.silverbullet.id,
authentik_provider_proxy.hosting_family.id,
]
}
# --- Machine & AI Assistant: Service Account & API Token ---
resource "authentik_user" "ai_assistant" {
username = "ai-assistant"
name = "AI Assistant"
type = "service_account"
groups = [authentik_group.infra_admins.id]
}
resource "authentik_token" "ai_assistant_token" {
identifier = "ai-assistant-api-token"
user = authentik_user.ai_assistant.id
intent = "api"
expiring = false
retrieve_key = true
}
# --- Native OIDC Providers & Applications ---
# Scope Mappings
data "authentik_property_mapping_provider_scope" "openid" {
scope_name = "openid"
}
data "authentik_property_mapping_provider_scope" "email" {
scope_name = "email"
}
data "authentik_property_mapping_provider_scope" "profile" {
scope_name = "profile"
}
data "authentik_certificate_key_pair" "generated" {
name = "authentik Self-signed Certificate"
}
# 1. Gitea
resource "random_password" "gitea_oauth_client_secret" {
length = 40
special = false
}
resource "authentik_provider_oauth2" "gitea" {
name = "Gitea"
client_id = "gitea"
client_secret = random_password.gitea_oauth_client_secret.result
client_type = "confidential"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
signing_key = data.authentik_certificate_key_pair.generated.id
grant_types = ["authorization_code", "refresh_token"]
allowed_redirect_uris = [
{
matching_mode = "strict"
url = "https://git.moritzgraf.de/user/oauth2/authentik/callback"
},
{
matching_mode = "strict"
url = "https://git.moritzgraf.de/user/oauth2/Authentik/callback"
}
]
sub_mode = "user_username"
include_claims_in_id_token = true
property_mappings = [
data.authentik_property_mapping_provider_scope.openid.id,
data.authentik_property_mapping_provider_scope.email.id,
data.authentik_property_mapping_provider_scope.profile.id,
]
}
resource "authentik_application" "gitea" {
name = "Gitea"
slug = "gitea"
protocol_provider = authentik_provider_oauth2.gitea.id
}
output "gitea_oauth_client_secret" {
description = "OAuth2 Client Secret for Gitea OIDC provider"
value = random_password.gitea_oauth_client_secret.result
sensitive = true
}
# 2. Grafana
resource "random_password" "grafana_oauth_client_secret" {
length = 40
special = false
}
resource "authentik_provider_oauth2" "grafana" {
name = "Grafana"
client_id = "grafana"
client_secret = random_password.grafana_oauth_client_secret.result
client_type = "confidential"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
signing_key = data.authentik_certificate_key_pair.generated.id
grant_types = ["authorization_code", "refresh_token"]
allowed_redirect_uris = [
{
matching_mode = "strict"
url = "https://grafana.haumdaucher.de/login/generic_oauth"
}
]
sub_mode = "user_username"
include_claims_in_id_token = true
property_mappings = [
data.authentik_property_mapping_provider_scope.openid.id,
data.authentik_property_mapping_provider_scope.email.id,
data.authentik_property_mapping_provider_scope.profile.id,
]
}
resource "authentik_application" "grafana" {
name = "Grafana"
slug = "grafana"
protocol_provider = authentik_provider_oauth2.grafana.id
}
output "grafana_oauth_client_secret" {
description = "OAuth2 Client Secret for Grafana OIDC provider"
value = random_password.grafana_oauth_client_secret.result
sensitive = true
}
# 3. Coder v2
resource "random_password" "coder_oauth_client_secret" {
length = 40
special = false
}
resource "authentik_provider_oauth2" "coder" {
name = "Coder"
client_id = "coder"
client_secret = random_password.coder_oauth_client_secret.result
client_type = "confidential"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
signing_key = data.authentik_certificate_key_pair.generated.id
grant_types = ["authorization_code", "refresh_token"]
allowed_redirect_uris = [
{
matching_mode = "strict"
url = "https://coder.moritzgraf.de/api/v2/users/oidc/callback"
}
]
sub_mode = "user_username"
include_claims_in_id_token = true
property_mappings = [
data.authentik_property_mapping_provider_scope.openid.id,
data.authentik_property_mapping_provider_scope.email.id,
data.authentik_property_mapping_provider_scope.profile.id,
]
}
resource "authentik_application" "coder" {
name = "Coder"
slug = "coder"
protocol_provider = authentik_provider_oauth2.coder.id
}
output "coder_oauth_client_secret" {
description = "OAuth2 Client Secret for Coder OIDC provider"
value = random_password.coder_oauth_client_secret.result
sensitive = true
}
# 4. MinIO Console
resource "authentik_property_mapping_provider_scope" "minio_policy" {
name = "minio-policy"
scope_name = "policy"
expression = <<EOF
if "infra-admins" in [group.name for group in request.user.groups.all()]:
return {"policy": "consoleAdmin"}
return {"policy": "readonly"}
EOF
}
resource "random_password" "minio_oauth_client_secret" {
length = 40
special = false
}
resource "authentik_provider_oauth2" "minio" {
name = "MinIO"
client_id = "minio"
client_secret = random_password.minio_oauth_client_secret.result
client_type = "confidential"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
signing_key = data.authentik_certificate_key_pair.generated.id
grant_types = ["authorization_code", "refresh_token"]
allowed_redirect_uris = [
{
matching_mode = "strict"
url = "https://minio-console.haumdaucher.de/oauth_callback"
}
]
sub_mode = "user_username"
include_claims_in_id_token = true
property_mappings = [
data.authentik_property_mapping_provider_scope.openid.id,
data.authentik_property_mapping_provider_scope.email.id,
data.authentik_property_mapping_provider_scope.profile.id,
authentik_property_mapping_provider_scope.minio_policy.id,
]
}
resource "authentik_application" "minio" {
name = "MinIO"
slug = "minio"
protocol_provider = authentik_provider_oauth2.minio.id
}
output "minio_oauth_client_secret" {
description = "OAuth2 Client Secret for MinIO OIDC provider"
value = random_password.minio_oauth_client_secret.result
sensitive = true
}
# 5. Nextcloud
resource "random_password" "nextcloud_oauth_client_secret" {
length = 40
special = false
}
resource "authentik_provider_oauth2" "nextcloud" {
name = "Nextcloud"
client_id = "nextcloud"
client_secret = random_password.nextcloud_oauth_client_secret.result
client_type = "confidential"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
signing_key = data.authentik_certificate_key_pair.generated.id
grant_types = ["authorization_code", "refresh_token"]
allowed_redirect_uris = [
{
matching_mode = "strict"
url = "https://cloud.haumdaucher.de/apps/user_oidc/code"
}
]
sub_mode = "user_username"
include_claims_in_id_token = true
property_mappings = [
data.authentik_property_mapping_provider_scope.openid.id,
data.authentik_property_mapping_provider_scope.email.id,
data.authentik_property_mapping_provider_scope.profile.id,
]
}
resource "authentik_application" "nextcloud" {
name = "Nextcloud"
slug = "nextcloud"
protocol_provider = authentik_provider_oauth2.nextcloud.id
}
output "nextcloud_oauth_client_secret" {
description = "OAuth2 Client Secret for Nextcloud OIDC provider"
value = random_password.nextcloud_oauth_client_secret.result
sensitive = true
}
# 6. Home Assistant
resource "random_password" "homeassistant_oauth_client_secret" {
length = 40
special = false
}
resource "authentik_provider_oauth2" "homeassistant" {
name = "Home Assistant"
client_id = "homeassistant"
client_secret = random_password.homeassistant_oauth_client_secret.result
client_type = "confidential"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
signing_key = data.authentik_certificate_key_pair.generated.id
grant_types = ["authorization_code", "refresh_token"]
allowed_redirect_uris = [
{
matching_mode = "strict"
url = "https://hass.moritzgraf.de/auth/oidc/callback"
},
{
matching_mode = "strict"
url = "https://hass.moritzgraf.de/auth/external/callback"
}
]
sub_mode = "user_username"
include_claims_in_id_token = true
property_mappings = [
data.authentik_property_mapping_provider_scope.openid.id,
data.authentik_property_mapping_provider_scope.email.id,
data.authentik_property_mapping_provider_scope.profile.id,
]
}
resource "authentik_application" "homeassistant" {
name = "Home Assistant"
slug = "homeassistant"
protocol_provider = authentik_provider_oauth2.homeassistant.id
}
output "homeassistant_oauth_client_secret" {
description = "OAuth2 Client Secret for Home Assistant OIDC provider"
value = random_password.homeassistant_oauth_client_secret.result
sensitive = true
}
# --- Application Policy Bindings (Access Control & RBAC) ---
# 1. Admin-Only Applications (Strictly restricted to infra-admins)
resource "authentik_policy_binding" "openclaw_admin_only" {
target = authentik_application.openclaw.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "zigbee2mqtt_admin_only" {
target = authentik_application.zigbee2mqtt.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "prometheus_admin_only" {
target = authentik_application.prometheus.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "alertmanager_admin_only" {
target = authentik_application.alertmanager.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "nextcloud_phpmyadmin_admin_only" {
target = authentik_application.nextcloud_phpmyadmin.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "gitea_phpmyadmin_admin_only" {
target = authentik_application.gitea_phpmyadmin.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "hass_coder_admin_only" {
target = authentik_application.hass_coder.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "n8n_admin_only" {
target = authentik_application.n8n.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "coder_admin_only" {
target = authentik_application.coder.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "minio_admin_only" {
target = authentik_application.minio.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "silverbullet_admin_only" {
target = authentik_application.silverbullet.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "silverbullet_family" {
target = authentik_application.silverbullet.uuid
group = authentik_group.family.id
order = 1
}
resource "authentik_policy_binding" "hosting_family_admin" {
target = authentik_application.hosting_family.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "hosting_family_family" {
target = authentik_application.hosting_family.uuid
group = authentik_group.family.id
order = 1
}
# 2. Shared & Member Applications (Accessible to all authenticated users)
resource "authentik_policy_binding" "grafana_users" {
target = authentik_application.grafana.uuid
group = authentik_group.users.id
order = 0
}
resource "authentik_policy_binding" "nextcloud_users" {
target = authentik_application.nextcloud.uuid
group = authentik_group.users.id
order = 0
}
resource "authentik_policy_binding" "gitea_users" {
target = authentik_application.gitea.uuid
group = authentik_group.users.id
order = 0
}
resource "authentik_policy_binding" "homeassistant_users" {
target = authentik_application.homeassistant.uuid
group = authentik_group.users.id
order = 0
}
+109
View File
@@ -0,0 +1,109 @@
locals {
openclaw_project_id = "octobot-486812"
}
# ------------------------------------------------------------------------------
# 1. API Services Enablement
# ------------------------------------------------------------------------------
resource "google_project_service" "apikeys_api" {
project = local.openclaw_project_id
service = "apikeys.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "youtube_api" {
project = local.openclaw_project_id
service = "youtube.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "routes_api" {
project = local.openclaw_project_id
service = "routes.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "geocoding_api" {
project = local.openclaw_project_id
service = "geocoding-backend.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "static_maps_api" {
project = local.openclaw_project_id
service = "static-maps-backend.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "places_api" {
project = local.openclaw_project_id
service = "places.googleapis.com"
disable_on_destroy = false
}
# ------------------------------------------------------------------------------
# 2. Dedicated API Keys
# ------------------------------------------------------------------------------
resource "google_apikeys_key" "openclaw_youtube_key" {
name = "openclaw-youtube-key"
display_name = "OpenClaw YouTube API Key"
project = local.openclaw_project_id
restrictions {
api_targets {
service = "youtube.googleapis.com"
}
}
depends_on = [
google_project_service.apikeys_api,
google_project_service.youtube_api
]
}
resource "google_apikeys_key" "openclaw_google_maps_key" {
name = "openclaw-google-maps-key"
display_name = "OpenClaw Google Maps API Key"
project = local.openclaw_project_id
restrictions {
api_targets {
service = "places.googleapis.com"
}
api_targets {
service = "routes.googleapis.com"
}
api_targets {
service = "geocoding-backend.googleapis.com"
}
api_targets {
service = "static-maps-backend.googleapis.com"
}
}
depends_on = [
google_project_service.apikeys_api,
google_project_service.places_api,
google_project_service.routes_api,
google_project_service.geocoding_api,
google_project_service.static_maps_api
]
}
# ------------------------------------------------------------------------------
# 3. Outputs
# ------------------------------------------------------------------------------
output "openclaw_youtube_api_key" {
value = google_apikeys_key.openclaw_youtube_key.key_string
description = "API Key dedicated to YouTube Data API v3 for OpenClaw"
sensitive = true
}
output "openclaw_google_maps_api_key" {
value = google_apikeys_key.openclaw_google_maps_key.key_string
description = "API Key dedicated to Google Maps Platform for OpenClaw"
sensitive = true
}
+62
View File
@@ -0,0 +1,62 @@
variable "gcp_project_id" {
type = string
description = "The GCP Project ID where the Service Account should be created. If empty, a new project named 'velero-backup-xxxx' will be created."
default = ""
}
resource "random_id" "project_suffix" {
byte_length = 4
}
resource "google_project" "backup_project" {
count = var.gcp_project_id == "" ? 1 : 0
name = "velero-backup"
project_id = "velero-backup-${random_id.project_suffix.hex}"
}
locals {
project_id = var.gcp_project_id == "" ? google_project.backup_project[0].project_id : var.gcp_project_id
}
resource "google_project_service" "drive_api" {
project = local.project_id
service = "drive.googleapis.com"
disable_on_destroy = false
}
resource "google_project_organization_policy" "allow_key_creation" {
project = local.project_id
constraint = "iam.disableServiceAccountKeyCreation"
boolean_policy {
enforced = false
}
}
resource "google_service_account" "velero_backup_sa" {
project = local.project_id
account_id = "velero-backup-sa"
display_name = "Velero GDrive Backup Service Account"
depends_on = [google_project_service.drive_api]
}
resource "google_service_account_key" "velero_backup_sa_key" {
service_account_id = google_service_account.velero_backup_sa.name
depends_on = [google_project_organization_policy.allow_key_creation]
}
resource "local_file" "gdrive_sa_key_file" {
content = base64decode(google_service_account_key.velero_backup_sa_key.private_key)
filename = "${path.module}/../k8s/velero/gdrive_sa.json.secret"
}
output "velero_backup_sa_email" {
value = google_service_account.velero_backup_sa.email
description = "The email address of the Google Cloud Service Account. Share your Google Drive backup folder with this email address and grant it Editor access."
}
resource "google_project_service" "photos_api" {
project = "default-api-key-project"
service = "photoslibrary.googleapis.com"
disable_on_destroy = false
}
+40
View File
@@ -0,0 +1,40 @@
locals {
calendar_project_id = "octobot-486812"
}
resource "google_project_service" "calendar_api" {
project = local.calendar_project_id
service = "calendar-json.googleapis.com"
disable_on_destroy = false
}
resource "google_project_organization_policy" "allow_calendar_key_creation" {
project = local.calendar_project_id
constraint = "iam.disableServiceAccountKeyCreation"
boolean_policy {
enforced = false
}
}
resource "google_service_account" "openclaw_calendar_sa" {
project = local.calendar_project_id
account_id = "openclaw-calendar"
display_name = "OpenClaw Google Calendar Service Account"
depends_on = [google_project_service.calendar_api]
}
resource "google_service_account_key" "openclaw_calendar_sa_key" {
service_account_id = google_service_account.openclaw_calendar_sa.name
depends_on = [google_project_organization_policy.allow_calendar_key_creation]
}
resource "local_file" "openclaw_calendar_sa_key_file" {
content = base64decode(google_service_account_key.openclaw_calendar_sa_key.private_key)
filename = "${path.module}/../k8s/openclaw/calendar_sa.json.secret"
}
output "openclaw_calendar_sa_email" {
value = google_service_account.openclaw_calendar_sa.email
description = "The email address of the Google Calendar Service Account. Share your Google Calendars with this email address and grant it 'Make changes to events' access."
}
@@ -0,0 +1,242 @@
# haumdaucher.de
resource "ovh_domain_zone_record" "haumdaucher_mx_record" {
zone = "haumdaucher.de"
fieldtype = "MX"
ttl = 60
target = "1 SMTP.GOOGLE.COM."
}
resource "ovh_domain_zone_record" "spf_haumdaucher_de" {
zone = "haumdaucher.de"
subdomain = "" # SPF record goes on the root domain
fieldtype = "TXT"
ttl = 60 # Time-to-live in seconds (1 hour is standard, but 60 is ok too)
target = "v=spf1 include:_spf.google.com ~all"
}
resource "ovh_domain_zone_record" "dmarc_haumdaucher_de" {
zone = "haumdaucher.de"
subdomain = "_dmarc" # DMARC record always goes on the _dmarc subdomain
fieldtype = "TXT"
ttl = 60 # 1 hour TTL
target = "v=DMARC1; p=none; rua=mailto:custom-admin@haumdaucher.de; ruf=mailto:custom-admin@haumdaucher.de"
}
resource "ovh_domain_zone_record" "dkim_google_haumdaucher_de" {
zone = "haumdaucher.de"
subdomain = "google._domainkey" # The DNS Host name provided by Google
fieldtype = "TXT"
ttl = 3600 # 1 hour TTL
target = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq4al3nYNZLlUBn0dcxqui5q48jjk4YvB2d2VNLfr864YdCm+3WqO6fc8Yx7sO4jKiX+OpMXnOc2RZazCCpcdCofJwTZxT+H0u2h4uyveEHq8UYqQZ66aAFLt3N1HYMT6+uFJZpKpSkFgqLagkQoW/cQHguJuu7Xjmt09fLTsBunjfJkKfUcjcyNlpnnAcVuo/TT7DKNzjB436yB1pf8QHxykUkV/lfz1/IzfRV//hR3H5pSNn4kR9qT9HqrTGgaZkPmStjoV2i2Dih4d3jO9FF1VDUNDm7W7739yiQhRZ5yelj5TaWGeRxWv+xM3YpVGqJzWI9oCuSpR2QaVKaOMQwIDAQAB"
}
# moritzgraf.de
resource "ovh_domain_zone_record" "mx_record_moritzgraf_de" {
zone = "moritzgraf.de"
fieldtype = "MX"
ttl = 60
target = "1 SMTP.GOOGLE.COM."
}
resource "ovh_domain_zone_record" "google_domain_verification_moritzgraf_de" {
zone = "moritzgraf.de"
fieldtype = "TXT"
ttl = 60
target = "google-site-verification=YwUd-Bmbq1-lA7uYGcowsLEMczqx1odndFZq_Nk3nvM"
}
resource "ovh_domain_zone_record" "spf_moritzgraf_de" {
zone = "moritzgraf.de"
subdomain = "" # SPF record goes on the root domain
fieldtype = "TXT"
ttl = 60 # Time-to-live in seconds (1 hour is standard, but 60 is ok too)
target = "v=spf1 include:_spf.google.com ~all"
}
resource "ovh_domain_zone_record" "dmarc_moritzgraf_de" {
zone = "moritzgraf.de"
subdomain = "_dmarc" # DMARC record always goes on the _dmarc subdomain
fieldtype = "TXT"
ttl = 60 # 1 hour TTL
target = "v=DMARC1; p=none; rua=mailto:custom-admin@haumdaucher.de; ruf=mailto:custom-admin@haumdaucher.de"
}
resource "ovh_domain_zone_record" "dkim_google_moritzgraf_de" {
zone = "moritzgraf.de"
subdomain = "google._domainkey" # The DNS Host name provided by Google
fieldtype = "TXT"
ttl = 3600 # 1 hour TTL
target = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz4hYXxdgHDaNwqiUrHa4S9vbqLx/iV2avvgwwPqa5+2aOef109Ap8U7OfgFQQgwRVXx5XWbkKL69as3sdzuVA+8A8fRKbprugZEFoL9gyoTxPQveKO2s+1CsP0fUIx1c7aemfgn6mrfC0Ep17D70Y9YcQQWgiNHGHlgQDE1z6ncfUquHdipcmd0wEIAmjdNDBoqf3b1SRz5xaayKIeAsf/f2AFB1IeYwvLEaOC/mQDZWsry86RwjAfKvxbw0zAQaxSLFOho+kuxL2YUPbT55mZaB9Bi1G4VemzZUGuqXoq8xPzFOO/wxNi6Pei0Vs9wLwFahtuxriin4EVYOkjF4uwIDAQAB"
}
### ameliegraf.de
resource "ovh_domain_zone_record" "mx_record_ameliegraf_de" {
zone = "ameliegraf.de"
fieldtype = "MX"
ttl = 60
target = "1 smtp.google.com."
}
resource "ovh_domain_zone_record" "google_domain_verification_ameliegraf_de" {
zone = "ameliegraf.de"
fieldtype = "TXT"
ttl = 60
target = "google-site-verification=LwCbVqOp4c200QYrEEX1m4lIebOzxl5eBb2IkHbrSVA"
}
resource "ovh_domain_zone_record" "spf_ameliegraf_de" {
zone = "ameliegraf.de"
subdomain = "" # SPF record goes on the root domain
fieldtype = "TXT"
ttl = 60 # Time-to-live in seconds (1 hour is standard, but 60 is ok too)
target = "v=spf1 include:_spf.google.com ~all"
}
resource "ovh_domain_zone_record" "dmarc_ameliegraf_de" {
zone = "ameliegraf.de"
subdomain = "_dmarc" # DMARC record always goes on the _dmarc subdomain
fieldtype = "TXT"
ttl = 60 # 1 hour TTL
target = "v=DMARC1; p=none; rua=mailto:custom-admin@haumdaucher.de; ruf=mailto:custom-admin@haumdaucher.de"
}
resource "ovh_domain_zone_record" "dkim_google_ameliegraf_de" {
zone = "ameliegraf.de"
subdomain = "google._domainkey" # The DNS Host name provided by Google
fieldtype = "TXT"
ttl = 3600 # 1 hour TTL
target = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxkPdqXqsRT9NMioibnQJ1MmOX4Yzp/pwpJAIEGawsVLwZCkkhZEH5SyT4CSVz3AS6/XG2Qgx/a2hYctkzXSXllBhFDvixie6oXCOsjqyfr7R5s4LaKb399djFFl/GC7gSV+PUAW6sI02vJPfmn8BaVRAkrH0OrcjX3LSaTS5RRR9QGNCx0JreM+ho2Mcp8VbZCwnCMu42IjilR1d+kQX+2x0+r6mO0eJfP2QKOVpRnBXgv/FT+8jE+dmusf8MFoXxKuX6QjUh5TBH2J9W5NHUiBB4eC2DTrceuO1bM8ycmaGS95e1BmYXMcwfJknhjIgK85jESA8uDa38PIrN/b7WwIDAQAB"
}
### graf-fam.de
resource "ovh_domain_zone_record" "mx_record_graf_fam_de" {
zone = "graf-fam.de"
fieldtype = "MX"
ttl = 60
target = "1 smtp.google.com."
}
resource "ovh_domain_zone_record" "google_domain_verification_graf_fam_de" {
zone = "graf-fam.de"
fieldtype = "TXT"
ttl = 60
target = "google-site-verification=DcG0tKp5YXxCRvw_Ekxa-3gbzdkZvxXUVkPs8aq9yQ4"
}
resource "ovh_domain_zone_record" "spf_graf_fam_de" {
zone = "graf-fam.de"
subdomain = "" # SPF record goes on the root domain
fieldtype = "TXT"
ttl = 60 # Time-to-live in seconds (1 hour is standard, but 60 is ok too)
target = "v=spf1 include:_spf.google.com ~all"
}
resource "ovh_domain_zone_record" "dmarc_graf_fam_de" {
zone = "graf-fam.de"
subdomain = "_dmarc" # DMARC record always goes on the _dmarc subdomain
fieldtype = "TXT"
ttl = 60 # 1 hour TTL
target = "v=DMARC1; p=none; rua=mailto:custom-admin@haumdaucher.de; ruf=mailto:custom-admin@haumdaucher.de"
}
resource "ovh_domain_zone_record" "dkim_google_graf_fam_de" {
zone = "graf-fam.de"
subdomain = "google._domainkey" # The DNS Host name provided by Google
fieldtype = "TXT"
ttl = 3600 # 1 hour TTL
target = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqT7j2pdU/dKDp7392+67cqI54QSwQsYrbdJa0aOH94m5gcXa3B2CZvuN2iKt6mO2mUtzWUbwKcNVL0p9yVW6nHpCMNefUS3NfZ8KQfl+lVsaYZXzYZwuTy2ZMvaBZpftIQjclfChFrOtMvBOTpdu8SBJT8hXnqkOYvtuHPBG+qDsFvsV+N4jjHtYbMPMnD2w7GeYciFWzp3bM9FbYIgdzyD+jzIYkaJlph4cMJBMoOredJhK+T2F95uvsvUk43QNU6KujLTFL6F/wNM/zSmH+5hYBAgAMG3QAguhicATJHV6bxd2oM1aLWNjZt90xBkTmMMzvtLjcGGpkgNuxa35WwIDAQAB"
}
### krassescheisse.de
resource "ovh_domain_zone_record" "mx_record_krassescheisse_de" {
zone = "krassescheisse.de"
fieldtype = "MX"
ttl = 60
target = "1 smtp.google.com."
}
resource "ovh_domain_zone_record" "google_domain_verification_krassescheisse_de" {
zone = "krassescheisse.de"
fieldtype = "TXT"
ttl = 60
target = "google-site-verification=SHFDcMegmOoh9syhj7_xBH9dLQxA2NMnh_zvg4Ma0CM"
}
resource "ovh_domain_zone_record" "spf_krassescheisse_de" {
zone = "krassescheisse.de"
subdomain = "" # SPF record goes on the root domain
fieldtype = "TXT"
ttl = 60 # Time-to-live in seconds (1 hour is standard, but 60 is ok too)
target = "v=spf1 include:_spf.google.com ~all"
}
resource "ovh_domain_zone_record" "dmarc_krassescheisse_de" {
zone = "krassescheisse.de"
subdomain = "_dmarc" # DMARC record always goes on the _dmarc subdomain
fieldtype = "TXT"
ttl = 60 # 1 hour TTL
target = "v=DMARC1; p=none; rua=mailto:custom-admin@haumdaucher.de; ruf=mailto:custom-admin@haumdaucher.de"
}
resource "ovh_domain_zone_record" "dkim_google_krassescheisse_de" {
zone = "krassescheisse.de"
subdomain = "google._domainkey" # The DNS Host name provided by Google
fieldtype = "TXT"
ttl = 3600 # 1 hour TTL
target = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9dLGkWdBZ0YyjBdRVTf26Fo+lVag1dfHHpO4N+oFLg0xV1gRJFcSGjteHUoKbrJxgesAwSmBYsaWAZB0RCI4wbAZKgb++6NX2Tk5gxkKys9pmc3ufGhCsjQkiiNepUkgalJRU7WAsWWolHXqn69D2DkXx9fPekdwaDeJwdtozoGacdbj46Ef1btGo48CJKk5rjh39c7BDNMmYEXXMztBIK2K80Y5WrLJZFxkvEKBrEB4pVoPDa3xEYcR0olLdDwB2SOEX6HSQ2xXkNB3KxbyIiHJTSJsNxquozTzfiWoqly/AdQLCU9j3jtqP88ne61NciQcxGdvOcQ0yUtFoydOwIDAQAB"
}
### grabengasse7.de
resource "ovh_domain_zone_record" "mx_record_grabengasse7_de" {
zone = "grabengasse7.de"
fieldtype = "MX"
ttl = 60
target = "1 smtp.google.com."
}
resource "ovh_domain_zone_record" "google_domain_verification_grabengasse7_de" {
zone = "grabengasse7.de"
fieldtype = "TXT"
ttl = 60
target = "google-site-verification=zwmmYhET0gyCJr_An22btaOVcpsE_nR2b1St0y4jn1A"
}
resource "ovh_domain_zone_record" "spf_grabengasse7_de" {
zone = "grabengasse7.de"
subdomain = "" # SPF record goes on the root domain
fieldtype = "TXT"
ttl = 60 # Time-to-live in seconds (1 hour is standard, but 60 is ok too)
target = "v=spf1 include:_spf.google.com ~all"
}
resource "ovh_domain_zone_record" "dmarc_grabengasse7_de" {
zone = "grabengasse7.de"
subdomain = "_dmarc" # DMARC record always goes on the _dmarc subdomain
fieldtype = "TXT"
ttl = 60 # 1 hour TTL
target = "v=DMARC1; p=none; rua=mailto:custom-admin@haumdaucher.de; ruf=mailto:custom-admin@haumdaucher.de"
}
resource "ovh_domain_zone_record" "dkim_google_grabengasse7_de" {
zone = "grabengasse7.de"
subdomain = "google._domainkey" # The DNS Host name provided by Google
fieldtype = "TXT"
ttl = 3600 # 1 hour TTL
target = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxR/e/Bc6/6Rfhos0ZQoVsq4HV8wkOdnOgkyubzvnepRDm/ARCws/CTvg9azJGFafsOsft1JZRAypj8mMovnDBjnW3mclVCyHQO34VLm13Et8ySX3GeJwwJQKx52PHsCQT1eEeEF3IjckS9wDiCkgdty9NEqpdF76N62nj6j8WY0czNxrYT0ivyiKQ6reB/mOeTiqPHiSHuB3ANxebwy5/wLilHSH320T9C+TDqcZ/Q6VTrUFpXIMjccEg93ZPaIu++FMTWZAe1pX6IWMuBXt5xRLLTamTzDUIOfwhvMxVee+d9mqwvIOc6J6ZiBhhCZpSVjiVLI5NcfNICQU4kjakwIDAQAB"
}
@@ -0,0 +1,685 @@
/* ==========================================================================
HAUMDAUCHER THEME FOR AUTHENTIK
Wurst und Spezialitäten n.e.V.
Core Principle: Black on White / White on Black (High Contrast & Consistency)
========================================================================== */
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&display=swap');
/* ==========================================================================
1. GLOBAL FOUNDATION & TYPOGRAPHY
========================================================================== */
:root {
--hd-font: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--pf-global--FontFamily--sans-serif: var(--hd-font);
--pf-v5-global--FontFamily--sans-serif: var(--hd-font);
/* PatternFly Button Variables (Light Mode / Default) */
--pf-c-button--m-primary--BackgroundColor: #000000;
--pf-c-button--m-primary--hover--BackgroundColor: #262626;
--pf-c-button--m-primary--focus--BackgroundColor: #262626;
--pf-c-button--m-primary--active--BackgroundColor: #000000;
--pf-c-button--m-primary--Color: #ffffff;
--pf-c-button--m-primary--hover--Color: #ffffff;
--pf-c-button--m-primary--focus--Color: #ffffff;
--pf-c-button--m-primary--active--Color: #ffffff;
--pf-c-button--m-secondary--BackgroundColor: #ffffff;
--pf-c-button--m-secondary--hover--BackgroundColor: #000000;
--pf-c-button--m-secondary--focus--BackgroundColor: #f4f5f8;
--pf-c-button--m-secondary--active--BackgroundColor: #000000;
--pf-c-button--m-secondary--Color: #000000;
--pf-c-button--m-secondary--hover--Color: #ffffff;
--pf-c-button--m-secondary--focus--Color: #000000;
--pf-c-button--m-secondary--active--Color: #ffffff;
--pf-c-button--m-secondary--BorderColor: #000000;
--pf-c-button--m-secondary--hover--BorderColor: #000000;
--pf-c-button--m-control--Color: #000000;
--pf-c-button--m-control--BackgroundColor: transparent;
--pf-c-button--m-plain--Color: #000000;
--pf-c-button--m-tertiary--Color: #000000;
}
/* Dark Mode Button Variables */
[data-theme='dark'] {
--pf-c-button--m-primary--BackgroundColor: #ffffff;
--pf-c-button--m-primary--hover--BackgroundColor: #e4e4e7;
--pf-c-button--m-primary--focus--BackgroundColor: #e4e4e7;
--pf-c-button--m-primary--active--BackgroundColor: #ffffff;
--pf-c-button--m-primary--Color: #000000;
--pf-c-button--m-primary--hover--Color: #000000;
--pf-c-button--m-primary--focus--Color: #000000;
--pf-c-button--m-primary--active--Color: #000000;
--pf-c-button--m-secondary--BackgroundColor: transparent;
--pf-c-button--m-secondary--hover--BackgroundColor: #ffffff;
--pf-c-button--m-secondary--focus--BackgroundColor: #27272a;
--pf-c-button--m-secondary--active--BackgroundColor: #ffffff;
--pf-c-button--m-secondary--Color: #ffffff;
--pf-c-button--m-secondary--hover--Color: #000000;
--pf-c-button--m-secondary--focus--Color: #ffffff;
--pf-c-button--m-secondary--active--Color: #000000;
--pf-c-button--m-secondary--BorderColor: #ffffff;
--pf-c-button--m-secondary--hover--BorderColor: #ffffff;
--pf-c-button--m-control--Color: #ffffff;
--pf-c-button--m-control--BackgroundColor: transparent;
--pf-c-button--m-plain--Color: #ffffff;
--pf-c-button--m-tertiary--Color: #ffffff;
}
/* ==========================================================================
2. UNIVERSAL BUTTON CONTRAST ENFORCEMENT
Guarantees that button text is NEVER the same color as the button background
in ANY state (normal, hover, focus, active, disabled).
========================================================================== */
/* Ensure all inner elements (spans, icons, text) inherit button text color */
.pf-c-button *,
.pf-v5-c-button *,
button * {
color: inherit !important;
}
/* --- Primary Buttons (Light Mode / Default: Solid Black with Solid White Text) --- */
.pf-c-button.pf-m-primary,
.pf-v5-c-button.pf-m-primary,
button[type="submit"] {
background-color: #000000 !important;
color: #ffffff !important;
border: 1px solid #000000 !important;
font-family: var(--hd-font) !important;
font-weight: 700 !important;
border-radius: 12px !important;
transition: all 0.2s ease-in-out !important;
}
.pf-c-button.pf-m-primary:hover,
.pf-v5-c-button.pf-m-primary:hover,
button[type="submit"]:hover {
background-color: #262626 !important;
color: #ffffff !important;
border-color: #262626 !important;
}
.pf-c-button.pf-m-primary:focus,
.pf-v5-c-button.pf-m-primary:focus,
button[type="submit"]:focus {
background-color: #262626 !important;
color: #ffffff !important;
outline: 2px solid #000000 !important;
outline-offset: 2px !important;
}
.pf-c-button.pf-m-primary:active,
.pf-v5-c-button.pf-m-primary:active,
button[type="submit"]:active {
background-color: #000000 !important;
color: #ffffff !important;
}
.pf-c-button.pf-m-primary:disabled,
.pf-c-button.pf-m-primary.pf-m-disabled,
.pf-v5-c-button.pf-m-primary:disabled,
.pf-v5-c-button.pf-m-primary.pf-m-disabled,
button[type="submit"]:disabled {
background-color: #e5e7eb !important;
color: #6b7280 !important;
border-color: #d1d5db !important;
cursor: not-allowed !important;
opacity: 0.8 !important;
}
/* --- Primary Buttons (Dark Mode: Solid White with Solid Black Text) --- */
[data-theme='dark'] .pf-c-button.pf-m-primary:not(ak-flow-executor *),
[data-theme='dark'] .pf-v5-c-button.pf-m-primary:not(ak-flow-executor *),
[data-theme='dark'] .pf-c-page button[type="submit"] {
background-color: #ffffff !important;
color: #000000 !important;
border: 1px solid #ffffff !important;
}
[data-theme='dark'] .pf-c-button.pf-m-primary:not(ak-flow-executor *):hover,
[data-theme='dark'] .pf-v5-c-button.pf-m-primary:not(ak-flow-executor *):hover,
[data-theme='dark'] .pf-c-page button[type="submit"]:hover {
background-color: #e4e4e7 !important;
color: #000000 !important;
border-color: #e4e4e7 !important;
}
[data-theme='dark'] .pf-c-button.pf-m-primary:not(ak-flow-executor *):focus,
[data-theme='dark'] .pf-v5-c-button.pf-m-primary:not(ak-flow-executor *):focus,
[data-theme='dark'] .pf-c-page button[type="submit"]:focus {
background-color: #e4e4e7 !important;
color: #000000 !important;
outline: 2px solid #ffffff !important;
outline-offset: 2px !important;
}
[data-theme='dark'] .pf-c-button.pf-m-primary:not(ak-flow-executor *):disabled,
[data-theme='dark'] .pf-v5-c-button.pf-m-primary:not(ak-flow-executor *):disabled,
[data-theme='dark'] .pf-c-page button[type="submit"]:disabled {
background-color: #27272a !important;
color: #71717a !important;
border-color: #3f3f46 !important;
}
/* --- Secondary Buttons (Light Mode / Default: Solid White with Black Border & Text) --- */
.pf-c-button.pf-m-secondary,
.pf-v5-c-button.pf-m-secondary {
background-color: #ffffff !important;
color: #000000 !important;
border: 1px solid #000000 !important;
font-family: var(--hd-font) !important;
font-weight: 600 !important;
border-radius: 12px !important;
transition: all 0.2s ease-in-out !important;
}
.pf-c-button.pf-m-secondary:hover,
.pf-v5-c-button.pf-m-secondary:hover {
background-color: #000000 !important;
color: #ffffff !important;
border-color: #000000 !important;
}
.pf-c-button.pf-m-secondary:focus,
.pf-v5-c-button.pf-m-secondary:focus {
background-color: #f4f5f8 !important;
color: #000000 !important;
outline: 2px solid #000000 !important;
outline-offset: 2px !important;
}
.pf-c-button.pf-m-secondary:active,
.pf-v5-c-button.pf-m-secondary:active {
background-color: #000000 !important;
color: #ffffff !important;
}
.pf-c-button.pf-m-secondary:disabled,
.pf-c-button.pf-m-secondary.pf-m-disabled,
.pf-v5-c-button.pf-m-secondary:disabled,
.pf-v5-c-button.pf-m-secondary.pf-m-disabled {
background-color: #f9fafb !important;
color: #9ca3af !important;
border-color: #e5e7eb !important;
cursor: not-allowed !important;
}
/* --- Secondary Buttons (Dark Mode) --- */
[data-theme='dark'] .pf-c-button.pf-m-secondary:not(ak-flow-executor *),
[data-theme='dark'] .pf-v5-c-button.pf-m-secondary:not(ak-flow-executor *) {
background-color: transparent !important;
color: #ffffff !important;
border: 1px solid #ffffff !important;
}
[data-theme='dark'] .pf-c-button.pf-m-secondary:not(ak-flow-executor *):hover,
[data-theme='dark'] .pf-v5-c-button.pf-m-secondary:not(ak-flow-executor *):hover {
background-color: #ffffff !important;
color: #000000 !important;
border-color: #ffffff !important;
}
[data-theme='dark'] .pf-c-button.pf-m-secondary:not(ak-flow-executor *):focus,
[data-theme='dark'] .pf-v5-c-button.pf-m-secondary:not(ak-flow-executor *):focus {
background-color: #27272a !important;
color: #ffffff !important;
outline: 2px solid #ffffff !important;
outline-offset: 2px !important;
}
[data-theme='dark'] .pf-c-button.pf-m-secondary:not(ak-flow-executor *):disabled,
[data-theme='dark'] .pf-v5-c-button.pf-m-secondary:not(ak-flow-executor *):disabled {
background-color: transparent !important;
color: #52525b !important;
border-color: #27272a !important;
}
/* --- Control / Plain / Tertiary Buttons (e.g. eye toggle on password fields) --- */
.pf-c-button.pf-m-control,
.pf-v5-c-button.pf-m-control,
.pf-c-button.pf-m-plain,
.pf-v5-c-button.pf-m-plain,
.pf-c-button.pf-m-tertiary,
.pf-v5-c-button.pf-m-tertiary {
color: #000000 !important;
background-color: transparent !important;
}
.pf-c-button.pf-m-control:hover,
.pf-v5-c-button.pf-m-control:hover,
.pf-c-button.pf-m-plain:hover,
.pf-v5-c-button.pf-m-plain:hover,
.pf-c-button.pf-m-tertiary:hover,
.pf-v5-c-button.pf-m-tertiary:hover {
color: #000000 !important;
background-color: rgba(0, 0, 0, 0.08) !important;
}
[data-theme='dark'] .pf-c-button.pf-m-control:not(ak-flow-executor *),
[data-theme='dark'] .pf-v5-c-button.pf-m-control:not(ak-flow-executor *),
[data-theme='dark'] .pf-c-button.pf-m-plain:not(ak-flow-executor *),
[data-theme='dark'] .pf-v5-c-button.pf-m-plain:not(ak-flow-executor *),
[data-theme='dark'] .pf-c-button.pf-m-tertiary:not(ak-flow-executor *),
[data-theme='dark'] .pf-v5-c-button.pf-m-tertiary:not(ak-flow-executor *) {
color: #ffffff !important;
background-color: transparent !important;
}
[data-theme='dark'] .pf-c-button.pf-m-control:not(ak-flow-executor *):hover,
[data-theme='dark'] .pf-v5-c-button.pf-m-control:not(ak-flow-executor *):hover,
[data-theme='dark'] .pf-c-button.pf-m-plain:not(ak-flow-executor *):hover,
[data-theme='dark'] .pf-v5-c-button.pf-m-plain:not(ak-flow-executor *):hover,
[data-theme='dark'] .pf-c-button.pf-m-tertiary:not(ak-flow-executor *):hover,
[data-theme='dark'] .pf-v5-c-button.pf-m-tertiary:not(ak-flow-executor *):hover {
color: #ffffff !important;
background-color: rgba(255, 255, 255, 0.12) !important;
}
/* --- Link Buttons --- */
.pf-c-button.pf-m-link,
.pf-v5-c-button.pf-m-link {
color: #000000 !important;
text-decoration: underline !important;
background-color: transparent !important;
}
.pf-c-button.pf-m-link:hover,
.pf-v5-c-button.pf-m-link:hover {
color: #333333 !important;
}
[data-theme='dark'] .pf-c-button.pf-m-link:not(ak-flow-executor *),
[data-theme='dark'] .pf-v5-c-button.pf-m-link:not(ak-flow-executor *) {
color: #ffffff !important;
text-decoration: underline !important;
}
[data-theme='dark'] .pf-c-button.pf-m-link:not(ak-flow-executor *):hover,
[data-theme='dark'] .pf-v5-c-button.pf-m-link:not(ak-flow-executor *):hover {
color: #d4d4d8 !important;
}
/* ==========================================================================
3. LOGIN & AUTHENTICATION FLOW SCOPE (Classic White Card & Black Elements)
========================================================================== */
/* Page Viewport Centering & Soft Minimal Backdrop */
ak-flow-executor,
.pf-c-login,
.pf-v5-c-login {
min-height: 100vh !important;
min-height: 100dvh !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
background-color: #f4f5f8 !important;
background-image: radial-gradient(circle at 50% 15%, rgba(0, 0, 0, 0.03) 0%, transparent 60%) !important;
background-attachment: fixed !important;
background-size: cover !important;
padding: 32px 16px 48px 16px !important;
box-sizing: border-box !important;
position: relative !important;
font-family: var(--hd-font) !important;
color: #111111 !important;
}
/* Centered Login Container */
ak-flow-executor .pf-c-login__container,
.pf-c-login .pf-c-login__container,
.pf-v5-c-login .pf-v5-c-login__container {
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
width: 100% !important;
max-width: 420px !important;
margin: auto !important;
padding: 0 !important;
box-sizing: border-box !important;
}
/* Header & Logo */
ak-flow-executor .pf-c-login__header,
.pf-c-login .pf-c-login__header,
.pf-v5-c-login .pf-v5-c-login__header {
display: flex !important;
justify-content: center !important;
align-items: center !important;
width: 100% !important;
margin-top: 0 !important;
margin-bottom: 24px !important;
padding: 0 !important;
}
ak-flow-executor .pf-c-login__header ak-brand,
ak-flow-executor .pf-c-login__header img,
.pf-c-login .pf-c-login__header ak-brand,
.pf-c-login .pf-c-login__header img,
.pf-v5-c-login .pf-v5-c-login__header ak-brand,
.pf-v5-c-login .pf-v5-c-login__header img {
display: block !important;
max-height: 85px !important;
max-width: 240px !important;
width: auto !important;
margin: 0 auto !important;
mix-blend-mode: multiply !important;
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.08)) !important;
transition: all 0.2s ease-in-out;
}
/* The White Card */
ak-flow-executor .pf-c-login__main,
.pf-c-login .pf-c-login__main,
.pf-v5-c-login .pf-v5-c-login__main,
ak-flow-executor ak-stage,
ak-flow-executor .ak-stage {
background: #ffffff !important;
border: 1px solid rgba(0, 0, 0, 0.08) !important;
border-radius: 20px !important;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04) !important;
padding: 36px 32px 32px 32px !important;
width: 100% !important;
box-sizing: border-box !important;
color: #111111 !important;
}
/* Hide Redundant / Generic Flow Title */
ak-flow-executor .pf-c-login__main-header-title,
ak-flow-executor .pf-v5-c-login__main-header-title,
ak-flow-executor .pf-c-title,
ak-flow-executor .pf-v5-c-title,
.pf-c-login .pf-c-login__main-header-title,
.pf-v5-c-login .pf-v5-c-login__main-header-title {
display: none !important;
}
ak-flow-executor .pf-c-login__main-header,
.pf-c-login .pf-c-login__main-header,
.pf-v5-c-login .pf-v5-c-login__main-header {
margin-bottom: 24px !important;
padding: 0 !important;
text-align: center !important;
}
ak-flow-executor .pf-c-login__main-header-desc,
.pf-c-login .pf-c-login__main-header-desc,
.pf-v5-c-login .pf-v5-c-login__main-header-desc {
font-family: var(--hd-font) !important;
font-size: 0.95rem !important;
line-height: 1.5 !important;
margin: 0 !important;
color: #555555 !important;
text-align: center !important;
}
ak-flow-executor .pf-c-login__main-header-desc strong,
ak-flow-executor .pf-c-login__main-header-desc b,
.pf-c-login .pf-c-login__main-header-desc strong,
.pf-c-login .pf-c-login__main-header-desc b,
.pf-v5-c-login .pf-v5-c-login__main-header-desc strong,
.pf-v5-c-login .pf-v5-c-login__main-header-desc b {
color: #000000 !important;
font-weight: 700 !important;
}
/* Form Groups & Labels */
ak-flow-executor .pf-c-form__group,
.pf-c-login .pf-c-form__group,
.pf-v5-c-login .pf-v5-c-form__group {
margin-bottom: 20px !important;
}
ak-flow-executor .pf-c-form__label,
ak-flow-executor .pf-v5-c-form__label,
ak-flow-executor .pf-c-form__label-text,
ak-flow-executor .pf-v5-c-form__label-text,
ak-flow-executor .pf-c-form__group-label,
ak-flow-executor .pf-v5-c-form__group-label,
ak-flow-executor label,
ak-flow-executor label *,
.pf-c-login .pf-c-form__label,
.pf-c-login .pf-v5-c-form__label,
.pf-c-login .pf-c-form__label-text,
.pf-c-login .pf-v5-c-form__label-text,
.pf-c-login label,
.pf-c-login label *,
.pf-v5-c-login label,
.pf-v5-c-login label * {
font-family: var(--hd-font) !important;
font-weight: 600 !important;
font-size: 0.9rem !important;
color: #000000 !important;
margin-bottom: 6px !important;
display: inline-block !important;
opacity: 1 !important;
}
/* Required Asterisk */
ak-flow-executor .pf-c-form__label-required,
ak-flow-executor .pf-v5-c-form__label-required,
.pf-c-login .pf-c-form__label-required,
.pf-v5-c-login .pf-v5-c-form__label-required {
color: #c9190b !important;
margin-left: 4px !important;
font-weight: 700 !important;
}
/* Inputs */
ak-flow-executor .pf-c-form-control,
ak-flow-executor .pf-v5-c-form-control,
ak-flow-executor input[type="text"],
ak-flow-executor input[type="password"],
ak-flow-executor input[type="email"],
ak-flow-executor input,
.pf-c-login .pf-c-form-control,
.pf-c-login .pf-v5-c-form-control,
.pf-c-login input,
.pf-v5-c-login input {
font-family: var(--hd-font) !important;
font-size: 0.98rem !important;
border-radius: 12px !important;
padding: 12px 14px !important;
background-color: #f9f9fb !important;
border: 1px solid #d0d5dd !important;
color: #000000 !important;
transition: all 0.2s ease-in-out !important;
width: 100% !important;
box-sizing: border-box !important;
}
ak-flow-executor input::placeholder,
.pf-c-login input::placeholder,
.pf-v5-c-login input::placeholder {
color: #666666 !important;
font-style: normal !important;
opacity: 1 !important;
}
ak-flow-executor input:focus,
.pf-c-login input:focus,
.pf-v5-c-login input:focus {
outline: none !important;
border-color: #000000 !important;
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.12) !important;
background-color: #ffffff !important;
color: #000000 !important;
}
/* Autofill Fix */
ak-flow-executor input:-webkit-autofill,
ak-flow-executor input:-webkit-autofill:hover,
ak-flow-executor input:-webkit-autofill:focus,
.pf-c-login input:-webkit-autofill,
.pf-v5-c-login input:-webkit-autofill {
-webkit-text-fill-color: #000000 !important;
-webkit-box-shadow: 0 0 0 1000px #f9f9fb inset !important;
box-shadow: 0 0 0 1000px #f9f9fb inset !important;
transition: background-color 5000s ease-in-out 0s !important;
}
/* Flow Submit Button Spacing */
ak-flow-executor button[type="submit"],
.pf-c-login button[type="submit"],
.pf-v5-c-login button[type="submit"] {
width: 100% !important;
padding: 13px 20px !important;
margin-top: 16px !important;
margin-bottom: 4px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
/* Language Selector at Bottom Right */
ak-flow-executor ak-locale-select,
ak-flow-executor [part="locale-select"],
ak-flow-executor::part(locale-select),
.pf-c-login ak-locale-select,
.pf-v5-c-login ak-locale-select {
position: fixed !important;
inset-block-start: auto !important;
inset-inline-start: auto !important;
top: auto !important;
left: auto !important;
bottom: 14px !important;
right: 18px !important;
z-index: 999 !important;
opacity: 0.65 !important;
transition: opacity 0.2s ease, transform 0.2s ease !important;
}
ak-flow-executor ak-locale-select:hover,
.pf-c-login ak-locale-select:hover {
opacity: 1 !important;
transform: translateY(-1px) !important;
}
ak-flow-executor ak-locale-select select,
.pf-c-login ak-locale-select select {
color: #000000 !important;
background: transparent !important;
cursor: pointer !important;
}
ak-flow-executor ak-locale-select label,
.pf-c-login ak-locale-select label {
color: #000000 !important;
cursor: pointer !important;
}
/* Footer & Watermark */
ak-flow-executor ak-brand-links,
ak-flow-executor .pf-c-login__footer,
ak-flow-executor .pf-v5-c-login__footer,
.pf-c-login .pf-c-login__footer,
.pf-v5-c-login .pf-v5-c-login__footer {
margin-top: 24px !important;
margin-bottom: 0 !important;
text-align: center !important;
font-size: 0.82rem !important;
color: #555555 !important;
}
ak-flow-executor ak-brand-links a,
ak-flow-executor .pf-c-login__footer a,
.pf-c-login .pf-c-login__footer a,
.pf-v5-c-login .pf-v5-c-login__footer a {
color: #000000 !important;
text-decoration: none !important;
font-weight: 600 !important;
}
ak-flow-executor ak-brand-links a:hover,
ak-flow-executor .pf-c-login__footer a:hover,
.pf-c-login .pf-c-login__footer a:hover,
.pf-v5-c-login .pf-v5-c-login__footer a:hover {
text-decoration: underline !important;
}
ak-flow-executor .pf-c-login__container::after,
.pf-c-login .pf-c-login__container::after,
.pf-v5-c-login .pf-v5-c-login__container::after {
content: "HAUMDAUCHER • Wurst und Spezialitäten n.e.V.";
display: block;
margin-top: 28px;
margin-bottom: 12px;
font-size: 0.76rem;
letter-spacing: 1.5px;
text-transform: uppercase;
font-weight: 600;
color: #888888;
text-align: center;
}
/* Mobile Responsiveness */
@media (max-width: 480px) {
ak-flow-executor,
.pf-c-login,
.pf-v5-c-login {
padding: 24px 12px 48px 12px !important;
}
ak-flow-executor .pf-c-login__container,
.pf-c-login .pf-c-login__container,
.pf-v5-c-login .pf-v5-c-login__container {
padding: 0 !important;
}
ak-flow-executor .pf-c-login__header,
.pf-c-login .pf-c-login__header,
.pf-v5-c-login .pf-v5-c-login__header {
margin-bottom: 18px !important;
}
ak-flow-executor .pf-c-login__main,
.pf-c-login .pf-c-login__main,
.pf-v5-c-login .pf-v5-c-login__main,
ak-flow-executor ak-stage,
ak-flow-executor .ak-stage {
padding: 28px 20px 24px 20px !important;
border-radius: 18px !important;
}
ak-flow-executor .pf-c-login__header img,
.pf-c-login .pf-c-login__header img,
.pf-v5-c-login .pf-v5-c-login__header img {
max-height: 72px !important;
}
ak-flow-executor .pf-c-login__container::after,
.pf-c-login .pf-c-login__container::after,
.pf-v5-c-login .pf-v5-c-login__container::after {
margin-top: 20px;
margin-bottom: 8px;
}
ak-flow-executor ak-locale-select,
.pf-c-login ak-locale-select,
.pf-v5-c-login ak-locale-select {
bottom: 10px !important;
right: 12px !important;
}
}
/* ==========================================================================
4. AUTHENTICATED DASHBOARD (User & Admin Interface)
========================================================================== */
ak-interface-user .pf-c-brand img,
ak-interface-admin .pf-c-brand img,
.pf-c-page__header-brand img,
.pf-v5-c-page__header-brand img {
max-height: 36px !important;
width: auto !important;
}
[data-theme='dark'] ak-interface-user .pf-c-brand img,
[data-theme='dark'] ak-interface-admin .pf-c-brand img,
[data-theme='dark'] .pf-c-page__header-brand img,
[data-theme='dark'] .pf-v5-c-page__header-brand img {
filter: invert(1) hue-rotate(180deg);
}
+26
View File
@@ -0,0 +1,26 @@
# -*- encoding: utf-8 -*-
###
try:
input = raw_input
except NameError:
pass
import ovh
import json
# create a client using configuration from ovh.conf file
client = ovh.Client()
# Request RO, /me API access
ck = client.new_consumer_key_request()
ck.add_recursive_rules(ovh.API_READ_ONLY, "/")
# Request token
validation = ck.request()
print("Please visit %s to authenticate" % validation['validationUrl'])
input("and press Enter to continue...")
# Print nice welcome message
print("Welcome", client.get('/me')['firstname'])
print("Btw, your 'consumerKey' is '%s'" % validation['consumerKey'])
@@ -0,0 +1,46 @@
# -*- encoding: utf-8 -*-
'''
List all domain relevant information to add them to Terraform
'''
import json
import ovh
import sys
# Instanciate an OVH Client.
# You can generate new credentials with full access to your account on
# the token creation page
client = ovh.Client(
endpoint='ovh-eu', # Endpoint of API OVH Europe (List of available endpoints)
application_key='pSnvZrS8R4LPCLEh', # Application Key
application_secret='CKUHMo5ZXDm8JLA6ZWWUzAolglQmvylv', # Application Secret
consumer_key='EVBu0meA2OYvEvVWUbR0qwU2lD7uXKiM', # Consumer Key
)
all_domains = client.get('/domain')
# Pretty print
print("All domains: "+json.dumps(all_domains, indent=4))
for domain in all_domains :
print("Looking at domain: " +domain)
try:
if(domain in ["moritzgraf.de"]):
all_resources = client.get('/domain/zone/'+str(domain)+"/record")
print("All resources:"+json.dumps(all_resources,indent=4))
for resource in all_resources:
try:
record_details = client.get('/domain/zone/'+str(domain)+"/record/"+str(resource))
if(record_details["subDomain"] and record_details["subDomain"] in ["git","auth","prometheus","alertmanager",
"chat","mqtt", "cloud","monitoring","smtp","pop3","imap","jaeger","ldap","code","hub","grafana",
"tt-rss","phpmyadmin","phppgadmin","www","terraformtest","rstudio","code" ]):
#print("Domain"+str(record_details["zone"])+"Subdomain:"+str(record_details["subDomain"]))
print("terraform import 'ovh_domain_zone_record.moritzgraf_subdomains[\""+record_details["subDomain"]+"\"]' "+ str(record_details["id"])+"."+str(record_details["zone"]))
except Exception as e:
print("Somthing did not work for domain:"+str(record_details))
print(e)
# print("###details###"+str(record_details))
else:
print("Domain "+str(domain)+" not on whitelist, ignoring")
except Exception as e:
print("Somthing did not work for domain:"+domain)
print(e)
+13
View File
@@ -0,0 +1,13 @@
; THIS FILE IS FOR MANUALLY CHECKING THE API
; TO BE USED WITH ovh_dev.py
[default]
; general configuration: default endpoint
endpoint=ovh-eu
[ovh-eu]
; configuration specific to 'ovh-eu' endpoint
application_key=pSnvZrS8R4LPCLEh
application_secret=CKUHMo5ZXDm8JLA6ZWWUzAolglQmvylv
; uncomment following line when writing a script application
; with a single consumer key.
;consumer_key=eNrAf7toCYw6u8vIyRcHNFp4KqyYW0Tp
+197
View File
@@ -0,0 +1,197 @@
provider "ovh" {
endpoint = "ovh-eu"
application_key = "grBXP8S9ugOfjfpj"
application_secret = "6mMRIUxMWaXff631ulc2jj5HybGg4ESx"
consumer_key = "m3D43OxSD03iyfKenn1fQ1AVl3eXodKn"
}
locals {
ttl = "60"
}
## old moritzgrafde
## => 91.121.84.190
## super old docker compose host
## => 91.121.64.43
variable "subdomains_moritzgraf" {
default = ["auth", "prometheus", "alertmanager", "chat", "mqtt", "cloud",
"monitoring", "smtp", "pop3", "imap", "jaeger", "ldap", "code", "hub",
"grafana", "phpmyadmin", "webapp", "kc", "phppgadmin", "www", "rstudio", "code"]
}
resource "ovh_domain_zone_record" "moritzgraf_subdomains" {
for_each = toset(var.subdomains_moritzgraf)
zone = "moritzgraf.de"
subdomain = each.key
fieldtype = "CNAME"
ttl = local.ttl
target = "moritzgraf.de."
}
## new haumdaucher k8s
variable "subdomains_moritzgraf_k8s" {
default = ["tt-rss", "git"]
}
resource "ovh_domain_zone_record" "moritzgraf_subdomains_k8s" {
for_each = toset(var.subdomains_moritzgraf_k8s)
zone = "moritzgraf.de"
subdomain = each.key
fieldtype = "CNAME"
ttl = local.ttl
target = "haumdaucher.de."
}
# Kochen
resource "ovh_domain_zone_record" "kochen_haumdaucher" {
zone = "haumdaucher.de"
subdomain = "kochen"
fieldtype = "CNAME"
ttl = local.ttl
target = "haumdaucher-datalab.github.io."
}
## nodes
resource "ovh_domain_zone_record" "one" {
zone = "haumdaucher.de"
subdomain = "one"
fieldtype = "A"
ttl = local.ttl
#target = "91.121.84.190"
target = "136.243.23.215"
}
## krassescheisse
resource "ovh_domain_zone_record" "krassescheisse_subdomains" {
for_each = toset(var.subdomains_moritzgraf)
zone = "krassescheisse.de"
subdomain = each.key
fieldtype = "CNAME"
ttl = local.ttl
target = "krassescheisse.de."
}
variable "subdomains_haumdaucher" {
default = []
}
# resource "ovh_domain_zone_record" "haumdaucher_subdomains" {
# for_each = toset( var.subdomains_haumdaucher )
# zone = "haumdaucher.de"
# subdomain = each.key
# fieldtype = "CNAME"
# ttl = local.ttl
# target = "haumdaucher.de."
# }
# ## graf.me -> problems with domain
variable "domains" {
default = ["moritzgraf.de"
, "graf-fam.de"
, "krassescheisse.de"]
}
resource "ovh_domain_zone_record" "haumdaucher_domain" {
zone = "haumdaucher.de"
subdomain = ""
fieldtype = "A"
ttl = local.ttl
#target = "91.121.84.190"
target = "136.243.23.215"
}
resource "ovh_domain_zone_record" "haumdaucher_wildcard_domain" {
zone = "haumdaucher.de"
subdomain = "*"
fieldtype = "A"
ttl = local.ttl
#target = "91.121.84.190"
target = "136.243.23.215"
}
resource "ovh_domain_zone_record" "moritzgraf_domain" {
zone = "moritzgraf.de"
subdomain = ""
fieldtype = "A"
ttl = local.ttl
target = "136.243.23.215"
}
resource "ovh_domain_zone_record" "moritzgraf_wildcard_domain" {
zone = "moritzgraf.de"
subdomain = "*"
fieldtype = "A"
ttl = local.ttl
#target = "91.121.84.190"
target = "136.243.23.215"
}
######################################
## ameliegraf.de
locals {
# adobe
first = "151.101.128.119"
second = "151.101.192.119"
}
resource "ovh_domain_zone_record" "ameliegraf_de_first" {
zone = "ameliegraf.de"
subdomain = ""
fieldtype = "A"
ttl = local.ttl
#target = "91.121.84.190"
target = local.first
}
resource "ovh_domain_zone_record" "ameliegraf_de_second" {
zone = "ameliegraf.de"
subdomain = ""
fieldtype = "A"
ttl = local.ttl
#target = "91.121.84.190"
target = local.second
}
resource "ovh_domain_zone_record" "wildcard_ameliegraf_de_first" {
zone = "ameliegraf.de"
subdomain = "*"
fieldtype = "A"
ttl = local.ttl
#target = "91.121.84.190"
target = local.first
}
resource "ovh_domain_zone_record" "wildcard_ameliegraf_de_second" {
zone = "ameliegraf.de"
subdomain = "*"
fieldtype = "A"
ttl = local.ttl
#target = "91.121.84.190"
target = local.second
}
######################################
## grabengasse7.de
resource "ovh_domain_zone_record" "grabengasse7_de_domain" {
zone = "grabengasse7.de"
subdomain = ""
fieldtype = "A"
ttl = local.ttl
target = "136.243.23.215"
}
resource "ovh_domain_zone_record" "grabengasse7_de_wildcard" {
zone = "grabengasse7.de"
subdomain = "*"
fieldtype = "A"
ttl = local.ttl
target = "136.243.23.215"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+20
View File
@@ -0,0 +1,20 @@
terraform {
required_providers {
ovh = {
source = "ovh/ovh"
}
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
local = {
source = "hashicorp/local"
version = "~> 2.0"
}
authentik = {
source = "goauthentik/authentik"
version = ">= 2024.12"
}
}
required_version = ">= 0.13"
}