···55echo "=== {{.DisplayName}} Setup: {{.BinaryName}} ==="
66echo "Started at $(date -u)"
7788-# Wait for DNS resolution
99-echo "Waiting for DNS..."
88+# Wait for network/DNS
109for i in $(seq 1 30); do
1111- if host go.dev >/dev/null 2>&1; then
1212- echo "DNS ready after ${i}s"
1010+ if getent hosts go.dev >/dev/null 2>&1; then
1111+ echo "Network ready after ${i}s"
1312 break
1413 fi
1514 sleep 1
+12-7
deploy/upcloud/provision.go
···44 "bufio"
55 "context"
66 "crypto/sha256"
77+ "encoding/base64"
78 "fmt"
89 "os"
910 "strings"
···891892 if remoteScript == "__MISSING__" {
892893 fmt.Printf(" cloud-init: not found on %s (server may need initial setup)\n", name)
893894 } else {
894894- localHash := fmt.Sprintf("%x", sha256.Sum256([]byte(localScript)))
895895+ localHash := fmt.Sprintf("%x", sha256.Sum256([]byte(strings.TrimSpace(localScript))))
895896 remoteHash := fmt.Sprintf("%x", sha256.Sum256([]byte(remoteScript)))
896897 if localHash == remoteHash {
897898 fmt.Printf(" cloud-init: up to date\n")
···913914 return nil
914915 }
915916917917+ // Write the reference file first so next provision can detect real diffs,
918918+ // regardless of whether the script execution succeeds or fails.
919919+ if err := writeRemoteCloudInit(ip, localScript); err != nil {
920920+ fmt.Printf(" WARNING: could not update remote cloud-init reference: %v\n", err)
921921+ }
922922+916923 fmt.Printf(" Running cloud-init on %s (%s)... (this may take several minutes)\n", name, ip)
917924 output, err := runSSH(ip, localScript, true)
918925 if err != nil {
···922929 }
923930924931 fmt.Printf(" %s: cloud-init complete\n", name)
925925-926926- // Write the script to the remote path so next provision can detect real diffs
927927- if err := writeRemoteCloudInit(ip, localScript); err != nil {
928928- fmt.Printf(" WARNING: could not update remote cloud-init reference: %v\n", err)
929929- }
930932 return nil
931933}
932934933935// writeRemoteCloudInit writes the local cloud-init script to the remote server
934936// so that subsequent provision runs can accurately detect real changes.
937937+// Uses base64 encoding to avoid heredoc nesting issues (the cloud-init script
938938+// itself contains heredocs like CFGEOF and SVCEOF).
935939func writeRemoteCloudInit(ip, script string) error {
936936- cmd := fmt.Sprintf("cat > %s << 'CLOUDINITEOF'\n%sCLOUDINITEOF", cloudInitPath, script)
940940+ encoded := base64.StdEncoding.EncodeToString([]byte(script))
941941+ cmd := fmt.Sprintf("mkdir -p $(dirname %s) && echo '%s' | base64 -d > %s", cloudInitPath, encoded, cloudInitPath)
937942 _, err := runSSH(ip, cmd, false)
938943 return err
939944}