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
2 changes: 1 addition & 1 deletion .github/workflows/smokeshow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- run: smokeshow upload htmlcov
env:
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 90
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 80
SMOKESHOW_GITHUB_CONTEXT: coverage
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
Expand Down
17 changes: 9 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,15 @@ jobs:
run: |
poetry run make testcov
env:
GOOGLE_ENABLED: 1
YANDEX_ENABLED: 1
TELEGRAM_OAUTH_ENABLED: 1
NOTIFY_SMTP_ENABLED: 1
NOTIFY_TELEGRAM_ENABLED: 1
TELEGRAM_BOT_TOKEN: 123456:BOT_SECRET
DB_URL: postgresql+asyncpg://${{ matrix.database-user }}:${{ matrix.database-password }}@${{ matrix.database-host }}:${{ matrix.database-port }}/${{ matrix.database-name }}
REDIS_URL: redis://${{ matrix.redis-user }}:${{ matrix.redis-password }}@${{ matrix.redis-host }}:${{ matrix.redis-port }}
FASTID_DB_URL: postgresql+asyncpg://${{ matrix.database-user }}:${{ matrix.database-password }}@${{ matrix.database-host }}:${{ matrix.database-port }}/${{ matrix.database-name }}
FASTID_REDIS_URL: redis://${{ matrix.redis-user }}:${{ matrix.redis-password }}@${{ matrix.redis-host }}:${{ matrix.redis-port }}
FASTID_GOOGLE_OAUTH_ENABLED: 1
FASTID_YANDEX_OAUTH_ENABLED: 1
FASTID_SMTP_ENABLED: 1
FASTID_TELEGRAM_WIDGET_ENABLED: 1
FASTID_TELEGRAM_NOTIFICATION_ENABLED: 1
FASTID_TELEGRAM_BOT_TOKEN: 123456:BOT_SECRET
FASTID_WEBHOOK_PAGE_EXPIRES_IN_SECONDS: 0

- name: Teardown test environment
run: |
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
.PHONY: certs
certs:
mkdir certs
openssl rand -hex 32 > certs/secret.key
openssl genrsa -out certs/jwt-private.pem 2048
openssl rsa -in certs/jwt-private.pem -pubout -out certs/jwt-public.pem

.PHONY: deps
deps:
docker compose -f docker-compose.dev.yml postgres redis up -d --build --remove-orphans --wait
docker compose -f docker-compose.dev.yml up postgres redis -d --build --remove-orphans --wait

.PHONY: up
up:
docker compose -f docker-compose.dev.yml up --build --remove-orphans --wait

.PHONY: build
build:
docker build -t fastid:latest -f docker/Dockerfile .

.PHONY: up-obs
up-obs:
docker compose -f docker-compose.dev.yml -f docker-compose.observability.yml up --build --remove-orphans --wait
Expand Down
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,34 @@
### Download the Compose file

```bash
wget https://raw.githubusercontent.com/everysoftware/fastid/refs/heads/master/docker-compose.example.yml
mv docker-compose.example.yml docker-compose.yml
wget https://raw.githubusercontent.com/everysoftware/fastid/refs/heads/master/docker-compose.yml
```

### Generate keys

```bash
mkdir certs
openssl rand -base64 32 > certs/secret.key
openssl genrsa -out certs/jwt-private.pem 2048
openssl rsa -in certs/jwt-private.pem -pubout -out certs/jwt-public.pem
```

### Set environment variables

Create a `.env` file with the following content:

