feat: unified launcher, multi-OS hardening, login alerts & auto-updates
Restructure around a single entry point (automations.sh) with a Gum wizard and a self-extracting bundle for repo-less installs. Add scripts/oslib.sh so the provisioning scripts (setup-host, harden-ssh, harden-jumphost, sshuser) run on Alpine/Debian/Alma; seed root keys from globals/. - ntfy SSH-login alerts (user, source IP, key, region, jump target) via pam_exec - daily auto-updates with AUTO_REBOOT=idle (reboots only when no SSH active) and opt-in Alpine stable-branch upgrades - generic + per-deployment cloud-init; Gitea release workflow on tag - README/LICENSE/.gitignore/.gitattributes (force LF); repo URLs -> Gitea
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# cloud-init/
|
||||
|
||||
Generic, distro-agnostic cloud-init templates for standing up a **base host**
|
||||
or a **bastion** from scratch. They run on Alpine, Debian, or Alma — the
|
||||
runcmd prelude detects the package manager and installs `bash`/`git`/`curl`
|
||||
before invoking the scripts.
|
||||
|
||||
| Template | What it does |
|
||||
|----------|--------------|
|
||||
| [`base.yml`](base.yml) | Hostname (per the schema) + shared MOTD + seed root keys from `globals/` + SSH hardening (`harden-ssh.sh`). |
|
||||
| [`jumphost.yml`](jumphost.yml) | Same base, but bastion hardening (`harden-jumphost.sh`) with an `ssh-admins`/`ssh-jumpers` split and a ProxyJump whitelist. |
|
||||
|
||||
These are for the **host itself**. To stand up a Docker stack on a host,
|
||||
use the per-deployment `cloud-init.yml` under `deployments/<name>/` instead
|
||||
(those clone the repo and run the stack's `deploy.sh`).
|
||||
|
||||
## Usage
|
||||
|
||||
1. Copy the template, fill in `REPO_URL`, `HOST` (`<svc>-<n>`, e.g. `sto-1`),
|
||||
and the other values at the top of the `runcmd` block.
|
||||
2. Paste it as the instance's **user-data** when creating the VM.
|
||||
3. On first boot the host names itself, installs the MOTD, seeds admin keys
|
||||
from `globals/`, and hardens SSH.
|
||||
|
||||
Hostnames follow [`../globals/Network Domain Name Schema.md`](../globals/Network%20Domain%20Name%20Schema.md);
|
||||
our VMs skip the region code and use `srvno.de` as the base.
|
||||
|
||||
> The harden scripts print a generated root private key to stdout, which lands
|
||||
> in the cloud provider's serial/console log. Capture it there, or rely on the
|
||||
> keys seeded from `globals/authorized_keys` (or `SSH_KEYS_URL`) and ignore it.
|
||||
@@ -0,0 +1,54 @@
|
||||
#cloud-config
|
||||
#
|
||||
# Generic base-host bootstrap -- Alpine, Debian, or Alma Linux.
|
||||
#
|
||||
# On first boot this:
|
||||
# 1. Installs prerequisites (bash, git, curl) for whichever distro this is.
|
||||
# 2. Clones this repo to /opt/automations.
|
||||
# 3. Sets the hostname per the Network Domain Name Schema and installs the
|
||||
# shared MOTD banner (scripts/setup-host.sh).
|
||||
# 4. Seeds root's authorized_keys from globals/ (URL-preferred).
|
||||
# 5. Applies SSH hardening: key-only auth, post-quantum KEX, sshguard
|
||||
# (scripts/harden-ssh.sh).
|
||||
#
|
||||
# Fill in REPO_URL, HOST, and the other values, then paste as instance
|
||||
# user-data. For a bastion host use jumphost.yml instead.
|
||||
#
|
||||
# NOTE: harden-ssh.sh prints a freshly generated root private key to stdout,
|
||||
# which lands in the cloud provider's console/serial log. Either capture it
|
||||
# from there, or rely on the keys seeded from globals/ and ignore it.
|
||||
|
||||
runcmd:
|
||||
- |
|
||||
set -e
|
||||
# ===== config =====
|
||||
REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git
|
||||
REPO_BRANCH=main
|
||||
HOST=sto-1 # <svc>-<n>; FQDN becomes HOST.BASE_DOMAIN
|
||||
BASE_DOMAIN=srvno.de
|
||||
DATACENTER="Globally Everywhere"
|
||||
SSH_PORT=22
|
||||
ALLOWED_IP= # optional: whitelist your client IP in sshguard
|
||||
AUTO_UPDATE=1 # schedule daily unattended updates (0 to skip)
|
||||
# ==================
|
||||
|
||||
# Prerequisites (OS-agnostic).
|
||||
if command -v apk >/dev/null 2>&1; then apk add --no-cache bash git curl
|
||||
elif command -v apt-get >/dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq bash git curl
|
||||
elif command -v dnf >/dev/null 2>&1; then dnf install -y -q bash git curl
|
||||
fi
|
||||
|
||||
git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations
|
||||
cd /opt/automations
|
||||
|
||||
# Hostname + shared MOTD.
|
||||
HOST="$HOST" BASE_DOMAIN="$BASE_DOMAIN" DATACENTER="$DATACENTER" bash scripts/setup-host.sh
|
||||
|
||||
# Seed root's authorized_keys from globals/ (SSH_KEYS_URL or authorized_keys).
|
||||
. scripts/lib.sh && load_globals \
|
||||
&& install -d -m 700 /root/.ssh \
|
||||
&& resolve_ssh_keys >> /root/.ssh/authorized_keys || true
|
||||
sort -u /root/.ssh/authorized_keys -o /root/.ssh/authorized_keys 2>/dev/null || true
|
||||
|
||||
# SSH hardening (key-only, PQ KEX, sshguard).
|
||||
SSH_PORT="$SSH_PORT" ALLOWED_IP="$ALLOWED_IP" FORCE=1 bash scripts/harden-ssh.sh
|
||||
@@ -0,0 +1,59 @@
|
||||
#cloud-config
|
||||
#
|
||||
# Jump-host (bastion) bootstrap -- Alpine, Debian, or Alma Linux.
|
||||
#
|
||||
# Same base steps as base.yml, but applies the bastion hardening
|
||||
# (scripts/harden-jumphost.sh) instead of plain SSH hardening:
|
||||
# - ssh-admins : full shell for maintenance
|
||||
# - ssh-jumpers : ProxyJump only, to the JUMP_TARGETS whitelist
|
||||
#
|
||||
# Fill in REPO_URL, HOST, and JUMP_TARGETS, then paste as instance user-data.
|
||||
# Add jumper/admin users afterwards with the installed `sshuser` tool.
|
||||
#
|
||||
# NOTE: harden-jumphost.sh prints a freshly generated root private key to
|
||||
# stdout (-> the cloud console/serial log). Capture it there, or rely on the
|
||||
# keys seeded from globals/ and ignore it.
|
||||
|
||||
runcmd:
|
||||
- |
|
||||
set -e
|
||||
# ===== config =====
|
||||
REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git
|
||||
REPO_BRANCH=main
|
||||
HOST=ssh-1 # <svc>-<n>; "ssh" is the bastion service code
|
||||
BASE_DOMAIN=srvno.de
|
||||
DATACENTER="Globally Everywhere"
|
||||
SSH_PORT=22
|
||||
ALLOWED_IP= # optional: whitelist your client IP
|
||||
JUMP_TARGETS="10.0.0.5:22 10.0.0.6:22" # hosts jumpers may ProxyJump to
|
||||
# Optional login notifications (pam_exec -> ntfy). Leave NTFY_URL empty to
|
||||
# skip. NTFY_REGION defaults to the region segment of this host's FQDN.
|
||||
NTFY_URL=
|
||||
NTFY_TOKEN= # bearer token; empty if unauth publish
|
||||
NTFY_EMAIL=
|
||||
NTFY_REGION=
|
||||
# ==================
|
||||
|
||||
# Prerequisites (OS-agnostic).
|
||||
if command -v apk >/dev/null 2>&1; then apk add --no-cache bash git curl
|
||||
elif command -v apt-get >/dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq bash git curl
|
||||
elif command -v dnf >/dev/null 2>&1; then dnf install -y -q bash git curl
|
||||
fi
|
||||
|
||||
git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations
|
||||
cd /opt/automations
|
||||
|
||||
# Hostname + shared MOTD.
|
||||
HOST="$HOST" BASE_DOMAIN="$BASE_DOMAIN" DATACENTER="$DATACENTER" bash scripts/setup-host.sh
|
||||
|
||||
# Seed root's (ssh-admins) authorized_keys from globals/.
|
||||
. scripts/lib.sh && load_globals \
|
||||
&& install -d -m 700 /root/.ssh \
|
||||
&& resolve_ssh_keys >> /root/.ssh/authorized_keys || true
|
||||
sort -u /root/.ssh/authorized_keys -o /root/.ssh/authorized_keys 2>/dev/null || true
|
||||
|
||||
# Bastion hardening (admins shell + jumpers ProxyJump whitelist + optional
|
||||
# login notifications).
|
||||
SSH_PORT="$SSH_PORT" ALLOWED_IP="$ALLOWED_IP" JUMP_TARGETS="$JUMP_TARGETS" \
|
||||
NTFY_URL="$NTFY_URL" NTFY_TOKEN="$NTFY_TOKEN" NTFY_EMAIL="$NTFY_EMAIL" NTFY_REGION="$NTFY_REGION" \
|
||||
FORCE=1 bash scripts/harden-jumphost.sh
|
||||
Reference in New Issue
Block a user