Releases: troke12/refity
Releases · troke12/refity
v1.2.1
Release notes – v1.2.1
Backend
Fix: Blob upload 404 on cached Docker push
- Symptom:
docker pushfails withblob unknown/ repeated retries when pushing images built
with cache (layers reused from previous builds). AllPUT /v2/.../blobs/uploads/...requests return
404. - Cause: The SFTP driver's
ensureDir,PutContent, andMovefunctions checked if the group
folder existed on SFTP before creating directories. AnyStaterror — including stale SSH connections
— was treated as "repository not found", returning404 NAME_INVALIDeven for repos that already
exist with multiple tags. - Fix: Removed the strict group folder existence checks. The recursive directory creation that
already existed inensureDirnow handles all cases correctly — creating directories as needed
instead of failing.
Fix: SFTP connection pool stale connections
- Symptom: After the SFTP server restarts or SSH connections time out, all registry operations
fail until the backend is restarted. - Cause: The SFTP connection pool had no health check or reconnection logic. Broken clients were
returned from the pool indefinitely. - Fix:
getClient()now performs a health check (Getwd()) before returning a client. If the
connection is dead, it automatically reconnects and logs[SFTP] Pool: stale connection detected, reconnecting....
Fix: PATCH handler race condition on blob chunk upload
- Symptom: Intermittent digest mismatch or incomplete blob data when Docker pushes layers in
parallel across multiple connections. - Cause: The PATCH handler (
uploadBlobData) sent the202 Acceptedresponse before closing
the file writer. If Docker sent the finalizing PUT on a different connection, the file could still be
unflushed. - Fix: File writer is now closed before sending the 202 response, ensuring data is fully
persisted to disk.
Summary
| Area | Change | Type |
|---|---|---|
| Registry | Fix blob upload 404 on cached docker push |
Fix |
| SFTP | Connection pool health check + auto-reconnect | Fix |
| Registry | Close chunk file before responding to PATCH | Fix |
Upgrade notes
- Pull the latest backend image:
docker pull troke12/refity-backend:latest - Restart the backend container. No database migration or config changes needed.
v1.2.0
Release notes – v1.2.0
Breaking changes
Registry authentication required
- All
/v2/endpoints (Docker Registry HTTP API) now require Basic Auth. Previously the registry was open (designed to be protected by a reverse proxy). docker login <registry>is now mandatory beforedocker pushordocker pull.- Credentials are validated against the same user database as the web UI.
- Unauthenticated requests receive
401 UnauthorizedwithWww-Authenticate: Basic realm="Refity Registry"header, following the Docker Distribution spec.
Default admin password is now random
- On first run (empty database), the admin account is created with a randomly generated password printed to the server log. The old default
admin:adminis removed. - If you are upgrading and already have users in the database, nothing changes — existing accounts are untouched.
JWT secret no longer has a hardcoded fallback
- If
JWT_SECRETis not set, a random secret is generated per session. Tokens will be invalidated on restart. SetJWT_SECRETin production to persist sessions across restarts.
Backend
Docker pull fix: Content-Length header
- Fix:
docker pullfrom the registry failed withmissing or empty Content-Length header. - Cause: The blob download and manifest GET handlers did not set the
Content-Lengthresponse header, which the Docker daemon requires. - Change: Both
handleBlobDownloadandhandleManifest(GET) now setContent-LengthandDocker-Content-Digestheaders before writing the response body.
Multiple tags per repository
- Fix: The web UI only showed 1 tag per repository, even after pushing multiple tags (e.g.
:31,:32,:latest). - Cause: The
imagestable hadUNIQUEon both(name, tag)anddigest. When two different tags shared the same digest (or whenINSERT OR REPLACEresolved a conflict ondigest), the old row was silently deleted. - Changes:
- Removed
UNIQUEconstraint from thedigestcolumn. Multiple tags can now point to the same digest. - Replaced
INSERT OR REPLACEwithINSERT ... ON CONFLICT(name, tag) DO UPDATE— only updates when the same tag is re-pushed, without affecting other tags. - Auto-migration: On startup, if the old schema is detected (
digest TEXT NOT NULL UNIQUE), the table is automatically recreated without the constraint. Existing data is preserved.
- Removed
Security hardening
Critical
- Random JWT secret — If
JWT_SECRETis not set, a cryptographically random 64-character hex secret is generated instead of using a hardcoded default. A warning is logged. - Random admin password — First-run admin account uses a random 24-character hex password, logged to console. No more
admin:admin. - Upload state HMAC — The
packUploadState/unpackUploadStatefunctions now return an error if the secret is empty, instead of falling back to a hardcoded key.
High
- Role-based access control (RBAC) — Destructive API operations (create/delete repository, create group, delete tag) now require the
adminrole. Read-only endpoints (dashboard, list groups/repos/tags) remain accessible to all authenticated users. - SFTP host key warning — If
FTP_KNOWN_HOSTSis not configured, a warning is logged at startup:SSH host key verification is DISABLED. - Server timeouts —
ReadHeaderTimeoutreduced from 60s to 10s (Slowloris protection). AddedIdleTimeout: 120sto reclaim idle keep-alive connections. - Blob digest validation — Blob HEAD/GET endpoints now validate the digest format with
^sha256:[a-f0-9]{64}$regex, preventing path traversal via crafted digest values. - Hetzner API log sanitization — API response bodies and error details are no longer logged or returned to the client. Only the HTTP status code is logged.
Medium
- Rate limiting — Login endpoint: max 10 failed attempts per IP per 5 minutes. Registry Basic Auth: max 20 failed attempts per IP per 5 minutes. Exceeding the limit returns
429 Too Many Requests. - Tamper-proof user context — User claims (ID, username, role) are now stored in Go's
context.Contextinstead of HTTP headers, preventing spoofing via reverse proxy header injection. - Manifest ref validation — Manifest references are limited to 128 characters and reject null bytes.
- Stricter repo name validation — Repository names must start with an alphanumeric character (
^[a-zA-Z0-9]...).
Frontend
Nginx security headers
Added the following headers to nginx.conf.template:
X-Frame-Options: DENY— clickjacking protectionX-Content-Type-Options: nosniff— MIME sniffing protectionX-XSS-Protection: 1; mode=block— reflected XSS protectionReferrer-Policy: strict-origin-when-cross-origin— referrer leakage protectionPermissions-Policy: camera=(), microphone=(), geolocation=()— disable unnecessary browser APIsserver_tokens off— hide nginx version
Summary
| Area | Change | Type |
|---|---|---|
| Registry | Basic Auth required for all /v2/ endpoints |
Breaking |
| Registry | Content-Length header on blob download and manifest GET |
Fix |
| Database | Allow multiple tags per repo (remove UNIQUE on digest) |
Fix |
| Database | Auto-migration for existing databases | Enhancement |
| Security | Random JWT secret / admin password / upload state HMAC | Critical |
| Security | RBAC on destructive API operations | High |
| Security | Rate limiting on login + registry auth | Medium |
| Security | Blob digest validation, manifest ref limits, repo name strictness | Medium |
| Security | User context in context.Context (not headers) |
Medium |
| Frontend | Nginx security headers + hide server version | Enhancement |
| Server | IdleTimeout, reduced ReadHeaderTimeout |
Enhancement |
| Server | SFTP host key warning, Hetzner log sanitization | Enhancement |
Upgrade notes
- Set
JWT_SECRETin your environment if you haven't already. Without it, sessions are lost on restart. - First-time users: Check server logs for the generated admin password.
- Existing users: Your accounts and passwords are unchanged. The RBAC changes only affect non-admin users (if you have any).
- Docker clients: Run
docker login <your-registry>before push/pull. Existing~/.docker/config.jsoncredentials will continue to work. - Database migration is automatic. No manual SQL needed. A log line confirms:
Migration complete: images table updated.
v1.1.2
Release notes – v1.1.2
Frontend
Docker pull copy per tag
- On the repository page (tag list), each tag row shows the full
docker pull <registry>/<group>/<repo>:<tag>command with a Copy button. - Registry URL is taken from
VITE_REGISTRY_URLwhen set; otherwise the current host in production orlocalhost:5000in development. - Optional env: see
.env.exampleforVITE_REGISTRY_URL.
Nginx template: registry proxy
/v2location proxies to the backend with:proxy_request_buffering off– streams large PATCH bodies (layer uploads) instead of buffering, reducing server-to-server push retries.client_max_body_size 0– no upload size limit.- Long timeouts (900s) for connect, send, and read.
- Use this when exposing the registry via the frontend (e.g. NPM → refity-frontend → backend).
Backend
Docker push from same host (connection reuse)
- Fix: When the Docker daemon and Refity registry run on the same host,
docker pushcould retry on the first layer; the POST (initiate blob
upload) succeeded but the following PATCH (upload blob) did not reach the handler. - Cause: Docker reuses the same TCP connection for POST then PATCH. The handler did not read the POST body, which can break connection reuse
for the next request. - Change: The initiate-blob-upload handler now drains the request body (
io.Copy(io.Discard, r.Body)andr.Body.Close()) before handling,
so the connection is ready for the PATCH. - No API or config change; upgrade and redeploy to benefit.
Docker push behind reverse proxy ("short copy" fix)
- Fix:
docker pushthrough a reverse proxy (e.g. Cloudflare → Nginx Proxy Manager → Refity) failed withshort copy: wrote 1 of 2294while
pushing via direct IP worked fine. - Cause: Three issues combined:
- Absolute Location URLs – Upload handlers returned
Location: https://host/v2/...with scheme and host. If the proxy didn't set
X-Forwarded-Protocorrectly or the scheme mismatched, the Docker client followed the Location to the wrong endpoint and the connection was reset
after 1 byte. - Monolithic uploads ignored in async mode – Docker sends small blobs (config, ~2 KB) via
POST /v2/.../uploads/?digest=sha256:...
(monolithic). The handler only accepted this whenSFTP_SYNC_UPLOAD=true; in the default async mode it discarded the body and returned 202,
forcing an unnecessary PATCH round-trip that could fail through proxies. - Manual 100-Continue – The PATCH handler called
w.WriteHeader(100)manually, which interfered with Nginx's ownExpect: 100-continue
handling and could produce duplicate or garbled interim responses.
- Absolute Location URLs – Upload handlers returned
- Changes:
- All
Locationheaders now use relative paths (/v2/...) instead of absolute URLs — matches the official Docker Distribution (registry)
behavior and works behind any proxy without configuration. - Monolithic blob uploads (
POSTwith?digest=) now work in both sync and async modes. In async mode the blob is written to local staging
first, then uploaded to SFTP in the background. - Removed manual
w.WriteHeader(http.StatusContinue)— Go'snet/httpsends 100 Continue automatically whenr.Bodyis read.
- All
- No API or config change; upgrade and redeploy to benefit.
Summary
| Area | Change |
|---|---|
| Frontend | Per-tag docker pull copy on repository page |
| Frontend | Nginx /v2 proxy: stream body, long timeouts |
| Backend | Drain POST body in initiateBlobUpload for same-host push |
| Backend | Relative Location URLs, monolithic async uploads, remove manual 100-Continue for reverse proxy compatibility |
| Config | Optional VITE_REGISTRY_URL in .env.example |
v1.1.1
v1.1.1
Fixed
- Frontend Docker startup
- Fixed
exec /docker-entrypoint.sh: no such file or directorycaused by CRLF line endings
in the entrypoint/template files. - Frontend image build now normalizes line endings to LF during build.
- Added
.gitattributesto keep*.sh,*.template,*.confcommitted with LF.
- Fixed
- Frontend nginx upstream (
BACKEND_UPSTREAM)- Fixed nginx crash:
invalid port in upstream "http://refity-backend:5000"when
BACKEND_UPSTREAMwas provided as a full URL. BACKEND_UPSTREAMnow accepts either:backend:5000(host:port), orhttp://backend:5000(full URL)
- Entrypoint normalizes
BACKEND_UPSTREAMand renders nginx config at container startup.
- Fixed nginx crash:
Docs / Website
- Installation section includes a complete Docker Hub–based
docker-compose.ymlexample (env
inline, noenv_file). - Added syntax highlighting for YAML/JSON/Bash code blocks.
- Fixed docs anchor navigation so headings (e.g.
#production) don’t get hidden under the
sticky header. - Configuration reference split into two tables: Backend env and Frontend env.
- Architecture section shows rendered Mermaid diagrams (system overview + request flow).
- Clarified
BACKEND_UPSTREAMformat in docs (host:port or URL).
Repo / README
- README simplified and points to web documentation.
- Clickable links + centered badges (Docker pulls backend+frontend, GitHub stars, website/docs).
Notes
- Minor release focused on Docker frontend reliability + docs/UX.
- If you use Docker Hub images, pull the latest frontend image for this tag.
v1.1.0
Release notes – v1.1.0
Highlights
- FTP Usage optional – Dashboard can run without Hetzner; when disabled, only Total Images, Total Groups, and Total Size are shown.
- Dashboard & tag size fixes – Total image count and tag size (including multi-arch) now stay correct after push.
- Responsive UI – Improved layout and navigation on mobile and tablet.
- Config & docs – Reorganized
.env.exampleand updated README.
New features
FTP Usage (Hetzner) – optional
- FTP Usage card on the dashboard is off by default so the app works for users who don’t use Hetzner Storage Box.
- Set
FTP_USAGE_ENABLED=truein.envand configureHCLOUD_TOKENandHETZNER_BOX_IDto show the FTP Usage card and fetch usage from the Hetzner API. - When disabled, the dashboard only shows Total Images, Total Groups, and Total Size (no API calls to Hetzner).
Bug fixes
- Dashboard cache – After pushing an image, the dashboard now refreshes correctly (total images, repo count, and stats).
- Tag size (multi-arch) – Tag size is now computed from the sum of layer sizes of each platform manifest (e.g. ~135 MB) instead of the manifest list JSON size (e.g. 3.99 KB).
- Mobile menu – Separator lines in the burger menu now span the full width on expand.
Improvements
Environment & configuration
.env.example– Reorganized into sections:- Storage (SFTP)
- Server
- Security & Auth
- Dashboard – FTP Usage (Hetzner)
- Frontend (Vite)
- Each variable has a short comment (required/optional, default, usage).
- README – Documented FTP Usage default and when to enable it for Hetzner.
UI / layout
- Dashboard – When FTP Usage is hidden, the three stat cards (Total Images, Total Groups, Total Size) use equal width and fill the row.
- Responsive layout (mobile & tablet):
- Navbar – Hamburger menu from 991px down; 48px touch targets; consistent padding.
- Repository (tag list) – On tablet/mobile, tags are shown as cards instead of a table; Docker pull and actions are easier to use on small screens.
- Dashboard – Stats and group cards use responsive columns (e.g. 1 column on small mobile, 2 on larger mobile, 4 on desktop).
- Group page – Repo grid: 1 / 2 / 3 columns by breakpoint; delete button has a minimum tap size.
- Login, Footer, StatCard, modals – Adjusted spacing and typography for small screens.
- Breakpoints used: 991px (tablet), 576px (mobile).
Upgrade from v1.0.0
- No breaking changes.
- Default change: FTP Usage is now disabled by default. If you use Hetzner Storage Box and want the usage card, set
FTP_USAGE_ENABLED=truein your.envand keepHCLOUD_TOKENandHETZNER_BOX_IDset. - Optional: Refresh your
.envlayout using the new.env.examplesections (your existing variable names and values stay valid).
v1.0.0
Refity v1.0.0
First stable release. Refity is a self-hosted Docker private registry with SFTP backend storage and a modern React web UI.
Highlights
- Docker Registry API v2 — Push and pull images with the standard Docker CLI
- SFTP storage — All image data stored on your SFTP server; no cloud lock-in
- React web UI — Dashboard, group-based repos, tag management, profile & change password
- JWT auth — Secure login; production options for JWT secret, CORS, and SSH host verification
- Docker images —
troke12/refity-backendandtroke12/refity-frontendon Docker Hub
Features
Registry & storage
- Docker Registry v2 API compatible
- SFTP backend for blobs, manifests, and metadata
- Async SFTP upload with progress and retry
- Strict group/folder control (no auto-create; push fails if group missing)
- Multi-architecture support (manifest list validation)
- Digest validation and
Docker-Content-Digestheaders - Tag listing and per-tag Docker pull copy in the UI
Web UI
- Dashboard with stats and group cards
- Group-based navigation: create groups, then repositories under each group
- Create/delete repositories and tags
- Profile page with change-password form
- Login, logout, JWT-protected API calls
- Responsive layout (desktop and mobile)
Security (production)
- JWT_SECRET — Configurable via env (required in production)
- CORS_ORIGINS — Configurable allowed origins
- FTP_KNOWN_HOSTS — Optional SSH host key verification
- Path traversal fixes (repo name validation, local driver path checks)
- SECURITY.md with production checklist
Docker images
| Image | Description |
|---|---|
| troke12/refity-backend | Go server: Registry API + REST API, SFTP, SQLite |
| troke12/refity-frontend | React app served by nginx |
Tags: latest and v1.0.0 (and future version tags on release).
Quick start
git clone https://github.com/troke12/refity.git
cd refity
cp .env.example .env
# Edit .env with FTP_* and JWT_SECRET
docker-compose up -d- Frontend: http://localhost:8080
- Backend API: http://localhost:5000
- Default login:
admin/admin— change after first login.
Documentation
- README — Quick start, env, API, architecture
- SECURITY.md — Production security checklist
Full changelog
- Registry: v2 API, SFTP driver, async upload, retry, digest validation, tag list, multi-arch manifest validation, strict group/folder
- Backend: Go server, SQLite, JWT auth, REST API (auth, dashboard, groups, repos, tags, profile/change password), config (JWT, CORS, FTP known hosts)
- Frontend: React dashboard, groups, repositories, tags, profile, login/logout, Docker pull copy
- CI: GitHub Actions workflow to build and push images to Docker Hub on version tags
- Docs: README with Mermaid architecture, SECURITY.md, MIT license, updated screenshot