Compare commits

...

3 Commits

Author SHA1 Message Date
80219a3d0a feat: Utils | add high level longhorn pvc construct 2025-11-22 20:27:36 +05:00
e8caa6a23d chore: Utils | use types not interfaces 2025-11-22 20:27:22 +05:00
3c31105fc6 feat: Gitea | add runners to utility-services stack
TBD if they will stay here
2025-11-22 20:27:04 +05:00
10 changed files with 223 additions and 28 deletions

View File

@@ -1,15 +1,3 @@
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: action-runner
namespace: homelab
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: longhorn
--- ---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment

View File

@@ -1 +1,2 @@
export { GiteaServer } from "./server"; export { GiteaServer } from "./server";
export { GiteaRunner } from "./runner";

View File

@@ -0,0 +1,144 @@
import { Construct } from "constructs";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
import { OnePasswordSecret, LonghornPvc } from "../../utils";
import { DeploymentV1 } from "@cdktf/provider-kubernetes/lib/deployment-v1";
import { PodDisruptionBudgetV1 } from "@cdktf/provider-kubernetes/lib/pod-disruption-budget-v1";
type GiteaRunnerOptions = {
provider: KubernetesProvider;
name: string;
namespace: string;
replicas?: number;
};
export class GiteaRunner extends Construct {
constructor(scope: Construct, id: string, options: GiteaRunnerOptions) {
super(scope, id);
const { provider, name, namespace } = options;
const replicas = options.replicas?.toString() ?? "1";
const pvc = new LonghornPvc(this, "data-pvc", {
provider,
name: `${name}-data`,
namespace: namespace,
size: "10Gi",
accessModes: ["ReadWriteMany"],
});
new OnePasswordSecret(this, "runner-secret", {
provider,
name: "runner-secret",
namespace: namespace,
itemPath: "vaults/Lab/items/Gitea",
});
new PodDisruptionBudgetV1(this, "pdb", {
provider,
metadata: {
name,
namespace,
},
spec: {
minAvailable: replicas,
selector: {
matchLabels: {
app: name,
},
},
},
});
new DeploymentV1(this, "gitea-runner", {
provider,
metadata: {
name: name,
namespace: namespace,
labels: {
app: name,
},
},
spec: {
replicas,
selector: {
matchLabels: {
app: name,
},
},
template: {
metadata: {
labels: {
app: name,
},
},
spec: {
nodeSelector: {
nodepool: "worker",
},
topologySpreadConstraint: [
{
maxSkew: 1,
topologyKey: "kubernetes.io/hostname",
whenUnsatisfiable: "DoNotSchedule",
labelSelector: [
{
matchLabels: {
app: name,
},
},
],
},
],
restartPolicy: "Always",
securityContext: {
fsGroup: "1000",
},
container: [
{
name: "gitea-runner",
image: "gitea/act_runner:nightly-dind-rootless",
env: [
{
name: "DOCKER_HOST",
value: "unix:///run/user/1000/docker.sock",
},
{
name: "GITEA_INSTANCE_URL",
value: "https://git.dogar.dev",
},
{
name: "GITEA_RUNNER_REGISTRATION_TOKEN",
valueFrom: {
secretKeyRef: {
name: "runner-secret",
key: "runner-token",
},
},
},
],
securityContext: {
privileged: true,
},
volumeMount: [
{
name: "runner-data",
mountPath: "/data",
},
],
},
],
volume: [
{
name: "runner-data",
persistentVolumeClaim: {
claimName: pvc.name,
},
},
],
},
},
},
});
}
}

View File

@@ -1,18 +1,13 @@
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
import { Release } from "@cdktf/provider-helm/lib/release"; import { Release } from "@cdktf/provider-helm/lib/release";
import { Construct } from "constructs"; import { Construct } from "constructs";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
import { OnePasswordSecret } from "../../utils"; import { OnePasswordSecret, IngressRoute, IngressRouteTcp } from "../../utils";
import { IngressRoute, IngressRouteTcp } from "../../utils/traefik"; import type { Providers } from "../../types";
type GiteaServerOptions = { type GiteaServerOptions = {
providers: { providers: Providers;
helm: HelmProvider;
kubernetes: KubernetesProvider;
};
name: string; name: string;
namespace: string; namespace: string;
r2Endpoint: string; r2Endpoint: string;

View File

@@ -5,7 +5,7 @@ import { DataTerraformRemoteStateS3, TerraformStack } from "cdktf";
import { Construct } from "constructs"; import { Construct } from "constructs";
import { ValkeyCluster } from "./valkey"; import { ValkeyCluster } from "./valkey";
import { GiteaServer } from "./gitea"; import { GiteaRunner, GiteaServer } from "./gitea";
import { AuthentikServer } from "./authentik"; import { AuthentikServer } from "./authentik";
import { PostgresCluster } from "./postgres"; import { PostgresCluster } from "./postgres";
import { DynamicDNS } from "./dynamic-dns"; import { DynamicDNS } from "./dynamic-dns";
@@ -110,5 +110,12 @@ export class UtilityServices extends TerraformStack {
}); });
gitea.node.addDependency(authentik); gitea.node.addDependency(authentik);
new GiteaRunner(this, "gitea-runner", {
provider: kubernetes,
namespace,
name: "gitea-runner",
replicas: 3,
});
} }
} }

