Run fixed administrator operations
Root operations let your app run a small set of shell scripts as root. You write the scripts in your project. An administrator approves them once. Your backend then runs one by name with root.run. Your app cannot run anything that is not in the approved set, and it cannot pass arguments.
Add it
Section titled “Add it”Create a project with the capability, or add it to a native-go project:
synopkgland init -t native-go --with root-ops my-appsynopkgland add root-opsBoth write capabilities: {rootOps: true} to dsm-package.yaml. os_min_ver must be at least 7.1-42661: init uses that value by default, and add root-ops raises a lower value to it and prints the change. A new project contains the example root/whoami.sh, which prints the user id of the process:
#!/bin/shset -euid -urootOps: true requires at least one root/<name>.sh. A root/ directory without rootOps: true fails check.
Script rules
Section titled “Script rules”| Rule | Value |
|---|---|
| File name | ^[a-z][a-z0-9_]{0,31}\.sh$, for example restart_service.sh |
Other files in root/ | Not allowed |
| File type | Regular file. No symlinks or directories. |
| Size | At most 16 KiB (16384 bytes) |
| First line | Exactly #!/bin/sh, followed by a newline |
| Content | UTF-8 text with no NUL byte |
| Arguments | None. The script runs as /bin/sh with no arguments. |
| Environment | PATH=/usr/syno/bin:/usr/syno/sbin:/usr/bin:/bin, LANG=C, HOME=/. Working directory /. |
| Run time | 20 seconds, then the script is stopped |
| Output | Standard output and standard error are merged. At most 1 MiB is returned. |
Approval
Section titled “Approval”The administrator approves the package once, with the DSM password, in the app’s Administrator setup panel. In a Vue UI this is the SDK’s ExecutorSettings, and the DSM window adds no panel of its own. The generated Vue project holds its pages in ExecutorGate.vue with the SDK’s ExecutorApproval until the executor is approved, with preload: the pages mount hidden while the executor’s first status is read, so their own reads run alongside it. A page that opens a dialog by itself on mount waits until the SDK’s useExecutorPreload() is false. In a plain UI the DSM window shows the approval panel in the DSM language, and the administrator enters the password inside it. An empty password sends nothing. Approval records a digest (SHA-256) of every root/*.sh file, so only the exact scripts the administrator approved can run.
The Vue approval view explains why the app needs system privileges before asking for the password. ExecutorApproval uses a centered consent card; ExecutorSettings and the ExecutorNotice dialog use the inline layout. Administrators enter their current DSM account password and select Approve. DSM checks it directly; the app’s service does not receive or store it. About this approval expands the declared-task limits, update behavior and uninstall steps. Ordinary users see instructions to ask an administrator, without a password field.
An empty submission shows a field message and sends no request. A password refusal appears under the field and keeps the entered value for retry. Other setup failures retain their specific error message. purpose on ExecutorApproval, ExecutorNotice and ExecutorSettings supplies the app’s own reason for using system privileges; AppShell’s built-in notice uses the generic reason. Background saves supply their file-work purpose automatically.
These changes force a new approval:
| Change | New approval |
|---|---|
Any edit to any root/*.sh file | Yes |
Adding or removing a root/*.sh file | Yes |
Changing entry in dsm-package.yaml | Yes |
Changing the delegate scope, destinations, operations or lifetime | Yes |
| Changing the version number or the UI only | No |
synopkgland status shows needs-admin-approval until the administrator approves, and ready when the approval matches the installed package.
The executor reads its approval tasks back itself: at start, every 10 minutes after a pass and every minute after a failure. executor.status reports the verdict as tasks (checking, ready, stale or failed). When the pinned, running executor reports ready, the DSM window shows an administrator the app without reading Task Scheduler and EventScheduler itself. A task another administrator changes after a readback is seen at the next one, up to 15 minutes later; until then a changed control task is still refused before the next start, stop or purge. Approval, the control task’s start, stop and purge, and a scope selection always read both tasks back first.
DSM 7.1
Section titled “DSM 7.1”DSM 7.1 has no SYNO.Core.TaskScheduler.Root or SYNO.Core.EventScheduler.Root, so approval there uses older APIs:
| Step | DSM 7.1 | DSM 7.2 and later |
|---|---|---|
| Check the password | PasswordConfirm v1 | PasswordConfirm v2, which returns a one-use token |
| Create the tasks | EventScheduler create v1 and TaskScheduler create v3 | EventScheduler.Root and TaskScheduler.Root, with the token |
| Read the tasks back | TaskScheduler list v2 and get v3 | TaskScheduler list v3 and get v4 |
The app, status, deploy and uninstall use the DSM 7.1 APIs only when DSM answers a scheduler read or the password check with an unsupported version: error 104 (the version is above the API’s maximum, as for PasswordConfirm v2 and TaskScheduler get v4) or error 103 (the method is missing at that version, as for TaskScheduler list v3). Other errors, such as a wrong password (3121) or a permission or session error, are reported and never retried with another version, and a create is never retried. On both paths both tasks are read back, and nothing runs until their owner, type and script match exactly.
If approval stops after it created one task, for example the boot task was created and the control task was not, the app deletes the tasks it created in that attempt, so no root boot task is left without its control task. The same happens when both tasks were created and read back but the executor then does not start or report healthy: without a running executor there is no task inventory, so uninstall could not find them. Tasks from an earlier approval stay. When approval fails, the app’s settings show the reason with its DSM error code, and after a wrong password or a withdrawn approval the window panel and the settings still offer to approve again.
On DSM 7.1 the password is checked, but DSM does not tie the creation of the root tasks to that check. Any signed-in administrator session can create root tasks on DSM 7.1 without your app. The check records the administrator’s consent; it is not the server-enforced proof that DSM 7.2 and later give.
The DSM 7.1 path is tested against the API set and error codes measured on DSM 7.1.1-42962. With the 104 fallback it is not yet verified on a NAS.
| Event | Effect |
|---|---|
| Stop the package | Root operations pause. The approval is kept. |
| Upgrade | Operations resume only if the approved policy and scripts still match; a new executor that matches the INFO anchor needs no approval. Otherwise the app stays at needs-admin-approval. |
| Uninstall | The approval, the root tasks and the app’s Application Privileges rules are removed, and a portal address stops. A reinstall needs a new approval. See Data and removal. |
Use it
Section titled “Use it”The generated root.run route exists in native-go projects. The generated UI has no button for it. Call it from your own UI code. Only administrators can call it.
const result = await ctx.call<{ output: string }>('root.run', { name: 'whoami' })name is the file name without .sh. The result is { output }, the merged output of the script.
executor.status returns whether the approval is ready, with tasks, the executor’s own readback of its approval tasks, and is available to every signed-in user. While the package is stopped, or an upgrade waits for approval, the executor is paused and the status is ready: false, state: "paused", with the next step in next: start the package, or, when the package is running, approve again; the root-only /usr/local/etc/synopkgland/<package>/executor.log names the cause.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Minimum DSM | 7.1-42661 |
| Architectures | x86_64, aarch64 and armv7. armv7 is not yet verified on a NAS. |
| Script size | 16 KiB |
| Run time | 20 seconds |
| Output returned | 1 MiB |
| Arguments | None |
Who can call root.run | Administrators |
Troubleshooting
Section titled “Troubleshooting”| Symptom or code | Cause | Fix |
|---|---|---|
internal from root.run | Every failure of a root operation reaches your app as internal: unknown name, non-zero exit, timeout, missing approval, or the executor not running. | Check synopkgland status, run the script by hand with sh, and keep it under 20 seconds. |
admin_required | The caller is not an administrator. | Call root.run from an administrator’s session. |
session_required | No live DSM session. | Sign in to DSM again. |
STANDALONE_ROOT_OPS_UNUSED | root/ exists but rootOps: true is missing. | Add the capability, or delete root/. |
needs-admin-approval in synopkgland status | No approval, or a script or policy changed. | An administrator approves again in Administrator setup. |
check rejects a script | The name, size or first line breaks a rule in the table. | Fix the file. |
| Uninstall takes long | The executor deletes the boot and control tasks through DSM, then its protected folders. | Read the system log for synopkgland-executor lines: task cleanup timing gives lookup, delete and verify time per task, and purge timing the task and file phases, each with the package name. On DSM 7.1 these lines are only in the journal, not in /var/log/messages: run journalctl -t synopkgland-executor. |
See Errors for every code.