Created
February 10, 2026 11:33
-
-
Save tvararu/43ac6eaa4cb89d09bbab55a4e8eac570 to your computer and use it in GitHub Desktop.
OpenClaw setup script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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