```env
```text
POSTGRES_PASSWORD=YOUR_POSTGRES_PASSWORD
REDIS_PASSWORD=YOUR_REDIS_PASSWORD
```

### Generate RSA keys
You can generate strong random passwords using the following command:

```bash
mkdir certs
openssl genrsa -out certs/jwt-private.pem 2048
openssl rsa -in certs/jwt-private.pem -pubout -out certs/jwt-public.pem
cat > .env << EOF
POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')
REDIS_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')
EOF
```

### Install and start FastID
Expand Down
11 changes: 5 additions & 6 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ services:
ports:
- "8025:8025"
- "1025:1025"
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8025/readyz"]
interval: 10s
Expand All @@ -61,11 +60,11 @@ services:
condition: service_healthy

environment:
DB_URL: ${DB_URL:-postgresql+asyncpg://${POSTGRES_USER:-fastid}:${POSTGRES_PASSWORD:?database password required}@postgres:5432/${POSTGRES_DB:-fastid}}
REDIS_URL: ${REDIS_URL:-redis://:${REDIS_PASSWORD:?redis password required}@redis:6379/0}
NOTIFY_SMTP_ENABLED: ${NOTIFY_SMTP_ENABLED:-true}
NOTIFY_SMTP_HOST: ${NOTIFY_SMTP_HOST:-mailpit}
NOTIFY_SMTP_PORT: ${NOTIFY_SMTP_PORT:-1025}
FASTID_DB_URL: ${FASTID_DB_URL:-postgresql+asyncpg://${POSTGRES_USER:-fastid}:${POSTGRES_PASSWORD:?database password required}@postgres:5432/${POSTGRES_DB:-fastid}}
FASTID_REDIS_URL: ${FASTID_REDIS_URL:-redis://:${REDIS_PASSWORD:?redis password required}@redis:6379/0}
FASTID_SMTP_ENABLED: ${FASTID_SMTP_ENABLED:-true}
FASTID_SMTP_HOST: ${FASTID_SMTP_HOST:-mailpit}
FASTID_SMTP_PORT: ${FASTID_SMTP_PORT:-1025}

volumes:
- "./migrations:/opt/fastid/migrations"
Expand Down
6 changes: 6 additions & 0 deletions docker-compose.observability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ x-logging: &default-logging
expression: '^(?P<time>\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}:\d{2},d{3}) (?P<message>(?s:.*))$$'
services:
postgres:
logging: *default-logging
redis:
logging: *default-logging
mailpit:
logging: *default-logging
fastid-app:
logging: *default-logging

Expand Down
17 changes: 8 additions & 9 deletions docker-compose.example.yml → docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ services:
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-fastid} -d ${POSTGRES_DB:-fastid}"]
test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-fastid} -d ${POSTGRES_DB:-fastid}" ]
interval: 10s
retries: 5
start_period: 30s
Expand All @@ -22,16 +22,15 @@ services:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD:?redis password required}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 5s
retries: 5

mailpit:
image: axllent/mailpit:v1.30.1
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8025/readyz"]
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8025/readyz" ]
interval: 10s
timeout: 5s
retries: 5
Expand All @@ -52,11 +51,11 @@ services:
condition: service_healthy

environment:
DB_URL: ${DB_URL:-postgresql+asyncpg://${POSTGRES_USER:-fastid}:${POSTGRES_PASSWORD:?database password required}@postgres:5432/${POSTGRES_DB:-fastid}}
REDIS_URL: ${REDIS_URL:-redis://:${REDIS_PASSWORD:?redis password required}@redis:6379/0}
NOTIFY_SMTP_ENABLED: ${NOTIFY_SMTP_ENABLED:-true}
NOTIFY_SMTP_HOST: ${NOTIFY_SMTP_HOST:-mailpit}
NOTIFY_SMTP_PORT: ${NOTIFY_SMTP_PORT:-1025}
FASTID_DB_URL: ${FASTID_DB_URL:-postgresql+asyncpg://${POSTGRES_USER:-fastid}:${POSTGRES_PASSWORD:?database password required}@postgres:5432/${POSTGRES_DB:-fastid}}
FASTID_REDIS_URL: ${FASTID_REDIS_URL:-redis://:${REDIS_PASSWORD:?redis password required}@redis:6379/0}
FASTID_SMTP_ENABLED: ${FASTID_SMTP_ENABLED:-true}
FASTID_SMTP_HOST: ${FASTID_SMTP_HOST:-mailpit}
FASTID_SMTP_PORT: ${FASTID_SMTP_PORT:-1025}

volumes:
- "./certs:/opt/fastid/certs"
Expand Down
21 changes: 18 additions & 3 deletions docker/entrypoint-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ set -e

poetry run alembic upgrade head

# You can put other setup logic here
# Evaluating passed command:
exec poetry run uvicorn "fastid.core.app:core_app" --host 0.0.0.0 --port 8000 --reload "$@"
APP=${FASTID_UVICORN_APP:-fastid.core.app:core_app}
HOST=${FASTID_UVICORN_HOST:-0.0.0.0}
PORT=${FASTID_UVICORN_PORT:-8000}
RELOAD=${FASTID_UVICORN_RELOAD:-1}

echo "Starting Uvicorn with the following configuration:"
echo " App: $APP"
echo " Host: $HOST"
echo " Port: $PORT"
echo " Reload: $RELOAD"

if [ "$RELOAD" -eq 1 ]; then
RELOAD="--reload"
else
RELOAD=""
fi

exec poetry run uvicorn "$APP" --host "$HOST" --port "$PORT" $RELOAD "$@"
48 changes: 41 additions & 7 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,48 @@ set -e

alembic upgrade head

if [ -z "$WORKERS" ]; then
if [ -z "$FASTID_GUNICORN_WORKERS" ]; then
CPU_COUNT=$(nproc --all 2>/dev/null || grep -c ^processor /proc/cpuinfo 2>/dev/null || echo 1)
# Например, 2 воркера на ядро + 1, но не меньше 1
WORKERS=$((CPU_COUNT * 2 + 1))
# Ограничим максимум разумным значением, чтобы не перегружать БД
[ "$WORKERS" -gt 16 ] && WORKERS=16
WORKERS=$CPU_COUNT
else
WORKERS=$FASTID_GUNICORN_WORKERS
fi

echo "Using $WORKERS workers"
APP=${FASTID_GUNICORN_APP:-fastid.core.app:core_app}
WORKER_CLASS=${FASTID_GUNICORN_WORKER_CLASS:-fastid.core.workers.MyUvicornWorker}
WORKER_CONNECTIONS=${FASTID_GUNICORN_WORKER_CONNECTIONS:-1000}
BIND=${FASTID_GUNICORN_BIND:-0.0.0.0:8000}
BACKLOG=${FASTID_GUNICORN_BACKLOG:-4096}
TIMEOUT=${FASTID_GUNICORN_TIMEOUT:-120}
GRACEFUL_TIMEOUT=${FASTID_GUNICORN_GRACEFUL_TIMEOUT:-30}
KEEP_ALIVE=${FASTID_GUNICORN_KEEP_ALIVE:-5}
MAX_REQUESTS=${FASTID_GUNICORN_MAX_REQUESTS:-10000}
MAX_REQUESTS_JITTER=${FASTID_GUNICORN_MAX_REQUESTS_JITTER:-2000}

exec gunicorn -w "$WORKERS" -k fastid.core.workers.MyUvicornWorker "fastid.core.app:core_app" -b 0.0.0.0:8000 "$@"
echo "Starting Gunicorn with the following configuration:"
echo " Workers: $WORKERS"
echo " Worker Class: $WORKER_CLASS"
echo " Worker Connections: $WORKER_CONNECTIONS"
echo " App: $APP"
echo " Bind: $BIND"
echo " Backlog: $BACKLOG"
echo " Timeout: $TIMEOUT"
echo " Graceful Timeout: $GRACEFUL_TIMEOUT"
echo " Keep Alive: $KEEP_ALIVE"
echo " Max Requests: $MAX_REQUESTS"
echo " Max Requests Jitter: $MAX_REQUESTS_JITTER"

exec gunicorn \
-w "$WORKERS" \
-k "$WORKER_CLASS" \
"$APP" \
-b "$BIND" \
--worker-connections "$WORKER_CONNECTIONS" \
--backlog "$BACKLOG" \
--timeout "$TIMEOUT" \
--graceful-timeout "$GRACEFUL_TIMEOUT" \
--keep-alive "$KEEP_ALIVE" \
--max-requests "$MAX_REQUESTS" \
--max-requests-jitter "$MAX_REQUESTS_JITTER" \
--preload \
"$@"
9 changes: 4 additions & 5 deletions fastid/admin/app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from fastid.admin.config import admin_settings
from fastid.admin.factory import AdminAppFactory
from fastid.core.config import main_settings
from fastid.core.config import branding_settings
from fastid.database.dependencies import engine

admin_app = AdminAppFactory(
engine,
title=f"{main_settings.title} Admin",
favicon_url=admin_settings.favicon_url,
logo_url=admin_settings.logo_url,
title=branding_settings.admin_swagger_title,
favicon_url=branding_settings.admin_favicon_url,
logo_url=branding_settings.admin_logo_url,
base_url="",
).create()
2 changes: 1 addition & 1 deletion fastid/admin/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ async def authenticate(self, request: Request) -> bool:
return bool(request.session.get("authenticated"))


admin_auth = AdminAuth(secret_key=auth_settings.jwt_private_key.read_text())
admin_auth = AdminAuth(secret_key=auth_settings.jwt_key.read_text())
12 changes: 3 additions & 9 deletions fastid/admin/config.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
from pydantic_settings import SettingsConfigDict

from fastid.core.config import main_settings
from fastid.core.schemas import BaseSettings
from fastid.core.schemas import ENV_PREFIX, BaseSettings


class AdminSettings(BaseSettings):
enabled: bool = True
email: str = "admin@fastid.com"
password: str = "admin"
favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png"
logo_url: str = "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"

model_config = SettingsConfigDict(env_prefix="admin_")
model_config = SettingsConfigDict(env_prefix=f"{ENV_PREFIX}admin_")


favicon_url = f"{main_settings.base_url}/static/assets/favicon.png"
logo_url = f"{main_settings.base_url}/static/assets/logo_text.png"
admin_settings = AdminSettings(favicon_url=favicon_url, logo_url=logo_url)
admin_settings = AdminSettings()
32 changes: 17 additions & 15 deletions fastid/api/app.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
from fastid.api.config import api_settings
from fastid.api.factory import APIAppFactory
from fastid.core.base import Plugin
from fastid.core.config import cors_settings, main_settings
from fastid.core.config import branding_settings, core_settings
from fastid.database.dependencies import engine
from fastid.plugins.obs.config import obs_settings
from fastid.plugins.obs.metrics import MetricsPlugin
from fastid.plugins.obs.tracing import TracingPlugin
from fastid.plugins.observability.config import observability_settings
from fastid.plugins.observability.metrics import MetricsPlugin
from fastid.plugins.observability.tracing import TracingPlugin

plugins: list[Plugin] = []

# Must be last plugin
if obs_settings.enabled:
metrics_plugin = MetricsPlugin(app_name=main_settings.discovery_name)
# Must be last plugins
if observability_settings.metrics_enabled:
metrics_plugin = MetricsPlugin(app_name=branding_settings.service_name)
plugins.append(metrics_plugin)
if observability_settings.tracing_enabled:
tracing_plugin = TracingPlugin(
app_name=main_settings.discovery_name,
export_url=obs_settings.tempo_url,
app_name=branding_settings.service_name,
export_url=observability_settings.tempo_url,
instrument=["logger", "httpx", "sqlalchemy"],
engine=engine,
)
plugins += [metrics_plugin, tracing_plugin]
plugins.append(tracing_plugin)

api_app = APIAppFactory(
title=main_settings.title,
version=main_settings.version,
base_url=main_settings.api_path,
allow_origins=cors_settings.origins,
allow_origin_regex=cors_settings.origin_regex,
title=branding_settings.api_swagger_title,
base_url=core_settings.api_path,
allow_origins=api_settings.cors_origins,
allow_origin_regex=api_settings.cors_origin_regex,
plugins=plugins,
).create()
11 changes: 11 additions & 0 deletions fastid/api/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from collections.abc import Sequence

from fastid.core.schemas import BaseSettings


class APISettings(BaseSettings):
cors_origins: Sequence[str] = ("*",)
cors_origin_regex: str | None = None


api_settings = APISettings()
File renamed without changes.
Loading
Loading