8 Commits
v0.5.0 ... main

Author SHA1 Message Date
aa1602efde docs: clean up CHANGELOG.md — remove 40 duplicate section blocks and RU mirror (#122) 2026-04-12 00:56:09 +03:00
b3239ed2d7 chore: DX improvements — typos, blame-ignore, PR template, registry checklist (#120)
* chore: add workspace clippy lints, release profiles, COMPAT.md, diff-registry.sh

- Workspace clippy lints: or_fun_call, redundant_clone, collection_is_never_read,
  naive_bytecount, stable_sort_primitive, large_types_passed_by_value, assigning_clones
- Fix or_fun_call in cargo_registry.rs (unwrap_or -> unwrap_or_else)
- Release profiles: release (thin LTO) + release-official (full LTO, codegen-units=1)
- COMPAT.md: protocol compatibility matrix for all 7 registries (40 endpoints)
- scripts/diff-registry.sh: differential smoke tests (Docker/npm/Cargo/PyPI/Go/Raw)

* ci: add typos spell-check job and config

* chore: add .git-blame-ignore-revs for bulk fmt/clippy commits

* chore: unify PR template with What/Why/Checklist format

* docs: add new registry checklist and improve contributing guide

* fix: correct typos action SHA to v1.45.0
2026-04-09 18:49:20 +03:00
e4168b7ee4 chore: add workspace clippy lints, release profiles, COMPAT.md, diff-registry.sh (#119)
- Workspace clippy lints: or_fun_call, redundant_clone, collection_is_never_read,
  naive_bytecount, stable_sort_primitive, large_types_passed_by_value, assigning_clones
- Fix or_fun_call in cargo_registry.rs (unwrap_or -> unwrap_or_else)
- Release profiles: release (thin LTO) + release-official (full LTO, codegen-units=1)
- COMPAT.md: protocol compatibility matrix for all 7 registries (40 endpoints)
- scripts/diff-registry.sh: differential smoke tests (Docker/npm/Cargo/PyPI/Go/Raw)
2026-04-09 12:38:59 +03:00
fda562fe21 chore: higher resolution dashboard screenshot (1920x1080) (#118) 2026-04-08 12:13:11 +03:00
e79b0f58f7 chore: update dashboard screenshot to v0.5.0 with all 7 registries (#117) 2026-04-08 12:08:03 +03:00
388ea8f6a5 test: add Go and Raw to Playwright dashboard and health checks (#116)
All 7 registry types now verified in UI visibility and health endpoint tests.
2026-04-08 11:56:51 +03:00
4003c54744 fix: add missing Go and Raw registries to OpenAPI, startup logs, and metrics tests (#115)
- openapi.rs: add Go Module Proxy and Raw File Storage tags, update version and description
- main.rs: add /go/ to Available endpoints startup log
- dashboard_metrics.rs: add go to download and upload test coverage for all 7 registries
2026-04-08 11:39:36 +03:00
71d8d83585 docs: add community contributions to v0.5.0 changelog (@TickTockBent PRs #97, #108, #109) (#114) 2026-04-08 09:53:39 +03:00
19 changed files with 604 additions and 701 deletions

32
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,32 @@
# Bulk formatting and lint-fix commits — ignore in git blame
# See: https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view
# style: cargo fmt
b2be7102fef2b42d7546ef66c8fd02f06b130bc4
26d30b622dc4d17d160999f043e8be9985cea263
8da4c4278a87ced1420fc6e7a9ea2c567e4e7b97
# style: apply rustfmt to registry handlers
8336166e0e541f213d0f3b20d55ea509bbb2f2d8
# style: fix formatting
a9125e6287e9f31fff0720e7c1c07cdc5e94c9db
bbdefff07cf588ad5f848bec9031f4e51cc47c41
ac4020d34f72b08e1eb3dc0c4248128b1012ddb5
# Fix formatting
08eea07cfe05ac64e9d6d4a7f8314f269d834e9c
c7098a4aed2a880dff418abe48c5016ea5ac20e0
# Fix code formatting
0a97b00278c59a267c0fc7cdca7eb2bd7aa5decf
# Fix clippy warnings
cf9feee5b2116e216cbcd6b0d3ae1fe5e93cf7d5
2f86b4852a9c9a1a5691e8b48da8be3fb45f6d0c
# fix: clippy let_and_return warning
dab3ee805edbd2e6fb3cffda9c9618468880153e
# fix: resolve clippy warnings and format code
00fbd201127defee9c24a8edeb01eba3c053f306

View File

@@ -1,16 +1,14 @@
## Summary ## What
<!-- What does this PR do? --> <!-- Brief description of changes -->
## Changes ## Why
<!-- List key changes --> <!-- Motivation / issue reference -->
## Checklist ## Checklist
- [ ] passes - [ ] Tests pass (`cargo test`)
- [ ] passes - [ ] No new clippy warnings (`cargo clippy -- -D warnings`)
- [ ] passes - [ ] Updated CHANGELOG.md (if user-facing change)
- [ ] No in production code - [ ] New registry? See CONTRIBUTING.md checklist
- [ ] New public API has documentation
- [ ] CHANGELOG updated (if user-facing change)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

BIN
.github/assets/dashboard.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

View File

@@ -1,15 +0,0 @@
## What does this PR do?
<!-- Brief description of the change -->
## Related issue
<!-- Link to issue, e.g. Fixes #123 -->
## Checklist
- [ ] `cargo fmt` passes
- [ ] `cargo clippy` passes with no warnings
- [ ] `cargo test --lib --bin nora` passes
- [ ] New functionality includes tests
- [ ] CHANGELOG.md updated (if user-facing change)

View File

@@ -9,6 +9,13 @@ on:
permissions: read-all permissions: read-all
jobs: jobs:
typos:
name: Typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0
test: test:
name: Test name: Test
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -11,6 +11,10 @@
- 577 total tests (up from 504), including 25 new Cargo tests and 18 new PyPI tests - 577 total tests (up from 504), including 25 new Cargo tests and 18 new PyPI tests
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- Cargo dependency field mapping: `version_req` correctly renamed to `req` and `explicit_name_in_toml` to `package` in sparse index entries, matching Cargo registry specification - Cargo dependency field mapping: `version_req` correctly renamed to `req` and `explicit_name_in_toml` to `package` in sparse index entries, matching Cargo registry specification
- Cargo crate names normalized to lowercase across all endpoints (publish, download, metadata, sparse index) for consistent storage keys - Cargo crate names normalized to lowercase across all endpoints (publish, download, metadata, sparse index) for consistent storage keys
- Cargo publish write ordering: index written before .crate tarball to prevent orphaned files on partial failure - Cargo publish write ordering: index written before .crate tarball to prevent orphaned files on partial failure
@@ -37,6 +41,10 @@
- fetch_blob_from_upstream and fetch_manifest_from_upstream are now pub for reuse in mirror module - fetch_blob_from_upstream and fetch_manifest_from_upstream are now pub for reuse in mirror module
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- tarpaulin exclude-files paths corrected to workspace-relative (coverage jumped from 29% to 61%) (#92) - tarpaulin exclude-files paths corrected to workspace-relative (coverage jumped from 29% to 61%) (#92)
- Env var naming unified across all registries (#39, #90) - Env var naming unified across all registries (#39, #90)
@@ -55,6 +63,10 @@
- clippy.toml added for consistent lint rules - clippy.toml added for consistent lint rules
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- Proxy request deduplication — concurrent requests coalesced (#83) - Proxy request deduplication — concurrent requests coalesced (#83)
- Multi-registry GC now handles all 7 registry types (#83) - Multi-registry GC now handles all 7 registry types (#83)
- TOCTOU race condition in credential validation (#83) - TOCTOU race condition in credential validation (#83)
@@ -91,6 +103,10 @@
- README restructured: roadmap in README, removed stale ROADMAP.md (#65, #66) - README restructured: roadmap in README, removed stale ROADMAP.md (#65, #66)
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- Remove all unwrap() from production code — proper error handling throughout (#72) - Remove all unwrap() from production code — proper error handling throughout (#72)
- Add `#![forbid(unsafe_code)]` — no unsafe code allowed at crate level (#72) - Add `#![forbid(unsafe_code)]` — no unsafe code allowed at crate level (#72)
- Add input validation to Cargo registry endpoints (#72) - Add input validation to Cargo registry endpoints (#72)
@@ -111,6 +127,10 @@
- **Anonymous read mode** (`NORA_AUTH_ANONYMOUS_READ=true`): allow pull/download without credentials while requiring auth for push. Use case: public demo registries, read-only mirrors. - **Anonymous read mode** (`NORA_AUTH_ANONYMOUS_READ=true`): allow pull/download without credentials while requiring auth for push. Use case: public demo registries, read-only mirrors.
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- Pin slsa-github-generator and codeql-action by SHA instead of tag - Pin slsa-github-generator and codeql-action by SHA instead of tag
- Replace anonymous tuple with named struct in activity grouping (readability) - Replace anonymous tuple with named struct in activity grouping (readability)
- Replace unwrap() with if-let pattern in activity grouping (safety) - Replace unwrap() with if-let pattern in activity grouping (safety)
@@ -119,6 +139,10 @@
## [0.2.34] - 2026-03-20 ## [0.2.34] - 2026-03-20
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- **UI**: Group consecutive identical activity entries — repeated cache hits show as "artifact (x4)" instead of 4 identical rows - **UI**: Group consecutive identical activity entries — repeated cache hits show as "artifact (x4)" instead of 4 identical rows
- **UI**: Fix table cell padding in Mount Points and Activity tables — th/td alignment now consistent - **UI**: Fix table cell padding in Mount Points and Activity tables — th/td alignment now consistent
- **Security**: Update tar crate 0.4.44 → 0.4.45 (CVE-2026-33055 PAX size header bypass, CVE-2026-33056 symlink chmod traversal) - **Security**: Update tar crate 0.4.44 → 0.4.45 (CVE-2026-33055 PAX size header bypass, CVE-2026-33056 symlink chmod traversal)
@@ -145,6 +169,10 @@
- Run containers as non-root user (USER nora) in all Dockerfiles - Run containers as non-root user (USER nora) in all Dockerfiles
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- Filter .meta.json from Docker tag list (fixes ArgoCD Image Updater tag recursion) - Filter .meta.json from Docker tag list (fixes ArgoCD Image Updater tag recursion)
- Fix catalog endpoint to show namespaced images correctly (library/alpine instead of library) - Fix catalog endpoint to show namespaced images correctly (library/alpine instead of library)
@@ -166,7 +194,6 @@
- **CI**: Исправлены проверки лицензий cargo-deny - **CI**: Исправлены проверки лицензий cargo-deny
## [0.2.31] - 2026-03-16 ## [0.2.31] - 2026-03-16
### Added / Добавлено ### Added / Добавлено
@@ -194,9 +221,6 @@
- **npm proxy_auth**: Поле `proxy_auth` было в конфиге, но не передавалось в `fetch_from_proxy` — теперь отправляет Basic Auth в upstream - **npm proxy_auth**: Поле `proxy_auth` было в конфиге, но не передавалось в `fetch_from_proxy` — теперь отправляет Basic Auth в upstream
All notable changes to NORA will be documented in this file.
--- ---
## [0.2.30] - 2026-03-16 ## [0.2.30] - 2026-03-16
@@ -228,19 +252,6 @@ All notable changes to NORA will be documented in this file.
### Removed / Удалено ### Removed / Удалено
- Removed unused `DockerAuth::fetch_with_auth()` method (dead code cleanup) - Removed unused `DockerAuth::fetch_with_auth()` method (dead code cleanup)
- Удалён неиспользуемый метод `DockerAuth::fetch_with_auth()` (очистка мёртвого кода) - Удалён неиспользуемый метод `DockerAuth::fetch_with_auth()` (очистка мёртвого кода)
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.28] - 2026-03-13 ## [0.2.28] - 2026-03-13
### Fixed / Исправлено ### Fixed / Исправлено
@@ -258,19 +269,6 @@ All notable changes to NORA will be documented in this file.
### Removed / Удалено ### Removed / Удалено
- Removed stale `CHANGELOG.md.bak` from repository - Removed stale `CHANGELOG.md.bak` from repository
- Удалён устаревший `CHANGELOG.md.bak` из репозитория - Удалён устаревший `CHANGELOG.md.bak` из репозитория
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.27] - 2026-03-03 ## [0.2.27] - 2026-03-03
### Added / Добавлено ### Added / Добавлено
@@ -284,19 +282,6 @@ All notable changes to NORA will be documented in this file.
### Fixed / Исправлено ### Fixed / Исправлено
- Docker push of images >100MB no longer fails with 413 error - Docker push of images >100MB no longer fails with 413 error
- Push Docker-образов >100MB больше не падает с ошибкой 413 - Push Docker-образов >100MB больше не падает с ошибкой 413
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.26] - 2026-03-03 ## [0.2.26] - 2026-03-03
### Added / Добавлено ### Added / Добавлено
@@ -318,19 +303,6 @@ All notable changes to NORA will be documented in this file.
### Security / Безопасность ### Security / Безопасность
- Read-only tokens (`role: read`) are now blocked from PUT/POST/DELETE/PATCH operations with HTTP 403 - Read-only tokens (`role: read`) are now blocked from PUT/POST/DELETE/PATCH operations with HTTP 403
- Токены только для чтения (`role: read`) теперь блокируются при PUT/POST/DELETE/PATCH с HTTP 403 - Токены только для чтения (`role: read`) теперь блокируются при PUT/POST/DELETE/PATCH с HTTP 403
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.25] - 2026-03-03 ## [0.2.25] - 2026-03-03
### Fixed / Исправлено ### Fixed / Исправлено
@@ -354,19 +326,6 @@ All notable changes to NORA will be documented in this file.
- `docker/build-push-action` 5 → 6 - `docker/build-push-action` 5 → 6
- Move scan/release to self-hosted runner with NORA cache - Move scan/release to self-hosted runner with NORA cache
- Сканирование/релиз перенесены на self-hosted runner с кэшем через NORA - Сканирование/релиз перенесены на self-hosted runner с кэшем через NORA
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.24] - 2026-02-24 ## [0.2.24] - 2026-02-24
### Added / Добавлено ### Added / Добавлено
@@ -376,19 +335,6 @@ All notable changes to NORA will be documented in this file.
### CI/CD ### CI/CD
- Restore Astra Linux SE Docker image build, Trivy scan, and release artifact (`-astra` tag) - Restore Astra Linux SE Docker image build, Trivy scan, and release artifact (`-astra` tag)
- Восстановлена сборка Docker-образа для Astra Linux SE, сканирование Trivy и артефакт релиза (тег `-astra`) - Восстановлена сборка Docker-образа для Astra Linux SE, сканирование Trivy и артефакт релиза (тег `-astra`)
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.23] - 2026-02-24 ## [0.2.23] - 2026-02-24
### Added / Добавлено ### Added / Добавлено
@@ -421,37 +367,11 @@ All notable changes to NORA will be documented in this file.
### Documentation / Документация ### Documentation / Документация
- Replace text title with SVG logo; `O` styled in blue-600 / Заголовок заменён SVG-логотипом; буква `O` стилизована в blue-600 - Replace text title with SVG logo; `O` styled in blue-600 / Заголовок заменён SVG-логотипом; буква `O` стилизована в blue-600
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.22] - 2026-02-24 ## [0.2.22] - 2026-02-24
### Changed / Изменено ### Changed / Изменено
- First stable release with Docker images published to container registry - First stable release with Docker images published to container registry
- Первый стабильный релиз с Docker-образами, опубликованными в container registry - Первый стабильный релиз с Docker-образами, опубликованными в container registry
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.21] - 2026-02-24 ## [0.2.21] - 2026-02-24
### CI/CD ### CI/CD
@@ -463,19 +383,6 @@ All notable changes to NORA will be documented in this file.
- Use GitHub-runner's own Rust toolchain (avoid path conflicts) / Используется Rust toolchain самого GitHub-runner'а - Use GitHub-runner's own Rust toolchain (avoid path conflicts) / Используется Rust toolchain самого GitHub-runner'а
- Use shared runner filesystem instead of artifact API (avoids network upload latency) / Общая файловая система runner'а вместо artifact API - Use shared runner filesystem instead of artifact API (avoids network upload latency) / Общая файловая система runner'а вместо artifact API
- Remove Astra Linux build temporarily / Сборка для Astra Linux временно удалена - Remove Astra Linux build temporarily / Сборка для Astra Linux временно удалена
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.20] - 2026-02-23 ## [0.2.20] - 2026-02-23
### Added / Добавлено ### Added / Добавлено
@@ -488,19 +395,6 @@ All notable changes to NORA will be documented in this file.
### Fixed / Исправлено ### Fixed / Исправлено
- Auth: replace `starts_with` with explicit `matches!` for token path checks / Аутентификация: `starts_with` заменён явной проверкой `matches!` для путей с токенами - Auth: replace `starts_with` with explicit `matches!` for token path checks / Аутентификация: `starts_with` заменён явной проверкой `matches!` для путей с токенами
- Remove unnecessary QEMU step for amd64-only builds / Удалён лишний шаг QEMU для amd64-сборок - Remove unnecessary QEMU step for amd64-only builds / Удалён лишний шаг QEMU для amd64-сборок
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.19] - 2026-01-31 ## [0.2.19] - 2026-01-31
### Added / Добавлено ### Added / Добавлено
@@ -512,126 +406,47 @@ All notable changes to NORA will be documented in this file.
### Fixed / Исправлено ### Fixed / Исправлено
- Use `div_ceil` instead of manual ceiling division / Использован `div_ceil` вместо ручной реализации деления с округлением вверх - Use `div_ceil` instead of manual ceiling division / Использован `div_ceil` вместо ручной реализации деления с округлением вверх
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.18] - 2026-01-31 ## [0.2.18] - 2026-01-31
### Changed ### Changed
- Logo styling refinements - Logo styling refinements
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.17] - 2026-01-31 ## [0.2.17] - 2026-01-31
### Added ### Added
- Copyright headers to all source files (Volkov Pavel | DevITWay) - Copyright headers to all source files (Volkov Pavel | DevITWay)
- SPDX-License-Identifier: MIT in all .rs files - SPDX-License-Identifier: MIT in all .rs files
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.16] - 2026-01-31 ## [0.2.16] - 2026-01-31
### Changed ### Changed
- N○RA branding: stylized O logo across dashboard - N○RA branding: stylized O logo across dashboard
- Fixed O letter alignment in logo - Fixed O letter alignment in logo
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.15] - 2026-01-31 ## [0.2.15] - 2026-01-31
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- Code formatting (cargo fmt) - Code formatting (cargo fmt)
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.14] - 2026-01-31 ## [0.2.14] - 2026-01-31
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- Docker dashboard now shows actual image size from manifest layers (config + layers sum) - Docker dashboard now shows actual image size from manifest layers (config + layers sum)
- Previously showed only manifest file size (~500 B instead of actual image size) - Previously showed only manifest file size (~500 B instead of actual image size)
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.13] - 2026-01-31 ## [0.2.13] - 2026-01-31
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- npm dashboard now shows correct version count and package sizes - npm dashboard now shows correct version count and package sizes
- Parses metadata.json for versions, dist.unpackedSize, and time.modified - Parses metadata.json for versions, dist.unpackedSize, and time.modified
- Previously showed 0 versions / 0 B for all packages - Previously showed 0 versions / 0 B for all packages
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.12] - 2026-01-30 ## [0.2.12] - 2026-01-30
### Added ### Added
@@ -653,106 +468,28 @@ All notable changes to NORA will be documented in this file.
#### Documentation #### Documentation
- Bilingual onboarding guide (EN/RU) - Bilingual onboarding guide (EN/RU)
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.11] - 2026-01-26 ## [0.2.11] - 2026-01-26
### Added ### Added
- Internationalization (i18n) support - Internationalization (i18n) support
- PyPI registry proxy - PyPI registry proxy
- UI improvements - UI improvements
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.10] - 2026-01-26 ## [0.2.10] - 2026-01-26
### Changed ### Changed
- Dark theme applied to all UI pages - Dark theme applied to all UI pages
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.9] - 2026-01-26 ## [0.2.9] - 2026-01-26
### Changed ### Changed
- Version bump release - Version bump release
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.8] - 2026-01-26 ## [0.2.8] - 2026-01-26
### Added ### Added
- Dashboard endpoint added to OpenAPI documentation - Dashboard endpoint added to OpenAPI documentation
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.7] - 2026-01-26 ## [0.2.7] - 2026-01-26
### Added ### Added
- Dynamic version display in UI sidebar - Dynamic version display in UI sidebar
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.6] - 2026-01-26 ## [0.2.6] - 2026-01-26
### Added ### Added
@@ -764,54 +501,23 @@ All notable changes to NORA will be documented in this file.
#### UI #### UI
- Dark theme (bg: #0f172a, cards: #1e293b) - Dark theme (bg: #0f172a, cards: #1e293b)
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.5] - 2026-01-26 ## [0.2.5] - 2026-01-26
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- Docker push/pull: added PATCH endpoint for chunked uploads - Docker push/pull: added PATCH endpoint for chunked uploads
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.4] - 2026-01-26 ## [0.2.4] - 2026-01-26
### Fixed ### Fixed
- Go and Raw registries missing from Prometheus metrics (`detect_registry` labeled both as "other") (PR #97, @TickTockBent)
- Go and Raw registries missing from `/health` endpoint `registries` object (PR #97, @TickTockBent)
- Garbage collection scoped to Docker-only blobs — prevents GC from deleting non-Docker registry data (PR #109, @TickTockBent)
- Correct `zeroize` annotation placement and avoid secret cloning in `protected.rs` (PR #108, @TickTockBent)
- Rate limiting: health/metrics endpoints now exempt - Rate limiting: health/metrics endpoints now exempt
- Increased upload rate limits for Docker parallel requests - Increased upload rate limits for Docker parallel requests
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.0] - 2026-01-25 ## [0.2.0] - 2026-01-25
### Added ### Added
@@ -879,19 +585,6 @@ All notable changes to NORA will be documented in this file.
- `src/error.rs` - application error types - `src/error.rs` - application error types
- `src/request_id.rs` - request ID middleware - `src/request_id.rs` - request ID middleware
- `src/rate_limit.rs` - rate limiting configuration - `src/rate_limit.rs` - rate limiting configuration
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.1.0] - 2026-01-24 ## [0.1.0] - 2026-01-24
### Added ### Added
@@ -908,308 +601,3 @@ All notable changes to NORA will be documented in this file.
- Environment variable configuration - Environment variable configuration
- Graceful shutdown (SIGTERM/SIGINT) - Graceful shutdown (SIGTERM/SIGINT)
- Backup/restore commands - Backup/restore commands
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
# Журнал изменений (RU)
Все значимые изменения NORA документируются в этом файле.
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.12] - 2026-01-30
### Добавлено
#### Настраиваемый Rate Limiting
- Rate limits настраиваются через `config.toml` и переменные окружения
- Новая секция `[rate_limit]` с параметрами: `auth_rps`, `auth_burst`, `upload_rps`, `upload_burst`, `general_rps`, `general_burst`
- Переменные окружения: `NORA_RATE_LIMIT_{AUTH|UPLOAD|GENERAL}_{RPS|BURST}`
#### Архитектура Secrets Provider
- Trait-based управление секретами (`SecretsProvider` trait)
- ENV provider по умолчанию (12-Factor App паттерн)
- Защищённые секреты с `zeroize` (память обнуляется при drop)
- Redacted Debug impl предотвращает утечку секретов в логи
- Новая секция `[secrets]` с опциями `provider` и `clear_env`
#### Docker Image Metadata
- Поддержка получения метаданных образов
#### Документация
- Двуязычный onboarding guide (EN/RU)
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.11] - 2026-01-26
### Добавлено
- Поддержка интернационализации (i18n)
- PyPI registry proxy
- Улучшения UI
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.10] - 2026-01-26
### Изменено
- Тёмная тема применена ко всем страницам UI
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.9] - 2026-01-26
### Изменено
- Релиз с обновлением версии
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.8] - 2026-01-26
### Добавлено
- Dashboard endpoint добавлен в OpenAPI документацию
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.7] - 2026-01-26
### Добавлено
- Динамическое отображение версии в сайдбаре UI
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.6] - 2026-01-26
### Добавлено
#### Dashboard Metrics
- Глобальная панель статистики: downloads, uploads, artifacts, cache hit rate, storage
- Расширенные карточки реестров с количеством артефактов, размером, счётчиками
- Лог активности (последние 20 событий)
#### UI
- Тёмная тема (bg: #0f172a, cards: #1e293b)
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.5] - 2026-01-26
### Исправлено
- Docker push/pull: добавлен PATCH endpoint для chunked uploads
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.4] - 2026-01-26
### Исправлено
- Rate limiting: health/metrics endpoints теперь исключены
- Увеличены лимиты upload для параллельных Docker запросов
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.2.0] - 2026-01-25
### Добавлено
#### UI: SVG иконки брендов
- Эмоджи заменены на SVG иконки брендов (стиль Simple Icons)
- Docker, Maven, npm, Cargo, PyPI теперь отображаются как векторная графика
- Единый стиль иконок на дашборде, сайдбаре и страницах деталей
#### Тестовая инфраструктура
- Unit-тесты для LocalStorage (8 тестов): put/get, list, stat, health_check
- Unit-тесты для S3Storage с HTTP-мокированием wiremock (11 тестов)
- Интеграционные тесты auth/htpasswd (7 тестов)
- Тесты жизненного цикла токенов (11 тестов)
- Тесты валидации (21 тест)
- **Всего: 75 тестов проходят**
#### Безопасность: Валидация ввода (`validation.rs`)
- Защита от path traversal: отклоняет `../`, `..\\`, null-байты, абсолютные пути
- Валидация имён Docker-образов по спецификации OCI distribution
- Валидация дайджестов (`sha256:[64 hex]`, `sha512:[128 hex]`)
- Валидация тегов и ссылок Docker
- Ограничение длины ключей хранилища (макс. 1024 символа)
#### Безопасность: Rate Limiting (`rate_limit.rs`)
- Auth endpoints: 1 req/sec, burst 5 (защита от брутфорса)
- Upload endpoints: 10 req/sec, burst 20
- Общие endpoints: 100 req/sec, burst 200
- Использует `tower_governor` 0.8 с `PeerIpKeyExtractor`
#### Наблюдаемость: Отслеживание Request ID (`request_id.rs`)
- Заголовок `X-Request-ID` добавляется ко всем ответам
- Принимает upstream request ID или генерирует UUID v4
- Tracing spans включают request_id для корреляции логов
#### CLI: Команда миграции (`migrate.rs`)
- `nora migrate --from local --to s3` - миграция между storage backends
- Флаг `--dry-run` для предпросмотра без копирования
- Прогресс-бар с indicatif
- Пропуск существующих файлов в destination
- Итоговая статистика (migrated, skipped, failed, bytes)
#### Обработка ошибок (`error.rs`)
- Enum `AppError` с `IntoResponse` для Axum
- Автоматическая конверсия из `StorageError` и `ValidationError`
- JSON-ответы об ошибках с поддержкой request_id
### Изменено
- `StorageError` теперь использует макрос `thiserror`
- `TokenError` теперь использует макрос `thiserror`
- Storage wrapper валидирует ключи перед делегированием backend
- Docker registry handlers валидируют name, digest, reference
- Лимит размера body установлен в 100MB через `DefaultBodyLimit`
### Добавлены зависимости
- `thiserror = "2"` - типизированная обработка ошибок
- `tower_governor = "0.8"` - rate limiting
- `governor = "0.10"` - backend для rate limiting
- `tempfile = "3"` (dev) - временные директории для тестов
- `wiremock = "0.6"` (dev) - HTTP-мокирование для S3 тестов
### Добавлены файлы
- `src/validation.rs` - модуль валидации ввода
- `src/migrate.rs` - модуль миграции хранилища
- `src/error.rs` - типы ошибок приложения
- `src/request_id.rs` - middleware для request ID
- `src/rate_limit.rs` - конфигурация rate limiting
---
## [0.2.30] - 2026-03-16
### Fixed / Исправлено
- **Dashboard**: Docker upstream now shown in mount points table (was null)
- **Dashboard**: Docker namespaced repositories (library/alpine, grafana/grafana) now visible in UI
- **Dashboard**: npm proxy-cached packages now appear in package list
- **Dashboard**: Отображение Docker upstream в таблице точек монтирования (было null)
- **Dashboard**: Namespaced Docker-репозитории (library/alpine, grafana/grafana) теперь видны в UI
- **Dashboard**: npm-пакеты из прокси-кеша теперь отображаются в списке пакетов
## [0.1.0] - 2026-01-24
### Добавлено
- Мульти-протокольная поддержка: Docker Registry v2, Maven, npm, Cargo, PyPI
- Web UI дашборд
- Swagger UI (`/api-docs`)
- Storage backends: локальная файловая система, S3-совместимое хранилище
- Умный прокси/кэш для Maven и npm
- Health checks (`/health`, `/ready`)
- Базовая аутентификация (htpasswd с bcrypt)
- API токены (отзываемые, per-user)
- Prometheus метрики (`/metrics`)
- JSON структурированное логирование
- Конфигурация через переменные окружения
- Graceful shutdown (SIGTERM/SIGINT)
- Команды backup/restore

