Initial Upload
Some checks failed
CI / Lint & Typecheck (push) Has been cancelled
CI / Test (routes) (push) Has been cancelled
CI / Test (security) (push) Has been cancelled
CI / Test (services) (push) Has been cancelled
CI / Test (unit) (push) Has been cancelled
CI / Test (integration) (push) Has been cancelled
CI / Test Coverage (push) Has been cancelled
CI / Build (push) Has been cancelled
Some checks failed
CI / Lint & Typecheck (push) Has been cancelled
CI / Test (routes) (push) Has been cancelled
CI / Test (security) (push) Has been cancelled
CI / Test (services) (push) Has been cancelled
CI / Test (unit) (push) Has been cancelled
CI / Test (integration) (push) Has been cancelled
CI / Test Coverage (push) Has been cancelled
CI / Build (push) Has been cancelled
This commit is contained in:
22
docker/.env.example
Normal file
22
docker/.env.example
Normal file
@@ -0,0 +1,22 @@
|
||||
# Docker Compose Environment Variables
|
||||
# Copy to .env and fill in your values
|
||||
|
||||
# Database password
|
||||
DB_PASSWORD=change-me-to-secure-password
|
||||
|
||||
# Authentication secrets (generate with: openssl rand -hex 32)
|
||||
JWT_SECRET=generate-with-openssl-rand-hex-32
|
||||
COOKIE_SECRET=generate-with-openssl-rand-hex-32
|
||||
|
||||
# CORS origin (* for all origins, or specific domain like https://your-domain.com)
|
||||
CORS_ORIGIN=*
|
||||
|
||||
# Optional
|
||||
# PORT=3000
|
||||
# LOG_LEVEL=info
|
||||
# POLLER_ENABLED=true
|
||||
# POLLER_INTERVAL=15000
|
||||
|
||||
# Mobile beta testing mode - enables reusable tokens, no expiry, unlimited devices
|
||||
# Useful for TestFlight/beta testing. Disable in production.
|
||||
# MOBILE_BETA_MODE=true
|
||||
74
docker/Dockerfile
Normal file
74
docker/Dockerfile
Normal file
@@ -0,0 +1,74 @@
|
||||
# Build stage
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@10.24.0 --activate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy ALL workspace package.json files for lockfile resolution
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY apps/server/package.json ./apps/server/
|
||||
COPY apps/web/package.json ./apps/web/
|
||||
COPY apps/mobile/package.json ./apps/mobile/
|
||||
COPY packages/shared/package.json ./packages/shared/
|
||||
COPY packages/test-utils/package.json ./packages/test-utils/
|
||||
|
||||
# Install dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build only production packages (excludes test-utils, mobile)
|
||||
RUN pnpm turbo run build --filter=@tracearr/shared --filter=@tracearr/server --filter=@tracearr/web
|
||||
|
||||
# Production stage
|
||||
FROM node:22-alpine AS runner
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@10.24.0 --activate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Copy package files for production install
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/pnpm-workspace.yaml ./
|
||||
COPY --from=builder /app/pnpm-lock.yaml ./
|
||||
|
||||
# Server package
|
||||
COPY --from=builder /app/apps/server/package.json ./apps/server/
|
||||
COPY --from=builder /app/apps/server/dist ./apps/server/dist
|
||||
|
||||
# Web static files (served by server or reverse proxy)
|
||||
COPY --from=builder /app/apps/web/dist ./apps/web/dist
|
||||
|
||||
# Shared package
|
||||
COPY --from=builder /app/packages/shared/package.json ./packages/shared/
|
||||
COPY --from=builder /app/packages/shared/dist ./packages/shared/dist
|
||||
|
||||
# Database migrations
|
||||
COPY --from=builder /app/apps/server/src/db/migrations ./apps/server/src/db/migrations
|
||||
|
||||
# GeoIP database (if exists in build context)
|
||||
COPY data/GeoLite2-City.mmdb ./data/GeoLite2-City.mmdb
|
||||
|
||||
# Install production dependencies only
|
||||
RUN pnpm install --prod --frozen-lockfile
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup --system --gid 1001 nodejs && \
|
||||
adduser --system --uid 1001 tracearr
|
||||
|
||||
# Set ownership
|
||||
RUN chown -R tracearr:nodejs /app
|
||||
|
||||
USER tracearr
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
# Health check - use 127.0.0.1 to force IPv4 (Alpine wget defaults to IPv6 first)
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/health || exit 1
|
||||
|
||||
CMD ["node", "apps/server/dist/index.js"]
|
||||
134
docker/Dockerfile.supervised
Normal file
134
docker/Dockerfile.supervised
Normal file
@@ -0,0 +1,134 @@
|
||||
# Tracearr All-in-One Image (Supervised)
|
||||
# Runs TimescaleDB, Redis, and Tracearr in a single container
|
||||
# Ideal for simple deployments, Unraid, Synology, etc.
|
||||
|
||||
FROM node:22-bookworm-slim AS builder
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@10.24.0 --activate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy ALL workspace package.json files for lockfile resolution
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY apps/server/package.json ./apps/server/
|
||||
COPY apps/web/package.json ./apps/web/
|
||||
COPY apps/mobile/package.json ./apps/mobile/
|
||||
COPY packages/shared/package.json ./packages/shared/
|
||||
COPY packages/test-utils/package.json ./packages/test-utils/
|
||||
|
||||
# Install dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build only production packages
|
||||
RUN pnpm turbo run build --filter=@tracearr/shared --filter=@tracearr/server --filter=@tracearr/web
|
||||
|
||||
# =============================================================================
|
||||
# Production All-in-One Image
|
||||
# =============================================================================
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
gnupg \
|
||||
lsb-release \
|
||||
supervisor \
|
||||
gosu \
|
||||
openssl \
|
||||
tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Node.js 22
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||
&& apt-get install -y nodejs \
|
||||
&& corepack enable \
|
||||
&& corepack prepare pnpm@10.24.0 --activate \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install PostgreSQL 15 + TimescaleDB
|
||||
RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql-keyring.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/postgresql-keyring.gpg] http://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
|
||||
&& curl -fsSL https://packagecloud.io/timescale/timescaledb/gpgkey | gpg --dearmor -o /usr/share/keyrings/timescaledb-keyring.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/timescaledb-keyring.gpg] https://packagecloud.io/timescale/timescaledb/debian/ bookworm main" > /etc/apt/sources.list.d/timescaledb.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
postgresql-15 \
|
||||
timescaledb-2-postgresql-15 \
|
||||
timescaledb-tools \
|
||||
timescaledb-toolkit-postgresql-15 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Redis
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
redis-server \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Note: PostgreSQL config is applied during initdb in entrypoint-supervised.sh
|
||||
# The /etc/postgresql/15/main/ config is not used since we use a custom data directory
|
||||
|
||||
# Configure Redis to listen only on localhost
|
||||
RUN sed -i 's/^bind .*/bind 127.0.0.1/' /etc/redis/redis.conf \
|
||||
&& sed -i 's/^daemonize yes/daemonize no/' /etc/redis/redis.conf
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built application from builder
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/pnpm-workspace.yaml ./
|
||||
COPY --from=builder /app/pnpm-lock.yaml ./
|
||||
COPY --from=builder /app/apps/server/package.json ./apps/server/
|
||||
COPY --from=builder /app/apps/server/dist ./apps/server/dist
|
||||
COPY --from=builder /app/apps/web/dist ./apps/web/dist
|
||||
COPY --from=builder /app/packages/shared/package.json ./packages/shared/
|
||||
COPY --from=builder /app/packages/shared/dist ./packages/shared/dist
|
||||
COPY --from=builder /app/apps/server/src/db/migrations ./apps/server/src/db/migrations
|
||||
|
||||
# GeoIP database (bundled for geolocation features)
|
||||
COPY data/GeoLite2-City.mmdb ./data/GeoLite2-City.mmdb
|
||||
|
||||
# Install production dependencies
|
||||
RUN pnpm install --prod --frozen-lockfile
|
||||
|
||||
# Create tracearr user for running the application (non-root)
|
||||
RUN groupadd --system --gid 1001 tracearr \
|
||||
&& useradd --system --uid 1001 --gid tracearr --shell /bin/false tracearr
|
||||
|
||||
# Create data directories with proper ownership
|
||||
RUN mkdir -p /data/postgres /data/redis /data/tracearr /var/log/supervisor \
|
||||
&& chown -R postgres:postgres /data/postgres \
|
||||
&& chown -R redis:redis /data/redis \
|
||||
&& chown -R tracearr:tracearr /data/tracearr /app
|
||||
|
||||
# Supervisord configuration
|
||||
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# Startup scripts
|
||||
COPY docker/entrypoint-supervised.sh /entrypoint.sh
|
||||
COPY docker/start-tracearr.sh /start-tracearr.sh
|
||||
RUN chmod 755 /entrypoint.sh /start-tracearr.sh
|
||||
|
||||
# Environment defaults
|
||||
ENV NODE_ENV=production \
|
||||
LOG_LEVEL=info \
|
||||
PORT=3000 \
|
||||
HOST=0.0.0.0 \
|
||||
TZ=UTC \
|
||||
DATABASE_URL=postgresql://tracearr:tracearr@127.0.0.1:5432/tracearr \
|
||||
REDIS_URL=redis://127.0.0.1:6379
|
||||
|
||||
# Expose only the web port
|
||||
EXPOSE 3000
|
||||
|
||||
# Volumes for persistent data
|
||||
VOLUME ["/data/postgres", "/data/redis", "/data/tracearr"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://127.0.0.1:3000/health || exit 1
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
22
docker/Dockerfile.timescale
Normal file
22
docker/Dockerfile.timescale
Normal file
@@ -0,0 +1,22 @@
|
||||
# TimescaleDB with Toolkit extension
|
||||
# Builds from official PostgreSQL image with TimescaleDB and Toolkit added
|
||||
# This provides feature parity with the supervised image
|
||||
FROM postgres:15-bookworm
|
||||
|
||||
# Install TimescaleDB and Toolkit from official TimescaleDB repo
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
gnupg \
|
||||
&& curl -fsSL https://packagecloud.io/timescale/timescaledb/gpgkey | gpg --dearmor -o /usr/share/keyrings/timescaledb-keyring.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/timescaledb-keyring.gpg] https://packagecloud.io/timescale/timescaledb/debian/ bookworm main" > /etc/apt/sources.list.d/timescaledb.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
timescaledb-2-postgresql-15 \
|
||||
timescaledb-toolkit-postgresql-15 \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get purge -y --auto-remove curl gnupg
|
||||
|
||||
# Enable TimescaleDB in PostgreSQL config
|
||||
RUN echo "shared_preload_libraries = 'timescaledb'" >> /usr/share/postgresql/postgresql.conf.sample
|
||||
35
docker/docker-compose.dev.yml
Normal file
35
docker/docker-compose.dev.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
services:
|
||||
timescale:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.timescale
|
||||
image: tracearr-timescale:latest
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
- POSTGRES_USER=tracearr
|
||||
- POSTGRES_PASSWORD=tracearr
|
||||
- POSTGRES_DB=tracearr
|
||||
volumes:
|
||||
- timescale_dev_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U tracearr"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_dev_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
timescale_dev_data:
|
||||
redis_dev_data:
|
||||
47
docker/docker-compose.supervised.yml
Normal file
47
docker/docker-compose.supervised.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
# Tracearr All-in-One (Supervised) Deployment
|
||||
#
|
||||
# This compose file runs the supervised image which includes:
|
||||
# - TimescaleDB (PostgreSQL 15)
|
||||
# - Redis
|
||||
# - Tracearr application
|
||||
#
|
||||
# No external database or Redis required!
|
||||
# Secrets are auto-generated on first run and persisted.
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker/docker-compose.supervised.yml up -d
|
||||
#
|
||||
# Data is stored in Docker volumes by default.
|
||||
# For bind mounts, uncomment the volumes section below.
|
||||
|
||||
services:
|
||||
tracearr:
|
||||
image: ghcr.io/connorgallopo/tracearr:supervised
|
||||
ports:
|
||||
- "${PORT:-3000}:3000"
|
||||
environment:
|
||||
- TZ=${TZ:-UTC}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
# Optional: Override auto-generated secrets
|
||||
# - JWT_SECRET=${JWT_SECRET}
|
||||
# - COOKIE_SECRET=${COOKIE_SECRET}
|
||||
volumes:
|
||||
- tracearr_postgres:/data/postgres
|
||||
- tracearr_redis:/data/redis
|
||||
- tracearr_data:/data/tracearr
|
||||
# Bind mount alternative (uncomment and adjust paths):
|
||||
# - ./data/postgres:/data/postgres
|
||||
# - ./data/redis:/data/redis
|
||||
# - ./data/tracearr:/data/tracearr
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
start_period: 60s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
tracearr_postgres:
|
||||
tracearr_redis:
|
||||
tracearr_data:
|
||||
37
docker/docker-compose.test.yml
Normal file
37
docker/docker-compose.test.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
version: "3.8"
|
||||
|
||||
# Test database services - completely isolated from dev
|
||||
# Run with: docker compose -f docker/docker-compose.test.yml up -d
|
||||
|
||||
services:
|
||||
timescale-test:
|
||||
image: timescale/timescaledb:latest-pg15
|
||||
ports:
|
||||
- "5433:5432" # Different port to avoid conflicts with dev
|
||||
environment:
|
||||
- POSTGRES_USER=test
|
||||
- POSTGRES_PASSWORD=test
|
||||
- POSTGRES_DB=tracearr_test
|
||||
volumes:
|
||||
- timescale_test_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U test -d tracearr_test"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
redis-test:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6380:6379" # Different port to avoid conflicts with dev
|
||||
volumes:
|
||||
- redis_test_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
timescale_test_data:
|
||||
redis_test_data:
|
||||
71
docker/docker-compose.yml
Normal file
71
docker/docker-compose.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
services:
|
||||
tracearr:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile
|
||||
image: ghcr.io/connorgallopo/tracearr:latest
|
||||
ports:
|
||||
- "${PORT:-3000}:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3000
|
||||
- HOST=0.0.0.0
|
||||
- TZ=${TZ:-America/New_York}
|
||||
- DATABASE_URL=postgres://tracearr:${DB_PASSWORD:-tracearr}@timescale:5432/tracearr
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- JWT_SECRET=${JWT_SECRET:?JWT_SECRET is required}
|
||||
- COOKIE_SECRET=${COOKIE_SECRET:?COOKIE_SECRET is required}
|
||||
- CORS_ORIGIN=${CORS_ORIGIN:-*}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
# Uncomment for TestFlight/beta testing: allows reusable tokens, no expiry, unlimited devices
|
||||
# - MOBILE_BETA_MODE=true
|
||||
depends_on:
|
||||
timescale:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- tracearr-network
|
||||
|
||||
timescale:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.timescale
|
||||
image: tracearr-timescale:latest
|
||||
environment:
|
||||
- POSTGRES_USER=tracearr
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD:-tracearr}
|
||||
- POSTGRES_DB=tracearr
|
||||
volumes:
|
||||
- timescale_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U tracearr"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- tracearr-network
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: redis-server --appendonly yes
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- tracearr-network
|
||||
|
||||
networks:
|
||||
tracearr-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
timescale_data:
|
||||
redis_data:
|
||||
210
docker/entrypoint-supervised.sh
Normal file
210
docker/entrypoint-supervised.sh
Normal file
@@ -0,0 +1,210 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log() { echo -e "${GREEN}[Tracearr]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[Tracearr]${NC} $1"; }
|
||||
error() { echo -e "${RED}[Tracearr]${NC} $1"; }
|
||||
|
||||
# Create log directory
|
||||
mkdir -p /var/log/supervisor
|
||||
|
||||
# =============================================================================
|
||||
# Timezone configuration
|
||||
# =============================================================================
|
||||
if [ -n "$TZ" ] && [ "$TZ" != "UTC" ]; then
|
||||
if [ -f "/usr/share/zoneinfo/$TZ" ]; then
|
||||
ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime
|
||||
echo "$TZ" > /etc/timezone
|
||||
log "Timezone set to $TZ"
|
||||
else
|
||||
warn "Invalid timezone '$TZ', using UTC"
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Generate secrets if not provided
|
||||
# =============================================================================
|
||||
mkdir -p /data/tracearr
|
||||
|
||||
if [ -z "$JWT_SECRET" ]; then
|
||||
if [ -f /data/tracearr/.jwt_secret ]; then
|
||||
export JWT_SECRET=$(cat /data/tracearr/.jwt_secret)
|
||||
log "Loaded JWT_SECRET from persistent storage"
|
||||
else
|
||||
export JWT_SECRET=$(openssl rand -hex 32)
|
||||
echo "$JWT_SECRET" > /data/tracearr/.jwt_secret
|
||||
chmod 600 /data/tracearr/.jwt_secret
|
||||
log "Generated new JWT_SECRET"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$COOKIE_SECRET" ]; then
|
||||
if [ -f /data/tracearr/.cookie_secret ]; then
|
||||
export COOKIE_SECRET=$(cat /data/tracearr/.cookie_secret)
|
||||
log "Loaded COOKIE_SECRET from persistent storage"
|
||||
else
|
||||
export COOKIE_SECRET=$(openssl rand -hex 32)
|
||||
echo "$COOKIE_SECRET" > /data/tracearr/.cookie_secret
|
||||
chmod 600 /data/tracearr/.cookie_secret
|
||||
log "Generated new COOKIE_SECRET"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ENCRYPTION_KEY is optional - only needed for migrating existing encrypted tokens
|
||||
# Load existing key if present (for backward compatibility), but don't generate new ones
|
||||
if [ -z "$ENCRYPTION_KEY" ] && [ -f /data/tracearr/.encryption_key ]; then
|
||||
export ENCRYPTION_KEY=$(cat /data/tracearr/.encryption_key)
|
||||
log "Loaded ENCRYPTION_KEY from persistent storage (for token migration)"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Initialize PostgreSQL if needed
|
||||
# =============================================================================
|
||||
init_postgres_db() {
|
||||
# Configure PostgreSQL
|
||||
cat >> /data/postgres/postgresql.conf <<EOF
|
||||
shared_preload_libraries = 'timescaledb'
|
||||
listen_addresses = '127.0.0.1'
|
||||
port = 5432
|
||||
log_timezone = 'UTC'
|
||||
timezone = 'UTC'
|
||||
EOF
|
||||
|
||||
# Allow local connections
|
||||
cat > /data/postgres/pg_hba.conf <<EOF
|
||||
local all all trust
|
||||
host all all 127.0.0.1/32 md5
|
||||
EOF
|
||||
|
||||
# Start PostgreSQL temporarily to create database and user
|
||||
gosu postgres /usr/lib/postgresql/15/bin/pg_ctl -D /data/postgres -w start
|
||||
|
||||
log "Creating tracearr database and user..."
|
||||
gosu postgres psql -c "CREATE USER tracearr WITH PASSWORD 'tracearr';" 2>/dev/null || true
|
||||
gosu postgres psql -c "CREATE DATABASE tracearr OWNER tracearr;" 2>/dev/null || true
|
||||
gosu postgres psql -d tracearr -c "CREATE EXTENSION IF NOT EXISTS timescaledb;"
|
||||
gosu postgres psql -d tracearr -c "CREATE EXTENSION IF NOT EXISTS timescaledb_toolkit;"
|
||||
gosu postgres psql -d tracearr -c "GRANT ALL PRIVILEGES ON DATABASE tracearr TO tracearr;"
|
||||
gosu postgres psql -d tracearr -c "GRANT ALL ON SCHEMA public TO tracearr;"
|
||||
|
||||
# Stop PostgreSQL (supervisord will start it)
|
||||
gosu postgres /usr/lib/postgresql/15/bin/pg_ctl -D /data/postgres -w stop
|
||||
|
||||
log "PostgreSQL initialized successfully"
|
||||
}
|
||||
|
||||
if [ ! -f /data/postgres/PG_VERSION ]; then
|
||||
log "Initializing PostgreSQL database..."
|
||||
|
||||
# Ensure data directory exists (may not if bind mount path is new)
|
||||
mkdir -p /data/postgres
|
||||
|
||||
# Check if this looks like an existing installation (secrets exist)
|
||||
# If secrets exist but postgres is empty, volumes may have been disconnected
|
||||
EXISTING_INSTALL=false
|
||||
if [ -f /data/tracearr/.jwt_secret ] || [ -f /data/tracearr/.cookie_secret ]; then
|
||||
EXISTING_INSTALL=true
|
||||
fi
|
||||
|
||||
# Handle corrupt/partial initialization (has files but no PG_VERSION)
|
||||
if [ "$(ls -A /data/postgres 2>/dev/null)" ]; then
|
||||
# Check if this looks like a real database (has pg_control)
|
||||
if [ -f /data/postgres/global/pg_control ]; then
|
||||
# Database files exist but PG_VERSION is missing - try to recover
|
||||
warn "PG_VERSION missing but database files exist - attempting recovery"
|
||||
warn "This can happen after filesystem issues or interrupted shutdowns"
|
||||
echo "15" > /data/postgres/PG_VERSION
|
||||
chown postgres:postgres /data/postgres/PG_VERSION
|
||||
log "Created PG_VERSION file, will attempt to start existing database"
|
||||
else
|
||||
# No pg_control - could be corrupt or volume mount issue
|
||||
if [ "$EXISTING_INSTALL" = true ] && [ "$FORCE_DB_REINIT" != "true" ]; then
|
||||
error "=========================================================="
|
||||
error "DATA LOSS PREVENTION: Database appears corrupt or missing"
|
||||
error "=========================================================="
|
||||
error ""
|
||||
error "Found existing secrets but PostgreSQL data is invalid."
|
||||
error "This usually means:"
|
||||
error " 1. Volume was not properly mounted after container update"
|
||||
error " 2. Database was corrupted"
|
||||
error ""
|
||||
error "If this is a FRESH INSTALL, set: FORCE_DB_REINIT=true"
|
||||
error "If this is an UPDATE, check your volume mounts!"
|
||||
error ""
|
||||
error "Your data may still exist in a Docker volume."
|
||||
error "Run: docker volume ls | grep tracearr"
|
||||
error "=========================================================="
|
||||
exit 1
|
||||
fi
|
||||
warn "Data directory has no valid database (missing global/pg_control)"
|
||||
warn "Initializing fresh database..."
|
||||
rm -rf /data/postgres/*
|
||||
chown -R postgres:postgres /data/postgres
|
||||
gosu postgres /usr/lib/postgresql/15/bin/initdb -D /data/postgres
|
||||
init_postgres_db
|
||||
fi
|
||||
else
|
||||
# Empty directory - initialize fresh
|
||||
# Note: Existing secrets (JWT/cookie) don't indicate data loss risk since
|
||||
# they only affect auth sessions, not actual data. If postgres is empty,
|
||||
# there's no user data to protect anyway.
|
||||
if [ "$EXISTING_INSTALL" = true ]; then
|
||||
warn "Found existing secrets but empty database - initializing fresh"
|
||||
warn "Previous sessions will be invalidated (users will need to log in again)"
|
||||
fi
|
||||
chown -R postgres:postgres /data/postgres
|
||||
gosu postgres /usr/lib/postgresql/15/bin/initdb -D /data/postgres
|
||||
init_postgres_db
|
||||
fi
|
||||
else
|
||||
log "PostgreSQL data directory exists, skipping initialization"
|
||||
fi
|
||||
|
||||
# Ensure data directories exist and have correct ownership
|
||||
# This handles fresh installs, upgrades, and bind mounts to new paths
|
||||
mkdir -p /data/postgres /data/redis /data/tracearr
|
||||
chown -R postgres:postgres /data/postgres
|
||||
chown -R redis:redis /data/redis
|
||||
chown -R tracearr:tracearr /data/tracearr
|
||||
chown -R tracearr:tracearr /app
|
||||
|
||||
# =============================================================================
|
||||
# Tune PostgreSQL for available resources (runs every startup)
|
||||
# =============================================================================
|
||||
# timescaledb-tune automatically optimizes PostgreSQL settings based on
|
||||
# available RAM and CPU. Safe to run repeatedly - recalculates if resources change.
|
||||
if command -v timescaledb-tune &> /dev/null; then
|
||||
log "Tuning PostgreSQL for available resources..."
|
||||
timescaledb-tune --pg-config=/usr/lib/postgresql/15/bin/pg_config \
|
||||
--conf-path=/data/postgres/postgresql.conf \
|
||||
--yes --quiet 2>/dev/null || warn "timescaledb-tune failed (non-fatal)"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Link GeoIP database if exists
|
||||
# =============================================================================
|
||||
if [ -f /data/tracearr/GeoLite2-City.mmdb ]; then
|
||||
mkdir -p /app/data
|
||||
ln -sf /data/tracearr/GeoLite2-City.mmdb /app/data/GeoLite2-City.mmdb
|
||||
log "GeoIP database linked from /data/tracearr/"
|
||||
elif [ -f /app/data/GeoLite2-City.mmdb ]; then
|
||||
log "Using bundled GeoIP database"
|
||||
else
|
||||
warn "GeoIP database not found - geolocation features will be limited"
|
||||
warn "Place GeoLite2-City.mmdb in /data/tracearr/ for full functionality"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Start supervisord
|
||||
# =============================================================================
|
||||
log "Starting Tracearr services..."
|
||||
log " - PostgreSQL 15 with TimescaleDB"
|
||||
log " - Redis"
|
||||
log " - Tracearr application"
|
||||
exec "$@"
|
||||
39
docker/start-tracearr.sh
Normal file
39
docker/start-tracearr.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
# Wrapper script for starting Tracearr after dependencies are ready
|
||||
# Used by supervisord to ensure PostgreSQL and Redis are available
|
||||
|
||||
set -e
|
||||
|
||||
MAX_RETRIES=30
|
||||
RETRY_INTERVAL=2
|
||||
|
||||
# Wait for PostgreSQL
|
||||
echo "[Tracearr] Waiting for PostgreSQL..."
|
||||
for i in $(seq 1 $MAX_RETRIES); do
|
||||
if pg_isready -h 127.0.0.1 -p 5432 -U tracearr -q; then
|
||||
echo "[Tracearr] PostgreSQL is ready"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq $MAX_RETRIES ]; then
|
||||
echo "[Tracearr] ERROR: PostgreSQL failed to become ready after $((MAX_RETRIES * RETRY_INTERVAL)) seconds"
|
||||
exit 1
|
||||
fi
|
||||
sleep $RETRY_INTERVAL
|
||||
done
|
||||
|
||||
# Wait for Redis
|
||||
echo "[Tracearr] Waiting for Redis..."
|
||||
for i in $(seq 1 $MAX_RETRIES); do
|
||||
if redis-cli -h 127.0.0.1 ping 2>/dev/null | grep -q PONG; then
|
||||
echo "[Tracearr] Redis is ready"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq $MAX_RETRIES ]; then
|
||||
echo "[Tracearr] ERROR: Redis failed to become ready after $((MAX_RETRIES * RETRY_INTERVAL)) seconds"
|
||||
exit 1
|
||||
fi
|
||||
sleep $RETRY_INTERVAL
|
||||
done
|
||||
|
||||
echo "[Tracearr] Starting application..."
|
||||
exec node /app/apps/server/dist/index.js
|
||||
53
docker/supervisord.conf
Normal file
53
docker/supervisord.conf
Normal file
@@ -0,0 +1,53 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
logfile_maxbytes=10MB
|
||||
logfile_backups=3
|
||||
pidfile=/var/run/supervisord.pid
|
||||
childlogdir=/var/log/supervisor
|
||||
|
||||
[program:postgres]
|
||||
command=/usr/lib/postgresql/15/bin/postgres -D /data/postgres
|
||||
user=postgres
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=10
|
||||
stdout_logfile=/var/log/supervisor/postgres.log
|
||||
stdout_logfile_maxbytes=10MB
|
||||
stdout_logfile_backups=2
|
||||
stderr_logfile=/var/log/supervisor/postgres-error.log
|
||||
stderr_logfile_maxbytes=10MB
|
||||
stderr_logfile_backups=2
|
||||
startsecs=10
|
||||
stopwaitsecs=30
|
||||
|
||||
[program:redis]
|
||||
command=/usr/bin/redis-server /etc/redis/redis.conf --dir /data/redis
|
||||
user=redis
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=20
|
||||
stdout_logfile=/var/log/supervisor/redis.log
|
||||
stdout_logfile_maxbytes=5MB
|
||||
stdout_logfile_backups=2
|
||||
stderr_logfile=/var/log/supervisor/redis-error.log
|
||||
stderr_logfile_maxbytes=5MB
|
||||
stderr_logfile_backups=2
|
||||
startsecs=5
|
||||
|
||||
[program:tracearr]
|
||||
command=/start-tracearr.sh
|
||||
directory=/app
|
||||
user=tracearr
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=30
|
||||
stdout_logfile=/var/log/supervisor/tracearr.log
|
||||
stdout_logfile_maxbytes=50MB
|
||||
stdout_logfile_backups=3
|
||||
stderr_logfile=/var/log/supervisor/tracearr-error.log
|
||||
stderr_logfile_maxbytes=50MB
|
||||
stderr_logfile_backups=3
|
||||
startsecs=10
|
||||
startretries=5
|
||||
Reference in New Issue
Block a user