Maintenance Operations
This page describes the supported procedure for applying schema migrations and restarting Pkgly in a production environment.
Applying database migrations
Pkgly uses SQL files under crates/core/migrations. To apply them manually:
- Back up the database using your standard tooling.
- Connect to Postgres with the same user Pkgly runs under. Example using
psql:bashpsql "$DATABASE_URL" - Run the pending migration scripts in chronological order. Each migration consists of an
*.up.sqlfile. For example:bashRepeat for each newer migration. Keep the session open so you can roll back with the matching\i crates/core/migrations/20251115103000_repository_indexes.up.sql*.down.sqlif needed. - Verify with
SELECTor\dthat the new indexes/tables exist.
Tip: If you manage Postgres with a migration runner (e.g.,
sqlx-cliorjust migrate), use that wrapper instead of applying SQL manually.
Restarting Pkgly services
After migrations, rebuild and restart the services:
- Stop the running stack:bash
docker compose down - Rebuild (if code changed):bashor run
./dev.shnpm --prefix site run build && cargo build --features frontend - Start:bash
docker compose up -d - Check logs for migration output:bash
docker compose logs pkgly
Keep maintenance windows short: apply migrations first, then restart services once the schema is in place so requests hitting old binaries do not fail mid-migration.
Audit logging
Pkgly now emits a dedicated audit stream at info level under the pkgly::audit target.
What is logged:
- Successful user actions on the management API (
/api/**) such as user, storage, repository, security, and token operations. - Successful and denied package operations routed through repository protocol endpoints (
/v2/**,/repositories/**, direct/{storage}/{repository}/...paths). - Successful and denied search, package listing, browse, and websocket browse actions.
What is not logged in the first pass:
- Static/frontend asset requests.
/api/infoand similar low-value informational routes.- Validation, conflict, not-found, or internal-error outcomes unless the result is an authorization failure (
401or403).
Important fields:
actionoutcomeactor_usernameactor_idrepository_idstorage_idpathtrace_id
The existing access log target, pkgly::access, is still emitted separately. Use:
docker compose logs pkgly | grep 'pkgly::audit'If you run JSON logs, filter on "target":"pkgly::audit" and join on trace_id when you need to correlate an audit event with lower-level request or tracing data.
HTTP access logs on the pkgly::access target include request identity and routing fields such as trace_id, request_id, http.request.method, http.route, url.path, and http.response.status_code. They also include client.address when Pkgly can determine it from X-Forwarded-For or the connection IP, and user_agent.original when the request sends a user-agent header.
Storage usage refresh
Pkgly caches each repository's storage usage in the repository row. The background scheduler checks storage usage every 30 seconds, but each repository is recalculated at most once per hour. Repositories with no cached usage are refreshed on the next scheduler tick after startup.
Manual API reads can still request usage with include_usage=true, and admins can force recalculation with refresh_usage=true.
Browser refresh routing
Pkgly serves the Vue app with history-mode routes. Browser refreshes for paths present in site/src/router/routes.json return the SPA index.html when the request is a GET or HEAD with Accept: text/html.
Package manager endpoints are still handled by repository routes. Requests under /api/**, /v2/**, /repositories/**, /storages/**, and direct package requests that do not ask for HTML keep their package/API behavior.
For HTTP deployments, session cookies use SameSite=Lax without the Secure attribute. HTTPS deployments use SameSite=None with Secure. This lets local and plain-HTTP installs preserve a valid login across browser refreshes while keeping cross-site cookie compatibility for HTTPS.