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
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ node_modules
*.md
.env
.env.*
deployment/.env
deployment/compose/.env
deployment/**/.env
dist
.astro
__pycache__
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ htmlcov/
.env
.env.*
!.env.example
deployment/.env
deployment/compose/.env
deployment/**/.env

# Logs
*.log
Expand Down
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Ordinary public browsing does **not** require the Backend at runtime. Administra
- **Frontend / Administration FE**: Astro 5, React 19 (Administration FE only), TypeScript
- **Backend**: FastAPI, Python 3.12, PostgreSQL 16
- **Object storage**: S3-compatible (MinIO locally)
- **Deployment**: Docker Compose in `deployment/`
- **Deployment**: Docker Compose + k3s under `deployment/`

## Repository layout

Expand All @@ -25,7 +25,9 @@ apps/
├── Frontend/
├── Administration-FE/
└── Backend/
deployment/ # Docker Compose, environment config, gateway
deployment/
├── compose/ # Docker Compose, .env, Caddy gateway
└── k8s/ # Kustomize manifests for Flycatch k3s
specs/001-website-foundation/ # Feature spec, plan, contracts, quickstart
specs/002-auth-rbac/ # JWT auth + RBAC spec, plan, contracts, quickstart
docs/ # Conventions and onboarding (implementation phase)
Expand Down Expand Up @@ -57,25 +59,25 @@ Compose does **not** create staff accounts or apply migrations. There is no defa
1. Copy environment config and set secrets (`JWT_SECRET` and the other `change-me` values):

```bash
cp deployment/.env.example deployment/.env
cp deployment/compose/.env.example deployment/compose/.env
```

Do not commit `deployment/.env`. Variable names are documented in `deployment/.env.example`.
Do not commit `deployment/compose/.env`. Variable names are documented in `deployment/compose/.env.example`.

2. Build and start all services from the repository root:

```bash
docker compose -f deployment/docker-compose.yml up -d --build
docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env up -d --build
```

From `deployment/` you can use `docker compose up -d --build` instead.
From `deployment/compose/` you can use `docker compose up -d --build` instead.

3. After Postgres and MinIO are healthy, migrate, seed, and bootstrap two staff users:

```bash
docker compose -f deployment/docker-compose.yml exec backend alembic upgrade head
docker compose -f deployment/docker-compose.yml exec backend flycatch-seed-records
docker compose -f deployment/docker-compose.yml exec backend flycatch-bootstrap \
docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env exec backend alembic upgrade head
docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env exec backend flycatch-seed-records
docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env exec backend flycatch-bootstrap \
--user-1-email admin1@example.com \
--user-2-email admin2@example.com \
--user-2-role editor
Expand All @@ -90,10 +92,10 @@ Compose does **not** create staff accounts or apply migrations. There is no defa
5. Rebuild app images after Frontend or Administration FE changes:

```bash
docker compose -f deployment/docker-compose.yml up -d --build
docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env up -d --build
```

Stop the stack with `docker compose -f deployment/docker-compose.yml down`. Add `-v` only if you intend to wipe Postgres and MinIO volumes.
Stop the stack with `docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env down`. Add `-v` only if you intend to wipe Postgres and MinIO volumes.

Gateway (default `http://localhost:8080`, `GATEWAY_PORT` in `.env`):

Expand All @@ -107,9 +109,10 @@ Gateway (default `http://localhost:8080`, `GATEWAY_PORT` in `.env`):

| File | Purpose |
| --- | --- |
| `deployment/docker-compose.yml` | All foundation services |
| `deployment/.env.example` | Shared environment configuration |
| `deployment/Caddyfile` | Path-based gateway routing |
| `deployment/compose/docker-compose.yml` | All foundation services |
| `deployment/compose/.env.example` | Shared environment configuration |
| `deployment/k8s/base/Caddyfile` | Path-based gateway routing (compose + k8s) |
| `deployment/k8s/` | Kubernetes manifests for the Flycatch k3s cluster |

## Local Development (Running Individually)

Expand Down
11 changes: 6 additions & 5 deletions apps/Administration-FE/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ RUN npm ci
COPY . .
COPY --from=specs . /specs
ENV CONTRACTS_DIR=/specs/002-auth-rbac/contracts
ENV PUBLIC_ORIGIN=http://localhost:8080
ARG PUBLIC_ORIGIN=http://localhost:8080
ARG PUBLIC_ENVIRONMENT=development
ENV PUBLIC_ORIGIN=$PUBLIC_ORIGIN
ENV PUBLIC_ENVIRONMENT=$PUBLIC_ENVIRONMENT
RUN npm run generate:client && npm run build

FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
RUN npm install -g serve@14
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./
EXPOSE 4173
CMD ["npm", "run", "preview"]
CMD ["serve", "dist", "-l", "tcp://0.0.0.0:4173", "--no-clipboard", "--no-port-switching"]
3 changes: 3 additions & 0 deletions apps/Administration-FE/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export default defineConfig({
trailingSlash: 'always',
integrations: [react()],
vite: {
preview: {
allowedHosts: true,
},
server: {
proxy: {
'/api': {
Expand Down
2 changes: 1 addition & 1 deletion apps/Administration-FE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"dev": "astro dev --host --port 4173",
"build": "astro build",
"preview": "astro preview --host 0.0.0.0 --port 4173",
"preview": "astro preview --host 0.0.0.0 --port 4173 --allowed-hosts",
"check": "astro check",
"generate:client": "node scripts/generate-client.mjs",
"check:contracts": "node scripts/check-contract-drift.mjs",
Expand Down
6 changes: 5 additions & 1 deletion apps/Backend/src/flycatch_api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fastapi.responses import JSONResponse
from sqlalchemy.exc import SQLAlchemyError

from flycatch_api.config import settings
from flycatch_api.api import (
admin_ai_services,
admin_auth,
Expand Down Expand Up @@ -74,7 +75,10 @@ async def security_headers(request: Request, call_next):
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
if request.url.path.startswith("/admin") or request.url.path.startswith("/api/v1/admin"):
is_admin_path = request.url.path.startswith("/admin") or request.url.path.startswith(
"/api/v1/admin"
)
if settings.environment != "production" or is_admin_path:
response.headers["X-Robots-Tag"] = "noindex, nofollow"
return response

Expand Down
10 changes: 6 additions & 4 deletions apps/Frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
ARG PUBLIC_ORIGIN=http://localhost:8080
ARG PUBLIC_ENVIRONMENT=development
ENV PUBLIC_ORIGIN=$PUBLIC_ORIGIN
ENV PUBLIC_ENVIRONMENT=$PUBLIC_ENVIRONMENT
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
RUN npm install -g serve@14
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./
EXPOSE 4321
CMD ["npm", "run", "preview"]
CMD ["serve", "dist", "-l", "tcp://0.0.0.0:4321", "--no-clipboard", "--no-port-switching"]
3 changes: 3 additions & 0 deletions apps/Frontend/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export default defineConfig({
inlineStylesheets: 'always',
},
vite: {
preview: {
allowedHosts: true,
},
build: {
rollupOptions: {
output: {
Expand Down
2 changes: 1 addition & 1 deletion apps/Frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"dev": "astro dev --host",
"build": "node scripts/ensure-published-snapshot.mjs && astro build",
"preview": "astro preview --host 0.0.0.0 --port 4321",
"preview": "astro preview --host 0.0.0.0 --port 4321 --allowed-hosts",
"check": "node scripts/ensure-published-snapshot.mjs && astro check",
"generate:types": "node scripts/generate-types.mjs",
"check:contracts": "node scripts/check-contract-drift.mjs",
Expand Down
7 changes: 0 additions & 7 deletions apps/Frontend/public/robots.txt

This file was deleted.

4 changes: 3 additions & 1 deletion apps/Frontend/src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ interface Props {
}

const { title, metadata, structuredData = [], lang = 'en', dir = 'ltr' } = Astro.props;
const isProduction = (import.meta.env.PUBLIC_ENVIRONMENT || 'development') === 'production';
const shouldNoindex = !metadata.indexable || !isProduction;
---
<!doctype html>
<html lang={lang} dir={dir}>
Expand All @@ -22,7 +24,7 @@ const { title, metadata, structuredData = [], lang = 'en', dir = 'ltr' } = Astro
<title>{title}</title>
<meta name="description" content={metadata.description} />
<link rel="canonical" href={metadata.canonical} />
{!metadata.indexable && <meta name="robots" content="noindex, nofollow" />}
{shouldNoindex && <meta name="robots" content="noindex, nofollow" />}
<meta property="og:title" content={metadata.socialTitle} />
<meta property="og:description" content={metadata.socialDescription} />
<meta property="og:url" content={metadata.canonical} />
Expand Down
24 changes: 24 additions & 0 deletions apps/Frontend/src/pages/robots.txt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { APIRoute } from 'astro';

const isProduction = (import.meta.env.PUBLIC_ENVIRONMENT || 'development') === 'production';

export const GET: APIRoute = () => {
const body = isProduction
? [
'User-agent: *',
'Allow: /',
'',
'Disallow: /admin',
'Disallow: /api',
'',
'Sitemap: /sitemap-index.xml',
'',
].join('\n')
: ['User-agent: *', 'Disallow: /', ''].join('\n');

return new Response(body, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
};
27 changes: 0 additions & 27 deletions deployment/Caddyfile

This file was deleted.

42 changes: 7 additions & 35 deletions deployment/README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,13 @@
# Deployment

Deployment-specific files for Docker Compose and environment configuration.
How this app runs locally and on the cluster. Everything lives under this folder.

**Project overview, stack, and full setup instructions:** see [README.md](../README.md) at the repository root.

## Files in this directory

| File | Purpose |
| Path | Purpose |
| --- | --- |
| `docker-compose.yml` | Frontend, Administration FE, Backend, PostgreSQL, MinIO, gateway |
| `.env.example` | Shared environment variables — copy to `.env` |
| `Caddyfile` | Gateway routing: `/`, `/admin`, `/api` |

## Quick start

From this directory:

```bash
cp .env.example .env
# Set JWT_SECRET and other change-me values

docker compose up -d --build
```

From the repository root: `docker compose -f deployment/docker-compose.yml up -d --build`.

Compose does not provision staff. After services are healthy:

```bash
docker compose exec backend alembic upgrade head
docker compose exec backend flycatch-seed-records
docker compose exec backend flycatch-bootstrap \
--user-1-email admin1@example.com \
--user-2-email admin2@example.com \
--user-2-role editor
```
| [compose/](compose/) | Docker Compose stack (local / preview): Postgres, MinIO, apps, Caddy gateway |
| [k8s/](k8s/) | Kubernetes manifests (Kustomize) for the Flycatch k3s cluster |

There is no default password. Bootstrap prompts for two passwords (min 12 characters). Sign in at `http://localhost:8080/admin`. Full startup notes: [README.md](../README.md#quick-start-docker-compose) and [docs/onboarding.md](../docs/onboarding.md).
Shared gateway routing for both compose and k8s: [k8s/base/Caddyfile](k8s/base/Caddyfile)
(Compose mounts this file; k8s loads it via ConfigMap).

Validation scenarios: [quickstart.md](../specs/001-website-foundation/quickstart.md).
**Project overview and day-to-day setup:** [README.md](../README.md).
5 changes: 5 additions & 0 deletions deployment/.env.example → deployment/compose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
PUBLIC_ORIGIN=http://localhost:8080
GATEWAY_PORT=8080

# Environment: development | production
# Non-production disables SEO indexing (robots.txt Disallow, noindex meta, X-Robots-Tag).
PUBLIC_ENVIRONMENT=development
ENVIRONMENT=development

# PostgreSQL
POSTGRES_DB=flycatch
POSTGRES_USER=flycatch
Expand Down
49 changes: 49 additions & 0 deletions deployment/compose/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Docker Compose

Local and preview stack: Frontend, Administration FE, Backend, PostgreSQL, MinIO, and the Caddy gateway.

**Project overview:** see [README.md](../../README.md) at the repository root.
**k3s / cluster deploy:** see [../k8s/README.md](../k8s/README.md).

## Files in this directory

| File | Purpose |
| --- | --- |
| `docker-compose.yml` | Frontend, Administration FE, Backend, PostgreSQL, MinIO, gateway |
| `.env.example` | Shared environment variables — copy to `.env` |

Gateway routing is shared with k8s: [../k8s/base/Caddyfile](../k8s/base/Caddyfile).

## Quick start

From this directory:

```bash
cp .env.example .env
# Set JWT_SECRET and other change-me values

docker compose up -d --build
```

From the repository root:

```bash
docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env up -d --build
```

Compose does not provision staff. After services are healthy:

```bash
docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env exec backend alembic upgrade head
docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env exec backend flycatch-seed-records
docker compose -f deployment/compose/docker-compose.yml --env-file deployment/compose/.env exec backend flycatch-bootstrap \
--user-1-email admin1@example.com \
--user-2-email admin2@example.com \
--user-2-role editor
```

There is no default password. Bootstrap prompts for two passwords (min 12 characters). Sign in at `http://localhost:8080/admin`. Full startup notes: [README.md](../../README.md#quick-start-docker-compose) and [docs/onboarding.md](../../docs/onboarding.md).

`PUBLIC_ENVIRONMENT` / `ENVIRONMENT` default to `development` so pages are not SEO-indexed.

Validation scenarios: [quickstart.md](../../specs/001-website-foundation/quickstart.md).
Loading
Loading