View File

@@ -2,7 +2,7 @@ import { Construct } from "constructs";
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest"; import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
export interface CertificateOptions { type CertificateOptions = {
provider: KubernetesProvider; provider: KubernetesProvider;
/** Namespace to create the Certificate in */ /** Namespace to create the Certificate in */
@@ -28,7 +28,7 @@ export interface CertificateOptions {
/** Optional renewBefore (default: cert-manager default) */ /** Optional renewBefore (default: cert-manager default) */
renewBefore?: string; renewBefore?: string;
} };
class Certificate extends Construct { class Certificate extends Construct {
public readonly manifest: Manifest; public readonly manifest: Manifest;

View File

@@ -1,3 +1,4 @@
export { CloudflareCertificate } from "./cert-manager"; export { CloudflareCertificate } from "./cert-manager";
export { OnePasswordSecret } from "./1password-secret"; export { OnePasswordSecret } from "./1password-secret";
export { IngressRoute } from "./traefik"; export { IngressRoute, IngressRouteTcp } from "./traefik";
export { LonghornPvc } from "./longhorn";

59
utils/longhorn/index.ts Normal file
View File

@@ -0,0 +1,59 @@
import { Construct } from "constructs";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
import { PersistentVolumeClaimV1 } from "@cdktf/provider-kubernetes/lib/persistent-volume-claim-v1";
type LonghornPvcOptions = {
provider: KubernetesProvider;
/** Name of the PVC */
name: string;
/** Namespace of the PVC */
namespace: string;
/** Size, e.g. "10Gi" */
size: string;
/** Access modes (default: ["ReadWriteOnce"]) */
accessModes?: string[];
/** Optional PVC labels */
labels?: Record<string, string>;
/** Add backup annotations */
backup?: boolean;
};
export class LonghornPvc extends Construct {
public readonly name: string;
constructor(scope: Construct, id: string, opts: LonghornPvcOptions) {
super(scope, id);
this.name = opts.name;
new PersistentVolumeClaimV1(this, id, {
provider: opts.provider,
metadata: {
name: opts.name,
namespace: opts.namespace,
labels: opts.labels ?? {},
annotations: opts.backup
? {
"recurring-job.longhorn.io/daily-backup": "enabled",
"recurring-job.longhorn.io/source": "enabled",
}
: {},
},
spec: {
accessModes: opts.accessModes ?? ["ReadWriteOnce"],
storageClassName: "longhorn", // HARD-CODED
resources: {
requests: {
storage: opts.size,
},
},
},
});
}
}

View File

@@ -2,7 +2,7 @@ import { Construct } from "constructs";
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest"; import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
export interface IngressRouteTcpOptions { type IngressRouteTcpOptions = {
provider: KubernetesProvider; provider: KubernetesProvider;
/** Namespace where the IngressRouteTCP will be created */ /** Namespace where the IngressRouteTCP will be created */
@@ -25,7 +25,7 @@ export interface IngressRouteTcpOptions {
/** Name override (CR name) */ /** Name override (CR name) */
name?: string; name?: string;
} };
export class IngressRouteTcp extends Construct { export class IngressRouteTcp extends Construct {
public readonly manifest: Manifest; public readonly manifest: Manifest;

View File

@@ -4,7 +4,7 @@ import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
import { CloudflareCertificate } from "../cert-manager"; import { CloudflareCertificate } from "../cert-manager";
export interface IngressRouteOptions { type IngressRouteOptions = {
provider: KubernetesProvider; provider: KubernetesProvider;
namespace: string; namespace: string;
@@ -29,7 +29,7 @@ export interface IngressRouteOptions {
/** Name override (otherwise auto) */ /** Name override (otherwise auto) */
name?: string; name?: string;
} };
export class IngressRoute extends Construct { export class IngressRoute extends Construct {
public readonly manifest: Manifest; public readonly manifest: Manifest;