From e0d1a9193fd7a7eab07fdec79248335f3223506c Mon Sep 17 00:00:00 2001 From: HRiggs Date: Sun, 28 Sep 2025 17:09:17 -0400 Subject: [PATCH] New tools and deploy --- .gitea/workflows/generate-readme.yml | 59 ++++++++ README.md | 42 +++++- list_root_domains.sh | 81 ++++++++++ scripts/generate_readme.sh | 120 +++++++++++++++ setup_deploy_user.sh | 211 +++++++++++++++++++++++++++ 5 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/generate-readme.yml create mode 100644 list_root_domains.sh create mode 100644 scripts/generate_readme.sh create mode 100644 setup_deploy_user.sh diff --git a/.gitea/workflows/generate-readme.yml b/.gitea/workflows/generate-readme.yml new file mode 100644 index 0000000..b0ffe47 --- /dev/null +++ b/.gitea/workflows/generate-readme.yml @@ -0,0 +1,59 @@ +name: Generate README + +on: + push: + branches: + - main + - '**' + paths: + - '**/*.sh' + - '**/*.bat' + - 'scripts/generate_readme.sh' + - '.gitea/workflows/generate-readme.yml' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup SSH with deploy key + env: + DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} + run: | + mkdir -p ~/.ssh + chmod 700 ~/.ssh + echo "$DEPLOY_KEY" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + eval "$(ssh-agent -s)" + ssh-add ~/.ssh/id_rsa + ssh-keyscan -H git.hudsonriggs.systems >> ~/.ssh/known_hosts + git remote set-url origin git@git.hudsonriggs.systems:HRiggs/tools.git + + - name: Generate README + run: | + bash scripts/generate_readme.sh + + - name: Commit changes + run: | + if git diff --quiet README.md; then + echo "No README changes" + exit 0 + fi + git config user.name "gitea-bot" + git config user.email "gitea-bot@users.noreply.local" + git add README.md + git commit -m "docs: auto-generate README for scripts [skip ci]" + + - name: Push changes + env: + BRANCH_NAME: ${{ github.ref_name }} + run: | + : "Pushing to branch ${BRANCH_NAME} via SSH" + git push origin HEAD:"${BRANCH_NAME}" + + diff --git a/README.md b/README.md index 1ce4e4a..e32c7d6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,43 @@ # tools -Just a collection of tools that I use \ No newline at end of file +Auto-generated README. Do not edit by hand. + +This file is regenerated by `scripts/generate_readme.sh` on every push. + +## Shell (.sh) + +Run on Linux/macOS using curl: + +### fakepve.sh + +```bash +curl -fsSL -o fakepve.sh "https://git.hudsonriggs.systems/HRiggs/tools/raw/branch/main/fakepve.sh" && chmod +x fakepve.sh && ./"fakepve.sh" +``` + +### setup_deploy_user.sh + +```bash +curl -fsSL -o setup_deploy_user.sh "https://git.hudsonriggs.systems/HRiggs/tools/raw/branch/main/setup_deploy_user.sh" && chmod +x setup_deploy_user.sh && ./"setup_deploy_user.sh" +``` + +### selfsigned_certs.sh + +```bash +curl -fsSL -o selfsigned_certs.sh "https://git.hudsonriggs.systems/HRiggs/tools/raw/branch/main/selfsigned_certs.sh" && chmod +x selfsigned_certs.sh && ./"selfsigned_certs.sh" +``` + +## Windows (.bat) + +Run from PowerShell: + +### win-mao.bat + +```powershell +$dest = Join-Path $env:TEMP "win-mao.bat"; +Invoke-WebRequest -Uri "https://git.hudsonriggs.systems/HRiggs/tools/raw/branch/main/win-mao.bat" -OutFile $dest; +& $dest +``` + +--- + +Generated from repo: `https://git.hudsonriggs.systems/HRiggs/tools` on branch `main`. \ No newline at end of file diff --git a/list_root_domains.sh b/list_root_domains.sh new file mode 100644 index 0000000..5a55d25 --- /dev/null +++ b/list_root_domains.sh @@ -0,0 +1,81 @@ +#!/bin/sh +set -eu + +# Print a comma-separated list of unique registrable root domains (eTLD+1) +# for non-suspended cPanel users based on /etc/userdomains. +# +# Tries to use `psl` (libpsl) if available for correct public suffix handling. +# Falls back to a heuristic for common multi-level TLDs if `psl` is unavailable. + +USERDOMAINS_PATH="/etc/userdomains" +SUSPENDED_DIR="/var/cpanel/suspended" + +error() { + printf "Error: %s\n" "$*" 1>&2 +} + +have_psl() { + command -v psl >/dev/null 2>&1 +} + +get_root_domain() { + # Derive registrable domain (eTLD+1) from a hostname + # 1) Prefer libpsl via `psl --print` + # 2) Fallback heuristic for common multi-level TLDs + hostname="$1" + # strip trailing dot if any + case "$hostname" in + *.) hostname=${hostname%\.} ;; + esac + + if have_psl; then + printed=$(psl --print "$hostname" 2>/dev/null | tr -d '\r') + if [ -n "$printed" ]; then + printf "%s\n" "$printed" + return 0 + fi + fi + + # Fallback heuristic implemented in awk to avoid bashisms + printf "%s\n" "$hostname" | awk ' + BEGIN { FS = "." } + { + n = NF + if (n >= 3) { + lastTwo = $(n-1) "." $n + if (lastTwo ~ /^(co\.uk|org\.uk|ac\.uk|gov\.uk|ltd\.uk|plc\.uk|me\.uk|co\.jp|ne\.jp|or\.jp|go\.jp|ac\.jp|ed\.jp|gr\.jp|com\.au|net\.au|org\.au|edu\.au|gov\.au|id\.au|asn\.au|com\.br|com\.cn|net\.cn|org\.cn|com\.sg|com\.my|co\.nz)$/) { + print $(n-2) "." lastTwo + next + } + } + if (n >= 2) { + print $(n-1) "." $n + } else { + print $0 + } + }' +} + +main() { + if [ ! -r "${USERDOMAINS_PATH}" ]; then + error "Cannot read ${USERDOMAINS_PATH}" + exit 1 + fi + + # Read, normalize whitespace removal like original, skip lines starting with '*' + # Format is typically: domain: user + # For each non-suspended user, emit root domain; then unique-sort and join with commas + sed -e 's/[[:space:]]*//g' -e '/^\*/d' "${USERDOMAINS_PATH}" \ + | awk -F: 'NF>=2 { print $1 ":" $2 }' \ + | while IFS=":" read -r domain username; do + if [ ! -f "${SUSPENDED_DIR}/${username}" ]; then + get_root_domain "${domain}" + fi + done \ + | sort -u \ + | paste -sd, - +} + +main "$@" + + diff --git a/scripts/generate_readme.sh b/scripts/generate_readme.sh new file mode 100644 index 0000000..06977d6 --- /dev/null +++ b/scripts/generate_readme.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Generate README.md with download & run commands for .sh and .bat scripts +# This file is intended to be run in CI and locally. + +determine_repo_web_base() { + local origin_url + origin_url="$(git config --get remote.origin.url || true)" + if [[ -z "${origin_url}" ]]; then + echo "Error: could not determine git remote origin URL" >&2 + exit 1 + fi + + local web_base + if [[ "${origin_url}" =~ ^https?:// ]]; then + web_base="${origin_url%.git}" + elif [[ "${origin_url}" =~ ^git@([^:]+):(.+)\.git$ ]]; then + local host path + host="${BASH_REMATCH[1]}" + path="${BASH_REMATCH[2]}" + web_base="https://${host}/${path}" + else + # Fallback: strip trailing .git if present + web_base="${origin_url%.git}" + fi + + printf '%s' "${web_base}" +} + +determine_branch() { + # Prefer BRANCH env, fall back to current branch, else main + if [[ -n "${BRANCH:-}" ]]; then + printf '%s' "${BRANCH}" + return + fi + local branch + branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)" + if [[ -z "${branch}" || "${branch}" == "HEAD" ]]; then + branch="main" + fi + printf '%s' "${branch}" +} + +list_files() { + # List tracked files matching a glob, excluding scripts/generate_readme.sh + local pattern="$1" + if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + # Use git to respect .gitignore and include only tracked files + git ls-files "${pattern}" 2>/dev/null | grep -v '^scripts/generate_readme.sh$' || true + else + find . -type f -name "${pattern}" -not -path './scripts/generate_readme.sh' -print | sed 's|^\./||' || true + fi +} + +main() { + local web_base branch raw_base + web_base="$(determine_repo_web_base)" + branch="$(determine_branch)" + raw_base="${web_base}/raw/branch/${branch}" + + # Collect files + mapfile -t sh_files < <(list_files '*.sh' | sort) + mapfile -t bat_files < <(list_files '*.bat' | sort) + + # Begin README content + { + echo "# tools" + echo + echo "Auto-generated README. Do not edit by hand." + echo + echo "This file is regenerated by \`scripts/generate_readme.sh\` on every push." + echo + if ((${#sh_files[@]} > 0)); then + echo "## Shell (.sh)" + echo + echo "Run on Linux/macOS using curl:" + echo + for f in "${sh_files[@]}"; do + local name url + name="$(basename "${f}")" + url="${raw_base}/${f}" + echo "### ${name}" + echo + echo '```bash' + echo "curl -fsSL -o ${name} \"${url}\" && chmod +x ${name} && ./\"${name}\"" + echo '```' + echo + done + fi + + if ((${#bat_files[@]} > 0)); then + echo "## Windows (.bat)" + echo + echo "Run from PowerShell:" + echo + for f in "${bat_files[@]}"; do + local name url + name="$(basename "${f}")" + url="${raw_base}/${f}" + echo "### ${name}" + echo + echo '```powershell' + echo '$dest = Join-Path $env:TEMP "'"${name}"'";' + echo 'Invoke-WebRequest -Uri '"\"${url}\""' -OutFile $dest;' + echo '& $dest' + echo '```' + echo + done + fi + + echo "---" + echo + echo "Generated from repo: \`${web_base}\` on branch \`${branch}\`." + } > README.md +} + +main "$@" + + diff --git a/setup_deploy_user.sh b/setup_deploy_user.sh new file mode 100644 index 0000000..8dfd71a --- /dev/null +++ b/setup_deploy_user.sh @@ -0,0 +1,211 @@ +#!/bin/sh +set -eu + +# Create a deploy user, generate an ed25519 keypair, copy keys to /root, +# update the user's authorized_keys, and (if supported) add an sshd per-user config. +# +# Usage: +# DEPLOY_USER=deploy ./setup_deploy_user.sh +# ./setup_deploy_user.sh mydeploy +# +# Defaults to user "deploy" if not provided via env or argument. + +info() { + printf "[INFO] %s\n" "$*" +} + +error() { + printf "[ERROR] %s\n" "$*" 1>&2 +} + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +require_root() { + if [ "$(id -u)" -ne 0 ]; then + error "This script must be run as root" + exit 1 + fi +} + +sanitize_username() { + # POSIX-ish username rules (simplified): letters, digits, _ and - + # must start with a letter or underscore + case "$1" in + '' ) return 1 ;; + *[!A-Za-z0-9_-]* ) return 1 ;; + [!A-Za-z_]* ) return 1 ;; + * ) return 0 ;; + esac +} + +get_home_dir() { + user="$1" + home="$(getent passwd "$user" 2>/dev/null | awk -F: 'NR==1 {print $6}')" + if [ -z "$home" ]; then + home="/home/$user" + fi + printf '%s' "$home" +} + +create_user_if_needed() { + user="$1" + if id "$user" >/dev/null 2>&1; then + info "User '$user' already exists" + return 0 + fi + + if command_exists useradd; then + info "Creating user '$user' with useradd" + # -m create home, -U create group, -s shell + useradd -m -U -s /bin/bash -c "Deployment User" "$user" + elif command_exists adduser; then + info "Creating user '$user' with adduser" + # Non-interactive, no password + adduser --disabled-password --gecos "Deployment User" "$user" + else + error "Neither useradd nor adduser is available" + exit 1 + fi +} + +backup_if_exists() { + p="$1" + if [ -e "$p" ]; then + ts="$(date +%s)" + mv "$p" "$p.bak.$ts" + info "Backed up existing $(basename "$p") to $(basename "$p.bak.$ts")" + fi +} + +write_user_ssh_client_config() { + user="$1" + home_dir="$2" + ssh_dir="$home_dir/.ssh" + cfg="$ssh_dir/config" + mkdir -p "$ssh_dir" + chmod 700 "$ssh_dir" + # Minimal, safe client config + if [ ! -f "$cfg" ]; then + { + echo "Host *" + echo " IdentitiesOnly yes" + echo " PubkeyAuthentication yes" + } > "$cfg" + chown "$user":"$user" "$cfg" + chmod 600 "$cfg" + info "Wrote SSH client config at $cfg" + else + info "SSH client config already present at $cfg" + fi + chown "$user":"$user" "$ssh_dir" +} + +configure_sshd_dropin_if_supported() { + user="$1" + dropin_dir="/etc/ssh/sshd_config.d" + if [ -d "$dropin_dir" ]; then + conf="$dropin_dir/99-deploy-user-$user.conf" + if [ ! -f "$conf" ]; then + { + echo "Match User $user" + echo " PubkeyAuthentication yes" + echo " PasswordAuthentication no" + echo " AuthorizedKeysFile %h/.ssh/authorized_keys" + } > "$conf" + chmod 644 "$conf" + info "Created sshd drop-in: $conf" + else + info "sshd drop-in already exists: $conf" + fi + + if command_exists sshd; then + if sshd -t >/dev/null 2>&1; then + if command_exists systemctl; then + systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true + else + service ssh reload 2>/dev/null || service sshd reload 2>/dev/null || true + fi + info "Reloaded sshd" + else + error "sshd configuration test failed; not reloading" + fi + fi + else + info "sshd drop-in directory not present; skipping server config" + fi +} + +main() { + require_root + + DEPLOY_USER_NAME="${DEPLOY_USER:-${1:-deploy}}" + + if ! sanitize_username "$DEPLOY_USER_NAME"; then + error "Invalid username '$DEPLOY_USER_NAME'. Use lowercase letters, digits, '_' or '-', starting with a letter or '_'" + exit 1 + fi + + info "Using deploy user: $DEPLOY_USER_NAME" + create_user_if_needed "$DEPLOY_USER_NAME" + + HOME_DIR="$(get_home_dir "$DEPLOY_USER_NAME")" + SSH_DIR="$HOME_DIR/.ssh" + AUTH_KEYS="$SSH_DIR/authorized_keys" + + mkdir -p "$SSH_DIR" + chmod 700 "$SSH_DIR" + touch "$AUTH_KEYS" + chmod 600 "$AUTH_KEYS" + chown -R "$DEPLOY_USER_NAME":"$DEPLOY_USER_NAME" "$SSH_DIR" + + # Generate keypair in a temp dir + TMPDIR="$(mktemp -d)" + trap 'rm -rf "$TMPDIR"' EXIT INT HUP TERM + KEYBASE="$TMPDIR/id_ed25519" + COMMENT="deploy key for $DEPLOY_USER_NAME@$(hostname -f 2>/dev/null || hostname)" + + info "Generating ed25519 keypair" + if ! command_exists ssh-keygen; then + error "ssh-keygen not found. Please install OpenSSH client tools." + exit 1 + fi + ssh-keygen -t ed25519 -N "" -C "$COMMENT" -f "$KEYBASE" >/dev/null 2>&1 + + # Copy keys to /root as deploy.priv and deploy.pub + DEST_PRIV="/root/deploy.priv" + DEST_PUB="/root/deploy.pub" + backup_if_exists "$DEST_PRIV" + backup_if_exists "$DEST_PUB" + cp "$KEYBASE" "$DEST_PRIV" + cp "$KEYBASE.pub" "$DEST_PUB" + chown root:root "$DEST_PRIV" "$DEST_PUB" + chmod 600 "$DEST_PRIV" + chmod 644 "$DEST_PUB" + info "Installed private key: $DEST_PRIV" + info "Installed public key: $DEST_PUB" + + # Add public key to authorized_keys if not present + PUB_LINE="$(cat "$KEYBASE.pub")" + if ! grep -qxF "$PUB_LINE" "$AUTH_KEYS" 2>/dev/null; then + printf '%s\n' "$PUB_LINE" >> "$AUTH_KEYS" + chown "$DEPLOY_USER_NAME":"$DEPLOY_USER_NAME" "$AUTH_KEYS" + chmod 600 "$AUTH_KEYS" + info "Added public key to $AUTH_KEYS" + else + info "Public key already present in $AUTH_KEYS" + fi + + # Minimal client-side SSH config for the deploy user + write_user_ssh_client_config "$DEPLOY_USER_NAME" "$HOME_DIR" + + # Optional: server-side sshd drop-in configuration + configure_sshd_dropin_if_supported "$DEPLOY_USER_NAME" + + printf "\nDone. You can distribute the public key at %s.\n" "$DEST_PUB" +} + +main "$@" + +