Skip to content

Instantly share code, notes, and snippets.

@tvararu
Created February 10, 2026 11:33
Show Gist options
  • Select an option

  • Save tvararu/43ac6eaa4cb89d09bbab55a4e8eac570 to your computer and use it in GitHub Desktop.

Select an option

Save tvararu/43ac6eaa4cb89d09bbab55a4e8eac570 to your computer and use it in GitHub Desktop.
OpenClaw setup script
#!/usr/bin/env bash
set -euo pipefail
# ============================================================
# Hetzner VPS hardening script — run as root on a fresh Ubuntu 24.04
# Creates a dedicated user, locks down SSH, sets up firewall,
# fail2ban, unattended-upgrades, mosh, and a swap file.
#
# Idempotent — safe to re-run.
#
# Usage:
# ssh root@your-vps-ip
# bash harden.sh
#
# After this completes, log out and reconnect as:
# ssh openclaw@your-vps-ip
# (or: mosh openclaw@your-vps-ip)
# ============================================================
USERNAME="openclaw"
SSH_PORT=22 # change if you want a non-standard port
# --- 1. System updates -----------------------------------------------
echo ">>> Updating system packages..."
apt update -y
apt upgrade -y
# --- 2. Create dedicated user ----------------------------------------
if ! id "$USERNAME" &>/dev/null; then
echo ">>> Creating user: $USERNAME"
adduser --gecos "" "$USERNAME"
usermod -aG sudo "$USERNAME"
else
echo ">>> User $USERNAME already exists, skipping."
fi
# Passwordless sudo
SUDOERS_FILE="/etc/sudoers.d/$USERNAME"
SUDOERS_DESIRED="$USERNAME ALL=(ALL) NOPASSWD:ALL"
if [ ! -f "$SUDOERS_FILE" ] || [ "$(cat "$SUDOERS_FILE")" != "$SUDOERS_DESIRED" ]; then
echo ">>> Enabling passwordless sudo for $USERNAME..."
echo "$SUDOERS_DESIRED" > "$SUDOERS_FILE"
chmod 440 "$SUDOERS_FILE"
else
echo ">>> Passwordless sudo already configured, skipping."
fi
# Copy root's authorized_keys to the new user (Hetzner injects your
# SSH key into root during provisioning)
echo ">>> Syncing SSH keys to $USERNAME..."
mkdir -p /home/$USERNAME/.ssh
if [ -f /root/.ssh/authorized_keys ]; then
cp /root/.ssh/authorized_keys /home/$USERNAME/.ssh/authorized_keys
fi
chown -R $USERNAME:$USERNAME /home/$USERNAME/.ssh
chmod 700 /home/$USERNAME/.ssh
chmod 600 /home/$USERNAME/.ssh/authorized_keys
# --- 3. Harden SSHD --------------------------------------------------
SSHD_HARDENING="/etc/ssh/sshd_config.d/99-hardening.conf"
SSHD_DESIRED=$(cat <<EOF
Port $SSH_PORT
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
X11Forwarding no
AllowAgentForwarding no
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
EOF
)
if [ ! -f "$SSHD_HARDENING" ] || [ "$(cat "$SSHD_HARDENING")" != "$SSHD_DESIRED" ]; then
echo ">>> Hardening SSH config..."
echo "$SSHD_DESIRED" > "$SSHD_HARDENING"
systemctl restart ssh
echo ">>> SSH hardened. Root login and password auth disabled."
else
echo ">>> SSH already hardened, skipping."
fi
# --- 4. UFW firewall --------------------------------------------------
echo ">>> Setting up UFW..."
apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow "$SSH_PORT"/tcp comment "SSH"
# Mosh uses UDP 60000-61000
ufw allow 60000:61000/udp comment "Mosh"
ufw --force enable
echo ">>> UFW enabled. SSH ($SSH_PORT) + Mosh (60000:61000/udp) allowed."
# --- 5. fail2ban ------------------------------------------------------
apt install -y fail2ban
JAIL_DESIRED=$(cat <<EOF
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = $SSH_PORT
logpath = /var/log/auth.log
backend = systemd
EOF
)
if [ ! -f /etc/fail2ban/jail.local ] || [ "$(cat /etc/fail2ban/jail.local)" != "$JAIL_DESIRED" ]; then
echo ">>> Configuring fail2ban..."
echo "$JAIL_DESIRED" > /etc/fail2ban/jail.local
systemctl enable fail2ban
systemctl restart fail2ban
echo ">>> fail2ban active for SSH."
else
echo ">>> fail2ban already configured, ensuring running..."
systemctl enable fail2ban
systemctl start fail2ban
fi
# --- 6. Mosh ----------------------------------------------------------
if ! command -v mosh-server &>/dev/null; then
echo ">>> Installing mosh..."
apt install -y mosh
else
echo ">>> Mosh already installed, skipping."
fi
# --- 7. Unattended upgrades -------------------------------------------
apt install -y unattended-upgrades apt-listchanges
UNATTENDED_DESIRED=$(cat <<'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
EOF
)
AUTO_DESIRED=$(cat <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
EOF
)
NEEDS_RESTART=false
if [ ! -f /etc/apt/apt.conf.d/50unattended-upgrades ] || \
[ "$(cat /etc/apt/apt.conf.d/50unattended-upgrades)" != "$UNATTENDED_DESIRED" ]; then
echo ">>> Configuring unattended-upgrades..."
echo "$UNATTENDED_DESIRED" > /etc/apt/apt.conf.d/50unattended-upgrades
NEEDS_RESTART=true
fi
if [ ! -f /etc/apt/apt.conf.d/20auto-upgrades ] || \
[ "$(cat /etc/apt/apt.conf.d/20auto-upgrades)" != "$AUTO_DESIRED" ]; then
echo "$AUTO_DESIRED" > /etc/apt/apt.conf.d/20auto-upgrades
NEEDS_RESTART=true
fi
if [ "$NEEDS_RESTART" = true ]; then
systemctl enable unattended-upgrades
systemctl restart unattended-upgrades
echo ">>> Unattended upgrades enabled (auto-reboot at 04:00)."
else
echo ">>> Unattended upgrades already configured, skipping."
fi
# --- 8. Swap file (2GB — safety net for OOM) --------------------------
if [ ! -f /swapfile ]; then
echo ">>> Creating 2GB swap file..."
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo ">>> Swap enabled (2GB)."
else
# Ensure it's active
swapon /swapfile 2>/dev/null || true
echo ">>> Swap already exists, ensuring active."
fi
# Ensure fstab entry exists (once)
if ! grep -q '/swapfile' /etc/fstab; then
echo '/swapfile none swap sw 0 0' >> /etc/fstab
fi
# Ensure swappiness is set
if ! grep -q 'vm.swappiness=10' /etc/sysctl.d/99-swappiness.conf 2>/dev/null; then
echo 'vm.swappiness=10' > /etc/sysctl.d/99-swappiness.conf
sysctl vm.swappiness=10
fi
# --- 9. Shared memory hardening ---------------------------------------
if ! grep -q '/run/shm' /etc/fstab; then
echo ">>> Hardening /run/shm..."
echo 'none /run/shm tmpfs defaults,noexec,nosuid,nodev 0 0' >> /etc/fstab
else
echo ">>> /run/shm already hardened, skipping."
fi
# --- Done -------------------------------------------------------------
echo ""
echo "============================================================"
echo " Hardening complete!"
echo ""
echo " IMPORTANT: Before closing this session, verify you can"
echo " log in from another terminal:"
echo ""
echo " ssh $USERNAME@your-vps-ip -p $SSH_PORT"
echo " mosh $USERNAME@your-vps-ip --ssh='ssh -p $SSH_PORT'"
echo ""
echo " Then proceed with OpenClaw setup as $USERNAME."
echo "============================================================"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment