diff --git a/.claude/projects/-home-rracine-hanalyx-openwatch/memory/project_bsd_minimal.md b/.claude/projects/-home-rracine-hanalyx-openwatch/memory/project_bsd_minimal.md deleted file mode 100644 index 7225459b..00000000 --- a/.claude/projects/-home-rracine-hanalyx-openwatch/memory/project_bsd_minimal.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: BSD minimal platform decision -description: OpenWatch will target BSD minimal as base for all containers and native deployments, replacing the current mix of Red Hat UBI 9 + Alpine + Debian -type: project ---- - -OpenWatch targets BSD minimal for all container images and native deployments (decision 2026-04-13). - -**Why:** Minimize dependencies and attack surface for air-gapped federal environments. Current setup uses 3 different distros (UBI 9, Debian, Alpine) across 6 containers. - -**How to apply:** When creating Dockerfiles, packaging scripts, or system-level code, target BSD minimal — not Alpine, not UBI 9, not Debian. FIPS compliance via OpenSSL 3.x FIPS provider module (portable, not tied to Red Hat's CMVP certificate). Native packages will include FreeBSD pkg format alongside RPM/DEB. diff --git a/BACKLOG.md b/BACKLOG.md index e58fb414..5b9cb3f7 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -206,7 +206,6 @@ Items from `docs/OW_SECURITY_ASSESSMENT.md` that require careful sequencing due | PostgreSQL job queue | Replaces Celery + Redis (SKIP LOCKED, 40 tasks, scheduler) | | Dependency cleanup | 13 packages removed, Chart.js removed from frontend | | Redis + Celery removed | Zero Redis/Celery in codebase, 4 containers (down from 6) | -| FreeBSD 15.0 packaging | Dockerfiles, docker-compose.freebsd.yml, rc.d scripts, pkg skeleton | | Rules-first transactions UI | `/transactions` → `/transactions/rule/:id` → `/transactions/:id` | --- diff --git a/docker-compose.freebsd.yml b/docker-compose.freebsd.yml deleted file mode 100644 index 3db57091..00000000 --- a/docker-compose.freebsd.yml +++ /dev/null @@ -1,136 +0,0 @@ -# OpenWatch FreeBSD 15.0 Deployment -# -# UNTESTED - Requires OCI spec v1.3 runtime for FreeBSD containers. -# This compose file is structurally complete but has not been validated -# against a live FreeBSD container runtime. -# -# Key differences from docker-compose.yml (Linux): -# - NO Redis container (worker uses PostgreSQL-backed job queue) -# - NO Celery Beat container (scheduler integrated into backend) -# - NO separate frontend container (SPA embedded in backend via Option A) -# - Worker runs `python3.12 -m app.services.job_queue` (not celery) -# - PostgreSQL data dir: /var/db/postgres/data15 (FreeBSD convention) -# - 3 containers total: backend, worker, db -# -# Usage: -# docker compose -f docker-compose.freebsd.yml up --build -# docker compose -f docker-compose.freebsd.yml down -# -# To use Option B (separate Nginx frontend), uncomment the frontend service below. - -services: - # PostgreSQL 15 on FreeBSD - openwatch-db: - build: - context: . - dockerfile: docker/Dockerfile.db.freebsd - container_name: openwatch-db-freebsd - environment: - POSTGRES_USER: openwatch - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-openwatch} - POSTGRES_DB: openwatch - volumes: - - pg_data:/var/db/postgres/data15 - ports: - - "127.0.0.1:5432:5432" - networks: - - openwatch-network - restart: unless-stopped - healthcheck: - test: ["CMD", "su", "-m", "postgres", "-c", "/usr/local/bin/pg_isready -U openwatch"] - interval: 10s - timeout: 5s - start_period: 30s - retries: 5 - - # FastAPI Backend + embedded frontend SPA (Option A) - openwatch-backend: - build: - context: . - dockerfile: docker/Dockerfile.backend.freebsd - container_name: openwatch-backend-freebsd - ports: - - "8000:8000" - environment: - OPENWATCH_DATABASE_URL: postgresql://openwatch:${POSTGRES_PASSWORD:-openwatch}@openwatch-db:5432/openwatch - OPENWATCH_SECRET_KEY: ${OPENWATCH_SECRET_KEY:-change-me-in-production} - OPENWATCH_MASTER_KEY: ${OPENWATCH_MASTER_KEY:-change-me-32-chars-minimum-key!!} - OPENWATCH_ENCRYPTION_KEY: ${OPENWATCH_ENCRYPTION_KEY:-change-me-32-chars-minimum-key!!} - OPENWATCH_DEBUG: "true" - OPENWATCH_FIPS_MODE: "false" - OPENWATCH_LICENSE_TIER: "${OPENWATCH_LICENSE_TIER:-openwatch_plus}" - OPENWATCH_SSH_STRICT_MODE: "false" - volumes: - - app_data:/opt/openwatch/data - - app_logs:/opt/openwatch/logs - depends_on: - openwatch-db: - condition: service_healthy - networks: - - openwatch-network - restart: unless-stopped - healthcheck: - test: ["CMD", "python3.12", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] - interval: 30s - timeout: 5s - retries: 3 - - # Background worker (PostgreSQL-backed job queue, no Redis/Celery) - openwatch-worker: - build: - context: . - dockerfile: docker/Dockerfile.backend.freebsd - container_name: openwatch-worker-freebsd - command: ["python3.12", "-m", "app.services.job_queue"] - environment: - OPENWATCH_DATABASE_URL: postgresql://openwatch:${POSTGRES_PASSWORD:-openwatch}@openwatch-db:5432/openwatch - OPENWATCH_SECRET_KEY: ${OPENWATCH_SECRET_KEY:-change-me-in-production} - OPENWATCH_MASTER_KEY: ${OPENWATCH_MASTER_KEY:-change-me-32-chars-minimum-key!!} - OPENWATCH_ENCRYPTION_KEY: ${OPENWATCH_ENCRYPTION_KEY:-change-me-32-chars-minimum-key!!} - OPENWATCH_DEBUG: "true" - OPENWATCH_FIPS_MODE: "false" - OPENWATCH_LICENSE_TIER: "${OPENWATCH_LICENSE_TIER:-openwatch_plus}" - OPENWATCH_SSH_STRICT_MODE: "false" - volumes: - - app_data:/opt/openwatch/data - - app_logs:/opt/openwatch/logs - depends_on: - openwatch-db: - condition: service_healthy - networks: - - openwatch-network - restart: unless-stopped - - # --- Option B: Uncomment to use a separate Nginx frontend container --- - # openwatch-frontend: - # build: - # context: . - # dockerfile: docker/Dockerfile.frontend.freebsd - # container_name: openwatch-frontend-freebsd - # ports: - # - "3000:80" - # depends_on: - # - openwatch-backend - # networks: - # - openwatch-network - # restart: unless-stopped - # healthcheck: - # test: ["CMD", "fetch", "-qo", "/dev/null", "http://localhost:80/health"] - # interval: 30s - # timeout: 5s - # retries: 3 - -volumes: - pg_data: - driver: local - app_data: - driver: local - app_logs: - driver: local - -networks: - openwatch-network: - driver: bridge - ipam: - config: - - subnet: 172.21.0.0/16 diff --git a/docker/Dockerfile.backend.freebsd b/docker/Dockerfile.backend.freebsd deleted file mode 100644 index 5a4bbf5d..00000000 --- a/docker/Dockerfile.backend.freebsd +++ /dev/null @@ -1,168 +0,0 @@ -# OpenWatch Backend - FreeBSD 15.0 Minimal -# -# UNTESTED - FreeBSD OCI containers require OCI spec v1.3 runtime support. -# This Dockerfile is structurally complete but has not been validated against -# a live FreeBSD container runtime. Package names and paths may need adjustment. -# -# Replaces Red Hat UBI 9 for reduced attack surface and dependency minimization. -# FreeBSD base provides a minimal, auditable userland with fewer CVE vectors -# than typical Linux distributions. -# -# Migration rationale: -# - Smaller base image (FreeBSD minimal vs UBI9 ~200MB) -# - BSD-licensed userland (no GPL entanglements for air-gapped deployments) -# - Native jails support for additional isolation layers -# - ZFS integration for data integrity (when host uses ZFS volumes) -# -# FreeBSD package manager: pkg(8) — NOT apt, dnf, or apk -# FreeBSD paths differ from Linux: -# /usr/local/bin/ — third-party binaries (Python, PostgreSQL client) -# /usr/local/lib/ — third-party libraries -# /var/db/postgres/ — PostgreSQL data (not /var/lib/postgresql/) -# -# Security Compliance: -# - NIST SP 800-53 Rev. 5: SI-2, SC-13, AC-6 -# - FedRAMP Moderate: SI-2, SC-13, AC-6 -# - CMMC Level 2: SI.L2-3.14.1 - -# --------------------------------------------------------------------------- -# Stage 1: Build frontend SPA (Option A — embedded SPA, no separate container) -# --------------------------------------------------------------------------- -FROM node:20-alpine AS frontend-builder - -WORKDIR /app - -# Copy VERSION file for Vite build injection -COPY VERSION ./VERSION - -# Install dependencies first (layer caching) -COPY frontend/package*.json ./ -RUN npm ci --no-audit --no-fund - -# Copy frontend source and build -COPY frontend/ ./ -RUN npm run build - -# --------------------------------------------------------------------------- -# Stage 2: Build Python dependencies in isolated builder -# --------------------------------------------------------------------------- -FROM freebsd/freebsd:15.0-RELEASE AS builder - -# Install build tools and Python 3.12 -# NOTE: FreeBSD package names may vary between quarterly and latest repos. -# Verify with `pkg search python312` on a FreeBSD 15.0 system. -# -# Package rationale: -# python312 — Python 3.12 interpreter -# py312-pip — pip for Python 3.12 -# py312-setuptools — setuptools (needed by many wheels) -# postgresql15-client — libpq headers for psycopg2 compilation -# openssl — may be redundant (FreeBSD base includes LibreSSL/OpenSSL) -# libssh2 — SSH library for paramiko native extensions -# gcc — C compiler for native Python extensions -# cmake — build system for some native deps -# pkgconf — pkg-config equivalent on FreeBSD -# libffi — foreign function interface (required by cffi/cryptography) -# git — required for pip install of Kensa from git+https URL -RUN pkg install -y \ - python312 \ - py312-pip \ - py312-setuptools \ - postgresql15-client \ - openssl \ - libssh2 \ - gcc \ - cmake \ - pkgconf \ - libffi \ - git && \ - pkg clean -a -y - -WORKDIR /opt/openwatch - -# Copy requirements and build venv with all dependencies -COPY backend/requirements.txt . -RUN python3.12 -m venv /opt/openwatch/venv && \ - /opt/openwatch/venv/bin/pip install --no-cache-dir --upgrade pip && \ - /opt/openwatch/venv/bin/pip install --no-cache-dir -r requirements.txt - -# --------------------------------------------------------------------------- -# Stage 3: Runtime (minimal — no compiler, no dev headers) -# --------------------------------------------------------------------------- -FROM freebsd/freebsd:15.0-RELEASE - -LABEL maintainer="OpenWatch Security Team" \ - org.opencontainers.image.title="OpenWatch Backend (FreeBSD)" \ - org.opencontainers.image.description="Compliance Scanning Platform - FreeBSD 15.0 Minimal" \ - org.opencontainers.image.vendor="OpenWatch" \ - org.opencontainers.image.os="freebsd" \ - python.version="3.12" \ - freebsd.version="15.0-RELEASE" \ - status="UNTESTED" - -# Install only runtime dependencies — no compiler, no -devel packages -# -# Package rationale: -# python312 — Python 3.12 runtime -# postgresql15-client — psql CLI and libpq shared library -# openssh-portable — SSH client for remote host scanning -# -# NOTE: OpenSSL is included in FreeBSD base system; no separate package needed -# for runtime. If the base image strips it, add `openssl` to the list. -RUN pkg install -y \ - python312 \ - postgresql15-client \ - openssh-portable && \ - pkg clean -a -y - -# Create non-root application user (Principle of Least Privilege) -# UID 10001 chosen to avoid conflicts with system users (0-999) and -# typical user UIDs (1000+). -# NOTE: FreeBSD uses pw(8) instead of useradd(8). If the base image -# includes useradd (from shadow), that works too. -RUN pw useradd openwatch -u 10001 -d /nonexistent -s /usr/sbin/nologin && \ - mkdir -p /opt/openwatch/data /opt/openwatch/logs \ - /opt/openwatch/security/keys /opt/openwatch/security/certs && \ - chown -R openwatch:openwatch /opt/openwatch - -# Copy Python venv from builder (includes all pip-installed packages) -COPY --from=builder /opt/openwatch/venv /opt/openwatch/venv - -# Copy application code -COPY backend/ /opt/openwatch/backend/ - -# Copy built frontend SPA from frontend-builder (Option A: embedded) -COPY --from=frontend-builder /app/build /opt/openwatch/frontend/build - -# Copy entrypoint script -COPY docker/entrypoint-backend.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/entrypoint.sh - -# Set permissions -# /opt/openwatch/security: 700 — owner only, contains SSH keys and certs -RUN chown -R openwatch:openwatch /opt/openwatch && \ - chmod -R 755 /opt/openwatch && \ - chmod -R 700 /opt/openwatch/security - -WORKDIR /opt/openwatch/backend - -# Environment -ENV PATH="/opt/openwatch/venv/bin:$PATH" \ - PYTHONPATH="/opt/openwatch/backend" \ - PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - OPENWATCH_LOG_LEVEL=INFO \ - KENSA_RULES_PATH=/opt/openwatch/backend/kensa-rules - -# Switch to non-root user -USER openwatch - -EXPOSE 8000 - -# Health check using Python stdlib (no curl on FreeBSD base) -HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ - CMD python3.12 -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 - -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] - -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker/Dockerfile.db.freebsd b/docker/Dockerfile.db.freebsd deleted file mode 100644 index c5e9a22b..00000000 --- a/docker/Dockerfile.db.freebsd +++ /dev/null @@ -1,110 +0,0 @@ -# OpenWatch Database - PostgreSQL 15 on FreeBSD 15.0 -# -# UNTESTED - FreeBSD OCI containers require OCI spec v1.3 runtime support. -# This Dockerfile is structurally complete but has not been validated against -# a live FreeBSD container runtime. Package names and paths may need adjustment. -# -# FreeBSD PostgreSQL paths (differ significantly from Linux): -# Binary: /usr/local/bin/postgres -# initdb: /usr/local/bin/initdb -# pg_ctl: /usr/local/bin/pg_ctl -# Data dir: /var/db/postgres/data15/ -# Config: /var/db/postgres/data15/postgresql.conf -# rc script: /usr/local/etc/rc.d/postgresql -# User: postgres (created by the package) -# -# Why not just use postgres:15-alpine? -# - Consistency: all OpenWatch containers on the same OS (FreeBSD 15.0) -# - Reduced attack surface: FreeBSD base + PostgreSQL only -# - Air-gapped deployments: single OS to patch and audit - -FROM freebsd/freebsd:15.0-RELEASE - -LABEL maintainer="OpenWatch Security Team" \ - org.opencontainers.image.title="OpenWatch Database (FreeBSD)" \ - org.opencontainers.image.description="PostgreSQL 15 on FreeBSD 15.0" \ - org.opencontainers.image.vendor="OpenWatch" \ - org.opencontainers.image.os="freebsd" \ - status="UNTESTED" - -# Install PostgreSQL 15 server and client -# The postgresql15-server package creates the 'postgres' user automatically. -RUN pkg install -y \ - postgresql15-server \ - postgresql15-client && \ - pkg clean -a -y - -# Enable PostgreSQL in rc.conf (FreeBSD convention) -RUN echo 'postgresql_enable="YES"' >> /etc/rc.conf - -# Initialize the database cluster -# NOTE: On FreeBSD, the data directory is /var/db/postgres/data15/ by default. -# The initdb must run as the postgres user. -# The `|| true` guard handles the case where the data dir already exists. -RUN mkdir -p /var/db/postgres/data15 && \ - chown -R postgres:postgres /var/db/postgres && \ - su -m postgres -c "/usr/local/bin/initdb -D /var/db/postgres/data15 --encoding=UTF8 --locale=C" || true - -# Configure PostgreSQL for container networking -# - listen_addresses: accept connections from any container on the bridge network -# - max_connections: reasonable default for development/small deployments -# - shared_buffers: tuned for container memory limits -RUN echo "listen_addresses = '*'" >> /var/db/postgres/data15/postgresql.conf && \ - echo "max_connections = 100" >> /var/db/postgres/data15/postgresql.conf && \ - echo "shared_buffers = 128MB" >> /var/db/postgres/data15/postgresql.conf && \ - echo "log_destination = 'stderr'" >> /var/db/postgres/data15/postgresql.conf && \ - echo "logging_collector = off" >> /var/db/postgres/data15/postgresql.conf - -# Configure host-based authentication -# Allow password auth from the Docker bridge network (172.16.0.0/12 covers common subnets) -RUN echo "# TYPE DATABASE USER ADDRESS METHOD" > /var/db/postgres/data15/pg_hba.conf && \ - echo "local all all trust" >> /var/db/postgres/data15/pg_hba.conf && \ - echo "host all all 127.0.0.1/32 md5" >> /var/db/postgres/data15/pg_hba.conf && \ - echo "host all all ::1/128 md5" >> /var/db/postgres/data15/pg_hba.conf && \ - echo "host all all 0.0.0.0/0 md5" >> /var/db/postgres/data15/pg_hba.conf - -# Copy init script for creating the openwatch database and user -# This runs on first start when POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB are set. -COPY docker/database/init.sql /docker-entrypoint-initdb.d/init.sql - -# Create the entrypoint script inline -# NOTE: This is a minimal entrypoint. The official postgres Docker image has -# extensive init logic; this handles the basics for OpenWatch. -# hadolint ignore=SC2016 -RUN echo '#!/bin/sh' > /usr/local/bin/docker-entrypoint.sh && \ - echo 'set -e' >> /usr/local/bin/docker-entrypoint.sh && \ - echo '' >> /usr/local/bin/docker-entrypoint.sh && \ - echo 'PGDATA="/var/db/postgres/data15"' >> /usr/local/bin/docker-entrypoint.sh && \ - echo '' >> /usr/local/bin/docker-entrypoint.sh && \ - echo '# Create database and user if they do not exist' >> /usr/local/bin/docker-entrypoint.sh && \ - echo 'if [ -n "$POSTGRES_USER" ] && [ -n "$POSTGRES_PASSWORD" ]; then' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' # Start temporarily to run init commands' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' su -m postgres -c "/usr/local/bin/pg_ctl -D $PGDATA -w start -o \"-p 5432\""' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' su -m postgres -c "psql -c \"SELECT 1 FROM pg_roles WHERE rolname='"'"'$POSTGRES_USER'"'"'\" | grep -q 1 || psql -c \"CREATE ROLE $POSTGRES_USER WITH LOGIN PASSWORD '"'"'$POSTGRES_PASSWORD'"'"' CREATEDB;\""' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' if [ -n "$POSTGRES_DB" ]; then' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' su -m postgres -c "psql -lqt | cut -d\\| -f1 | grep -qw $POSTGRES_DB || createdb -O $POSTGRES_USER $POSTGRES_DB"' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' # Run init SQL if it exists and DB was just created' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' if [ -f /docker-entrypoint-initdb.d/init.sql ]; then' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' su -m postgres -c "psql -d $POSTGRES_DB -f /docker-entrypoint-initdb.d/init.sql" 2>/dev/null || true' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' fi' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' fi' >> /usr/local/bin/docker-entrypoint.sh && \ - echo ' su -m postgres -c "/usr/local/bin/pg_ctl -D $PGDATA -w stop"' >> /usr/local/bin/docker-entrypoint.sh && \ - echo 'fi' >> /usr/local/bin/docker-entrypoint.sh && \ - echo '' >> /usr/local/bin/docker-entrypoint.sh && \ - echo '# Start PostgreSQL in foreground' >> /usr/local/bin/docker-entrypoint.sh && \ - echo 'exec su -m postgres -c "/usr/local/bin/postgres -D $PGDATA"' >> /usr/local/bin/docker-entrypoint.sh && \ - chmod +x /usr/local/bin/docker-entrypoint.sh - -# Create initdb directory -RUN mkdir -p /docker-entrypoint-initdb.d && \ - chown postgres:postgres /docker-entrypoint-initdb.d - -EXPOSE 5432 - -# Volume for persistent data -VOLUME ["/var/db/postgres/data15"] - -HEALTHCHECK --interval=10s --timeout=5s --start-period=30s --retries=5 \ - CMD su -m postgres -c "/usr/local/bin/pg_isready -U postgres" || exit 1 - -ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] diff --git a/docker/Dockerfile.frontend.freebsd b/docker/Dockerfile.frontend.freebsd deleted file mode 100644 index c091ce36..00000000 --- a/docker/Dockerfile.frontend.freebsd +++ /dev/null @@ -1,102 +0,0 @@ -# OpenWatch Frontend - FreeBSD 15.0 + Nginx (Option B) -# -# UNTESTED - FreeBSD OCI containers require OCI spec v1.3 runtime support. -# This Dockerfile is structurally complete but has not been validated against -# a live FreeBSD container runtime. Package names and paths may need adjustment. -# -# Use this ONLY if you want a separate Nginx container for the frontend SPA. -# The recommended deployment (Option A) embeds the SPA in the backend container -# via Dockerfile.backend.freebsd, eliminating this container entirely. -# -# When to use Option B: -# - You need CDN-style caching headers managed by Nginx -# - You want to scale frontend and backend independently -# - You need Nginx-specific features (rate limiting, gzip, SSL termination) -# -# FreeBSD Nginx paths (differ from Linux): -# Config: /usr/local/etc/nginx/nginx.conf -# Modules: /usr/local/libexec/nginx/ -# Logs: /var/log/nginx/ -# PID: /var/run/nginx.pid -# Web root: /usr/local/www/ (convention, configurable) - -# --------------------------------------------------------------------------- -# Stage 1: Build frontend SPA -# --------------------------------------------------------------------------- -FROM node:20-alpine AS builder - -ARG APP_VERSION=0.0.0-dev -ARG GIT_COMMIT="" -ARG BUILD_DATE="" - -WORKDIR /app - -# Copy VERSION file for Vite config -COPY VERSION ./VERSION - -# Install dependencies (layer caching) -COPY frontend/package*.json ./ -RUN npm ci --no-audit --no-fund - -# Copy source and build -COPY frontend/ ./ - -ENV VITE_APP_VERSION=${APP_VERSION} \ - VITE_GIT_COMMIT=${GIT_COMMIT} \ - VITE_BUILD_DATE=${BUILD_DATE} - -RUN npm run build - -# --------------------------------------------------------------------------- -# Stage 2: FreeBSD Nginx runtime -# --------------------------------------------------------------------------- -FROM freebsd/freebsd:15.0-RELEASE - -LABEL maintainer="OpenWatch Security Team" \ - org.opencontainers.image.title="OpenWatch Frontend (FreeBSD)" \ - org.opencontainers.image.description="React SPA served by Nginx on FreeBSD 15.0" \ - org.opencontainers.image.vendor="OpenWatch" \ - org.opencontainers.image.os="freebsd" \ - status="UNTESTED" - -# Install Nginx -# NOTE: On FreeBSD, nginx is a single package. The default config lives at -# /usr/local/etc/nginx/nginx.conf (not /etc/nginx/). -RUN pkg install -y nginx && \ - pkg clean -a -y - -# Create non-root user -RUN pw useradd openwatch -u 10002 -d /nonexistent -s /usr/sbin/nologin - -# Create web root and log directories -RUN mkdir -p /usr/local/www/openwatch \ - /var/log/nginx \ - /var/run && \ - chown -R openwatch:openwatch /usr/local/www/openwatch /var/log/nginx - -# Copy built SPA from builder -COPY --from=builder /app/build /usr/local/www/openwatch/ - -# Copy Nginx configuration -# NOTE: This reuses the existing simple config but adjusts the root path. -# The config is copied to the FreeBSD-standard location. -COPY docker/frontend/nginx.conf /usr/local/etc/nginx/nginx.conf -COPY docker/frontend/default-simple.conf /usr/local/etc/nginx/conf.d/default.conf - -# Fix permissions -RUN chown -R openwatch:openwatch /usr/local/www/openwatch && \ - chmod -R 755 /usr/local/www/openwatch - -# NOTE: Running Nginx as non-root on FreeBSD requires the config to use -# ports > 1024 or the container runtime to map capabilities. The default -# config listens on port 80, which requires root or NET_BIND_SERVICE. -# For rootless operation, change the listen port to 8080 in default.conf. - -EXPOSE 80 - -# Health check using fetch(1) (FreeBSD base utility, no curl needed) -# Falls back to Python if fetch is not in the base image. -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD fetch -qo /dev/null http://localhost:80/health || exit 1 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/packaging/freebsd/build-pkg.sh b/packaging/freebsd/build-pkg.sh deleted file mode 100755 index f0b634f4..00000000 --- a/packaging/freebsd/build-pkg.sh +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env bash -# Build FreeBSD pkg for OpenWatch -# UNTESTED -- requires FreeBSD 15.0 build environment (native or jail) -# -# This script must run on FreeBSD 15.0 or inside a FreeBSD jail. -# It uses pkg-create(8) to produce a .pkg file suitable for air-gapped -# deployment via `pkg add openwatch-.pkg`. -# -# Prerequisites: -# - FreeBSD 15.0-RELEASE or compatible jail -# - pkg, python312, py312-pip, postgresql15-client, openssh-portable -# - Node.js 20+ (for frontend build) -# - git (for Kensa install from GitHub) -# -# Usage: -# ./packaging/freebsd/build-pkg.sh -# -# Output: -# packaging/freebsd/output/openwatch-.pkg -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" - -# Source version info -# shellcheck source=packaging/version.env -source "${PROJECT_ROOT}/packaging/version.env" - -echo "========================================" -echo "OpenWatch FreeBSD Package Builder" -echo "Version: ${VERSION}" -echo "Codename: ${CODENAME}" -echo "========================================" -echo "" -echo "NOTE: This script must run on FreeBSD 15.0 or in a FreeBSD jail." -echo " It has NOT been tested and is provided as a structural skeleton." -echo "" - -# Verify we are on FreeBSD -if [ "$(uname -s)" != "FreeBSD" ]; then - echo "ERROR: This script must run on FreeBSD. Detected: $(uname -s)" - exit 1 -fi - -# --- Build directories --- -BUILD_DIR="${SCRIPT_DIR}/build" -STAGING="${BUILD_DIR}/staging" -OUTPUT_DIR="${SCRIPT_DIR}/output" - -rm -rf "${BUILD_DIR}" -mkdir -p "${STAGING}" "${OUTPUT_DIR}" - -# --- Stage 1: Python virtual environment --- -echo "[1/5] Creating Python virtual environment..." -python3.12 -m venv "${STAGING}/opt/openwatch/venv" -"${STAGING}/opt/openwatch/venv/bin/pip" install --no-cache-dir --upgrade pip -"${STAGING}/opt/openwatch/venv/bin/pip" install --no-cache-dir -r "${PROJECT_ROOT}/backend/requirements.txt" - -# --- Stage 2: Backend application --- -echo "[2/5] Copying backend application..." -mkdir -p "${STAGING}/opt/openwatch/backend" -cp -a "${PROJECT_ROOT}/backend/app" "${STAGING}/opt/openwatch/backend/app" -cp "${PROJECT_ROOT}/backend/requirements.txt" "${STAGING}/opt/openwatch/backend/" - -# --- Stage 3: Frontend SPA --- -echo "[3/5] Building frontend SPA..." -if command -v npm >/dev/null 2>&1; then - cd "${PROJECT_ROOT}/frontend" - npm ci --no-audit --no-fund - npm run build - mkdir -p "${STAGING}/opt/openwatch/frontend" - cp -a "${PROJECT_ROOT}/frontend/build" "${STAGING}/opt/openwatch/frontend/build" - cd "${PROJECT_ROOT}" -else - echo "WARNING: npm not found, skipping frontend build." - echo " Install node20 and npm to include the frontend SPA." -fi - -# --- Stage 4: Kensa rules and mappings --- -echo "[4/5] Bundling Kensa rules..." -KENSA_TEMP=$(mktemp -d) -python3.12 -m venv "${KENSA_TEMP}/venv" -"${KENSA_TEMP}/venv/bin/pip" install --no-cache-dir kensa 2>/dev/null || \ - "${KENSA_TEMP}/venv/bin/pip" install --no-cache-dir \ - "kensa @ git+https://github.com/Hanalyx/kensa.git@v1.2.5" 2>/dev/null || true - -KENSA_SHARE=$(find "${KENSA_TEMP}/venv" -type d -name "kensa" -path "*/share/*" 2>/dev/null | head -1) -if [ -n "${KENSA_SHARE}" ]; then - mkdir -p "${STAGING}/opt/openwatch/backend/kensa" - cp -a "${KENSA_SHARE}/"* "${STAGING}/opt/openwatch/backend/kensa/" - echo " Kensa data copied from ${KENSA_SHARE}" -else - echo "WARNING: Could not locate Kensa share data. Rules will not be bundled." -fi -rm -rf "${KENSA_TEMP}" - -# --- Stage 5: Configuration and services --- -echo "[5/5] Installing configuration and rc.d services..." - -# Configuration directory -mkdir -p "${STAGING}/usr/local/etc/openwatch" -# TODO: Copy default ow.yml, secrets.env.example, logging.yml from packaging/config/ - -# rc.d service scripts -mkdir -p "${STAGING}/usr/local/etc/rc.d" -install -m 0555 "${SCRIPT_DIR}/rc.d/openwatch_api" "${STAGING}/usr/local/etc/rc.d/openwatch_api" -install -m 0555 "${SCRIPT_DIR}/rc.d/openwatch_worker" "${STAGING}/usr/local/etc/rc.d/openwatch_worker" - -# --- Create package manifest --- -echo "Creating package manifest..." - -cat > "${BUILD_DIR}/+MANIFEST" < "${BUILD_DIR}/+COMPACT_MANIFEST" - -# --- Build the package --- -echo "" -echo "TODO: Run pkg-create(8) to produce the final .pkg file." -echo " The staging directory is ready at: ${STAGING}" -echo "" -echo " Example (untested):" -echo " pkg create -m ${BUILD_DIR} -r ${STAGING} -o ${OUTPUT_DIR}" -echo "" -echo " Expected output: ${OUTPUT_DIR}/openwatch-${VERSION}.pkg" -echo "" - -# Uncomment when ready to build: -# pkg create -m "${BUILD_DIR}" -r "${STAGING}" -o "${OUTPUT_DIR}" - -echo "Build skeleton complete. Package staging directory: ${STAGING}" diff --git a/packaging/freebsd/rc.d/openwatch_api b/packaging/freebsd/rc.d/openwatch_api deleted file mode 100755 index c28dc232..00000000 --- a/packaging/freebsd/rc.d/openwatch_api +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/sh -# -# PROVIDE: openwatch_api -# REQUIRE: LOGIN postgresql -# KEYWORD: shutdown -# -# OpenWatch API service (FastAPI/Uvicorn) -# -# Add the following lines to /etc/rc.conf to enable: -# openwatch_api_enable="YES" -# -# Optional rc.conf settings: -# openwatch_api_host="127.0.0.1" # Listen address (default: 127.0.0.1) -# openwatch_api_port="8000" # Listen port (default: 8000) -# openwatch_api_workers="4" # Uvicorn workers (default: 4) -# openwatch_api_user="openwatch" # Run as user (default: openwatch) -# openwatch_api_logfile="/var/log/openwatch/api.log" - -. /etc/rc.subr - -name="openwatch_api" -rcvar="${name}_enable" - -load_rc_config $name - -: ${openwatch_api_enable:="NO"} -: ${openwatch_api_host:="127.0.0.1"} -: ${openwatch_api_port:="8000"} -: ${openwatch_api_workers:="4"} -: ${openwatch_api_user:="openwatch"} -: ${openwatch_api_logfile:="/var/log/openwatch/api.log"} - -pidfile="/var/run/${name}.pid" -command="/opt/openwatch/venv/bin/uvicorn" -command_args="app.main:app --host ${openwatch_api_host} --port ${openwatch_api_port} --workers ${openwatch_api_workers}" - -start_precmd="${name}_prestart" -stop_postcmd="${name}_poststop" - -openwatch_api_prestart() -{ - # Ensure log directory exists - mkdir -p /var/log/openwatch - chown "${openwatch_api_user}" /var/log/openwatch - - # Set working directory and environment - cd /opt/openwatch/backend || return 1 - export PYTHONPATH=/opt/openwatch/backend - export PATH="/opt/openwatch/venv/bin:${PATH}" - - # Source environment file if it exists - if [ -f /usr/local/etc/openwatch/secrets.env ]; then - set -a - . /usr/local/etc/openwatch/secrets.env - set +a - fi -} - -openwatch_api_poststop() -{ - rm -f "${pidfile}" -} - -run_rc_command "$1" diff --git a/packaging/freebsd/rc.d/openwatch_worker b/packaging/freebsd/rc.d/openwatch_worker deleted file mode 100755 index fad2a361..00000000 --- a/packaging/freebsd/rc.d/openwatch_worker +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/sh -# -# PROVIDE: openwatch_worker -# REQUIRE: LOGIN postgresql openwatch_api -# KEYWORD: shutdown -# -# OpenWatch background worker service (PostgreSQL-backed job queue) -# -# Add the following lines to /etc/rc.conf to enable: -# openwatch_worker_enable="YES" -# -# Optional rc.conf settings: -# openwatch_worker_user="openwatch" # Run as user (default: openwatch) -# openwatch_worker_logfile="/var/log/openwatch/worker.log" - -. /etc/rc.subr - -name="openwatch_worker" -rcvar="${name}_enable" - -load_rc_config $name - -: ${openwatch_worker_enable:="NO"} -: ${openwatch_worker_user:="openwatch"} -: ${openwatch_worker_logfile:="/var/log/openwatch/worker.log"} - -pidfile="/var/run/${name}.pid" -command="/opt/openwatch/venv/bin/python3.12" -command_args="-m app.services.job_queue" - -start_precmd="${name}_prestart" -stop_postcmd="${name}_poststop" - -openwatch_worker_prestart() -{ - # Ensure log directory exists - mkdir -p /var/log/openwatch - chown "${openwatch_worker_user}" /var/log/openwatch - - # Set working directory and environment - cd /opt/openwatch/backend || return 1 - export PYTHONPATH=/opt/openwatch/backend - export PATH="/opt/openwatch/venv/bin:${PATH}" - - # Source environment file if it exists - if [ -f /usr/local/etc/openwatch/secrets.env ]; then - set -a - . /usr/local/etc/openwatch/secrets.env - set +a - fi -} - -openwatch_worker_poststop() -{ - rm -f "${pidfile}" -} - -run_rc_command "$1"