hopefully last rename

This commit is contained in:
Julian Sommer
2026-03-18 13:24:45 +10:00
parent ba0d8b67ff
commit 7d43d0f782
68 changed files with 451 additions and 451 deletions
+9 -9
View File
@@ -44,7 +44,7 @@ jobs:
- name: Clone and build
working-directory: mulga/scripts/tofu-cluster
run: ./build-artifacts.sh "${GIT_BRANCH}" "${RUNNER_TEMP}/hive-artifacts.tar"
run: ./build-artifacts.sh "${GIT_BRANCH}" "${RUNNER_TEMP}/spinifex-artifacts.tar"
- name: Provision VM
working-directory: mulga/scripts/tofu-cluster
@@ -59,8 +59,8 @@ jobs:
- name: Bootstrap Cluster
working-directory: mulga/scripts/tofu-cluster
run: |
HIVE_PREBUILT=true \
HIVE_ARTIFACT_TAR="${RUNNER_TEMP}/hive-artifacts.tar" \
SPX_PREBUILT=true \
SPX_ARTIFACT_TAR="${RUNNER_TEMP}/spinifex-artifacts.tar" \
./bootstrap.sh env3 >> "$TOFU_LOG" 2>&1
- name: Run E2E Tests
@@ -107,7 +107,7 @@ jobs:
- name: Clone and build
working-directory: mulga/scripts/tofu-cluster
run: ./build-artifacts.sh "${GIT_BRANCH}" "${RUNNER_TEMP}/hive-artifacts.tar"
run: ./build-artifacts.sh "${GIT_BRANCH}" "${RUNNER_TEMP}/spinifex-artifacts.tar"
- name: Provision VM
working-directory: mulga/scripts/tofu-cluster
@@ -122,8 +122,8 @@ jobs:
- name: Bootstrap Cluster
working-directory: mulga/scripts/tofu-cluster
run: |
HIVE_PREBUILT=true \
HIVE_ARTIFACT_TAR="${RUNNER_TEMP}/hive-artifacts.tar" \
SPX_PREBUILT=true \
SPX_ARTIFACT_TAR="${RUNNER_TEMP}/spinifex-artifacts.tar" \
./bootstrap.sh env4 >> "$TOFU_LOG" 2>&1
- name: Run E2E Tests
@@ -170,7 +170,7 @@ jobs:
- name: Clone and build
working-directory: mulga/scripts/tofu-cluster
run: ./build-artifacts.sh "${GIT_BRANCH}" "${RUNNER_TEMP}/hive-artifacts.tar"
run: ./build-artifacts.sh "${GIT_BRANCH}" "${RUNNER_TEMP}/spinifex-artifacts.tar"
- name: Provision VMs
working-directory: mulga/scripts/tofu-cluster
@@ -185,8 +185,8 @@ jobs:
- name: Bootstrap Cluster
working-directory: mulga/scripts/tofu-cluster
run: |
HIVE_PREBUILT=true \
HIVE_ARTIFACT_TAR="${RUNNER_TEMP}/hive-artifacts.tar" \
SPX_PREBUILT=true \
SPX_ARTIFACT_TAR="${RUNNER_TEMP}/spinifex-artifacts.tar" \
./bootstrap.sh env2 >> "$TOFU_LOG" 2>&1
- name: Run E2E Tests
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
uses: docker/setup-qemu-action@v3
- name: Build distribution tarballs
run: cd spinifex cd hive &&cd hive && make distro VERSION=${{ github.ref_name }}
run: cd spinifex && cd spinifex && make distro VERSION=${{ github.ref_name }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
+15 -15
View File
@@ -10,7 +10,7 @@ on:
- go.mod
- go.sum
- Makefile
- .github/workflows/hive.yml
- .github/workflows/spinifex.yml
workflow_dispatch:
jobs:
@@ -22,7 +22,7 @@ jobs:
- name: Checkout Spinifex
uses: actions/checkout@v6
with:
path: hive
path: spinifex
fetch-depth: 2
- name: Checkout Viperblock
@@ -44,16 +44,16 @@ jobs:
go-version-file: "spinifex/go.mod"
- name: Setup Go Workspace
run: go work init ./hive ./viperblock ./predastore
run: go work init ./spinifex ./viperblock ./predastore
- name: Build Spinifex
run: make -C hive build
run: make -C spinifex build
- name: Run Tests with Coverage
run: make -C hive test-cover
run: make -C spinifex test-cover
- name: Check Diff Coverage
run: cd hive && scripts/diff-coverage.sh coverage.out --base HEAD~1
run: cd spinifex && scripts/diff-coverage.sh coverage.out --base HEAD~1
race_detection:
name: Spinifex Race Detection
@@ -63,7 +63,7 @@ jobs:
- name: Checkout Spinifex
uses: actions/checkout@v6
with:
path: hive
path: spinifex
- name: Checkout Viperblock
uses: actions/checkout@v6
@@ -84,10 +84,10 @@ jobs:
go-version-file: "spinifex/go.mod"
- name: Setup Go Workspace
run: go work init ./hive ./viperblock ./predastore
run: go work init ./spinifex ./viperblock ./predastore
- name: Run Tests with Race Detector
run: make -C hive test-race
run: make -C spinifex test-race
lint_and_security:
name: Spinifex Lint and Security
@@ -97,7 +97,7 @@ jobs:
- name: Checkout Spinifex
uses: actions/checkout@v6
with:
path: hive
path: spinifex
- name: Checkout Viperblock
uses: actions/checkout@v6
@@ -118,16 +118,16 @@ jobs:
go-version-file: "spinifex/go.mod"
- name: Setup Go Workspace
run: go work init ./hive ./viperblock ./predastore
run: go work init ./spinifex ./viperblock ./predastore
- name: Check Format
run: make -C hive check-format
run: make -C spinifex check-format
- name: Check Modernize
run: make -C hive check-modernize
run: make -C spinifex check-modernize
- name: Go Vet
run: make -C hive vet
run: make -C spinifex vet
- name: Security Checks
run: make -C hive security-check
run: make -C spinifex security-check
+16 -16
View File
@@ -77,8 +77,8 @@ var supportedPlatforms = map[string]bool{
var adminCmd = &cobra.Command{
Use: "admin",
Short: "Administrative commands for Hive platform management",
Long: `Administrative commands for initializing and managing the Hive platform.`,
Short: "Administrative commands for Spinifex platform management",
Long: `Administrative commands for initializing and managing the Spinifex platform.`,
}
var clusterCmd = &cobra.Command{
@@ -98,8 +98,8 @@ Each phase waits for all nodes to ACK before proceeding to the next.`,
var adminInitCmd = &cobra.Command{
Use: "init",
Short: "Initialize Hive platform configuration",
Long: `Initialize Hive platform by creating configuration files, generating SSL certificates,
Short: "Initialize Spinifex platform configuration",
Long: `Initialize Spinifex platform by creating configuration files, generating SSL certificates,
and setting up AWS credentials. This creates the necessary directory structure and
configuration files in ~/spinifex/config.`,
Run: runAdminInit,
@@ -107,8 +107,8 @@ configuration files in ~/spinifex/config.`,
var adminJoinCmd = &cobra.Command{
Use: "join",
Short: "Join an existing Hive cluster",
Long: `Join an existing Hive cluster by connecting to a leader node and retrieving
Short: "Join an existing Spinifex cluster",
Long: `Join an existing Spinifex cluster by connecting to a leader node and retrieving
the cluster configuration. This command will configure the local node to join
the cluster and participate in distributed operations.`,
Run: runAdminJoin,
@@ -136,14 +136,14 @@ var imagesListCmd = &cobra.Command{
var accountCmd = &cobra.Command{
Use: "account",
Short: "Manage Hive accounts",
Long: `Create and manage Hive accounts. Each account namespaces IAM users, policies, and resources.`,
Short: "Manage Spinifex accounts",
Long: `Create and manage Spinifex accounts. Each account namespaces IAM users, policies, and resources.`,
}
var accountCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a new account with an admin user",
Long: `Create a new Hive account. This creates an account with a sequential 12-digit ID,
Long: `Create a new Spinifex account. This creates an account with a sequential 12-digit ID,
an admin user, and an AdministratorAccess policy attached to the admin user.
Requires the cluster to be running (connects to NATS).`,
Run: runAccountCreate,
@@ -152,7 +152,7 @@ Requires the cluster to be running (connects to NATS).`,
var accountListCmd = &cobra.Command{
Use: "list",
Short: "List all accounts",
Long: `List all Hive accounts with their ID, name, status, and creation time.`,
Long: `List all Spinifex accounts with their ID, name, status, and creation time.`,
Run: runAccountList,
}
@@ -400,7 +400,7 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) {
volumeId := utils.GenerateResourceID("ami")
manifest.AMIMetadata.ImageID = volumeId
manifest.AMIMetadata.Description = fmt.Sprintf("%s cloud image prepared for Hive", manifest.AMIMetadata.Name)
manifest.AMIMetadata.Description = fmt.Sprintf("%s cloud image prepared for Spinifex", manifest.AMIMetadata.Name)
manifest.AMIMetadata.Architecture = image.Arch
manifest.AMIMetadata.PlatformDetails = image.Platform
manifest.AMIMetadata.CreationDate = time.Now()
@@ -557,20 +557,20 @@ func runAdminInit(cmd *cobra.Command, args []string) {
clusterBind = bindIP
}
fmt.Printf("Initializing Hive with bind IP: %s, port: %d\n", bindIP, port)
fmt.Printf("Initializing Spinifex with bind IP: %s, port: %d\n", bindIP, port)
// Default config directory
if configDir == "" {
configDir = DefaultConfigDir()
}
fmt.Println("🚀 Initializing Hive platform...")
fmt.Println("🚀 Initializing Spinifex platform...")
fmt.Printf("Configuration directory: %s\n\n", configDir)
// Check if already initialized
spinifexTomlPath := filepath.Join(configDir, "spinifex.toml")
if !force && admin.FileExists(spinifexTomlPath) {
fmt.Println("⚠️ Hive already initialized!")
fmt.Println("⚠️ Spinifex already initialized!")
fmt.Printf("Config file exists: %s\n", spinifexTomlPath)
fmt.Println("\nTo re-initialize, run with --force flag:")
fmt.Println(" spx admin init --force")
@@ -767,7 +767,7 @@ func runAdminInit(cmd *cobra.Command, args []string) {
}
// Print success message
fmt.Println("\n🎉 Hive initialization complete!")
fmt.Println("\n🎉 Spinifex initialization complete!")
fmt.Println("\n📋 Next steps:")
fmt.Println(" 1. Start services:")
fmt.Println(" ./scripts/start-dev.sh")
@@ -1029,7 +1029,7 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
dataDir = DefaultDataDir()
}
fmt.Println("🚀 Joining Hive cluster...")
fmt.Println("🚀 Joining Spinifex cluster...")
fmt.Printf("Node: %s\n", node)
fmt.Printf("Leader: %s\n", leaderHost)
fmt.Printf("Region: %s\n", region)
+1 -1
View File
@@ -118,6 +118,6 @@ func initConfig() {
fmt.Fprintf(os.Stderr, "Error: failed to load config %s: %v\n", cfgFile, err)
os.Exit(1)
}
// No config specified — continue with env/defaults (e.g., hive --help)
// No config specified — continue with env/defaults (e.g., spx --help)
}
}
+1 -1
View File
@@ -863,7 +863,7 @@ func init() {
viper.BindEnv("jetstream", "SPINIFEX_NATS_JETSTREAM")
viper.BindPFlag("jetstream", natsCmd.PersistentFlags().Lookup("jetstream"))
// Hive
// Spinifex
serviceCmd.AddCommand(spinifexCmd)
spinifexCmd.AddCommand(spinifexStartCmd)
@@ -52,4 +52,4 @@ policy = [
nats_url = "nats://{{.BindIP}}:4222"
nats_token = "{{.NatsToken}}"
master_key_path = "{{.ConfigDir}}/master.key"
access_keys_bucket = "hive-iam-access-keys"
access_keys_bucket = "spinifex-iam-access-keys"
+1 -1
View File
@@ -86,4 +86,4 @@ policy = [
nats_url = "nats://{{if eq .BindIP "0.0.0.0"}}localhost{{else}}{{.BindIP}}{{end}}:4222"
nats_token = "{{.NatsToken}}"
master_key_path = "{{.ConfigDir}}/master.key"
access_keys_bucket = "hive-iam-access-keys"
access_keys_bucket = "spinifex-iam-access-keys"
+2 -2
View File
@@ -16,9 +16,9 @@ var (
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the Hive version",
Short: "Print the Spinifex version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("hive %s (%s) %s/%s\n", Version, Commit, runtime.GOOS, runtime.GOARCH)
fmt.Printf("spinifex %s (%s) %s/%s\n", Version, Commit, runtime.GOOS, runtime.GOARCH)
},
}
+2 -2
View File
@@ -351,8 +351,8 @@ spinifex/
1. Start NATS broker
2. Start Predastore (S3)
3. Start Viperblock (EBS)
4. Start Spinifex Gateway (`hive awsgw`)
5. Start Spinifex Daemon(s) (`hive daemon`)
4. Start Spinifex Gateway (`spx awsgw`)
5. Start Spinifex Daemon(s) (`spx daemon`)
### Testing with AWS CLI
+1 -1
View File
@@ -658,7 +658,7 @@ export AWS_PROFILE=spinifex-myteam
## UI Management Panel
The Spinifex Platform also offers a web interface for managing spx services. Simply have the hive server running and go to [https://localhost:3000](https://localhost:3000) in your browser to continue.
The Spinifex Platform also offers a web interface for managing spx services. Simply have the spinifex server running and go to [https://localhost:3000](https://localhost:3000) in your browser to continue.
**Note:** If you are not viewing the website on the same machine that is running the spinifex server, you will need to go to [https://localhost:9999](https://localhost:9999) and [https://localhost:8443](https://localhost:8443) and accept the certificates. If you have added the CA to your local machine this is not needed.
@@ -85,7 +85,7 @@ OVN must be configured on every server before forming the cluster. Server 1 runs
**Server 1 — OVN central + compute (run first):**
```bash
sudo /usr/local/share/hive/setup-ovn.sh --management --encap-ip=$SPINIFEX_NODE1
sudo /usr/local/share/spinifex/setup-ovn.sh --management --encap-ip=$SPINIFEX_NODE1
```
Verify OVN central is ready before proceeding:
@@ -97,13 +97,13 @@ sudo ovn-sbctl show
**Server 2 — Compute node (after server 1 is ready):**
```bash
sudo /usr/local/share/hive/setup-ovn.sh --ovn-remote=tcp:$SPINIFEX_NODE1:6642 --encap-ip=$SPINIFEX_NODE2
sudo /usr/local/share/spinifex/setup-ovn.sh --ovn-remote=tcp:$SPINIFEX_NODE1:6642 --encap-ip=$SPINIFEX_NODE2
```
**Server 3 — Compute node (after server 1 is ready):**
```bash
sudo /usr/local/share/hive/setup-ovn.sh --ovn-remote=tcp:$SPINIFEX_NODE1:6642 --encap-ip=$SPINIFEX_NODE3
sudo /usr/local/share/spinifex/setup-ovn.sh --ovn-remote=tcp:$SPINIFEX_NODE1:6642 --encap-ip=$SPINIFEX_NODE3
```
Verify all chassis have registered (from server 1):
+2 -2
View File
@@ -32,7 +32,7 @@ resources:
Spinifex is an open-source infrastructure platform that brings core AWS services — EC2, VPC, EBS, and S3 — to bare-metal, edge, and on-prem environments.
This guide installs Spinifex on a single server using the binary installer. For multi-server clusters, see [Multi-Node Installation](/docs/installing-hive-multi-node). To build from source, see [Source Install](/docs/source-install).
This guide installs Spinifex on a single server using the binary installer. For multi-server clusters, see [Multi-Node Installation](/docs/installing-spinifex-multi-node). To build from source, see [Source Install](/docs/source-install).
**Supported Operating Systems:**
- Ubuntu 22.04 / 24.04 / 25.10
@@ -126,7 +126,7 @@ ssh -i ~/.ssh/spinifex-key ec2-user@127.0.0.1 -p <port>
## Troubleshooting
## hive command not found
## spx command not found
The binary is not in your PATH. Run the installer again or add the install directory to your PATH:
+1 -1
View File
@@ -34,7 +34,7 @@ resources:
## Overview
This guide covers building Spinifex from source code. Use this if you want to contribute to Spinifex, build custom versions, or inspect the codebase. For production deployments, the [binary installer](/docs/installing-hive) is recommended.
This guide covers building Spinifex from source code. Use this if you want to contribute to Spinifex, build custom versions, or inspect the codebase. For production deployments, the [binary installer](/docs/installing-spinifex) is recommended.
**Supported Operating Systems:**
- Ubuntu 22.04 / 24.04 / 25.10
+1 -1
View File
@@ -75,7 +75,7 @@ node3 Ready 127.0.0.3 ap-southeast-2 ap-southeast-2a 2m 0
## Troubleshooting
## Permission denied running hive
## Permission denied running spinifex
The binary may not be executable. Fix permissions:
+7 -7
View File
@@ -64,15 +64,15 @@ done
# --- Step 2: Clone repo and build on all nodes ---
echo ""
echo "==> Cloning hive repo and building on all nodes..."
echo "==> Cloning Spinifex repo and building on all nodes..."
PIDS=()
for i in $(seq 0 $((NODE_COUNT - 1))); do
ip="${MGMT_IPS[$i]}"
(
echo " node$((i + 1)) ($ip): cloning..."
remote "$ip" "mkdir -p ~/Development/mulga && cd ~/Development/mulga && git clone https://github.com/mulgadc/hive.git && export PATH=\$PATH:/usr/local/go/bin && sudo make -C hive quickinstall"
remote "$ip" "mkdir -p ~/Development/mulga && cd ~/Development/mulga && git clone https://github.com/mulgadc/spinifex.git && export PATH=\$PATH:/usr/local/go/bin && sudo make -C spinifex quickinstall"
echo " node$((i + 1)) ($ip): clone-deps + dev-setup..."
remote "$ip" "export PATH=\$PATH:/usr/local/go/bin && cd ~/Development/mulga/hive && ./scripts/clone-deps.sh && ./scripts/dev-setup.sh"
remote "$ip" "export PATH=\$PATH:/usr/local/go/bin && cd ~/Development/mulga/spinifex && ./scripts/clone-deps.sh && ./scripts/dev-setup.sh"
echo " node$((i + 1)) ($ip): build complete"
) &
PIDS+=($!)
@@ -93,7 +93,7 @@ NODE1_IP="${MGMT_IPS[0]}"
# Node 1: init
echo " Starting init on node1 ($NODE1_IP)..."
remote_bg "$NODE1_IP" "export PATH=\$PATH:/usr/local/go/bin && cd ~/Development/mulga/hive && ./bin/spx admin init \
remote_bg "$NODE1_IP" "export PATH=\$PATH:/usr/local/go/bin && cd ~/Development/mulga/spinifex && ./bin/spx admin init \
--node node1 \
--bind $NODE1_IP \
--cluster-bind $NODE1_IP \
@@ -103,7 +103,7 @@ remote_bg "$NODE1_IP" "export PATH=\$PATH:/usr/local/go/bin && cd ~/Development/
--nodes $NODE_COUNT \
--region $REGION \
--az $AZ \
--hive-dir ~/spinifex/ \
--spinifex-dir ~/spinifex/ \
--config-dir ~/spinifex/config/"
INIT_PID=$!
@@ -128,7 +128,7 @@ if [ "$NODE_COUNT" -gt 1 ]; then
ip="${MGMT_IPS[$i]}"
node_num=$((i + 1))
echo " Starting join on node$node_num ($ip)..."
remote_bg "$ip" "export PATH=\$PATH:/usr/local/go/bin && cd ~/Development/mulga/hive && ./bin/spx admin join \
remote_bg "$ip" "export PATH=\$PATH:/usr/local/go/bin && cd ~/Development/mulga/spinifex && ./bin/spx admin join \
--node node$node_num \
--bind $ip \
--cluster-bind $ip \
@@ -158,7 +158,7 @@ for i in $(seq 0 $((NODE_COUNT - 1))); do
ip="${MGMT_IPS[$i]}"
(
echo " node$((i + 1)) ($ip): starting services..."
remote "$ip" "export PATH=\$PATH:/usr/local/go/bin && cd ~/Development/mulga/hive && ./scripts/start-dev.sh"
remote "$ip" "export PATH=\$PATH:/usr/local/go/bin && cd ~/Development/mulga/spinifex && ./scripts/start-dev.sh"
echo " node$((i + 1)) ($ip): services started"
) &
PIDS+=($!)
@@ -1,5 +1,5 @@
#!/bin/bash
# provision.sh — Installs all Hive dependencies, clones repos, caches Go modules,
# provision.sh — Installs all Spinifex dependencies, clones repos, caches Go modules,
# and generalizes the VM for use as a Proxmox template.
#
# Runs as tf-user via Terraform remote-exec. Uses sudo for system operations.
@@ -11,7 +11,7 @@ GIT_BRANCH="${GIT_BRANCH:-main}"
WORK_DIR="$HOME/Development/mulga"
echo "========================================"
echo " Hive Template Provisioning"
echo " Spinifex Template Provisioning"
echo " Branch: ${GIT_BRANCH}"
echo " Arch: $(uname -m)"
echo "========================================"
@@ -27,7 +27,7 @@ echo "=== [2/8] Cloning repositories ==="
mkdir -p "$WORK_DIR"
cd "$WORK_DIR"
for repo in hive viperblock predastore; do
for repo in spinifex viperblock predastore; do
if [ ! -d "$repo" ]; then
echo " Cloning $repo (branch: $GIT_BRANCH)..."
git clone -b "$GIT_BRANCH" "https://github.com/mulgadc/${repo}.git"
@@ -39,7 +39,7 @@ done
# ── 3. Install system dependencies via Makefile ─────────────────────────────
echo ""
echo "=== [3/8] Installing system dependencies ==="
cd "$WORK_DIR/hive"
cd "$WORK_DIR/spinifex"
sudo make quickinstall
# Ensure Go is in PATH for the rest of the script
@@ -52,10 +52,10 @@ echo ""
echo "=== [4/8] Creating go.work ==="
# Detect Go version from go.mod
GO_VERSION=$(grep '^go ' "$WORK_DIR/hive/go.mod" | awk '{print $2}')
GO_VERSION=$(grep '^go ' "$WORK_DIR/spinifex/go.mod" | awk '{print $2}')
echo " Using Go version from go.mod: ${GO_VERSION}"
cat > "$WORK_DIR/hive/go.work" << GOWORK
cat > "$WORK_DIR/spinifex/go.work" << GOWORK
go ${GO_VERSION}
use (
@@ -67,12 +67,12 @@ use (
)
GOWORK
echo " Created $WORK_DIR/hive/go.work"
echo " Created $WORK_DIR/spinifex/go.work"
# ── 5. Download Go modules ──────────────────────────────────────────────────
echo ""
echo "=== [5/8] Downloading Go modules ==="
cd "$WORK_DIR/hive" && go mod download && echo " hive: done"
cd "$WORK_DIR/spinifex" && go mod download && echo " spinifex: done"
cd "$WORK_DIR/viperblock" && go mod download && echo " viperblock: done"
cd "$WORK_DIR/predastore" && go mod download && echo " predastore: done"
@@ -110,7 +110,7 @@ echo ""
echo "=== [7/8] Applying system tuning ==="
# Persistent sysctl for NATS / high-throughput networking
sudo tee /etc/sysctl.d/99-hive.conf > /dev/null << 'SYSCTL'
sudo tee /etc/sysctl.d/99-spinifex.conf > /dev/null << 'SYSCTL'
net.core.rmem_max=4194304
net.core.wmem_max=4194304
SYSCTL
+1 -1
View File
@@ -10,7 +10,7 @@ echo "Removing data"
rm -rf ~/spinifex/predastore/
rm -rf ~/spinifex/hive/*
rm -rf ~/spinifex/spinifex/*
rm -rf ~/spinifex/viperblock/*
+4 -4
View File
@@ -158,7 +158,7 @@ install_aws_cli() {
# --- Download tarball ---
download_spinifex() {
SPINIFEX_TMPDIR=$(mktemp -d)
TARBALL="$SPINIFEX_TMPDIR/hive.tar.gz"
TARBALL="$SPINIFEX_TMPDIR/spinifex.tar.gz"
# Local tarball override — skip download (for testing and air-gapped installs)
if [ -n "$INSTALL_SPINIFEX_TARBALL" ]; then
@@ -284,7 +284,7 @@ install_systemd() {
for unit in "$EXTRACT_DIR"/systemd/*; do
# Substitute User= and Group= with the detected service user
sed "s/^User=hive$/User=$SPINIFEX_USER/;s/^Group=hive$/Group=$SPINIFEX_GROUP/" \
sed "s/^User=spinifex$/User=$SPINIFEX_USER/;s/^Group=spinifex$/Group=$SPINIFEX_GROUP/" \
"$unit" | $SUDO tee "/etc/systemd/system/$(basename "$unit")" > /dev/null
$SUDO chmod 0644 "/etc/systemd/system/$(basename "$unit")"
info " /etc/systemd/system/$(basename "$unit")"
@@ -297,7 +297,7 @@ install_systemd() {
# --- Install logrotate ---
install_logrotate() {
if [ -f "$EXTRACT_DIR/logrotate-spinifex" ]; then
$SUDO install -m 0644 "$EXTRACT_DIR/logrotate-spinifex" /etc/logrotate.d/hive
$SUDO install -m 0644 "$EXTRACT_DIR/logrotate-spinifex" /etc/logrotate.d/spinifex
else
warn "Logrotate config not found in tarball, skipping"
return
@@ -347,7 +347,7 @@ print_summary() {
echo " sudo spx admin init --region <region> --az <az> --node node1 --nodes 1"
echo ""
echo " 3. Trust the CA certificate:"
echo " sudo cp /etc/spinifex/ca.pem /usr/local/share/ca-certificates/hive-ca.crt"
echo " sudo cp /etc/spinifex/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt"
echo " sudo update-ca-certificates"
echo ""
echo " 4. Start services:"
+3 -3
View File
@@ -364,7 +364,7 @@ if has_service "daemon"; then
export SPINIFEX_BASE_DIR=$DATA_DIR/spinifex/
export SPINIFEX_WAL_DIR=$WAL_DIR
SPINIFEX_CMD="./bin/spx service hive start"
SPINIFEX_CMD="./bin/spx service spinifex start"
start_service "spinifex" "$SPINIFEX_CMD"
set_oom_score "spinifex" "-500"
else
@@ -411,8 +411,8 @@ if [ "${UI}" != "false" ] && has_service "ui"; then
echo ""
echo "7️⃣. Starting Spinifex UI..."
HIVEUI_CMD="./bin/spx service spinifex-ui start"
start_service "spinifex-ui" "$HIVEUI_CMD"
SPXUI_CMD="./bin/spx service spinifex-ui start"
start_service "spinifex-ui" "$SPXUI_CMD"
set_oom_score "spinifex-ui" "-500"
else
echo ""
+22 -22
View File
@@ -144,10 +144,10 @@ func GenerateCertificatesIfNeeded(configDir string, force bool, bindIP string) (
fmt.Printf(" Key: %s\n", serverKeyPath)
// Print instructions for adding CA to system trust store
fmt.Println("\n📋 To trust the Hive CA system-wide (recommended):")
fmt.Println("\n📋 To trust the Spinifex CA system-wide (recommended):")
fmt.Printf(" sudo cp %s /usr/local/share/ca-certificates/spinifex-ca.crt\n", caCertPath)
fmt.Println(" sudo update-ca-certificates")
fmt.Println("\n This allows AWS CLI and other tools to trust Hive services automatically.")
fmt.Println("\n This allows AWS CLI and other tools to trust Spinifex services automatically.")
} else {
fmt.Println("\n✅ CA and SSL certificates already exist")
}
@@ -172,19 +172,19 @@ func GenerateServerCertOnly(configDir string, bindIP string) error {
return GenerateSignedCert(serverCertPath, serverKeyPath, caCertPath, caKeyPath, bindIP)
}
func CreateServiceDirectories(hiveRoot string) {
func CreateServiceDirectories(spxRoot string) {
// Create additional directories
dirs := []string{
filepath.Join(hiveRoot, "images"),
filepath.Join(hiveRoot, "amis"),
filepath.Join(hiveRoot, "volumes"),
filepath.Join(hiveRoot, "state"),
filepath.Join(hiveRoot, "logs"),
filepath.Join(hiveRoot, "nats"),
filepath.Join(hiveRoot, "predastore"),
filepath.Join(hiveRoot, "viperblock"),
filepath.Join(hiveRoot, "hive"),
filepath.Join(spxRoot, "images"),
filepath.Join(spxRoot, "amis"),
filepath.Join(spxRoot, "volumes"),
filepath.Join(spxRoot, "state"),
filepath.Join(spxRoot, "logs"),
filepath.Join(spxRoot, "nats"),
filepath.Join(spxRoot, "predastore"),
filepath.Join(spxRoot, "viperblock"),
filepath.Join(spxRoot, "spinifex"),
}
fmt.Println("\n📁 Creating directory structure...")
@@ -197,7 +197,7 @@ func CreateServiceDirectories(hiveRoot string) {
}
}
}
fmt.Printf("✅ Directory structure created in %s\n", hiveRoot)
fmt.Printf("✅ Directory structure created in %s\n", spxRoot)
}
@@ -319,9 +319,9 @@ func DefaultAccountID() string {
return "000000000001"
}
// DefaultAccountName returns the default admin account name ("hive").
// DefaultAccountName returns the default admin account name ("spinifex").
func DefaultAccountName() string {
return "hive"
return "spinifex"
}
// GenerateNATSToken generates a secure random token for NATS
@@ -353,8 +353,8 @@ func GenerateCACert(caCertPath, caKeyPath string) error {
caTemplate := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "Hive Local CA",
Organization: []string{"Hive Platform"},
CommonName: "Spinifex Local CA",
Organization: []string{"Spinifex Platform"},
},
NotBefore: notBefore,
NotAfter: notAfter,
@@ -469,7 +469,7 @@ func GenerateSignedCert(certPath, keyPath, caCertPath, caKeyPath string, extraIP
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "localhost",
Organization: []string{"Hive Platform"},
Organization: []string{"Spinifex Platform"},
},
NotBefore: notBefore,
NotAfter: notAfter,
@@ -537,7 +537,7 @@ func GenerateSelfSignedCert(certPath, keyPath string) error {
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "localhost",
Organization: []string{"Hive Platform"},
Organization: []string{"Spinifex Platform"},
},
NotBefore: notBefore,
NotAfter: notAfter,
@@ -603,14 +603,14 @@ func SetupAWSCredentials(accessKey, secretKey, region, certPath, bindIP string)
// Determine profile name
//profileName := "default"
// Use hive as the default profile
profileName := "hive"
// Use Spinifex as the default profile
profileName := "spinifex"
if FileExists(credPath) {
// Check if default profile already exists
cfg, err := ini.Load(credPath)
if err == nil && cfg.HasSection("default") {
profileName = "hive"
profileName = "spinifex"
}
}
+11 -11
View File
@@ -174,7 +174,7 @@ func TestUpdateAWSINIFile_CreateNew(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "credentials")
err := UpdateAWSINIFile(path, "hive", map[string]string{
err := UpdateAWSINIFile(path, "spinifex", map[string]string{
"aws_access_key_id": "AKIATEST",
"aws_secret_access_key": "secrettest",
})
@@ -182,7 +182,7 @@ func TestUpdateAWSINIFile_CreateNew(t *testing.T) {
data, _ := os.ReadFile(path)
content := string(data)
assert.Contains(t, content, "[hive]")
assert.Contains(t, content, "[spinifex]")
assert.Contains(t, content, "AKIATEST")
assert.Contains(t, content, "secrettest")
}
@@ -192,9 +192,9 @@ func TestUpdateAWSINIFile_UpdateExisting(t *testing.T) {
path := filepath.Join(dir, "credentials")
// Create with initial value
require.NoError(t, UpdateAWSINIFile(path, "hive", map[string]string{"key": "old"}))
require.NoError(t, UpdateAWSINIFile(path, "spinifex", map[string]string{"key": "old"}))
// Update
require.NoError(t, UpdateAWSINIFile(path, "hive", map[string]string{"key": "new"}))
require.NoError(t, UpdateAWSINIFile(path, "spinifex", map[string]string{"key": "new"}))
data, _ := os.ReadFile(path)
content := string(data)
@@ -206,14 +206,14 @@ func TestUpdateAWSINIFile_AddNewSection(t *testing.T) {
path := filepath.Join(dir, "credentials")
require.NoError(t, UpdateAWSINIFile(path, "default", map[string]string{"key": "default-val"}))
require.NoError(t, UpdateAWSINIFile(path, "hive", map[string]string{"key": "hive-val"}))
require.NoError(t, UpdateAWSINIFile(path, "spinifex", map[string]string{"key": "spinifex-val"}))
data, _ := os.ReadFile(path)
content := string(data)
assert.Contains(t, content, "[default]")
assert.Contains(t, content, "[hive]")
assert.Contains(t, content, "[spinifex]")
assert.Contains(t, content, "default-val")
assert.Contains(t, content, "hive-val")
assert.Contains(t, content, "spinifex-val")
}
// --- SetupAWSCredentials ---
@@ -297,7 +297,7 @@ func TestGenerateCACert_CreatesValidCA(t *testing.T) {
cert, err := x509.ParseCertificate(block.Bytes)
require.NoError(t, err)
assert.True(t, cert.IsCA)
assert.Equal(t, "Hive Local CA", cert.Subject.CommonName)
assert.Equal(t, "Spinifex Local CA", cert.Subject.CommonName)
assert.NotZero(t, cert.KeyUsage&x509.KeyUsageCertSign)
// Parse key
@@ -505,7 +505,7 @@ func TestCreateServiceDirectories_CreatesAll(t *testing.T) {
dir := t.TempDir()
CreateServiceDirectories(dir)
expected := []string{"images", "amis", "volumes", "state", "logs", "nats", "predastore", "viperblock", "hive"}
expected := []string{"images", "amis", "volumes", "state", "logs", "nats", "predastore", "viperblock", "spinifex"}
for _, name := range expected {
path := filepath.Join(dir, name)
info, err := os.Stat(path)
@@ -603,9 +603,9 @@ host = "10.0.0.3"
// --- Integration: Full config generation flow ---
func TestGenerateConfigFile_HiveTomlTemplate(t *testing.T) {
func TestGenerateConfigFile_SpinifexTomlTemplate(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "hive.toml")
path := filepath.Join(dir, "spinifex.toml")
tmpl := `version = "1.0"
epoch = 1
+3 -3
View File
@@ -126,7 +126,7 @@ func (d *Daemon) handleHealthCheck(msg *nats.Msg) {
}
// handleNodeDiscover responds to node discovery requests with this node's ID
// Used by the gateway to dynamically discover active hive nodes in the cluster
// Used by the gateway to dynamically discover active spinifex nodes in the cluster
func (d *Daemon) handleNodeDiscover(msg *nats.Msg) {
response := types.NodeDiscoverResponse{
Node: d.node,
@@ -146,7 +146,7 @@ func (d *Daemon) daemonIP() string {
}
// handleNodeStatus responds with this node's status and resource stats.
// Used by the CLI: hive get nodes, hive top nodes.
// Used by the CLI: spx get nodes, spx top nodes.
func (d *Daemon) handleNodeStatus(msg *nats.Msg) {
totalVCPU, totalMemGB, allocVCPU, allocMemGB, caps := d.resourceMgr.GetResourceStats()
@@ -179,7 +179,7 @@ func (d *Daemon) handleNodeStatus(msg *nats.Msg) {
}
// handleNodeVMs responds with the list of VMs running on this node.
// Used by the CLI: hive get vms.
// Used by the CLI: spx get vms.
func (d *Daemon) handleNodeVMs(msg *nats.Msg) {
d.Instances.Mu.Lock()
vms := make([]types.VMInfo, 0, len(d.Instances.VMS))
+1 -1
View File
@@ -71,7 +71,7 @@ func (d *Daemon) handleEC2RunInstances(msg *nats.Msg) {
return
}
// Verify the caller can use this AMI: must own it or it must be a system/pre-phase4 AMI.
// System AMIs have non-account-ID owner aliases (e.g. "self", "hive", empty).
// System AMIs have non-account-ID owner aliases (e.g. "self", "spinifex", empty).
amiOwner := amiMeta.ImageOwnerAlias
if amiOwner != "" && amiOwner != accountID {
if utils.IsAccountID(amiOwner) {
+2 -2
View File
@@ -1153,8 +1153,8 @@ func TestSetConfigPath(t *testing.T) {
daemon := NewDaemon(clusterCfg)
assert.Empty(t, daemon.configPath)
daemon.SetConfigPath("/etc/hive/config.toml")
assert.Equal(t, "/etc/hive/config.toml", daemon.configPath)
daemon.SetConfigPath("/etc/spinifex/config.toml")
assert.Equal(t, "/etc/spinifex/config.toml", daemon.configPath)
}
// --- handleEC2StartStoppedInstance tests ---
+10 -10
View File
@@ -31,7 +31,7 @@ import (
// createTestDaemon creates a test daemon instance with minimal configuration
func createTestDaemon(t *testing.T, natsURL string) *Daemon {
// Create a temporary directory for test data
tmpDir, err := os.MkdirTemp("", "hive-daemon-test-*")
tmpDir, err := os.MkdirTemp("", "spx-daemon-test-*")
require.NoError(t, err, "Failed to create temp directory")
t.Cleanup(func() {
@@ -217,7 +217,7 @@ func TestHandleEC2RunInstances_MessageParsing(t *testing.T) {
// Skip "Valid RunInstancesInput" test as it requires full infrastructure
// (Predastore S3 backend, Viperblock, NBDkit) to actually create volumes
if tt.name == "Valid RunInstancesInput" {
t.Skip("Skipping valid input test - requires full hive infrastructure (viperblock, nbdkit, predastore)")
t.Skip("Skipping valid input test - requires full spinifex infrastructure (viperblock, nbdkit, predastore)")
}
// Start test NATS server
@@ -272,7 +272,7 @@ func TestHandleEC2RunInstances_ResourceManagement(t *testing.T) {
// - viperblock library with S3 backend
// - nbdkit for NBD mounting
// - QEMU for VM launch
t.Skip("Skipping resource management test - requires full hive infrastructure (viperblock, nbdkit, predastore)")
t.Skip("Skipping resource management test - requires full spinifex infrastructure (viperblock, nbdkit, predastore)")
tests := []struct {
name string
@@ -342,7 +342,7 @@ func TestHandleEC2RunInstances_ResourceManagement(t *testing.T) {
// TestDaemon_Initialization tests daemon initialization
func TestDaemon_Initialization(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "hive-daemon-init-test-*")
tmpDir, err := os.MkdirTemp("", "spx-daemon-init-test-*")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
@@ -851,7 +851,7 @@ func TestDaemon_BootAllocation(t *testing.T) {
natsURL := sharedJSNATSURL
// Create daemon temp directory
tmpDir, err := os.MkdirTemp("", "hive-daemon-boot-test-*")
tmpDir, err := os.MkdirTemp("", "spx-daemon-boot-test-*")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
@@ -3059,7 +3059,7 @@ func TestComputeConfigHash_ExcludesNodeField(t *testing.T) {
func TestSaveClusterConfig_WritesToDisk(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "hive.toml")
path := filepath.Join(dir, "spinifex.toml")
d := &Daemon{
configPath: path,
@@ -3102,7 +3102,7 @@ func TestSaveClusterConfig_ErrorOnEmptyPath(t *testing.T) {
func TestSaveClusterConfig_ErrorOnInvalidPath(t *testing.T) {
d := &Daemon{
configPath: "/nonexistent/dir/hive.toml",
configPath: "/nonexistent/dir/spinifex.toml",
clusterConfig: &config.ClusterConfig{},
}
err := d.saveClusterConfig()
@@ -3213,14 +3213,14 @@ func TestNewDaemon_WalDirDefaultsToBaseDir(t *testing.T) {
Node: "n1",
Nodes: map[string]config.Config{
"n1": {
BaseDir: "/data/hive",
BaseDir: "/data/spinifex",
WalDir: "", // Empty - should default to BaseDir
},
},
}
d := NewDaemon(cfg)
assert.Equal(t, "/data/hive", d.config.WalDir)
assert.Equal(t, "/data/spinifex", d.config.WalDir)
}
func TestNewDaemon_WalDirPreservedIfSet(t *testing.T) {
@@ -3228,7 +3228,7 @@ func TestNewDaemon_WalDirPreservedIfSet(t *testing.T) {
Node: "n1",
Nodes: map[string]config.Config{
"n1": {
BaseDir: "/data/hive",
BaseDir: "/data/spinifex",
WalDir: "/fast-ssd/wal",
},
},
+9 -9
View File
@@ -15,15 +15,15 @@ import (
const (
// InstanceStateBucket is the name of the KV bucket for storing instance state
InstanceStateBucket = "hive-instance-state"
InstanceStateBucket = "spinifex-instance-state"
// ClusterStateBucket is the name of the KV bucket for cluster state (heartbeats, shutdown markers, service maps)
ClusterStateBucket = "hive-cluster-state"
ClusterStateBucket = "spinifex-cluster-state"
// InstanceStatePrefix is the key prefix for per-node instance state entries
InstanceStatePrefix = "node."
// StoppedInstancePrefix is the key prefix for stopped instances in shared KV
StoppedInstancePrefix = "instance."
// TerminatedInstanceBucket is the name of the KV bucket for terminated instances (auto-expiry via TTL)
TerminatedInstanceBucket = "hive-terminated-instances"
TerminatedInstanceBucket = "spinifex-terminated-instances"
// TerminatedInstancePrefix is the key prefix for terminated instances
TerminatedInstancePrefix = "terminated."
)
@@ -31,9 +31,9 @@ const (
// JetStreamManager manages JetStream KV store operations for instance state
type JetStreamManager struct {
js nats.JetStreamContext
kv nats.KeyValue // hive-instance-state
clusterKV nats.KeyValue // hive-cluster-state
terminatedKV nats.KeyValue // hive-terminated-instances
kv nats.KeyValue // spinifex-instance-state
clusterKV nats.KeyValue // spinifex-cluster-state
terminatedKV nats.KeyValue // spinifex-terminated-instances
replicas int
kvMu sync.Mutex // protects kv during recovery
}
@@ -62,7 +62,7 @@ func (m *JetStreamManager) InitKVBucket() error {
slog.Debug("Creating JetStream KV bucket", "bucket", InstanceStateBucket, "replicas", m.replicas)
kv, err = m.js.CreateKeyValue(&nats.KeyValueConfig{
Bucket: InstanceStateBucket,
Description: "Hive instance state storage",
Description: "Spinifex instance state storage",
History: 1, // Only keep latest value
Replicas: m.replicas, // Replication across cluster nodes
})
@@ -88,7 +88,7 @@ func (m *JetStreamManager) InitClusterStateBucket() error {
slog.Debug("Creating JetStream KV bucket", "bucket", ClusterStateBucket, "replicas", m.replicas)
kv, err = m.js.CreateKeyValue(&nats.KeyValueConfig{
Bucket: ClusterStateBucket,
Description: "Hive cluster state (heartbeats, shutdown markers, service maps)",
Description: "Spinifex cluster state (heartbeats, shutdown markers, service maps)",
History: 1,
Replicas: m.replicas,
TTL: 1 * time.Hour,
@@ -191,7 +191,7 @@ func (m *JetStreamManager) recoverBucket(cfg *nats.KeyValueConfig, field *nats.K
func (m *JetStreamManager) recoverKVBucket() (nats.KeyValue, error) {
return m.recoverBucket(&nats.KeyValueConfig{
Bucket: InstanceStateBucket,
Description: "Hive instance state storage",
Description: "Spinifex instance state storage",
}, &m.kv)
}
+3 -3
View File
@@ -72,10 +72,10 @@ func (d *Daemon) handleShutdownGate(msg *nats.Msg) {
// Stop UI
if d.config.HasService("ui") {
if err := utils.StopProcessAt(d.pidDir(), "hive-ui"); err != nil {
slog.Warn("Failed to stop hive-ui", "error", err)
if err := utils.StopProcessAt(d.pidDir(), "spinifex-ui"); err != nil {
slog.Warn("Failed to stop spinifex-ui", "error", err)
} else {
stopped = append(stopped, "hive-ui")
stopped = append(stopped, "spinifex-ui")
}
}
+2 -2
View File
@@ -16,7 +16,7 @@ func TestShutdownACKMarshal(t *testing.T) {
ack := ShutdownACK{
Node: "node1",
Phase: "gate",
Stopped: []string{"awsgw", "hive-ui"},
Stopped: []string{"awsgw", "spinifex-ui"},
}
data, err := json.Marshal(ack)
@@ -165,7 +165,7 @@ func TestRespondShutdownACK(t *testing.T) {
ack: ShutdownACK{
Node: "test-node",
Phase: "gate",
Stopped: []string{"awsgw", "hive-ui"},
Stopped: []string{"awsgw", "spinifex-ui"},
},
},
{
+1 -1
View File
@@ -42,7 +42,7 @@ func createDaemonWithJetStream(t *testing.T) *Daemon {
natsURL := ns.ClientURL()
tmpDir, err := os.MkdirTemp("", "hive-state-test-*")
tmpDir, err := os.MkdirTemp("", "spx-state-test-*")
require.NoError(t, err)
t.Cleanup(func() { os.RemoveAll(tmpDir) })
+1 -1
View File
@@ -18,7 +18,7 @@ func testCreds() *SharedCredentials {
SecretKey: "testSecretKey1234567890",
AccountID: "123456789012",
NatsToken: "nats_testtoken123456",
ClusterName: "hive",
ClusterName: "spinifex",
Region: "us-west-1",
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Package gateway provides the AWS Gateway for the Hive platform.
// Package gateway provides the AWS Gateway for the Spinifex platform.
// It handles the incoming requests from the AWS SDK and delegates to the appropriate gateway functions (which calls the NATS handlers).
package gateway
@@ -7,7 +7,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
)
// DescribeAccountAttributes returns static account attributes for the Hive platform
// DescribeAccountAttributes returns static account attributes for the Spinifex platform
func DescribeAccountAttributes(input *ec2.DescribeAccountAttributesInput) (*ec2.DescribeAccountAttributesOutput, error) {
slog.Debug("DescribeAccountAttributes called")
@@ -11,7 +11,7 @@ import (
"github.com/nats-io/nats.go"
)
// DescribeInstanceTypes queries all hive nodes for their instance types via NATS
// DescribeInstanceTypes queries all spinifex nodes for their instance types via NATS
func DescribeInstanceTypes(input *ec2.DescribeInstanceTypesInput, natsConn *nats.Conn, expectedNodes int) (*ec2.DescribeInstanceTypesOutput, error) {
// Marshal input to JSON
jsonData, err := json.Marshal(input)
@@ -12,7 +12,7 @@ import (
"github.com/nats-io/nats.go"
)
// DescribeInstances queries all hive nodes for their instances via NATS
// DescribeInstances queries all spinifex nodes for their instances via NATS
// and aggregates the results into a single response
func DescribeInstances(input *ec2.DescribeInstancesInput, natsConn *nats.Conn, expectedNodes int, accountID string) (*ec2.DescribeInstancesOutput, error) {
// Marshal input to JSON
@@ -14,7 +14,7 @@ func DescribeAvailabilityZones(input *ec2.DescribeAvailabilityZonesInput, region
OptInStatus: aws.String("opt-in-not-required"),
RegionName: aws.String(region),
ZoneName: aws.String(az),
ZoneId: aws.String("hivez1"),
ZoneId: aws.String("spinifexz1"),
GroupName: aws.String(region),
NetworkBorderGroup: aws.String(region),
ZoneType: aws.String("availability-zone"),
+1 -1
View File
@@ -40,7 +40,7 @@ func TestDescribeAvailabilityZones(t *testing.T) {
assert.Equal(t, "opt-in-not-required", *zone.OptInStatus)
assert.Equal(t, tt.region, *zone.RegionName)
assert.Equal(t, tt.az, *zone.ZoneName)
assert.Equal(t, "hivez1", *zone.ZoneId)
assert.Equal(t, "spinifexz1", *zone.ZoneId)
assert.Equal(t, tt.region, *zone.GroupName)
assert.Equal(t, tt.region, *zone.NetworkBorderGroup)
assert.Equal(t, "availability-zone", *zone.ZoneType)
+4 -4
View File
@@ -40,7 +40,7 @@ type GatewayConfig struct {
DisableLogging bool `json:"disable_logging"`
NATSConn *nats.Conn // Shared NATS connection for service communication
Config string // Shared AWS Gateway config for S3 auth
ExpectedNodes int // Number of expected hive nodes for multi-node operations
ExpectedNodes int // Number of expected spinifex nodes for multi-node operations
Region string // Region this gateway is running in
AZ string // Availability zone this gateway is running in
IAMService handlers_iam.IAMService
@@ -108,7 +108,7 @@ func (gw *GatewayConfig) SetupRoutes() http.Handler {
}
// corsAllowedOrigins builds the set of allowed CORS origins from localhost and
// all local non-loopback IPs on the hive-ui port (default 3000). This allows
// all local non-loopback IPs on the spinifex-ui port (default 3000). This allows
// the UI to be accessed from any local address, not just localhost.
func corsAllowedOrigins() map[string]struct{} {
origins := map[string]struct{}{
@@ -143,7 +143,7 @@ func corsAllowedOrigins() map[string]struct{} {
return origins
}
// corsMiddleware adds CORS headers for browser requests from the hive-ui.
// corsMiddleware adds CORS headers for browser requests from the spinifex-ui.
func corsMiddleware(next http.Handler) http.Handler {
allowed := corsAllowedOrigins()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -415,7 +415,7 @@ func ParseArgsToStruct(input *any, args map[string]string) (err error) {
}
// DiscoverActiveNodes discovers the number of active hive daemon nodes in the cluster
// DiscoverActiveNodes discovers the number of active spinifex daemon nodes in the cluster
// by publishing a discovery request and counting unique responses.
// Returns the number of active nodes (minimum 1 if fallback is needed).
func (gw *GatewayConfig) DiscoverActiveNodes() int {
@@ -14,7 +14,7 @@ import (
)
const (
KVBucketAccountSettings = "hive-ec2-account-settings"
KVBucketAccountSettings = "spinifex-ec2-account-settings"
KeyEbsEncryptionDefault = "ebs-encryption-default"
KeySerialConsoleAccess = "serial-console-access"
)
+1 -1
View File
@@ -20,7 +20,7 @@ import (
// Ensure EgressOnlyIGWServiceImpl implements EgressOnlyIGWService
var _ EgressOnlyIGWService = (*EgressOnlyIGWServiceImpl)(nil)
const KVBucketEgressOnlyIGW = "hive-eigw"
const KVBucketEgressOnlyIGW = "spinifex-eigw"
// EgressOnlyIGWRecord represents a stored Egress-only Internet Gateway
type EgressOnlyIGWRecord struct {
+1 -1
View File
@@ -21,7 +21,7 @@ import (
// Ensure IGWServiceImpl implements IGWService
var _ IGWService = (*IGWServiceImpl)(nil)
const KVBucketIGW = "hive-igw"
const KVBucketIGW = "spinifex-igw"
// IGWRecord represents a stored Internet Gateway
type IGWRecord struct {
+1 -1
View File
@@ -162,7 +162,7 @@ func (s *ImageServiceImpl) DescribeImages(input *ec2.DescribeImagesInput, accoun
// Determine AMI ownership. Phase4+ AMIs store the creator's account ID
// in ImageOwnerAlias. Pre-phase4 AMIs have non-account values like "self"
// or "hive" and are treated as system/public images visible to all.
// or "spinifex" and are treated as system/public images visible to all.
amiOwner := amiMeta.ImageOwnerAlias
isSystemAMI := !utils.IsAccountID(amiOwner)
@@ -317,7 +317,7 @@ func TestDescribeImages_FilterByOwnerSelf(t *testing.T) {
createTestAMIConfigWithOwner(t, store, "ami-selfowned", "self-owned-ami", testAccountID)
// Create a system AMI (should not appear with --owners self)
createTestAMIConfigWithOwner(t, store, "ami-system", "system-ami", "hive")
createTestAMIConfigWithOwner(t, store, "ami-system", "system-ami", "spinifex")
// Filter by "self" should return only the caller's AMI
result, err := svc.DescribeImages(&ec2.DescribeImagesInput{
@@ -401,7 +401,7 @@ func TestDescribeImages_SystemAMIVisibleToAll(t *testing.T) {
svc, store := setupTestImageService(t)
// Create a system/pre-phase4 AMI (non-account-ID owner)
createTestAMIConfigWithOwner(t, store, "ami-system123", "system-ami", "hive")
createTestAMIConfigWithOwner(t, store, "ami-system123", "system-ami", "spinifex")
// Any account should be able to see system AMIs
result, err := svc.DescribeImages(&ec2.DescribeImagesInput{
@@ -18,7 +18,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/config"
"github.com/mulgadc/spinifex/spinifex/objectstore"
hivetypes "github.com/mulgadc/spinifex/spinifex/types"
spxtypes "github.com/mulgadc/spinifex/spinifex/types"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/mulgadc/spinifex/spinifex/vm"
"github.com/mulgadc/viperblock/types"
@@ -355,7 +355,7 @@ func (s *InstanceServiceImpl) prepareRootVolume(input *ec2.RunInstancesInput, im
// Append root volume to instance
instance.EBSRequests.Mu.Lock()
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, hivetypes.EBSRequest{
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, spxtypes.EBSRequest{
Name: imageId,
Boot: true,
DeleteOnTermination: deleteOnTermination,
@@ -497,7 +497,7 @@ func (s *InstanceServiceImpl) prepareEFIVolume(imageId string, volumeConfig vipe
}
instance.EBSRequests.Mu.Lock()
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, hivetypes.EBSRequest{
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, spxtypes.EBSRequest{
Name: efiVb.VolumeName,
Boot: false,
EFI: true,
@@ -573,7 +573,7 @@ func (s *InstanceServiceImpl) prepareCloudInitVolume(input *ec2.RunInstancesInpu
}
instance.EBSRequests.Mu.Lock()
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, hivetypes.EBSRequest{
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, spxtypes.EBSRequest{
Name: cloudInitVolumeName,
Boot: false,
CloudInit: true,
@@ -739,7 +739,7 @@ func (s *InstanceServiceImpl) createCloudInitISO(input *ec2.RunInstancesInput, i
func generateHostname(instanceID string) string {
if len(instanceID) > 2 {
uniquePart := instanceID[2:10] // Take first 8 chars after "i-"
return fmt.Sprintf("hive-vm-%s", uniquePart)
return fmt.Sprintf("spinifex-vm-%s", uniquePart)
}
return "hive-vm-unknown"
return "spinifex-vm-unknown"
}
@@ -45,22 +45,22 @@ func TestGenerateHostname(t *testing.T) {
{
name: "Normal instance ID",
instanceID: "i-0123456789abcdef0",
want: "hive-vm-01234567",
want: "spinifex-vm-01234567",
},
{
name: "Too short (2 chars)",
instanceID: "ab",
want: "hive-vm-unknown",
want: "spinifex-vm-unknown",
},
{
name: "Empty string",
instanceID: "",
want: "hive-vm-unknown",
want: "spinifex-vm-unknown",
},
{
name: "Exactly 10 chars",
instanceID: "i-abcdef01",
want: "hive-vm-abcdef01",
want: "spinifex-vm-abcdef01",
},
}
@@ -181,12 +181,12 @@ func TestCloudInitTemplateRendering(t *testing.T) {
data: CloudInitData{
Username: "ec2-user",
SSHKey: "ssh-rsa AAAAB3... user@host",
Hostname: "hive-vm-01234567",
Hostname: "spinifex-vm-01234567",
},
contains: []string{
"ec2-user",
"ssh-rsa AAAAB3... user@host",
"hive-vm-01234567",
"spinifex-vm-01234567",
"#cloud-config",
},
},
@@ -195,7 +195,7 @@ func TestCloudInitTemplateRendering(t *testing.T) {
data: CloudInitData{
Username: "ec2-user",
SSHKey: "ssh-ed25519 AAAA...",
Hostname: "hive-vm-abcdef01",
Hostname: "spinifex-vm-abcdef01",
UserDataCloudConfig: "packages:\n - nginx",
},
contains: []string{
@@ -209,7 +209,7 @@ func TestCloudInitTemplateRendering(t *testing.T) {
data: CloudInitData{
Username: "ec2-user",
SSHKey: "ssh-rsa AAAA...",
Hostname: "hive-vm-test",
Hostname: "spinifex-vm-test",
UserDataScript: " #!/bin/bash\n echo hello",
},
contains: []string{
@@ -239,7 +239,7 @@ func TestCloudInitTemplateRendering(t *testing.T) {
func TestCloudInitMetaTemplateRendering(t *testing.T) {
data := CloudInitMetaData{
InstanceID: "i-0123456789abcdef0",
Hostname: "hive-vm-01234567",
Hostname: "spinifex-vm-01234567",
}
tmpl := template.Must(template.New("meta-data").Parse(cloudInitMetaTemplate))
@@ -249,7 +249,7 @@ func TestCloudInitMetaTemplateRendering(t *testing.T) {
rendered := buf.String()
assert.Contains(t, rendered, "i-0123456789abcdef0")
assert.Contains(t, rendered, "hive-vm-01234567")
assert.Contains(t, rendered, "spinifex-vm-01234567")
assert.Contains(t, rendered, "instance-id:")
assert.Contains(t, rendered, "local-hostname:")
}
+1 -1
View File
@@ -101,7 +101,7 @@ func (s *KeyServiceImpl) CreateKeyPair(input *ec2.CreateKeyPairInput, accountID
}
// Create temporary directory for key generation
tmpDir, err := os.MkdirTemp("", "hive-keypair-*")
tmpDir, err := os.MkdirTemp("", "spinifex-keypair-*")
if err != nil {
slog.Error("Failed to create temp directory", "err", err)
return nil, errors.New(awserrors.ErrorServerInternal)
@@ -26,7 +26,7 @@ import (
// Ensure SnapshotServiceImpl implements SnapshotService
var _ SnapshotService = (*SnapshotServiceImpl)(nil)
const KVBucketVolumeSnapshots = "hive-volume-snapshots"
const KVBucketVolumeSnapshots = "spinifex-volume-snapshots"
// SnapshotServiceImpl implements SnapshotService with S3-backed storage
type SnapshotServiceImpl struct {
@@ -282,7 +282,7 @@ func TestCreateVolume_FromSnapshot_PassesValidation(t *testing.T) {
snapshotID := "snap-test123"
// Create snapshot metadata in store (matches hive snapshot service format)
// Create snapshot metadata in store (matches spinifex snapshot service format)
snapMeta := snapshotMetadata{
VolumeID: "vol-source",
VolumeSize: 50,
@@ -461,7 +461,7 @@ func setupTestVolumeKV(t *testing.T) nats.KeyValue {
require.NoError(t, err)
kv, err := js.CreateKeyValue(&nats.KeyValueConfig{
Bucket: "hive-volume-snapshots",
Bucket: "spinifex-volume-snapshots",
})
require.NoError(t, err)
return kv
+1 -1
View File
@@ -16,7 +16,7 @@ import (
)
const (
KVBucketENIs = "hive-vpc-enis"
KVBucketENIs = "spinifex-vpc-enis"
)
// ENIRecord represents a stored Elastic Network Interface
+1 -1
View File
@@ -13,7 +13,7 @@ import (
)
const (
KVBucketIPAM = "hive-vpc-ipam"
KVBucketIPAM = "spinifex-vpc-ipam"
)
// IPAMRecord tracks allocated IPs for a subnet.
+3 -3
View File
@@ -21,9 +21,9 @@ import (
var _ VPCService = (*VPCServiceImpl)(nil)
const (
KVBucketVPCs = "hive-vpc-vpcs"
KVBucketSubnets = "hive-vpc-subnets"
KVBucketVNICounter = "hive-vpc-vni-counter"
KVBucketVPCs = "spinifex-vpc-vpcs"
KVBucketSubnets = "spinifex-vpc-subnets"
KVBucketVNICounter = "spinifex-vpc-vni-counter"
vniCounterKey = "counter"
vniStart = 100 // Starting VNI value (avoid 0 and low numbers)
)
+3 -3
View File
@@ -280,7 +280,7 @@ func TestBootstrapData_ExtendedRoundTrip(t *testing.T) {
AccountID: "000000000000",
Admin: &AdminBootstrapData{
AccountID: "000000000001",
AccountName: "hive",
AccountName: "spinifex",
UserName: "admin",
AccessKeyID: "AKIAADMIN12345678901",
EncryptedSecret: "YWRtaW4tc2VjcmV0",
@@ -311,8 +311,8 @@ func TestBootstrapData_ExtendedRoundTrip(t *testing.T) {
if loaded.Admin.AccountID != "000000000001" {
t.Fatalf("Admin.AccountID: expected %q, got %q", "000000000001", loaded.Admin.AccountID)
}
if loaded.Admin.AccountName != "hive" {
t.Fatalf("Admin.AccountName: expected %q, got %q", "hive", loaded.Admin.AccountName)
if loaded.Admin.AccountName != "spinifex" {
t.Fatalf("Admin.AccountName: expected %q, got %q", "spinifex", loaded.Admin.AccountName)
}
if loaded.Admin.UserName != "admin" {
t.Fatalf("Admin.UserName: expected %q, got %q", "admin", loaded.Admin.UserName)
+5 -5
View File
@@ -21,11 +21,11 @@ import (
)
const (
KVBucketUsers = "hive-iam-users"
KVBucketAccessKeys = "hive-iam-access-keys"
KVBucketPolicies = "hive-iam-policies"
KVBucketAccounts = "hive-accounts"
KVBucketAccountCounter = "hive-account-counter"
KVBucketUsers = "spinifex-iam-users"
KVBucketAccessKeys = "spinifex-iam-access-keys"
KVBucketPolicies = "spinifex-iam-policies"
KVBucketAccounts = "spinifex-accounts"
KVBucketAccountCounter = "spinifex-account-counter"
maxAccessKeysPerUser = 2
)
+2 -2
View File
@@ -746,7 +746,7 @@ func TestSeedBootstrap_WithAdmin(t *testing.T) {
AccountID: utils.GlobalAccountID,
Admin: &AdminBootstrapData{
AccountID: "000000000001",
AccountName: "hive",
AccountName: "spinifex",
UserName: "admin",
AccessKeyID: "AKIAADMIN12345678901",
EncryptedSecret: adminSecret,
@@ -765,7 +765,7 @@ func TestSeedBootstrap_WithAdmin(t *testing.T) {
account, err := svc.GetAccount("000000000001")
require.NoError(t, err)
assert.Equal(t, "000000000001", account.AccountID)
assert.Equal(t, "hive", account.AccountName)
assert.Equal(t, "spinifex", account.AccountName)
assert.Equal(t, "ACTIVE", account.Status)
// Verify admin user exists
+1 -1
View File
@@ -119,7 +119,7 @@ type AdminBootstrapData struct {
EncryptedSecret string `json:"encrypted_secret"`
}
// Account represents a Hive account. Accounts namespace IAM users,
// Account represents a Spinifex account. Accounts namespace IAM users,
// policies, and resources. Created via CLI only in v1.
type Account struct {
AccountID string `json:"account_id"` // 12-digit zero-padded, sequential
+5 -5
View File
@@ -116,7 +116,7 @@ func TestMemoryObjectStore_ListObjects(t *testing.T) {
for key, value := range objects {
_, err := store.PutObject(&s3.PutObjectInput{
Bucket: aws.String("hive-metadata"),
Bucket: aws.String("spinifex-metadata"),
Key: aws.String(key),
Body: bytes.NewReader([]byte(value)),
})
@@ -125,14 +125,14 @@ func TestMemoryObjectStore_ListObjects(t *testing.T) {
// List all objects in bucket
output, err := store.ListObjects(&s3.ListObjectsInput{
Bucket: aws.String("hive-metadata"),
Bucket: aws.String("spinifex-metadata"),
})
require.NoError(t, err)
assert.Len(t, output.Contents, 5)
// List objects with prefix
output, err = store.ListObjects(&s3.ListObjectsInput{
Bucket: aws.String("hive-metadata"),
Bucket: aws.String("spinifex-metadata"),
Prefix: aws.String("images/"),
})
require.NoError(t, err)
@@ -162,7 +162,7 @@ func TestMemoryObjectStore_ListObjectsWithDelimiter(t *testing.T) {
for _, key := range objects {
_, err := store.PutObject(&s3.PutObjectInput{
Bucket: aws.String("hive"),
Bucket: aws.String("spinifex"),
Key: aws.String(key),
Body: bytes.NewReader([]byte("{}")),
})
@@ -171,7 +171,7 @@ func TestMemoryObjectStore_ListObjectsWithDelimiter(t *testing.T) {
// List with delimiter at root level
output, err := store.ListObjects(&s3.ListObjectsInput{
Bucket: aws.String("hive"),
Bucket: aws.String("spinifex"),
Delimiter: aws.String("/"),
})
require.NoError(t, err)
+2 -2
View File
@@ -4,7 +4,7 @@ Full-stack application for managing Spinifex AWS compatible products (EC2, S3)
## Development Setup
Note: This is not needed for actually building spinifex-ui and running it. We commit the assets so that when you build hive, it will include spinifex-ui without you needing to install node and build frontend from source.
Note: This is not needed for actually building spinifex-ui and running it. We commit the assets so that when you build spinifex, it will include spinifex-ui without you needing to install node and build frontend from source.
1. **Install Node.js using nvm**
@@ -37,7 +37,7 @@ Note: This is not needed for actually building spinifex-ui and running it. We co
4. **Accept Certs In Browser**
If you have added the CA to your machine you do not need to do this. But if you are sshd into a hive machine and want to view the ui, go to [https://localhost:9999](https://localhost:9999) and [https://localhost:8443](https://localhost:8443) and accept the certificates
If you have added the CA to your machine you do not need to do this. But if you are sshd into a spinifex machine and want to view the ui, go to [https://localhost:9999](https://localhost:9999) and [https://localhost:8443](https://localhost:8443) and accept the certificates
5. **Launch Server**
@@ -7,10 +7,10 @@ package viperblockd
// which blocks waiting for SIGTERM. The goroutines are cleaned up when the test process exits.
//
// IMPORTANT: Run these tests with a timeout flag to prevent hanging:
// go test ./hive/services/viperblockd/... -timeout 40s
// go test ./spinifex/services/viperblockd/... -timeout 40s
//
// To skip integration tests and run only unit tests:
// go test -short ./hive/services/viperblockd/...
// go test -short ./spinifex/services/viperblockd/...
import (
"encoding/json"
+4 -4
View File
@@ -86,8 +86,8 @@ func TestIntegration_VPCLifecycle(t *testing.T) {
if err != nil {
t.Fatalf("expected VPC router: %v", err)
}
if router.ExternalIDs["hive:vpc_id"] != "vpc-integ1" {
t.Errorf("router vpc_id = %s, want vpc-integ1", router.ExternalIDs["hive:vpc_id"])
if router.ExternalIDs["spinifex:vpc_id"] != "vpc-integ1" {
t.Errorf("router vpc_id = %s, want vpc-integ1", router.ExternalIDs["spinifex:vpc_id"])
}
// === Phase 2: Create Subnet ===
@@ -104,8 +104,8 @@ func TestIntegration_VPCLifecycle(t *testing.T) {
if err != nil {
t.Fatalf("expected logical switch: %v", err)
}
if ls.ExternalIDs["hive:subnet_id"] != "subnet-integ1" {
t.Errorf("switch subnet_id = %s, want subnet-integ1", ls.ExternalIDs["hive:subnet_id"])
if ls.ExternalIDs["spinifex:subnet_id"] != "subnet-integ1" {
t.Errorf("switch subnet_id = %s, want subnet-integ1", ls.ExternalIDs["spinifex:subnet_id"])
}
dhcp, err := mock.FindDHCPOptionsByCIDR(ctx, "10.0.1.0/24")
if err != nil {
+3 -3
View File
@@ -1,13 +1,13 @@
// Package nbdb contains Go structs representing the OVN Northbound Database schema.
// These models are used with libovsdb to interact with the OVN NB DB.
//
// The structs cover the core tables needed for Hive VPC networking:
// The structs cover the core tables needed for Spinifex VPC networking:
// LogicalSwitch, LogicalSwitchPort, LogicalRouter, LogicalRouterPort, and DHCPOptions.
//
// To regenerate from the full OVN NB schema (requires OVN installed):
//
// go install github.com/ovn-kubernetes/libovsdb/cmd/modelgen@latest
// modelgen -p nbdb -o hive/services/vpcd/nbdb /usr/share/ovn/ovn-nb.ovsschema
// modelgen -p nbdb -o spinifex/services/vpcd/nbdb /usr/share/ovn/ovn-nb.ovsschema
package nbdb
import "github.com/ovn-kubernetes/libovsdb/model"
@@ -102,7 +102,7 @@ type GatewayChassis struct {
}
// FullDatabaseModel returns a ClientDBModel for the OVN Northbound database
// containing all tables needed for Hive VPC networking.
// containing all tables needed for Spinifex VPC networking.
func FullDatabaseModel() (model.ClientDBModel, error) {
return model.NewClientDBModel("OVN_Northbound", map[string]model.Model{
"Logical_Switch": &LogicalSwitch{},
+32 -32
View File
@@ -131,8 +131,8 @@ func (h *TopologyHandler) handleVPCCreate(msg *nats.Msg) {
lr := &nbdb.LogicalRouter{
Name: routerName,
ExternalIDs: map[string]string{
"hive:vpc_id": evt.VpcId,
"hive:vni": fmt.Sprintf("%d", evt.VNI),
"spinifex:vpc_id": evt.VpcId,
"spinifex:vni": fmt.Sprintf("%d", evt.VNI),
},
}
@@ -168,7 +168,7 @@ func (h *TopologyHandler) handleVPCDelete(msg *nats.Msg) {
slog.Warn("vpcd: failed to list switches during VPC delete", "err", err)
} else {
for _, ls := range switches {
if ls.ExternalIDs["hive:vpc_id"] == evt.VpcId {
if ls.ExternalIDs["spinifex:vpc_id"] == evt.VpcId {
// Delete switch ports first
for range ls.Ports {
// Ports are UUIDs; best-effort cleanup
@@ -186,7 +186,7 @@ func (h *TopologyHandler) handleVPCDelete(msg *nats.Msg) {
slog.Warn("vpcd: failed to list DHCP options during VPC delete", "err", err)
} else {
for _, opts := range dhcpOpts {
if opts.ExternalIDs["hive:vpc_id"] == evt.VpcId {
if opts.ExternalIDs["spinifex:vpc_id"] == evt.VpcId {
if err := h.ovn.DeleteDHCPOptions(ctx, opts.UUID); err != nil {
slog.Warn("vpcd: failed to delete DHCP options during VPC cascade", "uuid", opts.UUID, "err", err)
}
@@ -248,8 +248,8 @@ func (h *TopologyHandler) handleSubnetCreate(msg *nats.Msg) {
ls := &nbdb.LogicalSwitch{
Name: switchName,
ExternalIDs: map[string]string{
"hive:subnet_id": evt.SubnetId,
"hive:vpc_id": evt.VpcId,
"spinifex:subnet_id": evt.SubnetId,
"spinifex:vpc_id": evt.VpcId,
},
}
if err := h.ovn.CreateLogicalSwitch(ctx, ls); err != nil {
@@ -264,8 +264,8 @@ func (h *TopologyHandler) handleSubnetCreate(msg *nats.Msg) {
MAC: routerMAC,
Networks: []string{gwCIDR},
ExternalIDs: map[string]string{
"hive:subnet_id": evt.SubnetId,
"hive:vpc_id": evt.VpcId,
"spinifex:subnet_id": evt.SubnetId,
"spinifex:vpc_id": evt.VpcId,
},
}
if err := h.ovn.CreateLogicalRouterPort(ctx, routerName, lrp); err != nil {
@@ -285,8 +285,8 @@ func (h *TopologyHandler) handleSubnetCreate(msg *nats.Msg) {
"router-port": routerPortName,
},
ExternalIDs: map[string]string{
"hive:subnet_id": evt.SubnetId,
"hive:vpc_id": evt.VpcId,
"spinifex:subnet_id": evt.SubnetId,
"spinifex:vpc_id": evt.VpcId,
},
}
if err := h.ovn.CreateLogicalSwitchPort(ctx, switchName, lsp); err != nil {
@@ -309,8 +309,8 @@ func (h *TopologyHandler) handleSubnetCreate(msg *nats.Msg) {
"mtu": "1442", // Geneve overhead
},
ExternalIDs: map[string]string{
"hive:subnet_id": evt.SubnetId,
"hive:vpc_id": evt.VpcId,
"spinifex:subnet_id": evt.SubnetId,
"spinifex:vpc_id": evt.VpcId,
},
}
if _, err := h.ovn.CreateDHCPOptions(ctx, dhcpOpts); err != nil {
@@ -408,14 +408,14 @@ func (h *TopologyHandler) handleCreatePort(msg *nats.Msg) {
Addresses: []string{addrStr},
PortSecurity: []string{addrStr},
ExternalIDs: map[string]string{
"hive:eni_id": evt.NetworkInterfaceId,
"hive:subnet_id": evt.SubnetId,
"hive:vpc_id": evt.VpcId,
"spinifex:eni_id": evt.NetworkInterfaceId,
"spinifex:subnet_id": evt.SubnetId,
"spinifex:vpc_id": evt.VpcId,
},
}
// Look up DHCP options for the subnet and attach to the port
dhcpOpts, err := h.ovn.FindDHCPOptionsByExternalID(ctx, "hive:subnet_id", evt.SubnetId)
dhcpOpts, err := h.ovn.FindDHCPOptionsByExternalID(ctx, "spinifex:subnet_id", evt.SubnetId)
if err != nil {
slog.Warn("vpcd: DHCP options not found for subnet, port will not have DHCP", "subnet", evt.SubnetId, "err", err)
} else {
@@ -501,9 +501,9 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
extSwitch := &nbdb.LogicalSwitch{
Name: extSwitchName,
ExternalIDs: map[string]string{
"hive:vpc_id": evt.VpcId,
"hive:igw_id": evt.InternetGatewayId,
"hive:role": "external",
"spinifex:vpc_id": evt.VpcId,
"spinifex:igw_id": evt.InternetGatewayId,
"spinifex:role": "external",
},
}
if err := h.ovn.CreateLogicalSwitch(ctx, extSwitch); err != nil {
@@ -521,8 +521,8 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
"network_name": "external",
},
ExternalIDs: map[string]string{
"hive:vpc_id": evt.VpcId,
"hive:igw_id": evt.InternetGatewayId,
"spinifex:vpc_id": evt.VpcId,
"spinifex:igw_id": evt.InternetGatewayId,
},
}
if err := h.ovn.CreateLogicalSwitchPort(ctx, extSwitchName, localnetPort); err != nil {
@@ -539,9 +539,9 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
MAC: gwMAC,
Networks: []string{"169.254.0.1/30"}, // link-local for external transit
ExternalIDs: map[string]string{
"hive:vpc_id": evt.VpcId,
"hive:igw_id": evt.InternetGatewayId,
"hive:role": "gateway",
"spinifex:vpc_id": evt.VpcId,
"spinifex:igw_id": evt.InternetGatewayId,
"spinifex:role": "gateway",
},
}
if err := h.ovn.CreateLogicalRouterPort(ctx, routerName, lrp); err != nil {
@@ -560,8 +560,8 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
"router-port": gwPortName,
},
ExternalIDs: map[string]string{
"hive:vpc_id": evt.VpcId,
"hive:igw_id": evt.InternetGatewayId,
"spinifex:vpc_id": evt.VpcId,
"spinifex:igw_id": evt.InternetGatewayId,
},
}
if err := h.ovn.CreateLogicalSwitchPort(ctx, extSwitchName, switchGWPort); err != nil {
@@ -578,7 +578,7 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
if err != nil {
slog.Warn("vpcd: failed to get router for SNAT, skipping NAT setup", "router", routerName, "err", err)
} else {
vpcCIDR := router.ExternalIDs["hive:cidr"]
vpcCIDR := router.ExternalIDs["spinifex:cidr"]
if vpcCIDR == "" {
// Fall back to a reasonable default — list subnets for this VPC
vpcCIDR = "10.0.0.0/8"
@@ -588,8 +588,8 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
ExternalIP: "169.254.0.1",
LogicalIP: vpcCIDR,
ExternalIDs: map[string]string{
"hive:vpc_id": evt.VpcId,
"hive:igw_id": evt.InternetGatewayId,
"spinifex:vpc_id": evt.VpcId,
"spinifex:igw_id": evt.InternetGatewayId,
},
}
if err := h.ovn.AddNAT(ctx, routerName, snatRule); err != nil {
@@ -602,8 +602,8 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
IPPrefix: "0.0.0.0/0",
Nexthop: "169.254.0.2",
ExternalIDs: map[string]string{
"hive:vpc_id": evt.VpcId,
"hive:igw_id": evt.InternetGatewayId,
"spinifex:vpc_id": evt.VpcId,
"spinifex:igw_id": evt.InternetGatewayId,
},
}
if err := h.ovn.AddStaticRoute(ctx, routerName, defaultRoute); err != nil {
@@ -650,7 +650,7 @@ func (h *TopologyHandler) handleIGWDetach(msg *nats.Msg) {
if err != nil {
slog.Warn("vpcd: failed to get router for NAT cleanup", "router", routerName, "err", err)
} else {
vpcCIDR := router.ExternalIDs["hive:cidr"]
vpcCIDR := router.ExternalIDs["spinifex:cidr"]
if vpcCIDR == "" {
vpcCIDR = "10.0.0.0/8"
}
+45 -45
View File
@@ -82,11 +82,11 @@ func TestTopologyHandler_VPCCreate(t *testing.T) {
if err != nil {
t.Fatalf("expected logical router: %v", err)
}
if lr.ExternalIDs["hive:vpc_id"] != "vpc-abc123" {
if lr.ExternalIDs["spinifex:vpc_id"] != "vpc-abc123" {
t.Errorf("expected vpc_id external_id, got %v", lr.ExternalIDs)
}
if lr.ExternalIDs["hive:vni"] != "100" {
t.Errorf("expected vni external_id=100, got %v", lr.ExternalIDs["hive:vni"])
if lr.ExternalIDs["spinifex:vni"] != "100" {
t.Errorf("expected vni external_id=100, got %v", lr.ExternalIDs["spinifex:vni"])
}
}
@@ -173,7 +173,7 @@ func TestTopologyHandler_SubnetCreate(t *testing.T) {
if err != nil {
t.Fatalf("expected logical switch: %v", err)
}
if ls.ExternalIDs["hive:subnet_id"] != "subnet-aaa" {
if ls.ExternalIDs["spinifex:subnet_id"] != "subnet-aaa" {
t.Errorf("expected subnet_id external_id, got %v", ls.ExternalIDs)
}
@@ -509,14 +509,14 @@ func TestTopologyHandler_CreatePort(t *testing.T) {
}
// Verify external IDs
if lsp.ExternalIDs["hive:eni_id"] != "eni-aaa111" {
t.Errorf("expected eni_id=eni-aaa111, got %s", lsp.ExternalIDs["hive:eni_id"])
if lsp.ExternalIDs["spinifex:eni_id"] != "eni-aaa111" {
t.Errorf("expected eni_id=eni-aaa111, got %s", lsp.ExternalIDs["spinifex:eni_id"])
}
if lsp.ExternalIDs["hive:subnet_id"] != "subnet-port1" {
t.Errorf("expected subnet_id=subnet-port1, got %s", lsp.ExternalIDs["hive:subnet_id"])
if lsp.ExternalIDs["spinifex:subnet_id"] != "subnet-port1" {
t.Errorf("expected subnet_id=subnet-port1, got %s", lsp.ExternalIDs["spinifex:subnet_id"])
}
if lsp.ExternalIDs["hive:vpc_id"] != "vpc-port1" {
t.Errorf("expected vpc_id=vpc-port1, got %s", lsp.ExternalIDs["hive:vpc_id"])
if lsp.ExternalIDs["spinifex:vpc_id"] != "vpc-port1" {
t.Errorf("expected vpc_id=vpc-port1, got %s", lsp.ExternalIDs["spinifex:vpc_id"])
}
// Verify port was added to the switch
@@ -553,9 +553,9 @@ func TestTopologyHandler_DeletePort(t *testing.T) {
Addresses: []string{"02:00:00:44:55:66 10.0.2.4"},
PortSecurity: []string{"02:00:00:44:55:66 10.0.2.4"},
ExternalIDs: map[string]string{
"hive:eni_id": "eni-bbb222",
"hive:subnet_id": "subnet-del2",
"hive:vpc_id": "vpc-del2",
"spinifex:eni_id": "eni-bbb222",
"spinifex:subnet_id": "subnet-del2",
"spinifex:vpc_id": "vpc-del2",
},
})
@@ -811,8 +811,8 @@ func TestTopologyHandler_IGWAttach(t *testing.T) {
_ = mock.CreateLogicalRouter(ctx, &nbdb.LogicalRouter{
Name: "vpc-vpc-igw1",
ExternalIDs: map[string]string{
"hive:vpc_id": "vpc-igw1",
"hive:cidr": "10.0.0.0/16",
"spinifex:vpc_id": "vpc-igw1",
"spinifex:cidr": "10.0.0.0/16",
},
})
@@ -830,11 +830,11 @@ func TestTopologyHandler_IGWAttach(t *testing.T) {
if err != nil {
t.Fatalf("expected external switch: %v", err)
}
if extSwitch.ExternalIDs["hive:role"] != "external" {
t.Errorf("expected role=external, got %s", extSwitch.ExternalIDs["hive:role"])
if extSwitch.ExternalIDs["spinifex:role"] != "external" {
t.Errorf("expected role=external, got %s", extSwitch.ExternalIDs["spinifex:role"])
}
if extSwitch.ExternalIDs["hive:igw_id"] != "igw-test1" {
t.Errorf("expected igw_id=igw-test1, got %s", extSwitch.ExternalIDs["hive:igw_id"])
if extSwitch.ExternalIDs["spinifex:igw_id"] != "igw-test1" {
t.Errorf("expected igw_id=igw-test1, got %s", extSwitch.ExternalIDs["spinifex:igw_id"])
}
// Verify localnet port created on external switch
@@ -890,8 +890,8 @@ func TestTopologyHandler_IGWDetach(t *testing.T) {
_ = mock.CreateLogicalRouter(ctx, &nbdb.LogicalRouter{
Name: "vpc-vpc-igw2",
ExternalIDs: map[string]string{
"hive:vpc_id": "vpc-igw2",
"hive:cidr": "10.0.0.0/16",
"spinifex:vpc_id": "vpc-igw2",
"spinifex:cidr": "10.0.0.0/16",
},
})
@@ -1351,7 +1351,7 @@ func TestTopologyHandler_CreatePort_Idempotent(t *testing.T) {
Name: "port-eni-idem",
Addresses: []string{"02:00:00:11:22:33 10.0.1.4"},
ExternalIDs: map[string]string{
"hive:eni_id": "eni-idem",
"spinifex:eni_id": "eni-idem",
},
})
@@ -1457,8 +1457,8 @@ func TestTopologyHandler_IGWAttach_Idempotent(t *testing.T) {
_ = mock.CreateLogicalSwitch(ctx, &nbdb.LogicalSwitch{
Name: "ext-vpc-igw-idem",
ExternalIDs: map[string]string{
"hive:vpc_id": "vpc-igw-idem",
"hive:role": "external",
"spinifex:vpc_id": "vpc-igw-idem",
"spinifex:role": "external",
},
})
@@ -1493,16 +1493,16 @@ func TestTopologyHandler_IGWDetach_PartialCleanup(t *testing.T) {
_ = mock.CreateLogicalSwitch(ctx, &nbdb.LogicalSwitch{
Name: "ext-vpc-partial",
ExternalIDs: map[string]string{
"hive:vpc_id": "vpc-partial",
"hive:role": "external",
"spinifex:vpc_id": "vpc-partial",
"spinifex:role": "external",
},
})
// Create router so NAT cleanup path is exercised (but NAT won't be found)
_ = mock.CreateLogicalRouter(ctx, &nbdb.LogicalRouter{
Name: "vpc-vpc-partial",
ExternalIDs: map[string]string{
"hive:vpc_id": "vpc-partial",
"hive:cidr": "10.0.0.0/16",
"spinifex:vpc_id": "vpc-partial",
"spinifex:cidr": "10.0.0.0/16",
},
})
@@ -1543,8 +1543,8 @@ func TestTopologyHandler_IGWDetach_NoRouter(t *testing.T) {
_ = mock.CreateLogicalSwitch(ctx, &nbdb.LogicalSwitch{
Name: "ext-vpc-nortr",
ExternalIDs: map[string]string{
"hive:vpc_id": "vpc-nortr",
"hive:role": "external",
"spinifex:vpc_id": "vpc-nortr",
"spinifex:role": "external",
},
})
@@ -1613,18 +1613,18 @@ func TestTopologyHandler_VPCDeleteCascade_WithPorts(t *testing.T) {
Name: "port-eni-casp1",
Addresses: []string{"02:00:00:11:22:33 10.0.1.4"},
ExternalIDs: map[string]string{
"hive:eni_id": "eni-casp1",
"hive:subnet_id": "sub-casp",
"hive:vpc_id": "vpc-casp",
"spinifex:eni_id": "eni-casp1",
"spinifex:subnet_id": "sub-casp",
"spinifex:vpc_id": "vpc-casp",
},
})
_ = mock.CreateLogicalSwitchPort(ctx, "subnet-sub-casp", &nbdb.LogicalSwitchPort{
Name: "port-eni-casp2",
Addresses: []string{"02:00:00:44:55:66 10.0.1.5"},
ExternalIDs: map[string]string{
"hive:eni_id": "eni-casp2",
"hive:subnet_id": "sub-casp",
"hive:vpc_id": "vpc-casp",
"spinifex:eni_id": "eni-casp2",
"spinifex:subnet_id": "sub-casp",
"spinifex:vpc_id": "vpc-casp",
},
})
_, _ = mock.CreateDHCPOptions(ctx, nbdbDHCPOptions("10.0.1.0/24", "sub-casp", "vpc-casp"))
@@ -1714,7 +1714,7 @@ func nbdbLogicalRouter(name, vpcId string) *nbdb.LogicalRouter {
return &nbdb.LogicalRouter{
Name: name,
ExternalIDs: map[string]string{
"hive:vpc_id": vpcId,
"spinifex:vpc_id": vpcId,
},
}
}
@@ -1723,8 +1723,8 @@ func nbdbLogicalSwitch(name, subnetId, vpcId string) *nbdb.LogicalSwitch {
return &nbdb.LogicalSwitch{
Name: name,
ExternalIDs: map[string]string{
"hive:subnet_id": subnetId,
"hive:vpc_id": vpcId,
"spinifex:subnet_id": subnetId,
"spinifex:vpc_id": vpcId,
},
}
}
@@ -1735,8 +1735,8 @@ func nbdbLogicalRouterPort(name, subnetId, vpcId string) *nbdb.LogicalRouterPort
MAC: "02:00:00:aa:bb:cc",
Networks: []string{"10.0.2.1/24"},
ExternalIDs: map[string]string{
"hive:subnet_id": subnetId,
"hive:vpc_id": vpcId,
"spinifex:subnet_id": subnetId,
"spinifex:vpc_id": vpcId,
},
}
}
@@ -1750,8 +1750,8 @@ func nbdbLogicalSwitchPortRouter(name, routerPort, subnetId, vpcId string) *nbdb
"router-port": routerPort,
},
ExternalIDs: map[string]string{
"hive:subnet_id": subnetId,
"hive:vpc_id": vpcId,
"spinifex:subnet_id": subnetId,
"spinifex:vpc_id": vpcId,
},
}
}
@@ -1764,8 +1764,8 @@ func nbdbDHCPOptions(cidr, subnetId, vpcId string) *nbdb.DHCPOptions {
"lease_time": "3600",
},
ExternalIDs: map[string]string{
"hive:subnet_id": subnetId,
"hive:vpc_id": vpcId,
"spinifex:subnet_id": subnetId,
"spinifex:vpc_id": vpcId,
},
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ type Config struct {
Debug bool
}
// Service implements the Hive service interface for vpcd.
// Service implements the Spinifex service interface for vpcd.
type Service struct {
Config *Config
}
+1 -1
View File
@@ -29,7 +29,7 @@ func TestIsAccountID(t *testing.T) {
{"123456789012", true},
{"999999999999", true},
{"self", false},
{"hive", false},
{"spinifex", false},
{"", false},
{"12345678901", false}, // 11 digits
{"1234567890123", false}, // 13 digits
+2 -2
View File
@@ -153,8 +153,8 @@ func pidPath() string {
if os.Getenv("XDG_RUNTIME_DIR") != "" {
return os.Getenv("XDG_RUNTIME_DIR")
}
if dirExists(fmt.Sprintf("%s/%s", os.Getenv("HOME"), "hive")) {
return filepath.Join(os.Getenv("HOME"), "hive")
if dirExists(fmt.Sprintf("%s/%s", os.Getenv("HOME"), "spinifex")) {
return filepath.Join(os.Getenv("HOME"), "spinifex")
}
return os.TempDir()
}
+5 -5
View File
@@ -594,7 +594,7 @@ func TestStopProcess(t *testing.T) {
func TestExtractDiskImageFromFile(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "hive-utils-test-*")
tmpDir, err := os.MkdirTemp("", "spx-utils-test-*")
t.Log("Temp dir:", tmpDir)
@@ -953,15 +953,15 @@ func TestPidPath_XDG(t *testing.T) {
assert.Equal(t, "/tmp/test-xdg-runtime", pidPath())
}
func TestPidPath_HomeHiveFallback(t *testing.T) {
func TestPidPath_HomeSpinifexFallback(t *testing.T) {
tmpHome := t.TempDir()
hiveDir := fmt.Sprintf("%s/hive", tmpHome)
require.NoError(t, os.Mkdir(hiveDir, 0755))
spinifexDir := fmt.Sprintf("%s/spinifex", tmpHome)
require.NoError(t, os.Mkdir(spinifexDir, 0755))
t.Setenv("XDG_RUNTIME_DIR", "")
t.Setenv("HOME", tmpHome)
assert.Equal(t, hiveDir, pidPath())
assert.Equal(t, spinifexDir, pidPath())
}
func TestPidPath_TempDirFallback(t *testing.T) {
+1 -1
View File
@@ -23,7 +23,7 @@ type EC2StateInfo struct {
}
// EC2StateCodes maps each InstanceState to its EC2 API code and name.
// Note: StateError and StateProvisioning are Hive-specific states with no direct
// Note: StateError and StateProvisioning are Spinifex-specific states with no direct
// AWS EC2 equivalent. Their Code/Name values are best-effort mappings.
var EC2StateCodes = map[InstanceState]EC2StateInfo{
StateProvisioning: {Code: 0, Name: "pending"},
+150 -150
View File
@@ -31,8 +31,8 @@ cleanup() {
fi
echo ""
echo "=== Spinifex Daemon Log ==="
if [ -f ~/spinifex/logs/hive.log ]; then
tail -200 ~/spinifex/logs/hive.log 2>/dev/null
if [ -f ~/spinifex/logs/spinifex.log ]; then
tail -200 ~/spinifex/logs/spinifex.log 2>/dev/null
fi
echo ""
echo "=== AWS Gateway Log ==="
@@ -111,7 +111,7 @@ if [ "$BOOTSTRAPPED" != "true" ]; then
# Trust the Spinifex CA certificate for AWS CLI SSL verification
echo "Adding Spinifex CA certificate to system trust store..."
sudo cp ~/spinifex/config/ca.pem /usr/local/share/ca-certificates/hive-ca.crt
sudo cp ~/spinifex/config/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt
sudo update-ca-certificates
# Start all services
@@ -159,15 +159,15 @@ if ! echo "$GET_NODES_OUTPUT" | grep -q "Ready"; then
fi
echo "spx get nodes passed"
# Test hive top nodes — should show CPU/MEM stats
echo "Testing hive top nodes..."
# Test spx top nodes — should show CPU/MEM stats
echo "Testing spx top nodes..."
TOP_NODES_OUTPUT=$(./bin/spx top nodes --config ~/spinifex/config/spinifex.toml --timeout 5s 2>/dev/null)
echo "$TOP_NODES_OUTPUT"
if ! echo "$TOP_NODES_OUTPUT" | grep -q "0/"; then
echo "hive top nodes did not show resource stats"
echo "spx top nodes did not show resource stats"
exit 1
fi
echo "hive top nodes passed"
echo "spx top nodes passed"
# Test spx get vms — should show no VMs yet
echo "Testing spx get vms (empty)..."
@@ -480,7 +480,7 @@ VM_HOSTNAME=$(ssh -o StrictHostKeyChecking=no \
-i "test-key-1.pem" \
ec2-user@"$SSH_HOST" 'hostname' 2>/dev/null)
echo "VM hostname: $VM_HOSTNAME"
# Hostname uses truncated ID: hive-vm-<first 8 hex chars of instance ID>
# Hostname uses truncated ID: spinifex-vm-<first 8 hex chars of instance ID>
SHORT_ID=$(echo "$INSTANCE_ID" | sed 's/^i-//' | cut -c1-8)
if echo "$VM_HOSTNAME" | grep -q "$SHORT_ID"; then
echo "Hostname contains instance ID prefix ($SHORT_ID)"
@@ -1476,14 +1476,14 @@ echo ""
echo "IAM Phase 3: User Authentication"
# Configure alice profile
echo " Configuring hive-alice profile..."
setup_test_profile hive-alice "$ALICE_KEY_ID" "$ALICE_SECRET"
echo " Configuring spx-alice profile..."
setup_test_profile spx-alice "$ALICE_KEY_ID" "$ALICE_SECRET"
# Deactivate key → auth should fail
echo " Deactivating alice key, verifying auth rejection..."
aws iam update-access-key --user-name alice --access-key-id "$ALICE_KEY_ID" --status Inactive
expect_error "InvalidClientTokenId" \
aws ec2 describe-instances --profile hive-alice
aws ec2 describe-instances --profile spx-alice
echo " Inactive key correctly rejected"
# Reactivate key
@@ -1492,22 +1492,22 @@ aws iam update-access-key --user-name alice --access-key-id "$ALICE_KEY_ID" --st
# Bad secret → SignatureDoesNotMatch
echo " Testing bad secret (expect SignatureDoesNotMatch)..."
setup_test_profile hive-bad "$ALICE_KEY_ID" "WRONG_SECRET_KEY_HERE_12345678901"
setup_test_profile spx-bad "$ALICE_KEY_ID" "WRONG_SECRET_KEY_HERE_12345678901"
expect_error "SignatureDoesNotMatch" \
aws ec2 describe-instances --profile hive-bad
aws ec2 describe-instances --profile spx-bad
# Non-existent key ID → InvalidClientTokenId
echo " Testing fake key ID (expect InvalidClientTokenId)..."
setup_test_profile hive-fake "AKIAXXXXXXXXXXXXXXXX" "doesntmatter"
setup_test_profile spx-fake "AKIAXXXXXXXXXXXXXXXX" "doesntmatter"
expect_error "InvalidClientTokenId" \
aws ec2 describe-instances --profile hive-fake
aws ec2 describe-instances --profile spx-fake
# Multi-user auth — create bob key and verify both auth
echo " Creating access key for bob..."
BOB_KEY=$(aws iam create-access-key --user-name bob)
BOB_KEY_ID=$(echo "$BOB_KEY" | jq -r '.AccessKey.AccessKeyId')
BOB_SECRET=$(echo "$BOB_KEY" | jq -r '.AccessKey.SecretAccessKey')
setup_test_profile hive-bob "$BOB_KEY_ID" "$BOB_SECRET"
setup_test_profile spx-bob "$BOB_KEY_ID" "$BOB_SECRET"
# Root still works
echo " Verifying root auth..."
@@ -1645,7 +1645,7 @@ aws iam create-user --user-name charlie > /dev/null
CHARLIE_KEY=$(aws iam create-access-key --user-name charlie)
CHARLIE_KEY_ID=$(echo "$CHARLIE_KEY" | jq -r '.AccessKey.AccessKeyId')
CHARLIE_SECRET=$(echo "$CHARLIE_KEY" | jq -r '.AccessKey.SecretAccessKey')
setup_test_profile hive-charlie "$CHARLIE_KEY_ID" "$CHARLIE_SECRET"
setup_test_profile spx-charlie "$CHARLIE_KEY_ID" "$CHARLIE_SECRET"
# Attach policies
echo " Attaching EC2ReadOnly + IAMReadOnly to alice..."
@@ -1692,40 +1692,40 @@ expect_error "NoSuchEntity" aws iam attach-user-policy --user-name ghost \
# Default Deny — charlie has no policies
echo " Testing default deny (charlie, no policies)..."
expect_error "AccessDenied" \
aws ec2 describe-instances --profile hive-charlie
aws ec2 describe-instances --profile spx-charlie
expect_error "AccessDenied" \
aws iam list-users --profile hive-charlie
aws iam list-users --profile spx-charlie
echo " Default deny passed"
# Explicit Allow — alice has EC2ReadOnly + IAMReadOnly
echo " Testing explicit allow (alice, EC2ReadOnly + IAMReadOnly)..."
aws ec2 describe-instances --profile hive-alice > /dev/null
aws ec2 describe-instances --profile spx-alice > /dev/null
echo " ec2:DescribeInstances — allowed"
aws ec2 describe-vpcs --profile hive-alice > /dev/null
aws ec2 describe-vpcs --profile spx-alice > /dev/null
echo " ec2:DescribeVpcs — allowed"
aws iam list-users --profile hive-alice > /dev/null
aws iam list-users --profile spx-alice > /dev/null
echo " iam:ListUsers — allowed"
# Actions NOT in alice's policies → denied
expect_error "AccessDenied" \
aws ec2 describe-key-pairs --profile hive-alice
aws ec2 describe-key-pairs --profile spx-alice
echo " ec2:DescribeKeyPairs — denied (not in policy)"
expect_error "AccessDenied" \
aws iam create-user --user-name hack --profile hive-alice
aws iam create-user --user-name hack --profile spx-alice
echo " iam:CreateUser — denied (not in policy)"
echo " Explicit allow passed"
# Wildcard Allow with Explicit Deny — bob has DenyTerminate (ec2:* Allow + ec2:TerminateInstances Deny)
echo " Testing deny override (bob, DenyTerminate)..."
aws ec2 describe-instances --profile hive-bob > /dev/null
aws ec2 describe-instances --profile spx-bob > /dev/null
echo " ec2:DescribeInstances — allowed (ec2:* wildcard)"
aws ec2 describe-key-pairs --profile hive-bob > /dev/null
aws ec2 describe-key-pairs --profile spx-bob > /dev/null
echo " ec2:DescribeKeyPairs — allowed (ec2:* wildcard)"
expect_error "AccessDenied" \
aws ec2 terminate-instances --instance-ids i-fake --profile hive-bob
aws ec2 terminate-instances --instance-ids i-fake --profile spx-bob
echo " ec2:TerminateInstances — denied (explicit Deny overrides Allow)"
expect_error "AccessDenied" \
aws iam list-users --profile hive-bob
aws iam list-users --profile spx-bob
echo " iam:ListUsers — denied (IAM not covered by ec2:*)"
echo " Deny override passed"
@@ -1746,15 +1746,15 @@ aws iam detach-user-policy --user-name alice \
aws iam attach-user-policy --user-name alice \
--policy-arn "arn:aws:iam::${ADMIN_ACCOUNT}:policy/EC2DescribeAll"
aws ec2 describe-instances --profile hive-alice > /dev/null
aws ec2 describe-instances --profile spx-alice > /dev/null
echo " ec2:DescribeInstances — allowed (Describe*)"
aws ec2 describe-key-pairs --profile hive-alice > /dev/null
aws ec2 describe-key-pairs --profile spx-alice > /dev/null
echo " ec2:DescribeKeyPairs — allowed (Describe*)"
expect_error "AccessDenied" \
aws ec2 create-key-pair --key-name x --profile hive-alice
aws ec2 create-key-pair --key-name x --profile spx-alice
echo " ec2:CreateKeyPair — denied (not Describe*)"
expect_error "AccessDenied" \
aws iam list-users --profile hive-alice
aws iam list-users --profile spx-alice
echo " iam:ListUsers — denied (not ec2:Describe*)"
echo " Prefix wildcard passed"
@@ -1762,9 +1762,9 @@ echo " Prefix wildcard passed"
echo " Testing FullAdmin (charlie)..."
aws iam attach-user-policy --user-name charlie \
--policy-arn "arn:aws:iam::${ADMIN_ACCOUNT}:policy/admin/FullAdmin"
aws ec2 describe-instances --profile hive-charlie > /dev/null
aws ec2 describe-instances --profile spx-charlie > /dev/null
echo " ec2:DescribeInstances — allowed (was denied)"
aws iam list-users --profile hive-charlie > /dev/null
aws iam list-users --profile spx-charlie > /dev/null
echo " iam:ListUsers — allowed (was denied)"
echo " FullAdmin passed"
@@ -1779,7 +1779,7 @@ echo " Detaching EC2DescribeAll from alice..."
aws iam detach-user-policy --user-name alice \
--policy-arn "arn:aws:iam::${ADMIN_ACCOUNT}:policy/EC2DescribeAll"
expect_error "AccessDenied" \
aws ec2 describe-instances --profile hive-alice
aws ec2 describe-instances --profile spx-alice
echo " Alice lost access after detach"
# DeletePolicy — conflict (DenyTerminate still attached to bob)
@@ -1878,7 +1878,7 @@ if [ -z "$ALPHA_ACCOUNT" ] || [ -z "$ALPHA_KEY_ID" ]; then
exit 1
fi
echo " Team Alpha: account=$ALPHA_ACCOUNT key=$ALPHA_KEY_ID"
setup_test_profile hive-team-alpha "$ALPHA_KEY_ID" "$ALPHA_SECRET"
setup_test_profile spx-team-alpha "$ALPHA_KEY_ID" "$ALPHA_SECRET"
echo " Creating Team Beta account..."
BETA_OUTPUT=$(./bin/spx admin account create --name "Team Beta" 2>&1)
@@ -1892,12 +1892,12 @@ if [ -z "$BETA_ACCOUNT" ] || [ -z "$BETA_KEY_ID" ]; then
exit 1
fi
echo " Team Beta: account=$BETA_ACCOUNT key=$BETA_KEY_ID"
setup_test_profile hive-team-beta "$BETA_KEY_ID" "$BETA_SECRET"
setup_test_profile spx-team-beta "$BETA_KEY_ID" "$BETA_SECRET"
# Verify auth
aws ec2 describe-instances --profile hive-team-alpha > /dev/null
aws ec2 describe-instances --profile spx-team-alpha > /dev/null
echo " Alpha auth OK"
aws ec2 describe-instances --profile hive-team-beta > /dev/null
aws ec2 describe-instances --profile spx-team-beta > /dev/null
echo " Beta auth OK"
echo " Account setup complete"
@@ -1908,8 +1908,8 @@ echo "Phase 8 Step 2: Instance Scoping"
echo "----------------------------------------"
# Create per-account key pairs (key pairs are account-scoped, root's test-key-1 is invisible)
aws ec2 create-key-pair --key-name alpha-instance-key --profile hive-team-alpha > /dev/null
aws ec2 create-key-pair --key-name beta-instance-key --profile hive-team-beta > /dev/null
aws ec2 create-key-pair --key-name alpha-instance-key --profile spx-team-alpha > /dev/null
aws ec2 create-key-pair --key-name beta-instance-key --profile spx-team-beta > /dev/null
echo " Created per-account key pairs for instance launches"
echo " Alpha launching instance..."
@@ -1917,7 +1917,7 @@ ALPHA_RUN=$(aws ec2 run-instances \
--image-id "$AMI_ID" \
--instance-type "$INSTANCE_TYPE" \
--key-name alpha-instance-key \
--profile hive-team-alpha)
--profile spx-team-alpha)
ALPHA_INST=$(echo "$ALPHA_RUN" | jq -r '.Instances[0].InstanceId')
echo " Alpha instance: $ALPHA_INST"
@@ -1926,7 +1926,7 @@ BETA_RUN=$(aws ec2 run-instances \
--image-id "$AMI_ID" \
--instance-type "$INSTANCE_TYPE" \
--key-name beta-instance-key \
--profile hive-team-beta)
--profile spx-team-beta)
BETA_INST=$(echo "$BETA_RUN" | jq -r '.Instances[0].InstanceId')
echo " Beta instance: $BETA_INST"
@@ -1934,9 +1934,9 @@ echo " Beta instance: $BETA_INST"
echo " Waiting for instances to reach running state..."
COUNT=0
while [ $COUNT -lt 60 ]; do
A_STATE=$(aws ec2 describe-instances --instance-ids "$ALPHA_INST" --profile hive-team-alpha \
A_STATE=$(aws ec2 describe-instances --instance-ids "$ALPHA_INST" --profile spx-team-alpha \
--query 'Reservations[0].Instances[0].State.Name' --output text 2>/dev/null || echo "pending")
B_STATE=$(aws ec2 describe-instances --instance-ids "$BETA_INST" --profile hive-team-beta \
B_STATE=$(aws ec2 describe-instances --instance-ids "$BETA_INST" --profile spx-team-beta \
--query 'Reservations[0].Instances[0].State.Name' --output text 2>/dev/null || echo "pending")
echo " Alpha=$A_STATE, Beta=$B_STATE"
if [ "$A_STATE" == "running" ] && [ "$B_STATE" == "running" ]; then break; fi
@@ -1950,7 +1950,7 @@ fi
echo " Both instances running"
# Describe isolation
ALPHA_INSTANCES=$(aws ec2 describe-instances --profile hive-team-alpha \
ALPHA_INSTANCES=$(aws ec2 describe-instances --profile spx-team-alpha \
--query 'Reservations[].Instances[].InstanceId' --output text)
if echo "$ALPHA_INSTANCES" | grep -q "$BETA_INST"; then
echo " ERROR: Alpha can see Beta's instance"
@@ -1958,7 +1958,7 @@ if echo "$ALPHA_INSTANCES" | grep -q "$BETA_INST"; then
fi
echo " Alpha sees only own instances"
BETA_INSTANCES=$(aws ec2 describe-instances --profile hive-team-beta \
BETA_INSTANCES=$(aws ec2 describe-instances --profile spx-team-beta \
--query 'Reservations[].Instances[].InstanceId' --output text)
if echo "$BETA_INSTANCES" | grep -q "$ALPHA_INST"; then
echo " ERROR: Beta can see Alpha's instance"
@@ -1967,7 +1967,7 @@ fi
echo " Beta sees only own instances"
# OwnerId verification
ALPHA_OWNER=$(aws ec2 describe-instances --profile hive-team-alpha \
ALPHA_OWNER=$(aws ec2 describe-instances --profile spx-team-alpha \
--query 'Reservations[0].OwnerId' --output text)
if [ "$ALPHA_OWNER" != "$ALPHA_ACCOUNT" ]; then
echo " ERROR: Alpha OwnerId mismatch: expected $ALPHA_ACCOUNT, got $ALPHA_OWNER"
@@ -1977,22 +1977,22 @@ echo " Alpha OwnerId correct: $ALPHA_OWNER"
# Cross-account operations
expect_error "InvalidInstanceID.NotFound" \
aws ec2 stop-instances --instance-ids "$BETA_INST" --profile hive-team-alpha
aws ec2 stop-instances --instance-ids "$BETA_INST" --profile spx-team-alpha
echo " Alpha cannot stop Beta's instance"
expect_error "InvalidInstanceID.NotFound" \
aws ec2 terminate-instances --instance-ids "$ALPHA_INST" --profile hive-team-beta
aws ec2 terminate-instances --instance-ids "$ALPHA_INST" --profile spx-team-beta
echo " Beta cannot terminate Alpha's instance"
expect_error "InvalidInstanceID.NotFound" \
aws ec2 reboot-instances --instance-ids "$BETA_INST" --profile hive-team-alpha
aws ec2 reboot-instances --instance-ids "$BETA_INST" --profile spx-team-alpha
echo " Alpha cannot reboot Beta's instance"
# Stop Alpha's instance for cross-account start/modify/console tests
aws ec2 stop-instances --instance-ids "$ALPHA_INST" --profile hive-team-alpha > /dev/null
aws ec2 stop-instances --instance-ids "$ALPHA_INST" --profile spx-team-alpha > /dev/null
COUNT=0
while [ $COUNT -lt 60 ]; do
A_STATE=$(aws ec2 describe-instances --instance-ids "$ALPHA_INST" --profile hive-team-alpha \
A_STATE=$(aws ec2 describe-instances --instance-ids "$ALPHA_INST" --profile spx-team-alpha \
--query 'Reservations[0].Instances[0].State.Name' --output text 2>/dev/null)
if [ "$A_STATE" == "stopped" ]; then break; fi
sleep 1
@@ -2000,23 +2000,23 @@ while [ $COUNT -lt 60 ]; do
done
expect_error "InvalidInstanceID.NotFound" \
aws ec2 start-instances --instance-ids "$ALPHA_INST" --profile hive-team-beta
aws ec2 start-instances --instance-ids "$ALPHA_INST" --profile spx-team-beta
echo " Beta cannot start Alpha's stopped instance"
expect_error "InvalidInstanceID.NotFound" \
aws ec2 modify-instance-attribute --instance-id "$ALPHA_INST" \
--instance-type '{"Value":"t2.small"}' --profile hive-team-beta
--instance-type '{"Value":"t2.small"}' --profile spx-team-beta
echo " Beta cannot modify Alpha's instance"
expect_error "InvalidInstanceID.NotFound" \
aws ec2 get-console-output --instance-id "$ALPHA_INST" --profile hive-team-beta
aws ec2 get-console-output --instance-id "$ALPHA_INST" --profile spx-team-beta
echo " Beta cannot get console output of Alpha's instance"
# Restart Alpha's instance for later tests
aws ec2 start-instances --instance-ids "$ALPHA_INST" --profile hive-team-alpha > /dev/null
aws ec2 start-instances --instance-ids "$ALPHA_INST" --profile spx-team-alpha > /dev/null
COUNT=0
while [ $COUNT -lt 60 ]; do
A_STATE=$(aws ec2 describe-instances --instance-ids "$ALPHA_INST" --profile hive-team-alpha \
A_STATE=$(aws ec2 describe-instances --instance-ids "$ALPHA_INST" --profile spx-team-alpha \
--query 'Reservations[0].Instances[0].State.Name' --output text 2>/dev/null)
if [ "$A_STATE" == "running" ]; then break; fi
sleep 1
@@ -2031,15 +2031,15 @@ echo "Phase 8 Step 3: Volume Scoping"
echo "----------------------------------------"
ALPHA_VOL=$(aws ec2 create-volume --availability-zone "$AZ_NAME" --size 10 \
--volume-type gp3 --profile hive-team-alpha | jq -r '.VolumeId')
--volume-type gp3 --profile spx-team-alpha | jq -r '.VolumeId')
echo " Alpha volume: $ALPHA_VOL"
BETA_VOL=$(aws ec2 create-volume --availability-zone "$AZ_NAME" --size 10 \
--volume-type gp3 --profile hive-team-beta | jq -r '.VolumeId')
--volume-type gp3 --profile spx-team-beta | jq -r '.VolumeId')
echo " Beta volume: $BETA_VOL"
# Describe isolation
ALPHA_VOLS=$(aws ec2 describe-volumes --profile hive-team-alpha \
ALPHA_VOLS=$(aws ec2 describe-volumes --profile spx-team-alpha \
--query 'Volumes[].VolumeId' --output text)
if echo "$ALPHA_VOLS" | grep -q "$BETA_VOL"; then
echo " ERROR: Alpha can see Beta's volume"
@@ -2049,36 +2049,36 @@ echo " Alpha sees only own volumes"
# Cross-account filter
expect_error "InvalidVolume.NotFound" \
aws ec2 describe-volumes --volume-ids "$BETA_VOL" --profile hive-team-alpha
aws ec2 describe-volumes --volume-ids "$BETA_VOL" --profile spx-team-alpha
echo " Alpha cannot describe Beta's volume by ID"
# Cross-account delete
expect_error "InvalidVolume.NotFound" \
aws ec2 delete-volume --volume-id "$ALPHA_VOL" --profile hive-team-beta
aws ec2 delete-volume --volume-id "$ALPHA_VOL" --profile spx-team-beta
echo " Beta cannot delete Alpha's volume"
# Cross-account attach (other's volume)
expect_error "InvalidVolume.NotFound" \
aws ec2 attach-volume --volume-id "$ALPHA_VOL" \
--instance-id "$BETA_INST" --device /dev/sdf --profile hive-team-beta
--instance-id "$BETA_INST" --device /dev/sdf --profile spx-team-beta
echo " Beta cannot attach Alpha's volume"
# Attach Alpha's volume to Alpha's instance, then test cross-account detach
aws ec2 attach-volume --volume-id "$ALPHA_VOL" \
--instance-id "$ALPHA_INST" --device /dev/sdf --profile hive-team-alpha > /dev/null
--instance-id "$ALPHA_INST" --device /dev/sdf --profile spx-team-alpha > /dev/null
sleep 1
expect_error "InvalidVolume.NotFound" \
aws ec2 detach-volume --volume-id "$ALPHA_VOL" --profile hive-team-beta
aws ec2 detach-volume --volume-id "$ALPHA_VOL" --profile spx-team-beta
echo " Beta cannot detach Alpha's volume"
# Cross-account modify
expect_error "InvalidVolume.NotFound" \
aws ec2 modify-volume --volume-id "$ALPHA_VOL" --size 20 --profile hive-team-beta
aws ec2 modify-volume --volume-id "$ALPHA_VOL" --size 20 --profile spx-team-beta
echo " Beta cannot modify Alpha's volume"
# Detach for cleanup later
aws ec2 detach-volume --volume-id "$ALPHA_VOL" --profile hive-team-alpha > /dev/null
aws ec2 detach-volume --volume-id "$ALPHA_VOL" --profile spx-team-alpha > /dev/null
sleep 1
echo " Volume scoping passed"
@@ -2088,16 +2088,16 @@ echo ""
echo "Phase 8 Step 4: Key Pair Scoping"
echo "----------------------------------------"
aws ec2 create-key-pair --key-name alpha-key --profile hive-team-alpha > /dev/null
aws ec2 create-key-pair --key-name alpha-key --profile spx-team-alpha > /dev/null
ALPHA_KEYPAIR_ID=$(aws ec2 describe-key-pairs --key-names alpha-key \
--profile hive-team-alpha --query 'KeyPairs[0].KeyPairId' --output text)
--profile spx-team-alpha --query 'KeyPairs[0].KeyPairId' --output text)
echo " Alpha key: alpha-key ($ALPHA_KEYPAIR_ID)"
aws ec2 create-key-pair --key-name beta-key --profile hive-team-beta > /dev/null
aws ec2 create-key-pair --key-name beta-key --profile spx-team-beta > /dev/null
echo " Beta key: beta-key"
# Describe isolation
ALPHA_KEYS=$(aws ec2 describe-key-pairs --profile hive-team-alpha \
ALPHA_KEYS=$(aws ec2 describe-key-pairs --profile spx-team-alpha \
--query 'KeyPairs[].KeyName' --output text)
if echo "$ALPHA_KEYS" | grep -q "beta-key"; then
echo " ERROR: Alpha can see Beta's key"
@@ -2106,12 +2106,12 @@ fi
echo " Alpha sees only own keys"
# Same name in both accounts (namespace isolation)
aws ec2 create-key-pair --key-name shared-name --profile hive-team-alpha > /dev/null
aws ec2 create-key-pair --key-name shared-name --profile hive-team-beta > /dev/null
aws ec2 create-key-pair --key-name shared-name --profile spx-team-alpha > /dev/null
aws ec2 create-key-pair --key-name shared-name --profile spx-team-beta > /dev/null
ALPHA_SHARED_ID=$(aws ec2 describe-key-pairs --key-names shared-name \
--profile hive-team-alpha --query 'KeyPairs[0].KeyPairId' --output text)
--profile spx-team-alpha --query 'KeyPairs[0].KeyPairId' --output text)
BETA_SHARED_ID=$(aws ec2 describe-key-pairs --key-names shared-name \
--profile hive-team-beta --query 'KeyPairs[0].KeyPairId' --output text)
--profile spx-team-beta --query 'KeyPairs[0].KeyPairId' --output text)
if [ "$ALPHA_SHARED_ID" == "$BETA_SHARED_ID" ]; then
echo " ERROR: Same KeyPairId for shared-name in both accounts"
exit 1
@@ -2119,9 +2119,9 @@ fi
echo " Namespace isolation: alpha=$ALPHA_SHARED_ID, beta=$BETA_SHARED_ID"
# Cross-account delete (idempotent, but shouldn't affect other account)
aws ec2 delete-key-pair --key-name alpha-key --profile hive-team-beta
aws ec2 delete-key-pair --key-name alpha-key --profile spx-team-beta
ALPHA_KEY_CHECK=$(aws ec2 describe-key-pairs --key-names alpha-key \
--profile hive-team-alpha --query 'KeyPairs[0].KeyPairId' --output text)
--profile spx-team-alpha --query 'KeyPairs[0].KeyPairId' --output text)
if [ "$ALPHA_KEY_CHECK" != "$ALPHA_KEYPAIR_ID" ]; then
echo " ERROR: Beta's delete affected Alpha's key"
exit 1
@@ -2131,8 +2131,8 @@ echo " Cross-account delete had no effect on Alpha's key"
# Import key pair — account scoped
ssh-keygen -t ed25519 -f /tmp/test-import-key -N "" -q
aws ec2 import-key-pair --key-name imported-key \
--public-key-material fileb:///tmp/test-import-key.pub --profile hive-team-alpha > /dev/null
BETA_IMPORT_CHECK=$(aws ec2 describe-key-pairs --profile hive-team-beta \
--public-key-material fileb:///tmp/test-import-key.pub --profile spx-team-alpha > /dev/null
BETA_IMPORT_CHECK=$(aws ec2 describe-key-pairs --profile spx-team-beta \
--query 'KeyPairs[].KeyName' --output text)
if echo "$BETA_IMPORT_CHECK" | grep -q "imported-key"; then
echo " ERROR: Beta can see Alpha's imported key"
@@ -2149,15 +2149,15 @@ echo "Phase 8 Step 5: Snapshot Scoping"
echo "----------------------------------------"
ALPHA_SNAP=$(aws ec2 create-snapshot --volume-id "$ALPHA_VOL" \
--description "Alpha snapshot" --profile hive-team-alpha | jq -r '.SnapshotId')
--description "Alpha snapshot" --profile spx-team-alpha | jq -r '.SnapshotId')
echo " Alpha snapshot: $ALPHA_SNAP"
BETA_SNAP=$(aws ec2 create-snapshot --volume-id "$BETA_VOL" \
--description "Beta snapshot" --profile hive-team-beta | jq -r '.SnapshotId')
--description "Beta snapshot" --profile spx-team-beta | jq -r '.SnapshotId')
echo " Beta snapshot: $BETA_SNAP"
# Describe isolation
ALPHA_SNAPS=$(aws ec2 describe-snapshots --owner-ids self --profile hive-team-alpha \
ALPHA_SNAPS=$(aws ec2 describe-snapshots --owner-ids self --profile spx-team-alpha \
--query 'Snapshots[].SnapshotId' --output text)
if echo "$ALPHA_SNAPS" | grep -q "$BETA_SNAP"; then
echo " ERROR: Alpha can see Beta's snapshot"
@@ -2166,7 +2166,7 @@ fi
echo " Alpha sees only own snapshots"
# OwnerId verification
ALPHA_SNAP_OWNER=$(aws ec2 describe-snapshots --owner-ids self --profile hive-team-alpha \
ALPHA_SNAP_OWNER=$(aws ec2 describe-snapshots --owner-ids self --profile spx-team-alpha \
--query 'Snapshots[0].OwnerId' --output text)
if [ "$ALPHA_SNAP_OWNER" != "$ALPHA_ACCOUNT" ]; then
echo " ERROR: Snapshot OwnerId mismatch: expected $ALPHA_ACCOUNT, got $ALPHA_SNAP_OWNER"
@@ -2176,13 +2176,13 @@ echo " Alpha snapshot OwnerId correct"
# Cross-account delete
expect_error "UnauthorizedOperation" \
aws ec2 delete-snapshot --snapshot-id "$ALPHA_SNAP" --profile hive-team-beta
aws ec2 delete-snapshot --snapshot-id "$ALPHA_SNAP" --profile spx-team-beta
echo " Beta cannot delete Alpha's snapshot"
# Cross-account snapshot from other's volume
expect_error "InvalidVolume.NotFound" \
aws ec2 create-snapshot --volume-id "$ALPHA_VOL" \
--description "stolen" --profile hive-team-beta
--description "stolen" --profile spx-team-beta
echo " Beta cannot snapshot Alpha's volume"
echo " Snapshot scoping passed"
@@ -2193,15 +2193,15 @@ echo "Phase 8 Step 6: VPC/Subnet Scoping"
echo "----------------------------------------"
ALPHA_VPC=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 \
--profile hive-team-alpha --query 'Vpc.VpcId' --output text)
--profile spx-team-alpha --query 'Vpc.VpcId' --output text)
echo " Alpha VPC: $ALPHA_VPC"
BETA_VPC=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 \
--profile hive-team-beta --query 'Vpc.VpcId' --output text)
--profile spx-team-beta --query 'Vpc.VpcId' --output text)
echo " Beta VPC: $BETA_VPC (same CIDR — no conflict)"
# Describe isolation
ALPHA_VPCS=$(aws ec2 describe-vpcs --profile hive-team-alpha \
ALPHA_VPCS=$(aws ec2 describe-vpcs --profile spx-team-alpha \
--query 'Vpcs[].VpcId' --output text)
if echo "$ALPHA_VPCS" | grep -q "$BETA_VPC"; then
echo " ERROR: Alpha can see Beta's VPC"
@@ -2211,25 +2211,25 @@ echo " VPC describe isolation OK"
# Cross-account describe by ID
expect_error "InvalidVpcID.NotFound" \
aws ec2 describe-vpcs --vpc-ids "$BETA_VPC" --profile hive-team-alpha
aws ec2 describe-vpcs --vpc-ids "$BETA_VPC" --profile spx-team-alpha
echo " Alpha cannot describe Beta's VPC by ID"
# Cross-account delete
expect_error "InvalidVpcID.NotFound" \
aws ec2 delete-vpc --vpc-id "$ALPHA_VPC" --profile hive-team-beta
aws ec2 delete-vpc --vpc-id "$ALPHA_VPC" --profile spx-team-beta
echo " Beta cannot delete Alpha's VPC"
# Create subnets
ALPHA_SUBNET=$(aws ec2 create-subnet --vpc-id "$ALPHA_VPC" --cidr-block 10.0.1.0/24 \
--profile hive-team-alpha --query 'Subnet.SubnetId' --output text)
--profile spx-team-alpha --query 'Subnet.SubnetId' --output text)
echo " Alpha subnet: $ALPHA_SUBNET"
BETA_SUBNET=$(aws ec2 create-subnet --vpc-id "$BETA_VPC" --cidr-block 10.0.1.0/24 \
--profile hive-team-beta --query 'Subnet.SubnetId' --output text)
--profile spx-team-beta --query 'Subnet.SubnetId' --output text)
echo " Beta subnet: $BETA_SUBNET"
# Subnet describe isolation
ALPHA_SUBNETS=$(aws ec2 describe-subnets --profile hive-team-alpha \
ALPHA_SUBNETS=$(aws ec2 describe-subnets --profile spx-team-alpha \
--query 'Subnets[].SubnetId' --output text)
if echo "$ALPHA_SUBNETS" | grep -q "$BETA_SUBNET"; then
echo " ERROR: Alpha can see Beta's subnet"
@@ -2240,12 +2240,12 @@ echo " Subnet describe isolation OK"
# Cross-account create subnet in other's VPC
expect_error "InvalidVpcID.NotFound" \
aws ec2 create-subnet --vpc-id "$ALPHA_VPC" --cidr-block 10.0.2.0/24 \
--profile hive-team-beta
--profile spx-team-beta
echo " Beta cannot create subnet in Alpha's VPC"
# Cross-account subnet delete
expect_error "InvalidSubnetID.NotFound" \
aws ec2 delete-subnet --subnet-id "$ALPHA_SUBNET" --profile hive-team-beta
aws ec2 delete-subnet --subnet-id "$ALPHA_SUBNET" --profile spx-team-beta
echo " Beta cannot delete Alpha's subnet"
echo " VPC/Subnet scoping passed"
@@ -2256,16 +2256,16 @@ echo "Phase 8 Step 7: IGW + EIGW Scoping"
echo "----------------------------------------"
# IGW
ALPHA_IGW=$(aws ec2 create-internet-gateway --profile hive-team-alpha \
ALPHA_IGW=$(aws ec2 create-internet-gateway --profile spx-team-alpha \
--query 'InternetGateway.InternetGatewayId' --output text)
echo " Alpha IGW: $ALPHA_IGW"
BETA_IGW=$(aws ec2 create-internet-gateway --profile hive-team-beta \
BETA_IGW=$(aws ec2 create-internet-gateway --profile spx-team-beta \
--query 'InternetGateway.InternetGatewayId' --output text)
echo " Beta IGW: $BETA_IGW"
# IGW describe isolation
ALPHA_IGWS=$(aws ec2 describe-internet-gateways --profile hive-team-alpha \
ALPHA_IGWS=$(aws ec2 describe-internet-gateways --profile spx-team-alpha \
--query 'InternetGateways[].InternetGatewayId' --output text)
if echo "$ALPHA_IGWS" | grep -q "$BETA_IGW"; then
echo " ERROR: Alpha can see Beta's IGW"
@@ -2276,42 +2276,42 @@ echo " IGW describe isolation OK"
# Cross-account IGW describe by ID
expect_error "InvalidInternetGatewayID.NotFound" \
aws ec2 describe-internet-gateways --internet-gateway-ids "$BETA_IGW" \
--profile hive-team-alpha
--profile spx-team-alpha
echo " Alpha cannot describe Beta's IGW by ID"
# Cross-account IGW delete
expect_error "InvalidInternetGatewayID.NotFound" \
aws ec2 delete-internet-gateway --internet-gateway-id "$ALPHA_IGW" \
--profile hive-team-beta
--profile spx-team-beta
echo " Beta cannot delete Alpha's IGW"
# Cross-account attach IGW to other's VPC
expect_error "InvalidInternetGatewayID.NotFound" \
aws ec2 attach-internet-gateway --internet-gateway-id "$BETA_IGW" \
--vpc-id "$ALPHA_VPC" --profile hive-team-alpha
--vpc-id "$ALPHA_VPC" --profile spx-team-alpha
echo " Alpha cannot attach Beta's IGW to own VPC"
# Attach Alpha's IGW, test cross-account detach
aws ec2 attach-internet-gateway --internet-gateway-id "$ALPHA_IGW" \
--vpc-id "$ALPHA_VPC" --profile hive-team-alpha > /dev/null
--vpc-id "$ALPHA_VPC" --profile spx-team-alpha > /dev/null
expect_error "InvalidInternetGatewayID.NotFound" \
aws ec2 detach-internet-gateway --internet-gateway-id "$ALPHA_IGW" \
--vpc-id "$ALPHA_VPC" --profile hive-team-beta
--vpc-id "$ALPHA_VPC" --profile spx-team-beta
echo " Beta cannot detach Alpha's IGW"
# EIGW
ALPHA_EIGW=$(aws ec2 create-egress-only-internet-gateway --vpc-id "$ALPHA_VPC" \
--profile hive-team-alpha \
--profile spx-team-alpha \
--query 'EgressOnlyInternetGateway.EgressOnlyInternetGatewayId' --output text)
echo " Alpha EIGW: $ALPHA_EIGW"
BETA_EIGW=$(aws ec2 create-egress-only-internet-gateway --vpc-id "$BETA_VPC" \
--profile hive-team-beta \
--profile spx-team-beta \
--query 'EgressOnlyInternetGateway.EgressOnlyInternetGatewayId' --output text)
echo " Beta EIGW: $BETA_EIGW"
# EIGW describe isolation
ALPHA_EIGWS=$(aws ec2 describe-egress-only-internet-gateways --profile hive-team-alpha \
ALPHA_EIGWS=$(aws ec2 describe-egress-only-internet-gateways --profile spx-team-alpha \
--query 'EgressOnlyInternetGateways[].EgressOnlyInternetGatewayId' --output text)
if echo "$ALPHA_EIGWS" | grep -q "$BETA_EIGW"; then
echo " ERROR: Alpha can see Beta's EIGW"
@@ -2322,9 +2322,9 @@ echo " EIGW describe isolation OK"
# Cross-account EIGW delete
expect_error "" \
aws ec2 delete-egress-only-internet-gateway \
--egress-only-internet-gateway-id "$ALPHA_EIGW" --profile hive-team-beta 2>&1 || true
--egress-only-internet-gateway-id "$ALPHA_EIGW" --profile spx-team-beta 2>&1 || true
# Verify Alpha's EIGW still exists
ALPHA_EIGW_CHECK=$(aws ec2 describe-egress-only-internet-gateways --profile hive-team-alpha \
ALPHA_EIGW_CHECK=$(aws ec2 describe-egress-only-internet-gateways --profile spx-team-alpha \
--query 'EgressOnlyInternetGateways[].EgressOnlyInternetGatewayId' --output text)
if ! echo "$ALPHA_EIGW_CHECK" | grep -q "$ALPHA_EIGW"; then
echo " ERROR: Alpha's EIGW was deleted by Beta"
@@ -2335,7 +2335,7 @@ echo " Beta cannot delete Alpha's EIGW"
# Cross-account EIGW creation in other's VPC
expect_error "" \
aws ec2 create-egress-only-internet-gateway --vpc-id "$ALPHA_VPC" \
--profile hive-team-beta 2>&1 || true
--profile spx-team-beta 2>&1 || true
echo " Beta cannot create EIGW in Alpha's VPC"
echo " IGW + EIGW scoping passed"
@@ -2345,8 +2345,8 @@ echo ""
echo "Phase 8 Step 8: Account Settings"
echo "----------------------------------------"
aws ec2 enable-ebs-encryption-by-default --profile hive-team-alpha > /dev/null
BETA_ENC=$(aws ec2 get-ebs-encryption-by-default --profile hive-team-beta \
aws ec2 enable-ebs-encryption-by-default --profile spx-team-alpha > /dev/null
BETA_ENC=$(aws ec2 get-ebs-encryption-by-default --profile spx-team-beta \
--query 'EbsEncryptionByDefault' --output text)
if [ "$BETA_ENC" != "False" ]; then
echo " ERROR: Alpha's encryption setting leaked to Beta (got $BETA_ENC)"
@@ -2355,11 +2355,11 @@ fi
echo " Alpha enable did not affect Beta"
# Independent toggle
aws ec2 enable-ebs-encryption-by-default --profile hive-team-beta > /dev/null
aws ec2 disable-ebs-encryption-by-default --profile hive-team-alpha > /dev/null
ALPHA_ENC=$(aws ec2 get-ebs-encryption-by-default --profile hive-team-alpha \
aws ec2 enable-ebs-encryption-by-default --profile spx-team-beta > /dev/null
aws ec2 disable-ebs-encryption-by-default --profile spx-team-alpha > /dev/null
ALPHA_ENC=$(aws ec2 get-ebs-encryption-by-default --profile spx-team-alpha \
--query 'EbsEncryptionByDefault' --output text)
BETA_ENC=$(aws ec2 get-ebs-encryption-by-default --profile hive-team-beta \
BETA_ENC=$(aws ec2 get-ebs-encryption-by-default --profile spx-team-beta \
--query 'EbsEncryptionByDefault' --output text)
if [ "$ALPHA_ENC" != "False" ] || [ "$BETA_ENC" != "True" ]; then
echo " ERROR: Independent settings failed: alpha=$ALPHA_ENC beta=$BETA_ENC"
@@ -2368,7 +2368,7 @@ fi
echo " Independent toggle verified: alpha=$ALPHA_ENC, beta=$BETA_ENC"
# Reset
aws ec2 disable-ebs-encryption-by-default --profile hive-team-beta > /dev/null
aws ec2 disable-ebs-encryption-by-default --profile spx-team-beta > /dev/null
echo " Account settings scoping passed"
@@ -2377,9 +2377,9 @@ echo ""
echo "Phase 8 Step 9: Global Resources"
echo "----------------------------------------"
ALPHA_REGIONS=$(aws ec2 describe-regions --profile hive-team-alpha \
ALPHA_REGIONS=$(aws ec2 describe-regions --profile spx-team-alpha \
--query 'Regions[].RegionName' --output text)
BETA_REGIONS=$(aws ec2 describe-regions --profile hive-team-beta \
BETA_REGIONS=$(aws ec2 describe-regions --profile spx-team-beta \
--query 'Regions[].RegionName' --output text)
if [ "$ALPHA_REGIONS" != "$BETA_REGIONS" ]; then
echo " ERROR: Regions differ between accounts"
@@ -2387,9 +2387,9 @@ if [ "$ALPHA_REGIONS" != "$BETA_REGIONS" ]; then
fi
echo " Regions identical"
ALPHA_AZS=$(aws ec2 describe-availability-zones --profile hive-team-alpha \
ALPHA_AZS=$(aws ec2 describe-availability-zones --profile spx-team-alpha \
--query 'AvailabilityZones[].ZoneName' --output text)
BETA_AZS=$(aws ec2 describe-availability-zones --profile hive-team-beta \
BETA_AZS=$(aws ec2 describe-availability-zones --profile spx-team-beta \
--query 'AvailabilityZones[].ZoneName' --output text)
if [ "$ALPHA_AZS" != "$BETA_AZS" ]; then
echo " ERROR: AZs differ between accounts"
@@ -2397,9 +2397,9 @@ if [ "$ALPHA_AZS" != "$BETA_AZS" ]; then
fi
echo " Availability zones identical"
ALPHA_TYPES=$(aws ec2 describe-instance-types --profile hive-team-alpha \
ALPHA_TYPES=$(aws ec2 describe-instance-types --profile spx-team-alpha \
--query 'InstanceTypes[].InstanceType' --output text | tr '\t' '\n' | sort)
BETA_TYPES=$(aws ec2 describe-instance-types --profile hive-team-beta \
BETA_TYPES=$(aws ec2 describe-instance-types --profile spx-team-beta \
--query 'InstanceTypes[].InstanceType' --output text | tr '\t' '\n' | sort)
if [ "$ALPHA_TYPES" != "$BETA_TYPES" ]; then
echo " ERROR: Instance types differ between accounts"
@@ -2419,9 +2419,9 @@ echo " Creating empty Gamma account..."
GAMMA_OUTPUT=$(./bin/spx admin account create --name "Team Gamma" 2>&1)
GAMMA_KEY_ID=$(echo "$GAMMA_OUTPUT" | grep "Access Key ID:" | awk '{print $NF}')
GAMMA_SECRET=$(echo "$GAMMA_OUTPUT" | grep "Secret Access Key:" | awk '{print $NF}')
setup_test_profile hive-team-gamma "$GAMMA_KEY_ID" "$GAMMA_SECRET"
setup_test_profile spx-team-gamma "$GAMMA_KEY_ID" "$GAMMA_SECRET"
GAMMA_INSTANCES=$(aws ec2 describe-instances --profile hive-team-gamma \
GAMMA_INSTANCES=$(aws ec2 describe-instances --profile spx-team-gamma \
--query 'Reservations' --output text)
if [ -n "$GAMMA_INSTANCES" ] && [ "$GAMMA_INSTANCES" != "None" ]; then
echo " ERROR: Gamma has instances"
@@ -2432,7 +2432,7 @@ echo " Gamma: no instances"
# Skip volume check: root-account volumes (empty TenantID) are visible to all accounts by design
echo " Gamma: volumes skipped (root legacy volumes visible to all)"
GAMMA_KEYS=$(aws ec2 describe-key-pairs --profile hive-team-gamma \
GAMMA_KEYS=$(aws ec2 describe-key-pairs --profile spx-team-gamma \
--query 'KeyPairs' --output text)
if [ -n "$GAMMA_KEYS" ] && [ "$GAMMA_KEYS" != "None" ]; then
echo " ERROR: Gamma has key pairs"
@@ -2440,7 +2440,7 @@ if [ -n "$GAMMA_KEYS" ] && [ "$GAMMA_KEYS" != "None" ]; then
fi
echo " Gamma: no key pairs"
GAMMA_SNAPS=$(aws ec2 describe-snapshots --owner-ids self --profile hive-team-gamma \
GAMMA_SNAPS=$(aws ec2 describe-snapshots --owner-ids self --profile spx-team-gamma \
--query 'Snapshots' --output text)
if [ -n "$GAMMA_SNAPS" ] && [ "$GAMMA_SNAPS" != "None" ]; then
echo " ERROR: Gamma has snapshots"
@@ -2451,7 +2451,7 @@ echo " Gamma: no snapshots"
# Root isolation from tenants
echo " Verifying root isolation from tenants..."
aws ec2 create-key-pair --key-name root-scoping-key > /dev/null
ALPHA_ROOT_CHECK=$(aws ec2 describe-key-pairs --profile hive-team-alpha \
ALPHA_ROOT_CHECK=$(aws ec2 describe-key-pairs --profile spx-team-alpha \
--query 'KeyPairs[].KeyName' --output text)
if echo "$ALPHA_ROOT_CHECK" | grep -q "root-scoping-key"; then
echo " ERROR: Alpha can see root's key pair"
@@ -2470,11 +2470,11 @@ aws ec2 delete-key-pair --key-name root-scoping-key > /dev/null
# Non-existent resource IDs — same error as cross-account
expect_error "InvalidVolume.NotFound" \
aws ec2 delete-volume --volume-id vol-00000000000000000 --profile hive-team-alpha
aws ec2 delete-volume --volume-id vol-00000000000000000 --profile spx-team-alpha
echo " Non-existent volume: same error as cross-account"
expect_error "InvalidSnapshot.NotFound" \
aws ec2 delete-snapshot --snapshot-id snap-00000000000000000 --profile hive-team-alpha
aws ec2 delete-snapshot --snapshot-id snap-00000000000000000 --profile spx-team-alpha
echo " Non-existent snapshot: same error as cross-account"
echo " Edge cases passed"
@@ -2486,16 +2486,16 @@ echo "----------------------------------------"
# Terminate instances
echo " Terminating Alpha instance..."
aws ec2 terminate-instances --instance-ids "$ALPHA_INST" --profile hive-team-alpha > /dev/null
aws ec2 terminate-instances --instance-ids "$ALPHA_INST" --profile spx-team-alpha > /dev/null
echo " Terminating Beta instance..."
aws ec2 terminate-instances --instance-ids "$BETA_INST" --profile hive-team-beta > /dev/null
aws ec2 terminate-instances --instance-ids "$BETA_INST" --profile spx-team-beta > /dev/null
# Wait for termination
COUNT=0
while [ $COUNT -lt 60 ]; do
A_STATE=$(aws ec2 describe-instances --instance-ids "$ALPHA_INST" --profile hive-team-alpha \
A_STATE=$(aws ec2 describe-instances --instance-ids "$ALPHA_INST" --profile spx-team-alpha \
--query 'Reservations[0].Instances[0].State.Name' --output text 2>/dev/null || echo "terminated")
B_STATE=$(aws ec2 describe-instances --instance-ids "$BETA_INST" --profile hive-team-beta \
B_STATE=$(aws ec2 describe-instances --instance-ids "$BETA_INST" --profile spx-team-beta \
--query 'Reservations[0].Instances[0].State.Name' --output text 2>/dev/null || echo "terminated")
if [ "$A_STATE" == "terminated" ] && [ "$B_STATE" == "terminated" ]; then
break
@@ -2507,52 +2507,52 @@ echo " Instances terminated"
# Delete snapshots
echo " Deleting snapshots..."
aws ec2 delete-snapshot --snapshot-id "$ALPHA_SNAP" --profile hive-team-alpha 2>/dev/null || true
aws ec2 delete-snapshot --snapshot-id "$BETA_SNAP" --profile hive-team-beta 2>/dev/null || true
aws ec2 delete-snapshot --snapshot-id "$ALPHA_SNAP" --profile spx-team-alpha 2>/dev/null || true
aws ec2 delete-snapshot --snapshot-id "$BETA_SNAP" --profile spx-team-beta 2>/dev/null || true
# Delete volumes (wait for detach/termination cleanup)
sleep 3
echo " Deleting volumes..."
aws ec2 delete-volume --volume-id "$ALPHA_VOL" --profile hive-team-alpha 2>/dev/null || true
aws ec2 delete-volume --volume-id "$BETA_VOL" --profile hive-team-beta 2>/dev/null || true
aws ec2 delete-volume --volume-id "$ALPHA_VOL" --profile spx-team-alpha 2>/dev/null || true
aws ec2 delete-volume --volume-id "$BETA_VOL" --profile spx-team-beta 2>/dev/null || true
# Delete key pairs
echo " Deleting key pairs..."
for key in alpha-key alpha-instance-key shared-name imported-key; do
aws ec2 delete-key-pair --key-name "$key" --profile hive-team-alpha 2>/dev/null || true
aws ec2 delete-key-pair --key-name "$key" --profile spx-team-alpha 2>/dev/null || true
done
for key in beta-key beta-instance-key shared-name; do
aws ec2 delete-key-pair --key-name "$key" --profile hive-team-beta 2>/dev/null || true
aws ec2 delete-key-pair --key-name "$key" --profile spx-team-beta 2>/dev/null || true
done
# Delete EIGWs
echo " Deleting EIGWs..."
aws ec2 delete-egress-only-internet-gateway \
--egress-only-internet-gateway-id "$ALPHA_EIGW" --profile hive-team-alpha 2>/dev/null || true
--egress-only-internet-gateway-id "$ALPHA_EIGW" --profile spx-team-alpha 2>/dev/null || true
aws ec2 delete-egress-only-internet-gateway \
--egress-only-internet-gateway-id "$BETA_EIGW" --profile hive-team-beta 2>/dev/null || true
--egress-only-internet-gateway-id "$BETA_EIGW" --profile spx-team-beta 2>/dev/null || true
# Detach + delete IGWs
echo " Deleting IGWs..."
aws ec2 detach-internet-gateway --internet-gateway-id "$ALPHA_IGW" \
--vpc-id "$ALPHA_VPC" --profile hive-team-alpha 2>/dev/null || true
--vpc-id "$ALPHA_VPC" --profile spx-team-alpha 2>/dev/null || true
aws ec2 delete-internet-gateway --internet-gateway-id "$ALPHA_IGW" \
--profile hive-team-alpha 2>/dev/null || true
--profile spx-team-alpha 2>/dev/null || true
aws ec2 delete-internet-gateway --internet-gateway-id "$BETA_IGW" \
--profile hive-team-beta 2>/dev/null || true
--profile spx-team-beta 2>/dev/null || true
# Delete subnets
echo " Deleting subnets..."
aws ec2 delete-subnet --subnet-id "$ALPHA_SUBNET" --profile hive-team-alpha 2>/dev/null || true
aws ec2 delete-subnet --subnet-id "$BETA_SUBNET" --profile hive-team-beta 2>/dev/null || true
aws ec2 delete-subnet --subnet-id "$ALPHA_SUBNET" --profile spx-team-alpha 2>/dev/null || true
aws ec2 delete-subnet --subnet-id "$BETA_SUBNET" --profile spx-team-beta 2>/dev/null || true
# Delete VPCs
echo " Deleting VPCs..."
aws ec2 delete-vpc --vpc-id "$ALPHA_VPC" --profile hive-team-alpha 2>/dev/null || true
aws ec2 delete-vpc --vpc-id "$BETA_VPC" --profile hive-team-beta 2>/dev/null || true
aws ec2 delete-vpc --vpc-id "$ALPHA_VPC" --profile spx-team-alpha 2>/dev/null || true
aws ec2 delete-vpc --vpc-id "$BETA_VPC" --profile spx-team-beta 2>/dev/null || true
# Clean up AWS CLI profiles
for p in hive-team-alpha hive-team-beta hive-team-gamma; do
for p in spx-team-alpha spx-team-beta spx-team-gamma; do
aws configure set aws_access_key_id "" --profile $p 2>/dev/null || true
aws configure set aws_secret_access_key "" --profile $p 2>/dev/null || true
done