Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docker-compose.cuda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ services:
- "${PORT:-21847}:21847"
environment:
- CIX_API_KEY=${CIX_API_KEY}
# Defense in depth β€” the image already defaults to 21847 but
# pinning it here keeps the host:container port mapping honest
# if a third-party fork or custom build sets a different default.
- CIX_PORT=${CIX_PORT:-21847}
- CIX_EMBEDDING_MODEL=${CIX_EMBEDDING_MODEL:-awhiteside/CodeRankEmbed-Q8_0-GGUF}
- CIX_CHROMA_PERSIST_DIR=/data/chroma
- CIX_SQLITE_PATH=/data/sqlite/projects.db
Expand Down
17 changes: 12 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ services:
- "${PORT:-21847}:21847"
environment:
- CIX_API_KEY=${CIX_API_KEY}
# Defense in depth β€” the image already defaults to 21847 (since
# v0.5.1) but pinning it here keeps the host:container port mapping
# honest if a third-party fork or custom build sets a different
# default.
- CIX_PORT=${CIX_PORT:-21847}
- CIX_EMBEDDING_MODEL=${CIX_EMBEDDING_MODEL:-awhiteside/CodeRankEmbed-Q8_0-GGUF}
- CIX_CHROMA_PERSIST_DIR=/data/chroma
- CIX_SQLITE_PATH=/data/sqlite/projects.db
- CIX_MAX_FILE_SIZE=${CIX_MAX_FILE_SIZE:-524288}
- CIX_EXCLUDED_DIRS=${CIX_EXCLUDED_DIRS:-node_modules,.git,.venv,__pycache__,dist,build,.next,.cache,.DS_Store}
# GGUF cache lives on the named volume below β€” survives `docker compose
# down` (without -v) and is owned by the image's 1001:1001 user, so the
# cix-server process can always write to it regardless of host
# bind-mount permissions.
# down` (without -v) and is owned by the image's 65532:65532 (nonroot)
# user on the CPU image, so the cix-server process can always write to
# it regardless of host bind-mount permissions.
- CIX_GGUF_CACHE_DIR=/data/models
- CIX_LLAMA_BIN_DIR=/app
- CIX_LLAMA_STARTUP_TIMEOUT=120
Expand All @@ -42,8 +47,10 @@ services:
- CIX_BOOTSTRAP_GGUF_PATH=${CIX_BOOTSTRAP_GGUF_PATH:-}
volumes:
# Operator-managed bind for sqlite + chroma so backups and inspection
# are one `cd` away on the host. Make sure the directory is owned by
# 1001:1001 OR use `user: "0:0"` β€” see CLAUDE.md.
# are one `cd` away on the host. The CPU image runs as
# nonroot:nonroot (uid 65532) β€” chown your bind directory to
# 65532:65532 OR add `user: "0:0"` to fall back to root. See
# doc/SECURITY_DEPLOYMENT.md.
- ${HOME}/.cix/data:/data
# Docker-managed named volume layered ON TOP of /data/models. This
# isolates the GGUF cache from host-side bind permission issues and
Expand Down
24 changes: 21 additions & 3 deletions server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,38 @@ RUN CGO_ENABLED=0 GOOS=linux go build \
-o /out/cix-server \
./cmd/cix-server

# Pre-create an empty /data tree so the final stage can COPY it in with
# nonroot (uid 65532) ownership. Without this, a fresh Docker named volume
# initialises root-owned and the distroless nonroot uid in the runtime
# stage cannot `mkdir /data/sqlite` on first boot.
RUN mkdir -p /out/data

FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /
COPY --from=builder /out/cix-server /cix-server

# Default port; override with CIX_PORT at runtime.
ENV CIX_PORT=8001
EXPOSE 8001
# Default port β€” matches CIX_PORT in docker-compose.yml port mapping
# (21847:21847) and the CUDA image (Dockerfile.cuda). The earlier 8001
# default was a Python-FastAPI compat carry-over from the migration era;
# the Python backend was archived 2026-04, so the parity is no longer
# meaningful and the mismatch caused fresh `docker compose up -d` runs
# to leave the host port pointing at a non-listening container port.
ENV CIX_PORT=21847
EXPOSE 21847

# Container data layout β€” pin paths under /data so the named volume in
# portainer-stack.yml / docker-compose receives both SQLite and chromem-go
# state. The Go default ($HOME/.cix/data) targets local `make run` and is
# inappropriate inside distroless where there is no usable HOME.
ENV CIX_SQLITE_PATH=/data/sqlite/projects.db
ENV CIX_CHROMA_PERSIST_DIR=/data/chroma

