Setup fips in a qube

This commit is contained in:
Laan Tungir
2026-04-11 14:20:42 -04:00
commit a351883b67
27 changed files with 1427 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
fips/

136
README.md Normal file
View File

@@ -0,0 +1,136 @@
# sys-fips Setup for Qubes OS
> Goal: run a **single FIPS node** inside one ProxyVM (`sys-fips`) so any AppVM routed through it can access the FIPS mesh.
## Architecture
```text
AppVMs → sys-fips (ProxyVM) → sys-firewall → sys-net → Internet
└─ fips0 (TUN) → FIPS mesh (encrypted)
```
This setup is intentionally single-node per VM.
If you want another independent FIPS identity/path, clone the VM (for example, `sys-fips2`).
---
## Files in this directory
| File | Purpose |
|---|---|
| `README.md` | This guide |
| `configs/fips.yaml` | Single-node config template |
| `scripts/00-build-fips.sh` | Run in build qube: build `fips`, `fipsctl`, `fipstop` into local `bin/` |
| `scripts/01-dom0-create-proxyvm.sh` | Run in dom0: creates `sys-fips` ProxyVM |
| `scripts/02-install-fips.sh` | Run in `sys-fips`: installs deps, binaries, and systemd units |
| `scripts/03-configure-identity.sh` | Run in `sys-fips`: configures `/etc/fips/fips.yaml`, identity mode |
| `scripts/03-generate-node-configs.sh` | Compatibility wrapper to `03-configure-identity.sh` |
| `scripts/04-start-fips.sh` | Run in `sys-fips`: starts single FIPS node |
| `scripts/04-start-fips-nodes.sh` | Compatibility wrapper to `04-start-fips.sh` |
| `scripts/05-stop-fips.sh` | Run in `sys-fips`: stops node + cleanup |
| `scripts/05-stop-fips-nodes.sh` | Compatibility wrapper to `05-stop-fips.sh` |
| `scripts/06-configure-dns.sh` | Run in `sys-fips`: dnsmasq forwards `.fips` to localhost:5354 and applies Qubes `/32` DNS compatibility patch |
| `scripts/07-route-appvms.sh` | Run in `sys-fips`: IPv6/ip6tables forwarding for routed AppVMs |
| `scripts/08-add-peer.sh` | Run in `sys-fips`: add static peer to `/etc/fips/fips.yaml` |
| `scripts/09-add-known-peers.sh` | Run in `sys-fips`: add two known bootstrap peers and restart FIPS |
| `scripts/test-connectivity.sh` | Unified test script |
| `scripts/test-local.sh` | Compatibility wrapper to `test-connectivity.sh --local` |
| `scripts/test-from-appvm.sh` | Compatibility wrapper to `test-connectivity.sh --appvm` |
| `scripts/test-full-suite.sh` | Compatibility wrapper to `test-connectivity.sh --full` |
---
## Quick start
### 1) Create `sys-fips` (run in dom0)
```bash
sudo bash 01-dom0-create-proxyvm.sh [template] [upstream_netvm]
```
Defaults:
- template: `debian-12`
- upstream netvm: `sys-firewall`
For VPN chaining, pass `sys-vpn` as upstream:
```bash
sudo bash 01-dom0-create-proxyvm.sh debian-12 sys-vpn
```
### 2) Build in this repo, then copy this entire folder
From this `fips_setup` repo in your build qube:
```bash
bash ./scripts/00-build-fips.sh
qvm-copy-to-vm sys-fips /home/user/anvil/fips_setup/
```
This keeps your update workflow simple: pull latest code, rebuild once in this repo, copy the folder, run install in `sys-fips`.
### 3) Install/configure/start (inside `sys-fips`)
```bash
cd ~/QubesIncoming/*/fips_setup/scripts
sudo bash 02-install-fips.sh
bash 03-configure-identity.sh
sudo bash 04-start-fips.sh
sudo bash 06-configure-dns.sh
sudo bash 07-route-appvms.sh
```
### 4) Add at least one external peer
```bash
sudo bash 08-add-peer.sh
sudo systemctl restart fips
```
Or add both known bootstrap peers automatically:
```bash
sudo bash 09-add-known-peers.sh
```
### 5) Test
```bash
sudo bash test-connectivity.sh --full
```
### 6) Point AppVMs at `sys-fips` (in dom0)
```bash
qvm-prefs my-appvm netvm sys-fips
```
---
## Clone workflow
```bash
# dom0
qvm-clone sys-fips sys-fips2
qvm-start sys-fips2
```
Inside `sys-fips2`:
```bash
sudo bash 05-stop-fips.sh
sudo rm -f /etc/fips/fips.key /etc/fips/fips.pub
bash 03-configure-identity.sh
sudo bash 04-start-fips.sh
```
Now `sys-fips` and `sys-fips2` are independent FIPS nodes.
---
## Notes
- One FIPS node per ProxyVM is the production target.
- The previous two-node local loopback approach was useful for local experiments but is not required for normal Qubes deployment.
- FIPS mesh encryption is end-to-end. A VPN upstream is optional and can be chained.
- `06-configure-dns.sh` removes dnsmasq `--local-service` from Debian's helper to avoid dropping AppVM DNS on Qubes `/32` links.

BIN
bin/fips Executable file

Binary file not shown.

BIN
bin/fipsctl Executable file

Binary file not shown.

BIN
bin/fipstop Executable file

Binary file not shown.

25
configs/fips-node-a.yaml Normal file
View File

@@ -0,0 +1,25 @@
# Deprecated in single-node sys-fips design.
#
# Use configs/fips.yaml instead.
# This file is kept only for backward compatibility with old references.
node:
identity:
persistent: true
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:2121"
mtu: 1472
peers: []

25
configs/fips-node-b.yaml Normal file
View File

@@ -0,0 +1,25 @@
# Deprecated in single-node sys-fips design.
#
# Use configs/fips.yaml instead.
# This file is kept only for backward compatibility with old references.
node:
identity:
persistent: true
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:2121"
mtu: 1472
peers: []

