Sign inSign up

jhangyu/albatross-api

By jhangyu

•Updated 8 days ago

Albatross 航班價格追蹤 — api

Image
0

1.9K

jhangyu/albatross-api repository overview

⁠Albatross — api

Albatross 是自架的航班價格追蹤系統。本 image 是其中的 API server(Hono + TypeScript,Better Auth 登入、業務 API、內建排程器,唯一的 DB 寫入者)。 本 image 不能單獨執行,必須與 jhangyu/albatross-scraper, jhangyu/albatross-browser-runtime, jhangyu/albatross-web 及 PostgreSQL 一起以 docker compose 啟動。

原始碼:https://github.com/jhangyu/albatross⁠

⁠使用 docker compose 一鍵部署

  1. 建立一個空的部署目錄,存入下方的 docker-compose.prod.yml。不需要任何 env 檔。
  2. 啟動:
    docker compose -f docker-compose.prod.yml up -d
    
  3. 瀏覽器開啟 http://<主機>:43280,註冊帳號。第一位註冊者自動成為 admin,其餘設定(自動排程、SMTP 通知信、來源帳號等)都在網頁的管理頁完成。

注意事項:

  • 三個密鑰(資料庫密碼、登入簽章密鑰、瀏覽器服務 token)由 secrets 服務首次啟動時自動產生,存在 ${DATA_PATH}/secrets。
  • 持久資料都在 DATA_PATH(預設 ./data-prod)。請備份 ${DATA_PATH}/secrets 與 ${DATA_PATH}/postgres。
  • 只有 web 對外開 port;db、api、scraper、browser-runtime 只在 compose 內部網路連線。
  • 選填覆寫:要改 port、資料目錄或版本時,把下方 .env.prod 範例中要改的行存成 .env.prod,改用 docker compose --env-file .env.prod -f docker-compose.prod.yml up -d 啟動。docker compose 一律讀取目前目錄的 .env,部署目錄內不要放其他 .env。
  • 升級:執行 docker compose -f docker-compose.prod.yml pull,再執行上面的 up -d。
