nctiggy

spectrocloud-appliance-mode

1
0
# Install this skill:
npx skills add nctiggy/claude-skills --skill "spectrocloud-appliance-mode"

Install specific skill from multi-skill repository

# Description

Build and deploy Spectro Cloud Palette Edge clusters using Appliance Mode. Covers CanvOS builds, provider images, ISOs, content bundles, 2-node deployments, and CI/CD integration patterns.

# SKILL.md


name: spectrocloud-appliance-mode
description: Build and deploy Spectro Cloud Palette Edge clusters using Appliance Mode. Covers CanvOS builds, provider images, ISOs, content bundles, 2-node deployments, and CI/CD integration patterns.


Spectro Cloud Appliance Mode

Appliance Mode creates immutable edge deployments using Kairos-based images built with CanvOS.

When to Use

Use Case Mode
Immutable, locked-down edge Appliance
2-node HA clusters Appliance (only option)
Existing OS/golden images Agent
Quick testing on VMs Agent

Key Concepts

  • CanvOS: Earthly-based build system for edge artifacts
  • Provider Image: Container image with OS + specific K8s version
  • Installer ISO: Bootable image with Palette agent + user-data
  • Content Bundle: Pre-cached images for offline deployments

Prerequisites

  • Linux AMD64 build machine (4+ CPU, 8GB+ RAM, 150GB+ storage)
  • Git, Docker, Earthly
# Install Earthly
curl -fsSL https://releases.earthly.dev/earthly-linux-amd64 -o earthly
sudo mv earthly /usr/local/bin/ && sudo chmod +x /usr/local/bin/earthly
earthly bootstrap

Build Process

Ask the user:
1. Registry for provider images? (ttl.sh for testing, or specify)
2. K8s distribution? (k3s, rke2, kubeadm, kubeadm-fips)
3. 2-node deployment? (requires K3s + TWO_NODE=true)
4. Need content bundles for offline?

Step 0: Determine K8s Version

Query CanvOS for supported versions before building. Default to n-1 minor for stability.

# Get latest CanvOS tag
CANVOS_TAG=$(gh api repos/spectrocloud/CanvOS/tags --jq '.[0].name')
echo "Latest CanvOS: $CANVOS_TAG"

# Fetch supported K8s versions
curl -s "https://raw.githubusercontent.com/spectrocloud/CanvOS/$CANVOS_TAG/k8s_version.json" > k8s_versions.json

