No description
  • Go 93.2%
  • Vue 3.6%
  • TypeScript 2.2%
  • Shell 0.5%
  • Makefile 0.2%
  • Other 0.2%
Find a file
2026-08-29 19:39:37 +03:00
.forgejo/workflows ci: merged integration coverage 2026-08-29 13:12:49 +03:00
cmd/khrazhevnik auth: bcrypt cost and timing parity 2026-08-29 11:26:11 +03:00
deploy Containerfile: node stage for web 2026-08-26 22:27:38 +03:00
docs docs: interrupted-by-cancel in spec 2026-08-29 19:39:37 +03:00
internal docs: post-verification cleanup 2026-08-29 17:17:38 +03:00
migrations auth: persistent session revocation 2026-08-29 11:17:33 +03:00
test auth: revocations in integration harness 2026-08-29 12:19:11 +03:00
web web: error codes and recovery hardening 2026-08-28 16:54:41 +03:00
.dockerignore Containerfile: node stage for web 2026-08-26 22:27:38 +03:00
.gitattributes chore: enforce lf line endings via gitattributes 2026-08-22 17:24:33 +03:00
.gitignore web: vue spa scaffold 2026-08-26 22:18:51 +03:00
.golangci.yaml test: shared contract suites 2026-08-26 05:29:06 +03:00
AGENTS.md chore: forbid mistyped repo path in agent config 2026-08-29 09:24:59 +03:00
CHANGELOG.md docs: post-audit changelog and security notes 2026-08-29 13:30:34 +03:00
go.mod db: postgres and mariadb adapters, migrations 2026-08-26 05:30:07 +03:00
go.sum db: postgres and mariadb adapters, migrations 2026-08-26 05:30:07 +03:00
LICENSE init: scaffold, tooling, docs 2026-08-17 05:27:30 +03:00
Makefile ci: merged integration coverage 2026-08-29 13:12:49 +03:00
opencode.json chore: forbid mistyped repo path in agent config 2026-08-29 09:24:59 +03:00
README.md docs: readme по образцу проектов (обзор, quickstart, конфигурация) 2026-08-27 18:34:19 +03:00
SECURITY.md docs: post-audit changelog and security notes 2026-08-29 13:30:34 +03:00

Хражевник

Кеш-прокси, зеркало и хостинг linux-репозиториев

Хражевник представляет собой автономный сервер кеширования и зеркалирования пакетных репозиториев Linux. Go-бинарник и встроенная веб-админка (Vue 3) объединены в одном исполняемом файле; хранилище объектов — локальный каталог или любое S3-совместимое, каталог — SQLite, PostgreSQL или MariaDB, выбор — TOML-конфигом без пересборки. Первичная дистрибуция — OCI-контейнер из scratch (non-root, read-only rootfs) под rootless podman quadlet.

License: AGPL-3.0 Go Vue Vite Platform CI

Прозрачность для клиентов. Метаданные upstream отдаются побайтово — ни байта переписывания: подписи и чексуммы остаются валидными, keyring клиентов не меняется:

# /etc/apt/sources.list.d/khrazhevnik.list
deb http://<хражевник>:29202/apt/debian stable main

Хражевник лишь добавляет сверху кеш (пакеты — навсегда, индексы — ревалидация по ETag/Last-Modified), фоновые зеркала и личные подписанные репозитории пользователей.


Содержание

Расширенная документация по развёртыванию, конфигурации, 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) — по умолчанию на loopback
  • Веб-админка (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/.


Быстрый старт

Требования

Компонент Версия Назначение
podman 4+ Rootless-контейнер, quadlet
systemd --user Генератор quadlet
OpenSSL Генерация JWT-секрета
Go 1.26+ Сборка из исходников (опционально)
Node.js 22+ Сборка Web UI (опционально)

Установка (контейнер)

# 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 khrazhevnik/cmd/khrazhevnik.Version=1.0.0" \
  -o khrazhevnik ./cmd/khrazhevnik

Релизные бинарники (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]
mutable_ttl     = "5m"          # индексы: revalidate по ETag/Last-Modified
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 127.0.0.1 админ-API /api/v1, /metrics, веб-админка /ui

Rootless-режим: оба порта ≥1024; публикация на 80/443 — через reverse-proxy на хосте. 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, …)
├── migrations/<driver>/      # Embedded goose-миграции (по каталогу на СУБД)
├── web/                      # Vue 3 + Vite + TypeScript SPA (бандл → core/web/assets)
├── deploy/                   # Containerfile (multi-stage → scratch) + quadlet/
├── test/                     # integration/ (in-process + binary-smoke), smoke/
├── docs/                     # ARCHITECTURE/SPECIFICATION/TESTING/ROADMAP; 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 Дымовой тест живого контейнера (локально, перед релизом)
make clean Удалить bin/, coverage/, восстановить заглушку web-assets

При новом клонировании репозитория сначала срабатывает заглушка web-assets (Go-команды собираются без фронтенда); реальный бандл — make web-build. Все тесты прогоняются в CI (Forgejo Actions): контрактные suite на postgres/mariadb/minio, binary-smoke собранного артефакта, опциональные -race и Playwright E2E.

Документация для разработчиков (порядок чтения перед правками):

  1. docs/ARCHITECTURE.md — слои, правила импортов, инварианты движков.
  2. docs/SPECIFICATION.md — требования, REST API, схема БД.
  3. docs/TESTING.md — стратегия тестирования.
  4. docs/ROADMAP.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.

  1. Разработка исходного кода выполнялась с использованием ИИ-ассистента в соответствии с заранее определённой архитектурой проекта; архитектурные решения, проверка результатов и итоговая интеграция осуществлялись автором. ↩︎