# Pre-owned /data directory (distroless has no shell to mkdir+chown).
# A fresh Docker named volume mounted onto /data inherits the 65532:65532
# ownership baked here, so the nonroot runtime user can write the sqlite
# and chroma trees on first boot. Bind mounts still need host-side chown
# to 65532:65532 β€” see doc/SECURITY_DEPLOYMENT.md.
COPY --from=builder --chown=65532:65532 /out/data /data
VOLUME ["/data"]

USER nonroot:nonroot
Expand Down
26 changes: 15 additions & 11 deletions server/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# api-go-poc β€” cix-server (Go)
# cix-server (Go)

Phase 1 scaffold of the Go rewrite of `api/` (Python/FastAPI). Runs in parallel
to Python during the PoC β€” default port is **8001** (Python uses 21847).
The Go HTTP server backing cix's indexing + dashboard. Default port is
**21847** (was 8001 during the Python parallel-rollout era; the Python
backend was archived 2026-04 and the parity is no longer meaningful).

## Layout

Expand All @@ -16,24 +17,27 @@ Dockerfile CPU multi-stage, distroless runtime
## Build / run / test

```bash
cd api-go-poc
cd server
go build ./...
go vet ./...
go test ./...

# Local run (binds :8001 by default)
CIX_SQLITE_PATH=/tmp/cix-phase1.db ./cix-server
# Local run (binds :21847 by default)
CIX_SQLITE_PATH=/tmp/cix.db ./cix-server
# Or with version injected:
go build -ldflags "-X main.version=0.2.0-go" -o cix-server ./cmd/cix-server
go build -ldflags "-X main.version=v0.5.1" -o cix-server ./cmd/cix-server
```

## Docker

```bash
docker build -t cix-server-go:phase1 --build-arg VERSION=0.2.0-go .
docker run --rm -p 8001:8001 \
docker build -t cix-server-go:dev --build-arg VERSION=v0.5.1 .
docker run --rm -p 21847:21847 \
-e CIX_API_KEY=cix_<hex> \
-e CIX_BOOTSTRAP_ADMIN_EMAIL=admin@example.com \
-e CIX_BOOTSTRAP_ADMIN_PASSWORD=<pw> \
-v cix-data:/data \
cix-server-go:phase1
cix-server-go:dev
```

## Environment variables
Expand All @@ -43,7 +47,7 @@ All are optional; defaults match `api/app/config.py` except `CIX_PORT`.
| Var | Default | Notes |
|---|---|---|
| `CIX_API_KEY` | `""` | Warned at startup if empty; enforced from Phase 2 |
| `CIX_PORT` | `8001` | Python uses 21847 β€” different to allow parallel run |
| `CIX_PORT` | `21847` | Listen port. Both Docker images bake this in. |
| `CIX_EMBEDDING_MODEL` | `awhiteside/CodeRankEmbed-Q8_0-GGUF` | |
| `CIX_CHROMA_PERSIST_DIR` | `/data/chroma` | Name kept for compat; backend changes in Phase 4 |
| `CIX_SQLITE_PATH` | `/data/sqlite/projects.db` | Suffixed with model-safe name on open |
Expand Down
10 changes: 6 additions & 4 deletions server/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"strings"
)

// Config holds all runtime settings. Defaults match api/app/config.py except
// for Port, which is 8001 by default so the Go server does not collide with
// the Python server (21847) during parallel PoC rollout.
// Config holds all runtime settings. Port defaults to 21847 β€” the same
// value the Docker images bake into ENV CIX_PORT and the same the
// docker-compose templates map on the host side. The earlier 8001
// default was a Python-FastAPI parallel-rollout carry-over; the Python
// backend was archived 2026-04 and the parity is no longer meaningful.
type Config struct {
APIKey string
// AuthDisabled, when true, makes the server skip the API-key check on
Expand Down Expand Up @@ -122,7 +124,7 @@ func Load() (*Config, error) {
}
c.AuthDisabled = authOff

port, err := getenvInt("CIX_PORT", 8001)
port, err := getenvInt("CIX_PORT", 21847)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions server/internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ func TestLoadDefaults(t *testing.T) {
if err != nil {
t.Fatalf("Load: %v", err)
}
if c.Port != 8001 {
t.Errorf("Port default = %d, want 8001", c.Port)
if c.Port != 21847 {
t.Errorf("Port default = %d, want 21847", c.Port)
}
if c.EmbeddingModel != "awhiteside/CodeRankEmbed-Q8_0-GGUF" {
t.Errorf("EmbeddingModel default = %q", c.EmbeddingModel)
Expand Down
Loading