# Get n-1 minor for chosen distribution (default: k3s)
DISTRO="k3s"  # or: rke2, kubeadm, kubeadm-fips
K8S_VERSION=$(jq -r --arg d "$DISTRO" '.[$d] |
  sort_by(split(".") | map(tonumber)) | reverse |
  group_by(split(".")[0:2] | join(".")) |
  sort_by(.[0] | split(".") | map(tonumber)) | reverse | .[1][0] // .[0][0]' k8s_versions.json)
echo "Recommended $DISTRO version: $K8S_VERSION"

# Show all available for reference
jq -r --arg d "$DISTRO" '.[$d] | sort_by(split(".") | map(tonumber)) | reverse | .[0:5] | join(", ")' k8s_versions.json

Use this K8S_VERSION in Step 1's .arg file.

Step 1: Clone and Configure

# Clone fresh or update existing repo
if [ -d "CanvOS" ]; then
  cd CanvOS && git fetch --tags && git pull
else
  git clone https://github.com/spectrocloud/CanvOS.git && cd CanvOS
fi
LATEST_TAG=$(git describe --tags --abbrev=0)
echo "Using CanvOS $LATEST_TAG"
git checkout "$LATEST_TAG"

# Use K8S_VERSION from Step 0, or set manually
cat << EOF > .arg
OS_DISTRIBUTION=ubuntu
OS_VERSION=24.04
K8S_DISTRIBUTION=${DISTRO:-k3s}
K8S_VERSION=${K8S_VERSION:-1.32.9}
IMAGE_REGISTRY=ttl.sh
IMAGE_REPO=my-edge-images
CUSTOM_TAG=demo
ARCH=amd64
FIPS_ENABLED=false
TWO_NODE=false
EOF

Step 2: Create user-data

Bridge networking is recommended for VM-based edge deployments (allows containers to get IPs on host network).

cat << 'EOF' > user-data
#cloud-config
install:
  reboot: true

users:
  - name: kairos
    passwd: kairos
    groups: [sudo, admin]
    sudo: ALL=(ALL) NOPASSWD:ALL

stages:
  initramfs:
    - name: "Setup bridge networking"
      files:
        - path: /etc/systemd/network/20-dhcp.network
          content: |
            [Match]
            Name=en*
            [Network]
            Bridge=br0
            LinkLocalAddressing=no
          permissions: 0644
          owner: 0
          group: 0
        - path: /etc/systemd/network/bridge0.netdev
          content: |
            [NetDev]
            Name=br0
            Kind=bridge
          permissions: 0644
          owner: 0
          group: 0
        - path: /etc/systemd/network/bridge0.network
          content: |
            [Match]
            Name=br0
            [Network]
            DHCP=yes
          permissions: 0644
          owner: 0
          group: 0

stylus:
  site:
    paletteEndpoint: api.spectrocloud.com
    edgeHostToken: <registration-token>
    projectName: <project-name>
EOF

For proxy, static IP, bonds: See references/user-data-examples.yaml

Step 3: Build

docker login <registry>  # Skip for ttl.sh
earthly +iso  # Build ISO first - start imaging nodes while provider images push
earthly --push +build-provider-images
# Output: build/palette-edge-installer.iso

Step 4: Version the ISO

Critical: The default ISO name has no version info. Rename it before uploading:

# Create versioned ISO name with date and K8s version
ISO_NAME="palette-edge-$(grep K8S_VERSION .arg | cut -d= -f2)-$(date +%Y%m%d-%H%M).iso"
mv build/palette-edge-installer.iso "build/$ISO_NAME"
echo "Built: $ISO_NAME"

Before imaging any VM, verify you're using the correct ISO:

# Check ISO build date (should match your recent build)
ls -la build/*.iso

# If uploading to Proxmox, include version in the storage name
# e.g., "palette-edge-k3s-1.30.5-20241222-1430.iso"

2-Node Deployment

2-node HA uses Postgres + Kine backend. Appliance-mode only.

Requirements

  • K3s only (not kubeadm/rke2)
  • Ubuntu 22.04 recommended
  • Cannot expand to 3+ nodes later

2-Node .arg

cat << 'EOF' > .arg
OS_DISTRIBUTION=ubuntu
OS_VERSION=22.04
K8S_DISTRIBUTION=k3s
K8S_VERSION=1.33.3
IMAGE_REGISTRY=ttl.sh
IMAGE_REPO=my-edge-2node
CUSTOM_TAG=two-node-demo
ARCH=amd64
TWO_NODE=true
EOF

Build & Deploy

earthly +iso  # Build ISO first - start imaging nodes while provider images push
earthly --push +provider-image

When creating cluster: toggle "Two-Node Mode", select exactly 2 edge hosts.

2-Node Failover Behavior

  • One node is leader (writes), other is follower
  • Follower detects leader failure and self-promotes (~30s failover)
  • On recovery, nodes compare timestamps; most recent becomes leader

Content Bundles

Pre-cache images for offline deployments:

# Install Palette CLI
curl -LO https://github.com/spectrocloud/palette-cli/releases/latest/download/palette-linux-amd64
chmod +x palette-linux-amd64 && sudo mv palette-linux-amd64 /usr/local/bin/palette

# Build bundle
palette content build --arch amd64 --project-id <uid> \
  --cluster-profile-ids <id1>,<id2> --output ./content-bundle.tar.zst

# Include in ISO
mkdir -p content && cp content-bundle.tar.zst content/
earthly +iso

Deploy

Pre-Deploy Checklist

  • [ ] ISO name includes version/date (not generic palette-edge-installer.iso)
  • [ ] Provider image tag matches ISO build (check .arg used)
  • [ ] Old ISOs cleaned up from hypervisor storage
  • [ ] Old edge hosts deleted from Palette (prevents duplicate ID errors)

Proxmox Upload

# Upload versioned ISO to Proxmox
ISO_PATH="/path/to/palette-edge-k3s-1.30.5-20241222.iso"
scp "$ISO_PATH" [email protected]:/var/lib/vz/template/iso/

# List ISOs on Proxmox to verify and clean up old ones
ssh [email protected] "ls -la /var/lib/vz/template/iso/palette-*.iso"

# Remove stale ISOs (keep only latest)
ssh [email protected] "rm /var/lib/vz/template/iso/palette-edge-OLD*.iso"

VM Creation

Sizing considerations:
| Use Case | Disk Size | Notes |
|----------|-----------|-------|
| Basic edge | 100GB | ~2.5GB free after OS for storage pools |
| With Piraeus file pool | 200GB+ | File pools use root partition space |
| With separate data disk | 100GB + data disk | Attach second disk for storage pool |

Boot order is critical:

# Proxmox boot order format:
boot: order=scsi0;ide2;net0
  • Disk first (scsi0), then CD-ROM (ide2)
  • Empty disk falls through to CD on first boot
  • After install, boots from disk (avoids reinstall loop)
  • Wrong order = reinstall loop or hang

VM creation steps:
1. Create VM: 4+ CPU, 8GB+ RAM, disk sized for use case
2. Attach the versioned ISO (verify name before attaching)
3. Set boot order: order=scsi0;ide2;net0
4. Boot - installation is automatic
5. Verify in Palette: Clusters > Edge Hosts > Registered

Automated Builds via SSH

For environments with a dedicated build machine, automate the entire process:

BUILD_HOST="[email protected]"  # Or your build machine

# Clone, configure, and build in one session
ssh $BUILD_HOST << 'ENDSSH'
cd ~
if [ -d "CanvOS" ]; then
  cd CanvOS && git fetch --tags && git pull
else
  git clone https://github.com/spectrocloud/CanvOS.git && cd CanvOS
fi
LATEST_TAG=$(git describe --tags --abbrev=0)
echo "Using CanvOS $LATEST_TAG"
git checkout "$LATEST_TAG"

cat << 'ARGFILE' > .arg
OS_DISTRIBUTION=ubuntu
OS_VERSION=22.04
K8S_DISTRIBUTION=k3s
K8S_VERSION=1.33.3
IMAGE_REGISTRY=ttl.sh
IMAGE_REPO=my-edge
CUSTOM_TAG=2node
ARCH=amd64
TWO_NODE=true
ARGFILE

cat << 'USERDATA' > user-data
#cloud-config
install:
  reboot: true
users:
  - name: kairos
    passwd: kairos
    groups: [sudo, admin]
    sudo: ALL=(ALL) NOPASSWD:ALL
stages:
  initramfs:
    - name: "Setup bridge networking"
      files:
        - path: /etc/systemd/network/20-dhcp.network
          content: |
            [Match]
            Name=en*
            [Network]
            Bridge=br0
            LinkLocalAddressing=no
        - path: /etc/systemd/network/bridge0.netdev
          content: |
            [NetDev]
            Name=br0
            Kind=bridge
        - path: /etc/systemd/network/bridge0.network
          content: |
            [Match]
            Name=br0
            [Network]
            DHCP=yes
stylus:
  site:
    paletteEndpoint: api.spectrocloud.com
    edgeHostToken: <TOKEN>
    projectName: <PROJECT>
USERDATA

earthly +iso  # Build ISO first - start imaging nodes while provider images push
earthly --push +provider-image
ENDSSH

# Copy ISO back with version name
scp $BUILD_HOST:~/CanvOS/build/palette-edge-installer.iso \
  "./palette-edge-k3s-1.33.3-$(date +%Y%m%d).iso"

CI/CD Integration

See references/cicd-workflow.md for GitHub Actions and GitLab CI examples.

Troubleshooting

Issue Solution
Registry errors Verify docker login, ttl.sh needs no login but expires in 24h
ISO boot hangs Check UEFI/BIOS mode, EFI partition size
Reinstall loop Boot order wrong - must be disk first, then CD-ROM. See fix below.
Not registering Check user-data, network, logs: journalctl -u spectro-stylus-agent.service -f
Re-imaging Delete old edge host from Palette first
Wrong K8s version Verify ISO name matches expected build, check for stale ISOs
Stale ISO used List ISOs on hypervisor, delete old ones, re-upload versioned ISO
Storage pool too small 100GB disk leaves ~2.5GB free - increase disk or add data disk
Cluster stuck Provisioning, no nodes VMs likely in install loop - check boot order and power cycle
user-data changes not applied Regenerate userdata: kairos-agent notify agent.bootstrap (see below)

User-Data and /oem Directory

All #cloud-config files in /oem are merged to generate /run/stylus/userdata. If you modify files in /oem, regenerate userdata:

# On the edge node - regenerates /run/stylus/userdata from /oem files
kairos-agent notify agent.bootstrap

Use cases:
- Debugging user-data issues: check /run/stylus/userdata for merged result
- Adding config post-install: place files in /oem/*.yaml, then regenerate
- Troubleshooting registration: verify stylus config in /run/stylus/userdata

Fixing Install Loop (Boot Order)

Symptom: VMs keep reinstalling from ISO instead of booting from installed disk. Cluster stays in "Provisioning" with no nodes appearing for 30+ minutes.

Cause: Boot order is ide2;scsi0 (CD-ROM first) instead of scsi0;ide2 (disk first).

Fix:

# 1. Change boot order via Proxmox API
curl -k -X PUT "https://<PROXMOX>:8006/api2/json/nodes/proxmox/qemu/<VMID>/config" \
  -H "Cookie: PVEAuthCookie=${TICKET}" \
  -H "CSRFPreventionToken: ${CSRF}" \
  --data-urlencode "boot=order=scsi0;ide2;net0"

# 2. MUST power off completely (not reboot/reset!)
curl -k -X POST ".../qemu/<VMID>/status/stop" ...

# 3. Wait for VM to stop, then power on
curl -k -X POST ".../qemu/<VMID>/status/start" ...

CRITICAL: Reboot and reset do NOT reliably apply boot order changes. You MUST do a full power off then power on.

BYOOS Pack Version

Always use the latest BYOOS pack version when creating cluster profiles. As of Jan 2025, this is 2.1.0.

Query to confirm latest:

curl -s "https://api.spectrocloud.com/v1/packs?filters=metadata.name=edge-native-byoi&limit=50" \
  -H "ApiKey: $API_KEY" | jq -r '[.items[] | select(.spec.registryUid == "5eecc89d0b150045ae661cef")] |
  sort_by(.spec.version | split(".") | map(tonumber)) | reverse | .[0].spec.version'

Registry note: BYOOS exists in two registries. Use Public Repo (type=spectro) for Terraform/API:
- Public Repo UID: 5eecc89d0b150045ae661cef
- Type in Terraform: type = "spectro" (NOT "oci")

Provider Image Tag

Don't assume the tag format - CanvOS generates tags based on .arg values but includes Kairos version, not OS version:
- Expected: k3s-1.33.5-ubuntu-22.04-2node
- Actual: k3s-1.33.5-v4.8.1-2node (Kairos version)

Always check the actual tag after build:

# Check local images after build
docker images | grep $IMAGE_REPO

# Check registry (for Docker Hub)
curl -s "https://hub.docker.com/v2/repositories/$IMAGE_REPO/tags" | jq '.results[].name'

# Check ttl.sh (use crane or skopeo)
crane ls ttl.sh/$IMAGE_REPO

CRITICAL: Use the EXACT tag from the build output in:
1. BYOOS pack options.system.uri in the cluster profile
2. Ensure K8s pack version matches what was built into the image

Quick Reference

Item Value
CanvOS https://github.com/spectrocloud/CanvOS
ISO output build/palette-edge-installer.iso (rename with version!)
Versioned ISO palette-edge-<K8S_VERSION>-<YYYYMMDD-HHMM>.iso
Image tag Check docker images after build - use exact tag
Proxmox boot order boot: order=scsi0;ide2;net0
SSH access kairos / kairos

Additional Resources

  • references/networking-examples.yaml - Bridge and bond configurations
  • references/user-data-examples.yaml - Advanced user-data options
  • references/cicd-workflow.md - CI/CD pipeline examples

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.