Add client onboarding script with Semaphore API integration
This commit is contained in:
227
scripts/onboard_client.sh
Executable file
227
scripts/onboard_client.sh
Executable file
@@ -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 ""
|
||||||
Reference in New Issue
Block a user