Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fc415ba
Add billing diagnostics and production project backend
vyakymenko May 27, 2026
24acda0
Add billing and production controls to UI
vyakymenko May 27, 2026
acfa393
Add deploy preflight and VPS runbook
vyakymenko May 27, 2026
e3c891d
Harden production billing and domain tests
vyakymenko May 27, 2026
b16727f
Use main as default Likeable deploy branch
vyakymenko May 27, 2026
d282474
Stop requiring unused Stripe publishable key
vyakymenko May 27, 2026
2aaed0e
Add admin production project grants
vyakymenko May 27, 2026
1145c42
Add custom domain DNS verification
vyakymenko May 27, 2026
aceab2b
Clarify custom domain DNS verification status
vyakymenko May 27, 2026
eaf1fe6
Start stopped playgrounds after production grant
vyakymenko May 27, 2026
5bb8ad7
Sweep stopped production playgrounds
vyakymenko May 27, 2026
3b6ffa0
Surface production runtime billing blocks
vyakymenko May 27, 2026
97c1012
Clarify custom domain routing status
vyakymenko May 27, 2026
84e337f
Handle runtime billing on manual starts
vyakymenko May 27, 2026
596d54f
Code runtime billing start failures
vyakymenko May 27, 2026
5f9ca35
Add admin production start retry
vyakymenko May 27, 2026
0b85001
Document production runtime support flow
vyakymenko May 27, 2026
32dd6a6
Sync custom domains to Fibe routing
vyakymenko May 27, 2026
c42c082
Roll out playgrounds after custom domain routing sync
vyakymenko May 27, 2026
0bea816
Reconcile already-running project previews
vyakymenko May 27, 2026
46969da
Skip Fibe clear for pending custom domains
vyakymenko May 27, 2026
f693d70
Expose provisioning failure causes to admins
vyakymenko May 27, 2026
b694421
Show Fibe pool health in admin config
vyakymenko May 27, 2026
88c36d0
Add Likeable deploy preflight CI
vyakymenko May 28, 2026
7399a46
Avoid duplicate CI runs on PR branches
vyakymenko May 28, 2026
3263107
Add admin launch readiness checks
vyakymenko May 28, 2026
18d12d8
Accept bundled greenfield template in readiness
vyakymenko May 28, 2026
020f292
Add live readiness smoke script
vyakymenko May 28, 2026
3240f02
Add live config apply script
vyakymenko May 28, 2026
f9c2611
Add actionable readiness details
vyakymenko May 28, 2026
77aa1c9
Validate live config inputs
vyakymenko May 28, 2026
14d2dae
Check ops scripts in deploy preflight
vyakymenko May 28, 2026
e8a68f0
Scope Likeable billing to playground usage
vyakymenko May 28, 2026
9e1d8b4
Hide legacy production and domain internals
vyakymenko May 28, 2026
079ff92
Clarify Likeable playground scope
vyakymenko May 28, 2026
93a74f6
Ignore legacy production Stripe purchases
vyakymenko May 28, 2026
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 .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ BASE_URL=http://localhost:5173
ADMIN_EMAIL=admin@example.com
REDIS_URL=redis://localhost:6379/0

# One-time public bootstrap. Use a long random value, configure Google OAuth,
# then remove it before real users sign in.
# LIKEABLE_BOOTSTRAP_TOKEN=

# Local/private bootstrap only. Unset after Google OAuth is configured in Admin.
LIKEABLE_DEV_AUTH=1
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
preflight:
name: Deploy preflight
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install Bun
run: npm install -g bun@1.3.10

- name: Install frontend dependencies
run: bun install --frozen-lockfile

- name: Run deploy preflight
run: bin/deploy-preflight
10 changes: 5 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ COPY internal ./internal
COPY --from=frontend /src/dist ./internal/likeable/web-dist
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -buildid=" -o /out/likeable ./cmd/likeable

FROM debian:bookworm-slim AS runtime
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates curl git \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --uid 10001 --create-home --home-dir /home/likeable --shell /usr/sbin/nologin likeable && mkdir -p /data && chown -R likeable:likeable /data
FROM alpine:3.22 AS runtime
RUN apk add --no-cache ca-certificates curl git \
&& adduser -D -u 10001 -h /home/likeable -s /sbin/nologin likeable \
&& mkdir -p /data \
&& chown -R likeable:likeable /data
COPY --from=backend /out/likeable /usr/local/bin/likeable
RUN mkdir -p /usr/local/share/likeable
COPY scripts/go-fibe-greenfield.yaml /usr/local/share/likeable/go-fibe-greenfield.yaml
Expand Down
122 changes: 121 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ After the first admin login, open Admin and configure:
- GitHub OAuth for repository export.
- Stripe prices and webhook secret.
- SMTP delivery.
- Signup mode, free daily messages, and project cap.
- Signup mode, free build minutes/window, playground idle-stop hours, project cap, and paid playground slot duration.