149
COMPAT.md Normal file
View File

@@ -0,0 +1,149 @@
# NORA Registry Protocol Compatibility
This document describes which parts of each registry protocol are implemented in NORA.
**Legend:** Full = complete implementation, Partial = basic support with limitations, Stub = placeholder, — = not implemented
## Docker (OCI Distribution Spec 1.1)
| Endpoint | Method | Status | Notes |
|----------|--------|--------|-------|
| `/v2/` | GET | Full | API version check |
| `/v2/_catalog` | GET | Full | List all repositories |
| `/v2/{name}/tags/list` | GET | Full | List image tags |
| `/v2/{name}/manifests/{ref}` | GET | Full | By tag or digest |
| `/v2/{name}/manifests/{ref}` | HEAD | Full | Check manifest exists |
| `/v2/{name}/manifests/{ref}` | PUT | Full | Push manifest |
| `/v2/{name}/manifests/{ref}` | DELETE | Full | Delete manifest |
| `/v2/{name}/blobs/{digest}` | GET | Full | Download layer/config |
| `/v2/{name}/blobs/{digest}` | HEAD | Full | Check blob exists |
| `/v2/{name}/blobs/{digest}` | DELETE | Full | Delete blob |
| `/v2/{name}/blobs/uploads/` | POST | Full | Start chunked upload |
| `/v2/{name}/blobs/uploads/{uuid}` | PATCH | Full | Upload chunk |
| `/v2/{name}/blobs/uploads/{uuid}` | PUT | Full | Complete upload |
| Namespaced `{ns}/{name}` | * | Full | Two-level paths |
| Deep paths `a/b/c/name` | * | — | Max 2-level (`org/image`) |
| Token auth (Bearer) | — | Full | WWW-Authenticate challenge |
| Cross-repo blob mount | POST | — | Not implemented |
| Referrers API | GET | — | OCI 1.1 referrers |
### Known Limitations
- Max 2-level image path: `org/image:tag` works, `org/sub/path/image:tag` returns 404
- Large monolithic blob PUT (>~500MB) may fail even with high body limit
- No cross-repository blob mounting
## npm
| Feature | Status | Notes |
|---------|--------|-------|
| Package metadata (GET) | Full | JSON with all versions |
| Scoped packages `@scope/name` | Full | URL-encoded path |
| Tarball download | Full | SHA256 verified |
| Tarball URL rewriting | Full | Points to NORA, not upstream |
| Publish (`npm publish`) | Full | Immutable versions |
| Unpublish | — | Not implemented |
| Dist-tags (`latest`, `next`) | Partial | Read from metadata, no explicit management |
| Search (`/-/v1/search`) | — | Not implemented |
| Audit (`/-/npm/v1/security/advisories`) | — | Not implemented |
| Upstream proxy | Full | Configurable TTL |
## Maven
| Feature | Status | Notes |
|---------|--------|-------|
| Artifact download (GET) | Full | JAR, POM, checksums |
| Artifact upload (PUT) | Full | Any file type |
| GroupId path layout | Full | Dots → slashes |
| SHA1/MD5 checksums | Full | Stored alongside artifacts |
| `maven-metadata.xml` | Partial | Stored as-is, no auto-generation |
| SNAPSHOT versions | — | No SNAPSHOT resolution |
| Multi-proxy fallback | Full | Tries proxies in order |
| Content-Type by extension | Full | .jar, .pom, .xml, .sha1, .md5 |
### Known Limitations
- `maven-metadata.xml` not auto-generated on publish (must be uploaded explicitly)
- No SNAPSHOT version management (`-SNAPSHOT` → latest timestamp)
## Cargo (Sparse Index, RFC 2789)
| Feature | Status | Notes |
|---------|--------|-------|
| `config.json` | Full | `dl` and `api` fields |
| Sparse index lookup | Full | Prefix rules (1/2/3/ab/cd) |
| Crate download | Full | `.crate` files by version |
| `cargo publish` | Full | Length-prefixed JSON + .crate |
| Dependency metadata | Full | `req`, `package` transforms |
| SHA256 verification | Full | On publish |
| Cache-Control headers | Full | `immutable` for downloads, `max-age=300` for index |
| Yank/unyank | — | Not implemented |
| Owner management | — | Not implemented |
| Categories/keywords | Partial | Stored but not searchable |
## PyPI (PEP 503/691)
| Feature | Status | Notes |
|---------|--------|-------|
| Simple index (HTML) | Full | PEP 503 |
| Simple index (JSON) | Full | PEP 691, via Accept header |
| Package versions page | Full | HTML + JSON |
| File download | Full | Wheel, sdist, egg |
| `twine upload` | Full | Multipart form-data |
| SHA256 hashes | Full | In metadata links |
| Case normalization | Full | `My-Package``my-package` |
| Upstream proxy | Full | Configurable TTL |
| JSON API metadata | Full | `application/vnd.pypi.simple.v1+json` |
| Yanking | — | Not implemented |
| Upload signatures (PGP) | — | Not implemented |
## Go Module Proxy (GOPROXY)
| Feature | Status | Notes |
|---------|--------|-------|
| `/@v/list` | Full | List known versions |
| `/@v/{version}.info` | Full | Version metadata JSON |
| `/@v/{version}.mod` | Full | go.mod file |
| `/@v/{version}.zip` | Full | Module zip archive |
| `/@latest` | Full | Latest version info |
| Module path escaping | Full | `!x``X` per spec |
| Immutability | Full | .info, .mod, .zip immutable after first write |
| Size limit for .zip | Full | Configurable |
| `$GONOSUMDB` / `$GONOSUMCHECK` | — | Not relevant (client-side) |
| Upstream proxy | — | Direct storage only |
## Raw File Storage
| Feature | Status | Notes |
|---------|--------|-------|
| Upload (PUT) | Full | Any file type |
| Download (GET) | Full | Content-Type by extension |
| Delete (DELETE) | Full | |
| Exists check (HEAD) | Full | Returns size + Content-Type |
| Max file size | Full | Configurable (default 1MB) |
| Directory listing | — | Not implemented |
| Versioning | — | Overwrite-only |
## Helm OCI
Helm charts are stored as OCI artifacts via the Docker registry endpoints. `helm push` and `helm pull` work through the standard `/v2/` API.
| Feature | Status | Notes |
|---------|--------|-------|
| `helm push` (OCI) | Full | Via Docker PUT manifest/blob |
| `helm pull` (OCI) | Full | Via Docker GET manifest/blob |
| Helm repo index (`index.yaml`) | — | Not implemented (OCI only) |
## Cross-Cutting Features
| Feature | Status | Notes |
|---------|--------|-------|
| Authentication (Bearer/Basic) | Full | Per-request token validation |
| Anonymous read | Full | `NORA_AUTH_ANONYMOUS_READ=true` |
| Rate limiting | Full | `tower_governor`, per-IP |
| Prometheus metrics | Full | `/metrics` endpoint |
| Health check | Full | `/health` |
| Swagger/OpenAPI | Full | `/swagger-ui/` |
| S3 backend | Full | AWS, MinIO, any S3-compatible |
| Local filesystem backend | Full | Default, content-addressable |
| Activity log | Full | Recent push/pull in dashboard |
| Backup/restore | Full | CLI commands |
| Mirror CLI | Full | `nora mirror` for npm/pip/cargo/maven/docker |

