first commit

This commit is contained in:
Melchior Reimers
2026-02-04 12:16:10 +01:00
commit 65de284a12
7 changed files with 358 additions and 0 deletions

20
data.tf Normal file
View File

@@ -0,0 +1,20 @@
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45"
}
}
}
provider "hcloud" {
token = var.hcloud_token
}
data "hcloud_server_type" "selected" {
name = var.server_type
}
data "hcloud_location" "selected" {
name = var.location
}

81
main.tf Normal file
View File

@@ -0,0 +1,81 @@
# SSH Key
resource "hcloud_ssh_key" "default" {
name = "${var.server_name}-ssh-key"
public_key = var.ssh_public_key
}
# Cloud-init script
locals {
user_data = templatefile("${path.module}/scripts/cloud-init.sh", {
tailscale_auth_key = var.tailscale_auth_key
username = var.username
ssh_public_key = var.ssh_public_key
})
}
# Server
resource "hcloud_server" "vps" {
name = var.server_name
image = var.image
server_type = data.hcloud_server_type.selected.name
location = data.hcloud_location.selected.name
ssh_keys = concat([hcloud_ssh_key.default.id], var.ssh_keys)
user_data = local.user_data
labels = {
managed-by = "terraform"
environment = var.environment
purpose = "openclaw"
}
}
# Firewall – locked down by default
resource "hcloud_firewall" "vps" {
name = "${var.server_name}-firewall"
# SSH: Tailscale CGNAT range + explicit allowed IPs
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = var.tailscale_auth_key != "" ? concat(["100.64.0.0/10"], var.allowed_ssh_ips) : var.allowed_ssh_ips
description = "SSH access"
}
# ICMP for diagnostics
rule {
direction = "in"
protocol = "icmp"
source_ips = ["0.0.0.0/0", "::/0"]
description = "ICMP (ping)"
}
# Egress – allow all (Hetzner default, but explicit is better)
rule {
direction = "out"
protocol = "tcp"
port = "1-65535"
destination_ips = ["0.0.0.0/0", "::/0"]
description = "All TCP outbound"
}
rule {
direction = "out"
protocol = "udp"
port = "1-65535"
destination_ips = ["0.0.0.0/0", "::/0"]
description = "All UDP outbound"
}
rule {
direction = "out"
protocol = "icmp"
destination_ips = ["0.0.0.0/0", "::/0"]
description = "ICMP outbound"
}
}
resource "hcloud_firewall_attachment" "vps" {
firewall_id = hcloud_firewall.vps.id
server_ids = [hcloud_server.vps.id]
}

14
outputs.tf Normal file
View File

@@ -0,0 +1,14 @@
output "server_ip" {
description = "Public IPv4 address"
value = hcloud_server.vps.ipv4_address
}
output "server_ipv6" {
description = "Public IPv6 address"
value = hcloud_server.vps.ipv6_address
}
output "ssh_command" {
description = "SSH connection string"
value = "ssh ${var.username}@${hcloud_server.vps.ipv4_address}"
}

154
scripts/cloud-init.sh Normal file
View File

