Interactive Linux server automation toolkit — one launcher, 24 scripts across 8 categories: system setup, security hardening, cloud panels, databases, app runtimes, monitoring & observability, CI/CD, and network & Proxmox tooling.
Run scripts individually or use install.sh, an interactive grouped checkbox
launcher that fetches and runs the chosen scripts in order. No authentication
required — public repo, served via GitHub Pages at scripts.wanforge.asia.
Scripts are organized under script/linux/<category>/, structured so future
macOS or Windows scripts can be added alongside without changing the layout.
script/linux/; macOS/Windows
variants would go in script/macos/ / script/windows/ when added.apt, dnf, yum, pacman, zypper, or apk.
Some scripts are Debian/Ubuntu only (noted in the table below).curl and sudo (or root). Node.js, Composer, and PM2 install
user-local — no sudo needed for those./dev/tty).curl first (fresh systems)A minimal install may not ship curl. Install it for your distro:
# Debian / Ubuntu
sudo apt update && sudo apt install -y curl
# Fedora / RHEL / CentOS / Rocky / Alma
sudo dnf install -y curl # or: sudo yum install -y curl
# Arch / Manjaro
sudo pacman -Sy --noconfirm curl
# openSUSE
sudo zypper install -y curl
# Alpine
sudo apk add curl
If you are root (e.g. a fresh container/VM), drop the sudo. No package
manager handy? curl usually rides along with wget — see the wget alternative
below.
curl -fsSL https://scripts.wanforge.asia/install.sh | bash
No curl? Use wget instead (present on many minimal images):
wget -qO- https://scripts.wanforge.asia/install.sh | bash
Menu controls:
| Key | Action |
|---|---|
| Up / Down | Move between rows |
| Space | Toggle a selection |
| A | Toggle all |
| Enter | Run the selected scripts |
| Q | Cancel and exit |
The menu is grouped by category and runs the selected scripts in menu order. If one fails, the rest still continue. No authentication is needed — this is a public repository.
Navigation: the launcher loops. After the chosen scripts finish you return to
this menu (press Enter), so you can keep picking more. Inside a sub-script’s own
menu (firewall / database / CloudPanel / Proxmox / net-tools managers) press Q
to go back to the launcher. Press Q at the launcher to quit entirely.
Select scripts to run: ↑/↓ move · SPACE toggle · A all · ENTER run · Q quit
── System ──
❯ [ ] install-packages Update system + base essentials (micro, curl, wget, git)
[ ] set-timezone Set timezone (UTC recommended for servers)
── Security ──
[ ] install-firewall Install & configure ufw firewall
[ ] firewall-manager Full ufw manager: allow/deny IP/port, multiple, rate-limit
[ ] install-fail2ban Install & enable Fail2Ban
[ ] secure-ssh Harden SSH: change port, disable root/password, pubkey
[ ] generate-ssh-key Generate an ed25519 SSH key (user-local)
── Panel & Console ──
[ ] install-cloudpanel Install CloudPanel CE v2 (Debian/Ubuntu only)
[ ] clpctl-manager Manage CloudPanel via clpctl (sites, db, users, certs)
[ ] install-cockpit Install Cockpit web console + modules (Debian/Ubuntu)
── Database ──
[ ] install-postgresql Install PostgreSQL + create roles + remote access
[ ] enable-mysql-remote Allow remote MySQL/MariaDB access (sensitive)
[ ] database-toolkit Monitor, optimize, config, datetime (MySQL/PostgreSQL)
── App Runtime ──
[ ] install-nodejs Install Node.js via nvm (user-local) + PM2
[ ] install-python Install Python 3 + pip, venv, dev, pipx
[ ] install-composer Install Composer (user-local, signature-verified)
[ ] setup-pm2-app Configure pm2-logrotate + register an app (ecosystem)
── Monitoring ──
[ ] monitor-system CPU, RAM, storage, processes, network (snapshot or realtime)
── Network ──
[ ] net-tools Local/public IP, ports, speedtest, ping, dig, scan
── Proxmox ──
[ ] proxmox-toolkit PVE: node/VM/CT resources, storage, realtime dashboard
── CI/CD ──
[ ] install-github-runner GitHub Actions self-hosted runner (avoid billed minutes)
── Observability ──
[ ] install-prometheus Prometheus + node_exporter (+ Alertmanager)
[ ] install-grafana Grafana + Prometheus data source
[ ] install-zabbix Zabbix agent or server (official repo)
Every script shares one verbosity control (defined in lib.sh). Set it with an
environment variable (recommended — it also propagates through the launcher) or
a flag:
| Mode | Shows | How |
|---|---|---|
silent |
Errors and final result only, no banner | MODE=silent · QUIET=1 · -q |
normal |
Banner + info/ok/warn/err (default) | MODE=normal (default) |
verbose |
Normal + extra dbg detail |
MODE=verbose · VERBOSE=1 · -v |
debug |
Verbose + shell trace (set -x) |
MODE=debug · DEBUG=1 · --debug |
# silent (good for automation / cron)
curl -fsSL https://scripts.wanforge.asia/install.sh | MODE=silent bash
# verbose
curl -fsSL .../script/linux/monitoring/monitor-system.sh | VERBOSE=1 bash
DRY_RUN=1 (or --dry-run / -n) makes every script print the
state-changing commands instead of running them — defined once in lib.sh, so
it works the same everywhere:
curl -fsSL .../script/linux/security/install-fail2ban.sh | DRY_RUN=1 bash
# → [dry-run] sudo apt-get install -y fail2ban
# [dry-run] sudo systemctl start fail2ban
Dry-run covers system mutations: package managers (install/upgrade/remove),
services (systemctl/rc-service), ufw, sed -i, tee config writes,
timedatectl, file ops, and PostgreSQL VACUUM/REINDEX. Read-only commands
still run so you see real state. A few user-local installs (nvm/Node, Composer,
PM2) and MySQL client mutations execute as normal.
| Variable / flag | Effect |
|---|---|
ASSUME_YES=1 · YES=1 · -y |
ask returns the default answer without prompting (non-interactive) |
LOG_FILE=/path |
Appends a plain-text (no-color) copy of every log line |
NO_COLOR=1 |
Disables colors in any mode |
# fully unattended, dry-run, logged
curl -fsSL .../script/linux/security/install-fail2ban.sh | ASSUME_YES=1 DRY_RUN=1 LOG_FILE=/var/log/wf.log bash
Note: ASSUME_YES only fills prompts that have a safe default; password prompts
and free-text inputs (e.g. role names) still need real input or are skipped.
The user-local scripts (install-nodejs, install-composer, setup-pm2-app)
install into a user’s home — not the system. When you run them as root, they
ask which user to install for (or set TARGET_USER=<name> / --user=<name>) and
re-run themselves as that user via sudo -u, so Node/Composer/PM2 land in that
user’s home. Perfect for CloudPanel site users:
# install Node + PM2 into the CloudPanel site user 'john'
curl -fsSL .../script/linux/runtime/install-nodejs.sh | TARGET_USER=john bash
All menus (launcher and the clpctl / database / firewall managers) are
arrow-key TUIs — ↑/↓ to move, ENTER to select, Q to go back.
Each script can also be run directly without the launcher.
# System
curl -fsSL https://scripts.wanforge.asia/script/linux/system/install-packages.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/system/set-timezone.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/system/install-firewall.sh | bash
# Security
curl -fsSL https://scripts.wanforge.asia/script/linux/security/firewall-manager.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/security/install-fail2ban.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/security/secure-ssh.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/security/generate-ssh-key.sh | bash
# Panels & consoles
curl -fsSL https://scripts.wanforge.asia/script/linux/cloud/install-cloudpanel.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/cloud/clpctl-manager.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/cloud/install-cockpit.sh | bash
# Databases
curl -fsSL https://scripts.wanforge.asia/script/linux/database/install-postgresql.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/database/enable-mysql-remote.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/database/database-toolkit.sh | bash
# Monitoring & network
curl -fsSL https://scripts.wanforge.asia/script/linux/monitoring/monitor-system.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/network/net-tools.sh | bash
# Proxmox (run on a PVE node)
curl -fsSL https://scripts.wanforge.asia/script/linux/network/proxmox-toolkit.sh | bash
# CI/CD
curl -fsSL https://scripts.wanforge.asia/script/linux/cicd/install-github-runner.sh | bash
# Observability stack
curl -fsSL https://scripts.wanforge.asia/script/linux/monitoring/install-prometheus.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/monitoring/install-grafana.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/monitoring/install-zabbix.sh | bash
# App runtime
curl -fsSL https://scripts.wanforge.asia/script/linux/runtime/install-nodejs.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/runtime/install-python.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/runtime/install-composer.sh | bash
curl -fsSL https://scripts.wanforge.asia/script/linux/runtime/setup-pm2-app.sh | bash
| Group | Script | Purpose | Sudo | Distro |
|---|---|---|---|---|
| — | install.sh |
Grouped checkbox launcher that runs the other scripts | — | Any |
| System | install-packages.sh |
Update/upgrade system, install base essentials (micro/curl/wget/git) | Yes | Multi |
| System | set-timezone.sh |
Set timezone via timedatectl (default Asia/Jakarta) |
Yes | Any (systemd) |
| System | install-firewall.sh |
Install ufw, open SSH/http/https, add custom ports, enable |
Yes | Mainly Deb/Ubu |
| Security | firewall-manager.sh |
Full ufw manager: allow/deny IP & port, multi-IP, rate-limit | Yes | Any (ufw) |
| Security | install-fail2ban.sh |
Install and enable the Fail2Ban service | Yes | Multi |
| Security | secure-ssh.sh |
Change SSH port, disable root/password login, enable pubkey | Yes | Any (OpenSSH) |
| Security | generate-ssh-key.sh |
Generate an ed25519 SSH key, fix perms, print public key | No | Any |
| Panel & Console | install-cloudpanel.sh |
Install CloudPanel CE v2, choose DB engine, verify checksum | Yes | Debian/Ubuntu |
| Panel & Console | clpctl-manager.sh |
Manage CloudPanel via clpctl: sites, db, users, certs, vhosts |
Yes | CloudPanel |
| Panel & Console | install-cockpit.sh |
Install Cockpit + modules, reverse-proxy config, open port 9090 | Yes | Debian/Ubuntu |
| Database | install-postgresql.sh |
Install latest PostgreSQL (PGDG), create roles, remote access | Yes | Debian/Ubuntu |
| Database | enable-mysql-remote.sh |
Remote MySQL/MariaDB: bind-address, firewall, create users | Yes | Debian/Ubuntu |
| Database | database-toolkit.sh |
Monitor / optimize / config / datetime — MySQL & PostgreSQL | Yes | Any (DB client) |
| App Runtime | install-nodejs.sh |
Install Node.js via nvm (user-local), choose version, PM2 | No | Any |
| App Runtime | install-python.sh |
Python 3 + pip, venv/virtualenv, dev headers, pipx (multi-distro) | Yes | Multi |
| App Runtime | install-composer.sh |
Install Composer to ~/.local/bin, verify signature |
No | Any (needs PHP) |
| App Runtime | setup-pm2-app.sh |
Configure pm2-logrotate + register an app (ecosystem.config.js) | No | Any |
| Monitoring | monitor-system.sh |
CPU/RAM/storage/processes/network — snapshot or realtime watch | Some | Any |
| Network | net-tools.sh |
Local/public IP, ports, speedtest, ping/traceroute/dig/whois/scan | Some | Any |
| Proxmox | proxmox-toolkit.sh |
PVE node/VM/CT resources, storage, cluster, realtime dashboard | Yes | Proxmox VE |
| CI/CD | install-github-runner.sh |
GitHub Actions self-hosted runner as a systemd service (avoid billed minutes) | Yes | Linux |
| Observability | install-prometheus.sh |
Prometheus + node_exporter + optional Alertmanager, scrape config | Yes | Debian/Ubuntu |
| Observability | install-grafana.sh |
Grafana (official repo) + auto Prometheus data source | Yes | Debian/Ubuntu |
| Observability | install-zabbix.sh |
Zabbix agent or full server (frontend + MySQL schema) | Yes | Debian/Ubuntu |
apt, dnf, yum, pacman, zypper, apk.micro, curl, wget,
git. Package names are resolved per distro.install-python.sh; speedtest-cli is in net-tools.sh.pip, venv/virtualenv, dev
headers (build C extensions), and pipx (install Python CLI apps isolated).pipx falls back to pip --user where the
repo has no package, then runs pipx ensurepath.timedatectl via an arrow-key menu: UTC (recommended
for servers & databases — no DST, consistent logs), Asia/Jakarta, a custom
zone, or skip. Best practice: keep the OS and DB in UTC and format to local
time in the application.database-toolkit.sh’s date/time action
reminds you to run MySQL/PostgreSQL in UTC.ufw if missing, allows OpenSSH, http, https.8443/tcp 3000/tcp).ufw manager (installs ufw if missing). Looping menu:
Dry-run: set DRY_RUN=1 to print every ufw command without executing —
safe to try the menus and inputs first:
curl -fsSL https://scripts.wanforge.asia/script/linux/security/firewall-manager.sh | DRY_RUN=1 bash
22 — keep it or set a custom one), disables
root login, optionally disables password auth, enables pubkey auth.sshd_config.d/ when Include is active, otherwise
edits the main config. Backs up sshd_config first.ufw before restarting, validates with
sshd -t, and refuses to disable password auth when no authorized_keys exists.ed25519 key in ~/.ssh (no sudo). Prompts for the file path,
comment (default wanforge-asia@<hostname>), and an optional passphrase.~/.ssh to
700, the private key to 600, the public key to 644.EXPECTED_SHA from the CloudPanel docs for new
releases. Web console at https://<server-ip>:8443.clpctl). Interactive menu over the documented v2 CLI
(reference). Loops until you quit.clpctl as flags, so they may briefly appear in the process list.9090 in ufw (skip if proxied)./etc/cockpit/cockpit.conf with AllowOrigins (bare
domain, e.g. cockpit.domain.id — TLS terminated by CloudPanel),
ProtocolHeader, AllowUnencrypted. Add the domain in CloudPanel as a
reverse proxy to http://127.0.0.1:9090.yes confirmation, since it can drop SSH).networkmanager, storaged, sosreport, pcp, machines,
podman (each individually selectable).pmcd + pmlogger.http://127.0.0.1:9090.postgresql-contrib.SUPERUSER (default off).pg_hba.conf + listen_addresses (paths
resolved via SHOW hba_file/config_file), restarts, and opens 5432 for a
chosen source CIDR.bind-address = 0.0.0.0, restarts the service, and opens 3306 for a
chosen source CIDR.sudo,
or a root password), then loops to create user@host with a password and a
grant on a chosen database (or all). Host defaults to % (any client).
Passwords are entered interactively and never stored.mysqlcheck), MySQLTuner.pg_stat_activity, date/time + timezone check, key settings, cache hit ratio,
VACUUM ANALYZE + optional REINDEX.sudo) or a prompted password (MySQL) / the
postgres system user (PostgreSQL). Read-only actions are safe; optimize
actions modify tables.lm-sensors).Realtime / watch mode: refreshes the selected sections on an interval until
Ctrl-C. Enable with the prompt, WATCH=1, or -w/--watch; set the cadence
with INTERVAL=<seconds> (default 2). bigdirs is skipped while watching.
Updates happen in place — the cursor homes and overwrites each line (no
full-screen clear), so the values refresh without flicker or a “page reload”.
curl -fsSL .../script/linux/monitoring/monitor-system.sh | WATCH=1 INTERVAL=2 bash
htop, btop, ncdu, glances, iotop
— full-screen realtime monitors if you prefer a TUI.host:port, scan ports (nmap or /dev/tcp).mtr, DNS lookup (dig), whois, HTTP
headers (curl -I), interface traffic stats.speedtest/speedtest-cli).pvesh/qm//etc/pve). Arrow-key TUI:
pvecm), recent tasks, HA.pvesm storage,
top processes, disk I/O.qm) and containers (pct); manage one VM/CT
(status / start / shutdown / stop / reboot / config / vzdump backup).DRY_RUN.A single-select TUI manager for GitHub Actions self-hosted runners.
Jobs with runs-on: self-hosted execute on your machine, so GitHub-hosted
runner minutes are not consumed — self-hosted runners are
free of per-minute billing.
Menu actions:
| Action | What it does |
|---|---|
| Install | Register a new runner and install it as a systemd service |
| List | Show every runner on this host (name, service state, user, target URL, dir) |
| Status | systemctl status of a chosen runner service |
| Logs | journalctl -u <svc> (last 100 lines) for a chosen runner |
| Start / Stop / Restart | Control a chosen runner service via svc.sh |
| Remove | Stop + uninstall the service, unregister from GitHub, optionally delete the dir |
owner/name) or a whole org. Fetches the
latest actions/runner release
for your arch (x64 / arm64 / arm), or prompts for a version if the
GitHub API is unreachable.--system user (default github-runner);
GitHub forbids running the service as root.
Override the user at the prompt.${RUNNER_ROOT}/<name> (default
RUNNER_ROOT=/opt/actions-runner), so multiple runners coexist. Install runs
bin/installdependencies.sh (libicu etc.), config.sh --unattended --replace
(with optional --ephemeral, --labels, --runnergroup, --work), then
svc.sh install <user> + svc.sh start.Use in a workflow:
jobs:
build:
runs-on: [self-hosted, linux, x64] # or a custom label you set at install
:9090), node_exporter
(:9100, host CPU/RAM/disk metrics), Alertmanager (:9093), and firewall./etc/prometheus/prometheus.yml. Prints the URLs at the end.grafana-server (:3000), optionally provisions a Prometheus data source,
and opens the firewall. Login admin/admin on first use; import dashboard
ID 1860 (Node Exporter Full) to visualize host metrics.zabbix-agent2, set the server IP + hostname, open :10050.zabbix
database, imports the schema, sets DBPassword, starts everything. Frontend
at http://<ip>/zabbix, default login Admin/zabbix.# 1) On each host you want to monitor: metrics exporter
curl -fsSL .../script/linux/monitoring/install-prometheus.sh | bash # pick node_exporter (+ Prometheus on the main host)
# 2) On the monitoring host: Grafana + Prometheus data source
curl -fsSL .../script/linux/monitoring/install-grafana.sh | bash # auto-add http://localhost:9090
# 3) In Grafana (http://host:3000) → Dashboards → Import → 1860 → done.
# Alternative all-in-one platform:
curl -fsSL .../script/linux/monitoring/install-zabbix.sh | bash # server on the main host, agent on the rest
nvm into $HOME/.nvm (no sudo) and the chosen Node version
(18, 20, lts, latest). Sets it as the default + stable alias.pm2-logrotate, runs pm2 save, and (optionally)
sets up boot startup via systemd (this single step needs sudo).~/.local/bin/composer (no sudo),
verifying the installer SHA-384 signature before running it.~/.local/bin to PATH in ~/.bashrc and runs composer self-update.install-nodejs.sh first). Sources nvm to find PM2.pm2-logrotate (max size, retention, compression, daily
rotation).ecosystem.config.js (name, cwd, script,
args, instances/cluster, NODE_ENV, memory restart limit), then runs
pm2 start + pm2 save.flowchart TD
A[curl install.sh] --> B[Show banner + checkbox menu]
B --> C{Select scripts}
C -->|Space toggle| C
C -->|A toggle all| C
C -->|Enter| D[Optional auth: skip for public repo]
D --> E[Fetch each selected script]
E --> F[Run in menu order]
F --> G[Done]
C -->|Q| H[Cancel]
.gitignore for the blocked patterns.install-postgresql.sh asks for role names and
passwords interactively. No passwords are stored in these scripts.install-postgresql.sh and enable-mysql-remote.sh
expose the database to the network. Prefer a restricted source CIDR over
0.0.0.0/0, and place the server behind a firewall or private network.secure-ssh.sh can lock you out. It opens the new port in
ufw before restarting, validates with sshd -t, backs up the config, and
refuses to disable password auth without an authorized_keys present. Keep
your current session open and test the new port before closing it.AllowUnencrypted = true is only safe when TLS is terminated by
the proxy (e.g. CloudPanel) in front of Cockpit.sudo. PM2
boot startup (pm2 startup) is optional and needs sudo for systemd.The banner, colors, logging helpers (info/ok/warn/err/hd), prompts
(ask/asks), and the grouped checkbox menu live once in
script/linux/lib.sh. Every script sources it:
TASK="my-script"
__LIB="https://scripts.wanforge.asia/script/linux/lib.sh"
__d="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || true)"
if [ -r "${__d}/../lib.sh" ]; then . "${__d}/../lib.sh"
else . <(curl -fsSL "${__LIB}"); fi
It looks for lib.sh one directory up (cloned repo layout: script/linux/<category>/),
otherwise fetches it from the public repo over HTTPS. Set TASK before sourcing — the
banner subtitle uses it. To add a script, copy this header, fill in TASK, place the
file under script/linux/<os>/<category>/, and register it in install.sh.
GNU General Public License v3.0 (GPL-3.0). Copyright (c) 2026 Sugeng Sulistiyawan.
See LICENSE.