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
12 changes: 11 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
# HTTP port the webhook server listens on
PORT=8080

# Path to the SQLite database file
# Path to the SQLite database file (the zero-config default)
DB_PATH=./data/standup.db

# Bring your own database: set a PostgreSQL connection string and the
# embedded SQLite is skipped entirely. Works with managed Postgres
# (RDS, Cloud SQL, Neon, Supabase, …) or the bundled compose service
# (docker compose --profile postgres up -d).
# DATABASE_URL=postgres://asyncup:password@postgres:5432/asyncup
DATABASE_URL=

# Password for the optional bundled Postgres compose service
# POSTGRES_PASSWORD=

# Chat adapter: "google" for production, "fake" for local demo (logs to console)
ADAPTER=google

Expand Down
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,31 @@ jobs:
name: coverage
path: coverage/lcov.info

test-postgres:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18-alpine
env:
POSTGRES_PASSWORD: ci
POSTGRES_DB: asyncup_ci
ports: ['5432:5432']
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm test
env:
TEST_DATABASE_URL: postgres://postgres:ci@localhost:5432/asyncup_ci

docker:
runs-on: ubuntu-latest
steps:
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ Each answer is posted as one card per person under a **per-date thread** in your
- **Insights** — `trends` (participation + mood over 4 weeks), weekly digest (`digest on`), CSV export endpoint.
- **AI summaries, bring your own key** — opt-in daily TL;DR and week-in-review via your Anthropic/OpenAI key; nothing leaves your infra otherwise.
- **Team admins & multiple standups per space** — config restricted to admins; address standups by `#id`.
- **Lightweight forever** — one container, SQLite inside (auto-migrating schema), scale-to-zero friendly (`/tick` + free-tier cron ≈ $0/month).
- **Lightweight forever** — one container, SQLite inside (auto-migrating schema), scale-to-zero friendly (`/tick` + free-tier cron ≈ $0/month). Runs happily on 1 vCPU / 512 MB.
- **Bring your own database** — set `DATABASE_URL` and AsyncUp uses your PostgreSQL (managed or `docker compose --profile postgres`) instead of embedded SQLite; both engines tested in CI.
- **Restart-safe** — all scheduling state lives in SQLite; ticks are idempotent.
- **Platform-agnostic core** — Google Chat is an adapter; Slack and Teams are planned.

Expand Down
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,30 @@ services:
required: false
environment:
DB_PATH: /data/standup.db
# Bring your own database: set DATABASE_URL in .env (any PostgreSQL —
# managed/RDS/Cloud SQL, or the bundled one below) and SQLite is skipped.
DATABASE_URL: ${DATABASE_URL:-}
volumes:
- standup-data:/data
# Mount your GCP service account key and set
# GOOGLE_APPLICATION_CREDENTIALS=/app/service-account.json in .env:
# - ./service-account.json:/app/service-account.json:ro

# Optional same-machine PostgreSQL:
# docker compose --profile postgres up -d
# with DATABASE_URL=postgres://asyncup:${POSTGRES_PASSWORD}@postgres:5432/asyncup in .env
postgres:
image: postgres:18-alpine
profiles: [postgres]
restart: unless-stopped
environment:
POSTGRES_USER: asyncup
POSTGRES_DB: asyncup
# Required when using the profile — the postgres image refuses to start without it.
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-}
volumes:
- postgres-data:/var/lib/postgresql/data

volumes:
standup-data:
postgres-data:
15 changes: 9 additions & 6 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Everything is configured via environment variables (see `.env.example`).
| Variable | Default | Purpose |
| --- | --- | --- |
| `PORT` | `8080` | Webhook port |
| `DB_PATH` | `./data/standup.db` | SQLite database file |
| `DB_PATH` | `./data/standup.db` | SQLite database file (default storage) |
| `DATABASE_URL` | *(empty)* | Bring-your-own PostgreSQL connection string — when set, SQLite is skipped (see [Deployment](./deployment#database-embedded-or-bring-your-own)) |
| `ADAPTER` | `google` | `google` for production, `fake` for a console demo |
| `GOOGLE_CHAT_AUDIENCE` | *(empty)* | Your GCP project **number**. Verifies incoming requests are signed by Google Chat. Empty skips verification — local development only |
| `GOOGLE_APPLICATION_CREDENTIALS` | — | Path to the service account key JSON |
Expand All @@ -31,8 +32,10 @@ Everything is configured via environment variables (see `.env.example`).

## Data

All state lives in a single SQLite file (`DB_PATH`): standups, participants,
admins, runs, submissions, blockers, and the DM-space cache. Back it up like
any file; the process can restart at any time without losing or double-sending
prompts. Schema migrations run automatically on startup (tracked via
`PRAGMA user_version`), so upgrading AsyncUp is just deploying the new image.
All state — standups, participants, admins, runs, submissions, blockers, and
the DM-space cache — lives either in a single SQLite file (`DB_PATH`, the
default) or in your own PostgreSQL (`DATABASE_URL`). Back up the file or use
your database's backup story; the process can restart at any time without
losing or double-sending prompts (graceful shutdown on SIGTERM included).
Schema migrations run automatically on startup in both modes, so upgrading
AsyncUp is just deploying the new image.
32 changes: 30 additions & 2 deletions docs/guide/deployment.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
# Deployment

AsyncUp is one small container with SQLite inside — no external database, no
queue, no other moving parts. Google Chat needs to reach it on a public
AsyncUp is one small container — by default with SQLite inside, so there are
no external moving parts at all. Google Chat needs to reach it on a public
HTTPS URL.

## Database: embedded or bring your own

| Mode | Configuration | When to choose it |
| --- | --- | --- |
| **Embedded SQLite** (default) | `DB_PATH` on a persistent volume | Simplest possible ops: one container, one file to back up. Plenty for any team size AsyncUp serves |
| **Bring your own PostgreSQL** | `DATABASE_URL=postgres://…` | You already run managed Postgres (RDS, Cloud SQL, Neon, Supabase, …) and want its backups/HA, or your platform has no persistent volumes (e.g. some scale-to-zero setups) |
| **Postgres on the same machine** | `docker compose --profile postgres up -d` + `DATABASE_URL=postgres://asyncup:…@postgres:5432/asyncup` | Postgres semantics without leaving the box |

Setting `DATABASE_URL` skips SQLite entirely; the schema is created and
migrated automatically on startup in both modes. The full test suite runs
against both engines in CI on every change.

## System requirements

AsyncUp idles at a once-a-minute scheduler tick; load is a few webhook calls
per person per day. CPU architecture: amd64 or arm64.

| Setup | Minimum | Recommended |
| --- | --- | --- |
| App + SQLite | 1 vCPU (shared is fine), 256 MB RAM, 1 GB disk | 1 vCPU, **512 MB RAM**, 5 GB disk |
| App + bundled Postgres | 1 vCPU, 768 MB RAM, 3 GB disk | **2 vCPU, 2 GB RAM**, 10 GB disk |
| App with external/managed DB | 1 vCPU, 256 MB RAM | 1 vCPU, 512 MB RAM |

Realistic sizing: the Node process uses ~100–150 MB RSS; the image is ~300 MB;
a year of standups for a 50-person team is well under 100 MB of data. The
smallest VPS tier (or Cloud Run's 512 MB default) is comfortably enough —
teams into the hundreds of users don't change this picture.

## Prebuilt image

Multi-arch images (`linux/amd64`, `linux/arm64`) are published to GHCR on
Expand Down
Loading