26
configs/fips.yaml Normal file
View File

@@ -0,0 +1,26 @@
# FIPS single-node config template for sys-fips
#
# This file is copied to /etc/fips/fips.yaml by 02-install-fips.sh.
# Identity is persistent by default so the node keeps the same npub across restarts.
node:
identity:
persistent: true
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:2121"
mtu: 1472
# Add external peers with scripts/08-add-peer.sh
peers: []

1
fips Submodule

Submodule fips added at 7494ed058d

199
plans/sys-fips-plan.md Normal file
View File

@@ -0,0 +1,199 @@
# sys-fips: Single-Node FIPS ProxyVM for Qubes OS
## Overview
Simplify the current `fips_setup` to run **one FIPS node** in a Qubes ProxyVM called `sys-fips`. This VM slots into the Qubes network chain just like `sys-vpn`:
```
AppVM → sys-fips → sys-firewall → sys-net → Internet
↘ FIPS Mesh (encrypted overlay)
```
To get a second independent FIPS node, simply **clone** `sys-fips` in dom0 — each clone gets its own identity and peer config.
## Architecture
```mermaid
graph LR
A[AppVM-1] --> SF[sys-fips]
B[AppVM-2] --> SF
SF -->|regular traffic| FW[sys-firewall]
SF -->|fips0 TUN| MESH[FIPS Mesh]
FW --> NET[sys-net]
NET --> INET[Internet]
```
### What changes from the current plan
| Current plan | Revised plan |
|---|---|
| Two FIPS nodes per VM - fips0 + fips1 | **One FIPS node** per VM - fips0 only |
| Two configs - node-a.yaml, node-b.yaml | **One config** - fips.yaml |
| Two keypairs generated | **One keypair** - persistent identity |
| Localhost peering between nodes | No local peering needed |
| Complex start/stop for two processes | **Single systemd service** |
| Clone not considered | **Clone-friendly** design |
### Network flow inside sys-fips
```mermaid
graph TD
subgraph sys-fips ProxyVM
VIF[vif* - from AppVMs] -->|fd00::/8 traffic| FW6[ip6tables FORWARD]
FW6 --> FIPS0[fips0 TUN interface]
VIF -->|regular traffic| ETH0[eth0 - to sys-firewall]
DNS53[dnsmasq :53] -->|.fips queries| DNS5354[FIPS DNS :5354]
DNS53 -->|other queries| UPSTREAM[upstream DNS]
end
FIPS0 --> MESH[FIPS Mesh Network]
ETH0 --> SYSFW[sys-firewall]
```
## Revised file structure
```
fips_setup/
├── README.md # Updated overview and instructions
├── configs/
│ └── fips.yaml # Single node config template
├── scripts/
│ ├── 01-dom0-create-sys-fips.sh # Create sys-fips ProxyVM in dom0
│ ├── 02-install-fips.sh # Install binary + deps in sys-fips
│ ├── 03-configure-identity.sh # Generate or import keypair
│ ├── 04-start-fips.sh # Start single FIPS node + routing
│ ├── 05-stop-fips.sh # Stop FIPS node + cleanup
│ ├── 06-configure-dns.sh # dnsmasq for .fips resolution
│ ├── 07-route-appvms.sh # ip6tables forwarding for AppVMs
│ ├── 08-add-peer.sh # Add an external peer to config
│ └── test-connectivity.sh # Unified test script
└── plans/
└── sys-fips-plan.md # This file
```
## Implementation plan
### 1. Simplify config to single node
Replace `fips-node-a.yaml` and `fips-node-b.yaml` with a single `fips.yaml`:
```yaml
node:
identity:
persistent: true # Auto-generates keypair on first run, reuses on restart
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:2121"
mtu: 1472
peers: []
```
Key difference: `persistent: true` instead of explicit `nsec`. FIPS auto-generates `/etc/fips/fips.key` and `/etc/fips/fips.pub` on first run. When you clone the VM, the clone starts fresh and generates its own identity.
### 2. Rewrite dom0 script - 01-dom0-create-sys-fips.sh
- Rename VM from `fips-proxy` to `sys-fips` to follow Qubes naming convention
- Accept optional `netvm` parameter - defaults to `sys-firewall` but supports chaining through `sys-vpn`
- Same `provides_network=True` setup
### 3. Simplify install script - 02-install-fips.sh
- Same as current but also installs the upstream systemd service files from FIPS packaging
- Installs `fips.service` and `fips-dns.service` from the FIPS repo packaging directory
- Copies the single `fips.yaml` config template
### 4. New identity script - 03-configure-identity.sh
- If `/etc/fips/fips.key` exists, show current npub and ask to keep or regenerate
- If not, let FIPS generate on first start via `persistent: true`
- Option to import an existing nsec for a known identity
- This is what makes cloning work: after cloning, run this script to get a fresh identity
### 5. Simplify start/stop - 04-start-fips.sh / 05-stop-fips.sh
- Start: `systemctl start fips` — uses the upstream systemd unit
- Enable IPv6 forwarding
- Add `fd00::/8 → fips0` route
- Wait for `fips0` TUN to appear
- Show npub and IPv6 address
- Stop: `systemctl stop fips` + cleanup routes
### 6. DNS - 06-configure-dns.sh
- Same dnsmasq approach but simplified — only one FIPS DNS backend on port 5354
- No changes needed from current plan
### 7. Routing - 07-route-appvms.sh
- Same ip6tables forwarding rules but only for `fips0` — no `fips1`
- Persist via `/rw/config/rc.local`
### 8. New peer management - 08-add-peer.sh
- Interactive script to add a peer to `/etc/fips/fips.yaml`
- Takes npub, alias, and address as arguments
- Appends to the peers list in the YAML config
- Restarts FIPS to pick up the new peer
### 9. Unified test script - test-connectivity.sh
- Merge `test-local.sh`, `test-from-appvm.sh`, and `test-full-suite.sh` into one
- Auto-detects whether running inside sys-fips or an AppVM
- Tests: binary installed, fips0 up, IPv6 address, DNS resolution, peer connectivity
## Cloning workflow
Once `sys-fips` is working:
```bash
# In dom0:
qvm-clone sys-fips sys-fips-2
# Start sys-fips-2, open terminal:
sudo bash 03-configure-identity.sh # Generates new keypair
sudo systemctl restart fips
# Now you have two independent FIPS nodes:
# sys-fips → identity A, peers X
# sys-fips-2 → identity B, peers Y
# Point AppVMs at whichever you want:
qvm-prefs work-vm netvm sys-fips
qvm-prefs personal-vm netvm sys-fips-2
```
## Qubes network chaining
sys-fips can chain through a VPN qube:
```
AppVM → sys-fips → sys-vpn → sys-firewall → sys-net → Internet
```
Set this up in dom0:
```bash
qvm-prefs sys-fips netvm sys-vpn
```
FIPS UDP traffic exits through the VPN tunnel, adding another layer of transport privacy. The FIPS mesh encryption is independent of the VPN — even if the VPN is compromised, FIPS traffic remains encrypted end-to-end.
## What gets deleted
- `configs/fips-node-a.yaml` — replaced by single `configs/fips.yaml`
- `configs/fips-node-b.yaml` — no longer needed
- `scripts/03-generate-node-configs.sh` — replaced by `03-configure-identity.sh`
- `scripts/test-local.sh` — merged into `test-connectivity.sh`
- `scripts/test-from-appvm.sh` — merged into `test-connectivity.sh`
- `scripts/test-full-suite.sh` — merged into `test-connectivity.sh`

