diff --git a/scripts/onboard_client.sh b/scripts/onboard_client.sh new file mode 100755 index 0000000..a54bad8 --- /dev/null +++ b/scripts/onboard_client.sh @@ -0,0 +1,227 @@ +#!/bin/bash +set -e + +# ─── Usage ─────────────────────────────────────────────────────────────────── +usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " -i, --id Client ID (e.g. DFA-001)" + echo " -n, --name Client name (e.g. 'DFA Tech Colo')" + echo " -s, --slug Short slug for filenames (e.g. dfa_tech)" + echo " -w, --webhook n8n webhook URL" + echo " -b, --billing Billing model (hybrid|per-host|time-saved|tiered) default: hybrid" + echo " -e, --estimate Human time estimate seconds (default: 2700)" + echo " -v, --vpn VPN type (ipsec|openvpn) default: ipsec" + echo " -h, --hypervisor Hypervisor type (proxmox|xcpng) default: proxmox" + echo " --semaphore-url Semaphore base URL (default: http://localhost:3000)" + echo " --semaphore-token Semaphore API token" + echo "" + echo "Example:" + echo " $0 -i DFA-001 -n 'DFA Tech Colo' -s dfa_tech -w https://n8n.voice1.me/webhook/xxx" + exit 1 +} + +# ─── Defaults ──────────────────────────────────────────────────────────────── +BILLING="hybrid" +ESTIMATE="2700" +VPN="ipsec" +HYPERVISOR="proxmox" +SEMAPHORE_URL="http://localhost:3000" +REPO_DIR="/opt/ansible-msp-automations" + +# ─── Parse args ────────────────────────────────────────────────────────────── +while [[ $# -gt 0 ]]; do + case $1 in + -i|--id) CLIENT_ID="$2"; shift 2 ;; + -n|--name) CLIENT_NAME="$2"; shift 2 ;; + -s|--slug) CLIENT_SLUG="$2"; shift 2 ;; + -w|--webhook) WEBHOOK_URL="$2"; shift 2 ;; + -b|--billing) BILLING="$2"; shift 2 ;; + -e|--estimate) ESTIMATE="$2"; shift 2 ;; + -v|--vpn) VPN="$2"; shift 2 ;; + -h|--hypervisor) HYPERVISOR="$2"; shift 2 ;; + --semaphore-url) SEMAPHORE_URL="$2"; shift 2 ;; + --semaphore-token) SEMAPHORE_TOKEN="$2"; shift 2 ;; + *) usage ;; + esac +done + +# ─── Validate required args ────────────────────────────────────────────────── +if [[ -z "$CLIENT_ID" || -z "$CLIENT_NAME" || -z "$CLIENT_SLUG" || -z "$WEBHOOK_URL" ]]; then + echo "ERROR: --id, --name, --slug, and --webhook are required" + usage +fi + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " Onboarding client: $CLIENT_NAME ($CLIENT_ID)" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +# ─── Step 1: Generate SSH key ───────────────────────────────────────────────── +echo "" +echo "[ 1/5 ] Generating SSH key..." +KEY_FILE="/root/.ssh/client_${CLIENT_SLUG}" +if [[ -f "$KEY_FILE" ]]; then + echo " ⚠ Key already exists at $KEY_FILE — skipping generation" +else + ssh-keygen -t ed25519 -C "ansible-${CLIENT_SLUG}" -f "$KEY_FILE" -N "" + echo " ✓ Key generated: $KEY_FILE" +fi +echo "" +echo " ┌─ Deploy this public key to all client hosts ───────────────────────" +echo " │" +cat "$KEY_FILE.pub" | sed 's/^/ │ /' +echo " │" +echo " └────────────────────────────────────────────────────────────────────" + +# ─── Step 2: Create inventory from template ─────────────────────────────────── +echo "" +echo "[ 2/5 ] Creating inventory from template..." +INVENTORY_DIR="$REPO_DIR/inventories/client_${CLIENT_SLUG}" + +if [[ -d "$INVENTORY_DIR" ]]; then + echo " ⚠ Inventory directory already exists — skipping" +else + cp -r "$REPO_DIR/inventories/client_template" "$INVENTORY_DIR" + + # Write hosts.yml + cat > "$INVENTORY_DIR/hosts.yml" << HOSTSEOF +--- +all: + vars: + client_id: "${CLIENT_ID}" + client_name: "${CLIENT_NAME}" + billing_model: "${BILLING}" + maintenance_window_start: "02:00" + maintenance_window_end: "05:00" + maintenance_window_tz: "UTC" + change_freeze: false + hypervisor_type: "${HYPERVISOR}" + vpn_type: "${VPN}" + auto_reboot: false + human_estimate_seconds: ${ESTIMATE} + + children: + linux_hosts: + hosts: {} + vars: + ansible_user: root + os_family: "debian" + + windows_hosts: + hosts: {} + vars: + ansible_user: Administrator + ansible_connection: winrm + ansible_winrm_transport: ntlm + ansible_winrm_server_cert_validation: validate + ansible_port: 5986 +HOSTSEOF + + # Write group_vars/all.yml + cat > "$INVENTORY_DIR/group_vars/all.yml" << VARSEOF +--- +# Client: ${CLIENT_NAME} (${CLIENT_ID}) +# Onboarded: $(date +%Y-%m-%d) +# VPN: ${VPN} +# Hypervisor: ${HYPERVISOR} +# Billing: ${BILLING} + +# Add client-specific overrides below +VARSEOF + + echo " ✓ Inventory created at $INVENTORY_DIR" +fi + +# ─── Step 3: Commit and push to Gitea ──────────────────────────────────────── +echo "" +echo "[ 3/5 ] Committing to Gitea..." +cd "$REPO_DIR" +git add . +git commit -m "Onboard client: ${CLIENT_NAME} (${CLIENT_ID}) — inventory scaffold" \ + || echo " ⚠ Nothing to commit" +git push origin main +echo " ✓ Pushed to Gitea" + +# ─── Step 4: Create Semaphore project via API ───────────────────────────────── +echo "" +echo "[ 4/5 ] Creating Semaphore project via API..." + +if [[ -z "$SEMAPHORE_TOKEN" ]]; then + echo " ⚠ No --semaphore-token provided — skipping Semaphore API setup" + echo " Create the project manually in Semaphore UI" +else + # Create project + PROJECT_RESPONSE=$(curl -s -X POST "$SEMAPHORE_URL/api/projects" \ + -H "Authorization: Bearer $SEMAPHORE_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"Client - ${CLIENT_NAME}\", \"alert\": false, \"max_parallel_tasks\": 0}") + + PROJECT_ID=$(echo "$PROJECT_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])" 2>/dev/null) + + if [[ -z "$PROJECT_ID" ]]; then + echo " ✗ Failed to create project — check token and Semaphore URL" + echo " Response: $PROJECT_RESPONSE" + else + echo " ✓ Project created (ID: $PROJECT_ID)" + + # Add gitea-deploy key to project key store + GITEA_KEY=$(cat /root/.ssh/gitea_ansible) + curl -s -X POST "$SEMAPHORE_URL/api/project/$PROJECT_ID/keys" \ + -H "Authorization: Bearer $SEMAPHORE_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"gitea-deploy\", \"type\": \"ssh\", \"ssh\": {\"private_key\": $(echo "$GITEA_KEY" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")}}" > /dev/null + echo " ✓ Gitea deploy key added" + + # Add client SSH key + CLIENT_KEY=$(cat "$KEY_FILE") + curl -s -X POST "$SEMAPHORE_URL/api/project/$PROJECT_ID/keys" \ + -H "Authorization: Bearer $SEMAPHORE_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"client-${CLIENT_SLUG}-ssh\", \"type\": \"ssh\", \"ssh\": {\"private_key\": $(echo "$CLIENT_KEY" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")}}" > /dev/null + echo " ✓ Client SSH key added to Key Store" + + # Add repository + REPO_RESPONSE=$(curl -s -X POST "$SEMAPHORE_URL/api/project/$PROJECT_ID/repositories" \ + -H "Authorization: Bearer $SEMAPHORE_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"ansible-msp-automations\", \"project_id\": $PROJECT_ID, \"git_url\": \"ssh://git@172.31.10.8:2222/VOICE1/ansible-msp-automations.git\", \"git_branch\": \"main\", \"ssh_key_id\": 1}") + REPO_ID=$(echo "$REPO_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])" 2>/dev/null) + echo " ✓ Repository linked (ID: $REPO_ID)" + + # Add variable group + curl -s -X POST "$SEMAPHORE_URL/api/project/$PROJECT_ID/environment" \ + -H "Authorization: Bearer $SEMAPHORE_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"${CLIENT_SLUG}-vars\", \"project_id\": $PROJECT_ID, \"json\": \"{\\\"N8N_WEBHOOK_URL\\\": \\\"${WEBHOOK_URL}\\\", \\\"CLIENT_ID\\\": \\\"${CLIENT_ID}\\\", \\\"CLIENT_NAME\\\": \\\"${CLIENT_NAME}\\\", \\\"BILLING_MODEL\\\": \\\"${BILLING}\\\", \\\"HUMAN_ESTIMATE_SECONDS\\\": \\\"${ESTIMATE}\\\"}\"}" > /dev/null + echo " ✓ Variable group created" + + echo "" + echo " Semaphore project ID: $PROJECT_ID" + echo " Still needed manually:" + echo " - Add inventory (File type → inventories/client_${CLIENT_SLUG}/hosts.yml)" + echo " - Create task templates" + echo " - Add hosts to inventory file in Gitea" + fi +fi + +# ─── Step 5: Summary ────────────────────────────────────────────────────────── +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " ✓ Client onboarding complete: $CLIENT_NAME" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo " SSH public key to deploy to client hosts:" +echo " $KEY_FILE.pub" +echo "" +echo " Inventory file to populate with hosts:" +echo " $INVENTORY_DIR/hosts.yml" +echo "" +echo " Next steps:" +echo " 1. Deploy SSH public key to all client hosts" +echo " 2. Add hosts to inventory file in Gitea" +echo " 3. Add inventory entry in Semaphore (File type)" +echo " 4. Create task templates in Semaphore" +echo " 5. Run preflight check" +echo ""