{%- set srv = servers | first -%} SERVER_NAME="{{ srv.hostname }}" FIREWALL_NAME="{{ srv.firewall | default(value='librecloud-fw') }}" # Color codes for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[✓]${NC} $1" } log_error() { echo -e "${RED}[✗]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } # ============================================================================ # STEP 1: Check if server already exists # ============================================================================ log_info "Step 1/3: Checking server '$SERVER_NAME'..." if hcloud server describe "$SERVER_NAME" > /dev/null 2>&1; then log_warn "Server '$SERVER_NAME' already exists" SERVER_ID=$(hcloud server describe "$SERVER_NAME" -o yaml 2>/dev/null | grep ^id: | cut -f2 -d":" | sed "s/ //g") log_success "Using existing server: ID=$SERVER_ID" else # ============================================================================ # STEP 1b: Create server # ============================================================================ log_info "Creating server '$SERVER_NAME'..." out_err="${STATE_DIR}/${SERVER_NAME}_create_out.text" set +e SERVER_INFO=$(hcloud server create \ --name "{{ srv.hostname }}" \ --type {{ srv.server_type }} \ --location {{ srv.location }} \ --image {{ srv.image }} \ {%- if srv.networking is defined and srv.networking.ipv6_enabled is defined and not srv.networking.ipv6_enabled %} --without-ipv6 \ {%- endif %} {%- if srv.networking is defined and srv.networking.ipv4_enabled is defined and not srv.networking.ipv4_enabled %} --without-ipv4 \ {%- endif %} {%- if srv.networking is defined and srv.networking.ipv4_enabled is defined and not srv.networking.ipv4_enabled and srv.networking.private_network is defined %} --network "{{ srv.networking.private_network }}" \ {%- endif %} {%- for key in srv.ssh_keys %} --ssh-key "{{ key }}" \ {%- endfor %} {%- for label_key, label_val in srv.labels %} --label "{{ label_key }}={{ label_val }}" \ {%- endfor %} 2>"$out_err" | tr -d '\n') HCLOUD_EXIT=$? set -e if [ $HCLOUD_EXIT -ne 0 ] || [ -z "$SERVER_INFO" ]; then log_error "Failed to create server '$SERVER_NAME' (exit=$HCLOUD_EXIT)" if [ -s "$out_err" ]; then log_error "hcloud: $(cat "$out_err")" fi exit 1 fi SERVER_ID=$(echo "$SERVER_INFO" | cut -f2 -d" ") SERVER_STATUS=$(echo "$SERVER_INFO" | cut -f3 -d" ") SERVER_IPv4=$(echo "$SERVER_INFO" | cut -f5 -d" ") log_success "$SERVER_ID $SERVER_NAME $SERVER_STATUS $SERVER_IPv4" log_success "Server created: ID=$SERVER_ID" # Wait for server to reach running state before network/protection ops log_info "Waiting for server '$SERVER_NAME' to reach running state..." for _i in $(seq 1 30); do _st=$(hcloud server describe "$SERVER_NAME" -o yaml 2>/dev/null | grep ^status: | awk '{print $2}') [ "$_st" = "running" ] && break sleep 3 done log_success "Server '$SERVER_NAME' is running" fi if [ -n "$SERVER_ID" ] ; then {%- if srv.networking is defined and srv.networking.private_network is defined %} {%- if srv.networking.private_ip is defined and srv.networking.private_ip %} _net_attached=false _attach_err="${STATE_DIR}/${SERVER_NAME}_net_attach.err" # Check if already attached before trying if HJSON="$(hcloud server describe "$SERVER_NAME" -o json 2>/dev/null)" \ nu -c '$env.HJSON | from json | get -o private_net | default [] | (any {|n| ($n | get -o ip | default "") == "{{ srv.networking.private_ip }}"}) | if $in { exit 0 } else { exit 1 }' 2>/dev/null; then log_success "Already attached to {{ srv.networking.private_network }} with IP {{ srv.networking.private_ip }}" _net_attached=true else for _r in $(seq 1 6); do if hcloud server attach-to-network \ --network "{{ srv.networking.private_network }}" --ip "{{ srv.networking.private_ip }}" \ "$SERVER_ID" 2>"$_attach_err"; then log_success "Attached to network {{ srv.networking.private_network }} with IP {{ srv.networking.private_ip }}" _net_attached=true break fi _err=$(cat "$_attach_err" 2>/dev/null) log_info "Attach attempt $_r/6 failed: $_err — retrying in 5s..." sleep 5 done [ "$_net_attached" = "false" ] && log_warn "Network attach failed after 6 attempts: $(cat "$_attach_err" 2>/dev/null)" fi {%- else %} _net_attached=false _attach_err="${STATE_DIR}/${SERVER_NAME}_net_attach.err" if HJSON="$(hcloud server describe "$SERVER_NAME" -o json 2>/dev/null)" \ nu -c 'if (($env.HJSON | from json | get -o private_net | default []) | is-not-empty) { exit 0 } else { exit 1 }' 2>/dev/null; then log_success "Already attached to {{ srv.networking.private_network }}" _net_attached=true else for _r in $(seq 1 6); do if hcloud server attach-to-network \ --network "{{ srv.networking.private_network }}" \ "$SERVER_ID" 2>"$_attach_err"; then log_success "Attached to network {{ srv.networking.private_network }}" _net_attached=true break fi _err=$(cat "$_attach_err" 2>/dev/null) log_info "Attach attempt $_r/6 failed: $_err — retrying in 5s..." sleep 5 done [ "$_net_attached" = "false" ] && log_warn "Network attach failed after 6 attempts: $(cat "$_attach_err" 2>/dev/null)" fi {%- endif %} {%- endif %} {%- if srv.protection is defined %} # Enable delete/rebuild protection as configured _prot_args="" {%- if srv.protection.delete is defined and srv.protection.delete == true %} _prot_args="$_prot_args delete" {%- endif %} {%- if srv.protection.rebuild is defined and srv.protection.rebuild == true %} _prot_args="$_prot_args rebuild" {%- endif %} if [ -n "$_prot_args" ]; then hcloud server enable-protection "$SERVER_NAME" $_prot_args 2>/dev/null \ && log_success "Protection enabled:$_prot_args" \ || log_warn "Failed to enable protection (may already be set)" fi {%- endif %} fi # ============================================================================ # STEP 2: Attach server to cluster firewall (firewall managed independently) # ============================================================================ log_info "Step 2/2: Attaching server to firewall '$FIREWALL_NAME'..." if hcloud firewall describe "$FIREWALL_NAME" > /dev/null 2>&1; then hcloud firewall apply-to-resource "$FIREWALL_NAME" \ --type server \ --server "$SERVER_NAME" 2>/dev/null || true log_success "Server attached to firewall '$FIREWALL_NAME'" else log_warn "Firewall '$FIREWALL_NAME' not found — run firewall reconciliation first" fi {%- if srv.floating_ip is defined and srv.floating_ip %} # ============================================================================ # STEP 3: Assign floating IP # ============================================================================ FLOATING_IP_NAME="{{ srv.floating_ip }}" log_info "Step 3/3: Assigning floating IP '$FLOATING_IP_NAME' to '$SERVER_NAME'..." set +e FIP_ID=$(hcloud floating-ip list -o noheader -o columns=id,name 2>/dev/null | awk -v name="$FLOATING_IP_NAME" '$2 == name { print $1 }') set -e if [ -z "$FIP_ID" ]; then log_error "Floating IP '$FLOATING_IP_NAME' not found — run bootstrap first" exit 1 fi set +e hcloud floating-ip assign "$FIP_ID" "$SERVER_ID" 2>"${STATE_DIR}/${SERVER_NAME}_fip_out.text" FIP_EXIT=$? set -e if [ $FIP_EXIT -ne 0 ]; then log_error "Failed to assign floating IP '$FLOATING_IP_NAME' (id=$FIP_ID) to '$SERVER_NAME'" if [ -s "${STATE_DIR}/${SERVER_NAME}_fip_out.text" ]; then log_error "hcloud: $(cat "${STATE_DIR}/${SERVER_NAME}_fip_out.text")" fi exit 1 fi log_success "Assigned floating IP '$FLOATING_IP_NAME' (id=$FIP_ID) → $SERVER_NAME (id=$SERVER_ID)" {%- endif %} # ============================================================================ # SUCCESS # ============================================================================ log_success "✓ Server provisioning complete" log_info "Summary:" log_info " • Server: $SERVER_NAME (ID: $SERVER_ID)" log_info " • Firewall: $FIREWALL_NAME" {%- if srv.floating_ip is defined and srv.floating_ip %} log_info " • Float IP: {{ srv.floating_ip }} (id=$FIP_ID)" {%- endif %} log_info " • Status: Ready for deployment"