36
scripts/00-build-fips.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# ==============================================================================
# 00-build-fips.sh
#
# Build FIPS binaries from the bundled ./fips source tree and stage them into
# ./bin so the whole fips_setup directory can be copied to sys-fips.
#
# Usage:
# bash ./scripts/00-build-fips.sh
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
FIPS_DIR="${ROOT_DIR}/fips"
OUT_DIR="${ROOT_DIR}/bin"
if [ ! -f "${FIPS_DIR}/Cargo.toml" ]; then
echo "✗ Missing bundled fips source at: ${FIPS_DIR}"
exit 1
fi
echo "=== Building FIPS binaries from ${FIPS_DIR} ==="
echo ""
cd "${FIPS_DIR}"
cargo build --release --bin fips --bin fipsctl --bin fipstop
mkdir -p "${OUT_DIR}"
install -m 755 "${FIPS_DIR}/target/release/fips" "${OUT_DIR}/fips"
install -m 755 "${FIPS_DIR}/target/release/fipsctl" "${OUT_DIR}/fipsctl"
install -m 755 "${FIPS_DIR}/target/release/fipstop" "${OUT_DIR}/fipstop"
echo ""
echo "✓ Staged binaries in ${OUT_DIR}:"
ls -lh "${OUT_DIR}/fips" "${OUT_DIR}/fipsctl" "${OUT_DIR}/fipstop"

View File

@@ -0,0 +1,99 @@
#!/bin/bash
# ==============================================================================
# 01-dom0-create-proxyvm.sh
#
# Run this script in dom0 to create the sys-fips ProxyVM.
#
# Usage (in dom0):
# sudo bash 01-dom0-create-proxyvm.sh [template] [upstream_netvm]
#
# Arguments:
# template TemplateVM to base on (default: debian-12)
# upstream_netvm Upstream NetVM (default: sys-firewall, can be sys-vpn)
# ==============================================================================
set -euo pipefail
TEMPLATE="${1:-debian-12}"
UPSTREAM_NETVM="${2:-sys-firewall}"
VM_NAME="sys-fips"
VM_LABEL="blue"
echo "=== sys-fips ProxyVM Creation ==="
echo ""
echo " VM Name: $VM_NAME"
echo " Template: $TEMPLATE"
echo " Upstream NetVM: $UPSTREAM_NETVM"
echo " Label: $VM_LABEL"
echo ""
# Check if VM already exists
if qvm-check "$VM_NAME" 2>/dev/null; then
echo "⚠ VM '$VM_NAME' already exists."
echo " To recreate, first remove it:"
echo " qvm-shutdown $VM_NAME"
echo " qvm-remove $VM_NAME"
exit 1
fi
# Verify template exists
if ! qvm-check "$TEMPLATE" 2>/dev/null; then
echo "✗ Template '$TEMPLATE' does not exist."
echo " Available templates:"
qvm-ls --raw-list --fields name,klass | grep TemplateVM | awk '{print " " $1}'
exit 1
fi
# Verify upstream netvm exists
if ! qvm-check "$UPSTREAM_NETVM" 2>/dev/null; then
echo "✗ Upstream NetVM '$UPSTREAM_NETVM' does not exist."
exit 1
fi
echo "Creating ProxyVM..."
# Create as AppVM + provides_network=True => ProxyVM behavior
qvm-create "$VM_NAME" \
--class AppVM \
--template "$TEMPLATE" \
--label "$VM_LABEL" \
--prop provides_network=True \
--prop netvm="$UPSTREAM_NETVM"
echo "✓ Created $VM_NAME"
# Memory defaults (lightweight baseline)
qvm-prefs "$VM_NAME" memory 512
qvm-prefs "$VM_NAME" maxmem 2048
echo "✓ Memory: 512MB initial, 2048MB max"
# Ensure networking prefs
qvm-prefs "$VM_NAME" netvm "$UPSTREAM_NETVM"
qvm-prefs "$VM_NAME" provides_network True
echo "✓ NetVM: $UPSTREAM_NETVM"
echo "✓ provides_network: True"
echo ""
echo "=== sys-fips Created Successfully ==="
echo ""
echo "Network chain examples:"
echo " AppVM -> sys-fips -> sys-firewall -> sys-net -> Internet"
echo " AppVM -> sys-fips -> sys-vpn -> sys-firewall -> sys-net -> Internet"
echo ""
echo "Next steps:"
echo ""
echo " 1) Start the VM:"
echo " qvm-start $VM_NAME"
echo ""
echo " 2) Copy FIPS binaries (from your build AppVM):"
echo " qvm-copy-to-vm $VM_NAME /home/user/lt/fips/target/release/fips"
echo " qvm-copy-to-vm $VM_NAME /home/user/lt/fips/target/release/fipsctl"
echo " qvm-copy-to-vm $VM_NAME /home/user/lt/fips/target/release/fipstop"
echo ""
echo " 3) Copy this setup directory:"
echo " qvm-copy-to-vm $VM_NAME /home/user/anvil/fips_setup/"
echo ""
echo " 4) In sys-fips terminal run:"
echo " cd ~/QubesIncoming/*/fips_setup/scripts/"
echo " sudo bash 02-install-fips.sh"
echo " bash 03-configure-identity.sh"
echo " sudo bash 04-start-fips.sh"