View File

@@ -107,16 +107,19 @@ All three must pass. CI will enforce this.
- Run `cargo fmt` before committing - Run `cargo fmt` before committing
- Fix all `cargo clippy` warnings - Fix all `cargo clippy` warnings
- No `unwrap()` in production code (use proper error handling)
- Follow Rust naming conventions - Follow Rust naming conventions
- Keep functions short and focused - Keep functions short and focused
- Add tests for new functionality - Add tests for new functionality
## Pull Request Process ## Pull Request Process
1. Update CHANGELOG.md if the change is user-facing 1. Branch from `main`, use descriptive branch names (`feat/`, `fix/`, `chore/`)
2. Add tests for new features or bug fixes 2. Update CHANGELOG.md if the change is user-facing
3. Ensure CI passes (fmt, clippy, test, security checks) 3. Add tests for new features or bug fixes
4. Keep PRs focused — one feature or fix per PR 4. Ensure CI passes (fmt, clippy, test, security checks)
5. Keep PRs focused — one feature or fix per PR
6. PRs are squash-merged to keep a clean history
## Commit Messages ## Commit Messages
@@ -131,6 +134,20 @@ Use conventional commits:
Example: `feat: add npm scoped package support` Example: `feat: add npm scoped package support`
## New Registry Checklist
When adding a new registry type (Docker, npm, Maven, etc.), ensure all of the following:
- [ ] Handler in `nora-registry/src/registry/`
- [ ] Health check endpoint
- [ ] Metrics (Prometheus)
- [ ] OpenAPI spec update
- [ ] Startup log line
- [ ] Dashboard UI tile
- [ ] Playwright e2e test
- [ ] CHANGELOG entry
- [ ] COMPAT.md update
## Reporting Issues ## Reporting Issues
- Use GitHub Issues with the provided templates - Use GitHub Issues with the provided templates

