From 93fcd96c2ec9b68b551b578c9e58ed4aba9e0072 Mon Sep 17 00:00:00 2001 From: notyusheng Date: Sun, 5 Apr 2026 20:19:34 +0800 Subject: [PATCH 1/5] fix: address review comments on sub-issue template - Add 'sub-issue' default label for consistency with other templates - Remove unnecessary backslash escaping from placeholder brackets Co-Authored-By: Claude Sonnet 4.6 --- .github/ISSUE_TEMPLATE/sub-issue.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/sub-issue.md b/.github/ISSUE_TEMPLATE/sub-issue.md index 0c2fc9d..7b935fb 100644 --- a/.github/ISSUE_TEMPLATE/sub-issue.md +++ b/.github/ISSUE_TEMPLATE/sub-issue.md @@ -2,11 +2,11 @@ name: Sub-Issue Template about: Use this for tracking sub-tasks under major issues title: "[Sub-Issue] " -labels: +labels: sub-issue assignees: '' --- -### Sub-Issue: \[Brief Description of the Sub-Task] +### Sub-Issue: [Brief Description of the Sub-Task] **Related to**: #X @@ -14,7 +14,7 @@ assignees: '' ### What needs to be done -\[Clearly describe the scope and purpose of this sub-issue. If applicable, mention relevant existing code, its limitations, and what the outcome of this sub-issue should achieve.] +[Clearly describe the scope and purpose of this sub-issue. If applicable, mention relevant existing code, its limitations, and what the outcome of this sub-issue should achieve.] --- @@ -33,4 +33,4 @@ assignees: '' ### Why this is needed -\[Explain why the change improves maintainability, reusability, or correctness. Focus on long-term benefits like reducing duplication, simplifying onboarding, or decoupling service responsibilities.] +[Explain why the change improves maintainability, reusability, or correctness. Focus on long-term benefits like reducing duplication, simplifying onboarding, or decoupling service responsibilities.] From c2952358fcccf1dd794600b304ea59f13a123d9b Mon Sep 17 00:00:00 2001 From: notyusheng Date: Mon, 13 Apr 2026 22:57:15 +0800 Subject: [PATCH 2/5] feat: add offline deployment support with image save/load scripts Adds docker-compose.offline.yml and two helper scripts for deploying TracePcap in air-gapped environments: pull-and-save-images.sh (builds and exports all images as tars) and load-images.sh (loads them on the offline host). Co-Authored-By: Claude Sonnet 4.6 --- docker-compose.offline.yml | 132 ++++++++++++++++++++++++++++++++ scripts/load-images.sh | 49 ++++++++++++ scripts/pull-and-save-images.sh | 115 ++++++++++++++++++++++++++++ 3 files changed, 296 insertions(+) create mode 100644 docker-compose.offline.yml create mode 100755 scripts/load-images.sh create mode 100755 scripts/pull-and-save-images.sh diff --git a/docker-compose.offline.yml b/docker-compose.offline.yml new file mode 100644 index 0000000..0e15e1d --- /dev/null +++ b/docker-compose.offline.yml @@ -0,0 +1,132 @@ +# docker-compose.offline.yml +# +# Offline deployment variant — uses pre-built images loaded via scripts/load-images.sh +# instead of building from source. +# +# Prerequisites: +# 1. On an internet-connected machine: bash scripts/pull-and-save-images.sh +# 2. Transfer images/, this file, and .env to the offline machine. +# 3. On the offline machine: bash scripts/load-images.sh +# +# Start: docker compose -f docker-compose.offline.yml up -d +# Stop: docker compose -f docker-compose.offline.yml down + +services: + # PostgreSQL Database + postgres: + image: postgres:15-alpine + container_name: tracepcap-postgres + environment: + POSTGRES_DB: tracepcap + POSTGRES_USER: tracepcap_user + POSTGRES_PASSWORD: tracepcap_pass + TZ: Asia/Singapore + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U tracepcap_user -d tracepcap"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - tracepcap-network + + # MinIO Object Storage + minio: + image: minio/minio:latest + container_name: tracepcap-minio + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + TZ: Asia/Singapore + ports: + - "9000:9000" # API + - "9001:9001" # Console + volumes: + - minio_data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + networks: + - tracepcap-network + + # MinIO Client (mc) - Create bucket on startup + minio-init: + image: minio/mc:latest + container_name: tracepcap-minio-init + depends_on: + - minio + entrypoint: > + /bin/sh -c " + sleep 5; + /usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin; + /usr/bin/mc mb myminio/tracepcap-files --ignore-existing; + /usr/bin/mc anonymous set public myminio/tracepcap-files; + exit 0; + " + networks: + - tracepcap-network + + # Spring Boot Backend (pre-built image) + backend: + image: tracepcap-backend:latest + container_name: tracepcap-backend + environment: + SPRING_PROFILES_ACTIVE: dev + DATABASE_URL: jdbc:postgresql://postgres:5432/tracepcap + DATABASE_USERNAME: tracepcap_user + DATABASE_PASSWORD: tracepcap_pass + MINIO_ENDPOINT: http://minio:9000 + MINIO_ACCESS_KEY: minioadmin + MINIO_SECRET_KEY: minioadmin + MINIO_BUCKET: tracepcap-files + APP_MEMORY_MB: ${APP_MEMORY_MB:-2048} + LLM_API_BASE_URL: ${LLM_API_BASE_URL:-https://api.openai.com/v1} + LLM_API_KEY: ${LLM_API_KEY:-your-api-key-here} + LLM_MODEL: ${LLM_MODEL:-gpt-4} + LLM_TEMPERATURE: ${LLM_TEMPERATURE:-0.7} + LLM_MAX_TOKENS: ${LLM_MAX_TOKENS:-2000} + LLM_TIMEOUT: ${LLM_TIMEOUT:-300} + TZ: Asia/Singapore + volumes: + - config_data:/app/config + depends_on: + postgres: + condition: service_healthy + minio: + condition: service_healthy + networks: + - tracepcap-network + + # Nginx - Serves frontend and proxies API to backend (pre-built image) + # Note: frontend build args (VITE_*) are baked into this image at build time. + # To change them, rebuild the image with pull-and-save-images.sh. + nginx: + image: tracepcap-nginx:latest + container_name: tracepcap-nginx + environment: + APP_MEMORY_MB: ${APP_MEMORY_MB:-2048} + TZ: Asia/Singapore + ports: + - "${NGINX_PORT:-80}:80" + depends_on: + - backend + networks: + - tracepcap-network + +volumes: + postgres_data: + driver: local + minio_data: + driver: local + config_data: + driver: local + +networks: + tracepcap-network: + driver: bridge diff --git a/scripts/load-images.sh b/scripts/load-images.sh new file mode 100755 index 0000000..13fe686 --- /dev/null +++ b/scripts/load-images.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# load-images.sh +# +# Run this on the OFFLINE machine after copying the images/ folder here. +# +# What it does: +# Loads every .tar file in ./images/ into the local Docker daemon. +# +# Usage: +# bash scripts/load-images.sh +# +# After loading, start the stack with: +# docker compose -f docker-compose.offline.yml up -d + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" +IMAGES_DIR="$ROOT_DIR/images" + +if [ ! -d "$IMAGES_DIR" ]; then + echo "Error: images/ directory not found at $IMAGES_DIR" + echo "Make sure you copied the images/ folder from the internet-connected machine." + exit 1 +fi + +# Collect .tar files +shopt -s nullglob +TAR_FILES=("$IMAGES_DIR"/*.tar) +shopt -u nullglob + +if [ ${#TAR_FILES[@]} -eq 0 ]; then + echo "Error: No .tar files found in $IMAGES_DIR/" + echo "Run pull-and-save-images.sh on an internet-connected machine first." + exit 1 +fi + +echo "=== Loading Docker images from images/ ===" +echo "" +for tarfile in "${TAR_FILES[@]}"; do + echo " Loading $(basename "$tarfile")..." + docker load -i "$tarfile" +done + +echo "" +echo "=== All images loaded successfully ===" +echo "" +echo "Start the application with:" +echo " docker compose -f docker-compose.offline.yml up -d" diff --git a/scripts/pull-and-save-images.sh b/scripts/pull-and-save-images.sh new file mode 100755 index 0000000..b541901 --- /dev/null +++ b/scripts/pull-and-save-images.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# pull-and-save-images.sh +# +# Run this on an internet-connected machine BEFORE transferring to the offline host. +# +# What it does: +# 1. Pulls all third-party images from Docker Hub +# 2. Builds the backend and nginx images locally +# 3. Saves every image as a .tar file under ./images/ +# +# Usage: +# bash scripts/pull-and-save-images.sh +# +# Build args for nginx are read from .env (if present) — copy .env.example first +# if you haven't already configured it. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" +IMAGES_DIR="$ROOT_DIR/images" + +BACKEND_IMAGE="tracepcap-backend:latest" +NGINX_IMAGE="tracepcap-nginx:latest" + +# --------------------------------------------------------------------------- +# Helper +# --------------------------------------------------------------------------- +save_image() { + local image="$1" + local filename="$2" + echo " Saving $image -> images/$filename" + docker save "$image" -o "$IMAGES_DIR/$filename" +} + +# --------------------------------------------------------------------------- +# Load build-arg overrides from .env when available +# --------------------------------------------------------------------------- +if [ -f "$ROOT_DIR/.env" ]; then + echo "Loading build args from .env" + set -a + # shellcheck source=/dev/null + source "$ROOT_DIR/.env" + set +a +fi + +mkdir -p "$IMAGES_DIR" + +# --------------------------------------------------------------------------- +# 1. Pull third-party images +# --------------------------------------------------------------------------- +echo "" +echo "=== [1/3] Pulling third-party images ===" + +# --- Docker Hub --- +DOCKERHUB_IMAGES=( + "postgres:15-alpine" + "minio/minio:latest" + "minio/mc:latest" +) + +for img in "${DOCKERHUB_IMAGES[@]}"; do + echo " Pulling $img (Docker Hub)..." + docker pull "$img" +done + +# --------------------------------------------------------------------------- +# 2. Build local images +# --------------------------------------------------------------------------- +echo "" +echo "=== [2/3] Building local images ===" +cd "$ROOT_DIR" + +echo " Building backend..." +docker build \ + -t "$BACKEND_IMAGE" \ + ./backend + +echo " Building nginx (frontend)..." +docker build \ + --build-arg VITE_API_BASE_URL=/api \ + --build-arg "VITE_SUPPORTED_FILE_TYPES=${VITE_SUPPORTED_FILE_TYPES:-.pcap,.pcapng,.cap}" \ + --build-arg "VITE_ANALYSIS_OPTIONS=${VITE_ANALYSIS_OPTIONS:-false}" \ + --build-arg "VITE_NETWORK_DIAGRAM_CONVERSATION_LIMIT=${VITE_NETWORK_DIAGRAM_CONVERSATION_LIMIT:-false}" \ + -t "$NGINX_IMAGE" \ + -f ./nginx/Dockerfile \ + . + +# --------------------------------------------------------------------------- +# 3. Save all images as tars +# --------------------------------------------------------------------------- +echo "" +echo "=== [3/3] Saving images to images/ ===" + +save_image "postgres:15-alpine" "postgres_15-alpine.tar" +save_image "minio/minio:latest" "minio_minio.tar" +save_image "minio/mc:latest" "minio_mc.tar" +save_image "$BACKEND_IMAGE" "tracepcap-backend.tar" +save_image "$NGINX_IMAGE" "tracepcap-nginx.tar" + +# --------------------------------------------------------------------------- +# Summary +# --------------------------------------------------------------------------- +echo "" +echo "=== Done ===" +echo "" +echo "Transfer the following to the offline machine:" +echo " images/ (all .tar files)" +echo " docker-compose.offline.yml" +echo " .env (or .env.example — configure before starting)" +echo " scripts/load-images.sh" +echo "" +echo "Then on the offline machine run:" +echo " bash scripts/load-images.sh" +echo " docker compose -f docker-compose.offline.yml up -d" From d909403321b351dc2aa1d994f6183d5d5b22e8a5 Mon Sep 17 00:00:00 2001 From: NotYuSheng Date: Tue, 14 Apr 2026 20:54:36 +0800 Subject: [PATCH 3/5] fix/offline-deploy-review-comments - Replace curl with wget --spider in minio healthcheck (more portable on minimal images) - Default LLM_API_BASE_URL to Ollama local endpoint instead of OpenAI public API - Pin minio/minio and minio/mc to specific release tags (non-deterministic latest removed) - Allow VITE_API_BASE_URL to be overridden via .env using ${VAR:-default} pattern - Loop over DOCKERHUB_IMAGES array to save tars instead of hardcoding image names Co-Authored-By: Claude Sonnet 4.6 --- docker-compose.offline.yml | 8 ++++---- scripts/pull-and-save-images.sh | 17 +++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docker-compose.offline.yml b/docker-compose.offline.yml index 0e15e1d..62aeea7 100644 --- a/docker-compose.offline.yml +++ b/docker-compose.offline.yml @@ -35,7 +35,7 @@ services: # MinIO Object Storage minio: - image: minio/minio:latest + image: minio/minio:RELEASE.2024-11-07T00-52-20Z container_name: tracepcap-minio command: server /data --console-address ":9001" environment: @@ -48,7 +48,7 @@ services: volumes: - minio_data:/data healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + test: ["CMD", "wget", "-q", "--spider", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 20s retries: 3 @@ -57,7 +57,7 @@ services: # MinIO Client (mc) - Create bucket on startup minio-init: - image: minio/mc:latest + image: minio/mc:RELEASE.2024-11-21T17-21-54Z container_name: tracepcap-minio-init depends_on: - minio @@ -86,7 +86,7 @@ services: MINIO_SECRET_KEY: minioadmin MINIO_BUCKET: tracepcap-files APP_MEMORY_MB: ${APP_MEMORY_MB:-2048} - LLM_API_BASE_URL: ${LLM_API_BASE_URL:-https://api.openai.com/v1} + LLM_API_BASE_URL: ${LLM_API_BASE_URL:-http://localhost:11434/v1} LLM_API_KEY: ${LLM_API_KEY:-your-api-key-here} LLM_MODEL: ${LLM_MODEL:-gpt-4} LLM_TEMPERATURE: ${LLM_TEMPERATURE:-0.7} diff --git a/scripts/pull-and-save-images.sh b/scripts/pull-and-save-images.sh index b541901..44a5e6e 100755 --- a/scripts/pull-and-save-images.sh +++ b/scripts/pull-and-save-images.sh @@ -55,8 +55,8 @@ echo "=== [1/3] Pulling third-party images ===" # --- Docker Hub --- DOCKERHUB_IMAGES=( "postgres:15-alpine" - "minio/minio:latest" - "minio/mc:latest" + "minio/minio:RELEASE.2024-11-07T00-52-20Z" + "minio/mc:RELEASE.2024-11-21T17-21-54Z" ) for img in "${DOCKERHUB_IMAGES[@]}"; do @@ -78,7 +78,7 @@ docker build \ echo " Building nginx (frontend)..." docker build \ - --build-arg VITE_API_BASE_URL=/api \ + --build-arg "VITE_API_BASE_URL=${VITE_API_BASE_URL:-/api}" \ --build-arg "VITE_SUPPORTED_FILE_TYPES=${VITE_SUPPORTED_FILE_TYPES:-.pcap,.pcapng,.cap}" \ --build-arg "VITE_ANALYSIS_OPTIONS=${VITE_ANALYSIS_OPTIONS:-false}" \ --build-arg "VITE_NETWORK_DIAGRAM_CONVERSATION_LIMIT=${VITE_NETWORK_DIAGRAM_CONVERSATION_LIMIT:-false}" \ @@ -92,11 +92,12 @@ docker build \ echo "" echo "=== [3/3] Saving images to images/ ===" -save_image "postgres:15-alpine" "postgres_15-alpine.tar" -save_image "minio/minio:latest" "minio_minio.tar" -save_image "minio/mc:latest" "minio_mc.tar" -save_image "$BACKEND_IMAGE" "tracepcap-backend.tar" -save_image "$NGINX_IMAGE" "tracepcap-nginx.tar" +for img in "${DOCKERHUB_IMAGES[@]}"; do + filename="$(echo "$img" | tr '/:' '_').tar" + save_image "$img" "$filename" +done +save_image "$BACKEND_IMAGE" "tracepcap-backend.tar" +save_image "$NGINX_IMAGE" "tracepcap-nginx.tar" # --------------------------------------------------------------------------- # Summary From bb3aad5dd2e82aa532dc1fdf087b829e1fa42746 Mon Sep 17 00:00:00 2001 From: NotYuSheng Date: Tue, 14 Apr 2026 21:01:48 +0800 Subject: [PATCH 4/5] docs: add offline deployment instructions to README Co-Authored-By: Claude Sonnet 4.6 --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index ac3cefb..c65cf49 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,42 @@ Navigate to **http://localhost:80/swagger-ui.html** to explore the API interacti TracePcap is designed for self-hosted deployment: +### Offline / Air-gapped Deployment + +For environments without internet access, use the offline deployment workflow: + +**On an internet-connected machine:** + +```bash +# Pull all third-party images, build local images, and save everything as .tar files +bash scripts/pull-and-save-images.sh +``` + +This creates a `images/` directory containing `.tar` files for all services. + +**Transfer to the offline machine:** + +``` +images/ # all .tar files +docker-compose.offline.yml +scripts/load-images.sh +.env # copy from .env.example and configure +``` + +**On the offline machine:** + +```bash +# Load all images into Docker +bash scripts/load-images.sh + +# Start the stack +docker compose -f docker-compose.offline.yml up -d +``` + +> **Note**: The offline compose file defaults `LLM_API_BASE_URL` to `http://localhost:11434/v1` (Ollama). Configure a locally-hosted LLM in `.env` before starting if you want AI features. + +--- + - **Development**: Use built-in configuration with exposed ports - **Production**: - Change default MinIO credentials in `docker-compose.yml` From 9d23b1a8ce8baafb4b882da4a31ae78d54f665cb Mon Sep 17 00:00:00 2001 From: NotYuSheng Date: Tue, 14 Apr 2026 21:02:34 +0800 Subject: [PATCH 5/5] docs: use LM Studio as default LLM example for offline deployment Co-Authored-By: Claude Sonnet 4.6 --- README.md | 2 +- docker-compose.offline.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c65cf49..366f5f1 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ bash scripts/load-images.sh docker compose -f docker-compose.offline.yml up -d ``` -> **Note**: The offline compose file defaults `LLM_API_BASE_URL` to `http://localhost:11434/v1` (Ollama). Configure a locally-hosted LLM in `.env` before starting if you want AI features. +> **Note**: The offline compose file defaults `LLM_API_BASE_URL` to `http://localhost:1234/v1` (LM Studio). Configure a locally-hosted LLM in `.env` before starting if you want AI features. --- diff --git a/docker-compose.offline.yml b/docker-compose.offline.yml index 62aeea7..c758d6d 100644 --- a/docker-compose.offline.yml +++ b/docker-compose.offline.yml @@ -86,7 +86,7 @@ services: MINIO_SECRET_KEY: minioadmin MINIO_BUCKET: tracepcap-files APP_MEMORY_MB: ${APP_MEMORY_MB:-2048} - LLM_API_BASE_URL: ${LLM_API_BASE_URL:-http://localhost:11434/v1} + LLM_API_BASE_URL: ${LLM_API_BASE_URL:-http://localhost:1234/v1} LLM_API_KEY: ${LLM_API_KEY:-your-api-key-here} LLM_MODEL: ${LLM_MODEL:-gpt-4} LLM_TEMPERATURE: ${LLM_TEMPERATURE:-0.7}