mirror of
https://github.com/KevinMidboe/schleppe-ha-project.git
synced 2026-02-13 11:09:08 +00:00
defines network, subnets, cloudflare dns & floatingip
This commit is contained in:
44
hetzner-pulumi/resources/cloudflare.ts
Normal file
44
hetzner-pulumi/resources/cloudflare.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import * as hcloud from "@pulumi/hcloud";
|
||||
import * as cloudflare from "@pulumi/cloudflare";
|
||||
|
||||
async function getZone(domain: string): Promise<cloudflare.Zone | null> {
|
||||
let match;
|
||||
const zones = await cloudflare.getZones();
|
||||
|
||||
zones.results.forEach((zone) => {
|
||||
if (domain.includes(zone.name)) match = zone;
|
||||
});
|
||||
|
||||
if (match) return match;
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function dns(
|
||||
domain: string,
|
||||
ipAddress: hcloud.FloatingIp,
|
||||
suffix: string,
|
||||
) {
|
||||
const ip = ipAddress.ipAddress.apply((ip) => ip);
|
||||
const name = `${domain}-${suffix}_dns_record`;
|
||||
const comment = "managed by pulumi - schleppe-ha-project";
|
||||
|
||||
const zone = await getZone(domain);
|
||||
if (!zone)
|
||||
throw new Error(
|
||||
"no matching zone found! check cloudflare token scopes & registration",
|
||||
);
|
||||
|
||||
return new cloudflare.DnsRecord(
|
||||
name,
|
||||
{
|
||||
zoneId: zone.id,
|
||||
name: domain,
|
||||
ttl: 1,
|
||||
type: "A",
|
||||
content: ip,
|
||||
proxied: false,
|
||||
comment,
|
||||
},
|
||||
{ dependsOn: [ipAddress] },
|
||||
);
|
||||
}
|
||||
@@ -1,51 +1,86 @@
|
||||
import * as pulumi from "@pulumi/pulumi";
|
||||
import * as hcloud from "@pulumi/hcloud";
|
||||
import * as random from "@pulumi/random";
|
||||
import { config } from './config';
|
||||
import { getCheapestServerType } from './utils';
|
||||
import { config } from "./config";
|
||||
import { getCheapestServerType, topicedLabel } from "./utils";
|
||||
|
||||
import { VmSize, OS, ServerLocations } from "./types";
|
||||
|
||||
// “Tag” servers using labels. Hetzner firewalls can target servers by label selectors. :contentReference[oaicite:2]{index=2}
|
||||
const serverLabels = {
|
||||
app: "demo",
|
||||
role: "web",
|
||||
env: pulumi.getStack(),
|
||||
managed: "pulumi",
|
||||
};
|
||||
|
||||
const sshPublicKey = config.require("sshPublicKey");
|
||||
const sshKey = new hcloud.SshKey("ssh-key", {
|
||||
name: `pulumi-${pulumi.getStack()}-ssh`,
|
||||
publicKey: sshPublicKey,
|
||||
});
|
||||
const sshKey = new hcloud.SshKey("ssh-key", {
|
||||
name: `pulumi-${pulumi.getStack()}-ssh`,
|
||||
publicKey: sshPublicKey,
|
||||
});
|
||||
|
||||
const serverName = (name: string, location: string) => {
|
||||
if (name.includes("-")) {
|
||||
const [n, id] = name.split("-");
|
||||
return `${n}-${location}-${id}`;
|
||||
}
|
||||
|
||||
return `${name}-${location}`;
|
||||
};
|
||||
|
||||
export function server(
|
||||
name: string,
|
||||
size: VmSize,
|
||||
os: OS = OS.debian,
|
||||
location: ServerLocations,
|
||||
network: hcloud.NetworkSubnet
|
||||
network: hcloud.NetworkSubnet,
|
||||
ipv4: boolean = false,
|
||||
): hcloud.Server {
|
||||
const ceap = getCheapestServerType('eu');
|
||||
const extraLabel = topicedLabel(name)
|
||||
name = serverName(name, location);
|
||||
const networkId = network.networkId.apply((id) => String(id).split("-")[0]);
|
||||
|
||||
const hexId = new random.RandomId(`${name}-${location}`, {
|
||||
byteLength: 2, // 2 bytes = 4 hex characters
|
||||
});
|
||||
|
||||
name = `${name}-${location}`
|
||||
|
||||
return new hcloud.Server(name, {
|
||||
const server = new hcloud.Server(
|
||||
name,
|
||||
image: os,
|
||||
serverType: ceap,
|
||||
location,
|
||||
backups: false,
|
||||
publicNets: [{
|
||||
ipv4Enabled: false,
|
||||
ipv6Enabled: true,
|
||||
}],
|
||||
networks: [network],
|
||||
sshKeys: [sshKey.name],
|
||||
labels: serverLabels
|
||||
})
|
||||
{
|
||||
name,
|
||||
image: os,
|
||||
serverType: size,
|
||||
location,
|
||||
backups: false,
|
||||
publicNets: [
|
||||
{
|
||||
ipv4Enabled: ipv4,
|
||||
ipv6Enabled: true,
|
||||
},
|
||||
],
|
||||
networks: [
|
||||
{
|
||||
networkId: networkId.apply((nid) => Number(nid)),
|
||||
},
|
||||
],
|
||||
sshKeys: [sshKey.name],
|
||||
labels: {
|
||||
...serverLabels,
|
||||
...extraLabel
|
||||
},
|
||||
},
|
||||
{ dependsOn: [network] },
|
||||
);
|
||||
|
||||
const serverNet = new hcloud.ServerNetwork(
|
||||
`${name}-servernet-${location}`,
|
||||
{
|
||||
serverId: server.id.apply((id) => Number(id)),
|
||||
subnetId: network.id,
|
||||
},
|
||||
{
|
||||
dependsOn: [network, server],
|
||||
parent: server,
|
||||
deleteBeforeReplace: true,
|
||||
|
||||
ignoreChanges: [ 'serverId', 'ip', 'aliasIps', 'networkId', 'subnetId' ]
|
||||
},
|
||||
);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
@@ -2,24 +2,22 @@ import * as pulumi from "@pulumi/pulumi";
|
||||
import * as hcloud from "@pulumi/hcloud";
|
||||
|
||||
import type { NetworkRegion } from "./types";
|
||||
import { currentIPAddress } from "./utils";
|
||||
|
||||
// Required
|
||||
|
||||
// make sure to have regional parent networks
|
||||
|
||||
// NETWORKS
|
||||
const networkName = (name: string, region: NetworkRegion) =>
|
||||
`${name}-net-${region}`;
|
||||
|
||||
export function regionalNetwork(
|
||||
prefix: string,
|
||||
name: string,
|
||||
cidr: string,
|
||||
region: NetworkRegion,
|
||||
) {
|
||||
const name = networkName(prefix, region);
|
||||
const parentNetworkRange = 8;
|
||||
const parentNetworkRange = 22;
|
||||
const [ip, _] = cidr.split("/");
|
||||
|
||||
const net = new hcloud.Network(name, {
|
||||
name,
|
||||
ipRange: `${ip}/${parentNetworkRange}`,
|
||||
labels: {
|
||||
region,
|
||||
@@ -38,16 +36,33 @@ export function subNetwork(
|
||||
): hcloud.NetworkSubnet {
|
||||
const name = `${prefix}-subnet-${region}`;
|
||||
|
||||
const net = new hcloud.NetworkSubnet(name, {
|
||||
networkId: parentNetwork.id.apply(id => Number(id)),
|
||||
type: "cloud",
|
||||
networkZone: "eu-central",
|
||||
ipRange: cidr,
|
||||
});
|
||||
const net = new hcloud.NetworkSubnet(
|
||||
name,
|
||||
{
|
||||
networkId: parentNetwork.id.apply((id) => Number(id)),
|
||||
type: "cloud",
|
||||
networkZone: region,
|
||||
ipRange: cidr,
|
||||
},
|
||||
{ parent: parentNetwork, dependsOn: [parentNetwork] },
|
||||
);
|
||||
|
||||
return net;
|
||||
}
|
||||
|
||||
// FLOATING IPs
|
||||
export function floatingIP(name: string, server: hcloud.Server) {
|
||||
return new hcloud.FloatingIp(
|
||||
name,
|
||||
{
|
||||
type: "ipv4",
|
||||
serverId: server.id.apply((i) => Number(i)),
|
||||
},
|
||||
{ dependsOn: [server] },
|
||||
);
|
||||
}
|
||||
|
||||
// FIREWALL RULES
|
||||
export const allowHttp = new hcloud.Firewall("allow-http", {
|
||||
name: "allow-http",
|
||||
applyTos: [
|
||||
@@ -80,16 +95,30 @@ export const allowHttp = new hcloud.Firewall("allow-http", {
|
||||
],
|
||||
});
|
||||
|
||||
export const allowSSH = new hcloud.Firewall("allow-ssh", {
|
||||
name: "allow-ssh",
|
||||
rules: [
|
||||
{
|
||||
direction: "in",
|
||||
protocol: "tcp",
|
||||
port: "22",
|
||||
sourceIps: ["127.0.0.0/24"],
|
||||
description: "Allow SSH from approved CIDRs only",
|
||||
},
|
||||
],
|
||||
});
|
||||
export function allowSSHToCurrentIP() {
|
||||
const ip = currentIPAddress()
|
||||
|
||||
return new hcloud.Firewall("allow-ssh", {
|
||||
name: "allow-ssh",
|
||||
rules: [
|
||||
{
|
||||
direction: "in",
|
||||
protocol: "tcp",
|
||||
port: "22",
|
||||
sourceIps: [ip],
|
||||
description: "Allow SSH from approved CIDRs only",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function attach(
|
||||
name: string,
|
||||
firewall: hcloud.Firewall,
|
||||
servers: hcloud.Server[],
|
||||
) {
|
||||
return new hcloud.FirewallAttachment(name, {
|
||||
firewallId: firewall.id.apply((id) => Number(id)),
|
||||
serverIds: servers.map((server) => server.id.apply((id) => Number(id))),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ export enum VmSize {
|
||||
small = "small",
|
||||
medium = "medium",
|
||||
large = "large",
|
||||
cx23 = "cx23",
|
||||
cax11 = "cax11",
|
||||
cpx11 = "cpx11"
|
||||
}
|
||||
|
||||
export enum OS {
|
||||
debian = "debian",
|
||||
debian = "debian-13",
|
||||
ubuntu = "ubuntu",
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as pulumi from "@pulumi/pulumi";
|
||||
import * as hcloud from "@pulumi/hcloud";
|
||||
import { z } from "zod";
|
||||
import * as crypto from "node:crypto";
|
||||
|
||||
@@ -103,3 +104,62 @@ export function getCheapestServerType(
|
||||
});
|
||||
}
|
||||
|
||||
interface Label {
|
||||
role?: string
|
||||
}
|
||||
|
||||
export function topicedLabel(name: string) {
|
||||
let labels: Label = {};
|
||||
if (name.includes("haproxy")) {
|
||||
labels.role = 'load-balancer';
|
||||
} else if (name.includes("web")) {
|
||||
labels.role = 'web'
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
|
||||
export const summarizeServer = (s: hcloud.Server) => ({
|
||||
name: s.name,
|
||||
publicIpv4: s.ipv4Address,
|
||||
publicIpv6: s.ipv6Address,
|
||||
privateIp: s.networks.apply(nets => nets?.[0]?.ip ?? 'null'),
|
||||
});
|
||||
|
||||
export const summarizeNetwork = (n: hcloud.Network) => ({
|
||||
name: n.name,
|
||||
cidr: n.ipRange
|
||||
});
|
||||
|
||||
export const summarizeSubNetwork = (n: hcloud.NetworkSubnet) => ({
|
||||
gateway: n.gateway,
|
||||
cidr: n.ipRange,
|
||||
zone: n.networkZone,
|
||||
type: n.type
|
||||
});
|
||||
|
||||
export const summarizeFloatingIp = (floatingIp: hcloud.FloatingIp) => ({
|
||||
name: floatingIp.name,
|
||||
address: floatingIp.ipAddress,
|
||||
attachedTo: floatingIp.serverId,
|
||||
location: floatingIp.homeLocation,
|
||||
labels: floatingIp.labels
|
||||
})
|
||||
|
||||
export const summarizeFirewall = (firewall: hcloud.Firewall) => ({
|
||||
name: firewall.name,
|
||||
rules: firewall.rules,
|
||||
labels: firewall.labels
|
||||
})
|
||||
|
||||
export const summarizeDns = (firewall: hcloud.Firewall) => ({
|
||||
name: firewall.name,
|
||||
rules: firewall.rules,
|
||||
labels: firewall.labels
|
||||
})
|
||||
|
||||
export async function currentIPAddress(): Promise<string> {
|
||||
return fetch('https://ifconfig.me/ip')
|
||||
.then(resp => resp.text())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user