diff --git a/main.ts b/main.ts index c1caa17..c414eb6 100644 --- a/main.ts +++ b/main.ts @@ -1,7 +1,7 @@ import * as dotenv from "dotenv"; import { cleanEnv, str } from "envalid"; import { Construct } from "constructs"; -import { App, TerraformStack, LocalBackend } from "cdktf"; +import { App, TerraformStack, LocalBackend, PgBackend } from "cdktf"; import { HelmProvider } from "@cdktf/provider-helm/lib/provider"; import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; import { NamespaceV1 } from "@cdktf/provider-kubernetes/lib/namespace-v1"; @@ -13,16 +13,18 @@ import { Longhorn } from "./longhorn"; import { AuthentikServer } from "./authentik"; import { ValkeyCluster } from "./valkey"; import { CertManager } from "./cert-manager"; -import { Nginx } from "./nginx"; import { Traefik } from "./traefik"; import { Prometheus } from "./prometheus"; import { MetalLB } from "./metallb"; -import { ExternalDNS } from "./external-dns"; +import { NixCache } from "./nixcache"; dotenv.config(); const env = cleanEnv(process.env, { ACCOUNT_ID: str({ desc: "Cloudflare account id." }), + PG_CONN_STR: str({ + desc: "PostgreSQL connection string for Terraform state backend.", + }), }); const r2Endpoint = `https://${env.ACCOUNT_ID}.r2.cloudflarestorage.com`; @@ -69,12 +71,6 @@ class Homelab extends TerraformStack { namespace, }); - const nginx = new Nginx(this, "nginx", { - provider: helm, - namespace, - name: "nginx-ingress", - }); - new Traefik(this, "traefik", { provider: helm, namespace, @@ -83,7 +79,7 @@ class Homelab extends TerraformStack { const certManagerApiVersion = "cert-manager.io/v1"; - const cm = new CertManager(this, "cert-manager", { + new CertManager(this, "cert-manager", { certManagerApiVersion, name: "cert-manager", namespace, @@ -94,15 +90,6 @@ class Homelab extends TerraformStack { }, }); - const externalDNS = new ExternalDNS(this, "external-dns", { - namespace, - provider: helm, - name: "external-dns", - }); - - externalDNS.node.addDependency(nginx); - externalDNS.node.addDependency(cm); - new Prometheus(this, "prometheus", { provider: helm, namespace, @@ -154,11 +141,19 @@ class Homelab extends TerraformStack { } const app = new App(); -const stack = new Homelab(app, "homelab"); +const homelab = new Homelab(app, "homelab"); -new LocalBackend(stack, { +const nixCache = new NixCache(app, "nix-cache"); +nixCache.node.addDependency(homelab); + +new LocalBackend(homelab, { path: "terraform.tfstate", workspaceDir: ".", }); +new PgBackend(nixCache, { + schemaName: "nix_cache", + connStr: env.PG_CONN_STR, +}); + app.synth(); diff --git a/nixcache/index.ts b/nixcache/index.ts new file mode 100644 index 0000000..0da1755 --- /dev/null +++ b/nixcache/index.ts @@ -0,0 +1,147 @@ +import * as fs from "fs"; +import * as path from "path"; +import { Construct } from "constructs"; +import { TerraformStack } from "cdktf"; +import { PersistentVolumeClaimV1 } from "@cdktf/provider-kubernetes/lib/persistent-volume-claim-v1"; +import { ConfigMapV1 } from "@cdktf/provider-kubernetes/lib/config-map-v1"; + +import { DeploymentV1 } from "@cdktf/provider-kubernetes/lib/deployment-v1"; +import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; +import { TraefikIngressRoute } from "../traefik/ingress-route"; +import { ServiceV1 } from "@cdktf/provider-kubernetes/lib/service-v1"; + +export class NixCache extends TerraformStack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const kubernetes = new KubernetesProvider(this, "kubernetes", { + configPath: "~/.kube/config", + }); + + const pvc = new PersistentVolumeClaimV1(this, "pvc", { + provider: kubernetes, + metadata: { + name: "nix-cache", + namespace: "homelab", + }, + spec: { + storageClassName: "longhorn", + accessModes: ["ReadWriteMany"], + resources: { + requests: { + storage: "64Gi", + }, + }, + }, + }); + + const nginxConfig = fs.readFileSync( + path.join(__dirname, "./nginx.conf"), + "utf-8", + ); + + const configMap = new ConfigMapV1(this, "config-map", { + provider: kubernetes, + metadata: { + name: "nix-cache", + namespace: "homelab", + }, + data: { + "nix-cache.conf": nginxConfig, + }, + }); + + new ServiceV1(this, "service", { + provider: kubernetes, + metadata: { + name: "nix-cache", + namespace: "homelab", + }, + spec: { + selector: { + app: "nix-cache", + }, + port: [ + { + name: "http", + port: 80, + targetPort: "80", + }, + ], + type: "ClusterIP", + }, + }); + + new DeploymentV1(this, "deployment", { + provider: kubernetes, + metadata: { + name: "nix-cache", + namespace: "homelab", + }, + spec: { + replicas: "3", + selector: { + matchLabels: { + app: "nix-cache", + }, + }, + template: { + metadata: { + labels: { + app: "nix-cache", + }, + }, + spec: { + container: [ + { + name: "nginx", + image: "nginx:latest", + volumeMount: [ + { + name: "cache", + mountPath: "/var/cache/nginx/nix", + }, + { + name: "nginx-config", + mountPath: "/etc/nginx/conf.d/nix-cache.conf", + subPath: "nix-cache.conf", + }, + ], + }, + ], + volume: [ + { + name: "cache", + persistentVolumeClaim: { + claimName: pvc.metadata.name, + }, + }, + { + name: "nginx-config", + configMap: { + name: configMap.metadata.name, + items: [ + { + key: "nix-cache.conf", + path: "nix-cache.conf", + }, + ], + }, + }, + ], + }, + }, + }, + }); + + new TraefikIngressRoute(this, "ingress-route", { + provider: kubernetes, + namespace: "homelab", + host: "nix.dogar.dev", + serviceName: "nix-cache", + servicePort: 80, + entryPoints: ["websecure"], + tlsSecretName: "nix-cache-tls", + }); + } +} diff --git a/nixcache/nginx.conf b/nixcache/nginx.conf new file mode 100644 index 0000000..5085187 --- /dev/null +++ b/nixcache/nginx.conf @@ -0,0 +1,32 @@ +proxy_cache_path /var/cache/nginx/nix levels=1:2 keys_zone=nix_cache:32m max_size=60g inactive=365d use_temp_path=off; + +map $status $cache_header { + 200 "public"; + 302 "public"; + default "no-cache"; +} + +server { + listen 80; + server_name nix.dogar.dev; + + location / { + proxy_pass https://cache.nixos.org; + proxy_http_version 1.1; + + proxy_set_header Host cache.nixos.org; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_ssl_server_name on; + + proxy_cache nix_cache; + proxy_cache_valid 200 302 365d; + proxy_cache_valid any 1m; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; + + add_header X-Cache-Status $upstream_cache_status always; + add_header Cache-Control $cache_header always; + } +}