View File

@@ -26,3 +26,27 @@ sha2 = "0.11"
async-trait = "0.1" async-trait = "0.1"
hmac = "0.13" hmac = "0.13"
hex = "0.4" hex = "0.4"
[workspace.lints.clippy]
or_fun_call = "deny"
redundant_clone = "deny"
collection_is_never_read = "deny"
naive_bytecount = "deny"
stable_sort_primitive = "deny"
large_types_passed_by_value = "deny"
assigning_clones = "deny"
[workspace.lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [] }
[profile.release]
debug = "line-tables-only"
codegen-units = 4
panic = "abort"
lto = "thin"
# Maximum optimization for GitHub Releases and published binaries
[profile.release-official]
inherits = "release"
codegen-units = 1
lto = true

View File

@@ -9,7 +9,7 @@ docker run -d -p 4000:4000 -v nora-data:/data ghcr.io/getnora-io/nora:latest
Open [http://localhost:4000/ui/](http://localhost:4000/ui/) — your registry is ready. Open [http://localhost:4000/ui/](http://localhost:4000/ui/) — your registry is ready.
<p align="center"> <p align="center">
<img src=".github/assets/dashboard.gif" alt="NORA Dashboard" width="960" /> <img src=".github/assets/dashboard.png" alt="NORA Dashboard" width="960" />
</p> </p>
## Why NORA ## Why NORA

10
_typos.toml Normal file
View File

@@ -0,0 +1,10 @@
[default.extend-words]
# HashiCorp is not a typo
Hashi = "Hashi"
# flate2 is a Rust crate for compression
flate = "flate"
# grep pattern fragment in lint script
validat = "validat"
[files]
extend-exclude = ["vendor/", "*.lock", "target/", "fuzz/corpus/"]

View File

@@ -64,3 +64,6 @@ http-body-util = "0.1"
[[bench]] [[bench]]
name = "parsing" name = "parsing"
harness = false harness = false
[lints]
workspace = true

View File

@@ -212,15 +212,16 @@ mod tests {
#[test] #[test]
fn test_record_download_all_registries() { fn test_record_download_all_registries() {
let m = DashboardMetrics::new(); let m = DashboardMetrics::new();
for reg in &["docker", "npm", "maven", "cargo", "pypi", "raw"] { for reg in &["docker", "npm", "maven", "cargo", "pypi", "go", "raw"] {
m.record_download(reg); m.record_download(reg);
} }
assert_eq!(m.downloads.load(Ordering::Relaxed), 6); assert_eq!(m.downloads.load(Ordering::Relaxed), 7);
assert_eq!(m.get_registry_downloads("docker"), 1); assert_eq!(m.get_registry_downloads("docker"), 1);
assert_eq!(m.get_registry_downloads("npm"), 1); assert_eq!(m.get_registry_downloads("npm"), 1);
assert_eq!(m.get_registry_downloads("maven"), 1); assert_eq!(m.get_registry_downloads("maven"), 1);
assert_eq!(m.get_registry_downloads("cargo"), 1); assert_eq!(m.get_registry_downloads("cargo"), 1);
assert_eq!(m.get_registry_downloads("pypi"), 1); assert_eq!(m.get_registry_downloads("pypi"), 1);
assert_eq!(m.get_registry_downloads("go"), 1);
assert_eq!(m.get_registry_downloads("raw"), 1); assert_eq!(m.get_registry_downloads("raw"), 1);
} }
@@ -233,14 +234,18 @@ mod tests {
} }
#[test] #[test]
fn test_record_upload() { fn test_record_upload_all_registries() {
let m = DashboardMetrics::new(); let m = DashboardMetrics::new();
m.record_upload("docker"); for reg in &["docker", "npm", "maven", "cargo", "pypi", "go", "raw"] {
m.record_upload("maven"); m.record_upload(reg);
m.record_upload("raw"); }
assert_eq!(m.uploads.load(Ordering::Relaxed), 3); assert_eq!(m.uploads.load(Ordering::Relaxed), 7);
assert_eq!(m.get_registry_uploads("docker"), 1); assert_eq!(m.get_registry_uploads("docker"), 1);
assert_eq!(m.get_registry_uploads("npm"), 1);
assert_eq!(m.get_registry_uploads("maven"), 1); assert_eq!(m.get_registry_uploads("maven"), 1);
assert_eq!(m.get_registry_uploads("cargo"), 1);
assert_eq!(m.get_registry_uploads("pypi"), 1);
assert_eq!(m.get_registry_uploads("go"), 1);
assert_eq!(m.get_registry_uploads("raw"), 1); assert_eq!(m.get_registry_uploads("raw"), 1);
} }

View File

@@ -438,6 +438,7 @@ async fn run_server(config: Config, storage: Storage) {
npm = "/npm/", npm = "/npm/",
cargo = "/cargo/", cargo = "/cargo/",
pypi = "/simple/", pypi = "/simple/",
go = "/go/",
raw = "/raw/", raw = "/raw/",
"Available endpoints" "Available endpoints"
); );

