chore(*): adopt project templates

This commit is contained in:
hh
2026-09-09 01:17:12 +02:00
parent 5ec0212af5
commit ccbc167005
33 changed files with 409 additions and 220 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
{
"hooks": [
{
"command": "bun fix --skip=correctness/noUnusedImports",
"command": "cd frontend && bun run fix --skip=correctness/noUnusedImports",
"type": "command"
}
],
+17
View File
@@ -0,0 +1,17 @@
_commit: v0.3.2
_src_path: https://git.kotikot.com/templates/backend-python.git
author_email: h@kotikot.com
author_name: h
backend_services:
- api
- kurigram_userbot
database: postgres
dynamic_config: false
install_deps: false
layout: part
project_description: A read-only Telegram client that keeps everything
project_name: beavergram
project_slug: beavergram
serve_spa: true
use_llm: false
use_redis: false
+5
View File
@@ -0,0 +1,5 @@
_commit: v0.1.2
_src_path: https://git.kotikot.com/templates/caddy.git
project_slug: beavergram
routes:
- api
+9
View File
@@ -0,0 +1,9 @@
_commit: v0.3.1
_src_path: https://git.kotikot.com/templates/frontend-svelte.git
include_impeccable: false
install_deps: false
layout: part
project_name: beavergram
project_slug: beavergram
serve: api
ui_library: none
+9
View File
@@ -0,0 +1,9 @@
_commit: v0.3.1
_src_path: https://git.kotikot.com/templates/infra.git
init_git: false
layout: parts
parts:
- backend
- frontend
project_description: A read-only Telegram client that keeps everything
project_name: beavergram
+2 -16
View File
@@ -1,16 +1,2 @@
COMPOSE_PROFILES=db,userbot,api
RUN_ENVIRONMENT=prod
# Leave empty for *.localhost.
FRONTEND_DEV_HOST=
DB__HOST=postgres
DB__PORT=5432
DB__USER=beavergram
DB__PASSWORD=beavergram
DB__DB_NAME=beavergram
DB__MIN_POOL_SIZE=5
DB__MAX_POOL_SIZE=20
DB__SCRIPTS_CONNECTION_URL=postgresql://beavergram:beavergram@localhost:5433/beavergram
STORAGE__ROOT=./storage
COMPOSE_FILE=docker-compose.yml:backend/docker-compose.override.yml:frontend/docker-compose.override.yml
COMPOSE_PROFILES=external,services
+5 -3
View File
@@ -1,6 +1,8 @@
.idea
docker-compose.override.yml
.env
.env.*
!.env.example
docker-compose.override.yml
.DS_Store
.idea
docs
storage
backend/sessions
+18 -22
View File
@@ -1,31 +1,27 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.15
hooks:
- id: ruff-check
types_or: [ python, pyi ]
args: [ --fix ]
- id: ruff-format
types_or: [ python, pyi ]
- repo: local
hooks:
- id: ultracite
name: ultracite
entry: bash -c 'cd frontend && bun x ultracite fix'
- id: backend-fmt
name: backend fmt
entry: make -C backend fmt
language: system
files: ^backend/
pass_filenames: false
- id: backend-check
name: backend check
entry: make -C backend check
language: system
files: ^backend/
pass_filenames: false
- id: frontend-fmt
name: frontend fmt
entry: make -C frontend fmt
language: system
files: ^frontend/
pass_filenames: false
- id: svelte-check
name: svelte check
entry: bash -c 'cd frontend && bun check'
- id: frontend-check
name: frontend check
entry: make -C frontend check
language: system
files: ^frontend/
pass_filenames: false
- id: ty
name: ty check
entry: uvx ty check
language: python
types_or: [ python, pyi ]
+3 -2
View File
@@ -6,10 +6,11 @@
- **Reusability**: Create reusable components and functions
- **Less Code = Less Debt**: Minimize code footprint
- **NEVER write comments** - code should be self-documenting
- **Docstrings ONLY if explicitly asked**
## Checking commands
After writing code, always run:
```shell
bun check # svelte-check
bun fix # biome linter
make fmt
make check
```
+20 -21
View File
@@ -1,30 +1,29 @@
.PHONY: recreate down restart rebuild deploy migrate session-create frontend
PARTS := $(patsubst %/Makefile,%,$(wildcard */Makefile))
.PHONY: env recreate rebuild restart logs down deploy fmt check build pre-deploy post-deploy
env:
@test -f .env || awk 'FNR == 1 && NR > 1 { print "" } 1' .env.example $(addsuffix /.env.example,$(PARTS)) > .env
@for part in $(PARTS); do test -f $$part/docker-compose.override.yml || cp $$part/docker-compose.override.yml.example $$part/docker-compose.override.yml; done
recreate:
docker compose up -d --force-recreate
rebuild: build
docker compose up -d
restart:
docker compose restart
logs:
docker compose logs -f --tail=100
down:
docker compose down
restart:
$(MAKE) recreate
rebuild:
docker compose build
deploy: pre-deploy
docker compose up -d
$(MAKE) post-deploy
frontend:
docker compose run --rm --no-deps frontend-dev sh -c "bun install && bun run build"
deploy:
$(MAKE) frontend
$(MAKE) rebuild
migrate:
docker compose --profile db --profile migrate run --rm migrator $(filter-out $@,$(MAKECMDGOALS))
session-create:
cd backend && uv run python scripts/session/create.py
%:
@:
fmt check build pre-deploy post-deploy:
@for part in $(PARTS); do $(MAKE) -C $$part $@ || exit 1; done
+8 -9
View File
@@ -19,7 +19,7 @@ Nothing is ever deleted from the archive.
- `backend/` - `userbot` (kurigram, the capture loop and jobs) and `api` (FastAPI: the web API, the MCP server, and the built frontend as static files).
- `frontend/` - SvelteKit, `adapter-static`; a chat client over the archive: folders, chat list, thread with media, versions and the deleted messages inline, search, stories, presence graphs.
- `docker-compose.yml` - Postgres with TimescaleDB, `userbot`, `api`; profiles `db,userbot,api`.
- `docker-compose.yml` - includes `backend/` (Postgres with TimescaleDB, `userbot`, `api`) and `frontend/` (the dev server); profiles `external` and `services`.
## MCP
@@ -28,18 +28,17 @@ Read-only: `search_messages_tool`, `list_chats`, `get_chat_history` (with delete
## Run
```sh
cp .env.example .env # DB__*, STORAGE__ROOT, profiles
cp backend/.env.example backend/.env # TG__*, API__*, AUTH__TOKEN
make session-create # log the account in once; the session file stays in backend/sessions
make migrate upgrade head
make deploy # build the frontend, build and start the containers
make env # .env and docker-compose.override.yml from the examples
make -C backend session # log the account in once; the session file stays in backend/sessions
make deploy # build what changed, migrate, start the containers
```
The web client and the MCP endpoint are served by `api`; the bearer for both is `AUTH__TOKEN`.
`COMPOSE_PROFILES` in `.env` selects what runs on the machine, `docker-compose.override.yml` in each part holds the published ports and the `caddy` network aliases. The web client and the MCP endpoint are served by `api`; the bearer for both is `AUTH__TOKEN`.
## Develop
```sh
cd backend && uv sync && uv run ruff check && uvx ty check src
cd frontend && bun install && bun run dev && bun run check
make fmt
make check
make -C frontend dev
```
+7
View File
@@ -0,0 +1,7 @@
.venv
.env
__pycache__
*.pyc
.ruff_cache
.pytest_cache
sessions
+19 -6
View File
@@ -1,14 +1,27 @@
RUN_ENVIRONMENT=prod
TZ=UTC
LOG__LEVEL=INFO
LOG__LEVEL_EXTERNAL=WARNING
LOG__SHOW_TIME=false
LOG__CONSOLE_WIDTH=150
DB__HOST=postgres
DB__PORT=5432
DB__USER=beavergram
DB__PASSWORD=beavergram
DB__DB_NAME=beavergram
DB__MIN_POOL_SIZE=5
DB__MAX_POOL_SIZE=20
DB__SCRIPTS_CONNECTION_URL=postgresql://beavergram:beavergram@localhost:5432/beavergram
TG__SESSION_NAME=beavergram
TG__SESSIONS_DIR=sessions
API__HOST=0.0.0.0
API__PORT=8080
AUTH__TOKEN=<AUTH__TOKEN>
AUTH__TOKEN=
STORAGE__ROOT=./storage
STORAGE__SHARD_DEPTH=2
LOG__LEVEL=INFO
LOG__LEVEL_EXTERNAL=WARNING
LOG__SHOW_TIME=false
LOG__CONSOLE_WIDTH=150
+7
View File
@@ -0,0 +1,7 @@
## Checking commands
After writing code, always run from `backend/`:
```shell
ruff format
ruff check --fix
ty check
```
+5 -4
View File
@@ -1,5 +1,7 @@
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*
WORKDIR /app
ENV PATH="/app/.venv/bin:$PATH" \
@@ -7,10 +9,6 @@ ENV PATH="/app/.venv/bin:$PATH" \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential \
&& rm -rf /var/lib/apt/lists/*
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-install-project --no-dev
@@ -20,4 +18,7 @@ RUN uv sync --frozen --no-dev
COPY alembic.ini ./
COPY migrations ./migrations
COPY scripts ./scripts
ENTRYPOINT ["python", "-m"]
CMD ["api"]
+47
View File
@@ -0,0 +1,47 @@
COMPOSE := cd .. && docker compose
IMAGE := beavergram/backend
IMAGE_INPUTS := pyproject.toml uv.lock Dockerfile
CODE := src scripts ../frontend
SERVICES := api userbot
changed = ! git diff --quiet $(1) -- $(2) 2>/dev/null
stamp = { git update-ref $(1) HEAD 2>/dev/null || true; }
.PHONY: fmt check build pre-deploy post-deploy migrate revision session
fmt:
uv run ruff format
uv run ruff check --fix
check:
uv run ruff format --check
uv run ruff check
uv run ty check
build:
$(COMPOSE) build api
pre-deploy:
@if $(call changed,refs/deploy/backend,$(IMAGE_INPUTS)) || ! docker image inspect $(IMAGE) >/dev/null 2>&1; then \
$(MAKE) build && $(call stamp,refs/deploy/backend) && $(call stamp,refs/deploy/backend-code); \
fi
@$(COMPOSE) config --services | grep -qx postgres && $(COMPOSE) up -d postgres || true
$(MAKE) migrate
post-deploy:
@if $(call changed,refs/deploy/backend-code,$(CODE)); then \
running=$$($(COMPOSE) ps --services --status running | grep -x $(addprefix -e ,$(SERVICES))); \
[ -z "$$running" ] || $(COMPOSE) restart $$running; \
$(call stamp,refs/deploy/backend-code); \
fi
migrate:
$(COMPOSE) run --rm migrator $(or $(filter-out $@,$(MAKECMDGOALS)),upgrade head)
revision:
$(COMPOSE) run --rm migrator revision --autogenerate -m "$(m)"
%:
@:
session:
uv run python scripts/session/create.py
@@ -0,0 +1,15 @@
services:
api:
networks:
default: {}
caddy:
aliases:
- beavergram-api
postgres:
ports:
- "127.0.0.1:${DB__PORT:-5432}:5432"
networks:
caddy:
external: true
+107
View File
@@ -0,0 +1,107 @@
services:
api:
build: backend
image: beavergram/backend
profiles: [api, services]
restart: unless-stopped
env_file:
- path: .env
required: false
- path: backend/.env
required: false
environment:
RUN_ENVIRONMENT: prod
TZ: ${TZ:-UTC}
STORAGE__ROOT: /app/storage
volumes:
- ./backend/src:/app/src
- ./backend/scripts:/app/scripts
- ./backend/sessions:/app/sessions
- ./frontend/build:/app/static:ro
- ${STORAGE__ROOT:-./storage}:/app/storage
command: [api]
depends_on:
postgres:
condition: service_healthy
required: false
networks:
default:
database:
userbot:
build: backend
image: beavergram/backend
profiles: [userbot, services]
restart: unless-stopped
env_file:
- path: .env
required: false
- path: backend/.env
required: false
environment:
RUN_ENVIRONMENT: prod
TZ: ${TZ:-UTC}
STORAGE__ROOT: /app/storage
volumes:
- ./backend/src:/app/src
- ./backend/scripts:/app/scripts
- ./backend/sessions:/app/sessions
- ${STORAGE__ROOT:-./storage}:/app/storage
command: [userbot]
depends_on:
postgres:
condition: service_healthy
required: false
networks:
database:
postgres:
image: timescale/timescaledb:2.27.1-pg17
profiles: [postgres, external]
restart: unless-stopped
environment:
POSTGRES_USER: ${DB__USER:-beavergram}
POSTGRES_PASSWORD: ${DB__PASSWORD:-beavergram}
POSTGRES_DB: ${DB__DB_NAME:-beavergram}
volumes:
- pgdata:/var/lib/postgresql/data
- ./backend/migrations/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB__USER:-beavergram}"]
interval: 5s
timeout: 5s
retries: 10
networks:
database:
aliases:
- ${DB__HOST:-postgres}
migrator:
build: backend
image: beavergram/backend
profiles: [migrate]
env_file:
- path: .env
required: false
- path: backend/.env
required: false
environment:
RUN_ENVIRONMENT: prod
volumes:
- ./backend/src:/app/src
- ./backend/migrations:/app/migrations
- ./backend/alembic.ini:/app/alembic.ini
entrypoint: [alembic]
command: [upgrade, head]
depends_on:
postgres:
condition: service_healthy
required: false
networks:
database:
volumes:
pgdata:
networks:
database:
-1
View File
@@ -1,4 +1,3 @@
BEAVERGRAM_DOMAIN=beavergram.localhost
BEAVERGRAM_DEV_DOMAIN=dev.beavergram.localhost
CLOUDFLARE_API_TOKEN=
+1 -1
View File
@@ -1,2 +1,2 @@
Caddyfile
.env
Caddyfile
+2 -7
View File
@@ -1,6 +1,5 @@
{
admin off
# acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
admin localhost:2019
log {
format console
@@ -12,8 +11,4 @@
}
}
(compress) {
encode zstd gzip
}
import /etc/caddy/projects.d/*.caddy
import /etc/caddy/projects.d/*/site.caddy
+22
View File
@@ -0,0 +1,22 @@
# caddy
`site.caddy` routes `{$BEAVERGRAM_DOMAIN}` to the project's services over the external `caddy` docker network. It works standalone and inside a shared Caddy that fronts several projects.
Standalone:
```sh
docker network create caddy
cp Caddyfile.example Caddyfile
cp .env.example .env
docker compose up -d
```
Shared Caddy: mount this directory as `/etc/caddy/projects.d/beavergram:ro`, keep `import /etc/caddy/projects.d/*/site.caddy` in its Caddyfile and put `BEAVERGRAM_DOMAIN` in its environment.
Rules that keep `site.caddy` portable:
- the domain comes only from the environment variable
- no `tls` block, certificates are configured by the host Caddy (`acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}` in the global block)
- snippets are prefixed with the project slug because Caddy snippets are global
- mount the directory, not the file: a single-file bind mount goes stale when git replaces the inode
- keep `admin localhost:2019`, `admin off` makes `caddy reload` impossible
+1 -1
View File
@@ -12,7 +12,7 @@ services:
- .env
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./site.caddy:/etc/caddy/projects.d/beavergram.caddy:ro
- .:/etc/caddy/projects.d/beavergram:ro
- caddy_data:/data
networks:
+1 -1
View File
@@ -5,5 +5,5 @@
{$BEAVERGRAM_DEV_DOMAIN} {
encode zstd gzip
reverse_proxy beavergram-frontend:5173
reverse_proxy beavergram-frontend:3000
}
-26
View File
@@ -1,26 +0,0 @@
services:
# postgres:
# ports:
# - "127.0.0.1:5432:5432"
api:
# ports:
# - "127.0.0.1:8080:8080"
networks:
default: {}
caddy:
aliases:
- beavergram-api
frontend-dev:
# ports:
# - "127.0.0.1:5173:5173"
networks:
default: { }
caddy:
aliases:
- beavergram-frontend
networks:
caddy:
external: true
+5 -98
View File
@@ -1,98 +1,5 @@
services:
postgres:
image: timescale/timescaledb:2.27.1-pg17
profiles: [db]
environment:
POSTGRES_USER: ${DB__USER:-beavergram}
POSTGRES_PASSWORD: ${DB__PASSWORD:-beavergram}
POSTGRES_DB: ${DB__DB_NAME:-beavergram}
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
- ./backend/migrations/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB__USER:-beavergram}"]
interval: 5s
timeout: 5s
retries: 10
userbot:
build: ./backend
profiles: [userbot]
command: [userbot]
env_file:
- path: backend/.env
required: false
- path: .env
required: false
environment:
RUN_ENVIRONMENT: prod
STORAGE__ROOT: /app/storage
restart: unless-stopped
volumes:
- ./backend/src:/app/src
- ./backend/sessions:/app/sessions
- ${STORAGE__ROOT:-./storage}:/app/storage
depends_on:
postgres:
condition: service_healthy
api:
build: ./backend
profiles: [api]
command: [api]
env_file:
- path: backend/.env
required: false
- path: .env
required: false
environment:
RUN_ENVIRONMENT: prod
STORAGE__ROOT: /app/storage
restart: unless-stopped
volumes:
- ./backend/src:/app/src
- ./backend/sessions:/app/sessions
- ./frontend/build:/app/static:ro
- ${STORAGE__ROOT:-./storage}:/app/storage
depends_on:
postgres:
condition: service_healthy
migrator:
build: ./backend
profiles: [migrate]
env_file:
- path: backend/.env
required: false
- path: .env
required: false
environment:
RUN_ENVIRONMENT: prod
volumes:
- ./backend/src:/app/src
- ./backend/migrations:/app/migrations
- ./backend/alembic.ini:/app/alembic.ini
depends_on:
postgres:
condition: service_healthy
entrypoint: [alembic]
command: [upgrade, head]
frontend-dev:
image: oven/bun:1
profiles: [frontend]
working_dir: /app
command: ["sh", "-c", "bun install && bun run dev --host 0.0.0.0 --port 5173"]
environment:
API_PROXY_TARGET: http://api:8080
ALLOWED_HOSTS: ${FRONTEND_DEV_HOST:-}
volumes:
- ./frontend:/app
- frontend_node_modules:/app/node_modules
- frontend_svelte_kit:/app/.svelte-kit
volumes:
pgdata:
frontend_node_modules:
frontend_svelte_kit:
include:
- path: backend/docker-compose.yml
project_directory: .
- path: frontend/docker-compose.yml
project_directory: .
+5
View File
@@ -0,0 +1,5 @@
node_modules
.svelte-kit
build
.env
.env.*
+2
View File
@@ -0,0 +1,2 @@
PUBLIC_API_BASE_URL=/api
ALLOWED_HOSTS=
+6
View File
@@ -0,0 +1,6 @@
## Checking commands
After writing code, always run from `frontend/`:
```shell
bun fix
bun check
```
+26
View File
@@ -0,0 +1,26 @@
COMPOSE := cd .. && docker compose
changed = ! git diff --quiet $(1) -- $(2) 2>/dev/null
stamp = { git update-ref $(1) HEAD 2>/dev/null || true; }
.PHONY: fmt check build pre-deploy post-deploy dev
fmt:
bun run fix
check:
bun run check
bun run lint
build:
$(COMPOSE) run --rm frontend-dev sh -c "bun install && bun run build"
pre-deploy:
@if $(call changed,refs/deploy/frontend,.) || ! test -d build; then \
$(MAKE) build && $(call stamp,refs/deploy/frontend); \
fi
post-deploy:
@:
dev:
bun run dev
@@ -0,0 +1,11 @@
services:
frontend-dev:
networks:
default: {}
caddy:
aliases:
- beavergram-frontend
networks:
caddy:
external: true
+22
View File
@@ -0,0 +1,22 @@
services:
frontend-dev:
image: oven/bun:alpine
profiles: [frontend-dev]
restart: unless-stopped
working_dir: /app
env_file:
- path: .env
required: false
- path: frontend/.env
required: false
environment:
API_PROXY_TARGET: http://api:8080
volumes:
- ./frontend:/app
- frontend_node_modules:/app/node_modules
- frontend_svelte_kit:/app/.svelte-kit
command: [sh, -c, "bun install && bun run dev --host 0.0.0.0 --port 3000"]
volumes:
frontend_node_modules:
frontend_svelte_kit:
+1 -1
View File
@@ -2,4 +2,4 @@
python = "backend/.venv"
[src]
exclude = ["docs"]
exclude = ["backend/migrations", "docs"]