Windsurf rules
.windsurf/rules/harden-vps.mdHarden a production Linux VPS for your application across three layers — application (systemd hardening, monitoring alerts, backup automation), Ubuntu OS (UFW firewall, fail2ban SSH, unattended-upgrades, SSH hardening), and VPS provider (health checks, daily backups, monthly snapshots). Use when the user wants to secure a production server, harden a VPS, audit server security, or mentions production hardening, VPS security, or harden the server.
Windsurf rules
Quality
46/100
Scores the file, not the repository.Length
1,208 words
46 headings · 12 code blocksRepository
119
— · pushed 1 days agoLast changed
3 days ago
First indexed 3 days ago.1234567# Harden VPS89Three-layer production hardening for a self-hosted app on any VPS. Each layer independently verifiable. Apply OS first (firewall blocks attacks immediately), then your application (alerts + backups), then your VPS provider (snapshots). See [REFERENCE.md](REFERENCE.md) for full script bodies, systemd unit template, and gotchas.1011## Quick start1213SSH as root into the VPS. Find credentials in the your VPS provider Customer Control Panel.1415> **HARD GATE** — Run `ufw status` first. No firewall = layer 1 takes priority over everything.1617## Layer 1 — Ubuntu OS1819```bash20# UFW21ufw default deny incoming && ufw default allow outgoing22ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw enable23# → verify: ufw status | grep -q active2425# fail2ban26apt install -y fail2ban27# Configure /etc/fail2ban/jail.local: sshd, maxretry=3, bantime=3600, findtime=60028systemctl restart fail2ban29# → verify: fail2ban-client status sshd3031# unattended-upgrades32apt install -y unattended-upgrades && dpkg-reconfigure -plow unattended-upgrades33# → verify: systemctl is-active unattended-upgrades | grep -q active3435# SSH: PermitRootLogin no, PasswordAuthentication no, PubkeyAuthentication yes36# → verify: sshd -T | grep -E 'permitrootlogin no|passwordauthentication no'3738# Deploy healthcheck.sh → /opt/your-app/scripts/healthcheck.sh39# Crontab: */5 * * * * /opt/your-app/scripts/healthcheck.sh40```4142## Layer 2 — applicationlication4344```bash45# systemd: User=your-app, NoNewPrivileges=yes, ProtectSystem=full,46# ProtectKernelTunables=yes, ProtectKernelModules=yes,47# ProtectControlGroups=yes, RestrictAddressFamilies=AF_INET AF_INET6,48# RestrictRealtime=yes, PrivateTmp=yes, LimitNOFILE=6553649# → verify: systemctl show your-app -p NoNewPrivileges -p ProtectSystem -p User5051# Alerts (your application requires auth; insert via SQLite)52sqlite3 /opt/your-app/data/your-app.db "53INSERT INTO monitoring_alerts (id,name,metric,threshold,operator,enabled,duration_seconds)54VALUES ('a1','Disk >80%','disk_used_percent',80,'gt',1,300);55INSERT INTO monitoring_alerts (id,name,metric,threshold,operator,enabled,duration_seconds)56VALUES ('a2','CPU >90%','cpu_percent',90,'gt',1,60);57INSERT INTO monitoring_alerts (id,name,metric,threshold,operator,enabled,duration_seconds)58VALUES ('a3','RAM >85%','mem_used_percent',85,'gt',1,120);59"60systemctl restart your-app6162# Backup crontab (root):63# 0 2 * * * cp /opt/your-app/data/your-app.db /backup/your-app-$(date +\%Y\%m\%d).db64# 0 3 * * * find /backup/ -name "your-app-*.db" -mtime +90 -delete65```6667## Layer 3 — VPS provider6869```bash70# cntb CLI71curl -sL "$(curl -sL https://api.github.com/repos/contabo/cntb/releases/latest \72 | grep browser_download_url.*linux_amd64.tar.gz | head -1 | cut -d'"' -f4)" \73 | tar xz -C /usr/local/bin7475# Snapshot script → /opt/your-app/scripts/contabo-snapshot.sh (reads from /opt/your-app/.env)76# Credentials as env vars in /opt/your-app/.env (deployed by GitHub Actions):77# CONTABO_CLIENT_ID, CONTABO_CLIENT_SECRET, CONTABO_API_USER, CONTABO_API_PASSWORD78# Crontab: 0 4 1 * * /opt/your-app/scripts/contabo-snapshot.sh7980# > HARD GATE — Snapshot cron silently fails until env vars are set in .env.81# Credentials source: your VPS provider Customer Panel → API Details.82# Local dev: add to .envrc. Production: GitHub Secrets → deploy → /opt/your-app/.env83```8485## CRITICAL GOTCHAS86871. **Shell escaping in Orca terminals:** `$VAR`, `$(…)`, and `%` get eaten by the local shell. Always use base64: `echo '<base64>' | base64 -d > script.sh`882. **Crontab `%`:** cron interprets `%` as newline. Escape as `\%` in `$(date +\%Y\%m\%d)`893. **fail2ban exit 255:** means a jail references a missing log file. Remove the broken jail, restart.904. **your application alerts need auth:** POST to `/api/monitoring/alerts` requires Bearer token. Workaround: insert directly into SQLite, then restart your application.9192## Verify all 8 gates9394```bash95ufw status|grep -q active||echo FAIL:ufw96fail2ban-client status sshd>/dev/null 2>&1||echo FAIL:fail2ban97systemctl is-active unattended-upgrades|grep -q active||echo FAIL:unattended98sshd -T|grep -q 'permitrootlogin no'||echo FAIL:sshd99systemctl show your-app -p NoNewPrivileges|grep -q yes||echo FAIL:systemd100systemctl is-active your-app|grep -q active||echo FAIL:your-app101sqlite3 /opt/your-app/data/your-app.db "SELECT count(*) FROM monitoring_alerts"|grep -q 3||echo FAIL:alerts102crontab -l|grep -q healthcheck&&crontab -l|grep -q your-app.db&&crontab -l|grep -q contabo-snapshot||echo FAIL:crontab103echo ALL 8 GATES PASSED104```105106→ verify: # requires VPS SSH — run the 8-gate one-liner on the VPS manually107108---109110# Harden VPS — Reference111112## Health check script113114Deploy to `/opt/your-app/scripts/healthcheck.sh`:115116```bash117#!/bin/bash118LOG=/var/log/your-app/health.log119mkdir -p /var/log/your-app120DISK=$(df / | awk 'NR==2 {print $5}' | tr -d '%')121RAM=$(free | awk '/Mem:/ {printf "%.0f", $3/$2*100}')122NOW=$(date -Iseconds)123[ "$DISK" -gt 85 ] && echo "$NOW ALERT: Disk ${DISK}%" >> "$LOG"124[ "$RAM" -gt 90 ] && echo "$NOW ALERT: RAM ${RAM}%" >> "$LOG"125systemctl is-active --quiet your-app || { echo "$NOW ALERT: your application DOWN" >> "$LOG"; systemctl restart your-app; }126[ -f /var/run/reboot-required ] && echo "$NOW WARN: kernel reboot pending" >> "$LOG"127```128129Make executable: `chmod +x /opt/your-app/scripts/healthcheck.sh`130131## your application systemd unit132133Full hardened unit at `/etc/systemd/system/your-app.service`:134135```ini136[Unit]137Description=your application BaaS Platform138Documentation=https://github.com/example-org/example-app139After=network.target caddy.service140Wants=caddy.service141142[Service]143Type=simple144User=your-app145Group=your-app146WorkingDirectory=/opt/your-app147ExecStart=/opt/your-app/bin/your-app serve \148 --port 8080 \149 --db /opt/your-app/data/your-app.db \150 --sites-domain <your-domain.example.com>151EnvironmentFile=-/opt/your-app/.env152Environment=APP_HOME=/opt/your-app153Environment=HOME=/opt/your-app154Environment=NPM_CONFIG_CACHE=/opt/your-app/.npm155Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin156Restart=always157RestartSec=5158StartLimitIntervalSec=60159StartLimitBurst=3160TimeoutStopSec=30161KillSignal=SIGTERM162NoNewPrivileges=yes163PrivateTmp=yes164ProtectSystem=full165ProtectHome=yes166ProtectKernelTunables=yes167ProtectKernelModules=yes168ProtectControlGroups=yes169RestrictAddressFamilies=AF_INET AF_INET6170RestrictRealtime=yes171ReadWritePaths=/opt/your-app/data172ReadWritePaths=/opt/your-app/backups173ReadWritePaths=/opt/your-app/secrets174ReadWritePaths=/opt/your-app/.npm175ReadWritePaths=/opt/your-app/logs176LimitNOFILE=65536177178[Install]179WantedBy=multi-user.target180```181182## fail2ban jail.local183184```ini185[sshd]186enabled = true187port = ssh188filter = sshd189logpath = /var/log/auth.log190maxretry = 3191bantime = 3600192findtime = 600193```194195Note: your application auth jail deferred — your application logs to journald, not a file. To enable, use `backend = systemd` with a journald filter.196197## provider-snapshot.sh — worked example (Contabo API)198199> Snapshot APIs are provider-specific. This is one worked example, not a200> bigpowers default; swap the auth and endpoint calls for your own provider.201202Deploy to `/opt/your-app/scripts/contabo-snapshot.sh`. Reads credentials from `/opt/your-app/.env` (same file used by your application systemd unit):203204```bash205#!/bin/bash206set -e207LOG=/var/log/your-app/snapshot.log208ENVFILE=/opt/your-app/.env209INSTANCE_ID=<your-contabo-instance-id>210211[ -f "$ENVFILE" ] || { echo "$(date -Iseconds) ERROR: $ENVFILE missing" >> "$LOG"; exit 1; }212set -a; source "$ENVFILE"; set +a213214[ -z "$CONTABO_CLIENT_ID" ] && { echo "$(date -Iseconds) ERROR: CONTABO_CLIENT_ID not set" >> "$LOG"; exit 1; }215216TOKEN=$(curl -s -d "client_id=$CONTABO_CLIENT_ID" \217 -d "client_secret=$CONTABO_CLIENT_SECRET" \218 --data-urlencode "username=$CONTABO_API_USER" \219 --data-urlencode "password=$CONTABO_API_PASSWORD" \220 -d 'grant_type=password' \221 'https://auth.contabo.com/auth/realms/contabo/protocol/openid-connect/token' \222 | jq -r '.access_token')223224[ -z "$TOKEN" ] || [ "$TOKEN" = "null" ] && { echo "$(date -Iseconds) ERROR: auth failed" >> "$LOG"; exit 1; }225226curl -s -X POST \227 -H "Authorization: Bearer $TOKEN" \228 -H "Content-Type: application/json" \229 -H "x-request-id: $(uuidgen 2>/dev/null || echo $RANDOM)" \230 "https://api.contabo.com/v1/compute/instances/${INSTANCE_ID}/snapshots" \231 | tee -a "$LOG"232233echo >> "$LOG"234echo "$(date -Iseconds) Snapshot requested for $INSTANCE_ID" >> "$LOG"235```236237## your VPS provider credentials238239Add to `/opt/your-app/.env` (same file used by your application systemd unit, deployed by GitHub Actions):240241```242CONTABO_CLIENT_ID=243CONTABO_CLIENT_SECRET=244CONTABO_API_USER=245CONTABO_API_PASSWORD=246```247248Source: your VPS provider Customer Control Panel → API Details. ClientId and ClientSecret are generated there. API User is your email. API Password is set separately in the panel.249250**Pipeline:** GitHub Secrets → deploy workflow → `/opt/your-app/.env` on VPS. For local dev (`cntb get instances`), add to `.envrc`.251252## your VPS provider instance info template253254Fill in your instance details from the your VPS provider Customer Control Panel:255256```257IP: <your-instance-ip>258IPv6: <your-instance-ipv6>259Region: <your-region>260OS: <your-os-version>261Disk: <your-disk-size>262Default user: root263Customer ID: <your-customer-id>264```265266## your application monitoring alert SQL267268```sql269INSERT INTO monitoring_alerts (id, name, metric, threshold, operator, enabled, duration_seconds)270VALUES ('alert-001', 'VPS Disk above 80%', 'disk_used_percent', 80, 'gt', 1, 300);271272INSERT INTO monitoring_alerts (id, name, metric, threshold, operator, enabled, duration_seconds)273VALUES ('alert-002', 'VPS CPU above 90%', 'cpu_percent', 90, 'gt', 1, 60);274275INSERT INTO monitoring_alerts (id, name, metric, threshold, operator, enabled, duration_seconds)276VALUES ('alert-003', 'VPS RAM above 85%', 'mem_used_percent', 85, 'gt', 1, 120);277```278279Alerts are loaded at your application startup. Restart with `systemctl restart your-app` after inserting.280281## Base64 encoding workaround282283When sending scripts through Orca terminal `--text`, the local bash shell interprets `$`, `(`, and `%`. Encode locally and decode remotely:284285```bash286# Local287cat script.sh | base64288289# Remote terminal290echo '<base64-output>' | base64 -d > script.sh291```292
Also in danielvm-git/bigpowers
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| danielvm-git/bigpowers.cursor/rules/align-grid.mdc · 119 | Cursor rules | lint-formatdo-notagent-behaviour | 65/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/assess-impact.mdc · 119 | Cursor rules | testtesting-strategydeployment | 66/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/audit-code.mdc · 119 | Cursor rules | setuptestlint-formatstyle+4 | 66/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/audit-plan.mdc · 119 | Cursor rules | buildteststylegit | 74/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/build-epic.mdc · 119 | Cursor rules | buildgit | 58/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/change-request.mdc · 119 | Cursor rules | no sections | 48/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/commit-message.mdc · 119 | Cursor rules | lint-formatstyletypesgit+3 | 82/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/compose-workflow.mdc · 119 | Cursor rules | styledo-notagent-behaviour | 65/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/context7-mcp.mdc · 119 | Cursor rules | style | 54/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/deepen-architecture.mdc · 119 | Cursor rules | testtesting-strategydo-not | 57/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/define-language.mdc · 119 | Cursor rules | lint-formatdo-not | 65/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/delegate-task.mdc · 119 | Cursor rules | git | 62/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/deploy.mdc · 119 | Cursor rules | setupbuildtestdeployment | 77/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/develop-tdd.mdc · 119 | Cursor rules | teststylearchtesting-strategy+5 | 85/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/diagnose-root.mdc · 119 | Cursor rules | no sections | 39/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/dispatch-agents.mdc · 119 | Cursor rules | git | 54/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/edit-document.mdc · 119 | Cursor rules | no sections | 39/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/elaborate-spec.mdc · 119 | Cursor rules | test | 58/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/enforce-first.mdc · 119 | Cursor rules | no sections | 50/100 | 3 days ago | |
| danielvm-git/bigpowers.cursor/rules/evolve-skill.mdc · 119 | Cursor rules | no sections | 50/100 | 3 days ago |
Diff against .cursor/rules/align-grid.mdc Diff against .cursor/rules/assess-impact.mdc Diff against .cursor/rules/audit-code.mdc Diff against .cursor/rules/audit-plan.mdc Diff against .cursor/rules/build-epic.mdc Diff against .cursor/rules/change-request.mdc Diff against .cursor/rules/commit-message.mdc Diff against .cursor/rules/compose-workflow.mdc Diff against .cursor/rules/context7-mcp.mdc Diff against .cursor/rules/deepen-architecture.mdc Diff against .cursor/rules/define-language.mdc Diff against .cursor/rules/delegate-task.mdc Diff against .cursor/rules/deploy.mdc Diff against .cursor/rules/develop-tdd.mdc Diff against .cursor/rules/diagnose-root.mdc Diff against .cursor/rules/dispatch-agents.mdc Diff against .cursor/rules/edit-document.mdc Diff against .cursor/rules/elaborate-spec.mdc Diff against .cursor/rules/enforce-first.mdc Diff against .cursor/rules/evolve-skill.mdc
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| danielvm-git/bigpowers.windsurf/rules/guard-git.md · 119 | Windsurf rules | stylearchgitsecurity+2 | 89/100 | 3 days ago | |
| danielvm-git/bigpowers.windsurf/rules/organize-workspace.md · 119 | Windsurf rules | buildstylegitdeployment+2 | 89/100 | 3 days ago | |
| danielvm-git/bigpowers.windsurf/rules/quick-fix.md · 119 | Windsurf rules | teststylegitdeployment+1 | 85/100 | 3 days ago | |
| danielvm-git/bigpowers.windsurf/rules/develop-tdd.md · 119 | Windsurf rules | teststylearchtesting-strategy+5 | 85/100 | 3 days ago | |
| danielvm-git/bigpowers.windsurf/rules/commit-message.md · 119 | Windsurf rules | lint-formatstyletypesgit+3 | 82/100 | 3 days ago | |
| danielvm-git/bigpowers.windsurf/rules/extract-design.md · 119 | Windsurf rules | lint-formatstyledependenciesui | 82/100 | 3 days ago | |
| danielvm-git/bigpowers.windsurf/rules/session-state.md · 119 | Windsurf rules | lint-formatstyleagent-behaviour | 82/100 | 3 days ago | |
| danielvm-git/bigpowers.windsurf/rules/setup-environment.md · 119 | Windsurf rules | setupstylesecuritydo-not+1 | 81/100 | 3 days ago |