View File

@@ -18,8 +18,8 @@ use crate::AppState;
#[openapi( #[openapi(
info( info(
title = "Nora", title = "Nora",
version = "0.2.12", version = "0.5.0",
description = "Multi-protocol package registry supporting Docker, Maven, npm, Cargo, and PyPI", description = "Multi-protocol package registry supporting Docker, Maven, npm, Cargo, PyPI, Go, and Raw",
license(name = "MIT"), license(name = "MIT"),
contact(name = "DevITWay", url = "https://github.com/getnora-io/nora") contact(name = "DevITWay", url = "https://github.com/getnora-io/nora")
), ),
@@ -35,6 +35,8 @@ use crate::AppState;
(name = "npm", description = "npm Registry API"), (name = "npm", description = "npm Registry API"),
(name = "cargo", description = "Cargo Registry API"), (name = "cargo", description = "Cargo Registry API"),
(name = "pypi", description = "PyPI Simple API"), (name = "pypi", description = "PyPI Simple API"),
(name = "go", description = "Go Module Proxy API"),
(name = "raw", description = "Raw File Storage API"),
(name = "auth", description = "Authentication & API Tokens") (name = "auth", description = "Authentication & API Tokens")
), ),
paths( paths(

View File

@@ -448,7 +448,7 @@ async fn publish(State(state): State<Arc<AppState>>, body: Bytes) -> Response {
let features = metadata let features = metadata
.get("features") .get("features")
.cloned() .cloned()
.unwrap_or(serde_json::json!({})); .unwrap_or_else(|| serde_json::json!({}));
let features2 = metadata.get("features2").cloned(); let features2 = metadata.get("features2").cloned();
let links = metadata.get("links").cloned(); let links = metadata.get("links").cloned();

278
scripts/diff-registry.sh Executable file
View File

@@ -0,0 +1,278 @@
#!/usr/bin/env bash
# diff-registry.sh — Differential testing: NORA vs reference registry
# Usage:
# ./scripts/diff-registry.sh docker [nora_url]
# ./scripts/diff-registry.sh npm [nora_url]
# ./scripts/diff-registry.sh cargo [nora_url]
# ./scripts/diff-registry.sh pypi [nora_url]
# ./scripts/diff-registry.sh all [nora_url]
#
# Requires: curl, jq, skopeo (for docker), diff
set -euo pipefail
NORA_URL="${2:-http://localhost:5000}"
TMPDIR=$(mktemp -d)
PASS=0
FAIL=0
SKIP=0
cleanup() { rm -rf "$TMPDIR"; }
trap cleanup EXIT
ok() { PASS=$((PASS+1)); echo " PASS: $1"; }
fail() { FAIL=$((FAIL+1)); echo " FAIL: $1"; }
skip() { SKIP=$((SKIP+1)); echo " SKIP: $1"; }
check_tool() {
if ! command -v "$1" &>/dev/null; then
echo "WARNING: $1 not found, some tests will be skipped"
return 1
fi
return 0
}
# --- Docker ---
diff_docker() {
echo "=== Docker Registry V2 ==="
# 1. /v2/ endpoint returns 200 or 401
local status
status=$(curl -s -o /dev/null -w "%{http_code}" "$NORA_URL/v2/")
if [[ "$status" == "200" || "$status" == "401" ]]; then
ok "/v2/ returns $status"
else
fail "/v2/ returns $status (expected 200 or 401)"
fi
# 2. _catalog returns valid JSON with repositories array
local catalog
catalog=$(curl -s "$NORA_URL/v2/_catalog" 2>/dev/null)
if echo "$catalog" | jq -e '.repositories' &>/dev/null; then
ok "/v2/_catalog has .repositories array"
else
fail "/v2/_catalog invalid JSON: $catalog"
fi
# 3. Push+pull roundtrip with skopeo
if check_tool skopeo; then
local test_image="diff-test/alpine"
local test_tag="diff-$(date +%s)"
# Copy a tiny image to NORA (resolves multi-arch to current platform)
if skopeo copy --dest-tls-verify=false \
docker://docker.io/library/alpine:3.20 \
"docker://${NORA_URL#http*://}/$test_image:$test_tag" 2>/dev/null; then
# Verify manifest structure: must have layers[] and config.digest
skopeo inspect --tls-verify=false --raw \
"docker://${NORA_URL#http*://}/$test_image:$test_tag" \
> "$TMPDIR/nora-manifest.json" 2>/dev/null
local has_layers has_config
has_layers=$(jq -e '.layers | length > 0' "$TMPDIR/nora-manifest.json" 2>/dev/null)
has_config=$(jq -e '.config.digest' "$TMPDIR/nora-manifest.json" 2>/dev/null)
if [[ "$has_layers" == "true" && -n "$has_config" ]]; then
ok "Docker push+pull roundtrip: valid manifest with layers"
else
fail "Docker manifest missing layers or config"
jq . "$TMPDIR/nora-manifest.json" 2>/dev/null || true
fi
# Verify blob is retrievable by digest
local first_layer
first_layer=$(jq -r '.layers[0].digest' "$TMPDIR/nora-manifest.json" 2>/dev/null)
if [[ -n "$first_layer" ]]; then
local blob_status
blob_status=$(curl -s -o /dev/null -w "%{http_code}" \
"$NORA_URL/v2/$test_image/blobs/$first_layer")
if [[ "$blob_status" == "200" ]]; then
ok "Docker blob retrievable by digest"
else
fail "Docker blob GET returned $blob_status"
fi
fi
# Check tags/list
local tags
tags=$(curl -s "$NORA_URL/v2/$test_image/tags/list" 2>/dev/null)
if echo "$tags" | jq -e ".tags[] | select(. == \"$test_tag\")" &>/dev/null; then
ok "tags/list contains pushed tag"
else
fail "tags/list missing pushed tag: $tags"
fi
else
fail "skopeo copy to NORA failed"
fi
else
skip "Docker roundtrip (skopeo not installed)"
fi
}
# --- npm ---
diff_npm() {
echo "=== npm Registry ==="
# 1. Package metadata format
local meta
meta=$(curl -s "$NORA_URL/npm/lodash" 2>/dev/null)
local status=$?
if [[ $status -ne 0 ]]; then
skip "npm metadata (no packages published or upstream unavailable)"
return
fi
if echo "$meta" | jq -e '.name' &>/dev/null; then
ok "npm metadata has .name field"
else
skip "npm metadata (no packages or proxy not configured)"
return
fi
# 2. Tarball URLs point to NORA, not upstream
local tarball_url
tarball_url=$(echo "$meta" | jq -r '.versions | to_entries | last | .value.dist.tarball // empty' 2>/dev/null)
if [[ -n "$tarball_url" ]]; then
if echo "$tarball_url" | grep -qvE "registry.npmjs.org|registry.yarnpkg.com"; then
ok "npm tarball URLs rewritten to NORA"
else
fail "npm tarball URL points to upstream: $tarball_url"
fi
else
skip "npm tarball URL check (no versions)"
fi
}
# --- Cargo ---
diff_cargo() {
echo "=== Cargo Sparse Index ==="
# 1. config.json exists and has dl field
local config
config=$(curl -s "$NORA_URL/cargo/index/config.json" 2>/dev/null)
if echo "$config" | jq -e '.dl' &>/dev/null; then
ok "Cargo config.json has .dl field"
else
fail "Cargo config.json missing .dl: $config"
fi
# 2. config.json has api field
if echo "$config" | jq -e '.api' &>/dev/null; then
ok "Cargo config.json has .api field"
else
fail "Cargo config.json missing .api"
fi
}
# --- PyPI ---
diff_pypi() {
echo "=== PyPI Simple API ==="
# 1. /simple/ returns HTML or JSON
local simple_html
simple_html=$(curl -s -H "Accept: text/html" "$NORA_URL/simple/" 2>/dev/null)
if echo "$simple_html" | grep -qi "<!DOCTYPE\|<html\|simple" &>/dev/null; then
ok "/simple/ returns HTML index"
else
skip "/simple/ HTML (no packages published)"
fi
# 2. PEP 691 JSON response
local simple_json
simple_json=$(curl -s -H "Accept: application/vnd.pypi.simple.v1+json" "$NORA_URL/simple/" 2>/dev/null)
if echo "$simple_json" | jq -e '.projects // .meta' &>/dev/null; then
ok "/simple/ PEP 691 JSON works"
else
skip "/simple/ PEP 691 (not supported or empty)"
fi
}
# --- Go ---
diff_go() {
echo "=== Go Module Proxy ==="
# Basic health: try a known module
local status
status=$(curl -s -o /dev/null -w "%{http_code}" "$NORA_URL/go/golang.org/x/text/@v/list" 2>/dev/null)
if [[ "$status" == "200" || "$status" == "404" ]]; then
ok "Go proxy responds ($status)"
else
skip "Go proxy (status: $status)"
fi
}
# --- Raw ---
diff_raw() {
echo "=== Raw Storage ==="
local test_path="diff-test/test-$(date +%s).txt"
local test_content="diff-registry-test"
# 1. PUT + GET roundtrip
local put_status
put_status=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
-H "Content-Type: text/plain" \
-d "$test_content" \
"$NORA_URL/raw/$test_path" 2>/dev/null)
if [[ "$put_status" == "200" || "$put_status" == "201" ]]; then
local got
got=$(curl -s "$NORA_URL/raw/$test_path" 2>/dev/null)
if [[ "$got" == "$test_content" ]]; then
ok "Raw PUT+GET roundtrip"
else
fail "Raw GET returned different content: '$got'"
fi
# 2. HEAD returns size
local head_status
head_status=$(curl -s -o /dev/null -w "%{http_code}" -I "$NORA_URL/raw/$test_path" 2>/dev/null)
if [[ "$head_status" == "200" ]]; then
ok "Raw HEAD returns 200"
else
fail "Raw HEAD returned $head_status"
fi
# 3. DELETE
curl -s -o /dev/null -X DELETE "$NORA_URL/raw/$test_path" 2>/dev/null
local after_delete
after_delete=$(curl -s -o /dev/null -w "%{http_code}" "$NORA_URL/raw/$test_path" 2>/dev/null)
if [[ "$after_delete" == "404" ]]; then
ok "Raw DELETE works"
else
fail "Raw DELETE: GET after delete returned $after_delete"
fi
elif [[ "$put_status" == "401" ]]; then
skip "Raw PUT (auth required)"
else
fail "Raw PUT returned $put_status"
fi
}
# --- Main ---
case "${1:-all}" in
docker) diff_docker ;;
npm) diff_npm ;;
cargo) diff_cargo ;;
pypi) diff_pypi ;;
go) diff_go ;;
raw) diff_raw ;;
all)
diff_docker
diff_npm
diff_cargo
diff_pypi
diff_go
diff_raw
;;
*)
echo "Usage: $0 {docker|npm|cargo|pypi|go|raw|all} [nora_url]"
exit 1
;;
esac
echo ""
echo "=== Results: $PASS passed, $FAIL failed, $SKIP skipped ==="
[[ $FAIL -eq 0 ]] && exit 0 || exit 1