Signup defaults to forbidden until the admin changes it.

Expand Down Expand Up @@ -64,6 +64,126 @@ To run the fully containerized app instead of the live-reload development server
docker compose --profile app up --build
```

Before deploying a test build, run the deploy preflight:

```bash
bin/deploy-preflight
```

It runs the TypeScript check, frontend build, Go tests, production Docker image build, and a container smoke against `/healthz` and the admin billing health API. Override the image tag or smoke port with `LIKEABLE_IMAGE_TAG` and `LIKEABLE_PREFLIGHT_PORT`.

After deploying a live test environment, run the remote readiness smoke:

```bash
LIKEABLE_BASE_URL=https://likeable.example.com \
LIKEABLE_ADMIN_EMAIL=admin@example.com \
bin/live-readiness
```

For an environment without dev auth, pass an existing admin session cookie with `LIKEABLE_SESSION_COOKIE` instead of `LIKEABLE_ADMIN_EMAIL`.

`bin/live-readiness` exits non-zero when readiness or billing has blockers. Set `LIKEABLE_ALLOW_BLOCKERS=1` to print the full diagnostic payload while a test environment is intentionally incomplete.

To apply launch-only live config without printing secret values:

```bash
LIKEABLE_BASE_URL=https://likeable.example.com \
LIKEABLE_ADMIN_EMAIL=admin@example.com \
LIKEABLE_PLAYGROUND_IDLE_STOP_HOURS=8 \
LIKEABLE_GOOGLE_CLIENT_ID=... \
LIKEABLE_GOOGLE_CLIENT_SECRET=... \
LIKEABLE_SMTP_HOST=smtp.example.com \
LIKEABLE_SMTP_FROM_EMAIL=support@example.com \
bin/live-configure
```

Optional SMTP env keys are `LIKEABLE_SMTP_PORT`, `LIKEABLE_SMTP_USERNAME`, `LIKEABLE_SMTP_PASSWORD`, `LIKEABLE_SMTP_FROM_NAME`, and `LIKEABLE_SMTP_TLS_MODE`. Use `LIKEABLE_DRY_RUN=1` to print the config keys that would be applied without changing the live environment.

`bin/live-configure` validates common operator mistakes before it writes config: playground idle-stop hours must be between 1 and 168, Google client ID/secret must be supplied together, SMTP port must be numeric, and SMTP TLS mode must be one of `auto`, `tls`, `starttls`, or `none`.

## Test Deployment Runbook

Use this flow for the Vyakymenko Likeable test environment or any equivalent test VPS.

The automated path is:

```bash
LIKEABLE_DEPLOY_HOST=user@server.example.com \
LIKEABLE_IMAGE_TAG=registry.example.com/likeable:test \
LIKEABLE_BASE_URL=https://likeable.example.com \
LIKEABLE_ADMIN_EMAIL=admin@example.com \
bin/deploy-vps
```

`bin/deploy-vps` runs the local preflight, pushes the image, updates Redis and the app container over SSH, then waits for `/healthz`. Set `LIKEABLE_RUN_PREFLIGHT=0` or `LIKEABLE_PUSH_IMAGE=0` only when the image was already tested or already pushed.

The manual path is:

1. Run the local preflight and tag the exact image you want to deploy:

```bash
LIKEABLE_IMAGE_TAG=registry.example.com/likeable:test bin/deploy-preflight
docker push registry.example.com/likeable:test
```

2. Run Redis and the app container with a persistent database volume:

```bash
docker network create likeable-net || true
docker run -d --name likeable-redis \
--restart unless-stopped \
--network likeable-net \
-v likeable_redis:/data \
redis:7.4-alpine redis-server --appendonly yes --save 60 1

