mirror of
https://github.com/KevinMidboe/schleppe-pulumi.git
synced 2026-01-07 18:05:53 +00:00
Compare commits
2 Commits
ec0eb23acd
...
7b42f2e3bd
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b42f2e3bd | |||
| 80b58a9f3e |
74
docker-compose/00_traefik/docker-compose.yml
Normal file
74
docker-compose/00_traefik/docker-compose.yml
Normal file
@@ -0,0 +1,74 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:latest"
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
|
||||
# PORTS
|
||||
ports:
|
||||
# HTTP entrypoint
|
||||
# Exposed on all external addresses (0.0.0.0:80)
|
||||
- "80:80"
|
||||
|
||||
# Traefik API & Dashboard
|
||||
# Accessible on http://<host>:8080
|
||||
- "8080:8080"
|
||||
|
||||
# COMMAND (STATIC CONFIGURATION)
|
||||
command:
|
||||
# Enable Traefik API & Dashboard
|
||||
- "--api.dashboard=true"
|
||||
- "--api.insecure=true"
|
||||
|
||||
# Log settings
|
||||
- "--log.level=INFO"
|
||||
- "--accesslog=true"
|
||||
|
||||
# EntryPoints
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.traefik.address=:8080"
|
||||
|
||||
# Docker provider
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
|
||||
# Optional: file provider for dynamic config
|
||||
- "--providers.file.directory=/etc/traefik/dynamic"
|
||||
- "--providers.file.watch=true"
|
||||
|
||||
# Global settings
|
||||
- "--global.checknewversion=true"
|
||||
- "--global.sendanonymoususage=false"
|
||||
|
||||
# VOLUMES
|
||||
volumes:
|
||||
# Docker socket (required for Docker provider)
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
# Dynamic configuration directory (middlewares, routers, TLS, etc.)
|
||||
- ./dynamic:/etc/traefik/dynamic:ro
|
||||
|
||||
# Logs (optional)
|
||||
- ./logs:/logs
|
||||
|
||||
# NETWORKS
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
# LABELS (OPTIONAL SELF-ROUTING)
|
||||
labels:
|
||||
# Enable Traefik for this container
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Router for dashboard (via Traefik itself)
|
||||
- "traefik.http.routers.traefik.rule=Host(`traefik.localhost`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=web"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
|
||||
# NETWORK DEFINITIONS
|
||||
networks:
|
||||
traefik:
|
||||
name: traefik
|
||||
driver: bridge
|
||||
62
docker-compose/compose-all.sh
Normal file
62
docker-compose/compose-all.sh
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
########################################
|
||||
# CONFIG
|
||||
########################################
|
||||
COMPOSE_FILE_NAME="docker-compose.yml"
|
||||
|
||||
########################################
|
||||
# ARGUMENT CHECK
|
||||
########################################
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: $0 {up|down}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACTION="$1"
|
||||
|
||||
if [[ "$ACTION" != "up" && "$ACTION" != "down" ]]; then
|
||||
echo "Invalid action: $ACTION"
|
||||
echo "Allowed actions: up, down"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
########################################
|
||||
# SAVE STARTING DIRECTORY
|
||||
########################################
|
||||
START_DIR="$(pwd)"
|
||||
|
||||
########################################
|
||||
# FIND COMPOSE FILES
|
||||
########################################
|
||||
mapfile -t COMPOSE_DIRS < <(
|
||||
find . -type f -name "$COMPOSE_FILE_NAME" -print0 \
|
||||
| xargs -0 -n1 dirname | sort
|
||||
)
|
||||
|
||||
########################################
|
||||
# LOOP THROUGH DIRECTORIES
|
||||
########################################
|
||||
for DIR in "${COMPOSE_DIRS[@]}"; do
|
||||
echo "----------------------------------------"
|
||||
echo "Processing: $DIR"
|
||||
echo "Action: docker-compose $ACTION"
|
||||
echo "----------------------------------------"
|
||||
|
||||
cd "$DIR"
|
||||
|
||||
if [[ "$ACTION" == "up" ]]; then
|
||||
docker-compose up -d
|
||||
else
|
||||
docker-compose down
|
||||
fi
|
||||
|
||||
cd "$START_DIR"
|
||||
done
|
||||
|
||||
echo "========================================"
|
||||
echo "Completed docker-compose $ACTION for all stacks"
|
||||
echo "========================================"
|
||||
|
||||
28
docker-compose/k9e/docker-compose.yml
Normal file
28
docker-compose/k9e/docker-compose.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
k9e:
|
||||
image: kevinmidboe/k9e.no:latest
|
||||
container_name: k9e
|
||||
restart: unless-stopped
|
||||
|
||||
# NETWORK
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
# TRAEFIK LABELS
|
||||
labels:
|
||||
# Enable Traefik for this container
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Router definition
|
||||
- "traefik.http.routers.k9e.rule=Host(`k9e.no`)"
|
||||
- "traefik.http.routers.k9e.entrypoints=web"
|
||||
|
||||
# Service definition
|
||||
- "traefik.http.services.k9e.loadbalancer.server.port=80"
|
||||
|
||||
# NETWORK DEFINITIONS
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
28
docker-compose/planetposen/docker-compose.yml
Normal file
28
docker-compose/planetposen/docker-compose.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
planetposen-original:
|
||||
image: kevinmidboe/planetposen-original:latest
|
||||
container_name: planetposen
|
||||
restart: unless-stopped
|
||||
|
||||
# NETWORK
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
# TRAEFIK LABELS
|
||||
labels:
|
||||
# Enable Traefik for this container
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Router definition
|
||||
- "traefik.http.routers.planetposen-original.rule=Host(`planetposen.no`)"
|
||||
- "traefik.http.routers.planetposen-original.entrypoints=web"
|
||||
|
||||
# Service definition
|
||||
- "traefik.http.services.planetposen-original.loadbalancer.server.port=80"
|
||||
|
||||
# NETWORK DEFINITIONS
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
29
docker-compose/whoami/docker-compose.yml
Normal file
29
docker-compose/whoami/docker-compose.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
whoami:
|
||||
image: traefik/whoami
|
||||
container_name: whoami
|
||||
restart: unless-stopped
|
||||
|
||||
# NETWORK
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
# TRAEFIK LABELS
|
||||
labels:
|
||||
# Enable Traefik for this container
|
||||
- "traefik.enable=true"
|
||||
|
||||
# Router definition
|
||||
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
|
||||
- "traefik.http.routers.whoami.entrypoints=web"
|
||||
|
||||
# Service definition
|
||||
- "traefik.http.services.whoami.loadbalancer.server.port=80"
|
||||
|
||||
# NETWORK DEFINITIONS
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
|
||||
2
hetzner-pulumi/.gitignore
vendored
Normal file
2
hetzner-pulumi/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/bin/
|
||||
/node_modules/
|
||||
6
hetzner-pulumi/Pulumi.yaml
Normal file
6
hetzner-pulumi/Pulumi.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
name: hetzner-pulumi
|
||||
description: Manages schleppe ha project hetzner resources
|
||||
runtime:
|
||||
name: nodejs
|
||||
options:
|
||||
packagemanager: yarn
|
||||
54
hetzner-pulumi/index.ts
Normal file
54
hetzner-pulumi/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
subNetwork,
|
||||
regionalNetwork,
|
||||
} from "./resources/network";
|
||||
import { genServer } from "./resources/compute";
|
||||
|
||||
import {
|
||||
VmSize,
|
||||
OS,
|
||||
NetworkRegion,
|
||||
NetworkRole,
|
||||
ServerLocations,
|
||||
} from "./resources/types";
|
||||
|
||||
const eu = regionalNetwork("ha", "10.24.0.0/18", NetworkRegion.eu);
|
||||
const usEast = regionalNetwork("ha", "10.25.0.0/18", NetworkRegion.usEast);
|
||||
|
||||
const network = {
|
||||
eu: {
|
||||
lb: subNetwork(eu, NetworkRole.lb, NetworkRegion.eu, "10.24.1.0/24"),
|
||||
cache: subNetwork(eu, NetworkRole.cache, NetworkRegion.eu, "10.24.2.0/24"),
|
||||
web: subNetwork(eu, NetworkRole.web, NetworkRegion.eu, "10.24.3.0/24"),
|
||||
// db: subNetwork(eu, NetworkRole.db, "10.24.4.0/24")
|
||||
},
|
||||
us: {
|
||||
lb: subNetwork(usEast, NetworkRole.lb, NetworkRegion.usEast, "10.25.1.0/24"),
|
||||
web: subNetwork(usEast, NetworkRole.web, NetworkRegion.usEast, "10.25.2.0/24"),
|
||||
},
|
||||
};
|
||||
|
||||
const hel1 = ServerLocations.helsinki;
|
||||
const hil = ServerLocations.hillsboro;
|
||||
|
||||
const haproxyEU1 = genServer("haproxy-1", VmSize.small, OS.debian, hel1, network.eu.lb);
|
||||
const haproxyEU2 = genServer("haproxy-2", VmSize.small, OS.debian, hel1, network.eu.lb);
|
||||
const haproxyUS1 = genServer("haproxy-1", VmSize.small, OS.debian, hil, network.us.lb);
|
||||
|
||||
const haproxyCache1 = genServer("varnish-1", VmSize.small, OS.debian, hel1, network.eu.cache);
|
||||
const haproxyCache2 = genServer("varnish-2", VmSize.small, OS.debian, hel1, network.eu.cache);
|
||||
// const varnishUS = genServer(2, 'varnish', VmSize.small, OS.debian, hel1, network.us.cache)
|
||||
|
||||
export const servers = [
|
||||
haproxyEU1, haproxyEU2, haproxyUS1, haproxyCache1, haproxyCache2
|
||||
];
|
||||
|
||||
export const networks = [
|
||||
eu,
|
||||
usEast,
|
||||
network.eu.lb,
|
||||
network.eu.cache,
|
||||
network.eu.web,
|
||||
network.us.lb,
|
||||
network.us.web,
|
||||
];
|
||||
14
hetzner-pulumi/package.json
Normal file
14
hetzner-pulumi/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "hetzner-pulumi",
|
||||
"main": "index.ts",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pulumi/hcloud": "^1.29.0",
|
||||
"@pulumi/pulumi": "^3.213.0",
|
||||
"@pulumi/random": "^4.18.4",
|
||||
"zod": "^4.2.1"
|
||||
}
|
||||
}
|
||||
52
hetzner-pulumi/resources/compute.ts
Normal file
52
hetzner-pulumi/resources/compute.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
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 { 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(),
|
||||
};
|
||||
|
||||
/*
|
||||
function getSshPublicKey(): hcloud.SshKey {
|
||||
const sshPublicKey = config.require("sshPublicKey");
|
||||
return sshKey;
|
||||
}
|
||||
*/
|
||||
|
||||
const sshPublicKey = config.require("sshPublicKey");
|
||||
const sshKey = new hcloud.SshKey("ssh-key", {
|
||||
name: `pulumi-${pulumi.getStack()}-ssh`,
|
||||
publicKey: sshPublicKey,
|
||||
});
|
||||
|
||||
export function genServer(
|
||||
name: string,
|
||||
size: VmSize,
|
||||
os: OS = OS.debian,
|
||||
location: ServerLocations,
|
||||
network: hcloud.NetworkSubnet
|
||||
): hcloud.Server {
|
||||
const ceap = getCheapestServerType('eu');
|
||||
const hexId = new random.RandomId(`${name}-${location}`, {
|
||||
byteLength: 2, // 2 bytes = 4 hex characters
|
||||
});
|
||||
|
||||
name = `${name}-${location}`
|
||||
|
||||
return new hcloud.Server(name, {
|
||||
name,
|
||||
image: os,
|
||||
serverType: ceap,
|
||||
location,
|
||||
networks: [network],
|
||||
sshKeys: [sshKey.name],
|
||||
labels: serverLabels
|
||||
})
|
||||
}
|
||||
16
hetzner-pulumi/resources/config.ts
Normal file
16
hetzner-pulumi/resources/config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as pulumi from "@pulumi/pulumi";
|
||||
|
||||
const config = new pulumi.Config();
|
||||
|
||||
const variables = {
|
||||
osImage: config.get("image") || "debian-11",
|
||||
machineType: config.get("serverType") || "f1-micro",
|
||||
machineLocation: config.get("location") || "hel1",
|
||||
instanceTag: config.get("instanceTag") || "webserver",
|
||||
servicePort: config.get("servicePort") || "80"
|
||||
}
|
||||
|
||||
export {
|
||||
variables,
|
||||
config
|
||||
}
|
||||
95
hetzner-pulumi/resources/network.ts
Normal file
95
hetzner-pulumi/resources/network.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import * as pulumi from "@pulumi/pulumi";
|
||||
import * as hcloud from "@pulumi/hcloud";
|
||||
|
||||
import type { NetworkRegion } from "./types";
|
||||
|
||||
// Required
|
||||
|
||||
// make sure to have regional parent networks
|
||||
|
||||
const networkName = (name: string, region: NetworkRegion) =>
|
||||
`${name}-net-${region}`;
|
||||
|
||||
export function regionalNetwork(
|
||||
prefix: string,
|
||||
cidr: string,
|
||||
region: NetworkRegion,
|
||||
) {
|
||||
const name = networkName(prefix, region);
|
||||
const parentNetworkRange = 8;
|
||||
const [ip, _] = cidr.split("/");
|
||||
|
||||
const net = new hcloud.Network(name, {
|
||||
ipRange: `${ip}/${parentNetworkRange}`,
|
||||
labels: {
|
||||
region,
|
||||
hiearchy: "parent",
|
||||
},
|
||||
});
|
||||
|
||||
return net;
|
||||
}
|
||||
|
||||
export function subNetwork(
|
||||
parentNetwork: hcloud.Network,
|
||||
prefix: string,
|
||||
region: NetworkRegion,
|
||||
cidr: string,
|
||||
): 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,
|
||||
});
|
||||
|
||||
return net;
|
||||
}
|
||||
|
||||
export const allowHttp = new hcloud.Firewall("allow-http", {
|
||||
name: "allow-http",
|
||||
applyTos: [
|
||||
{
|
||||
labelSelector: `role=load-balancer,env=${pulumi.getStack()}`,
|
||||
},
|
||||
],
|
||||
rules: [
|
||||
{
|
||||
direction: "in",
|
||||
protocol: "tcp",
|
||||
port: "80",
|
||||
sourceIps: ["0.0.0.0/0", "::/0"],
|
||||
description: "Allow HTTP",
|
||||
},
|
||||
{
|
||||
direction: "in",
|
||||
protocol: "tcp",
|
||||
port: "443",
|
||||
sourceIps: ["0.0.0.0/0", "::/0"],
|
||||
description: "Allow HTTPS",
|
||||
},
|
||||
{
|
||||
direction: "in",
|
||||
protocol: "udp",
|
||||
port: "443",
|
||||
sourceIps: ["0.0.0.0/0", "::/0"],
|
||||
description: "Allow QUIC",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
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",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
2
hetzner-pulumi/resources/types/index.ts
Normal file
2
hetzner-pulumi/resources/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./network";
|
||||
export * from "./server";
|
||||
12
hetzner-pulumi/resources/types/network.ts
Normal file
12
hetzner-pulumi/resources/types/network.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export enum NetworkRegion {
|
||||
eu = "eu-central",
|
||||
usWest = "us-west",
|
||||
usEast = "us-east",
|
||||
}
|
||||
|
||||
export enum NetworkRole {
|
||||
lb = "load-balancer",
|
||||
cache = "varnish-cache",
|
||||
web = "webserver",
|
||||
db = "database",
|
||||
}
|
||||
19
hetzner-pulumi/resources/types/server.ts
Normal file
19
hetzner-pulumi/resources/types/server.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export enum VmSize {
|
||||
small = "small",
|
||||
medium = "medium",
|
||||
large = "large",
|
||||
}
|
||||
|
||||
export enum OS {
|
||||
debian = "debian",
|
||||
ubuntu = "ubuntu",
|
||||
}
|
||||
|
||||
export enum ServerLocations {
|
||||
helsinki = "hel1",
|
||||
falkenstein = "fsn1",
|
||||
nuremberg = "nbg1",
|
||||
hillsboro = "hil",
|
||||
ashburn = "ash",
|
||||
sinapore = "sig",
|
||||
}
|
||||
105
hetzner-pulumi/resources/utils.ts
Normal file
105
hetzner-pulumi/resources/utils.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import * as pulumi from "@pulumi/pulumi";
|
||||
import { z } from "zod";
|
||||
import * as crypto from "node:crypto";
|
||||
|
||||
/**
|
||||
* Region abstraction exposed to users
|
||||
*/
|
||||
export type PricingRegion = "eu" | "us" | "ap";
|
||||
|
||||
/**
|
||||
* Hetzner region → locations mapping
|
||||
*/
|
||||
const regionToLocations: Record<PricingRegion, string[]> = {
|
||||
eu: ["nbg1", "fsn1", "hel1"],
|
||||
us: ["ash", "hil"],
|
||||
ap: ["sin"],
|
||||
};
|
||||
|
||||
const HCLOUD_API = "https://api.hetzner.cloud/v1";
|
||||
|
||||
/**
|
||||
* Runtime validation for Hetzner /server_types response
|
||||
*/
|
||||
const serverTypesResponseSchema = z.object({
|
||||
server_types: z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
deprecated: z.boolean().optional(),
|
||||
prices: z.array(
|
||||
z.object({
|
||||
location: z.string(),
|
||||
price_monthly: z.object({
|
||||
gross: z.string(),
|
||||
}),
|
||||
price_hourly: z.object({
|
||||
gross: z.string(),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the cheapest available server type name
|
||||
* for a given abstract region (eu | us | ap).
|
||||
*
|
||||
* Pricing basis: monthly gross
|
||||
*/
|
||||
export function getCheapestServerType(
|
||||
region: PricingRegion,
|
||||
): pulumi.Output<string> {
|
||||
const locations = regionToLocations[region];
|
||||
const hcloudCfg = new pulumi.Config("hcloud");
|
||||
const token = hcloudCfg.requireSecret("token");
|
||||
|
||||
return pulumi.all([token]).apply(async ([t]) => {
|
||||
const res = await fetch(`${HCLOUD_API}/server_types`, {
|
||||
headers: { Authorization: `Bearer ${t}` },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new pulumi.RunError(
|
||||
`Hetzner API error: ${res.status} ${res.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
const parsed = serverTypesResponseSchema.safeParse(json);
|
||||
|
||||
if (!parsed.success) {
|
||||
const hash = crypto
|
||||
.createHash("sha256")
|
||||
.update(JSON.stringify(json))
|
||||
.digest("hex")
|
||||
.slice(0, 12);
|
||||
|
||||
throw new pulumi.RunError(
|
||||
`Unexpected Hetzner /server_types payload (sha256:${hash})`,
|
||||
);
|
||||
}
|
||||
|
||||
const cheapest = parsed.data.server_types
|
||||
.filter((st) => st.deprecated !== true)
|
||||
.flatMap((st) =>
|
||||
st.prices
|
||||
.filter((p) => locations.includes(p.location))
|
||||
.map((p) => ({
|
||||
name: st.name,
|
||||
price: Number.parseFloat(p.price_hourly.gross),
|
||||
})),
|
||||
)
|
||||
.filter((x) => Number.isFinite(x.price))
|
||||
.sort((a, b) => a.price - b.price)[0];
|
||||
|
||||
if (!cheapest) {
|
||||
throw new pulumi.RunError(
|
||||
`No priced server types found for region=${region}`,
|
||||
);
|
||||
}
|
||||
|
||||
return cheapest.name;
|
||||
});
|
||||
}
|
||||
|
||||
18
hetzner-pulumi/tsconfig.json
Normal file
18
hetzner-pulumi/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"outDir": "bin",
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"pretty": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"files": [
|
||||
"index.ts"
|
||||
]
|
||||
}
|
||||
1917
hetzner-pulumi/yarn.lock
Normal file
1917
hetzner-pulumi/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user