From e5d59473d2017e5e42398bc17e56033e705b8665 Mon Sep 17 00:00:00 2001 From: Damien Date: Sat, 1 Aug 2026 20:26:54 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20r=C3=A9duire=20le=20d=C3=A9p=C3=B4t=20?= =?UTF-8?q?=C3=A0=20netlab=20+=20komodo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration Gitea → GitHub et bascule de l'infra sur Komodo (suppression de Proxmox) : proxy/, openbao/ et gitea-runner/ n'ont plus lieu d'être, et lib/common.sh n'était sourcé que par ces deux derniers. Réf: #30 --- README.md | 16 +- gitea-runner/README.md | 71 ----- gitea-runner/install.sh | 409 ---------------------------- komodo/README.md | 2 +- lib/common.sh | 120 -------- netlab/README.md | 4 +- openbao/README.md | 125 --------- openbao/install.sh | 586 ---------------------------------------- proxy/README.md | 62 ----- proxy/install.sh | 360 ------------------------ 10 files changed, 9 insertions(+), 1746 deletions(-) delete mode 100644 gitea-runner/README.md delete mode 100644 gitea-runner/install.sh delete mode 100644 lib/common.sh delete mode 100644 openbao/README.md delete mode 100755 openbao/install.sh delete mode 100644 proxy/README.md delete mode 100644 proxy/install.sh diff --git a/README.md b/README.md index c712543..fd95e43 100644 --- a/README.md +++ b/README.md @@ -12,21 +12,17 @@ These scripts automate the deployment of personal infrastructure components. The - **Multi-OS**: Supports Debian and Alpine-based deployments, chosen per-script based on that service's requirements - **Loopback by default**: Services bind to `127.0.0.1`; Tailscale handles the reverse proxy and TLS termination - **Log hygiene**: Every long-running service ships with a `logrotate` config (no unbounded log files) -- **Console auto-login**: Proxmox LXCs are configured for root auto-login on `tty1` (fast `pct enter` and Web UI shell access) - **Keep it simple**: One script per service, plain bash, no frameworks — readability over cleverness ### Available Scripts -| Script | Description | Usage | -| ------------------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| [`proxy/install.sh`](proxy/) | Reverse proxy with Tailscale + Nginx Proxy Manager | `curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/proxy/install.sh` \| `bash` | -| [`netlab/install.sh`](netlab/) | Network lab with ContainerLab | `curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/netlab/install.sh` \| `bash` | -| [`gitea-runner/install.sh`](gitea-runner/) | Gitea Act Runner on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh)"` | -| [`openbao/install.sh`](openbao/) | OpenBao secrets manager on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/openbao/install.sh)"` | -| [`komodo/install.sh`](komodo/) | Komodo (Docker + MongoDB) on Alpine VM | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/komodo/install.sh)"` | +| Script | Description | Usage | +| -------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| [`netlab/install.sh`](netlab/) | Network lab with ContainerLab | `curl -fsSL https://raw.githubusercontent.com/darnodo/infra-scripts/main/netlab/install.sh` \| `bash` | +| [`komodo/install.sh`](komodo/) | Komodo (Docker + MongoDB) on Alpine VM | `bash -c "$(curl -fsSL https://raw.githubusercontent.com/darnodo/infra-scripts/main/komodo/install.sh)"` | ### Requirements -- Fresh Debian 12/13 installation (proxy, netlab) or Proxmox VE host (gitea-runner, openbao) or Alpine VM (komodo) -- User with sudo privileges (do not run as root) — except gitea-runner, openbao, and komodo which run as root +- Fresh Debian 12/13 installation (netlab) or Alpine VM (komodo) +- User with sudo privileges (do not run as root) — except komodo, which runs as root - Internet access diff --git a/gitea-runner/README.md b/gitea-runner/README.md deleted file mode 100644 index 28921b1..0000000 --- a/gitea-runner/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Gitea Act Runner - -Automated installation script for a Gitea Actions runner in an Alpine LXC on Proxmox. - -### Features - -Single script, three automatic modes: - -| Context | Action | -|---------|--------| -| From Proxmox host | Creates Alpine LXC + installs everything | -| From empty LXC | Installs Docker + act_runner + OpenRC service | -| From LXC with act_runner installed | Updates binary to latest version | - -### Usage - -#### Full install (from Proxmox shell) - -```bash -bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh)" -``` - -The script automatically creates an Alpine LXC (template auto-detected from `pveam available`) with Docker and act_runner. - -#### Customization - -Environment variables to override defaults: - -```bash -CTID=120 HOSTNAME=runner-02 CORES=4 RAM=4096 bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh)" -``` - -| Variable | Default | Description | -|----------|---------|-------------| -| `CTID` | auto | Container ID | -| `RUNNER_HOSTNAME` | `gitea-runner` | LXC Hostname | -| `TEMPLATE` | auto-detected | Alpine template; auto-detected from `pveam available` | -| `CORES` | `2` | CPU cores | -| `RAM` | `2048` | RAM in MiB | -| `DISK` | `8` | Disk in GB | -| `STORAGE` | `local-lvm` | Proxmox storage for the LXC | -| `BRIDGE` | `vmbr0` | Network bridge | - -#### Runner registration - -After installation, enter the LXC and register the runner: - -```bash -pct enter -cd /var/lib/gitea-runner -su -s /bin/bash gitea-runner -c "act_runner register" -rc-service gitea-runner start -``` - -#### Update - -From inside the LXC: - -```bash -curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh | bash -``` - -The script detects that act_runner is already installed and switches to update mode automatically. Re-running from the Proxmox host does the same, plus refreshes the LXC's Alpine packages first (`apk update && apk upgrade`). - -### Architecture - -- **OS**: Alpine 3.23 (LXC non-privileged, nesting active) -- **Docker**: installed via apk, OpenRC service -- **act_runner**: official binary from gitea.com/gitea/act_runner -- **Service**: OpenRC with logs in `/var/log/gitea-runner.log` -- **User**: `gitea-runner` (group `docker`) diff --git a/gitea-runner/install.sh b/gitea-runner/install.sh deleted file mode 100644 index 5e6ab36..0000000 --- a/gitea-runner/install.sh +++ /dev/null @@ -1,409 +0,0 @@ -#!/bin/bash -# install.sh - Gitea Act Runner: LXC creation, installation & update -# Usage: -# From Proxmox host : curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh | bash -# From inside LXC : bash /root/install.sh (updates act_runner binary) - -set -euo pipefail - -# --- Config (override via environment) --- -CTID="${CTID:-}" -HOSTNAME_LXC="${RUNNER_HOSTNAME:-gitea-runner}" -TEMPLATE="${TEMPLATE:-}" # auto-detected when empty -STORAGE="${STORAGE:-local-lvm}" -TEMPLATE_STORAGE="${TEMPLATE_STORAGE:-local}" -CORES="${CORES:-2}" -RAM="${RAM:-2048}" -DISK="${DISK:-8}" -BRIDGE="${BRIDGE:-vmbr0}" -LXC_TAG="${LXC_TAG:-gitea-runner}" # stable identifier for the container -# SCRIPT_URL is what the host-side flow pipes into the LXC. Override it when -# testing from a non-main branch, e.g. -# SCRIPT_URL="https://gitea.arnodo.fr/.../branch/chore/standardize-lxc-scripts/gitea-runner/install.sh" -SCRIPT_URL="${SCRIPT_URL:-https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh}" -GITEA_HOSTNAME="${GITEA_HOSTNAME:-gitea.taila5ad8.ts.net}" -GITEA_API="https://gitea.com/api/v1/repos/gitea/act_runner/releases" -VERSION_FILE="/opt/gitea-runner_version.txt" - -# --- Colors --- -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -# Logs go to stderr so callers can safely use $(fn) without capturing log noise. -log_info() { echo -e "${GREEN}[INFO]${NC} $1" >&2; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2; } -log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } - -require_root() { - if [[ "$(id -u)" -ne 0 ]]; then - log_error "This script must be run as root (current uid: $(id -u))." - log_error "On Proxmox, launch it from the host shell or via the Web UI shell, both of which run as root." - exit 1 - fi -} - -# ============================================================ -# Load shared helpers (lib/common.sh: detect_latest_alpine_template, -# enable_tty1_autologin, find_existing_lxc, refresh_os_packages). -# -# Same reasoning as openbao/install.sh: a local checkout has the file -# on disk right next to us, but the documented curl one-liner (host or -# piped into `pct exec` inside the LXC) has no BASH_SOURCE path worth -# trusting, so fall back to fetching lib/common.sh over HTTP next to -# SCRIPT_URL. The LXC already needs outbound network to curl this very -# script and to download the act_runner binary, so this adds no new -# failure mode. -# ============================================================ -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" 2>/dev/null && pwd || true)" -LIB_COMMON_URL="$(dirname "$(dirname "$SCRIPT_URL")")/lib/common.sh" -if [[ -n "$SCRIPT_DIR" && -f "${SCRIPT_DIR}/../lib/common.sh" ]]; then - source "${SCRIPT_DIR}/../lib/common.sh" -else - # shellcheck source=/dev/null - source <(curl -fsSL "$LIB_COMMON_URL") -fi - -# `source <(curl ...)` swallows curl failures: an empty stream still makes -# `source` return 0, so a 404/network error would otherwise only surface -# later as a confusing "command not found" for detect_latest_alpine_template -# et al. Fail loudly here instead, with the URL that was tried. -if ! declare -F detect_latest_alpine_template >/dev/null; then - log_error "Failed to load lib/common.sh (tried: ${LIB_COMMON_URL})." - exit 1 -fi - -# --- Helpers --- -get_latest_release() { - local release - release=$(curl -fsSL "$GITEA_API" | jq -r '.[0].tag_name') - if [[ -z "$release" || "$release" == "null" ]]; then - log_error "Failed to fetch latest release from Gitea API" - exit 1 - fi - echo "$release" -} - -get_arch() { - local arch - arch=$(uname -m) - case "$arch" in - x86_64) echo "amd64" ;; - aarch64) echo "arm64" ;; - armv7l) echo "armv7" ;; - *) log_error "Unsupported architecture: $arch"; exit 1 ;; - esac -} - -download_runner() { - local release="$1" - local version="${release#v}" - local arch - arch=$(get_arch) - local url="https://gitea.com/gitea/act_runner/releases/download/${release}/gitea-runner-${version}-linux-${arch}" - - log_info "Downloading act_runner ${release} (${arch})..." - curl -fsSL "$url" -o /usr/local/bin/act_runner - chmod +x /usr/local/bin/act_runner - ln -sf /usr/local/bin/act_runner /usr/bin/act_runner - echo "$release" > "$VERSION_FILE" -} - -# Inject the script into the container and execute it in the requested mode. -# Forwards the runtime configuration the inner invocation needs to reproduce -# what the user requested on the host (mirrors openbao/install.sh's helper -# of the same name). -exec_in_lxc() { - local ctid="$1" - local mode="$2" # --install or --update - - pct exec "$ctid" -- sh -c "apk add --no-cache bash curl jq ca-certificates > /dev/null 2>&1" - curl -fsSL "$SCRIPT_URL" \ - | pct exec "$ctid" -- env \ - SCRIPT_URL="$SCRIPT_URL" \ - GITEA_HOSTNAME="$GITEA_HOSTNAME" \ - bash -s -- "$mode" -} - -# ============================================================ -# MODE 1: Proxmox host — create LXC container -# ============================================================ -create_lxc() { - log_info "=== Gitea Act Runner — LXC Creation ===" - - if [[ -z "$TEMPLATE" ]]; then - TEMPLATE=$(detect_latest_alpine_template) - else - log_info "Using user-provided template: $TEMPLATE" - fi - - # Auto-select next CTID if not specified - if [[ -z "$CTID" ]]; then - CTID=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null \ - | jq '[.[].vmid] | max + 1' 2>/dev/null || echo "100") - log_info "Auto-selected CTID: $CTID" - fi - - ensure_template_present "$TEMPLATE" - - log_info "Creating LXC $CTID ($HOSTNAME_LXC)..." - pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" \ - --hostname "$HOSTNAME_LXC" \ - --cores "$CORES" \ - --memory "$RAM" \ - --rootfs "${STORAGE}:${DISK}" \ - --net0 "name=eth0,bridge=${BRIDGE},ip=dhcp" \ - --unprivileged 1 \ - --features nesting=1,keyctl=1 \ - --tags "infra-script,${LXC_TAG}" \ - --start 0 - - log_info "Configuring LXC for Docker and Tailscale..." - cat >> "/etc/pve/lxc/${CTID}.conf" </dev/null | awk '/inet /{print $2}' | cut -d/ -f1) - - echo "" - log_info "=========================================" - log_info "LXC $CTID created successfully!" - log_info "=========================================" - echo "" - echo " Hostname : $HOSTNAME_LXC" - echo " IP : ${ip:-pending}" - echo "" - echo "Next steps:" - echo " pct enter $CTID" - echo " cd /var/lib/gitea-runner" - echo " su -s /bin/bash gitea-runner -c 'act_runner register'" - echo " rc-service gitea-runner start" - echo "" -} - -# ============================================================ -# MODE 1b: Proxmox host — update an existing LXC -# ============================================================ -update_lxc() { - local ctid="$1" - log_info "=== Gitea Act Runner — updating existing LXC ${ctid} ===" - - if ! pct status "$ctid" | grep -q running; then - log_info "Starting LXC ${ctid}..." - pct start "$ctid" - sleep 3 - fi - - log_info "Refreshing Alpine packages inside LXC ${ctid}..." - pct exec "$ctid" -- sh -c "apk update >/dev/null && apk upgrade >/dev/null" - - log_info "Upgrading act_runner binary inside LXC ${ctid}..." - exec_in_lxc "$ctid" "--update" - - log_info "Update of LXC ${ctid} complete." -} - -# ============================================================ -# MODE 2: Inside LXC — fresh install -# ============================================================ -install_runner() { - log_info "=== Gitea Act Runner — Installation ===" - - log_info "Updating system..." - apk update > /dev/null && apk upgrade > /dev/null - - log_info "Installing dependencies..." - apk add --no-cache curl jq tar bash docker docker-cli-compose > /dev/null - - log_info "Installing Tailscale..." - apk add --no-cache tailscale > /dev/null - rc-update add tailscale default > /dev/null 2>&1 - rc-service tailscale start > /dev/null 2>&1 - - log_info "Starting Docker..." - rc-update add docker default > /dev/null 2>&1 - rc-service docker start > /dev/null 2>&1 - - local release - release=$(get_latest_release) - download_runner "$release" - log_info "act_runner $(act_runner --version 2>&1 || true)" - - log_info "Creating gitea-runner user..." - adduser -S -D -H -h /var/lib/gitea-runner -s /bin/bash -G docker gitea-runner 2>/dev/null || true - addgroup gitea-runner docker 2>/dev/null || true - mkdir -p /var/lib/gitea-runner - chown -R gitea-runner:docker /var/lib/gitea-runner - - log_info "Generating act_runner config with Prometheus metrics enabled..." - act_runner generate-config > /var/lib/gitea-runner/config.yaml - sed -i '/^metrics:/,/enabled:/{s/enabled: false/enabled: true/}' /var/lib/gitea-runner/config.yaml - chown gitea-runner:docker /var/lib/gitea-runner/config.yaml - chmod 640 /var/lib/gitea-runner/config.yaml - - log_info "Creating OpenRC service..." - cat <<'EOF' > /etc/init.d/gitea-runner -#!/sbin/openrc-run - -name="Gitea Act Runner" -description="Gitea Actions Runner Daemon" -command="/usr/local/bin/act_runner" -command_args="daemon --config /var/lib/gitea-runner/config.yaml" -command_user="gitea-runner:docker" -command_background=true -pidfile="/run/${RC_SVCNAME}.pid" -directory="/var/lib/gitea-runner" - -output_log="/var/log/gitea-runner.log" -error_log="/var/log/gitea-runner.log" - -depend() { - need net docker tailscale - after docker tailscale -} - -start_pre() { - export PATH="/usr/local/bin:$PATH" - checkpath --directory --owner gitea-runner:docker --mode 0755 /var/lib/gitea-runner - checkpath --file --owner gitea-runner:docker --mode 0644 /var/log/gitea-runner.log - - local timeout=30 - local elapsed=0 - ebegin "Waiting for Tailscale MagicDNS to resolve __GITEA_HOSTNAME__" - while ! getent hosts "__GITEA_HOSTNAME__" > /dev/null 2>&1; do - if [ "$elapsed" -ge "$timeout" ]; then - eend 1 - eerror "Timed out after ${timeout}s waiting for MagicDNS resolution of __GITEA_HOSTNAME__" - return 1 - fi - sleep 1 - elapsed=$(( elapsed + 1 )) - done - eend 0 -} -EOF - sed -i "s/__GITEA_HOSTNAME__/${GITEA_HOSTNAME}/g" /etc/init.d/gitea-runner - chmod +x /etc/init.d/gitea-runner - rc-update add gitea-runner default > /dev/null - - log_info "Configuring logrotate for gitea-runner..." - apk add --no-cache logrotate > /dev/null - - cat > /etc/logrotate.d/gitea-runner << 'LOGROTATE' -/var/log/gitea-runner.log { - daily - rotate 7 - compress - missingok - notifempty - copytruncate -} -LOGROTATE - - ln -sf /usr/sbin/logrotate /etc/periodic/daily/logrotate 2>/dev/null || true - - enable_tty1_autologin - - log_info "Cleaning up..." - rm -rf /var/cache/apk/* - - echo "" - log_info "=========================================" - log_info "Installation complete!" - log_info "=========================================" - echo "" - echo "Connect to Tailscale first:" - echo " tailscale up --ssh" - echo "" - echo "Register the runner:" - echo " cd /var/lib/gitea-runner" - echo " su -s /bin/bash gitea-runner -c 'act_runner register'" - echo " rc-service gitea-runner start" - echo "" -} - -# ============================================================ -# MODE 3: Inside LXC — update binary -# ============================================================ -update_runner() { - log_info "=== Gitea Act Runner — Update ===" - - refresh_os_packages - - local release - release=$(get_latest_release) - - if [[ -f "$VERSION_FILE" && "$release" == "$(cat "$VERSION_FILE")" ]]; then - log_info "Already at latest version: $release" - exit 0 - fi - - local current - current=$(cat "$VERSION_FILE" 2>/dev/null || echo "unknown") - log_info "Updating: $current → $release" - - log_info "Stopping service..." - rc-service gitea-runner stop 2>/dev/null || true - - log_info "Backing up current binary..." - cp /usr/local/bin/act_runner "/usr/local/bin/act_runner.bak.$(date +%s)" - - download_runner "$release" - - log_info "Starting service..." - rc-service gitea-runner start - - log_info "Updated to $release" -} - -# ============================================================ -# Main — detect context -# ============================================================ -main() { - case "${1:-}" in - --install) - install_runner - return - ;; - --update) - update_runner - return - ;; - esac - - if command -v pct &> /dev/null; then - # We're on the Proxmox host - require_root - local existing="" - if existing=$(find_existing_lxc); then - log_info "Found existing gitea-runner LXC (CTID ${existing}, hostname/tag match) — switching to update mode." - update_lxc "$existing" - else - create_lxc - fi - else - # Inside a container (no Proxmox tooling) - require_root - if [[ -f /usr/local/bin/act_runner ]]; then - # act_runner exists — update mode - update_runner - else - # Fresh LXC — install mode - install_runner - fi - fi -} - -main "$@" diff --git a/komodo/README.md b/komodo/README.md index 98c4df0..f86b21d 100644 --- a/komodo/README.md +++ b/komodo/README.md @@ -32,7 +32,7 @@ script can just run `apk add docker` and let dockerd own the kernel namespace. #### Install / update ```bash -bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/komodo/install.sh)" +bash -c "$(curl -fsSL https://raw.githubusercontent.com/darnodo/infra-scripts/main/komodo/install.sh)" ``` The script prints the generated `KOMODO_DATABASE_PASSWORD`, `KOMODO_WEBHOOK_SECRET`, and diff --git a/lib/common.sh b/lib/common.sh deleted file mode 100644 index 8caad69..0000000 --- a/lib/common.sh +++ /dev/null @@ -1,120 +0,0 @@ -# lib/common.sh - Shared helpers for Proxmox LXC creator scripts. -# -# Sourced (not executed) by openbao/install.sh and gitea-runner/install.sh. -# Assumes the sourcing script already defines log_info/log_warn/log_error -# (both scripts do, identically) — this file does not redefine them. -# -# Contract for future LXC creator scripts: -# - detect_latest_alpine_template(): Alpine only. A future Debian-based -# script needs its own detect_latest_debian_template() (same pattern: -# pveam available + sort -V + hardcoded fallback) — do not overload -# this function with an OS parameter. -# - enable_tty1_autologin(): implements the Alpine/OpenRC autologin -# mechanism (inittab + agetty). A future Debian-based script needs a -# distinct function (systemd container-getty override) rather than a -# branch inside this one. -# - find_existing_lxc(): OS-agnostic, works by tag/hostname via `pct -# config`. Reusable as-is by any LXC creator script. -# - refresh_os_packages(): Alpine only (apk update && apk upgrade). A -# future Debian-based script needs its own apt-get variant. -# -# Does not set shell options (set -e/-u/-o pipefail): a sourced file must -# not impose those on the caller's shell. Both openbao/install.sh and -# gitea-runner/install.sh already set them before sourcing this file. - -# ============================================================ -# #12 - Detect newest Alpine LXC template available from the Proxmox repos. -# Echoes the template filename. Falls back to a hardcoded known-good -# template if `pveam` is unavailable or returns nothing. -# ============================================================ -detect_latest_alpine_template() { - local tmpl - tmpl=$(pveam available --section system 2>/dev/null \ - | awk '/^system[[:space:]]+alpine-/ {print $2}' \ - | sort -V \ - | tail -n1) - - if [[ -z "$tmpl" ]]; then - log_warn "Could not query pveam; falling back to a known-good Alpine template." - tmpl="alpine-3.22-default_20250617_amd64.tar.xz" - fi - log_info "Selected Alpine template: $tmpl" - echo "$tmpl" -} - -# ============================================================ -# #14 - Enable root auto-login on tty1 for an Alpine/OpenRC LXC. -# Idempotent: safe to call on every install/update. -# ============================================================ -enable_tty1_autologin() { - log_info "Enabling console auto-login on tty1..." - # Alpine ships busybox getty by default; agetty (from util-linux) is what - # supports --autologin. - apk add --no-cache agetty >/dev/null 2>&1 || apk add --no-cache util-linux >/dev/null - - # Replace any existing tty1 entry, then append our autologin line. Doing it - # in two steps (delete + append) is more robust than an in-place sed against - # a pattern that may drift across Alpine releases. - sed -i '/^tty1::/d' /etc/inittab - echo 'tty1::respawn:/sbin/agetty --autologin root --noclear 38400 tty1' >> /etc/inittab - - # Tell PID 1 to re-read /etc/inittab so the change takes effect without a reboot. - kill -HUP 1 2>/dev/null || true - - # Kick any getty/agetty still attached to tty1 so init respawns it *now* with - # the new line — otherwise the first web-console session lands on the stale - # process and the operator has to type `exit` once before autologin kicks in. - pkill -KILL -f '(getty|agetty).*tty1' 2>/dev/null || true -} - -# ============================================================ -# #12 - Ensure the given template is downloaded to TEMPLATE_STORAGE, doing a -# `pveam update` first so a stale local cache doesn't silently settle for an -# older version than the one detect_latest_alpine_template() just picked. -# Expects TEMPLATE_STORAGE to be set by the caller. -# ============================================================ -ensure_template_present() { - local tmpl="$1" - if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$tmpl"; then - log_info "Downloading template ${tmpl} to storage ${TEMPLATE_STORAGE}..." - pveam update >/dev/null - pveam download "$TEMPLATE_STORAGE" "$tmpl" - else - log_info "Template ${tmpl} already present on ${TEMPLATE_STORAGE}." - fi -} - -# ============================================================ -# #15 - Find an existing LXC by tag or hostname (host-side, requires pct). -# Echoes the CTID on match, returns 1 if none found. -# -# Expects HOSTNAME_LXC and LXC_TAG to be set by the caller (as openbao's -# find_existing_lxc already does). -# ============================================================ -find_existing_lxc() { - local id host tags - while read -r id _; do - [[ -z "$id" || "$id" == "VMID" ]] && continue - host=$(pct config "$id" 2>/dev/null | awk -F': ' '/^hostname:/ {print $2}' || true) - tags=$(pct config "$id" 2>/dev/null | awk -F': ' '/^tags:/ {print $2}' || true) - if [[ "$host" == "$HOSTNAME_LXC" ]] || [[ ",${tags//;/,}," == *",${LXC_TAG},"* ]]; then - echo "$id" - return 0 - fi - done < <(pct list | awk 'NR>1 {print $1}') - return 1 -} - -# ============================================================ -# #15 - Refresh OS packages (Alpine: apk update && apk upgrade). -# Callable only from inside the LXC: this is a plain bash function in the -# current process, so it cannot run across a `pct exec ... sh -c` boundary -# without shipping its definition into the container. Host-side callers -# (see openbao/install.sh's update_lxc()) invoke apk update/upgrade inline -# via `pct exec` instead — do not try to dedupe that call site onto this -# function. -# ============================================================ -refresh_os_packages() { - log_info "Refreshing Alpine packages..." - apk update >/dev/null && apk upgrade >/dev/null -} diff --git a/netlab/README.md b/netlab/README.md index 192b28b..820374c 100644 --- a/netlab/README.md +++ b/netlab/README.md @@ -5,7 +5,7 @@ Deploys a network lab server with ContainerLab for network simulation and testin ## Quick Start ```bash -curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/netlab/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/darnodo/infra-scripts/main/netlab/install.sh | bash ``` ## Components @@ -27,7 +27,7 @@ curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/netlab/i Example: ```bash -NETLAB_HOSTNAME=clab01 SSH_PORT=22222 curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/netlab/install.sh | bash +NETLAB_HOSTNAME=clab01 SSH_PORT=22222 curl -fsSL https://raw.githubusercontent.com/darnodo/infra-scripts/main/netlab/install.sh | bash ``` ## Network Access diff --git a/openbao/README.md b/openbao/README.md deleted file mode 100644 index 06e27b0..0000000 --- a/openbao/README.md +++ /dev/null @@ -1,125 +0,0 @@ -# OpenBao - -Automated installation and update script for an [OpenBao](https://openbao.org) secrets-manager -server running inside an Alpine LXC on Proxmox. - -### Features - -Single script, automatic mode selection: - -| Context | Action | -| ---------------------------------------------------- | --------------------------------------------------------------------------------- | -| From Proxmox host, no existing OpenBao container | Detects newest Alpine template, creates LXC, installs `bao` + OpenRC service | -| From Proxmox host, OpenBao container already present | Reuses the existing LXC, refreshes packages, upgrades `bao` to the latest release | -| From inside an LXC, no `bao` binary | Installs OpenBao from scratch | -| From inside an LXC, `bao` already present | Updates the binary only (no config / data changes) | - -The container is identified by hostname **and** the `openbao` tag, so it is -re-found across reruns even if the CTID was auto-allocated the first time. - -### Requirements - -- Proxmox VE host with `pveam`, `pct`, `pvesh`, `jq` available -- Internet access from both the host (template download) and the LXC (binary download) -- Script must be run as **root** on the Proxmox host (enforced; the Web UI shell qualifies) - -### Usage - -#### Full install (from Proxmox shell) - -```bash -bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/openbao/install.sh)" -``` - -Re-running the exact same command later upgrades packages inside the LXC and -brings the `bao` binary to the latest release, without touching the config or -the raft data directory. - -#### Customisation - -Every parameter is exposed as an environment variable: - -| Variable | Default | Description | -| --------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| `CTID` | auto | Container ID (auto-allocated via `pvesh get /cluster/nextid`) | -| `OPENBAO_HOSTNAME` | `openbao` | LXC hostname (also used as raft `node_id`) | -| `TEMPLATE` | auto-detected | Alpine template; auto-detected from `pveam available` | -| `STORAGE` | `local-lvm` | Proxmox storage for the LXC root disk | -| `TEMPLATE_STORAGE` | `local` | Storage where Alpine templates live | -| `CORES` | `2` | vCPU cores | -| `RAM` | `1024` | RAM in MiB | -| `DISK` | `8` | Root disk size in GB | -| `BRIDGE` | `vmbr0` | Network bridge | -| `LXC_TAG` | `openbao` | Stable tag used to re-discover the container | -| `OPENBAO_VERSION` | `latest` | Pin a specific release (e.g. `v2.0.3`) or `latest` | -| `OPENBAO_LISTEN_ADDR` | `127.0.0.1:8200` | TCP listener address. Loopback by default — Tailscale fronts it. | -| `OPENBAO_API_ADDR` | `http://` | Public API URL (used for UI / OIDC redirects). Set to `https://..ts.net` once known. | -| `TS_AUTHKEY` | _(unset)_ | Pre-auth key (generate at ). If unset, finish `tailscale up` manually inside the LXC. | - -```bash -CTID=210 OPENBAO_HOSTNAME=vault CORES=4 RAM=2048 \ - bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/openbao/install.sh)" -``` - -#### First-time initialisation - -OpenBao starts sealed. Once the LXC is up: - -```bash -pct enter -export VAULT_ADDR=http://127.0.0.1:8200 -bao operator init # save the unseal keys + root token somewhere safe -bao operator unseal # repeat with each key share until unsealed -``` - -#### Tailscale reverse proxy - -The listener binds to `127.0.0.1:8200` only — Tailscale (running inside the -same LXC) acts as the reverse proxy and terminates TLS via tailnet -certificates. - -If `TS_AUTHKEY` was supplied at install time, the script runs -`tailscale up` and `tailscale serve --bg --https=443 http://127.0.0.1:8200` -automatically. OpenBao then becomes reachable at -`https://..ts.net`. - -Otherwise, finish setup manually inside the LXC: - -```bash -pct enter -tailscale up --ssh -tailscale serve --bg --https=443 http://127.0.0.1:8200 -tailscale status # prints the tailnet FQDN -``` - -Then point `OPENBAO_API_ADDR` at that FQDN and rerun the script so the UI / OIDC redirects use it: - -```bash -OPENBAO_API_ADDR=https://openbao..ts.net \ - bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/feat/lxc-OpenBao/openbao/install.sh)" -``` - -> HTTPS in `tailscale serve` requires HTTPS to be enabled on your tailnet -> (Admin console → DNS → HTTPS Certificates). - -#### Update (from inside the LXC) - -```bash -curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/feat/lxc-OpenBao/openbao/install.sh | bash -``` - -The script auto-detects the presence of `/usr/local/bin/bao` and switches to -update mode. The OpenRC service is stopped, the binary is swapped (the old one -is kept as `bao.bak.`), then the service is restarted. Re-running from the -Proxmox host does the same, plus refreshes the LXC's Alpine packages first -(`apk update && apk upgrade`). - -### Architecture - -- **OS**: latest Alpine LXC template (auto-detected), unprivileged, `nesting=1`, `/dev/net/tun` passthrough for Tailscale -- **Binary**: official `bao` release from `github.com/openbao/openbao`, installed in `/usr/local/bin` -- **Service**: OpenRC, runs as user `openbao`, logs to `/var/log/openbao.log` (rotated daily, 7 days retained) -- **Network**: listener bound to `127.0.0.1:8200`; **Tailscale** runs in the LXC and acts as the reverse proxy (`tailscale serve --https=443`) -- **Config**: `/etc/openbao/config.hcl` — raft storage, TLS disabled on the listener (Tailscale terminates TLS), `disable_mlock = true` for unprivileged LXC -- **Data**: `/var/lib/openbao/data` (raft) -- **Version tracking**: `/opt/openbao_version.txt` records the currently installed tag for idempotent reruns diff --git a/openbao/install.sh b/openbao/install.sh deleted file mode 100755 index bd6c495..0000000 --- a/openbao/install.sh +++ /dev/null @@ -1,586 +0,0 @@ -#!/bin/bash -# install.sh - OpenBao: LXC creation, installation & update -# Usage: -# From Proxmox host : bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/feat/lxc-OpenBao/openbao/install.sh)" -# From inside LXC : bash /root/install.sh (updates bao binary) -# -# Single entrypoint, three automatic modes: -# 1. Proxmox host, no existing container -> create LXC + install OpenBao -# 2. Proxmox host, container already present -> update packages + upgrade bao -# 3. Inside an LXC -> install bao if missing, otherwise update -# -# The OpenBao binary install/upgrade logic lives in a single reusable function -# (install_or_upgrade_bao) shared by both the create and update paths. - -set -euo pipefail - -# --- Config (override via environment) --- -CTID="${CTID:-}" -HOSTNAME_LXC="${OPENBAO_HOSTNAME:-openbao}" -TEMPLATE="${TEMPLATE:-}" # auto-detected when empty -STORAGE="${STORAGE:-local-lvm}" -TEMPLATE_STORAGE="${TEMPLATE_STORAGE:-local}" -CORES="${CORES:-2}" -RAM="${RAM:-1024}" -DISK="${DISK:-8}" -BRIDGE="${BRIDGE:-vmbr0}" -LXC_TAG="${LXC_TAG:-openbao}" # stable identifier for the container -OPENBAO_VERSION="${OPENBAO_VERSION:-latest}" # "latest" or e.g. "v2.0.3" -# GitHub releases endpoint for the openbao/openbao repo (used to resolve "latest"). -OPENBAO_RELEASES_URL="${OPENBAO_RELEASES_URL:-https://api.github.com/repos/openbao/openbao/releases}" -OPENBAO_LISTEN_ADDR="${OPENBAO_LISTEN_ADDR:-127.0.0.1:8200}" -# Public API address advertised to clients (also used for OIDC / UI redirects). -# Defaults to the local listener; override with the tailnet URL once known, -# e.g. OPENBAO_API_ADDR="https://openbao..ts.net". -OPENBAO_API_ADDR="${OPENBAO_API_ADDR:-http://${OPENBAO_LISTEN_ADDR}}" -# Optional: pre-authorise the LXC's Tailscale non-interactively. -# Generate at https://login.tailscale.com/admin/settings/keys -TS_AUTHKEY="${TS_AUTHKEY:-}" -# SCRIPT_URL is what the host-side flow pipes into the LXC. Override it when -# testing from a non-main branch, e.g. -# SCRIPT_URL="https://gitea.arnodo.fr/.../branch/feat/lxc-OpenBao/openbao/install.sh" -SCRIPT_URL="${SCRIPT_URL:-https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/openbao/install.sh}" -VERSION_FILE="${VERSION_FILE:-/opt/openbao_version.txt}" -BAO_USER="openbao" -BAO_CONFIG_DIR="/etc/openbao" -BAO_DATA_DIR="/var/lib/openbao" - -# --- Colors --- -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -# Logs go to stderr so callers can safely use $(fn) without capturing log noise. -log_info() { echo -e "${GREEN}[INFO]${NC} $1" >&2; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2; } -log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } - -# ============================================================ -# Load shared helpers (lib/common.sh: detect_latest_alpine_template, -# enable_tty1_autologin, find_existing_lxc, refresh_os_packages). -# -# This script runs in three different contexts, only one of which has a -# real file on disk next to it: -# - local checkout (`bash openbao/install.sh`) -> lib/common.sh -# sits right there at ../lib/common.sh, source it straight from disk. -# - Proxmox host, documented one-liner (`bash -c "$(curl ... )"`) -# -> no checkout, no BASH_SOURCE path worth trusting. -# - inside the LXC (exec_in_lxc does `curl ... | pct exec ... bash -s --`) -# -> same story, script arrives on stdin. -# For the latter two we fetch lib/common.sh over HTTP, next to SCRIPT_URL. -# The LXC already needs outbound network to curl this very script and to -# download the bao binary, so this adds no new failure mode. -# ============================================================ -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" 2>/dev/null && pwd || true)" -LIB_COMMON_URL="$(dirname "$(dirname "$SCRIPT_URL")")/lib/common.sh" -if [[ -n "$SCRIPT_DIR" && -f "${SCRIPT_DIR}/../lib/common.sh" ]]; then - source "${SCRIPT_DIR}/../lib/common.sh" -else - source <(curl -fsSL "$LIB_COMMON_URL") -fi - -# `source <(curl ...)` swallows curl failures: an empty stream still makes -# `source` return 0, so a 404/network error would otherwise only surface -# later as a confusing "command not found" for detect_latest_alpine_template -# et al. Fail loudly here instead, with the URL that was tried. -if ! declare -F detect_latest_alpine_template >/dev/null; then - log_error "Failed to load lib/common.sh (tried: ${LIB_COMMON_URL})." - exit 1 -fi - -# ============================================================ -# Generic helpers -# ============================================================ -require_root() { - if [[ "$(id -u)" -ne 0 ]]; then - log_error "This script must be run as root (current uid: $(id -u))." - log_error "On Proxmox, launch it from the host shell or via the Web UI shell, both of which run as root." - exit 1 - fi -} - -# OpenBao publishes release assets named with the raw `uname -m` arch -# (e.g. x86_64, aarch64), not Go-style amd64/arm64. -get_arch() { - case "$(uname -m)" in - x86_64) echo "x86_64" ;; - aarch64) echo "aarch64" ;; - *) log_error "Unsupported architecture: $(uname -m)"; exit 1 ;; - esac -} - -# Resolve "latest" -> concrete tag name, otherwise echo input unchanged. -resolve_openbao_version() { - local requested="$1" - if [[ "$requested" != "latest" ]]; then - echo "$requested" - return 0 - fi - local tag - tag=$(curl -fsSL "${OPENBAO_RELEASES_URL}/latest" | jq -r '.tag_name') - if [[ -z "$tag" || "$tag" == "null" ]]; then - log_error "Failed to resolve latest OpenBao release from GitHub API." - exit 1 - fi - echo "$tag" -} - -# ============================================================ -# Reusable: install or upgrade the bao binary in-place. -# Used by both fresh-install and update flows. -# Returns 0 on success, exits on hard error. -# ============================================================ -install_or_upgrade_bao() { - local tag arch version url tmpdir current - tag=$(resolve_openbao_version "$OPENBAO_VERSION") - arch=$(get_arch) - version="${tag#v}" - - current="" - if [[ -f "$VERSION_FILE" ]]; then - current=$(cat "$VERSION_FILE") - fi - - if [[ "$current" == "$tag" && -x /usr/local/bin/bao ]]; then - log_info "OpenBao already at $tag, nothing to do." - return 0 - fi - - # Asset naming convention: bao__Linux_.tar.gz - url="https://github.com/openbao/openbao/releases/download/${tag}/bao_${version}_Linux_${arch}.tar.gz" - log_info "Downloading OpenBao ${tag} (${arch}) from ${url}..." - - tmpdir=$(mktemp -d) - curl -fsSL "$url" -o "${tmpdir}/bao.tar.gz" - tar -xzf "${tmpdir}/bao.tar.gz" -C "$tmpdir" - - if [[ ! -f "${tmpdir}/bao" ]]; then - log_error "Archive did not contain expected 'bao' binary." - rm -rf "$tmpdir" - exit 1 - fi - - # Stop service if running, swap binary atomically, then restart. - local service_was_running=0 - if command -v rc-service >/dev/null 2>&1 && rc-service openbao status >/dev/null 2>&1; then - service_was_running=1 - log_info "Stopping openbao service for upgrade..." - rc-service openbao stop || true - fi - - if [[ -x /usr/local/bin/bao ]]; then - cp /usr/local/bin/bao "/usr/local/bin/bao.bak.$(date +%s)" - fi - install -m 0755 "${tmpdir}/bao" /usr/local/bin/bao - ln -sf /usr/local/bin/bao /usr/bin/bao - echo "$tag" > "$VERSION_FILE" - - log_info "Installed: $(/usr/local/bin/bao --version 2>&1 | head -n1 || true)" - - if [[ "$service_was_running" -eq 1 ]]; then - log_info "Restarting openbao service..." - rc-service openbao start - fi - - rm -rf "$tmpdir" -} - -# ============================================================ -# Reusable: bring Tailscale up and publish OpenBao on the tailnet. -# Idempotent: re-running is a no-op once Tailscale is logged in and the -# serve mapping is already in place. -# ============================================================ -configure_tailscale_proxy() { - if ! command -v tailscale >/dev/null 2>&1; then - log_warn "tailscale CLI not found, skipping reverse-proxy setup." - return 0 - fi - - # 1. Authenticate the node (if it isn't already). - local backend_state - backend_state=$(tailscale status --json 2>/dev/null | jq -r '.BackendState // "unknown"') - if [[ "$backend_state" != "Running" ]]; then - if [[ -n "$TS_AUTHKEY" ]]; then - log_info "Bringing Tailscale up with provided auth key..." - tailscale up --authkey "$TS_AUTHKEY" --ssh --hostname "$HOSTNAME_LXC" \ - || log_warn "tailscale up failed — run it manually inside the LXC." - else - log_warn "Tailscale not authenticated and TS_AUTHKEY was not supplied." - log_warn "Finish setup inside the LXC with: tailscale up --ssh" - log_warn "Then publish OpenBao with: tailscale serve --bg --https=443 http://${OPENBAO_LISTEN_ADDR}" - return 0 - fi - fi - - # 2. Publish the local OpenBao listener on the tailnet (auto-HTTPS). - if tailscale serve status 2>/dev/null | grep -q "${OPENBAO_LISTEN_ADDR}"; then - log_info "Tailscale serve already publishes http://${OPENBAO_LISTEN_ADDR}." - else - log_info "Publishing OpenBao on the tailnet via 'tailscale serve' (HTTPS:443)..." - tailscale serve --bg --https=443 "http://${OPENBAO_LISTEN_ADDR}" \ - || log_warn "tailscale serve failed — enable HTTPS on your tailnet and retry." - fi - - local fqdn - fqdn=$(tailscale status --json 2>/dev/null | jq -r '.Self.DNSName // ""' | sed 's/\.$//') - if [[ -n "$fqdn" ]]; then - log_info "OpenBao should now be reachable at: https://${fqdn}" - if [[ "$OPENBAO_API_ADDR" != "https://${fqdn}" ]]; then - log_warn "OPENBAO_API_ADDR is '${OPENBAO_API_ADDR}'." - log_warn "For OIDC / UI redirects, set it to 'https://${fqdn}' and re-run, or edit ${BAO_CONFIG_DIR}/config.hcl." - fi - fi -} - -# ============================================================ -# Proxmox-host helpers -# ============================================================ - -# Pick next available CTID if user did not provide one. -allocate_ctid() { - pvesh get /cluster/nextid 2>/dev/null \ - || pvesh get /cluster/resources --type vm --output-format json 2>/dev/null \ - | jq '[.[].vmid] | max + 1' \ - || echo 100 -} - -# Inject the script into the container and execute it in the requested mode. -# Forwards the relevant runtime configuration through the environment so the -# inner invocation produces the same config the user requested on the host. -exec_in_lxc() { - local ctid="$1" - local mode="$2" # --install or --update - - # Ensure base tooling exists inside the container before piping the script. - pct exec "$ctid" -- sh -c "apk add --no-cache bash curl jq ca-certificates >/dev/null 2>&1" - curl -fsSL "$SCRIPT_URL" \ - | pct exec "$ctid" -- env \ - SCRIPT_URL="$SCRIPT_URL" \ - OPENBAO_VERSION="$OPENBAO_VERSION" \ - OPENBAO_HOSTNAME="$HOSTNAME_LXC" \ - OPENBAO_LISTEN_ADDR="$OPENBAO_LISTEN_ADDR" \ - OPENBAO_API_ADDR="$OPENBAO_API_ADDR" \ - TS_AUTHKEY="$TS_AUTHKEY" \ - bash -s -- "$mode" -} - -# ============================================================ -# MODE: Proxmox host — create LXC + install -# ============================================================ -create_lxc() { - log_info "=== OpenBao — LXC creation ===" - - if [[ -z "$TEMPLATE" ]]; then - TEMPLATE=$(detect_latest_alpine_template) - else - log_info "Using user-provided template: $TEMPLATE" - fi - ensure_template_present "$TEMPLATE" - - if [[ -z "$CTID" ]]; then - CTID=$(allocate_ctid) - log_info "Auto-selected CTID: $CTID" - fi - - log_info "Creating LXC ${CTID} (${HOSTNAME_LXC})..." - pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" \ - --hostname "$HOSTNAME_LXC" \ - --cores "$CORES" \ - --memory "$RAM" \ - --rootfs "${STORAGE}:${DISK}" \ - --net0 "name=eth0,bridge=${BRIDGE},ip=dhcp" \ - --unprivileged 1 \ - --features "nesting=1" \ - --tags "infra-script,${LXC_TAG}" \ - --onboot 1 \ - --start 0 - - # Tailscale needs /dev/net/tun inside the unprivileged container. - log_info "Adding /dev/net/tun passthrough for Tailscale..." - cat >> "/etc/pve/lxc/${CTID}.conf" </dev/null; do - tries=$((tries + 1)) - if (( tries > 20 )); then - log_error "LXC ${CTID} did not acquire an IP after 20s." - exit 1 - fi - sleep 1 - done - - log_info "Running installer inside LXC ${CTID}..." - exec_in_lxc "$CTID" "--install" - - local ip - ip=$(pct exec "$CTID" -- ip -4 addr show eth0 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1 || true) - - echo "" - log_info "=========================================" - log_info "LXC ${CTID} created successfully!" - log_info "=========================================" - echo "" - echo " Hostname : ${HOSTNAME_LXC}" - echo " IP : ${ip:-pending}" - echo " API : http://${ip:-}:8200" - echo "" - echo "Next steps:" - echo " pct enter ${CTID}" - echo " export VAULT_ADDR=http://127.0.0.1:8200" - echo " bao operator init # initialise & retrieve unseal keys + root token" - echo " bao operator unseal # repeat with the unseal keys" - echo "" -} - -# ============================================================ -# MODE: Proxmox host — update existing LXC -# ============================================================ -update_lxc() { - local ctid="$1" - log_info "=== OpenBao — updating existing LXC ${ctid} ===" - - if ! pct status "$ctid" | grep -q running; then - log_info "Starting LXC ${ctid}..." - pct start "$ctid" - sleep 3 - fi - - log_info "Refreshing Alpine packages inside LXC ${ctid}..." - # refresh_os_packages() is a bash function local to this process; it can't - # run over `pct exec ... sh -c` without shipping the function definition - # into the container, so this call site stays inline rather than dedupe. - pct exec "$ctid" -- sh -c "apk update >/dev/null && apk upgrade >/dev/null" - - log_info "Upgrading bao binary inside LXC ${ctid}..." - exec_in_lxc "$ctid" "--update" - - log_info "Update of LXC ${ctid} complete." -} - -# ============================================================ -# MODE: inside LXC — fresh install of OpenBao -# ============================================================ -install_inside_lxc() { - log_info "=== OpenBao — installation ===" - - log_info "Updating package index..." - apk update >/dev/null - apk upgrade >/dev/null - - log_info "Installing dependencies..." - apk add --no-cache bash curl jq ca-certificates gcompat openrc logrotate tailscale >/dev/null - - log_info "Enabling tailscaled..." - rc-update add tailscale default >/dev/null 2>&1 || true - rc-service tailscale start >/dev/null 2>&1 || log_warn "tailscaled failed to start (is /dev/net/tun mapped into the LXC?)" - - install_or_upgrade_bao - - log_info "Creating ${BAO_USER} system user..." - if ! id "$BAO_USER" >/dev/null 2>&1; then - addgroup -S "$BAO_USER" 2>/dev/null || true - adduser -S -D -H -h "$BAO_DATA_DIR" -s /sbin/nologin -G "$BAO_USER" "$BAO_USER" - fi - - log_info "Provisioning directories..." - mkdir -p "$BAO_CONFIG_DIR" "$BAO_DATA_DIR/data" - chown -R "${BAO_USER}:${BAO_USER}" "$BAO_DATA_DIR" - chmod 750 "$BAO_DATA_DIR" - - if [[ ! -f "${BAO_CONFIG_DIR}/config.hcl" ]]; then - log_info "Writing default ${BAO_CONFIG_DIR}/config.hcl..." - # OpenBao listens on loopback only; Tailscale (running in the same LXC) - # acts as the reverse proxy and terminates TLS via tailnet certificates. - # https://openbao.org/docs/configuration/ - cat > "${BAO_CONFIG_DIR}/config.hcl" < /etc/init.d/openbao <<'EOF' -#!/sbin/openrc-run - -name="OpenBao" -description="OpenBao secrets manager" -command="/usr/local/bin/bao" -command_args="server -config=/etc/openbao/config.hcl" -command_user="openbao:openbao" -command_background=true -pidfile="/run/${RC_SVCNAME}.pid" -directory="/var/lib/openbao" - -output_log="/var/log/openbao.log" -error_log="/var/log/openbao.log" - -depend() { - need net - after net -} - -start_pre() { - checkpath --directory --owner openbao:openbao --mode 0750 /var/lib/openbao - checkpath --directory --owner openbao:openbao --mode 0750 /var/lib/openbao/data - checkpath --file --owner openbao:openbao --mode 0644 /var/log/openbao.log -} -EOF - chmod +x /etc/init.d/openbao - rc-update add openbao default >/dev/null - - cat > /etc/logrotate.d/openbao <<'EOF' -/var/log/openbao.log { - daily - rotate 7 - compress - missingok - notifempty - copytruncate -} -EOF - ln -sf /usr/sbin/logrotate /etc/periodic/daily/logrotate 2>/dev/null || true - - log_info "Starting openbao service..." - rc-service openbao start || log_warn "openbao failed to start — inspect /var/log/openbao.log" - - enable_tty1_autologin - - configure_tailscale_proxy - - log_info "Configuring MOTD..." - # /etc/profile.d/ runs for every interactive login shell — works for both - # the auto-login tty and Tailscale SSH. Quoted heredoc: every variable is - # resolved at login time, not at install time. - cat > /etc/profile.d/00-openbao.sh <<'MOTD' -TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' ' - /"Self"/ { in_self=1 } - in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit } -') -[[ -z "$TS_FQDN" ]] && TS_FQDN="$(hostname).ts.net" - -BAO_VERSION=$(cat /opt/openbao_version.txt 2>/dev/null || echo "unknown") - -# `bao status` exit codes: 0 = unsealed, 2 = sealed, anything else = error. -VAULT_ADDR=http://127.0.0.1:8200 /usr/local/bin/bao status >/dev/null 2>&1 -case $? in - 0) SEAL_STATE="unsealed" ;; - 2) SEAL_STATE="SEALED (run: bao operator unseal)" ;; - *) SEAL_STATE="unreachable" ;; -esac - -echo "" -echo " ___ ____ " -echo " / _ \ _ __ ___ _ __ | __ ) __ _ ___ " -echo "| | | | '_ \ / _ \ '_ \| _ \ / _\` |/ _ \\" -echo "| |_| | |_) | __/ | | | |_) | (_| | (_) |" -echo " \___/| .__/ \___|_| |_|____/ \__,_|\___/" -echo " |_| " -echo "" -echo "OpenBao Secrets Manager (${BAO_VERSION})" -echo "─────────────────────────────────────────" -echo "Access:" -echo " • API (local) : http://127.0.0.1:8200" -echo " • Tailnet : https://${TS_FQDN}" -echo " • Seal status : ${SEAL_STATE}" -echo "" -echo "Useful commands:" -echo " export VAULT_ADDR=http://127.0.0.1:8200" -echo " bao status" -echo " bao operator init # first-time only" -echo " bao operator unseal # after every restart" -echo " rc-service openbao status" -echo " tail -f /var/log/openbao.log" -echo "─────────────────────────────────────────" -echo "" -MOTD - chmod +x /etc/profile.d/00-openbao.sh - - log_info "Cleaning up..." - rm -rf /var/cache/apk/* - - echo "" - log_info "=========================================" - log_info "OpenBao installation complete!" - log_info "=========================================" - echo "" - echo "Initialise the server with:" - echo " export VAULT_ADDR=http://127.0.0.1:8200" - echo " bao operator init" - echo " bao operator unseal # repeat with each unseal key share" - echo "" -} - -# ============================================================ -# MODE: inside LXC — update only -# ============================================================ -update_inside_lxc() { - log_info "=== OpenBao — update ===" - refresh_os_packages - install_or_upgrade_bao - configure_tailscale_proxy - log_info "Update complete." -} - -# ============================================================ -# Main — dispatch on explicit mode flag or auto-detect context -# ============================================================ -main() { - case "${1:-}" in - --install) - install_inside_lxc - return - ;; - --update) - update_inside_lxc - return - ;; - esac - - if command -v pct >/dev/null 2>&1; then - # Running on a Proxmox host - require_root - - local existing="" - if existing=$(find_existing_lxc); then - log_info "Found existing OpenBao LXC (CTID ${existing}, hostname/tag match) — switching to update mode." - update_lxc "$existing" - else - create_lxc - fi - else - # Inside a container (no Proxmox tooling) - require_root - if [[ -x /usr/local/bin/bao ]]; then - update_inside_lxc - else - install_inside_lxc - fi - fi -} - -main "$@" diff --git a/proxy/README.md b/proxy/README.md deleted file mode 100644 index 946c749..0000000 --- a/proxy/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Proxy Server - -Deploys a secure reverse proxy with Tailscale + Nginx Proxy Manager. - -## Quick Start - -```bash -curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/proxy/install.sh | bash -``` - -## Components - -- **Tailscale**: Private network access (SSH, admin panel) -- **Nginx Proxy Manager**: Public reverse proxy (HTTP/HTTPS) -- **UFW**: Firewall (only 80/443 exposed publicly) -- **fail2ban** + **unattended-upgrades**: Basic hardening - -## Environment Variables - -| Variable | Default | Description | -|----------|---------|-------------| -| `PROXY_HOSTNAME` | `proxy` | Server hostname | -| `TZ` | `Europe/Paris` | Timezone | - -Example: - -```bash -PROXY_HOSTNAME=myproxy TZ=America/New_York curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/proxy/install.sh | bash -``` - -## What it does - -1. Sets hostname -2. Installs base packages (vim, fail2ban, unattended-upgrades, at) -3. Installs and connects Tailscale (will prompt for authentication) -4. Configures sysctl for exit-node capability -5. Installs Docker -6. Configures UFW (80/443 public, everything else via Tailscale only) -7. Deploys Nginx Proxy Manager -8. Exposes NPM admin panel via Tailscale serve -9. Temporarily opens SSH port 22 for 5 minutes (safety net) - -## SSH Safety Net - -During installation, SSH port 22 is temporarily opened for 5 minutes to prevent lockout if you're connected via public IP. After 5 minutes, it will be automatically closed and only Tailscale SSH will work. - -```bash -# List scheduled jobs -sudo atq - -# Cancel the scheduled SSH closure (replace N with job number) -sudo atrm N - -# Manually close SSH port 22 if needed -sudo ufw delete allow 22/tcp -``` - -## Post-install - -- Access NPM admin: `https://proxy..ts.net` -- Default credentials: `admin@example.com` / `changeme` -- Optionally approve exit-node in Tailscale admin console diff --git a/proxy/install.sh b/proxy/install.sh deleted file mode 100644 index cfca993..0000000 --- a/proxy/install.sh +++ /dev/null @@ -1,360 +0,0 @@ -#!/bin/bash -# install.sh - Automated deployment of Proxy Server with Tailscale + Traefik v3 + Fail2ban -# Usage: curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/proxy/install.sh | bash - -set -euo pipefail - -# Colors for logging -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -# Pre-flight checks -check_root() { - if [[ $EUID -eq 0 ]]; then - log_error "Do not run as root directly. Use a user with sudo privileges." - exit 1 - fi - if ! sudo -v; then - log_error "User must have sudo privileges." - exit 1 - fi -} - -check_debian() { - if ! grep -qi debian /etc/os-release 2>/dev/null; then - log_warn "This script is optimized for Debian. Continuing anyway..." - fi -} - -# Configuration variables (can be overridden via environment) -PROXY_HOSTNAME="${PROXY_HOSTNAME:-proxy}" -TRAEFIK_DIR="$HOME/traefik" - -# ACME_EMAIL is required for Let's Encrypt certificate issuance notifications. -# Export it before running: export ACME_EMAIL=you@example.com -ACME_EMAIL="${ACME_EMAIL:-}" - -# Optional: pre-authorize Tailscale non-interactively (recommended for curl|bash). -# Generate at https://login.tailscale.com/admin/settings/keys -TS_AUTHKEY="${TS_AUTHKEY:-}" - -main() { - log_info "=== Proxy Server Deployment (Traefik v3) ===" - - check_root - check_debian - - # Prompt for ACME email if not set. Only attempt interactive prompt when a - # TTY is available — when invoked via `curl … | bash`, stdin is the pipe - # and reading from /dev/tty may also fail (e.g. non-interactive runners). - if [[ -z "$ACME_EMAIL" ]]; then - if [[ -r /dev/tty ]]; then - log_warn "ACME_EMAIL is not set in the environment." - read -rp "Enter your ACME email address: " ACME_EMAIL < /dev/tty || true - fi - if [[ -z "$ACME_EMAIL" ]]; then - log_error "ACME_EMAIL is required. Export it before running:" - log_error " export ACME_EMAIL=you@example.com" - exit 1 - fi - fi - - if [[ "$(hostname)" != "$PROXY_HOSTNAME" ]]; then - log_info "Setting hostname to: $PROXY_HOSTNAME" - echo "$PROXY_HOSTNAME" | sudo tee /etc/hostname > /dev/null - sudo hostnamectl set-hostname "$PROXY_HOSTNAME" - else - log_info "Hostname already set to $PROXY_HOSTNAME, skipping." - fi - - log_info "Installing base packages..." - sudo apt update -qq - sudo apt install -y -qq vim ca-certificates curl gnupg lsb-release fail2ban unattended-upgrades ufw ethtool networkd-dispatcher > /dev/null - - log_info "Installing Tailscale..." - curl -fsSL https://tailscale.com/install.sh | sh - - log_info "Configuring sysctl for exit-node support..." - echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-tailscale.conf > /dev/null - echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf > /dev/null - sudo sysctl -p /etc/sysctl.d/99-tailscale.conf > /dev/null - - log_info "Configuring ethtool for Tailscale UDP GRO forwarding..." - # Determine the default-route interface and disable rx-gro-list / enable - # rx-udp-gro-forwarding to avoid the Tailscale throughput warning. - NETDEV=$(ip -o route show default | awk '{print $5; exit}') - if [[ -z "$NETDEV" ]]; then - log_error "Could not determine default network interface." - exit 1 - fi - sudo ethtool -K "$NETDEV" rx-udp-gro-forwarding on rx-gro-list off - # Persist across reboots via networkd-dispatcher - sudo mkdir -p /etc/networkd-dispatcher/routable.d - printf '#!/bin/sh\nethtool -K %s rx-udp-gro-forwarding on rx-gro-list off\n' "$NETDEV" \ - | sudo tee /etc/networkd-dispatcher/routable.d/50-tailscale > /dev/null - sudo chmod 755 /etc/networkd-dispatcher/routable.d/50-tailscale - - # Connect to Tailscale only if not already logged in (idempotent re-runs). - if ! sudo tailscale status >/dev/null 2>&1; then - log_info "Connecting to Tailscale..." - if [[ -n "$TS_AUTHKEY" ]]; then - sudo tailscale up --ssh --advertise-exit-node --authkey="$TS_AUTHKEY" - else - log_warn "TS_AUTHKEY not set — interactive browser auth required." - sudo tailscale up --ssh --advertise-exit-node - fi - else - log_info "Tailscale already connected, skipping." - fi - - log_info "Installing Docker..." - sudo mkdir -m 0755 -p /etc/apt/keyrings - curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - sudo apt update -qq - sudo apt install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin > /dev/null - - log_info "Adding current user to docker group..." - sudo usermod -aG docker "$USER" - - log_info "Configuring UFW firewall..." - # Idempotent: only reset if our marker rules are absent. This preserves any - # rules added later by the operator on re-runs. - if ! sudo ufw status | grep -q "tailscale0"; then - sudo ufw --force reset > /dev/null - sudo ufw default deny incoming > /dev/null - sudo ufw default allow outgoing > /dev/null - # Allow HTTP/HTTPS from the public internet (handled by Traefik) - sudo ufw allow 80/tcp > /dev/null - sudo ufw allow 443/tcp > /dev/null - # Allow all traffic on Tailscale interface (dashboard, metrics, SSH, admin — everything internal) - sudo ufw allow in on tailscale0 > /dev/null - # Port 22 is intentionally NOT opened publicly; Tailscale SSH covers management access - sudo ufw --force enable > /dev/null - else - log_info "UFW already configured, skipping reset." - fi - - log_info "Configuring Fail2ban for Traefik..." - # Create the log file and fail2ban socket dir now so: - # - fail2ban can open the log file when the jail loads. - # - docker doesn't bind-mount /var/run/fail2ban as an empty dir if the - # exporter container starts before fail2ban writes its socket. - sudo mkdir -p /var/log/traefik /var/run/fail2ban - sudo chown "$USER":"$USER" /var/log/traefik - sudo touch /var/log/traefik/access.log - - sudo tee /etc/fail2ban/filter.d/traefik.conf > /dev/null << 'EOF' -[Definition] -# Match JSON log lines where ClientHost is the offending IP and DownstreamStatus -# is an auth/abuse status (401, 403, 429) or a server error (5xx). -# Legitimate 404s on missing assets are excluded so dev traffic doesn't ban users. -# Two patterns cover both possible field orderings in the JSON. -failregex = ^.*"ClientHost":"".*"DownstreamStatus":(401|403|429|5[0-9]{2}) - ^.*"DownstreamStatus":(401|403|429|5[0-9]{2}).*"ClientHost":"" -ignoreregex = -EOF - - sudo tee /etc/fail2ban/jail.d/traefik.conf > /dev/null << 'EOF' -[traefik-auth] -enabled = true -filter = traefik -logpath = /var/log/traefik/access.log -maxretry = 10 -findtime = 5m -bantime = 1h -action = iptables-multiport[name=traefik, port="80,443", protocol=tcp] -EOF - - sudo systemctl restart fail2ban - - log_info "Creating Traefik stack under $TRAEFIK_DIR..." - mkdir -p "$TRAEFIK_DIR/conf.d" - - # acme.json must be 600 or Traefik refuses to use it - touch "$TRAEFIK_DIR/acme.json" - chmod 600 "$TRAEFIK_DIR/acme.json" - - # --- docker-compose.yml --- - # SECURITY: The dashboard/API entrypoint is bound to 127.0.0.1 ONLY. - # Combined with `api.insecure: true` in traefik.yml, the dashboard has no - # authentication — it is only reachable via the host loopback and exposed - # selectively over the tailnet through `tailscale serve`. DO NOT change - # this port binding to 0.0.0.0 or any non-loopback address. - cat > "$TRAEFIK_DIR/docker-compose.yml" << 'EOF' -services: - traefik: - image: traefik:v3 - container_name: traefik - restart: unless-stopped - dns: - - 100.100.100.100 - ports: - - "80:80" - - "443:443" - # MUST stay on 127.0.0.1: dashboard is unauthenticated (see traefik.yml). - - "127.0.0.1:8080:8080" - volumes: - - ./traefik.yml:/etc/traefik/traefik.yml:ro - - ./conf.d:/etc/traefik/conf.d:ro - - ./acme.json:/acme.json - - /var/log/traefik:/var/log/traefik - - /etc/localtime:/etc/localtime:ro - - fail2ban-exporter: - image: registry.gitlab.com/hctrdev/fail2ban-prometheus-exporter:latest - container_name: fail2ban-exporter - restart: unless-stopped - user: root - ports: - # Metrics reachable only via Tailscale (127.0.0.1 binding + UFW blocks public access) - - "127.0.0.1:9191:9191" - volumes: - # Mount the directory, not the socket file: avoids Docker creating a directory - # at the path when fail2ban is briefly down and recreating its socket. - - /var/run/fail2ban:/var/run/fail2ban -EOF - - # --- traefik.yml (static config) --- - # Unquoted EOF: ${ACME_EMAIL} must expand at write time into the static config. - cat > "$TRAEFIK_DIR/traefik.yml" << EOF -entryPoints: - web: - address: ":80" - http: - redirections: - entryPoint: - to: websecure - scheme: https - - websecure: - address: ":443" - - traefik: - address: ":8080" - -certificatesResolvers: - letsencrypt: - acme: - email: "${ACME_EMAIL}" - storage: /acme.json - httpChallenge: - entryPoint: web - -providers: - file: - directory: /etc/traefik/conf.d - watch: true - -metrics: - prometheus: - addEntryPointsLabels: true - addServicesLabels: true - addRoutersLabels: true - entryPoint: traefik - -api: - dashboard: true - # insecure exposes the dashboard on the :8080 entrypoint without auth. - # This is acceptable ONLY because docker-compose.yml binds 8080 to 127.0.0.1. - # Public reach requires going through `tailscale serve` (tailnet-authenticated). - insecure: true - -accessLog: - filePath: /var/log/traefik/access.log - format: json -EOF - - # --- conf.d/gitea.yml (dynamic config) --- - cat > "$TRAEFIK_DIR/conf.d/gitea.yml" << 'EOF' -http: - routers: - gitea: - rule: "Host(`gitea.arnodo.fr`)" - entryPoints: - - websecure - service: gitea - tls: - certResolver: letsencrypt - - services: - gitea: - loadBalancer: - servers: - - url: "http://gitea.taila5ad8.ts.net:3000" -EOF - - log_info "Starting Traefik stack..." - # Use sg to apply the docker group without requiring a re-login. - # cd into the dir so paths inside the command don't break on spaces in $HOME. - (cd "$TRAEFIK_DIR" && sg docker -c "docker compose up -d") - - # Idempotent: only register the serve mapping if it isn't already present. - # Use --json (stable contract) and capture stdout+stderr so any help/error - # output on older tailscale builds doesn't leak to the user's terminal. - if ! sudo tailscale serve status --json 2>&1 | grep -q '"127.0.0.1:8080"'; then - log_info "Exposing Traefik dashboard via Tailscale serve..." - sudo tailscale serve --bg http://localhost:8080 - else - log_info "Tailscale serve already configured for dashboard, skipping." - fi - - log_info "Configuring MOTD..." - # /etc/profile.d/ runs for every interactive login shell regardless of the SSH - # implementation (works for both Tailscale SSH and regular OpenSSH). - cat << 'MOTD' | sudo tee /etc/profile.d/00-proxy.sh > /dev/null -TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' ' - /"Self"/ { in_self=1 } - in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit } -') -[[ -z "$TS_FQDN" ]] && TS_FQDN="$(hostname).ts.net" - -echo "" -echo " ____ ____ _____ ____ __" -echo "| _ \| _ \ / _ \ \/ /\ \ / /" -echo "| |_) | |_) | | | \ / \ V /" -echo "| __/| _ <| |_| / \ | |" -echo "|_| |_| \_\\___/_/\_\ |_|" -echo "" -echo "Traefik v3 Reverse Proxy" -echo "─────────────────────────────────────────" -echo "Access:" -echo " • Dashboard : https://${TS_FQDN} (Tailscale)" -echo " • HTTP/HTTPS: Public ports 80/443" -echo "" -echo "Services:" -docker ps --format ' • {{.Names}} : {{.Status}}' 2>/dev/null || echo " Docker not running" -echo "" -echo "Useful commands:" -echo " cd ~/traefik && docker compose logs -f" -echo " sudo tailscale serve status" -echo "─────────────────────────────────────────" -echo "" -MOTD - - TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' ' - /"Self"/ { in_self=1 } - in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit } - ' || echo "${PROXY_HOSTNAME}.ts.net") - - echo "" - log_info "==========================================" - log_info "Deployment complete!" - log_info "==========================================" - echo "" - echo "Traefik dashboard : https://${TS_FQDN}" - echo "Stack directory : $TRAEFIK_DIR" - echo "" - echo "Note: Approve exit-node in Tailscale admin console if needed." - echo "Note: Fail2ban is running on the host; fail2ban-exporter exposes" - echo " metrics on port 9191 (Tailscale-only, not public)." - echo "" -} - -main "$@"