Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build-ova.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ jobs:
echo "Uploaded: s3://defguard-downloads/ova/${FILENAME}"

- name: Test deployment modes on Proxmox
if: ${{ github.event_name == 'workflow_dispatch' }}
env:
PROXMOX_HOST: ${{ secrets.PROXMOX_HOST }}
PROXMOX_SSH_KEY: ${{ secrets.PROXMOX_SSH_KEY }}
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/test-ova.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ jobs:
- name: Checkout
uses: actions/checkout@v7

- name: Install bats
- name: Install bats and jq
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends bats
sudo apt-get install -y --no-install-recommends bats jq

- name: Run logic and config tests
run: bats ova/tests/start.bats ova/tests/generate-env.bats ova/tests/config.bats ova/tests/firewall.bats
run: bats ova/tests/generate-env.bats ova/tests/generate-compose.bats ova/tests/firewall.bats

integration:
name: Real bring-up
Expand All @@ -37,10 +37,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v7

- name: Install bats
- name: Install bats and jq
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends bats
sudo apt-get install -y --no-install-recommends bats jq

- name: Login to GitHub container registry
uses: docker/login-action@v4
Expand Down
33 changes: 17 additions & 16 deletions ova/defguard.pkr.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ build {
}

provisioner "file" {
source = "files/docker-compose.yaml"
destination = "/tmp/docker-compose.yaml"
source = "files/docker-compose.template.yaml"
destination = "/tmp/docker-compose.template.yaml"
}

provisioner "file" {
source = "files/docker-compose.standalone.yaml"
destination = "/tmp/docker-compose.standalone.yaml"
source = "files/lib.sh"
destination = "/tmp/lib.sh"
}

