Go to file
2026-07-28 05:05:03 +03:00
config/zsh zsh: add distro-guarded apt alias set (apt/apti/aptu/apts/aptsh) mirroring pacman 2026-07-28 04:50:08 +03:00
lib skeleton: launcher, entrypoint, distro/log/prompt libs 2026-07-28 04:32:38 +03:00
modules create-user: passwordless sudo user (NOPASSWD, !tty_tickets, 7d cache) + retarget config; --create-user/--username flags 2026-07-28 05:05:03 +03:00
.gitignore docs: README + gitignore 2026-07-28 04:32:38 +03:00
install.sh skeleton: launcher, entrypoint, distro/log/prompt libs 2026-07-28 04:32:38 +03:00
README.md docs: document apt/pac aliases + Angie config layout 2026-07-28 04:50:08 +03:00
setup.sh create-user: passwordless sudo user (NOPASSWD, !tty_tickets, 7d cache) + retarget config; --create-user/--username flags 2026-07-28 05:05:03 +03:00

bootstrap

Turn a fresh Debian/Ubuntu/Arch VPS into a familiar workstation in one command. Detects the distro, installs a curated CLI package set, sets up zsh + oh-my-zsh with your ported config, brings your nvim config, hardens sane system defaults, and reports state.

Usage

curl -fsSL <shortener> | bash
# run everything non-interactively:
curl -fsSL <shortener> | bash -s -- --yes
# run a single step:
curl -fsSL <shortener> | bash -s -- --only packages

The shortener points at the raw install.sh in this repo, which clones the full repo to ~/.bootstrap and runs setup.sh. To update: cd ~/.bootstrap && git pull (or just re-run the one-liner — it pulls existing clones).

Re-run individual steps locally after the initial bootstrap:

bash ~/.bootstrap/setup.sh --only shell-zsh
bash ~/.bootstrap/setup.sh --only hardening

Steps

# step what it does
0 preflight verify distro + network + privileges
1 packages git, byobu, ufw, mosh, btop, eza, fzf, ripgrep, bat, zoxide, jq, fd, tree, ncdu, lazygit, starship, … (+ yay on Arch)
2 docker Docker engine + compose v2 plugin, $USER in docker group
3 angie Angie (nginx fork) web server
4 shell-zsh zsh + oh-my-zsh + plugins, set default shell, ship config
5 nvm nvm → ~/.nvm + Node LTS
6 nvim clone g.mk.fo/bootstrap/nvim-minimal~/.config/nvim
7 hardening ssh (key-only root), ufw, updates, unattended-upgrades, tz, hostname, swapfile, locale, fail2ban, angie config layout
8 sanity read-only report + recommended manual follow-ups

Every step is idempotent — re-running is safe.

Zsh plugins enabled (oh-my-zsh)

git sudo command-not-found colored-man-pages extract copypath dirhistory plus, cloned into $ZSH_CUSTOM: zsh-autosuggestions, zsh-syntax-highlighting (loaded last), zsh-completions, history-substring-search, fzf-tab. Prompt: starship.

Config layout (shipped to ~/.config/zsh)

config/zsh/
  zshrc              # main config (sources the pieces below)
  path.zsh           # PATH + env (XDG, EDITOR, LANG, nvm, pnpm)
  aliases.zsh        # ported from fish + curated defaults
  aliases-extras.zsh # your personal additions (preserved across re-runs)
  functions.zsh      # mcd, conf, so, ssh-wrapper, dot-expansion
  plugins.zsh        # omz plugin list + load oh-my-zsh

~/.zshrc is a tiny shim that sets ZDOTDIR=~/.config/zsh and sources the real config there — keeps omz "happy" while your config lives under XDG.

Hardening choices

  • SSH: PermitRootLogin prohibit-password + PasswordAuthentication no. Root key login stays available; password login is disabled. A backup is made and sshd -t runs before reload. Always keep your current session open and test a new login before closing it.
  • ufw: deny incoming, allow 22/tcp, 60000:61000/udp (mosh), 80,443/tcp (angie).
  • updates + unattended-upgrades (Debian), system upgrade on Arch.
  • timezone (default UTC), hostname (optional).
  • swapfile 2G if RAM < 2G and no swap present.
  • locale en_US.UTF-8.
  • fail2ban with an sshd jail.
  • angie config — see Angie config layout below.

Supported hosts

  • Debian / Ubuntu (via apt)
  • Arch (via pacman + yay for AUR)
  • Architectures: amd64, arm64

Angie config layout

The hardening step sets up a sane /etc/angie/ structure (Angie is an nginx fork; same config syntax):

/etc/angie/
  angie.conf          # managed by bootstrap (written once; not overwritten on re-runs)
  mime.types          # shipped by the package — left alone
  _on/*.conf          # enabled hosts (sites-enabled alt.)   ← included from http{}
  targets/*.conf      # all host definitions (sites-available alt.)
  modules/*.conf       # top-level context snippets (stream{}, env, load_module, ...)
  modules/http/*.conf  # http{}-level snippets (upstreams, maps, real_ip, proxy defaults)

Convention: write each host in targets/<name>.conf, then enable it with angie-enable <name> (symlinks targets/<name>.conf_on/<name>.conf, validates, reloads). Disable with angie-disable <name>. Both helpers are installed to /usr/local/bin/.

The root angie.conf ships: worker_processes auto, server_tokens off, gzip, client_max_body_size 16m, a main log_format, and includes modules/http/*.conf then _on/*.conf. user/pid/error_log/load_module directives are extracted from the original packaged angie.conf and preserved (so Debian's user angie vs Arch's user http is carried over). A backup is kept at /etc/angie/angie.conf.orig.bootstrap.

Defaults shipped:

  • modules/http/00-common.confreal_ip from private ranges (Cloudflare commented), proxy_set_header defaults (inherited by every server/location).
  • _on/00-default.confdefault_server returning 444 for requests that match no enabled host (drops drive-by scanners / bare-IP probes).

Notes

  • Distro-guarded package-manager aliases ship in aliases.zsh: pac/pacs/pacu/pacy/pa (Arch, sudo pacman …) and apt/apti/aptu/apts/aptsh (Debian/Ubuntu, sudo apt …).
  • aliases-extras.zsh is seeded once and then left alone — re-running the shell-zsh step won't clobber your personal aliases.
  • On Debian, fd/bat ship as fdfind/batcat; symlinks to the canonical names are created in ~/.local/bin (which is on PATH via path.zsh).
  • Some Go/Rust CLIs (dust, duf, procs, lazygit) are fetched from the latest GitHub release tarball when the distro package is missing; this is best-effort and warned (not fatal) on failure.