Merge branch 'feat/iso-installer' of github.com:mulgadc/spinifex into feat/iso-installer

This commit is contained in:
tomnewton-mulga
2026-04-21 14:33:46 +10:00
69 changed files with 1711 additions and 1583 deletions
+55 -9
View File
@@ -26,7 +26,7 @@ env:
jobs:
e2e_single:
name: Single-Node E2E
runs-on: [self-hosted, ci-single]
runs-on: [ self-hosted, ci-single ]
timeout-minutes: 30
env:
TOFU_LOG: /tmp/e2e-tofu-single-${{ github.run_id }}.log
@@ -41,14 +41,29 @@ jobs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories: mulga
- name: Resolve Mulga Branch
id: mulga-branch
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
BRANCH: ${{ github.ref_name }}
run: |
if gh api "repos/mulgadc/mulga/branches/${BRANCH}" >/dev/null 2>&1; then
echo "ref=${BRANCH}" >> "$GITHUB_OUTPUT"
echo "mulga: using branch ${BRANCH}"
else
echo "ref=main" >> "$GITHUB_OUTPUT"
echo "mulga: branch ${BRANCH} not found, falling back to main"
fi
- uses: actions/checkout@v6
with:
repository: mulgadc/mulga
ref: ${{ steps.mulga-branch.outputs.ref }}
token: ${{ steps.app-token.outputs.token }}
sparse-checkout: scripts/tofu-cluster
path: mulga
- name: Build install artifacts
- name: Build Install Artifacts
working-directory: mulga/scripts/tofu-cluster
run: |
./build-install-artifacts.sh "${GIT_BRANCH}" \
@@ -95,7 +110,7 @@ jobs:
e2e_pseudo_multi:
name: Pseudo Multi-Node E2E
runs-on: [self-hosted, ci-pseudo]
runs-on: [ self-hosted, ci-pseudo ]
timeout-minutes: 30
env:
TOFU_LOG: /tmp/e2e-tofu-pseudo-${{ github.run_id }}.log
@@ -110,16 +125,32 @@ jobs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories: mulga
- name: Resolve Mulga Branch
id: mulga-branch
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
BRANCH: ${{ github.ref_name }}
run: |
if gh api "repos/mulgadc/mulga/branches/${BRANCH}" >/dev/null 2>&1; then
echo "ref=${BRANCH}" >> "$GITHUB_OUTPUT"
echo "mulga: using branch ${BRANCH}"
else
echo "ref=main" >> "$GITHUB_OUTPUT"
echo "mulga: branch ${BRANCH} not found, falling back to main"
fi
- uses: actions/checkout@v6
with:
repository: mulgadc/mulga
ref: ${{ steps.mulga-branch.outputs.ref }}
token: ${{ steps.app-token.outputs.token }}
sparse-checkout: scripts/tofu-cluster
path: mulga
- name: Clone and build
- name: Clone and Build
working-directory: mulga/scripts/tofu-cluster
run: ./build-artifacts.sh "${GIT_BRANCH}" "${RUNNER_TEMP}/spinifex-artifacts.tar"
run: ./build-artifacts.sh "${GIT_BRANCH}"
"${RUNNER_TEMP}/spinifex-artifacts.tar"
- name: Provision VM
working-directory: mulga/scripts/tofu-cluster
@@ -158,7 +189,7 @@ jobs:
e2e_real_multi:
name: Real Multi-Node E2E
runs-on: [self-hosted, ci-multi]
runs-on: [ self-hosted, ci-multi ]
timeout-minutes: 35
env:
TOFU_LOG: /tmp/e2e-tofu-multi-${{ github.run_id }}.log
@@ -173,9 +204,24 @@ jobs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories: mulga
- name: Resolve Mulga Branch
id: mulga-branch
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
BRANCH: ${{ github.ref_name }}
run: |
if gh api "repos/mulgadc/mulga/branches/${BRANCH}" >/dev/null 2>&1; then
echo "ref=${BRANCH}" >> "$GITHUB_OUTPUT"
echo "mulga: using branch ${BRANCH}"
else
echo "ref=main" >> "$GITHUB_OUTPUT"
echo "mulga: branch ${BRANCH} not found, falling back to main"
fi
- uses: actions/checkout@v6
with:
repository: mulgadc/mulga
ref: ${{ steps.mulga-branch.outputs.ref }}
token: ${{ steps.app-token.outputs.token }}
sparse-checkout: scripts/tofu-cluster
path: mulga
@@ -238,11 +284,11 @@ jobs:
e2e_status:
name: Report E2E Status
if: always() && inputs.sha
needs: [e2e_single, e2e_pseudo_multi, e2e_real_multi]
runs-on: [self-hosted, ci-single]
needs: [ e2e_single, e2e_pseudo_multi, e2e_real_multi ]
runs-on: [ self-hosted, ci-single ]
timeout-minutes: 5
steps:
- name: Report status to PR
- name: Report Status to PR
env:
GH_TOKEN: ${{ github.token }}
run: |
+18 -3
View File
@@ -2,7 +2,7 @@ name: Release
on:
push:
tags: ["v*"]
tags: [ "v*" ]
permissions:
contents: write
@@ -82,7 +82,7 @@ jobs:
spinifex/dist/spinifex-${{ github.ref_name }}-linux-arm64.tar.gz.sha256
release:
needs: [distro-amd64, distro-arm64]
needs: [ distro-amd64, distro-arm64 ]
runs-on: ubuntu-24.04
steps:
- name: Download amd64 Artifacts
@@ -115,7 +115,7 @@ jobs:
verify:
needs: release
runs-on: [self-hosted, ci-single]
runs-on: [ self-hosted, ci-single ]
timeout-minutes: 25
env:
TOFU_LOG: /tmp/release-verify-${{ github.run_id }}.log
@@ -132,9 +132,24 @@ jobs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories: mulga
- name: Resolve Mulga Branch
id: mulga-branch
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
BRANCH: ${{ github.ref_name }}
run: |
if gh api "repos/mulgadc/mulga/branches/${BRANCH}" >/dev/null 2>&1; then
echo "ref=${BRANCH}" >> "$GITHUB_OUTPUT"
echo "mulga: using branch ${BRANCH}"
else
echo "ref=main" >> "$GITHUB_OUTPUT"
echo "mulga: branch ${BRANCH} not found, falling back to main"
fi
- uses: actions/checkout@v6
with:
repository: mulgadc/mulga
ref: ${{ steps.mulga-branch.outputs.ref }}
token: ${{ steps.app-token.outputs.token }}
sparse-checkout: scripts/tofu-cluster
path: mulga
+2 -3
View File
@@ -3,10 +3,9 @@
</p>
<p align="center">
<a href="https://go.dev"><img src="https://img.shields.io/badge/Go-1.22+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go"></a>
<a href="https://go.dev"><img src="https://img.shields.io/badge/Go-1.26+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-AGPL--3.0-3fb950?style=flat-square" alt="License"></a>
<a href="https://docs.mulgadc.com"><img src="https://img.shields.io/badge/Docs-mulgadc.com-DC2626?style=flat-square" alt="Docs"></a>
<a href="https://github.com/mulgadc/spinifex/actions"><img src="https://img.shields.io/badge/CI-e2e-58a6ff?style=flat-square" alt="CI"></a>
<a href="https://docs.mulgadc.com"><img src="https://img.shields.io/badge/Docs-docs.mulgadc.com-DC2626?style=flat-square" alt="Docs"></a>
</p>
<p align="center">
+1 -1
View File
@@ -1192,7 +1192,7 @@ func runAdminInitMultiNode(cmd *cobra.Command, accessKey, secretKey, accountID,
fmt.Printf(" Waiting for %d more node(s) to join...\n", expectedNodes-1)
fmt.Printf(" Token expires in %s\n\n", tokenTTL)
fmt.Printf(" Other nodes should run:\n")
fmt.Printf(" spx admin join --host %s --token %s --node <name> --bind <ip>\n\n", formationAddr, joinToken)
fmt.Printf(" sudo spx admin join --host %s --token %s --node <name> --bind <ip>\n\n", formationAddr, joinToken)
// Wait for all nodes to register
if err := fs.WaitForCompletion(formationTimeout); err != nil {
@@ -35,13 +35,25 @@ All services in the Spinifex platform are managed through this single binary.
## Account Management
Create a new isolated account with an admin user and AdministratorAccess policy:
```bash
spx admin account create --name myteam
export AWS_PROFILE=spinifex-myteam
```
List all accounts:
```bash
spx admin account list
```
## Node Management
List nodes in the cluster:
```bash
spx get nodes
```
@@ -59,6 +71,8 @@ node3 Ready 127.0.0.3 ap-southeast-2 ap-southeast-2a 2m 0
spx top nodes
```
Prints per-node CPU/memory usage and cluster-wide instance type availability.
## Image Management
```bash
@@ -66,8 +80,13 @@ spx admin images list
spx admin images import --name debian-12-arm64
```
Catalog imports verify the image against the catalog-declared SHA-256/SHA-512 digest before extraction. Use `--file` to import operator-supplied media (verification skipped — operator is responsible for integrity), or `--force` to re-download after a checksum mismatch.
## Cluster Shutdown
Coordinated, phased shutdown of the entire cluster (API/UI → VMs → viperblock → predastore → NATS/daemon):
```bash
spx admin cluster shutdown
```
@@ -86,11 +105,12 @@ If you get permission errors during operations, ensure you're running with appro
### Services Fail to Start
Check the daemon logs for specific errors:
Check the daemon logs for specific errors via `systemctl`/`journalctl`:
```bash
ls ~/spinifex/logs/
cat ~/spinifex/logs/spinifex.log
systemctl status 'spinifex-*'
journalctl -u spinifex-daemon -f
journalctl -u 'spinifex-*' -f
```
Common causes include port conflicts, missing OVN configuration, or untrusted CA certificates.
+1 -1
View File
@@ -149,7 +149,7 @@ For production SSH via public IPs, see [Setting Up Your Cluster](/docs/setting-u
Check QEMU logs for the instance and verify the AMI architecture matches your host:
```bash
cat ~/spinifex/logs/spinifex.log
journalctl -u spinifex-daemon -f
aws ec2 describe-images --image-ids $SPINIFEX_AMI
```
+4 -4
View File
@@ -1014,13 +1014,13 @@ sudo tcpdump -i {nic} -n "host {public_ip}"
```bash
# vpcd log (reconcile, NAT, topology operations)
cat ~/spinifex/logs/vpcd.log
journalctl -u spinifex-vpcd -f
# ovn-controller log (port binding, commit failures)
sudo cat /var/log/ovn/ovn-controller.log | tail -50
# Daemon log (instance launch, network setup)
cat ~/spinifex/logs/spinifex.log
journalctl -u spinifex-daemon -f
```
### VPC Creation Fails
@@ -1029,7 +1029,7 @@ Check OVN services and vpcd daemon:
```bash
sudo systemctl is-active ovn-controller
cat ~/spinifex/logs/vpcd.log
journalctl -u spinifex-vpcd -f
```
### Instances Cannot Reach Each Other
@@ -1218,7 +1218,7 @@ sudo ovs-vsctl get Open_vSwitch . external-ids:system-id
# What SB registered
sudo ovn-sbctl list Chassis | grep name
# What vpcd expects
grep node ~/spinifex/config/spinifex.toml | head -1
grep node /etc/spinifex/spinifex.toml | head -1
```
**Fix:** `reset-dev-env.sh` passes `--chassis-id=chassis-node1` to
+118 -63
View File
@@ -1,6 +1,6 @@
---
title: "Air-Gapped Install"
description: "Deploy Spinifex in environments without internet connectivity. Covers offline package caching, USB deployment, and package verification."
description: "Deploy Spinifex in environments without internet connectivity. Covers using a pre-built release tarball, mirrored APT packages, and locally-staged cloud images."
category: "Getting Started"
tags:
- install
@@ -9,8 +9,8 @@ tags:
resources:
- title: "Spinifex Repository"
url: "https://github.com/mulgadc/spinifex"
- title: "apt-cacher-ng"
url: "https://wiki.debian.org/AptCacherNg"
- title: "External Connection Inventory"
url: "/docs/security/network-connections"
---
# Air-Gapped Installation
@@ -29,103 +29,158 @@ resources:
In air-gapped or disconnected environments, Spinifex can be deployed without internet access. This guide covers preparing offline packages on a connected machine, creating USB deployment media, and installing on the target server with package verification.
**Deployment methods:**
- **apt-cacher-ng** — Local APT cache for Debian/Ubuntu packages
- **Go module cache** — Pre-downloaded Go dependencies
- **USB deployment** — Portable installation media with all dependencies
- **GPG verification** — Cryptographic package integrity checks
## Instructions
## Step 1. Prepare Packages (on a connected machine)
## Step 1. Download the Release (on a connected machine)
Set up a local APT cache to collect all required packages:
Each Spinifex release publishes a self-contained tarball, the matching `setup.sh`, and a SHA-256 checksum to GitHub Releases. Resolve the latest tag and download the assets for your architecture:
```bash
sudo apt install apt-cacher-ng
sudo systemctl enable --now apt-cacher-ng
ARCH=amd64 # or arm64
TAG=$(curl -fsSL https://api.github.com/repos/mulgadc/spinifex/releases/latest \
| grep '"tag_name"' | cut -d'"' -f4)
BASE="https://github.com/mulgadc/spinifex/releases/download/${TAG}"
curl -fsSLO "${BASE}/spinifex-${TAG}-linux-${ARCH}.tar.gz"
curl -fsSLO "${BASE}/spinifex-${TAG}-linux-${ARCH}.tar.gz.sha256"
curl -fsSLO "${BASE}/setup.sh"
sha256sum -c "spinifex-${TAG}-linux-${ARCH}.tar.gz.sha256"
```
Pre-download all Spinifex dependencies:
## Step 2. Stage Dependencies (on the connected machine)
Pre-download the APT packages installed by the production setup. `apt install --download-only` writes `.deb` files to `/var/cache/apt/archives/` without installing them, so the connected machine isn't modified:
```bash
sudo apt install --download-only nbdkit nbdkit-plugin-dev pkg-config \
qemu-system qemu-utils qemu-kvm libvirt-daemon-system \
libvirt-clients libvirt-dev make gcc unzip xz-utils file \
ovn-central ovn-host openvswitch-switch
sudo apt update
sudo apt install --download-only -y \
nbdkit nbdkit-plugin-dev pkg-config \
qemu-system-x86 qemu-utils qemu-kvm \
libvirt-daemon-system libvirt-clients libvirt-dev \
ovn-central ovn-host openvswitch-switch \
dhcpcd-base make gcc jq curl iproute2 netcat-openbsd \
wget unzip xz-utils file
```
## Step 2. Create USB Deployment Media
Download AWS CLI v2:
```bash
mkdir -p /media/spinifex-deploy/{apt-packages,go-cache,spinifex-source}
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" \
-o awscliv2.zip
```
Mirror the cloud image you intend to run as guest VMs:
```bash
mkdir -p images
curl -fsSL "https://cdimage.debian.org/cdimage/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2" \
-o images/debian-12-amd64.qcow2
```
## Step 3. Assemble Transfer Media
```bash
mkdir -p /media/spinifex-deploy/{tarball,apt-packages,aws,images}
cp spinifex-${TAG}-linux-${ARCH}.tar.gz /media/spinifex-deploy/tarball/
cp setup.sh /media/spinifex-deploy/
cp /var/cache/apt/archives/*.deb /media/spinifex-deploy/apt-packages/
cp -r ~/go/pkg/mod/cache/ /media/spinifex-deploy/go-cache/
cp -r ~/Development/mulga/spinifex /media/spinifex-deploy/spinifex-source/
cp awscliv2.zip /media/spinifex-deploy/aws/
cp images/*.qcow2 /media/spinifex-deploy/images/
```
## Step 3. Verify Package Integrity
## Step 4. Install on the Air-Gapped Target
Sign and verify packages before transferring to the target:
```bash
gpg --import /media/spinifex-deploy/mulga-signing-key.asc
gpg --verify spinifex-v1.0.tar.gz.sig spinifex-v1.0.tar.gz
sha256sum -c /media/spinifex-deploy/checksums.sha256
```
## Step 4. Install on the Target Server
Mount the USB and install:
Mount the media, install APT dependencies, install AWS CLI, then run `setup.sh` with the local tarball:
```bash
sudo mount /dev/sdb1 /mnt/usb
sudo dpkg -i /mnt/usb/apt-packages/*.deb
cp -r /mnt/usb/spinifex-source ~/Development/mulga/spinifex
cd ~/Development/mulga/spinifex && make build
sudo apt-get install -f --no-download # resolve any leftover deps from local cache
cd /tmp && unzip /mnt/usb/aws/awscliv2.zip && sudo ./aws/install
INSTALL_SPINIFEX_TARBALL=/mnt/usb/tarball/spinifex-*-linux-*.tar.gz \
INSTALL_SPINIFEX_SKIP_APT=1 \
INSTALL_SPINIFEX_SKIP_AWS=1 \
bash /mnt/usb/setup.sh
```
Then follow the [Source Install](/docs/install-source) guide from Step 3 (Setup OVN) onwards.
Run `setup.sh` without `sudo` — the script handles privilege escalation internally and ends by `exec`-ing into a `newgrp spinifex` subshell so your current shell picks up `spinifex` group membership. Without that membership, AWS CLI cannot traverse `/etc/spinifex/` (mode `0750`) to read `ca.pem` and Step 9 will fail with a TLS error. Type `exit` to leave the subshell when finished.
## Step 5. Setup OVN Networking
`spx admin init` requires OVN/OVS to be configured before the daemon can manage tenant networks. `setup-ovn.sh` ships in the tarball and runs purely against local commands (no network access required):
```bash
sudo /usr/local/share/spinifex/setup-ovn.sh --management
```
If your WAN interface is a physical NIC rather than a bridge, pass `--wan-bridge=br-wan --wan-iface=<nic>`.
## Step 6. Initialize Without Telemetry
```bash
sudo spx admin init --node node1 --nodes 1 --no-telemetry
```
This generates the cluster configuration, TLS certificates, installs the CA into the system trust store, and writes AWS CLI credentials. See [Single-Node Install](/docs/install) for what `spx admin init` does.
## Step 7. Start Services
`setup.sh` enables `spinifex.target` but does not start it on a fresh install. Start it now so predastore is online before you import images:
```bash
sudo systemctl start spinifex.target
```
## Step 8. Import Cloud Images
Register the pre-staged image with Spinifex:
```bash
sudo spx admin images import --file /mnt/usb/images/debian-12-amd64.qcow2 \
--distro debian --version 12 --arch x86_64
```
## Step 9. Verify
```bash
export AWS_PROFILE=spinifex
aws ec2 describe-instance-types
aws ec2 describe-images
```
If both calls return data, your air-gapped install is working. Continue to [Setting Up Your Cluster](/docs/setting-up-your-cluster) to launch your first instance.
## Troubleshooting
### Missing Dependencies After dpkg Install
### Missing APT Dependencies
Some packages may have unresolved dependencies. Fix with:
`dpkg -i` does not resolve transitive dependencies. After installing, run:
```bash
sudo dpkg -i /mnt/usb/apt-packages/*.deb
sudo apt-get install -f
sudo apt-get install -f --no-download
```
The `-f` flag tells apt to fix broken dependencies using what's available locally.
The `--no-download` flag forces apt to use only what's in the local cache. If a dependency is genuinely missing, add it to the `apt --download-only` step on the connected machine and re-stage.
### GPG Verification Fails
### Setup.sh Tries to Download Anyway
The signing key may not match or the download was corrupted. Re-import the key and verify:
Confirm both skip flags are exported and that `INSTALL_SPINIFEX_TARBALL` points at a readable file:
```bash
gpg --import /media/spinifex-deploy/mulga-signing-key.asc
gpg --verify spinifex-v1.0.tar.gz.sig spinifex-v1.0.tar.gz
sudo INSTALL_SPINIFEX_TARBALL=/mnt/usb/tarball/spinifex-...tar.gz \
INSTALL_SPINIFEX_SKIP_APT=1 \
INSTALL_SPINIFEX_SKIP_AWS=1 \
bash -x /mnt/usb/setup.sh
```
If verification still fails, re-download the package on the connected machine and compare checksums:
### Init Telemetry Attempted
```bash
sha256sum spinifex-v1.0.tar.gz
```
`spx admin init` posts a one-shot record to `https://install.mulgadc.com/install`. Pass `--no-telemetry` on every `init` and `join` invocation, or export `SPX_NO_TELEMETRY=1` in the operator's shell profile.
### Go Module Cache Errors
### Image Import Fails
The Go module cache may have been incompletely copied. On the connected machine, re-export the full cache:
```bash
cp -r ~/go/pkg/mod/cache/ /media/spinifex-deploy/go-cache/
```
On the target, restore it:
```bash
mkdir -p ~/go/pkg/mod/
cp -r /mnt/usb/go-cache ~/go/pkg/mod/cache
```
`spx admin images import --file` requires the distro/version/arch flags so the image registers in the catalogue. If the import succeeds but `aws ec2 describe-images` returns empty, check `journalctl -u spinifex-daemon -f` for predastore upload errors.
@@ -31,7 +31,7 @@ resources:
A Spinifex cluster distributes services across multiple servers for high availability, data durability, and fault tolerance. Cluster formation is automatic — the init node waits for peers to join, then distributes credentials, CA certificates, and configuration.
**Network requirements:**
**Network Requirements:**
- Minimum 1 NIC per server (2 recommended for production)
- UDP port 6081 open between hosts (Geneve tunnels)
@@ -59,7 +59,7 @@ export AWS_AZ=us-east-1a
## Step 3. Setup OVN Networking
Server 1 runs OVN central and must be set up first. If your WAN interface is already a bridge (e.g. `br-wan`), setup-ovn.sh auto-detects it. Otherwise use `--wan-bridge=br-wan --wan-iface=eth1` (dedicated WAN NIC).
Server 1 runs OVN central and must be set up first. If your WAN interface is already a bridge, setup-ovn.sh auto-detects it. Otherwise use `--wan-bridge=br-wan --wan-iface=eth1` (dedicated WAN NIC).
**Server 1:**
@@ -104,8 +104,6 @@ sudo spx admin init \
--port 4432 --region $AWS_REGION --az $AWS_AZ
```
Save the admin credentials — they will not be shown again.
The init output displays the join command including the token:
```
@@ -114,7 +112,7 @@ The init output displays the join command including the token:
Token expires in 30m0s
Other nodes should run:
spx admin join --host 10.0.0.1:4432 --token spx_join_a8Bf3x9Kz2mN --node <name> --bind <ip>
sudo spx admin join --host 10.0.0.1:4432 --token spx_join_a8Bf3x9Kz2mN --node <name> --bind <ip>
```
**Server 2 — Join** (while init is running):
@@ -135,8 +133,7 @@ sudo spx admin join \
--region $AWS_REGION --az $AWS_AZ
```
> **Note:** The join token expires 30 minutes after init by default. For larger deployments
> with slower provisioning, use `--token-ttl 2h` on the init command.
**Note:** The join token expires 30 minutes after init by default. For larger deployments with slower provisioning, use `--token-ttl 2h`
## Step 5. Start Services
@@ -181,7 +178,7 @@ sudo ss -tlnp | grep 6642
### CA Certificate Not Trusted
```bash
sudo cp ~/spinifex/config/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt
sudo cp /etc/spinifex/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt
sudo update-ca-certificates
```
+4 -36
View File
@@ -52,43 +52,17 @@ export PATH=$PATH:/usr/local/go/bin
```bash
cd spinifex
./scripts/clone-deps.sh
./scripts/dev-setup.sh
```
Confirm `./bin/spx` exists.
## Step 3. Setup OVN
If your WAN interface is already a bridge (e.g. `br-wan`), setup-ovn.sh auto-detects it:
```bash
./scripts/setup-ovn.sh --management
```
If your WAN is a physical NIC:
```bash
# Dedicated WAN NIC (not your SSH connection):
./scripts/setup-ovn.sh --management --wan-bridge=br-wan --wan-iface=eth1
```
## Step 4. Development initialisation
## Step 3. Development Initialisation
```bash
./scripts/dev-install.sh
```
Note, this will bootstrap a single node development environment
This bootstraps a single-node development environment: builds binaries, configures OVN, initialises the cluster, installs the CA certificate, and starts all services.
## Step 5. Start services
```bash
sudo systemctl status spinifex.target
```
Start all services
## Step 6. Verify Installation
## Step 4. Verify Installation
```bash
export AWS_PROFILE=spinifex
@@ -109,15 +83,9 @@ Continue to [Setting Up Your Cluster](/docs/setting-up-your-cluster) to import a
export PATH=$PATH:/usr/local/go/bin
```
### ./bin/spx Missing After Build
```bash
./scripts/dev-setup.sh
```
### CA Certificate Not Trusted
```bash
sudo cp ~/spinifex/config/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt
sudo cp /etc/spinifex/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt
sudo update-ca-certificates
```
+3 -4
View File
@@ -29,7 +29,7 @@ resources:
## Overview
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.
Spinifex is an open-source infrastructure platform that brings core AWS services 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 Install](/docs/install-multi-node). To build from source, see [Source Install](/docs/install-source).
@@ -65,10 +65,9 @@ If your WAN interface is already a bridge (e.g. `br-wan`), setup-ovn.sh auto-det
sudo /usr/local/share/spinifex/setup-ovn.sh --management
```
If your WAN is a physical NIC, choose one:
If your WAN is a physical NIC:
```bash
# Dedicated WAN NIC (not your SSH connection):
sudo /usr/local/share/spinifex/setup-ovn.sh --management --wan-bridge=br-wan --wan-iface=eth1
```
@@ -86,7 +85,7 @@ This is recommended for production and required in multi-node deployments. See [
sudo spx admin init --node node1 --nodes 1
```
This auto-detects your network topology, generates configuration and TLS certificates, installs the CA into the system trust store, and configures AWS CLI credentials. Save the admin credentials printed during init — they will not be shown again.
This auto-detects your network topology, generates configuration and TLS certificates, installs the CA into the system trust store, and configures AWS CLI credentials (saved in `~/.aws/credentials`)
## Step 4. Start Services
@@ -34,7 +34,7 @@ resources:
## Overview
This guide assumes Spinifex is already installed and running. If not, follow one of the installation guides first:
- [Binary Install (Single Node)](/docs/install)
- [Single-Node Install)](/docs/install)
- [Multi-Node Install](/docs/install-multi-node)
- [Source Install](/docs/install-source)
@@ -57,11 +57,12 @@ spx admin images list
```
```
NAME | DISTRO | VERSION | ARCH | BOOT
debian-12-arm64 | debian | 12 | arm64 | bios
debian-12-x86_64 | debian | 12 | x86_64 | bios
ubuntu-24.04-arm64 | ubuntu | 24.04 | arm64 | bios
ubuntu-24.04-x86_64 | ubuntu | 24.04 | x86_64 | bios
NAME | DISTRO | VERSION | ARCH | BOOT
debian-12-arm64 | debian | 12 | arm64 | bios
debian-12-x86_64 | debian | 12 | x86_64 | bios
lb-alpine-3.21.6-x86_64 | alpine | 3.21.6 | x86_64 | bios
ubuntu-24.04-arm64 | ubuntu | 24.04 | arm64 | bios
ubuntu-24.04-x86_64 | ubuntu | 24.04 | x86_64 | bios
```
Import an image:
@@ -344,7 +345,7 @@ export AWS_PROFILE=spinifex-myteam
### Instance Stuck in Pending
```bash
cat ~/spinifex/logs/spinifex.log
journalctl -u spinifex-daemon -f
aws ec2 describe-images
```
+2 -2
View File
@@ -1,7 +1,7 @@
---
title: "Updating Spinifex"
description: "Upgrade an existing Spinifex installation to the latest release."
category: "Getting Started"
category: "Administration"
tags:
- update
- upgrade
@@ -10,7 +10,7 @@ resources:
- title: "Spinifex Repository"
url: "https://github.com/mulgadc/spinifex"
- title: "Single-Node Install"
url: "https://docs.mulgadc.com/docs/install"
url: "/docs/install"
---
# Updating Spinifex
@@ -129,7 +129,7 @@ Operators own remediation for layers Spinifex does not ship:
| Host OS + kernel | Track distribution advisories (Debian DSA, Ubuntu USN) at matching cadence. |
| OVN / OVS | Patched via the operator's package channel alongside the OS. |
| QEMU / KVM / libvirt | As above — hypervisor CVEs affect tenant isolation. |
| Host AV / EDR agent | See https://docs.mulgadc.com/docs/security/malware-protection §2. |
| Host AV / EDR agent | See [Malware Protection §2](/docs/security/malware-protection#2-update-requirements-sil1-3144). |
Operators should subscribe to the `mulgadc/spinifex` release feed (Watch → Custom → Releases + Security Advisories) so Critical/High advisories arrive via GitHub's notification channel rather than polling.
+1 -1
View File
@@ -114,7 +114,7 @@ Spinifex nodes initiate a small, fixed set of outbound connections.
**Update checks and metadata.** Spinifex does not check for updates and does not consume a cloud metadata service (`169.254.169.254` is served *by* the cluster to guest VMs). Node software updates come from the operator's OS package channel. The install-telemetry endpoint above is the only vendor-operated destination contacted by a node; closed-egress deployments should disable it and record the opt-out in the security plan.
**Air-gapped deployments.** The three image URLs are the only destinations needed for the standard image catalogue. Mirror them locally and use `spx admin images import --file` with pre-staged files. Telemetry must also be disabled. See [Air-gapped install](https://docs.mulgadc.com/docs/install-airgapped).
**Air-gapped deployments.** The three image URLs are the only destinations needed for the standard image catalogue. Mirror them locally and use `spx admin images import --file` with pre-staged files. Telemetry must also be disabled. See [Air-Gapped Install](/docs/install-airgapped).
## 3. Cross-Node (Internal) Connections
@@ -144,7 +144,7 @@ Every entry into a protected space must produce a log entry capturing:
### 4.3 Integration with Spinifex Logs
Forward physical-access events to the same SIEM or log collector used for Spinifex service logs (see [Malware Protection §3](https://docs.mulgadc.com/docs/security/malware-protection#3-scan-schedule-sil1-3145)) so a `master.key` access attempt can be correlated with a physical entry into the rack.
Forward physical-access events to the same SIEM or log collector used for Spinifex service logs (see [Malware Protection §3](/docs/security/malware-protection#3-scan-schedule-sil1-3145)) so a `master.key` access attempt can be correlated with a physical entry into the rack.
## 5. Manage Physical Access Devices (PE.L1-3.10.5)
@@ -199,7 +199,7 @@ Use this list to confirm a site meets the four CMMC practices before admitting S
- CCTV or equivalent monitoring covers the rack fronts, rears, and console positions, with 30-day minimum retention.
- Physical access logs are produced for every entry, retained 12 months, and reviewed on the cadence in [§4.2](#42-retention-and-review).
- Door-forced, door-held, and out-of-hours events alert the on-call operator.
- Physical access events forwarded to the SIEM used for Spinifex service logs (see [Malware Protection §3](https://docs.mulgadc.com/docs/security/malware-protection#3-scan-schedule-sil1-3145)).
- Physical access events forwarded to the SIEM used for Spinifex service logs (see [Malware Protection §3](/docs/security/malware-protection#3-scan-schedule-sil1-3145)).
- Device register reconciles quarterly against the access-control system and physical key count; discrepancies are closed before sign-off.
- Lost-device and termination procedures trigger immediate revocation per [§5.2](#52-control-b).
- System security plan references this guide and records the facility, access-control product, CCTV retention, and log-forwarding destination.
@@ -58,7 +58,6 @@ Deploy a VPC with public and private subnets where the private subnet has no int
- Spinifex installed and running (see [Installing Spinifex](/docs/install))
- A Debian 12 AMI imported (see [Setting Up Your Cluster](/docs/setting-up-your-cluster))
- OpenTofu or Terraform installed
- `AWS_PROFILE=spinifex` configured
## Instructions
@@ -68,7 +68,6 @@ Deploy two Nginx web servers behind an internet-facing Application Load Balancer
- Spinifex installed and running (see [Installing Spinifex](/docs/install))
- A Debian 12 AMI imported (see [Setting Up Your Cluster](/docs/setting-up-your-cluster))
- OpenTofu or Terraform installed
- `AWS_PROFILE=spinifex` configured
## Instructions
+23 -99
View File
@@ -1,14 +1,18 @@
# Example: Nginx Web Servers with ALB on Spinifex
#
# Deploys a VPC with two public subnets (ALB + NAT Gateway) and two private
# subnets hosting Nginx EC2 instances. An internet-facing Application Load
# Balancer distributes HTTP traffic between the private workers, and the
# NAT Gateway gives them outbound internet access so cloud-init can install
# Nginx from the Debian apt repository.
# Deploys a VPC with two public subnets hosting Nginx EC2 instances and an
# internet-facing Application Load Balancer. Workers sit in public subnets
# with auto-assigned public IPs purely so cloud-init can apt-install nginx.
# The ALB targets them by primary private IP (target_type=instance), so
# load-balanced traffic stays on the private VPC network.
#
# Demonstrates: VPC, public and private subnets, internet gateway, NAT
# Gateway with Elastic IP, route tables, security group, key pair, cloud-init
# user-data, EC2 instances, ALB, target group, and listener.
# In a production deployment, workers would be in private subnets with
# nginx baked into a custom AMI (or installed via a private repository
# mirror), and a NAT Gateway would be unnecessary for this workload.
#
# Demonstrates: VPC, public subnets, internet gateway, route tables,
# security group, key pair, cloud-init user-data, EC2 instances with
# auto-assigned public IPs, ALB, target group, and listener.
#
# Usage:
# cd spinifex/docs/terraform/nginx-alb
@@ -152,9 +156,10 @@ resource "aws_internet_gateway" "igw" {
# ---------------------------------------------------------------------------
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.20.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
vpc_id = aws_vpc.main.id
cidr_block = "10.20.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "nginx-alb-public-a"
@@ -162,9 +167,10 @@ resource "aws_subnet" "public_a" {
}
resource "aws_subnet" "public_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.20.2.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
vpc_id = aws_vpc.main.id
cidr_block = "10.20.2.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "nginx-alb-public-b"
@@ -172,58 +178,7 @@ resource "aws_subnet" "public_b" {
}
# ---------------------------------------------------------------------------
# Private Subnets (two AZs for the Nginx workers)
# ---------------------------------------------------------------------------
resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.20.11.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
tags = {
Name = "nginx-alb-private-a"
}
}
resource "aws_subnet" "private_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.20.12.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
tags = {
Name = "nginx-alb-private-b"
}
}
# ---------------------------------------------------------------------------
# NAT Gateway — outbound internet for the private subnets
#
# Background plumbing: the private-subnet workers need outbound connectivity
# during cloud-init so apt-get can install Nginx. A single NAT Gateway in
# public_a provides SNAT for both private subnets via the private route table.
# ---------------------------------------------------------------------------
resource "aws_eip" "nat" {
domain = "vpc"
tags = {
Name = "nginx-alb-nat-eip"
}
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public_a.id
tags = {
Name = "nginx-alb-nat"
}
depends_on = [aws_internet_gateway.igw]
}
# ---------------------------------------------------------------------------
# Route Tables — public subnets egress via IGW, private subnets via NAT GW
# Route Table — public subnets egress via IGW
# ---------------------------------------------------------------------------
resource "aws_route_table" "public" {
@@ -249,29 +204,6 @@ resource "aws_route_table_association" "public_b" {
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = {
Name = "nginx-alb-private-rt"
}
}
resource "aws_route_table_association" "private_a" {
subnet_id = aws_subnet.private_a.id
route_table_id = aws_route_table.private.id
}
resource "aws_route_table_association" "private_b" {
subnet_id = aws_subnet.private_b.id
route_table_id = aws_route_table.private.id
}
# ---------------------------------------------------------------------------
# Security Group — SSH + HTTP inbound, all outbound
# ---------------------------------------------------------------------------
@@ -318,14 +250,10 @@ resource "aws_instance" "nginx_1" {
ami = data.aws_ami.debian12.id
instance_type = var.instance_type
subnet_id = aws_subnet.private_a.id
subnet_id = aws_subnet.public_a.id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = aws_key_pair.nginx.key_name
# Workers pull nginx from apt via the NAT Gateway — creation must wait
# until the NAT Gateway is available so cloud-init can reach the repos.
depends_on = [aws_nat_gateway.main]
user_data_base64 = base64encode(<<-USERDATA
#!/bin/bash
set -euo pipefail
@@ -362,14 +290,10 @@ resource "aws_instance" "nginx_2" {
ami = data.aws_ami.debian12.id
instance_type = var.instance_type
subnet_id = aws_subnet.private_b.id
subnet_id = aws_subnet.public_b.id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = aws_key_pair.nginx.key_name
# Workers pull nginx from apt via the NAT Gateway — creation must wait
# until the NAT Gateway is available so cloud-init can reach the repos.
depends_on = [aws_nat_gateway.main]
user_data_base64 = base64encode(<<-USERDATA
#!/bin/bash
set -euo pipefail
@@ -45,7 +45,6 @@ Deploy a complete Nginx web server on Spinifex using Terraform/OpenTofu. This wo
- Spinifex installed and running (see [Installing Spinifex](/docs/install))
- A Debian 12 AMI imported (see [Setting Up Your Cluster](/docs/setting-up-your-cluster))
- OpenTofu or Terraform installed
- `AWS_PROFILE=spinifex` configured
## Instructions
@@ -55,7 +55,6 @@ Deploy an EC2 instance running a Flask file-sharing web application backed by S3
- Predastore running (S3 API on port 8443)
- A Debian 12 AMI imported (see [Setting Up Your Cluster](/docs/setting-up-your-cluster))
- OpenTofu or Terraform installed
- `AWS_PROFILE=spinifex` configured
- The EC2 instance must be able to reach Predastore — use the host's br-wan IP, not localhost
## Instructions
+2 -2
View File
@@ -13,8 +13,8 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0
github.com/mulgadc/predastore v1.1.6
github.com/mulgadc/viperblock v1.1.6
github.com/nats-io/nats-server/v2 v2.12.6
github.com/nats-io/nats.go v1.50.0
github.com/nats-io/nats-server/v2 v2.12.7
github.com/nats-io/nats.go v1.51.0
github.com/ovn-kubernetes/libovsdb v0.8.1
github.com/pelletier/go-toml/v2 v2.3.0
github.com/pterm/pterm v0.12.83
+4 -4
View File
@@ -301,10 +301,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt/v2 v2.8.1 h1:V0xpGuD/N8Mi+fQNDynXohVvp7ZztevW5io8CUWlPmU=
github.com/nats-io/jwt/v2 v2.8.1/go.mod h1:nWnOEEiVMiKHQpnAy4eXlizVEtSfzacZ1Q43LIRavZg=
github.com/nats-io/nats-server/v2 v2.12.6 h1:Egbx9Vl7Ch8wTtpXPGqbehkZ+IncKqShUxvrt1+Enc8=
github.com/nats-io/nats-server/v2 v2.12.6/go.mod h1:4HPlrvtmSO3yd7KcElDNMx9kv5EBJBnJJzQPptXlheo=
github.com/nats-io/nats.go v1.50.0 h1:5zAeQrTvyrKrWLJ0fu02W3br8ym57qf7csDzgLOpcds=
github.com/nats-io/nats.go v1.50.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=
github.com/nats-io/nats-server/v2 v2.12.7 h1:prQ9cPiWHcnwfT81Wi5lU9LL8TLY+7pxDru6fQYLCQQ=
github.com/nats-io/nats-server/v2 v2.12.7/go.mod h1:dOnmkprKMluTmTF7/QHZioxlau3sKHUM/LBPy9AiBPw=
github.com/nats-io/nats.go v1.51.0 h1:ByW84XTz6W03GSSsygsZcA+xgKK8vPGaa/FCAAEHnAI=
github.com/nats-io/nats.go v1.51.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=
github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
+8 -37
View File
@@ -144,9 +144,8 @@ type Daemon struct {
// Delay after QMP device_del before blockdev-del (default 1s, 0 in tests)
detachDelay time.Duration
// NATS connect retry parameters (zero values use defaults: 5min max, 500ms initial delay)
natsMaxWait time.Duration
natsRetryDelay time.Duration
// NATS connect retry options (nil uses defaults: 5min max, 500ms initial delay)
natsRetryOpts []utils.RetryOption
// NetworkPlumber handles tap device lifecycle for VPC networking
networkPlumber NetworkPlumber
@@ -839,40 +838,12 @@ func (d *Daemon) Start() error {
// be ready immediately after daemon start (e.g. if start-dev.sh is still
// launching services). This retries for up to 5 minutes before giving up.
func (d *Daemon) connectNATS() error {
maxWait := 5 * time.Minute
retryDelay := 500 * time.Millisecond
if d.natsMaxWait > 0 {
maxWait = d.natsMaxWait
}
if d.natsRetryDelay > 0 {
retryDelay = d.natsRetryDelay
}
start := time.Now()
for {
nc, err := utils.ConnectNATS(d.config.NATS.Host, d.config.NATS.ACL.Token, d.config.NATS.CACert)
if err == nil {
d.natsConn = nc
if time.Since(start) > time.Second {
slog.Info("NATS connection established", "elapsed", time.Since(start))
}
return nil
}
// TLS configuration errors are permanent — retrying will not help.
if strings.Contains(err.Error(), "CA cert") {
return fmt.Errorf("NATS TLS configuration error: %w", err)
}
elapsed := time.Since(start)
if elapsed >= maxWait {
return fmt.Errorf("NATS connect failed after %s: %w", elapsed.Round(time.Second), err)
}
slog.Warn("NATS not ready, retrying...", "error", err, "elapsed", elapsed.Round(time.Second), "retryIn", retryDelay)
time.Sleep(retryDelay)
retryDelay = min(retryDelay*2, 10*time.Second)
nc, err := utils.ConnectNATSWithRetry(d.config.NATS.Host, d.config.NATS.ACL.Token, d.config.NATS.CACert, d.natsRetryOpts...)
if err != nil {
return err
}
d.natsConn = nc
return nil
}
// initJetStream initializes JetStream with retry/backoff and upgrades replicas
@@ -2817,7 +2788,7 @@ func (d *Daemon) wireLBAgentConfig() {
}
if d.mgmtBridgeIP != "" {
if awsgwBindIP != "" && awsgwBindIP != "0.0.0.0" {
if awsgwBindIP != "" && awsgwBindIP != "0.0.0.0" && !net.ParseIP(awsgwBindIP).IsLoopback() {
// Multi-node: AWSGW listens on a specific WAN IP. Use that IP
// as the gateway URL. Internal LBs will get a host route via
// br-mgmt to reach it (see LaunchSystemInstance).
+86 -16
View File
@@ -153,26 +153,70 @@ func (d *Daemon) LaunchSystemInstance(input *handlers_elbv2.SystemInstanceInput)
}
}
// Allocate public IP for internet-facing ALBs
// Allocate public IP for internet-facing ALBs. Route through the EIP
// service when available so an EIPRecord is created in the KV bucket —
// otherwise AWS SDK callers (OpenTofu) can't observe the EIP via
// DescribeAddresses and their provisioning flow hangs. Falls back to
// direct IPAM allocation for daemons where the EIP service isn't wired.
publicIP := ""
if input.Scheme == handlers_elbv2.SchemeInternetFacing && d.externalIPAM != nil && d.vpcService != nil && instance.ENIId != "" {
region := ""
az := ""
if d.config != nil {
region = d.config.Region
az = d.config.AZ
}
allocatedIP, poolName, allocErr := d.externalIPAM.AllocateIP(region, az, "auto_assign", "", instance.ENIId, instance.ID)
if allocErr != nil {
slog.Error("LaunchSystemInstance: failed to allocate public IP for internet-facing ALB", "instanceId", instance.ID, "err", allocErr)
d.cleanupFailedSystemInstance(instance, instanceType)
return nil, fmt.Errorf("allocate public IP for internet-facing ALB: %w", allocErr)
} else {
if input.Scheme == handlers_elbv2.SchemeInternetFacing && d.vpcService != nil && instance.ENIId != "" {
if d.eipService != nil {
allocOut, allocErr := d.eipService.AllocateAddress(&ec2.AllocateAddressInput{}, eniAccountID)
if allocErr != nil {
slog.Error("LaunchSystemInstance: EIP AllocateAddress failed", "instanceId", instance.ID, "err", allocErr)
d.cleanupFailedSystemInstance(instance, instanceType)
return nil, fmt.Errorf("allocate public IP for internet-facing ALB: %w", allocErr)
}
publicIP = aws.StringValue(allocOut.PublicIp)
poolName := aws.StringValue(allocOut.PublicIpv4Pool)
allocID := aws.StringValue(allocOut.AllocationId)
assocOut, assocErr := d.eipService.AssociateAddress(&ec2.AssociateAddressInput{
AllocationId: allocOut.AllocationId,
NetworkInterfaceId: aws.String(instance.ENIId),
}, eniAccountID)
if assocErr != nil {
slog.Error("LaunchSystemInstance: EIP AssociateAddress failed", "instanceId", instance.ID, "allocationId", allocID, "err", assocErr)
if _, relErr := d.eipService.ReleaseAddress(&ec2.ReleaseAddressInput{
AllocationId: allocOut.AllocationId,
}, eniAccountID); relErr != nil {
slog.Warn("LaunchSystemInstance: failed to release EIP after associate failure", "allocationId", allocID, "err", relErr)
}
d.cleanupFailedSystemInstance(instance, instanceType)
return nil, fmt.Errorf("associate public IP for internet-facing ALB: %w", assocErr)
}
if updateErr := d.vpcService.UpdateENIPublicIP(eniAccountID, instance.ENIId, publicIP, poolName); updateErr != nil {
slog.Warn("LaunchSystemInstance: failed to update ENI with public IP", "eniId", instance.ENIId, "err", updateErr)
}
instance.PublicIP = publicIP
instance.PublicIPPool = poolName
instance.PublicIPAllocID = allocID
instance.PublicIPAssocID = aws.StringValue(assocOut.AssociationId)
slog.Info("LaunchSystemInstance: allocated public IP via EIP service",
"instanceId", instance.ID,
"publicIp", publicIP,
"pool", poolName,
"allocationId", allocID,
"associationId", instance.PublicIPAssocID,
)
} else if d.externalIPAM != nil {
region := ""
az := ""
if d.config != nil {
region = d.config.Region
az = d.config.AZ
}
allocatedIP, poolName, allocErr := d.externalIPAM.AllocateIP(region, az, "auto_assign", "", instance.ENIId, instance.ID)
if allocErr != nil {
slog.Error("LaunchSystemInstance: failed to allocate public IP for internet-facing ALB", "instanceId", instance.ID, "err", allocErr)
d.cleanupFailedSystemInstance(instance, instanceType)
return nil, fmt.Errorf("allocate public IP for internet-facing ALB: %w", allocErr)
}
publicIP = allocatedIP
if updateErr := d.vpcService.UpdateENIPublicIP(eniAccountID, instance.ENIId, publicIP, poolName); updateErr != nil {
slog.Warn("LaunchSystemInstance: failed to update ENI with public IP", "eniId", instance.ENIId, "err", updateErr)
}
// Look up VpcId from the ENI for the NAT event
vpcID := ""
result, descErr := d.vpcService.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{
NetworkInterfaceIds: []*string{aws.String(instance.ENIId)},
@@ -184,7 +228,7 @@ func (d *Daemon) LaunchSystemInstance(input *handlers_elbv2.SystemInstanceInput)
d.publishNATEvent("vpc.add-nat", vpcID, publicIP, privateIP, portName, instance.ENIMac)
instance.PublicIP = publicIP
instance.PublicIPPool = poolName
slog.Info("LaunchSystemInstance: allocated public IP",
slog.Info("LaunchSystemInstance: allocated public IP via direct IPAM",
"instanceId", instance.ID,
"publicIp", publicIP,
"pool", poolName,
@@ -316,6 +360,32 @@ func (d *Daemon) TerminateSystemInstance(instanceID string) error {
return fmt.Errorf("transition to shutting-down: %w", err)
}
// Release EIP through the EIP service for system VMs whose public IP was
// allocated via AllocateAddress (internet-facing ALBs). Clears the fields
// so the generic teardown path in stopInstance doesn't try to
// double-release the same IP through externalIPAM directly.
if instance.PublicIPAllocID != "" && d.eipService != nil {
eniAccount := instance.AccountID
if instance.PublicIPAssocID != "" {
if _, err := d.eipService.DisassociateAddress(&ec2.DisassociateAddressInput{
AssociationId: aws.String(instance.PublicIPAssocID),
}, eniAccount); err != nil {
slog.Warn("TerminateSystemInstance: DisassociateAddress failed", "instanceId", instanceID, "associationId", instance.PublicIPAssocID, "err", err)
}
}
if _, err := d.eipService.ReleaseAddress(&ec2.ReleaseAddressInput{
AllocationId: aws.String(instance.PublicIPAllocID),
}, eniAccount); err != nil {
slog.Warn("TerminateSystemInstance: ReleaseAddress failed", "instanceId", instanceID, "allocationId", instance.PublicIPAllocID, "err", err)
} else {
slog.Info("TerminateSystemInstance: released EIP", "instanceId", instanceID, "ip", instance.PublicIP, "allocationId", instance.PublicIPAllocID)
}
instance.PublicIP = ""
instance.PublicIPPool = ""
instance.PublicIPAllocID = ""
instance.PublicIPAssocID = ""
}
// Stop the VM (QEMU shutdown, volume unmount, tap cleanup)
if err := d.stopInstance(map[string]*vm.VM{instanceID: instance}, true); err != nil {
slog.Error("TerminateSystemInstance: stopInstance failed", "instanceId", instanceID, "err", err)
+5 -2
View File
@@ -32,6 +32,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/objectstore"
"github.com/mulgadc/spinifex/spinifex/qmp"
"github.com/mulgadc/spinifex/spinifex/types"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/mulgadc/spinifex/spinifex/vm"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
@@ -3744,8 +3745,10 @@ func TestConnectNATS_RetriesOnFailure(t *testing.T) {
clusterCfg.Nodes["node-1"] = cfg
daemon, err := NewDaemon(clusterCfg)
require.NoError(t, err)
daemon.natsMaxWait = 500 * time.Millisecond
daemon.natsRetryDelay = 50 * time.Millisecond
daemon.natsRetryOpts = []utils.RetryOption{
utils.WithMaxWait(500 * time.Millisecond),
utils.WithRetryDelay(50 * time.Millisecond),
}
start := time.Now()
err = daemon.connectNATS()
+1 -33
View File
@@ -102,7 +102,7 @@ func launchService(config *config.ClusterConfig) error {
// Connect to NATS for service communication. On concurrent startup the
// local NATS server may not be listening yet, so retry with backoff.
natsConn, err := connectNATS(nodeConfig.NATS.Host, nodeConfig.NATS.ACL.Token, nodeConfig.NATS.CACert)
natsConn, err := utils.ConnectNATSWithRetry(nodeConfig.NATS.Host, nodeConfig.NATS.ACL.Token, nodeConfig.NATS.CACert)
if err != nil {
return err
}
@@ -203,38 +203,6 @@ func launchService(config *config.ClusterConfig) error {
return nil
}
// connectNATS establishes a connection to NATS with retry/backoff. On concurrent
// startup the local NATS server may not be listening yet.
func connectNATS(host, token, caCertPath string) (*nats.Conn, error) {
const maxWait = 5 * time.Minute
retryDelay := 500 * time.Millisecond
start := time.Now()
for {
nc, err := utils.ConnectNATS(host, token, caCertPath)
if err == nil {
if time.Since(start) > time.Second {
slog.Info("NATS connection established", "elapsed", time.Since(start).Round(time.Second))
}
return nc, nil
}
// TLS configuration errors are permanent — retrying will not help.
if strings.Contains(err.Error(), "CA cert") {
return nil, fmt.Errorf("NATS TLS configuration error: %w", err)
}
elapsed := time.Since(start)
if elapsed >= maxWait {
return nil, fmt.Errorf("NATS connect failed after %s: %w", elapsed.Round(time.Second), err)
}
slog.Warn("NATS not ready, retrying...", "error", err, "elapsed", elapsed.Round(time.Second), "retryIn", retryDelay)
time.Sleep(retryDelay)
retryDelay = min(retryDelay*2, 10*time.Second)
}
}
// initIAMService initializes the IAM service with retry/backoff. On multi-node
// clusters, JetStream requires NATS cluster quorum before KV buckets can be
// created. This retries for up to 5 minutes to allow late-joining nodes.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -46,5 +46,5 @@ ${r.join(`;`)}
${n}`}async createStringToSign(e,t,n,r){let i=new this.sha256;return i.update(rc(n)),`${r}
${e}
${t}
${$(await i.digest())}`}getCanonicalPath({path:e}){if(this.uriEscapePath){let t=[];for(let n of e.split(`/`))n?.length!==0&&n!==`.`&&(n===`..`?t.pop():t.push(n));return Bc(`${e?.startsWith(`/`)?`/`:``}${t.join(`/`)}${t.length>0&&e?.endsWith(`/`)?`/`:``}`).replace(/%2F/g,`/`)}return e}validateResolvedCredentials(e){if(typeof e!=`object`||typeof e.accessKeyId!=`string`||typeof e.secretAccessKey!=`string`)throw Error(`Resolved credential object is not valid`)}formatDate(e){let t=Uc(e).replace(/[\-:]/g,``);return{longDate:t,shortDate:t.slice(0,8)}}getCanonicalHeaderList(e){return Object.keys(e).sort().join(`;`)}},Kc=class extends Gc{headerFormatter=new jc;constructor({applyChecksum:e,credentials:t,region:n,service:r,sha256:i,uriEscapePath:a=!0}){super({applyChecksum:e,credentials:t,region:n,service:r,sha256:i,uriEscapePath:a})}async presign(e,t={}){let{signingDate:n=new Date,expiresIn:r=3600,unsignableHeaders:i,unhoistableHeaders:a,signableHeaders:o,hoistableHeaders:s,signingRegion:c,signingService:l}=t,u=await this.credentialProvider();this.validateResolvedCredentials(u);let d=c??await this.regionProvider(),{longDate:f,shortDate:p}=this.formatDate(n);if(r>604800)return Promise.reject(`Signature version 4 presigned URLs must have an expiration date less than one week in the future`);let m=Tc(p,d,l??this.service),h=Lc(Rc(e),{unhoistableHeaders:a,hoistableHeaders:s});u.sessionToken&&(h.query[uc]=u.sessionToken),h.query[ic]=yc,h.query[ac]=`${u.accessKeyId}/${m}`,h.query[oc]=f,h.query[cc]=r.toString(10);let g=Oc(h,i,o);return h.query[sc]=this.getCanonicalHeaderList(g),h.query[lc]=await this.getSignature(f,m,this.getSigningKey(u,d,p,l),this.createCanonicalRequest(h,g,await Ac(e,this.sha256))),h}async sign(e,t){return typeof e==`string`?this.signString(e,t):e.headers&&e.payload?this.signEvent(e,t):e.message?this.signMessage(e,t):this.signRequest(e,t)}async signEvent({headers:e,payload:t},{signingDate:n=new Date,priorSignature:r,signingRegion:i,signingService:a}){let o=i??await this.regionProvider(),{shortDate:s,longDate:c}=this.formatDate(n),l=Tc(s,o,a??this.service),u=await Ac({headers:{},body:t},this.sha256),d=new this.sha256;d.update(e);let f=[bc,c,l,r,$(await d.digest()),u].join(`
`);return this.signString(f,{signingDate:n,signingRegion:o,signingService:a})}async signMessage(e,{signingDate:t=new Date,signingRegion:n,signingService:r}){return this.signEvent({headers:this.headerFormatter.format(e.message.headers),payload:e.message.body},{signingDate:t,signingRegion:n,signingService:r,priorSignature:e.priorSignature}).then(t=>({message:e.message,signature:t}))}async signString(e,{signingDate:t=new Date,signingRegion:n,signingService:r}={}){let i=await this.credentialProvider();this.validateResolvedCredentials(i);let a=n??await this.regionProvider(),{shortDate:o}=this.formatDate(t),s=new this.sha256(await this.getSigningKey(i,a,o,r));return s.update(rc(e)),$(await s.digest())}async signRequest(e,{signingDate:t=new Date,signableHeaders:n,unsignableHeaders:r,signingRegion:i,signingService:a}={}){let o=await this.credentialProvider();this.validateResolvedCredentials(o);let s=i??await this.regionProvider(),c=Rc(e),{longDate:l,shortDate:u}=this.formatDate(t),d=Tc(u,s,a??this.service);c.headers[fc]=l,o.sessionToken&&(c.headers[hc]=o.sessionToken);let f=await Ac(c,this.sha256);!Ic(`x-amz-content-sha256`,c.headers)&&this.applyChecksum&&(c.headers[mc]=f);let p=Oc(c,r,n),m=await this.getSignature(l,d,this.getSigningKey(o,s,u,a),this.createCanonicalRequest(c,p,f));return c.headers[dc]=`${yc} Credential=${o.accessKeyId}/${d}, SignedHeaders=${this.getCanonicalHeaderList(p)}, Signature=${m}`,c}async getSignature(e,t,n,r){let i=await this.createStringToSign(e,t,r,yc),a=new this.sha256(await n);return a.update(rc(i)),$(await a.digest())}getSigningKey(e,t,n,r){return Ec(this.sha256,e,n,t,r||this.service)}};export{Yo as A,Ze as B,ws as C,No as D,Fo as E,nt as F,et as H,qe as I,tt as L,Ut as M,Gt as N,jo as O,Rt as P,rt as R,Cs as S,Vo as T,n as U,Qe as V,ks as _,rc as a,Ss as b,Qs as c,Rs as d,zs as f,As as g,js as h,kc as i,lo as j,Uo as k,Zs as l,Is as m,Bc as n,nc as o,Bs as p,zc as r,$ as s,Kc as t,Js as u,Os as v,Zo as w,Ts as x,Es as y,Ye as z};
${$(await i.digest())}`}getCanonicalPath({path:e}){if(this.uriEscapePath){let t=[];for(let n of e.split(`/`))n?.length!==0&&n!==`.`&&(n===`..`?t.pop():t.push(n));return Bc(`${e?.startsWith(`/`)?`/`:``}${t.join(`/`)}${t.length>0&&e?.endsWith(`/`)?`/`:``}`).replace(/%2F/g,`/`)}return e}validateResolvedCredentials(e){if(typeof e!=`object`||typeof e.accessKeyId!=`string`||typeof e.secretAccessKey!=`string`)throw Error(`Resolved credential object is not valid`)}formatDate(e){let t=Uc(e).replace(/[\-:]/g,``);return{longDate:t,shortDate:t.slice(0,8)}}getCanonicalHeaderList(e){return Object.keys(e).sort().join(`;`)}},Kc=class extends Gc{headerFormatter=new jc;constructor({applyChecksum:e,credentials:t,region:n,service:r,sha256:i,uriEscapePath:a=!0}){super({applyChecksum:e,credentials:t,region:n,service:r,sha256:i,uriEscapePath:a})}async presign(e,t={}){let{signingDate:n=new Date,expiresIn:r=3600,unsignableHeaders:i,unhoistableHeaders:a,signableHeaders:o,hoistableHeaders:s,signingRegion:c,signingService:l}=t,u=await this.credentialProvider();this.validateResolvedCredentials(u);let d=c??await this.regionProvider(),{longDate:f,shortDate:p}=this.formatDate(n);if(r>604800)return Promise.reject(`Signature version 4 presigned URLs must have an expiration date less than one week in the future`);let m=Tc(p,d,l??this.service),h=Lc(Rc(e),{unhoistableHeaders:a,hoistableHeaders:s});u.sessionToken&&(h.query[uc]=u.sessionToken),h.query[ic]=yc,h.query[ac]=`${u.accessKeyId}/${m}`,h.query[oc]=f,h.query[cc]=r.toString(10);let g=Oc(h,i,o);return h.query[sc]=this.getCanonicalHeaderList(g),h.query[lc]=await this.getSignature(f,m,this.getSigningKey(u,d,p,l),this.createCanonicalRequest(h,g,await Ac(e,this.sha256))),h}async sign(e,t){return typeof e==`string`?this.signString(e,t):e.headers&&e.payload?this.signEvent(e,t):e.message?this.signMessage(e,t):this.signRequest(e,t)}async signEvent({headers:e,payload:t},{signingDate:n=new Date,priorSignature:r,signingRegion:i,signingService:a,eventStreamCredentials:o}){let s=i??await this.regionProvider(),{shortDate:c,longDate:l}=this.formatDate(n),u=Tc(c,s,a??this.service),d=await Ac({headers:{},body:t},this.sha256),f=new this.sha256;f.update(e);let p=[bc,l,u,r,$(await f.digest()),d].join(`
`);return this.signString(p,{signingDate:n,signingRegion:s,signingService:a,eventStreamCredentials:o})}async signMessage(e,{signingDate:t=new Date,signingRegion:n,signingService:r,eventStreamCredentials:i}){return this.signEvent({headers:this.headerFormatter.format(e.message.headers),payload:e.message.body},{signingDate:t,signingRegion:n,signingService:r,priorSignature:e.priorSignature,eventStreamCredentials:i}).then(t=>({message:e.message,signature:t}))}async signString(e,{signingDate:t=new Date,signingRegion:n,signingService:r,eventStreamCredentials:i}={}){let a=i??await this.credentialProvider();this.validateResolvedCredentials(a);let o=n??await this.regionProvider(),{shortDate:s}=this.formatDate(t),c=new this.sha256(await this.getSigningKey(a,o,s,r));return c.update(rc(e)),$(await c.digest())}async signRequest(e,{signingDate:t=new Date,signableHeaders:n,unsignableHeaders:r,signingRegion:i,signingService:a}={}){let o=await this.credentialProvider();this.validateResolvedCredentials(o);let s=i??await this.regionProvider(),c=Rc(e),{longDate:l,shortDate:u}=this.formatDate(t),d=Tc(u,s,a??this.service);c.headers[fc]=l,o.sessionToken&&(c.headers[hc]=o.sessionToken);let f=await Ac(c,this.sha256);!Ic(`x-amz-content-sha256`,c.headers)&&this.applyChecksum&&(c.headers[mc]=f);let p=Oc(c,r,n),m=await this.getSignature(l,d,this.getSigningKey(o,s,u,a),this.createCanonicalRequest(c,p,f));return c.headers[dc]=`${yc} Credential=${o.accessKeyId}/${d}, SignedHeaders=${this.getCanonicalHeaderList(p)}, Signature=${m}`,c}async getSignature(e,t,n,r){let i=await this.createStringToSign(e,t,r,yc),a=new this.sha256(await n);return a.update(rc(i)),$(await a.digest())}getSigningKey(e,t,n,r){return Ec(this.sha256,e,n,t,r||this.service)}};export{Yo as A,Ze as B,ws as C,No as D,Fo as E,nt as F,et as H,qe as I,tt as L,Ut as M,Gt as N,jo as O,Rt as P,rt as R,Cs as S,Vo as T,n as U,Qe as V,ks as _,rc as a,Ss as b,Qs as c,Rs as d,zs as f,As as g,js as h,kc as i,lo as j,Uo as k,Zs as l,Is as m,Bc as n,nc as o,Bs as p,zc as r,$ as s,Kc as t,Js as u,Os as v,Zo as w,Ts as x,Es as y,Ye as z};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_2.js","assets/compiler-runtime.js","assets/s3.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/upload-button.js","assets/button.js","assets/dist.js","assets/link.js","assets/useStore.js","assets/createLucideIcon.js","assets/download.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/s32.js","assets/queryOptions.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/back-link.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_2.js","assets/compiler-runtime.js","assets/s3.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/upload-button.js","assets/button.js","assets/dist.js","assets/link.js","assets/useStore.js","assets/createLucideIcon.js","assets/download.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/s32.js","assets/queryOptions.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/back-link.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{F as r,L as i}from"./SignatureV4.js";import{t as a}from"./s32.js";var o=e(`/_auth/s3/ls/$bucket/$`)({loader:async({context:e,params:t})=>{let n=i(t._splat??``);await e.queryClient.ensureQueryData(a(t.bucket,n))},head:({params:e})=>({meta:[{title:`${r(e._splat??``,`${e.bucket}/`)} | S3 | Mulga`}]}),component:t(()=>n(()=>import(`./_2.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29])),`component`)});export{o as t};
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_bucket2.js","assets/compiler-runtime.js","assets/s3.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/upload-button.js","assets/button.js","assets/dist.js","assets/link.js","assets/useStore.js","assets/createLucideIcon.js","assets/download.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/s32.js","assets/queryOptions.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/back-link.js","assets/page-heading.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_bucket2.js","assets/compiler-runtime.js","assets/s3.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/upload-button.js","assets/button.js","assets/dist.js","assets/link.js","assets/useStore.js","assets/createLucideIcon.js","assets/download.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/s32.js","assets/queryOptions.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/back-link.js","assets/page-heading.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{t as r}from"./s32.js";var i=e(`/_auth/s3/ls/$bucket/`)({loader:async({context:e,params:t})=>{await e.queryClient.ensureQueryData(r(t.bucket))},head:({params:e})=>({meta:[{title:`${e.bucket} | S3 | Mulga`}]}),component:t(()=>n(()=>import(`./_bucket2.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30])),`component`)});export{i as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id22.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/useStore.js","assets/select.js","assets/getDisabledMountTransitionStyles.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useNavigate.js","assets/back-link.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/field.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id22.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/useStore.js","assets/select.js","assets/usePositioner.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useNavigate.js","assets/back-link.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/field.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{t as r}from"./createLucideIcon.js";import{o as i,v as a}from"./ec22.js";var o=r(`camera`,[[`path`,{d:`M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z`,key:`18u6gg`}],[`circle`,{cx:`12`,cy:`13`,r:`3`,key:`1vg3eu`}]]),s=e(`/_auth/ec2/(volumes)/describe-volumes/$id`)({loader:async({context:e,params:t})=>{await Promise.all([e.queryClient.ensureQueryData(a(t.id)),e.queryClient.ensureQueryData(i)])},head:({params:e})=>({meta:[{title:`${e.id} | EC2 | Mulga`}]}),component:t(()=>n(()=>import(`./_id22.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39])),`component`)});export{o as n,s as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id15.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/useStore.js","assets/back-link.js","assets/link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/detail-row.js","assets/page-heading.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id15.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/useStore.js","assets/back-link.js","assets/link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/detail-row.js","assets/page-heading.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{s as r}from"./ec22.js";var i=e(`/_auth/ec2/(key)/describe-key-pairs/$id`)({loader:async({context:e,params:t})=>await e.queryClient.ensureQueryData(r(t.id)),head:({loaderData:e})=>({meta:[{title:`${e?.KeyPairs?.[0]?.KeyName??`Key Pair`} | EC2 | Mulga`}]}),component:t(()=>n(()=>import(`./_id15.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31])),`component`)});export{i as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id14.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/useStore.js","assets/select.js","assets/getDisabledMountTransitionStyles.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/back-link.js","assets/trash-2.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/field.js","assets/textarea.js","assets/ami-details.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id14.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/useStore.js","assets/select.js","assets/usePositioner.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/back-link.js","assets/trash-2.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/field.js","assets/textarea.js","assets/ami-details.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{a as r,i,n as a}from"./ec22.js";var o=e(`/_auth/ec2/(instances)/describe-instances/$id`)({loader:async({context:e,params:t})=>{let[n]=await Promise.all([e.queryClient.ensureQueryData(i(t.id)),e.queryClient.ensureQueryData(r)]),o=n.Reservations?.[0]?.Instances?.[0]?.ImageId;return await e.queryClient.ensureQueryData(a(o)),n},head:({loaderData:e})=>({meta:[{title:`${e?.Reservations?.[0]?.Instances?.[0]?.InstanceId??`Instance`} | EC2 | Mulga`}]}),component:t(()=>n(()=>import(`./_id14.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39])),`component`)});export{o as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id18.js","assets/compiler-runtime.js","assets/ec2.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/useStore.js","assets/select.js","assets/getDisabledMountTransitionStyles.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/zod.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useNavigate.js","assets/back-link.js","assets/plus.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/field.js","assets/ec23.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id18.js","assets/compiler-runtime.js","assets/ec2.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/useStore.js","assets/select.js","assets/usePositioner.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/zod.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useNavigate.js","assets/back-link.js","assets/plus.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/field.js","assets/ec23.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{t as r}from"./createLucideIcon.js";import{f as i}from"./ec22.js";var a=r(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),o=e(`/_auth/ec2/(security-groups)/describe-security-groups/$id`)({loader:async({context:e,params:t})=>{await e.queryClient.ensureQueryData(i(t.id))},head:({params:e})=>({meta:[{title:`${e.id} | Security Group | Mulga`}]}),component:t(()=>n(()=>import(`./_id18.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41])),`component`)});export{a as n,o as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id24.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useStore.js","assets/useNavigate.js","assets/back-link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id24.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useStore.js","assets/useNavigate.js","assets/back-link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{_ as r,b as i}from"./ec22.js";var a=e(`/_auth/ec2/(vpc)/describe-vpcs/$id`)({loader:async({context:e,params:t})=>{await Promise.all([e.queryClient.ensureQueryData(i(t.id)),e.queryClient.ensureQueryData(r)])},head:({params:e})=>({meta:[{title:`${e.id} | VPC | Mulga`}]}),component:t(()=>n(()=>import(`./_id24.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32])),`component`)});export{a as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id21.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec22.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/elbv2.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/elbv22.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/useStore.js","assets/tabs.js","assets/select.js","assets/getDisabledMountTransitionStyles.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/field.js","assets/useQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useSuspenseQuery.js","assets/useNavigate.js","assets/back-link.js","assets/link.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id21.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec22.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/elbv2.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/elbv22.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/useStore.js","assets/tabs.js","assets/select.js","assets/usePositioner.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/field.js","assets/useQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useSuspenseQuery.js","assets/useNavigate.js","assets/back-link.js","assets/link.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{o as r}from"./ec22.js";import{a as i,o as a,s as o}from"./elbv22.js";var s=e(`/_auth/ec2/(target-groups)/describe-target-groups/$id`)({loader:async({context:e,params:t})=>{let n=decodeURIComponent(t.id);await Promise.all([e.queryClient.ensureQueryData(o(n)),e.queryClient.ensureQueryData(a(n)),e.queryClient.ensureQueryData(i([n])),e.queryClient.ensureQueryData(r)])},head:({params:e})=>({meta:[{title:`${decodeURIComponent(e.id)} | Target Group | Mulga`}]}),component:t(()=>n(()=>import(`./_id21.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41])),`component`)});export{s as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id20.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useStore.js","assets/useNavigate.js","assets/back-link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id20.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useStore.js","assets/useNavigate.js","assets/back-link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{g as r}from"./ec22.js";var i=e(`/_auth/ec2/(subnet)/describe-subnets/$id`)({loader:async({context:e,params:t})=>{await e.queryClient.ensureQueryData(r(t.id))},head:({params:e})=>({meta:[{title:`${e.id} | Subnet | Mulga`}]}),component:t(()=>n(()=>import(`./_id20.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32])),`component`)});export{i as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id19.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/useStore.js","assets/input.js","assets/separator.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useNavigate.js","assets/back-link.js","assets/createLucideIcon.js","assets/copy.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/field.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id19.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec2.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/useStore.js","assets/input.js","assets/separator.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useNavigate.js","assets/back-link.js","assets/createLucideIcon.js","assets/copy.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/field.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{d as r,m as i}from"./ec22.js";var a=e(`/_auth/ec2/(snapshots)/describe-snapshots/$id`)({loader:async({context:e,params:t})=>{await Promise.all([e.queryClient.ensureQueryData(i(t.id)),e.queryClient.ensureQueryData(r)])},head:({params:e})=>({meta:[{title:`${e.id} | EC2 | Mulga`}]}),component:t(()=>n(()=>import(`./_id19.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36])),`component`)});export{a as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id17.js","assets/compiler-runtime.js","assets/ec2.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useStore.js","assets/useNavigate.js","assets/back-link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id17.js","assets/compiler-runtime.js","assets/ec2.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/link.js","assets/useStore.js","assets/useNavigate.js","assets/back-link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{l as r,o as i}from"./ec22.js";var a=e(`/_auth/ec2/(placement-groups)/describe-placement-groups/$id`)({loader:async({context:e,params:t})=>{await Promise.all([e.queryClient.ensureQueryData(r(t.id)),e.queryClient.ensureQueryData(i)])},head:({params:e})=>({meta:[{title:`${e.id} | EC2 | Mulga`}]}),component:t(()=>n(()=>import(`./_id17.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32])),`component`)});export{a as t};
+1 -1
View File
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id16.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec22.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/elbv2.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/elbv22.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/useStore.js","assets/tabs.js","assets/select.js","assets/getDisabledMountTransitionStyles.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/field.js","assets/zod.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/back-link.js","assets/link.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/elbv23.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_id16.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/ec22.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/elbv2.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/elbv22.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/useStore.js","assets/tabs.js","assets/select.js","assets/usePositioner.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/input.js","assets/field.js","assets/zod.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/back-link.js","assets/link.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/elbv23.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{_ as r}from"./ec22.js";import{a as i,c as a,n as o,r as s,t as c}from"./elbv22.js";var l=e(`/_auth/ec2/(load-balancers)/describe-load-balancers/$id`)({loader:async({context:e,params:t})=>{let n=decodeURIComponent(t.id);await Promise.all([e.queryClient.ensureQueryData(s(n)),e.queryClient.ensureQueryData(c(n)),e.queryClient.ensureQueryData(o(n)),e.queryClient.ensureQueryData(i([n])),e.queryClient.ensureQueryData(a),e.queryClient.ensureQueryData(r)])},head:({params:e})=>({meta:[{title:`${decodeURIComponent(e.id)} | Load Balancer | Mulga`}]}),component:t(()=>n(()=>import(`./_id16.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43])),`component`)});export{l as t};
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_policyArn2.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/iam.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/iam2.js","assets/queryOptions.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/useStore.js","assets/back-link.js","assets/link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/detail-row.js","assets/page-heading.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_policyArn2.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/iam.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/iam2.js","assets/queryOptions.js","assets/button.js","assets/dist.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/useStore.js","assets/back-link.js","assets/link.js","assets/createLucideIcon.js","assets/trash-2.js","assets/delete-confirmation-dialog.js","assets/alert-dialog.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/detail-row.js","assets/page-heading.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{a as r,i}from"./iam2.js";var a=e(`/_auth/iam/(policies)/list-policies/$policyArn`)({loader:async({context:e,params:t})=>{let n=decodeURIComponent(t.policyArn),a=await e.queryClient.ensureQueryData(i(n)),o=a.Policy?.DefaultVersionId;return o&&await e.queryClient.ensureQueryData(r(n,o)),a},head:({loaderData:e})=>({meta:[{title:`${e?.Policy?.PolicyName??`Policy`} | IAM | Mulga`}]}),component:t(()=>n(()=>import(`./_policyArn2.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30])),`component`)});export{a as t};
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_userName2.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/iam.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/iam2.js","assets/queryOptions.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/useStore.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/back-link.js","assets/link.js","assets/createLucideIcon.js","assets/check.js","assets/trash-2.js","assets/triangle-alert.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/use-copy-to-clipboard.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_userName2.js","assets/compiler-runtime.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/iam.js","assets/awsClient.js","assets/toUtf8.browser.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/iam2.js","assets/queryOptions.js","assets/alert-dialog.js","assets/button.js","assets/dist.js","assets/DialogTitle.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/useStore.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/back-link.js","assets/link.js","assets/createLucideIcon.js","assets/check.js","assets/trash-2.js","assets/triangle-alert.js","assets/delete-confirmation-dialog.js","assets/detail-row.js","assets/page-heading.js","assets/state-badge.js","assets/use-copy-to-clipboard.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{n as r,o as i,r as a,t as o}from"./iam2.js";var s=e(`/_auth/iam/(users)/list-users/$userName`)({loader:async({context:e,params:t})=>{await Promise.all([e.queryClient.ensureQueryData(i(t.userName)),e.queryClient.ensureQueryData(o(t.userName)),e.queryClient.ensureQueryData(r(t.userName)),e.queryClient.ensureQueryData(a)])},head:({params:e})=>({meta:[{title:`${e.userName} | IAM | Mulga`}]}),component:t(()=>n(()=>import(`./_userName2.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34])),`component`)});export{s as t};
@@ -0,0 +1 @@
import{a as e,r as t}from"./compiler-runtime.js";import{hn as n}from"./awsClient.js";import{p as r}from"./dist.js";import{c as i,o as a}from"./button.js";var o=e(n()),s=e(t()),c=[];function l(e){s.useEffect(e,c)}var u=null;globalThis.requestAnimationFrame;var d=new class{callbacks=[];callbacksCount=0;nextId=1;startId=1;isScheduled=!1;tick=e=>{this.isScheduled=!1;let t=this.callbacks,n=this.callbacksCount;if(this.callbacks=[],this.callbacksCount=0,this.startId=this.nextId,n>0)for(let n=0;n<t.length;n+=1)t[n]?.(e)};request(e){let t=this.nextId;return this.nextId+=1,this.callbacks.push(e),this.callbacksCount+=1,this.isScheduled||=(requestAnimationFrame(this.tick),!0),t}cancel(e){let t=e-this.startId;t<0||t>=this.callbacks.length||(this.callbacks[t]=null,--this.callbacksCount)}},f=class e{static create(){return new e}static request(e){return d.request(e)}static cancel(e){return d.cancel(e)}currentId=u;request(e){this.cancel(),this.currentId=d.request(()=>{this.currentId=u,e()})}cancel=()=>{this.currentId!==u&&(d.cancel(this.currentId),this.currentId=u)};disposeEffect=()=>this.cancel};function p(){let e=r(f.create).current;return l(e.disposeEffect),e}function m(e){return e==null?e:`current`in e?e.current:e}var h=function(e){return e.startingStyle=`data-starting-style`,e.endingStyle=`data-ending-style`,e}({}),g={[h.startingStyle]:``},_={[h.endingStyle]:``},v={transitionStatus(e){return e===`starting`?g:e===`ending`?_:null}};function y(e,t=!1,n=!0){let r=p();return i((i,a=null)=>{r.cancel();let s=m(e);if(s==null)return;let c=s,l=()=>{o.flushSync(i)};if(typeof c.getAnimations!=`function`||globalThis.BASE_UI_ANIMATIONS_DISABLED){i();return}function u(){Promise.all(c.getAnimations().map(e=>e.finished)).then(()=>{a?.aborted||l()}).catch(()=>{if(n){a?.aborted||l();return}let e=c.getAnimations();!a?.aborted&&e.length>0&&e.some(e=>e.pending||e.playState!==`finished`)&&u()})}if(t){let e=h.startingStyle;if(!c.hasAttribute(e)){r.request(u);return}let t=new MutationObserver(()=>{c.hasAttribute(e)||(t.disconnect(),u())});t.observe(c,{attributes:!0,attributeFilter:[e]}),a?.addEventListener(`abort`,()=>t.disconnect(),{once:!0});return}r.request(u)})}function b(e){let{enabled:t=!0,open:n,ref:r,onComplete:a}=e,o=i(a),c=y(r,n,!1);s.useEffect(()=>{if(!t)return;let e=new AbortController;return c(o,e.signal),()=>{e.abort()}},[t,n,o,c])}function x(e,t=!1,n=!1){let[r,i]=s.useState(e&&t?`idle`:void 0),[o,c]=s.useState(e);return e&&!o&&(c(!0),i(`starting`)),!e&&o&&r!==`ending`&&!n&&i(`ending`),!e&&!o&&r===`ending`&&i(void 0),a(()=>{if(!e&&o&&r!==`ending`&&n){let e=f.request(()=>{i(`ending`)});return()=>{f.cancel(e)}}},[e,o,r,n]),a(()=>{if(!e||t)return;let n=f.request(()=>{i(void 0)});return()=>{f.cancel(n)}},[t,e]),a(()=>{if(!e||!t)return;e&&o&&r!==`idle`&&i(`starting`);let n=f.request(()=>{i(`idle`)});return()=>{f.cancel(n)}},[t,e,o,r]),{mounted:o,setMounted:c,transitionStatus:r}}function S(e,t,n,r){return e.addEventListener(t,n,r),()=>{e.removeEventListener(t,n,r)}}export{h as a,f as c,y as i,p as l,x as n,v as o,b as r,m as s,S as t,l as u};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/create-snapshot2.js","assets/compiler-runtime.js","assets/ec2.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/select.js","assets/button.js","assets/dist.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/useTransitionStatus.js","assets/useStore.js","assets/getDisabledMountTransitionStyles.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/zod.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/back-link.js","assets/link.js","assets/cli-command-panel.js","assets/copy.js","assets/use-copy-to-clipboard.js","assets/form-actions.js","assets/page-heading.js","assets/field.js","assets/textarea.js","assets/ec23.js"])))=>i.map(i=>d[i]);
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/create-snapshot2.js","assets/compiler-runtime.js","assets/ec2.js","assets/awsClient.js","assets/SignatureV4.js","assets/fromUtf8.browser.js","assets/toUtf8.browser.js","assets/ec22.js","assets/DescribeInstancesCommand.js","assets/queryOptions.js","assets/QueryClientProvider.js","assets/error-banner.js","assets/mutation.js","assets/select.js","assets/button.js","assets/dist.js","assets/useOpenInteractionType.js","assets/createBaseUIEventDetails.js","assets/addEventListener.js","assets/useStore.js","assets/usePositioner.js","assets/separator.js","assets/createLucideIcon.js","assets/check.js","assets/chevron-down.js","assets/zod.js","assets/useSuspenseQuery.js","assets/useBaseQuery.js","assets/query.js","assets/useNavigate.js","assets/back-link.js","assets/link.js","assets/cli-command-panel.js","assets/copy.js","assets/use-copy-to-clipboard.js","assets/form-actions.js","assets/page-heading.js","assets/field.js","assets/textarea.js","assets/ec23.js"])))=>i.map(i=>d[i]);
import{n as e,t}from"./lazyRouteComponent.js";import{cn as n}from"./awsClient.js";import{y as r}from"./ec22.js";var i=e(`/_auth/ec2/(snapshots)/create-snapshot`)({validateSearch:e=>({volumeId:typeof e.volumeId==`string`?e.volumeId:void 0}),loader:async({context:e})=>{await e.queryClient.ensureQueryData(r)},head:()=>({meta:[{title:`Create Snapshot | EC2 | Mulga`}]}),component:t(()=>n(()=>import(`./create-snapshot2.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39])),`component`)});export{i as t};
@@ -1 +1 @@
import{a as e,r as t}from"./compiler-runtime.js";import{l as n}from"./dist.js";import{b as r,s as i,w as a,y as o}from"./button.js";var s=e(t()),c=0;function l(e,t=`mui`){let[n,r]=s.useState(e),i=e||n;return s.useEffect(()=>{n??(c+=1,r(`${t}-${c}`))},[n,t]),i}var u=i.useId;function d(e,t){if(u!==void 0){let n=u();return e??(t?`${t}-${n}`:n)}return l(e,t)}function f(e){return d(e,`base-ui`)}function p(e){return e?.ownerDocument||document}var m=typeof navigator<`u`,h=w(),g=E(),_=T(),v=typeof CSS>`u`||!CSS.supports?!1:CSS.supports(`-webkit-backdrop-filter:none`),y=h.platform===`MacIntel`&&h.maxTouchPoints>1?!0:/iP(hone|ad|od)|iOS/.test(h.platform);m&&/firefox/i.test(_);var b=m&&/apple/i.test(navigator.vendor);m&&/Edg/i.test(_);var x=m&&/android/i.test(g)||/android/i.test(_),S=m&&g.toLowerCase().startsWith(`mac`)&&!navigator.maxTouchPoints,C=_.includes(`jsdom/`);function w(){if(!m)return{platform:``,maxTouchPoints:-1};let e=navigator.userAgentData;return e?.platform?{platform:e.platform,maxTouchPoints:navigator.maxTouchPoints}:{platform:navigator.platform??``,maxTouchPoints:navigator.maxTouchPoints??-1}}function T(){if(!m)return``;let e=navigator.userAgentData;return e&&Array.isArray(e.brands)?e.brands.map(({brand:e,version:t})=>`${e}/${t}`).join(` `):navigator.userAgent}function E(){if(!m)return``;let e=navigator.userAgentData;return e?.platform?e.platform:navigator.platform??``}var D=`data-base-ui-focusable`,O=`active`,k=`selected`,A=`input:not([type='hidden']):not([disabled]),[contenteditable]:not([contenteditable='false']),textarea:not([disabled])`,j=`ArrowLeft`,M=`ArrowRight`,N=`ArrowUp`,P=`ArrowDown`;function F(e){let t=e.activeElement;for(;t?.shadowRoot?.activeElement!=null;)t=t.shadowRoot.activeElement;return t}function I(e,t){if(!e||!t)return!1;let n=t.getRootNode?.();if(e.contains(t))return!0;if(n&&a(n)){let n=t;for(;n;){if(e===n)return!0;n=n.parentNode||n.host}}return!1}function L(e,t){if(!o(e))return!1;let n=e;if(t.hasElement(n))return!n.hasAttribute(`data-trigger-disabled`);for(let[,e]of t.entries())if(I(e,n))return!e.hasAttribute(`data-trigger-disabled`);return!1}function R(e){return`composedPath`in e?e.composedPath()[0]:e.target}function ee(e,t){if(t==null)return!1;if(`composedPath`in e)return e.composedPath().includes(t);let n=e;return n.target!=null&&t.contains(n.target)}function z(e){return e.matches(`html,body`)}function B(e){return r(e)&&e.matches(`input:not([type='hidden']):not([disabled]),[contenteditable]:not([contenteditable='false']),textarea:not([disabled])`)}function V(e){return e?e.getAttribute(`role`)===`combobox`&&B(e):!1}function H(e){if(!e||C)return!0;try{return e.matches(`:focus-visible`)}catch{return!0}}function U(e){return e?e.hasAttribute(`data-base-ui-focusable`)?e:e.querySelector(`[data-base-ui-focusable]`)||e:null}var W=`none`,G=`trigger-press`,K=`trigger-hover`,q=`trigger-focus`,J=`outside-press`,Y=`item-press`,X=`close-press`,Z=`focus-out`,Q=`escape-key`,$=`list-navigation`,te=`cancel-open`,ne=`disabled`,re=`imperative-action`,ie=`window-resize`;function ae(e,t,r,i){let a=!1,o=!1,s=i??n;return{reason:e,event:t??new Event(`base-ui`),cancel(){a=!0},allowPropagation(){o=!0},get isCanceled(){return a},get isPropagationAllowed(){return o},trigger:r,...s}}export{N as A,p as B,V as C,P as D,O as E,y as F,d as H,C as I,S as L,k as M,A as N,j as O,x as P,b as R,L as S,H as T,f as V,I as _,Q as a,ee as b,Y as c,J as d,q as f,F as g,ie as h,ne as i,D as j,M as k,$ as l,G as m,te as n,Z as o,K as p,X as r,re as s,ae as t,W as u,U as v,B as w,z as x,R as y,v as z};
import{a as e,r as t}from"./compiler-runtime.js";import{l as n}from"./dist.js";import{s as r,w as i}from"./button.js";var a=e(t()),o=0;function s(e,t=`mui`){let[n,r]=a.useState(e),i=e||n;return a.useEffect(()=>{n??(o+=1,r(`${t}-${o}`))},[n,t]),i}var c=r.useId;function l(e,t){if(c!==void 0){let n=c();return e??(t?`${t}-${n}`:n)}return s(e,t)}function u(e){return l(e,`base-ui`)}function d(e){return e?.ownerDocument||document}function f(e){let t=e.activeElement;for(;t?.shadowRoot?.activeElement!=null;)t=t.shadowRoot.activeElement;return t}function p(e,t){if(!e||!t)return!1;let n=t.getRootNode?.();if(e.contains(t))return!0;if(n&&i(n)){let n=t;for(;n;){if(e===n)return!0;n=n.parentNode||n.host}}return!1}function m(e){return`composedPath`in e?e.composedPath()[0]:e.target}var h=`none`,g=`trigger-press`,_=`trigger-hover`,v=`trigger-focus`,y=`outside-press`,b=`item-press`,x=`close-press`,S=`focus-out`,C=`escape-key`,w=`list-navigation`,T=`cancel-open`,E=`disabled`,D=`imperative-action`,O=`window-resize`;function k(e,t,r,i){let a=!1,o=!1,s=i??n;return{reason:e,event:t??new Event(`base-ui`),cancel(){a=!0},allowPropagation(){o=!0},get isCanceled(){return a},get isPropagationAllowed(){return o},trigger:r,...s}}export{p as _,C as a,u as b,b as c,y as d,v as f,f as g,O as h,E as i,w as l,g as m,T as n,S as o,_ as p,x as r,D as s,k as t,h as u,m as v,l as x,d as y};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{n as i}from"./dist.js";import{I as a}from"./SignatureV4.js";import{o}from"./button.js";import{a as s,c,i as l,n as u,r as d,s as f}from"./separator.js";import{B as p,g as m,t as h,u as g}from"./createBaseUIEventDetails.js";var _=e(n()),v=_.forwardRef(function(e,t){let{render:n,className:r,id:a,name:v,value:y,disabled:b=!1,onValueChange:x,defaultValue:S,autoFocus:C=!1,...w}=e,{state:T,name:E,disabled:D,setTouched:O,setDirty:k,validityData:A,setFocused:j,setFilled:M,validationMode:N,validation:P}=f(),F=D||b,I=E??v,L={...T,disabled:F},{labelId:R}=s(),z=d({id:a});o(()=>{let e=y!=null;P.inputRef.current?.value||e&&y!==``?M(!0):e&&y===``&&M(!1)},[P.inputRef,M,y]);let B=_.useRef(null);o(()=>{C&&B.current===m(p(B.current))&&j(!0)},[C,j]);let[V]=l({controlled:y,default:S,name:`FieldControl`,state:`value`}),H=y!==void 0,U=H?V:void 0;return u({id:z,name:I,commit:P.commit,value:U,getValue:()=>P.inputRef.current?.value,controlRef:P.inputRef}),i(`input`,e,{ref:[t,B],state:L,props:[{id:z,disabled:F,name:I,ref:P.inputRef,"aria-labelledby":R,autoFocus:C,...H?{value:U}:{defaultValue:S},onChange(e){let t=e.currentTarget.value;x?.(t,h(g,e.nativeEvent)),k(t!==A.initialValue),M(t!==``)},onFocus(){j(!0)},onBlur(e){O(!0),j(!1),N===`onBlur`&&P.commit(e.currentTarget.value)},onKeyDown(e){e.currentTarget.tagName===`INPUT`&&e.key===`Enter`&&(O(!0),P.commit(e.currentTarget.value))}},P.getInputValidationProps(),w],stateAttributesMapping:c})}),y=t(),b=_.forwardRef(function(e,t){return(0,y.jsx)(v,{ref:t,...e})}),x=r();function S(e){let t=(0,x.c)(10),n,r,i;t[0]===e?(n=t[1],r=t[2],i=t[3]):({className:n,type:i,...r}=e,t[0]=e,t[1]=n,t[2]=r,t[3]=i);let o;t[4]===n?o=t[5]:(o=a(`h-7 w-full min-w-0 rounded-md border border-input bg-input/20 px-2 py-0.5 text-sm transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs/relaxed file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40`,n),t[4]=n,t[5]=o);let s;return t[6]!==r||t[7]!==o||t[8]!==i?(s=(0,y.jsx)(b,{className:o,"data-slot":`input`,type:i,...r}),t[6]=r,t[7]=o,t[8]=i,t[9]=s):s=t[9],s}export{S as t};
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{n as i}from"./dist.js";import{I as a}from"./SignatureV4.js";import{c as o,o as s}from"./button.js";import{a as c,i as l,n as u,o as d,r as f,s as p}from"./separator.js";import{g as m,t as h,u as g,y as _}from"./createBaseUIEventDetails.js";var v=e(n()),y=v.forwardRef(function(e,t){let{render:n,className:r,id:a,name:y,value:b,disabled:x=!1,onValueChange:S,defaultValue:C,autoFocus:w=!1,style:T,...E}=e,{state:D,name:O,disabled:k,setTouched:A,setDirty:j,validityData:M,setFocused:N,setFilled:P,validationMode:F,validation:I}=d(),L=k||x,R=O??y,z={...D,disabled:L},{labelId:B}=c(),V=u({id:a});s(()=>{let e=b!=null;I.inputRef.current?.value||e&&b!==``?P(!0):e&&b===``&&P(!1)},[I.inputRef,P,b]);let H=v.useRef(null);s(()=>{w&&H.current===m(_(H.current))&&N(!0)},[w,N]);let[U]=l({controlled:b,default:C,name:`FieldControl`,state:`value`}),W=b!==void 0,G=W?U:void 0,K=o(()=>I.inputRef.current?.value);return f(I.inputRef,{id:V,value:G,getValue:K}),i(`input`,e,{ref:[t,H],state:z,props:[{id:V,disabled:L,name:R,ref:I.inputRef,"aria-labelledby":B,autoFocus:w,...W?{value:G}:{defaultValue:C},onChange(e){let t=e.currentTarget.value;S?.(t,h(g,e.nativeEvent)),j(t!==M.initialValue),P(t!==``)},onFocus(){N(!0)},onBlur(e){A(!0),N(!1),F===`onBlur`&&I.commit(e.currentTarget.value)},onKeyDown(e){e.currentTarget.tagName===`INPUT`&&e.key===`Enter`&&(A(!0),I.commit(e.currentTarget.value))}},I.getInputValidationProps(),E],stateAttributesMapping:p})}),b=t(),x=v.forwardRef(function(e,t){return(0,b.jsx)(y,{ref:t,...e})}),S=r();function C(e){let t=(0,S.c)(10),n,r,i;t[0]===e?(n=t[1],r=t[2],i=t[3]):({className:n,type:i,...r}=e,t[0]=e,t[1]=n,t[2]=r,t[3]=i);let o;t[4]===n?o=t[5]:(o=a(`h-7 w-full min-w-0 rounded-md border border-input bg-input/20 px-2 py-0.5 text-sm transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs/relaxed file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40`,n),t[4]=n,t[5]=o);let s;return t[6]!==r||t[7]!==o||t[8]!==i?(s=(0,b.jsx)(x,{className:o,"data-slot":`input`,type:i,...r}),t[6]=r,t[7]=o,t[8]=i,t[9]=s):s=t[9],s}export{C as t};
@@ -1 +1 @@
import{a as e,n as t,r as n}from"./compiler-runtime.js";import{B as r,P as i,_ as a,f as o,g as s,i as c,t as l,w as u,z as d}from"./useStore.js";import{n as f,r as p,t as m}from"./matchContext.js";import{n as h}from"./redirect.js";import{t as g}from"./link.js";import{t as _}from"./useNavigate.js";var v=class{get to(){return this._to}get id(){return this._id}get path(){return this._path}get fullPath(){return this._fullPath}constructor(e){if(this.init=e=>{this.originalIndex=e.originalIndex;let t=this.options,n=!t?.path&&!t?.id;this.parentRoute=this.options.getParentRoute?.(),n?this._path=p:this.parentRoute||u();let r=n?p:t?.path;r&&r!==`/`&&(r=s(r));let i=t?.id||r,c=n?p:o([this.parentRoute.id===`__root__`?``:this.parentRoute.id,i]);r===`__root__`&&(r=`/`),c!==`__root__`&&(c=o([`/`,c]));let l=c===`__root__`?`/`:o([this.parentRoute.fullPath,r]);this._path=r,this._id=c,this._fullPath=l,this._to=a(l)},this.addChildren=e=>this._addFileChildren(e),this._addFileChildren=e=>(Array.isArray(e)&&(this.children=e),typeof e==`object`&&e&&(this.children=Object.values(e)),this),this._addFileTypes=()=>this,this.updateLoader=e=>(Object.assign(this.options,e),this),this.update=e=>(Object.assign(this.options,e),this),this.lazy=e=>(this.lazyFn=e,this),this.redirect=e=>h({from:this.fullPath,...e}),this.options=e||{},this.isRoot=!e?.getParentRoute,e?.id&&e?.path)throw Error(`Route cannot have both an 'id' and a 'path' option.`)}},y=class extends v{constructor(e){super(e)}},b=e(n(),1),x={get:()=>void 0,subscribe:()=>({unsubscribe:()=>{}})};function S(e){let t=c(),n=b.useContext(e.from?m:f),r=e.from??n,i=r?e.from?t.stores.getRouteMatchStore(r):t.stores.matchStores.get(r):void 0,a=b.useRef(void 0);return l(i??x,n=>{if((e.shouldThrow??!0)&&!n&&u(),n===void 0)return;let r=e.select?e.select(n):n;if(e.structuralSharing??t.options.defaultStructuralSharing){let e=d(a.current,r);return a.current=e,e}return r})}function C(e){return S({from:e.from,strict:e.strict,structuralSharing:e.structuralSharing,select:t=>e.select?e.select(t.loaderData):t.loaderData})}function w(e){let{select:t,...n}=e;return S({...n,select:e=>t?t(e.loaderDeps):e.loaderDeps})}function T(e){return S({from:e.from,shouldThrow:e.shouldThrow,structuralSharing:e.structuralSharing,strict:e.strict,select:t=>{let n=e.strict===!1?t.params:t._strictParams;return e.select?e.select(n):n}})}function E(e){return S({from:e.from,strict:e.strict,shouldThrow:e.shouldThrow,structuralSharing:e.structuralSharing,select:t=>e.select?e.select(t.search):t.search})}function D(e){return S({...e,select:t=>e.select?e.select(t.context):t.context})}var O=t(),k=class extends v{constructor(e){super(e),this.useMatch=e=>S({select:e?.select,from:this.id,structuralSharing:e?.structuralSharing}),this.useRouteContext=e=>D({...e,from:this.id}),this.useSearch=e=>E({select:e?.select,structuralSharing:e?.structuralSharing,from:this.id}),this.useParams=e=>T({select:e?.select,structuralSharing:e?.structuralSharing,from:this.id}),this.useLoaderDeps=e=>w({...e,from:this.id}),this.useLoaderData=e=>C({...e,from:this.id}),this.useNavigate=()=>_({from:this.fullPath}),this.Link=b.forwardRef((e,t)=>(0,O.jsx)(g,{ref:t,from:this.fullPath,...e}))}};function A(e){return new k(e)}function j(){return e=>N(e)}var M=class extends y{constructor(e){super(e),this.useMatch=e=>S({select:e?.select,from:this.id,structuralSharing:e?.structuralSharing}),this.useRouteContext=e=>D({...e,from:this.id}),this.useSearch=e=>E({select:e?.select,structuralSharing:e?.structuralSharing,from:this.id}),this.useParams=e=>T({select:e?.select,structuralSharing:e?.structuralSharing,from:this.id}),this.useLoaderDeps=e=>w({...e,from:this.id}),this.useLoaderData=e=>C({...e,from:this.id}),this.useNavigate=()=>_({from:this.fullPath}),this.Link=b.forwardRef((e,t)=>(0,O.jsx)(g,{ref:t,from:this.fullPath,...e}))}};function N(e){return new M(e)}function P(e){return typeof e==`object`?new F(e,{silent:!0}).createRoute(e):new F(e,{silent:!0}).createRoute}var F=class{constructor(e,t){this.path=e,this.createRoute=e=>{let t=A(e);return t.isRoot=!1,t},this.silent=t?.silent}};function I(e,t){let n,a,o,s,c=()=>(n||=e().then(e=>{n=void 0,a=e[t??`default`]}).catch(e=>{if(o=e,i(o)&&o instanceof Error&&typeof window<`u`&&typeof sessionStorage<`u`){let e=`tanstack_router_reload:${o.message}`;sessionStorage.getItem(e)||(sessionStorage.setItem(e,`1`),s=!0)}}),n),l=function(e){if(s)throw window.location.reload(),new Promise(()=>{});if(o)throw o;if(!a)if(r)r(c());else throw c();return b.createElement(a,e)};return l.preload=c,l}export{P as n,j as r,I as t};
import{a as e,n as t,r as n}from"./compiler-runtime.js";import{B as r,P as i,_ as a,f as o,g as s,i as c,t as l,w as u,z as d}from"./useStore.js";import{n as f,r as p,t as m}from"./matchContext.js";import{n as h}from"./redirect.js";import{t as g}from"./link.js";import{t as _}from"./useNavigate.js";var v=class{get to(){return this._to}get id(){return this._id}get path(){return this._path}get fullPath(){return this._fullPath}constructor(e){if(this.init=e=>{this.originalIndex=e.originalIndex;let t=this.options,n=!t?.path&&!t?.id;this.parentRoute=this.options.getParentRoute?.(),n?this._path=p:this.parentRoute||u();let r=n?p:t?.path;r&&r!==`/`&&(r=s(r));let i=t?.id||r,c=n?p:o([this.parentRoute.id===`__root__`?``:this.parentRoute.id,i]);r===`__root__`&&(r=`/`),c!==`__root__`&&(c=o([`/`,c]));let l=c===`__root__`?`/`:o([this.parentRoute.fullPath,r]);this._path=r,this._id=c,this._fullPath=l,this._to=a(l)},this.addChildren=e=>this._addFileChildren(e),this._addFileChildren=e=>(Array.isArray(e)&&(this.children=e),typeof e==`object`&&e&&(this.children=Object.values(e)),this),this._addFileTypes=()=>this,this.updateLoader=e=>(Object.assign(this.options,e),this),this.update=e=>(Object.assign(this.options,e),this),this.lazy=e=>(this.lazyFn=e,this),this.redirect=e=>h({from:this.fullPath,...e}),this.options=e||{},this.isRoot=!e?.getParentRoute,e?.id&&e?.path)throw Error(`Route cannot have both an 'id' and a 'path' option.`)}},y=class extends v{constructor(e){super(e)}},b=e(n(),1),x={get:()=>void 0,subscribe:()=>({unsubscribe:()=>{}})};function S(e){let t=c(),n=b.useContext(e.from?m:f),r=e.from??n,i=r?e.from?t.stores.getRouteMatchStore(r):t.stores.matchStores.get(r):void 0,a=b.useRef(void 0);return l(i??x,n=>{if((e.shouldThrow??!0)&&!n&&u(),n===void 0)return;let r=e.select?e.select(n):n;if(e.structuralSharing??t.options.defaultStructuralSharing){let e=d(a.current,r);return a.current=e,e}return r})}function C(e){return S({from:e.from,strict:e.strict,structuralSharing:e.structuralSharing,select:t=>e.select?e.select(t.loaderData):t.loaderData})}function w(e){let{select:t,...n}=e;return S({...n,select:e=>t?t(e.loaderDeps):e.loaderDeps})}function T(e){return S({from:e.from,shouldThrow:e.shouldThrow,structuralSharing:e.structuralSharing,strict:e.strict,select:t=>{let n=e.strict===!1?t.params:t._strictParams;return e.select?e.select(n):n}})}function E(e){return S({from:e.from,strict:e.strict,shouldThrow:e.shouldThrow,structuralSharing:e.structuralSharing,select:t=>e.select?e.select(t.search):t.search})}function D(e){return S({...e,select:t=>e.select?e.select(t.context):t.context})}var O=t(),k=class extends v{constructor(e){super(e),this.useMatch=e=>S({select:e?.select,from:this.id,structuralSharing:e?.structuralSharing}),this.useRouteContext=e=>D({...e,from:this.id}),this.useSearch=e=>E({select:e?.select,structuralSharing:e?.structuralSharing,from:this.id}),this.useParams=e=>T({select:e?.select,structuralSharing:e?.structuralSharing,from:this.id}),this.useLoaderDeps=e=>w({...e,from:this.id}),this.useLoaderData=e=>C({...e,from:this.id}),this.useNavigate=()=>_({from:this.fullPath}),this.Link=b.forwardRef((e,t)=>(0,O.jsx)(g,{ref:t,from:this.fullPath,...e}))}};function A(e){return new k(e)}function j(){return e=>N(e)}var M=class extends y{constructor(e){super(e),this.useMatch=e=>S({select:e?.select,from:this.id,structuralSharing:e?.structuralSharing}),this.useRouteContext=e=>D({...e,from:this.id}),this.useSearch=e=>E({select:e?.select,structuralSharing:e?.structuralSharing,from:this.id}),this.useParams=e=>T({select:e?.select,structuralSharing:e?.structuralSharing,from:this.id}),this.useLoaderDeps=e=>w({...e,from:this.id}),this.useLoaderData=e=>C({...e,from:this.id}),this.useNavigate=()=>_({from:this.fullPath}),this.Link=b.forwardRef((e,t)=>(0,O.jsx)(g,{ref:t,from:this.fullPath,...e}))}};function N(e){return new M(e)}function P(e){return new F(e,{silent:!0}).createRoute}var F=class{constructor(e,t){this.path=e,this.createRoute=e=>{let t=A(e);return t.isRoot=!1,t},this.silent=t?.silent}};function I(e,t){let n,a,o,s,c=()=>(n||=e().then(e=>{n=void 0,a=e[t??`default`]}).catch(e=>{if(o=e,i(o)&&o instanceof Error&&typeof window<`u`&&typeof sessionStorage<`u`){let e=`tanstack_router_reload:${o.message}`;sessionStorage.getItem(e)||(sessionStorage.setItem(e,`1`),s=!0)}}),n),l=function(e){if(s)throw window.location.reload(),new Promise(()=>{});if(o)throw o;if(!a)if(r)r(c());else throw c();return b.createElement(a,e)};return l.preload=c,l}export{P as n,j as r,I as t};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{hn as i}from"./awsClient.js";import{l as a,m as o,n as s,p as c,u as l}from"./dist.js";import{I as u}from"./SignatureV4.js";import{c as d,o as f,y as p}from"./button.js";import{V as m}from"./createBaseUIEventDetails.js";var h=e(n()),g=e(i()),_=function(e){return e.disabled=`data-disabled`,e.valid=`data-valid`,e.invalid=`data-invalid`,e.touched=`data-touched`,e.dirty=`data-dirty`,e.filled=`data-filled`,e.focused=`data-focused`,e}({}),v={badInput:!1,customError:!1,patternMismatch:!1,rangeOverflow:!1,rangeUnderflow:!1,stepMismatch:!1,tooLong:!1,tooShort:!1,typeMismatch:!1,valid:null,valueMissing:!1},y={valid:null,touched:!1,dirty:!1,filled:!1,focused:!1},b={disabled:!1,...y},x={valid(e){return e===null?null:e?{[_.valid]:``}:{[_.invalid]:``}}},S=h.createContext({invalid:void 0,name:void 0,validityData:{state:v,errors:[],error:``,value:``,initialValue:null},setValidityData:l,disabled:void 0,touched:y.touched,setTouched:l,dirty:y.dirty,setDirty:l,filled:y.filled,setFilled:l,focused:y.focused,setFocused:l,validate:()=>null,validationMode:`onSubmit`,validationDebounceTime:0,shouldValidateOnChange:()=>!1,state:b,markedDirtyRef:{current:!1},validation:{getValidationProps:(e=a)=>e,getInputValidationProps:(e=a)=>e,inputRef:{current:null},commit:async()=>{}}});function C(e=!0){let t=h.useContext(S);if(t.setValidityData===l&&!e)throw Error(o(28));return t}var w=h.createContext({formRef:{current:{fields:new Map}},errors:{},clearErrors:l,validationMode:`onSubmit`,submitAttemptedRef:{current:!1}});function T(){return h.useContext(w)}var E=h.createContext({controlId:void 0,registerControlId:l,labelId:void 0,setLabelId:l,messageIds:[],setMessageIds:l,getDescriptionProps:e=>e});function D(){return h.useContext(E)}function O(e,t){return{...e,state:{...e.state,valid:!t&&e.state.valid}}}function k({controlled:e,default:t,name:n,state:r=`value`}){let{current:i}=h.useRef(e!==void 0),[a,o]=h.useState(t);return[i?e:a,h.useCallback(e=>{i||o(e)},[])]}function A(e={}){let{id:t,implicit:n=!1,controlRef:r}=e,{controlId:i,registerControlId:a}=D(),o=m(t),s=n?i:void 0,u=c(()=>Symbol(`labelable-control`)),g=h.useRef(!1),_=h.useRef(t!=null),v=d(()=>{!g.current||a===l||(g.current=!1,a(u.current,void 0))});return f(()=>{if(a===l)return;let e;if(n){let n=r?.current;e=p(n)&&n.closest(`label`)!=null?t??null:s??o}else if(t!=null)_.current=!0,e=t;else if(_.current)e=o;else{v();return}if(e===void 0){v();return}g.current=!0,a(u.current,e)},[t,r,s,a,n,o,u,v]),h.useEffect(()=>v,[v]),i??o}function j(e){let{enabled:t=!0,value:n,id:r,name:i,controlRef:a,commit:o}=e,{formRef:s}=T(),{invalid:c,markedDirtyRef:l,validityData:u,setValidityData:p}=C(),m=d(e.getValue);f(()=>{if(!t)return;let e=n;e===void 0&&(e=m()),u.initialValue===null&&e!==null&&p(t=>({...t,initialValue:e}))},[t,p,n,u.initialValue,m]),f(()=>{!t||!r||s.current.fields.set(r,{getValue:m,name:i,controlRef:a,validityData:O(u,c),validate(e=!0){let t=n;t===void 0&&(t=m()),l.current=!0,e?g.flushSync(()=>o(t)):o(t)}})},[o,a,t,s,m,r,c,l,i,u,n]),f(()=>{let e=s.current.fields;return()=>{r&&e.delete(r)}},[s,r])}var M=h.forwardRef(function(e,t){let{className:n,render:r,orientation:i=`horizontal`,...a}=e;return s(`div`,e,{state:{orientation:i},ref:t,props:[{role:`separator`,"aria-orientation":i},a]})}),N=r(),P=t();function F(e){let t=(0,N.c)(10),n,r,i;t[0]===e?(n=t[1],r=t[2],i=t[3]):({className:n,orientation:i,...r}=e,t[0]=e,t[1]=n,t[2]=r,t[3]=i);let a=i===void 0?`horizontal`:i,o;t[4]===n?o=t[5]:(o=u(`shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch`,n),t[4]=n,t[5]=o);let s;return t[6]!==a||t[7]!==r||t[8]!==o?(s=(0,P.jsx)(M,{className:o,"data-slot":`separator`,orientation:a,...r}),t[6]=a,t[7]=r,t[8]=o,t[9]=s):s=t[9],s}export{D as a,x as c,k as i,j as n,T as o,A as r,C as s,F as t};
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{l as i,m as a,n as o,p as s,u as c}from"./dist.js";import{I as l}from"./SignatureV4.js";import{c as u,o as d,y as f}from"./button.js";import{b as p}from"./createBaseUIEventDetails.js";var m=e(n()),h=function(e){return e.disabled=`data-disabled`,e.valid=`data-valid`,e.invalid=`data-invalid`,e.touched=`data-touched`,e.dirty=`data-dirty`,e.filled=`data-filled`,e.focused=`data-focused`,e}({}),g={badInput:!1,customError:!1,patternMismatch:!1,rangeOverflow:!1,rangeUnderflow:!1,stepMismatch:!1,tooLong:!1,tooShort:!1,typeMismatch:!1,valid:null,valueMissing:!1},_={valid:null,touched:!1,dirty:!1,filled:!1,focused:!1},v={disabled:!1,..._},y={valid(e){return e===null?null:e?{[h.valid]:``}:{[h.invalid]:``}}},b=m.createContext({invalid:void 0,name:void 0,validityData:{state:g,errors:[],error:``,value:``,initialValue:null},setValidityData:c,disabled:void 0,touched:_.touched,setTouched:c,dirty:_.dirty,setDirty:c,filled:_.filled,setFilled:c,focused:_.focused,setFocused:c,validate:()=>null,validationMode:`onSubmit`,validationDebounceTime:0,shouldValidateOnChange:()=>!1,state:v,markedDirtyRef:{current:!1},registerFieldControl:c,validation:{getValidationProps:(e=i)=>e,getInputValidationProps:(e=i)=>e,inputRef:{current:null},commit:async()=>{}}});function x(e=!0){let t=m.useContext(b);if(t.setValidityData===c&&!e)throw Error(a(28));return t}var S=m.createContext({controlId:void 0,registerControlId:c,labelId:void 0,setLabelId:c,messageIds:[],setMessageIds:c,getDescriptionProps:e=>e});function C(){return m.useContext(S)}function w({controlled:e,default:t,name:n,state:r=`value`}){let{current:i}=m.useRef(e!==void 0),[a,o]=m.useState(t);return[i?e:a,m.useCallback(e=>{i||o(e)},[])]}function T(e,t){let{enabled:n=!0,getValue:r,id:i,value:a}=t,{registerFieldControl:o}=x(),s=m.useRef(null);s.current||=Symbol(),d(()=>{let t=s.current;if(!(!t||!n))return o(t,{controlRef:e,getValue:r,id:i,value:a}),()=>{o(t,void 0)}},[e,n,r,i,o,a])}function E(e={}){let{id:t,implicit:n=!1,controlRef:r}=e,{controlId:i,registerControlId:a}=C(),o=p(t),l=n?i:void 0,h=s(()=>Symbol(`labelable-control`)),g=m.useRef(!1),_=m.useRef(t!=null),v=u(()=>{!g.current||a===c||(g.current=!1,a(h.current,void 0))});return d(()=>{if(a===c)return;let e;if(n){let n=r?.current;e=f(n)&&n.closest(`label`)!=null?t??null:l??o}else if(t!=null)_.current=!0,e=t;else if(_.current)e=o;else{v();return}if(e===void 0){v();return}g.current=!0,a(h.current,e)},[t,r,l,a,n,o,h,v]),m.useEffect(()=>v,[v]),i??o}var D=m.forwardRef(function(e,t){let{className:n,render:r,orientation:i=`horizontal`,style:a,...s}=e;return o(`div`,e,{state:{orientation:i},ref:t,props:[{role:`separator`,"aria-orientation":i},s]})}),O=r(),k=t();function A(e){let t=(0,O.c)(10),n,r,i;t[0]===e?(n=t[1],r=t[2],i=t[3]):({className:n,orientation:i,...r}=e,t[0]=e,t[1]=n,t[2]=r,t[3]=i);let a=i===void 0?`horizontal`:i,o;t[4]===n?o=t[5]:(o=l(`shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch`,n),t[4]=n,t[5]=o);let s;return t[6]!==a||t[7]!==r||t[8]!==o?(s=(0,k.jsx)(D,{className:o,"data-slot":`separator`,orientation:a,...r}),t[6]=a,t[7]=r,t[8]=o,t[9]=s):s=t[9],s}export{C as a,w as i,E as n,x as o,T as r,y as s,A as t};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import{a as e,r as t}from"./compiler-runtime.js";import{hn as n}from"./awsClient.js";import{p as r}from"./dist.js";import{c as i,o as a}from"./button.js";var o=e(n()),s=e(t()),c=[];function l(e){s.useEffect(e,c)}var u=null;globalThis.requestAnimationFrame;var d=new class{callbacks=[];callbacksCount=0;nextId=1;startId=1;isScheduled=!1;tick=e=>{this.isScheduled=!1;let t=this.callbacks,n=this.callbacksCount;if(this.callbacks=[],this.callbacksCount=0,this.startId=this.nextId,n>0)for(let n=0;n<t.length;n+=1)t[n]?.(e)};request(e){let t=this.nextId;return this.nextId+=1,this.callbacks.push(e),this.callbacksCount+=1,this.isScheduled||=(requestAnimationFrame(this.tick),!0),t}cancel(e){let t=e-this.startId;t<0||t>=this.callbacks.length||(this.callbacks[t]=null,--this.callbacksCount)}},f=class e{static create(){return new e}static request(e){return d.request(e)}static cancel(e){return d.cancel(e)}currentId=u;request(e){this.cancel(),this.currentId=d.request(()=>{this.currentId=u,e()})}cancel=()=>{this.currentId!==u&&(d.cancel(this.currentId),this.currentId=u)};disposeEffect=()=>this.cancel};function p(){let e=r(f.create).current;return l(e.disposeEffect),e}function m(e){return e==null?e:`current`in e?e.current:e}var h=function(e){return e.startingStyle=`data-starting-style`,e.endingStyle=`data-ending-style`,e}({}),g={[h.startingStyle]:``},_={[h.endingStyle]:``},v={transitionStatus(e){return e===`starting`?g:e===`ending`?_:null}};function y(e,t=!1,n=!0){let r=p();return i((i,a=null)=>{r.cancel();function s(){o.flushSync(i)}let c=m(e);if(c==null)return;let l=c;if(typeof l.getAnimations!=`function`||globalThis.BASE_UI_ANIMATIONS_DISABLED)i();else{function e(){let e=h.startingStyle;if(!l.hasAttribute(e)){r.request(i);return}let t=new MutationObserver(()=>{l.hasAttribute(e)||(t.disconnect(),i())});t.observe(l,{attributes:!0,attributeFilter:[e]}),a?.addEventListener(`abort`,()=>t.disconnect(),{once:!0})}function i(){Promise.all(l.getAnimations().map(e=>e.finished)).then(()=>{a?.aborted||s()}).catch(()=>{let e=l.getAnimations();if(n){if(a?.aborted)return;s()}else e.length>0&&e.some(e=>e.pending||e.playState!==`finished`)&&i()})}if(t){e();return}r.request(i)}})}function b(e){let{enabled:t=!0,open:n,ref:r,onComplete:a}=e,o=i(a),c=y(r,n,!1);s.useEffect(()=>{if(!t)return;let e=new AbortController;return c(o,e.signal),()=>{e.abort()}},[t,n,o,c])}function x(e,t=!1,n=!1){let[r,i]=s.useState(e&&t?`idle`:void 0),[o,c]=s.useState(e);return e&&!o&&(c(!0),i(`starting`)),!e&&o&&r!==`ending`&&!n&&i(`ending`),!e&&!o&&r===`ending`&&i(void 0),a(()=>{if(!e&&o&&r!==`ending`&&n){let e=f.request(()=>{i(`ending`)});return()=>{f.cancel(e)}}},[e,o,r,n]),a(()=>{if(!e||t)return;let n=f.request(()=>{i(void 0)});return()=>{f.cancel(n)}},[t,e]),a(()=>{if(!e||!t)return;e&&o&&r!==`idle`&&i(`starting`);let n=f.request(()=>{i(`idle`)});return()=>{f.cancel(n)}},[t,e,o,i,r]),s.useMemo(()=>({mounted:o,setMounted:c,transitionStatus:r}),[o,r])}export{v as a,p as c,h as i,l,b as n,m as o,y as r,f as s,x as t};
+2 -2
View File
@@ -22,11 +22,11 @@
<link rel="modulepreload" crossorigin href="/assets/dist.js">
<link rel="modulepreload" crossorigin href="/assets/button.js">
<link rel="modulepreload" crossorigin href="/assets/createBaseUIEventDetails.js">
<link rel="modulepreload" crossorigin href="/assets/useTransitionStatus.js">
<link rel="modulepreload" crossorigin href="/assets/addEventListener.js">
<link rel="modulepreload" crossorigin href="/assets/useStore.js">
<link rel="modulepreload" crossorigin href="/assets/useOpenInteractionType.js">
<link rel="modulepreload" crossorigin href="/assets/DialogTitle.js">
<link rel="modulepreload" crossorigin href="/assets/getDisabledMountTransitionStyles.js">
<link rel="modulepreload" crossorigin href="/assets/usePositioner.js">
<link rel="modulepreload" crossorigin href="/assets/separator.js">
<link rel="modulepreload" crossorigin href="/assets/input.js">
<link rel="modulepreload" crossorigin href="/assets/admin-context.js">
@@ -14,19 +14,19 @@
},
"dependencies": {
"@aws-crypto/sha256-browser": "^5.2.0",
"@aws-sdk/client-ec2": "^3.1029.0",
"@aws-sdk/client-elastic-load-balancing-v2": "^3.1029.0",
"@aws-sdk/client-iam": "^3.1029.0",
"@aws-sdk/client-s3": "^3.1029.0",
"@aws-sdk/lib-storage": "^3.1029.0",
"@base-ui/react": "^1.3.0",
"@aws-sdk/client-ec2": "^3.1032.0",
"@aws-sdk/client-elastic-load-balancing-v2": "^3.1032.0",
"@aws-sdk/client-iam": "^3.1032.0",
"@aws-sdk/client-s3": "^3.1032.0",
"@aws-sdk/lib-storage": "^3.1032.0",
"@base-ui/react": "^1.4.0",
"@fontsource-variable/geist": "^5.2.8",
"@fontsource-variable/jetbrains-mono": "^5.2.8",
"@hookform/resolvers": "^5.2.2",
"@smithy/protocol-http": "^5.3.13",
"@smithy/signature-v4": "^5.3.13",
"@tanstack/react-query": "^5.99.0",
"@tanstack/react-router": "^1.168.18",
"@smithy/protocol-http": "^5.3.14",
"@smithy/signature-v4": "^5.3.14",
"@tanstack/react-query": "^5.99.1",
"@tanstack/react-router": "^1.168.23",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.8.0",
@@ -38,28 +38,28 @@
"zod": "^4.3.6"
},
"devDependencies": {
"@rolldown/plugin-babel": "^0.2.2",
"@rolldown/plugin-babel": "^0.2.3",
"@tailwindcss/vite": "^4.2.2",
"@tanstack/eslint-plugin-query": "^5.99.0",
"@tanstack/eslint-plugin-query": "^5.99.1",
"@tanstack/eslint-plugin-router": "^1.161.6",
"@tanstack/router-plugin": "^1.167.18",
"@tanstack/router-plugin": "^1.167.22",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/node": "24.12.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript/native-preview": "7.0.0-dev.20260411.1",
"@typescript/native-preview": "7.0.0-dev.20260418.1",
"@vitejs/plugin-basic-ssl": "^2.3.0",
"@vitejs/plugin-react": "^6.0.1",
"@vitest/coverage-v8": "^4.1.4",
"babel-plugin-react-compiler": "^1.0.0",
"happy-dom": "^20.8.9",
"oxfmt": "^0.44.0",
"oxlint": "^1.59.0",
"oxlint-tsgolint": "^0.20.0",
"typescript": "^6.0.2",
"ultracite": "7.5.6",
"happy-dom": "^20.9.0",
"oxfmt": "^0.45.0",
"oxlint": "^1.60.0",
"oxlint-tsgolint": "^0.21.1",
"typescript": "^6.0.3",
"ultracite": "7.6.0",
"vite": "^8.0.8",
"vitest": "^4.1.4"
},
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -348,8 +348,8 @@ var AvailableImages = map[string]Images{
Arch: "x86_64",
Platform: "Linux/UNIX",
CreatedAt: time.Date(2026, 03, 27, 0, 0, 0, 0, time.UTC),
URL: "https://d2yp8ipz5jfqcw.cloudfront.net/alb-alpine-3.21.6-x86_64.raw",
Checksum: "https://d2yp8ipz5jfqcw.cloudfront.net/alb-alpine-3.21.6-x86_64.raw.sha512",
URL: "https://iso.mulgadc.com/system-ami/lb-alpine-3.21.6-x86_64.raw",
Checksum: "https://iso.mulgadc.com/system-ami/lb-alpine-3.21.6-x86_64.raw.sha512",
ChecksumType: "sha512",
BootMode: "bios",
Starred: false,
+68 -2
View File
@@ -13,6 +13,14 @@ import (
"github.com/nats-io/nats.go"
)
// Sentinel errors for TLS certificate configuration failures in ConnectNATS.
// Callers can use errors.Is to detect permanent TLS errors without relying
// on error message text.
var (
ErrCACertRead = errors.New("failed to read CA cert")
ErrCACertParse = errors.New("failed to parse CA cert")
)
// ConnectNATS establishes a connection to a NATS server with standard reconnect
// handling and logging. If token is non-empty, token authentication is used.
// If caCertPath is non-empty, TLS is enabled using the given CA certificate.
@@ -35,11 +43,11 @@ func ConnectNATS(host, token, caCertPath string) (*nats.Conn, error) {
if caCertPath != "" {
caCert, err := os.ReadFile(caCertPath)
if err != nil {
return nil, fmt.Errorf("read CA cert %s: %w", caCertPath, err)
return nil, fmt.Errorf("%w %s: %v", ErrCACertRead, caCertPath, err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(caCert) {
return nil, fmt.Errorf("failed to parse CA cert from %s", caCertPath)
return nil, fmt.Errorf("%w from %s", ErrCACertParse, caCertPath)
}
opts = append(opts, nats.Secure(&tls.Config{
RootCAs: pool,
@@ -55,6 +63,64 @@ func ConnectNATS(host, token, caCertPath string) (*nats.Conn, error) {
return nc, nil
}
// retryConfig holds parameters for ConnectNATSWithRetry.
type retryConfig struct {
maxWait time.Duration
retryDelay time.Duration
}
// RetryOption configures ConnectNATSWithRetry behavior.
type RetryOption func(*retryConfig)
// WithMaxWait sets the maximum total time to keep retrying before giving up.
func WithMaxWait(d time.Duration) RetryOption {
return func(c *retryConfig) { c.maxWait = d }
}
// WithRetryDelay sets the initial delay between retries (doubles each attempt, capped at 10s).
func WithRetryDelay(d time.Duration) RetryOption {
return func(c *retryConfig) { c.retryDelay = d }
}
// ConnectNATSWithRetry calls ConnectNATS in a retry loop with exponential
// backoff. It retries for up to 5 minutes (default) before giving up. TLS
// configuration errors (ErrCACertRead, ErrCACertParse) are permanent and
// cause an immediate return without retrying.
func ConnectNATSWithRetry(host, token, caCertPath string, opts ...RetryOption) (*nats.Conn, error) {
cfg := retryConfig{
maxWait: 5 * time.Minute,
retryDelay: 500 * time.Millisecond,
}
for _, o := range opts {
o(&cfg)
}
start := time.Now()
for {
nc, err := ConnectNATS(host, token, caCertPath)
if err == nil {
if time.Since(start) > time.Second {
slog.Info("NATS connection established", "elapsed", time.Since(start).Round(time.Second))
}
return nc, nil
}
// TLS configuration errors are permanent — retrying will not help.
if errors.Is(err, ErrCACertRead) || errors.Is(err, ErrCACertParse) {
return nil, fmt.Errorf("NATS TLS configuration error: %w", err)
}
elapsed := time.Since(start)
if elapsed >= cfg.maxWait {
return nil, fmt.Errorf("NATS connect failed after %s: %w", elapsed.Round(time.Second), err)
}
slog.Warn("NATS not ready, retrying...", "error", err, "elapsed", elapsed.Round(time.Second), "retryIn", cfg.retryDelay)
time.Sleep(cfg.retryDelay)
cfg.retryDelay = min(cfg.retryDelay*2, 10*time.Second)
}
}
// AccountIDHeader is the NATS message header key used to pass the caller's
// AWS account ID from the gateway to daemon handlers.
const AccountIDHeader = "X-Account-ID"
+40 -2
View File
@@ -90,7 +90,7 @@ func TestConnectNATS_BadAddress(t *testing.T) {
func TestConnectNATS_MissingCACert(t *testing.T) {
_, err := ConnectNATS("nats://127.0.0.1:4222", "", "/nonexistent/ca.pem")
require.Error(t, err)
assert.Contains(t, err.Error(), "read CA cert")
assert.ErrorIs(t, err, ErrCACertRead)
assert.Contains(t, err.Error(), "/nonexistent/ca.pem")
}
@@ -101,7 +101,7 @@ func TestConnectNATS_MalformedCACert(t *testing.T) {
_, err := ConnectNATS("nats://127.0.0.1:4222", "", badCert)
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to parse CA cert")
assert.ErrorIs(t, err, ErrCACertParse)
}
// generateTestCA creates an ephemeral CA cert+key and writes PEM files to dir.
@@ -535,3 +535,41 @@ func TestAccountIDFromMsg_NilHeader(t *testing.T) {
msg := &nats.Msg{Subject: "test"}
assert.Equal(t, "", AccountIDFromMsg(msg))
}
// --- ConnectNATSWithRetry tests ---
func TestConnectNATSWithRetry_Success(t *testing.T) {
ns := startTestNATSServer(t)
nc, err := ConnectNATSWithRetry(ns.ClientURL(), "", "")
require.NoError(t, err)
defer nc.Close()
assert.True(t, nc.IsConnected())
}
func TestConnectNATSWithRetry_RetriesOnFailure(t *testing.T) {
start := time.Now()
_, err := ConnectNATSWithRetry("nats://127.0.0.1:14222", "", "",
WithMaxWait(500*time.Millisecond),
WithRetryDelay(50*time.Millisecond),
)
elapsed := time.Since(start)
require.Error(t, err)
assert.Contains(t, err.Error(), "NATS connect failed")
assert.GreaterOrEqual(t, elapsed, 100*time.Millisecond, "should have retried at least once")
assert.Less(t, elapsed, 5*time.Second, "should fail within a few seconds")
}
func TestConnectNATSWithRetry_TLSErrorNoRetry(t *testing.T) {
start := time.Now()
_, err := ConnectNATSWithRetry("nats://127.0.0.1:4222", "", "/nonexistent/ca.pem",
WithMaxWait(5*time.Second),
)
elapsed := time.Since(start)
require.Error(t, err)
assert.ErrorIs(t, err, ErrCACertRead)
assert.Contains(t, err.Error(), "NATS TLS configuration error")
assert.Less(t, elapsed, time.Second, "should fail immediately without retrying")
}
+8
View File
@@ -87,6 +87,14 @@ type VM struct {
PublicIP string `json:"public_ip,omitempty"`
PublicIPPool string `json:"public_ip_pool,omitempty"`
// PublicIPAllocID / PublicIPAssocID are set when the public IP was
// allocated through the EIP service (handlers/ec2/eip) rather than
// directly via externalIPAM. Presence signals that teardown must go
// back through the EIP service (DisassociateAddress + ReleaseAddress)
// so the EIP KV record is removed.
PublicIPAllocID string `json:"public_ip_alloc_id,omitempty"`
PublicIPAssocID string `json:"public_ip_assoc_id,omitempty"`
// DevMAC is the MAC for the dev/hostfwd NIC (DEV_NETWORKING mode).
// Set before cloud-init ISO generation so netplan can suppress its default route.
DevMAC string `json:"dev_mac,omitempty"`