Merge pull request #4 from recklessop/zroc-project-recreation

fix: close OVA build gaps — Ubuntu 24.04, overlay copy, full compose stack
This commit is contained in:
2026-04-12 20:40:05 -04:00
committed by GitHub
8 changed files with 379 additions and 131 deletions
+47
View File
@@ -0,0 +1,47 @@
{
admin off
auto_https off
log {
format json
}
}
:443 {
tls internal
handle /auth/* {
reverse_proxy authentik-server:9000 {
header_up X-Forwarded-Proto https
header_up X-Forwarded-For {remote_host}
}
}
handle /outpost.goauthentik.io/* {
reverse_proxy authentik-server:9000 {
header_up X-Forwarded-Proto https
}
}
handle {
reverse_proxy zroc-ui:3001 {
header_up X-Forwarded-Proto https
header_up X-Forwarded-For {remote_host}
header_up X-Real-IP {remote_host}
health_uri /api/health
health_interval 15s
}
}
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
Referrer-Policy "strict-origin-when-cross-origin"
Strict-Transport-Security "max-age=31536000; includeSubDomains"
-Server
}
}
:80 {
redir https://{host}{uri} permanent
}
+256 -78
View File
@@ -1,111 +1,289 @@
version: '3.7'
---
# zROC — Zerto Resiliency Observation Console
# Full stack: Caddy (TLS), zroc-ui (React dashboard + Node backend), Authentik (SSO),
# Prometheus, Grafana, Zerto Exporter, Watchtower (auto-updates).
#
# Configuration is driven entirely by /opt/zroc/.env — see zroc-setup wizard.
version: '3.8'
networks:
front-tier:
back-tier:
auth-tier:
volumes:
prometheus_data: {}
grafana_data: {}
prometheus_data: {}
grafana_data: {}
zroc_ui_data: {}
authentik_postgres: {}
authentik_redis: {}
authentik_media: {}
caddy_data: {}
services:
# Exporter for ZVM/vCenter site 1
zertoexporter:
container_name: zvmexporter1
hostname: zvmexporter1 # this hostname will need to be set in the prometheus.yaml file as well
image: recklessop/zerto-exporter:stable
command: python python-node-exporter.py
# ── Reverse proxy / TLS termination ───────────────────────────────────────
caddy:
image: caddy:2-alpine
container_name: zroc-caddy
restart: unless-stopped
ports:
- "9999:9999"
- "80:80"
- "443:443"
volumes:
- ./zvmexporter/:/usr/src/app/logs/
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- ./certs:/certs:ro
- caddy_data:/data
networks:
- front-tier
depends_on:
- zroc-ui
- authentik-server
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:80"]
interval: 30s
timeout: 5s
retries: 3
# ── zROC React UI + Node backend ──────────────────────────────────────────
zroc-ui:
image: recklessop/zroc-ui:stable
container_name: zroc-ui
restart: unless-stopped
environment:
# Site 1 configuration settings
- VERIFY_SSL=False
- ZVM_HOST=192.168.50.60
- ZVM_PORT=443
- SCRAPE_SPEED=20 #how often should the exporter scrape the Zerto API
- CLIENT_ID=api-script
- CLIENT_SECRET=js51tDM8oappYUGRJBhF7bcsedNoHA5j
- LOGLEVEL=DEBUG
- VCENTER_HOST=vcenter.local
- VCENTER_USER=administrator@vsphere.local
- VCENTER_PASSWORD=password
networks:
- back-tier
restart: always
# This is used for a second ZVM / vCenter (maybe your DR site?)
#zertoexporter2:
# container_name: zvmexporter2
# hostname: zvmexporter2
# image: recklessop/zerto-exporter:stable
# command: python python-node-exporter.py
# ports:
# - "9998:9999" # if you add a third or more exporters change the port number before the :
# volumes:
# - ./zvmexporter/:/usr/src/app/logs/
# environment:
# # Site 2 configuration settings
# - VERIFY_SSL=False
# - ZVM_HOST=192.168.50.30
# - ZVM_PORT=443
# - SCRAPE_SPEED=20 #how often should the exporter scrape the Zerto API
# - CLIENT_ID=api-script
# - CLIENT_SECRET=x2aokKGPyS1O6LCW2uNqm2tbko2PLUSn
# - LOGLEVEL=DEBUG
# - VCENTER_HOST=192.168.50.20
# - VCENTER_USER=administrator@vsphere.local
# - VCENTER_PASSWORD=password
# networks:
# - back-tier
# restart: always
prometheus:
image: prom/prometheus:v2.40.6
NODE_ENV: production
PORT: "3001"
PROMETHEUS_URL: http://zroc-prometheus:9090
AUTHENTIK_URL: http://authentik-server:9000
AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID}
AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET}
AUTHENTIK_ADMIN_TOKEN: ${AUTHENTIK_ADMIN_TOKEN}
PUBLIC_URL: ${PUBLIC_URL}
SESSION_SECRET: ${SESSION_SECRET}
JWT_EXPIRY_HOURS: "24"
AUTHENTIK_ADMIN_GROUP: zroc-admins
AUTHENTIK_VIEWER_GROUP: zroc-viewers
volumes:
- ./prometheus/:/etc/prometheus/
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
ports:
- 9090:9090
- zroc_ui_data:/app/data
networks:
- front-tier
- back-tier
- auth-tier
depends_on:
- zroc-prometheus
- authentik-server
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 20s
# ── SSO — Authentik ───────────────────────────────────────────────────────
authentik-postgresql:
image: postgres:16-alpine
container_name: authentik-db
restart: unless-stopped
environment:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: ${AUTHENTIK_PG_PASS}
volumes:
- authentik_postgres:/var/lib/postgresql/data
networks:
- auth-tier
healthcheck:
test: ["CMD-SHELL", "pg_isready -U authentik"]
interval: 10s
timeout: 5s
retries: 5
authentik-redis:
image: redis:7-alpine
container_name: authentik-redis
restart: unless-stopped
command: --save 60 1 --loglevel warning
volumes:
- authentik_redis:/data
networks:
- auth-tier
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
authentik-server:
image: ghcr.io/goauthentik/server:latest
container_name: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_PG_PASS}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_DISABLE_STARTUP_ANALYTICS: "true"
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
ZROC_OIDC_CLIENT_ID: ${ZROC_OIDC_CLIENT_ID}
ZROC_OIDC_CLIENT_SECRET: ${ZROC_OIDC_CLIENT_SECRET}
ZROC_PUBLIC_URL: ${ZROC_PUBLIC_URL}
volumes:
- authentik_media:/media
- ./zroc-ui/authentik/blueprints:/blueprints/custom:ro
networks:
- auth-tier
- front-tier
depends_on:
authentik-postgresql:
condition: service_healthy
authentik-redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "ak healthcheck || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
authentik-worker:
image: ghcr.io/goauthentik/server:latest
container_name: authentik-worker
restart: unless-stopped
command: worker
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_PG_PASS}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_DISABLE_STARTUP_ANALYTICS: "true"
volumes:
- authentik_media:/media
- /var/run/docker.sock:/var/run/docker.sock
networks:
- auth-tier
depends_on:
- authentik-server
user: root
# ── Metrics — Zerto exporter ──────────────────────────────────────────────
zertoexporter:
image: recklessop/zerto-exporter:stable
container_name: zvmexporter1
hostname: zvmexporter1
restart: unless-stopped
volumes:
- ./zvmexporter:/usr/src/app/logs
environment:
VERIFY_SSL: "False"
ZVM_HOST: ${ZVM_HOST}
ZVM_PORT: "443"
ZVM_USERNAME: ${ZVM_USERNAME}
ZVM_PASSWORD: ${ZVM_PASSWORD}
SCRAPE_SPEED: "20"
CLIENT_ID: ${ZVM_CLIENT_ID:-api-script}
CLIENT_SECRET: ${ZVM_CLIENT_SECRET}
LOGLEVEL: INFO
VCENTER_HOST: ${VCENTER_HOST:-}
VCENTER_USER: ${VCENTER_USER:-administrator@vsphere.local}
VCENTER_PASSWORD: ${VCENTER_PASSWORD:-}
networks:
- back-tier
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9999/metrics"]
interval: 30s
timeout: 10s
retries: 3
# Optional second ZVM/vCenter site — uncomment and set ZVM2_* env vars
# zertoexporter2:
# image: recklessop/zerto-exporter:stable
# container_name: zvmexporter2
# hostname: zvmexporter2
# restart: unless-stopped
# ports:
# - "9998:9999"
# volumes:
# - ./zvmexporter:/usr/src/app/logs
# environment:
# VERIFY_SSL: "False"
# ZVM_HOST: ${ZVM2_HOST}
# ZVM_PORT: "443"
# ZVM_USERNAME: ${ZVM2_USERNAME}
# ZVM_PASSWORD: ${ZVM2_PASSWORD}
# SCRAPE_SPEED: "20"
# LOGLEVEL: INFO
# networks:
# - back-tier
# ── Metrics — Prometheus ──────────────────────────────────────────────────
zroc-prometheus:
image: prom/prometheus:v2.51.0
container_name: zroc-prometheus
restart: unless-stopped
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=30d
- --storage.tsdb.retention.size=20GB
- --web.listen-address=0.0.0.0:9090
- --web.enable-lifecycle
volumes:
- ./prometheus:/etc/prometheus:ro
- prometheus_data:/prometheus
networks:
- back-tier
restart: always
depends_on:
- zertoexporter
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9090/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
# ── Dashboards — Grafana ──────────────────────────────────────────────────
grafana:
image: grafana/grafana
image: grafana/grafana:10.4.2
container_name: zroc-grafana
restart: unless-stopped
user: "472"
depends_on:
- prometheus
ports:
- 3000:3000
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning/:/etc/grafana/provisioning/
- ./grafana/provisioning:/etc/grafana/provisioning:ro
environment:
- GF_SECURITY_ADMIN_PASSWORD=zertodata
- GF_USERS_ALLOW_SIGN_UP=false
- data-source-url=http://prometheus:9090
- name=Prometheus
- type=prometheus
- update-interval=10
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-zertodata}
GF_USERS_ALLOW_SIGN_UP: "false"
GF_SERVER_ROOT_URL: "%(protocol)s://%(domain)s:%(http_port)s/grafana/"
GF_AUTH_GENERIC_OAUTH_ENABLED: ${GRAFANA_OIDC_ENABLED:-false}
GF_AUTH_GENERIC_OAUTH_NAME: Authentik
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: ${GRAFANA_CLIENT_ID:-}
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: ${GRAFANA_CLIENT_SECRET:-}
GF_AUTH_GENERIC_OAUTH_SCOPES: openid profile email
GF_AUTH_GENERIC_OAUTH_AUTH_URL: ${PUBLIC_URL:-}/auth/application/o/authorize/
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: http://authentik-server:9000/application/o/token/
GF_AUTH_GENERIC_OAUTH_API_URL: http://authentik-server:9000/application/o/userinfo/
networks:
- back-tier
- front-tier
restart: always
depends_on:
- zroc-prometheus
# ── Auto-updates — Watchtower ─────────────────────────────────────────────
watchtower:
image: containrrr/watchtower
container_name: zroc-watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_POLL_INTERVAL=360 # 1 hour
restart: always
WATCHTOWER_POLL_INTERVAL: "3600"
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_INCLUDE_STOPPED: "false"
command: --label-enable
+4 -4
View File
@@ -31,7 +31,7 @@ jobs:
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
echo "ova_name=zroc-appliance-${VERSION}-ubuntu-26.04-amd64.ova" >> $GITHUB_OUTPUT
echo "ova_name=zroc-appliance-${VERSION}-ubuntu-24.04-amd64.ova" >> $GITHUB_OUTPUT
- name: Install Packer
run: |
@@ -42,7 +42,7 @@ jobs:
- name: Packer init
working-directory: packer
run: packer init ubuntu-2604.pkr.hcl
run: packer init ubuntu-2404.pkr.hcl
- name: Validate
working-directory: packer
@@ -50,7 +50,7 @@ jobs:
packer validate \
-var "vm_version=${{ steps.ver.outputs.version }}" \
-var-file=variables.pkrvars.hcl \
ubuntu-2604.pkr.hcl
ubuntu-2404.pkr.hcl
- name: Build OVA
working-directory: packer
@@ -62,7 +62,7 @@ jobs:
-var "vm_version=${{ steps.ver.outputs.version }}" \
-var "headless=true" \
-var-file=variables.pkrvars.hcl \
ubuntu-2604.pkr.hcl
ubuntu-2404.pkr.hcl
- name: Locate OVA
id: ova
+2 -2
View File
@@ -1,11 +1,11 @@
# zroc-ova — zROC Appliance Builder
Packer build definitions and provisioner scripts for the **zROC Ubuntu 26.04 LTS OVA appliance**.
Packer build definitions and provisioner scripts for the **zROC Ubuntu 24.04 LTS OVA appliance**.
## What you get
A 100 GB thin-provisioned VMware OVA containing:
- Ubuntu Server 26.04 LTS
- Ubuntu Server 24.04 LTS
- Docker Engine + Compose plugin
- Full zROC stack (cloned from recklessop/zroc)
- Interactive first-boot setup wizard (`zroc-setup`)
@@ -14,12 +14,12 @@ packer {
variable "ubuntu_iso_url" {
type = string
default = "https://releases.ubuntu.com/26.04/ubuntu-26.04-live-server-amd64.iso"
default = "https://releases.ubuntu.com/24.04/ubuntu-24.04.2-live-server-amd64.iso"
}
variable "ubuntu_iso_checksum" {
type = string
default = "file:https://releases.ubuntu.com/26.04/SHA256SUMS"
default = "file:https://releases.ubuntu.com/24.04/SHA256SUMS"
}
variable "vm_name" {
@@ -57,46 +57,46 @@ variable "headless" {
default = true
}
source "vmware-iso" "ubuntu2604" {
vm_name = "${var.vm_name}-${var.vm_version}"
guest_os_type = "ubuntu-64"
headless = var.headless
iso_url = var.ubuntu_iso_url
iso_checksum = var.ubuntu_iso_checksum
disk_size = var.disk_size_mb
disk_adapter_type = "pvscsi"
memory = var.memory_mb
cpus = var.cpus
network_adapter_type = "vmxnet3"
network = "nat"
disk_type_id = 0
http_directory = "http"
http_port_min = 8100
http_port_max = 8199
boot_wait = "5s"
source "vmware-iso" "ubuntu2404" {
vm_name = "${var.vm_name}-${var.vm_version}"
guest_os_type = "ubuntu-64"
headless = var.headless
iso_url = var.ubuntu_iso_url
iso_checksum = var.ubuntu_iso_checksum
disk_size = var.disk_size_mb
disk_adapter_type = "pvscsi"
memory = var.memory_mb
cpus = var.cpus
network_adapter_type = "vmxnet3"
network = "nat"
disk_type_id = 0
http_directory = "http"
http_port_min = 8100
http_port_max = 8199
boot_wait = "5s"
boot_command = [
"e<wait>",
"<down><down><down><end>",
" autoinstall ds=nocloud-net;seedfrom=http://{{.HTTPIP}}:{{.HTTPPort}}/",
"<f10><wait30s>",
]
ssh_username = "zroc"
ssh_password = "zroc-setup-temp"
ssh_timeout = "30m"
ssh_port = 22
shutdown_command = "echo 'zroc-setup-temp' | sudo -S shutdown -P now"
output_directory = "${var.output_dir}/vmware"
skip_export = false
format = "ovf"
ssh_username = "zroc"
ssh_password = "zroc-setup-temp"
ssh_timeout = "30m"
ssh_port = 22
shutdown_command = "echo 'zroc-setup-temp' | sudo -S shutdown -P now"
output_directory = "${var.output_dir}/vmware"
skip_export = false
format = "ovf"
vmx_data = {
"virtualHW.version" = "19"
"tools.syncTime" = "TRUE"
"annotation" = "zROC Appliance v${var.vm_version}"
"guestOS" = "ubuntu-64"
"virtualHW.version" = "19"
"tools.syncTime" = "TRUE"
"annotation" = "zROC Appliance v${var.vm_version}"
"guestOS" = "ubuntu-64"
}
}
source "qemu" "ubuntu2604" {
source "qemu" "ubuntu2404" {
vm_name = "${var.vm_name}-${var.vm_version}"
iso_url = var.ubuntu_iso_url
iso_checksum = var.ubuntu_iso_checksum
@@ -109,7 +109,7 @@ source "qemu" "ubuntu2604" {
http_directory = "http"
http_port_min = 8100
http_port_max = 8199
boot_wait = "5s"
boot_wait = "5s"
boot_command = [
"e<wait>",
"<down><down><down><end>",
@@ -125,12 +125,18 @@ source "qemu" "ubuntu2604" {
}
build {
name = "zroc-appliance"
sources = ["source.vmware-iso.ubuntu2604"]
name = "zroc-appliance"
sources = ["source.vmware-iso.ubuntu2404"]
# Copy overlay files (setup wizard, etc.) into the VM before provisioning
provisioner "file" {
source = "../overlays/"
destination = "/tmp/overlays/"
}
provisioner "shell" {
script = "../scripts/00-base.sh"
execute_command = "echo 'zroc-setup-temp' | sudo -S bash {{.Path}}"
script = "../scripts/00-base.sh"
execute_command = "echo 'zroc-setup-temp' | sudo -S bash {{.Path}}"
expect_disconnect = true
}
@@ -161,12 +167,12 @@ build {
}
post-processor "shell-local" {
only = ["vmware-iso.ubuntu2604"]
only = ["vmware-iso.ubuntu2404"]
inline = [
"cd ${var.output_dir}/vmware",
"ovftool --compress=9 *.ovf ../${var.vm_name}-${var.vm_version}-ubuntu-26.04-amd64.ova",
"ovftool --compress=9 *.ovf ../${var.vm_name}-${var.vm_version}-ubuntu-24.04-amd64.ova",
"cd ..",
"sha256sum ${var.vm_name}-${var.vm_version}-ubuntu-26.04-amd64.ova > ${var.vm_name}-${var.vm_version}-ubuntu-26.04-amd64.ova.sha256",
"sha256sum ${var.vm_name}-${var.vm_version}-ubuntu-24.04-amd64.ova > ${var.vm_name}-${var.vm_version}-ubuntu-24.04-amd64.ova.sha256",
"echo 'OVA packaged successfully'",
]
}
+2 -2
View File
@@ -1,8 +1,8 @@
# zroc-ova/packer/variables.pkrvars.hcl
vm_version = "1.0.0"
ubuntu_iso_url = "https://releases.ubuntu.com/26.04/ubuntu-26.04-live-server-amd64.iso"
ubuntu_iso_checksum = "file:https://releases.ubuntu.com/26.04/SHA256SUMS"
ubuntu_iso_url = "https://releases.ubuntu.com/24.04/ubuntu-24.04.2-live-server-amd64.iso"
ubuntu_iso_checksum = "file:https://releases.ubuntu.com/24.04/SHA256SUMS"
memory_mb = 8192
cpus = 4
+18 -4
View File
@@ -8,15 +8,29 @@ ZROC_REPO="https://github.com/recklessop/zroc.git"
git clone --depth=1 "$ZROC_REPO" "$INSTALL_DIR"
# Ensure expected directories exist
mkdir -p \
"$INSTALL_DIR/certs" \
"$INSTALL_DIR/zvmexporter" \
"$INSTALL_DIR/certs" \
"$INSTALL_DIR/zvmexporter" \
"$INSTALL_DIR/data"
cd "$INSTALL_DIR"
docker compose pull prometheus grafana authentik-server authentik-worker \
|| echo "[02-zroc] Some images not yet available — will pull on first start"
# Pre-pull all container images into the OVA image layer so first-boot is fast.
# Failures are non-fatal — any missing images will be pulled on first docker compose up.
echo "==> [02-zroc] Pre-pulling container images (this may take a while)…"
docker compose pull \
caddy \
zroc-ui \
authentik-postgresql \
authentik-redis \
authentik-server \
authentik-worker \
zertoexporter \
zroc-prometheus \
grafana \
watchtower \
|| echo "[02-zroc] Warning: some images could not be pre-pulled — they will pull on first start"
chown -R zroc:zroc "$INSTALL_DIR"
+4 -1
View File
@@ -3,7 +3,10 @@
set -euo pipefail
echo "==> [03-setup-wizard] Installing setup wizard"
install -m 0755 /tmp/zroc-setup /usr/local/bin/zroc-setup
# The Packer file provisioner copies overlays/ to /tmp/overlays/
# Mirror the full directory tree into place
cp -r /tmp/overlays/usr /
chmod 0755 /usr/local/bin/zroc-setup
cat > /etc/systemd/system/zroc-firstboot.service << 'EOF'
[Unit]