diff --git a/lib/prompt.sh b/lib/prompt.sh index d301a7b..fe8d88a 100755 --- a/lib/prompt.sh +++ b/lib/prompt.sh @@ -2,9 +2,15 @@ # prompt.sh — plain y/N prompts. # ask "prompt?" [default y|n] -> sets REPLY +# Under ${ALL:-0} (the --yes flag) assume the stated default without +# reading stdin, so a fully non-interactive run never blocks on a read. ask() { local p="$1" d="${2:-y}" hint if [[ "$d" == "y" ]]; then hint="[Y/n]"; else hint="[y/N]"; fi + if (( ${ALL:-0} )); then + REPLY="$d" + return + fi read -rp "$p $hint " REPLY [[ -z "$REPLY" ]] && REPLY="$d" } diff --git a/modules/05-create-user.sh b/modules/05-create-user.sh index 6d5183e..272f498 100755 --- a/modules/05-create-user.sh +++ b/modules/05-create-user.sh @@ -14,6 +14,7 @@ # - Root + no SUDO_USER + Debian/Ubuntu -> ask y/N (optional, apt works as root). # - --create-user flag -> force prompt regardless. # - --username NAME -> override default name, no prompt. +# - --yes (no --username) -> default to 'mk' silently (no stall). # # Passwordless, no user password (NOPASSWD). Idempotent: re-runs re-sync # authorized_keys + sudoers rather than failing. @@ -40,7 +41,8 @@ run_create_user() { _handle_direct_root() { local should_create=0 - if (( CREATE_USER )); then + if (( CREATE_USER )) || [[ -n "${USERNAME_ARG:-}" ]]; then + [[ -n "${USERNAME_ARG:-}" ]] && log "--username given: forcing user creation" should_create=1 elif [[ "$DISTRO" == "arch" ]]; then # AUR/makepkg cannot run as root — we genuinely need a non-root sudoer. diff --git a/modules/50-hardening.sh b/modules/50-hardening.sh index 841648c..ddd3b3e 100755 --- a/modules/50-hardening.sh +++ b/modules/50-hardening.sh @@ -2,7 +2,7 @@ # 50-hardening — system defaults. Each sub-step y/N, all idempotent. step_name="hardening" -step_desc="ssh, ufw, updates, unattended, tz, hostname, swap, locale, fail2ban, angie config" +step_desc="ssh, ufw, updates, unattended, tz, hostname, swap, locale, fail2ban, angie config + built-in ACME" step_run="run_hardening" run_hardening() { @@ -15,6 +15,7 @@ run_hardening() { _h_locale _h_fail2ban _h_angie_config + _h_angie_acme ok "hardening pass complete" } @@ -104,8 +105,12 @@ _h_tz_hostname() { if command -v timedatectl >/dev/null 2>&1; then if yn "Set timezone (default UTC)?" y; then local tz="${REPLY_TZ:-}" - read -rp " timezone [UTC]: " tz - [[ -z "$tz" ]] && tz="UTC" + if (( ${ALL:-0} )); then + tz="UTC"; ok "--yes: timezone defaulting to UTC" + else + read -rp " timezone [UTC]: " tz + [[ -z "$tz" ]] && tz="UTC" + fi sudo_ timedatectl set-timezone "$tz" && ok "timezone set to $tz" fi else @@ -367,4 +372,86 @@ if command -v angie >/dev/null 2>&1; then fi EOF sudo_ chmod +x /usr/local/bin/angie-enable /usr/local/bin/angie-disable +} + +# ── angie built-in ACME (Let's Encrypt) ──────────────────────────────────── +# No certbot: Angie ships http_acme and fetches/renews certs itself. +# Bootstrap can't know your domain/email, so we ship an example TLS server +# target + an `angie-issue` helper that creates the live acme_client on demand. +_h_angie_acme() { + if ! command -v angie >/dev/null 2>&1; then + echo " skip angie ACME (angie not installed)"; return + fi + if ! yn "Ship Angie built-in ACME template + angie-issue helper?" y; then + echo " skip angie ACME"; return + fi + + local d=/etc/angie + # example TLS server target — NOT auto-enabled (.example, so angie-enable won't glob it) + if [[ ! -f "$d/targets/example-https.conf.example" ]]; then + sudo_ tee "$d/targets/example-https.conf.example" >/dev/null <<'EOF' +# Template for a TLS host using Angie built-in ACME. +# Use it via the helper: angie-issue [you@email] +# Or by hand: copy to targets/.conf, fill , then: angie-enable +server { + listen 80; + listen [::]:80; + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name ; + + acme default; # add this server_name to the shared 'default' ACME certificate + ssl_certificate $acme_cert_default; + ssl_certificate_key $acme_cert_key_default; + + # http -> https redirect + if ($scheme = http) { return 301 https://$host$request_uri; } + + location / { + # replace with your app / proxy_pass upstream; + return 200 "angie + builtin ACME ok\n"; + add_header Content-Type text/plain; + } +} +EOF + fi + + sudo_ tee /usr/local/bin/angie-issue >/dev/null <<'EOF' +#!/usr/bin/env bash +# angie-issue [email] — create a TLS host backed by Angie built-in ACME. +# First call (needs email) writes /etc/angie/modules/http/acme.conf with the +# shared 'default' acme_client; every call writes targets/.conf and enables it. +# All enabled domains using `acme default` share one cert covering all their server_names. +set -euo pipefail +domain="${1:-}"; email="${2:-}" +[ -n "$domain" ] || { echo "usage: angie-issue [email]" >&2; exit 2; } +acme_conf=/etc/angie/modules/http/acme.conf + +if [ ! -f "$acme_conf" ]; then + [ -n "$email" ] || { echo "first-time: also pass your email for Let's Encrypt" >&2; exit 2; } + cat > "$acme_conf" <|${domain}|g" /etc/angie/targets/example-https.conf.example > "$target" +echo "wrote $target" +ln -sfn "../targets/${domain}.conf" /etc/angie/_on/${domain}.conf +echo "enabled: $domain" +sudo angie -t +sudo systemctl reload angie && echo "angie reloaded — certificate is obtained automatically." +EOF + sudo_ chmod +x /usr/local/bin/angie-issue + + ok "angie ACME ready: example host at $d/targets/example-https.conf.example" + echo " issue a cert: angie-issue you@email" + echo " then reload picks it up; Angie renews automatically before expiry." } \ No newline at end of file