@@ -0,0 +1,154 @@
#!/bin/bash
set -euo pipefail
# Variables from Terraform
TAILSCALE_AUTH_KEY="${tailscale_auth_key}"
USERNAME="${username}"
SSH_PUBLIC_KEY="${ssh_public_key}"
# Logging
exec > >(tee /var/log/cloud-init-custom.log) 2>&1
echo "=== Cloud-init started at $(date) ==="
# System updates
apt update
DEBIAN_FRONTEND=noninteractive apt upgrade -y
# Install essentials
DEBIAN_FRONTEND=noninteractive apt install -y \
curl \
git \
htop \
ufw \
unattended-upgrades \
apt-listchanges
# Create non-root user
#if ! id "$USERNAME" &>/dev/null; then
# useradd -m -s /bin/bash -G sudo "$USERNAME"
# echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME
# chmod 0440 /etc/sudoers.d/$USERNAME
#fi
## SSH key for user
#USER_HOME="/home/$USERNAME"
#mkdir -p "$USER_HOME/.ssh"
#echo "$SSH_PUBLIC_KEY" > "$USER_HOME/.ssh/authorized_keys"
#chmod 700 "$USER_HOME/.ssh"
#chmod 600 "$USER_HOME/.ssh/authorized_keys"
#chown -R "$USERNAME:$USERNAME" "$USER_HOME/.ssh"
# SSH hardening
cat > /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
# Disable password authentication
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
## Disable root login
#PermitRootLogin no
# Key-based auth only
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
# Timeouts and limits
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 3
MaxSessions 3
LoginGraceTime 30
# Disable unused auth methods
HostbasedAuthentication no
PermitEmptyPasswords no
KerberosAuthentication no
GSSAPIAuthentication no
# Logging
LogLevel VERBOSE
EOF
# Restart SSH
systemctl restart ssh
## fail2ban configuration
#cat > /etc/fail2ban/jail.local << 'EOF'
#[DEFAULT]
#bantime = 1h
#findtime = 10m
#maxretry = 5
#banaction = ufw
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 3
bantime = 24h
EOF
#systemctl enable fail2ban
#systemctl restart fail2ban
#
# UFW firewall
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw --force enable
# Unattended upgrades – security patches only
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
EOF
cat > /etc/apt/apt.conf.d/20auto-upgrades << 'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
EOF
systemctl enable unattended-upgrades
# Kernel hardening via sysctl
cat > /etc/sysctl.d/99-security.conf << 'EOF'
# IP Spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# Ignore source routed packets
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Log Martian packets
net.ipv4.conf.all.log_martians = 1
# Ignore broadcast pings
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Disable IPv6 if not needed (optional)
# net.ipv6.conf.all.disable_ipv6 = 1
EOF
sysctl -p /etc/sysctl.d/99-security.conf
## Tailscale (optional)
#if [ -n "$TAILSCALE_AUTH_KEY" ]; then
# curl -fsSL https://tailscale.com/install.sh | sh
# tailscale up --authkey="$TAILSCALE_AUTH_KEY" --ssh
# echo "Tailscale installed and connected"
#fi
echo "=== Cloud-init completed at $(date) ==="

12
terraform.tfvars Normal file
View File

@@ -0,0 +1,12 @@
hcloud_token = "your-hetzner-api-token"
server_name = "clawdbot-prod"
server_type = "cax21"
image = "ubuntu-24.04"
location = "nbg1-dc3"
ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCkKMAoaJiEWf5ruXnI1KOcwq3IRA2nShwPGaubklTVrCkTaXE8gSwpUtqnCJZvGxufUNVBLO/n/3KQNm8aFsEHX+c90F5FknrDro1S5szGRGlJz39YaMJeI0RSyHXNlOxIMl3zV1TpkvJGkdVCzTgafwSLfXws3/o/9fskNs5+jnTP03P8J4u1yEDbjmDNDRQRfBMHBbWOQ/+4Ci9japLSJrbUbmIpP78I8w2qCGPLBd/ksK3VTmn88sQ274m7nfjFMCIVondPz0u+oHg/5q0eiK1mbquTe9rdpx07BzNSLJbIHoEw+nqqf4L3mfoTNW7D67AgLZAztdrEj/GOzSL+tmgtO/lRQumZVCjWtyMtlL0D8F0O8lODngJhDbQHOOaoJz1kCBlJZOg2QqDsHQEO4FrNZ6gzlfj+q4LpYgEYMstJ2u/XFf5dEBWIA8JukYQeT/RLrMQJom0SmrEZ4kyzWWc7Rp9wF1QXk0Apgl2aKpqO6UTUP92P+hAPKmAArDgsSNZgiC/6ARYGi6igGObuGHW2pfB/5tK8mw55yiyH25XjP91BAaUDQfCab1yRU09nQenPzWQQ2bg87ZA6GQYi3xfzZoERCxmgfvhbvd7kEqBMHyOzD8WyFuNeBP8T06yKg0Qeo5jjQkrJySp8smnDqIkYcmhVAOYeZV/WfXtEzQ=="
# Security: restrict SSH to your IP or VPN
allowed_ssh_ips = ["YOUR_IP/32"]
# Optional: Tailscale for zero-trust access
# tailscale_auth_key = "tskey-auth-xxxxx"

