diff --git a/.github/workflows/appliance.yml b/.github/workflows/appliance.yml index 364e4b2..3b03e4d 100644 --- a/.github/workflows/appliance.yml +++ b/.github/workflows/appliance.yml @@ -1,10 +1,10 @@ name: Appliance # Builds the downloadable virtual appliances for ALL hypervisors in one job: customizes the Ubuntu 24.04 -# cloud image with libguestfs (no Hyper-V host / nested virt needed) into a Gen2/UEFI image with PostgreSQL -# + Dispatch baked in, emits it as VHDX (Hyper-V), OVA (VMware), and qcow2 (KVM/Proxmox), then boots -# it under QEMU/UEFI to prove the service comes up on first boot. Artifacts: dispatch-appliance-hyperv, -# dispatch-appliance-vmware, dispatch-appliance-kvm. +# cloud image with libguestfs (no Hyper-V host / nested virt needed) into a Gen2/UEFI image with Dispatch +# baked in (bundled SQLite - no database server), emits it as VHDX (Hyper-V), OVA (VMware), and qcow2 +# (KVM/Proxmox), then boots it under QEMU/UEFI to prove the service comes up on first boot. Artifacts: +# dispatch-appliance-hyperv, dispatch-appliance-vmware, dispatch-appliance-kvm. on: # Runs AFTER a release is published - linked to the release, but it never blocks or races it (the release # is already out; this attaches the appliance images to it). The Build workflow also kicks this off after @@ -209,7 +209,7 @@ jobs: sudo convert /tmp/screen.ppm "$1" 2>/dev/null && echo "captured $1" || echo "no screenshot" } ok=0 - for i in $(seq 1 90); do # up to ~8 min (KVM boot is fast; this is mostly PostgreSQL first-run) + for i in $(seq 1 90); do # up to ~8 min - generous headroom for slow CI runners' boot + first-run if curl -fsSk -m 3 https://localhost:8420/health >/dev/null 2>&1; then ok=1; break; fi [ "$i" = 18 ] && screenshot boot-90s.png # ~90s in: capture whatever's on screen sleep 5 diff --git a/appliance/Import-DispatchAppliance.ps1 b/appliance/Import-DispatchAppliance.ps1 index ccd6de2..11730c9 100644 --- a/appliance/Import-DispatchAppliance.ps1 +++ b/appliance/Import-DispatchAppliance.ps1 @@ -5,8 +5,9 @@ .DESCRIPTION Creates a Generation 2 VM, copies the appliance VHDX into the chosen storage location, sets the Secure Boot template to the Microsoft UEFI Certificate Authority (required for Linux), connects a virtual - switch (optionally on a specific VLAN), and (optionally) starts it. The appliance configures PostgreSQL - + Dispatch on first boot; browse to https://:8420 and set the admin password. + switch (optionally on a specific VLAN), and (optionally) starts it. The appliance creates its bundled + SQLite database and starts Dispatch on first boot; browse to https://:8420 and set the admin + password. Run it with no networking/storage flags for a guided menu: it lists the host's virtual switches and storage volumes and prompts for the VLAN, memory, and CPU. Pass -SwitchName for fully unattended use. @@ -88,7 +89,7 @@ function Select-Storage([string]$Default) { Where-Object { $_.DriveLetter -and $_.Size -gt 0 } | Sort-Object DriveLetter | ForEach-Object { Write-Host (" {0}: {1:N0} GB free of {2:N0} GB {3}" -f $_.DriveLetter, ($_.SizeRemaining / 1GB), ($_.Size / 1GB), $_.FileSystemLabel) } - Write-Host " (The appliance disk is ~6-10 GB thin-provisioned; PostgreSQL + logs grow it over time.)" + Write-Host " (The appliance disk is ~6-10 GB thin-provisioned; the SQLite database + logs grow it over time.)" return (Read-WithDefault "VM storage folder" $Default) } @@ -106,13 +107,13 @@ if (-not ($isAdmin -or $isHyperVAdm)) { if (-not $VhdxPath) { $found = @(Get-ChildItem -LiteralPath $PSScriptRoot -Filter *.vhdx -File -ErrorAction SilentlyContinue) if ($found.Count -eq 1) { $VhdxPath = $found[0].FullName; Write-Host "Using VHDX: $VhdxPath" } - elseif ($found.Count -eq 0) { throw "No .vhdx found next to this script. Unzip dispatch-appliance.vhdx.zip here first, or pass -VhdxPath." } + elseif ($found.Count -eq 0) { throw "No .vhdx found next to this script. Unzip the downloaded dispatch-appliance-*-hyperv.zip here first, or pass -VhdxPath." } else { throw "Multiple .vhdx files found next to this script; pass -VhdxPath to pick one." } } if (-not (Test-Path -LiteralPath $VhdxPath -PathType Leaf)) { throw "VHDX file not found: $VhdxPath" } $VhdxPath = (Resolve-Path -LiteralPath $VhdxPath).Path if ([System.IO.Path]::GetExtension($VhdxPath) -notin @('.vhdx', '.vhd')) { - throw "-VhdxPath must point at the appliance .vhdx file, but got '$VhdxPath'. Unzip dispatch-appliance.vhdx.zip and pass the dispatch-appliance.vhdx inside it." + throw "-VhdxPath must point at the appliance .vhdx file, but got '$VhdxPath'. Unzip the downloaded dispatch-appliance-*-hyperv.zip and pass the dispatch-appliance.vhdx inside it." } # Detect a failover cluster on this host (Get-Cluster ships in the FailoverClusters module, present only on @@ -177,7 +178,7 @@ try { Set-VMProcessor -VM $vm -Count $CpuCount # Linux on Gen2 needs the Microsoft UEFI CA Secure Boot template (not the default Windows one). Set-VMFirmware -VM $vm -EnableSecureBoot On -SecureBootTemplate "MicrosoftUEFICertificateAuthority" - # PostgreSQL needs a stable working set; disable Dynamic Memory. + # A relay service needs a stable working set; disable Dynamic Memory. Set-VMMemory -VM $vm -DynamicMemoryEnabled $false # Apply a VLAN tag to the adapter if requested (access mode = single tagged VLAN). if ($VlanId -gt 0) { @@ -211,7 +212,7 @@ Write-Host ("Created '{0}' (Gen2, {1} vCPU, {2} GB, switch '{3}'{4}{5})." -f ` $Name, $CpuCount, $MemoryGB, $SwitchName, $(if ($VlanId -gt 0) { ", VLAN $VlanId" } else { "" }), $(if ($AddToCluster) { ", clustered" } else { "" })) if ($Start) { Start-VM -VM $vm - Write-Host "Started. First boot configures PostgreSQL + Dispatch (a few minutes)." + Write-Host "Started. First boot creates the bundled SQLite database and starts Dispatch - it's quick." } else { Write-Host "Start it with: Start-VM -Name '$Name'" } diff --git a/appliance/README.md b/appliance/README.md index 6a7493b..9d36951 100644 --- a/appliance/README.md +++ b/appliance/README.md @@ -1,6 +1,6 @@ # Dispatch SMTP Relay - virtual appliance -A ready-to-run **Ubuntu 24.04 LTS** virtual machine with Dispatch and PostgreSQL pre-installed. Import it, power it on, and the dashboard comes up - no .NET, database, or command line needed. +A ready-to-run **Ubuntu 24.04 LTS** virtual machine with Dispatch pre-installed, using its bundled SQLite database. Import it, power it on, and the dashboard comes up - no .NET, database server, or command line needed. There's a **separate download per hypervisor** - each is a single zip (unzip once) with that format's image, its README, and the import helper: @@ -10,7 +10,7 @@ There's a **separate download per hypervisor** - each is a single zip (unzip onc | **VMware** (vSphere/ESXi/Workstation/Fusion) | `dispatch-appliance-vmware` | `dispatch-appliance.ova` + README | | **KVM/libvirt & Proxmox** | `dispatch-appliance-kvm` | `dispatch-appliance.qcow2` + `import-libvirt.sh` / `import-proxmox.sh` + README | -All are **Gen2/UEFI**, ~4 GB RAM recommended (PostgreSQL needs ~2 GB). They boot on DHCP, but a relay your apps point at should have a **static IP** - set one after first boot (see [Static IP](#static-ip)). +All are **Gen2/UEFI**, ~4 GB RAM recommended. They boot on DHCP, but a relay your apps point at should have a **static IP** - set one after first boot (see [Static IP](#static-ip)). ## Hyper-V @@ -49,7 +49,7 @@ Creates a q35/OVMF VM, imports the disk as `scsi0`, and sets it to boot. ## First boot -On first start the appliance gives itself a **unique** database password, creates the `dispatch` role and `DispatchLog` database, starts PostgreSQL, and starts Dispatch (the schema is created automatically). This takes a couple of minutes the first time. +On first start Dispatch creates its bundled SQLite database under `/var/lib/dispatch` (the schema is created automatically) and starts - there's no database server to bootstrap, so it's quick. Then browse to the dashboard: @@ -90,15 +90,14 @@ Pass `-i ` to target a specific interface (see `ip a`); run `dispatch-set-i ## Maintenance (console) - Service: `systemctl status dispatch` ยท logs: `journalctl -u dispatch -f` and `/var/log/dispatch/`. -- PostgreSQL: `systemctl status postgresql`. +- Database: bundled SQLite file at `/var/lib/dispatch` - no separate server process to manage. - Config (connection string only): `/var/lib/dispatch/appsettings.json` - everything else is in the dashboard. ## Security notes -- Every appliance generates its **own** database password, at-rest encryption key (`.dispatch-key`), dashboard TLS cert, SSH host keys, and machine-id on first boot - nothing secret is shared across downloads. +- Every appliance generates its **own** at-rest encryption key (`.dispatch-key`), dashboard TLS cert, SSH host keys, and machine-id on first boot - nothing secret is shared across downloads. - The **OS login** (`ubuntu`/`dispatch`) is a known default but **must be changed on first login** - do so immediately, especially before exposing the VM beyond a trusted LAN. - The dashboard admin password is set by **you** on first login and is stored only as a bcrypt hash in the database. -- The appliance bundles **PostgreSQL**, which is free and open source. ## Building it yourself diff --git a/appliance/build-appliance.sh b/appliance/build-appliance.sh index 0b3d63c..e2fde1e 100755 --- a/appliance/build-appliance.sh +++ b/appliance/build-appliance.sh @@ -3,8 +3,8 @@ # Dispatch SMTP Relay - Hyper-V appliance builder (Ubuntu 24.04 LTS). # # Customizes the official Ubuntu cloud image offline with libguestfs (no Hyper-V host, no nested virt) and -# converts it to a Gen2/UEFI dynamic VHDX. PostgreSQL's binaries are baked in; each VM configures the -# Dispatch starts on first boot against the bundled SQLite database it creates under /var/lib/dispatch. +# converts it to a Gen2/UEFI dynamic VHDX. Dispatch is baked in and starts on first boot against the +# bundled SQLite database it creates under /var/lib/dispatch - no database server to install or configure. # # Requires (host): libguestfs-tools, qemu-utils, curl. # Usage: @@ -79,7 +79,6 @@ docker run --rm -e KVER="$KVER" -v "$STAGE/debs:/debs" ubuntu:24.04 bash -ec ' export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq curl ca-certificates >/dev/null - # PostgreSQL 16 ships in the default Ubuntu 24.04 repos, so no extra apt source or signing key is needed. cd /debs # Full recursive runtime dependency closure of the target packages (skip virtual/undownloadable entries). deps=$(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances \ @@ -107,7 +106,7 @@ echo "==> Expanding the root partition into a ${DISK_SIZE} working image" qemu-img create -f qcow2 "$WORK/disk.qcow2" "$DISK_SIZE" virt-resize --expand /dev/sda1 "$WORK/base.img" "$WORK/disk.qcow2" -echo "==> Customizing the image (PostgreSQL + Dispatch + first-boot)" +echo "==> Customizing the image (Dispatch + first-boot)" # --no-network: provisioning installs pre-downloaded .debs offline, so the appliance needs no in-guest # network (and we avoid libguestfs's passt networking, which fails on CI runners). virt-customize -a "$WORK/disk.qcow2" \ diff --git a/appliance/dispatch.ovf.template b/appliance/dispatch.ovf.template index 5e513d0..1d888d0 100644 --- a/appliance/dispatch.ovf.template +++ b/appliance/dispatch.ovf.template @@ -1,7 +1,7 @@ diff --git a/appliance/hyperv-README.txt b/appliance/hyperv-README.txt index 0c67ff9..27c49b2 100644 --- a/appliance/hyperv-README.txt +++ b/appliance/hyperv-README.txt @@ -63,7 +63,8 @@ This zip contains: ------------------------------------------------------------------------------ Notes ------------------------------------------------------------------------------ - * First boot configures PostgreSQL + Dispatch; allow a few minutes. + * First boot creates the bundled SQLite database and starts Dispatch - no + database server to bootstrap, so it's quick. * The appliance is self-contained - no separate .NET runtime needed. - * Full docs: https://chrismuench.github.io/Dispatch-SMTP-Relay/ + * Full docs: https://docs.dispatchrelay.app/ ============================================================================== diff --git a/appliance/import-libvirt.sh b/appliance/import-libvirt.sh index 9b51fa5..05aa653 100755 --- a/appliance/import-libvirt.sh +++ b/appliance/import-libvirt.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # # Import the Dispatch SMTP Relay appliance qcow2 into KVM/libvirt as a UEFI VM (virt-install --import). -# The appliance configures PostgreSQL + Dispatch on first boot; then browse to https://:8420. +# The appliance creates its bundled SQLite database and starts Dispatch on first boot (quick - no database +# server to bootstrap); then browse to https://:8420. # # Usage: # sudo ./import-libvirt.sh dispatch-appliance.qcow2 [--name dispatch] [--memory 4096] [--vcpus 2] @@ -54,7 +55,7 @@ virt-install \ # qemu-guest-agent then reports the VM's IP to libvirt ("virsh domifaddr --source agent"). echo -echo "VM '$NAME' defined. First boot configures PostgreSQL + Dispatch (a few minutes)." +echo "VM '$NAME' defined. First boot creates the bundled SQLite database and starts Dispatch - it's quick." [ "$START" = 1 ] || echo "Start it with: virsh start $NAME" echo "Find its IP with: virsh domifaddr $NAME" echo "Then browse to https://:8420 and set the admin password." diff --git a/appliance/import-proxmox.sh b/appliance/import-proxmox.sh index 644f42b..1b83b34 100755 --- a/appliance/import-proxmox.sh +++ b/appliance/import-proxmox.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # # Import the Dispatch SMTP Relay appliance qcow2 into Proxmox VE as a UEFI (OVMF) VM. Run on the Proxmox -# host. The appliance configures PostgreSQL + Dispatch on first boot; then browse to https://:8420. +# host. The appliance creates its bundled SQLite database and starts Dispatch on first boot (quick - no +# database server to bootstrap); then browse to https://:8420. # # Usage: # ./import-proxmox.sh dispatch-appliance.qcow2 [--storage local-lvm] [--bridge vmbr0] @@ -50,6 +51,6 @@ qm set "$VMID" --scsi0 "$disk" qm set "$VMID" --boot order=scsi0 echo -echo "VM $VMID ($NAME) created. First boot configures PostgreSQL + Dispatch (a few minutes)." +echo "VM $VMID ($NAME) created. First boot creates the bundled SQLite database and starts Dispatch - it's quick." if [ "$START" = 1 ]; then qm start "$VMID"; echo "Started."; else echo "Start it with: qm start $VMID"; fi echo "Then browse to https://:8420 and set the admin password." diff --git a/appliance/kvm-README.txt b/appliance/kvm-README.txt index c59bb9e..c0f56f7 100644 --- a/appliance/kvm-README.txt +++ b/appliance/kvm-README.txt @@ -26,9 +26,10 @@ This zip contains: ------------------------------------------------------------------------------ After import ------------------------------------------------------------------------------ - * Start the VM. First boot configures PostgreSQL + Dispatch (allow a - few minutes). The VM uses DHCP. + * Start the VM. First boot creates the bundled SQLite database and starts + Dispatch - no database server to bootstrap, so it's quick. The VM uses + DHCP. * Browse to https://:8420 (self-signed cert - accept the warning) and SET THE ADMIN PASSWORD on the first login. - * Full docs: https://chrismuench.github.io/Dispatch-SMTP-Relay/ + * Full docs: https://docs.dispatchrelay.app/ ============================================================================== diff --git a/appliance/vmware-README.txt b/appliance/vmware-README.txt index 9f5cda9..4951311 100644 --- a/appliance/vmware-README.txt +++ b/appliance/vmware-README.txt @@ -23,9 +23,10 @@ This zip contains: ------------------------------------------------------------------------------ After import ------------------------------------------------------------------------------ - * Power on the VM. First boot configures PostgreSQL + Dispatch (allow a - few minutes). The VM uses DHCP. + * Power on the VM. First boot creates the bundled SQLite database and + starts Dispatch - no database server to bootstrap, so it's quick. The VM + uses DHCP. * Browse to https://:8420 (self-signed cert - accept the warning) and SET THE ADMIN PASSWORD on the first login. - * Full docs: https://chrismuench.github.io/Dispatch-SMTP-Relay/ + * Full docs: https://docs.dispatchrelay.app/ ==============================================================================