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" {
|
||||
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
|
||||
# Dumps all Postgres databases + Gitea data nightly at 2am
|
||||
# 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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Legion backup — CronJob and secret managed by Argo CD + ESO
|
||||
# See: apps/backup.yaml, k8s/backup/
|
||||
|
||||
@@ -1,73 +1,3 @@
|
||||
# ddclient — dynamic DNS daemon that keeps dot.nook.family pointing at the home public IP
|
||||
# Updates Cloudflare A record every 5 minutes; required for DoT (port 853) which can't use Cloudflare proxy
|
||||
|
||||
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]
|
||||
}
|
||||
# ddclient — dynamic DNS daemon
|
||||
# Deployment and config secret managed by Argo CD + ESO
|
||||
# See: apps/adguard-config.yaml, k8s/adguard/ddclient-*.yaml
|
||||
|
||||
@@ -1,14 +1,2 @@
|
||||
# Gitea act-runner — executes Gitea Actions CI jobs on Legion
|
||||
# Registered at the instance level so it can serve all repos/orgs
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
# Gitea act-runner — secrets managed by Argo CD + ESO
|
||||
# See: apps/gitea-runner.yaml, apps/ci-config.yaml, k8s/gitea-runner/
|
||||
|
||||
+2
-176
@@ -1,117 +1,6 @@
|
||||
# Gitea — local git server for private/sensitive infrastructure repos
|
||||
# Accessible at git.neuralplatform.ai (internal only, real TLS)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
# Namespace only — all other resources managed by Argo CD + ESO
|
||||
# See: apps/gitea.yaml, apps/gitea-config.yaml, k8s/gitea/
|
||||
|
||||
resource "kubernetes_namespace" "git" {
|
||||
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
|
||||
# Deployment managed by Argo CD — see servers/legion/apps/github-runner.yaml
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
# GitHub Actions Self-Hosted Runner — secrets managed by Argo CD + ESO
|
||||
# See: apps/github-runner.yaml, apps/ci-config.yaml, k8s/github-runner/
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Headscale — self-hosted Tailscale control plane
|
||||
# Coordination endpoint: https://headscale.neuralplatform.ai (Cloudflare tunnel)
|
||||
# Clients: Tailscale app on Mac/iOS/Android, pointed at custom server
|
||||
# Subnet router: tailscaled on Legion host, advertises k3s + LAN CIDRs
|
||||
# Headscale — namespace only
|
||||
# PVC, Deployment, Service managed by Argo CD
|
||||
# See: apps/headscale.yaml, apps/headscale-config.yaml, k8s/headscale/
|
||||
|
||||
resource "kubernetes_namespace" "headscale" {
|
||||
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
|
||||
# Kong and open-webui ingresses moved to inactive/ — restore when needed
|
||||
|
||||
resource "kubernetes_ingress_v1" "ollama" {
|
||||
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]
|
||||
}
|
||||
# Ingresses — all moved to k8s/<service>/ingress.yaml, managed by Argo CD
|
||||
# See: k8s/neuron/ingress.yaml, k8s/gitea/ingress.yaml, k8s/ollama/ingress.yaml,
|
||||
# k8s/registry/ingress.yaml, k8s/packages/ingress.yaml,
|
||||
# k8s/monitoring/ingress.yaml, k8s/adguard/ingress.yaml
|
||||
|
||||
@@ -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
|
||||
# Grafana UI: https://grafana.neuralplatform.ai (admin / var.grafana_admin_password)
|
||||
#
|
||||
# TODO: Migrate Tempo to tempo-distributed once R2 is wired as trace storage backend
|
||||
# TODO: Replace loki.useTestSchema=true with proper schema_config for production
|
||||
# Monitoring stack — Prometheus, Grafana, Loki, Tempo, Alloy
|
||||
# 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
|
||||
# See: apps/monitoring-config.yaml, k8s/monitoring/
|
||||
|
||||
resource "kubernetes_namespace" "monitoring" {
|
||||
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
|
||||
# MCP: https://neuron.nook.family (internal, Traefik)
|
||||
# Webhooks: https://axon.neuralplatform.ai (external, Cloudflare tunnel)
|
||||
#
|
||||
# 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)
|
||||
# Namespace only — all other resources managed by Argo CD + ESO
|
||||
# See: apps/neuron.yaml, apps/neuron-config.yaml, k8s/neuron/
|
||||
|
||||
resource "kubernetes_namespace" "neuron" {
|
||||
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" {
|
||||
metadata {
|
||||
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)
|
||||
# Both proxy to public registries and cache locally on Legion
|
||||
# Package registries — namespace only
|
||||
# PVCs, ConfigMap, Ingresses managed by Argo CD
|
||||
# See: apps/packages.yaml, apps/packages-config.yaml, k8s/packages/
|
||||
|
||||
resource "kubernetes_namespace" "packages" {
|
||||
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
|
||||
resource "helm_release" "postgres" {
|
||||
name = "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;"
|
||||
}
|
||||
}
|
||||
# Shared Postgres — namespace only
|
||||
# Helm release and secrets managed by Argo CD + ESO
|
||||
# See: apps/postgres.yaml, apps/postgres-config.yaml, k8s/postgres/
|
||||
|
||||
+3
-34
@@ -1,34 +1,3 @@
|
||||
# Shared Redis — caching, session store, pub/sub
|
||||
resource "helm_release" "redis" {
|
||||
name = "redis"
|
||||
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"
|
||||
}
|
||||
}
|
||||
# Shared Redis — namespace only
|
||||
# Helm release managed by Argo CD
|
||||
# See: apps/redis.yaml
|
||||
|
||||
@@ -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" {
|
||||
metadata {
|
||||
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" {
|
||||
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
|
||||
default = "nook.family"
|
||||
}
|
||||
@@ -34,116 +34,8 @@ variable "cloudflare_email" {
|
||||
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" {
|
||||
description = "Gitea API token for Argo CD repo access"
|
||||
type = string
|
||||
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
|
||||
# UI: https://vault.neuralplatform.ai
|
||||
# Auto-unseal: GCP KMS projects/neuron-785695/locations/global/keyRings/vault/cryptoKeys/vault-unseal
|
||||
# Vault — namespace only
|
||||
# Helm release and GCP SA secret managed by Argo CD + ESO
|
||||
# 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" {
|
||||
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