diff --git a/deployments/docker/Dockerfile.api b/deployments/docker/Dockerfile.api deleted file mode 100644 index 2217819..0000000 --- a/deployments/docker/Dockerfile.api +++ /dev/null @@ -1,42 +0,0 @@ -# Build stage -FROM golang:1.22-alpine AS builder - -WORKDIR /app - -# Install dependencies -RUN apk add --no-cache git ca-certificates - -# Copy go mod files -COPY go.mod go.sum ./ -RUN go mod download - -# Copy source code -COPY . . - -# Build the binary -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o pnf-api ./cmd/api - -# Final stage -FROM alpine:3.19 - -WORKDIR /app - -# Install ca-certificates for HTTPS requests -RUN apk add --no-cache ca-certificates tzdata - -# Copy binary from builder -COPY --from=builder /app/pnf-api . - -# Create non-root user -RUN adduser -D -g '' appuser -USER appuser - -# Expose port -EXPOSE 8080 - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 - -# Run the binary -CMD ["./pnf-api"] diff --git a/deployments/docker/Dockerfile.api.dev b/deployments/docker/Dockerfile.api.dev deleted file mode 100644 index a4aa61f..0000000 --- a/deployments/docker/Dockerfile.api.dev +++ /dev/null @@ -1,18 +0,0 @@ -# Development Dockerfile with Air for live reload -FROM golang:1.23-alpine - -WORKDIR /app - -# Install Air for live reload -RUN go install github.com/air-verse/air@v1.61.7 - -# Copy go mod files -COPY go.mod go.sum ./ -RUN go mod download - -# The source code will be mounted as a volume -# Air will watch for changes and rebuild - -EXPOSE 8080 - -CMD ["air", "-c", ".air.toml"] diff --git a/deployments/docker/Dockerfile.tui b/deployments/docker/Dockerfile.tui deleted file mode 100644 index 0e8067b..0000000 --- a/deployments/docker/Dockerfile.tui +++ /dev/null @@ -1,35 +0,0 @@ -# Build stage -FROM golang:1.22-alpine AS builder - -WORKDIR /app - -# Install dependencies -RUN apk add --no-cache git ca-certificates - -# Copy go mod files -COPY go.mod go.sum ./ -RUN go mod download - -# Copy source code -COPY . . - -# Build the binary -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o pnf ./cmd/tui - -# Final stage -FROM alpine:3.19 - -WORKDIR /app - -# Install ca-certificates for HTTPS requests -RUN apk add --no-cache ca-certificates tzdata - -# Copy binary from builder -COPY --from=builder /app/pnf . - -# Create non-root user -RUN adduser -D -g '' appuser -USER appuser - -# Run the binary -ENTRYPOINT ["./pnf"] diff --git a/deployments/docker/docker-compose.yml b/deployments/docker/docker-compose.yml deleted file mode 100644 index 03e756e..0000000 --- a/deployments/docker/docker-compose.yml +++ /dev/null @@ -1,77 +0,0 @@ -version: "3.8" - -services: - # PostgreSQL with TimescaleDB - db: - image: timescale/timescaledb:latest-pg15 - container_name: p95-db - environment: - POSTGRES_USER: p95 - POSTGRES_PASSWORD: p95 - POSTGRES_DB: p95 - volumes: - - pgdata:/var/lib/postgresql/data - - ./init:/docker-entrypoint-initdb.d:ro - ports: - - "5432:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U p95"] - interval: 10s - timeout: 5s - retries: 5 - - # Redis (optional, for caching and pub/sub) - redis: - image: redis:7-alpine - container_name: p95-redis - ports: - - "6379:6379" - volumes: - - redisdata:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - - # API Server (development with live reload) - api: - build: - context: ../.. - dockerfile: deployments/docker/Dockerfile.api.dev - container_name: p95-api - environment: - - PORT=8080 - - DATABASE_URL=postgres://p95:p95@db:5432/p95?sslmode=disable - - REDIS_URL=redis://redis:6379 - - JWT_SECRET=change-me-in-production - - DEPLOYMENT_MODE=self-hosted - - ADMIN_EMAIL=admin@localhost - - ADMIN_PASSWORD=admin123 - - DEBUG=true - volumes: - - ../..:/app - ports: - - "8080:8080" - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy - restart: unless-stopped - - # Web Dashboard - web: - build: - context: ../.. - dockerfile: web/Dockerfile - container_name: p95-web - ports: - - "3000:80" - depends_on: - - api - restart: unless-stopped - -volumes: - pgdata: - redisdata: diff --git a/deployments/docker/init/001_initial_schema.up.sql b/deployments/docker/init/001_initial_schema.up.sql deleted file mode 100644 index 2511ddb..0000000 --- a/deployments/docker/init/001_initial_schema.up.sql +++ /dev/null @@ -1,174 +0,0 @@ --- p95 Initial Schema --- PostgreSQL + TimescaleDB - --- Enable UUID extension -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- ============================================================================ --- Users --- ============================================================================ -CREATE TABLE users ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - email VARCHAR(255) UNIQUE NOT NULL, - password_hash VARCHAR(255), -- NULL for SSO users - name VARCHAR(255) NOT NULL, - avatar_url VARCHAR(512), - is_active BOOLEAN DEFAULT true, - is_admin BOOLEAN DEFAULT false, -- For self-hosted admin - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_users_email ON users(email); - --- ============================================================================ --- Teams (Organizations/Workspaces) --- ============================================================================ -CREATE TABLE teams ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - name VARCHAR(255) NOT NULL, - slug VARCHAR(255) UNIQUE NOT NULL, - description TEXT, - plan VARCHAR(50) DEFAULT 'free', -- free, pro, enterprise (for cloud) - is_personal BOOLEAN DEFAULT false, -- Personal workspace - settings JSONB DEFAULT '{}', - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_teams_slug ON teams(slug); - --- ============================================================================ --- Team Memberships --- ============================================================================ -CREATE TABLE team_members ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - role VARCHAR(50) NOT NULL DEFAULT 'member', -- owner, admin, member, viewer - created_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(team_id, user_id) -); - -CREATE INDEX idx_team_members_team ON team_members(team_id); -CREATE INDEX idx_team_members_user ON team_members(user_id); - --- ============================================================================ --- API Keys --- ============================================================================ -CREATE TABLE api_keys ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - team_id UUID REFERENCES teams(id) ON DELETE CASCADE, -- Optional team scope - key_hash VARCHAR(255) NOT NULL, -- bcrypt hash of the key - key_prefix VARCHAR(12) NOT NULL, -- First chars for identification (ss67_xxxx) - name VARCHAR(255) NOT NULL, - scopes JSONB DEFAULT '["read", "write"]', -- Permission scopes - last_used_at TIMESTAMPTZ, - expires_at TIMESTAMPTZ, -- NULL = never expires - created_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_api_keys_prefix ON api_keys(key_prefix); -CREATE INDEX idx_api_keys_user ON api_keys(user_id); -CREATE INDEX idx_api_keys_team ON api_keys(team_id); - --- ============================================================================ --- Sessions (for TUI/Dashboard authentication) --- ============================================================================ -CREATE TABLE sessions ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - token_hash VARCHAR(255) NOT NULL, - device_info JSONB, - ip_address INET, - expires_at TIMESTAMPTZ NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_sessions_user ON sessions(user_id); -CREATE INDEX idx_sessions_token ON sessions(token_hash); -CREATE INDEX idx_sessions_expires ON sessions(expires_at); - --- ============================================================================ --- Apps (Projects) --- ============================================================================ -CREATE TABLE apps ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE, - name VARCHAR(255) NOT NULL, - slug VARCHAR(255) NOT NULL, - description TEXT, - visibility VARCHAR(50) DEFAULT 'private', -- private, team, public - settings JSONB DEFAULT '{}', - archived_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(team_id, slug) -); - -CREATE INDEX idx_apps_team ON apps(team_id); -CREATE INDEX idx_apps_slug ON apps(team_id, slug); - --- ============================================================================ --- Training Runs --- ============================================================================ -CREATE TABLE runs ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - app_id UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES users(id), - name VARCHAR(255), -- Auto-generated or user-provided - description TEXT, - status VARCHAR(50) DEFAULT 'running', -- running, completed, failed, aborted - tags TEXT[], -- Array of tags - git_info JSONB, -- {commit, branch, remote, dirty} - system_info JSONB, -- {hostname, os, python_version, gpu_info} - config JSONB DEFAULT '{}', -- Hyperparameters/config logged at start - error_message TEXT, -- Error message if failed - started_at TIMESTAMPTZ DEFAULT NOW(), - ended_at TIMESTAMPTZ, - duration_seconds FLOAT, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_runs_app ON runs(app_id); -CREATE INDEX idx_runs_user ON runs(user_id); -CREATE INDEX idx_runs_status ON runs(status); -CREATE INDEX idx_runs_started_at ON runs(started_at DESC); -CREATE INDEX idx_runs_tags ON runs USING GIN(tags); - --- ============================================================================ --- Run Artifacts (files, models, etc.) --- ============================================================================ -CREATE TABLE artifacts ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - run_id UUID NOT NULL REFERENCES runs(id) ON DELETE CASCADE, - name VARCHAR(255) NOT NULL, - path VARCHAR(1024) NOT NULL, -- Storage path (S3, local, etc.) - size_bytes BIGINT, - content_type VARCHAR(255), - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_artifacts_run ON artifacts(run_id); - --- ============================================================================ --- Trigger for updated_at --- ============================================================================ -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ language 'plpgsql'; - -CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - -CREATE TRIGGER update_teams_updated_at BEFORE UPDATE ON teams - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - -CREATE TRIGGER update_apps_updated_at BEFORE UPDATE ON apps - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); diff --git a/deployments/docker/init/002_timescale_hypertables.up.sql b/deployments/docker/init/002_timescale_hypertables.up.sql deleted file mode 100644 index 58d2985..0000000 --- a/deployments/docker/init/002_timescale_hypertables.up.sql +++ /dev/null @@ -1,128 +0,0 @@ --- p95 TimescaleDB Hypertables for Metrics --- Requires TimescaleDB extension - --- Enable TimescaleDB -CREATE EXTENSION IF NOT EXISTS timescaledb; - --- ============================================================================ --- Metrics Table (main time-series data) --- ============================================================================ --- Stores numeric metrics like loss, accuracy, learning_rate, etc. -CREATE TABLE metrics ( - time TIMESTAMPTZ NOT NULL, - run_id UUID NOT NULL, - name VARCHAR(255) NOT NULL, - step BIGINT NOT NULL, - value DOUBLE PRECISION NOT NULL -); - --- Convert to hypertable with time-based partitioning --- Chunks are created per day for efficient querying and compression -SELECT create_hypertable('metrics', 'time', - chunk_time_interval => INTERVAL '1 day', - if_not_exists => TRUE -); - --- Create composite indexes for efficient queries --- Primary access pattern: get metrics for a run by name, ordered by time/step -CREATE INDEX idx_metrics_run_name_time ON metrics(run_id, name, time DESC); -CREATE INDEX idx_metrics_run_name_step ON metrics(run_id, name, step); - --- Enable compression for older data (90%+ storage savings) -ALTER TABLE metrics SET ( - timescaledb.compress, - timescaledb.compress_segmentby = 'run_id, name', - timescaledb.compress_orderby = 'time DESC' -); - --- Add compression policy (compress chunks older than 7 days) -SELECT add_compression_policy('metrics', INTERVAL '7 days'); - --- ============================================================================ --- System Metrics Table (GPU usage, memory, etc.) --- ============================================================================ -CREATE TABLE system_metrics ( - time TIMESTAMPTZ NOT NULL, - run_id UUID NOT NULL, - metric_type VARCHAR(50) NOT NULL, -- gpu_memory, gpu_utilization, cpu, memory - device_id INTEGER DEFAULT 0, -- For multi-GPU setups - value DOUBLE PRECISION NOT NULL -); - -SELECT create_hypertable('system_metrics', 'time', - chunk_time_interval => INTERVAL '1 day', - if_not_exists => TRUE -); - -CREATE INDEX idx_system_metrics_run ON system_metrics(run_id, metric_type, time DESC); - --- Enable compression for system metrics -ALTER TABLE system_metrics SET ( - timescaledb.compress, - timescaledb.compress_segmentby = 'run_id, metric_type', - timescaledb.compress_orderby = 'time DESC' -); - -SELECT add_compression_policy('system_metrics', INTERVAL '7 days'); - --- ============================================================================ --- Continuous Aggregates for Dashboard Summaries --- ============================================================================ --- Pre-computed hourly aggregates for faster dashboard queries -CREATE MATERIALIZED VIEW metrics_hourly -WITH (timescaledb.continuous) AS -SELECT - run_id, - name, - time_bucket('1 hour', time) AS bucket, - AVG(value) AS avg_value, - MIN(value) AS min_value, - MAX(value) AS max_value, - FIRST(value, time) AS first_value, - LAST(value, time) AS last_value, - COUNT(*) AS sample_count -FROM metrics -GROUP BY run_id, name, bucket -WITH NO DATA; - --- Refresh policy for continuous aggregate --- Refreshes data from 3 hours ago to 1 hour ago, every hour -SELECT add_continuous_aggregate_policy('metrics_hourly', - start_offset => INTERVAL '3 hours', - end_offset => INTERVAL '1 hour', - schedule_interval => INTERVAL '1 hour' -); - --- ============================================================================ --- Helper Views --- ============================================================================ - --- Latest metric values for a run (useful for dashboards) -CREATE VIEW run_latest_metrics AS -SELECT DISTINCT ON (run_id, name) - run_id, - name, - value, - step, - time -FROM metrics -ORDER BY run_id, name, time DESC; - --- Run statistics summary -CREATE VIEW run_stats AS -SELECT - run_id, - COUNT(DISTINCT name) AS metric_count, - COUNT(*) AS total_points, - MIN(time) AS first_metric_at, - MAX(time) AS last_metric_at, - MAX(step) AS max_step -FROM metrics -GROUP BY run_id; - --- ============================================================================ --- Retention Policy (optional, uncomment for cloud tier management) --- ============================================================================ --- Automatically delete data older than 90 days (for free tier) --- SELECT add_retention_policy('metrics', INTERVAL '90 days'); --- SELECT add_retention_policy('system_metrics', INTERVAL '90 days');