Русский | English |
Хражевник
Кеш-прокси, зеркало и хостинг linux-репозиториев
Название — словослияние «Хранить» + «Отражать»: корни «Хран» и «раж» склеены на общем слоге «-ра-».
Хражевник представляет собой автономный сервер кеширования и
зеркалирования пакетных репозиториев Linux. Go-бинарник и встроенная
веб-админка (Vue 3) объединены в одном исполняемом файле; хранилище
объектов — локальный каталог или любое S3-совместимое, каталог —
SQLite, PostgreSQL или MariaDB, выбор — TOML-конфигом без пересборки.
Первичная дистрибуция — OCI-контейнер из scratch (non-root,
read-only rootfs) под rootless podman quadlet.
Прозрачность для клиентов. Метаданные upstream отдаются побайтово — ни байта переписывания: подписи и чексуммы остаются валидными, keyring клиентов не меняется:
# /etc/apt/sources.list.d/khrazhevnik.list deb http://<хражевник>:29202/apt/debian stable mainХражевник лишь добавляет сверху кеш (пакеты — навсегда, индексы — ревалидация по ETag/Last-Modified), фоновые зеркала и личные подписанные репозитории пользователей.
Скриншоты
Веб-админка (/ui/).
Дашборд — статистика кеша и живые задачи:
Репозитории — список, upload, reindex:
Содержание
- Скриншоты
- Возможности
- Производительность
- Быстрый старт
- Конфигурация
- Развёртывание и безопасность
- API и веб-админка
- Структура проекта
- Технологический стек
- Разработка
- Планы
- Лицензия
Расширенная документация по развёртыванию, конфигурации, REST API, клиентам экосистем и личным репозиториям приведена в каталоге
docs/func/ru/. Настоящий файл содержит обзор системы и инструкцию по первоначальному запуску.
Проект разработан в соответствии с заранее определённой архитектурой; при подготовке исходного кода использовался ИИ-ассистент.1
Возможности
Кеш-прокси
- Прозрачный pull-through для поддерживаемых пакетных менеджеров:
первый запрос к объекту уходит upstream, ответ складывается в кеш и
дальше отдаётся локально (
X-Cache: HIT) - Классификация объектов по правилам экосистемы: пакеты и
content-addressed объекты — immutable (навсегда); индексы (
dists/,repodata/,APKINDEX,{repo}.db, narinfo) — mutable с conditional revalidate по ETag/Last-Modified и TTL -
stale-if-error(опционально) и отрицательное кеширование 404/5xx в памяти: upstream-отказ не транслируется клиентам - Singleflight на ключ: параллельные запросы одного объекта не бьют в
upstream; лимит размера кешируемого объекта (
cache.max_object_size)
Зеркало
- Полная локальная копия upstream-репозитория (
mode = mirror): фоновый sync с worker pool, retry и bandwidth-лимитом (token-bucket) - Идемпотентный resume: каждый запуск сравнивает перечень upstream с
имеющимися объектами и докачивает недостающее; прогресс — в каталоге
(
files=N;bytes=M), состояние задачи переживает рестарт - Планировщик per-remote:
sync_interval± случайный jitter; ручной запуск — через API или веб-админку (409 на дубль, 429 на лимит воркеров) - Include-фильтры: apt — dists и компоненты (
stable,stable/main); pacman —repo/arch; apk — архитектуры
Личные репозитории
- Upload пакетов пользователями: стрим прямо в хранилище с обязательным
Content-Lengthи сверкой байтов на лету (abort чиститtmp/) - RBAC: admin — везде; владелец репо и scoped-токен
repo:<id>:write— upload/delete/reindex/list; чтение публичное без auth - Квоты
bytes/filesна репозиторий, лимит одного объекта, перезапись существующего ключа — 409 (force— только админ, с аудитом) - Генерация индексов фоновой задачей reindex (apt:
Packages+.gz,by-hash/SHA256/*,Release) - Подпись ключом инстанса (OpenPGP ed25519):
InRelease(cleartext) иRelease.gpg(detached); для nix — переподпись narinfo (заменяется только полеSig, остальное байт-точно); при недоступном подписчике репо работают без подписи - Публичный ключ —
GET /repo/<name>/key.ascна публичном порту
Экосистемы
| Экосистема | Клиенты | URL-префикс | Зеркало |
|---|---|---|---|
| apt | Debian, Ubuntu | /apt/ |
да (+ include-фильтр) |
| rpm-md | dnf, Zypper | /rpm/ |
да |
| pacman | Arch Linux | /pacman/ |
да (+ include-фильтр) |
| apk | Alpine Linux | /apk/ |
да (+ include-фильтр) |
| nix | binary cache | /nix/ |
нет — только pull-through «по использованию» |
Парсеры метаданных всех экосистем (deb822, repomd/primary XML, tar.zst
{repo}.db, APKINDEX.tar.gz, narinfo) — streaming, с декомпресс-лимитом
и фаззингом с первого адаптера.
Интерфейсы и безопасность
- Два слушателя: публика
:29202(раздача пакетов без auth +/healthz), админка:30202(/api/v1,/metrics, SPA/ui). Дефолт:30202— все интерфейсы (старт пишет warning в лог); loopback обеспечивает quadlet (PublishPort=127.0.0.1:30202:30202) - Веб-админка (Vue 3, русский/английский): дашборд со статистикой кеша и
живыми задачами, remotes, репозитории с upload/reindex, пользователи и
scoped-токены, аудит-журнал, публичные ключи с готовыми строками
клиентов (
signed-by=…,rpm --import,pacman-key --add, …) - Аутентификация: JWT-сессии (bcrypt, rate-limit 10/min на логин,
отзыв при logout) и scoped API-токены (хранится только sha256,
показывается один раз); роль и
token_versionсверяются с БД на каждом запросе - Аудит всех мутаций (actor/action/object/result/detail) с keyset-пагинацией
- Метрики Prometheus (
/metrics, за auth): hits/misses/stale/negative по экосистемам, байты от upstream/клиентам, гистограммы длительности и размеров объектов - Единая точка path-traversal для всех путей из запросов
Подробное описание приведено в docs/func/ru/.
Производительность
Замер живого homelab-инстанса (S3 + PostgreSQL на отдельном NAS, ВМ
4 vCPU / 8 GB; методика и полные таблицы — в
docs/func/ru/benchmarks.md, сценарии —
в bench/):
| Метрика | Значение |
|---|---|
| CPU инстанса при насыщении | ≤1.6% (из 4 vCPU) при ~340 запросах/с и ~300 МБ/с отдачи |
| RAM | 26 МБ в idle; пик 981 МБ (memory.peak cgroup) под максимальной нагрузкой |
| Надёжность | 0 ответов 5xx за 269 ГБ отдачи (~150 тыс. запросов, все прогоны) |
| apt-клиенты | 50 параллельных машин: полный apt update + установка ~5 с/машину; 200 машин — упор в сетевой тракт (~2.4 Гбит/с) |
Узкое место профиля — read-path S3-хранилища (RustFS: 85–91% CPU при насыщении, сублинейный масштаб стримов), а не Хражевник: каталог на PostgreSQL при сотнях тысяч SELECT держит ≤1.8% CPU.
Быстрый старт
Требования
| Компонент | Версия | Назначение |
|---|---|---|
| podman | 4+ | Rootless-контейнер, quadlet |
| systemd --user | — | Генератор quadlet |
| OpenSSL | — | Генерация JWT-секрета |
| Go | 1.26+ | Сборка из исходников (опционально) |
| Node.js | 22+ | Сборка Web UI (опционально) |
Установка (контейнер)
Образ публикуется в реестрах-зеркалах (содержимое идентично):
docker pull git.alexrus1234.ru/alexrus1234/khrazhevnik:latest # основной
docker pull ghcr.io/alexrus1234/khrazhevnik:latest
docker pull docker.io/alexrus1234/khrazhevnik:latest
Полный список реестров (codeberg.org, quay.io) — в
docs/func/ru/deploy.md.
# 1. Quadlet — в пользовательский путь генератора systemd.
mkdir -p ~/.config/containers/systemd
cp deploy/quadlet/khrazhevnik.container ~/.config/containers/systemd/
# 2. JWT-секрет — Podman Secret (не светится в env и `systemctl show`).
podman secret create jwt-secret "$(openssl rand -hex 32)"
# 3. Каталог данных: контейнер работает под UID 65534 (nobody).
sudo mkdir -p /var/lib/khrazhevnik
sudo chown 65534:65534 /var/lib/khrazhevnik
# 4. Старт.
systemctl --user daemon-reload
systemctl --user start khrazhevnik.service
curl -s http://localhost:29202/healthz # → ok
Bootstrap и первый remote
При пустой таблице users веб-админка http://127.0.0.1:30202/ui/
сама предложит создать первого админа; далее upstream'ы и личные репо
настраиваются из UI. Те же шаги через API:
# Первый админ (один раз, пока таблица users пуста).
curl -s -X POST http://127.0.0.1:30202/api/v1/setup \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<пароль>"}'
# Логин → JWT; регистрируем кеш-прокси Debian.
TOKEN=$(curl -s -X POST http://127.0.0.1:30202/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<пароль>"}' | jq -r .token)
curl -s -X POST http://127.0.0.1:30202/api/v1/remotes \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"debian","ecosystem":"apt","base_url":"https://deb.debian.org/debian","mode":"proxy","enabled":true}'
Headless-альтернатива для init-скриптов (без поднятия сервера):
podman exec khrazhevnik /khrazhevnik -add-remote apt/debian=https://deb.debian.org/debian
Настройка клиента
На любой Debian/Ubuntu-машине укажите Хражевник вместо upstream:
echo 'deb http://<хражевник>:29202/apt/debian stable main' \
> /etc/apt/sources.list.d/khrazhevnik.list
apt-get update && apt-get install hello
Подписи и чексуммы валидны: метаданные upstream отдаются побайтово.
Проверка кеша: повторный запрос к dists/…/Packages.gz возвращает
X-Cache: HIT. Клиенты dnf/zypper, pacman, apk и nix — в
docs/func/ru/ecosystems/.
Сборка из исходников
# Web UI и серверная часть в порядке, используемом CI:
make web-build
make build # исполняемый файл bin/khrazhevnik
CGO не требуется (SQLite — modernc.org/sqlite), исполняемый файл
статический. Сборка для рабочей среды (как в CI):
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags="-s -w \
-X main.Version=1.0.0" \
-o khrazhevnik ./cmd/khrazhevnik
Для package main линкер принимает только
-X main.Version=…; полный import path (khrazhevnik/cmd/khrazhevnik.Version) молча не применяется — версия останетсяdev(как в CI и Containerfile).
Релизные бинарники (
khrazhevnik-<version>-linux-amd64+.sha256) и OCI-образ публикуются вручную из CI в Packages и Releases подключённых реестров. Для разработки — сборка из исходного кода.
Пошаговое руководство — в docs/func/ru/quickstart.md.
Конфигурация
khrazhevnik.toml (флаг -config; пустое значение — только
defaults + env):
[server]
public_listen = ":29202" # раздача пакетов + /healthz
admin_listen = ":30202" # /api/v1, /metrics, /ui
[storage]
driver = "fs" # fs | s3
[storage.fs]
path = "/var/lib/khrazhevnik/store"
[storage.s3]
endpoint = "" ; region = "" ; bucket = "" ; path_style = true
[database]
driver = "sqlite" # sqlite | postgres | mariadb
dsn = "/var/lib/khrazhevnik/khrazhevnik.db"
[auth]
jwt_secret = "" # НЕ в проде-файле: env/file (обязателен)
session_ttl = "8h"
setup_token = "" # опц. защита bootstrap первого админа
[cache]
stale_if_error = true
max_object_size = "20GiB"
negative_ttl_404 = "5m" ; negative_ttl_5xx = "30s"
[mirror]
workers = 4 ; interval_jitter = "10m"
[publish]
max_object_size = "1GiB" # лимит одного загружаемого объекта
default_quota_bytes = "5GiB" # квота нового репо (0 = без лимита)
default_quota_files = 10000
[signing]
keys_dir = "/var/lib/khrazhevnik/keys" # ed25519-ключ инстанса
# passphrase = "" # опц.: env KHRZ_SIGNING__PASSPHRASE
[metrics]
enabled = true
[ecosystem.apt] # apt | rpm-md | pacman | apk | nix;
enabled = true # секция нужна только для переопределения
Слои применения: defaults → TOML → env. Env-префикс KHRZ_,
сегменты пути — через __, верхний регистр: KHRZ_AUTH__JWT_SECRET,
KHRZ_STORAGE__S3__SECRET_ACCESS_KEY, KHRZ_ECOSYSTEM__RPM_MD__ENABLED.
Значения вида file:///run/secrets/x (env или TOML) читаются из файла —
поддержка quadlet Secret. Валидация fail-fast со списком всех проблем
сразу. Полная схема — в docs/func/ru/config.md.
Развёртывание и безопасность
Основная дистрибуция — OCI-образ из scratch: бинарник + CA-bundle,
USER 65534:65534, read-only rootfs, writable — только volume
/var/lib/khrazhevnik (SQLite, fs-store, ключи подписи). Бинарник —
PID 1, сабпроцессов нет (OpenPGP in-process), зумби-реапер не нужен;
graceful shutdown: SIGTERM → HTTP 5с → фоновые задачи 30с.
| Порт | Доступ | Назначение |
|---|---|---|
| 29202 | публичный | раздача пакетов (/<eco>/<remote>/<путь>, /repo/<name>/*), /healthz
|
| 30202 | все интерфейсы (дефолт; warn при старте — loopback через PublishPort quadlet'а) | админ-API /api/v1, /metrics, веб-админка /ui
|
Rootless-режим: оба порта ≥1024; публикация на 80/443 — через
reverse-proxy на хосте (Caddy/Traefik/nginx —
docs/func/ru/reverse-proxy.md).
AutoUpdate=registry в quadlet включает
автообновление образа через podman auto-update. Дефолт (fs + sqlite) —
для homelab; для прода — S3 + postgres/mariadb (пример quadlet —
deploy/quadlet/khrazhevnik-s3.container, рекомендации — в
docs/func/ru/storage-db.md).
Альтернатива контейнеру — голый бинарник под systemd или любым
процесс-супервизором; JWT-секрет передаётся env
KHRZ_AUTH__JWT_SECRET=file:///run/secrets/jwt-secret.
Полное руководство по развёртыванию — в
docs/func/ru/deploy.md.
API и веб-админка
| Интерфейс | Кратко | Подробности |
|---|---|---|
| REST API |
/api/v1: setup/login, remotes, repos (+objects/perms/reindex), users, api-tokens, tasks, cache/stats, audit; ошибки — {"error":"snake_case"}
|
docs/func/ru/api.md |
| Веб-админка |
/ui/ на админском порту; RU/EN; встроена в бинарник (go:embed) |
docs/func/ru/ui.md |
| Метрики |
/metrics (Prometheus exposition, за auth) |
docs/func/ru/api.md |
| Личные репо | upload по токену, reindex, подпись, настройка клиентов | docs/func/ru/personal-repos.md |
Аутентификация админ-API: Authorization: Bearer <jwt> (браузер) или
scoped API-токен Bearer khz_... (CI-скрипты: admin,
repo:<id>:write).
Структура проекта
.
├── cmd/khrazhevnik/ # Точка входа: main.go (~40 строк), wire.go —
│ # единственная склейка (compile-time реестр)
├── internal/
│ ├── core/
│ │ ├── port/ # Контракты: Storage, Ecosystem, Catalog*, Signer, Clock, Rand, HTTP
│ │ ├── domain/ # Модели + типизированные ошибки (только stdlib)
│ │ ├── config/ # Слои: defaults → TOML → env KHRZ_* (+file://-секреты)
│ │ ├── dbtalk/ # Шим SQL-диалектов каталога (placeholder/upsert)
│ │ ├── engine/ # Usecase-логика: cache, mirror, publish, auth
│ │ ├── registry/ # Compile-time реестр модулей
│ │ └── web/ # chi-роутеры, middleware, TaskRegistry, embed SPA
│ ├── mod/ # Модули (регистрируются в init()): ecosystem/
│ │ # {apt, rpmmmd, pacman, apk, nix}, storage/{fs, s3},
│ │ # db/{sqlite, postgres, mariadb}, sign/{openpgp, ed25519}
│ ├── testutil/ # Общие test doubles (FixedClock, FakeStorage, …)
│ └── contract/ # Контрактные suite каталога/хранилища (integration)
├── migrations/<driver>/ # Embedded goose-миграции (по каталогу на СУБД)
├── web/ # Vue 3 + Vite + TypeScript SPA (бандл → core/web/assets)
├── deploy/ # Containerfile (node → golang → scratch) + quadlet/
├── bench/ # Нагрузочные сценарии k6 + сэмплеры CPU/RAM (методика — docs/func/ru/benchmarks.md)
├── test/ # integration/ (in-process + binary-smoke), smoke/
├── docs/ # ARCHITECTURE/SPECIFICATION/TESTING/ROADMAP/HISTORY; func/ru/
└── .forgejo/workflows/ # CI: сборка, тесты, e2e, OCI
Бизнес-логика (core/domain, core/engine) не импортирует os,
syscall, net, net/http и конкретные модули — весь ввод-вывод через
интерфейсы core/port; ядро и модули не знают друг о друге, склейка —
только в wire.go. Правила проверяются линтером depguard.
Технологический стек
Бэкенд: Go 1.26 · chi v5 · pelletier/go-toml/v2 · modernc.org/sqlite
(без CGO) · jackc/pgx/v5 · go-sql-driver/mysql · pressly/goose/v3 ·
golang-jwt/jwt/v5 · golang.org/x/crypto (bcrypt) · golang.org/x/sync
(singleflight) · golang.org/x/time (rate) · ProtonMail/go-crypto
(OpenPGP) · minio/minio-go/v7 · klauspost/compress (zstd) ·
prometheus/client_golang · log/slog · go:embed.
Фронтенд: Vue 3 (Composition API) · vue-router 4 · Vite 7 · TypeScript 5.9 (vue-tsc).
Инфраструктура и качество: Forgejo Actions (CI) · golangci-lint
(строгий конфиг, depguard слоёв) · go vet / gofmt ·
unit- / integration- / binary-smoke-уровни тестов · фаззинг парсеров
чужих форматов · Playwright E2E (опционально) · podman (OCI из scratch,
multi-arch).
Разработка
| Команда | Назначение |
|---|---|
make lint |
golangci-lint run ./... (строгий конфиг) |
make vet |
go vet ./... |
make test |
go test ./... |
make test-race |
go test -race ./... |
make test-integration |
go test -race -tags integration ./test/integration/... |
make cover |
Отчёт о покрытии |
make build |
Сборка bin/khrazhevnik
|
make web-build |
Vite-сборка Web UI в internal/core/web/assets/
|
make web-dev |
Dev-сервер Vite с прокси /api на :30202
|
make image |
OCI-образ из deploy/Containerfile (PLATFORMS, TAG) |
make smoke |
Дымовой тест живого контейнера (локально, перед релизом) |
Нагрузочное тестирование живого инстанса (k6-сценарии, сэмплеры
CPU/RAM, методика) — в bench/, эталонные
результаты — в docs/func/ru/benchmarks.md.
| make clean | Удалить bin/, coverage/, восстановить заглушку web-assets |
При новом клонировании репозитория сначала срабатывает заглушка
web-assets (Go-команды собираются без фронтенда); реальный бандл —
make web-build. Все тесты прогоняются в CI (Forgejo Actions):
контрактные suite на postgres/mariadb/minio, binary-smoke собранного
артефакта, опциональные -race и Playwright E2E.
Документация для разработчиков (порядок чтения перед правками):
- docs/ARCHITECTURE.md — слои, правила импортов, инварианты движков.
- docs/SPECIFICATION.md — требования, REST API, схема БД.
- docs/TESTING.md — стратегия тестирования.
- docs/ROADMAP.md — план (путеводитель); история этапов — docs/HISTORY.md.
Планы
Ближайшее после релиза v1.0.0 — расширение экосистем: XBPS и pkg (модель «каталог + индекс» повторяет уже решённые задачи), затем Guix (протокол nix), Flatpak — последним. Дальше — без фиксированного порядка: автономный офлайн-экспорт зеркала, федерация инстансов, eviction и чистка кеша, OIDC/OAuth2, уведомления, CLI (khzr-cli), глобальный поиск пакетов.
Полный путеводитель с деталями и границами дизайна — docs/ROADMAP.md; история завершённых этапов — docs/HISTORY.md.
Лицензия
Проект распространяется под лицензией GNU Affero General Public License v3.0 или более поздней.
Хражевник — кеш-прокси и зеркало linux-репозиториев
Copyright (C) 2026 AlexRus1234
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
-
Разработка исходного кода выполнялась с использованием ИИ-ассистента в соответствии с заранее определённой архитектурой проекта; архитектурные решения, проверка результатов и итоговая интеграция осуществлялись автором.
↩

