mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-19 07:35:25 +00:00
feat(dev): add coold VM dev workflow
Add scripts and Lima config for managing local coold endpoint VMs, mint Flux dev host JWTs, expose coold host details on the V5 home page, and document the local development workflow.
This commit is contained in:
@@ -40,3 +40,6 @@ CHANGELOG.md
|
||||
/.workspaces
|
||||
tests/Browser/Screenshots
|
||||
tests/v4/Browser/Screenshots
|
||||
|
||||
# Local generated Lima configs
|
||||
.dev/lima/*.generated.yaml
|
||||
|
||||
@@ -30,6 +30,25 @@ You can find the installation script source [here](./scripts/install.sh).
|
||||
> Please refer to the [docs](https://coolify.io/docs/installation) for more information about the installation.
|
||||
|
||||
|
||||
## Local dev with coold VM
|
||||
|
||||
For v5 development that needs a packaged `coold` endpoint VM with Corrosion, use:
|
||||
|
||||
```bash
|
||||
scripts/dev.sh up
|
||||
```
|
||||
|
||||
This starts two Lima endpoint VMs by default, configures a WireGuard mesh between them, starts the Docker stack in the background, mints dev host JWTs with `php artisan flux:dev --caps=coold,builder`, installs them into the VMs, starts each VM `coold` agent service with builder capacity, and follows Coolify + VM agent logs by default. Flux runs with the Coolify container, not inside the VM. Set `COOLIFY_COOLD_VM_COUNT=1` for a single endpoint, `COOLIFY_COOLD_VM_ENABLED=false` to skip the VMs, `COOLIFY_COOLD_VM_BUILDER_CAPACITY=0` to disable builder capability, or `COOLIFY_DEV_FOLLOW_LOGS=false` to leave `up` detached. Use `scripts/dev.sh down` to stop the stack and VM agent; set `COOLIFY_COOLD_VM_STOP_ON_DOWN=true` if you also want the VM stopped. The dev entrypoints are intentionally kept to `scripts/dev.sh` for the full stack and `scripts/coold-vm.sh` for VM-only operations.
|
||||
|
||||
Useful Corrosion checks:
|
||||
|
||||
```bash
|
||||
scripts/dev.sh corrosion check
|
||||
scripts/dev.sh corrosion containers
|
||||
scripts/dev.sh corrosion config
|
||||
scripts/dev.sh corrosion sql 'select count(*) from service_endpoints;'
|
||||
```
|
||||
|
||||
## Container roles and Flux
|
||||
|
||||
The Coolify image can run different process roles with `COOLIFY_CONTAINER_ROLE`. The value can be a single role or a comma-separated list. If `all` is present anywhere in the list, every role starts.
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FluxDev extends Command
|
||||
{
|
||||
protected $signature = 'flux:dev
|
||||
{host_id=coold-dev : Stable coold host id}
|
||||
{--caps=coold : Comma-separated host capabilities}
|
||||
{--ttl=3600 : Token lifetime in seconds}
|
||||
{--output= : Optional path to write the token with 0600 permissions}
|
||||
{--force : Allow running outside local/development environments}';
|
||||
|
||||
protected $description = 'Run Flux development helpers.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
if (! app()->environment(['local', 'development', 'testing']) && ! $this->option('force')) {
|
||||
$this->error('This command is intended for development only. Use --force to override.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$privateKeyPath = config('flux.jwt_private_key_path');
|
||||
|
||||
if (! is_string($privateKeyPath) || $privateKeyPath === '' || ! File::isReadable($privateKeyPath)) {
|
||||
$this->error("Flux JWT private key not found at {$privateKeyPath}.");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$hostId = (string) $this->argument('host_id');
|
||||
$ttl = max(60, (int) $this->option('ttl'));
|
||||
$now = time();
|
||||
$caps = collect(explode(',', (string) $this->option('caps')))
|
||||
->map(fn (string $cap) => trim($cap))
|
||||
->filter()
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
if ($caps === []) {
|
||||
$caps = ['coold'];
|
||||
}
|
||||
|
||||
$token = JWT::encode([
|
||||
'sub' => $hostId,
|
||||
'aud' => 'coold',
|
||||
'caps' => $caps,
|
||||
'iat' => $now,
|
||||
'exp' => $now + $ttl,
|
||||
], File::get($privateKeyPath), 'ES256');
|
||||
|
||||
$output = $this->option('output');
|
||||
|
||||
if (is_string($output) && $output !== '') {
|
||||
$outputPath = Str::startsWith($output, '/') ? $output : base_path($output);
|
||||
File::ensureDirectoryExists(dirname($outputPath));
|
||||
File::put($outputPath, $token.PHP_EOL);
|
||||
chmod($outputPath, 0600);
|
||||
$this->info("Host JWT written to {$outputPath}.");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$this->line($token);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ class HomeController extends Controller
|
||||
return Inertia::render('Home', [
|
||||
'status' => 'v5-ready',
|
||||
'flux' => $fluxHealth->check(),
|
||||
'cooldHosts' => $this->cooldHosts(),
|
||||
'currentTeam' => $currentTeam instanceof Team ? [
|
||||
'id' => $currentTeam->id,
|
||||
'name' => $currentTeam->name,
|
||||
@@ -41,4 +42,29 @@ class HomeController extends Controller
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{id: string, wireguardIp: string|null, capabilities: array<int, string>, builderEnabled: bool, builderCapacity: int}>
|
||||
*/
|
||||
private function cooldHosts(): array
|
||||
{
|
||||
$baseId = (string) config('coold.dev_host_id');
|
||||
$count = max(0, (int) config('coold.dev_host_count'));
|
||||
$builderCapacity = (int) config('coold.dev_builder_capacity');
|
||||
$builderEnabled = $builderCapacity > 0;
|
||||
|
||||
if ($count === 0 || $baseId === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect(range(1, $count))
|
||||
->map(fn (int $index) => [
|
||||
'id' => $index === 1 ? $baseId : (string) config("coold.dev_host_id_{$index}", "{$baseId}-{$index}"),
|
||||
'wireguardIp' => (string) config("coold.dev_wireguard_ip_{$index}") ?: null,
|
||||
'capabilities' => $builderEnabled ? ['coold', 'builder'] : ['coold'],
|
||||
'builderEnabled' => $builderEnabled,
|
||||
'builderCapacity' => $builderCapacity,
|
||||
])
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'dev_host_count' => (int) env('COOLIFY_COOLD_VM_COUNT', 2),
|
||||
'dev_host_id' => env('COOLIFY_COOLD_DEV_HOST_ID', 'coolify-coold-dev'),
|
||||
'dev_host_id_2' => env('COOLIFY_COOLD_LIMA_INSTANCE_2', env('COOLIFY_COOLD_DEV_HOST_ID', 'coolify-coold-dev').'-2'),
|
||||
'dev_wireguard_ip_1' => env('COOLIFY_COOLD_VM_WG_IP_1', '100.64.0.10'),
|
||||
'dev_wireguard_ip_2' => env('COOLIFY_COOLD_VM_WG_IP_2', '100.64.0.11'),
|
||||
'dev_builder_capacity' => (int) env('COOLIFY_COOLD_VM_BUILDER_CAPACITY', 2),
|
||||
'dev_builder_enabled' => (int) env('COOLIFY_COOLD_VM_BUILDER_CAPACITY', 2) > 0,
|
||||
];
|
||||
@@ -2,5 +2,7 @@
|
||||
|
||||
return [
|
||||
'unix_socket_path' => env('COOLIFY_FLUX_UNIX_SOCKET_PATH', '/run/coolify/flux.sock'),
|
||||
'jwt_private_key_path' => env('COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH', storage_path('app/flux/jwt.priv')),
|
||||
'jwt_public_key_path' => env('COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH', storage_path('app/flux/jwt.pub')),
|
||||
'health_timeout_seconds' => (float) env('COOLIFY_FLUX_HEALTH_TIMEOUT_SECONDS', 1.0),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# Lima VM for testing Coolify v5 against a packaged coold endpoint.
|
||||
# Start with: scripts/coold-vm.sh up
|
||||
# Run endpoint: scripts/coold-vm.sh dev
|
||||
#
|
||||
# This file is copied into .dev/lima/coold.generated.yaml by the wrapper script,
|
||||
# with {{COOLIFY_REPO}}, {{COOLIFY_COOLD_VERSION}}, and {{COOLIFY_CORROSION_VERSION}} replaced before Lima sees it.
|
||||
|
||||
vmType: "vz"
|
||||
arch: "default"
|
||||
cpus: 4
|
||||
memory: "8GiB"
|
||||
disk: "100GiB"
|
||||
containerd:
|
||||
system: false
|
||||
user: false
|
||||
|
||||
images:
|
||||
- location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
|
||||
arch: "x86_64"
|
||||
- location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img"
|
||||
arch: "aarch64"
|
||||
|
||||
mountType: "virtiofs"
|
||||
mounts:
|
||||
- location: "{{COOLIFY_REPO}}"
|
||||
mountPoint: "/workspace/coolify"
|
||||
writable: true
|
||||
|
||||
portForwards:
|
||||
- guestPort: 5173
|
||||
hostPort: 5173
|
||||
hostIP: "127.0.0.1"
|
||||
- guestPort: 3000
|
||||
hostPort: 3000
|
||||
hostIP: "127.0.0.1"
|
||||
provision:
|
||||
- mode: system
|
||||
script: |
|
||||
#!/usr/bin/env bash
|
||||
set -euxo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
echo "[coold-vm] Updating Ubuntu package index..."
|
||||
apt-get update
|
||||
echo "[coold-vm] Installing VM dependencies..."
|
||||
apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
buildah \
|
||||
ca-certificates \
|
||||
clang \
|
||||
curl \
|
||||
git \
|
||||
iproute2 \
|
||||
iptables \
|
||||
jq \
|
||||
libssl-dev \
|
||||
mold \
|
||||
nftables \
|
||||
openssl \
|
||||
perl \
|
||||
pkg-config \
|
||||
podman \
|
||||
protobuf-compiler \
|
||||
sqlite3 \
|
||||
sudo \
|
||||
wireguard-tools
|
||||
echo "[coold-vm] Enabling Podman socket..."
|
||||
systemctl enable --now podman.socket
|
||||
cat >/usr/local/bin/rtk <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
exec "$@"
|
||||
SH
|
||||
chmod +x /usr/local/bin/rtk
|
||||
mkdir -p /etc/coolify /var/lib/corrosion /run/corrosion /var/lib/coolify-dev /var/lib/coolify-builder/work
|
||||
chmod 755 /etc/coolify
|
||||
chmod 777 /var/lib/corrosion /run/corrosion
|
||||
|
||||
install_coolify_binary() {
|
||||
name="$1"
|
||||
version="{{COOLIFY_COOLD_VERSION}}"
|
||||
arch_raw="$(uname -m)"
|
||||
case "$arch_raw" in
|
||||
x86_64) arch=amd64 ;;
|
||||
aarch64) arch=arm64 ;;
|
||||
*) echo "unsupported arch: $arch_raw" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
url="https://github.com/coollabsio/coold/releases/download/${version}/${name}-linux-${arch}.tar.gz"
|
||||
echo "[coold-vm] Installing ${name} from ${url}..."
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' RETURN
|
||||
curl -fsSL --retry 3 --max-time 120 -o "$tmpdir/${name}.tar.gz" "$url"
|
||||
tar -xzf "$tmpdir/${name}.tar.gz" -C "$tmpdir"
|
||||
test -f "$tmpdir/$name"
|
||||
install -m 0755 "$tmpdir/$name" "/usr/local/bin/${name}.tmp"
|
||||
mv "/usr/local/bin/${name}.tmp" "/usr/local/bin/${name}"
|
||||
echo "$version" > "/usr/local/bin/${name}.version"
|
||||
}
|
||||
|
||||
install_corrosion_binary() {
|
||||
version="{{COOLIFY_CORROSION_VERSION}}"
|
||||
arch_raw="$(uname -m)"
|
||||
case "$arch_raw" in
|
||||
x86_64) arch=x86_64-unknown-linux-gnu ;;
|
||||
aarch64) arch=aarch64-unknown-linux-gnu ;;
|
||||
*) echo "unsupported arch: $arch_raw" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
url="https://github.com/superfly/corrosion/releases/download/${version}/corrosion-${arch}.tar.gz"
|
||||
echo "[coold-vm] Installing corrosion from ${url}..."
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' RETURN
|
||||
curl -fsSL --retry 3 --max-time 180 -o "$tmpdir/corrosion.tar.gz" "$url"
|
||||
tar -xzf "$tmpdir/corrosion.tar.gz" -C "$tmpdir"
|
||||
test -f "$tmpdir/corrosion"
|
||||
install -m 0755 "$tmpdir/corrosion" /usr/local/bin/corrosion.tmp
|
||||
mv /usr/local/bin/corrosion.tmp /usr/local/bin/corrosion
|
||||
echo "$version" > /usr/local/bin/corrosion.version
|
||||
}
|
||||
|
||||
install_coolify_binary coold
|
||||
install_coolify_binary builder
|
||||
install_corrosion_binary
|
||||
echo "[coold-vm] Endpoint binaries installed."
|
||||
|
||||
echo "[coold-vm] Provisioning complete."
|
||||
@@ -12,8 +12,8 @@ services:
|
||||
- "${APP_PORT:-8000}:8080"
|
||||
environment:
|
||||
AUTORUN_ENABLED: false
|
||||
PUSHER_HOST: "${PUSHER_HOST}"
|
||||
PUSHER_PORT: "${PUSHER_PORT}"
|
||||
PUSHER_HOST: "${PUSHER_HOST:-}"
|
||||
PUSHER_PORT: "${PUSHER_PORT:-}"
|
||||
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
|
||||
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
|
||||
@@ -8,14 +8,18 @@ services:
|
||||
args:
|
||||
- USER_ID=${USERID:-1000}
|
||||
- GROUP_ID=${GROUPID:-1000}
|
||||
- COOLIFY_FLUX_VERSION=${COOLIFY_FLUX_VERSION:-nightly}
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:8080"
|
||||
- "${FORWARD_FLUX_PORT:-6443}:6443"
|
||||
environment:
|
||||
AUTORUN_ENABLED: false
|
||||
COOLIFY_CONTAINER_ROLE: "${COOLIFY_CONTAINER_ROLE:-all}"
|
||||
PUSHER_HOST: "${PUSHER_HOST}"
|
||||
PUSHER_PORT: "${PUSHER_PORT}"
|
||||
COOLIFY_COOLD_VERSION: "${COOLIFY_COOLD_VERSION:-nightly}"
|
||||
COOLIFY_FLUX_VERSION: "${COOLIFY_FLUX_VERSION:-nightly}"
|
||||
COOLIFY_CORROSION_VERSION: "${COOLIFY_CORROSION_VERSION:-v1.0.0}"
|
||||
PUSHER_HOST: "${PUSHER_HOST:-}"
|
||||
PUSHER_PORT: "${PUSHER_PORT:-}"
|
||||
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
|
||||
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Head } from '@inertiajs/react';
|
||||
|
||||
export default function Home({ status, currentTeam, teams, flux }) {
|
||||
export default function Home({ status, currentTeam, teams, flux, cooldHosts }) {
|
||||
return (
|
||||
<>
|
||||
<Head title="V5" />
|
||||
@@ -27,6 +27,24 @@ export default function Home({ status, currentTeam, teams, flux }) {
|
||||
{flux.socket ? <p>Socket: {flux.socket}</p> : null}
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="coold-host-heading">
|
||||
<h2 id="coold-host-heading">coold host</h2>
|
||||
|
||||
<ul>
|
||||
{cooldHosts.map((host) => (
|
||||
<li key={host.id}>
|
||||
<strong>{host.id}</strong>
|
||||
{host.wireguardIp ? ` (${host.wireguardIp})` : ''}:
|
||||
{' '}
|
||||
{host.capabilities.join(', ')}; builder{' '}
|
||||
{host.builderEnabled
|
||||
? `enabled, capacity ${host.builderCapacity}`
|
||||
: 'disabled'}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2>Current team</h2>
|
||||
|
||||
{currentTeam ? (
|
||||
|
||||
Executable
+591
@@ -0,0 +1,591 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
read_coolify_env() {
|
||||
key="$1"
|
||||
default_value="$2"
|
||||
current_value="${!key:-}"
|
||||
|
||||
if [ -n "$current_value" ]; then
|
||||
printf '%s\n' "$current_value"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -f "$ROOT/.env" ]; then
|
||||
env_value="$(grep -E "^${key}=" "$ROOT/.env" 2>/dev/null | tail -n1 | cut -d= -f2- | sed "s/^['\"]//; s/['\"]$//")"
|
||||
if [ -n "$env_value" ]; then
|
||||
printf '%s\n' "$env_value"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '%s\n' "$default_value"
|
||||
}
|
||||
|
||||
INSTANCE="$(read_coolify_env COOLIFY_COOLD_LIMA_INSTANCE coold-dev)"
|
||||
VERSION="$(read_coolify_env COOLIFY_COOLD_VERSION nightly)"
|
||||
CORROSION_VERSION="$(read_coolify_env COOLIFY_CORROSION_VERSION v1.0.0)"
|
||||
FLUX_URL="$(read_coolify_env COOLIFY_COOLD_VM_FLUX_URL http://host.lima.internal:6443)"
|
||||
BUILDER_CAPACITY="$(read_coolify_env COOLIFY_COOLD_VM_BUILDER_CAPACITY 2)"
|
||||
WG_IP="$(read_coolify_env COOLIFY_COOLD_VM_WG_IP "")"
|
||||
WG_PEER_IP="$(read_coolify_env COOLIFY_COOLD_VM_WG_PEER_IP "")"
|
||||
WG_PEER_ENDPOINT="$(read_coolify_env COOLIFY_COOLD_VM_WG_PEER_ENDPOINT "")"
|
||||
WG_PEER_PUBLIC_KEY="$(read_coolify_env COOLIFY_COOLD_VM_WG_PEER_PUBLIC_KEY "")"
|
||||
CONTAINER_SUBNET="$(read_coolify_env COOLIFY_COOLD_VM_CONTAINER_SUBNET 10.210.0.0/24)"
|
||||
CONTAINER_GATEWAY="$(read_coolify_env COOLIFY_COOLD_VM_CONTAINER_GATEWAY 10.210.0.1)"
|
||||
BUILDER_ENABLED="true"
|
||||
if [ "$BUILDER_CAPACITY" = "0" ]; then
|
||||
BUILDER_ENABLED="false"
|
||||
fi
|
||||
TEMPLATE="$ROOT/dev/lima/coold.yaml"
|
||||
GENERATED="$ROOT/.dev/lima/coold.generated.yaml"
|
||||
GUEST_COOLIFY_ROOT="/workspace/coolify"
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: scripts/coold-vm.sh <command>
|
||||
|
||||
Commands:
|
||||
up Create/start the Lima VM and install packaged coold endpoint binaries
|
||||
dev Start packaged coold + Corrosion inside the VM
|
||||
start-agent
|
||||
Start production-like coold.service + corrosion.service inside the VM
|
||||
stop-agent
|
||||
Stop the VM coold.service + corrosion.service
|
||||
logs-agent
|
||||
Follow the VM coold.service + corrosion.service logs
|
||||
install-host-jwt [token]
|
||||
Install a Flux host JWT into the VM at /etc/coolify/host-jwt
|
||||
shell Open a shell inside the VM
|
||||
status Show Lima instance status
|
||||
stop Stop the VM
|
||||
delete Delete the VM and all VM-local runtime state
|
||||
|
||||
Environment:
|
||||
COOLIFY_COOLD_LIMA_INSTANCE Override Lima instance name (default: coold-dev)
|
||||
COOLIFY_COOLD_VERSION coold release tag to install (default: nightly)
|
||||
COOLIFY_CORROSION_VERSION corrosion release tag to install (default: v1.0.0)
|
||||
COOLIFY_COOLD_VM_FLUX_URL Flux gRPC URL visible from the VM (default: http://host.lima.internal:6443)
|
||||
COOLIFY_COOLD_VM_BUILDER_CAPACITY VM builder capacity to advertise (default: 2; set 0 to disable)
|
||||
COOLIFY_COOLD_VM_WG_IP Optional WireGuard mgmt IP for this host
|
||||
COOLIFY_COOLD_VM_CONTAINER_SUBNET Podman mesh subnet for this host
|
||||
COOLIFY_COOLD_VM_CONTAINER_GATEWAY Podman mesh gateway for this host
|
||||
|
||||
Guest mounts:
|
||||
$ROOT -> $GUEST_COOLIFY_ROOT
|
||||
|
||||
Installed from:
|
||||
https://github.com/coollabsio/coold/releases/tag/$VERSION
|
||||
https://github.com/superfly/corrosion/releases/tag/$CORROSION_VERSION
|
||||
USAGE
|
||||
}
|
||||
|
||||
require_lima() {
|
||||
command -v limactl >/dev/null 2>&1 || {
|
||||
echo "limactl is required. Install Lima first: brew install lima" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
instance_exists() {
|
||||
limactl list 2>/dev/null | awk 'NR > 1 {print $1}' | grep -qx "$INSTANCE"
|
||||
}
|
||||
|
||||
instance_running() {
|
||||
limactl list 2>/dev/null | awk -v name="$INSTANCE" 'NR > 1 && $1 == name {print $2}' | grep -qx Running
|
||||
}
|
||||
|
||||
lima_shell() {
|
||||
(cd /tmp && limactl shell "$INSTANCE" -- "$@")
|
||||
}
|
||||
|
||||
vm_primary_ip() {
|
||||
lima_shell sh -lc "ip -4 route get 1.1.1.1 | awk '{print \$7; exit}'"
|
||||
}
|
||||
|
||||
wireguard_public_key() {
|
||||
lima_shell sudo sh -lc 'install -d -m 0700 /etc/wireguard; if [ ! -s /etc/wireguard/privatekey ]; then wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey; chmod 600 /etc/wireguard/privatekey; fi; cat /etc/wireguard/publickey'
|
||||
}
|
||||
|
||||
setup_wireguard() {
|
||||
local ip="${1:-$WG_IP}"
|
||||
local peer_ip="${2:-$WG_PEER_IP}"
|
||||
local peer_endpoint="${3:-$WG_PEER_ENDPOINT}"
|
||||
local peer_public_key="${4:-$WG_PEER_PUBLIC_KEY}"
|
||||
local listen_port="${5:-51820}"
|
||||
local peer_port="${6:-51820}"
|
||||
local peer_subnet="${7:-}"
|
||||
|
||||
if [ -z "$ip" ]; then
|
||||
echo "ERROR: WireGuard IP is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
wireguard_public_key >/dev/null
|
||||
|
||||
if [ -n "$peer_ip" ] && [ -n "$peer_endpoint" ] && [ -n "$peer_public_key" ]; then
|
||||
lima_shell sudo sh -lc "cat >/etc/wireguard/wg0.conf.tmp <<WG
|
||||
[Interface]
|
||||
Address = ${ip}/32
|
||||
ListenPort = ${listen_port}
|
||||
PrivateKey = \$(cat /etc/wireguard/privatekey)
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${peer_public_key}
|
||||
AllowedIPs = ${peer_ip}/32${peer_subnet:+, ${peer_subnet}}
|
||||
Endpoint = ${peer_endpoint}:${peer_port}
|
||||
PersistentKeepalive = 5
|
||||
WG
|
||||
chmod 600 /etc/wireguard/wg0.conf.tmp && mv /etc/wireguard/wg0.conf.tmp /etc/wireguard/wg0.conf && wg-quick down wg0 >/dev/null 2>&1 || true; wg-quick up wg0"
|
||||
else
|
||||
lima_shell sudo sh -lc "cat >/etc/wireguard/wg0.conf.tmp <<WG
|
||||
[Interface]
|
||||
Address = ${ip}/32
|
||||
ListenPort = ${listen_port}
|
||||
PrivateKey = \$(cat /etc/wireguard/privatekey)
|
||||
WG
|
||||
chmod 600 /etc/wireguard/wg0.conf.tmp && mv /etc/wireguard/wg0.conf.tmp /etc/wireguard/wg0.conf && wg-quick down wg0 >/dev/null 2>&1 || true; wg-quick up wg0"
|
||||
fi
|
||||
}
|
||||
|
||||
install_host_jwt() {
|
||||
token="${1:-}"
|
||||
|
||||
if [ -z "$token" ]; then
|
||||
token="$(cat)"
|
||||
fi
|
||||
|
||||
if [ -z "$token" ]; then
|
||||
echo "ERROR: host JWT is empty." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$token" | lima_shell sudo sh -c 'install -d -m 0755 /etc/coolify && cat > /tmp/coolify-host-jwt && install -m 0600 /tmp/coolify-host-jwt /etc/coolify/host-jwt && rm -f /tmp/coolify-host-jwt'
|
||||
}
|
||||
|
||||
|
||||
stop_agent_processes() {
|
||||
lima_shell sudo systemctl stop coold.service corrosion.service coold-dev-agent.service >/dev/null 2>&1 || true
|
||||
lima_shell sudo pkill -x coold >/dev/null 2>&1 || true
|
||||
lima_shell sudo pkill -x corrosion >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
ensure_podman_networks() {
|
||||
local current_subnet
|
||||
current_subnet="$(lima_shell sudo podman network inspect coolify-default-mesh --format '{{range .Subnets}}{{.Subnet}}{{end}}' 2>/dev/null || true)"
|
||||
|
||||
if [ -n "$current_subnet" ] && [ "$current_subnet" != "$CONTAINER_SUBNET" ]; then
|
||||
lima_shell sudo podman network rm coolify-default-mesh >/dev/null
|
||||
current_subnet=""
|
||||
fi
|
||||
|
||||
if [ -z "$current_subnet" ]; then
|
||||
lima_shell sudo podman network create --subnet "$CONTAINER_SUBNET" --gateway "$CONTAINER_GATEWAY" coolify-default-mesh >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
write_runtime_config() {
|
||||
local gossip_addr="127.0.0.1:8787"
|
||||
local bootstrap=""
|
||||
|
||||
if [ -n "$WG_IP" ]; then
|
||||
gossip_addr="$WG_IP:8787"
|
||||
fi
|
||||
|
||||
if [ -n "$WG_PEER_IP" ]; then
|
||||
bootstrap="\"$WG_PEER_IP:8787\""
|
||||
fi
|
||||
|
||||
lima_shell sudo install -d -m 0755 /etc/corrosion/schemas /etc/coolify /run/coolify /var/lib/corrosion /var/run/corrosion /var/lib/coolify-dev /var/lib/coolify-builder/work
|
||||
|
||||
lima_shell sudo tee /etc/corrosion/schemas/coolify.sql >/dev/null <<'SQL'
|
||||
CREATE TABLE service_endpoints (
|
||||
container_id TEXT NOT NULL DEFAULT '' PRIMARY KEY,
|
||||
container_name TEXT NOT NULL DEFAULT '',
|
||||
namespace TEXT NOT NULL DEFAULT '',
|
||||
host_mgmt_ip TEXT NOT NULL DEFAULT '',
|
||||
container_ip TEXT NOT NULL DEFAULT '',
|
||||
state TEXT NOT NULL DEFAULT '',
|
||||
health TEXT NOT NULL DEFAULT 'unknown',
|
||||
updated_at INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
SQL
|
||||
|
||||
lima_shell sudo tee /etc/corrosion/config.toml >/dev/null <<TOML
|
||||
[db]
|
||||
path = "/var/lib/corrosion/corrosion.db"
|
||||
schema_paths = ["/etc/corrosion/schemas"]
|
||||
|
||||
[gossip]
|
||||
addr = "$gossip_addr"
|
||||
bootstrap = [$bootstrap]
|
||||
plaintext = true
|
||||
|
||||
[api]
|
||||
addr = "127.0.0.1:8080"
|
||||
|
||||
[admin]
|
||||
path = "/var/run/corrosion/admin.sock"
|
||||
TOML
|
||||
|
||||
}
|
||||
|
||||
run_foreground() {
|
||||
stop_agent_processes
|
||||
write_runtime_config
|
||||
ensure_podman_networks
|
||||
install_mesh_firewall
|
||||
|
||||
(cd /tmp && limactl shell "$INSTANCE" -- sudo \
|
||||
env COOLIFY_COOLD_HOST_MGMT_IP="${WG_IP:-127.0.0.1}" \
|
||||
COOLIFY_COOLD_FLUX_URL="$FLUX_URL" \
|
||||
COOLIFY_COOLD_BUILDER_ENABLED="$BUILDER_ENABLED" \
|
||||
COOLIFY_COOLD_BUILDER_CAPACITY="$BUILDER_CAPACITY" \
|
||||
CONTAINER_GATEWAY="$CONTAINER_GATEWAY" \
|
||||
bash -s) <<'RUNNER'
|
||||
set -euo pipefail
|
||||
|
||||
echo "coold: $(/usr/local/bin/coold --version)"
|
||||
echo "builder: $(/usr/local/bin/builder --version)"
|
||||
echo "corrosion: $(/usr/local/bin/corrosion --version 2>/dev/null || cat /usr/local/bin/corrosion.version)"
|
||||
echo "starting packaged coold endpoint with corrosion in local mode"
|
||||
|
||||
cleanup() {
|
||||
jobs -pr | xargs -r kill || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
/usr/local/bin/corrosion agent --config /etc/corrosion/config.toml &
|
||||
|
||||
COOLIFY_COOLD_HOST_MGMT_IP="${COOLIFY_COOLD_HOST_MGMT_IP:-127.0.0.1}" \
|
||||
COOLIFY_COOLD_PODMAN_SOCKET="${COOLIFY_COOLD_PODMAN_SOCKET:-/run/podman/podman.sock}" \
|
||||
COOLIFY_COOLD_CORROSION_URL="${COOLIFY_COOLD_CORROSION_URL:-http://127.0.0.1:8080}" \
|
||||
COOLIFY_COOLD_NAMESPACES="${COOLIFY_COOLD_NAMESPACES:-default:coolify-default-mesh:$CONTAINER_GATEWAY}" \
|
||||
COOLIFY_COOLD_DNS_ZONE="${COOLIFY_COOLD_DNS_ZONE:-coolify.internal}" \
|
||||
COOLIFY_COOLD_API_BIND="${COOLIFY_COOLD_API_BIND:-${WG_IP:-127.0.0.1}:8443}" \
|
||||
COOLIFY_COOLD_API_TOKEN_FILE="${COOLIFY_COOLD_API_TOKEN_FILE:-/etc/coolify/api-token}" \
|
||||
COOLIFY_COOLD_FLUX_URL="${COOLIFY_COOLD_FLUX_URL:-http://host.lima.internal:6443}" \
|
||||
COOLIFY_COOLD_HOST_JWT_PATH="${COOLIFY_COOLD_HOST_JWT_PATH:-/etc/coolify/host-jwt}" \
|
||||
COOLIFY_COOLD_BUILDER_ENABLED="${COOLIFY_COOLD_BUILDER_ENABLED:-true}" \
|
||||
COOLIFY_COOLD_BUILDER_CAPACITY="${COOLIFY_COOLD_BUILDER_CAPACITY:-2}" \
|
||||
COOLIFY_COOLD_BUILDER_BIN="${COOLIFY_COOLD_BUILDER_BIN:-/usr/local/bin/builder}" \
|
||||
COOLIFY_COOLD_BUILDER_WORK_DIR="${COOLIFY_COOLD_BUILDER_WORK_DIR:-/var/lib/coolify-builder/work}" \
|
||||
/usr/local/bin/coold &
|
||||
|
||||
wait
|
||||
RUNNER
|
||||
}
|
||||
|
||||
install_mesh_firewall() {
|
||||
lima_shell sudo install -d -m 0755 /etc/coolify
|
||||
lima_shell sudo touch /etc/coolify/allow.rules /etc/coolify/allow.nft
|
||||
|
||||
lima_shell sudo tee /etc/coolify/bridge-fw.nft >/dev/null <<NFT
|
||||
add table bridge coolify_bridge
|
||||
add chain bridge coolify_bridge coolify_allow
|
||||
flush chain bridge coolify_bridge coolify_allow
|
||||
add chain bridge coolify_bridge coolify_intra
|
||||
flush chain bridge coolify_bridge coolify_intra
|
||||
add rule bridge coolify_bridge coolify_intra jump coolify_allow
|
||||
add rule bridge coolify_bridge coolify_intra drop
|
||||
add chain bridge coolify_bridge forward { type filter hook forward priority -200; policy accept; }
|
||||
flush chain bridge coolify_bridge forward
|
||||
add rule bridge coolify_bridge forward meta protocol != ip accept
|
||||
add rule bridge coolify_bridge forward ct state established,related accept
|
||||
add rule bridge coolify_bridge forward ip saddr { $CONTAINER_SUBNET } jump coolify_intra
|
||||
add rule bridge coolify_bridge forward ip daddr { $CONTAINER_SUBNET } jump coolify_intra
|
||||
NFT
|
||||
|
||||
lima_shell sudo tee /etc/systemd/system/coolify-mesh-fw.service >/dev/null <<UNIT
|
||||
[Unit]
|
||||
Description=Coolify mesh firewall rules
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/sbin/sysctl -w net.ipv4.ip_forward=1
|
||||
ExecStart=/bin/sh -c "/usr/sbin/iptables -t nat -C POSTROUTING -s $CONTAINER_SUBNET -o wg0 -j RETURN 2>/dev/null || /usr/sbin/iptables -t nat -I POSTROUTING -s $CONTAINER_SUBNET -o wg0 -j RETURN"
|
||||
ExecStart=/bin/sh -c "/usr/sbin/iptables -D FORWARD -s $CONTAINER_SUBNET -j ACCEPT 2>/dev/null || true"
|
||||
ExecStart=/bin/sh -c "/usr/sbin/iptables -D FORWARD -d $CONTAINER_SUBNET -j ACCEPT 2>/dev/null || true"
|
||||
ExecStart=/bin/sh -c "/usr/sbin/iptables -N COOLIFY-ALLOW 2>/dev/null || true"
|
||||
ExecStart=/bin/sh -c "/usr/sbin/iptables -N COOLIFY-INTRA 2>/dev/null || true"
|
||||
ExecStart=/usr/sbin/iptables -F COOLIFY-ALLOW
|
||||
ExecStart=/usr/sbin/iptables -F COOLIFY-INTRA
|
||||
ExecStart=/usr/sbin/iptables -A COOLIFY-INTRA -j COOLIFY-ALLOW
|
||||
ExecStart=/usr/sbin/iptables -A COOLIFY-INTRA -j DROP
|
||||
ExecStart=/bin/sh -c "/usr/sbin/iptables -C FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || /usr/sbin/iptables -I FORWARD 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT"
|
||||
ExecStart=/bin/sh -c "/usr/sbin/iptables -C FORWARD -d $CONTAINER_SUBNET -j COOLIFY-INTRA 2>/dev/null || /usr/sbin/iptables -A FORWARD -d $CONTAINER_SUBNET -j COOLIFY-INTRA"
|
||||
ExecStart=/bin/sh -c "/usr/sbin/iptables -C FORWARD -s $CONTAINER_SUBNET -j COOLIFY-INTRA 2>/dev/null || /usr/sbin/iptables -A FORWARD -s $CONTAINER_SUBNET -j COOLIFY-INTRA"
|
||||
ExecStart=/bin/sh -c "nft delete table bridge coolify_bridge 2>/dev/null || true"
|
||||
ExecStart=/bin/sh -c "nft -f /etc/coolify/bridge-fw.nft"
|
||||
ExecStart=/bin/sh -c "[ -s /etc/coolify/allow.nft ] && nft -f /etc/coolify/allow.nft || true"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNIT
|
||||
|
||||
lima_shell sudo systemctl daemon-reload
|
||||
lima_shell sudo systemctl enable --now coolify-mesh-fw.service
|
||||
lima_shell sudo systemctl restart coolify-mesh-fw.service
|
||||
}
|
||||
|
||||
start_agent() {
|
||||
stop_agent_processes
|
||||
write_runtime_config
|
||||
ensure_podman_networks
|
||||
install_mesh_firewall
|
||||
lima_shell sudo sh -c 'if [ ! -s /etc/coolify/api-token ]; then openssl rand -hex 32 > /etc/coolify/api-token.tmp && chmod 600 /etc/coolify/api-token.tmp && mv /etc/coolify/api-token.tmp /etc/coolify/api-token; fi'
|
||||
|
||||
lima_shell sudo tee /etc/systemd/system/corrosion.service >/dev/null <<'UNIT'
|
||||
[Unit]
|
||||
Description=Corrosion local state store
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/corrosion agent --config /etc/corrosion/config.toml
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNIT
|
||||
|
||||
lima_shell sudo tee /etc/systemd/system/coold.service >/dev/null <<UNIT
|
||||
[Unit]
|
||||
Description=Coolify host agent
|
||||
Wants=corrosion.service
|
||||
After=corrosion.service network-online.target podman.socket coolify-mesh-fw.service
|
||||
|
||||
[Service]
|
||||
Environment=COOLIFY_COOLD_HOST_MGMT_IP=${WG_IP:-127.0.0.1}
|
||||
Environment=COOLIFY_COOLD_PODMAN_SOCKET=/run/podman/podman.sock
|
||||
Environment=COOLIFY_COOLD_CORROSION_URL=http://127.0.0.1:8080
|
||||
Environment=COOLIFY_COOLD_NAMESPACES=default:coolify-default-mesh:$CONTAINER_GATEWAY
|
||||
Environment=COOLIFY_COOLD_DNS_ZONE=coolify.internal
|
||||
Environment=COOLIFY_COOLD_API_BIND=${WG_IP:-127.0.0.1}:8443
|
||||
Environment=COOLIFY_COOLD_API_TOKEN_FILE=/etc/coolify/api-token
|
||||
Environment=COOLIFY_COOLD_FLUX_URL=$FLUX_URL
|
||||
Environment=COOLIFY_COOLD_HOST_JWT_PATH=/etc/coolify/host-jwt
|
||||
Environment=COOLIFY_COOLD_BUILDER_ENABLED=$BUILDER_ENABLED
|
||||
Environment=COOLIFY_COOLD_BUILDER_CAPACITY=$BUILDER_CAPACITY
|
||||
Environment=COOLIFY_COOLD_BUILDER_BIN=/usr/local/bin/builder
|
||||
Environment=COOLIFY_COOLD_BUILDER_WORK_DIR=/var/lib/coolify-builder/work
|
||||
ExecStart=/usr/local/bin/coold
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_NET_RAW
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNIT
|
||||
|
||||
lima_shell sudo systemctl daemon-reload
|
||||
lima_shell sudo systemctl enable --now corrosion.service coold.service
|
||||
lima_shell sudo systemctl restart corrosion.service coold.service
|
||||
}
|
||||
|
||||
generate_yaml() {
|
||||
mkdir -p "$(dirname "$GENERATED")"
|
||||
sed \
|
||||
-e "s#{{COOLIFY_REPO}}#$ROOT#g" \
|
||||
-e "s#{{COOLIFY_COOLD_VERSION}}#$VERSION#g" \
|
||||
-e "s#{{COOLIFY_CORROSION_VERSION}}#$CORROSION_VERSION#g" \
|
||||
"$TEMPLATE" > "$GENERATED"
|
||||
}
|
||||
|
||||
start_vm() {
|
||||
generate_yaml
|
||||
|
||||
if instance_running; then
|
||||
return
|
||||
fi
|
||||
|
||||
if instance_exists; then
|
||||
limactl start --tty=false "$INSTANCE"
|
||||
else
|
||||
limactl start --tty=false --name="$INSTANCE" "$GENERATED"
|
||||
fi
|
||||
}
|
||||
|
||||
latest_lima_message() {
|
||||
log_file="$HOME/.lima/$INSTANCE/ha.stderr.log"
|
||||
|
||||
if [ ! -f "$log_file" ]; then
|
||||
echo "creating Lima instance directory"
|
||||
return 0
|
||||
fi
|
||||
|
||||
grep -E '"msg":"(Starting VZ|Waiting for|The essential requirement|Executing /mnt/lima|SSH Local Port|Port is available|Attempting|Downloaded|Using the existing instance|The instance)' "$log_file" | tail -n 1 | sed -E 's/^.*"msg":"(.*)","time":.*$/\1/' | sed 's/\\"/"/g' || true
|
||||
}
|
||||
|
||||
wait_for_lima_start() {
|
||||
start_vm &
|
||||
start_pid=$!
|
||||
elapsed=0
|
||||
|
||||
while kill -0 "$start_pid" 2>/dev/null; do
|
||||
if instance_exists && lima_shell true >/dev/null 2>&1; then
|
||||
status="$(lima_shell cloud-init status 2>/dev/null || true)"
|
||||
printf '==> [%3ss] Lima start: guest SSH ready, cloud-init %s
|
||||
' "$elapsed" "${status:-unknown}"
|
||||
lima_shell sudo sh -c 'test -f /var/log/cloud-init-output.log && tail -n 12 /var/log/cloud-init-output.log || true' 2>/dev/null | awk '{ print "[guest] " $0; fflush(); }' || true
|
||||
|
||||
if ! printf '%s' "$status" | grep -q running; then
|
||||
kill "$start_pid" >/dev/null 2>&1 || true
|
||||
break
|
||||
fi
|
||||
else
|
||||
message="$(latest_lima_message)"
|
||||
printf '==> [%3ss] Lima start: %s
|
||||
' "$elapsed" "${message:-booting}"
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
elapsed=$((elapsed + 5))
|
||||
done
|
||||
|
||||
wait "$start_pid" 2>/dev/null || true
|
||||
}
|
||||
|
||||
wait_for_guest_provisioning() {
|
||||
echo "==> Waiting for guest SSH..."
|
||||
until instance_exists && lima_shell true >/dev/null 2>&1; do
|
||||
message="$(latest_lima_message)"
|
||||
printf '==> Waiting for guest SSH: %s
|
||||
' "${message:-booting}"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
status="$(lima_shell cloud-init status 2>/dev/null || true)"
|
||||
|
||||
if printf '%s' "$status" | grep -q running; then
|
||||
echo "==> Guest SSH is ready; streaming cloud-init output until provisioning completes..."
|
||||
(
|
||||
lima_shell sudo sh -c 'touch /var/log/cloud-init-output.log; tail -n 40 -F /var/log/cloud-init-output.log' 2>/dev/null | awk '{ print "[guest] " $0; fflush(); }'
|
||||
) &
|
||||
tail_pid=$!
|
||||
|
||||
while true; do
|
||||
status="$(lima_shell cloud-init status 2>/dev/null || true)"
|
||||
printf '==> Guest cloud-init: %s
|
||||
' "${status:-unknown}"
|
||||
|
||||
if ! printf '%s' "$status" | grep -q running; then
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
|
||||
kill "$tail_pid" >/dev/null 2>&1 || true
|
||||
wait "$tail_pid" 2>/dev/null || true
|
||||
else
|
||||
printf '==> Guest cloud-init: %s
|
||||
' "${status:-unknown}"
|
||||
fi
|
||||
|
||||
echo "==> Final guest provisioning status:"
|
||||
lima_shell bash -lc 'cloud-init status 2>/dev/null || true; if command -v coold >/dev/null; then coold --version; else echo "coold not installed yet"; fi; if command -v corrosion >/dev/null; then echo "corrosion installed"; else echo "corrosion not installed yet"; fi; if command -v builder >/dev/null; then echo "builder installed"; else echo "builder not installed yet"; fi; true' | awk '{ print "[guest] " $0; fflush(); }' || true
|
||||
|
||||
if ! lima_shell bash -lc 'command -v coold >/dev/null && command -v corrosion >/dev/null && command -v builder >/dev/null' >/dev/null 2>&1; then
|
||||
echo "ERROR: VM provisioning finished but coold, corrosion, or builder is missing." >&2
|
||||
echo "Check with: scripts/coold-vm.sh shell" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
up_with_logs() {
|
||||
echo "==> Coolify coold VM: $INSTANCE"
|
||||
echo "==> coold package tag: $VERSION"
|
||||
echo "==> corrosion package tag: $CORROSION_VERSION"
|
||||
echo "==> Lima config: $GENERATED"
|
||||
|
||||
wait_for_lima_start
|
||||
wait_for_guest_provisioning
|
||||
|
||||
echo "==> VM is ready. Run: scripts/coold-vm.sh dev"
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
case "$cmd" in
|
||||
up)
|
||||
require_lima
|
||||
up_with_logs
|
||||
;;
|
||||
dev)
|
||||
require_lima
|
||||
start_vm >/dev/null
|
||||
run_foreground
|
||||
;;
|
||||
start-agent)
|
||||
require_lima
|
||||
start_vm >/dev/null
|
||||
start_agent
|
||||
;;
|
||||
stop-agent)
|
||||
require_lima
|
||||
if instance_running; then
|
||||
stop_agent_processes
|
||||
fi
|
||||
;;
|
||||
logs-agent)
|
||||
require_lima
|
||||
start_vm >/dev/null
|
||||
exec bash -lc "cd /tmp && limactl shell '$INSTANCE' -- sudo journalctl -u coold.service -u corrosion.service -f -n 100"
|
||||
;;
|
||||
install-host-jwt)
|
||||
require_lima
|
||||
start_vm >/dev/null
|
||||
install_host_jwt "${2:-}"
|
||||
;;
|
||||
wg-public-key)
|
||||
require_lima
|
||||
start_vm >/dev/null
|
||||
wireguard_public_key
|
||||
;;
|
||||
vm-ip)
|
||||
require_lima
|
||||
start_vm >/dev/null
|
||||
vm_primary_ip
|
||||
;;
|
||||
setup-wireguard)
|
||||
require_lima
|
||||
start_vm >/dev/null
|
||||
setup_wireguard "${2:-}" "${3:-}" "${4:-}" "${5:-}" "${6:-}" "${7:-}" "${8:-}"
|
||||
;;
|
||||
shell)
|
||||
require_lima
|
||||
start_vm >/dev/null
|
||||
exec bash -lc "cd /tmp && limactl shell '$INSTANCE' -- env TERM=xterm-256color SYSTEMD_PAGER=cat SYSTEMD_LESS=FRXMK bash -l"
|
||||
;;
|
||||
status)
|
||||
require_lima
|
||||
if instance_exists; then
|
||||
exec limactl list "$INSTANCE"
|
||||
fi
|
||||
|
||||
echo "No Lima instance named '$INSTANCE' exists yet."
|
||||
echo "Run: scripts/coold-vm.sh up"
|
||||
;;
|
||||
stop)
|
||||
require_lima
|
||||
exec limactl stop "$INSTANCE"
|
||||
;;
|
||||
delete|destroy)
|
||||
require_lima
|
||||
exec limactl delete --force --tty=false "$INSTANCE"
|
||||
;;
|
||||
-h|--help|help|"")
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "unknown command: $cmd" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+680
@@ -0,0 +1,680 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
read_coolify_env() {
|
||||
key="$1"
|
||||
default_value="$2"
|
||||
current_value="${!key:-}"
|
||||
|
||||
if [ -n "$current_value" ]; then
|
||||
printf '%s\n' "$current_value"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -f .env ]; then
|
||||
env_value="$(grep -E "^${key}=" .env 2>/dev/null | tail -n1 | cut -d= -f2- | sed "s/^['\"]//; s/['\"]$//")"
|
||||
if [ -n "$env_value" ]; then
|
||||
printf '%s\n' "$env_value"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '%s\n' "$default_value"
|
||||
}
|
||||
|
||||
coold_vm_count() {
|
||||
local count
|
||||
count="$(read_coolify_env COOLIFY_COOLD_VM_COUNT 2)"
|
||||
|
||||
if [ "$count" != "1" ] && [ "$count" != "2" ]; then
|
||||
echo "ERROR: COOLIFY_COOLD_VM_COUNT supports 1 or 2 for now." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$count"
|
||||
}
|
||||
|
||||
coold_vm_instance() {
|
||||
local index="$1"
|
||||
local base
|
||||
base="$(read_coolify_env COOLIFY_COOLD_LIMA_INSTANCE coold-dev)"
|
||||
|
||||
if [ "$index" = "1" ]; then
|
||||
printf '%s\n' "$base"
|
||||
return
|
||||
fi
|
||||
|
||||
read_coolify_env "COOLIFY_COOLD_LIMA_INSTANCE_${index}" "${base}-${index}"
|
||||
}
|
||||
|
||||
coold_vm_wg_ip() {
|
||||
local index="$1"
|
||||
read_coolify_env "COOLIFY_COOLD_VM_WG_IP_${index}" "100.64.0.$((9 + index))"
|
||||
}
|
||||
|
||||
coold_vm_wg_port() {
|
||||
local index="$1"
|
||||
read_coolify_env "COOLIFY_COOLD_VM_WG_PORT_${index}" "$((51820 + index))"
|
||||
}
|
||||
|
||||
coold_vm_container_subnet() {
|
||||
local index="$1"
|
||||
read_coolify_env "COOLIFY_COOLD_VM_CONTAINER_SUBNET_${index}" "10.210.$((index - 1)).0/24"
|
||||
}
|
||||
|
||||
coold_vm_container_gateway() {
|
||||
local index="$1"
|
||||
read_coolify_env "COOLIFY_COOLD_VM_CONTAINER_GATEWAY_${index}" "10.210.$((index - 1)).1"
|
||||
}
|
||||
|
||||
coold_vm() {
|
||||
local index="$1"
|
||||
shift
|
||||
COOLIFY_COOLD_LIMA_INSTANCE="$(coold_vm_instance "$index")" \
|
||||
COOLIFY_COOLD_VM_WG_IP="$(coold_vm_wg_ip "$index")" \
|
||||
COOLIFY_COOLD_VM_CONTAINER_SUBNET="$(coold_vm_container_subnet "$index")" \
|
||||
COOLIFY_COOLD_VM_CONTAINER_GATEWAY="$(coold_vm_container_gateway "$index")" \
|
||||
scripts/coold-vm.sh "$@"
|
||||
}
|
||||
|
||||
mint_host_jwt_for_host() {
|
||||
local host_id="$1"
|
||||
local attempts=60
|
||||
local output
|
||||
local caps
|
||||
local builder_capacity
|
||||
|
||||
builder_capacity="$(read_coolify_env COOLIFY_COOLD_VM_BUILDER_CAPACITY 2)"
|
||||
caps="coold"
|
||||
if [ "$builder_capacity" != "0" ]; then
|
||||
caps="coold,builder"
|
||||
fi
|
||||
|
||||
for attempt in $(seq 1 "$attempts"); do
|
||||
if output="$(spin exec -T coolify php artisan flux:dev "$host_id" --caps="$caps" 2>&1)"; then
|
||||
printf '%s\n' "$output" | tail -n 1
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "==> Waiting for Flux dev JWT key for ${host_id} (${attempt}/${attempts})..." >&2
|
||||
printf '%s\n' "$output" | tail -n 3 | sed 's/^/[coolify] /' >&2
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "ERROR: Could not mint Flux dev host JWT for ${host_id}." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
setup_wireguard_mesh() {
|
||||
local count="$1"
|
||||
|
||||
if [ "$count" -lt 2 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "==> Configuring WireGuard mesh for ${count} coold VMs..."
|
||||
|
||||
local pub1 pub2 ip1 ip2 port1 port2 subnet1 subnet2
|
||||
pub1="$(coold_vm 1 wg-public-key)"
|
||||
pub2="$(coold_vm 2 wg-public-key)"
|
||||
ip1="$(coold_vm_wg_ip 1)"
|
||||
ip2="$(coold_vm_wg_ip 2)"
|
||||
port1="$(coold_vm_wg_port 1)"
|
||||
port2="$(coold_vm_wg_port 2)"
|
||||
subnet1="$(coold_vm_container_subnet 1)"
|
||||
subnet2="$(coold_vm_container_subnet 2)"
|
||||
|
||||
coold_vm 1 setup-wireguard "$ip1" "$ip2" "host.lima.internal" "$pub2" "$port1" "$port2" "$subnet2"
|
||||
coold_vm 2 setup-wireguard "$ip2" "$ip1" "host.lima.internal" "$pub1" "$port2" "$port1" "$subnet1"
|
||||
}
|
||||
|
||||
follow_logs() {
|
||||
local coold_vm_enabled="$1"
|
||||
local count
|
||||
local vm_logs_pids=""
|
||||
count="$(coold_vm_count)"
|
||||
|
||||
echo "==> Following dev logs. Press Ctrl-C to stop the dev environment."
|
||||
|
||||
cleanup_logs() {
|
||||
for pid in $vm_logs_pids; do
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
done
|
||||
}
|
||||
|
||||
stop_from_signal() {
|
||||
trap - INT TERM EXIT
|
||||
cleanup_logs
|
||||
echo
|
||||
echo "==> Ctrl-C received; stopping dev environment..."
|
||||
down
|
||||
exit 130
|
||||
}
|
||||
|
||||
trap cleanup_logs EXIT
|
||||
trap stop_from_signal INT TERM
|
||||
|
||||
if [ "$coold_vm_enabled" != "false" ]; then
|
||||
for index in $(seq 1 "$count"); do
|
||||
instance="$(coold_vm_instance "$index")"
|
||||
coold_vm "$index" logs-agent | sed "s/^/[${instance}] /" &
|
||||
vm_logs_pids="$vm_logs_pids $!"
|
||||
done
|
||||
fi
|
||||
|
||||
spin logs -f
|
||||
}
|
||||
|
||||
up() {
|
||||
local coold_vm_enabled
|
||||
local follow_dev_logs
|
||||
local count
|
||||
coold_vm_enabled="$(read_coolify_env COOLIFY_COOLD_VM_ENABLED true)"
|
||||
follow_dev_logs="$(read_coolify_env COOLIFY_DEV_FOLLOW_LOGS true)"
|
||||
count="$(coold_vm_count)"
|
||||
|
||||
if [ "$coold_vm_enabled" != "false" ]; then
|
||||
echo "==> Starting ${count} Coolify coold VM(s) before Spin..."
|
||||
for index in $(seq 1 "$count"); do
|
||||
coold_vm "$index" up
|
||||
done
|
||||
setup_wireguard_mesh "$count"
|
||||
else
|
||||
echo "==> COOLIFY_COOLD_VM_ENABLED=false; skipping coold VM."
|
||||
fi
|
||||
|
||||
echo "==> Starting Coolify Docker stack with Spin..."
|
||||
spin up -d "$@"
|
||||
|
||||
if [ "$coold_vm_enabled" != "false" ]; then
|
||||
for index in $(seq 1 "$count"); do
|
||||
host_id="$(coold_vm_instance "$index")"
|
||||
echo "==> Minting Flux dev host JWT for ${host_id}..."
|
||||
host_jwt="$(mint_host_jwt_for_host "$host_id")"
|
||||
echo "==> Installing host JWT into ${host_id}..."
|
||||
coold_vm "$index" install-host-jwt "$host_jwt"
|
||||
done
|
||||
|
||||
for index in $(seq 1 "$count"); do
|
||||
echo "==> Starting coold VM agent service on $(coold_vm_instance "$index")..."
|
||||
if [ "$count" -ge 2 ]; then
|
||||
peer_index=1
|
||||
if [ "$index" = "1" ]; then
|
||||
peer_index=2
|
||||
fi
|
||||
COOLIFY_COOLD_VM_WG_PEER_IP="$(coold_vm_wg_ip "$peer_index")" coold_vm "$index" start-agent
|
||||
else
|
||||
coold_vm "$index" start-agent
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$follow_dev_logs" = "false" ]; then
|
||||
echo "==> Dev environment is ready. Use 'spin logs -f' or 'scripts/coold-vm.sh logs-agent' to follow logs."
|
||||
return
|
||||
fi
|
||||
|
||||
follow_logs "$coold_vm_enabled"
|
||||
}
|
||||
|
||||
down() {
|
||||
local coold_vm_enabled
|
||||
local stop_coold_vm
|
||||
coold_vm_enabled="$(read_coolify_env COOLIFY_COOLD_VM_ENABLED true)"
|
||||
stop_coold_vm="$(read_coolify_env COOLIFY_COOLD_VM_STOP_ON_DOWN false)"
|
||||
|
||||
if [ "$coold_vm_enabled" != "false" ]; then
|
||||
for index in $(seq 1 "$(coold_vm_count)"); do
|
||||
echo "==> Stopping coold VM agent service on $(coold_vm_instance "$index")..."
|
||||
coold_vm "$index" stop-agent || true
|
||||
done
|
||||
fi
|
||||
|
||||
echo "==> Stopping Coolify Docker stack with Spin..."
|
||||
spin down "$@"
|
||||
|
||||
if [ "$stop_coold_vm" = "true" ]; then
|
||||
echo "==> Stopping Coolify coold VM..."
|
||||
for index in $(seq 1 "$(coold_vm_count)"); do
|
||||
coold_vm "$index" stop
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
corrosion_for_each_vm() {
|
||||
local label="$1"
|
||||
shift
|
||||
local count
|
||||
local script
|
||||
count="$(coold_vm_count)"
|
||||
script="$(cat)"
|
||||
|
||||
for index in $(seq 1 "$count"); do
|
||||
instance="$(coold_vm_instance "$index")"
|
||||
echo "--- ${instance}: ${label} ---"
|
||||
printf '%s\n' "$script" | COOLIFY_COOLD_LIMA_INSTANCE="$instance" scripts/coold-vm.sh shell "$@"
|
||||
done
|
||||
}
|
||||
|
||||
corrosion_check() {
|
||||
local count
|
||||
count="$(coold_vm_count)"
|
||||
|
||||
for index in $(seq 1 "$count"); do
|
||||
instance="$(coold_vm_instance "$index")"
|
||||
peer_index=1
|
||||
if [ "$index" = "1" ]; then
|
||||
peer_index=2
|
||||
fi
|
||||
peer_ip="$(coold_vm_wg_ip "$peer_index")"
|
||||
|
||||
echo "--- ${instance}: check ---"
|
||||
COOLIFY_COOLD_LIMA_INSTANCE="$instance" scripts/coold-vm.sh shell <<SH
|
||||
set -e
|
||||
printf 'services: '
|
||||
systemctl is-active corrosion.service || true
|
||||
printf 'coold: '
|
||||
systemctl is-active coold.service || true
|
||||
printf 'wireguard: '
|
||||
sudo wg show wg0 >/dev/null 2>&1 && echo active || echo unavailable
|
||||
printf 'peer ping (${peer_ip}): '
|
||||
ping -c 1 -W 2 ${peer_ip} >/dev/null 2>&1 && echo ok || echo failed
|
||||
printf 'gossip: '
|
||||
awk '/^\[gossip\]/{section=1; next} /^\[/{section=0} section && /^addr =|^bootstrap =/ {printf "%s ", \$0} END {print ""}' /etc/corrosion/config.toml
|
||||
printf 'registered containers: '
|
||||
sudo sqlite3 /var/lib/corrosion/corrosion.db 'select count(*) from service_endpoints;' 2>/dev/null || echo unavailable
|
||||
SH
|
||||
done
|
||||
}
|
||||
|
||||
corrosion_containers() {
|
||||
corrosion_for_each_vm containers <<'SH'
|
||||
echo '[corrosion service_endpoints]'
|
||||
sudo sqlite3 -header -column /var/lib/corrosion/corrosion.db \
|
||||
'select container_id, container_name, namespace, host_mgmt_ip, container_ip, state, health, updated_at from service_endpoints order by updated_at desc;' 2>/dev/null \
|
||||
|| echo 'service_endpoints table unavailable'
|
||||
|
||||
echo
|
||||
echo '[rootful podman]'
|
||||
sudo podman ps --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}' 2>/dev/null \
|
||||
|| echo 'rootful podman unavailable'
|
||||
|
||||
echo
|
||||
echo '[rootless podman]'
|
||||
podman ps --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}' 2>/dev/null \
|
||||
|| echo 'rootless podman unavailable'
|
||||
SH
|
||||
}
|
||||
|
||||
corrosion_config() {
|
||||
corrosion_for_each_vm config <<'SH'
|
||||
sudo cat /etc/corrosion/config.toml
|
||||
SH
|
||||
}
|
||||
|
||||
corrosion_logs() {
|
||||
local index="${1:-1}"
|
||||
coold_vm "$index" logs-agent
|
||||
}
|
||||
|
||||
corrosion_sql() {
|
||||
local query="$*"
|
||||
|
||||
if [ -z "$query" ]; then
|
||||
echo "Usage: scripts/dev.sh corrosion sql <query>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
corrosion_for_each_vm sql <<SH
|
||||
sudo sqlite3 -header -column /var/lib/corrosion/corrosion.db $(printf '%q' "$query")
|
||||
SH
|
||||
}
|
||||
|
||||
corrosion() {
|
||||
local command="${1:-help}"
|
||||
if [ $# -gt 0 ]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
case "$command" in
|
||||
check)
|
||||
corrosion_check
|
||||
;;
|
||||
containers|registered-containers)
|
||||
corrosion_containers
|
||||
;;
|
||||
config)
|
||||
corrosion_config
|
||||
;;
|
||||
logs)
|
||||
corrosion_logs "${1:-1}"
|
||||
;;
|
||||
sql)
|
||||
corrosion_sql "$@"
|
||||
;;
|
||||
-h|--help|help)
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/dev.sh corrosion <command>
|
||||
|
||||
Commands:
|
||||
check Show Corrosion service, WireGuard, gossip, and row-count state
|
||||
containers List registered service_endpoints rows on each VM
|
||||
config Print /etc/corrosion/config.toml from each VM
|
||||
logs [n] Follow coold/corrosion logs for VM n (default: 1)
|
||||
sql <query> Run a read-only sqlite query against each Corrosion DB
|
||||
USAGE
|
||||
;;
|
||||
*)
|
||||
echo "unknown corrosion command: $command" >&2
|
||||
echo "Run: scripts/dev.sh corrosion help" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
example_nginx_name() {
|
||||
local index="$1"
|
||||
|
||||
if [ "$index" = "1" ]; then
|
||||
printf '%s\n' coolify-example-nginx
|
||||
return
|
||||
fi
|
||||
|
||||
printf 'coolify-example-nginx-%s\n' "$index"
|
||||
}
|
||||
|
||||
example_nginx_up() {
|
||||
local count
|
||||
count="$(coold_vm_count)"
|
||||
|
||||
for index in $(seq 1 "$count"); do
|
||||
instance="$(coold_vm_instance "$index")"
|
||||
name="$(example_nginx_name "$index")"
|
||||
gateway="$(coold_vm_container_gateway "$index")"
|
||||
|
||||
echo "--- ${instance}: starting ${name} with coold DNS (${gateway}) ---"
|
||||
COOLIFY_COOLD_LIMA_INSTANCE="$instance" scripts/coold-vm.sh shell <<SH
|
||||
set -e
|
||||
sudo podman rm -f ${name} >/dev/null 2>&1 || true
|
||||
sudo podman run -d \\
|
||||
--name ${name} \\
|
||||
--network coolify-default-mesh \\
|
||||
--dns ${gateway} \\
|
||||
--dns-search default.coolify.internal \\
|
||||
docker.io/library/nginx:alpine
|
||||
SH
|
||||
done
|
||||
}
|
||||
|
||||
example_nginx_down() {
|
||||
local count
|
||||
count="$(coold_vm_count)"
|
||||
|
||||
for index in $(seq 1 "$count"); do
|
||||
instance="$(coold_vm_instance "$index")"
|
||||
name="$(example_nginx_name "$index")"
|
||||
|
||||
echo "--- ${instance}: removing ${name} ---"
|
||||
COOLIFY_COOLD_LIMA_INSTANCE="$instance" scripts/coold-vm.sh shell <<SH
|
||||
sudo podman rm -f ${name} >/dev/null 2>&1 || true
|
||||
SH
|
||||
done
|
||||
}
|
||||
|
||||
example_nginx_check_dns() {
|
||||
COOLIFY_COOLD_LIMA_INSTANCE="$(coold_vm_instance 1)" scripts/coold-vm.sh shell <<'SH'
|
||||
set -e
|
||||
echo '--- resolv.conf ---'
|
||||
sudo podman exec coolify-example-nginx cat /etc/resolv.conf
|
||||
echo
|
||||
echo '--- coold DNS lookup through search domain ---'
|
||||
sudo podman exec coolify-example-nginx nslookup coolify-example-nginx-2
|
||||
echo
|
||||
echo '--- coold DNS lookup by full name ---'
|
||||
sudo podman exec coolify-example-nginx nslookup coolify-example-nginx-2.default.coolify.internal
|
||||
SH
|
||||
}
|
||||
|
||||
example_nginx_help() {
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/dev.sh example-nginx <command>
|
||||
|
||||
Commands:
|
||||
up Start one nginx container on each coold VM with coold DNS configured
|
||||
down Remove the example nginx containers
|
||||
check-dns Verify host 1 nginx can resolve host 2 nginx through coold DNS
|
||||
USAGE
|
||||
}
|
||||
|
||||
example_nginx() {
|
||||
local command="${1:-help}"
|
||||
if [ $# -gt 0 ]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
case "$command" in
|
||||
up)
|
||||
example_nginx_up
|
||||
;;
|
||||
down)
|
||||
example_nginx_down
|
||||
;;
|
||||
check-dns)
|
||||
example_nginx_check_dns
|
||||
;;
|
||||
-h|--help|help)
|
||||
example_nginx_help
|
||||
;;
|
||||
*)
|
||||
echo "unknown example-nginx command: $command" >&2
|
||||
echo "Run: scripts/dev.sh example-nginx help" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
firewall_help() {
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/dev.sh firewall <command>
|
||||
|
||||
Commands:
|
||||
allow <src> <dst> [proto] [port] Allow traffic on every coold VM (proto/port optional)
|
||||
revoke [id|src] [dst] [proto] [port]
|
||||
Remove an allow rule from every coold VM
|
||||
list List allow rules on every coold VM
|
||||
reconcile Re-apply firewall snapshot on every coold VM
|
||||
|
||||
Examples:
|
||||
scripts/dev.sh firewall allow 10.210.0.2 10.210.1.2 tcp 80
|
||||
scripts/dev.sh firewall revoke
|
||||
scripts/dev.sh firewall revoke 10.210.0.2 10.210.1.2 tcp 80
|
||||
scripts/dev.sh firewall revoke 3ba6e0c235a6
|
||||
scripts/dev.sh firewall list
|
||||
USAGE
|
||||
}
|
||||
|
||||
firewall_api_for_each_vm() {
|
||||
local label="$1"
|
||||
local method="$2"
|
||||
local path="$3"
|
||||
local body="${4:-}"
|
||||
local count
|
||||
count="$(coold_vm_count)"
|
||||
|
||||
for index in $(seq 1 "$count"); do
|
||||
instance="$(coold_vm_instance "$index")"
|
||||
api_ip="$(coold_vm_wg_ip "$index")"
|
||||
echo "--- ${instance}: ${label} ---"
|
||||
COOLIFY_COOLD_LIMA_INSTANCE="$instance" scripts/coold-vm.sh shell <<SH
|
||||
set -e
|
||||
token="\$(sudo cat /etc/coolify/api-token)"
|
||||
curl_args=(-fsS -X "${method}" "http://${api_ip}:8443${path}" -H "Authorization: Bearer \${token}")
|
||||
if [ -n '${body}' ]; then
|
||||
curl_args+=(-H 'Content-Type: application/json' -d '${body}')
|
||||
fi
|
||||
curl "\${curl_args[@]}"
|
||||
echo
|
||||
SH
|
||||
done
|
||||
}
|
||||
|
||||
firewall_allow() {
|
||||
local src="${1:-}"
|
||||
local dst="${2:-}"
|
||||
local proto="${3:-}"
|
||||
local port="${4:-}"
|
||||
local body
|
||||
|
||||
if [ -z "$src" ] || [ -z "$dst" ]; then
|
||||
firewall_help >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$port" ] && [ -z "$proto" ]; then
|
||||
echo "ERROR: port requires proto (tcp or udp)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
body="$(printf '{"namespace":"default","src":"%s","dst":"%s"' "$src" "$dst")"
|
||||
if [ -n "$proto" ]; then
|
||||
body="${body}$(printf ',"proto":"%s"' "$proto")"
|
||||
fi
|
||||
if [ -n "$port" ]; then
|
||||
body="${body}$(printf ',"port":%s' "$port")"
|
||||
fi
|
||||
body="${body}}"
|
||||
|
||||
firewall_api_for_each_vm "allow ${src} -> ${dst}" POST /api/v1/firewall/allow "$body"
|
||||
}
|
||||
|
||||
firewall_rule_id() {
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
local proto="${3:-}"
|
||||
local port="${4:-0}"
|
||||
|
||||
if [ -z "$proto" ]; then
|
||||
port=0
|
||||
fi
|
||||
|
||||
printf 'default|%s|%s|%s|%s' "$src" "$dst" "$proto" "$port" \
|
||||
| shasum -a 256 \
|
||||
| awk '{print substr($1, 1, 12)}'
|
||||
}
|
||||
|
||||
firewall_revoke() {
|
||||
local id_or_src="${1:-}"
|
||||
local dst="${2:-}"
|
||||
local proto="${3:-}"
|
||||
local port="${4:-}"
|
||||
local id
|
||||
|
||||
if [ -z "$id_or_src" ]; then
|
||||
echo "Current firewall allow rule IDs:"
|
||||
firewall_list
|
||||
echo
|
||||
echo "Revoke one with: scripts/dev.sh firewall revoke <id>"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$dst" ]; then
|
||||
id="$id_or_src"
|
||||
else
|
||||
id="$(firewall_rule_id "$id_or_src" "$dst" "$proto" "$port")"
|
||||
fi
|
||||
|
||||
firewall_api_for_each_vm "revoke ${id}" DELETE "/api/v1/firewall/allow/${id}"
|
||||
}
|
||||
|
||||
firewall_list() {
|
||||
firewall_api_for_each_vm list GET '/api/v1/firewall/allow?namespace=default'
|
||||
}
|
||||
|
||||
firewall_reconcile() {
|
||||
firewall_api_for_each_vm reconcile POST /api/v1/firewall/reconcile
|
||||
}
|
||||
|
||||
firewall() {
|
||||
local command="${1:-help}"
|
||||
if [ $# -gt 0 ]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
case "$command" in
|
||||
allow)
|
||||
firewall_allow "$@"
|
||||
;;
|
||||
revoke|remove|delete|deny)
|
||||
firewall_revoke "$@"
|
||||
;;
|
||||
list)
|
||||
firewall_list
|
||||
;;
|
||||
reconcile)
|
||||
firewall_reconcile
|
||||
;;
|
||||
-h|--help|help)
|
||||
firewall_help
|
||||
;;
|
||||
*)
|
||||
echo "unknown firewall command: $command" >&2
|
||||
echo "Run: scripts/dev.sh firewall help" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/dev.sh <command> [spin args]
|
||||
|
||||
Commands:
|
||||
up Start the coold VM, Spin stack, and dev coold agent
|
||||
down Stop the dev coold agent and Spin stack
|
||||
shell [n] Open a shell inside coold VM n (default: 1)
|
||||
list Show Lima instances
|
||||
corrosion <command> Inspect Corrosion state, config, logs, and registered containers
|
||||
firewall <command> Manage dev coold firewall allow rules
|
||||
example-nginx <command> Start/check example nginx containers with coold DNS
|
||||
USAGE
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
if [ $# -gt 0 ]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
case "$cmd" in
|
||||
up)
|
||||
up "$@"
|
||||
;;
|
||||
down)
|
||||
down "$@"
|
||||
;;
|
||||
shell)
|
||||
coold_vm "${1:-1}" shell
|
||||
;;
|
||||
list)
|
||||
limactl list
|
||||
;;
|
||||
corrosion)
|
||||
corrosion "$@"
|
||||
;;
|
||||
firewall)
|
||||
firewall "$@"
|
||||
;;
|
||||
example-nginx)
|
||||
example_nginx "$@"
|
||||
;;
|
||||
-h|--help|help|"")
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "unknown command: $cmd" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
it('does not include coold dev tooling defaults in the development env example', function (string $variable) {
|
||||
$developmentExample = file_get_contents(base_path('.env.development.example'));
|
||||
|
||||
expect($developmentExample)->not->toContain($variable.'=');
|
||||
})->with([
|
||||
'coold package version default' => 'COOLIFY_COOLD_VERSION',
|
||||
'flux package version default' => 'COOLIFY_FLUX_VERSION',
|
||||
'corrosion package version default' => 'COOLIFY_CORROSION_VERSION',
|
||||
'coold VM count default' => 'COOLIFY_COOLD_VM_COUNT',
|
||||
'coold VM flux URL default' => 'COOLIFY_COOLD_VM_FLUX_URL',
|
||||
'coold VM WireGuard IP 1 default' => 'COOLIFY_COOLD_VM_WG_IP_1',
|
||||
'coold VM WireGuard IP 2 default' => 'COOLIFY_COOLD_VM_WG_IP_2',
|
||||
'coold VM WireGuard port 1 default' => 'COOLIFY_COOLD_VM_WG_PORT_1',
|
||||
'coold VM WireGuard port 2 default' => 'COOLIFY_COOLD_VM_WG_PORT_2',
|
||||
'coold VM builder capacity default' => 'COOLIFY_COOLD_VM_BUILDER_CAPACITY',
|
||||
'coold VM enabled default' => 'COOLIFY_COOLD_VM_ENABLED',
|
||||
'coold VM stop on down default' => 'COOLIFY_COOLD_VM_STOP_ON_DOWN',
|
||||
'dev follow logs default' => 'COOLIFY_DEV_FOLLOW_LOGS',
|
||||
]);
|
||||
|
||||
it('defaults coold dev VM settings in Laravel config', function () {
|
||||
expect(config('coold.dev_host_count'))->toBe(2)
|
||||
->and(config('coold.dev_host_id'))->toBe('coolify-coold-dev')
|
||||
->and(config('coold.dev_host_id_2'))->toBe('coolify-coold-dev-2')
|
||||
->and(config('coold.dev_wireguard_ip_1'))->toBe('100.64.0.10')
|
||||
->and(config('coold.dev_wireguard_ip_2'))->toBe('100.64.0.11')
|
||||
->and(config('coold.dev_builder_capacity'))->toBe(2)
|
||||
->and(config('coold.dev_builder_enabled'))->toBeTrue();
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
it('mints a host jwt signed by the configured flux private key', function () {
|
||||
[$privateKeyPath, $publicKeyPath] = createFluxJwtKeypair();
|
||||
|
||||
Config::set('flux.jwt_private_key_path', $privateKeyPath);
|
||||
|
||||
$exitCode = Artisan::call('flux:dev', [
|
||||
'host_id' => 'coold-dev',
|
||||
'--caps' => 'coold,builder',
|
||||
'--ttl' => '600',
|
||||
]);
|
||||
|
||||
expect($exitCode)->toBe(0);
|
||||
|
||||
$token = trim(Artisan::output());
|
||||
$claims = JWT::decode($token, new Key(file_get_contents($publicKeyPath), 'ES256'));
|
||||
|
||||
expect($claims->sub)->toBe('coold-dev')
|
||||
->and($claims->aud)->toBe('coold')
|
||||
->and($claims->caps)->toBe(['coold', 'builder'])
|
||||
->and($claims->exp)->toBeGreaterThan(time());
|
||||
});
|
||||
|
||||
it('writes the host jwt to an output path with owner-only permissions', function () {
|
||||
[$privateKeyPath] = createFluxJwtKeypair();
|
||||
$outputPath = storage_path('framework/testing/host-jwt');
|
||||
|
||||
Config::set('flux.jwt_private_key_path', $privateKeyPath);
|
||||
|
||||
$exitCode = Artisan::call('flux:dev', [
|
||||
'host_id' => 'coold-dev',
|
||||
'--output' => $outputPath,
|
||||
]);
|
||||
|
||||
expect($exitCode)->toBe(0);
|
||||
|
||||
expect($outputPath)->toBeFile()
|
||||
->and(substr(sprintf('%o', fileperms($outputPath)), -4))->toBe('0600');
|
||||
});
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: string}
|
||||
*/
|
||||
function createFluxJwtKeypair(): array
|
||||
{
|
||||
$directory = storage_path('framework/testing/flux-keys-'.bin2hex(random_bytes(4)));
|
||||
mkdir($directory, 0777, true);
|
||||
|
||||
$privateKeyPath = $directory.'/jwt.priv';
|
||||
$publicKeyPath = $directory.'/jwt.pub';
|
||||
|
||||
exec('openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out '.escapeshellarg($privateKeyPath), $output, $exitCode);
|
||||
expect($exitCode)->toBe(0);
|
||||
|
||||
exec('openssl pkey -in '.escapeshellarg($privateKeyPath).' -pubout -out '.escapeshellarg($publicKeyPath), $output, $exitCode);
|
||||
expect($exitCode)->toBe(0);
|
||||
|
||||
return [$privateKeyPath, $publicKeyPath];
|
||||
}
|
||||
@@ -105,6 +105,12 @@ it('serves the v5 inertia shell', function () {
|
||||
->assertSee('v5-ready', false)
|
||||
->assertSee('Running')
|
||||
->assertSee('Flux is running.')
|
||||
->assertSee('coolify-coold-dev')
|
||||
->assertSee('coolify-coold-dev-2')
|
||||
->assertSee('100.64.0.10')
|
||||
->assertSee('100.64.0.11')
|
||||
->assertSee('builder')
|
||||
->assertSee('builderCapacity')
|
||||
->assertSee('V5 Shared Team')
|
||||
->assertSee('Shared team details')
|
||||
->assertSee('owner')
|
||||
|
||||
Reference in New Issue
Block a user