From b4286e8186bd0e5e271ec32a7dd2b3416fb067ea Mon Sep 17 00:00:00 2001 From: Oscar Hermoso Date: Sun, 8 Feb 2026 10:47:45 +0800 Subject: [PATCH 1/9] Bump k8s to v1.35, automount service account token --- README.md | 6 ++-- .../Chart.yaml | 4 +-- .../README.md | 1 + .../tests/deployment_test.yaml | 20 +++++++++++ deploy/kubernetes/deployment.yaml | 2 +- deploy/kubernetes/rbac.yaml | 34 +++++++++++++++---- scripts/deploy-cluster.sh | 6 ++-- 7 files changed, 58 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 232b06c..ca73a8e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ kubectl create secret generic binarylane-api-token \ # Install the chart from GitHub Container Registry helm install binarylane-ccm \ oci://ghcr.io/oscarhermoso/charts/binarylane-cloud-controller-manager \ - --version 0.2.2 \ + --version 0.2.4 \ --namespace kube-system \ --set cloudControllerManager.secret.name="binarylane-api-token" ``` @@ -60,13 +60,13 @@ kubectl create secret generic binarylane-api-token \ 2. **Deploy the RBAC configuration:** ```bash -kubectl apply -f https://raw.githubusercontent.com/oscarhermoso/binarylane-cloud-controller-manager/main/deploy/kubernetes/rbac.yaml +kubectl apply -f https://raw.githubusercontent.com/oscarhermoso/binarylane-cloud-controller-manager/v0.2.4/deploy/kubernetes/rbac.yaml ``` 3. **Deploy the cloud controller manager:** ```bash -kubectl apply -f https://raw.githubusercontent.com/oscarhermoso/binarylane-cloud-controller-manager/main/deploy/kubernetes/deployment.yaml +kubectl apply -f https://raw.githubusercontent.com/oscarhermoso/binarylane-cloud-controller-manager/v0.2.4/deploy/kubernetes/deployment.yaml ``` ## Configuration diff --git a/charts/binarylane-cloud-controller-manager/Chart.yaml b/charts/binarylane-cloud-controller-manager/Chart.yaml index 6643df2..7d93062 100644 --- a/charts/binarylane-cloud-controller-manager/Chart.yaml +++ b/charts/binarylane-cloud-controller-manager/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: binarylane-cloud-controller-manager description: Kubernetes Cloud Controller Manager for BinaryLane type: application -version: 0.2.3 -appVersion: '0.2.3' +version: 0.2.4 +appVersion: '0.2.4' keywords: - kubernetes - cloud-controller-manager diff --git a/charts/binarylane-cloud-controller-manager/README.md b/charts/binarylane-cloud-controller-manager/README.md index 4c15e6b..15d7de3 100644 --- a/charts/binarylane-cloud-controller-manager/README.md +++ b/charts/binarylane-cloud-controller-manager/README.md @@ -19,6 +19,7 @@ The following table lists the configurable parameters of the chart and their def | `cloudControllerManager.secret.name` | Name of secret containing API token | `""` | | `cloudControllerManager.secret.key` | Key in secret for API token | `api-token` | | `serviceAccount.create` | Create service account | `true` | +| `serviceAccount.automount` | Automount SA token | `true` | | `serviceAccount.name` | Service account name | Generated from template | | `resources.limits.cpu` | CPU limit | `200m` | | `resources.limits.memory` | Memory limit | `128Mi` | diff --git a/charts/binarylane-cloud-controller-manager/tests/deployment_test.yaml b/charts/binarylane-cloud-controller-manager/tests/deployment_test.yaml index c84b2f8..2d9da0c 100644 --- a/charts/binarylane-cloud-controller-manager/tests/deployment_test.yaml +++ b/charts/binarylane-cloud-controller-manager/tests/deployment_test.yaml @@ -2,6 +2,7 @@ suite: test deployment templates: - deployment.yaml - rbac.yaml + - serviceaccount.yaml - test-secret.yaml tests: - it: should create a deployment with correct replicas @@ -191,3 +192,22 @@ tests: - equal: path: spec.template.spec.containers[0].resources.requests.cpu value: '200m' + + - it: should set automountServiceAccountToken by default + template: serviceaccount.yaml + set: + cloudControllerManager.secret.name: 'binarylane-api-token' + asserts: + - equal: + path: automountServiceAccountToken + value: true + + - it: should set automountServiceAccountToken when configured + template: serviceaccount.yaml + set: + cloudControllerManager.secret.name: 'binarylane-api-token' + serviceAccount.automount: false + asserts: + - equal: + path: automountServiceAccountToken + value: false diff --git a/deploy/kubernetes/deployment.yaml b/deploy/kubernetes/deployment.yaml index f6834ae..de3b68e 100644 --- a/deploy/kubernetes/deployment.yaml +++ b/deploy/kubernetes/deployment.yaml @@ -56,7 +56,7 @@ spec: operator: Exists containers: - name: binarylane-cloud-controller-manager - image: ghcr.io/oscarhermoso/binarylane-cloud-controller-manager:latest + image: ghcr.io/oscarhermoso/binarylane-cloud-controller-manager:v0.2.4 imagePullPolicy: IfNotPresent command: - '/binarylane-cloud-controller-manager' diff --git a/deploy/kubernetes/rbac.yaml b/deploy/kubernetes/rbac.yaml index 82c3a9b..2503a0d 100644 --- a/deploy/kubernetes/rbac.yaml +++ b/deploy/kubernetes/rbac.yaml @@ -30,31 +30,38 @@ rules: resources: - nodes verbs: - - '*' + - get + - list + - watch + - update + - patch - apiGroups: - '' resources: - nodes/status verbs: - patch + - update - apiGroups: - '' resources: - services verbs: + - get - list + - watch - patch - update - - watch - apiGroups: - '' resources: - services/status verbs: + - get - list + - watch - patch - update - - watch - apiGroups: - '' resources: @@ -62,6 +69,15 @@ rules: verbs: - create - get + - list + - watch + - update + - apiGroups: + - '' + resources: + - serviceaccounts/token + verbs: + - create - apiGroups: - '' resources: @@ -69,8 +85,8 @@ rules: verbs: - get - list - - update - watch + - update - apiGroups: - '' resources: @@ -81,6 +97,14 @@ rules: - list - watch - update + - apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch - apiGroups: - coordination.k8s.io resources: @@ -88,8 +112,6 @@ rules: verbs: - create - get - - list - - watch - update - apiGroups: - '' diff --git a/scripts/deploy-cluster.sh b/scripts/deploy-cluster.sh index de16a00..8bedf91 100755 --- a/scripts/deploy-cluster.sh +++ b/scripts/deploy-cluster.sh @@ -419,9 +419,9 @@ set -euo pipefail curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null -curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg -echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list -KUBE_VERSION="1.33.3-1.1" +curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.35/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg +echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.35/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list +KUBE_VERSION="1.35.0-1.1" apt-get update From b3af2d77853a34b6f867bafe8551240875628671 Mon Sep 17 00:00:00 2001 From: Oscar Hermoso Date: Sun, 8 Feb 2026 11:43:09 +0800 Subject: [PATCH 2/9] Fix cloud-controller-manager tests --- AGENTS.md | 7 ++- .../README.md | 2 +- .../templates/deployment.yaml | 21 +++++-- .../tests/deployment_test.yaml | 58 +++++++++++++++++++ .../values-example.yaml | 2 + .../values.yaml | 11 ++-- deploy/kubernetes/deployment.yaml | 56 ++++++++++++++---- deploy/kubernetes/rbac.yaml | 37 ++++++------ deploy/kubernetes/secret.yaml | 2 + 9 files changed, 156 insertions(+), 40 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fe5595e..15b28d4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,10 +5,13 @@ - Don't include comments unless they add meaningful context. - Use the `errors` package for sentinel errors. - Don't ignore errors by assigning to `_`. +- Ensure any defaults are the same in deploy/ and charts/ ## Testing instructions -- Fix any test or type errors until everything succeeds. +- Tests are defined in: + - `charts/binarylane-cloud-controller-manager/tests` + - `scripts/e2e-tests.sh` + - `internal/**/*_test.go` - Add or update tests for the code you change, even if nobody asked. -- Update Go tests and scripts/e2e-tests.sh as needed. - Run deploy-cluster.sh before e2e-tests.sh, and then delete-cluster.sh after. - Run golangci-lint locally to ensure no linting errors. diff --git a/charts/binarylane-cloud-controller-manager/README.md b/charts/binarylane-cloud-controller-manager/README.md index 15d7de3..4385875 100644 --- a/charts/binarylane-cloud-controller-manager/README.md +++ b/charts/binarylane-cloud-controller-manager/README.md @@ -16,7 +16,7 @@ The following table lists the configurable parameters of the chart and their def | `image.repository` | Image repository | `ghcr.io/oscarhermoso/binarylane-cloud-controller-manager` | | `image.pullPolicy` | Image pull policy | `IfNotPresent` | | `image.tag` | Image tag | Chart appVersion | -| `cloudControllerManager.secret.name` | Name of secret containing API token | `""` | +| `cloudControllerManager.secret.name` | Name of secret containing API token | `"binarylane-api-token"` | | `cloudControllerManager.secret.key` | Key in secret for API token | `api-token` | | `serviceAccount.create` | Create service account | `true` | | `serviceAccount.automount` | Automount SA token | `true` | diff --git a/charts/binarylane-cloud-controller-manager/templates/deployment.yaml b/charts/binarylane-cloud-controller-manager/templates/deployment.yaml index 699c2aa..607411f 100644 --- a/charts/binarylane-cloud-controller-manager/templates/deployment.yaml +++ b/charts/binarylane-cloud-controller-manager/templates/deployment.yaml @@ -41,13 +41,14 @@ spec: securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - - name: cloud-controller-manager + - name: binarylane-cloud-controller-manager securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} command: - /binarylane-cloud-controller-manager + - --allow-untagged-cloud - --cloud-provider=binarylane - --leader-elect=true - --use-service-account-credentials=true @@ -66,14 +67,24 @@ spec: {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} - {{- with .Values.extraVolumeMounts }} volumeMounts: + - mountPath: /etc/kubernetes/ + name: k8s + - mountPath: /etc/kubernetes/config + name: cloud-config + {{- with .Values.extraVolumeMounts }} {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.extraVolumes }} + {{- end }} volumes: + - name: k8s + hostPath: + path: /etc/kubernetes + - name: cloud-config + hostPath: + path: /etc/kubernetes/config + {{- with .Values.extraVolumes }} {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/binarylane-cloud-controller-manager/tests/deployment_test.yaml b/charts/binarylane-cloud-controller-manager/tests/deployment_test.yaml index 2d9da0c..cf023d8 100644 --- a/charts/binarylane-cloud-controller-manager/tests/deployment_test.yaml +++ b/charts/binarylane-cloud-controller-manager/tests/deployment_test.yaml @@ -211,3 +211,61 @@ tests: - equal: path: automountServiceAccountToken value: false + + - it: should use correct container name + template: deployment.yaml + set: + cloudControllerManager.secret.name: 'binarylane-api-token' + asserts: + - equal: + path: spec.template.spec.containers[0].name + value: binarylane-cloud-controller-manager + + - it: should include --allow-untagged-cloud flag + template: deployment.yaml + set: + cloudControllerManager.secret.name: 'binarylane-api-token' + asserts: + - contains: + path: spec.template.spec.containers[0].command + content: --allow-untagged-cloud + + - it: should mount kubernetes config volumes + template: deployment.yaml + set: + cloudControllerManager.secret.name: 'binarylane-api-token' + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: k8s + hostPath: + path: /etc/kubernetes + - contains: + path: spec.template.spec.volumes + content: + name: cloud-config + hostPath: + path: /etc/kubernetes/config + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /etc/kubernetes/ + name: k8s + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /etc/kubernetes/config + name: cloud-config + + - it: should use default secret name + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: BINARYLANE_API_TOKEN + valueFrom: + secretKeyRef: + name: binarylane-api-token + key: api-token diff --git a/charts/binarylane-cloud-controller-manager/values-example.yaml b/charts/binarylane-cloud-controller-manager/values-example.yaml index f18e703..bb0da3f 100644 --- a/charts/binarylane-cloud-controller-manager/values-example.yaml +++ b/charts/binarylane-cloud-controller-manager/values-example.yaml @@ -60,6 +60,8 @@ nodeSelector: # Tolerations - allow scheduling on control plane nodes tolerations: + - key: CriticalAddonsOnly + operator: Exists - effect: NoSchedule key: node-role.kubernetes.io/control-plane operator: Exists diff --git a/charts/binarylane-cloud-controller-manager/values.yaml b/charts/binarylane-cloud-controller-manager/values.yaml index 6a00722..ef0a171 100644 --- a/charts/binarylane-cloud-controller-manager/values.yaml +++ b/charts/binarylane-cloud-controller-manager/values.yaml @@ -21,7 +21,7 @@ cloudControllerManager: # Name of secret containing BinaryLane API token # Create: kubectl create secret generic binarylane-api-token --from-literal=api-token="YOUR_TOKEN" -n kube-system secret: - name: '' + name: 'binarylane-api-token' key: 'api-token' serviceAccount: @@ -53,12 +53,12 @@ securityContext: readOnlyRootFilesystem: true resources: - limits: - cpu: 200m - memory: 128Mi requests: cpu: 100m memory: 64Mi + limits: + cpu: 500m + memory: 1Gi # Node selector for pod assignment nodeSelector: @@ -66,6 +66,9 @@ nodeSelector: # Tolerations for pod assignment tolerations: + # Tolerate CriticalAddonsOnly taint + - key: CriticalAddonsOnly + operator: Exists # Tolerate control-plane taint (Kubernetes 1.24+) - effect: NoSchedule key: node-role.kubernetes.io/control-plane diff --git a/deploy/kubernetes/deployment.yaml b/deploy/kubernetes/deployment.yaml index de3b68e..62d07e2 100644 --- a/deploy/kubernetes/deployment.yaml +++ b/deploy/kubernetes/deployment.yaml @@ -5,27 +5,37 @@ metadata: namespace: kube-system labels: app.kubernetes.io/name: binarylane-cloud-controller-manager + app.kubernetes.io/instance: binarylane-cloud-controller-manager + app.kubernetes.io/version: '0.2.4' app.kubernetes.io/component: cloud-controller-manager app.kubernetes.io/part-of: kubernetes spec: replicas: 1 # Adjust the number of replicas as needed - revisionHistoryLimit: 2 strategy: type: Recreate selector: matchLabels: app.kubernetes.io/name: binarylane-cloud-controller-manager + app.kubernetes.io/instance: binarylane-cloud-controller-manager template: metadata: labels: app.kubernetes.io/name: binarylane-cloud-controller-manager + app.kubernetes.io/instance: binarylane-cloud-controller-manager + app.kubernetes.io/version: '0.2.4' app.kubernetes.io/component: cloud-controller-manager app.kubernetes.io/part-of: kubernetes spec: dnsPolicy: Default hostNetwork: true - serviceAccountName: cloud-controller-manager + serviceAccountName: binarylane-cloud-controller-manager priorityClassName: system-cluster-critical + securityContext: + runAsNonRoot: true + runAsUser: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: @@ -33,7 +43,10 @@ spec: labelSelector: matchLabels: app.kubernetes.io/name: binarylane-cloud-controller-manager + app.kubernetes.io/instance: binarylane-cloud-controller-manager tolerations: + - key: CriticalAddonsOnly + operator: Exists - effect: NoSchedule key: node-role.kubernetes.io/control-plane operator: Exists @@ -54,16 +67,25 @@ spec: - effect: NoSchedule key: node.kubernetes.io/not-ready operator: Exists + nodeSelector: + node-role.kubernetes.io/control-plane: '' containers: - name: binarylane-cloud-controller-manager - image: ghcr.io/oscarhermoso/binarylane-cloud-controller-manager:v0.2.4 + image: ghcr.io/oscarhermoso/binarylane-cloud-controller-manager:0.2.4 imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true command: - - '/binarylane-cloud-controller-manager' - - '--cloud-provider=binarylane' - - '--leader-elect=true' - - '--use-service-account-credentials=true' - - '--v=2' + - /binarylane-cloud-controller-manager + - --allow-untagged-cloud + - --cloud-provider=binarylane + - --leader-elect=true + - --use-service-account-credentials=true + - --v=2 env: - name: BINARYLANE_API_TOKEN valueFrom: @@ -73,7 +95,19 @@ spec: resources: requests: cpu: 100m - memory: 50Mi + memory: 64Mi limits: - cpu: 200m - memory: 100Mi + cpu: 500m + memory: 1Gi + volumeMounts: + - mountPath: /etc/kubernetes/ + name: k8s + - mountPath: /etc/kubernetes/config + name: cloud-config + volumes: + - name: k8s + hostPath: + path: /etc/kubernetes + - name: cloud-config + hostPath: + path: /etc/kubernetes/config diff --git a/deploy/kubernetes/rbac.yaml b/deploy/kubernetes/rbac.yaml index 2503a0d..b27e357 100644 --- a/deploy/kubernetes/rbac.yaml +++ b/deploy/kubernetes/rbac.yaml @@ -1,19 +1,23 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: cloud-controller-manager + name: binarylane-cloud-controller-manager namespace: kube-system labels: app.kubernetes.io/name: binarylane-cloud-controller-manager + app.kubernetes.io/instance: binarylane-cloud-controller-manager + app.kubernetes.io/version: '0.2.4' app.kubernetes.io/component: cloud-controller-manager app.kubernetes.io/part-of: kubernetes --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: system:cloud-controller-manager + name: binarylane-cloud-controller-manager labels: app.kubernetes.io/name: binarylane-cloud-controller-manager + app.kubernetes.io/instance: binarylane-cloud-controller-manager + app.kubernetes.io/version: '0.2.4' app.kubernetes.io/component: cloud-controller-manager app.kubernetes.io/part-of: kubernetes rules: @@ -57,9 +61,6 @@ rules: resources: - services/status verbs: - - get - - list - - watch - patch - update - apiGroups: @@ -97,21 +98,13 @@ rules: - list - watch - update - - apiGroups: - - '' - resources: - - configmaps - verbs: - - get - - list - - watch - apiGroups: - coordination.k8s.io resources: - leases verbs: - - create - get + - create - update - apiGroups: - '' @@ -121,20 +114,30 @@ rules: - get - list - watch + - apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: system:cloud-controller-manager + name: binarylane-cloud-controller-manager labels: app.kubernetes.io/name: binarylane-cloud-controller-manager + app.kubernetes.io/instance: binarylane-cloud-controller-manager + app.kubernetes.io/version: '0.2.4' app.kubernetes.io/component: cloud-controller-manager app.kubernetes.io/part-of: kubernetes roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: system:cloud-controller-manager + name: binarylane-cloud-controller-manager subjects: - kind: ServiceAccount - name: cloud-controller-manager + name: binarylane-cloud-controller-manager namespace: kube-system diff --git a/deploy/kubernetes/secret.yaml b/deploy/kubernetes/secret.yaml index 17d99fd..fa79f95 100644 --- a/deploy/kubernetes/secret.yaml +++ b/deploy/kubernetes/secret.yaml @@ -5,6 +5,8 @@ metadata: namespace: kube-system labels: app.kubernetes.io/name: binarylane-cloud-controller-manager + app.kubernetes.io/instance: binarylane-cloud-controller-manager + app.kubernetes.io/version: '0.2.4' app.kubernetes.io/component: cloud-controller-manager app.kubernetes.io/part-of: kubernetes type: Opaque From e5bc227c46d4806b8a671e3d876f8e31b24e30ba Mon Sep 17 00:00:00 2001 From: Oscar Hermoso Date: Sun, 8 Feb 2026 12:26:29 +0800 Subject: [PATCH 3/9] Move k8s installation into user_data script --- AGENTS.md | 3 +- scripts/deploy-cluster.sh | 282 ++++++++++++++++---------------------- 2 files changed, 123 insertions(+), 162 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 15b28d4..1c8b253 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,10 +8,11 @@ - Ensure any defaults are the same in deploy/ and charts/ ## Testing instructions +- Add or update tests for the code you change, even if nobody asked. - Tests are defined in: - `charts/binarylane-cloud-controller-manager/tests` - `scripts/e2e-tests.sh` - `internal/**/*_test.go` -- Add or update tests for the code you change, even if nobody asked. - Run deploy-cluster.sh before e2e-tests.sh, and then delete-cluster.sh after. - Run golangci-lint locally to ensure no linting errors. +- Before finishing, run tests and ensure they pass. diff --git a/scripts/deploy-cluster.sh b/scripts/deploy-cluster.sh index 8bedf91..6fafc65 100755 --- a/scripts/deploy-cluster.sh +++ b/scripts/deploy-cluster.sh @@ -233,20 +233,101 @@ create_server() { # Generate random password to avoid email notifications local random_password=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32) - local data=$(cat < /dev/null + +curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.35/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg +echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.35/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list +KUBE_VERSION="1.35.0-1.1" + +apt-get update + +# Install required packages +apt-get install -y --no-install-recommends \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + containerd.io \ + kubelet=$KUBE_VERSION kubeadm=$KUBE_VERSION kubectl=$KUBE_VERSION + +apt-mark hold kubelet kubeadm kubectl + +# Configure containerd +mkdir -p /etc/containerd +containerd config default | tee /etc/containerd/config.toml > /dev/null +systemctl restart containerd + +# Disable swap +swapoff -a +sed -i '/ swap / s/^/#/' /etc/fstab + +# Load kernel modules +cat > /etc/modules-load.d/k8s.conf <<'EOL' +overlay +br_netfilter +EOL +modprobe overlay +modprobe br_netfilter + +# Set kernel parameters +cat > /etc/sysctl.d/k8s.conf <<'EOL' +net.bridge.bridge-nf-call-iptables = 1 +net.bridge.bridge-nf-call-ip6tables = 1 +net.ipv4.ip_forward = 1 +EOL +sysctl --system + +# Enable kubelet service +systemctl enable kubelet + +# Configure kubelet to use external cloud provider +mkdir -p /etc/systemd/system/kubelet.service.d +cat > /etc/systemd/system/kubelet.service.d/20-cloud-provider.conf <<'EOL' +[Service] +Environment="KUBELET_EXTRA_ARGS=--cloud-provider=external" +EOL +systemctl daemon-reload + +# Pre-pull Kubernetes images +kubeadm config images pull + +echo "Kubernetes installation complete" +USERDATA ) + # Escape user_data for JSON (escape backslashes, quotes, and convert newlines) + local user_data_json=$(echo "$user_data_script" | jq -Rs .) + + # Create JSON payload + local data=$(jq -n \ + --arg name "$name" \ + --arg region "$REGION" \ + --arg size "$SERVER_SIZE" \ + --argjson image "$image_id" \ + --argjson ssh_key "$SSH_KEY_ID" \ + --arg password "$random_password" \ + --argjson vpc "$VPC_ID" \ + --argjson user_data "$user_data_json" \ + '{ + name: $name, + region: $region, + size: $size, + image: $image, + ssh_keys: [$ssh_key], + password: $password, + backups: false, + vpc_id: $vpc, + user_data: $user_data + }' + ) + log_info "Creating with: region=$REGION, size=$SERVER_SIZE, image=$image_id, ssh_key=$SSH_KEY_ID, vpc_id=$VPC_ID" local response=$(api_call POST "/servers" "$data") @@ -281,6 +362,34 @@ EOF fi log_success "Created server: $name (ID: $server_id, IP: $server_ip)" + + # Remove old SSH host key (BinaryLane recycles IPs) + ssh-keygen -f "$HOME/.ssh/known_hosts" -R "$server_ip" 2>/dev/null || true + + # Wait for SSH to be available + log_info "Waiting for SSH on $name ($server_ip)..." + local ssh_attempts=0 + while [ $ssh_attempts -lt 60 ]; do + if ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ConnectTimeout=3 -o BatchMode=yes root@$server_ip "echo 'SSH ready'" 2>/dev/null; then + log_success "SSH ready on $name" + break + fi + echo -n "." >&2 + sleep 2 + ssh_attempts=$((ssh_attempts + 1)) + done + echo "" >&2 + + # Wait for cloud-init to complete + log_info "Waiting for cloud-init to complete on $name..." + ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no root@$server_ip bash <<'EOSSH' +tail -f /var/log/cloud-init-output.log & +TAIL_PID=$! +cloud-init status --wait >/dev/null || cloud-init status --format json +kill $TAIL_PID 2>/dev/null || true +EOSSH + log_success "Cloud-init complete on $name" + echo "$server_id:$server_ip" } @@ -365,119 +474,6 @@ get_or_create_servers() { log_success "All servers ready" } -wait_for_ssh() { - local ip="$1" - local hostname="${2:-server}" - local max_attempts=60 - local attempt=0 - - log_info "Waiting for SSH on $hostname ($ip)..." - - while [ $attempt -lt $max_attempts ]; do - if ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ConnectTimeout=3 -o BatchMode=yes root@$ip "echo 'SSH ready'" 2>/dev/null; then - echo "" >&2 # New line after dots - log_success "SSH ready on $hostname ($ip)" - return 0 - fi - - # Show progress every 12 attempts - if [ $((attempt % 12)) -eq 0 ] && [ $attempt -gt 0 ]; then - log_info "Still waiting for SSH (attempt $attempt/$max_attempts)..." >&2 - fi - - echo -n "." >&2 - sleep 2 - attempt=$((attempt + 1)) - done - - echo "" >&2 # New line after dots - log_error "SSH did not become ready on $hostname ($ip) after $((max_attempts * 2)) seconds" - log_error "Please verify:" - log_error " 1. SSH key is added to your BinaryLane account" - log_error " 2. Server can be accessed at: ssh root@$ip" - log_error " 3. Security groups allow SSH access" - return 1 -} - -install_kubernetes_prerequisites() { - local ip="$1" - local hostname="${2:-server}" - - log_info "Installing Kubernetes prerequisites on $hostname ($ip)..." - - # Check if already installed - if ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes root@$ip "which kubeadm" &>/dev/null; then - log_info "Kubernetes already installed on $hostname" - return 0 - fi - - ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no root@$ip bash <<'EOF' -set -euo pipefail - -# Update system - -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg -echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null - -curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.35/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg -echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.35/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list -KUBE_VERSION="1.35.0-1.1" - -apt-get update - -# Install required packages -apt-get install -y --no-install-recommends \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg \ - containerd.io \ - kubelet=$KUBE_VERSION kubeadm=$KUBE_VERSION kubectl=$KUBE_VERSION - -apt-mark hold kubelet kubeadm kubectl - -# Configure containerd -mkdir -p /etc/containerd -containerd config default | tee /etc/containerd/config.toml > /dev/null -systemctl restart containerd - -# Disable swap -swapoff -a -sed -i '/ swap / s/^/#/' /etc/fstab - -# Load kernel modules -cat > /etc/modules-load.d/k8s.conf <<'EOL' -overlay -br_netfilter -EOL -modprobe overlay -modprobe br_netfilter - -# Set kernel parameters -cat > /etc/sysctl.d/k8s.conf <<'EOL' -net.bridge.bridge-nf-call-iptables = 1 -net.bridge.bridge-nf-call-ip6tables = 1 -net.ipv4.ip_forward = 1 -EOL -sysctl --system - -# Enable kubelet service -systemctl enable kubelet - -# Configure kubelet to use external cloud provider -mkdir -p /etc/systemd/system/kubelet.service.d -cat > /etc/systemd/system/kubelet.service.d/20-cloud-provider.conf <<'EOL' -[Service] -Environment="KUBELET_EXTRA_ARGS=--cloud-provider=external" -EOL -systemctl daemon-reload - -echo "Kubernetes installation complete" -EOF - - log_success "Kubernetes installed on $hostname" -} - initialize_control_plane() { log_info "Initializing Kubernetes control plane..." @@ -774,42 +770,6 @@ main() { get_or_create_servers - # Wait for SSH on all servers in parallel - log_info "Waiting for SSH on all nodes in parallel..." - declare -a ssh_pids - - wait_for_ssh $CONTROL_PLANE_IP "${CLUSTER_NAME}-control-1" & - ssh_pids+=( $! ) - - for i in "${!WORKER_IPS[@]}"; do - wait_for_ssh "${WORKER_IPS[$i]}" "${CLUSTER_NAME}-worker-$((i+1))" & - ssh_pids+=( $! ) - done - - # Wait for all SSH connections - for pid in "${ssh_pids[@]}"; do - wait $pid || { log_error "Failed to connect to a node via SSH"; exit 1; } - done - log_success "All nodes are accessible via SSH" - - # Install Kubernetes prerequisites on all nodes in parallel - log_info "Installing Kubernetes prerequisites on all nodes in parallel..." - install_kubernetes_prerequisites $CONTROL_PLANE_IP "${CLUSTER_NAME}-control-1" & - local control_k8s_pid=$! - - declare -a k8s_pids - for i in "${!WORKER_IPS[@]}"; do - install_kubernetes_prerequisites "${WORKER_IPS[$i]}" "${CLUSTER_NAME}-worker-$((i+1))" & - k8s_pids+=( $! ) - done - - # Wait for all Kubernetes installations to complete - wait $control_k8s_pid || { log_error "Failed to install Kubernetes on control plane"; exit 1; } - for pid in "${k8s_pids[@]}"; do - wait $pid || { log_error "Failed to install Kubernetes on worker node"; exit 1; } - done - log_success "Kubernetes installed on all nodes" - initialize_control_plane join_worker_nodes From a284c09feee7d71d8e6ba3ed842f935183f6be2f Mon Sep 17 00:00:00 2001 From: Oscar Hermoso Date: Sun, 8 Feb 2026 12:47:15 +0800 Subject: [PATCH 4/9] Fix client_loop: send disconnect: Broken pipe --- scripts/deploy-cluster.sh | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/scripts/deploy-cluster.sh b/scripts/deploy-cluster.sh index 6fafc65..f63acc2 100755 --- a/scripts/deploy-cluster.sh +++ b/scripts/deploy-cluster.sh @@ -382,11 +382,12 @@ USERDATA # Wait for cloud-init to complete log_info "Waiting for cloud-init to complete on $name..." - ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no root@$server_ip bash <<'EOSSH' -tail -f /var/log/cloud-init-output.log & -TAIL_PID=$! + ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o ServerAliveCountMax=5 root@$server_ip bash <<'EOSSH' +set -e +# tail is noisy, uncomment for debugging +# tail -f /var/log/cloud-init-output.log 2>/dev/null || echo "Cloud-init log not yet available" cloud-init status --wait >/dev/null || cloud-init status --format json -kill $TAIL_PID 2>/dev/null || true +echo "Cloud-init completed successfully" EOSSH log_success "Cloud-init complete on $name" @@ -483,7 +484,7 @@ initialize_control_plane() { return 0 fi - ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no root@$CONTROL_PLANE_IP bash </dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'") - local token=$(ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no root@$CONTROL_PLANE_IP \ + local token=$(ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ServerAliveInterval=60 root@$CONTROL_PLANE_IP \ "kubeadm token create") - ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no root@$worker_ip bash < Date: Sun, 8 Feb 2026 12:57:09 +0800 Subject: [PATCH 5/9] Fix "ssh: Could not resolve hostname ssh" --- scripts/deploy-cluster.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/deploy-cluster.sh b/scripts/deploy-cluster.sh index f63acc2..971d975 100755 --- a/scripts/deploy-cluster.sh +++ b/scripts/deploy-cluster.sh @@ -478,13 +478,21 @@ get_or_create_servers() { initialize_control_plane() { log_info "Initializing Kubernetes control plane..." + # Validate CONTROL_PLANE_IP is set + if [ -z "${CONTROL_PLANE_IP:-}" ]; then + log_error "CONTROL_PLANE_IP is not set. Cannot initialize control plane." + return 1 + fi + + log_info "Control plane IP: $CONTROL_PLANE_IP" + # Check if cluster is already initialized - if ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no root@$CONTROL_PLANE_IP "test -f /etc/kubernetes/admin.conf" 2>/dev/null; then + if ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no root@"$CONTROL_PLANE_IP" "test -f /etc/kubernetes/admin.conf" 2>/dev/null; then log_info "Control plane already initialized" return 0 fi - ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ServerAliveInterval=60 root@$CONTROL_PLANE_IP bash < Date: Sat, 7 Mar 2026 09:42:55 +0800 Subject: [PATCH 6/9] Fix hostname contains invalid characters error --- scripts/deploy-cluster.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/deploy-cluster.sh b/scripts/deploy-cluster.sh index 971d975..efe4c61 100755 --- a/scripts/deploy-cluster.sh +++ b/scripts/deploy-cluster.sh @@ -370,7 +370,7 @@ USERDATA log_info "Waiting for SSH on $name ($server_ip)..." local ssh_attempts=0 while [ $ssh_attempts -lt 60 ]; do - if ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ConnectTimeout=3 -o BatchMode=yes root@$server_ip "echo 'SSH ready'" 2>/dev/null; then + if ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ConnectTimeout=3 -o BatchMode=yes root@$server_ip "exit 0" >/dev/null 2>&1; then log_success "SSH ready on $name" break fi From e01bdad440faad112eee6ce5158b57c96a3adc50 Mon Sep 17 00:00:00 2001 From: Oscar Hermoso Date: Sat, 7 Mar 2026 09:49:12 +0800 Subject: [PATCH 7/9] Update openapi schema --- internal/binarylane/types_gen.go | 6 +- openapi.json | 253 ++++++++++++++++++++----------- 2 files changed, 170 insertions(+), 89 deletions(-) diff --git a/internal/binarylane/types_gen.go b/internal/binarylane/types_gen.go index c80f3d3..ade4d4f 100644 --- a/internal/binarylane/types_gen.go +++ b/internal/binarylane/types_gen.go @@ -1141,7 +1141,7 @@ type ChangeSizeOptionsRequest struct { // If specified this is the absolute value, not just the additional transfer above what is included in the size. // Leave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options. // - // Valid values (when converted to GB by multiplying the value provided by 1024): + // Valid values (when converted to GB by multiplying the value provided by 1000): // - must be a multiple of 5GB // - > 30GB must be a multiple of 10 // - > 200GB must be a multiple of 100 @@ -2877,7 +2877,7 @@ type SizeOptions struct { // TransferCostPerAdditionalGigabyte The additional cost per GB per month for additional included transfer. TransferCostPerAdditionalGigabyte float64 `json:"transfer_cost_per_additional_gigabyte"` - // TransferMax The maximum transfer in TB permitted for this size. + // TransferMax The maximum transfer in TB permitted for this size. If this is the same as Size.Transfer no additional transfer is supported. TransferMax float64 `json:"transfer_max"` // WeeklyBackups The number of weekly backups included in the base size cost. @@ -2923,7 +2923,7 @@ type SizeOptionsRequest struct { // If specified this is the absolute value, not just the additional transfer above what is included in the size. // Leave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options. // - // Valid values (when converted to GB by multiplying the value provided by 1024): + // Valid values (when converted to GB by multiplying the value provided by 1000): // - must be a multiple of 5GB // - > 30GB must be a multiple of 10 // - > 200GB must be a multiple of 100 diff --git a/openapi.json b/openapi.json index eb14f1e..6d13b36 100644 --- a/openapi.json +++ b/openapi.json @@ -8,7 +8,7 @@ "url": "https://support.binarylane.com.au/support/home", "email": "support@binarylane.com.au" }, - "version": "0.34.0", + "version": "0.34.4", "x-logo": { "url": "https://www.binarylane.com.au/res/images/binarylane/logo.png", "backgroundColor": "#000000", @@ -407,7 +407,8 @@ "type": "integer", "description": "The target server id.", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "responses": { @@ -1814,7 +1815,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "load-balancer list" } ], "responses": { @@ -1865,7 +1867,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "load-balancer list" } ], "requestBody": { @@ -1938,7 +1941,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "load-balancer list" } ], "responses": { @@ -2122,7 +2126,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "load-balancer list" } ], "requestBody": { @@ -2188,7 +2193,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "load-balancer list" } ], "requestBody": { @@ -2235,7 +2241,7 @@ "x-codeSamples": [ { "lang": "Curl", - "source": "curl -X DELETE \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}/servers\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + "source": "curl -X DELETE \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}/servers\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" } ], "x-cli-command": "load-balancer server delete" @@ -2256,7 +2262,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "load-balancer list" } ], "requestBody": { @@ -2322,7 +2329,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "load-balancer list" } ], "requestBody": { @@ -2369,7 +2377,7 @@ "x-codeSamples": [ { "lang": "Curl", - "source": "curl -X DELETE \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}/forwarding_rules\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + "source": "curl -X DELETE \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}/forwarding_rules\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" } ], "x-cli-command": "load-balancer rule delete" @@ -2527,6 +2535,9 @@ } } }, + "204": { + "description": "No Content" + }, "400": { "description": "Bad Request", "content": { @@ -2577,7 +2588,8 @@ "type": "integer", "description": "The target server id.", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" }, { "name": "data_interval", @@ -2642,7 +2654,8 @@ "type": "integer", "description": "The target server id.", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" }, { "name": "data_interval", @@ -2760,7 +2773,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" }, { "name": "page", @@ -2837,7 +2851,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -3089,7 +3104,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "responses": { @@ -3139,7 +3155,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" }, { "name": "reason", @@ -3311,7 +3328,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" }, { "name": "action_id", @@ -3373,7 +3391,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "responses": { @@ -3425,7 +3444,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "responses": { @@ -3477,7 +3497,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" }, { "name": "page", @@ -3553,7 +3574,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -3629,7 +3651,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" }, { "name": "page", @@ -3708,7 +3731,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" }, { "name": "page", @@ -3787,7 +3811,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "responses": { @@ -3879,7 +3904,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" }, { "name": "page", @@ -3957,7 +3983,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "responses": { @@ -4009,7 +4036,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "responses": { @@ -4359,7 +4387,8 @@ "type": "integer", "description": "The target vpc id.", "format": "int64" - } + }, + "x-cli-entity-lookup": "vpc list" } ], "responses": { @@ -4411,7 +4440,8 @@ "type": "integer", "description": "The target vpc id.", "format": "int64" - } + }, + "x-cli-entity-lookup": "vpc list" } ], "requestBody": { @@ -4487,7 +4517,8 @@ "type": "integer", "description": "The target vpc id.", "format": "int64" - } + }, + "x-cli-entity-lookup": "vpc list" } ], "requestBody": { @@ -4561,7 +4592,8 @@ "type": "integer", "description": "The target vpc id.", "format": "int64" - } + }, + "x-cli-entity-lookup": "vpc list" } ], "responses": { @@ -4727,7 +4759,8 @@ "type": "integer", "description": "The target vpc id.", "format": "int64" - } + }, + "x-cli-entity-lookup": "vpc list" }, { "name": "resource_type", @@ -4818,7 +4851,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -4893,7 +4927,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -4968,7 +5003,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5043,7 +5079,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5118,7 +5155,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5193,7 +5231,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5268,7 +5307,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5343,7 +5383,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5418,7 +5459,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5493,7 +5535,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5568,7 +5611,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5643,7 +5687,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5718,7 +5763,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5793,7 +5839,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5868,7 +5915,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -5943,7 +5991,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6018,7 +6067,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6093,7 +6143,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6168,7 +6219,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6243,7 +6295,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6318,7 +6371,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6393,7 +6447,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6468,7 +6523,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6543,7 +6599,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6618,7 +6675,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6693,7 +6751,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6768,7 +6827,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6843,7 +6903,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6918,7 +6979,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -6983,7 +7045,7 @@ "ServerActions" ], "summary": "Create an Additional Disk for a Server", - "description": "This is used to add an additional disk in unallocated storage space. \nThis does not alter the total disk space available to your server: to add additional disk space for your server use the 'Resize' action. \n \n", + "description": "This is used to add an additional disk in unallocated storage space. \nThis does not alter the total disk space available to your server: to add additional disk space for your server use the 'Resize' action. \n**This action may require the server to be rebooted.** \n \n", "parameters": [ { "name": "server_id", @@ -6993,7 +7055,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7068,7 +7131,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7133,7 +7197,7 @@ "ServerActions" ], "summary": "Delete an Additional Disk for a Server", - "description": "This is used to delete a disk added using the 'AddDisk' action. \n**NB: This is a destructive operation and no further confirmation will be requested.** \n \n", + "description": "This is used to delete a disk added using the 'AddDisk' action. \n**NB: This is a destructive operation and no further confirmation will be requested.** \n**This action may require the server to be rebooted.** \n \n", "parameters": [ { "name": "server_id", @@ -7143,7 +7207,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7218,7 +7283,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7293,7 +7359,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7368,7 +7435,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7443,7 +7511,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7518,7 +7587,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7593,7 +7663,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7668,7 +7739,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7743,7 +7815,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7818,7 +7891,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -7893,7 +7967,8 @@ "schema": { "type": "integer", "format": "int64" - } + }, + "x-cli-entity-lookup": "server list" } ], "requestBody": { @@ -9328,7 +9403,7 @@ }, "transfer": { "type": "number", - "description": "The total transfer per month in TB for this server.\nIf specified this is the absolute value, not just the additional transfer above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values (when converted to GB by multiplying the value provided by 1024):\n- must be a multiple of 5GB\n- > 30GB must be a multiple of 10\n- > 200GB must be a multiple of 100\n- > 2000GB must be a multiple of 1000", + "description": "The total transfer per month in TB for this server.\nIf specified this is the absolute value, not just the additional transfer above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values (when converted to GB by multiplying the value provided by 1000):\n- must be a multiple of 5GB\n- > 30GB must be a multiple of 10\n- > 200GB must be a multiple of 100\n- > 2000GB must be a multiple of 1000", "format": "double", "nullable": true }, @@ -11277,7 +11352,8 @@ }, "description": "The server IDs of the servers that are currently in the load balancer pool (regardless of their current 'health')." } - } + }, + "x-cli-entity-ref": "name" }, "LoadBalancerAvailabilityOption": { "required": [ @@ -11395,7 +11471,8 @@ "$ref": "#/components/schemas/LoadBalancer" } } - } + }, + "x-cli-entity-list": "load_balancers" }, "LocalNameserversResponse": { "required": [ @@ -12657,7 +12734,8 @@ ], "description": "The currently enabled advanced features, machine type and processor flags." } - } + }, + "x-cli-entity-ref": "name" }, "ServerAction": { "required": [ @@ -12922,7 +13000,8 @@ "$ref": "#/components/schemas/Server" } } - } + }, + "x-cli-entity-list": "servers" }, "Shutdown": { "required": [ @@ -13118,7 +13197,7 @@ }, "transfer_max": { "type": "number", - "description": "The maximum transfer in TB permitted for this size.", + "description": "The maximum transfer in TB permitted for this size. If this is the same as Size.Transfer no additional transfer is supported.", "format": "double" }, "transfer_cost_per_additional_gigabyte": { @@ -13231,7 +13310,7 @@ }, "transfer": { "type": "number", - "description": "The total transfer per month in TB for this server.\nIf specified this is the absolute value, not just the additional transfer above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values (when converted to GB by multiplying the value provided by 1024):\n- must be a multiple of 5GB\n- > 30GB must be a multiple of 10\n- > 200GB must be a multiple of 100\n- > 2000GB must be a multiple of 1000", + "description": "The total transfer per month in TB for this server.\nIf specified this is the absolute value, not just the additional transfer above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values (when converted to GB by multiplying the value provided by 1000):\n- must be a multiple of 5GB\n- > 30GB must be a multiple of 10\n- > 200GB must be a multiple of 100\n- > 2000GB must be a multiple of 1000", "format": "double", "nullable": true } @@ -14148,7 +14227,8 @@ }, "description": "The route entries that control how network traffic is directed through the VPC environment." } - } + }, + "x-cli-entity-ref": "name" }, "VpcMember": { "required": [ @@ -14256,7 +14336,8 @@ "$ref": "#/components/schemas/Vpc" } } - } + }, + "x-cli-entity-list": "vpcs" } }, "securitySchemes": { From 87a6e2007fb5203955bf6285c16a64cc9e94cb30 Mon Sep 17 00:00:00 2001 From: Oscar Hermoso Date: Sat, 7 Mar 2026 10:01:53 +0800 Subject: [PATCH 8/9] Try fix hostname contains invalid characters error again --- scripts/deploy-cluster.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/deploy-cluster.sh b/scripts/deploy-cluster.sh index efe4c61..0bab189 100755 --- a/scripts/deploy-cluster.sh +++ b/scripts/deploy-cluster.sh @@ -364,7 +364,7 @@ USERDATA log_success "Created server: $name (ID: $server_id, IP: $server_ip)" # Remove old SSH host key (BinaryLane recycles IPs) - ssh-keygen -f "$HOME/.ssh/known_hosts" -R "$server_ip" 2>/dev/null || true + ssh-keygen -f "$HOME/.ssh/known_hosts" -R "$server_ip" >/dev/null 2>&1 || true # Wait for SSH to be available log_info "Waiting for SSH on $name ($server_ip)..." @@ -382,11 +382,11 @@ USERDATA # Wait for cloud-init to complete log_info "Waiting for cloud-init to complete on $name..." - ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o ServerAliveCountMax=5 root@$server_ip bash <<'EOSSH' + ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o ServerAliveCountMax=5 root@$server_ip bash >&2 <<'EOSSH' set -e # tail is noisy, uncomment for debugging # tail -f /var/log/cloud-init-output.log 2>/dev/null || echo "Cloud-init log not yet available" -cloud-init status --wait >/dev/null || cloud-init status --format json +cloud-init status --wait || cloud-init status --format json echo "Cloud-init completed successfully" EOSSH log_success "Cloud-init complete on $name" From d702150b37a031280a7f63a42d86df6f6e33542b Mon Sep 17 00:00:00 2001 From: Oscar Hermoso Date: Sat, 7 Mar 2026 10:09:45 +0800 Subject: [PATCH 9/9] Remove unnecessary install dependencies step --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97a8745..7c0b26d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -192,9 +192,6 @@ jobs: go-version: "1.25" cache: true - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y jq sshpass - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.12.0