Actions
40 actions, callable from the panel, the command palette and the API as
POST /api/v1/a/<id>. Internal actions used between modules are not listed.
| Action | What it does | Risk | Preview |
|---|---|---|---|
web.server.ensure | Prepare the web server on a node | high | No dry run |
web.php.versions | PHP versions offered on a node (read) | low | No dry run |
web.sites.create | Create a site for a domain | medium | |
web.sites.list | List sites (read) | low | No dry run |
web.sites.get | Get a site (read) | low | No dry run |
web.sites.update | Change a site | medium | |
web.sites.suspend | Suspend a site | medium | No dry run |
web.sites.unsuspend | Resume a site | medium | No dry run |
web.sites.delete | Delete a site | high | |
web.sites.reconcile | Re-apply every site of a node | medium | No dry run |
web.layers.get | Read a configuration layer (read) | low | No dry run |
web.layers.set | Replace a configuration layer | medium | |
web.layers.effective | Effective configuration of a site (read) | low | No dry run |
web.config.history | Configuration history of a node (read) | low | No dry run |
web.config.status | Hand-edited web files (read) | low | No dry run |
web.config.adopt | Keep a hand edit on top of the panel configuration | medium | |
web.config.reapply | Render the panel version again | medium | |
web.config.reset | Reset a file to defaults | medium | |
web.config.unmanage | Hand a file off to the administrator | high | |
web.config.rollback | Roll a node's web configuration back to an earlier generation | high | |
web.sites.cert.install | Install a certificate on a site | medium | No dry run |
web.sites.cert.remove | Remove the certificate of a site | medium | No dry run |
web.sites.set_nodes | Serve a site from several nodes | high | |
web.panel_host.ensure | Serve the panel on a hostname | medium | |
web.panel_host.remove | Stop serving the panel on a hostname | medium | |
web.panel_host.list | List panel hostnames (read) | low | No dry run |
web.static.publish | Publish an error, suspension or parked page | medium | |
web.static.remove | Stop serving a customer page on a hostname | medium | No dry run |
web.static.list | List hostnames that serve customer pages (read) | low | No dry run |
web.config.reset_all | Reset the whole service to defaults | high | |
web.config.snapshot.list | Snapshots before resets (read) | low | No dry run |
web.config.snapshot.restore | Undo a reset (restore a snapshot) | high | |
web.config.history.list | Earlier versions of a file (read) | low | No dry run |
web.config.history.diff | Compare an earlier version (read) | low | No dry run |
web.config.history.restore | Restore an earlier version of a file | high | |
web.unmanaged.scan | Scan now for objects made outside the panel | low | No dry run |
web.unmanaged.list | Objects made outside the panel (read) | low | No dry run |
web.unmanaged.ignore | Ignore or un-ignore an unmanaged object | low | |
web.unmanaged.adopt | Adopt an object made outside the panel | medium | |
web.unmanaged.remove | Remove an object made outside the panel | high |
Permissions and limits
Permissions
web.viewView sitesweb.manageCreate, change and delete sitesweb.config.editEdit configuration layers and snippets of own sitesweb.cert.installInstall a certificate on a siteweb.adminPrepare web servers, edit global/server/plan layers, roll back configuration
Plan limits
web.sitesWebsites
Engineering notes
Generated from modules/web/docs.md at build d90e9e2. These are the notes the engineers keep
next to the code: precise, technical, and honest about what is not done yet.
nginx (Ubuntu/Debian package) + one PHP-FPM master per account and PHP version. Spec: docs/specs/web.md.
Runtimes beyond PHP, presets, deploys, WordPress toolkit are the next card.
How a change is applied (AC-web-12/13/16)
The agent keeps immutable numbered generations under /var/lib/respirecloud/web/gen/<N>/ and a symlink
/etc/respirecloud/web/current -> gen/<N>. nginx and every PHP-FPM unit read their config through that symlink
(nginx -c /etc/respirecloud/web/current/nginx/nginx.conf, nginx uses only relative includes).
web.config.apply = load generation N, apply the change, render N+1 into a scratch dir, run nginx -t -c and
php-fpm -t -y on the exact bytes that would go live, diff, create docroot/tmp/log dirs (symlink-safe), swap the
symlink (atomic rename), start/reload units, health-check every changed site through the local nginx
(refused connection or HTTP 500; 502/504 for PHP sites) and on any failure flip back to N and reload again.
A rejected snippet never touches the live config (hash unchanged). History = the generation list
(web.config.history, who/when/why also in m_web.history); web.config.rollback activates an older generation
with the same validate/health/auto-revert flow. 30 generations are kept. dry_run on create/update/delete/layers.set/
rollback returns the per-file unified diffs as a module.Plan.
Layers (AC-web-14/15)
global -> server (node) -> plan -> reseller -> user (account) -> site. Typed settings (http.*, php.*, pool.*,
proxy.allow_internal, site.auto_create) are merged lowest-authority-first; a layer may lock keys so lower layers
cannot change them (checked on save and on resolve). Admin-only keys cannot be set from reseller/user/site layers.
web.layers.effective shows value + source layer. Snippet hooks: http (admin layers only; users may only declare
limit_req_zone/limit_conn_zone prefixed with their account name), server, location, php_ini, php_pool.
Plan/reseller ids of an account come from core.principal.get; when the caller may not read them the last value seen
(account_ctx) is used so everybody resolves the same layers.
Snippet allow-list (non-admin layers)
Parsed with a real tokenizer; every statement is checked, errors carry the line number. Allowed: timeouts, gzip,
add_header, expires, index, try_files, return, rewrite, error_page, allow/deny, auth_basic(+file inside home),
root/alias inside the account home only (no variables, no ..), set (not $rc_*), proxy_pass to non-internal http(s)
hosts, proxy_set_header, limit_req/limit_conn (own zones), access_log off, location and a restricted if.
Rejected with a reason: load_module, lua/perl/js, include (all), fastcgi_pass/uwsgi/scgi, server_name, listen,
ssl_*, error_log, fastcgi_param, disable_symlinks, resolver, real_ip*, anything unknown. php.ini keys are allow-listed
with value checks; open_basedir must stay inside home; pool keys are bounded (pm.max_children 1..200).
The agent re-validates everything (control plane validation is only for early, friendly errors).
Isolation (AC-web-18)
- PHP workers run as the account user, in a per-account master unit
rc-fpm-<ver>-<user>.servicewithSlice=rc-acct-<user>.slice(limits fromsystem.slice.applyapply). Socket/run/rc-php/<user>-<ver>.sockisuser:www-data 0660: other accounts cannot connect;fastcgi_passcannot be written by users. - Pool-level
php_admin_value[open_basedir]= own home (+tmp, system php dirs); upload/session/tmp dirs in~/tmp. - nginx runs as www-data, which is added to each account's group to read docroots; every server block has
disable_symlinks if_not_owner from=$document_root, so a symlink to another account's files is not served. - Docroot/tmp creation by root uses
openat(O_NOFOLLOW)per component: a symlink planted in the home cannot redirect it. - Logs live in root-owned
/var/log/rc-web/<account>/(a user cannot symlink a log file onto a root-opened path). - Unknown Host headers get a neutral 404 page (
00-default.conf); the https default server rejects the handshake. - Proxy sites to loopback/private hosts are refused unless the admin sets
proxy.allow_internal=true(SSRF to the panel).
Actions
web.server.ensure (prepare node: nginx, PHP versions from the distro or the Ondrej Sury repo with pinned key
fingerprint, drop-in for nginx.service, generation 1), web.php.versions, web.sites.create|list|get|update|suspend| unsuspend|delete|reconcile, web.layers.get|set|effective, web.config.history|rollback,
web.sites.cert.install|remove, and event handlers web.on_domain_created (auto site per plan's site.auto_create;
alias/parked join the parent's site), web.on_domain_deleted, web.on_account_deleted.
Site types: static, php, proxy. Limit key web.sites. Resource kind site for sub-user grants.
Contract for the ssl module
- HTTP-01: every plain-HTTP server block (and the default vhost) serves
/.well-known/acme-challenge/from/var/lib/rc-acme(agentop.WebAcmeDir), before any redirect rule. The ssl agent writes the token file to/var/lib/rc-acme/.well-known/acme-challenge/<token>(dir exists, root 0755). Works for any domain of any site, and unknown hosts. - Install a certificate: call action
web.sites.cert.install {site_id, fullchain_pem, privkey_pem}. The agent validates the pair (tls.X509KeyPair, not expired), stores them in/etc/respirecloud/web/certs/<site id>/(fullchain.pem,privkey.pem0600), re-renders the site with alisten 443 sslblock (HTTP/2, HSTS when thehttp.hsts_max_agesetting is > 0) through the normal validate/swap/health/rollback path, and restores the previous certificate if that fails.web.sites.update {force_https:true}turns plain HTTP into a 301 to https (ACME path stays on HTTP).web.sites.cert.removereverses it. Certificates live outside the generations, so a rollback never loses them. All names of the site (primary, serve and redirect aliases) share the one certificate; ssl should request a SAN cert forweb.sites.get->domains[].name. - Events:
web.site.created|updated|deletedcarry site id and account for ssl to react (issue after create).
Verified
See docs/tasks/w2/w2-02-web.handover.md for the lab run (curl proofs, failed-config rollback, isolation).
Not done / next
PHP version install/remove by admin after first ensure (only additive via web.server.ensure), per-site pool tuning
beyond the first site of a pool, extension manager, error/slow log viewers, permission fixer, site manifest
export, UI bundle, basic-auth/IP/hotlink consumption from security, FrankenPHP, runtimes.