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
71 changes: 71 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: CI

on:
push:
branches: [master, dev]
pull_request:
branches: [master, dev]

# A new push to a branch makes an in-flight run for that branch obsolete.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
backend:
name: Backend (pytest)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
# Matches the runtime stage of the production Dockerfile.
python-version: "3.12"
cache: pip
cache-dependency-path: |
backend/requirements.txt
backend/requirements-dev.txt

- name: Install torch (CPU build)
# Default PyPI resolves torch to the CUDA build: ~2.5 GB of NVIDIA
# wheels a CI runner can never use. Installing from the CPU index first
# means the requirements step below finds it already satisfied.
run: pip install --index-url https://download.pytorch.org/whl/cpu "torch>=2.2,<3.0"

- name: Install dependencies
working-directory: backend
run: pip install -r requirements-dev.txt

- name: Run tests
# pyproject.toml sets testpaths relative to this directory, and the
# suite imports "app" as a top-level package, so cwd matters.
working-directory: backend
run: pytest

frontend:
name: Frontend (lint, types, build)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
# Matches the build stage of the production Dockerfile.
node-version: "24"
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
working-directory: frontend
run: npm ci

- name: Lint
working-directory: frontend
run: npm run lint

- name: Type-check and build
# "build" is `tsc -b && vite build`, so this covers type errors and
# verifies the bundle the production image actually ships.
working-directory: frontend
run: npm run build
50 changes: 50 additions & 0 deletions deploy/nginx.conf.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# nginx equivalent of Caddyfile.example, for hosts already running nginx.
#
# Install as /etc/nginx/sites-available/embeddings, then:
# ln -s /etc/nginx/sites-available/embeddings /etc/nginx/sites-enabled/
# nginx -t && systemctl reload nginx
# certbot --nginx -d embeddings.mannymcgrail.com
#
# certbot rewrites this file in place to add the TLS listener and to redirect
# port 80, so apply it as plain HTTP first and let certbot do the rest. Point
# the DNS A record at this server before running certbot: the HTTP-01
# challenge has to reach port 80. Behind Cloudflare, leave the record
# unproxied ("DNS only") until the certificate is issued.

server {
listen 80;
listen [::]:80;
server_name embeddings.mannymcgrail.com;

# The container publishes on loopback only, so nginx is the sole way in.
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# uvicorn runs with --proxy-headers; this is what tells it the original
# request was HTTPS, so generated URLs do not downgrade to http://.
proxy_set_header X-Forwarded-Proto $scheme;

# The first request for an uncached model downloads weights and then
# computes a UMAP projection, which the app allows up to
# MODEL_LOAD_TIMEOUT_SECONDS (300) to finish. nginx defaults to a 60s
# read timeout, which would sever that request and surface as a
# confusing 504 well before the backend gave up. Keep this at or above
# the application timeout.
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}

# Projection payloads are large, highly compressible JSON.
gzip on;
gzip_proxied any;
gzip_min_length 1000;
gzip_types application/json application/javascript text/css text/plain image/svg+xml;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
}
Loading