docker run -d --name likeable \
--restart unless-stopped \
--network likeable-net \
-p 8080:8080 \
-v likeable_data:/data \
-e ADDR=:8080 \
-e DATABASE_PATH=/data/likeable.db \
-e BASE_URL=https://likeable.example.com \
-e ADMIN_EMAIL=admin@example.com \
-e REDIS_URL=redis://likeable-redis:6379/0 \
registry.example.com/likeable:test
```

Put TLS and the public hostname in front of port `8080` with your reverse proxy. For a single small test host, the default `LIKEABLE_ROLE=all` is fine. On a busier host, run one `LIKEABLE_ROLE=web` container behind HTTP and one `LIKEABLE_ROLE=worker` container against the same database and Redis.

3. Bootstrap admin access.

For a private smoke only, temporarily set `LIKEABLE_DEV_AUTH=1` and sign in with `ADMIN_EMAIL`. For a public test URL, set `LIKEABLE_BOOTSTRAP_TOKEN` once, POST Google OAuth config to `/api/bootstrap/config`, then remove the token and restart before inviting users.

4. Configure Admin.

Set Fibe, Google OAuth, GitHub export, SMTP, signup mode, free minutes/window, playground idle-stop hours, project cap, paid project-slot duration, and Stripe:

- `stripe_secret_key`
- `stripe_webhook_secret`
- `stripe_price_id_1_hour`, `stripe_price_id_10_hours`, `stripe_price_id_100_hours`
- `stripe_project_quota_price_id`

Stripe webhook URL: `${BASE_URL}/api/stripe/webhook`.

Checkout uses backend-created Stripe Checkout Sessions and does not require `stripe_publishable_key`. It also uses Stripe dynamic payment methods by not sending `payment_method_types`. Enable Link in the Stripe Dashboard payment method settings; the app will not override that Dashboard configuration.

5. Smoke the user flows.

- Admin launch readiness is `ready`, or every remaining failed check is an explicitly accepted rollout warning.
- Admin billing health has no blocking issues.
- A new user can sign in, create a project, and send a first message.
- Sending a message to a stopped project wakes the playground and then sends the prompt.
- Idle playgrounds pause after the configured inactive window; users can start them again when they return.
- Hour-pack checkout grants build minutes.
- Project-slot checkout increases the project cap for the configured number of days.
- Likeable does not sell production hosting or custom domains. For always-on production hosting and domain setup, continue the project in Fibe.
- Admin diagnostics for a user project shows conversation, agent, playground, server, repositories, payments, hour ledger, and work sessions.

### Production Handoff

Likeable is scoped to experiments and development playgrounds. Keep billing in Likeable limited to build-hour packs and extra playground slots. Production hosting, custom domains, always-on runtimes, and customer DNS work belong in Fibe.

## Development With Live Reload

The short development workflow mirrors the main Fibe app:
Expand Down
83 changes: 83 additions & 0 deletions bin/deploy-preflight
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TAG="${LIKEABLE_IMAGE_TAG:-likeable:deploy-preflight}"
PORT="${LIKEABLE_PREFLIGHT_PORT:-18080}"
CONTAINER="likeable-preflight-$PORT"
GO_IMAGE="${LIKEABLE_GO_IMAGE:-golang:1.26.1-bookworm}"
COMMIT_SHA="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo local)"

cleanup() {
docker stop "$CONTAINER" >/dev/null 2>&1 || true
}
trap cleanup EXIT

cd "$ROOT"

echo "==> Operational script checks"
for script in bin/*; do
if [ -f "$script" ]; then
bash -n "$script"
fi
done
LIKEABLE_BASE_URL=http://localhost \
LIKEABLE_DRY_RUN=1 \
LIKEABLE_PLAYGROUND_IDLE_STOP_HOURS=8 \
LIKEABLE_GOOGLE_CLIENT_ID=google-client-preflight \
LIKEABLE_GOOGLE_CLIENT_SECRET=google-secret-preflight \
LIKEABLE_SMTP_HOST=smtp.example.test \
LIKEABLE_SMTP_PORT=587 \
LIKEABLE_SMTP_TLS_MODE=starttls \
LIKEABLE_SMTP_FROM_EMAIL=support@example.test \
bin/live-configure >/dev/null

echo "==> TypeScript check"
npm run lint

echo "==> Frontend build"
npm run build

echo "==> Go tests"
docker run --rm \
-v "$ROOT":/src \
-v /tmp/likeable-go-mod:/go/pkg/mod \
-v /tmp/likeable-go-build:/root/.cache/go-build \
-w /src \
"$GO_IMAGE" go test ./...

echo "==> Docker image build: $TAG"
docker build \
--build-arg "FIBE_BUILD_GIT_COMMIT_SHA=$COMMIT_SHA" \
-t "$TAG" \
.

cleanup

echo "==> Container smoke on http://localhost:$PORT"
docker run -d --rm \
--name "$CONTAINER" \
-p "$PORT:8080" \
-e ADDR=:8080 \
-e DATABASE_PATH=/data/likeable.db \
-e BASE_URL="http://localhost:$PORT" \
-e ADMIN_EMAIL=admin@example.com \
-e LIKEABLE_DEV_AUTH=1 \
"$TAG" >/dev/null

for _ in $(seq 1 40); do
if curl -fsS "http://localhost:$PORT/healthz" >/dev/null 2>&1; then
break
fi
sleep 0.5
done

curl -fsS "http://localhost:$PORT/healthz" >/dev/null
cookie_jar="$(mktemp)"
curl -fsS -c "$cookie_jar" "http://localhost:$PORT/api/dev/login?email=admin@example.com" >/dev/null
curl -fsS -b "$cookie_jar" "http://localhost:$PORT/admin" | grep -q '<div id="root">'
curl -fsS -b "$cookie_jar" "http://localhost:$PORT/api/admin/billing/health" >/dev/null
rm -f "$cookie_jar"

image_id="$(docker image inspect "$TAG" --format '{{.Id}}')"
echo "==> OK: $TAG ($image_id)"
Loading
Loading