Migrate k8s config from Terraform to Argo CD + ESO
Phase 1: Install External Secrets Operator via Argo CD app - apps/external-secrets.yaml — ESO Helm chart install - apps/external-secrets-config.yaml — ClusterSecretStore deployment - k8s/external-secrets/cluster-secret-store.yaml — Vault backend using vault-token Secret Phase 2: Create k8s manifests for all services - k8s/neuron/ — PVC, ConfigMap, ExternalSecrets (neuron-secrets, cloudflared-secret), Ingress - k8s/gitea/ — PVC, ConfigMap (custom CSS), ExternalSecret (gitea-db), Ingress - k8s/github-runner/ — ExternalSecret (github-runner-secret) - k8s/gitea-runner/ — ExternalSecret (gitea-runner-secret) - k8s/monitoring/ — ExternalSecrets (grafana, slack), Alloy OTLP service+middleware, datasources ConfigMap, Ingress - k8s/postgres/ — ExternalSecret (postgres-passwords) - k8s/vault/ — ExternalSecret (vault-gcp-sa from Vault) - k8s/adguard/ — PVCs, ConfigMap, Certificate, Ingress, ddclient Deployment+ExternalSecret - k8s/ollama/ — PVC, Ingress - k8s/headscale/ — PVC - k8s/packages/ — PVCs, ConfigMap, Ingresses - k8s/registry/ — PVC, Ingresses - k8s/backup/ — CronJob, ExternalSecret (backup-credentials) New Argo CD apps for Helm releases: - apps/kube-prometheus-stack.yaml, loki.yaml, tempo.yaml, alloy.yaml - apps/postgres.yaml, redis.yaml, vault.yaml New Argo CD apps for k8s config paths: - apps/neuron-config, gitea-config, ci-config, gitea-runner-config - apps/monitoring-config, adguard-config, ollama-config, headscale-config - apps/packages-config, registry-config, postgres-config, vault-config, backup Phase 3: Strip Terraform to infrastructure-only - All kubernetes_* and helm_release resources removed from service .tf files - Each service .tf now contains only kubernetes_namespace (bootstrap dependency) - variables.tf stripped to only cloudflare_api_key, cloudflare_email, gitea_api_token - namespaces.tf gains external-secrets namespace - ingress.tf, backup.tf, ddclient.tf emptied (resources in k8s/) - cert-manager.tf, argocd.tf, traefik.tf unchanged (bootstrap)
This commit is contained in:
+3
-167
@@ -1,4 +1,6 @@
|
|||||||
# AdGuard Home — household DNS with ad blocking, local resolution, DoH via dns.nook.family, and DoT via dot.nook.family:853
|
# AdGuard Home — namespace only
|
||||||
|
# PVCs, ConfigMap, Certificate, Ingress, ddclient managed by Argo CD + ESO
|
||||||
|
# See: apps/adguard.yaml, apps/adguard-config.yaml, k8s/adguard/
|
||||||
|
|
||||||
resource "kubernetes_namespace" "dns" {
|
resource "kubernetes_namespace" "dns" {
|
||||||
metadata {
|
metadata {
|
||||||
@@ -9,169 +11,3 @@ resource "kubernetes_namespace" "dns" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "kubernetes_persistent_volume_claim" "adguard_config" {
|
|
||||||
wait_until_bound = false
|
|
||||||
metadata {
|
|
||||||
name = "adguard-config"
|
|
||||||
namespace = kubernetes_namespace.dns.metadata[0].name
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
access_modes = ["ReadWriteOnce"]
|
|
||||||
resources {
|
|
||||||
requests = {
|
|
||||||
storage = "1Gi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_persistent_volume_claim" "adguard_data" {
|
|
||||||
wait_until_bound = false
|
|
||||||
metadata {
|
|
||||||
name = "adguard-data"
|
|
||||||
namespace = kubernetes_namespace.dns.metadata[0].name
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
access_modes = ["ReadWriteOnce"]
|
|
||||||
resources {
|
|
||||||
requests = {
|
|
||||||
storage = "5Gi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Desired AdGuard settings — applied by init container on every pod start.
|
|
||||||
# To change DNS upstreams, filter lists, or rewrites: edit here, terraform apply, then push apps/adguard.yaml.
|
|
||||||
resource "kubernetes_config_map" "adguard_defaults" {
|
|
||||||
metadata {
|
|
||||||
name = "adguard-defaults"
|
|
||||||
namespace = kubernetes_namespace.dns.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"AdGuardHome.yaml" = <<-EOT
|
|
||||||
dns:
|
|
||||||
bind_hosts:
|
|
||||||
- 0.0.0.0
|
|
||||||
port: 53
|
|
||||||
upstream_dns:
|
|
||||||
- https://dns.cloudflare.com/dns-query
|
|
||||||
- https://dns.google/dns-query
|
|
||||||
bootstrap_dns:
|
|
||||||
- 1.1.1.1
|
|
||||||
- 8.8.8.8
|
|
||||||
- 9.9.9.10
|
|
||||||
- 149.112.112.10
|
|
||||||
upstream_mode: load_balance
|
|
||||||
cache_enabled: true
|
|
||||||
cache_size: 4194304
|
|
||||||
filters:
|
|
||||||
- enabled: true
|
|
||||||
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
|
|
||||||
name: AdGuard DNS filter
|
|
||||||
id: 1
|
|
||||||
- enabled: true
|
|
||||||
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt
|
|
||||||
name: AdAway Default Blocklist
|
|
||||||
id: 2
|
|
||||||
- enabled: true
|
|
||||||
url: https://big.oisd.nl/domainswild
|
|
||||||
name: OISD Big
|
|
||||||
id: 3
|
|
||||||
- enabled: true
|
|
||||||
url: https://easylist.to/easylist/easylist.txt
|
|
||||||
name: EasyList
|
|
||||||
id: 4
|
|
||||||
- enabled: true
|
|
||||||
url: https://easylist.to/easylist/easyprivacy.txt
|
|
||||||
name: EasyPrivacy
|
|
||||||
id: 5
|
|
||||||
tls:
|
|
||||||
enabled: true
|
|
||||||
server_name: dot.nook.family
|
|
||||||
port_dns_over_tls: 853
|
|
||||||
certificate_path: /etc/adguard/tls/tls.crt
|
|
||||||
private_key_path: /etc/adguard/tls/tls.key
|
|
||||||
allow_unencrypted_doh: true
|
|
||||||
filtering:
|
|
||||||
filtering_enabled: true
|
|
||||||
rewrites:
|
|
||||||
- domain: '*.nook.family'
|
|
||||||
answer: ${var.legion_ip}
|
|
||||||
enabled: true
|
|
||||||
- domain: '*.neuralplatform.ai'
|
|
||||||
answer: ${var.legion_ip}
|
|
||||||
enabled: true
|
|
||||||
EOT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# TLS certificate for DoT — issued via cert-manager DNS-01 (Cloudflare)
|
|
||||||
resource "kubernetes_manifest" "adguard_dot_cert" {
|
|
||||||
manifest = {
|
|
||||||
apiVersion = "cert-manager.io/v1"
|
|
||||||
kind = "Certificate"
|
|
||||||
metadata = {
|
|
||||||
name = "adguard-dot-tls"
|
|
||||||
namespace = kubernetes_namespace.dns.metadata[0].name
|
|
||||||
}
|
|
||||||
spec = {
|
|
||||||
secretName = "adguard-dot-tls"
|
|
||||||
issuerRef = {
|
|
||||||
name = "letsencrypt-prod"
|
|
||||||
kind = "ClusterIssuer"
|
|
||||||
}
|
|
||||||
dnsNames = ["dot.nook.family"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [kubernetes_manifest.cluster_issuer_prod]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deployment + Service managed by Argo CD — see servers/legion/apps/adguard.yaml
|
|
||||||
|
|
||||||
resource "kubernetes_ingress_v1" "adguard" {
|
|
||||||
metadata {
|
|
||||||
name = "adguard"
|
|
||||||
namespace = kubernetes_namespace.dns.metadata[0].name
|
|
||||||
annotations = {
|
|
||||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
|
||||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
tls {
|
|
||||||
hosts = ["dns.nook.family"]
|
|
||||||
secret_name = "adguard-tls"
|
|
||||||
}
|
|
||||||
|
|
||||||
rule {
|
|
||||||
host = "dns.nook.family"
|
|
||||||
http {
|
|
||||||
path {
|
|
||||||
path = "/"
|
|
||||||
path_type = "Prefix"
|
|
||||||
backend {
|
|
||||||
service {
|
|
||||||
name = "adguard-ui"
|
|
||||||
port {
|
|
||||||
number = 3000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [helm_release.cert_manager]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Host-level bootstrap notes (run bootstrap.sh once on new Legion installs):
|
|
||||||
# - systemd-resolved disabled (conflicts with AdGuard on port 53)
|
|
||||||
# - k3s config.yaml includes tls-san for LAN IP
|
|
||||||
# - /etc/resolv.conf set to 1.1.1.1 fallback
|
|
||||||
# See: servers/legion/bootstrap.sh
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: adguard-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/adguard
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: dns
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: alloy
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://grafana.github.io/helm-charts
|
||||||
|
chart: alloy
|
||||||
|
targetRevision: "*"
|
||||||
|
helm:
|
||||||
|
values: |
|
||||||
|
controller:
|
||||||
|
type: daemonset
|
||||||
|
alloy:
|
||||||
|
configMap:
|
||||||
|
content: |
|
||||||
|
discovery.kubernetes "pods" {
|
||||||
|
role = "pod"
|
||||||
|
}
|
||||||
|
|
||||||
|
discovery.relabel "pods" {
|
||||||
|
targets = discovery.kubernetes.pods.targets
|
||||||
|
rule {
|
||||||
|
source_labels = ["__meta_kubernetes_namespace"]
|
||||||
|
target_label = "namespace"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__meta_kubernetes_pod_name"]
|
||||||
|
target_label = "pod"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__meta_kubernetes_pod_container_name"]
|
||||||
|
target_label = "container"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__meta_kubernetes_pod_label_app"]
|
||||||
|
target_label = "app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loki.source.kubernetes "pods" {
|
||||||
|
targets = discovery.relabel.pods.output
|
||||||
|
forward_to = [loki.write.default.receiver]
|
||||||
|
}
|
||||||
|
|
||||||
|
loki.write "default" {
|
||||||
|
endpoint {
|
||||||
|
url = "http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push"
|
||||||
|
tenant_id = "legion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
otelcol.receiver.otlp "default" {
|
||||||
|
grpc { endpoint = "0.0.0.0:4317" }
|
||||||
|
http { endpoint = "0.0.0.0:4318" }
|
||||||
|
output {
|
||||||
|
traces = [otelcol.exporter.otlp.tempo.input]
|
||||||
|
logs = [otelcol.exporter.loki.pantheon.input]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
otelcol.exporter.otlp "tempo" {
|
||||||
|
client {
|
||||||
|
endpoint = "tempo.monitoring.svc.cluster.local:4317"
|
||||||
|
tls { insecure = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
otelcol.exporter.loki "pantheon" {
|
||||||
|
forward_to = [loki.write.default.receiver]
|
||||||
|
}
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: monitoring
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: backup
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/backup
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: git
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: ci-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/github-runner
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: ci
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
---
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/gitea-runner
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: ci
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: external-secrets-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/external-secrets
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: external-secrets
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: external-secrets
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://charts.external-secrets.io
|
||||||
|
chart: external-secrets
|
||||||
|
targetRevision: 0.10.7
|
||||||
|
helm:
|
||||||
|
values: |
|
||||||
|
installCRDs: true
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: external-secrets
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: gitea-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/gitea
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: git
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: headscale-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/headscale
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: headscale
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: kube-prometheus-stack
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://prometheus-community.github.io/helm-charts
|
||||||
|
chart: kube-prometheus-stack
|
||||||
|
targetRevision: "*"
|
||||||
|
helm:
|
||||||
|
values: |
|
||||||
|
grafana:
|
||||||
|
adminPassword:
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: grafana-admin-secret
|
||||||
|
key: admin-password
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
size: 5Gi
|
||||||
|
sidecar:
|
||||||
|
datasources:
|
||||||
|
defaultDatasourceEnabled: false
|
||||||
|
initChownData:
|
||||||
|
enabled: false
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
ingressClassName: traefik
|
||||||
|
hosts:
|
||||||
|
- grafana.neuralplatform.ai
|
||||||
|
tls:
|
||||||
|
- secretName: grafana-tls
|
||||||
|
hosts:
|
||||||
|
- grafana.neuralplatform.ai
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
prometheus:
|
||||||
|
prometheusSpec:
|
||||||
|
storageSpec:
|
||||||
|
volumeClaimTemplate:
|
||||||
|
spec:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 20Gi
|
||||||
|
kubeEtcd:
|
||||||
|
enabled: false
|
||||||
|
kubeControllerManager:
|
||||||
|
enabled: false
|
||||||
|
kubeScheduler:
|
||||||
|
enabled: false
|
||||||
|
kubeProxy:
|
||||||
|
enabled: false
|
||||||
|
alertmanager:
|
||||||
|
config:
|
||||||
|
global:
|
||||||
|
resolve_timeout: 5m
|
||||||
|
inhibit_rules:
|
||||||
|
- source_matchers: [severity="critical"]
|
||||||
|
target_matchers: [severity=~"warning|info"]
|
||||||
|
equal: [namespace, alertname]
|
||||||
|
- source_matchers: [severity="warning"]
|
||||||
|
target_matchers: [severity="info"]
|
||||||
|
equal: [namespace, alertname]
|
||||||
|
receivers:
|
||||||
|
- name: "null"
|
||||||
|
- name: slack-alerts
|
||||||
|
slack_configs:
|
||||||
|
- channel: "#infrastructure-alerts"
|
||||||
|
api_url: "https://slack.com/api/chat.postMessage"
|
||||||
|
http_config:
|
||||||
|
authorization:
|
||||||
|
type: Bearer
|
||||||
|
credentials_file: /etc/alertmanager/secrets/slack-bot-token/bot_token
|
||||||
|
send_resolved: true
|
||||||
|
title: '[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} ({{ .CommonLabels.namespace }})'
|
||||||
|
text: |
|
||||||
|
{{ range .Alerts }}
|
||||||
|
*{{ if .Annotations.summary }}{{ .Annotations.summary }}{{ else }}{{ .Labels.alertname }}{{ end }}*{{ if .Labels.severity }} · {{ .Labels.severity }}{{ end }}
|
||||||
|
{{ if .Annotations.description }}{{ .Annotations.description }}{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
footer: "Legion · neuralplatform.ai"
|
||||||
|
route:
|
||||||
|
group_by: [namespace, alertname]
|
||||||
|
group_wait: 30s
|
||||||
|
group_interval: 5m
|
||||||
|
repeat_interval: 4h
|
||||||
|
receiver: slack-alerts
|
||||||
|
routes:
|
||||||
|
- matchers: [alertname="Watchdog"]
|
||||||
|
receiver: "null"
|
||||||
|
- matchers: [severity="info"]
|
||||||
|
receiver: "null"
|
||||||
|
alertmanagerSpec:
|
||||||
|
secrets:
|
||||||
|
- slack-bot-token
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: monitoring
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
- ServerSideApply=true
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: loki
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://grafana.github.io/helm-charts
|
||||||
|
chart: loki
|
||||||
|
targetRevision: "*"
|
||||||
|
helm:
|
||||||
|
values: |
|
||||||
|
loki:
|
||||||
|
commonConfig:
|
||||||
|
replication_factor: 1
|
||||||
|
storage:
|
||||||
|
type: filesystem
|
||||||
|
useTestSchema: true
|
||||||
|
singleBinary:
|
||||||
|
replicas: 1
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
deploymentMode: SingleBinary
|
||||||
|
backend:
|
||||||
|
replicas: 0
|
||||||
|
read:
|
||||||
|
replicas: 0
|
||||||
|
write:
|
||||||
|
replicas: 0
|
||||||
|
chunksCache:
|
||||||
|
enabled: false
|
||||||
|
resultsCache:
|
||||||
|
enabled: false
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: monitoring
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: monitoring-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/monitoring
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: monitoring
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: neuron-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/neuron
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: neuron
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: ollama-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/ollama
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: ollama
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: packages-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/packages
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: packages
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: postgres-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/postgres
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: platform
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
sources:
|
||||||
|
# Helm chart from Docker Hub OCI registry
|
||||||
|
- repoURL: registry-1.docker.io/bitnamicharts
|
||||||
|
chart: postgresql
|
||||||
|
targetRevision: "*"
|
||||||
|
helm:
|
||||||
|
values: |
|
||||||
|
auth:
|
||||||
|
existingSecret: postgres-passwords
|
||||||
|
secretKeys:
|
||||||
|
adminPasswordKey: postgres-password
|
||||||
|
primary:
|
||||||
|
persistence:
|
||||||
|
size: 20Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 256Mi
|
||||||
|
cpu: 100m
|
||||||
|
initdb:
|
||||||
|
scripts:
|
||||||
|
create-kong-db.sql: |
|
||||||
|
CREATE DATABASE kong;
|
||||||
|
CREATE USER kong WITH PASSWORD '$(KONG_DB_PASSWORD)';
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE kong TO kong;
|
||||||
|
ALTER DATABASE kong OWNER TO kong;
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: platform
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: redis
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: registry-1.docker.io/bitnamicharts
|
||||||
|
chart: redis
|
||||||
|
targetRevision: "*"
|
||||||
|
helm:
|
||||||
|
values: |
|
||||||
|
auth:
|
||||||
|
enabled: false
|
||||||
|
architecture: standalone
|
||||||
|
master:
|
||||||
|
persistence:
|
||||||
|
size: 5Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 128Mi
|
||||||
|
cpu: 50m
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: platform
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: registry-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/registry
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: registry
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: tempo
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://grafana.github.io/helm-charts
|
||||||
|
chart: tempo
|
||||||
|
targetRevision: "*"
|
||||||
|
helm:
|
||||||
|
values: |
|
||||||
|
tempo:
|
||||||
|
storage:
|
||||||
|
trace:
|
||||||
|
backend: local
|
||||||
|
local:
|
||||||
|
path: /var/tempo
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: monitoring
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: vault-config
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://10.43.1.53:3000/will/infrastructure.git
|
||||||
|
targetRevision: main
|
||||||
|
path: servers/legion/k8s/vault
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: vault
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: vault
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://helm.releases.hashicorp.com
|
||||||
|
chart: vault
|
||||||
|
targetRevision: "0.29.1"
|
||||||
|
helm:
|
||||||
|
values: |
|
||||||
|
server:
|
||||||
|
image:
|
||||||
|
repository: hashicorp/vault
|
||||||
|
tag: "1.18.3"
|
||||||
|
|
||||||
|
extraEnvironmentVars:
|
||||||
|
GOOGLE_APPLICATION_CREDENTIALS: /vault/gcp/sa.json
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: gcp-sa
|
||||||
|
secret:
|
||||||
|
secretName: vault-gcp-sa
|
||||||
|
|
||||||
|
volumeMounts:
|
||||||
|
- name: gcp-sa
|
||||||
|
mountPath: /vault/gcp
|
||||||
|
readOnly: true
|
||||||
|
|
||||||
|
ha:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
dataStorage:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
|
||||||
|
standalone:
|
||||||
|
enabled: true
|
||||||
|
config: |
|
||||||
|
ui = true
|
||||||
|
|
||||||
|
listener "tcp" {
|
||||||
|
tls_disable = 1
|
||||||
|
address = "[::]:8200"
|
||||||
|
cluster_address = "[::]:8201"
|
||||||
|
}
|
||||||
|
|
||||||
|
storage "file" {
|
||||||
|
path = "/vault/data"
|
||||||
|
}
|
||||||
|
|
||||||
|
seal "gcpckms" {
|
||||||
|
project = "neuron-785695"
|
||||||
|
region = "global"
|
||||||
|
key_ring = "vault"
|
||||||
|
crypto_key = "vault-unseal"
|
||||||
|
}
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
ingressClassName: traefik
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
hosts:
|
||||||
|
- host: vault.neuralplatform.ai
|
||||||
|
paths:
|
||||||
|
- /
|
||||||
|
tls:
|
||||||
|
- secretName: vault-tls
|
||||||
|
hosts:
|
||||||
|
- vault.neuralplatform.ai
|
||||||
|
|
||||||
|
ui:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
injector:
|
||||||
|
enabled: false
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: vault
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
+2
-137
@@ -1,137 +1,2 @@
|
|||||||
# Legion backup — restic to Cloudflare R2
|
# Legion backup — CronJob and secret managed by Argo CD + ESO
|
||||||
# Dumps all Postgres databases + Gitea data nightly at 2am
|
# See: apps/backup.yaml, k8s/backup/
|
||||||
# Retains 30 daily / 12 weekly / 6 monthly snapshots
|
|
||||||
|
|
||||||
resource "kubernetes_secret" "backup_credentials" {
|
|
||||||
metadata {
|
|
||||||
name = "backup-credentials"
|
|
||||||
namespace = "git"
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
RESTIC_PASSWORD = "gitea-restic-2026"
|
|
||||||
AWS_ACCESS_KEY_ID = var.r2_access_key_id
|
|
||||||
AWS_SECRET_ACCESS_KEY = var.r2_secret_access_key
|
|
||||||
RESTIC_REPOSITORY = "s3:${var.r2_endpoint}/legion-gitea-backup"
|
|
||||||
PGPASSWORD = var.postgres_password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_cron_job_v1" "gitea_backup" {
|
|
||||||
metadata {
|
|
||||||
name = "gitea-backup"
|
|
||||||
namespace = "git"
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
schedule = "0 2 * * *"
|
|
||||||
concurrency_policy = "Forbid"
|
|
||||||
failed_jobs_history_limit = 3
|
|
||||||
successful_jobs_history_limit = 3
|
|
||||||
|
|
||||||
job_template {
|
|
||||||
metadata {}
|
|
||||||
spec {
|
|
||||||
backoff_limit = 2
|
|
||||||
template {
|
|
||||||
metadata {}
|
|
||||||
spec {
|
|
||||||
restart_policy = "OnFailure"
|
|
||||||
|
|
||||||
# Dump all Postgres databases before restic runs
|
|
||||||
init_container {
|
|
||||||
name = "pg-dump"
|
|
||||||
image = "postgres:18-alpine"
|
|
||||||
|
|
||||||
command = ["/bin/sh", "-c",
|
|
||||||
"pg_dumpall -h postgres-postgresql.platform.svc.cluster.local -U postgres --no-role-passwords > /dump/all-databases.sql && echo 'DB dump complete, size:' $(du -sh /dump/all-databases.sql)"
|
|
||||||
]
|
|
||||||
|
|
||||||
env_from {
|
|
||||||
secret_ref {
|
|
||||||
name = kubernetes_secret.backup_credentials.metadata[0].name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
volume_mount {
|
|
||||||
name = "dump"
|
|
||||||
mount_path = "/dump"
|
|
||||||
}
|
|
||||||
|
|
||||||
resources {
|
|
||||||
requests = { memory = "128Mi", cpu = "100m" }
|
|
||||||
limits = { memory = "256Mi" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
container {
|
|
||||||
name = "backup"
|
|
||||||
image = "restic/restic:latest"
|
|
||||||
|
|
||||||
command = ["/bin/sh", "-c", <<-EOT
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Init repo if first run
|
|
||||||
restic snapshots 2>/dev/null || restic init
|
|
||||||
|
|
||||||
# Back up Gitea data + full DB dump
|
|
||||||
echo "Running restic backup..."
|
|
||||||
restic backup \
|
|
||||||
/gitea-data \
|
|
||||||
/dump/all-databases.sql \
|
|
||||||
--tag legion \
|
|
||||||
--host legion
|
|
||||||
|
|
||||||
# Prune old snapshots
|
|
||||||
restic forget \
|
|
||||||
--keep-daily 30 \
|
|
||||||
--keep-weekly 12 \
|
|
||||||
--keep-monthly 6 \
|
|
||||||
--prune
|
|
||||||
|
|
||||||
echo "Backup complete."
|
|
||||||
restic snapshots --latest 3
|
|
||||||
EOT
|
|
||||||
]
|
|
||||||
|
|
||||||
env_from {
|
|
||||||
secret_ref {
|
|
||||||
name = kubernetes_secret.backup_credentials.metadata[0].name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
volume_mount {
|
|
||||||
name = "gitea-data"
|
|
||||||
mount_path = "/gitea-data"
|
|
||||||
read_only = true
|
|
||||||
}
|
|
||||||
|
|
||||||
volume_mount {
|
|
||||||
name = "dump"
|
|
||||||
mount_path = "/dump"
|
|
||||||
}
|
|
||||||
|
|
||||||
resources {
|
|
||||||
requests = { memory = "256Mi", cpu = "100m" }
|
|
||||||
limits = { memory = "512Mi" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
volume {
|
|
||||||
name = "gitea-data"
|
|
||||||
persistent_volume_claim {
|
|
||||||
claim_name = "gitea-data"
|
|
||||||
read_only = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
volume {
|
|
||||||
name = "dump"
|
|
||||||
empty_dir {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,73 +1,3 @@
|
|||||||
# ddclient — dynamic DNS daemon that keeps dot.nook.family pointing at the home public IP
|
# ddclient — dynamic DNS daemon
|
||||||
# Updates Cloudflare A record every 5 minutes; required for DoT (port 853) which can't use Cloudflare proxy
|
# Deployment and config secret managed by Argo CD + ESO
|
||||||
|
# See: apps/adguard-config.yaml, k8s/adguard/ddclient-*.yaml
|
||||||
resource "kubernetes_secret" "ddclient_config" {
|
|
||||||
metadata {
|
|
||||||
name = "ddclient-config"
|
|
||||||
namespace = kubernetes_namespace.dns.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"ddclient.conf" = <<-EOT
|
|
||||||
daemon=300
|
|
||||||
syslog=yes
|
|
||||||
pid=/var/run/ddclient/ddclient.pid
|
|
||||||
use=web, web=https://api.ipify.org
|
|
||||||
|
|
||||||
protocol=cloudflare
|
|
||||||
zone=nook.family
|
|
||||||
ttl=120
|
|
||||||
login=${var.cloudflare_email}
|
|
||||||
password=${var.cloudflare_api_key}
|
|
||||||
dot.nook.family
|
|
||||||
EOT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_deployment" "ddclient" {
|
|
||||||
metadata {
|
|
||||||
name = "ddclient"
|
|
||||||
namespace = kubernetes_namespace.dns.metadata[0].name
|
|
||||||
labels = { app = "ddclient" }
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
replicas = 1
|
|
||||||
selector {
|
|
||||||
match_labels = { app = "ddclient" }
|
|
||||||
}
|
|
||||||
template {
|
|
||||||
metadata {
|
|
||||||
labels = { app = "ddclient" }
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
container {
|
|
||||||
name = "ddclient"
|
|
||||||
image = "ghcr.io/ddclient/ddclient:latest"
|
|
||||||
command = ["ddclient", "-file", "/etc/ddclient/ddclient.conf", "-daemon", "300", "-noquiet", "-foreground"]
|
|
||||||
|
|
||||||
volume_mount {
|
|
||||||
name = "config"
|
|
||||||
mount_path = "/etc/ddclient/ddclient.conf"
|
|
||||||
sub_path = "ddclient.conf"
|
|
||||||
read_only = true
|
|
||||||
}
|
|
||||||
|
|
||||||
resources {
|
|
||||||
requests = { memory = "32Mi", cpu = "10m" }
|
|
||||||
limits = { memory = "64Mi" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
volume {
|
|
||||||
name = "config"
|
|
||||||
secret {
|
|
||||||
secret_name = kubernetes_secret.ddclient_config.metadata[0].name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [kubernetes_namespace.dns]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +1,2 @@
|
|||||||
# Gitea act-runner — executes Gitea Actions CI jobs on Legion
|
# Gitea act-runner — secrets managed by Argo CD + ESO
|
||||||
# Registered at the instance level so it can serve all repos/orgs
|
# See: apps/gitea-runner.yaml, apps/ci-config.yaml, k8s/gitea-runner/
|
||||||
|
|
||||||
resource "kubernetes_secret" "gitea_runner_secret" {
|
|
||||||
metadata {
|
|
||||||
name = "gitea-runner-secret"
|
|
||||||
namespace = kubernetes_namespace.ci.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
GITEA_INSTANCE_URL = "https://git.neuralplatform.ai"
|
|
||||||
GITEA_RUNNER_REGISTRATION_TOKEN = var.gitea_runner_token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-176
@@ -1,117 +1,6 @@
|
|||||||
# Gitea — local git server for private/sensitive infrastructure repos
|
# Gitea — local git server for private/sensitive infrastructure repos
|
||||||
# Accessible at git.neuralplatform.ai (internal only, real TLS)
|
# Namespace only — all other resources managed by Argo CD + ESO
|
||||||
|
# See: apps/gitea.yaml, apps/gitea-config.yaml, k8s/gitea/
|
||||||
resource "kubernetes_config_map" "gitea_custom_css" {
|
|
||||||
metadata {
|
|
||||||
name = "gitea-custom-css"
|
|
||||||
namespace = kubernetes_namespace.git.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"header.tmpl" = <<-TMPL
|
|
||||||
<link rel="stylesheet" href="/assets/css/custom.css">
|
|
||||||
TMPL
|
|
||||||
|
|
||||||
|
|
||||||
"custom.css" = <<-CSS
|
|
||||||
/* ── Typography & base ── */
|
|
||||||
:root {
|
|
||||||
--color-primary: #6366f1;
|
|
||||||
--color-primary-dark: #4f46e5;
|
|
||||||
--color-secondary: #8b5cf6;
|
|
||||||
--color-bg: #0f0f17;
|
|
||||||
--color-surface: #16161f;
|
|
||||||
--color-surface-2: #1e1e2e;
|
|
||||||
--color-border: #2a2a3d;
|
|
||||||
--color-text: #e2e2f0;
|
|
||||||
--color-text-muted: #8888aa;
|
|
||||||
--color-green: #22d3a5;
|
|
||||||
--radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: var(--color-bg) !important;
|
|
||||||
color: var(--color-text) !important;
|
|
||||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Top navbar ── */
|
|
||||||
.navbar, #navbar {
|
|
||||||
background: var(--color-surface) !important;
|
|
||||||
border-bottom: 1px solid var(--color-border) !important;
|
|
||||||
box-shadow: 0 1px 12px rgba(0,0,0,0.4) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar .brand svg, .navbar .brand img { filter: brightness(1.2); }
|
|
||||||
|
|
||||||
/* ── Sidebar & panels ── */
|
|
||||||
.repository, .ui.container, .ui.segment,
|
|
||||||
.ui.card, .ui.cards > .card {
|
|
||||||
background: var(--color-surface) !important;
|
|
||||||
border: 1px solid var(--color-border) !important;
|
|
||||||
border-radius: var(--radius) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Buttons ── */
|
|
||||||
.ui.primary.button, .ui.green.button {
|
|
||||||
background: var(--color-primary) !important;
|
|
||||||
border: none !important;
|
|
||||||
border-radius: 8px !important;
|
|
||||||
}
|
|
||||||
.ui.primary.button:hover { background: var(--color-primary-dark) !important; }
|
|
||||||
|
|
||||||
/* ── Inputs ── */
|
|
||||||
input, textarea, select,
|
|
||||||
.ui.input > input, .ui.dropdown {
|
|
||||||
background: var(--color-surface-2) !important;
|
|
||||||
border: 1px solid var(--color-border) !important;
|
|
||||||
color: var(--color-text) !important;
|
|
||||||
border-radius: 8px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Sign-in page ── */
|
|
||||||
.user.signin .ui.segment,
|
|
||||||
.user.signup .ui.segment {
|
|
||||||
background: var(--color-surface) !important;
|
|
||||||
border: 1px solid var(--color-border) !important;
|
|
||||||
border-radius: 16px !important;
|
|
||||||
box-shadow: 0 8px 32px rgba(0,0,0,0.5) !important;
|
|
||||||
padding: 2.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Repo file tree ── */
|
|
||||||
.repository.file.list .file-list {
|
|
||||||
background: var(--color-surface) !important;
|
|
||||||
border-radius: var(--radius) !important;
|
|
||||||
border: 1px solid var(--color-border) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repository.file.list .file-list tr:hover td {
|
|
||||||
background: var(--color-surface-2) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Labels & badges ── */
|
|
||||||
.ui.label { border-radius: 6px !important; }
|
|
||||||
|
|
||||||
/* ── Dashboard activity feed ── */
|
|
||||||
.feeds .news { border-bottom: 1px solid var(--color-border) !important; }
|
|
||||||
|
|
||||||
/* ── Code blocks ── */
|
|
||||||
pre, code {
|
|
||||||
background: var(--color-surface-2) !important;
|
|
||||||
border: 1px solid var(--color-border) !important;
|
|
||||||
border-radius: 6px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Muted footer ── */
|
|
||||||
#footer {
|
|
||||||
background: var(--color-surface) !important;
|
|
||||||
border-top: 1px solid var(--color-border) !important;
|
|
||||||
color: var(--color-text-muted) !important;
|
|
||||||
}
|
|
||||||
CSS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_namespace" "git" {
|
resource "kubernetes_namespace" "git" {
|
||||||
metadata {
|
metadata {
|
||||||
@@ -122,66 +11,3 @@ resource "kubernetes_namespace" "git" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "kubernetes_persistent_volume_claim" "gitea_data" {
|
|
||||||
wait_until_bound = false
|
|
||||||
metadata {
|
|
||||||
name = "gitea-data"
|
|
||||||
namespace = kubernetes_namespace.git.metadata[0].name
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
access_modes = ["ReadWriteOnce"]
|
|
||||||
resources {
|
|
||||||
requests = {
|
|
||||||
storage = "20Gi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_secret" "gitea_db_secret" {
|
|
||||||
metadata {
|
|
||||||
name = "gitea-db"
|
|
||||||
namespace = kubernetes_namespace.git.metadata[0].name
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
password = var.gitea_db_password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deployment + Service managed by Argo CD — see servers/legion/apps/gitea.yaml
|
|
||||||
|
|
||||||
resource "kubernetes_ingress_v1" "gitea" {
|
|
||||||
metadata {
|
|
||||||
name = "gitea"
|
|
||||||
namespace = kubernetes_namespace.git.metadata[0].name
|
|
||||||
annotations = {
|
|
||||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
|
||||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
tls {
|
|
||||||
hosts = [var.gitea_domain]
|
|
||||||
secret_name = "gitea-tls"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
host = var.gitea_domain
|
|
||||||
http {
|
|
||||||
path {
|
|
||||||
path = "/"
|
|
||||||
path_type = "Prefix"
|
|
||||||
backend {
|
|
||||||
service {
|
|
||||||
name = "gitea"
|
|
||||||
port { number = 3000 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [helm_release.cert_manager]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,2 @@
|
|||||||
# GitHub Actions Self-Hosted Runner — org-level, serves all repos
|
# GitHub Actions Self-Hosted Runner — secrets managed by Argo CD + ESO
|
||||||
# Deployment managed by Argo CD — see servers/legion/apps/github-runner.yaml
|
# See: apps/github-runner.yaml, apps/ci-config.yaml, k8s/github-runner/
|
||||||
|
|
||||||
resource "kubernetes_secret" "github_runner_secret" {
|
|
||||||
metadata {
|
|
||||||
name = "github-runner-secret"
|
|
||||||
namespace = kubernetes_namespace.ci.metadata[0].name
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
ACCESS_TOKEN = var.github_classic_pat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# Headscale — self-hosted Tailscale control plane
|
# Headscale — namespace only
|
||||||
# Coordination endpoint: https://headscale.neuralplatform.ai (Cloudflare tunnel)
|
# PVC, Deployment, Service managed by Argo CD
|
||||||
# Clients: Tailscale app on Mac/iOS/Android, pointed at custom server
|
# See: apps/headscale.yaml, apps/headscale-config.yaml, k8s/headscale/
|
||||||
# Subnet router: tailscaled on Legion host, advertises k3s + LAN CIDRs
|
|
||||||
|
|
||||||
resource "kubernetes_namespace" "headscale" {
|
resource "kubernetes_namespace" "headscale" {
|
||||||
metadata {
|
metadata {
|
||||||
@@ -12,22 +11,3 @@ resource "kubernetes_namespace" "headscale" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "kubernetes_persistent_volume_claim" "headscale_data" {
|
|
||||||
metadata {
|
|
||||||
name = "headscale-data"
|
|
||||||
namespace = kubernetes_namespace.headscale.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
# wait_until_bound = false because local-path only provisions on pod schedule
|
|
||||||
wait_until_bound = false
|
|
||||||
|
|
||||||
spec {
|
|
||||||
access_modes = ["ReadWriteOnce"]
|
|
||||||
resources {
|
|
||||||
requests = {
|
|
||||||
storage = "1Gi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-243
@@ -1,243 +1,4 @@
|
|||||||
# Traefik ingress routes for all services — TLS via cert-manager + Cloudflare DNS-01
|
# Ingresses — all moved to k8s/<service>/ingress.yaml, managed by Argo CD
|
||||||
# Kong and open-webui ingresses moved to inactive/ — restore when needed
|
# See: k8s/neuron/ingress.yaml, k8s/gitea/ingress.yaml, k8s/ollama/ingress.yaml,
|
||||||
|
# k8s/registry/ingress.yaml, k8s/packages/ingress.yaml,
|
||||||
resource "kubernetes_ingress_v1" "ollama" {
|
# k8s/monitoring/ingress.yaml, k8s/adguard/ingress.yaml
|
||||||
metadata {
|
|
||||||
name = "ollama"
|
|
||||||
namespace = kubernetes_namespace.ollama.metadata[0].name
|
|
||||||
annotations = {
|
|
||||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
|
||||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
tls {
|
|
||||||
hosts = ["ollama.${var.infra_domain}"]
|
|
||||||
secret_name = "ollama-tls"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
host = "ollama.${var.infra_domain}"
|
|
||||||
http {
|
|
||||||
path {
|
|
||||||
path = "/"
|
|
||||||
path_type = "Prefix"
|
|
||||||
backend {
|
|
||||||
service {
|
|
||||||
name = "ollama"
|
|
||||||
port { number = 11434 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [helm_release.cert_manager]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
resource "kubernetes_ingress_v1" "registry" {
|
|
||||||
metadata {
|
|
||||||
name = "registry"
|
|
||||||
namespace = kubernetes_namespace.registry.metadata[0].name
|
|
||||||
annotations = {
|
|
||||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
|
||||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
|
||||||
"traefik.ingress.kubernetes.io/router.middlewares" = "registry-chunked@kubernetescrd"
|
|
||||||
"nginx.ingress.kubernetes.io/proxy-body-size" = "0"
|
|
||||||
"nginx.ingress.kubernetes.io/proxy-read-timeout" = "600"
|
|
||||||
"nginx.ingress.kubernetes.io/proxy-send-timeout" = "600"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
tls {
|
|
||||||
hosts = ["registry.${var.infra_domain}"]
|
|
||||||
secret_name = "registry-tls"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
host = "registry.${var.infra_domain}"
|
|
||||||
http {
|
|
||||||
path {
|
|
||||||
path = "/"
|
|
||||||
path_type = "Prefix"
|
|
||||||
backend {
|
|
||||||
service {
|
|
||||||
name = "registry"
|
|
||||||
port { number = 5000 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [helm_release.cert_manager]
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_ingress_v1" "registry_ui" {
|
|
||||||
metadata {
|
|
||||||
name = "registry-ui"
|
|
||||||
namespace = kubernetes_namespace.registry.metadata[0].name
|
|
||||||
annotations = {
|
|
||||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
|
||||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
tls {
|
|
||||||
hosts = ["docker.neuralplatform.ai"]
|
|
||||||
secret_name = "docker-ui-tls"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
host = "docker.neuralplatform.ai"
|
|
||||||
http {
|
|
||||||
path {
|
|
||||||
path = "/"
|
|
||||||
path_type = "Prefix"
|
|
||||||
backend {
|
|
||||||
service {
|
|
||||||
name = "registry-ui"
|
|
||||||
port { number = 80 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [helm_release.cert_manager]
|
|
||||||
}
|
|
||||||
|
|
||||||
# npm registry — Verdaccio (proxies npmjs.org, caches locally)
|
|
||||||
resource "kubernetes_ingress_v1" "npm_registry" {
|
|
||||||
metadata {
|
|
||||||
name = "npm-registry"
|
|
||||||
namespace = kubernetes_namespace.packages.metadata[0].name
|
|
||||||
annotations = {
|
|
||||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
|
||||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
tls {
|
|
||||||
hosts = ["npm.neuralplatform.ai"]
|
|
||||||
secret_name = "npm-registry-tls"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
host = "npm.neuralplatform.ai"
|
|
||||||
http {
|
|
||||||
path {
|
|
||||||
path = "/"
|
|
||||||
path_type = "Prefix"
|
|
||||||
backend {
|
|
||||||
service {
|
|
||||||
name = "verdaccio"
|
|
||||||
port { number = 4873 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [helm_release.cert_manager]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Alloy OTLP HTTP ingress — external log/trace ingestion from RunPod pods
|
|
||||||
# Auth: HTTP Basic Auth (username: pantheon, password: ALLOY_PUSH_TOKEN from Vault)
|
|
||||||
# Pods set: Authorization: Basic base64(pantheon:<token>)
|
|
||||||
# Traefik middleware strips auth before forwarding to Alloy.
|
|
||||||
resource "kubernetes_manifest" "alloy_otlp_basicauth_middleware" {
|
|
||||||
manifest = {
|
|
||||||
apiVersion = "traefik.io/v1alpha1"
|
|
||||||
kind = "Middleware"
|
|
||||||
metadata = {
|
|
||||||
name = "alloy-otlp-auth"
|
|
||||||
namespace = "monitoring"
|
|
||||||
}
|
|
||||||
spec = {
|
|
||||||
basicAuth = {
|
|
||||||
secret = "alloy-otlp-auth"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
depends_on = [kubernetes_secret.alloy_otlp_auth]
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_ingress_v1" "alloy_otlp" {
|
|
||||||
metadata {
|
|
||||||
name = "alloy-otlp"
|
|
||||||
namespace = "monitoring"
|
|
||||||
annotations = {
|
|
||||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
|
||||||
"traefik.ingress.kubernetes.io/router.middlewares" = "monitoring-alloy-otlp-auth@kubernetescrd"
|
|
||||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
tls {
|
|
||||||
hosts = ["alloy.${var.infra_domain}"]
|
|
||||||
secret_name = "alloy-otlp-tls"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
host = "alloy.${var.infra_domain}"
|
|
||||||
http {
|
|
||||||
path {
|
|
||||||
path = "/"
|
|
||||||
path_type = "Prefix"
|
|
||||||
backend {
|
|
||||||
service {
|
|
||||||
name = "alloy-otlp"
|
|
||||||
port { number = 4318 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
depends_on = [
|
|
||||||
kubernetes_manifest.alloy_otlp_basicauth_middleware,
|
|
||||||
kubernetes_service.alloy_otlp,
|
|
||||||
helm_release.cert_manager,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# PyPI registry — devpi (proxies pypi.org, caches locally)
|
|
||||||
resource "kubernetes_ingress_v1" "pypi_registry" {
|
|
||||||
metadata {
|
|
||||||
name = "pypi-registry"
|
|
||||||
namespace = kubernetes_namespace.packages.metadata[0].name
|
|
||||||
annotations = {
|
|
||||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
|
||||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
tls {
|
|
||||||
hosts = ["pypi.neuralplatform.ai"]
|
|
||||||
secret_name = "pypi-registry-tls"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
host = "pypi.neuralplatform.ai"
|
|
||||||
http {
|
|
||||||
path {
|
|
||||||
path = "/"
|
|
||||||
path_type = "Prefix"
|
|
||||||
backend {
|
|
||||||
service {
|
|
||||||
name = "devpi"
|
|
||||||
port { number = 3141 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [helm_release.cert_manager]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
# TLS certificate for DoT (DNS-over-TLS) on dot.nook.family:853
|
||||||
|
# Issued via cert-manager DNS-01 challenge (Cloudflare)
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: adguard-dot-tls
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
secretName: adguard-dot-tls
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- dot.nook.family
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: adguard-defaults
|
||||||
|
namespace: dns
|
||||||
|
data:
|
||||||
|
AdGuardHome.yaml: |
|
||||||
|
dns:
|
||||||
|
bind_hosts:
|
||||||
|
- 0.0.0.0
|
||||||
|
port: 53
|
||||||
|
upstream_dns:
|
||||||
|
- https://dns.cloudflare.com/dns-query
|
||||||
|
- https://dns.google/dns-query
|
||||||
|
bootstrap_dns:
|
||||||
|
- 1.1.1.1
|
||||||
|
- 8.8.8.8
|
||||||
|
- 9.9.9.10
|
||||||
|
- 149.112.112.10
|
||||||
|
upstream_mode: load_balance
|
||||||
|
cache_enabled: true
|
||||||
|
cache_size: 4194304
|
||||||
|
filters:
|
||||||
|
- enabled: true
|
||||||
|
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
|
||||||
|
name: AdGuard DNS filter
|
||||||
|
id: 1
|
||||||
|
- enabled: true
|
||||||
|
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt
|
||||||
|
name: AdAway Default Blocklist
|
||||||
|
id: 2
|
||||||
|
- enabled: true
|
||||||
|
url: https://big.oisd.nl/domainswild
|
||||||
|
name: OISD Big
|
||||||
|
id: 3
|
||||||
|
- enabled: true
|
||||||
|
url: https://easylist.to/easylist/easylist.txt
|
||||||
|
name: EasyList
|
||||||
|
id: 4
|
||||||
|
- enabled: true
|
||||||
|
url: https://easylist.to/easylist/easyprivacy.txt
|
||||||
|
name: EasyPrivacy
|
||||||
|
id: 5
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
server_name: dot.nook.family
|
||||||
|
port_dns_over_tls: 853
|
||||||
|
certificate_path: /etc/adguard/tls/tls.crt
|
||||||
|
private_key_path: /etc/adguard/tls/tls.key
|
||||||
|
allow_unencrypted_doh: true
|
||||||
|
filtering:
|
||||||
|
filtering_enabled: true
|
||||||
|
rewrites:
|
||||||
|
- domain: '*.nook.family'
|
||||||
|
answer: 192.168.68.77
|
||||||
|
enabled: true
|
||||||
|
- domain: '*.neuralplatform.ai'
|
||||||
|
answer: 192.168.68.77
|
||||||
|
enabled: true
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
# ddclient — dynamic DNS daemon keeping dot.nook.family pointed at home public IP
|
||||||
|
# Updates Cloudflare A record every 5 minutes (required for DoT port 853, no CF proxy)
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: ddclient
|
||||||
|
namespace: dns
|
||||||
|
labels:
|
||||||
|
app: ddclient
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: ddclient
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: ddclient
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: ddclient
|
||||||
|
image: ghcr.io/ddclient/ddclient:latest
|
||||||
|
command: ["ddclient", "-file", "/etc/ddclient/ddclient.conf", "-daemon", "300", "-noquiet", "-foreground"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/ddclient/ddclient.conf
|
||||||
|
subPath: ddclient.conf
|
||||||
|
readOnly: true
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 32Mi
|
||||||
|
cpu: 10m
|
||||||
|
limits:
|
||||||
|
memory: 64Mi
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
secret:
|
||||||
|
secretName: ddclient-config
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
# ddclient-config — Cloudflare credentials for dynamic DNS updates
|
||||||
|
# cloudflare_api_key and cloudflare_email stored in Vault at secret/cloudflare
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: ddclient-config
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: ddclient-config
|
||||||
|
creationPolicy: Owner
|
||||||
|
template:
|
||||||
|
data:
|
||||||
|
ddclient.conf: |
|
||||||
|
daemon=300
|
||||||
|
syslog=yes
|
||||||
|
pid=/var/run/ddclient/ddclient.pid
|
||||||
|
use=web, web=https://api.ipify.org
|
||||||
|
|
||||||
|
protocol=cloudflare
|
||||||
|
zone=nook.family
|
||||||
|
ttl=120
|
||||||
|
login={{ .cloudflare_email }}
|
||||||
|
password={{ .cloudflare_api_key }}
|
||||||
|
dot.nook.family
|
||||||
|
data:
|
||||||
|
- secretKey: cloudflare_api_key
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/cloudflare
|
||||||
|
property: api_key
|
||||||
|
- secretKey: cloudflare_email
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/cloudflare
|
||||||
|
property: email
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: adguard
|
||||||
|
namespace: dns
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- dns.nook.family
|
||||||
|
secretName: adguard-tls
|
||||||
|
rules:
|
||||||
|
- host: dns.nook.family
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: adguard-ui
|
||||||
|
port:
|
||||||
|
number: 3000
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: adguard-config
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: adguard-data
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
---
|
||||||
|
# Nightly backup — dumps Postgres + Gitea data to Cloudflare R2 via restic
|
||||||
|
# Schedule: 2am daily. Retains 30 daily / 12 weekly / 6 monthly snapshots.
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: gitea-backup
|
||||||
|
namespace: git
|
||||||
|
spec:
|
||||||
|
schedule: "0 2 * * *"
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
failedJobsHistoryLimit: 3
|
||||||
|
successfulJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 2
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
initContainers:
|
||||||
|
- name: pg-dump
|
||||||
|
image: postgres:18-alpine
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- >-
|
||||||
|
pg_dumpall
|
||||||
|
-h postgres-postgresql.platform.svc.cluster.local
|
||||||
|
-U postgres
|
||||||
|
--no-role-passwords
|
||||||
|
> /dump/all-databases.sql
|
||||||
|
&& echo 'DB dump complete, size:' $(du -sh /dump/all-databases.sql)
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: backup-credentials
|
||||||
|
volumeMounts:
|
||||||
|
- name: dump
|
||||||
|
mountPath: /dump
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 128Mi
|
||||||
|
cpu: 100m
|
||||||
|
limits:
|
||||||
|
memory: 256Mi
|
||||||
|
containers:
|
||||||
|
- name: backup
|
||||||
|
image: restic/restic:latest
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Init repo if first run
|
||||||
|
restic snapshots 2>/dev/null || restic init
|
||||||
|
|
||||||
|
# Back up Gitea data + full DB dump
|
||||||
|
echo "Running restic backup..."
|
||||||
|
restic backup \
|
||||||
|
/gitea-data \
|
||||||
|
/dump/all-databases.sql \
|
||||||
|
--tag legion \
|
||||||
|
--host legion
|
||||||
|
|
||||||
|
# Prune old snapshots
|
||||||
|
restic forget \
|
||||||
|
--keep-daily 30 \
|
||||||
|
--keep-weekly 12 \
|
||||||
|
--keep-monthly 6 \
|
||||||
|
--prune
|
||||||
|
|
||||||
|
echo "Backup complete."
|
||||||
|
restic snapshots --latest 3
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: backup-credentials
|
||||||
|
volumeMounts:
|
||||||
|
- name: gitea-data
|
||||||
|
mountPath: /gitea-data
|
||||||
|
readOnly: true
|
||||||
|
- name: dump
|
||||||
|
mountPath: /dump
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 256Mi
|
||||||
|
cpu: 100m
|
||||||
|
limits:
|
||||||
|
memory: 512Mi
|
||||||
|
volumes:
|
||||||
|
- name: gitea-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: gitea-data
|
||||||
|
readOnly: true
|
||||||
|
- name: dump
|
||||||
|
emptyDir: {}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
# backup-credentials — R2 access keys + restic password + Postgres password for nightly backup
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: backup-credentials
|
||||||
|
namespace: git
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: backup-credentials
|
||||||
|
creationPolicy: Owner
|
||||||
|
template:
|
||||||
|
data:
|
||||||
|
RESTIC_PASSWORD: "gitea-restic-2026"
|
||||||
|
AWS_ACCESS_KEY_ID: "{{ .r2_access_key_id }}"
|
||||||
|
AWS_SECRET_ACCESS_KEY: "{{ .r2_secret_access_key }}"
|
||||||
|
RESTIC_REPOSITORY: "s3:https://651161e0a3d321561b4c90b5bcd5f15b.r2.cloudflarestorage.com/legion-gitea-backup"
|
||||||
|
PGPASSWORD: "{{ .postgres_password }}"
|
||||||
|
data:
|
||||||
|
- secretKey: r2_access_key_id
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/r2
|
||||||
|
property: access_key_id
|
||||||
|
- secretKey: r2_secret_access_key
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/r2
|
||||||
|
property: secret_access_key
|
||||||
|
- secretKey: postgres_password
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/legion-db
|
||||||
|
property: postgres_password
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
# ClusterSecretStore — connects ESO to Vault using the root token Secret.
|
||||||
|
# The vault-token Secret (name: vault-token, key: token) must be created
|
||||||
|
# via kubectl as a one-time bootstrap step before this store can sync:
|
||||||
|
#
|
||||||
|
# kubectl create secret generic vault-token \
|
||||||
|
# --namespace external-secrets \
|
||||||
|
# --from-literal=token=$(cat ~/Secrets/tokens/vault-root-token)
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
metadata:
|
||||||
|
name: vault
|
||||||
|
spec:
|
||||||
|
provider:
|
||||||
|
vault:
|
||||||
|
server: "https://vault.neuralplatform.ai"
|
||||||
|
path: "secret"
|
||||||
|
version: "v2"
|
||||||
|
auth:
|
||||||
|
tokenSecretRef:
|
||||||
|
name: vault-token
|
||||||
|
namespace: external-secrets
|
||||||
|
key: token
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
# gitea-runner-secret — Gitea act-runner registration token
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-secret
|
||||||
|
namespace: ci
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: gitea-runner-secret
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: GITEA_INSTANCE_URL
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/gitea
|
||||||
|
property: instance_url
|
||||||
|
- secretKey: GITEA_RUNNER_REGISTRATION_TOKEN
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/gitea
|
||||||
|
property: runner_token
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: gitea-custom-css
|
||||||
|
namespace: git
|
||||||
|
data:
|
||||||
|
header.tmpl: |
|
||||||
|
<link rel="stylesheet" href="/assets/css/custom.css">
|
||||||
|
custom.css: |
|
||||||
|
/* ── Typography & base ── */
|
||||||
|
:root {
|
||||||
|
--color-primary: #6366f1;
|
||||||
|
--color-primary-dark: #4f46e5;
|
||||||
|
--color-secondary: #8b5cf6;
|
||||||
|
--color-bg: #0f0f17;
|
||||||
|
--color-surface: #16161f;
|
||||||
|
--color-surface-2: #1e1e2e;
|
||||||
|
--color-border: #2a2a3d;
|
||||||
|
--color-text: #e2e2f0;
|
||||||
|
--color-text-muted: #8888aa;
|
||||||
|
--color-green: #22d3a5;
|
||||||
|
--radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--color-bg) !important;
|
||||||
|
color: var(--color-text) !important;
|
||||||
|
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Top navbar ── */
|
||||||
|
.navbar, #navbar {
|
||||||
|
background: var(--color-surface) !important;
|
||||||
|
border-bottom: 1px solid var(--color-border) !important;
|
||||||
|
box-shadow: 0 1px 12px rgba(0,0,0,0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .brand svg, .navbar .brand img { filter: brightness(1.2); }
|
||||||
|
|
||||||
|
/* ── Sidebar & panels ── */
|
||||||
|
.repository, .ui.container, .ui.segment,
|
||||||
|
.ui.card, .ui.cards > .card {
|
||||||
|
background: var(--color-surface) !important;
|
||||||
|
border: 1px solid var(--color-border) !important;
|
||||||
|
border-radius: var(--radius) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Buttons ── */
|
||||||
|
.ui.primary.button, .ui.green.button {
|
||||||
|
background: var(--color-primary) !important;
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
.ui.primary.button:hover { background: var(--color-primary-dark) !important; }
|
||||||
|
|
||||||
|
/* ── Inputs ── */
|
||||||
|
input, textarea, select,
|
||||||
|
.ui.input > input, .ui.dropdown {
|
||||||
|
background: var(--color-surface-2) !important;
|
||||||
|
border: 1px solid var(--color-border) !important;
|
||||||
|
color: var(--color-text) !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Sign-in page ── */
|
||||||
|
.user.signin .ui.segment,
|
||||||
|
.user.signup .ui.segment {
|
||||||
|
background: var(--color-surface) !important;
|
||||||
|
border: 1px solid var(--color-border) !important;
|
||||||
|
border-radius: 16px !important;
|
||||||
|
box-shadow: 0 8px 32px rgba(0,0,0,0.5) !important;
|
||||||
|
padding: 2.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Repo file tree ── */
|
||||||
|
.repository.file.list .file-list {
|
||||||
|
background: var(--color-surface) !important;
|
||||||
|
border-radius: var(--radius) !important;
|
||||||
|
border: 1px solid var(--color-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repository.file.list .file-list tr:hover td {
|
||||||
|
background: var(--color-surface-2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Labels & badges ── */
|
||||||
|
.ui.label { border-radius: 6px !important; }
|
||||||
|
|
||||||
|
/* ── Dashboard activity feed ── */
|
||||||
|
.feeds .news { border-bottom: 1px solid var(--color-border) !important; }
|
||||||
|
|
||||||
|
/* ── Code blocks ── */
|
||||||
|
pre, code {
|
||||||
|
background: var(--color-surface-2) !important;
|
||||||
|
border: 1px solid var(--color-border) !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Muted footer ── */
|
||||||
|
#footer {
|
||||||
|
background: var(--color-surface) !important;
|
||||||
|
border-top: 1px solid var(--color-border) !important;
|
||||||
|
color: var(--color-text-muted) !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
# gitea-db — password used by the Gitea pod to connect to Postgres
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: gitea-db
|
||||||
|
namespace: git
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: gitea-db
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: password
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/gitea
|
||||||
|
property: db_password
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: gitea
|
||||||
|
namespace: git
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- git.neuralplatform.ai
|
||||||
|
secretName: gitea-tls
|
||||||
|
rules:
|
||||||
|
- host: git.neuralplatform.ai
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: gitea
|
||||||
|
port:
|
||||||
|
number: 3000
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: gitea-data
|
||||||
|
namespace: git
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 20Gi
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
# github-runner-secret — GitHub classic PAT for runner registration
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: github-runner-secret
|
||||||
|
namespace: ci
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: github-runner-secret
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: ACCESS_TOKEN
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/github
|
||||||
|
property: classic_pat
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: headscale-data
|
||||||
|
namespace: headscale
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
# Traefik BasicAuth middleware — strips auth before forwarding to Alloy
|
||||||
|
# Secret alloy-otlp-auth is synced from Vault via ExternalSecret (see external-secrets.yaml)
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: alloy-otlp-auth
|
||||||
|
namespace: monitoring
|
||||||
|
spec:
|
||||||
|
basicAuth:
|
||||||
|
secret: alloy-otlp-auth
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
# Expose Alloy's OTLP HTTP port (4318) for external log/trace ingestion from RunPod pods
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: alloy-otlp
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
app: alloy-otlp
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: alloy
|
||||||
|
ports:
|
||||||
|
- name: otlp-http
|
||||||
|
port: 4318
|
||||||
|
targetPort: 4318
|
||||||
|
protocol: TCP
|
||||||
|
type: ClusterIP
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
# alloy-otlp-auth — bcrypt htpasswd entry for Traefik BasicAuth middleware
|
||||||
|
# Username: pantheon, password: alloy push token from Vault secret/alloy
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: alloy-otlp-auth
|
||||||
|
namespace: monitoring
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: alloy-otlp-auth
|
||||||
|
creationPolicy: Owner
|
||||||
|
# ESO does not bcrypt — the htpasswd entry must be pre-hashed in Vault.
|
||||||
|
# Store via: vault kv patch secret/alloy htpasswd_entry="pantheon:$(htpasswd -nbB pantheon <token> | cut -d: -f2)"
|
||||||
|
data:
|
||||||
|
- secretKey: users
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/alloy
|
||||||
|
property: htpasswd_entry
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
# Grafana extra datasources — Loki and Tempo with cross-linking
|
||||||
|
# Discovered by Grafana sidecar via label grafana_datasource=1
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: grafana-extra-datasources
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
grafana_datasource: "1"
|
||||||
|
data:
|
||||||
|
datasources.yaml: |
|
||||||
|
apiVersion: 1
|
||||||
|
datasources:
|
||||||
|
- name: Loki
|
||||||
|
type: loki
|
||||||
|
uid: loki
|
||||||
|
url: http://loki-gateway.monitoring.svc.cluster.local
|
||||||
|
access: proxy
|
||||||
|
jsonData:
|
||||||
|
httpHeaderName1: X-Scope-OrgID
|
||||||
|
secureJsonData:
|
||||||
|
httpHeaderValue1: legion
|
||||||
|
- name: Tempo
|
||||||
|
type: tempo
|
||||||
|
uid: tempo
|
||||||
|
url: http://tempo.monitoring.svc.cluster.local:3100
|
||||||
|
access: proxy
|
||||||
|
jsonData:
|
||||||
|
lokiSearch:
|
||||||
|
datasourceUid: loki
|
||||||
|
tracesToLogsV2:
|
||||||
|
datasourceUid: loki
|
||||||
|
filterByTraceID: true
|
||||||
|
filterBySpanID: false
|
||||||
|
tracesToMetrics:
|
||||||
|
datasourceUid: prometheus
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
# grafana-admin-secret — Grafana admin password from Vault
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: grafana-admin-secret
|
||||||
|
namespace: monitoring
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: grafana-admin-secret
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: admin-password
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/grafana
|
||||||
|
property: admin_password
|
||||||
|
---
|
||||||
|
# slack-bot-token — Slack bot token for Alertmanager notifications
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: slack-bot-token
|
||||||
|
namespace: monitoring
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: slack-bot-token
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: bot_token
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/slack
|
||||||
|
property: bot_token
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
# Alloy OTLP HTTP ingress — external log/trace ingestion from RunPod pods
|
||||||
|
# Auth: HTTP Basic Auth (username: pantheon, password: ALLOY_PUSH_TOKEN)
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: alloy-otlp
|
||||||
|
namespace: monitoring
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
traefik.ingress.kubernetes.io/router.middlewares: monitoring-alloy-otlp-auth@kubernetescrd
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- alloy.neuralplatform.ai
|
||||||
|
secretName: alloy-otlp-tls
|
||||||
|
rules:
|
||||||
|
- host: alloy.neuralplatform.ai
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: alloy-otlp
|
||||||
|
port:
|
||||||
|
number: 4318
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: neuron-config
|
||||||
|
namespace: neuron
|
||||||
|
data:
|
||||||
|
MCP_PORT: "8001"
|
||||||
|
MCP_HOST: "0.0.0.0"
|
||||||
|
WEBHOOK_PORT: "3847"
|
||||||
|
NEURON_DB_PATH: "/data/neuron.db"
|
||||||
|
AXON_DB_PATH: "/data/axon.db"
|
||||||
|
TUNNEL_NAME: "neural-platform"
|
||||||
|
default.yaml: |
|
||||||
|
runtime:
|
||||||
|
name: neuron
|
||||||
|
version: 0.1.0
|
||||||
|
storage:
|
||||||
|
db_path: /data/neuron.db
|
||||||
|
cache:
|
||||||
|
max_l1_size: 1000
|
||||||
|
gc:
|
||||||
|
gen0_threshold: 3
|
||||||
|
gen1_threshold: 5
|
||||||
|
knowledge:
|
||||||
|
base_path: /data/knowledge
|
||||||
|
synapse:
|
||||||
|
path: /data/synapse
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
# neuron-secrets — GITHUB_WEBHOOK_SECRET, GITEA_WEBHOOK_SECRET, SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: neuron-secrets
|
||||||
|
namespace: neuron
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: neuron-secrets
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: GITHUB_WEBHOOK_SECRET
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/github
|
||||||
|
property: webhook_secret
|
||||||
|
- secretKey: GITEA_WEBHOOK_SECRET
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/gitea
|
||||||
|
property: api_token
|
||||||
|
- secretKey: SLACK_BOT_TOKEN
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/slack
|
||||||
|
property: bot_token
|
||||||
|
- secretKey: SLACK_SIGNING_SECRET
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/slack
|
||||||
|
property: signing_secret
|
||||||
|
---
|
||||||
|
# cloudflared-secret — TUNNEL_TOKEN for the Cloudflare tunnel
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: cloudflared-secret
|
||||||
|
namespace: neuron
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: cloudflared-secret
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: TUNNEL_TOKEN
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/cloudflare-tunnel
|
||||||
|
property: token
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: neuron
|
||||||
|
namespace: neuron
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- secretName: neuron-tls
|
||||||
|
hosts:
|
||||||
|
- neuron.neuralplatform.ai
|
||||||
|
rules:
|
||||||
|
- host: neuron.neuralplatform.ai
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: neuron
|
||||||
|
port:
|
||||||
|
number: 8001
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: neuron-data
|
||||||
|
namespace: neuron
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: ollama
|
||||||
|
namespace: ollama
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- ollama.neuralplatform.ai
|
||||||
|
secretName: ollama-tls
|
||||||
|
rules:
|
||||||
|
- host: ollama.neuralplatform.ai
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: ollama
|
||||||
|
port:
|
||||||
|
number: 11434
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: ollama-models
|
||||||
|
namespace: ollama
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 50Gi
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: verdaccio-config
|
||||||
|
namespace: packages
|
||||||
|
data:
|
||||||
|
config.yaml: |
|
||||||
|
storage: /verdaccio/storage
|
||||||
|
auth:
|
||||||
|
htpasswd:
|
||||||
|
file: /verdaccio/storage/htpasswd
|
||||||
|
max_users: 1
|
||||||
|
uplinks:
|
||||||
|
npmjs:
|
||||||
|
url: https://registry.npmjs.org/
|
||||||
|
cache: true
|
||||||
|
packages:
|
||||||
|
'@harmonic-framework/*':
|
||||||
|
access: $authenticated
|
||||||
|
publish: $authenticated
|
||||||
|
unpublish: $authenticated
|
||||||
|
'**':
|
||||||
|
access: $all
|
||||||
|
publish: $authenticated
|
||||||
|
proxy: npmjs
|
||||||
|
listen: 0.0.0.0:4873
|
||||||
|
server:
|
||||||
|
keepAliveTimeout: 60
|
||||||
|
log:
|
||||||
|
type: stdout
|
||||||
|
format: pretty
|
||||||
|
level: http
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
# npm registry — Verdaccio
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: npm-registry
|
||||||
|
namespace: packages
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- npm.neuralplatform.ai
|
||||||
|
secretName: npm-registry-tls
|
||||||
|
rules:
|
||||||
|
- host: npm.neuralplatform.ai
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: verdaccio
|
||||||
|
port:
|
||||||
|
number: 4873
|
||||||
|
---
|
||||||
|
# PyPI registry — devpi
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: pypi-registry
|
||||||
|
namespace: packages
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- pypi.neuralplatform.ai
|
||||||
|
secretName: pypi-registry-tls
|
||||||
|
rules:
|
||||||
|
- host: pypi.neuralplatform.ai
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: devpi
|
||||||
|
port:
|
||||||
|
number: 3141
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: verdaccio-data
|
||||||
|
namespace: packages
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: devpi-data
|
||||||
|
namespace: packages
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
# postgres-passwords — Postgres superuser + Kong DB passwords from Vault
|
||||||
|
# These are passed to the Bitnami postgresql chart via the app's Helm values.
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: postgres-passwords
|
||||||
|
namespace: platform
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: postgres-passwords
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: postgres-password
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/legion-db
|
||||||
|
property: postgres_password
|
||||||
|
- secretKey: kong-db-password
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/legion-db
|
||||||
|
property: kong_db_password
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
# Docker registry ingress with chunked upload middleware
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: registry
|
||||||
|
namespace: registry
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
traefik.ingress.kubernetes.io/router.middlewares: registry-chunked@kubernetescrd
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- registry.neuralplatform.ai
|
||||||
|
secretName: registry-tls
|
||||||
|
rules:
|
||||||
|
- host: registry.neuralplatform.ai
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: registry
|
||||||
|
port:
|
||||||
|
number: 5000
|
||||||
|
---
|
||||||
|
# Docker registry UI
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: registry-ui
|
||||||
|
namespace: registry
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- docker.neuralplatform.ai
|
||||||
|
secretName: docker-ui-tls
|
||||||
|
rules:
|
||||||
|
- host: docker.neuralplatform.ai
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: registry-ui
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: registry-data
|
||||||
|
namespace: registry
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 50Gi
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
# vault-gcp-sa — GCP service account JSON for KMS auto-unseal
|
||||||
|
# The SA JSON is stored in Vault at secret/data/vault-gcp-sa
|
||||||
|
# Store it: vault kv put secret/vault-gcp-sa sa_json=@vault-gcp-sa.json
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: vault-gcp-sa
|
||||||
|
namespace: vault
|
||||||
|
spec:
|
||||||
|
refreshInterval: 24h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: vault-gcp-sa
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: sa.json
|
||||||
|
remoteRef:
|
||||||
|
key: secret/data/vault-gcp-sa
|
||||||
|
property: sa_json
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
# Monitoring stack — Prometheus, Grafana, Loki, Tempo, Alloy, Alertmanager
|
# Monitoring stack — Prometheus, Grafana, Loki, Tempo, Alloy
|
||||||
# Grafana UI: https://grafana.neuralplatform.ai (admin / var.grafana_admin_password)
|
# Namespace only — all Helm releases and config managed by Argo CD + ESO
|
||||||
#
|
# See: apps/kube-prometheus-stack.yaml, apps/loki.yaml, apps/tempo.yaml, apps/alloy.yaml
|
||||||
# TODO: Migrate Tempo to tempo-distributed once R2 is wired as trace storage backend
|
# See: apps/monitoring-config.yaml, k8s/monitoring/
|
||||||
# TODO: Replace loki.useTestSchema=true with proper schema_config for production
|
|
||||||
|
|
||||||
resource "kubernetes_namespace" "monitoring" {
|
resource "kubernetes_namespace" "monitoring" {
|
||||||
metadata {
|
metadata {
|
||||||
@@ -13,372 +12,3 @@ resource "kubernetes_namespace" "monitoring" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# kube-prometheus-stack — Prometheus, Grafana, Alertmanager, node exporters
|
|
||||||
resource "helm_release" "kube_prometheus_stack" {
|
|
||||||
name = "kube-prometheus-stack"
|
|
||||||
namespace = kubernetes_namespace.monitoring.metadata[0].name
|
|
||||||
repository = "https://prometheus-community.github.io/helm-charts"
|
|
||||||
chart = "kube-prometheus-stack"
|
|
||||||
timeout = 600
|
|
||||||
|
|
||||||
# Grafana
|
|
||||||
set {
|
|
||||||
name = "grafana.adminPassword"
|
|
||||||
value = var.grafana_admin_password
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.persistence.enabled"
|
|
||||||
value = "true"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.persistence.size"
|
|
||||||
value = "5Gi"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.sidecar.datasources.defaultDatasourceEnabled"
|
|
||||||
value = "false"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.initChownData.enabled"
|
|
||||||
value = "false"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.ingress.enabled"
|
|
||||||
value = "true"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.ingress.ingressClassName"
|
|
||||||
value = "traefik"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.ingress.hosts[0]"
|
|
||||||
value = "grafana.${var.infra_domain}"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.ingress.tls[0].secretName"
|
|
||||||
value = "grafana-tls"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.ingress.tls[0].hosts[0]"
|
|
||||||
value = "grafana.${var.infra_domain}"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.ingress.annotations.traefik\\.ingress\\.kubernetes\\.io/router\\.entrypoints"
|
|
||||||
value = "websecure"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "grafana.ingress.annotations.cert-manager\\.io/cluster-issuer"
|
|
||||||
value = "letsencrypt-prod"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Prometheus persistent storage
|
|
||||||
set {
|
|
||||||
name = "prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage"
|
|
||||||
value = "20Gi"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Disable k3s-incompatible scrapers
|
|
||||||
set {
|
|
||||||
name = "kubeEtcd.enabled"
|
|
||||||
value = "false"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "kubeControllerManager.enabled"
|
|
||||||
value = "false"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "kubeScheduler.enabled"
|
|
||||||
value = "false"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "kubeProxy.enabled"
|
|
||||||
value = "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Alertmanager — route alerts to #infrastructure-alerts in Slack
|
|
||||||
values = [<<-EOT
|
|
||||||
alertmanager:
|
|
||||||
config:
|
|
||||||
global:
|
|
||||||
resolve_timeout: 5m
|
|
||||||
inhibit_rules:
|
|
||||||
- source_matchers: [severity="critical"]
|
|
||||||
target_matchers: [severity=~"warning|info"]
|
|
||||||
equal: [namespace, alertname]
|
|
||||||
- source_matchers: [severity="warning"]
|
|
||||||
target_matchers: [severity="info"]
|
|
||||||
equal: [namespace, alertname]
|
|
||||||
receivers:
|
|
||||||
- name: "null"
|
|
||||||
- name: slack-alerts
|
|
||||||
slack_configs:
|
|
||||||
- channel: "#infrastructure-alerts"
|
|
||||||
api_url: "https://slack.com/api/chat.postMessage"
|
|
||||||
http_config:
|
|
||||||
authorization:
|
|
||||||
type: Bearer
|
|
||||||
credentials: "${var.slack_bot_token}"
|
|
||||||
send_resolved: true
|
|
||||||
title: '[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} ({{ .CommonLabels.namespace }})'
|
|
||||||
text: |
|
|
||||||
{{ range .Alerts }}
|
|
||||||
*{{ if .Annotations.summary }}{{ .Annotations.summary }}{{ else }}{{ .Labels.alertname }}{{ end }}*{{ if .Labels.severity }} · {{ .Labels.severity }}{{ end }}
|
|
||||||
{{ if .Annotations.description }}{{ .Annotations.description }}{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
footer: "Legion · neuralplatform.ai"
|
|
||||||
route:
|
|
||||||
group_by: [namespace, alertname]
|
|
||||||
group_wait: 30s
|
|
||||||
group_interval: 5m
|
|
||||||
repeat_interval: 4h
|
|
||||||
receiver: slack-alerts
|
|
||||||
routes:
|
|
||||||
- matchers: [alertname="Watchdog"]
|
|
||||||
receiver: "null"
|
|
||||||
- matchers: [severity="info"]
|
|
||||||
receiver: "null"
|
|
||||||
EOT
|
|
||||||
]
|
|
||||||
|
|
||||||
depends_on = [helm_release.cert_manager]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grafana extra datasources — Loki and Tempo wired up with cross-linking
|
|
||||||
resource "kubernetes_config_map" "grafana_extra_datasources" {
|
|
||||||
metadata {
|
|
||||||
name = "grafana-extra-datasources"
|
|
||||||
namespace = kubernetes_namespace.monitoring.metadata[0].name
|
|
||||||
labels = { grafana_datasource = "1" }
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"datasources.yaml" = <<-EOT
|
|
||||||
apiVersion: 1
|
|
||||||
datasources:
|
|
||||||
- name: Loki
|
|
||||||
type: loki
|
|
||||||
uid: loki
|
|
||||||
url: http://loki-gateway.monitoring.svc.cluster.local
|
|
||||||
access: proxy
|
|
||||||
jsonData:
|
|
||||||
httpHeaderName1: X-Scope-OrgID
|
|
||||||
secureJsonData:
|
|
||||||
httpHeaderValue1: legion
|
|
||||||
- name: Tempo
|
|
||||||
type: tempo
|
|
||||||
uid: tempo
|
|
||||||
url: http://tempo.monitoring.svc.cluster.local:3100
|
|
||||||
access: proxy
|
|
||||||
jsonData:
|
|
||||||
lokiSearch:
|
|
||||||
datasourceUid: loki
|
|
||||||
tracesToLogsV2:
|
|
||||||
datasourceUid: loki
|
|
||||||
filterByTraceID: true
|
|
||||||
filterBySpanID: false
|
|
||||||
tracesToMetrics:
|
|
||||||
datasourceUid: prometheus
|
|
||||||
EOT
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [helm_release.kube_prometheus_stack]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Loki — log aggregation (single binary mode)
|
|
||||||
# TODO: Replace useTestSchema with proper schema_config before storing long-term logs
|
|
||||||
resource "helm_release" "loki" {
|
|
||||||
name = "loki"
|
|
||||||
namespace = kubernetes_namespace.monitoring.metadata[0].name
|
|
||||||
repository = "https://grafana.github.io/helm-charts"
|
|
||||||
chart = "loki"
|
|
||||||
timeout = 300
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "loki.commonConfig.replication_factor"
|
|
||||||
value = "1"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "loki.storage.type"
|
|
||||||
value = "filesystem"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "loki.useTestSchema"
|
|
||||||
value = "true"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "singleBinary.replicas"
|
|
||||||
value = "1"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "singleBinary.persistence.enabled"
|
|
||||||
value = "true"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "singleBinary.persistence.size"
|
|
||||||
value = "10Gi"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "deploymentMode"
|
|
||||||
value = "SingleBinary"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "backend.replicas"
|
|
||||||
value = "0"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "read.replicas"
|
|
||||||
value = "0"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "write.replicas"
|
|
||||||
value = "0"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "chunksCache.enabled"
|
|
||||||
value = "false"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "resultsCache.enabled"
|
|
||||||
value = "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Tempo — distributed tracing backend (local storage)
|
|
||||||
# TODO: Migrate to tempo-distributed with R2 storage once R2 trace bucket is created
|
|
||||||
resource "helm_release" "tempo" {
|
|
||||||
name = "tempo"
|
|
||||||
namespace = kubernetes_namespace.monitoring.metadata[0].name
|
|
||||||
repository = "https://grafana.github.io/helm-charts"
|
|
||||||
chart = "tempo"
|
|
||||||
timeout = 300
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "tempo.storage.trace.backend"
|
|
||||||
value = "local"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "tempo.storage.trace.local.path"
|
|
||||||
value = "/var/tempo"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "persistence.enabled"
|
|
||||||
value = "true"
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
name = "persistence.size"
|
|
||||||
value = "10Gi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Alloy — log shipping + OTLP trace forwarding (replaces deprecated Promtail)
|
|
||||||
resource "helm_release" "alloy" {
|
|
||||||
name = "alloy"
|
|
||||||
namespace = kubernetes_namespace.monitoring.metadata[0].name
|
|
||||||
repository = "https://grafana.github.io/helm-charts"
|
|
||||||
chart = "alloy"
|
|
||||||
timeout = 300
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "controller.type"
|
|
||||||
value = "daemonset"
|
|
||||||
}
|
|
||||||
|
|
||||||
values = [<<-EOT
|
|
||||||
alloy:
|
|
||||||
configMap:
|
|
||||||
content: |
|
|
||||||
discovery.kubernetes "pods" {
|
|
||||||
role = "pod"
|
|
||||||
}
|
|
||||||
|
|
||||||
discovery.relabel "pods" {
|
|
||||||
targets = discovery.kubernetes.pods.targets
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_kubernetes_namespace"]
|
|
||||||
target_label = "namespace"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_kubernetes_pod_name"]
|
|
||||||
target_label = "pod"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_kubernetes_pod_container_name"]
|
|
||||||
target_label = "container"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_kubernetes_pod_label_app"]
|
|
||||||
target_label = "app"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loki.source.kubernetes "pods" {
|
|
||||||
targets = discovery.relabel.pods.output
|
|
||||||
forward_to = [loki.write.default.receiver]
|
|
||||||
}
|
|
||||||
|
|
||||||
loki.write "default" {
|
|
||||||
endpoint {
|
|
||||||
url = "http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push"
|
|
||||||
tenant_id = "legion"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
otelcol.receiver.otlp "default" {
|
|
||||||
grpc { endpoint = "0.0.0.0:4317" }
|
|
||||||
http { endpoint = "0.0.0.0:4318" }
|
|
||||||
output {
|
|
||||||
traces = [otelcol.exporter.otlp.tempo.input]
|
|
||||||
logs = [otelcol.exporter.loki.pantheon.input]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
otelcol.exporter.otlp "tempo" {
|
|
||||||
client {
|
|
||||||
endpoint = "tempo.monitoring.svc.cluster.local:4317"
|
|
||||||
tls { insecure = true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
otelcol.exporter.loki "pantheon" {
|
|
||||||
forward_to = [loki.write.default.receiver]
|
|
||||||
}
|
|
||||||
EOT
|
|
||||||
]
|
|
||||||
|
|
||||||
depends_on = [helm_release.loki, helm_release.tempo]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Expose Alloy's OTLP HTTP port (4318) for external log/trace ingestion from RunPod pods
|
|
||||||
resource "kubernetes_service" "alloy_otlp" {
|
|
||||||
metadata {
|
|
||||||
name = "alloy-otlp"
|
|
||||||
namespace = kubernetes_namespace.monitoring.metadata[0].name
|
|
||||||
labels = { app = "alloy-otlp" }
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
selector = { "app.kubernetes.io/name" = "alloy" }
|
|
||||||
port {
|
|
||||||
name = "otlp-http"
|
|
||||||
port = 4318
|
|
||||||
target_port = 4318
|
|
||||||
protocol = "TCP"
|
|
||||||
}
|
|
||||||
type = "ClusterIP"
|
|
||||||
}
|
|
||||||
depends_on = [helm_release.alloy]
|
|
||||||
}
|
|
||||||
|
|
||||||
# BasicAuth secret for Traefik middleware — pods authenticate with
|
|
||||||
# Authorization: Basic base64(pantheon:<ALLOY_PUSH_TOKEN>)
|
|
||||||
resource "kubernetes_secret" "alloy_otlp_auth" {
|
|
||||||
metadata {
|
|
||||||
name = "alloy-otlp-auth"
|
|
||||||
namespace = kubernetes_namespace.monitoring.metadata[0].name
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
# htpasswd entry — bcrypt hash of the push token, username "pantheon"
|
|
||||||
users = "pantheon:${bcrypt(var.alloy_push_token)}"
|
|
||||||
}
|
|
||||||
depends_on = [kubernetes_namespace.monitoring]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,3 +17,13 @@ resource "kubernetes_namespace" "ci" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_namespace" "external_secrets" {
|
||||||
|
metadata {
|
||||||
|
name = "external-secrets"
|
||||||
|
labels = {
|
||||||
|
managed-by = "terraform"
|
||||||
|
tier = "infrastructure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-125
@@ -1,12 +1,6 @@
|
|||||||
# Neuron — MCP server + Axon webhook hub
|
# Neuron — MCP server + Axon webhook hub
|
||||||
# MCP: https://neuron.nook.family (internal, Traefik)
|
# Namespace only — all other resources managed by Argo CD + ESO
|
||||||
# Webhooks: https://axon.neuralplatform.ai (external, Cloudflare tunnel)
|
# See: apps/neuron.yaml, apps/neuron-config.yaml, k8s/neuron/
|
||||||
#
|
|
||||||
# Image: registry.nook.family/neural-platform/neuron:latest
|
|
||||||
# Built by GitHub Actions runner on Legion — see neural-platform/neuron ci.yml
|
|
||||||
#
|
|
||||||
# TODO: Migrate SQLite → Postgres once neuron db migration is ready
|
|
||||||
# TODO: Wire Gmail OAuth credentials via k8s Secret (currently unauthenticated)
|
|
||||||
|
|
||||||
resource "kubernetes_namespace" "neuron" {
|
resource "kubernetes_namespace" "neuron" {
|
||||||
metadata {
|
metadata {
|
||||||
@@ -17,120 +11,3 @@ resource "kubernetes_namespace" "neuron" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "kubernetes_persistent_volume_claim" "neuron_data" {
|
|
||||||
metadata {
|
|
||||||
name = "neuron-data"
|
|
||||||
namespace = kubernetes_namespace.neuron.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
access_modes = ["ReadWriteOnce"]
|
|
||||||
resources {
|
|
||||||
requests = {
|
|
||||||
storage = "5Gi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_config_map" "neuron_config" {
|
|
||||||
metadata {
|
|
||||||
name = "neuron-config"
|
|
||||||
namespace = kubernetes_namespace.neuron.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
MCP_PORT = "8001"
|
|
||||||
MCP_HOST = "0.0.0.0"
|
|
||||||
WEBHOOK_PORT = "3847"
|
|
||||||
NEURON_DB_PATH = "/data/neuron.db"
|
|
||||||
AXON_DB_PATH = "/data/axon.db"
|
|
||||||
TUNNEL_NAME = "neural-platform"
|
|
||||||
|
|
||||||
"default.yaml" = <<-EOT
|
|
||||||
runtime:
|
|
||||||
name: neuron
|
|
||||||
version: 0.1.0
|
|
||||||
storage:
|
|
||||||
db_path: /data/neuron.db
|
|
||||||
cache:
|
|
||||||
max_l1_size: 1000
|
|
||||||
gc:
|
|
||||||
gen0_threshold: 3
|
|
||||||
gen1_threshold: 5
|
|
||||||
knowledge:
|
|
||||||
base_path: /data/knowledge
|
|
||||||
synapse:
|
|
||||||
path: /data/synapse
|
|
||||||
EOT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_secret" "neuron_secrets" {
|
|
||||||
metadata {
|
|
||||||
name = "neuron-secrets"
|
|
||||||
namespace = kubernetes_namespace.neuron.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
GITHUB_WEBHOOK_SECRET = var.github_webhook_secret
|
|
||||||
GITEA_WEBHOOK_SECRET = var.gitea_webhook_secret
|
|
||||||
SLACK_BOT_TOKEN = var.slack_bot_token
|
|
||||||
SLACK_SIGNING_SECRET = var.slack_signing_secret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deployment + Service + cloudflared managed by Argo CD — see servers/legion/apps/neuron.yaml
|
|
||||||
|
|
||||||
resource "kubernetes_secret" "cloudflared_secret" {
|
|
||||||
metadata {
|
|
||||||
name = "cloudflared-secret"
|
|
||||||
namespace = kubernetes_namespace.neuron.metadata[0].name
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
TUNNEL_TOKEN = var.cloudflared_tunnel_token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Internal MCP ingress — accessible within nook.family network
|
|
||||||
resource "kubernetes_ingress_v1" "neuron" {
|
|
||||||
metadata {
|
|
||||||
name = "neuron"
|
|
||||||
namespace = kubernetes_namespace.neuron.metadata[0].name
|
|
||||||
annotations = {
|
|
||||||
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
|
||||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
ingress_class_name = "traefik"
|
|
||||||
|
|
||||||
tls {
|
|
||||||
secret_name = "neuron-tls"
|
|
||||||
hosts = ["neuron.${var.infra_domain}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
rule {
|
|
||||||
host = "neuron.${var.infra_domain}"
|
|
||||||
http {
|
|
||||||
path {
|
|
||||||
path = "/"
|
|
||||||
path_type = "Prefix"
|
|
||||||
backend {
|
|
||||||
service {
|
|
||||||
name = "neuron"
|
|
||||||
port {
|
|
||||||
number = 8001
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depends_on = [helm_release.cert_manager]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# Ollama — local LLM inference with GPU
|
# Ollama — namespace only
|
||||||
|
# PVC, Deployment, Service, Ingress managed by Argo CD
|
||||||
|
# See: apps/ollama.yaml, apps/ollama-config.yaml, k8s/ollama/
|
||||||
|
|
||||||
resource "kubernetes_namespace" "ollama" {
|
resource "kubernetes_namespace" "ollama" {
|
||||||
metadata {
|
metadata {
|
||||||
name = "ollama"
|
name = "ollama"
|
||||||
@@ -8,21 +11,3 @@ resource "kubernetes_namespace" "ollama" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "kubernetes_persistent_volume_claim" "ollama_models" {
|
|
||||||
metadata {
|
|
||||||
name = "ollama-models"
|
|
||||||
namespace = kubernetes_namespace.ollama.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
access_modes = ["ReadWriteOnce"]
|
|
||||||
resources {
|
|
||||||
requests = {
|
|
||||||
storage = "50Gi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deployment + Service managed by Argo CD — see servers/legion/apps/ollama.yaml
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Package registries — Verdaccio (npm) and devpi (PyPI)
|
# Package registries — namespace only
|
||||||
# Both proxy to public registries and cache locally on Legion
|
# PVCs, ConfigMap, Ingresses managed by Argo CD
|
||||||
|
# See: apps/packages.yaml, apps/packages-config.yaml, k8s/packages/
|
||||||
|
|
||||||
resource "kubernetes_namespace" "packages" {
|
resource "kubernetes_namespace" "packages" {
|
||||||
metadata {
|
metadata {
|
||||||
@@ -10,69 +11,3 @@ resource "kubernetes_namespace" "packages" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "kubernetes_persistent_volume_claim" "verdaccio_data" {
|
|
||||||
metadata {
|
|
||||||
name = "verdaccio-data"
|
|
||||||
namespace = kubernetes_namespace.packages.metadata[0].name
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
access_modes = ["ReadWriteOnce"]
|
|
||||||
resources {
|
|
||||||
requests = { storage = "10Gi" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "kubernetes_persistent_volume_claim" "devpi_data" {
|
|
||||||
metadata {
|
|
||||||
name = "devpi-data"
|
|
||||||
namespace = kubernetes_namespace.packages.metadata[0].name
|
|
||||||
}
|
|
||||||
spec {
|
|
||||||
access_modes = ["ReadWriteOnce"]
|
|
||||||
resources {
|
|
||||||
requests = { storage = "10Gi" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verdaccio — npm proxy/cache
|
|
||||||
resource "kubernetes_config_map" "verdaccio_config" {
|
|
||||||
metadata {
|
|
||||||
name = "verdaccio-config"
|
|
||||||
namespace = kubernetes_namespace.packages.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"config.yaml" = <<-EOT
|
|
||||||
storage: /verdaccio/storage
|
|
||||||
auth:
|
|
||||||
htpasswd:
|
|
||||||
file: /verdaccio/storage/htpasswd
|
|
||||||
max_users: 1
|
|
||||||
uplinks:
|
|
||||||
npmjs:
|
|
||||||
url: https://registry.npmjs.org/
|
|
||||||
cache: true
|
|
||||||
packages:
|
|
||||||
'@harmonic-framework/*':
|
|
||||||
access: $authenticated
|
|
||||||
publish: $authenticated
|
|
||||||
unpublish: $authenticated
|
|
||||||
'**':
|
|
||||||
access: $all
|
|
||||||
publish: $authenticated
|
|
||||||
proxy: npmjs
|
|
||||||
listen: 0.0.0.0:4873
|
|
||||||
server:
|
|
||||||
keepAliveTimeout: 60
|
|
||||||
log:
|
|
||||||
type: stdout
|
|
||||||
format: pretty
|
|
||||||
level: http
|
|
||||||
EOT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deployments + Services managed by Argo CD — see servers/legion/apps/packages.yaml
|
|
||||||
|
|||||||
@@ -1,34 +1,3 @@
|
|||||||
# Shared Postgres — Kong, app databases
|
# Shared Postgres — namespace only
|
||||||
resource "helm_release" "postgres" {
|
# Helm release and secrets managed by Argo CD + ESO
|
||||||
name = "postgres"
|
# See: apps/postgres.yaml, apps/postgres-config.yaml, k8s/postgres/
|
||||||
namespace = kubernetes_namespace.platform.metadata[0].name
|
|
||||||
repository = "oci://registry-1.docker.io/bitnamicharts"
|
|
||||||
chart = "postgresql"
|
|
||||||
|
|
||||||
timeout = 600
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "auth.postgresPassword"
|
|
||||||
value = var.postgres_password
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "primary.persistence.size"
|
|
||||||
value = "20Gi"
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "primary.resources.requests.memory"
|
|
||||||
value = "256Mi"
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "primary.resources.requests.cpu"
|
|
||||||
value = "100m"
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "primary.initdb.scripts.create-kong-db\\.sql"
|
|
||||||
value = "CREATE DATABASE kong; CREATE USER kong WITH PASSWORD '${var.kong_db_password}'; GRANT ALL PRIVILEGES ON DATABASE kong TO kong; ALTER DATABASE kong OWNER TO kong;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-34
@@ -1,34 +1,3 @@
|
|||||||
# Shared Redis — caching, session store, pub/sub
|
# Shared Redis — namespace only
|
||||||
resource "helm_release" "redis" {
|
# Helm release managed by Argo CD
|
||||||
name = "redis"
|
# See: apps/redis.yaml
|
||||||
namespace = kubernetes_namespace.platform.metadata[0].name
|
|
||||||
repository = "oci://registry-1.docker.io/bitnamicharts"
|
|
||||||
chart = "redis"
|
|
||||||
|
|
||||||
timeout = 600
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "auth.enabled"
|
|
||||||
value = "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "architecture"
|
|
||||||
value = "standalone"
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "master.persistence.size"
|
|
||||||
value = "5Gi"
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "master.resources.requests.memory"
|
|
||||||
value = "128Mi"
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
name = "master.resources.requests.cpu"
|
|
||||||
value = "50m"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# Local Docker Registry — push images locally, no DockerHub
|
# Local Docker Registry — namespace only
|
||||||
|
# PVC, Deployment, Service, Ingresses managed by Argo CD
|
||||||
|
# See: apps/registry.yaml, apps/registry-config.yaml, k8s/registry/
|
||||||
|
|
||||||
resource "kubernetes_namespace" "registry" {
|
resource "kubernetes_namespace" "registry" {
|
||||||
metadata {
|
metadata {
|
||||||
name = "registry"
|
name = "registry"
|
||||||
@@ -8,21 +11,3 @@ resource "kubernetes_namespace" "registry" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "kubernetes_persistent_volume_claim" "registry_data" {
|
|
||||||
metadata {
|
|
||||||
name = "registry-data"
|
|
||||||
namespace = kubernetes_namespace.registry.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
spec {
|
|
||||||
access_modes = ["ReadWriteOnce"]
|
|
||||||
resources {
|
|
||||||
requests = {
|
|
||||||
storage = "50Gi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deployments + Services managed by Argo CD — see servers/legion/apps/registry.yaml
|
|
||||||
|
|||||||
+1
-109
@@ -11,7 +11,7 @@ variable "legion_ip" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
variable "domain_suffix" {
|
variable "domain_suffix" {
|
||||||
description = "Internal-only domain (nook.family) — not exposed via Cloudflare tunnel. Use infra_domain for public services."
|
description = "Internal-only domain (nook.family)"
|
||||||
type = string
|
type = string
|
||||||
default = "nook.family"
|
default = "nook.family"
|
||||||
}
|
}
|
||||||
@@ -34,116 +34,8 @@ variable "cloudflare_email" {
|
|||||||
default = "andersonwilliam85@gmail.com"
|
default = "andersonwilliam85@gmail.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "gitea_domain" {
|
|
||||||
description = "Domain for Gitea git server"
|
|
||||||
type = string
|
|
||||||
default = "git.neuralplatform.ai"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "gitea_admin_password" {
|
|
||||||
description = "Gitea admin user password"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "gitea_db_password" {
|
|
||||||
description = "Gitea database password"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "postgres_password" {
|
|
||||||
description = "Postgres superuser password"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "kong_db_password" {
|
|
||||||
description = "Kong database password"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "grafana_admin_password" {
|
|
||||||
description = "Grafana admin password"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "github_classic_pat" {
|
|
||||||
description = "GitHub classic PAT with admin:org scope for runner registration"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "github_org" {
|
|
||||||
description = "GitHub organization"
|
|
||||||
type = string
|
|
||||||
default = "harmonic-framework"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "r2_endpoint" {
|
|
||||||
description = "Cloudflare R2 S3-compatible endpoint"
|
|
||||||
type = string
|
|
||||||
default = "https://651161e0a3d321561b4c90b5bcd5f15b.r2.cloudflarestorage.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "r2_access_key_id" {
|
|
||||||
description = "Cloudflare R2 access key ID"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "r2_secret_access_key" {
|
|
||||||
description = "Cloudflare R2 secret access key"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cloudflared_tunnel_token" {
|
|
||||||
description = "Cloudflare tunnel token for neural-platform tunnel"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "slack_bot_token" {
|
|
||||||
description = "Slack bot token for Neuron bot"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "slack_signing_secret" {
|
|
||||||
description = "Slack signing secret for webhook verification"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "github_webhook_secret" {
|
|
||||||
description = "GitHub webhook secret for Axon event ingestion"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "gitea_webhook_secret" {
|
|
||||||
description = "Gitea webhook secret for Axon event ingestion"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "gitea_api_token" {
|
variable "gitea_api_token" {
|
||||||
description = "Gitea API token for Argo CD repo access"
|
description = "Gitea API token for Argo CD repo access"
|
||||||
type = string
|
type = string
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "gitea_runner_token" {
|
|
||||||
description = "Gitea act-runner registration token"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "alloy_push_token" {
|
|
||||||
description = "Bearer token required to push OTLP logs/traces to alloy.neuralplatform.ai"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|||||||
+5
-99
@@ -1,6 +1,8 @@
|
|||||||
# Vault — secrets management with GCP KMS auto-unseal
|
# Vault — namespace only
|
||||||
# UI: https://vault.neuralplatform.ai
|
# Helm release and GCP SA secret managed by Argo CD + ESO
|
||||||
# Auto-unseal: GCP KMS projects/neuron-785695/locations/global/keyRings/vault/cryptoKeys/vault-unseal
|
# See: apps/vault.yaml, apps/vault-config.yaml, k8s/vault/
|
||||||
|
# Note: vault-gcp-sa.json must be stored in Vault at secret/vault-gcp-sa (sa_json field)
|
||||||
|
# before the ESO ExternalSecret can sync it.
|
||||||
|
|
||||||
resource "kubernetes_namespace" "vault" {
|
resource "kubernetes_namespace" "vault" {
|
||||||
metadata {
|
metadata {
|
||||||
@@ -11,99 +13,3 @@ resource "kubernetes_namespace" "vault" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "kubernetes_secret" "vault_gcp_sa" {
|
|
||||||
metadata {
|
|
||||||
name = "vault-gcp-sa"
|
|
||||||
namespace = kubernetes_namespace.vault.metadata[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"sa.json" = file("${path.module}/vault-gcp-sa.json")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "helm_release" "vault" {
|
|
||||||
name = "vault"
|
|
||||||
namespace = kubernetes_namespace.vault.metadata[0].name
|
|
||||||
repository = "https://helm.releases.hashicorp.com"
|
|
||||||
chart = "vault"
|
|
||||||
version = "0.29.1"
|
|
||||||
timeout = 300
|
|
||||||
|
|
||||||
values = [<<-EOT
|
|
||||||
server:
|
|
||||||
image:
|
|
||||||
repository: hashicorp/vault
|
|
||||||
tag: "1.18.3"
|
|
||||||
|
|
||||||
extraEnvironmentVars:
|
|
||||||
GOOGLE_APPLICATION_CREDENTIALS: /vault/gcp/sa.json
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: gcp-sa
|
|
||||||
secret:
|
|
||||||
secretName: vault-gcp-sa
|
|
||||||
|
|
||||||
volumeMounts:
|
|
||||||
- name: gcp-sa
|
|
||||||
mountPath: /vault/gcp
|
|
||||||
readOnly: true
|
|
||||||
|
|
||||||
ha:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
dataStorage:
|
|
||||||
enabled: true
|
|
||||||
size: 10Gi
|
|
||||||
|
|
||||||
standalone:
|
|
||||||
enabled: true
|
|
||||||
config: |
|
|
||||||
ui = true
|
|
||||||
|
|
||||||
listener "tcp" {
|
|
||||||
tls_disable = 1
|
|
||||||
address = "[::]:8200"
|
|
||||||
cluster_address = "[::]:8201"
|
|
||||||
}
|
|
||||||
|
|
||||||
storage "file" {
|
|
||||||
path = "/vault/data"
|
|
||||||
}
|
|
||||||
|
|
||||||
seal "gcpckms" {
|
|
||||||
project = "neuron-785695"
|
|
||||||
region = "global"
|
|
||||||
key_ring = "vault"
|
|
||||||
crypto_key = "vault-unseal"
|
|
||||||
}
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
ingressClassName: traefik
|
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
||||||
hosts:
|
|
||||||
- host: vault.${var.infra_domain}
|
|
||||||
paths:
|
|
||||||
- /
|
|
||||||
tls:
|
|
||||||
- secretName: vault-tls
|
|
||||||
hosts:
|
|
||||||
- vault.${var.infra_domain}
|
|
||||||
|
|
||||||
ui:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
injector:
|
|
||||||
enabled: false
|
|
||||||
EOT
|
|
||||||
]
|
|
||||||
|
|
||||||
depends_on = [
|
|
||||||
kubernetes_secret.vault_gcp_sa,
|
|
||||||
helm_release.cert_manager,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user