This commit is contained in:
59
.gitea/workflows/generate-readme.yml
Normal file
59
.gitea/workflows/generate-readme.yml
Normal file
@@ -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}"
|
||||
|
||||
|
||||
42
README.md
42
README.md
@@ -1,3 +1,43 @@
|
||||
# tools
|
||||
|
||||
Just a collection of tools that I use
|
||||
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`.
|
||||
81
list_root_domains.sh
Normal file
81
list_root_domains.sh
Normal file
@@ -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 "$@"
|
||||
|
||||
|
||||
120
scripts/generate_readme.sh
Normal file
120
scripts/generate_readme.sh
Normal file
@@ -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 "$@"
|
||||
|
||||
|
||||
211
setup_deploy_user.sh
Normal file
211
setup_deploy_user.sh
Normal file
@@ -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 "$@"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user