View File

@@ -10,12 +10,14 @@ test.describe('NORA Dashboard', () => {
test('dashboard shows registry sections', async ({ page }) => { test('dashboard shows registry sections', async ({ page }) => {
await page.goto('/ui/'); await page.goto('/ui/');
// All registry types should be visible // All 7 registry types should be visible
await expect(page.getByText(/Docker/i).first()).toBeVisible(); await expect(page.getByText(/Docker/i).first()).toBeVisible();
await expect(page.getByText(/npm/i).first()).toBeVisible(); await expect(page.getByText(/npm/i).first()).toBeVisible();
await expect(page.getByText(/Maven/i).first()).toBeVisible(); await expect(page.getByText(/Maven/i).first()).toBeVisible();
await expect(page.getByText(/PyPI/i).first()).toBeVisible(); await expect(page.getByText(/PyPI/i).first()).toBeVisible();
await expect(page.getByText(/Cargo/i).first()).toBeVisible(); await expect(page.getByText(/Cargo/i).first()).toBeVisible();
await expect(page.getByText(/Go/i).first()).toBeVisible();
await expect(page.getByText(/Raw/i).first()).toBeVisible();
}); });
test('dashboard shows non-zero npm count after proxy fetch', async ({ page, request }) => { test('dashboard shows non-zero npm count after proxy fetch', async ({ page, request }) => {
@@ -65,6 +67,8 @@ test.describe('NORA Dashboard', () => {
expect(health.registries.maven).toBe('ok'); expect(health.registries.maven).toBe('ok');
expect(health.registries.pypi).toBe('ok'); expect(health.registries.pypi).toBe('ok');
expect(health.registries.cargo).toBe('ok'); expect(health.registries.cargo).toBe('ok');
expect(health.registries.go).toBe('ok');
expect(health.registries.raw).toBe('ok');
}); });
test('OpenAPI docs endpoint accessible', async ({ request }) => { test('OpenAPI docs endpoint accessible', async ({ request }) => {