feat: organize all services into separate stacks by dependency
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
import * as fs from "fs";
|
||||
import { Construct } from "constructs";
|
||||
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
|
||||
type OnePasswordSecret = {
|
||||
id?: string;
|
||||
namespace?: string;
|
||||
name: string;
|
||||
itemPath: string;
|
||||
};
|
||||
|
||||
type OnePasswordOptions = {
|
||||
provider: KubernetesProvider;
|
||||
namespace: string;
|
||||
};
|
||||
|
||||
export class OnePassword extends Construct {
|
||||
constructor(scope: Construct, id: string, options: OnePasswordOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const secrets: OnePasswordSecret[] = JSON.parse(
|
||||
fs.readFileSync("1password/secrets.json", {
|
||||
encoding: "utf8",
|
||||
}),
|
||||
);
|
||||
|
||||
secrets.forEach((secret) => {
|
||||
new Manifest(this, secret.id ?? secret.name, {
|
||||
provider: options.provider,
|
||||
manifest: {
|
||||
apiVersion: "onepassword.com/v1",
|
||||
kind: "OnePasswordItem",
|
||||
metadata: {
|
||||
name: secret.name,
|
||||
namespace: secret.namespace ?? options.namespace,
|
||||
annotations: {
|
||||
"operator.1password.io/auto-restart": "true",
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
itemPath: secret.itemPath,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "gitea-admin",
|
||||
"itemPath": "vaults/Lab/items/gitea-admin"
|
||||
},
|
||||
{
|
||||
"name": "pihole-admin",
|
||||
"itemPath": "vaults/Lab/items/pihole"
|
||||
},
|
||||
{
|
||||
"name": "postgres-password",
|
||||
"itemPath": "vaults/Lab/items/Postgres"
|
||||
},
|
||||
{
|
||||
"name": "runner-secret",
|
||||
"itemPath": "vaults/Lab/items/Gitea"
|
||||
},
|
||||
{
|
||||
"name": "cloudflare-token",
|
||||
"itemPath": "vaults/Lab/items/cloudflare"
|
||||
},
|
||||
{
|
||||
"id": "cloudflare-token-longhorn",
|
||||
"name": "cloudflare-token",
|
||||
"itemPath": "vaults/Lab/items/cloudflare",
|
||||
"namespace": "longhorn-system"
|
||||
},
|
||||
{
|
||||
"name": "valkey",
|
||||
"itemPath": "vaults/Lab/items/valkey"
|
||||
},
|
||||
{
|
||||
"name": "gitea-oauth",
|
||||
"itemPath": "vaults/Lab/items/gitea-oauth"
|
||||
},
|
||||
{
|
||||
"name": "gitea-elasticsearch",
|
||||
"itemPath": "vaults/Lab/items/gitea-elasticsearch"
|
||||
},
|
||||
{
|
||||
"name": "smtp-token",
|
||||
"itemPath": "vaults/Lab/items/smtp-token"
|
||||
},
|
||||
{
|
||||
"name": "longhorn-encryption",
|
||||
"itemPath": "vaults/Lab/items/longhorn-encryption"
|
||||
},
|
||||
{
|
||||
"name": "authentik-secret-key",
|
||||
"itemPath": "vaults/Lab/items/authentik-secret-key"
|
||||
},
|
||||
{
|
||||
"name": "curseforge",
|
||||
"itemPath": "vaults/Lab/items/curseforge"
|
||||
},
|
||||
{
|
||||
"name": "devpi-secret",
|
||||
"itemPath": "vaults/Lab/items/devpi-secret"
|
||||
}
|
||||
]
|
||||
@@ -1,28 +0,0 @@
|
||||
import * as fs from "fs";
|
||||
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
|
||||
import { Release } from "@cdktf/provider-helm/lib/release";
|
||||
import { Construct } from "constructs";
|
||||
|
||||
type AuthentikServerOptions = {
|
||||
provider: HelmProvider;
|
||||
name: string;
|
||||
namespace: string;
|
||||
};
|
||||
|
||||
export class AuthentikServer extends Construct {
|
||||
constructor(scope: Construct, id: string, options: AuthentikServerOptions) {
|
||||
super(scope, id);
|
||||
|
||||
new Release(this, id, {
|
||||
...options,
|
||||
repository: "https://charts.goauthentik.io",
|
||||
chart: "authentik",
|
||||
createNamespace: true,
|
||||
values: [
|
||||
fs.readFileSync("helm/values/authentik.values.yaml", {
|
||||
encoding: "utf8",
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
1085
barman.yaml
Normal file
1085
barman.yaml
Normal file
File diff suppressed because it is too large
Load Diff
17
cache-infrastructure/index.ts
Normal file
17
cache-infrastructure/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Construct } from "constructs";
|
||||
import { TerraformStack } from "cdktf";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
import { NixCache } from "./nixcache";
|
||||
|
||||
export class CacheInfrastructure extends TerraformStack {
|
||||
constructor(scope: Construct, id: string) {
|
||||
super(scope, id);
|
||||
|
||||
const kubernetes = new KubernetesProvider(this, "kubernetes", {
|
||||
configPath: "~/.kube/config",
|
||||
});
|
||||
|
||||
// Add cache-related infrastructure components here
|
||||
new NixCache(this, "nix-cache", kubernetes);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,18 @@
|
||||
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 { IngressRoute } from "../../utils";
|
||||
import { ServiceV1 } from "@cdktf/provider-kubernetes/lib/service-v1";
|
||||
|
||||
export class NixCache extends TerraformStack {
|
||||
constructor(scope: Construct, id: string) {
|
||||
export class NixCache extends Construct {
|
||||
constructor(scope: Construct, id: string, kubernetes: KubernetesProvider) {
|
||||
super(scope, id);
|
||||
|
||||
const kubernetes = new KubernetesProvider(this, "kubernetes", {
|
||||
configPath: "~/.kube/config",
|
||||
});
|
||||
|
||||
const pvc = new PersistentVolumeClaimV1(this, "pvc", {
|
||||
provider: kubernetes,
|
||||
metadata: {
|
||||
@@ -134,7 +129,7 @@ export class NixCache extends TerraformStack {
|
||||
},
|
||||
});
|
||||
|
||||
new TraefikIngressRoute(this, "ingress-route", {
|
||||
new IngressRoute(this, "ingress-route", {
|
||||
provider: kubernetes,
|
||||
namespace: "homelab",
|
||||
host: "nix.dogar.dev",
|
||||
@@ -1,69 +0,0 @@
|
||||
import * as fs from "fs";
|
||||
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
|
||||
import { Release } from "@cdktf/provider-helm/lib/release";
|
||||
import { Construct } from "constructs";
|
||||
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
|
||||
type GiteaServerOptions = {
|
||||
providers: {
|
||||
helm: HelmProvider;
|
||||
kubernetes: KubernetesProvider;
|
||||
};
|
||||
name: string;
|
||||
namespace: string;
|
||||
r2Endpoint: string;
|
||||
};
|
||||
|
||||
export class GiteaServer extends Construct {
|
||||
constructor(scope: Construct, id: string, options: GiteaServerOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const { kubernetes, helm } = options.providers;
|
||||
|
||||
new Release(this, id, {
|
||||
...options,
|
||||
provider: helm,
|
||||
repository: "https://dl.gitea.com/charts",
|
||||
chart: "gitea",
|
||||
createNamespace: true,
|
||||
set: [
|
||||
{
|
||||
name: "gitea.config.storage.MINIO_ENDPOINT",
|
||||
value: options.r2Endpoint,
|
||||
},
|
||||
],
|
||||
values: [
|
||||
fs.readFileSync("helm/values/gitea.values.yaml", {
|
||||
encoding: "utf8",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
new Manifest(this, `${id}-ssh-ingress`, {
|
||||
provider: kubernetes,
|
||||
manifest: {
|
||||
apiVersion: "traefik.io/v1alpha1",
|
||||
kind: "IngressRouteTCP",
|
||||
metadata: {
|
||||
name: "gitea-ssh-ingress",
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
entryPoints: ["ssh"],
|
||||
routes: [
|
||||
{
|
||||
match: "HostSNI(`*`)",
|
||||
services: [
|
||||
{
|
||||
name: `${options.name}-ssh`,
|
||||
port: 22,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
42
k8s-operators/barman.ts
Normal file
42
k8s-operators/barman.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Construct } from "constructs";
|
||||
import { NullProvider } from "@cdktf/provider-null/lib/provider";
|
||||
import { Resource } from "@cdktf/provider-null/lib/resource";
|
||||
|
||||
export interface BarmanCloudPluginInstallOptions {
|
||||
/** URL to the CloudNativePG barman-cloud plugin manifest */
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class BarmanCloudPluginInstall extends Construct {
|
||||
constructor(
|
||||
scope: Construct,
|
||||
id: string,
|
||||
opts: BarmanCloudPluginInstallOptions,
|
||||
) {
|
||||
super(scope, id);
|
||||
|
||||
const { url } = opts;
|
||||
|
||||
const applyCmd = ["kubectl", "apply", "-f", url].join(" ");
|
||||
const deleteCmd = ["kubectl", "delete", "-f", url].join(" ");
|
||||
|
||||
new Resource(this, "barman-install", {
|
||||
provider: new NullProvider(this, "barman"),
|
||||
provisioners: [
|
||||
{
|
||||
type: "local-exec",
|
||||
when: "create",
|
||||
command: applyCmd,
|
||||
},
|
||||
{
|
||||
type: "local-exec",
|
||||
when: "destroy",
|
||||
command: deleteCmd,
|
||||
},
|
||||
],
|
||||
triggers: {
|
||||
url,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
72
k8s-operators/index.ts
Normal file
72
k8s-operators/index.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
|
||||
import { Release } from "@cdktf/provider-helm/lib/release";
|
||||
import { TerraformStack } from "cdktf";
|
||||
import { Construct } from "constructs";
|
||||
import { BarmanCloudPluginInstall } from "./barman";
|
||||
import { Prometheus } from "./prometheus";
|
||||
|
||||
export class K8SOperators extends TerraformStack {
|
||||
constructor(scope: Construct, id: string) {
|
||||
super(scope, id);
|
||||
|
||||
const helm = new HelmProvider(this, "helm", {
|
||||
kubernetes: {
|
||||
configPath: "~/.kube/config",
|
||||
},
|
||||
});
|
||||
|
||||
new Prometheus(this, "prometheus", {
|
||||
provider: helm,
|
||||
namespace: "monitoring",
|
||||
name: "prometheus-operator",
|
||||
version: "75.10.0",
|
||||
});
|
||||
|
||||
new Release(this, "onepassword-operator", {
|
||||
provider: helm,
|
||||
name: "onepassword-operator",
|
||||
chart: "connect",
|
||||
repository: "https://1password.github.io/connect-helm-charts/",
|
||||
namespace: "1password",
|
||||
createNamespace: true,
|
||||
set: [
|
||||
{
|
||||
name: "operator.create",
|
||||
value: "true",
|
||||
},
|
||||
],
|
||||
setSensitive: [
|
||||
{
|
||||
name: "operator.token.value",
|
||||
value: process.env.OP_CONNECT_TOKEN!,
|
||||
},
|
||||
{
|
||||
name: "connect.credentials_base64",
|
||||
value: btoa(
|
||||
fs.readFileSync(
|
||||
path.join(__dirname, "1password-credentials.json"),
|
||||
"utf-8",
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const cnpg = new Release(this, "cnpg-operator", {
|
||||
provider: helm,
|
||||
repository: "https://cloudnative-pg.github.io/charts",
|
||||
chart: "cloudnative-pg",
|
||||
name: "postgres-system",
|
||||
namespace: "cnpg-system",
|
||||
createNamespace: true,
|
||||
});
|
||||
|
||||
const barman = new BarmanCloudPluginInstall(this, "barman-cloud-plugin", {
|
||||
url: "https://github.com/cloudnative-pg/plugin-barman-cloud/releases/download/v0.9.0/manifest.yaml",
|
||||
});
|
||||
|
||||
barman.node.addDependency(cnpg);
|
||||
}
|
||||
}
|
||||
113
main.ts
113
main.ts
@@ -1,35 +1,27 @@
|
||||
import * as dotenv from "dotenv";
|
||||
import { cleanEnv, str } from "envalid";
|
||||
import { Construct } from "constructs";
|
||||
import { App, TerraformStack, LocalBackend, PgBackend } from "cdktf";
|
||||
import { App, TerraformStack, LocalBackend, TerraformOutput } 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";
|
||||
|
||||
import { GiteaServer } from "./gitea";
|
||||
import { OnePassword } from "./1password";
|
||||
import { PostgresCluster } from "./postgres";
|
||||
import { Longhorn } from "./longhorn";
|
||||
import { AuthentikServer } from "./authentik";
|
||||
import { ValkeyCluster } from "./valkey";
|
||||
import { CertManager } from "./cert-manager";
|
||||
import { Traefik } from "./traefik";
|
||||
import { Prometheus } from "./prometheus";
|
||||
import { MetalLB } from "./metallb";
|
||||
import { NixCache } from "./nixcache";
|
||||
import { CacheInfrastructure } from "./cache-infrastructure";
|
||||
import { UtilityServices } from "./utility-services";
|
||||
import { K8SOperators } from "./k8s-operators";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const env = cleanEnv(process.env, {
|
||||
cleanEnv(process.env, {
|
||||
ACCOUNT_ID: str({ desc: "Cloudflare account id." }),
|
||||
PG_CONN_STR: str({
|
||||
desc: "PostgreSQL connection string for Terraform state backend.",
|
||||
}),
|
||||
OP_CONNECT_TOKEN: str({ desc: "1Password Connect token." }),
|
||||
});
|
||||
|
||||
const r2Endpoint = `https://${env.ACCOUNT_ID}.r2.cloudflarestorage.com`;
|
||||
|
||||
class Homelab extends TerraformStack {
|
||||
class CoreServices extends TerraformStack {
|
||||
constructor(scope: Construct, id: string) {
|
||||
super(scope, id);
|
||||
|
||||
@@ -52,6 +44,10 @@ class Homelab extends TerraformStack {
|
||||
},
|
||||
});
|
||||
|
||||
new TerraformOutput(this, "namespace-output", {
|
||||
value: namespace,
|
||||
});
|
||||
|
||||
new Longhorn(this, "longhorn", {
|
||||
name: "longhorn",
|
||||
providers: {
|
||||
@@ -66,21 +62,14 @@ class Homelab extends TerraformStack {
|
||||
namespace: "metallb-system",
|
||||
});
|
||||
|
||||
new OnePassword(this, "one-password", {
|
||||
provider: kubernetes,
|
||||
namespace,
|
||||
});
|
||||
|
||||
new Traefik(this, "traefik", {
|
||||
provider: helm,
|
||||
namespace,
|
||||
name: "traefik",
|
||||
});
|
||||
|
||||
const certManagerApiVersion = "cert-manager.io/v1";
|
||||
|
||||
new CertManager(this, "cert-manager", {
|
||||
certManagerApiVersion,
|
||||
certManagerApiVersion: "cert-manager.io/v1",
|
||||
name: "cert-manager",
|
||||
namespace,
|
||||
version: "1.18.2",
|
||||
@@ -89,71 +78,39 @@ class Homelab extends TerraformStack {
|
||||
helm,
|
||||
},
|
||||
});
|
||||
|
||||
new Prometheus(this, "prometheus", {
|
||||
provider: helm,
|
||||
namespace,
|
||||
name: "prometheus-operator",
|
||||
version: "75.10.0",
|
||||
});
|
||||
|
||||
const pg = new PostgresCluster(this, "postgres-cluster", {
|
||||
certManagerApiVersion,
|
||||
name: "postgres-cluster",
|
||||
namespace,
|
||||
providers: {
|
||||
kubernetes,
|
||||
helm,
|
||||
},
|
||||
users: ["shahab", "budget-tracker", "authentik", "gitea"],
|
||||
primaryUser: "shahab",
|
||||
initSecretName: "postgres-password",
|
||||
backupR2EndpointURL: r2Endpoint,
|
||||
});
|
||||
|
||||
const valkey = new ValkeyCluster(this, "valkey-cluster", {
|
||||
provider: kubernetes,
|
||||
namespace,
|
||||
name: "valkey",
|
||||
});
|
||||
|
||||
const authentik = new AuthentikServer(this, "authentik-server", {
|
||||
provider: helm,
|
||||
name: "authentik",
|
||||
namespace,
|
||||
});
|
||||
|
||||
authentik.node.addDependency(pg);
|
||||
authentik.node.addDependency(valkey);
|
||||
|
||||
const gitea = new GiteaServer(this, "gitea-server", {
|
||||
name: "gitea",
|
||||
namespace,
|
||||
providers: {
|
||||
helm,
|
||||
kubernetes,
|
||||
},
|
||||
r2Endpoint: `${env.ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
||||
});
|
||||
|
||||
gitea.node.addDependency(authentik);
|
||||
}
|
||||
}
|
||||
|
||||
const app = new App();
|
||||
const homelab = new Homelab(app, "homelab");
|
||||
const coreServices = new CoreServices(app, "homelab");
|
||||
|
||||
const nixCache = new NixCache(app, "nix-cache");
|
||||
nixCache.node.addDependency(homelab);
|
||||
const k8sOperators = new K8SOperators(app, "k8s-operators");
|
||||
k8sOperators.node.addDependency(coreServices);
|
||||
|
||||
new LocalBackend(homelab, {
|
||||
const utilityServices = new UtilityServices(app, "utility-services");
|
||||
utilityServices.node.addDependency(k8sOperators);
|
||||
|
||||
const caches = new CacheInfrastructure(app, "cache-infrastructure");
|
||||
caches.node.addDependency(utilityServices);
|
||||
|
||||
new LocalBackend(coreServices, {
|
||||
path: "terraform.tfstate",
|
||||
workspaceDir: ".",
|
||||
});
|
||||
|
||||
new PgBackend(nixCache, {
|
||||
schemaName: "nix_cache",
|
||||
connStr: env.PG_CONN_STR,
|
||||
new LocalBackend(caches, {
|
||||
path: "terraform.tfstate",
|
||||
workspaceDir: "./cachestf",
|
||||
});
|
||||
|
||||
new LocalBackend(utilityServices, {
|
||||
path: "terraform.tfstate",
|
||||
workspaceDir: "./utilityservicestf",
|
||||
});
|
||||
|
||||
new LocalBackend(k8sOperators, {
|
||||
path: "terraform.tfstate",
|
||||
workspaceDir: "./k8soperatorstf",
|
||||
});
|
||||
|
||||
app.synth();
|
||||
|
||||
124
package-lock.json
generated
124
package-lock.json
generated
@@ -9,46 +9,60 @@
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@cdktf/provider-helm": "10.5.0",
|
||||
"@cdktf/provider-kubernetes": "11.12.1",
|
||||
"cdktf": "^0.20.12",
|
||||
"constructs": "^10.4.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"envalid": "^8.0.0"
|
||||
"@cdktf/provider-helm": "12.1.1",
|
||||
"@cdktf/provider-kubernetes": "12.1.0",
|
||||
"@cdktf/provider-null": "^11.0.0",
|
||||
"cdktf": "^0.21.0",
|
||||
"constructs": "^10.4.3",
|
||||
"dotenv": "^17.2.3",
|
||||
"envalid": "^8.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.3",
|
||||
"@types/node": "^24.10.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "24"
|
||||
}
|
||||
},
|
||||
"node_modules/@cdktf/provider-helm": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@cdktf/provider-helm/-/provider-helm-10.5.0.tgz",
|
||||
"integrity": "sha512-u3Q6VNIayaSFfEKZh+JG3PDrwcl9igHLWUdi6cK1G385tw4UyUpZ8osUnGhOErxbZtlcp4yeZ1c5+1OMP4epLA==",
|
||||
"version": "12.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@cdktf/provider-helm/-/provider-helm-12.1.1.tgz",
|
||||
"integrity": "sha512-bi1Smig+b38eKs0yP/JJhbwTKHclp91fNLkcEDS7nI+6AQ4+uqN24CxHGUc6hpwNmatNnLH90gR0+iq/p6KEuw==",
|
||||
"license": "MPL-2.0",
|
||||
"engines": {
|
||||
"node": ">= 18.12.0"
|
||||
"node": ">= 20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cdktf": "^0.20.0",
|
||||
"constructs": "^10.3.0"
|
||||
"cdktf": "^0.21.0",
|
||||
"constructs": "^10.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@cdktf/provider-kubernetes": {
|
||||
"version": "11.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@cdktf/provider-kubernetes/-/provider-kubernetes-11.12.1.tgz",
|
||||
"integrity": "sha512-8LgaY0VULF/2f8iXqojGujP7DKSzl1didqbxMb7uMX0oE3EVDdtmJNIAY2D6oXjW95b9+NVQmhg4iN/jiF7zpA==",
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@cdktf/provider-kubernetes/-/provider-kubernetes-12.1.0.tgz",
|
||||
"integrity": "sha512-GVFbQIPaMeGbzbGyvTWwBUgdc9kKOGWRQNmzvD5A1bFtDTAVk77kRfdfooVuj869TDHF77WXIn6LGp8uuHoJrQ==",
|
||||
"license": "MPL-2.0",
|
||||
"engines": {
|
||||
"node": ">= 18.12.0"
|
||||
"node": ">= 20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cdktf": "^0.20.0",
|
||||
"constructs": "^10.3.0"
|
||||
"cdktf": "^0.21.0",
|
||||
"constructs": "^10.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@cdktf/provider-null": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cdktf/provider-null/-/provider-null-11.0.0.tgz",
|
||||
"integrity": "sha512-OX/ADMXtPWBV/ZTBxCiMGUX0EMI+ooxXZWrZAskJAKIO2Ny1tpBXLE13NpfU9fG+6GkR4e1hLNsOMdO99DuhCA==",
|
||||
"license": "MPL-2.0",
|
||||
"engines": {
|
||||
"node": ">= 20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cdktf": "^0.21.0",
|
||||
"constructs": "^10.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
@@ -93,9 +107,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -121,9 +135,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
|
||||
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
|
||||
"version": "24.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -165,9 +179,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cdktf": {
|
||||
"version": "0.20.12",
|
||||
"resolved": "https://registry.npmjs.org/cdktf/-/cdktf-0.20.12.tgz",
|
||||
"integrity": "sha512-ZBg2gA3Uw0WvGFlgrY1uxo6QHWn+ZdHiDkZQyOsTBl68k62UlaV8K7RR51d0E/amQG/CjtKOJr5XPFFAcOq0VA==",
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/cdktf/-/cdktf-0.21.0.tgz",
|
||||
"integrity": "sha512-bdTOOyrFSXw0p5d7/3dye7ZWYzrUatyMjWEAAwTGoqghjygRj6Q55y1QZnSB021NRDzYZ3BhFGsOkpmIjQMzNQ==",
|
||||
"bundleDependencies": [
|
||||
"archiver",
|
||||
"json-stable-stringify",
|
||||
@@ -177,11 +191,11 @@
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"archiver": "7.0.1",
|
||||
"json-stable-stringify": "1.2.1",
|
||||
"semver": "7.7.1"
|
||||
"json-stable-stringify": "1.3.0",
|
||||
"semver": "7.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"constructs": "^10.3.0"
|
||||
"constructs": "^10.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/cdktf/node_modules/@isaacs/cliui": {
|
||||
@@ -766,12 +780,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cdktf/node_modules/json-stable-stringify": {
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.3",
|
||||
"call-bound": "^1.0.4",
|
||||
"isarray": "^2.0.5",
|
||||
"jsonify": "^0.0.1",
|
||||
"object-keys": "^1.1.1"
|
||||
@@ -852,6 +866,17 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/cdktf/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"inBundle": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/cdktf/node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"inBundle": true,
|
||||
@@ -940,17 +965,6 @@
|
||||
"minimatch": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cdktf/node_modules/readdir-glob/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"inBundle": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/cdktf/node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"funding": [
|
||||
@@ -971,7 +985,7 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cdktf/node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"version": "7.7.2",
|
||||
"inBundle": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
@@ -1249,9 +1263,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/constructs": {
|
||||
"version": "10.4.2",
|
||||
"resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz",
|
||||
"integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==",
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.3.tgz",
|
||||
"integrity": "sha512-3+ZB67qWGM1vEstNpj6pGaLNN1qz4gxC1CBhEUhZDZk0PqzQWY65IzC1Doq17MGPa9xa2wJ1G/DJ3swU8kWAHQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
@@ -1273,9 +1287,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
||||
"version": "17.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -1285,9 +1299,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/envalid": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/envalid/-/envalid-8.1.0.tgz",
|
||||
"integrity": "sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==",
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/envalid/-/envalid-8.1.1.tgz",
|
||||
"integrity": "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
|
||||
17
package.json
17
package.json
@@ -23,16 +23,17 @@
|
||||
"upgrade:next": "npm i cdktf@next cdktf-cli@next"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cdktf/provider-helm": "10.5.0",
|
||||
"@cdktf/provider-kubernetes": "11.12.1",
|
||||
"cdktf": "^0.20.12",
|
||||
"constructs": "^10.4.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"envalid": "^8.0.0"
|
||||
"@cdktf/provider-helm": "12.1.1",
|
||||
"@cdktf/provider-kubernetes": "12.1.0",
|
||||
"@cdktf/provider-null": "^11.0.0",
|
||||
"cdktf": "^0.21.0",
|
||||
"constructs": "^10.4.3",
|
||||
"dotenv": "^17.2.3",
|
||||
"envalid": "^8.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.3",
|
||||
"@types/node": "^24.10.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
|
||||
import { Release } from "@cdktf/provider-helm/lib/release";
|
||||
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
import { Construct } from "constructs";
|
||||
|
||||
type PostgresClusterOptions = {
|
||||
providers: {
|
||||
kubernetes: KubernetesProvider;
|
||||
helm: HelmProvider;
|
||||
};
|
||||
provider: KubernetesProvider;
|
||||
name: string;
|
||||
namespace: string;
|
||||
users: string[];
|
||||
@@ -22,16 +17,7 @@ export class PostgresCluster extends Construct {
|
||||
constructor(scope: Construct, id: string, options: PostgresClusterOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const { kubernetes, helm } = options.providers;
|
||||
|
||||
new Release(this, "cnpg-operator", {
|
||||
provider: helm,
|
||||
repository: "https://cloudnative-pg.github.io/charts",
|
||||
chart: "cloudnative-pg",
|
||||
name: "postgres-system",
|
||||
namespace: "cnpg-system",
|
||||
createNamespace: true,
|
||||
});
|
||||
const { provider } = options;
|
||||
|
||||
const destinationPath = "s3://postgres-backups/";
|
||||
const endpointURL = options.backupR2EndpointURL;
|
||||
@@ -64,7 +50,7 @@ export class PostgresCluster extends Construct {
|
||||
};
|
||||
|
||||
new Manifest(this, "r2-backup-store", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "barmancloud.cnpg.io/v1",
|
||||
kind: "ObjectStore",
|
||||
@@ -95,7 +81,7 @@ export class PostgresCluster extends Construct {
|
||||
|
||||
// Self-signed issuer for creating CA certificates
|
||||
new Manifest(this, "selfsigned-issuer", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Issuer",
|
||||
@@ -111,7 +97,7 @@ export class PostgresCluster extends Construct {
|
||||
|
||||
// Server CA certificate
|
||||
new Manifest(this, "server-ca-cert", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
@@ -140,7 +126,7 @@ export class PostgresCluster extends Construct {
|
||||
|
||||
// Issuer using the server CA
|
||||
new Manifest(this, "server-ca-issuer", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Issuer",
|
||||
@@ -158,7 +144,7 @@ export class PostgresCluster extends Construct {
|
||||
|
||||
// Server certificate
|
||||
new Manifest(this, "server-cert", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
@@ -187,7 +173,7 @@ export class PostgresCluster extends Construct {
|
||||
|
||||
// Client CA certificate
|
||||
new Manifest(this, "client-ca", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
@@ -216,7 +202,7 @@ export class PostgresCluster extends Construct {
|
||||
|
||||
// Issuer using the client CA
|
||||
new Manifest(this, "client-ca-issuer", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Issuer",
|
||||
@@ -234,7 +220,7 @@ export class PostgresCluster extends Construct {
|
||||
|
||||
// Secret for client certificate
|
||||
new Manifest(this, `${certNames.client}-secret`, {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "v1",
|
||||
kind: "Secret",
|
||||
@@ -250,7 +236,7 @@ export class PostgresCluster extends Construct {
|
||||
|
||||
// Client certificate for streaming replica
|
||||
new Manifest(this, "streaming-replica-cert", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
@@ -277,7 +263,7 @@ export class PostgresCluster extends Construct {
|
||||
options.users.forEach(
|
||||
(user) =>
|
||||
new Manifest(this, `${user}-client-cert`, {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
@@ -302,7 +288,7 @@ export class PostgresCluster extends Construct {
|
||||
);
|
||||
|
||||
new Manifest(this, "postgres-cluster", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
fieldManager: { forceConflicts: true },
|
||||
manifest: {
|
||||
apiVersion: "postgresql.cnpg.io/v1",
|
||||
@@ -435,7 +421,7 @@ export class PostgresCluster extends Construct {
|
||||
});
|
||||
|
||||
new Manifest(this, "postgres-backup-job", {
|
||||
provider: kubernetes,
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "postgresql.cnpg.io/v1",
|
||||
kind: "ScheduledBackup",
|
||||
|
||||
7
types/index.ts
Normal file
7
types/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
|
||||
|
||||
export type Providers = {
|
||||
kubernetes: KubernetesProvider;
|
||||
helm: HelmProvider;
|
||||
};
|
||||
47
utility-services/authentik/index.ts
Normal file
47
utility-services/authentik/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { Release } from "@cdktf/provider-helm/lib/release";
|
||||
import { Construct } from "constructs";
|
||||
import { OnePasswordSecret } from "../../utils";
|
||||
import { Providers } from "../../types";
|
||||
|
||||
type AuthentikServerOptions = {
|
||||
providers: Providers;
|
||||
name: string;
|
||||
namespace: string;
|
||||
};
|
||||
|
||||
export class AuthentikServer extends Construct {
|
||||
constructor(scope: Construct, id: string, options: AuthentikServerOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const { kubernetes, helm } = options.providers;
|
||||
|
||||
new OnePasswordSecret(this, "secret-key", {
|
||||
provider: kubernetes,
|
||||
name: "authentik-secret-key",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/authentik-secret-key",
|
||||
});
|
||||
|
||||
new OnePasswordSecret(this, "smtp", {
|
||||
provider: kubernetes,
|
||||
name: "authentik-smtp-token",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/smtp-token",
|
||||
});
|
||||
|
||||
new Release(this, id, {
|
||||
...options,
|
||||
provider: helm,
|
||||
repository: "https://charts.goauthentik.io",
|
||||
chart: "authentik",
|
||||
createNamespace: true,
|
||||
values: [
|
||||
fs.readFileSync(path.join(__dirname, "values.yaml"), {
|
||||
encoding: "utf8",
|
||||
}),
|
||||
],
|
||||
}).importFrom("homelab/authentik");
|
||||
}
|
||||
}
|
||||
110
utility-services/authentik/values.yaml
Normal file
110
utility-services/authentik/values.yaml
Normal file
@@ -0,0 +1,110 @@
|
||||
global:
|
||||
addPrometheusAnnotations: true
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
podLabels:
|
||||
app: authentik
|
||||
nodeSelector:
|
||||
nodepool: worker
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: DoNotSchedule
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app: authentik
|
||||
env:
|
||||
- name: AUTHENTIK_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-secret-key
|
||||
key: password
|
||||
- name: AUTHENTIK_EMAIL__USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-smtp-token
|
||||
key: authentik-username
|
||||
- name: AUTHENTIK_EMAIL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-smtp-token
|
||||
key: authentik-password
|
||||
- name: AUTHENTIK_EMAIL__FROM
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-smtp-token
|
||||
key: authentik-username
|
||||
- name: AUTHENTIK_EMAIL__USE_TLS
|
||||
value: "true"
|
||||
- name: AUTHENTIK_POSTGRESQL__SSLMODE
|
||||
value: verify-full
|
||||
- name: AUTHENTIK_POSTGRESQL__SSLROOTCERT
|
||||
value: "/opt/authentik/certs/ca.crt"
|
||||
- name: AUTHENTIK_POSTGRESQL__SSLCERT
|
||||
value: "/opt/authentik/certs/tls.crt"
|
||||
- name: AUTHENTIK_POSTGRESQL__SSLKEY
|
||||
value: "/opt/authentik/certs/tls.key"
|
||||
- name: AUTHENTIK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: valkey
|
||||
key: password
|
||||
volumes:
|
||||
- name: ssl-bundle
|
||||
projected:
|
||||
sources:
|
||||
- secret:
|
||||
name: authentik-client-cert
|
||||
items:
|
||||
- key: tls.crt
|
||||
path: tls.crt
|
||||
- key: tls.key
|
||||
path: tls.key
|
||||
mode: 0600
|
||||
- secret:
|
||||
name: postgres-server-cert
|
||||
items:
|
||||
- key: ca.crt
|
||||
path: ca.crt
|
||||
volumeMounts:
|
||||
- name: ssl-bundle
|
||||
mountPath: /opt/authentik/certs
|
||||
readOnly: true
|
||||
|
||||
authentik:
|
||||
error_reporting:
|
||||
enabled: false
|
||||
email:
|
||||
host: "smtp.protonmail.ch"
|
||||
port: 587
|
||||
postgresql:
|
||||
host: postgres-cluster-rw
|
||||
user: authentik
|
||||
name: authentik
|
||||
redis:
|
||||
host: valkey
|
||||
|
||||
server:
|
||||
replicas: 3
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: cloudflare-issuer
|
||||
cert-manager.io/acme-challenge-type: dns01
|
||||
cert-manager.io/private-key-size: "4096"
|
||||
ingressClassName: traefik
|
||||
hosts:
|
||||
- auth.dogar.dev
|
||||
tls:
|
||||
- secretName: authentik-tls
|
||||
hosts:
|
||||
- auth.dogar.dev
|
||||
|
||||
worker:
|
||||
replicas: 3
|
||||
|
||||
postgresql:
|
||||
enabled: false
|
||||
redis:
|
||||
enabled: false
|
||||
1
utility-services/gitea/index.ts
Normal file
1
utility-services/gitea/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { GiteaServer } from "./server";
|
||||
82
utility-services/gitea/server.ts
Normal file
82
utility-services/gitea/server.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
|
||||
import { Release } from "@cdktf/provider-helm/lib/release";
|
||||
import { Construct } from "constructs";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
|
||||
import { OnePasswordSecret } from "../../utils";
|
||||
import { IngressRouteTcp } from "../../utils/traefik";
|
||||
|
||||
type GiteaServerOptions = {
|
||||
providers: {
|
||||
helm: HelmProvider;
|
||||
kubernetes: KubernetesProvider;
|
||||
};
|
||||
name: string;
|
||||
namespace: string;
|
||||
r2Endpoint: string;
|
||||
};
|
||||
|
||||
export class GiteaServer extends Construct {
|
||||
constructor(scope: Construct, id: string, options: GiteaServerOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const { kubernetes, helm } = options.providers;
|
||||
|
||||
new OnePasswordSecret(this, "admin", {
|
||||
provider: kubernetes,
|
||||
name: "gitea-admin",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/gitea-admin",
|
||||
});
|
||||
|
||||
new OnePasswordSecret(this, "oauth", {
|
||||
provider: kubernetes,
|
||||
name: "gitea-oauth",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/gitea-oauth",
|
||||
});
|
||||
|
||||
new OnePasswordSecret(this, "smtp", {
|
||||
provider: kubernetes,
|
||||
name: "gitea-smtp-token",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/smtp-token",
|
||||
});
|
||||
|
||||
new OnePasswordSecret(this, "r2", {
|
||||
provider: kubernetes,
|
||||
name: "gitea-cloudflare-token",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/cloudflare",
|
||||
});
|
||||
|
||||
new Release(this, id, {
|
||||
...options,
|
||||
provider: helm,
|
||||
repository: "https://dl.gitea.com/charts",
|
||||
chart: "gitea",
|
||||
createNamespace: true,
|
||||
set: [
|
||||
{
|
||||
name: "gitea.config.storage.MINIO_ENDPOINT",
|
||||
value: options.r2Endpoint,
|
||||
},
|
||||
],
|
||||
values: [
|
||||
fs.readFileSync(path.join(__dirname, "values.yaml"), {
|
||||
encoding: "utf8",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
new IngressRouteTcp(this, "ssh-ingress", {
|
||||
provider: kubernetes,
|
||||
namespace: options.namespace,
|
||||
entryPoint: "ssh",
|
||||
serviceName: `${options.name}-ssh`,
|
||||
servicePort: 22,
|
||||
});
|
||||
}
|
||||
}
|
||||
161
utility-services/gitea/values.yaml
Normal file
161
utility-services/gitea/values.yaml
Normal file
@@ -0,0 +1,161 @@
|
||||
global:
|
||||
storageClass: longhorn
|
||||
image:
|
||||
rootless: false
|
||||
service:
|
||||
http:
|
||||
annotations:
|
||||
metallb.universe.tf/allow-shared-ip: gitea
|
||||
ssh:
|
||||
annotations:
|
||||
metallb.universe.tf/allow-shared-ip: gitea
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: cloudflare-issuer
|
||||
cert-manager.io/acme-challenge-type: dns01
|
||||
cert-manager.io/private-key-size: 4096
|
||||
className: traefik
|
||||
hosts:
|
||||
- host: git.dogar.dev
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: gitea-tls
|
||||
hosts:
|
||||
- git.dogar.dev
|
||||
gitea:
|
||||
podAnnotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "6060"
|
||||
admin:
|
||||
existingSecret: gitea-admin
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
config:
|
||||
server:
|
||||
ENABLE_PPROF: true
|
||||
ENABLE_GZIP: true
|
||||
LFS_START_SERVER: true
|
||||
SSH_DOMAIN: git.dogar.dev
|
||||
database:
|
||||
DB_TYPE: postgres
|
||||
HOST: postgres-cluster-rw
|
||||
NAME: gitea
|
||||
USER: gitea
|
||||
SSL_MODE: verify-full
|
||||
metrics:
|
||||
ENABLED: true
|
||||
cache:
|
||||
ADAPTER: memory
|
||||
session:
|
||||
PROVIDER: db
|
||||
PROVIDER_CONFIG: ""
|
||||
queue:
|
||||
TYPE: channel
|
||||
storage:
|
||||
STORAGE_TYPE: minio
|
||||
MINIO_USE_SSL: true
|
||||
MINIO_BUCKET_LOOKUP_STYLE: path
|
||||
MINIO_LOCATION: auto
|
||||
service:
|
||||
DISABLE_REGISTRATION: true
|
||||
oauth2_client:
|
||||
ENABLE_AUTO_REGISTRATION: true
|
||||
mailer:
|
||||
ENABLED: true
|
||||
PROTOCOL: smtp+starttls
|
||||
SMTP_ADDR: smtp.protonmail.ch
|
||||
SMTP_PORT: 587
|
||||
FROM: git@dogar.dev
|
||||
picture:
|
||||
GRAVATAR_SOURCE: gravatar
|
||||
oauth:
|
||||
- name: "authentik"
|
||||
provider: "openidConnect"
|
||||
existingSecret: gitea-oauth
|
||||
autoDiscoverUrl: "https://auth.dogar.dev/application/o/gitea/.well-known/openid-configuration"
|
||||
iconUrl: "https://goauthentik.io/img/icon.png"
|
||||
scopes: "email profile"
|
||||
additionalConfigFromEnvs:
|
||||
- name: GITEA__MAILER__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-smtp-token
|
||||
key: gitea-password
|
||||
- name: GITEA__PACKAGES__CHUNKED_UPLOAD_PATH
|
||||
value: "/tmp/gitea-uploads"
|
||||
- name: GITEA__PACKAGES__CHUNKED_UPLOAD_CONCURRENCY
|
||||
value: "4"
|
||||
- name: GITEA__STORAGE__MINIO_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-cloudflare-token
|
||||
key: access_key_id
|
||||
- name: GITEA__STORAGE__MINIO_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-cloudflare-token
|
||||
key: secret_access_key
|
||||
persistence:
|
||||
labels:
|
||||
recurring-job.longhorn.io/source: "enabled"
|
||||
recurring-job.longhorn.io/daily-backup: "enabled"
|
||||
enabled: true
|
||||
size: 50Gi
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
deployment:
|
||||
env:
|
||||
- name: PGSSLMODE
|
||||
value: verify-full
|
||||
- name: PGSSLROOTCERT
|
||||
value: /opt/gitea/.postgresql/root.crt
|
||||
- name: PGSSLCERT
|
||||
value: /opt/gitea/.postgresql/postgresql.crt
|
||||
- name: PGSSLKEY
|
||||
value: /opt/gitea/.postgresql/postgresql.key
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 6
|
||||
memory: 6Gi
|
||||
extraVolumes:
|
||||
- name: ssl-bundle
|
||||
projected:
|
||||
sources:
|
||||
- secret:
|
||||
name: gitea-client-cert
|
||||
items:
|
||||
- key: tls.crt
|
||||
path: postgresql.crt
|
||||
- key: tls.key
|
||||
path: postgresql.key
|
||||
mode: 0600
|
||||
- secret:
|
||||
name: postgres-server-cert
|
||||
items:
|
||||
- key: ca.crt
|
||||
path: root.crt
|
||||
- name: gitea-temp
|
||||
emptyDir: {}
|
||||
extraInitVolumeMounts:
|
||||
- name: ssl-bundle
|
||||
mountPath: /opt/gitea/.postgresql
|
||||
readOnly: true
|
||||
extraContainerVolumeMounts:
|
||||
- name: ssl-bundle
|
||||
mountPath: /opt/gitea/.postgresql
|
||||
readOnly: true
|
||||
readOnly: true
|
||||
- name: gitea-temp
|
||||
mountPath: /tmp/gitea-uploads
|
||||
postgresql-ha:
|
||||
enabled: false
|
||||
valkey-cluster:
|
||||
enabled: false
|
||||
94
utility-services/index.ts
Normal file
94
utility-services/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import * as path from "path";
|
||||
import { DataKubernetesNamespaceV1 } from "@cdktf/provider-kubernetes/lib/data-kubernetes-namespace-v1";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
|
||||
import { DataTerraformRemoteStateLocal, TerraformStack } from "cdktf";
|
||||
import { Construct } from "constructs";
|
||||
|
||||
import { ValkeyCluster } from "./valkey";
|
||||
import { GiteaServer } from "./gitea";
|
||||
import { AuthentikServer } from "./authentik";
|
||||
import { PostgresCluster } from "./postgres";
|
||||
|
||||
export class UtilityServices extends TerraformStack {
|
||||
constructor(scope: Construct, id: string) {
|
||||
super(scope, id);
|
||||
|
||||
const kubernetes = new KubernetesProvider(this, "kubernetes", {
|
||||
configPath: "~/.kube/config",
|
||||
});
|
||||
|
||||
const helm = new HelmProvider(this, "helm", {
|
||||
kubernetes: {
|
||||
configPath: "~/.kube/config",
|
||||
},
|
||||
});
|
||||
|
||||
const homelabState = new DataTerraformRemoteStateLocal(
|
||||
this,
|
||||
"homelab-state",
|
||||
{
|
||||
path: path.join(
|
||||
__dirname,
|
||||
"../cdktf.out/stacks/homelab/terraform.tfstate",
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const namespaceName = homelabState.getString("namespace-output");
|
||||
const namespaceResource = new DataKubernetesNamespaceV1(
|
||||
this,
|
||||
"homelab-namespace",
|
||||
{
|
||||
provider: kubernetes,
|
||||
metadata: {
|
||||
name: namespaceName,
|
||||
},
|
||||
},
|
||||
);
|
||||
const namespace = namespaceResource.metadata.name;
|
||||
|
||||
const r2Endpoint = `${process.env.ACCOUNT_ID!}.r2.cloudflarestorage.com`;
|
||||
|
||||
const valkeyCluster = new ValkeyCluster(this, "valkey-cluster", {
|
||||
namespace,
|
||||
provider: kubernetes,
|
||||
name: "valkey",
|
||||
});
|
||||
|
||||
const postgres = new PostgresCluster(this, "postgres-cluster", {
|
||||
certManagerApiVersion: "cert-manager.io/v1",
|
||||
name: "postgres-cluster",
|
||||
namespace,
|
||||
provider: kubernetes,
|
||||
users: ["shahab", "budget-tracker", "authentik", "gitea"],
|
||||
primaryUser: "shahab",
|
||||
initSecretName: "postgres-password",
|
||||
backupR2EndpointURL: `https://${r2Endpoint}`,
|
||||
});
|
||||
|
||||
const authentik = new AuthentikServer(this, "authentik-server", {
|
||||
providers: {
|
||||
helm,
|
||||
kubernetes,
|
||||
},
|
||||
name: "authentik",
|
||||
namespace,
|
||||
});
|
||||
|
||||
authentik.node.addDependency(valkeyCluster);
|
||||
authentik.node.addDependency(postgres);
|
||||
|
||||
const gitea = new GiteaServer(this, "gitea-server", {
|
||||
providers: {
|
||||
helm,
|
||||
kubernetes,
|
||||
},
|
||||
name: "gitea",
|
||||
namespace,
|
||||
r2Endpoint: r2Endpoint,
|
||||
});
|
||||
|
||||
gitea.node.addDependency(authentik);
|
||||
}
|
||||
}
|
||||
456
utility-services/postgres/index.ts
Normal file
456
utility-services/postgres/index.ts
Normal file
@@ -0,0 +1,456 @@
|
||||
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
import { Construct } from "constructs";
|
||||
import { OnePasswordSecret } from "../../utils";
|
||||
|
||||
type PostgresClusterOptions = {
|
||||
provider: KubernetesProvider;
|
||||
name: string;
|
||||
namespace: string;
|
||||
users: string[];
|
||||
primaryUser: string;
|
||||
initSecretName: string;
|
||||
certManagerApiVersion: string;
|
||||
backupR2EndpointURL: string;
|
||||
};
|
||||
|
||||
export class PostgresCluster extends Construct {
|
||||
constructor(scope: Construct, id: string, options: PostgresClusterOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const { provider } = options;
|
||||
|
||||
const destinationPath = "s3://postgres-backups/";
|
||||
const endpointURL = options.backupR2EndpointURL;
|
||||
const barmanStoreName = "r2-postgres-backup-store";
|
||||
const backupServerName = `${options.name}-backup`;
|
||||
|
||||
const barmanConfiguration = {
|
||||
destinationPath,
|
||||
endpointURL,
|
||||
s3Credentials: {
|
||||
accessKeyId: {
|
||||
name: "barman-cloudflare-token",
|
||||
key: "access_key_id",
|
||||
},
|
||||
secretAccessKey: {
|
||||
name: "barman-cloudflare-token",
|
||||
key: "secret_access_key",
|
||||
},
|
||||
region: {
|
||||
name: "barman-cloudflare-token",
|
||||
key: "AWS_REGION",
|
||||
},
|
||||
},
|
||||
wal: {
|
||||
compression: "gzip",
|
||||
},
|
||||
data: {
|
||||
compression: "gzip",
|
||||
},
|
||||
};
|
||||
|
||||
new OnePasswordSecret(this, "barman-cloudflare-token", {
|
||||
provider: options.provider,
|
||||
name: "barman-cloudflare-token",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/cloudflare",
|
||||
});
|
||||
|
||||
new Manifest(this, "r2-backup-store", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "barmancloud.cnpg.io/v1",
|
||||
kind: "ObjectStore",
|
||||
metadata: {
|
||||
namespace: options.namespace,
|
||||
name: barmanStoreName,
|
||||
},
|
||||
spec: {
|
||||
retentionPolicy: "15d",
|
||||
configuration: {
|
||||
...barmanConfiguration,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { certManagerApiVersion } = options;
|
||||
|
||||
const certNames = {
|
||||
server: "postgres-server-cert",
|
||||
client: "postgres-client-cert",
|
||||
};
|
||||
|
||||
const caNames = {
|
||||
server: "postgres-server-ca",
|
||||
client: "postgres-client-ca",
|
||||
};
|
||||
|
||||
// Self-signed issuer for creating CA certificates
|
||||
new Manifest(this, "selfsigned-issuer", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Issuer",
|
||||
metadata: {
|
||||
name: "selfsigned-issuer",
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
selfSigned: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Server CA certificate
|
||||
new Manifest(this, "server-ca-cert", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: "server-ca",
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
isCA: true,
|
||||
commonName: caNames.server,
|
||||
secretName: caNames.server,
|
||||
privateKey: {
|
||||
algorithm: "ECDSA",
|
||||
size: 384,
|
||||
},
|
||||
duration: "52560h", // 6 years
|
||||
renewBefore: "8760h", // 1 year before expiration
|
||||
issuerRef: {
|
||||
name: "selfsigned-issuer",
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Issuer using the server CA
|
||||
new Manifest(this, "server-ca-issuer", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Issuer",
|
||||
metadata: {
|
||||
name: `${caNames.server}-issuer`,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
ca: {
|
||||
secretName: caNames.server,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Server certificate
|
||||
new Manifest(this, "server-cert", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: certNames.server,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
secretName: certNames.server,
|
||||
usages: ["server auth"],
|
||||
dnsNames: [
|
||||
"postgres-cluster-rw",
|
||||
"postgres-cluster-rw.homelab.svc.cluster.local",
|
||||
"postgres.dogar.dev",
|
||||
],
|
||||
duration: "4380h", // 6 months
|
||||
renewBefore: "720h", // 30 days before expiration
|
||||
issuerRef: {
|
||||
name: `${caNames.server}-issuer`,
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Client CA certificate
|
||||
new Manifest(this, "client-ca", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: "client-ca",
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
isCA: true,
|
||||
commonName: caNames.client,
|
||||
secretName: caNames.client,
|
||||
privateKey: {
|
||||
algorithm: "ECDSA",
|
||||
size: 256,
|
||||
},
|
||||
duration: "52560h", // 6 years
|
||||
renewBefore: "8760h", // 1 year before expiration
|
||||
issuerRef: {
|
||||
name: "selfsigned-issuer",
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Issuer using the client CA
|
||||
new Manifest(this, "client-ca-issuer", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Issuer",
|
||||
metadata: {
|
||||
name: `${caNames.client}-issuer`,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
ca: {
|
||||
secretName: caNames.client,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Secret for client certificate
|
||||
new Manifest(this, `${certNames.client}-secret`, {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "v1",
|
||||
kind: "Secret",
|
||||
metadata: {
|
||||
name: certNames.client,
|
||||
namespace: options.namespace,
|
||||
labels: {
|
||||
"cnpg.io/reload": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Client certificate for streaming replica
|
||||
new Manifest(this, "streaming-replica-cert", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: certNames.client,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
secretName: certNames.client,
|
||||
usages: ["client auth"],
|
||||
commonName: "streaming_replica",
|
||||
duration: "4380h", // 6 months
|
||||
renewBefore: "720h", // 30 days before expiration
|
||||
issuerRef: {
|
||||
name: "postgres-client-ca-issuer",
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Client certificates for users
|
||||
options.users.forEach(
|
||||
(user) =>
|
||||
new Manifest(this, `${user}-client-cert`, {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: `${user}-client-cert`,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
secretName: `${user}-client-cert`,
|
||||
usages: ["client auth"],
|
||||
commonName: user,
|
||||
duration: "4380h", // 6 months
|
||||
renewBefore: "720h", // 30 days before expiration
|
||||
issuerRef: {
|
||||
name: "postgres-client-ca-issuer",
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
new Manifest(this, "postgres-cluster", {
|
||||
provider,
|
||||
fieldManager: { forceConflicts: true },
|
||||
manifest: {
|
||||
apiVersion: "postgresql.cnpg.io/v1",
|
||||
kind: "Cluster",
|
||||
metadata: {
|
||||
name: options.name,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
instances: 3,
|
||||
minSyncReplicas: 1,
|
||||
maxSyncReplicas: 2,
|
||||
primaryUpdateStrategy: "unsupervised",
|
||||
certificates: {
|
||||
serverCASecret: certNames.server,
|
||||
serverTLSSecret: certNames.server,
|
||||
clientCASecret: certNames.client,
|
||||
replicationTLSSecret: certNames.client,
|
||||
},
|
||||
postgresql: {
|
||||
parameters: {
|
||||
archive_mode: "on",
|
||||
archive_timeout: "60min",
|
||||
checkpoint_timeout: "10min",
|
||||
checkpoint_completion_target: "0.7",
|
||||
dynamic_shared_memory_type: "posix",
|
||||
full_page_writes: "on",
|
||||
log_destination: "csvlog",
|
||||
log_directory: "/controller/log",
|
||||
log_filename: "postgres",
|
||||
log_rotation_age: "0",
|
||||
log_rotation_size: "0",
|
||||
log_truncate_on_rotation: "false",
|
||||
logging_collector: "on",
|
||||
max_parallel_workers: "32",
|
||||
max_replication_slots: "32",
|
||||
max_worker_processes: "32",
|
||||
max_slot_wal_keep_size: "256MB",
|
||||
max_wal_size: "512MB",
|
||||
min_wal_size: "128MB",
|
||||
shared_memory_type: "mmap",
|
||||
shared_preload_libraries: "",
|
||||
ssl_max_protocol_version: "TLSv1.3",
|
||||
ssl_min_protocol_version: "TLSv1.3",
|
||||
wal_compression: "on",
|
||||
wal_keep_size: "128MB",
|
||||
wal_level: "replica",
|
||||
wal_log_hints: "on",
|
||||
wal_receiver_timeout: "5s",
|
||||
wal_sender_timeout: "5s",
|
||||
},
|
||||
pg_hba: [
|
||||
`hostssl all ${options.primaryUser} all cert`,
|
||||
"hostssl sameuser all all cert",
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: "barman-cloud.cloudnative-pg.io",
|
||||
isWALArchiver: true,
|
||||
parameters: {
|
||||
barmanObjectName: barmanStoreName,
|
||||
serverName: backupServerName,
|
||||
},
|
||||
},
|
||||
],
|
||||
bootstrap: {
|
||||
recovery: {
|
||||
source: "clusterBackup",
|
||||
},
|
||||
},
|
||||
externalClusters: [
|
||||
{
|
||||
name: "clusterBackup",
|
||||
plugin: {
|
||||
name: "barman-cloud.cloudnative-pg.io",
|
||||
parameters: {
|
||||
barmanObjectName: barmanStoreName,
|
||||
serverName: backupServerName,
|
||||
skipWalArchiveCheck: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
managed: {
|
||||
services: {
|
||||
disabledDefaultServices: ["ro", "r"],
|
||||
additional: [
|
||||
{
|
||||
selectorType: "rw",
|
||||
serviceTemplate: {
|
||||
metadata: {
|
||||
name: "postgres-cluster",
|
||||
superuser: true,
|
||||
},
|
||||
spec: {
|
||||
type: "LoadBalancer",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
roles: [
|
||||
{
|
||||
name: options.primaryUser,
|
||||
inRoles: ["postgres"],
|
||||
inherit: true,
|
||||
disablePassword: true,
|
||||
createdb: true,
|
||||
createrole: true,
|
||||
login: true,
|
||||
ensure: "present",
|
||||
},
|
||||
],
|
||||
},
|
||||
storage: {
|
||||
size: "10Gi",
|
||||
storageClass: "longhorn",
|
||||
},
|
||||
walStorage: {
|
||||
size: "2Gi",
|
||||
storageClass: "longhorn",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
new Manifest(this, "postgres-backup-job", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "postgresql.cnpg.io/v1",
|
||||
kind: "ScheduledBackup",
|
||||
metadata: {
|
||||
name: "postgres-cluster",
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
immediate: true,
|
||||
// weekly midnight on Sunday
|
||||
schedule: "* 0 0 * * 0",
|
||||
backupOwnerReference: "self",
|
||||
method: "plugin",
|
||||
pluginConfiguration: {
|
||||
name: "barman-cloud.cloudnative-pg.io",
|
||||
parameters: {
|
||||
barmanObjectName: barmanStoreName,
|
||||
serverName: backupServerName,
|
||||
},
|
||||
},
|
||||
cluster: {
|
||||
name: options.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { DeploymentV1 } from "@cdktf/provider-kubernetes/lib/deployment-v1";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
import { ServiceV1 } from "@cdktf/provider-kubernetes/lib/service-v1";
|
||||
import { Construct } from "constructs";
|
||||
import { OnePasswordSecret } from "../../utils";
|
||||
|
||||
type ValkeyClusterOptions = {
|
||||
provider: KubernetesProvider;
|
||||
@@ -17,6 +18,13 @@ export class ValkeyCluster extends Construct {
|
||||
const labels = { app: "valkey" };
|
||||
const { provider, name, namespace } = options;
|
||||
|
||||
new OnePasswordSecret(this, "valkey-secret", {
|
||||
provider,
|
||||
name: "valkey",
|
||||
namespace,
|
||||
itemPath: "vaults/Lab/items/valkey",
|
||||
});
|
||||
|
||||
new DeploymentV1(this, "valkey-deployment", {
|
||||
provider,
|
||||
metadata: {
|
||||
36
utils/1password-secret/index.ts
Normal file
36
utils/1password-secret/index.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Construct } from "constructs";
|
||||
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
|
||||
type SecretOptions = {
|
||||
provider: KubernetesProvider;
|
||||
namespace: string;
|
||||
name: string;
|
||||
itemPath: string;
|
||||
};
|
||||
|
||||
export class OnePasswordSecret extends Construct {
|
||||
constructor(scope: Construct, id: string, options: SecretOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const { itemPath, name, namespace, provider } = options;
|
||||
|
||||
new Manifest(this, name, {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "onepassword.com/v1",
|
||||
kind: "OnePasswordItem",
|
||||
metadata: {
|
||||
name,
|
||||
namespace,
|
||||
annotations: {
|
||||
"operator.1password.io/auto-restart": "true",
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
itemPath,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
85
utils/cert-manager/index.ts
Normal file
85
utils/cert-manager/index.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Construct } from "constructs";
|
||||
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
|
||||
export interface CertificateOptions {
|
||||
provider: KubernetesProvider;
|
||||
|
||||
/** Namespace to create the Certificate in */
|
||||
namespace: string;
|
||||
|
||||
/** Required name of the certificate (and CRD name) */
|
||||
name: string;
|
||||
|
||||
/** Secret name for storing the issued TLS cert */
|
||||
secretName: string;
|
||||
|
||||
/** One or more DNS names the certificate should cover */
|
||||
dnsNames: string[];
|
||||
|
||||
/** Reference to the cert-manager issuer */
|
||||
issuerRef: {
|
||||
name: string;
|
||||
kind?: string; // ClusterIssuer or Issuer
|
||||
};
|
||||
|
||||
/** Optional duration (default: cert-manager default) */
|
||||
duration?: string;
|
||||
|
||||
/** Optional renewBefore (default: cert-manager default) */
|
||||
renewBefore?: string;
|
||||
}
|
||||
|
||||
class Certificate extends Construct {
|
||||
public readonly manifest: Manifest;
|
||||
|
||||
constructor(scope: Construct, id: string, opts: CertificateOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const manifest: any = {
|
||||
apiVersion: "cert-manager.io/v1",
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: opts.name,
|
||||
namespace: opts.namespace,
|
||||
},
|
||||
spec: {
|
||||
secretName: opts.secretName,
|
||||
dnsNames: opts.dnsNames,
|
||||
issuerRef: {
|
||||
name: opts.issuerRef.name,
|
||||
kind: opts.issuerRef.kind ?? "ClusterIssuer",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (opts.duration) {
|
||||
manifest.spec.duration = opts.duration;
|
||||
}
|
||||
|
||||
if (opts.renewBefore) {
|
||||
manifest.spec.renewBefore = opts.renewBefore;
|
||||
}
|
||||
|
||||
this.manifest = new Manifest(this, id, {
|
||||
provider: opts.provider,
|
||||
manifest,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class CloudflareCertificate extends Certificate {
|
||||
constructor(
|
||||
scope: Construct,
|
||||
id: string,
|
||||
opts: Omit<CertificateOptions, "issuerRef">,
|
||||
) {
|
||||
super(scope, id, {
|
||||
...opts,
|
||||
issuerRef: {
|
||||
name: "cloudflare-issuer",
|
||||
kind: "ClusterIssuer",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
3
utils/index.ts
Normal file
3
utils/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { CloudflareCertificate } from "./cert-manager";
|
||||
export { OnePasswordSecret } from "./1password-secret";
|
||||
export { IngressRoute } from "./traefik";
|
||||
2
utils/traefik/index.ts
Normal file
2
utils/traefik/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { IngressRoute } from "./ingress";
|
||||
export { IngressRouteTcp } from "./ingress-tcp";
|
||||
71
utils/traefik/ingress-tcp.ts
Normal file
71
utils/traefik/ingress-tcp.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Construct } from "constructs";
|
||||
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
|
||||
export interface IngressRouteTcpOptions {
|
||||
provider: KubernetesProvider;
|
||||
|
||||
/** Namespace where the IngressRouteTCP will be created */
|
||||
namespace: string;
|
||||
|
||||
/** EntryPoint name (e.g., "ssh", "mc25565", "postgres", etc.) */
|
||||
entryPoint: string;
|
||||
|
||||
/** Backend service name */
|
||||
serviceName: string;
|
||||
|
||||
/** Backend service port */
|
||||
servicePort: number;
|
||||
|
||||
/**
|
||||
* Match rule.
|
||||
* Default is `HostSNI(\`*\`)` which is correct for most TCP services.
|
||||
*/
|
||||
match?: string;
|
||||
|
||||
/** Name override (CR name) */
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export class IngressRouteTcp extends Construct {
|
||||
public readonly manifest: Manifest;
|
||||
|
||||
constructor(scope: Construct, id: string, opts: IngressRouteTcpOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const name =
|
||||
opts.name ??
|
||||
`tcp-${opts.entryPoint}-${opts.serviceName}`.replace(
|
||||
/[^a-zA-Z0-9-]/g,
|
||||
"",
|
||||
);
|
||||
|
||||
const matchRule = opts.match ?? "HostSNI(`*`)";
|
||||
|
||||
this.manifest = new Manifest(this, name, {
|
||||
provider: opts.provider,
|
||||
manifest: {
|
||||
apiVersion: "traefik.io/v1alpha1",
|
||||
kind: "IngressRouteTCP",
|
||||
metadata: {
|
||||
name,
|
||||
namespace: opts.namespace,
|
||||
},
|
||||
spec: {
|
||||
entryPoints: [opts.entryPoint],
|
||||
routes: [
|
||||
{
|
||||
match: matchRule,
|
||||
services: [
|
||||
{
|
||||
name: opts.serviceName,
|
||||
port: opts.servicePort,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
|
||||
import { CloudflareCertificate } from "../cert-manager";
|
||||
|
||||
export interface TraefikIngressRouteOptions {
|
||||
export interface IngressRouteOptions {
|
||||
provider: KubernetesProvider;
|
||||
namespace: string;
|
||||
|
||||
@@ -31,10 +31,10 @@ export interface TraefikIngressRouteOptions {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export class TraefikIngressRoute extends Construct {
|
||||
export class IngressRoute extends Construct {
|
||||
public readonly manifest: Manifest;
|
||||
|
||||
constructor(scope: Construct, id: string, opts: TraefikIngressRouteOptions) {
|
||||
constructor(scope: Construct, id: string, opts: IngressRouteOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const name = opts.name ?? `route-${opts.host.replace(/\./g, "-")}`;
|
||||
Reference in New Issue
Block a user