first commit
This commit is contained in:
20
data.tf
Normal file
20
data.tf
Normal 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
81
main.tf
Normal 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
14
outputs.tf
Normal 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
154
scripts/cloud-init.sh
Normal 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
12
terraform.tfvars
Normal 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
12
terraform.tfvars.example
Normal 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
65
variables.tf
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user