Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeddc1f168
|
||
|
|
045b2426b3
|
||
|
|
58519f1413
|
||
|
|
1fcc189afc
|
||
|
|
16ab872ba4
|
||
|
|
85ebeb4197
|
||
|
|
a09f3c629e
|
||
|
|
90bdb4a3ea
|
||
|
|
88a53076e9
|
||
|
|
4b6d5cdc85
|
||
|
|
b794eea87a
|
||
|
|
9acf687530
|
@@ -875,16 +875,18 @@ session:
|
||||
## The inactivity time before the session is reset. If expiration is set to 1h, and this is set to 5m, if the user
|
||||
## does not select the remember me option their session will get destroyed after 1h, or after 5m since the last
|
||||
## time Authelia detected user activity.
|
||||
# inactivity: '5 minutes'
|
||||
## Дефолты (5 минут / 1 час) для личного сервера слишком агрессивны: любая пауза в работе
|
||||
## заставляет логиниться заново. Держим сессию живой неделю простоя.
|
||||
inactivity: "1 week"
|
||||
|
||||
## The time before the session cookie expires and the session is destroyed if remember me IS NOT selected by the
|
||||
## user.
|
||||
# expiration: '1 hour'
|
||||
expiration: "1 month"
|
||||
|
||||
## The time before the cookie expires and the session is destroyed if remember me IS selected by the user. Setting
|
||||
## this value to -1 disables remember me for this session cookie domain. If allowed and the user uses the remember
|
||||
## me checkbox this overrides the expiration option and disables the inactivity option.
|
||||
# remember_me: '1 month'
|
||||
remember_me: "3 months"
|
||||
|
||||
## Cookie Session Domain default 'name' value.
|
||||
# name: 'authelia_session'
|
||||
@@ -1370,13 +1372,17 @@ identity_providers:
|
||||
|
||||
## The lifespans configure the expiration for these token types in the duration common syntax. In addition to this
|
||||
## syntax the lifespans can be customized per-client.
|
||||
# lifespans:
|
||||
## Configures the default/fallback lifespan for given token types. This behaviour applies to all clients and all
|
||||
## grant types but you can override this behaviour using the custom lifespans.
|
||||
# access_token: '1 hour'
|
||||
# authorize_code: '1 minute'
|
||||
# id_token: '1 hour'
|
||||
# refresh_token: '90 minutes'
|
||||
## Дефолты: access_token '1 hour', authorize_code '1 minute', id_token '1 hour', refresh_token '90 minutes'.
|
||||
##
|
||||
## Outline раз в час (после каждого auth.info, если lastSignedInAt старше часа) запускает
|
||||
## ValidateSSOAccessTask: обновляет access token по refresh token и дёргает userinfo. Если обновить
|
||||
## не удалось — ротирует jwtSecret пользователя и убивает ВСЕ свои сессии, то есть выкидывает на
|
||||
## повторный вход. С дефолтным refresh token в 90 минут это происходит почти при каждом визите.
|
||||
lifespans:
|
||||
custom:
|
||||
outline:
|
||||
access_token: "1 hour"
|
||||
refresh_token: "3 months"
|
||||
|
||||
## Cross-Origin Resource Sharing (CORS) settings.
|
||||
# cors:
|
||||
@@ -1486,20 +1492,25 @@ identity_providers:
|
||||
# audience: []
|
||||
|
||||
## Scopes this client is allowed to request.
|
||||
## offline_access нужен, чтобы Authelia выдала refresh token: без него Outline не может
|
||||
## продлить access token и убивает свою сессию (см. кастомный lifespan 'outline' выше).
|
||||
scopes:
|
||||
- "openid"
|
||||
- "profile"
|
||||
- "email"
|
||||
- "offline_access"
|
||||
|
||||
## Grant Types configures which grants this client can obtain.
|
||||
## It's not recommended to define this unless you know what you're doing.
|
||||
# grant_types:
|
||||
# - 'authorization_code'
|
||||
grant_types:
|
||||
- "authorization_code"
|
||||
- "refresh_token"
|
||||
|
||||
## Response Types configures which responses this client can be sent.
|
||||
## It's not recommended to define this unless you know what you're doing.
|
||||
# response_types:
|
||||
# - 'code'
|
||||
## Задан явно: без этого Authelia ругается на scope 'offline_access'.
|
||||
response_types:
|
||||
- "code"
|
||||
|
||||
## Response Modes configures which response modes this client supports.
|
||||
# response_modes:
|
||||
@@ -1512,14 +1523,16 @@ identity_providers:
|
||||
|
||||
## The custom lifespan name to use for this client. This must be configured independent of the client before
|
||||
## utilization. Custom lifespans are reusable similar to authorization policies.
|
||||
# lifespan: ''
|
||||
lifespan: "outline"
|
||||
|
||||
## The consent mode controls how consent is obtained.
|
||||
# consent_mode: 'auto'
|
||||
## 'pre-configured' — согласие запоминается на pre_configured_consent_duration, поэтому при
|
||||
## повторном входе экран согласия не показывается и редирект проходит незаметно.
|
||||
consent_mode: "pre-configured"
|
||||
|
||||
## This value controls the duration a consent on this client remains remembered when the consent mode is
|
||||
## configured as 'auto' or 'pre-configured' in the duration common syntax.
|
||||
# pre_configured_consent_duration: '1 week'
|
||||
pre_configured_consent_duration: "3 months"
|
||||
|
||||
## Requires the use of Pushed Authorization Requests for this client when set to true.
|
||||
# require_pushed_authorization_requests: false
|
||||
|
||||
@@ -16,16 +16,21 @@
|
||||
thinking-mode gating, корректные reasoning-поля для v4-моделей — issue #3139):
|
||||
не нужны ни custom_provider_config, ни base_url (дефолт api.deepseek.com).
|
||||
|
||||
Роутинг — декларативно через governance.routing_rules (требует включённого
|
||||
config_store). tududi ходит на drop-in эндпоинт /openai и шлёт ЗАХАРДКОЖЕННОЕ
|
||||
имя модели gpt-4o-mini «голым». Правило ловит его по cel и подменяет на
|
||||
реальную модель deepseek/deepseek-v4-pro. Фолбэка нет — единственный провайдер.
|
||||
Чтобы сменить бэкенд-модель, правим ТОЛЬКО этот файл, конфиг tududi не трогаем.
|
||||
Роутинг правилами больше не нужен: клиенты шлют имя модели с префиксом
|
||||
провайдера (tududi — deepseek/deepseek-v4-pro на drop-in эндпоинт /openai),
|
||||
Bifrost резолвит провайдера сам. Раньше здесь жило правило, подменявшее
|
||||
захардкоженное у tududi gpt-4o-mini; с tududi 1.3.1 модель задаётся переменной.
|
||||
|
||||
routing_rules оставлен ПУСТЫМ МАССИВОМ, а не удалён: при source_of_truth=config.json
|
||||
авторитетно перезаписываются только присутствующие секции, а отсутствующие
|
||||
оставляют строки в config.db нетронутыми — удали ключ, и старое правило
|
||||
продолжило бы жить в БД (config.go, isGovernanceSectionPresent).
|
||||
|
||||
Эти Jinja-комментарии в готовый config.json не попадают (на выходе чистый JSON). #}
|
||||
{
|
||||
"$schema": "https://www.getbifrost.ai/schema",
|
||||
"encryption_key": {{ bifrost_encryption_key | to_json }},
|
||||
"source_of_truth": "config.json",
|
||||
"client": {
|
||||
"enable_logging": true
|
||||
},
|
||||
@@ -40,8 +45,7 @@
|
||||
"config_store": {
|
||||
"enabled": true,
|
||||
"type": "sqlite",
|
||||
"config": { "path": "/app/store/config.db" },
|
||||
"source_of_truth": "config.json"
|
||||
"config": { "path": "/app/store/config.db" }
|
||||
},
|
||||
"logs_store": {
|
||||
"enabled": true,
|
||||
@@ -49,18 +53,6 @@
|
||||
"config": { "path": "/app/store/logs.db" }
|
||||
},
|
||||
"governance": {
|
||||
"routing_rules": [
|
||||
{
|
||||
"id": "tududi-gpt4omini-to-deepseek-pro",
|
||||
"name": "Tududi gpt-4o-mini -> DeepSeek v4 Pro",
|
||||
"enabled": true,
|
||||
"cel_expression": "model == 'gpt-4o-mini'",
|
||||
"targets": [
|
||||
{ "provider": "deepseek", "model": "deepseek-v4-pro", "weight": 1 }
|
||||
],
|
||||
"scope": "global",
|
||||
"priority": 0
|
||||
}
|
||||
]
|
||||
"routing_rules": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
# Клиенты ходят в него по имени bifrost_app:8080 в общей web_proxy_network и про
|
||||
# провайдера/модель ничего не знают — роутинг решает config.json.
|
||||
# See versions: https://hub.docker.com/r/maximhq/bifrost/tags
|
||||
# Тег образа = GitHub-релиз transports/vX.Y.Z (там же changelog и миграции БД);
|
||||
# теги 2.1.x в том же списке — это helm-chart, не образ. Конфиг после апдейта
|
||||
# валидировать по transports/config.schema.json из нужного тега: Bifrost на Go
|
||||
# молча игнорирует ключ, положенный не в ту секцию, в логах этого не видно.
|
||||
#
|
||||
# В контейнере Bifrost слушает 8080 (дефолт). Наружу host-порт НЕ пробрасываем:
|
||||
# веб-UI (Dashboard + Logs) и API доступны только через Caddy
|
||||
@@ -15,7 +19,7 @@
|
||||
services:
|
||||
|
||||
bifrost_app:
|
||||
image: maximhq/bifrost:v1.6.3 # проверь актуальный стабильный тег в releases
|
||||
image: maximhq/bifrost:v1.6.8 # проверь актуальный стабильный тег в releases
|
||||
container_name: bifrost_app
|
||||
restart: unless-stopped
|
||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM allinurl/goaccess:1.10.2
|
||||
FROM allinurl/goaccess:1.11
|
||||
|
||||
RUN apk add --no-cache jq
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
goaccess_processor:
|
||||
build: .
|
||||
image: local/goaccess-jq:1.10.2
|
||||
image: local/goaccess-jq:1.11
|
||||
container_name: goaccess_processor
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
services:
|
||||
miniflux_app:
|
||||
image: miniflux/miniflux:2.3.2
|
||||
image: miniflux/miniflux:2.3.3
|
||||
container_name: miniflux_app
|
||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||
depends_on:
|
||||
|
||||
@@ -35,7 +35,9 @@ services:
|
||||
OIDC_USERINFO_URI: 'https://auth.vakhrushev.me/api/oidc/userinfo'
|
||||
OIDC_LOGOUT_URI: 'https://auth.vakhrushev.me/logout'
|
||||
OIDC_USERNAME_CLAIM: 'email'
|
||||
OIDC_SCOPES: 'openid profile email'
|
||||
# offline_access — чтобы Authelia выдала refresh token. Без него Outline не может продлить
|
||||
# access token, ValidateSSOAccessTask признаёт вход невалидным и сбрасывает все сессии.
|
||||
OIDC_SCOPES: 'openid profile email offline_access'
|
||||
OIDC_DISPLAY_NAME: 'Authelia'
|
||||
|
||||
SMTP_HOST: '{{ postbox_host }}'
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
services:
|
||||
|
||||
tududi_app:
|
||||
image: chrisvel/tududi:1.2.4
|
||||
image: chrisvel/tududi:1.3.1
|
||||
container_name: tududi_app
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
@@ -31,7 +31,9 @@ services:
|
||||
|
||||
- BASE_URL=https://tududi.vakhrushev.me
|
||||
- TUDUDI_ALLOWED_ORIGINS=https://tududi.vakhrushev.me
|
||||
- TUDUDI_TRUST_PROXY=true
|
||||
# Ровно один хоп (Caddy). `true` доверяет всей цепочке X-Forwarded-For и
|
||||
# позволяет подделать IP клиента; tududi с 1.3.x сам приводит его к 1 и ругается.
|
||||
- TUDUDI_TRUST_PROXY=1
|
||||
|
||||
- TUDUDI_SESSION_SECRET={{ tududi_session_secret }}
|
||||
# Чистый OIDC: сид-админ не создаётся (TUDUDI_USER_EMAIL/PASSWORD не заданы),
|
||||
@@ -67,11 +69,13 @@ services:
|
||||
- FF_ENABLE_MCP=true
|
||||
|
||||
# AI-фичи через Bifrost (drop-in OpenAI-эндпоинт в общей сети — НЕ localhost,
|
||||
# это сам контейнер). Модель у tududi захардкожена как gpt-4o-mini; Bifrost
|
||||
# по governance-правилу роутит её на deepseek-v4-pro. openai-node сам
|
||||
# подхватывает OPENAI_BASE_URL.
|
||||
- OPENAI_API_KEY={{ tududi_openai_api_key }}
|
||||
- OPENAI_BASE_URL=http://bifrost_app:8080/openai
|
||||
# это сам контейнер). С 1.3.1 модель задаётся переменной, а не захардкожена,
|
||||
# поэтому шлём deepseek/deepseek-v4-pro сразу: Bifrost резолвит провайдера по
|
||||
# префиксу, и подменять модель governance-правилом больше не нужно.
|
||||
# Пара LLM_* имеет приоритет над устаревшей OPENAI_*, значения те же.
|
||||
- LLM_API_KEY={{ tududi_openai_api_key }}
|
||||
- LLM_BASE_URL=http://bifrost_app:8080/openai
|
||||
- LLM_MODEL=deepseek/deepseek-v4-pro
|
||||
|
||||
networks:
|
||||
web_proxy_network:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
services:
|
||||
|
||||
tuwunel_app:
|
||||
image: jevolk/tuwunel:v1.8.1
|
||||
image: jevolk/tuwunel:v1.8.3
|
||||
container_name: tuwunel_app
|
||||
restart: unless-stopped
|
||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||
|
||||
+7
-7
@@ -12,11 +12,11 @@
|
||||
eget_install_path: '{{ (bin_prefix, "eget") | path_join }}'
|
||||
eget_packages_to: "{{ bin_prefix }}"
|
||||
eget_packages:
|
||||
- {repo: "rclone/rclone", tag: "v1.73.4", asset: "zip"}
|
||||
- {repo: "restic/restic", tag: "v0.18.1"}
|
||||
- {repo: "creativeprojects/resticprofile", tag: "v0.32.0", asset: "^no_self_update"}
|
||||
- {repo: "aristocratos/btop", tag: "v1.4.6"}
|
||||
- {repo: "gobackup/gobackup", tag: "v2.17.0"}
|
||||
- {repo: "go-task/task", tag: "v3.48.0", asset: "tar.gz"}
|
||||
- {repo: "rclone/rclone", tag: "v1.75.0", asset: "zip"}
|
||||
- {repo: "restic/restic", tag: "v0.19.1"}
|
||||
- {repo: "creativeprojects/resticprofile", tag: "v0.33.1", asset: "^no_self_update"}
|
||||
- {repo: "aristocratos/btop", tag: "v1.4.7"}
|
||||
- {repo: "gobackup/gobackup", tag: "v3.1.1"}
|
||||
- {repo: "go-task/task", tag: "v3.52.0", asset: "tar.gz"}
|
||||
- {repo: "bootandy/dust", tag: "v1.2.4", asset: "gnu"}
|
||||
- {repo: "zellij-org/zellij", tag: "v0.43.1", asset: "no-web"}
|
||||
- {repo: "zellij-org/zellij", tag: "v0.44.3", asset: "no-web"}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"""Invoke tasks — замена Taskfile.yml"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
@@ -11,7 +12,7 @@ from invoke.tasks import task
|
||||
|
||||
HOSTS_FILE = "production.yml"
|
||||
VARS_FILE = "group_vars/all/main.yml"
|
||||
AUTHELIA_DOCKER = "docker run --rm -v $PWD:/data authelia/authelia:4.39.4 authelia"
|
||||
AUTHELIA_COMPOSE_FILE = "files/authelia/docker-compose.template.yml"
|
||||
|
||||
|
||||
# --- Базовый набор: единый интерфейс команд во всех репозиториях ---
|
||||
@@ -33,6 +34,17 @@ def _remote_host() -> str:
|
||||
return _yq(".ungrouped.hosts.server.ansible_host")
|
||||
|
||||
|
||||
def _authelia_docker() -> str:
|
||||
"""Команда запуска authelia CLI на том же образе, что и задеплоенный сервис"""
|
||||
pattern = re.compile(r"""image:\s*["']?(\S*authelia/authelia:[^"'\s]+)""")
|
||||
with open(AUTHELIA_COMPOSE_FILE, encoding="utf-8") as compose:
|
||||
for line in compose:
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
return f"docker run --rm -v $PWD:/data {match.group(1)} authelia"
|
||||
raise Exit(f"Образ authelia не найден в {AUTHELIA_COMPOSE_FILE}", code=1)
|
||||
|
||||
|
||||
def _rest_args() -> list[str]:
|
||||
"""Возвращает аргументы после '--' из sys.argv"""
|
||||
try:
|
||||
@@ -247,13 +259,15 @@ def login_as_app(ctx: Context, app: str) -> None:
|
||||
@task
|
||||
def authelia_cli(ctx: Context, args: str = "") -> None:
|
||||
"""Запустить authelia CLI в docker"""
|
||||
ctx.run(f"{AUTHELIA_DOCKER} {args}")
|
||||
ctx.run(f"{_authelia_docker()} {args}")
|
||||
|
||||
|
||||
@task
|
||||
def authelia_validate_config(ctx: Context) -> None:
|
||||
"""Отрендерить конфиг authelia из шаблона и проверить его"""
|
||||
dest = "temp/configuration.yml"
|
||||
# temp/ в .gitignore, на свежем клоне его нет — ansible сам директорию не создаёт.
|
||||
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||
try:
|
||||
ctx.run(
|
||||
"uv run ansible localhost"
|
||||
@@ -262,7 +276,7 @@ def authelia_validate_config(ctx: Context) -> None:
|
||||
" --extra-vars @group_vars/all/secrets.yml"
|
||||
" --extra-vars @files/authelia/secrets.yml"
|
||||
)
|
||||
ctx.run(f"{AUTHELIA_DOCKER} validate-config --config /data/{dest}")
|
||||
ctx.run(f"{_authelia_docker()} validate-config --config /data/{dest}")
|
||||
finally:
|
||||
ctx.run(f"rm -f {dest}", warn=True)
|
||||
|
||||
@@ -270,14 +284,16 @@ def authelia_validate_config(ctx: Context) -> None:
|
||||
@task
|
||||
def authelia_gen_random_string(ctx: Context, length: int = 10) -> None:
|
||||
"""Сгенерировать случайную alphanumeric-строку"""
|
||||
ctx.run(f"{AUTHELIA_DOCKER} crypto rand --length {length} --charset alphanumeric")
|
||||
ctx.run(
|
||||
f"{_authelia_docker()} crypto rand --length {length} --charset alphanumeric"
|
||||
)
|
||||
|
||||
|
||||
@task
|
||||
def authelia_gen_secret_and_hash(ctx: Context, length: int = 72) -> None:
|
||||
"""Сгенерировать случайный секрет и его pbkdf2-sha512 хэш"""
|
||||
ctx.run(
|
||||
f"{AUTHELIA_DOCKER} crypto hash generate pbkdf2"
|
||||
f"{_authelia_docker()} crypto hash generate pbkdf2"
|
||||
f" --variant sha512 --random --random.length {length} --random.charset rfc3986"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user