# Robot U Site Prototype Thin frontend layer over Forgejo for community learning content, discussions, and events. ## Stack - FastAPI backend - Preact + TypeScript frontend built with Vite - `bun` for frontend tooling - `uv` + `ruff` for Python checks ## Local Development ### Backend ```bash python3 -m venv .venv .venv/bin/pip install -r requirements.txt .venv/bin/python -m uvicorn app:app --reload ``` ### Start App Build the frontend and then serve the app from FastAPI: ```bash ./scripts/start.sh ``` The script automatically loads `.env` and `.env.local` if present. Optional runtime overrides: ```bash HOST=0.0.0.0 PORT=8800 ./scripts/start.sh ``` Optional live Forgejo configuration: ```bash export APP_BASE_URL="http://kacper-dev-pod:8800" export AUTH_SECRET_KEY="$(openssl rand -hex 32)" export AUTH_COOKIE_SECURE="false" export CORS_ALLOW_ORIGINS="http://kacper-dev-pod:8800" export FORGEJO_BASE_URL="https://aksal.cloud" export FORGEJO_OAUTH_CLIENT_ID="your-forgejo-oauth-client-id" export FORGEJO_OAUTH_CLIENT_SECRET="your-forgejo-oauth-client-secret" export FORGEJO_OAUTH_SCOPES="openid profile" export FORGEJO_GENERAL_DISCUSSION_REPO="Robot-U/general_forum" export FORGEJO_WEBHOOK_SECRET="shared-webhook-secret" export FORGEJO_CACHE_TTL_SECONDS="60.0" export CALENDAR_FEED_URLS="webcal://example.com/calendar.ics,https://example.com/other.ics" ``` `APP_BASE_URL` must match the URL you use in the browser. Create the OAuth app in Forgejo with this redirect URI: ```text http://kacper-dev-pod:8800/api/auth/forgejo/callback ``` `AUTH_SECRET_KEY` is required for Forgejo OAuth sign-in. It encrypts the `HttpOnly` browser session cookie that carries the signed-in user's Forgejo token and identity. Set `AUTH_COOKIE_SECURE=true` when serving over HTTPS. `CORS_ALLOW_ORIGINS` defaults to `APP_BASE_URL` when that value is set. Use a comma-separated list if the API must be called from additional browser origins. `FORGEJO_TOKEN` is optional. When set, it is a read fallback for the public content cache. Browser OAuth requests only identity scopes, then the backend uses the signed-in user's Forgejo identity from the encrypted session cookie for public discussion creation and replies. The backend must verify repositories are public before reading discussion data or writing issues/comments. `FORGEJO_CACHE_TTL_SECONDS` controls how long the server reuses the public Forgejo content scan for `/api/prototype`. Set it to `0` to disable caching while debugging discovery behavior. `FORGEJO_GENERAL_DISCUSSION_REPO` should point at the public `owner/repo` used for general discussion threads. Post- and lesson-linked discussions are created in the same repo as the content being discussed. `FORGEJO_WEBHOOK_SECRET` is optional but recommended. Configure Forgejo webhooks to POST to `/api/forgejo/webhook`; matching webhook events invalidate the public content cache and notify open browsers over `/api/events/stream`. Or put those values in `.env`: ```bash cp .env.example .env ``` Use the site `Sign in` button for Forgejo OAuth, or query the API directly with: ```bash curl -H "Authorization: token your-forgejo-api-token" http://127.0.0.1:8800/api/prototype ``` ### Frontend ```bash cd frontend ~/.bun/bin/bun install ~/.bun/bin/bun run dev ``` ### Quality Checks ```bash ./scripts/check_python_quality.sh ./scripts/check_frontend_quality.sh ``` ## Deployment ### Forgejo Actions SSH Clone Bootstrap If the Forgejo instance only supports SSH clone in Actions, create a repo deploy key and matching Actions secret: ```bash export FORGEJO_API_TOKEN=your-forgejo-api-token ./scripts/bootstrap_ci_clone_key.py ``` Defaults: ```text FORGEJO_BASE_URL=https://aksal.cloud FORGEJO_REPO=Robot-U/robot-u-site CI_CLONE_KEY_TITLE=robot-u-site-actions-clone CI_CLONE_SECRET_NAME=CI_REPO_SSH_KEY ``` The script generates a temporary Ed25519 keypair, adds the public key as a read-only deploy key on the repo, and stores the private key in the repo Actions secret `CI_REPO_SSH_KEY`. ### Forgejo Actions LXC Deploy Bootstrap The `main` branch CI workflow deploys to the current Robot U LXC after checks pass. Create or rotate the deploy SSH key with: ```bash export FORGEJO_API_TOKEN=your-forgejo-api-token ./scripts/bootstrap_lxc_deploy_key.py ``` Defaults: ```text FORGEJO_BASE_URL=https://aksal.cloud FORGEJO_REPO=Robot-U/robot-u-site LXC_DEPLOY_HOST=192.168.1.220 LXC_DEPLOY_PORT=22 LXC_DEPLOY_USER=root LXC_DEPLOY_KEY_COMMENT=robot-u-site-actions-deploy LXC_DEPLOY_SECRET_NAME=DEPLOY_SSH_KEY ``` The script generates a temporary Ed25519 keypair, appends the public key to the LXC user's `authorized_keys`, verifies SSH login with the generated key, and stores the private key in the repo Actions secret `DEPLOY_SSH_KEY`. The deploy job only runs for pushes to `main`. It rsyncs the repository into `/opt/robot-u-site`, preserves production `.env` files, runs `./scripts/check_deploy_config.py`, rebuilds Docker Compose, and verifies `http://127.0.0.1:8800/health`. ### Required Production Settings Create a production `.env` from `.env.example` and set at least: ```bash APP_BASE_URL=https://your-site.example AUTH_SECRET_KEY=$(openssl rand -hex 32) AUTH_COOKIE_SECURE=true CORS_ALLOW_ORIGINS=https://your-site.example FORGEJO_BASE_URL=https://aksal.cloud FORGEJO_OAUTH_CLIENT_ID=... FORGEJO_OAUTH_CLIENT_SECRET=... FORGEJO_GENERAL_DISCUSSION_REPO=Robot-U/general_forum FORGEJO_WEBHOOK_SECRET=$(openssl rand -hex 32) ``` Then validate the deployment environment: ```bash ./scripts/check_deploy_config.py ``` The Forgejo OAuth app must include this redirect URI: ```text https://your-site.example/api/auth/forgejo/callback ``` ### Docker Compose ```bash cp .env.example .env $EDITOR .env ./scripts/check_deploy_config.py docker compose up --build -d curl -fsS http://127.0.0.1:8800/health ``` The compose file exposes the app on host port `8800` and runs Uvicorn on container port `8000`. ### Non-Container Deployment ```bash python3 -m venv .venv .venv/bin/pip install -r requirements.txt cd frontend ~/.bun/bin/bun install ~/.bun/bin/bun run build cd .. HOST=0.0.0.0 PORT=8000 ./scripts/run_prod.sh ``` Put a reverse proxy in front of the app for TLS. Preserve long-lived connections for `/api/events/stream`, and configure Forgejo webhooks to POST to: ```text https://your-site.example/api/forgejo/webhook ```