Nextcloud app: surface real /srv XFS quota for the configured customer
  • PHP 91.4%
  • Shell 4.9%
  • CSS 3.7%
Find a file
raf 4d1322f36e v0.2.0: host-only config — /etc/customquota/config.json (root-writable)
Loop task 270: move customer name + two warning-email recipients out of
Nextcloud's oc_appconfig (which any NC admin can edit) into a JSON file on the
host. The file is bind-mounted read-only into the container, so only the host
root can change which customer this instance reports on or where warnings go.

- new lib/Service/HostConfig.php reads + validates the file
- QuotaService no longer takes a $customer arg; pulls it from HostConfig
- admin UI becomes display-only for those three fields (form + JS removed)
- xfs_quota_path and mount remain in oc_appconfig (deployment plumbing)
- warning emails are stored but not yet sent (cron worker = future work, noted
  in README roadmap)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:16:04 +02:00
appinfo v0.2.0: host-only config — /etc/customquota/config.json (root-writable) 2026-05-20 15:16:04 +02:00
bin customquota v0.1.0 — Nextcloud "Customer XFS quota" admin app 2026-05-20 10:42:20 +02:00
css v0.2.0: host-only config — /etc/customquota/config.json (root-writable) 2026-05-20 15:16:04 +02:00
lib v0.2.0: host-only config — /etc/customquota/config.json (root-writable) 2026-05-20 15:16:04 +02:00
templates v0.2.0: host-only config — /etc/customquota/config.json (root-writable) 2026-05-20 15:16:04 +02:00
.gitignore customquota v0.1.0 — Nextcloud "Customer XFS quota" admin app 2026-05-20 10:42:20 +02:00
README.md v0.2.0: host-only config — /etc/customquota/config.json (root-writable) 2026-05-20 15:16:04 +02:00

customquota — Nextcloud "Customer XFS quota" app

Surfaces the real on-host filesystem quota of /srv for the configured customer inside Nextcloud's admin settings, by parsing the output of:

xfs_quota -x -c "report -h" /srv

The output is filtered to rows whose first column (project/user/group name) matches the configured customer substring.

Host config — /etc/customquota/config.json

The customer name and warning-email recipients are read from a JSON file on the host. The file is bind-mounted read-only into the Nextcloud container so that Nextcloud admins (who can edit oc_appconfig) cannot change which customer this instance reports on or where warnings go — only the host root can edit it.

{
  "customer": "acme",
  "warning_email_primary": "ops@example.com",
  "warning_email_secondary": "billing@example.com"
}
  • customer — substring filter against the first column of the xfs_quota report. Validated [A-Za-z0-9._-]{1,64}.
  • warning_email_primary / warning_email_secondary — recipients for quota-warning notifications. Either can be empty (string).

Required file perms on the host (raf:raf installation):

sudo install -d -m 0755 -o root -g root /etc/customquota
sudo install -m 0644 -o root -g root config.json /etc/customquota/config.json

The bind mount is declared in ~/.config/containers/systemd/nextcloud-app.container:

Volume=/etc/customquota:/etc/customquota:ro

App-config (occ) — deployment plumbing only

Two settings live in Nextcloud's app-config because they're deployment plumbing, not customer-facing config:

# path to the xfs_quota binary (default: "xfs_quota" via PATH)
podman exec -u www-data nextcloud-app php occ config:app:set customquota \
    xfs_quota_path --value=/var/www/html/custom_apps/customquota/bin/xfs_quota-mock

# mount to inspect (default: /srv)
podman exec -u www-data nextcloud-app php occ config:app:set customquota mount --value=/srv

Files

appinfo/{info.xml,routes.php}
lib/AppInfo/Application.php
lib/Settings/{AdminSection,Admin}.php
lib/Service/HostConfig.php               — loads /etc/customquota/config.json + validates
lib/Service/QuotaService.php             — xfs_quota invocation + parser
lib/Controller/QuotaController.php
templates/admin.php                      — read-only admin panel
css/admin.css
bin/xfs_quota-mock                       — fake xfs_quota for local demo

Security model

  • Customer name is regex-validated [A-Za-z0-9._-]{1,64} before use; emails are validated via filter_var(FILTER_VALIDATE_EMAIL).
  • xfs_quota is invoked via proc_open with an argv array, never a shell string — no metacharacter escaping concerns.
  • Filtering is a literal PHP stripos() substring match in PHP-land; the customer string never reaches the shell.
  • Host config is mounted read-only; Nextcloud admins cannot edit it from the admin UI.

Versions

  • 0.2.0 (2026-05-20) — customer + warning emails moved into /etc/customquota/config.json (root-only writable); admin UI is now display-only for those values (loop task 270).
  • 0.1.0 (2026-05-20) — initial release (loop task 267).

Roadmap — not yet implemented

  • A cron-driven worker reading the report, checking per-row used against soft/hard, and emailing warning_email_primary (and _secondary) when crossings happen. v0.2.0 stores the addresses but does not yet send any mail. Likely a small Cron\SendQuotaWarnings background job + a state file so warnings aren't re-sent every tick.

Forgejo

https://ps1raf.tn.ps1.at:3300/raf/customquota