diff --git a/README.md b/README.md index c0f85c5..ff78228 100644 --- a/README.md +++ b/README.md @@ -81,12 +81,9 @@ tool" cases) is in **Production-ready and running in production.** Releases are multi-arch (amd64 + arm64) and [cosign-signed]; the [releases page](https://github.com/StrategicProjects/ruscker/releases) -has the current version. **Current release: [v0.2.49]** — the process -Logs page uses finite cursor polling, so it cannot strand an HTTP/1.1 -connection and stall later admin navigation behind a reverse proxy. +always identifies the current version and carries its release notes. [cosign-signed]: https://strategicprojects.github.io/ruscker/installation.html#verifying-release-artifacts -[v0.2.49]: https://github.com/StrategicProjects/ruscker/releases/tag/v0.2.49 > **~14 MB idle** — measured on a real production deployment serving a > real 31-spec config, with apps spawning on demand. (The JVM-based @@ -113,13 +110,20 @@ What's in the box: - **Admin panel** — full apps CRUD (including API, scaling, resource and lifecycle settings), Media, encrypted Credentials, a live-preview Appearance editor, **Viewer / Editor / Admin** accounts with a password - policy and generator, consolidated per-user editing, server-side - pagination (50 per page) with case-insensitive search (accented letters - included) across usernames, groups and profiles, and an **Activity** section - with two tables — **user activity** (logins and interactive app accesses, - filterable and paginated) alongside the **administrative audit** trail. - The process-log terminal follows new lines with finite cursor requests, - pauses while hidden and never occupies a persistent browser connection. + policy and generator, and group-scoped delegation: Editors administer open + apps plus apps whose `access-groups` overlap their own, and non-Admin users + and memberships in those groups; Admin and break-glass sessions stay global, + while Media remains shared. Out-of-scope app and user ids deliberately + return `404`. User deletion, CSV import, MFA reset, and group + create/rename/delete remain Admin-only. The Users page has consolidated + editing, server-side pagination (50 per page), and case-insensitive search + (accented letters included) across usernames, groups and profiles. The + **Activity** section pairs user activity (logins and interactive app + accesses, filterable and paginated) with the administrative audit trail. + Historical timestamps render in each viewer's browser timezone and table + dates sort by instant. The process-log terminal also shows viewer-local + times, follows new lines with finite cursor requests, pauses while hidden + and never occupies a persistent browser connection. - **Containers dashboard** — CPU/memory sparklines, live-follow logs and stop/restart controls, refreshed every five seconds by polling `GET /admin/dashboard/snapshot`. @@ -149,6 +153,9 @@ What's in the box: with an optional command override, run history/log tail, leader-only HA firing, no fire-on-create, one catch-up after downtime and a per-run timeout (1 hour by default); failures raise the `job-failed` alert webhook. + Cron is evaluated in the IANA zone configured by `server.timezone`, and + next/last-run times are labelled in that same zone. Unset or invalid values + use UTC, with invalid names reported as validation warnings. (Local Docker backend; not yet available with the multi-host backend.) - **Named-volume management** — the Disk panel lists Docker volumes with live reference counts and can create labelled volumes; removal is offered diff --git a/book/src/alternatives.md b/book/src/alternatives.md index 6e6f2bb..21557d5 100644 --- a/book/src/alternatives.md +++ b/book/src/alternatives.md @@ -19,14 +19,16 @@ catalog and operational state live in SQLite, or Postgres for HA. landing page; anything in a container is first-class. - **A real admin panel** — apps CRUD, a media library, an encrypted credentials store, a live monitoring dashboard, an audit log, and user - roles — instead of editing a file and restarting. + roles — instead of editing a file and restarting. Editors are scoped by + their group memberships to open/shared-group apps and non-Admin accounts; + Admin and break-glass access remains global. - **Sensitive internal apps** — per-app step-up TOTP MFA is enforced before a container starts, while opt-in identity headers let the app consume the signed-in username, groups and selected profile claims. - **Scheduled operations** (local Docker backend) — run the same app image/environment/volumes to completion on a cron for ETL, reports and - maintenance, with history, - timeouts and failure alerts. + maintenance, with history, timeouts and failure alerts. Cron uses the IANA + zone in `server.timezone` and remains on UTC when unset. - **Light to run** — **~14 MB idle**, a single static binary, no JVM and instant startup. @@ -104,7 +106,7 @@ the systemd service) and reference them by name in the YAML. Ruscker schedules onto Docker hosts (over ssh/tcp), not Kubernetes. - You need **enterprise SSO gating app access per user** (OIDC/SAML/LDAP at the proxy level) — Ruscker's auth covers admin roles (Viewer / - Editor / Admin) and per-app visibility (`access-groups` / + group-scoped Editor / Admin) and per-app visibility (`access-groups` / `access-users`), but proxy-level SSO is not yet supported. If none of those apply, start with the [Quickstart](./quickstart.md). diff --git a/book/src/deploying.md b/book/src/deploying.md index 773173a..d454c3b 100644 --- a/book/src/deploying.md +++ b/book/src/deploying.md @@ -28,6 +28,34 @@ Keep `RUSCKER_MASTER_KEY` backed up: 2FA enrolment returns `503` without it, and changing it makes existing encrypted credentials and TOTP factors undecryptable. +### Choose the scheduler clock + +Scheduled jobs interpret five-field cron expressions in the IANA timezone +configured in `ruscker.yml`: + +```yaml +server: + timezone: America/Recife +``` + +When the field is absent, cron stays on UTC, so upgrading does not move +existing schedules. An invalid name emits a validation warning and also +falls back to UTC rather than blocking startup. The Schedules page labels +next/last runs in the effective zone, and the startup banner includes that +zone. This setting is intentionally separate from historical admin +timestamps: Activity, audit, Apps, Credentials, Users and process Logs use +the timezone of each viewer's browser. + +### Delegate administration by group + +Editor memberships are authorization boundaries, not just labels. Editors +can administer open apps, apps whose `access-groups` overlap their own, +and non-Admin Viewer/Editor accounts and memberships in those groups. Media +is shared across Editors. Admin accounts and the break-glass token remain +global; account deletion, user CSV import, MFA reset, and group +create/rename/delete remain Admin-only. Direct requests for an out-of-scope +app or user return `404`, deliberately hiding whether the identifier exists. + ## 2. Enable the container backend The shipped unit serves landing + admin + proxy but not the `--docker` @@ -196,9 +224,9 @@ the flag already. > } > ``` > -> Since v0.2.49, `/admin/logs/stream` is a retired compatibility endpoint -> that returns `204 No Content`, which tells pages loaded before a rolling -> upgrade to stop reconnecting. Do not proxy it as a long-lived stream. +> `/admin/logs/stream` is a retired compatibility endpoint that returns +> `204 No Content`, which tells pages loaded before a rolling upgrade to +> stop reconnecting. Do not proxy it as a long-lived stream. ## 4. Side-by-side with ShinyProxy diff --git a/book/src/faq.md b/book/src/faq.md index 333592c..b642647 100644 --- a/book/src/faq.md +++ b/book/src/faq.md @@ -47,10 +47,19 @@ one warm). Routing is least-connections (interactive) or round-robin ### How does authentication work? The **admin panel** has user accounts with Viewer / Editor / Admin -roles (plus a break-glass token). The same accounts gate **per-app -visibility**: every spec can declare `access-groups` / `access-users` -and only matching users see the card and reach `/app` / `/api` -(specs with no access keys remain open to anyone) — +roles (plus a break-glass token). An Editor's own group memberships are +also their administrative boundary: they can manage open apps, apps whose +`access-groups` overlap their groups, and non-Admin Viewer/Editor accounts +and memberships in those groups. They can create those accounts and reset +their passwords; account deletion, CSV import, MFA reset, and group +create/rename/delete remain Admin-only. The Media library is shared. +Out-of-scope app and user ids return `404` so guessed identifiers do not +reveal another team's resources. Admin and break-glass sessions remain +unrestricted. + +The same accounts gate **per-app visibility**: every spec can declare +`access-groups` / `access-users` and only matching users see the card and +reach `/app` / `/api` (specs with no access keys remain open to anyone) — see [Per-user access](./configuration.md#per-user-access). An app can also set `require-mfa: true` for a TOTP step-up before its container starts; each user enrols one factor and each app chooses how @@ -90,6 +99,19 @@ also accepts a pure `${VAR}` env-ref as the password (stored verbatim, resolved at pull time, so the decryption key is never needed for env-based credentials). +### Which timezone does Ruscker use? + +Stored instants remain UTC. Historical timestamps in Activity, audit, Apps, +Credentials, Users and process Logs render in the viewer's browser timezone; +sortable table dates compare the underlying instant, not the formatted text. +Hover a table timestamp for its full localized date and zone. + +Cron has no viewer, so the scheduler uses the IANA zone in +`server.timezone`. The Schedules page labels next/last runs in that zone. +Leaving it unset preserves the historical UTC behavior, so an upgrade does +not move existing jobs. An invalid name warns and also falls back to UTC; +the effective zone is included in the startup banner. + ### Can I run more than one instance for high availability? Yes. Several instances share one Postgres for the catalog and session diff --git a/book/src/installation.md b/book/src/installation.md index 8c05d25..d5d97ff 100644 --- a/book/src/installation.md +++ b/book/src/installation.md @@ -43,6 +43,19 @@ in the catalog DB, not the YAML). `ruscker.yml` holds **deployment** settings; put secrets in `/etc/ruscker/ruscker.env`. After editing either file, `sudo systemctl restart ruscker`. +One such deployment setting is the scheduler's clock. Cron stays on UTC +unless you opt into an IANA timezone: + +```yaml +server: + timezone: America/Recife +``` + +The Schedules page labels next and last runs in the effective zone. An +invalid name produces a validation warning and falls back to UTC instead of +preventing startup. This setting does not change admin history timestamps, +which render in each viewer's browser timezone. + To actually run the demo apps (and your own containers) enable the Docker backend: `sudo ruscker-enable-docker`. See [Deploying in production](./deploying.md) for nginx + TLS, and diff --git a/book/src/introduction.md b/book/src/introduction.md index a97599f..de1b7a3 100644 --- a/book/src/introduction.md +++ b/book/src/introduction.md @@ -79,11 +79,17 @@ what's next. env-ref, resolved only at pull time), a landing-page editor (colors, intros, SEO, social meta, analytics, custom HTML blocks, header/footer logos with alignment and links), audit log, **user accounts with - Viewer / Editor / Admin roles**, and a live monitoring dashboard - (CPU/memory, live-follow logs, stop/restart). On the local Docker backend, - operators can also manage named Docker volumes and run a spec's image to - completion on a cron schedule, with history, log tails, timeouts and - failure alerts (both are unavailable with the multi-host backend). + Viewer / Editor / Admin roles** and group-scoped Editor delegation, and a + live monitoring dashboard (CPU/memory, live-follow logs, stop/restart). + Editors manage open apps, apps and non-Admin accounts in their own groups, + while Admin and break-glass access stays global; the Media library remains + shared. Admin tables and process logs show timestamps in each viewer's + browser timezone. On the local Docker backend, operators can also manage + named Docker volumes and run a spec's image to completion on a cron + schedule, with history, log tails, timeouts and failure alerts. Cron uses + `server.timezone` (an IANA name) and remains on UTC when it is unset or + invalid; the Schedules page labels the effective zone. Volumes and + schedules are unavailable with the multi-host backend. - **Sub-path mounting**: serve the whole portal under a prefix via `server.context-path` or `--base-path`. Health probes (`/healthz`, `/readyz`) stay at the root for load balancers. diff --git a/book/src/migrating.md b/book/src/migrating.md index 37ee2bd..4ce3958 100644 --- a/book/src/migrating.md +++ b/book/src/migrating.md @@ -160,14 +160,32 @@ says what to do for each. Two to know about: instead of Shiny's 3838 — apps that previously needed an explicit port "just work"; an explicit `container-port`/`port:` still wins. +### Preserve or choose the scheduler clock + +An existing schedule keeps its historical behavior after migration: +without `server.timezone`, Ruscker evaluates cron in UTC. To write cron in +a local clock, opt in with an IANA name: + +```yaml +server: + timezone: America/Recife +``` + +The Schedules page then labels next and last runs in that zone. Invalid +names produce a validation warning and fall back to UTC instead of blocking +startup; the startup banner reports the effective zone. This does not +control Activity, audit, Apps, Credentials, Users or process Logs, whose +stored UTC timestamps render in each viewer's browser timezone. + ## What Ruscker adds Beyond parity, you also get: a real admin panel (no more hand-editing YAML), a live monitoring dashboard, per-spec `container-env` / `container-cmd` injection, per-API rate-limiting and CORS, per-user -and per-group app visibility, per-app step-up MFA, scheduled jobs and named -volume management (local Docker backend), health probes, graceful shutdown, -and **~14 MB idle**. +and per-group app visibility, group-scoped Editor delegation, viewer-local +admin timestamps, per-app step-up MFA, timezone-aware scheduled jobs and +named volume management (local Docker backend), health probes, graceful +shutdown, and **~14 MB idle**. The JVM-based proxy it replaced on the same machine used about 540 MB. See [The admin panel](./admin.md). diff --git a/book/src/quickstart.md b/book/src/quickstart.md index ef39334..3cc4841 100644 --- a/book/src/quickstart.md +++ b/book/src/quickstart.md @@ -28,6 +28,18 @@ The master key encrypts saved registry credentials and TOTP secrets. Keep it stable if you reuse this database; without it, 2FA enrolment fails with `503`. +If you plan to use **Schedules**, choose the clock in which cron expressions +are written by adding an IANA timezone to the service config: + +```yaml +server: + timezone: America/Recife +``` + +Leave it out to retain the UTC default. This setting controls the scheduler +and its labelled next/last-run times; Activity, audit, Apps, Credentials, +Users and process Logs instead follow the timezone of each viewer's browser. + - Ruscker **auto-connects to Docker** when the daemon socket is reachable, so app containers spawn out of the box. Pass `--no-docker` to run landing-only (then `/app/*` returns 503); pass `--docker` to @@ -124,7 +136,9 @@ limits…). apps by user / group. - [The admin panel](./admin.md) — manage specs, images, users, and the live container dashboard, identity headers, per-app 2FA and schedules - without editing YAML. + without editing YAML. Editors are delegated by group: they see open apps + plus apps and non-Admin accounts in groups they belong to; Admin and the + break-glass token remain global. - [Deploying in production](./deploying.md) — systemd + nginx, TLS, multi-host, and active-active HA (including [mounting the portal under a subpath][base-path] and the diff --git a/book/src/troubleshooting.md b/book/src/troubleshooting.md index f79deac..d8ea767 100644 --- a/book/src/troubleshooting.md +++ b/book/src/troubleshooting.md @@ -218,9 +218,44 @@ slow/unreachable Docker daemon will stop or delay updates. nginx buffering does not affect the dashboard poll; it matters only for the explicitly enabled per-replica live log stream described in [Deploying](./deploying.md). +## Admin dates or scheduled-job times look wrong + +These use two intentionally different clocks: + +- Activity, audit, Apps, Credentials and Users table dates render in the + viewer's browser timezone. Process Logs also show the viewer-local clock. + Check the operating system/browser timezone if they are wrong. Hover a + table date for the full localized timestamp and zone; process-log hover + keeps the original UTC token for correlation with downloaded logs. +- The scheduler evaluates cron in `server.timezone`, and the Schedules page + labels next/last runs in that zone. With no setting, it uses UTC. Use an + IANA name such as `America/Recife`, not an abbreviation such as `BRT`. + +Run `ruscker validate ` after changing the setting. An invalid name +produces a warning and falls back to UTC without blocking boot. The startup +banner's `timezone` field shows the effective scheduler zone. + +## An Editor cannot find an app or user + +This is normally the group scope working as designed. An Editor sees and +administers: + +- apps with no `access-groups` or `access-users`, plus restricted apps whose + `access-groups` overlap the Editor's groups; +- non-Admin users who share at least one of those groups, and memberships in + the Editor's own groups; +- the shared Media library. + +An app restricted only by `access-users` has no group boundary and is +Admin-only. Direct requests for an out-of-scope app or user return `404`, +not `403`, so a guessed id cannot reveal another team's resource. Editors +may create Viewer/Editor accounts in their groups and reset their passwords, +but deleting accounts, importing users by CSV, resetting MFA, and +creating/renaming/deleting groups require Admin or break-glass access. + ## Admin navigation hangs after visiting Logs -Upgrade to **v0.2.49 or newer**. Older releases automatically opened +Upgrade to a current release. Older builds automatically opened `/admin/logs/stream` as an infinite SSE response. On a deployment with an HTTP/1.1 load-balancer or reverse-proxy hop, an intermediary could retain that response after browser navigation and then reuse the same backend diff --git a/book/src/use-cases.md b/book/src/use-cases.md index 857825e..06ec44d 100644 --- a/book/src/use-cases.md +++ b/book/src/use-cases.md @@ -123,12 +123,14 @@ Multiplex GPUs and isolate users in front of generative tools. ## Scheduled ETL, reports and maintenance The admin **Schedules** page (local Docker backend) can run a containerized -spec to completion on a five-field UTC cron: nightly ETL, report generation, -cache refreshes or -small maintenance tasks. A job reuses the spec's image, environment, -volumes, resource limits and registry credentials, with an optional command -override. Runs have a configurable timeout (1 hour by default), history and -log tails; failures can trigger the `job-failed` alert webhook. +spec to completion on a five-field cron: nightly ETL, report generation, +cache refreshes or small maintenance tasks. Set `server.timezone` to an IANA +name for the clock the expressions use; absent or invalid values retain the +UTC default. The Schedules page labels next and last executions in that same +zone. A job reuses the spec's image, environment, volumes, resource limits +and registry credentials, with an optional command override. Runs have a +configurable timeout (1 hour by default), history and log tails; failures can +trigger the `job-failed` alert webhook. ## Database admin consoles @@ -158,8 +160,8 @@ Surface a DB console as just another card on the portal. ## Who it's for -- **Public sector, universities and research centers** publishing analytics - dashboards for the public or for staff. +- **Universities, research centers and other organizations** publishing + analytics dashboards for broad or internal audiences. - **BI and data-science teams** that want to ship R/Shiny and Python/Streamlit apps without standing up a Kubernetes cluster. - **Consultancies** delivering analytical tools to each client at isolated