chore: монорепо, dev-окружение и деплой
Nix flake на два шелла (backend и + Flutter), justfile как единая точка входа, локальный Postgres в ./.pgdata на порту 54329, docker-compose из пяти сервисов (db, api, worker, caddy, pg-backup) и CI на ruff/pyright/pytest + дрейф OpenAPI. Российские корневые сертификаты лежат в репозитории: они публичные, но нужны и Caddy, и gRPC-каналу T-Invest, а глобальный gitignore отбрасывает *.pem.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# Copy to .env (never committed). On the VPS, docker compose reads this file.
|
||||
|
||||
# --- database ---
|
||||
POSTGRES_DB=fintracker
|
||||
POSTGRES_USER=fintracker
|
||||
POSTGRES_PASSWORD=change-me
|
||||
# used by api/worker; inside compose the host is `db`
|
||||
DATABASE_URL=postgresql+asyncpg://fintracker:change-me@db:5432/fintracker
|
||||
|
||||
# --- auth ---
|
||||
# 32+ random bytes: `openssl rand -hex 32`
|
||||
JWT_SECRET=change-me
|
||||
ACCESS_TOKEN_TTL_SECONDS=3600
|
||||
REFRESH_TOKEN_TTL_SECONDS=2592000
|
||||
|
||||
# --- web ---
|
||||
DOMAIN=fin.example.com
|
||||
# comma-separated; leave empty when the Flutter web build is served by Caddy from the same origin
|
||||
CORS_ORIGINS=
|
||||
# dev only: serve a Flutter web build from the API process (prod uses Caddy)
|
||||
WEB_DIR=
|
||||
|
||||
# --- sources (filled in later phases) ---
|
||||
ZENMONEY_TOKEN=
|
||||
TINVEST_TOKEN=
|
||||
@@ -0,0 +1,42 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
backend:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: postgres
|
||||
ports: ["5432:5432"]
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U postgres" --health-interval 5s --health-timeout 3s --health-retries 20
|
||||
defaults:
|
||||
run:
|
||||
working-directory: backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
- run: uv python install 3.12
|
||||
- run: uv sync --frozen
|
||||
- run: uv run ruff check .
|
||||
- run: uv run ruff format --check .
|
||||
- run: uv run pyright
|
||||
- name: tests
|
||||
run: uv run pytest -q
|
||||
env:
|
||||
# tests spawn their own postgres via pg_ctl; the client tools come from the OS package
|
||||
PATH: /usr/lib/postgresql/17/bin:${{ env.PATH }}
|
||||
- name: openapi drift
|
||||
run: |
|
||||
uv run fintracker openapi /tmp/openapi.json
|
||||
diff -u ../openapi/openapi.json /tmp/openapi.json
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# secrets and local state
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.direnv/
|
||||
.pgdata/
|
||||
backups/
|
||||
|
||||
# python
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
.mypy_cache/
|
||||
dist/
|
||||
*.egg-info/
|
||||
|
||||
# real broker reports and other personal inputs — never committed
|
||||
backend/tests/fixtures/reports/raw/*
|
||||
!backend/tests/fixtures/reports/raw/.gitkeep
|
||||
|
||||
# flutter / dart
|
||||
app/.dart_tool/
|
||||
app/build/
|
||||
app/.flutter-plugins*
|
||||
app/packages/api_client/.dart_tool/
|
||||
app/packages/api_client/build/
|
||||
|
||||
# editor / os
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
result
|
||||
|
||||
# public Russian trusted CA bundle (not a secret) — the global gitignore drops *.pem
|
||||
!deploy/certs/*.pem
|
||||
!backend/certs/*.pem
|
||||
@@ -0,0 +1,11 @@
|
||||
# fin-tracker
|
||||
|
||||
Личная аналитика финансов и инвестиций: ZenMoney (повседневные деньги) + брокеры
|
||||
(T-Invest API, отчёты Сбера и ВТБ) в одной базе, с XIRR/TWR, календарём дивидендов и
|
||||
купонов, аллокацией и net worth. Замена Snowball Income.
|
||||
|
||||
- `backend/` — Python 3.12, FastAPI, PostgreSQL, worker с расписанием синков.
|
||||
- `app/` — Flutter-клиент (web, Linux, Windows, Android) поверх REST/OpenAPI.
|
||||
- `deploy/` — Caddy и сертификаты; `docker-compose.yml` — прод на VPS.
|
||||
|
||||
Для агентов: [AGENTS.md](AGENTS.md). Команды: `just --list`.
|
||||
@@ -0,0 +1,6 @@
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
tests/fixtures/reports/raw/
|
||||
@@ -0,0 +1,27 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
|
||||
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=never
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml uv.lock ./
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-dev --no-install-project
|
||||
COPY . .
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-dev
|
||||
|
||||
FROM docker.io/library/python:3.12-slim-bookworm
|
||||
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1 TZ=Europe/Moscow
|
||||
# Russian trusted CA bundle for T-Invest gRPC
|
||||
ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/app/certs/russian_ca.pem
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends tzdata ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* && useradd -r -u 10001 app
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/.venv /app/.venv
|
||||
COPY --from=builder /app/src /app/src
|
||||
COPY --from=builder /app/alembic.ini /app/alembic.ini
|
||||
COPY --from=builder /app/alembic /app/alembic
|
||||
COPY certs /app/certs
|
||||
RUN mkdir -p /data/uploads && chown -R app /data
|
||||
USER app
|
||||
EXPOSE 8000
|
||||
CMD ["fintracker", "serve", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -0,0 +1,3 @@
|
||||
# fintracker backend
|
||||
|
||||
FastAPI + PostgreSQL. См. корневой `AGENTS.md` и `justfile`.
|
||||
@@ -0,0 +1,76 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIG6DCCBNCgAwIBAgICEAUwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
||||
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
||||
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
||||
Q0EwHhcNMjQwNzE1MTI1MDQxWhcNMjkwNzE5MTI1MDQxWjBvMQswCQYDVQQGEwJS
|
||||
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
||||
YW5kIENvbW11bmljYXRpb25zMR8wHQYDVQQDDBZSdXNzaWFuIFRydXN0ZWQgU3Vi
|
||||
IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1j0rkZECOt1S8o7I
|
||||
JY+4YKAxuEa5xaHKHXT2EpkuC/0krqMOjUy2oPIRNgR5g8X0Jl6jamxeGLc4Q1tf
|
||||
ju6or9oSRYThIUhRsFDQNBiBBEXoBgWxTfiKB2eyT97+pz5TBtBiRCPaLGRHYLRb
|
||||
9Jz2HkJlxbtNPjtDrF5DPHym+mZ1M1z3hIQYAqJwLpsEBnsw/VxWMlxqHoeewd0h
|
||||
uJMd71KQ5vOKlz7KrIZ6EobNNa6wItuvsfj3kYCK7O78uLHGXXFxdr8Hae9lMUmC
|
||||
8F7AFwa+bO1LRlTlqW7rE3rLf+jj70N01N8T3o22v14YBaFBWQWncAVYD2JuL3tH
|
||||
252+kdNOERf1fLbLRigJAbd+hOhWYlNf963TFDgnNPliHNIW72SygVBnI2V3JwO1
|
||||
dp1hVKpK/zt8ziGdHW4gmOLTsH50YKdR4jNqUgQv4wASlKn9OpN6zHYc5G8h86fY
|
||||
BM+zxE5ikGI+I/vIqBuI0eaDU92AWN/YjFLpu8tMu9kLRSCf1vug6FIfDPWVo7iP
|
||||
ac/SI2v8jnnpaW7ph/Pz3WkzaG7ZZJsfFs+8dploWc6LOoDtbFBhMdGMxu024msC
|
||||
0PSjZb5ODXPIaO2NsA7fMiAtZcoK6anTUJh4zOP/stA9qsJGNxdrEmiPXSmBZY/N
|
||||
Y0wkZgZ6JTDhw7038bPvctkblJkCAwEAAaOCAYswggGHMB0GA1UdDgQWBBR3Pdk5
|
||||
r0K93FvKduru/c4+YSkwXzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qws
|
||||
hzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADCBmAYIKwYBBQUH
|
||||
AQEEgYswgYgwQAYIKwYBBQUHMAKGNGh0dHA6Ly9udWMtY2RwLnZvc2tob2QucnUv
|
||||
Y2RwL3Jvb3RjYV9zc2xfcnNhMjAyMi5jcnQwRAYIKwYBBQUHMAKGOGh0dHA6Ly9u
|
||||
dWMtY2RwLmRpZ2l0YWwuZ292LnJ1L2NkcC9yb290Y2Ffc3NsX3JzYTIwMjIuY3J0
|
||||
MIGFBgNVHR8EfjB8MDqgOKA2hjRodHRwOi8vbnVjLWNkcC52b3NraG9kLnJ1L2Nk
|
||||
cC9yb290Y2Ffc3NsX3JzYTIwMjIuY3JsMD6gPKA6hjhodHRwOi8vbnVjLWNkcC5k
|
||||
aWdpdGFsLmdvdi5ydS9jZHAvcm9vdGNhX3NzbF9yc2EyMDIyLmNybDANBgkqhkiG
|
||||
9w0BAQsFAAOCAgEAmsINXtQ7wwUWvIeOr80MdJS/5G4xhyZOVEmeUorThquT672y
|
||||
cCg3XCxc4fwbiZqSSbBqntQ7RtiTAKMYMvBageKoVHbzz+R4jX01tKcTx8cDePrz
|
||||
dJ73bLNUorE7RU9QsW4KyiUeRmjMDV23AUlEvuQFTwgkHXvbac1BBdPn9CrssQuF
|
||||
5EGohZKcQPFiAAc4SHbRNhlr7uAwgpc/erzI9EAcvA6BVAXcVKoeGpV01uexUgZ6
|
||||
St5RP9UmDWNA7T4yVXWJ233N0Q8bl+6AswINQ3PosPu6yQQHQjr65YS06epK+AeI
|
||||
6j+oGR4xI7EhTQhQvaobnGmX/8QQ7XDRYCP2HXYxiffnn/CfZ/BVyKLYeY1ZipjE
|
||||
nzqdQIC2+Q3WtY8jsVRQMP38WFRmtsIt5snehnPTs5bKGVIcYzj3o3Ex/K7agEz0
|
||||
zAJ0JR5ivXZOvNkT0g9x1v+S1IkU3e/nX1a+tpRquMtnHX0L2lXArNHUbaOO9EJt
|
||||
d57WaIpofV5cVhhwShOgAuBc9UMJF3/n4t4RKiPxtsK8P67gcmphMhslj7AMYrYM
|
||||
ej2NvQZY4m3ub3CPC/PrTjDONvb+8g5xrKtxBjYqC74HSB4dg9G3WimSDUuP2Su6
|
||||
G2y2TUeyJuCvCLz289VoO0vg7cNdMobE3KCqAiiNhN2VBFxHAUKmUoRcRdw=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
subject=C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Root CA
|
||||
issuer=C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Root CA
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFwjCCA6qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
||||
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
||||
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
||||
Q0EwHhcNMjIwMzAxMjEwNDE1WhcNMzIwMjI3MjEwNDE1WjBwMQswCQYDVQQGEwJS
|
||||
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
||||
YW5kIENvbW11bmljYXRpb25zMSAwHgYDVQQDDBdSdXNzaWFuIFRydXN0ZWQgUm9v
|
||||
dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMfFOZ8pUAL3+r2n
|
||||
qqE0Zp52selXsKGFYoG0GM5bwz1bSFtCt+AZQMhkWQheI3poZAToYJu69pHLKS6Q
|
||||
XBiwBC1cvzYmUYKMYZC7jE5YhEU2bSL0mX7NaMxMDmH2/NwuOVRj8OImVa5s1F4U
|
||||
zn4Kv3PFlDBjjSjXKVY9kmjUBsXQrIHeaqmUIsPIlNWUnimXS0I0abExqkbdrXbX
|
||||
YwCOXhOO2pDUx3ckmJlCMUGacUTnylyQW2VsJIyIGA8V0xzdaeUXg0VZ6ZmNUr5Y
|
||||
Ber/EAOLPb8NYpsAhJe2mXjMB/J9HNsoFMBFJ0lLOT/+dQvjbdRZoOT8eqJpWnVD
|
||||
U+QL/qEZnz57N88OWM3rabJkRNdU/Z7x5SFIM9FrqtN8xewsiBWBI0K6XFuOBOTD
|
||||
4V08o4TzJ8+Ccq5XlCUW2L48pZNCYuBDfBh7FxkB7qDgGDiaftEkZZfApRg2E+M9
|
||||
G8wkNKTPLDc4wH0FDTijhgxR3Y4PiS1HL2Zhw7bD3CbslmEGgfnnZojNkJtcLeBH
|
||||
BLa52/dSwNU4WWLubaYSiAmA9IUMX1/RpfpxOxd4Ykmhz97oFbUaDJFipIggx5sX
|
||||
ePAlkTdWnv+RWBxlJwMQ25oEHmRguNYf4Zr/Rxr9cS93Y+mdXIZaBEE0KS2iLRqa
|
||||
OiWBki9IMQU4phqPOBAaG7A+eP8PAgMBAAGjZjBkMB0GA1UdDgQWBBTh0YHlzlpf
|
||||
BKrS6badZrHF+qwshzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzAS
|
||||
BgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
|
||||
AAOCAgEAALIY1wkilt/urfEVM5vKzr6utOeDWCUczmWX/RX4ljpRdgF+5fAIS4vH
|
||||
tmXkqpSCOVeWUrJV9QvZn6L227ZwuE15cWi8DCDal3Ue90WgAJJZMfTshN4OI8cq
|
||||
W9E4EG9wglbEtMnObHlms8F3CHmrw3k6KmUkWGoa+/ENmcVl68u/cMRl1JbW2bM+
|
||||
/3A+SAg2c6iPDlehczKx2oa95QW0SkPPWGuNA/CE8CpyANIhu9XFrj3RQ3EqeRcS
|
||||
AQQod1RNuHpfETLU/A2gMmvn/w/sx7TB3W5BPs6rprOA37tutPq9u6FTZOcG1Oqj
|
||||
C/B7yTqgI7rbyvox7DEXoX7rIiEqyNNUguTk/u3SZ4VXE2kmxdmSh3TQvybfbnXV
|
||||
4JbCZVaqiZraqc7oZMnRoWrXRG3ztbnbes/9qhRGI7PqXqeKJBztxRTEVj8ONs1d
|
||||
WN5szTwaPIvhkhO3CO5ErU2rVdUr89wKpNXbBODFKRtgxUT70YpmJ46VVaqdAhOZ
|
||||
D9EUUn4YaeLaS8AjSF/h7UkjOibNc4qVDiPP+rkehFWM66PVnP1Msh93tc+taIfC
|
||||
EYVMxjh8zNbFuoc7fzvvrFILLe7ifvEIUqSVIC/AzplM/Jxw7buXFeGP1qVCBEHq
|
||||
391d/9RAfaZ12zkwFsl+IKwE/OZxW8AHa9i1p4GO0YSNuczzEm4=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{$DOMAIN} {
|
||||
encode zstd gzip
|
||||
|
||||
handle /api/* {
|
||||
reverse_proxy api:8000
|
||||
}
|
||||
|
||||
# Flutter web build (app/build/web), SPA fallback to index.html
|
||||
handle {
|
||||
root * /srv/app
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIG6DCCBNCgAwIBAgICEAUwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
||||
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
||||
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
||||
Q0EwHhcNMjQwNzE1MTI1MDQxWhcNMjkwNzE5MTI1MDQxWjBvMQswCQYDVQQGEwJS
|
||||
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
||||
YW5kIENvbW11bmljYXRpb25zMR8wHQYDVQQDDBZSdXNzaWFuIFRydXN0ZWQgU3Vi
|
||||
IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1j0rkZECOt1S8o7I
|
||||
JY+4YKAxuEa5xaHKHXT2EpkuC/0krqMOjUy2oPIRNgR5g8X0Jl6jamxeGLc4Q1tf
|
||||
ju6or9oSRYThIUhRsFDQNBiBBEXoBgWxTfiKB2eyT97+pz5TBtBiRCPaLGRHYLRb
|
||||
9Jz2HkJlxbtNPjtDrF5DPHym+mZ1M1z3hIQYAqJwLpsEBnsw/VxWMlxqHoeewd0h
|
||||
uJMd71KQ5vOKlz7KrIZ6EobNNa6wItuvsfj3kYCK7O78uLHGXXFxdr8Hae9lMUmC
|
||||
8F7AFwa+bO1LRlTlqW7rE3rLf+jj70N01N8T3o22v14YBaFBWQWncAVYD2JuL3tH
|
||||
252+kdNOERf1fLbLRigJAbd+hOhWYlNf963TFDgnNPliHNIW72SygVBnI2V3JwO1
|
||||
dp1hVKpK/zt8ziGdHW4gmOLTsH50YKdR4jNqUgQv4wASlKn9OpN6zHYc5G8h86fY
|
||||
BM+zxE5ikGI+I/vIqBuI0eaDU92AWN/YjFLpu8tMu9kLRSCf1vug6FIfDPWVo7iP
|
||||
ac/SI2v8jnnpaW7ph/Pz3WkzaG7ZZJsfFs+8dploWc6LOoDtbFBhMdGMxu024msC
|
||||
0PSjZb5ODXPIaO2NsA7fMiAtZcoK6anTUJh4zOP/stA9qsJGNxdrEmiPXSmBZY/N
|
||||
Y0wkZgZ6JTDhw7038bPvctkblJkCAwEAAaOCAYswggGHMB0GA1UdDgQWBBR3Pdk5
|
||||
r0K93FvKduru/c4+YSkwXzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qws
|
||||
hzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADCBmAYIKwYBBQUH
|
||||
AQEEgYswgYgwQAYIKwYBBQUHMAKGNGh0dHA6Ly9udWMtY2RwLnZvc2tob2QucnUv
|
||||
Y2RwL3Jvb3RjYV9zc2xfcnNhMjAyMi5jcnQwRAYIKwYBBQUHMAKGOGh0dHA6Ly9u
|
||||
dWMtY2RwLmRpZ2l0YWwuZ292LnJ1L2NkcC9yb290Y2Ffc3NsX3JzYTIwMjIuY3J0
|
||||
MIGFBgNVHR8EfjB8MDqgOKA2hjRodHRwOi8vbnVjLWNkcC52b3NraG9kLnJ1L2Nk
|
||||
cC9yb290Y2Ffc3NsX3JzYTIwMjIuY3JsMD6gPKA6hjhodHRwOi8vbnVjLWNkcC5k
|
||||
aWdpdGFsLmdvdi5ydS9jZHAvcm9vdGNhX3NzbF9yc2EyMDIyLmNybDANBgkqhkiG
|
||||
9w0BAQsFAAOCAgEAmsINXtQ7wwUWvIeOr80MdJS/5G4xhyZOVEmeUorThquT672y
|
||||
cCg3XCxc4fwbiZqSSbBqntQ7RtiTAKMYMvBageKoVHbzz+R4jX01tKcTx8cDePrz
|
||||
dJ73bLNUorE7RU9QsW4KyiUeRmjMDV23AUlEvuQFTwgkHXvbac1BBdPn9CrssQuF
|
||||
5EGohZKcQPFiAAc4SHbRNhlr7uAwgpc/erzI9EAcvA6BVAXcVKoeGpV01uexUgZ6
|
||||
St5RP9UmDWNA7T4yVXWJ233N0Q8bl+6AswINQ3PosPu6yQQHQjr65YS06epK+AeI
|
||||
6j+oGR4xI7EhTQhQvaobnGmX/8QQ7XDRYCP2HXYxiffnn/CfZ/BVyKLYeY1ZipjE
|
||||
nzqdQIC2+Q3WtY8jsVRQMP38WFRmtsIt5snehnPTs5bKGVIcYzj3o3Ex/K7agEz0
|
||||
zAJ0JR5ivXZOvNkT0g9x1v+S1IkU3e/nX1a+tpRquMtnHX0L2lXArNHUbaOO9EJt
|
||||
d57WaIpofV5cVhhwShOgAuBc9UMJF3/n4t4RKiPxtsK8P67gcmphMhslj7AMYrYM
|
||||
ej2NvQZY4m3ub3CPC/PrTjDONvb+8g5xrKtxBjYqC74HSB4dg9G3WimSDUuP2Su6
|
||||
G2y2TUeyJuCvCLz289VoO0vg7cNdMobE3KCqAiiNhN2VBFxHAUKmUoRcRdw=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
subject=C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Root CA
|
||||
issuer=C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Root CA
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFwjCCA6qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
||||
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
||||
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
||||
Q0EwHhcNMjIwMzAxMjEwNDE1WhcNMzIwMjI3MjEwNDE1WjBwMQswCQYDVQQGEwJS
|
||||
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
||||
YW5kIENvbW11bmljYXRpb25zMSAwHgYDVQQDDBdSdXNzaWFuIFRydXN0ZWQgUm9v
|
||||
dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMfFOZ8pUAL3+r2n
|
||||
qqE0Zp52selXsKGFYoG0GM5bwz1bSFtCt+AZQMhkWQheI3poZAToYJu69pHLKS6Q
|
||||
XBiwBC1cvzYmUYKMYZC7jE5YhEU2bSL0mX7NaMxMDmH2/NwuOVRj8OImVa5s1F4U
|
||||
zn4Kv3PFlDBjjSjXKVY9kmjUBsXQrIHeaqmUIsPIlNWUnimXS0I0abExqkbdrXbX
|
||||
YwCOXhOO2pDUx3ckmJlCMUGacUTnylyQW2VsJIyIGA8V0xzdaeUXg0VZ6ZmNUr5Y
|
||||
Ber/EAOLPb8NYpsAhJe2mXjMB/J9HNsoFMBFJ0lLOT/+dQvjbdRZoOT8eqJpWnVD
|
||||
U+QL/qEZnz57N88OWM3rabJkRNdU/Z7x5SFIM9FrqtN8xewsiBWBI0K6XFuOBOTD
|
||||
4V08o4TzJ8+Ccq5XlCUW2L48pZNCYuBDfBh7FxkB7qDgGDiaftEkZZfApRg2E+M9
|
||||
G8wkNKTPLDc4wH0FDTijhgxR3Y4PiS1HL2Zhw7bD3CbslmEGgfnnZojNkJtcLeBH
|
||||
BLa52/dSwNU4WWLubaYSiAmA9IUMX1/RpfpxOxd4Ykmhz97oFbUaDJFipIggx5sX
|
||||
ePAlkTdWnv+RWBxlJwMQ25oEHmRguNYf4Zr/Rxr9cS93Y+mdXIZaBEE0KS2iLRqa
|
||||
OiWBki9IMQU4phqPOBAaG7A+eP8PAgMBAAGjZjBkMB0GA1UdDgQWBBTh0YHlzlpf
|
||||
BKrS6badZrHF+qwshzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzAS
|
||||
BgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
|
||||
AAOCAgEAALIY1wkilt/urfEVM5vKzr6utOeDWCUczmWX/RX4ljpRdgF+5fAIS4vH
|
||||
tmXkqpSCOVeWUrJV9QvZn6L227ZwuE15cWi8DCDal3Ue90WgAJJZMfTshN4OI8cq
|
||||
W9E4EG9wglbEtMnObHlms8F3CHmrw3k6KmUkWGoa+/ENmcVl68u/cMRl1JbW2bM+
|
||||
/3A+SAg2c6iPDlehczKx2oa95QW0SkPPWGuNA/CE8CpyANIhu9XFrj3RQ3EqeRcS
|
||||
AQQod1RNuHpfETLU/A2gMmvn/w/sx7TB3W5BPs6rprOA37tutPq9u6FTZOcG1Oqj
|
||||
C/B7yTqgI7rbyvox7DEXoX7rIiEqyNNUguTk/u3SZ4VXE2kmxdmSh3TQvybfbnXV
|
||||
4JbCZVaqiZraqc7oZMnRoWrXRG3ztbnbes/9qhRGI7PqXqeKJBztxRTEVj8ONs1d
|
||||
WN5szTwaPIvhkhO3CO5ErU2rVdUr89wKpNXbBODFKRtgxUT70YpmJ46VVaqdAhOZ
|
||||
D9EUUn4YaeLaS8AjSF/h7UkjOibNc4qVDiPP+rkehFWM66PVnP1Msh93tc+taIfC
|
||||
EYVMxjh8zNbFuoc7fzvvrFILLe7ifvEIUqSVIC/AzplM/Jxw7buXFeGP1qVCBEHq
|
||||
391d/9RAfaZ12zkwFsl+IKwE/OZxW8AHa9i1p4GO0YSNuczzEm4=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFwjCCA6qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
||||
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
||||
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
||||
Q0EwHhcNMjIwMzAxMjEwNDE1WhcNMzIwMjI3MjEwNDE1WjBwMQswCQYDVQQGEwJS
|
||||
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
||||
YW5kIENvbW11bmljYXRpb25zMSAwHgYDVQQDDBdSdXNzaWFuIFRydXN0ZWQgUm9v
|
||||
dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMfFOZ8pUAL3+r2n
|
||||
qqE0Zp52selXsKGFYoG0GM5bwz1bSFtCt+AZQMhkWQheI3poZAToYJu69pHLKS6Q
|
||||
XBiwBC1cvzYmUYKMYZC7jE5YhEU2bSL0mX7NaMxMDmH2/NwuOVRj8OImVa5s1F4U
|
||||
zn4Kv3PFlDBjjSjXKVY9kmjUBsXQrIHeaqmUIsPIlNWUnimXS0I0abExqkbdrXbX
|
||||
YwCOXhOO2pDUx3ckmJlCMUGacUTnylyQW2VsJIyIGA8V0xzdaeUXg0VZ6ZmNUr5Y
|
||||
Ber/EAOLPb8NYpsAhJe2mXjMB/J9HNsoFMBFJ0lLOT/+dQvjbdRZoOT8eqJpWnVD
|
||||
U+QL/qEZnz57N88OWM3rabJkRNdU/Z7x5SFIM9FrqtN8xewsiBWBI0K6XFuOBOTD
|
||||
4V08o4TzJ8+Ccq5XlCUW2L48pZNCYuBDfBh7FxkB7qDgGDiaftEkZZfApRg2E+M9
|
||||
G8wkNKTPLDc4wH0FDTijhgxR3Y4PiS1HL2Zhw7bD3CbslmEGgfnnZojNkJtcLeBH
|
||||
BLa52/dSwNU4WWLubaYSiAmA9IUMX1/RpfpxOxd4Ykmhz97oFbUaDJFipIggx5sX
|
||||
ePAlkTdWnv+RWBxlJwMQ25oEHmRguNYf4Zr/Rxr9cS93Y+mdXIZaBEE0KS2iLRqa
|
||||
OiWBki9IMQU4phqPOBAaG7A+eP8PAgMBAAGjZjBkMB0GA1UdDgQWBBTh0YHlzlpf
|
||||
BKrS6badZrHF+qwshzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzAS
|
||||
BgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
|
||||
AAOCAgEAALIY1wkilt/urfEVM5vKzr6utOeDWCUczmWX/RX4ljpRdgF+5fAIS4vH
|
||||
tmXkqpSCOVeWUrJV9QvZn6L227ZwuE15cWi8DCDal3Ue90WgAJJZMfTshN4OI8cq
|
||||
W9E4EG9wglbEtMnObHlms8F3CHmrw3k6KmUkWGoa+/ENmcVl68u/cMRl1JbW2bM+
|
||||
/3A+SAg2c6iPDlehczKx2oa95QW0SkPPWGuNA/CE8CpyANIhu9XFrj3RQ3EqeRcS
|
||||
AQQod1RNuHpfETLU/A2gMmvn/w/sx7TB3W5BPs6rprOA37tutPq9u6FTZOcG1Oqj
|
||||
C/B7yTqgI7rbyvox7DEXoX7rIiEqyNNUguTk/u3SZ4VXE2kmxdmSh3TQvybfbnXV
|
||||
4JbCZVaqiZraqc7oZMnRoWrXRG3ztbnbes/9qhRGI7PqXqeKJBztxRTEVj8ONs1d
|
||||
WN5szTwaPIvhkhO3CO5ErU2rVdUr89wKpNXbBODFKRtgxUT70YpmJ46VVaqdAhOZ
|
||||
D9EUUn4YaeLaS8AjSF/h7UkjOibNc4qVDiPP+rkehFWM66PVnP1Msh93tc+taIfC
|
||||
EYVMxjh8zNbFuoc7fzvvrFILLe7ifvEIUqSVIC/AzplM/Jxw7buXFeGP1qVCBEHq
|
||||
391d/9RAfaZ12zkwFsl+IKwE/OZxW8AHa9i1p4GO0YSNuczzEm4=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIG6DCCBNCgAwIBAgICEAUwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
||||
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
||||
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
||||
Q0EwHhcNMjQwNzE1MTI1MDQxWhcNMjkwNzE5MTI1MDQxWjBvMQswCQYDVQQGEwJS
|
||||
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
||||
YW5kIENvbW11bmljYXRpb25zMR8wHQYDVQQDDBZSdXNzaWFuIFRydXN0ZWQgU3Vi
|
||||
IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1j0rkZECOt1S8o7I
|
||||
JY+4YKAxuEa5xaHKHXT2EpkuC/0krqMOjUy2oPIRNgR5g8X0Jl6jamxeGLc4Q1tf
|
||||
ju6or9oSRYThIUhRsFDQNBiBBEXoBgWxTfiKB2eyT97+pz5TBtBiRCPaLGRHYLRb
|
||||
9Jz2HkJlxbtNPjtDrF5DPHym+mZ1M1z3hIQYAqJwLpsEBnsw/VxWMlxqHoeewd0h
|
||||
uJMd71KQ5vOKlz7KrIZ6EobNNa6wItuvsfj3kYCK7O78uLHGXXFxdr8Hae9lMUmC
|
||||
8F7AFwa+bO1LRlTlqW7rE3rLf+jj70N01N8T3o22v14YBaFBWQWncAVYD2JuL3tH
|
||||
252+kdNOERf1fLbLRigJAbd+hOhWYlNf963TFDgnNPliHNIW72SygVBnI2V3JwO1
|
||||
dp1hVKpK/zt8ziGdHW4gmOLTsH50YKdR4jNqUgQv4wASlKn9OpN6zHYc5G8h86fY
|
||||
BM+zxE5ikGI+I/vIqBuI0eaDU92AWN/YjFLpu8tMu9kLRSCf1vug6FIfDPWVo7iP
|
||||
ac/SI2v8jnnpaW7ph/Pz3WkzaG7ZZJsfFs+8dploWc6LOoDtbFBhMdGMxu024msC
|
||||
0PSjZb5ODXPIaO2NsA7fMiAtZcoK6anTUJh4zOP/stA9qsJGNxdrEmiPXSmBZY/N
|
||||
Y0wkZgZ6JTDhw7038bPvctkblJkCAwEAAaOCAYswggGHMB0GA1UdDgQWBBR3Pdk5
|
||||
r0K93FvKduru/c4+YSkwXzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qws
|
||||
hzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADCBmAYIKwYBBQUH
|
||||
AQEEgYswgYgwQAYIKwYBBQUHMAKGNGh0dHA6Ly9udWMtY2RwLnZvc2tob2QucnUv
|
||||
Y2RwL3Jvb3RjYV9zc2xfcnNhMjAyMi5jcnQwRAYIKwYBBQUHMAKGOGh0dHA6Ly9u
|
||||
dWMtY2RwLmRpZ2l0YWwuZ292LnJ1L2NkcC9yb290Y2Ffc3NsX3JzYTIwMjIuY3J0
|
||||
MIGFBgNVHR8EfjB8MDqgOKA2hjRodHRwOi8vbnVjLWNkcC52b3NraG9kLnJ1L2Nk
|
||||
cC9yb290Y2Ffc3NsX3JzYTIwMjIuY3JsMD6gPKA6hjhodHRwOi8vbnVjLWNkcC5k
|
||||
aWdpdGFsLmdvdi5ydS9jZHAvcm9vdGNhX3NzbF9yc2EyMDIyLmNybDANBgkqhkiG
|
||||
9w0BAQsFAAOCAgEAmsINXtQ7wwUWvIeOr80MdJS/5G4xhyZOVEmeUorThquT672y
|
||||
cCg3XCxc4fwbiZqSSbBqntQ7RtiTAKMYMvBageKoVHbzz+R4jX01tKcTx8cDePrz
|
||||
dJ73bLNUorE7RU9QsW4KyiUeRmjMDV23AUlEvuQFTwgkHXvbac1BBdPn9CrssQuF
|
||||
5EGohZKcQPFiAAc4SHbRNhlr7uAwgpc/erzI9EAcvA6BVAXcVKoeGpV01uexUgZ6
|
||||
St5RP9UmDWNA7T4yVXWJ233N0Q8bl+6AswINQ3PosPu6yQQHQjr65YS06epK+AeI
|
||||
6j+oGR4xI7EhTQhQvaobnGmX/8QQ7XDRYCP2HXYxiffnn/CfZ/BVyKLYeY1ZipjE
|
||||
nzqdQIC2+Q3WtY8jsVRQMP38WFRmtsIt5snehnPTs5bKGVIcYzj3o3Ex/K7agEz0
|
||||
zAJ0JR5ivXZOvNkT0g9x1v+S1IkU3e/nX1a+tpRquMtnHX0L2lXArNHUbaOO9EJt
|
||||
d57WaIpofV5cVhhwShOgAuBc9UMJF3/n4t4RKiPxtsK8P67gcmphMhslj7AMYrYM
|
||||
ej2NvQZY4m3ub3CPC/PrTjDONvb+8g5xrKtxBjYqC74HSB4dg9G3WimSDUuP2Su6
|
||||
G2y2TUeyJuCvCLz289VoO0vg7cNdMobE3KCqAiiNhN2VBFxHAUKmUoRcRdw=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
subject=C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Root CA
|
||||
issuer=C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Root CA
|
||||
@@ -0,0 +1,81 @@
|
||||
services:
|
||||
db:
|
||||
image: docker.io/library/postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --no-locale"
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
|
||||
api:
|
||||
build: ./backend
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
command: ["sh", "-c", "alembic upgrade head && exec fintracker serve --host 0.0.0.0 --port 8000"]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- uploads:/data/uploads
|
||||
expose:
|
||||
- "8000"
|
||||
|
||||
worker:
|
||||
build: ./backend
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
command: ["fintracker", "worker"]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- uploads:/data/uploads
|
||||
|
||||
caddy:
|
||||
image: docker.io/library/caddy:2-alpine
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./app/build/web:/srv/app:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
depends_on:
|
||||
- api
|
||||
|
||||
pg-backup:
|
||||
image: docker.io/library/postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
entrypoint: ["sh", "-c"]
|
||||
# nightly logical dump, keep 14 days; copy ./backups off-site with rclone/cron on the host
|
||||
command:
|
||||
- |
|
||||
while true; do
|
||||
ts=$(date +%F_%H%M)
|
||||
pg_dump -h db -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -Fc -f "/backups/fintracker_$$ts.dump" && \
|
||||
find /backups -name 'fintracker_*.dump' -mtime +14 -delete
|
||||
sleep 86400
|
||||
done
|
||||
environment:
|
||||
PGPASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- ./backups:/backups
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
uploads:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1789546076,
|
||||
"narHash": "sha256-zVxLZiSnmaaPLwnhj7pwmqe3axBg/C6nG5JZsJMh2g4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b1b875982b17dabde9b4a37f3e229e74913e6db3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
description = "fin-tracker — personal finance + investment analytics";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
python = pkgs.python312;
|
||||
|
||||
# manylinux wheels pulled by uv (grpcio for T-Invest, asyncpg, polars, …)
|
||||
# dlopen these at runtime. nix-ld is enabled system-wide; we only extend
|
||||
# its search path (never overwrite — keeps direnv diffs sane).
|
||||
runtimeLibs = pkgs.lib.makeLibraryPath [
|
||||
pkgs.stdenv.cc.cc.lib # libstdc++.so.6
|
||||
pkgs.zlib
|
||||
pkgs.openssl
|
||||
];
|
||||
|
||||
backendTools = [
|
||||
python
|
||||
pkgs.uv
|
||||
pkgs.just
|
||||
pkgs.postgresql_17 # local dev cluster (`just db-start`) and psql
|
||||
pkgs.openapi-generator-cli # `just gen-client`
|
||||
];
|
||||
|
||||
commonHook = ''
|
||||
export UV_PYTHON="${python}/bin/python3"
|
||||
export UV_PYTHON_DOWNLOADS=never
|
||||
export NIX_LD_LIBRARY_PATH="${runtimeLibs}''${NIX_LD_LIBRARY_PATH:+:$NIX_LD_LIBRARY_PATH}"
|
||||
export LD_LIBRARY_PATH="${runtimeLibs}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
# T-Invest gRPC must not go through a local SOCKS/HTTP proxy
|
||||
export no_proxy="invest-public-api.tbank.ru,.tbank.ru,invest-public-api.tinkoff.ru,.tinkoff.ru,localhost,127.0.0.1''${no_proxy:+,$no_proxy}"
|
||||
export NO_PROXY="$no_proxy"
|
||||
export PGDATA="$PWD/.pgdata"
|
||||
export PGHOST="$PWD/.pgdata"
|
||||
export PGPORT=54329
|
||||
'';
|
||||
in {
|
||||
devShells.${system} = {
|
||||
# backend only — fast to enter
|
||||
default = pkgs.mkShell {
|
||||
packages = backendTools;
|
||||
shellHook = commonHook + ''
|
||||
echo "fin-tracker: 'just --list' for recipes; 'nix develop .#app' adds Flutter"
|
||||
'';
|
||||
};
|
||||
# backend + Flutter toolchain (large download on first use)
|
||||
app = pkgs.mkShell {
|
||||
packages = backendTools ++ [ pkgs.flutter ];
|
||||
shellHook = commonHook;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
set shell := ["bash", "-cu"]
|
||||
|
||||
backend := "backend"
|
||||
db_url := "postgresql+asyncpg://fintracker:fintracker@localhost:54329/fintracker"
|
||||
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# --- python ---
|
||||
|
||||
# install backend deps (uv)
|
||||
sync:
|
||||
cd {{backend}} && uv sync
|
||||
|
||||
# run API with reload against the local dev DB
|
||||
api:
|
||||
cd {{backend}} && DATABASE_URL={{db_url}} uv run fintracker serve --reload
|
||||
|
||||
# API + Flutter web build from one origin (run `just build-web` first)
|
||||
web:
|
||||
cd {{backend}} && DATABASE_URL={{db_url}} WEB_DIR=../app/build/web uv run fintracker serve --port 8000
|
||||
|
||||
# --- flutter ---
|
||||
|
||||
build-web:
|
||||
cd app && flutter pub get && flutter build web --release
|
||||
|
||||
app-check:
|
||||
cd app && flutter analyze && flutter test
|
||||
|
||||
# one-off source sync against the local dev DB (zenmoney | cbr)
|
||||
pull SOURCE:
|
||||
cd {{backend}} && DATABASE_URL={{db_url}} uv run fintracker sync {{SOURCE}}
|
||||
|
||||
# run the sync worker against the local dev DB
|
||||
worker:
|
||||
cd {{backend}} && DATABASE_URL={{db_url}} uv run fintracker worker
|
||||
|
||||
lint:
|
||||
cd {{backend}} && uv run ruff check . && uv run ruff format --check .
|
||||
|
||||
fmt:
|
||||
cd {{backend}} && uv run ruff format . && uv run ruff check --fix .
|
||||
|
||||
typecheck:
|
||||
cd {{backend}} && uv run pyright
|
||||
|
||||
test *ARGS:
|
||||
cd {{backend}} && uv run pytest {{ARGS}}
|
||||
|
||||
check: lint typecheck test openapi-check app-check
|
||||
|
||||
# --- database (local dev cluster in ./.pgdata, port 54329) ---
|
||||
|
||||
db-init:
|
||||
test -d .pgdata || initdb -D .pgdata -U postgres --auth=trust --no-locale --encoding=UTF8 >/dev/null
|
||||
grep -q "unix_socket_directories = '$PWD/.pgdata'" .pgdata/postgresql.conf || \
|
||||
printf "\nport = 54329\nunix_socket_directories = '%s/.pgdata'\nlisten_addresses = 'localhost'\n" "$PWD" >> .pgdata/postgresql.conf
|
||||
|
||||
db-start: db-init
|
||||
pg_ctl -D .pgdata status >/dev/null 2>&1 || pg_ctl -D .pgdata -l .pgdata/log start -w
|
||||
psql -h localhost -p 54329 -U postgres -tc "SELECT 1 FROM pg_roles WHERE rolname='fintracker'" | grep -q 1 || \
|
||||
psql -h localhost -p 54329 -U postgres -c "CREATE ROLE fintracker LOGIN PASSWORD 'fintracker' CREATEDB"
|
||||
psql -h localhost -p 54329 -U postgres -tc "SELECT 1 FROM pg_database WHERE datname='fintracker'" | grep -q 1 || \
|
||||
psql -h localhost -p 54329 -U postgres -c "CREATE DATABASE fintracker OWNER fintracker"
|
||||
|
||||
db-stop:
|
||||
pg_ctl -D .pgdata status >/dev/null 2>&1 && pg_ctl -D .pgdata stop -m fast || true
|
||||
|
||||
db-psql:
|
||||
psql -h localhost -p 54329 -U fintracker fintracker
|
||||
|
||||
migrate *ARGS="upgrade head":
|
||||
cd {{backend}} && DATABASE_URL={{db_url}} uv run alembic {{ARGS}}
|
||||
|
||||
# create a new migration from model changes
|
||||
revision MSG:
|
||||
cd {{backend}} && DATABASE_URL={{db_url}} uv run alembic revision --autogenerate -m "{{MSG}}"
|
||||
|
||||
create-user EMAIL:
|
||||
cd {{backend}} && DATABASE_URL={{db_url}} uv run fintracker create-user {{EMAIL}}
|
||||
|
||||
# --- openapi / dart client ---
|
||||
|
||||
# export openapi/openapi.json from the FastAPI app
|
||||
openapi:
|
||||
cd {{backend}} && uv run fintracker openapi ../openapi/openapi.json
|
||||
|
||||
# fail if the committed spec drifted from the code
|
||||
openapi-check:
|
||||
cd {{backend}} && uv run fintracker openapi /tmp/fin-tracker-openapi.json
|
||||
diff -q openapi/openapi.json /tmp/fin-tracker-openapi.json
|
||||
|
||||
# regenerate app/packages/api_client from openapi/openapi.json
|
||||
gen-client: openapi
|
||||
rm -rf app/packages/api_client/lib app/packages/api_client/doc
|
||||
openapi-generator-cli generate -g dart-dio -i openapi/openapi.json -o app/packages/api_client \
|
||||
-c app/packages/api_client/openapi-config.yaml
|
||||
# the generator pins an old language version; null-aware elements need >= 3.8
|
||||
sed -i "s/sdk: '>=3.5.0 <4.0.0'/sdk: '>=3.8.0 <4.0.0'/" app/packages/api_client/pubspec.yaml
|
||||
cd app/packages/api_client && dart pub get && dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
# --- deploy (VPS) ---
|
||||
|
||||
# build without the host proxy vars: podman/buildah bakes HTTP_PROXY into the image otherwise
|
||||
up:
|
||||
env -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY -u all_proxy \
|
||||
docker compose up -d --build
|
||||
|
||||
logs *ARGS:
|
||||
docker compose logs -f {{ARGS}}
|
||||
Reference in New Issue
Block a user