Refocus as pure hardening base: drop docker + omaterm prep, add README/LICENSE
- arch-setup.sh now takes a fresh Arch box to a locked-down base, nothing else (omaterm one-shot now lives in the sibling omaserver project) - Port proven UX from omaserver's install-server.sh: machine name asked once (hostname + ssh alias), answers persist across the kernel-upgrade reboot via /root/.arch-setup.conf, reboot offered in-script, public IP detected early, ufw tailscale0 pre-allow (dormant), default-yes prompts - MIT license, README with usage + Arch-specific notes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
99a3598c68
commit
036e1f4010
3 changed files with 182 additions and 138 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Gavin Nugent
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
42
README.md
Normal file
42
README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# arch-boot-strap
|
||||||
|
|
||||||
|
Setup & hardening script for a fresh Arch Linux VPS. Takes a bare box to a clean, locked-down base — **nothing else**. No app stack, no opinions beyond security and maintenance basics.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run as root on a fresh Arch VPS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -t root@YOUR_SERVER 'bash <(curl -fsSL https://raw.githubusercontent.com/28allday/arch-boot-strap/main/arch-setup.sh)'
|
||||||
|
```
|
||||||
|
|
||||||
|
or scp it over and `ssh -t root@YOUR_SERVER bash arch-setup.sh`.
|
||||||
|
|
||||||
|
Two questions up front — machine name (becomes the hostname and your local `ssh <name>` alias) and a deploy username. Answers survive a mid-run reboot: if the first `pacman -Syu` upgrades the kernel, the script offers to reboot, and the re-run picks up where it left off without re-asking.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
- **Deploy user** — wheel group, passwordless sudo, root's `authorized_keys` copied over
|
||||||
|
- **SSH hardening** — root login disabled, password auth disabled, key-only, `AllowUsers <deploy-user>`, config validated with `sshd -t` before restart (auto-reverts on failure)
|
||||||
|
- **Firewall** — UFW deny incoming, allow 22/tcp; `tailscale0` pre-allowed (dormant unless you install Tailscale later)
|
||||||
|
- **fail2ban** — sshd jail, 3 strikes → 1h ban, systemd journal backend (Arch has no auth.log)
|
||||||
|
- **Auto-updates** — weekly `pacman -Syu` timer (Arch has no unattended-upgrades)
|
||||||
|
- **Housekeeping** — 2GB swap (btrfs-aware, skipped if the image ships its own), journald capped at 50MB, weekly package-cache pruning, ModemManager/udisks2/multipathd disabled
|
||||||
|
- **Hostname, timezone, locale** set; prints a ready-to-paste `~/.ssh/config` snippet when done
|
||||||
|
|
||||||
|
## Arch-specific details baked in
|
||||||
|
|
||||||
|
- Kernel-upgrade guard: if `-Syu` replaces the running kernel's modules, the script stops and offers a reboot instead of letting module loads fail mysteriously
|
||||||
|
- fail2ban `backend = systemd`, service name `sshd` (not `ssh`), `wheel` (not `sudo`)
|
||||||
|
- Some provider images strip the `sshd_config.d` Include or ship cloud-init SSH overrides — both handled
|
||||||
|
- `kitty-terminfo` installed so SSHing in from kitty doesn't break interactive tools
|
||||||
|
- btrfs swapfiles need `btrfs filesystem mkswapfile`, not fallocate
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Safe to re-run; completed steps skip themselves. After the first run root SSH login is disabled — re-run as the deploy user with `sudo`.
|
||||||
|
- Want hardening **+ OMATERM** in one shot? That's the sibling project: [omaserver](https://github.com/28allday/omaserver).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
253
arch-setup.sh
253
arch-setup.sh
|
|
@ -1,8 +1,20 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# VPS Setup & Hardening Script for Arch Linux
|
# VPS Setup & Hardening Script for Arch Linux
|
||||||
# Prepares a fresh box for OMATERM (docker-only) + Docker deployments
|
# Takes a fresh Arch box to a clean, locked-down base — nothing else.
|
||||||
# Run as root on a fresh Arch Linux VPS
|
#
|
||||||
|
# Run as root on a fresh Arch Linux VPS:
|
||||||
|
#
|
||||||
|
# ssh -t root@YOUR_SERVER 'bash <(curl -fsSL https://raw.githubusercontent.com/28allday/arch-boot-strap/main/arch-setup.sh)'
|
||||||
|
#
|
||||||
|
# or scp it over and: ssh -t root@YOUR_SERVER bash arch-setup.sh
|
||||||
|
#
|
||||||
|
# Safe to re-run — completed steps skip themselves, and your answers survive
|
||||||
|
# a mid-run reboot. After the first run root SSH login is disabled, so
|
||||||
|
# re-run as the deploy user with sudo.
|
||||||
|
#
|
||||||
|
# Want hardening + OMATERM in one shot? That's the sibling project:
|
||||||
|
# https://github.com/28allday/omaserver
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
@ -11,8 +23,9 @@ set -euo pipefail
|
||||||
# DEFAULTS
|
# DEFAULTS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
TIMEZONE="Europe/London"
|
TIMEZONE="${TIMEZONE:-Europe/London}"
|
||||||
LOCALE="en_GB.UTF-8"
|
LOCALE="${LOCALE:-en_GB.UTF-8}"
|
||||||
|
SELF_URL="https://raw.githubusercontent.com/28allday/arch-boot-strap/main/arch-setup.sh"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# COLOURS
|
# COLOURS
|
||||||
|
|
@ -35,7 +48,7 @@ step() { echo -e "\n${CYAN}━━━ $1 ━━━${NC}\n"; }
|
||||||
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
err "This script must be run as root."
|
err "This script must be run as root."
|
||||||
err "Usage: sudo bash arch-setup.sh"
|
err "Usage: ssh -t root@YOUR_SERVER 'bash <(curl -fsSL $SELF_URL)'"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -50,25 +63,46 @@ fi
|
||||||
echo -e "${CYAN}"
|
echo -e "${CYAN}"
|
||||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||||
echo "║ VPS Setup & Hardening Script ║"
|
echo "║ VPS Setup & Hardening Script ║"
|
||||||
echo "║ Arch Linux · Docker · OMATERM ║"
|
echo "║ Arch Linux · clean locked-down base ║"
|
||||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||||
echo -e "${NC}"
|
echo -e "${NC}"
|
||||||
|
|
||||||
# SSH alias
|
# Public IP — used in re-run instructions and the SSH config snippet
|
||||||
echo ""
|
SERVER_IP=$(curl -4 -s --max-time 5 ifconfig.me 2>/dev/null || \
|
||||||
echo " This name is used in your local SSH config so you can"
|
curl -4 -s --max-time 5 icanhazip.com 2>/dev/null || \
|
||||||
echo " connect with: ssh <alias> (e.g. ssh titan, ssh nebula)"
|
echo "YOUR_SERVER_IP")
|
||||||
read -rp "SSH alias for this server [vps]: " input_alias
|
|
||||||
SSH_ALIAS="${input_alias:-vps}"
|
|
||||||
|
|
||||||
# Username
|
# Answers survive the kernel-upgrade reboot — a re-run finds them here and
|
||||||
echo ""
|
# doesn't ask again
|
||||||
read -rp "Choose a username for the deploy user [deploy]: " input_user
|
STATE_FILE="/root/.arch-setup.conf"
|
||||||
DEPLOY_USER="${input_user:-deploy}"
|
SSH_ALIAS=""
|
||||||
|
DEPLOY_USER=""
|
||||||
|
if [ -f "$STATE_FILE" ]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$STATE_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$SSH_ALIAS" ] && [ -n "$DEPLOY_USER" ]; then
|
||||||
|
log "Using saved settings from the previous run"
|
||||||
|
else
|
||||||
|
# Machine name — used as the hostname AND the alias in your local SSH
|
||||||
|
# config (ssh <name>). Asked ONCE, used everywhere.
|
||||||
|
echo ""
|
||||||
|
echo " This name becomes the hostname and your local SSH alias"
|
||||||
|
echo " (ssh <name>)."
|
||||||
|
read -rp "Machine name for this server [vps]: " input_alias
|
||||||
|
SSH_ALIAS="${input_alias:-vps}"
|
||||||
|
|
||||||
|
# Username
|
||||||
|
echo ""
|
||||||
|
read -rp "Choose a username for the deploy user [deploy]: " input_user
|
||||||
|
DEPLOY_USER="${input_user:-deploy}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Validate username
|
# Validate username
|
||||||
if ! [[ "$DEPLOY_USER" =~ ^[a-z_][a-z0-9_-]*$ ]]; then
|
if ! [[ "$DEPLOY_USER" =~ ^[a-z_][a-z0-9_-]*$ ]]; then
|
||||||
err "Invalid username. Use lowercase letters, numbers, hyphens, underscores."
|
err "Invalid username. Use lowercase letters, numbers, hyphens, underscores."
|
||||||
|
rm -f "$STATE_FILE"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -76,17 +110,22 @@ fi
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}━━━ Confirm settings ━━━${NC}"
|
echo -e "${CYAN}━━━ Confirm settings ━━━${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo " SSH alias: $SSH_ALIAS"
|
echo " Machine name: $SSH_ALIAS (hostname + ssh alias)"
|
||||||
echo " Deploy user: $DEPLOY_USER"
|
echo " Deploy user: $DEPLOY_USER"
|
||||||
echo " Timezone: $TIMEZONE"
|
echo " Timezone: $TIMEZONE"
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Continue? (y/n) " -n 1 -r
|
read -p "Continue? (Y/n) " -n 1 -r
|
||||||
echo ""
|
echo ""
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
echo "Aborted."
|
rm -f "$STATE_FILE"
|
||||||
|
echo "Aborted — saved settings cleared. Re-run to start over."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Persist for re-runs (cleared on abort above)
|
||||||
|
printf 'SSH_ALIAS=%q\nDEPLOY_USER=%q\n' "$SSH_ALIAS" "$DEPLOY_USER" > "$STATE_FILE"
|
||||||
|
chmod 600 "$STATE_FILE"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 1. SYSTEM UPDATES & ESSENTIALS
|
# 1. SYSTEM UPDATES & ESSENTIALS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
@ -96,38 +135,51 @@ step "1/8 · System updates & essential packages"
|
||||||
pacman -Syu --noconfirm
|
pacman -Syu --noconfirm
|
||||||
|
|
||||||
# If -Syu upgraded the kernel, the running kernel's modules are gone and
|
# If -Syu upgraded the kernel, the running kernel's modules are gone and
|
||||||
# Docker can't load overlay/netfilter — reboot and re-run (script is
|
# anything that loads modules (netfilter for ufw, tun, etc.) will fail —
|
||||||
# safe to re-run; completed steps skip themselves)
|
# reboot and re-run (script is safe to re-run; completed steps skip
|
||||||
|
# themselves). This runs BEFORE SSH hardening, so root login still works
|
||||||
|
# for the re-run.
|
||||||
if [ ! -d "/usr/lib/modules/$(uname -r)" ]; then
|
if [ ! -d "/usr/lib/modules/$(uname -r)" ]; then
|
||||||
warn "Kernel was upgraded — running kernel $(uname -r) has no modules on disk."
|
warn "Kernel was upgraded — running kernel $(uname -r) has no modules on disk."
|
||||||
warn "Reboot, then re-run this script:"
|
warn "The box must reboot before setup can continue."
|
||||||
warn " reboot"
|
echo ""
|
||||||
warn " bash arch-setup.sh"
|
echo " After the reboot, reconnect and re-run — it picks up where it left off:"
|
||||||
|
echo ""
|
||||||
|
if [ -f "$0" ] && [[ "$0" != /dev/fd/* ]]; then
|
||||||
|
echo -e " ${CYAN}ssh -t root@$SERVER_IP bash $(basename "$0")${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${CYAN}ssh -t root@$SERVER_IP 'bash <(curl -fsSL $SELF_URL)'${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
read -p "Reboot now? (Y/n) " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
||||||
|
systemctl reboot
|
||||||
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Minimal host package set — omaterm installs everything else it needs
|
# Hardening essentials only — anything app-shaped (docker, etc.) is the
|
||||||
# (git, tmux, agents, tailscale all live inside its container).
|
# consuming project's job.
|
||||||
# kitty-terminfo: without it, SSHing in from kitty gives
|
# kitty-terminfo: without it, SSHing in from kitty gives
|
||||||
# "'xterm-kitty': unknown terminal type" and the omaterm installer dies
|
# "'xterm-kitty': unknown terminal type" and interactive tools die
|
||||||
pacman -S --noconfirm --needed \
|
pacman -S --noconfirm --needed \
|
||||||
ufw \
|
ufw \
|
||||||
fail2ban \
|
fail2ban \
|
||||||
curl \
|
curl \
|
||||||
kitty-terminfo \
|
kitty-terminfo \
|
||||||
docker
|
pacman-contrib
|
||||||
|
|
||||||
# Docker must be running before omaterm installs — the new omaterm is
|
log "System updated, hardening packages installed"
|
||||||
# docker-only (everything runs in the ghcr.io/omacom-io/omaterm container)
|
|
||||||
systemctl enable --now docker.service
|
|
||||||
|
|
||||||
log "System updated, packages installed, Docker running"
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 2. TIMEZONE & LOCALE
|
# 2. HOSTNAME, TIMEZONE & LOCALE
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
step "2/8 · Timezone & locale"
|
step "2/8 · Hostname, timezone & locale"
|
||||||
|
|
||||||
|
hostnamectl set-hostname "$SSH_ALIAS"
|
||||||
|
log "Hostname set to $SSH_ALIAS"
|
||||||
|
|
||||||
timedatectl set-timezone "$TIMEZONE"
|
timedatectl set-timezone "$TIMEZONE"
|
||||||
log "Timezone set to $TIMEZONE"
|
log "Timezone set to $TIMEZONE"
|
||||||
|
|
@ -155,21 +207,21 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Allow sudo without password for deploy user
|
# Allow sudo without password for deploy user
|
||||||
echo "$DEPLOY_USER ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$DEPLOY_USER
|
echo "$DEPLOY_USER ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/"$DEPLOY_USER"
|
||||||
chmod 440 /etc/sudoers.d/$DEPLOY_USER
|
chmod 440 /etc/sudoers.d/"$DEPLOY_USER"
|
||||||
log "Passwordless sudo enabled for $DEPLOY_USER"
|
log "Passwordless sudo enabled for $DEPLOY_USER"
|
||||||
|
|
||||||
# Copy root's authorized_keys to deploy user if they exist
|
# Copy root's authorized_keys to deploy user if they exist
|
||||||
if [ -f /root/.ssh/authorized_keys ]; then
|
if [ -f /root/.ssh/authorized_keys ]; then
|
||||||
mkdir -p /home/$DEPLOY_USER/.ssh
|
mkdir -p /home/"$DEPLOY_USER"/.ssh
|
||||||
cp /root/.ssh/authorized_keys /home/$DEPLOY_USER/.ssh/authorized_keys
|
cp /root/.ssh/authorized_keys /home/"$DEPLOY_USER"/.ssh/authorized_keys
|
||||||
chown -R $DEPLOY_USER:$DEPLOY_USER /home/$DEPLOY_USER/.ssh
|
chown -R "$DEPLOY_USER":"$DEPLOY_USER" /home/"$DEPLOY_USER"/.ssh
|
||||||
chmod 700 /home/$DEPLOY_USER/.ssh
|
chmod 700 /home/"$DEPLOY_USER"/.ssh
|
||||||
chmod 600 /home/$DEPLOY_USER/.ssh/authorized_keys
|
chmod 600 /home/"$DEPLOY_USER"/.ssh/authorized_keys
|
||||||
log "SSH keys copied from root to $DEPLOY_USER"
|
log "SSH keys copied from root to $DEPLOY_USER"
|
||||||
else
|
else
|
||||||
warn "No SSH keys found for root — you'll need to add keys manually:"
|
warn "No SSH keys found for root — you'll need to add keys manually:"
|
||||||
warn " ssh-copy-id $DEPLOY_USER@YOUR_SERVER_IP"
|
warn " ssh-copy-id $DEPLOY_USER@$SERVER_IP"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
@ -236,11 +288,15 @@ ufw default deny incoming
|
||||||
ufw default allow outgoing
|
ufw default allow outgoing
|
||||||
ufw allow 22/tcp comment 'SSH'
|
ufw allow 22/tcp comment 'SSH'
|
||||||
|
|
||||||
|
# Dormant unless tailscale is installed later — without it, a future
|
||||||
|
# `tailscale up` silently drops all incoming tailnet traffic
|
||||||
|
ufw allow in on tailscale0 comment 'Tailscale'
|
||||||
|
|
||||||
# Enable without prompt, and make it survive reboots
|
# Enable without prompt, and make it survive reboots
|
||||||
echo "y" | ufw enable
|
echo "y" | ufw enable
|
||||||
systemctl enable ufw
|
systemctl enable ufw
|
||||||
|
|
||||||
log "UFW enabled — port 22 only"
|
log "UFW enabled — 22/tcp + tailscale0 only"
|
||||||
ufw status verbose
|
ufw status verbose
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
@ -310,51 +366,12 @@ log "Weekly auto-update timer enabled (pacman-update.timer)"
|
||||||
warn "Rolling release: check 'journalctl -u pacman-update' if anything misbehaves after an update"
|
warn "Rolling release: check 'journalctl -u pacman-update' if anything misbehaves after an update"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 8. OMATERM PREP (Docker already installed via pacman in step 1)
|
# 8. HOUSEKEEPING — swap, services, journal, pacman cache
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
step "8/8 · Omaterm prep"
|
step "8/8 · Housekeeping"
|
||||||
|
|
||||||
# Omaterm itself is installed interactively after first login as the deploy
|
|
||||||
# user — here we just make sure the box is ready for it:
|
|
||||||
|
|
||||||
# Deploy user needs docker group membership to run the omaterm container
|
|
||||||
if getent group docker &>/dev/null; then
|
|
||||||
usermod -aG docker $DEPLOY_USER
|
|
||||||
log "$DEPLOY_USER added to docker group"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Pre-pull the omaterm image so the installer doesn't have to
|
|
||||||
log "Pre-pulling omaterm image (this may take a while)..."
|
|
||||||
if docker pull ghcr.io/omacom-io/omaterm:latest; then
|
|
||||||
log "Omaterm image pre-pulled"
|
|
||||||
else
|
|
||||||
warn "Image pre-pull failed — the omaterm installer will pull it instead"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Installer wrapper: `curl | bash` makes stdin the pipe, so the installer's
|
|
||||||
# final `docker run -it` attach fails with "stdin is not a terminal".
|
|
||||||
# Download-then-run keeps stdin on the real TTY.
|
|
||||||
cat > /home/$DEPLOY_USER/install-omaterm << 'OMAEOF'
|
|
||||||
#!/bin/bash
|
|
||||||
# Fetch and run the omaterm installer with a real TTY on stdin
|
|
||||||
set -euo pipefail
|
|
||||||
tmp=$(mktemp)
|
|
||||||
trap 'rm -f "$tmp"' EXIT
|
|
||||||
curl -fsSL https://omaterm.org/install -o "$tmp"
|
|
||||||
bash "$tmp"
|
|
||||||
OMAEOF
|
|
||||||
|
|
||||||
chown $DEPLOY_USER:$DEPLOY_USER /home/$DEPLOY_USER/install-omaterm
|
|
||||||
chmod +x /home/$DEPLOY_USER/install-omaterm
|
|
||||||
log "Installer wrapper created at ~/install-omaterm"
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# SWAP (2GB)
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
step "Swap file"
|
|
||||||
|
|
||||||
|
# --- Swap (2GB) ---
|
||||||
if swapon --show --noheadings 2>/dev/null | grep -q .; then
|
if swapon --show --noheadings 2>/dev/null | grep -q .; then
|
||||||
warn "Swap already active — skipping:"
|
warn "Swap already active — skipping:"
|
||||||
swapon --show
|
swapon --show
|
||||||
|
|
@ -374,12 +391,7 @@ else
|
||||||
log "2GB swap created and enabled"
|
log "2GB swap created and enabled"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# =============================================================================
|
# --- Disable unnecessary services ---
|
||||||
# DISABLE UNNECESSARY SERVICES
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
step "Disable unnecessary services"
|
|
||||||
|
|
||||||
for svc in ModemManager udisks2 multipathd; do
|
for svc in ModemManager udisks2 multipathd; do
|
||||||
if systemctl is-enabled "$svc" &>/dev/null; then
|
if systemctl is-enabled "$svc" &>/dev/null; then
|
||||||
systemctl stop "$svc"
|
systemctl stop "$svc"
|
||||||
|
|
@ -388,50 +400,34 @@ for svc in ModemManager udisks2 multipathd; do
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# =============================================================================
|
# --- Cap journal logs (50MB) ---
|
||||||
# CAP JOURNAL LOGS (50MB)
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
step "Cap journal logs"
|
|
||||||
|
|
||||||
mkdir -p /etc/systemd/journald.conf.d
|
mkdir -p /etc/systemd/journald.conf.d
|
||||||
cat > /etc/systemd/journald.conf.d/size.conf << EOF
|
cat > /etc/systemd/journald.conf.d/size.conf << EOF
|
||||||
[Journal]
|
[Journal]
|
||||||
SystemMaxUse=50M
|
SystemMaxUse=50M
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
systemctl restart systemd-journald
|
systemctl restart systemd-journald
|
||||||
log "Journal capped at 50MB"
|
log "Journal capped at 50MB"
|
||||||
|
|
||||||
# =============================================================================
|
# --- Pacman cache hygiene (prune old package versions weekly) ---
|
||||||
# CLEAN PACMAN CACHE (keep last 2 versions of each package)
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
step "Pacman cache hygiene"
|
|
||||||
|
|
||||||
pacman -S --noconfirm --needed pacman-contrib
|
|
||||||
systemctl enable --now paccache.timer
|
systemctl enable --now paccache.timer
|
||||||
log "paccache.timer enabled — old package versions pruned weekly"
|
log "paccache.timer enabled — old package versions pruned weekly"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# GENERATE LOCAL SSH CONFIG FILE
|
# LOCAL SSH CONFIG SNIPPET
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
step "SSH config for your local machine"
|
cat > /home/"$DEPLOY_USER"/ssh-config-snippet.txt << SSHEOF
|
||||||
|
|
||||||
SERVER_IP=$(curl -4 -s --max-time 5 ifconfig.me 2>/dev/null || \
|
|
||||||
curl -4 -s --max-time 5 icanhazip.com 2>/dev/null || \
|
|
||||||
echo "YOUR_SERVER_IP")
|
|
||||||
|
|
||||||
cat > /home/$DEPLOY_USER/ssh-config-snippet.txt << SSHEOF
|
|
||||||
Host $SSH_ALIAS
|
Host $SSH_ALIAS
|
||||||
HostName $SERVER_IP
|
HostName $SERVER_IP
|
||||||
User $DEPLOY_USER
|
User $DEPLOY_USER
|
||||||
IdentityFile ~/.ssh/id_ed25519
|
IdentityFile ~/.ssh/id_ed25519
|
||||||
SSHEOF
|
SSHEOF
|
||||||
|
|
||||||
chown $DEPLOY_USER:$DEPLOY_USER /home/$DEPLOY_USER/ssh-config-snippet.txt
|
chown "$DEPLOY_USER":"$DEPLOY_USER" /home/"$DEPLOY_USER"/ssh-config-snippet.txt
|
||||||
log "SSH config snippet saved to ~/ssh-config-snippet.txt"
|
|
||||||
|
# Hardening complete — saved answers no longer needed
|
||||||
|
rm -f "$STATE_FILE"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# SUMMARY
|
# SUMMARY
|
||||||
|
|
@ -450,20 +446,14 @@ echo " ├─ Root login: disabled"
|
||||||
echo " └─ Auth: key-only"
|
echo " └─ Auth: key-only"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${GREEN}Firewall${NC}"
|
echo -e " ${GREEN}Firewall${NC}"
|
||||||
echo " └─ 22/tcp SSH (only open port)"
|
echo " ├─ 22/tcp SSH"
|
||||||
|
echo " └─ tailscale0 allowed (dormant unless tailscale is installed)"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${GREEN}Services${NC}"
|
echo -e " ${GREEN}Services${NC}"
|
||||||
echo " ├─ Docker: $(docker --version 2>/dev/null || echo 'installed')"
|
|
||||||
echo " ├─ Omaterm: image pre-pulled — install after first login (see below)"
|
|
||||||
echo " ├─ Fail2ban: active (systemd backend)"
|
echo " ├─ Fail2ban: active (systemd backend)"
|
||||||
echo " └─ Auto-updates: weekly pacman-update.timer"
|
echo " └─ Auto-updates: weekly pacman-update.timer"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${GREEN}Paths${NC}"
|
echo " On your local machine, add the SSH config:"
|
||||||
echo " └─ SSH config: ~/ssh-config-snippet.txt"
|
|
||||||
echo ""
|
|
||||||
echo -e " ${YELLOW}NEXT STEPS${NC}"
|
|
||||||
echo ""
|
|
||||||
echo " 1. On your local machine, run this to add the SSH config:"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${CYAN}────── COPY BELOW THIS LINE ──────${NC}"
|
echo -e " ${CYAN}────── COPY BELOW THIS LINE ──────${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -477,16 +467,7 @@ echo "EOF"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${CYAN}────── COPY ABOVE THIS LINE ──────${NC}"
|
echo -e " ${CYAN}────── COPY ABOVE THIS LINE ──────${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 2. Test SSH: ssh $SSH_ALIAS"
|
echo " Then test: ssh $SSH_ALIAS"
|
||||||
echo " 3. Install omaterm (from an interactive SSH session — do NOT"
|
|
||||||
echo " curl|bash it, the container attach step needs a real TTY):"
|
|
||||||
echo ""
|
|
||||||
echo -e " ${CYAN}ssh $SSH_ALIAS${NC}"
|
|
||||||
echo -e " ${CYAN}./install-omaterm${NC}"
|
|
||||||
echo ""
|
|
||||||
echo " If it complains about the terminal: TERM=xterm-256color ./install-omaterm"
|
|
||||||
echo ""
|
|
||||||
echo " Once step 1 is done, Claude Code can control this server."
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${YELLOW}A reboot is recommended to apply kernel updates:${NC}"
|
echo -e " ${YELLOW}A reboot is recommended to apply kernel updates:${NC}"
|
||||||
echo -e " ${CYAN} reboot${NC}"
|
echo -e " ${CYAN} reboot${NC}"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue