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
}
+243 -65
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: networks:
front-tier: front-tier:
back-tier: back-tier:
auth-tier:
volumes: volumes:
prometheus_data: {} prometheus_data: {}
grafana_data: {} grafana_data: {}
zroc_ui_data: {}
authentik_postgres: {}
authentik_redis: {}
authentik_media: {}
caddy_data: {}
services: services:
# Exporter for ZVM/vCenter site 1 # ── Reverse proxy / TLS termination ───────────────────────────────────────
zertoexporter: caddy:
container_name: zvmexporter1 image: caddy:2-alpine
hostname: zvmexporter1 # this hostname will need to be set in the prometheus.yaml file as well container_name: zroc-caddy
image: recklessop/zerto-exporter:stable restart: unless-stopped
command: python python-node-exporter.py
ports: ports:
- "9999:9999" - "80:80"
- "443:443"
volumes: 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: environment:
# Site 1 configuration settings NODE_ENV: production
- VERIFY_SSL=False PORT: "3001"
- ZVM_HOST=192.168.50.60 PROMETHEUS_URL: http://zroc-prometheus:9090
- ZVM_PORT=443 AUTHENTIK_URL: http://authentik-server:9000
- SCRAPE_SPEED=20 #how often should the exporter scrape the Zerto API AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID}
- CLIENT_ID=api-script AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET}
- CLIENT_SECRET=js51tDM8oappYUGRJBhF7bcsedNoHA5j AUTHENTIK_ADMIN_TOKEN: ${AUTHENTIK_ADMIN_TOKEN}
- LOGLEVEL=DEBUG PUBLIC_URL: ${PUBLIC_URL}
- VCENTER_HOST=vcenter.local SESSION_SECRET: ${SESSION_SECRET}
- VCENTER_USER=administrator@vsphere.local JWT_EXPIRY_HOURS: "24"
- VCENTER_PASSWORD=password AUTHENTIK_ADMIN_GROUP: zroc-admins
AUTHENTIK_VIEWER_GROUP: zroc-viewers
volumes:
- 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: networks:
- back-tier - back-tier
restart: always healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9999/metrics"]
interval: 30s
timeout: 10s
retries: 3
# This is used for a second ZVM / vCenter (maybe your DR site?) # Optional second ZVM/vCenter site — uncomment and set ZVM2_* env vars
#zertoexporter2: # zertoexporter2:
# image: recklessop/zerto-exporter:stable
# container_name: zvmexporter2 # container_name: zvmexporter2
# hostname: zvmexporter2 # hostname: zvmexporter2
# image: recklessop/zerto-exporter:stable # restart: unless-stopped
# command: python python-node-exporter.py
# ports: # ports:
# - "9998:9999" # if you add a third or more exporters change the port number before the : # - "9998:9999"
# volumes: # volumes:
# - ./zvmexporter/:/usr/src/app/logs/ # - ./zvmexporter:/usr/src/app/logs
# environment: # environment:
# # Site 2 configuration settings # VERIFY_SSL: "False"
# - VERIFY_SSL=False # ZVM_HOST: ${ZVM2_HOST}
# - ZVM_HOST=192.168.50.30 # ZVM_PORT: "443"
# - ZVM_PORT=443 # ZVM_USERNAME: ${ZVM2_USERNAME}
# - SCRAPE_SPEED=20 #how often should the exporter scrape the Zerto API # ZVM_PASSWORD: ${ZVM2_PASSWORD}
# - CLIENT_ID=api-script # SCRAPE_SPEED: "20"
# - CLIENT_SECRET=x2aokKGPyS1O6LCW2uNqm2tbko2PLUSn # LOGLEVEL: INFO
# - LOGLEVEL=DEBUG
# - VCENTER_HOST=192.168.50.20
# - VCENTER_USER=administrator@vsphere.local
# - VCENTER_PASSWORD=password
# networks: # networks:
# - back-tier # - back-tier
# restart: always
prometheus: # ── Metrics — Prometheus ──────────────────────────────────────────────────
image: prom/prometheus:v2.40.6 zroc-prometheus:
volumes: image: prom/prometheus:v2.51.0
- ./prometheus/:/etc/prometheus/ container_name: zroc-prometheus
- prometheus_data:/prometheus restart: unless-stopped
command: command:
- '--config.file=/etc/prometheus/prometheus.yml' - --config.file=/etc/prometheus/prometheus.yml
- '--storage.tsdb.path=/prometheus' - --storage.tsdb.path=/prometheus
- '--web.console.libraries=/usr/share/prometheus/console_libraries' - --storage.tsdb.retention.time=30d
- '--web.console.templates=/usr/share/prometheus/consoles' - --storage.tsdb.retention.size=20GB
ports: - --web.listen-address=0.0.0.0:9090
- 9090:9090 - --web.enable-lifecycle
volumes:
- ./prometheus:/etc/prometheus:ro
- prometheus_data:/prometheus
networks: networks:
- back-tier - back-tier
restart: always
depends_on: depends_on:
- zertoexporter - zertoexporter
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9090/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
# ── Dashboards — Grafana ──────────────────────────────────────────────────
grafana: grafana:
image: grafana/grafana image: grafana/grafana:10.4.2
container_name: zroc-grafana
restart: unless-stopped
user: "472" user: "472"
depends_on:
- prometheus
ports: ports:
- 3000:3000 - "3000:3000"
volumes: volumes:
- grafana_data:/var/lib/grafana - grafana_data:/var/lib/grafana
- ./grafana/provisioning/:/etc/grafana/provisioning/ - ./grafana/provisioning:/etc/grafana/provisioning:ro
environment: environment:
- GF_SECURITY_ADMIN_PASSWORD=zertodata GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-zertodata}
- GF_USERS_ALLOW_SIGN_UP=false GF_USERS_ALLOW_SIGN_UP: "false"
- data-source-url=http://prometheus:9090 GF_SERVER_ROOT_URL: "%(protocol)s://%(domain)s:%(http_port)s/grafana/"
- name=Prometheus GF_AUTH_GENERIC_OAUTH_ENABLED: ${GRAFANA_OIDC_ENABLED:-false}
- type=prometheus GF_AUTH_GENERIC_OAUTH_NAME: Authentik
- update-interval=10 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: networks:
- back-tier - back-tier
- front-tier - front-tier
restart: always depends_on:
- zroc-prometheus
# ── Auto-updates — Watchtower ─────────────────────────────────────────────
watchtower: watchtower:
image: containrrr/watchtower image: containrrr/watchtower
container_name: zroc-watchtower
restart: unless-stopped
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
environment: environment:
- WATCHTOWER_POLL_INTERVAL=360 # 1 hour WATCHTOWER_POLL_INTERVAL: "3600"
restart: always WATCHTOWER_CLEANUP: "true"
WATCHTOWER_INCLUDE_STOPPED: "false"
command: --label-enable
+4 -4
View File
@@ -31,7 +31,7 @@ jobs:
fi fi
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=v$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 - name: Install Packer
run: | run: |
@@ -42,7 +42,7 @@ jobs:
- name: Packer init - name: Packer init
working-directory: packer working-directory: packer
run: packer init ubuntu-2604.pkr.hcl run: packer init ubuntu-2404.pkr.hcl
- name: Validate - name: Validate
working-directory: packer working-directory: packer
@@ -50,7 +50,7 @@ jobs:
packer validate \ packer validate \
-var "vm_version=${{ steps.ver.outputs.version }}" \ -var "vm_version=${{ steps.ver.outputs.version }}" \
-var-file=variables.pkrvars.hcl \ -var-file=variables.pkrvars.hcl \
ubuntu-2604.pkr.hcl ubuntu-2404.pkr.hcl
- name: Build OVA - name: Build OVA
working-directory: packer working-directory: packer
@@ -62,7 +62,7 @@ jobs:
-var "vm_version=${{ steps.ver.outputs.version }}" \ -var "vm_version=${{ steps.ver.outputs.version }}" \
-var "headless=true" \ -var "headless=true" \
-var-file=variables.pkrvars.hcl \ -var-file=variables.pkrvars.hcl \
ubuntu-2604.pkr.hcl ubuntu-2404.pkr.hcl
- name: Locate OVA - name: Locate OVA
id: ova id: ova
+2 -2
View File
@@ -1,11 +1,11 @@
# zroc-ova — zROC Appliance Builder # 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 ## What you get
A 100 GB thin-provisioned VMware OVA containing: A 100 GB thin-provisioned VMware OVA containing:
- Ubuntu Server 26.04 LTS - Ubuntu Server 24.04 LTS
- Docker Engine + Compose plugin - Docker Engine + Compose plugin
- Full zROC stack (cloned from recklessop/zroc) - Full zROC stack (cloned from recklessop/zroc)
- Interactive first-boot setup wizard (`zroc-setup`) - Interactive first-boot setup wizard (`zroc-setup`)
@@ -14,12 +14,12 @@ packer {
variable "ubuntu_iso_url" { variable "ubuntu_iso_url" {
type = string 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" { variable "ubuntu_iso_checksum" {
type = string type = string
default = "file:https://releases.ubuntu.com/26.04/SHA256SUMS" default = "file:https://releases.ubuntu.com/24.04/SHA256SUMS"
} }
variable "vm_name" { variable "vm_name" {
@@ -57,7 +57,7 @@ variable "headless" {
default = true default = true
} }
source "vmware-iso" "ubuntu2604" { source "vmware-iso" "ubuntu2404" {
vm_name = "${var.vm_name}-${var.vm_version}" vm_name = "${var.vm_name}-${var.vm_version}"
guest_os_type = "ubuntu-64" guest_os_type = "ubuntu-64"
headless = var.headless headless = var.headless
@@ -96,7 +96,7 @@ source "vmware-iso" "ubuntu2604" {
} }
} }
source "qemu" "ubuntu2604" { source "qemu" "ubuntu2404" {
vm_name = "${var.vm_name}-${var.vm_version}" vm_name = "${var.vm_name}-${var.vm_version}"
iso_url = var.ubuntu_iso_url iso_url = var.ubuntu_iso_url
iso_checksum = var.ubuntu_iso_checksum iso_checksum = var.ubuntu_iso_checksum
@@ -126,7 +126,13 @@ source "qemu" "ubuntu2604" {
build { build {
name = "zroc-appliance" name = "zroc-appliance"
sources = ["source.vmware-iso.ubuntu2604"] 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" { provisioner "shell" {
script = "../scripts/00-base.sh" script = "../scripts/00-base.sh"
@@ -161,12 +167,12 @@ build {
} }
post-processor "shell-local" { post-processor "shell-local" {
only = ["vmware-iso.ubuntu2604"] only = ["vmware-iso.ubuntu2404"]
inline = [ inline = [
"cd ${var.output_dir}/vmware", "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 ..", "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'", "echo 'OVA packaged successfully'",
] ]
} }
+2 -2
View File
@@ -1,8 +1,8 @@
# zroc-ova/packer/variables.pkrvars.hcl # zroc-ova/packer/variables.pkrvars.hcl
vm_version = "1.0.0" vm_version = "1.0.0"
ubuntu_iso_url = "https://releases.ubuntu.com/26.04/ubuntu-26.04-live-server-amd64.iso" 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/26.04/SHA256SUMS" ubuntu_iso_checksum = "file:https://releases.ubuntu.com/24.04/SHA256SUMS"
memory_mb = 8192 memory_mb = 8192
cpus = 4 cpus = 4
+16 -2
View File
@@ -8,6 +8,7 @@ ZROC_REPO="https://github.com/recklessop/zroc.git"
git clone --depth=1 "$ZROC_REPO" "$INSTALL_DIR" git clone --depth=1 "$ZROC_REPO" "$INSTALL_DIR"
# Ensure expected directories exist
mkdir -p \ mkdir -p \
"$INSTALL_DIR/certs" \ "$INSTALL_DIR/certs" \
"$INSTALL_DIR/zvmexporter" \ "$INSTALL_DIR/zvmexporter" \
@@ -15,8 +16,21 @@ mkdir -p \
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
docker compose pull prometheus grafana authentik-server authentik-worker \ # Pre-pull all container images into the OVA image layer so first-boot is fast.
|| echo "[02-zroc] Some images not yet available — will pull on first start" # 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" chown -R zroc:zroc "$INSTALL_DIR"
+4 -1
View File
@@ -3,7 +3,10 @@
set -euo pipefail set -euo pipefail
echo "==> [03-setup-wizard] Installing setup wizard" 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' cat > /etc/systemd/system/zroc-firstboot.service << 'EOF'
[Unit] [Unit]