12
terraform.tfvars.example Normal file
View File

@@ -0,0 +1,12 @@
hcloud_token = "your-hetzner-api-token"
server_name = "clawdbot-prod"
server_type = "cax21"
image = "ubuntu-24.04"
location = "nbg1-dc3"
ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCkKMAoaJiEWf5ruXnI1KOcwq3IRA2nShwPGaubklTVrCkTaXE8gSwpUtqnCJZvGxufUNVBLO/n/3KQNm8aFsEHX+c90F5FknrDro1S5szGRGlJz39YaMJeI0RSyHXNlOxIMl3zV1TpkvJGkdVCzTgafwSLfXws3/o/9fskNs5+jnTP03P8J4u1yEDbjmDNDRQRfBMHBbWOQ/+4Ci9japLSJrbUbmIpP78I8w2qCGPLBd/ksK3VTmn88sQ274m7nfjFMCIVondPz0u+oHg/5q0eiK1mbquTe9rdpx07BzNSLJbIHoEw+nqqf4L3mfoTNW7D67AgLZAztdrEj/GOzSL+tmgtO/lRQumZVCjWtyMtlL0D8F0O8lODngJhDbQHOOaoJz1kCBlJZOg2QqDsHQEO4FrNZ6gzlfj+q4LpYgEYMstJ2u/XFf5dEBWIA8JukYQeT/RLrMQJom0SmrEZ4kyzWWc7Rp9wF1QXk0Apgl2aKpqO6UTUP92P+hAPKmAArDgsSNZgiC/6ARYGi6igGObuGHW2pfB/5tK8mw55yiyH25XjP91BAaUDQfCab1yRU09nQenPzWQQ2bg87ZA6GQYi3xfzZoERCxmgfvhbvd7kEqBMHyOzD8WyFuNeBP8T06yKg0Qeo5jjQkrJySp8smnDqIkYcmhVAOYeZV/WfXtEzQ=="
# Security: restrict SSH to your IP or VPN
allowed_ssh_ips = ["YOUR_IP/32"]
# Optional: Tailscale for zero-trust access
# tailscale_auth_key = "tskey-auth-xxxxx"

65
variables.tf Normal file
View File

@@ -0,0 +1,65 @@
variable "hcloud_token" {
description = "Hetzner Cloud API token"
type = string
sensitive = true
}
variable "server_name" {
description = "Server hostname"
type = string
default = "clawdbot"
}
variable "server_type" {
description = "Hetzner server type (cx22 = 2 vCPU, 4GB RAM)"
type = string
default = "cx22"
}
variable "image" {
description = "OS image"
type = string
default = "ubuntu-24.04"
}
variable "location" {
description = "Hetzner datacenter"
type = string
default = "nbg1" # Nuremberg, DE
}
variable "ssh_public_key" {
description = "SSH public key for access"
type = string
}
variable "ssh_keys" {
description = "Additional SSH key IDs"
type = list(string)
default = []
}
variable "username" {
description = "Non-root user to create"
type = string
default = "clawdbot"
}
variable "tailscale_auth_key" {
description = "Tailscale auth key (optional)"
type = string
default = ""
sensitive = true
}
variable "allowed_ssh_ips" {
description = "IPs allowed to SSH (use your static IP or VPN range)"
type = list(string)
default = [] # Empty = SSH only via Tailscale if enable
}
variable "environment" {
description = "Environment label"
type = string
default = "production"
}