From 163682f7d50848a83b5b654b9421fcb30a1520b5 Mon Sep 17 00:00:00 2001 From: Abdelkader Boudih Date: Sun, 11 Jan 2026 21:02:25 +0100 Subject: [PATCH] feat: added docker compose file --- Dockerfile | 26 ++- README.md | 25 ++- crates/vein-adapter/Cargo.toml | 2 +- .../postgres/20250111000001_init.sql | 102 +++++++++ .../migrations/sqlite/20250111000001_init.sql | 95 ++++++++ crates/vein-adapter/src/cache.rs | 6 - crates/vein-adapter/src/cache/postgres.rs | 119 +--------- crates/vein-adapter/src/cache/sqlite.rs | 208 ++---------------- crates/vein-admin/src/commands/index.rs | 6 - docker-compose.yml | 42 +++- src/main.rs | 7 - src/quarantine.rs | 24 -- vein.docker.toml | 15 ++ 13 files changed, 310 insertions(+), 367 deletions(-) create mode 100644 crates/vein-adapter/migrations/postgres/20250111000001_init.sql create mode 100644 crates/vein-adapter/migrations/sqlite/20250111000001_init.sql create mode 100644 vein.docker.toml diff --git a/Dockerfile b/Dockerfile index 0bae220..f7b040c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,24 +20,36 @@ COPY Cargo.toml ./ COPY src ./src COPY crates ./crates -# Build for native architecture -RUN cargo build --release +# Build for native architecture with PostgreSQL support +RUN cargo build --release --no-default-features --features tls,postgres -# Stage 2: Runtime - Distroless -FROM gcr.io/distroless/cc-debian12:nonroot +# Stage 2: Runtime +FROM debian:bookworm-slim + +# Install runtime dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + libssl3 \ + zlib1g \ + curl \ + && rm -rf /var/lib/apt/lists/* \ + && useradd -r -u 1000 -m vein # Copy binary from builder COPY --from=builder /build/target/release/vein /usr/local/bin/vein -# Set working directory +# Set working directory and permissions WORKDIR /data +RUN chown vein:vein /data + +USER vein # Expose port EXPOSE 8346 # Container healthcheck hitting Vein's liveness endpoint -HEALTHCHECK --interval=30s --timeout=5s --start-period=45s --retries=3 \ - CMD ["/usr/local/bin/vein", "health"] +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -fsS http://localhost:8346/up || exit 1 # Run vein ENTRYPOINT ["/usr/local/bin/vein"] diff --git a/README.md b/README.md index 3a35131..fd3db24 100644 --- a/README.md +++ b/README.md @@ -268,19 +268,34 @@ docker run -d \ docker logs -f vein ``` -### Using Docker Compose +### Using Docker Compose (Recommended) + +The included `docker-compose.yml` spins up a **fully configured, production-ready** Vein proxy with PostgreSQL backend: ```bash -# Start the service -docker-compose up -d +# Clone and start +git clone https://github.com/contriboss/vein.git +cd vein +docker compose up -d + +# Verify health +docker compose ps # Both services should show "healthy" # View logs -docker-compose logs -f +docker compose logs -f vein # Stop the service -docker-compose down +docker compose down ``` +**What's included:** +- PostgreSQL 18 with persistent storage +- Vein proxy with auto-migrations (no manual setup) +- Health checks for both services +- Preconfigured networking + +Point your Bundler at `http://localhost:8346` and you're done. + ### Custom Configuration ```bash diff --git a/crates/vein-adapter/Cargo.toml b/crates/vein-adapter/Cargo.toml index f4b7039..0888c0a 100644 --- a/crates/vein-adapter/Cargo.toml +++ b/crates/vein-adapter/Cargo.toml @@ -18,7 +18,7 @@ tls = ["sqlx/runtime-tokio-rustls"] [dependencies] anyhow = "1.0.95" -sqlx = { version = "0.8.2", features = ["runtime-tokio", "chrono"] } +sqlx = { version = "0.8.2", features = ["runtime-tokio", "chrono", "migrate"] } tokio = { version = "1.43", features = ["fs", "macros", "rt-multi-thread", "sync"] } chrono = { version = "0.4.38", features = ["serde"] } serde = { version = "1.0.228", features = ["derive"] } diff --git a/crates/vein-adapter/migrations/postgres/20250111000001_init.sql b/crates/vein-adapter/migrations/postgres/20250111000001_init.sql new file mode 100644 index 0000000..025240a --- /dev/null +++ b/crates/vein-adapter/migrations/postgres/20250111000001_init.sql @@ -0,0 +1,102 @@ +-- Vein PostgreSQL Schema + +CREATE TABLE cached_assets ( + kind TEXT NOT NULL, + name TEXT NOT NULL, + version TEXT NOT NULL, + platform TEXT, + path TEXT NOT NULL, + sha256 TEXT NOT NULL, + size_bytes BIGINT NOT NULL, + last_accessed TIMESTAMPTZ DEFAULT NOW(), + CONSTRAINT cached_assets_unique UNIQUE (kind, name, version, platform) +); + +CREATE INDEX idx_cached_assets_kind ON cached_assets(kind); +CREATE INDEX idx_cached_assets_name ON cached_assets(name); + +CREATE TABLE catalog_gems ( + name TEXT PRIMARY KEY, + latest_version TEXT, + synced_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE catalog_meta ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); + +CREATE TABLE gem_metadata ( + name TEXT NOT NULL, + version TEXT NOT NULL, + platform TEXT NOT NULL DEFAULT 'ruby', + summary TEXT, + description TEXT, + licenses TEXT, + authors TEXT, + emails TEXT, + homepage TEXT, + documentation_url TEXT, + changelog_url TEXT, + source_code_url TEXT, + bug_tracker_url TEXT, + wiki_url TEXT, + funding_url TEXT, + metadata_json TEXT, + dependencies_json TEXT NOT NULL DEFAULT '[]', + executables_json TEXT, + extensions_json TEXT, + native_languages_json TEXT, + has_native_extensions BOOLEAN NOT NULL DEFAULT FALSE, + has_embedded_binaries BOOLEAN NOT NULL DEFAULT FALSE, + required_ruby_version TEXT, + required_rubygems_version TEXT, + rubygems_version TEXT, + specification_version INTEGER, + built_at TEXT, + size_bytes BIGINT, + sha256 TEXT, + sbom_json TEXT, + PRIMARY KEY (name, version, platform) +); + +CREATE TABLE gem_versions ( + id BIGSERIAL PRIMARY KEY, + name TEXT NOT NULL, + version TEXT NOT NULL, + platform TEXT, + sha256 TEXT, + published_at TIMESTAMPTZ NOT NULL, + available_after TIMESTAMPTZ NOT NULL, + status TEXT NOT NULL DEFAULT 'quarantine', + status_reason TEXT, + upstream_yanked BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + CONSTRAINT gem_versions_unique UNIQUE (name, version, platform) +); + +CREATE INDEX idx_gem_versions_name ON gem_versions(name); +CREATE INDEX idx_gem_versions_status ON gem_versions(status); +CREATE INDEX idx_gv_available ON gem_versions(available_after); + +CREATE TABLE gem_symbols ( + id BIGSERIAL PRIMARY KEY, + gem_name TEXT NOT NULL, + gem_version TEXT NOT NULL, + gem_platform TEXT NOT NULL DEFAULT 'ruby', + file_path TEXT NOT NULL, + symbol_type TEXT NOT NULL, + symbol_name TEXT NOT NULL, + parent_name TEXT, + line_number INTEGER, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + CONSTRAINT gem_symbols_unique UNIQUE (gem_name, gem_version, gem_platform, file_path, symbol_name), + CONSTRAINT fk_gem_symbols_metadata + FOREIGN KEY (gem_name, gem_version, gem_platform) + REFERENCES gem_metadata(name, version, platform) + ON DELETE CASCADE +); + +CREATE INDEX idx_gem_symbols_name ON gem_symbols(symbol_name); +CREATE INDEX idx_gem_symbols_gem ON gem_symbols(gem_name, gem_version); diff --git a/crates/vein-adapter/migrations/sqlite/20250111000001_init.sql b/crates/vein-adapter/migrations/sqlite/20250111000001_init.sql new file mode 100644 index 0000000..e1eefb8 --- /dev/null +++ b/crates/vein-adapter/migrations/sqlite/20250111000001_init.sql @@ -0,0 +1,95 @@ +-- Vein SQLite Schema + +CREATE TABLE cached_assets ( + kind TEXT NOT NULL, + name TEXT NOT NULL, + version TEXT NOT NULL, + platform TEXT, + path TEXT NOT NULL, + sha256 TEXT NOT NULL, + size_bytes INTEGER NOT NULL, + last_accessed TIMESTAMP DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), + PRIMARY KEY (kind, name, version, platform) +); + +CREATE TABLE catalog_gems ( + name TEXT PRIMARY KEY, + latest_version TEXT, + synced_at TIMESTAMP DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')) +); + +CREATE TABLE catalog_meta ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); + +CREATE TABLE gem_metadata ( + name TEXT NOT NULL, + version TEXT NOT NULL, + platform TEXT NOT NULL DEFAULT 'ruby', + summary TEXT, + description TEXT, + licenses TEXT, + authors TEXT, + emails TEXT, + homepage TEXT, + documentation_url TEXT, + changelog_url TEXT, + source_code_url TEXT, + bug_tracker_url TEXT, + wiki_url TEXT, + funding_url TEXT, + metadata_json TEXT, + dependencies_json TEXT NOT NULL DEFAULT '[]', + executables_json TEXT, + extensions_json TEXT, + native_languages_json TEXT, + has_native_extensions INTEGER NOT NULL DEFAULT 0, + has_embedded_binaries INTEGER NOT NULL DEFAULT 0, + required_ruby_version TEXT, + required_rubygems_version TEXT, + rubygems_version TEXT, + specification_version INTEGER, + built_at TEXT, + size_bytes INTEGER, + sha256 TEXT, + sbom_json TEXT, + PRIMARY KEY (name, version, platform) +); + +CREATE TABLE gem_versions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + version TEXT NOT NULL, + platform TEXT, + sha256 TEXT, + published_at TIMESTAMP NOT NULL, + available_after TIMESTAMP NOT NULL, + status TEXT NOT NULL DEFAULT 'quarantine', + status_reason TEXT, + upstream_yanked INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), + updated_at TIMESTAMP NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), + UNIQUE (name, version, platform) +); + +CREATE INDEX idx_gem_versions_name ON gem_versions(name); +CREATE INDEX idx_gem_versions_status ON gem_versions(status); +CREATE INDEX idx_gv_available ON gem_versions(available_after); + +CREATE TABLE gem_symbols ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + gem_name TEXT NOT NULL, + gem_version TEXT NOT NULL, + gem_platform TEXT, + file_path TEXT NOT NULL, + symbol_type TEXT NOT NULL, + symbol_name TEXT NOT NULL, + parent_name TEXT, + line_number INTEGER, + created_at TIMESTAMP DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), + UNIQUE(gem_name, gem_version, gem_platform, file_path, symbol_name) +); + +CREATE INDEX idx_gem_symbols_name ON gem_symbols(symbol_name); +CREATE INDEX idx_gem_symbols_gem ON gem_symbols(gem_name, gem_version); diff --git a/crates/vein-adapter/src/cache.rs b/crates/vein-adapter/src/cache.rs index 3c31eec..f7e5f6e 100644 --- a/crates/vein-adapter/src/cache.rs +++ b/crates/vein-adapter/src/cache.rs @@ -128,14 +128,8 @@ pub trait CacheBackendTrait: Send + Sync { name: &str, ) -> impl Future>> + Send; - fn quarantine_table_exists(&self) -> impl Future> + Send; - - fn run_quarantine_migrations(&self) -> impl Future> + Send; - // ==================== Symbol Indexing Methods ==================== - fn run_symbols_migrations(&self) -> impl Future> + Send; - fn insert_symbols( &self, gem_name: &str, diff --git a/crates/vein-adapter/src/cache/postgres.rs b/crates/vein-adapter/src/cache/postgres.rs index c4b0236..9a8cb2f 100644 --- a/crates/vein-adapter/src/cache/postgres.rs +++ b/crates/vein-adapter/src/cache/postgres.rs @@ -21,9 +21,19 @@ impl PostgresCacheBackend { pub async fn connect(url: &str, max_connections: u32) -> Result { let pool = PgPoolOptions::new() .max_connections(max_connections.max(1)) + .acquire_timeout(std::time::Duration::from_secs(5)) + .idle_timeout(std::time::Duration::from_secs(60)) + .test_before_acquire(true) .connect(url) .await .with_context(|| format!("connecting to postgres database {}", url))?; + + // Run migrations + sqlx::migrate!("./migrations/postgres") + .run(&pool) + .await + .context("running postgres migrations")?; + Ok(Self { pool }) } @@ -360,7 +370,7 @@ impl CacheBackendTrait for PostgresCacheBackend { .context("counting unique gems (postgres)")?; let total_size_bytes: i64 = - sqlx::query_scalar("SELECT COALESCE(SUM(size_bytes), 0) FROM cached_assets") + sqlx::query_scalar("SELECT COALESCE(SUM(size_bytes), 0)::BIGINT FROM cached_assets") .fetch_one(&self.pool) .await .context("summing cached asset sizes (postgres)")?; @@ -818,113 +828,6 @@ impl CacheBackendTrait for PostgresCacheBackend { Ok(rows.into_iter().map(Into::into).collect()) } - async fn quarantine_table_exists(&self) -> Result { - let exists: bool = sqlx::query_scalar( - r#" - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_name = 'gem_versions' - ) - "#, - ) - .fetch_one(&self.pool) - .await - .context("checking quarantine table exists (postgres)")?; - - Ok(exists) - } - - async fn run_quarantine_migrations(&self) -> Result<()> { - // Create gem_versions table - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS gem_versions ( - id BIGSERIAL PRIMARY KEY, - name TEXT NOT NULL, - version TEXT NOT NULL, - platform TEXT, - sha256 TEXT, - published_at TIMESTAMPTZ NOT NULL, - available_after TIMESTAMPTZ NOT NULL, - status TEXT NOT NULL DEFAULT 'quarantine', - status_reason TEXT, - upstream_yanked BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT gem_versions_unique UNIQUE (name, version, platform) - ) - "#, - ) - .execute(&self.pool) - .await - .context("creating gem_versions table (postgres)")?; - - // Create indexes - sqlx::query( - "CREATE INDEX IF NOT EXISTS idx_gem_versions_name ON gem_versions(name)", - ) - .execute(&self.pool) - .await - .context("creating name index (postgres)")?; - - sqlx::query( - "CREATE INDEX IF NOT EXISTS idx_gem_versions_status ON gem_versions(status)", - ) - .execute(&self.pool) - .await - .context("creating status index (postgres)")?; - - sqlx::query( - "CREATE INDEX IF NOT EXISTS idx_gv_available ON gem_versions(available_after)", - ) - .execute(&self.pool) - .await - .context("creating available_after index (postgres)")?; - - Ok(()) - } - - async fn run_symbols_migrations(&self) -> Result<()> { - // Create gem_symbols table - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS gem_symbols ( - id BIGSERIAL PRIMARY KEY, - gem_name TEXT NOT NULL, - gem_version TEXT NOT NULL, - gem_platform TEXT, - file_path TEXT NOT NULL, - symbol_type TEXT NOT NULL, - symbol_name TEXT NOT NULL, - parent_name TEXT, - line_number INTEGER, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT gem_symbols_unique UNIQUE (gem_name, gem_version, gem_platform, file_path, symbol_name) - ) - "#, - ) - .execute(&self.pool) - .await - .context("creating gem_symbols table (postgres)")?; - - // Create indexes - sqlx::query( - "CREATE INDEX IF NOT EXISTS idx_gem_symbols_name ON gem_symbols(symbol_name)", - ) - .execute(&self.pool) - .await - .context("creating symbol_name index (postgres)")?; - - sqlx::query( - "CREATE INDEX IF NOT EXISTS idx_gem_symbols_gem ON gem_symbols(gem_name, gem_version)", - ) - .execute(&self.pool) - .await - .context("creating gem index (postgres)")?; - - Ok(()) - } - async fn insert_symbols( &self, gem_name: &str, diff --git a/crates/vein-adapter/src/cache/sqlite.rs b/crates/vein-adapter/src/cache/sqlite.rs index b6e83aa..90af7ba 100644 --- a/crates/vein-adapter/src/cache/sqlite.rs +++ b/crates/vein-adapter/src/cache/sqlite.rs @@ -24,7 +24,7 @@ impl SqliteCacheBackend { .with_context(|| format!("creating database directory {}", parent.display()))?; } let conn_str = format!( - "sqlite://{}", + "sqlite://{}?mode=rwc", path.to_str().context("database path not UTF-8")? ); let pool = SqlitePoolOptions::new() @@ -32,9 +32,14 @@ impl SqliteCacheBackend { .connect(&conn_str) .await .with_context(|| format!("connecting to sqlite database {}", path.display()))?; - let backend = Self { pool }; - backend.init_schema().await?; - Ok(backend) + + // Run migrations + sqlx::migrate!("./migrations/sqlite") + .run(&pool) + .await + .context("running sqlite migrations")?; + + Ok(Self { pool }) } pub async fn connect_memory() -> Result { @@ -43,129 +48,14 @@ impl SqliteCacheBackend { .connect("sqlite::memory:") .await .context("connecting to in-memory sqlite database")?; - let backend = Self { pool }; - backend.init_schema().await?; - Ok(backend) - } - - async fn init_schema(&self) -> Result<()> { - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS cached_assets ( - kind TEXT NOT NULL, - name TEXT NOT NULL, - version TEXT NOT NULL, - platform TEXT, - path TEXT NOT NULL, - sha256 TEXT NOT NULL, - size_bytes INTEGER NOT NULL, - last_accessed TIMESTAMP DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), - PRIMARY KEY (kind, name, version, platform) - ) - "#, - ) - .execute(&self.pool) - .await - .context("creating cached_assets table (sqlite)")?; - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS catalog_gems ( - name TEXT PRIMARY KEY, - latest_version TEXT, - synced_at TIMESTAMP DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')) - ) - "#, - ) - .execute(&self.pool) - .await - .context("creating catalog_gems table (sqlite)")?; - - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS catalog_meta ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - ) - "#, - ) - .execute(&self.pool) - .await - .context("creating catalog_meta table (sqlite)")?; - - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS gem_metadata ( - name TEXT NOT NULL, - version TEXT NOT NULL, - platform TEXT, - summary TEXT, - description TEXT, - licenses TEXT, - authors TEXT, - emails TEXT, - homepage TEXT, - documentation_url TEXT, - changelog_url TEXT, - source_code_url TEXT, - bug_tracker_url TEXT, - wiki_url TEXT, - funding_url TEXT, - metadata_json TEXT, - dependencies_json TEXT NOT NULL, - executables_json TEXT, - extensions_json TEXT, - native_languages_json TEXT, - has_native_extensions INTEGER NOT NULL, - has_embedded_binaries INTEGER NOT NULL, - required_ruby_version TEXT, - required_rubygems_version TEXT, - rubygems_version TEXT, - specification_version INTEGER, - built_at TEXT, - size_bytes INTEGER, - sha256 TEXT, - sbom_json TEXT, - PRIMARY KEY (name, version, platform) - ) - "#, - ) - .execute(&self.pool) - .await - .context("creating gem_metadata table (sqlite)")?; - - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS gem_symbols ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - gem_name TEXT NOT NULL, - gem_version TEXT NOT NULL, - gem_platform TEXT, - file_path TEXT NOT NULL, - symbol_type TEXT NOT NULL, - symbol_name TEXT NOT NULL, - parent_name TEXT, - line_number INTEGER, - created_at TIMESTAMP DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), - UNIQUE(gem_name, gem_version, gem_platform, file_path, symbol_name) - ) - "#, - ) - .execute(&self.pool) - .await - .context("creating gem_symbols table (sqlite)")?; - - sqlx::query("CREATE INDEX IF NOT EXISTS idx_gem_symbols_name ON gem_symbols(symbol_name)") - .execute(&self.pool) - .await - .context("creating idx_gem_symbols_name index (sqlite)")?; - - sqlx::query("CREATE INDEX IF NOT EXISTS idx_gem_symbols_gem ON gem_symbols(gem_name, gem_version)") - .execute(&self.pool) + // Run migrations + sqlx::migrate!("./migrations/sqlite") + .run(&pool) .await - .context("creating idx_gem_symbols_gem index (sqlite)")?; + .context("running sqlite migrations")?; - Ok(()) + Ok(Self { pool }) } async fn touch(&self, key: &AssetKey<'_>) -> Result<()> { @@ -984,76 +874,6 @@ impl CacheBackendTrait for SqliteCacheBackend { Ok(rows.into_iter().map(Into::into).collect()) } - async fn quarantine_table_exists(&self) -> Result { - let exists: i64 = sqlx::query_scalar( - r#" - SELECT COUNT(*) - FROM sqlite_master - WHERE type = 'table' AND name = 'gem_versions' - "#, - ) - .fetch_one(&self.pool) - .await - .context("checking quarantine table exists (sqlite)")?; - - Ok(exists > 0) - } - - async fn run_quarantine_migrations(&self) -> Result<()> { - // Create gem_versions table - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS gem_versions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - version TEXT NOT NULL, - platform TEXT, - sha256 TEXT, - published_at TEXT NOT NULL, - available_after TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'quarantine', - status_reason TEXT, - upstream_yanked INTEGER NOT NULL DEFAULT 0, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - UNIQUE(name, version, platform) - ) - "#, - ) - .execute(&self.pool) - .await - .context("creating gem_versions table (sqlite)")?; - - // Create indexes - sqlx::query( - "CREATE INDEX IF NOT EXISTS idx_gem_versions_name ON gem_versions(name)", - ) - .execute(&self.pool) - .await - .context("creating name index (sqlite)")?; - - sqlx::query( - "CREATE INDEX IF NOT EXISTS idx_gem_versions_status ON gem_versions(status)", - ) - .execute(&self.pool) - .await - .context("creating status index (sqlite)")?; - - sqlx::query( - "CREATE INDEX IF NOT EXISTS idx_gv_available ON gem_versions(available_after)", - ) - .execute(&self.pool) - .await - .context("creating available_after index (sqlite)")?; - - Ok(()) - } - - async fn run_symbols_migrations(&self) -> Result<()> { - // Symbols migrations are already in init_schema - Ok(()) - } - async fn insert_symbols( &self, gem_name: &str, diff --git a/crates/vein-admin/src/commands/index.rs b/crates/vein-admin/src/commands/index.rs index ed0acf5..7bee0a5 100644 --- a/crates/vein-admin/src/commands/index.rs +++ b/crates/vein-admin/src/commands/index.rs @@ -8,12 +8,6 @@ use crate::commands::AppContext; pub async fn run(name: String, version: Option) -> Result<()> { let ctx = AppContext::init().await?; - // Run symbols migrations to ensure table exists - ctx.cache - .run_symbols_migrations() - .await - .context("running symbols migrations")?; - if let Some(ver) = version { // Index specific version info!(gem = %name, version = %ver, "Indexing gem"); diff --git a/docker-compose.yml b/docker-compose.yml index 30d935d..7cdc5a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,26 +1,50 @@ services: + postgres: + image: ghcr.io/seuros/postgis-with-extensions:18 + container_name: vein-postgres + environment: + POSTGRES_DB: vein + POSTGRES_USER: vein + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-vein} + volumes: + - vein-pgdata:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U vein -d vein"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - vein-network + restart: unless-stopped + vein: + build: . image: ghcr.io/contriboss/vein:latest container_name: vein + depends_on: + postgres: + condition: service_healthy ports: - "8346:8346" + environment: + RUST_LOG: ${RUST_LOG:-info} volumes: - # Persistent gem cache - vein-gems:/data/gems - # Persistent database - - vein-db:/data/db - # Optional: custom configuration - # - ./vein.toml:/data/vein.toml:ro - environment: - - RUST_LOG=info - restart: unless-stopped + - ./vein.docker.toml:/data/vein.toml:ro + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:8346/up"] + interval: 30s + timeout: 5s + start_period: 10s + retries: 3 networks: - vein-network + restart: unless-stopped volumes: vein-gems: driver: local - vein-db: + vein-pgdata: driver: local networks: diff --git a/src/main.rs b/src/main.rs index 3f536ed..8693788 100644 --- a/src/main.rs +++ b/src/main.rs @@ -303,13 +303,6 @@ fn run_server(config_path: PathBuf) -> Result<()> { .block_on(connect_cache_backend(config.as_ref())) .context("connecting to cache index")?; - // Ensure quarantine tables exist if enabled - rt.block_on(quarantine::ensure_tables( - index.as_ref(), - &config.delay_policy, - )) - .context("initializing quarantine tables")?; - // Start quarantine promotion scheduler if enabled quarantine::spawn_promotion_scheduler(&config.delay_policy, index.clone(), None); diff --git a/src/quarantine.rs b/src/quarantine.rs index d278f6c..a30549a 100644 --- a/src/quarantine.rs +++ b/src/quarantine.rs @@ -90,30 +90,6 @@ pub fn spawn_promotion_scheduler( }); } -/// Ensures quarantine database tables exist. -/// -/// Should be called during startup before using quarantine features. -pub async fn ensure_tables(index: &CacheBackend, config: &DelayPolicyConfig) -> Result<()> { - if !config.enabled { - return Ok(()); - } - - if !index - .quarantine_table_exists() - .await - .context("checking quarantine table")? - { - tracing::info!("Creating quarantine database tables"); - index - .run_quarantine_migrations() - .await - .context("running quarantine migrations")?; - tracing::info!("Quarantine tables created"); - } - - Ok(()) -} - /// Manually triggers promotion of expired quarantines. /// /// Useful for CLI commands. diff --git a/vein.docker.toml b/vein.docker.toml new file mode 100644 index 0000000..78c334b --- /dev/null +++ b/vein.docker.toml @@ -0,0 +1,15 @@ +# Vein configuration for Docker/Podman deployment + +[server] +host = "0.0.0.0" +port = 8346 + +[storage] +path = "/data/gems" + +[database] +url = "postgres://vein:vein@postgres/vein?sslmode=disable" +max_connections = 4 + +[logging] +level = "info"