provisioner "file" {
Expand All @@ -83,8 +83,8 @@ build {
}

provisioner "file" {
source = "files/start.sh"
destination = "/tmp/start.sh"
source = "files/generate-compose.sh"
destination = "/tmp/generate-compose.sh"
}

provisioner "file" {
Expand Down Expand Up @@ -115,16 +115,15 @@ build {
provisioner "shell" {
inline = [
"sudo bash /tmp/docker-setup.sh",
"sudo mkdir -p /opt/stacks/defguard",
"sudo mv /tmp/docker-compose.yaml /opt/stacks/defguard/docker-compose.yaml",
"sudo mv /tmp/docker-compose.standalone.yaml /opt/stacks/defguard/docker-compose.standalone.yaml",
"sudo mv /tmp/generate-env.sh /opt/stacks/defguard/generate-env.sh",
"sudo chmod +x /opt/stacks/defguard/generate-env.sh",
"sudo mv /tmp/start.sh /opt/stacks/defguard/start.sh",
"sudo chmod +x /opt/stacks/defguard/start.sh",
"echo 'DEFGUARD_CORE_TAG=${var.core_tag}' | sudo tee /opt/stacks/defguard/.image-tags > /dev/null",
"echo 'DEFGUARD_PROXY_TAG=${var.proxy_tag}' | sudo tee -a /opt/stacks/defguard/.image-tags > /dev/null",
"echo 'DEFGUARD_GATEWAY_TAG=${var.gateway_tag}' | sudo tee -a /opt/stacks/defguard/.image-tags > /dev/null",
"sudo mkdir -p /opt/stacks/defguard/init",
"sudo mv /tmp/docker-compose.template.yaml /opt/stacks/defguard/init/docker-compose.template.yaml",
"sudo mv /tmp/lib.sh /opt/stacks/defguard/init/lib.sh",
"sudo mv /tmp/generate-env.sh /opt/stacks/defguard/init/generate-env.sh",
"sudo mv /tmp/generate-compose.sh /opt/stacks/defguard/init/generate-compose.sh",
"sudo chmod +x /opt/stacks/defguard/init/generate-env.sh /opt/stacks/defguard/init/generate-compose.sh",
"echo 'DEFGUARD_CORE_TAG=${var.core_tag}' | sudo tee /opt/stacks/defguard/init/.image-tags > /dev/null",
"echo 'DEFGUARD_PROXY_TAG=${var.proxy_tag}' | sudo tee -a /opt/stacks/defguard/init/.image-tags > /dev/null",
"echo 'DEFGUARD_GATEWAY_TAG=${var.gateway_tag}' | sudo tee -a /opt/stacks/defguard/init/.image-tags > /dev/null",
"sudo mv /tmp/99-defguard.cfg /etc/cloud/cloud.cfg.d/99-defguard.cfg",
"sudo mv /tmp/defguard-init.service /etc/systemd/system/defguard-init.service",
"sudo mv /tmp/defguard-firewall.sh /opt/stacks/defguard/defguard-firewall.sh",
Expand All @@ -139,6 +138,8 @@ build {
"sudo systemctl enable defguard-init.service",
"sudo systemctl enable defguard-firewall.service",
"sudo chown -R ubuntu:ubuntu /opt/stacks/defguard",
"sudo chown -R root:root /opt/stacks/defguard/init",
"sudo chmod -R go-w /opt/stacks/defguard/init",
"sudo rm -f /etc/netplan/00-installer-config.yaml /etc/netplan/50-cloud-init.yaml",
"sudo cloud-init clean --logs",
"sudo rm -f /etc/ssh/ssh_host_*",
Expand Down
5 changes: 3 additions & 2 deletions ova/files/defguard-init.service
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ Type=oneshot
WorkingDirectory=/opt/stacks/defguard
StandardOutput=append:/var/log/defguard-startup.log
StandardError=append:/var/log/defguard-startup.log
ExecStart=/bin/bash /opt/stacks/defguard/generate-env.sh
ExecStart=/bin/bash /opt/stacks/defguard/start.sh
ExecStart=/bin/bash /opt/stacks/defguard/init/generate-env.sh
ExecStart=/bin/bash /opt/stacks/defguard/init/generate-compose.sh
ExecStart=/usr/bin/docker compose -f /opt/stacks/defguard/docker-compose.yml up -d

[Install]
WantedBy=multi-user.target
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ services:
core:
restart: unless-stopped
profiles: [core]
image: ghcr.io/defguard/defguard:${DEFGUARD_CORE_TAG:?DEFGUARD_CORE_TAG is required}
image: ghcr.io/defguard/defguard:__DEFGUARD_CORE_TAG__
env_file: .env
environment:
DEFGUARD_DB_HOST: db
DEFGUARD_DB_PORT: 5432
DEFGUARD_ADOPT_EDGE: ${DEFGUARD_ADOPT_EDGE:-}
DEFGUARD_ADOPT_GATEWAY: ${DEFGUARD_ADOPT_GATEWAY:-}
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
- db
ports:
Expand All @@ -15,19 +19,21 @@ services:
edge:
restart: unless-stopped
profiles: [edge]
image: ghcr.io/defguard/defguard-proxy:${DEFGUARD_PROXY_TAG:?DEFGUARD_PROXY_TAG is required}
image: ghcr.io/defguard/defguard-proxy:__DEFGUARD_PROXY_TAG__
volumes:
- ./.volumes/certs/edge:/etc/defguard/certs
ports:
- "8080:8080"
- "50051:50051"
- "443:443"
- "80:80"
# 50051 is added conditionally by generate-compose.sh: exposed for segmented
# deployments (core reaches edge from another host), omitted for the full
# all-in-one stack (core reaches edge via the docker-internal network).

gateway:
restart: unless-stopped
profiles: [gateway]
image: ghcr.io/defguard/gateway:${DEFGUARD_GATEWAY_TAG:?DEFGUARD_GATEWAY_TAG is required}
image: ghcr.io/defguard/gateway:__DEFGUARD_GATEWAY_TAG__
cap_add:
- NET_ADMIN
volumes:
Expand Down
60 changes: 0 additions & 60 deletions ova/files/docker-compose.yaml

This file was deleted.

3 changes: 2 additions & 1 deletion ova/files/docker-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ set -e
apt-get update
apt-get full-upgrade -y
# open-vm-tools: graceful shutdown, guest IP reporting, and time sync under VMware.
apt-get install -y ca-certificates curl open-vm-tools
# jq: strips profiles from generate-compose.sh's flattened docker-compose.yml
apt-get install -y ca-certificates curl open-vm-tools jq
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
Expand Down
67 changes: 67 additions & 0 deletions ova/files/generate-compose.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/bin/bash
# Flattens the profile-gated compose template into a single, self-contained
# /opt/stacks/defguard/docker-compose.yml on first boot, then removes the
# cloud-init mode-selection flag files so the stack directory stays clean.
# If docker-compose.yml already exists, this script does nothing.
set -euo pipefail

STACK_DIR="${DEFGUARD_STACK_DIR:-/opt/stacks/defguard}"
INIT_DIR="${DEFGUARD_INIT_DIR:-$STACK_DIR/init}"
TEMPLATE="$INIT_DIR/docker-compose.template.yaml"
COMPOSE_FILE="$STACK_DIR/docker-compose.yml"
PROFILES_FILE="$STACK_DIR/active-profiles"
ENABLE_DOCKER_MGMT_FILE="$STACK_DIR/enable-docker-management"

if [ -f "$COMPOSE_FILE" ]; then
echo "DefGuard: docker-compose.yml already exists, skipping generation."
exit 0
fi

# shellcheck source=lib.sh
source "$INIT_DIR/lib.sh"

mapfile -t _profiles < <(resolve_profiles "$STACK_DIR")

FULL_STACK=false
if is_full_stack "${_profiles[@]}"; then
FULL_STACK=true
fi

_profile_args=()
for p in "${_profiles[@]}"; do _profile_args+=(--profile "$p"); done

# `docker compose config --profile X` (repeatable) filters the emitted service
# list to active-profile + unprofiled services, but surviving services keep
# their `profiles:` key - which would make them invisible to a future plain
# `docker compose up -d` with no COMPOSE_PROFILES set. jq strips that key and
# injects the conditional depends_on/port tweaks; the result is then piped
# back through `docker compose config` (stdin) to render real YAML rather
# than leaving the file as JSON-flavored-as-YAML.
TMP_COMPOSE_FILE="$COMPOSE_FILE.tmp"
trap 'rm -f "$TMP_COMPOSE_FILE"' EXIT
{
echo "# Generated by defguard OVA first-boot init on $(date -u +%FT%TZ)."
echo "# Selected profiles: ${_profiles[*]}"
docker compose -f "$TEMPLATE" -p defguard --project-directory "$STACK_DIR" --env-file "$STACK_DIR/.env" "${_profile_args[@]}" \
config --format json \
| jq --argjson full "$FULL_STACK" '
del(.services[].profiles) | del(.name)
| if $full then
.services.core.depends_on = ["db", "edge", "gateway"]
else
(.services.edge // empty) |= (.ports += ["50051:50051"])
end
' \
| docker compose -f - -p defguard --project-directory "$STACK_DIR" config
} > "$TMP_COMPOSE_FILE"

sed -i \
-e "s/__DEFGUARD_CORE_TAG__/\${DEFGUARD_CORE_TAG}/" \
-e "s/__DEFGUARD_PROXY_TAG__/\${DEFGUARD_PROXY_TAG}/" \
-e "s/__DEFGUARD_GATEWAY_TAG__/\${DEFGUARD_GATEWAY_TAG}/" \
"$TMP_COMPOSE_FILE"

mv "$TMP_COMPOSE_FILE" "$COMPOSE_FILE"

rm -f "$PROFILES_FILE" "$ENABLE_DOCKER_MGMT_FILE"
echo "DefGuard: generated $COMPOSE_FILE for profiles: ${_profiles[*]}"
23 changes: 21 additions & 2 deletions ova/files/generate-env.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#!/bin/bash
# Generates /opt/stacks/defguard/.env with random secrets on first boot.
# If .env already exists (e.g. provided via cloud-init), this script does nothing.
set -euo pipefail

STACK_DIR="${DEFGUARD_STACK_DIR:-/opt/stacks/defguard}"
INIT_DIR="${DEFGUARD_INIT_DIR:-$STACK_DIR/init}"
ENV_FILE="$STACK_DIR/.env"

if [ -f "$ENV_FILE" ]; then
Expand All @@ -12,16 +14,30 @@ fi

echo "DefGuard: generating .env with random secrets..."

# shellcheck source=lib.sh
source "$INIT_DIR/lib.sh"

DB_PASSWORD=$(openssl rand -hex 16)

if [ -f "$STACK_DIR/.image-tags" ]; then
source "$STACK_DIR/.image-tags"
if [ -f "$INIT_DIR/.image-tags" ]; then
source "$INIT_DIR/.image-tags"
fi

: "${DEFGUARD_CORE_TAG:?DEFGUARD_CORE_TAG is required}"
: "${DEFGUARD_PROXY_TAG:?DEFGUARD_PROXY_TAG is required}"
: "${DEFGUARD_GATEWAY_TAG:?DEFGUARD_GATEWAY_TAG is required}"

# Only default the adopt targets when core/edge/gateway are genuinely
# co-located on this host; segmented deployments must fill these in manually
# since edge/gateway live on other VMs entirely.
mapfile -t _profiles < <(resolve_profiles "$STACK_DIR")
ADOPT_EDGE=""
ADOPT_GATEWAY=""
if is_full_stack "${_profiles[@]}"; then
ADOPT_EDGE="edge:50051"
ADOPT_GATEWAY="host.docker.internal:50066"
fi

cat > "$ENV_FILE" <<EOF
DEFGUARD_COOKIE_INSECURE=false

Expand All @@ -37,6 +53,9 @@ POSTGRES_DB=defguard
DEFGUARD_CORE_TAG=${DEFGUARD_CORE_TAG}
DEFGUARD_PROXY_TAG=${DEFGUARD_PROXY_TAG}
DEFGUARD_GATEWAY_TAG=${DEFGUARD_GATEWAY_TAG}

DEFGUARD_ADOPT_EDGE=${ADOPT_EDGE}
DEFGUARD_ADOPT_GATEWAY=${ADOPT_GATEWAY}
EOF

chmod 600 "$ENV_FILE"
Expand Down
46 changes: 46 additions & 0 deletions ova/files/lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/bash
# shellcheck shell=bash
# Shared helpers for generate-env.sh and generate-compose.sh.

# Prints the resolved profile list (one per line): the contents of
# $1/active-profiles if present (falling back to the full stack if empty or
# whitespace-only), otherwise core+edge+gateway. Appends "dockge" if
# $1/enable-docker-management exists.
resolve_profiles() {
local stack_dir="$1"
local profiles_file="$stack_dir/active-profiles"
local dockge_file="$stack_dir/enable-docker-management"
local -a profiles=()

if [ -f "$profiles_file" ]; then
mapfile -t profiles < <(tr '[:space:]' '\n' < "$profiles_file" | sed '/^$/d')
fi

if [ "${#profiles[@]}" -eq 0 ]; then
if [ -f "$profiles_file" ]; then
echo "Warning: $profiles_file is empty or contains only whitespace; using full all-in-one stack." >&2
fi
profiles=(core edge gateway)
fi

if [ -f "$dockge_file" ]; then
profiles+=(dockge)
fi

printf '%s\n' "${profiles[@]}"
}

# True (exit 0) only if core, edge, and gateway are ALL present among the given
# profiles (dockge and ordering are irrelevant) - i.e. this is a genuine
# same-host all-in-one deployment, not a segmented one.
is_full_stack() {
local has_core=0 has_edge=0 has_gateway=0 p
for p in "$@"; do
case "$p" in
core) has_core=1 ;;
edge) has_edge=1 ;;
gateway) has_gateway=1 ;;
esac
done
[ "$has_core" = 1 ] && [ "$has_edge" = 1 ] && [ "$has_gateway" = 1 ]
}
Loading