diff --git a/nix/treefmt.nix b/nix/treefmt.nix index 0c38c2fae..533875001 100644 --- a/nix/treefmt.nix +++ b/nix/treefmt.nix @@ -64,5 +64,6 @@ settings.global.excludes = [ "*/gen/*" "docs/*" + "*/generated.*" ]; } diff --git a/packages/oyasai-cdktf/src/stacks/common-infra.ts b/packages/oyasai-cdktf/src/stacks/common-infra.ts index 62a208345..6b6f3e613 100644 --- a/packages/oyasai-cdktf/src/stacks/common-infra.ts +++ b/packages/oyasai-cdktf/src/stacks/common-infra.ts @@ -82,8 +82,7 @@ export class CommonInfra extends OyasaiTerraformStack { const secrets = createSecrets(this, this); - // TODO: Data because Cloudflare doesn't support importing registrar domain - // - shun 2026-04 + // "Data" because Cloudflare doesn't support importing this resource this.oyasaiIoRegistrarDomain = new DataCloudflareRegistrarDomain( this, this.t("oyasai-io-registrar-domain"), diff --git a/packages/oyasai-cdktf/src/stacks/platform-infra.ts b/packages/oyasai-cdktf/src/stacks/platform-infra.ts index 9b07b98a9..bdaec7070 100644 --- a/packages/oyasai-cdktf/src/stacks/platform-infra.ts +++ b/packages/oyasai-cdktf/src/stacks/platform-infra.ts @@ -15,6 +15,7 @@ export class PlatformInfra extends OyasaiPlatformTerraformStack { public readonly ipv4 = "121.81.157.109"; public readonly r2Bucket: R2Bucket; + public readonly rootDnsRecord: DnsRecord; public constructor( scope: Construct, @@ -41,19 +42,26 @@ export class PlatformInfra extends OyasaiPlatformTerraformStack { location: this.isMaster ? "apac" : "enam", }); + this.rootDnsRecord = new DnsRecord(this, this.t("root-dns-record"), { + ttl: 1, // automatic + zoneId: oyasaiIoZone.id, + name: `${this.environment}.${oyasaiIoRegistrarDomain.domainName}`, + type: "A", + proxied: false, + content: this.isMaster ? this.ipv4 : "0.0.0.0", + }); + + // Vanity domains if (this.isMaster) { - // We _can_ make dns record for every environment, though it's currently - // unnecessary. - shun 2026-04 - new DnsRecord(this, this.t("root-dns-record"), { + new DnsRecord(this, "root-vanity-dns-record", { ttl: 1, // automatic zoneId: oyasaiIoZone.id, name: oyasaiIoRegistrarDomain.domainName, type: "A", proxied: false, - content: this.ipv4, + content: this.rootDnsRecord.content, }); - // Proxy to our seesaawiki. Implicitly reserves `wiki.oyasai.io`. new DnsRecord(this, "seesaawiki-cname-dns-record", { ttl: 1, // automatic zoneId: oyasaiIoZone.id, diff --git a/packages/oyasai-cdktf/src/stacks/platform-services.ts b/packages/oyasai-cdktf/src/stacks/platform-services.ts index d11613fb8..aebd798c5 100644 --- a/packages/oyasai-cdktf/src/stacks/platform-services.ts +++ b/packages/oyasai-cdktf/src/stacks/platform-services.ts @@ -56,6 +56,7 @@ export class PlatformServices extends OyasaiPlatformTerraformStack { minecraftLobby: imageIds["oyasai-minecraft-lobby"], minecraftMain: imageIds["oyasai-minecraft-main"], mysqlBackup: imageIds["mysql-backup"], + traefik: imageIds["traefik"], velocity: imageIds["oyasai-velocity"], // keep-sorted end } as const; @@ -67,6 +68,7 @@ export class PlatformServices extends OyasaiPlatformTerraformStack { minecraftAxiom: join(baseHostPath, "minecraft-axiom"), minecraftLobby: join(baseHostPath, "minecraft-lobby"), minecraftMain: join(baseHostPath, "minecraft-main"), + traefik: join(baseHostPath, "traefik"), velocity: join(baseHostPath, "velocity"), // keep-sorted end } as const; @@ -134,6 +136,25 @@ export class PlatformServices extends OyasaiPlatformTerraformStack { hostPath: hostPaths.minecraftMain, }, ], + labels: [ + { label: "traefik.enable", value: "true" }, + { + label: "traefik.http.routers.bluemap.rule", + value: `Host(\`bluemap.${platformInfra.rootDnsRecord.name}\`)`, + }, + { + label: "traefik.http.routers.bluemap.entrypoints", + value: "websecure", + }, + { + label: "traefik.http.routers.bluemap.tls.certresolver", + value: "myresolver", + }, + { + label: "traefik.http.services.bluemap.loadbalancer.server.port", + value: "8100", + }, + ], ...(this.isMaster && { // Pin container to P-cores only (logical CPUs 0–11) to avoid latency // spikes caused by the main tick thread being scheduled on E-cores. @@ -289,6 +310,61 @@ export class PlatformServices extends OyasaiPlatformTerraformStack { DB_DEBUG: true, }), }); + + new Container(this, this.t("traefik-container"), { + image: images.traefik, + name: "traefik", + restart: "unless-stopped", + networksAdvanced: [network], + ports: ports({ + tcp: [80, 443], + }), + command: [ + "--providers.docker=true", + "--providers.docker.exposedbydefault=false", + "--entrypoints.web.address=:80", + "--entrypoints.websecure.address=:443", + "--entrypoints.web.http.redirections.entrypoint.to=websecure", + "--entrypoints.web.http.redirections.entrypoint.scheme=https", + "--certificatesresolvers.myresolver.acme.tlschallenge=true", + ], + volumes: [ + { + hostPath: "/var/run/docker.sock", + containerPath: "/var/run/docker.sock", + }, + { + hostPath: hostPaths.traefik, + containerPath: "/etc/traefik/acme", + }, + ], + }); + + new Container(this, this.t("hello-world-container"), { + image: "containous/whoami:latest", + name: "hello-world-web", + restart: "unless-stopped", + networksAdvanced: [network], + labels: [ + { label: "traefik.enable", value: "true" }, + { + label: "traefik.http.routers.helloworld.rule", + value: `Host(\`${platformInfra.rootDnsRecord.name}\`)`, + }, + { + label: "traefik.http.routers.helloworld.entrypoints", + value: "websecure", + }, + { + label: "traefik.http.routers.helloworld.tls.certresolver", + value: "myresolver", + }, + { + label: "traefik.http.services.helloworld.loadbalancer.server.port", + value: "80", + }, + ], + }); } } } diff --git a/packages/oyasai-standalone-images/_sources/generated.json b/packages/oyasai-standalone-images/_sources/generated.json index caacc7f2d..4edd6fadb 100644 --- a/packages/oyasai-standalone-images/_sources/generated.json +++ b/packages/oyasai-standalone-images/_sources/generated.json @@ -1,62 +1,82 @@ { - "mariadb": { - "cargoLock": null, - "date": null, - "extract": null, - "name": "mariadb", - "passthru": null, - "pinned": false, - "src": { - "arch": "amd64", - "finalImageName": null, - "finalImageTag": null, - "imageDigest": "sha256:d8369cb7020907b44ab0a7a73f855e41cbd5b6da16e01fbd8b8fe21200c7a854", - "imageName": "mariadb", - "imageTag": "10.4.28", - "os": "linux", - "sha256": "sha256-pIEYzawoBIJI295R0ToQyueVVDGLfYItnn8mySwhvLs=", - "tlsVerify": null + "mariadb": { + "cargoLock": null, + "date": null, + "extract": null, + "name": "mariadb", + "passthru": null, + "pinned": false, + "src": { + "arch": "amd64", + "finalImageName": null, + "finalImageTag": null, + "imageDigest": "sha256:d8369cb7020907b44ab0a7a73f855e41cbd5b6da16e01fbd8b8fe21200c7a854", + "imageName": "mariadb", + "imageTag": "10.4.28", + "os": "linux", + "sha256": "sha256-pIEYzawoBIJI295R0ToQyueVVDGLfYItnn8mySwhvLs=", + "tlsVerify": null + }, + "version": "10.4.28" }, - "version": "10.4.28" - }, - "mc-backup": { - "cargoLock": null, - "date": null, - "extract": null, - "name": "mc-backup", - "passthru": null, - "pinned": false, - "src": { - "arch": "amd64", - "finalImageName": null, - "finalImageTag": null, - "imageDigest": "sha256:3ee78d8c49d70efc4dfc06d362051b5a028d4f28f2f0fe095e3cbccd29ab511d", - "imageName": "itzg/mc-backup", - "imageTag": "2026.5.0", - "os": "linux", - "sha256": "sha256-VbYN9VF/9LJPXhH5HSISQ+3YzqJlliUtiE39oWFPcBw=", - "tlsVerify": null + "mc-backup": { + "cargoLock": null, + "date": null, + "extract": null, + "name": "mc-backup", + "passthru": null, + "pinned": false, + "src": { + "arch": "amd64", + "finalImageName": null, + "finalImageTag": null, + "imageDigest": "sha256:f4f2b46f4cd67c74ba65ea3c2a4effb19804e834e84a64c071a4f3a47ebd6b44", + "imageName": "itzg/mc-backup", + "imageTag": "2026.6.0", + "os": "linux", + "sha256": "sha256-AqUbsoGMSb9SebWLrzWcjWMhYrC6COVcUIikjjUGG2Q=", + "tlsVerify": null + }, + "version": "2026.6.0" }, - "version": "2026.5.0" - }, - "mysql-backup": { - "cargoLock": null, - "date": null, - "extract": null, - "name": "mysql-backup", - "passthru": null, - "pinned": false, - "src": { - "arch": "amd64", - "finalImageName": null, - "finalImageTag": null, - "imageDigest": "sha256:9a9a12dc942c370040ced1d43ab9f7d1162e9fde7f38b0f7749c3516d79a812c", - "imageName": "databack/mysql-backup", - "imageTag": "1.4.0", - "os": "linux", - "sha256": "sha256-6I4SlD+N7jzTDqH7hfu6C7Sh4ALaL6WpkWqTjrhoCK0=", - "tlsVerify": null + "mysql-backup": { + "cargoLock": null, + "date": null, + "extract": null, + "name": "mysql-backup", + "passthru": null, + "pinned": false, + "src": { + "arch": "amd64", + "finalImageName": null, + "finalImageTag": null, + "imageDigest": "sha256:9a9a12dc942c370040ced1d43ab9f7d1162e9fde7f38b0f7749c3516d79a812c", + "imageName": "databack/mysql-backup", + "imageTag": "1.4.0", + "os": "linux", + "sha256": "sha256-6I4SlD+N7jzTDqH7hfu6C7Sh4ALaL6WpkWqTjrhoCK0=", + "tlsVerify": null + }, + "version": "1.4.0" }, - "version": "1.4.0" - } -} + "traefik": { + "cargoLock": null, + "date": null, + "extract": null, + "name": "traefik", + "passthru": null, + "pinned": false, + "src": { + "arch": "amd64", + "finalImageName": null, + "finalImageTag": null, + "imageDigest": "sha256:fcdef599e6259359833dd2e1d49f9e964f66825d69bd3dd468f51102ce013d03", + "imageName": "traefik", + "imageTag": "3.7.4", + "os": "linux", + "sha256": "sha256-R/XwyezYqVIK1+wm8DuDcdnCU4Z+qhfz8M9OWs64qFo=", + "tlsVerify": null + }, + "version": "3.7.4" + } +} \ No newline at end of file diff --git a/packages/oyasai-standalone-images/_sources/generated.nix b/packages/oyasai-standalone-images/_sources/generated.nix index 1d2418e09..50b910a62 100644 --- a/packages/oyasai-standalone-images/_sources/generated.nix +++ b/packages/oyasai-standalone-images/_sources/generated.nix @@ -20,12 +20,12 @@ }; mc-backup = { pname = "mc-backup"; - version = "2026.5.0"; + version = "2026.6.0"; src = dockerTools.pullImage { imageName = "itzg/mc-backup"; - imageDigest = "sha256:3ee78d8c49d70efc4dfc06d362051b5a028d4f28f2f0fe095e3cbccd29ab511d"; - sha256 = "sha256-VbYN9VF/9LJPXhH5HSISQ+3YzqJlliUtiE39oWFPcBw="; - finalImageTag = "2026.5.0"; + imageDigest = "sha256:f4f2b46f4cd67c74ba65ea3c2a4effb19804e834e84a64c071a4f3a47ebd6b44"; + sha256 = "sha256-AqUbsoGMSb9SebWLrzWcjWMhYrC6COVcUIikjjUGG2Q="; + finalImageTag = "2026.6.0"; os = "linux"; arch = "amd64"; }; @@ -42,4 +42,16 @@ arch = "amd64"; }; }; + traefik = { + pname = "traefik"; + version = "3.7.4"; + src = dockerTools.pullImage { + imageName = "traefik"; + imageDigest = "sha256:fcdef599e6259359833dd2e1d49f9e964f66825d69bd3dd468f51102ce013d03"; + sha256 = "sha256-R/XwyezYqVIK1+wm8DuDcdnCU4Z+qhfz8M9OWs64qFo="; + finalImageTag = "3.7.4"; + os = "linux"; + arch = "amd64"; + }; + }; } diff --git a/packages/oyasai-standalone-images/nvfetcher.toml b/packages/oyasai-standalone-images/nvfetcher.toml index fc1705e93..c709bd226 100644 --- a/packages/oyasai-standalone-images/nvfetcher.toml +++ b/packages/oyasai-standalone-images/nvfetcher.toml @@ -15,3 +15,9 @@ docker.os = "linux" docker.arch = "amd64" fetch.docker = "databack/mysql-backup" src.manual = "1.4.0" + +[traefik] +docker.os = "linux" +docker.arch = "amd64" +fetch.docker = "traefik" +src.manual = "3.7.4" diff --git a/packages/oyasai-standalone-images/package.nix b/packages/oyasai-standalone-images/package.nix index 2c2548c72..d968ac6f2 100644 --- a/packages/oyasai-standalone-images/package.nix +++ b/packages/oyasai-standalone-images/package.nix @@ -5,11 +5,19 @@ }: let - inherit (callPackage ./_sources/generated.nix { }) mariadb mc-backup mysql-backup; + inherit (callPackage ./_sources/generated.nix { }) + # keep-sorted start + mariadb + mc-backup + mysql-backup + traefik + # keep-sorted end + ; in runCommandLocal "oyasai-standalone-images" { passthru = { + # keep-sorted start block=yes mariadb = oyasaiDockerTools.buildImage { name = mariadb.pname; fromImage = mariadb.src; @@ -33,6 +41,15 @@ runCommandLocal "oyasai-standalone-images" Entrypoint = [ "/entrypoint" ]; }; }; + traefik = oyasaiDockerTools.buildImage { + name = traefik.pname; + fromImage = traefik.src; + config = { + Entrypoint = [ "/entrypoint.sh" ]; + Cmd = [ "traefik" ]; + }; + }; + # keep-sorted end }; } ''