190
scripts/02-install-fips.sh Executable file
View File

@@ -0,0 +1,190 @@
#!/bin/bash
# ==============================================================================
# 02-install-fips.sh
#
# Run inside sys-fips to install FIPS binaries, dependencies, config, and service.
#
# Usage:
# sudo bash 02-install-fips.sh
# ==============================================================================
set -euo pipefail
echo "=== sys-fips Installation ==="
echo ""
if [ "$(id -u)" -ne 0 ]; then
echo "✗ This script must be run as root (sudo)"
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
# ── Install dependencies ────────────────────────────────────────────────────────
echo "Installing system dependencies..."
apt-get update -qq
apt-get install -y --no-install-recommends \
iproute2 iputils-ping dnsutils \
dnsmasq iptables \
curl python3 jq \
openssh-server openssh-client \
iperf3 2>/dev/null || {
echo "⚠ Some packages may not be available — continuing"
}
echo "✓ Dependencies installed"
# ── Ensure control-socket access group exists ──────────────────────────────────
if ! getent group fips >/dev/null 2>&1; then
groupadd --system fips
echo "✓ Created system group: fips"
else
echo "✓ Group exists: fips"
fi
# ── Locate binaries ─────────────────────────────────────────────────────────────
echo ""
echo "Looking for FIPS binaries..."
FIPS_BIN=""
FIPSCTL_BIN=""
FIPSTOP_BIN=""
LOCAL_BIN_DIR="${ROOT_DIR}/bin"
LOCAL_RELEASE_DIR="${ROOT_DIR}/fips/target/release"
# Preferred: binaries prebuilt in this same repo copy
[ -z "$FIPS_BIN" ] && [ -f "${LOCAL_BIN_DIR}/fips" ] && FIPS_BIN="${LOCAL_BIN_DIR}/fips"
[ -z "$FIPSCTL_BIN" ] && [ -f "${LOCAL_BIN_DIR}/fipsctl" ] && FIPSCTL_BIN="${LOCAL_BIN_DIR}/fipsctl"
[ -z "$FIPSTOP_BIN" ] && [ -f "${LOCAL_BIN_DIR}/fipstop" ] && FIPSTOP_BIN="${LOCAL_BIN_DIR}/fipstop"
# Fallback: release artifacts in bundled fips source tree
[ -z "$FIPS_BIN" ] && [ -f "${LOCAL_RELEASE_DIR}/fips" ] && FIPS_BIN="${LOCAL_RELEASE_DIR}/fips"
[ -z "$FIPSCTL_BIN" ] && [ -f "${LOCAL_RELEASE_DIR}/fipsctl" ] && FIPSCTL_BIN="${LOCAL_RELEASE_DIR}/fipsctl"
[ -z "$FIPSTOP_BIN" ] && [ -f "${LOCAL_RELEASE_DIR}/fipstop" ] && FIPSTOP_BIN="${LOCAL_RELEASE_DIR}/fipstop"
# Legacy fallback: individually copied binaries in QubesIncoming
for incoming_dir in /home/user/QubesIncoming/*/; do
[ -z "$FIPS_BIN" ] && [ -f "${incoming_dir}fips" ] && FIPS_BIN="${incoming_dir}fips"
[ -z "$FIPSCTL_BIN" ] && [ -f "${incoming_dir}fipsctl" ] && FIPSCTL_BIN="${incoming_dir}fipsctl"
[ -z "$FIPSTOP_BIN" ] && [ -f "${incoming_dir}fipstop" ] && FIPSTOP_BIN="${incoming_dir}fipstop"
done
if [ -z "$FIPS_BIN" ]; then
echo "✗ Could not find 'fips' binary in this repo copy."
echo " Expected one of:"
echo " ${LOCAL_BIN_DIR}/fips"
echo " ${LOCAL_RELEASE_DIR}/fips"
echo ""
echo " Build before copying into sys-fips:"
echo " bash ./scripts/00-build-fips.sh"
exit 1
fi
echo " Found fips: $FIPS_BIN"
[ -n "$FIPSCTL_BIN" ] && echo " Found fipsctl: $FIPSCTL_BIN"
[ -n "$FIPSTOP_BIN" ] && echo " Found fipstop: $FIPSTOP_BIN"
# ── Install binaries ────────────────────────────────────────────────────────────
install -m 755 "$FIPS_BIN" /usr/local/bin/fips
echo "✓ Installed /usr/local/bin/fips"
if [ -n "$FIPSCTL_BIN" ]; then
install -m 755 "$FIPSCTL_BIN" /usr/local/bin/fipsctl
echo "✓ Installed /usr/local/bin/fipsctl"
fi
if [ -n "$FIPSTOP_BIN" ]; then
install -m 755 "$FIPSTOP_BIN" /usr/local/bin/fipstop
echo "✓ Installed /usr/local/bin/fipstop"
fi
# ── Ensure runtime dirs and TUN ─────────────────────────────────────────────────
mkdir -p /etc/fips /var/log/fips /var/run/fips
if [ ! -c /dev/net/tun ]; then
echo "Creating /dev/net/tun..."
mkdir -p /dev/net
mknod /dev/net/tun c 10 200
chmod 666 /dev/net/tun
fi
echo "✓ Runtime directories and TUN are ready"
# ── Install config ──────────────────────────────────────────────────────────────
if [ -f /etc/fips/fips.yaml ]; then
echo "⚠ /etc/fips/fips.yaml already exists; preserving existing file"
else
if [ -f "${ROOT_DIR}/configs/fips.yaml" ]; then
install -m 600 "${ROOT_DIR}/configs/fips.yaml" /etc/fips/fips.yaml
echo "✓ Installed /etc/fips/fips.yaml"
else
cat > /etc/fips/fips.yaml << 'EOF'
node:
identity:
persistent: true
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:2121"
mtu: 1472
peers: []
EOF
chmod 600 /etc/fips/fips.yaml
echo "✓ Installed default /etc/fips/fips.yaml"
fi
fi
# ── Install systemd service (single node) ──────────────────────────────────────
cat > /etc/systemd/system/fips.service << 'EOF'
[Unit]
Description=FIPS Mesh Network Daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/fips --config /etc/fips/fips.yaml
Restart=on-failure
RestartSec=5
RuntimeDirectory=fips
RuntimeDirectoryMode=0750
ProtectHome=yes
PrivateTmp=yes
ProtectKernelModules=yes
ProtectKernelTunables=no
[Install]
WantedBy=multi-user.target
EOF
echo "✓ Installed /etc/systemd/system/fips.service"
systemctl daemon-reload
systemctl enable fips >/dev/null 2>&1 || true
echo ""
echo "=== Installation complete ==="
echo ""
if [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
echo "Control socket access tip:"
echo " sudo usermod -aG fips ${SUDO_USER}"
echo " (then log out/in, or use 'sudo fipsctl ...')"
echo ""
fi
echo "Next steps:"
echo " sudo bash 03-configure-identity.sh"
echo " sudo bash 04-start-fips.sh"
echo " sudo bash 06-configure-dns.sh"
echo " sudo bash 07-route-appvms.sh"

View File

@@ -0,0 +1,94 @@
#!/bin/bash
# ==============================================================================
# 03-configure-identity.sh
#
# Configure identity behavior for single-node sys-fips.
#
# Usage:
# sudo bash 03-configure-identity.sh
# ==============================================================================
set -euo pipefail
CONFIG_FILE="/etc/fips/fips.yaml"
echo "=== FIPS Identity Configuration ==="
echo ""
if [ "$(id -u)" -ne 0 ]; then
echo "✗ Run as root: sudo bash 03-configure-identity.sh"
exit 1
fi
if [ ! -f "$CONFIG_FILE" ]; then
echo "$CONFIG_FILE not found"
echo " Run: sudo bash 02-install-fips.sh"
exit 1
fi
if [ -f /etc/fips/fips.pub ]; then
echo "Current identity detected:"
echo " npub: $(cat /etc/fips/fips.pub)"
echo ""
fi
echo "Identity modes:"
echo " 1) persistent (recommended): key auto-generated once and reused"
echo " 2) explicit nsec: pin node identity to a specific key"
echo ""
read -r -p "Select mode [1/2] (default 1): " MODE
MODE="${MODE:-1}"
set_identity_block() {
local identity_line="$1"
local tmp
tmp=$(mktemp)
# Replace node.identity block in known config format: node: ... then tun:
awk -v ident="$identity_line" '
BEGIN { replaced=0; skipping=0 }
/^node:[[:space:]]*$/ && replaced==0 {
print "node:";
print " identity:";
print " " ident;
replaced=1;
skipping=1;
next;
}
skipping==1 {
if (/^tun:[[:space:]]*$/) {
skipping=0;
print $0;
}
next;
}
{ print $0 }
' "$CONFIG_FILE" > "$tmp"
mv "$tmp" "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
}
case "$MODE" in
1)
set_identity_block "persistent: true"
echo "✓ Set node.identity.persistent=true"
;;
2)
read -r -p "Enter nsec (bech32 or 64-char hex): " NSEC
if [ -z "$NSEC" ]; then
echo "✗ Empty nsec is not allowed"
exit 1
fi
set_identity_block "nsec: \"$NSEC\""
echo "✓ Set node.identity.nsec"
;;
*)
echo "✗ Invalid selection"
exit 1
;;
esac
echo ""
echo "Config updated: $CONFIG_FILE"
echo "Next: sudo bash 04-start-fips.sh"

View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Compatibility wrapper: old two-node script name -> new single-node identity script
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec bash "${SCRIPT_DIR}/03-configure-identity.sh"

6
scripts/04-start-fips-nodes.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Compatibility wrapper: old two-node script name -> new single-node start script
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec bash "${SCRIPT_DIR}/04-start-fips.sh"

81
scripts/04-start-fips.sh Executable file
View File

@@ -0,0 +1,81 @@
#!/bin/bash
# ==============================================================================
# 04-start-fips.sh
#
# Start a single FIPS node in sys-fips, wait for fips0, and set route.
#
# Usage:
# sudo bash 04-start-fips.sh
# ==============================================================================
set -euo pipefail
CONFIG_FILE="/etc/fips/fips.yaml"
ENV_FILE="/etc/fips/node.env"
echo "=== Starting FIPS (single node) ==="
echo ""
if [ "$(id -u)" -ne 0 ]; then
echo "✗ This script must be run as root (sudo)"
exit 1
fi
if [ ! -f "$CONFIG_FILE" ]; then
echo "$CONFIG_FILE not found"
echo " Run: sudo bash 02-install-fips.sh"
exit 1
fi
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1 || true
sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null 2>&1 || true
echo "✓ IPv6 + forwarding enabled"
systemctl daemon-reload
systemctl restart fips
sleep 1
if ! systemctl is-active --quiet fips; then
echo "✗ fips service is not active"
echo " Check: journalctl -u fips -n 100 --no-pager"
exit 1
fi
echo "✓ fips service active"
echo "Waiting for fips0..."
for i in $(seq 1 30); do
if ip link show fips0 >/dev/null 2>&1; then
break
fi
sleep 1
done
if ! ip link show fips0 >/dev/null 2>&1; then
echo "✗ fips0 did not appear within 30s"
echo " Check: journalctl -u fips -n 100 --no-pager"
exit 1
fi
IPV6=$(ip -6 addr show fips0 scope global 2>/dev/null | awk '/inet6/{print $2}' | cut -d/ -f1 | head -1)
NPUB=""
if [ -f /etc/fips/fips.pub ]; then
NPUB=$(cat /etc/fips/fips.pub)
elif command -v fipsctl >/dev/null 2>&1; then
NPUB=$(fipsctl show status 2>/dev/null | python3 -c 'import json,sys; print(json.load(sys.stdin).get("npub",""))' 2>/dev/null || true)
fi
ip -6 route replace fd00::/8 dev fips0 2>/dev/null || true
cat > "$ENV_FILE" << EOF
NPUB=${NPUB}
IPV6=${IPV6}
EOF
echo ""
echo "✓ fips0 is up"
echo " npub: ${NPUB:-unknown}"
echo " ipv6: ${IPV6:-unknown}"
echo " route: fd00::/8 -> fips0"
echo ""
echo "Next: sudo bash 06-configure-dns.sh"

6
scripts/05-stop-fips-nodes.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Compatibility wrapper: old two-node script name -> new single-node stop script
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec bash "${SCRIPT_DIR}/05-stop-fips.sh"

31
scripts/05-stop-fips.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# ==============================================================================
# 05-stop-fips.sh
#
# Stop single FIPS node and clean up route.
#
# Usage:
# sudo bash 05-stop-fips.sh
# ==============================================================================
set -euo pipefail
echo "=== Stopping FIPS (single node) ==="
echo ""
if [ "$(id -u)" -ne 0 ]; then
echo "✗ This script must be run as root (sudo)"
exit 1
fi
systemctl stop fips 2>/dev/null || true
ip -6 route del fd00::/8 dev fips0 2>/dev/null || true
echo "✓ fips service stopped"
if ip link show fips0 >/dev/null 2>&1; then
echo " fips0 still present (will disappear when process exits completely)"
else
echo " fips0 removed"
fi
echo ""
echo "Done"

93
scripts/06-configure-dns.sh Executable file
View File

@@ -0,0 +1,93 @@
#!/bin/bash
# ==============================================================================
# 06-configure-dns.sh
#
# Configure dnsmasq in sys-fips so .fips queries are forwarded to local
# FIPS DNS resolver on 127.0.0.1:5354, and all other DNS goes upstream.
#
# Usage:
# sudo bash 06-configure-dns.sh
# ==============================================================================
set -euo pipefail
echo "=== sys-fips DNS Configuration ==="
echo ""
if [ "$(id -u)" -ne 0 ]; then
echo "✗ This script must be run as root (sudo)"
exit 1
fi
systemctl stop dnsmasq 2>/dev/null || true
killall dnsmasq 2>/dev/null || true
UPSTREAM_DNS=$(grep '^nameserver' /etc/resolv.conf | awk '{print $2}' | head -1)
UPSTREAM_DNS="${UPSTREAM_DNS:-10.139.1.1}"
echo " Upstream DNS: $UPSTREAM_DNS"
mkdir -p /etc/dnsmasq.d
cat > /etc/dnsmasq.d/fips.conf << EOF
# sys-fips DNS resolution
port=53
listen-address=0.0.0.0
bind-interfaces
# .fips domains -> FIPS resolver
server=/fips/127.0.0.1#5354
# everything else -> upstream
server=$UPSTREAM_DNS
no-resolv
no-hosts
# log-queries
EOF
if [ ! -f /etc/dnsmasq.conf ]; then
touch /etc/dnsmasq.conf
fi
if ! grep -q 'conf-dir=/etc/dnsmasq.d/,*.conf' /etc/dnsmasq.conf; then
echo 'conf-dir=/etc/dnsmasq.d/,*.conf' >> /etc/dnsmasq.conf
fi
if ! dnsmasq --test >/dev/null 2>&1; then
echo "✗ dnsmasq config failed validation"
exit 1
fi
# Qubes AppVMs often use /32 point-to-point addressing.
# Debian's dnsmasq systemd-helper adds --local-service by default,
# which can reject DNS queries from downstream AppVMs in this topology.
DNSMASQ_HELPER="/usr/share/dnsmasq/systemd-helper"
if [ -f "$DNSMASQ_HELPER" ] && grep -q -- '--local-service' "$DNSMASQ_HELPER"; then
cp "$DNSMASQ_HELPER" "${DNSMASQ_HELPER}.bak"
sed -i 's/--local-service//g' "$DNSMASQ_HELPER"
echo " Patched dnsmasq helper: removed --local-service for Qubes /32 compatibility"
fi
if command -v systemctl >/dev/null 2>&1; then
systemctl restart dnsmasq || true
fi
if ! pgrep -x dnsmasq >/dev/null 2>&1; then
dnsmasq
fi
echo ""
echo "Verifying DNS..."
if dig @127.0.0.1 google.com A +short +time=2 >/dev/null 2>&1; then
echo " Upstream DNS: OK"
else
echo " Upstream DNS: WARN (may be expected in isolated setup)"
fi
STATUS=$(dig @127.0.0.1 test.fips AAAA +time=2 2>&1 | grep 'status:' || true)
if echo "$STATUS" | grep -qE 'NXDOMAIN|SERVFAIL|NOERROR'; then
echo " .fips forwarding: OK"
else
echo " .fips forwarding: WARN"
fi
echo ""
echo "✓ DNS configured"

72
scripts/07-route-appvms.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/bin/bash
# ==============================================================================
# 07-route-appvms.sh
#
# Configure IPv6 forwarding and ip6tables rules in sys-fips so AppVMs routed
# through sys-fips can reach FIPS fd00::/8 addresses via fips0.
#
# Usage:
# sudo bash 07-route-appvms.sh
# ==============================================================================
set -euo pipefail
echo "=== sys-fips AppVM Routing Configuration ==="
echo ""
if [ "$(id -u)" -ne 0 ]; then
echo "✗ This script must be run as root (sudo)"
exit 1
fi
sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null
sysctl -w net.ipv6.conf.all.proxy_ndp=1 >/dev/null 2>&1 || true
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1 || true
echo " IPv6 forwarding enabled"
if ! ip link show fips0 >/dev/null 2>&1; then
echo " WARNING: fips0 not found (start fips first)"
fi
ip -6 route replace fd00::/8 dev fips0 2>/dev/null || true
echo " route: fd00::/8 -> fips0"
# idempotent rule refresh
ip6tables -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true
ip6tables -D FORWARD -d fd00::/8 -o fips0 -j ACCEPT 2>/dev/null || true
ip6tables -D FORWARD -s fd00::/8 -i fips0 -j ACCEPT 2>/dev/null || true
ip6tables -I FORWARD 1 -m state --state RELATED,ESTABLISHED -j ACCEPT
ip6tables -I FORWARD 2 -d fd00::/8 -o fips0 -j ACCEPT
ip6tables -I FORWARD 3 -s fd00::/8 -i fips0 -j ACCEPT
echo " ip6tables forwarding rules installed"
RCLOCAL="/rw/config/rc.local"
MARK_START="# === SYS-FIPS ROUTING START ==="
MARK_END="# === SYS-FIPS ROUTING END ==="
if [ -f "$RCLOCAL" ]; then
sed -i "/$MARK_START/,/$MARK_END/d" "$RCLOCAL"
fi
cat >> "$RCLOCAL" << 'EOF'
# === SYS-FIPS ROUTING START ===
sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null 2>&1
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1
ip6tables -I FORWARD 1 -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null
ip6tables -I FORWARD 2 -d fd00::/8 -o fips0 -j ACCEPT 2>/dev/null
ip6tables -I FORWARD 3 -s fd00::/8 -i fips0 -j ACCEPT 2>/dev/null
# === SYS-FIPS ROUTING END ===
EOF
chmod +x "$RCLOCAL"
echo ""
echo "Current fd00 routes:"
ip -6 route show | grep fd00 || true
echo ""
echo "✓ AppVM routing configured"
echo ""
echo "In dom0, point an AppVM at sys-fips:"
echo " qvm-prefs <appvm-name> netvm sys-fips"

88
scripts/08-add-peer.sh Executable file
View File

@@ -0,0 +1,88 @@
#!/bin/bash
# ==============================================================================
# 08-add-peer.sh
#
# Add a static UDP peer to /etc/fips/fips.yaml (single-node mode).
#
# Usage:
# sudo bash 08-add-peer.sh [npub] [alias] [host:port]
#
# If arguments are omitted, prompts interactively.
# ==============================================================================
set -euo pipefail
CONFIG_FILE="/etc/fips/fips.yaml"
echo "=== Add FIPS Peer ==="
echo ""
if [ "$(id -u)" -ne 0 ]; then
echo "✗ This script must be run as root (sudo)"
exit 1
fi
if [ ! -f "$CONFIG_FILE" ]; then
echo "$CONFIG_FILE not found"
echo " Run: sudo bash 02-install-fips.sh"
exit 1
fi
NPUB="${1:-}"
ALIAS="${2:-}"
ADDR="${3:-}"
if [ -z "$NPUB" ]; then
read -r -p "Peer npub: " NPUB
fi
if [ -z "$ALIAS" ]; then
read -r -p "Alias (default peer): " ALIAS
ALIAS="${ALIAS:-peer}"
fi
if [ -z "$ADDR" ]; then
read -r -p "UDP address (host:port): " ADDR
fi
if ! echo "$NPUB" | grep -q '^npub1'; then
echo "✗ npub must start with 'npub1'"
exit 1
fi
if ! echo "$ADDR" | grep -q ':'; then
echo "✗ address must be in host:port format"
exit 1
fi
tmp=$(mktemp)
if grep -q '^peers:[[:space:]]*\[\][[:space:]]*$' "$CONFIG_FILE"; then
sed 's/^peers:[[:space:]]*\[\][[:space:]]*$/peers:/' "$CONFIG_FILE" > "$tmp"
cat >> "$tmp" << EOF
- npub: "$NPUB"
alias: "$ALIAS"
addresses:
- transport: udp
addr: "$ADDR"
connect_policy: auto_connect
EOF
else
cp "$CONFIG_FILE" "$tmp"
cat >> "$tmp" << EOF
- npub: "$NPUB"
alias: "$ALIAS"
addresses:
- transport: udp
addr: "$ADDR"
connect_policy: auto_connect
EOF
fi
mv "$tmp" "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
echo ""
echo "✓ Peer added"
echo " npub: $NPUB"
echo " alias: $ALIAS"
echo " addr: $ADDR"
echo ""
echo "Restart FIPS to apply:"
echo " sudo systemctl restart fips"

View File

@@ -0,0 +1,65 @@
#!/bin/bash
# ==============================================================================
# 09-add-known-peers.sh
#
# Add known public bootstrap peers to /etc/fips/fips.yaml.
# Safe to run multiple times (idempotent by npub check).
#
# Usage:
# sudo bash 09-add-known-peers.sh
# ==============================================================================
set -euo pipefail
CONFIG_FILE="/etc/fips/fips.yaml"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ADD_PEER_SCRIPT="${SCRIPT_DIR}/08-add-peer.sh"
PEER1_NPUB="npub1qmc3cvfz0yu2hx96nq3gp55zdan2qclealn7xshgr448d3nh6lks7zel98"
PEER1_ALIAS="fips-test-node"
PEER1_ADDR="217.77.8.91:2121"
PEER2_NPUB="npub1zv58cn7v83mxvttl70w5fwjwuclfmntv9cnmv5wmz2nzz88u5urqvdx96n"
PEER2_ALIAS="fips.v0l.io"
PEER2_ADDR="fips.v0l.io:2121"
if [ "$(id -u)" -ne 0 ]; then
echo "✗ This script must be run as root (sudo)"
exit 1
fi
if [ ! -f "$CONFIG_FILE" ]; then
echo "$CONFIG_FILE not found"
echo " Run: sudo bash 02-install-fips.sh"
exit 1
fi
if [ ! -x "$ADD_PEER_SCRIPT" ]; then
echo "✗ Missing helper script: $ADD_PEER_SCRIPT"
exit 1
fi
add_if_missing() {
local npub="$1"
local alias="$2"
local addr="$3"
if grep -q "$npub" "$CONFIG_FILE"; then
echo "• already present: $alias ($npub)"
else
bash "$ADD_PEER_SCRIPT" "$npub" "$alias" "$addr"
fi
}
echo "=== Add Known Bootstrap Peers ==="
echo ""
add_if_missing "$PEER1_NPUB" "$PEER1_ALIAS" "$PEER1_ADDR"
add_if_missing "$PEER2_NPUB" "$PEER2_ALIAS" "$PEER2_ADDR"
echo ""
echo "Restarting fips service..."
systemctl restart fips
echo ""
echo "✓ Known peers configured"
echo "Check with: sudo fipsctl show peers"

132
scripts/test-connectivity.sh Executable file
View File

@@ -0,0 +1,132 @@
#!/bin/bash
# ==============================================================================
# test-connectivity.sh
#
# Unified connectivity tests for sys-fips.
#
# Modes:
# --local tests expected to run in sys-fips
# --appvm tests expected to run in an AppVM routed via sys-fips
# --full run everything reasonably applicable (default)
# ==============================================================================
set -euo pipefail
MODE="${1:---full}"
PASSED=0
FAILED=0
SKIPPED=0
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
NC='\033[0m'
pass() { echo -e "${GREEN}PASS${NC}: $1"; PASSED=$((PASSED + 1)); }
fail() { echo -e "${RED}FAIL${NC}: $1"; FAILED=$((FAILED + 1)); }
skip() { echo -e "${YELLOW}SKIP${NC}: $1"; SKIPPED=$((SKIPPED + 1)); }
is_sys_fips=false
if ip link show fips0 >/dev/null 2>&1 || [ -f /etc/systemd/system/fips.service ]; then
is_sys_fips=true
fi
echo "=== FIPS Connectivity Test ($MODE) ==="
echo ""
run_local_tests() {
if command -v fips >/dev/null 2>&1; then
pass "fips binary installed"
else
fail "fips binary missing"
fi
if systemctl is-active --quiet fips 2>/dev/null; then
pass "fips service active"
else
fail "fips service not active"
fi
if ip link show fips0 >/dev/null 2>&1; then
pass "fips0 exists"
IPV6=$(ip -6 addr show fips0 scope global 2>/dev/null | awk '/inet6/{print $2}' | cut -d/ -f1 | head -1)
if [ -n "${IPV6:-}" ]; then
pass "fips0 has IPv6 (${IPV6})"
else
fail "fips0 has no global IPv6"
fi
else
fail "fips0 missing"
fi
if ip -6 route show | grep -q 'fd00::/8'; then
pass "fd00::/8 route present"
else
fail "fd00::/8 route missing"
fi
if pgrep -x dnsmasq >/dev/null 2>&1; then
pass "dnsmasq running"
else
fail "dnsmasq not running"
fi
if dig @127.0.0.1 test.fips AAAA +time=2 >/dev/null 2>&1; then
pass ".fips DNS query path reachable"
else
fail ".fips DNS query path failed"
fi
}
run_appvm_tests() {
GW=$(ip route show default 2>/dev/null | awk '{print $3}' | head -1)
if [ -n "$GW" ]; then
pass "default gateway present ($GW)"
else
fail "default gateway missing"
fi
if [ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null || echo 1)" = "0" ]; then
pass "IPv6 enabled"
else
fail "IPv6 disabled"
fi
if command -v dig >/dev/null 2>&1; then
STATUS=$(dig test.fips AAAA +time=3 2>&1 | grep 'status:' || true)
if echo "$STATUS" | grep -qE 'NXDOMAIN|SERVFAIL|NOERROR'; then
pass ".fips DNS forwarding path reachable"
else
fail ".fips DNS forwarding path unreachable"
fi
else
skip "dig not installed"
fi
}
case "$MODE" in
--local)
run_local_tests
;;
--appvm)
run_appvm_tests
;;
--full)
if [ "$is_sys_fips" = true ]; then
run_local_tests
else
skip "local sys-fips checks skipped (not in sys-fips)"
fi
run_appvm_tests
;;
*)
echo "Usage: $0 [--local|--appvm|--full]"
exit 1
;;
esac
echo ""
echo "Results: pass=$PASSED fail=$FAILED skip=$SKIPPED"
if [ "$FAILED" -gt 0 ]; then
exit 1
fi

5
scripts/test-from-appvm.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Compatibility wrapper
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec bash "${SCRIPT_DIR}/test-connectivity.sh" --appvm

5
scripts/test-full-suite.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Compatibility wrapper
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec bash "${SCRIPT_DIR}/test-connectivity.sh" --full

5
scripts/test-local.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Compatibility wrapper
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec bash "${SCRIPT_DIR}/test-connectivity.sh" --local