Pulumi code for my server setup
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor ContainerService

+311 -331
+4 -4
lib/service/mounts.ts
··· 3 3 4 4 type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; 5 5 6 - export type MountOpts = docker.types.input.ContainerMount & CustomMountOpts; 6 + type CustomMountOpts = { 7 + kind?: Input<"directory" | "file">; 8 + }; 7 9 8 - type CustomMountOpts = Partial<{ 9 - kind: Input<"directory" | "file">; 10 - }>; 10 + export type MountOpts = docker.types.input.ContainerMount & CustomMountOpts; 11 11 12 12 export function _mount({ 13 13 source,
+163 -144
lib/service/service.ts
··· 1 1 import command from "@pulumi/command"; 2 2 import type { input } from "@pulumi/command/types"; 3 3 import docker from "@pulumi/docker"; 4 - import type { Input, Output } from "@pulumi/pulumi"; 4 + import type { Input, Output, Resource } from "@pulumi/pulumi"; 5 5 import pulumi, { all, output } from "@pulumi/pulumi"; 6 6 import path from "path"; 7 7 import { defaultNetwork } from "./networks"; 8 - import { convertLabels, convertEnvs } from "../util"; 9 8 import { convertPorts } from "./ports"; 10 9 import { getEnv } from "~lib/env"; 11 10 import { haringDockerProvider } from "./providers"; 12 11 import { MountOpts } from "./mounts"; 12 + import { ContainerCapabilities, ContainerPort } from "@pulumi/docker/types/input"; 13 13 14 14 export const defaultConnection = { 15 15 host: getEnv("CONNECTION_HOST"), ··· 19 19 } satisfies input.remote.ConnectionArgs; 20 20 21 21 type Env = string | number | boolean; 22 + type Port = (number | string | ContainerPort)[]; 22 23 23 24 export type ContainerServiceArgs = Partial< 24 25 Omit<docker.ContainerArgs, "ports" | "labels" | "mounts" | "envs" | "capabilities"> & { 25 - enabled: boolean; 26 26 localImage: Input<string>; 27 - disabled: boolean; 28 - servicePort: number; 29 - subdomain: string; 30 - hostRule: string; 31 - hostRulePriority: number; 32 - ports: Input<Input<number | string | docker.types.input.ContainerPort>[]>; 33 - middlewares: string[]; 34 - otherServicePorts: Record<string, number>; 35 - labels: Input<Record<string, Input<string | number | undefined>> | undefined>; 36 - mounts: MountOpts[]; 27 + servicePort: Input<number>; 28 + subdomain: Input<string>; 29 + hostRule: Input<string>; 30 + hostRulePriority: Input<number>; 31 + ports: Input<Port>; 32 + middlewares: Input<Input<string>[]>; 33 + otherServicePorts: Input<Record<string, Input<number>>>; 34 + labels: Input<Record<string, Input<string | number>>>; 35 + mounts: Input<Input<MountOpts>[]>; 37 36 envs: Input<Record<string, Input<Env | Env[]>>>; 38 - capabilities: string[] | { adds?: string[]; drops?: string[] }; 39 - internalHttps: boolean; 40 - dontUpdateIf: () => boolean; 41 - commandConnection: input.remote.ConnectionArgs; 42 - monitor: boolean; 37 + capabilities: Input<Input<string>[]> | ContainerCapabilities; 38 + internalHttps: Input<boolean>; 39 + commandConnection: Input<input.remote.ConnectionArgs>; 40 + monitor: Input<boolean>; 43 41 } 44 42 >; 45 43 46 - export class ServiceDef {} 47 - 48 44 // TODO: turn ContainerService into a factory function like https://sst.dev/docs/examples/#api-gateway-auth 49 45 class ContainerService extends pulumi.ComponentResource { 50 - public readonly container: Output<docker.Container> | undefined; 51 - public readonly localUrl: Output<string> | undefined; 52 - public readonly remoteUrl: string | undefined; 53 - public readonly ip: Output<string | undefined> | undefined; 54 - public readonly enabled: boolean; 55 - private readonly commandConnection: input.remote.ConnectionArgs; 56 - private readonly dependsOn: Input<pulumi.Resource>[] = []; 57 - 58 - constructor(name: string, _args: ContainerServiceArgs, opts?: pulumi.CustomResourceOptions) { 59 - super("bas:docker:ContainerService", name, _args, opts); 60 - 61 - const args = { 62 - enabled: true, 63 - disabled: false, 64 - ports: [], 65 - envs: {}, 66 - mounts: [], 67 - middlewares: [], 68 - labels: {}, 69 - otherServicePorts: {}, 70 - networksAdvanced: [], 71 - hosts: [], 72 - commandConnection: defaultConnection, 73 - ..._args, 74 - } satisfies ContainerServiceArgs; 46 + public readonly container: Output<docker.Container>; 47 + public readonly localUrl: Output<string>; 48 + public readonly ip: Output<string>; 49 + public readonly mounts: docker.Container["mounts"]; 50 + public readonly envs: docker.Container["envs"]; 51 + public readonly capabilities: docker.Container["capabilities"]; 52 + public readonly ports: docker.Container["ports"]; 75 53 76 - this.commandConnection = args.commandConnection; 54 + private commandConnection: Input<input.remote.ConnectionArgs>; 77 55 78 - if (!args.enabled || args.disabled) { 79 - this.enabled = false; 80 - return; 81 - } 82 - 83 - if (opts?.dependsOn) { 84 - this.dependsOn.push(...(Array.isArray(opts.dependsOn) ? opts.dependsOn : [])); 85 - } 56 + constructor(name: string, args: ContainerServiceArgs, opts?: pulumi.CustomResourceOptions) { 57 + super("bas:docker:ContainerService", name, args, opts); 86 58 87 - const mounts = output(args.mounts).apply((mounts) => { 88 - let i = 0; 89 - for (const mount of mounts) { 90 - if (mount.type === "bind" && mount.source) { 91 - const dir = mount.kind === "file" ? path.dirname(mount.source) : mount.source; 92 - this.dependsOn.push(this.createRemoteDir(dir, name, i)); 93 - i++; 94 - } 59 + this.commandConnection = args.commandConnection ?? defaultConnection; 95 60 96 - delete mount.kind; 61 + const dependsOn: Resource[] = []; 62 + output(opts?.dependsOn ?? []).apply((optsDependsOn) => { 63 + if (Array.isArray(optsDependsOn)) { 64 + dependsOn.push(...optsDependsOn); 65 + } else { 66 + dependsOn.push(optsDependsOn); 97 67 } 98 - 99 - return mounts; 100 68 }); 101 69 102 70 const image = ··· 104 72 new docker.RemoteImage( 105 73 `${name}`, 106 74 { 107 - name: output(args.image).apply(async (image) => 75 + name: output(args.image ?? `lscr.io/linuxserver/${name}`).apply(async (image) => 108 76 docker 109 - .getRegistryImage({ name: image ?? `lscr.io/linuxserver/${name}` }, { parent: this }) 77 + .getRegistryImage({ name: image }, { parent: this }) 110 78 .then((registryImage) => `${registryImage.name}@${registryImage.sha256Digest}`), 111 79 ), 112 80 keepLocally: true, ··· 114 82 { parent: this }, 115 83 ).repoDigest; 116 84 117 - function createLabels(host: string, port: string, priority?: number) { 118 - const id = host.replaceAll(/[^\w]+/g, "-"); 85 + const mounts = output(args.mounts ?? []).apply((mounts) => { 86 + let n = 0; 87 + for (const mount of mounts) { 88 + if (mount.type === "bind" && mount.source) { 89 + const dir = mount.kind === "file" ? path.dirname(mount.source) : mount.source; 90 + dependsOn.push(this.createRemoteDir(dir, name, n)); 91 + n++; 92 + } 93 + 94 + delete mount.kind; 95 + } 96 + 97 + return mounts; 98 + }); 99 + this.mounts = mounts; 100 + 101 + this.localUrl = all([args.networkMode, args.servicePort]).apply(([networkMode, servicePort]) => 102 + servicePort && (networkMode === "host" || networkMode?.startsWith("container:")) 103 + ? `http://host.docker.internal:${servicePort}` 104 + : `http://${name}:${servicePort}`, 105 + ); 106 + 107 + const host = all([args.hostRule, args.subdomain]).apply( 108 + ([hostRule, subdomain]) => hostRule ?? `${subdomain ?? name}.bas.sh`, 109 + ); 119 110 120 - return { 121 - "traefik.enable": "true", 122 - [`traefik.http.services.${id}.loadbalancer.server.port`]: port, 123 - [`traefik.http.routers.${id}.service`]: id, 124 - [`traefik.http.routers.${id}.rule`]: 125 - host.includes("/") || host.includes("(") ? host : `Host(\`${host}\`)`, 126 - [`traefik.http.routers.${id}.entrypoints`]: "https", 127 - [`traefik.http.routers.${id}.middlewares`]: ["cloudflare", ...args.middlewares].join(","), 128 - ...(priority && { 129 - [`traefik.http.routers.${id}.priority`]: priority, 130 - }), 131 - ...(port === "443" && { 132 - [`traefik.http.services.${id}.loadbalancer.server.scheme`]: "https", 133 - }), 134 - ...(args.monitor && { 135 - [`kuma.${id}.http.name`]: name, 136 - [`kuma.${id}.http.url`]: `http://${name}:${port}`, 137 - }), 138 - }; 139 - } 111 + const createLabels = all([ 112 + args.middlewares ?? [], 113 + args.hostRulePriority, 114 + args.monitor, 115 + this.localUrl, 116 + ]).apply(([middlewares, hostRulePriority, monitor, localUrl]) => { 117 + return (host: string, port: string | number) => { 118 + const id = host.replaceAll(/[^\w]+/g, "-"); 119 + 120 + const labels = { 121 + "traefik.enable": "true", 122 + [`traefik.http.services.${id}.loadbalancer.server.port`]: port.toString(), 123 + [`traefik.http.routers.${id}.service`]: `${id}`, 124 + [`traefik.http.routers.${id}.entrypoints`]: "https", 125 + [`traefik.http.routers.${id}.rule`]: 126 + host.includes("/") || host.includes("(") ? host : `Host(\`${host}\`)`, 127 + [`traefik.http.routers.${id}.middlewares`]: ["cloudflare", ...middlewares].join(","), 128 + }; 129 + 130 + if (hostRulePriority) { 131 + labels[`traefik.http.routers.${id}.priority`] = hostRulePriority.toString(); 132 + } 133 + 134 + if (port.toString() === "443") { 135 + labels[`traefik.http.services.${id}.loadbalancer.server.scheme`] = "https"; 136 + } 140 137 141 - let labels: Record<string, string | number> = {}; 142 - if (args.servicePort) { 143 - this.localUrl = all([args.networkMode, args.servicePort]).apply( 144 - ([networkMode, servicePort]) => 145 - networkMode === "host" || networkMode?.startsWith("container:") 146 - ? `http://host.docker.internal:${servicePort}` 147 - : `http://${name}:${servicePort}`, 148 - ); 149 - const host = args.hostRule ?? `${args.subdomain ?? name}.bas.sh`; 150 - this.remoteUrl = `https://${host}`; 138 + if (monitor) { 139 + labels[`kuma.${id}.http.name`] = name; 140 + labels[`kuma.${id}.http.url`] = localUrl; 141 + } 151 142 152 - labels = { 153 - ...labels, 154 - ...createLabels(host, args.servicePort.toString(), args.hostRulePriority), 143 + return labels; 155 144 }; 156 - } 145 + }); 157 146 158 - for (const service of Object.keys(args.otherServicePorts)) { 159 - let host = service; 160 - const port = args.otherServicePorts[service]; 147 + const labels = all([ 148 + host, 149 + args.labels ?? {}, 150 + args.servicePort, 151 + args.otherServicePorts ?? {}, 152 + createLabels, 153 + ]).apply(([host, labels, servicePort, otherServicePorts, createLabels]) => { 154 + const additionalLabels = {}; 161 155 162 - if (!host.includes(".bas.sh") && !host.includes("/")) { 163 - host += ".bas.sh"; 164 - } else if (host.startsWith("/")) { 165 - host = this.remoteUrl + host; 156 + for (const [service, port] of Object.entries(otherServicePorts)) { 157 + let name = service; 158 + if (!name.includes(".bas.sh") && !name.includes("/")) { 159 + name += ".bas.sh"; 160 + } else if (name.startsWith("/")) { 161 + name = name.slice(1); 162 + } 163 + 164 + Object.assign(additionalLabels, createLabels(name, port)); 166 165 } 167 166 168 - labels = { 167 + return Object.entries({ 168 + ...(servicePort ? createLabels(host, servicePort) : {}), 169 169 ...labels, 170 - ...createLabels(host, port.toString()), 171 - }; 172 - } 170 + ...additionalLabels, 171 + }).map(([label, value]) => ({ label, value: value.toString() })); 172 + }); 173 173 174 - const allLabels = output(args.labels).apply((inputLabels) => ({ 175 - ...labels, 176 - ...inputLabels, 177 - })); 174 + const envs = output(args.envs ?? {}).apply((envs) => [ 175 + ...Object.entries({ 176 + PUID: `${getEnv("PUID")}`, 177 + PGID: `${getEnv("PGID")}`, 178 + TZ: `${getEnv("TZ")}`, 179 + ...envs, 180 + }).map(([env, value]) => `${env}=${Array.isArray(value) ? value.join(",") : value}`), 181 + ]); 178 182 179 - args.envs = output(args.envs).apply((envs) => ({ 180 - PUID: `${getEnv("PUID")}`, 181 - PGID: `${getEnv("PGID")}`, 182 - TZ: `${getEnv("TZ")}`, 183 - ...envs, 184 - })); 183 + this.envs = envs; 185 184 186 - const capabilities = Array.isArray(args.capabilities) 187 - ? { adds: args.capabilities } 188 - : args.capabilities; 185 + const ports = output(args.ports ?? []).apply(convertPorts); 186 + this.ports = ports; 189 187 190 188 const ensureCapPrefix = (cap: string) => (cap.startsWith("CAP_") ? cap : `CAP_${cap}`); 191 189 192 - if (capabilities) { 193 - capabilities.adds &&= capabilities.adds.map(ensureCapPrefix); 194 - capabilities.drops &&= capabilities.drops.map(ensureCapPrefix); 195 - } 190 + const capabilities = 191 + args.capabilities && 192 + output(args.capabilities).apply((caps) => { 193 + if (Array.isArray(caps)) { 194 + return { adds: caps.map(ensureCapPrefix) }; 195 + } 196 + 197 + caps.adds &&= caps.adds.map(ensureCapPrefix); 198 + caps.drops &&= caps.drops.map(ensureCapPrefix); 199 + return caps; 200 + }); 201 + this.capabilities = output(capabilities); 196 202 197 203 this.container = output( 198 204 new docker.Container( ··· 203 209 ...(opts?.deleteBeforeReplace !== false && { name: args.name ?? name }), 204 210 command: args.command, 205 211 restart: args.restart ?? "unless-stopped", 206 - labels: allLabels.apply(convertLabels), 207 - envs: convertEnvs(args.envs), 208 - ports: convertPorts(args.ports), 212 + labels, 213 + envs, 214 + ports, 209 215 mounts, 210 216 volumes: args.volumes, 211 217 logDriver: "local", ··· 215 221 networksAdvanced: args.networkMode 216 222 ? [] 217 223 : pulumi 218 - .output(args.networksAdvanced) 224 + .output(args.networksAdvanced ?? []) 219 225 .apply((networksAdvanced) => [...networksAdvanced, { name: defaultNetwork.name }]), 220 226 hosts: args.networkMode 221 227 ? [] 222 228 : pulumi 223 - .output(args.hosts) 229 + .output(args.hosts ?? []) 224 230 .apply((hosts) => [{ host: "host.docker.internal", ip: "host-gateway" }, ...hosts]), 225 231 capabilities, 226 232 }, ··· 229 235 deleteBeforeReplace: true, 230 236 replaceOnChanges: ["mounts", "volumes"], 231 237 ignoreChanges: opts?.ignoreChanges, 232 - dependsOn: this.dependsOn, 238 + dependsOn, 233 239 ...opts, 234 240 }, 235 241 ), 236 242 ); 237 243 238 244 this.ip = pulumi 239 - .all([this.container.networkDatas, defaultNetwork.name]) 240 - .apply(([networks, networkName]) => { 241 - const net = networks?.find((n) => n.networkName === networkName); 242 - return net?.ipAddress; 243 - }); 245 + .all([args.networkMode, this.container.networkDatas, defaultNetwork.name]) 246 + .apply(([networkMode, networks, haringNetwork]) => { 247 + if (networkMode === "host") { 248 + return "host.docker.internal"; 249 + } else if (networkMode?.startsWith("container")) { 250 + return ""; // TODO: container lookup 251 + } 244 252 245 - this.enabled = true; 253 + const net = networks?.find( 254 + (n) => n.networkName === haringNetwork || n.networkName === "bridge", 255 + ); 256 + 257 + if (!net) { 258 + throw Error( 259 + `Could not find IP address on network ${haringNetwork} for container "${name}". Networks: ${JSON.stringify(networks)}`, 260 + ); 261 + } 262 + 263 + return net.ipAddress; 264 + }); 246 265 247 266 this.registerOutputs(); 248 267 } ··· 280 299 ...args, 281 300 interpreter: [ 282 301 "/usr/bin/ssh", 283 - `-p ${defaultConnection.port.toString()}`, 302 + `-p ${defaultConnection.port}`, 284 303 `${defaultConnection.user}@${defaultConnection.host}`, 285 304 ], 286 305 } satisfies command.local.RunArgs;
-19
lib/util.ts
··· 1 1 import assert from "assert"; 2 - import { ContainerServiceArgs } from "./service/service"; 3 - import { output } from "@pulumi/pulumi"; 4 2 import ky from "ky"; 5 - 6 - export function convertLabels(labels: Record<string, string | number | undefined>) { 7 - return Object.entries(labels).map(([label, value]) => { 8 - if (value === undefined) { 9 - throw Error(`value for label "${label}" was undefined`); 10 - } 11 - return { label, value: value.toString() }; 12 - }); 13 - } 14 - 15 - export function convertEnvs(envs: ContainerServiceArgs["envs"]) { 16 - return output(envs).apply((envs) => 17 - Object.entries(envs ?? {}).map( 18 - ([env, value]) => `${env}=${Array.isArray(value) ? value.join(",") : value}`, 19 - ), 20 - ); 21 - } 22 3 23 4 export async function getLatestCommit(url: string) { 24 5 const html = await ky.get(url, { retry: 5 }).text();
+33 -14
services/haring/atproto/tranquil/tranquil.ts
··· 7 7 import { getLatestCommit } from "~lib/util"; 8 8 import { fetchRelays } from "~lib/relay-hosts"; 9 9 import path from "path"; 10 + import { DnsRecord } from "@pulumi/cloudflare"; 10 11 11 12 const tranquilImage = new dockerBuild.Image( 12 13 "tranquil-pds", ··· 50 51 }, 51 52 }); 52 53 53 - let tranquilService: ContainerService | undefined; 54 + const msmtprcFile = new asset.FileAsset(path.join(import.meta.dirname, "msmtprc")); 55 + const copyMsmtprc = new remote.CopyToRemote("tranquil-msmtprc", { 56 + connection: defaultConnection, 57 + source: msmtprcFile, 58 + remotePath: "/home/bas/docker/tranquil/msmtprc", 59 + }); 54 60 55 - if (postgresTranquilService.container) { 56 - const msmtprcFile = new asset.FileAsset(path.join(import.meta.dirname, "msmtprc")); 57 - const copyMsmtprc = new remote.CopyToRemote("tranquil-msmtprc", { 58 - connection: defaultConnection, 59 - source: msmtprcFile, 60 - remotePath: "/home/bas/docker/tranquil/msmtprc", 61 - }); 61 + const PDS_USER_HANDLE_DOMAINS = ["tranquil.bas.sh", "t.bas.sh", "on.bas.sh"]; 62 + export const tranquilDnsRecords = PDS_USER_HANDLE_DOMAINS.flatMap((host) => [ 63 + new DnsRecord(`tranquil-${host}`, { 64 + zoneId: getEnv("CLOUDFLARE_ZONE_ID"), 65 + name: host, 66 + ttl: 1, 67 + type: "CNAME", 68 + content: "haring.bas.sh", 69 + proxied: false, 70 + }), 71 + new DnsRecord(`tranquil-wildcard-${host}`, { 72 + zoneId: getEnv("CLOUDFLARE_ZONE_ID"), 73 + name: `*.${host}`, 74 + ttl: 1, 75 + type: "CNAME", 76 + content: "tranquil.bas.sh", 77 + proxied: false, 78 + }), 79 + ]); 62 80 63 - tranquilService = new ContainerService("tranquil", { 81 + export const tranquilService = new ContainerService( 82 + "tranquil", 83 + { 64 84 localImage: tranquilImage.digest, 65 85 servicePort: 3000, 66 86 hostRule: "HostRegexp(`^(.+?\\.)?(t(ranquil)|on)\\.bas\\.sh`)", ··· 82 102 DISCORD_BOT_TOKEN: getEnv("TRANQUIL_DISCORD_BOT_TOKEN"), 83 103 INVITE_CODE_REQUIRED: true, 84 104 ACCEPTING_REPO_IMPORTS: true, 85 - PDS_USER_HANDLE_DOMAINS: ["tranquil.bas.sh", "t.bas.sh", "on.bas.sh"], 105 + PDS_USER_HANDLE_DOMAINS, 86 106 CONTACT_EMAIL: getEnv("EMAIL"), 87 107 PDS_AGE_ASSURANCE_OVERRIDE: true, 88 108 CRAWLERS: fetchRelays(), ··· 106 126 "traefik.http.routers.tranquil-user-redirect.middlewares": 107 127 "cloudflare,tranquil-user-redirect", 108 128 }, 109 - }); 110 - } 111 - 112 - export { tranquilService }; 129 + }, 130 + { dependsOn: [...tranquilDnsRecords, copyMsmtprc] }, 131 + );
+21 -23
services/haring/files/nextcloud.ts
··· 3 3 import { confMount, ssdcacheMount } from "~lib/service/mounts"; 4 4 import { ContainerService } from "~lib/service/service"; 5 5 6 + const valkeyNextcloudService = new ContainerService("valkey-nextcloud", { 7 + image: "valkey/valkey", 8 + command: [ 9 + interpolate`--requirepass ${getEnv("VALKEY_PASSWORD")}`, 10 + "--save 60", 11 + "--loglevel warning", 12 + ], 13 + volumes: [{ volumeName: "valkey-unbound", containerPath: "/data" }], 14 + }); 15 + 16 + const postgresNextcloudService = new ContainerService("postgres-nextcloud", { 17 + image: "postgres", 18 + volumes: [{ volumeName: "postgres-nextcloud", containerPath: "/var/lib/postgresql" }], 19 + envs: { 20 + POSTGRES_PASSWORD: getEnv("POSTGRES_PASSWORD"), 21 + POSTGRES_DB: "nextcloud", 22 + }, 23 + }); 24 + 6 25 export const nextcloudService = new ContainerService("nextcloud", { 7 26 servicePort: 443, 8 27 mounts: [ ··· 12 31 ], 13 32 envs: { 14 33 DOCKER_MODS: "linuxserver/mods:nextcloud-notify-push|linuxserver/mods:nextcloud-mediadc", 15 - DATABASE_URL: interpolate`postgres://postgres:${getEnv("POSTGRES_PASSWORD")}@postgres-nextcloud/nextcloud`, 34 + DATABASE_URL: interpolate`postgres://postgres:${getEnv("POSTGRES_PASSWORD")}@${postgresNextcloudService.container.name}/nextcloud`, 16 35 DATABASE_PREFIX: "oc_", 17 - REDIS_URL: interpolate`redis://default:${getEnv("VALKEY_PASSWORD")}@valkey-nextcloud`, 36 + REDIS_URL: interpolate`redis://default:${getEnv("VALKEY_PASSWORD")}@${valkeyNextcloudService.container.name}`, 18 37 NEXTCLOUD_URL: "https://nextcloud.bas.sh", 19 38 }, 20 39 }); 21 - 22 - if (nextcloudService.container) { 23 - const valkeyNextcloudService = new ContainerService("valkey-nextcloud", { 24 - image: "valkey/valkey", 25 - command: [ 26 - interpolate`--requirepass ${getEnv("VALKEY_PASSWORD")}`, 27 - "--save 60", 28 - "--loglevel warning", 29 - ], 30 - volumes: [{ volumeName: "valkey-unbound", containerPath: "/data" }], 31 - }); 32 - 33 - const postgresService = new ContainerService("postgres-nextcloud", { 34 - image: "postgres", 35 - volumes: [{ volumeName: "postgres-nextcloud", containerPath: "/var/lib/postgresql" }], 36 - envs: { 37 - POSTGRES_PASSWORD: getEnv("POSTGRES_PASSWORD"), 38 - POSTGRES_DB: "nextcloud", 39 - }, 40 - }); 41 - }
-1
services/haring/files/resilio.ts
··· 2 2 import { ContainerService } from "~lib/service/service"; 3 3 4 4 export const resilioSyncService = new ContainerService("resilio-sync", { 5 - enabled: true, 6 5 subdomain: "sync", 7 6 servicePort: 8888, 8 7 // ports: [55555, "55555/udp"],
+6 -4
services/haring/files/sist2.ts
··· 12 12 middlewares: ["auth"], 13 13 }); 14 14 15 - if (sist2Service.container) { 16 - const elasticsearchService = new ContainerService("elasticsearch", { 15 + export const elasticsearchService = new ContainerService( 16 + "elasticsearch", 17 + { 17 18 image: "elasticsearch:7.17.28", 18 19 ports: ["127.0.0.1:9200:9200", "127.0.0.1:9300:9300"], 19 20 mounts: [confMount("elasticsearch", "/usr/share/elasticsearch/data")], 20 21 envs: { "discovery.type": "single-node" }, 21 - }); 22 - } 22 + }, 23 + { dependsOn: [sist2Service.container] }, 24 + );
-1
services/haring/games/minecraft/index.ts
··· 1 - import "./servers"; 2 1 import "./velocity"; 3 2 import "./rcon"; 4 3 // import "./web";
+1 -2
services/haring/games/minecraft/servers/frog.ts
··· 2 2 import { ContainerService } from "~lib/service/service"; 3 3 import { getEnv } from "~lib/env"; 4 4 5 - const minecraftService = new ContainerService( 5 + export const minecraftService = new ContainerService( 6 6 "minecraft", 7 7 { 8 - enabled: false, 9 8 image: "itzg/minecraft-server:java21-jdk", 10 9 servicePort: 8804, 11 10 subdomain: "mc",
+1 -2
services/haring/games/minecraft/servers/mau.ts
··· 2 2 import { ContainerService } from "~lib/service/service"; 3 3 import { getEnv } from "~lib/env"; 4 4 5 - const minecraftMauService = new ContainerService( 5 + export const minecraftMauService = new ContainerService( 6 6 "minecraft-mau", 7 7 { 8 - enabled: false, 9 8 image: "itzg/minecraft-server", 10 9 servicePort: 8804, // Plan 11 10 subdomain: "mau",
+1 -2
services/haring/games/minecraft/servers/meow.ts
··· 2 2 import { ContainerService } from "~lib/service/service"; 3 3 import { getEnv } from "~lib/env"; 4 4 5 - const minecraftMeow = new ContainerService( 5 + export const minecraftMeow = new ContainerService( 6 6 "minecraft-meow", 7 7 { 8 - enabled: false, 9 8 image: "itzg/minecraft-server:java24-graalvm", 10 9 // servicePort: 8804, // Plan 11 10 // subdomain: "meowmeowmeow",
+36 -38
services/haring/games/minecraft/servers/rengoku.ts
··· 3 3 import { getEnv } from "~lib/env"; 4 4 import { mcHasOnlinePlayers } from "../util"; 5 5 6 - const velocityRengokuService = new ContainerService("velocity-rengoku", { 7 - enabled: false, 8 - image: "itzg/mc-proxy:java25", 9 - servicePort: 8804, // Plan 10 - ports: [ 11 - { 12 - internal: 25565, 13 - external: 25562, 14 - }, 15 - ], 16 - mounts: [nvmeMount("velocity-rengoku", "/config")], 17 - envs: { 18 - TYPE: "VELOCITY", 19 - EULA: true, 20 - VELOCITY_VERSION: "3.4", 21 - MINECRAFT_VERSION: "1.21.11", 22 - MEMORY: "2G", 23 - ICON: "https://i.bas.sh/rengoku.jpg", 24 - OVERRIDE_ICON: true, 25 - JVM_XX_OPTS: 26 - "-XX:+UseZGC -XX:+UseCompactObjectHeaders -XX:+UseTransparentHugePages -XX:+EnableDynamicAgentLoading", 27 - ENABLE_RCON: true, 28 - RCON_PASSWORD: getEnv("RCON_PASSWORD"), 29 - 30 - // plugins 31 - MODRINTH_DOWNLOAD_DEPENDENCIES: "required", 32 - MODRINTH_PROJECTS: ["onetimepack", "plan", "antipopup"], 33 - }, 34 - }); 6 + // export const velocityRengokuService = new ContainerService("velocity-rengoku", { 7 + // image: "itzg/mc-proxy:java25", 8 + // servicePort: 8804, // Plan 9 + // ports: [ 10 + // { 11 + // internal: 25565, 12 + // external: 25562, 13 + // }, 14 + // ], 15 + // mounts: [nvmeMount("velocity-rengoku", "/config")], 16 + // envs: { 17 + // TYPE: "VELOCITY", 18 + // EULA: true, 19 + // VELOCITY_VERSION: "3.4", 20 + // MINECRAFT_VERSION: "1.21.11", 21 + // MEMORY: "2G", 22 + // ICON: "https://i.bas.sh/rengoku.jpg", 23 + // OVERRIDE_ICON: true, 24 + // JVM_XX_OPTS: 25 + // "-XX:+UseZGC -XX:+UseCompactObjectHeaders -XX:+UseTransparentHugePages -XX:+EnableDynamicAgentLoading", 26 + // ENABLE_RCON: true, 27 + // RCON_PASSWORD: getEnv("RCON_PASSWORD"), 28 + // 29 + // // plugins 30 + // MODRINTH_DOWNLOAD_DEPENDENCIES: "required", 31 + // MODRINTH_PROJECTS: ["onetimepack", "plan", "antipopup"], 32 + // }, 33 + // }); 35 34 36 - const limboRengokuService = new ContainerService("limbo-rengoku", { 37 - enabled: false, 38 - image: "itzg/minecraft-server:java25-graalvm", 39 - envs: { 40 - TYPE: "LIMBO", 41 - EULA: true, 42 - LIMBO_BUILD: "66", 43 - }, 44 - }); 35 + // export const limboRengokuService = new ContainerService("limbo-rengoku", { 36 + // image: "itzg/minecraft-server:java25-graalvm", 37 + // envs: { 38 + // TYPE: "LIMBO", 39 + // EULA: true, 40 + // LIMBO_BUILD: "66", 41 + // }, 42 + // }); 45 43 46 44 export const minecraftRengokuService = new ContainerService( 47 45 "minecraft-rengoku",
-1
services/haring/games/minecraft/servers/teena.ts
··· 5 5 const minecraftTeenaService = new ContainerService( 6 6 "minecraft-teena", 7 7 { 8 - enabled: false, 9 8 // image: "itzg/minecraft-server:java24-graalvm", 10 9 image: "itzg/minecraft-server", 11 10 // servicePort: 8804,
+1 -1
services/haring/index.ts
··· 76 76 export * from "./remote/czkawka"; 77 77 export * from "./remote/mkvtoolnix"; 78 78 export * from "./remote/mkv-muxing-batch"; 79 - // export * from "./remote/redroid"; 79 + // export * from "./remote/redroid"; // TODO: seems to break my entire server's networking setup somehow? 80 80 export * from "./remote/sealskin"; 81 81 82 82 export * from "./other/anki";
+7 -13
services/haring/media/plex.ts
··· 3 3 import { ContainerService } from "~lib/service/service"; 4 4 import { wireguardProtonService } from "../networking/wireguard"; 5 5 6 - let plexService: ContainerService | undefined; 7 - 8 - if (wireguardProtonService.container) { 9 - plexService = new ContainerService("plex", { 10 - servicePort: 32400, 11 - mounts: [confMount("plex"), dataMount(), gitMount()], 12 - networkMode: interpolate`container:${wireguardProtonService.container?.id}`, 13 - cpuShares: 2048, 14 - monitor: true, 15 - }); 16 - } 17 - 18 - export { plexService }; 6 + export const plexService = new ContainerService("plex", { 7 + servicePort: 32400, 8 + mounts: [confMount("plex"), dataMount(), gitMount()], 9 + networkMode: interpolate`container:${wireguardProtonService.container.id}`, 10 + cpuShares: 2048, 11 + monitor: true, 12 + });
+9 -10
services/haring/monitoring/uptimekuma.ts
··· 10 10 middlewares: ["auth"], 11 11 }); 12 12 13 - export const autokumaService = new ContainerService("autokuma", { 14 - enabled: false, 15 - image: "ghcr.io/bigboot/autokuma", 16 - mounts: [confMount("autokuma", "/data"), dockerSocket], 17 - envs: { 18 - AUTOKUMA__KUMA__URL: "http://uptimekuma:3001", 19 - AUTOKUMA__KUMA__USERNAME: getEnv("AUTOKUMA_USERNAME"), 20 - AUTOKUMA__KUMA__PASSWORD: getEnv("AUTOKUMA_PASSWORD"), 21 - }, 22 - }); 13 + // export const autokumaService = new ContainerService("autokuma", { 14 + // image: "ghcr.io/bigboot/autokuma", 15 + // mounts: [confMount("autokuma", "/data"), dockerSocket], 16 + // envs: { 17 + // AUTOKUMA__KUMA__URL: "http://uptimekuma:3001", 18 + // AUTOKUMA__KUMA__USERNAME: getEnv("AUTOKUMA_USERNAME"), 19 + // AUTOKUMA__KUMA__PASSWORD: getEnv("AUTOKUMA_PASSWORD"), 20 + // }, 21 + // });
+10 -16
services/haring/networking/tinyproxy.ts
··· 1 1 import { interpolate } from "@pulumi/pulumi"; 2 2 import { ContainerService } from "~lib/service/service"; 3 - import { wireguardMullvadService, wireguardProtonService } from "./wireguard"; 4 - 5 - let tinyproxyService: ContainerService | undefined; 6 - 7 - if (wireguardMullvadService.container || wireguardProtonService.container) { 8 - tinyproxyService = new ContainerService("tinyproxy", { 9 - image: "kalaksi/tinyproxy", 10 - envs: { 11 - LOG_LEVEL: "Info", 12 - TINYPROXY_UID: 1000, 13 - TINYPROXY_GID: 1000, 14 - }, 15 - networkMode: interpolate`container:${(wireguardMullvadService.container ?? wireguardProtonService.container)?.id}`, 16 - }); 17 - } 3 + import { wireguardProtonService } from "./wireguard"; 18 4 19 - export { tinyproxyService }; 5 + export const tinyproxyService = new ContainerService("tinyproxy", { 6 + image: "kalaksi/tinyproxy", 7 + envs: { 8 + LOG_LEVEL: "Info", 9 + TINYPROXY_UID: 1000, 10 + TINYPROXY_GID: 1000, 11 + }, 12 + networkMode: interpolate`container:${wireguardProtonService.container.id}`, 13 + });
+1 -1
services/haring/networking/unbound/unbound.ts
··· 52 52 networksAdvanced: [{ name: defaultNetwork.name, ipv4Address: STATIC_IPS.UNBOUND }], 53 53 }, 54 54 { 55 - dependsOn: valkeyUnboundService.container, 55 + dependsOn: [valkeyUnboundService.container], 56 56 }, 57 57 );
+16 -17
services/haring/networking/wireguard.ts
··· 19 19 cpuShares: 2048, 20 20 }); 21 21 22 - export const wireguardMullvadService = new ContainerService("wireguard-mullvad", { 23 - enabled: false, 24 - image: "lscr.io/linuxserver/wireguard", 25 - // ports: [8888], 26 - mounts: [confMount("wireguard-mullvad"), mount("/lib/modules")], 27 - // envs: ["PEERS=2"], 28 - // privileged: true, 29 - capabilities: ["NET_ADMIN", "SYS_MODULE"], 30 - sysctls: { "net.ipv4.conf.all.src_valid_mark": "1" }, 31 - healthcheck: { 32 - tests: ["CMD", "/usr/bin/curl", "-sS", "icanhazip.com"], 33 - interval: "20s", 34 - retries: 3, 35 - timeout: "10s", 36 - }, 37 - cpuShares: 2048, 38 - }); 22 + // export const wireguardMullvadService = new ContainerService("wireguard-mullvad", { 23 + // image: "lscr.io/linuxserver/wireguard", 24 + // // ports: [8888], 25 + // mounts: [confMount("wireguard-mullvad"), mount("/lib/modules")], 26 + // // envs: ["PEERS=2"], 27 + // // privileged: true, 28 + // capabilities: ["NET_ADMIN", "SYS_MODULE"], 29 + // sysctls: { "net.ipv4.conf.all.src_valid_mark": "1" }, 30 + // healthcheck: { 31 + // tests: ["CMD", "/usr/bin/curl", "-sS", "icanhazip.com"], 32 + // interval: "20s", 33 + // retries: 3, 34 + // timeout: "10s", 35 + // }, 36 + // cpuShares: 2048, 37 + // });
-1
services/haring/other/librespeed.ts
··· 2 2 import { ContainerService } from "~lib/service/service"; 3 3 4 4 export const librespeedService = new ContainerService("librespeed", { 5 - enabled: true, 6 5 image: "ghcr.io/librespeed/speedtest", 7 6 servicePort: 8080, 8 7 subdomain: "speedtest",
-1
services/haring/remote/redroid.ts
··· 2 2 import { ContainerService } from "~lib/service/service"; 3 3 4 4 export const redroidService = new ContainerService("redroid", { 5 - enabled: false, // seems to break my entire server's networking setup somehow? 6 5 image: "redroid/redroid:12.0.0_64only-latest", 7 6 privileged: true, 8 7 ports: ["100.93.167.100:5555:5555"],
+1
services/haring/web/traefik.ts
··· 11 11 { 12 12 retainOnDelete: true, 13 13 provider: haringDockerProvider, 14 + replacementTrigger: true, 14 15 }, 15 16 ); 16 17
-16
services/kaneelnas.ts
··· 45 45 provider: kaneelnasDockerProvider, 46 46 }, 47 47 ); 48 - 49 - const kaneelnasWhoamiService = new ContainerService( 50 - "whoami-kaneelnas", 51 - { 52 - enabled: false, 53 - name: "whoami-bas", 54 - image: "ghcr.io/traefik/whoami", 55 - // servicePort: 80, 56 - ports: ["8452:80"], 57 - networkMode: "bridge", 58 - commandConnection: kaneelnasConnection, 59 - }, 60 - { 61 - provider: kaneelnasDockerProvider, 62 - }, 63 - );