⁠docker-compose.prod.yml
# Runs with NO env file: `docker compose -f docker-compose.prod.yml up -d`.
# Every value has a default below. Optional overrides go in .env.prod (see .env.prod.example)
# and are passed with `--env-file .env.prod`.
# TRAP: `docker compose` always loads the project-root `.env` for variable interpolation, even
# with `-f`. Inside a checkout that has a dev root `.env`, pass `--env-file .env.prod` or run
# from another directory, or the stack inherits the dev WEB_PORT and DATA_PATH and collides.
services:
  # One-shot: env value wins, else the persisted file is kept, else a 64-hex value is generated once (R-51).
  # Layout ${DATA_PATH}/secrets/<name>/value: root 0700, dir 0755, file 0444 (browser-runtime runs non-root).
  secrets:
    container_name: albatross-prod-secrets
    image: postgres:18-alpine
    entrypoint: ["/bin/sh", "-ec"]
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-albatross}
      POSTGRES_DB: ${POSTGRES_DB:-albatross}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-}
      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-}
      BROWSER_RUNTIME_TOKEN: ${BROWSER_RUNTIME_TOKEN:-}
    volumes:
      - ${DATA_PATH:-./data-prod}/secrets:/secrets
    restart: "no"
    command:
      - |
        chmod 700 /secrets
        put() { mkdir -p /secrets/$$1; chmod 755 /secrets/$$1; printf %s "$$2" > /secrets/$$1/value.tmp; chmod 444 /secrets/$$1/value.tmp; mv -f /secrets/$$1/value.tmp /secrets/$$1/value; }
        get() { cat /secrets/$$1/value 2>/dev/null || true; }
        resolve() {
          cur=$$(get $$1)
          if [ -n "$$2" ]; then
            # postgres reads its password only at first init: a changed env value would split api and db.
            if [ "$$1" = postgres_password ] && [ -n "$$cur" ] && [ "$$cur" != "$$2" ]; then
              echo "POSTGRES_PASSWORD differs from DATA_PATH/secrets/postgres_password/value. Run ALTER ROLE first, then delete that file." >&2; exit 1
            fi
            [ "$$cur" = "$$2" ] || put $$1 "$$2"
          elif [ -z "$$cur" ]; then put $$1 "$$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"; fi
        }
        resolve postgres_password "$$POSTGRES_PASSWORD"
        resolve better_auth_secret "$$BETTER_AUTH_SECRET"
        resolve browser_runtime_token "$$BROWSER_RUNTIME_TOKEN"
        # ponytail: an env POSTGRES_PASSWORD with URL-special chars breaks this URL (same ceiling as before). Generated values are hex.
        put database_url "postgresql://$$POSTGRES_USER:$$(get postgres_password)@db:5432/$$POSTGRES_DB"

  db:
    container_name: albatross-prod-db
    image: postgres:18-alpine
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-albatross}
      POSTGRES_PASSWORD_FILE: /run/albatross/postgres_password/value
      POSTGRES_DB: ${POSTGRES_DB:-albatross}
    volumes:
      - ${DATA_PATH:-./data-prod}/postgres:/var/lib/postgresql
      - ${DATA_PATH:-./data-prod}/secrets/postgres_password:/run/albatross/postgres_password:ro
    depends_on:
      secrets:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-albatross}"]
      interval: 30s
      start_interval: 1s
      start_period: 10s
      timeout: 2s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 512M
    restart: unless-stopped

  api:
    container_name: albatross-prod-api
    image: jhangyu/albatross-api:${IMAGE_TAG:-latest}
    environment:
      PORT: 4891
      DATABASE_URL_FILE: /run/albatross/database_url/value
      BETTER_AUTH_SECRET_FILE: /run/albatross/better_auth_secret/value
      WORKER_URL: http://scraper:8000
      BROWSER_RUNTIME_WS_URL: ws://browser-runtime:8000/vnc/ws
      BROWSER_RUNTIME_TOKEN_FILE: /run/albatross/browser_runtime_token/value
      # Seals the SMTP password stored by the admin settings page (base64 of 32 bytes). Optional:
      # empty = the api generates a key on first use into the api volume below and keeps it.
      SETTINGS_ENCRYPTION_KEY: ${SETTINGS_ENCRYPTION_KEY:-}
    volumes:
      # Auto-generated settings encryption key (settings.key), kept across container recreation.
      - ${DATA_PATH:-./data-prod}/api:/app/data/api
      - ${DATA_PATH:-./data-prod}/secrets/database_url:/run/albatross/database_url:ro
      - ${DATA_PATH:-./data-prod}/secrets/better_auth_secret:/run/albatross/better_auth_secret:ro
      - ${DATA_PATH:-./data-prod}/secrets/browser_runtime_token:/run/albatross/browser_runtime_token:ro
    depends_on:
      db:
        condition: service_healthy
      secrets:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4891/health"]
      interval: 30s
      start_interval: 2s
      start_period: 15s
      timeout: 3s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 512M
    restart: unless-stopped

  scraper:
    container_name: albatross-prod-scraper
    image: jhangyu/albatross-scraper:${IMAGE_TAG:-latest}
    environment:
      BROWSER_RUNTIME_URL: http://browser-runtime:8000
      BROWSER_RUNTIME_TOKEN_FILE: /run/albatross/browser_runtime_token/value
    volumes:
      - ${DATA_PATH:-./data-prod}/profiles:/app/profiles
      - ${DATA_PATH:-./data-prod}/logs:/app/data/logs
      - ${DATA_PATH:-./data-prod}/secrets/browser_runtime_token:/run/albatross/browser_runtime_token:ro
    depends_on:
      browser-runtime:
        condition: service_healthy
      secrets:
        condition: service_completed_successfully
    deploy:
      resources:
        limits:
          memory: 512M
    restart: unless-stopped

  browser-runtime:
    container_name: albatross-prod-runtime
    image: jhangyu/albatross-browser-runtime:${IMAGE_TAG:-latest}
    environment:
      BROWSER_RUNTIME_TOKENS_FILE: /run/albatross/browser_runtime_token/value
    # Chromium interactive sessions and transient Firefox bootstraps need headroom.
    shm_size: 2g
    volumes:
      - ${DATA_PATH:-./data-prod}/browser-runtime:/app/state
      - ${DATA_PATH:-./data-prod}/secrets/browser_runtime_token:/run/albatross/browser_runtime_token:ro
    depends_on:
      secrets:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD", "curl", "-sf", "http://127.0.0.1:8000/health"]
      interval: 30s
      start_interval: 2s
      start_period: 15s
      timeout: 3s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 3G
    restart: unless-stopped

  web:
    container_name: albatross-prod-web
    image: jhangyu/albatross-web:${IMAGE_TAG:-latest}
    ports:
      - "${WEB_PORT:-43280}:80"
    depends_on:
      api:
        condition: service_healthy
    deploy:
      resources:
        limits:
          memory: 128M
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    restart: unless-stopped
⁠.env.prod(選填覆寫範例)
# Prod stack optional overrides. The stack runs with NO env file:
#   docker compose -f docker-compose.prod.yml up -d
# Every value below has a default inside docker-compose.prod.yml. Copy this file to
# .env.prod and keep only the lines you want to change, then start with:
#   docker compose --env-file .env.prod -f docker-compose.prod.yml up -d
#
# TRAP: `docker compose` always loads the project-root `.env` for variable
# interpolation, even when `-f docker-compose.prod.yml` is passed. Inside a checkout that
# has a dev root `.env`, pass --env-file .env.prod (or run from another directory), or the
# stack silently inherits the dev stack's WEB_PORT and DATA_PATH and collides with it.

# Image tag to deploy (set by scripts/release.py; override manually
# for a specific version).
IMAGE_TAG=latest

# Secrets (POSTGRES_PASSWORD, BETTER_AUTH_SECRET, BROWSER_RUNTIME_TOKEN) are optional. A non-empty value wins and is
# written to ${DATA_PATH}/secrets/<name>/value. Empty = the `secrets` service keeps the existing file or generates one once.
# Back up ${DATA_PATH}/secrets. Rotate DB password: ALTER ROLE ... PASSWORD, delete secrets/postgres_password/value, set the
# new value here, `up -d`. Rotate the others: delete the file (or set the env), `up -d`; a new BETTER_AUTH_SECRET signs everyone out.

# Database
POSTGRES_USER=albatross
POSTGRES_PASSWORD=
POSTGRES_DB=albatross

# API
BETTER_AUTH_SECRET=

# Alert email: SMTP settings live in the DB (admin page /smtp-settings). The key below seals the
# stored SMTP password: base64 of exactly 32 bytes (openssl rand -base64 32). Optional: empty = the API
# generates one on first use into ${DATA_PATH}/api/settings.key and keeps it (back that file up).
SETTINGS_ENCRYPTION_KEY=

# Browser runtime (interactive link sessions + the display WebSocket relay).
# Shared secret between api/scraper and the runtime (optional, see Secrets above).
BROWSER_RUNTIME_TOKEN=

# Web
WEB_PORT=43280

# Data paths (bind mounts for persistent state) — kept separate from the dev
# stack's ./data so the two can run side by side without corrupting a shared
# Postgres data directory.
DATA_PATH=./data-prod

Tag summary

Content type

Image

Digest

sha256:852a73dcf…

Size

180.8 MB

Last updated

8 days ago

docker pull jhangyu/albatross-api