feat(provision): Phase-0 throwaway test VM via vendored @olsitec/pulumi-hetzner

- Vendor hetzner module (Stage-1, trimmed to @pulumi/hcloud+js-yaml; dropped unused
  types.ts + bcrypt/axios/tls/vault deps). GOTCHA documented: cloud-init moves SSH
  to port 222.
- provision/: isolated stack (platformName foundation-test, no collision with
  olsicloud4-*) — one cx23 in nbg1-dc3 + firewall (222/80/443/2222) + Docker cloud-init.
  Dedicated throwaway ed25519 key (operator id_rsa already registered → uniqueness_error).
- Provisioned + verified: foundation-test @ 91.98.117.152, Docker 29.6.1, docker-over-SSH OK.

Token via ENV (pass), never committed; provision/state gitignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Niemann 2026-06-30 18:57:54 +02:00
parent 6a29db386f
commit 80a99c6f7e
13 changed files with 754 additions and 1 deletions

View file

@ -0,0 +1,392 @@
// hetzner/index.ts
import * as pulumi from "@pulumi/pulumi";
import * as hcloud from "@pulumi/hcloud";
import * as fs from "fs";
import {
getCloudInitConfig,
type GetCloudInitConfigArgs,
} from "./cloudinit-config";
import { LoadBalancerServiceHealthCheck } from "@pulumi/hcloud/types/input";
interface HetznerDeploymentConfig {
platformName: string;
datacenter: string;
}
interface HetznerDeploymentArgs {
platformName: string;
hcloudToken: pulumi.Input<string>;
vswitchId?: number;
sshKeyPath: string;
datacenter: string;
servers: {
name: string;
hostname: string;
image?: string;
type: string;
labels?: Record<string, string>;
cloudInitConfig?: GetCloudInitConfigArgs;
}[];
loadbalancers?: {
name: string;
services: {
name: string;
port: number;
targetPort: number;
proxyprotocol?: boolean;
healthCheck?: LoadBalancerServiceHealthCheck;
}[];
targets: Partial<hcloud.LoadBalancerTargetArgs>[];
}[];
}
class HetznerDeployment extends pulumi.ComponentResource {
public readonly deploymentConfig: HetznerDeploymentConfig;
public readonly servers: hcloud.Server[];
public readonly platformNetwork: hcloud.Network;
public readonly platformNetworkSubnetCloud: hcloud.NetworkSubnet;
public readonly platformNetworkSubnetDedicated?:
| hcloud.NetworkSubnet
| undefined;
public readonly platformNetworkSubnetLoadBalancers: hcloud.NetworkSubnet;
public readonly publicIps: hcloud.PrimaryIp[];
public readonly placementGroup: hcloud.PlacementGroup;
public readonly networkAttachments: hcloud.ServerNetwork[];
// public readonly loadBalancers: hcloud.LoadBalancer[];
public readonly loadBalancerInstances: {
loadBalancer: hcloud.LoadBalancer;
networkAttachment: hcloud.LoadBalancerNetwork;
services: hcloud.LoadBalancerService[];
targets: hcloud.LoadBalancerTarget[];
}[];
public readonly serverInfo: {
name: pulumi.Output<string>;
publicIp: pulumi.Output<string>;
privateIp: string;
}[];
constructor(
name: string,
args: HetznerDeploymentArgs,
opts?: pulumi.ComponentResourceOptions,
) {
super("custom:resource:HetznerDeployment", name, {}, opts);
this.deploymentConfig = args;
// Hetzner Provider
const hcloudProvider = new hcloud.Provider(
`${this.deploymentConfig.platformName}-hcloud-provider`,
{
token: args.hcloudToken,
},
{ parent: this },
);
/*
Hetzner API has 2 functions to get datacenters:
function getDatacenters(args: GetDatacentersArgs, opts?: InvokeOptions): Promise<GetDatacentersResult>
function getDatacentersOutput(args: GetDatacentersOutputArgs, opts?: InvokeOptions): Output<GetDatacentersResult>
We need to check wether this.deploymentConfig.datacenter is a valid datacenter.
*/
hcloud
.getDatacentersOutput({ provider: hcloudProvider, parent: this })
.apply((datacenters) => {
const isValid = datacenters.datacenters.some(
(dc) => dc.name === this.deploymentConfig.datacenter,
);
if (!isValid) {
throw new Error(
`Invalid datacenter: ${this.deploymentConfig.datacenter}, valid datacenters are: ${datacenters.datacenters
.map((dc) => dc.name)
.join(", ")}`,
);
}
});
const sshPublicKey = fs.readFileSync(args.sshKeyPath, "utf-8");
const sshKey = new hcloud.SshKey(
`${this.deploymentConfig.platformName}-ssh-key`,
{
name: `${this.deploymentConfig.platformName}-ssh-key`,
publicKey: sshPublicKey,
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [hcloudProvider],
deleteBeforeReplace: true,
},
);
this.placementGroup = new hcloud.PlacementGroup(
`${this.deploymentConfig.platformName}-hcloud-placement-group`,
{
name: `${this.deploymentConfig.platformName}-hcloud-placement-group`,
type: "spread",
labels: {
platform: this.deploymentConfig.platformName,
},
},
{ parent: this, provider: hcloudProvider, dependsOn: [hcloudProvider] },
);
// Allocate public IPs
this.publicIps = args.servers.map((server, serverIndex) => {
return new hcloud.PrimaryIp(
`${this.deploymentConfig.platformName}-hcloud-primary-ip-${(serverIndex + 1).toString().padStart(2, "0")}`,
{
type: "ipv4",
assigneeType: "server",
autoDelete: false,
datacenter: this.deploymentConfig.datacenter,
labels: {
platform: this.deploymentConfig.platformName,
role: "controlplane",
node: server.hostname,
},
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [hcloudProvider],
},
);
});
// Create a network
this.platformNetwork = new hcloud.Network(
`${this.deploymentConfig.platformName}-hcloud-network`,
{
name: `${this.deploymentConfig.platformName}-hcloud-network`,
ipRange: "10.254.0.0/16",
labels: {
platform: this.deploymentConfig.platformName,
},
deleteProtection: false,
},
{ parent: this, provider: hcloudProvider, dependsOn: [hcloudProvider] },
);
// Create a network subnet for cloud vms
this.platformNetworkSubnetCloud = new hcloud.NetworkSubnet(
`${this.deploymentConfig.platformName}-hcloud-network-subnet-cloud`,
{
type: "cloud",
networkId: this.platformNetwork.id.apply((id) => Number(id)),
networkZone: "eu-central",
ipRange: "10.254.1.0/24",
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [hcloudProvider, this.platformNetwork],
},
);
if (args.vswitchId) {
// Create a network subnet for dedicated servers
this.platformNetworkSubnetDedicated = new hcloud.NetworkSubnet(
`${this.deploymentConfig.platformName}-hcloud-network-subnet-dedicated`,
{
type: "vswitch",
networkId: this.platformNetwork.id.apply((id) => Number(id)),
networkZone: "eu-central",
ipRange: "10.254.2.0/24",
vswitchId: args.vswitchId,
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [hcloudProvider, this.platformNetwork],
},
);
}
this.servers = args.servers.map((server, serverIndex) => {
return new hcloud.Server(
`${this.deploymentConfig.platformName}-hcloud-server-${(serverIndex + 1).toString().padStart(2, "0")}-${server.name}`,
{
name: server.name,
serverType: server.type,
deleteProtection: false,
image: server.image || "debian-12",
datacenter: this.deploymentConfig.datacenter,
placementGroupId: this.placementGroup.id.apply((id) => Number(id)),
sshKeys: [sshKey.id],
publicNets: [
{
ipv4Enabled: true,
ipv6Enabled: false,
ipv4: this.publicIps[serverIndex].id.apply((id) => Number(id)),
},
],
labels: {
platform: this.deploymentConfig.platformName,
...server.labels,
},
userData: getCloudInitConfig({
...(server.cloudInitConfig || {}),
sshAuthorizedKeys: [sshPublicKey],
}),
},
{
parent: this,
provider: hcloudProvider,
ignoreChanges: ["userData"],
dependsOn: [
this.placementGroup,
this.platformNetwork,
this.platformNetworkSubnetCloud,
this.publicIps[serverIndex],
sshKey,
],
},
);
});
this.networkAttachments = this.servers.map((server, serverIndex) => {
return new hcloud.ServerNetwork(
`${this.deploymentConfig.platformName}-hcloud-server-network-${(serverIndex + 1).toString().padStart(2, "0")}-${args.servers[serverIndex].name}`,
{
serverId: server.id.apply((id) => Number(id)),
networkId: this.platformNetworkSubnetCloud.networkId.apply((id) =>
Number(id),
),
ip: `10.254.1.${serverIndex + 1 + 10}`,
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [
this.platformNetwork,
this.publicIps[serverIndex],
this.servers[serverIndex],
],
deletedWith: this.servers[serverIndex],
},
);
});
// Create a network subnet for dedicated servers
this.platformNetworkSubnetLoadBalancers = new hcloud.NetworkSubnet(
`${this.deploymentConfig.platformName}-hcloud-network-subnet-loadbalancers`,
{
type: "cloud",
networkId: this.platformNetwork.id.apply((id) => Number(id)),
networkZone: "eu-central",
ipRange: "10.254.3.0/24",
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [hcloudProvider, this.platformNetwork],
},
);
this.loadBalancerInstances =
args.loadbalancers?.map((loadBalancerArg, loadBalancerIndex) => {
const loadBalancer = new hcloud.LoadBalancer(
`${this.deploymentConfig.platformName}-hcloud-loadbalancer-${loadBalancerIndex}-${loadBalancerArg.name}`,
{
name: loadBalancerArg.name,
loadBalancerType: "lb11",
networkZone: "eu-central",
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [
this.platformNetwork,
this.platformNetworkSubnetLoadBalancers,
],
},
);
const networkAttachment = new hcloud.LoadBalancerNetwork(
`${this.deploymentConfig.platformName}-hcloud-loadbalancer-network-${loadBalancerIndex}-${loadBalancerArg.name}`,
{
subnetId: this.platformNetworkSubnetLoadBalancers.id.apply(
(id) => id,
),
// networkId: this.platformNetwork.id.apply((id) => Number(id)),
loadBalancerId: loadBalancer.id.apply((id) => Number(id)),
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [
loadBalancer,
this.platformNetwork,
this.platformNetworkSubnetLoadBalancers,
],
},
);
const loadBalancerTargets = loadBalancerArg.targets.map(
(target, targetIndex) => {
return new hcloud.LoadBalancerTarget(
`${this.deploymentConfig.platformName}-hcloud-loadbalancer-target-${loadBalancerIndex}-${loadBalancerArg.name}-${targetIndex}`,
{
type: target.type || "ip",
loadBalancerId: loadBalancer.id.apply((id) => Number(id)),
...target,
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [loadBalancer],
},
);
},
);
const loadBalancerServices = loadBalancerArg.services.map(
(service, serviceIndex) => {
return new hcloud.LoadBalancerService(
`${this.deploymentConfig.platformName}-hcloud-loadbalancer-service-${loadBalancerIndex}-${loadBalancerArg.name}-${serviceIndex}`,
{
loadBalancerId: loadBalancer.id.apply((id) => id),
protocol: "tcp",
listenPort: service.port,
destinationPort: service.targetPort,
proxyprotocol: service.proxyprotocol || false,
healthCheck: service.healthCheck || {
port: service.targetPort,
protocol: "tcp",
interval: 15,
timeout: 10,
retries: 3,
},
},
{
parent: this,
provider: hcloudProvider,
dependsOn: [loadBalancer],
deleteBeforeReplace: true,
},
);
},
);
return {
loadBalancer: loadBalancer,
networkAttachment: networkAttachment,
targets: loadBalancerTargets,
services: loadBalancerServices,
};
}) || [];
this.serverInfo = this.servers.map((server, serverIndex) => {
return {
name: server.name,
publicIp: this.publicIps[serverIndex].ipAddress,
privateIp: `10.254.1.${serverIndex + 1 + 10}`,
};
});
this.registerOutputs({
servers: this.servers,
platformNetwork: this.platformNetwork,
publicIps: this.publicIps,
serverInfo: this.serverInfo,
networkAttachments: this.networkAttachments,
});
}
}
export { HetznerDeployment };