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