Bucket & Code Server IDE: user guide
Bucket & Code Server IDE (QPKG bucketvs) keeps source code and files on a QNAP NAS and builds software there. It brings its own web server, tools and accounts.
This guide describes the package as it runs on the NAS.
Contents
- What runs on the NAS
- Installing the package
- First sign-in
- Working in the panel
- Accounts
- The data folder
- Network
- Behind a reverse proxy (Plesk)
- Hostnames for the modules
- Publishing on the internet (Cloudflare Tunnel)
- Modules
- Git
- CI runners
- SVN and Bucket
- Editor and AI tools
- Toolchains and environments
- App Builder
- Package repository
- Kernel workshop
- Cron jobs and operations
- Backups
- Notices
- Logs and the support bundle
- Updates
- The bvs command
- Files and folders
What runs on the NAS
| Part | What it does | Where it listens |
|---|---|---|
| nginx | the only door on the LAN: serves the panel and passes each path to its module | 9950 (HTTP), 9951 (HTTPS) |
| bvsd | starts and watches every other process, keeps the accounts and settings, runs jobs and cron jobs, answers the panel | two unix sockets in /usr/bin/bucketvs/var/run/ |
| Git (Forgejo) | Git hosting with a web interface | a unix socket in /usr/bin/bucketvs/var/run/forgejo/, and 9952 for Git over SSH |
| SVN and Bucket (httpd) | Subversion repositories and WebDAV folders with history | 127.0.0.1:9961, or the next free port when another program holds it |
| Editor (code-server) | VS Code in the browser, one instance per account | a unix socket per account in /usr/bin/bucketvs/var/run/ide/ |
| CI | the Forgejo Actions runner | no port of its own; nginx listens on 127.0.0.1:9960 for it while the module is on, or on the next free port when another program holds 9960 |
Core (nginx, bvsd, the panel, cron jobs, toolchains) is always on. Git, CI, SVN and Bucket, and the Editor are modules that an admin switches on and off. On every address of the NAS the modules answer under their paths: /git/, /svn/, /bucket/, /ide/. The panel is at /, and this guide at /guide/.
While the package runs, its programs are in /usr/bin/bucketvs/bin. Everything the package keeps (accounts, settings, repositories, environments, logs) lives in one folder on a data volume: the data folder.
Installing the package
The package needs QTS 6.0 or QuTS hero 6.0 or newer, on an x86_64 or ARM 64-bit NAS; there is one package per architecture (bucketvs_<version>_x86_64.qpkg, bucketvs_<version>_arm_64.qpkg). It is code-signed: install it from App Center (a repository that lists it, or Install Manually with the downloaded file). App Center checks the signature; there is no need to allow apps without a valid signature.
The installation creates the service user bucketvs (for Forgejo and httpd), the link /usr/bin/bucketvs to the install folder, and starts bvsd. System Logs names the data folder.
Where the data folder starts
On the first start the data folder is BucketVS on the volume the package is installed on, for example /share/CACHEDEV1_DATA/BucketVS (QTS) or /share/ZFS2_DATA/BucketVS (QuTS hero). It can be moved to another volume later from the panel (Moving the data folder).
The package remembers its data folder in /etc/config/bucketvs-data-root, which outlives the package. Removing the package keeps the data folder and this file, so an installation after a removal starts on the same data and the same accounts. A start never depends on a folder that is not there. When the data folder is gone (deleted, on a disk that was replaced, or on a volume that is offline at that moment), the start makes BucketVS on the package's own volume, a new folder unless one of that name is already there, and on a new one the first sign-in sets the administrator's password. This holds after a reinstall and for an installed package, at boot too. System Logs has a warning about it, and .prev files keep the old path: /etc/config/bucketvs-data-root.prev, and for an installed package also etc/data-root.prev in the install folder. If the old folder was on a volume that was only offline at that moment, bring the volume online, stop the package, copy that file over etc/data-root in the install folder (for example /share/CACHEDEV1_DATA/.qpkg/bucketvs/etc/data-root) and start the package: it runs on the old data again.
Opening the panel from App Center
Open on the package in App Center opens the panel on port 9950 (HTTP) or 9951 (HTTPS). When the ports are changed in the panel (Ports), the package tells App Center the new ones at its next start.
Auto launch from an SSH session
While the package's switch in App Center is on, the NAS starts the package at boot. In an SSH session on the NAS, as admin, the service script sets the same switch:
/etc/init.d/bucketvs.sh disable # the next start is refused, at boot as well
/etc/init.d/bucketvs.sh enable # the package starts at boot again
Each command writes the switch (Enable in /etc/config/qpkg.conf) and nothing else, prints what it set and writes the same line to System Logs. A running package keeps running after disable, and enable starts nothing: stop and start do that, and start refuses while the package is disabled. status says whether bvsd runs.
First sign-in
Open the panel at https://<NAS address>:9951/, or http://<NAS address>:9950/. The HTTPS address uses a self-signed certificate at first, so the browser warns until the device trusts it; the certificate section shows how to compare its fingerprint.
A new installation has no account. Until the first administrator has a password, the panel opens on one screen, "Set the administrator password", for the account administrator. Type a password twice: at least 10 characters and at most 72 bytes. Saving it creates the account and signs you in.
The screen accepts a password for 60 minutes after the package starts. After that it says the setup window has closed; restarting Bucket & Code Server IDE in App Center opens a new 60-minute window.
It accepts a password only from the local network: from the NAS itself, or from an address in a private range (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,fc00::/7,fe80::/10).While the screen is open, the QTS System Logs show an information entry for each LAN address of the NAS, such as:
bucketvs: no administrator yet: open https://192.168.1.20:9951/ within 60 minutes to set the administrator's passwordIt refuses a request that came through a proxy (one that carries
X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,X-Real-IPorForwarded), even from a proxy on the LAN: open the panel directly from a computer in the local network to set the password.
From an SSH session on the NAS, /usr/bin/bucketvs/bin/bvs user password administrator creates the account when there is none yet and prints a random password; the panel then asks for a password of your own at the first sign-in (When an admin is locked out).
After the first sign-in the Overview says that only the core is running. Each module's tile says what the module gives and has a button that turns it on, such as Turn on Git: turn on the ones you need (Modules).
Working in the panel
The panel works like a code editor: every page opens in a tab, and a command palette reaches the pages and the actions by name.
- Activity bar (the icons at the left). Each icon opens its page and shows its tree in the side bar: the modules, repositories and buckets, apps, packages, environments, jobs and backups. A dot on an icon marks a module in trouble, a failed build or a failed backup. Clicking the icon of the page in front hides the side bar; Ctrl+B does the same.
- Tabs. A page opened with a single click is a preview, in italics: the next page opens in its place. Edit the page or double-click its tab to keep it. The tabs come back after a reload, for each account in each browser. In a window at least 1280 pixels wide, Move the tab to the other side (
Ctrl+\) shows two pages side by side. - Objects and their menus. One click on a row of the side bar, a row of a table or a card opens that object: its page comes to the front with the object in a green ring, or with its details open. The address names the object, so back, forward and a copied link return to it. A double click keeps the tab. A right click, Shift+F10 or a long press on a touch screen opens the object's menu: the ways of opening it (in this tab, in a kept tab, on the other side, in a new browser tab), what its page can do with it, and copies of its name and address. A tab's menu closes it, the other tabs, the tabs to its right or all of them. Only a control marked ↗ leaves the panel. The Administration icon opens the System page.
- Command palette. Ctrl+Shift+P (or Ctrl+K, or F1) lists the commands your role may run: open a page, build or publish an app, run a backup, restart a module, open a shell in an environment. Ctrl+P goes to a page, an app, a repository, an environment or a package by a few letters of its name.
- Bottom panel (Ctrl+J). Jobs shows the running and recent jobs with their progress and log. Problems lists what the modules report: failed builds, disabled backup targets, modules in trouble, a certificate about to expire, and the ELF inspector's findings for the app in front. Output follows the job you started last.
- Moving and resizing. Drag an edge to change the size of what it borders: the side bar, the bottom panel, two pages side by side, or two parts of a page side by side, such as a wizard and its summary or a project's file list and the file. A double click on an edge puts it back. Drag a dialog by its title to move it, and by an edge or its bottom right corner to resize it; a double click on its title puts it back in the middle. To put the bottom panel beside the pages, drag its header to the right edge of the editor or click the button next to its ×; drag it to the bottom edge, or click the button again, to put it back. A tab can be dragged to another place in the tab strip or onto the other side. The panel keeps each size and place for your account in this browser, and Reset layout (in the command palette, or under your account's button at the bottom of the activity bar) puts them all back. From the keyboard, the arrows move a focused edge (Shift takes bigger steps, Home puts it back), Ctrl+Shift+Left and Right move a focused tab, and a dialog has a bar at its top and a grip at its bottom right that the arrows move and resize it with. On a phone, dialogs and the bottom panel stay where they are.
- Status bar. Module states, running jobs, problems, free space on the data volume, the certificate, the account that is signed in, the language (the editor takes it at its next start, English for Swedish and Dutch, and Git follows it unless you pick another language there), the theme (dark, light, as the system, deep blue, graphite or paper: it belongs to your account, and the editor and Git wear it too, unless you pick another theme there) and the version. Each item opens the place to look closer.
- Keyboard. Ctrl+/ or ? lists the shortcuts. A browser tab keeps Ctrl+W and Ctrl+Tab for itself, so the panel closes a tab with Alt+W and switches tabs with Alt+PageDown and Alt+PageUp.
- On a phone the first activities are at the bottom, More opens the side bar with the rest, and the open pages are a list at the top.
Accounts
The panel has its own accounts. QTS accounts do not sign in to it.
Roles
| Role | What it may do |
|---|---|
| viewer | reads the panel; the Backups, Storage, Users and Logs pages are for admins. In Forgejo it creates, forks and imports no repository, creates no organization and never pushes; it reads the SVN repositories and buckets it has access to and writes to none |
| member | reads what a viewer reads, and writes: it pushes to the Git repositories it is given write access to, writes to the SVN repositories and buckets it has read and write on, and restores older versions of their files. In Forgejo it creates, forks and imports no repository and creates no organization, and it cannot push to a repository that an account below developer owns or administers, nor, while the CI module is on, to one whose Actions are on. It gets no editor, cron jobs or builds |
| developer | also opens the editor, reads the bvs use lines of installed toolchains, creates, changes and runs its own cron jobs, creates and builds apps, publishes packages to the repository (an admin lists them), checks kernel modules, and creates Git repositories whose workflows run on the NAS while the CI module is on |
| admin | everything: accounts, settings, storage, network, modules, toolchains, environments, every cron job, activating apps, loading kernel modules, listing packages in the repository |
The developer role is root on the NAS: its cron commands run as root, so does its editor's terminal and, while the CI module is on, the workflows of the repositories it can push to. Give it only to people you would give the NAS's admin password. For someone who only commits to repositories, the member role is enough.
Under Your account each person reads their own role and what it allows, with a link to this section. A page above someone's role says which role it needs, and what only an admin changes says so where it is shown. Network, Git and CI show their settings to everyone else as values, with "Not set" where one is empty.
Workflows run as root on the NAS, so bvsd keeps Actions off in every Git repository that a member or a viewer owns or can write to: giving such an account write access to a repository turns that repository's Actions off within a minute, and the Git page lists those repositories for admins. To use Actions there, first remove that write access in Forgejo, or move a repository such an account owns to a developer's account.
Creating and changing accounts
An admin manages accounts on the Users page.
- A new account starts with the viewer role. Developer and admin are chosen on purpose: the developer role is root on the NAS (above).
- A new account starts with no cron jobs, even when an account of the same name had some.
- A name has 2 to 32 characters: a lower-case letter first, then lower-case letters, digits,
.,_or-. Two of.,_,-in a row, or one at the end, are refused. So are the names Forgejo reserves (admin,api,userand others),bucketvs(the package's system user),cliandsystem(bvsd uses both itself). - "Must choose their own password at the first sign-in" makes the account set its own password before it can do anything else.
- Disabling an account ends its sessions at once and stops its API tokens. Its data and its cron jobs stay, but the jobs do not run while it is disabled, and a run of one that is going is stopped.
- Giving an account the member or viewer role does the same to its cron jobs: they stay, do not run while its role is below developer, and a run that is going is stopped.
- A viewer never pushes to Git, also where it was given write access in Forgejo. An account that should push gets the member role.
- Deleting an account removes it with its sessions and API tokens. Its home folder moves to
trash/home/<name>-<time>in the data folder, where the "Empty the trash" job removes it after 7 days, and itstmp/tmux-<name>folder is removed. Its cron jobs are deleted with their runs and logs. - The last enabled admin cannot be demoted, disabled or deleted.
- "Set a new password" ends the account's sessions and revokes its API tokens.
Passwords and sessions
- A password has at least 10 characters and at most 72 bytes.
- Changing your own password (under Your account) ends your other sessions and revokes your API tokens. The session you changed it in stays signed in.
- A session ends 12 hours after its last request, and 7 days after sign-in in any case. An account keeps at most 32 sessions; signing in once more ends the one used least recently.
- The session cookie is
HttpOnlyandSameSite=Lax. It is markedSecurewhen the browser reached the NAS over HTTPS, directly or through a trusted proxy.
Wrong passwords
- Five failed sign-ins within 15 minutes from one address lock that address for 15 minutes, even with the right password. An IPv6 client counts as its /64 network.
- Five failures for one account from one address lock that account from that address for 15 minutes.
- Failures for one account from many addresses slow down its next sign-ins: 125 ms after one failure, doubling up to 2 seconds. From the local network they never lock it.
- From the internet (any address outside the local network), 20 failed sign-ins for one account since its last successful one lock it there, whichever addresses they came from, and the right password is then refused there too. The account still signs in from the local network. An admin unlocks it on the Users page (Unlock) or with
bvs user unlock <name>, or gives it a new password. The number is set on the Users page under Sign-ins from the internet; 0 turns this lock off. - The counts are kept in memory, so a restart of bvsd clears them; a lock stays until it is ended.
Behind a proxy, the address counted is the one the proxy reports, but only when the proxy is a trusted proxy. Otherwise every client counts as the proxy's own address, and five wrong passwords from anyone lock everyone out for 15 minutes.
Two-factor authentication
Each account turns it on for itself under Your account: scan the QR code with an authenticator app (or type the key), then enter the 6-digit code the app shows. After that, sign-in asks for a code after the password.
- Codes follow RFC 6238: 6 digits, a new code every 30 seconds. A code 30 seconds early or late is accepted. The same code is never accepted twice.
- If a fresh code fails, the phone's clock is probably off; set it to automatic time.
- Turning it off needs the account's password.
- For a lost phone, an admin turns it off on the Users page. The password alone then signs in.
API tokens
Scripts and CI use API tokens instead of a password. Under Your account, "New token" makes one:
- It looks like
bvs_followed by 40 hexadecimal digits, and it is shown once. bvsd keeps only its hash. - A script sends it as
Authorization: Bearer bvs_.... - Its scopes are
read,writeandadmin, which stand for the roles viewer, developer and admin. A token acts with its highest scope, never above its owner's current role, so demoting an account also demotes its tokens. No scope stands for member: a member makesreadtokens. - A new password revokes all of the owner's tokens. A disabled owner's tokens stop working.
- A token cannot change passwords or two-factor authentication, and cannot make or delete tokens: those need a signed-in session. A leaked token therefore cannot lock its owner out.
When an admin is locked out
The NAS admin can reset any account from an SSH session on the NAS. The bvs command talks to bvsd over a socket that only root can open (/usr/bin/bucketvs/var/run/bvsd-cli.sock, mode 600):
/usr/bin/bucketvs/bin/bvs user list
/usr/bin/bucketvs/bin/bvs user password administrator
/usr/bin/bucketvs/bin/bvs user totp-reset administrator
/usr/bin/bucketvs/bin/bvs user unlock administrator
bvs user password <name>sets a new random password of 20 characters and prints it. The account must choose its own at the next sign-in. Its sessions end and its API tokens are revoked. On a new installation with no account yet,bvs user password administratorcreates the account.bvs user totp-reset <name>turns two-factor authentication off for the account.bvs user unlock <name>ends the account's lock after too many wrong passwords from the internet.
Every request on that socket acts as the built-in admin cli and is written to log/audit.log in the data folder. bvsd must be running for these commands.
The data folder
The data folder is the folder that holds everything the package keeps. Its path is the one line in /usr/bin/bucketvs/etc/data-root. The Storage page shows it, with the volume it is on and the free space there.
bvsd starts only on a data folder that is a folder on a real volume. The package's start checks this first: a missing folder, or one on a tmpfs or ramfs, gives a new data folder on the install volume, as Where the data folder starts describes. A volume that is offline leaves its mount point behind as an empty folder in the NAS's RAM, and settings written there would be gone after the next reboot. bvsd itself refuses such a folder only when it is started by hand, and the reason then goes to the QTS System Logs.
What bvsd gives every process
QTS keeps /tmp in a small RAM disk and /root on a RAM disk that a reboot empties. So every process bvsd starts (module programs, cron jobs, installs) gets its temporary files and its home folder in the data folder:
| Variable | Value |
|---|---|
HOME | <data folder>/home/<user> |
TMPDIR | <data folder>/tmp |
XDG_CACHE_HOME, XDG_CONFIG_HOME | .cache and .config in that home |
npm_config_cache | .npm in that home |
PATH | ~/.local/bin, ~/.npm-global/bin, then the package's bin, sbin and node/bin, then the system's folders |
SSL_CERT_FILE, GIT_SSL_CAINFO, CURL_CA_BUNDLE | the package's CA bundle, /usr/bin/bucketvs/etc/ssl/cacert.pem |
TZ | the NAS's time zone, as set in QTS |
SHELL, EDITOR | the package's bash and nano |
LANG, LESSCHARSET | C.UTF-8 and utf-8, so file names with non-English letters show as they are |
Moving the data folder
The Storage page moves the whole data folder to another folder under /share. Pick a shared folder or a volume; the data goes into a new folder named BucketVS inside it. To use a folder itself, open an empty folder named BucketVS. Shared folders are created in QTS (Control Panel, Shared Folders); inside a shared folder or a volume, the page can create a new folder. The picker opens where the data is now and says so; from there, open the shared folder or volume it should move to.
The page checks the target before the move starts and lists every reason it refuses one:
- the path must be under
/share, and not/shareitself; - no
:,&,|or control character in any part of the path, nor in the path a link leads to; - not the current data folder, not a folder inside it, not a folder that holds it;
- a new folder, or an empty one;
- a filesystem that keeps Unix owners and permissions (FAT, exFAT and NTFS are refused: Forgejo and SVN need owners and modes);
- a volume mounted with
exec,devandsuid, which build environments need; - writable, with free space for the data plus 10 %.
While the move runs:
- Git, CI, SVN and Bucket, and the Editor do not answer. The panel stays on and shows the progress.
- No new cron run starts. A run that was already going continues.
- Switching a module on or off is refused until the move ends.
After the switch bvsd stops and starts itself again on the new root, so the panel is gone for a few seconds.
When the move fails, the old data folder stays in use and the modules start again. The job's error says what went wrong and what became of the copy:
- A full volume, or a cancel, removes what the move created.
- Any other failure (the copy, the check, writing to disk) keeps the copy, marked with a file named
INCOMPLETE, and lists it on the Storage page under "Leftover copies of failed moves". - A stop of the package during the move keeps the old folder as the data folder. At the next start bvsd reports the unfinished move in the System Logs and lists the copy.
The old copy and leftover copies
After a successful move the old data folder stays where it was, with a file named MOVED-TO in it. Nothing uses it. "Remove the old copy" on the Storage page deletes it, and "Remove this copy" deletes a leftover copy of a failed move. Both removals stay on one filesystem and never follow a link. A folder that is a mount point or a shared folder's own folder is emptied and kept.
Network
The Network page holds the ports, the plain HTTP mode, the module hostnames, the trusted proxies, the public URL and the certificate. Saving restarts what the change needs and lists it.
Ports
| Setting | Default | What it is |
|---|---|---|
| HTTPS | 9951 | the panel and the modules over TLS |
| HTTP | 9950 | the panel and the modules over plain HTTP |
| Git over SSH | 9952 | Git over SSH |
| SSH port in clone URLs | 0 | the port shown in SSH clone addresses; 0 means the SSH port above. Set it when a router forwards another port to the NAS. |
| SSH host in clone URLs | empty | the name or address shown in SSH clone addresses. Empty means the Git hostname, else the public URL's host, else the NAS's LAN address; for a name published through the Cloudflare Tunnel, the LAN address and the Git over SSH port. Set it to a DNS-only name or to the router's address when the router forwards the SSH port. |
Ports are 1024 to 65535 and differ from each other. The module ports on 127.0.0.1 (9960 and 9961) are refused, and so is the port httpd or the CI runner's server uses when another program took 9960 or 9961 (the message names it). When saving leaves nginx on its old ports because it could not use a new one, a notice says why. nginx also listens on IPv6 when the NAS has it.
Plain HTTP
- "Serve" (the default): the panel and the modules answer on plain HTTP too. Keep this when a proxy on the LAN talks plain HTTP to port 9950.
- "Redirect to HTTPS": a request on plain HTTP gets a redirect (307) to the HTTPS port. A request from a trusted proxy that says its client used HTTPS is served on plain HTTP, so such a proxy does not loop.
The sign-in page warns when it came over plain HTTP: the password then crosses the network readable.
The certificate
At first start bvsd makes a self-signed certificate (ECDSA P-256) for:
- the NAS's host name, the module hostnames and
localhost; - the NAS's LAN addresses and
127.0.0.1.
It is valid for 825 days, the most Apple devices accept, even for a certificate trusted by hand. bvsd checks it at start and once a day, and makes a new one when 30 days or less are left, when the set of names changes, or when its key does not match. A new one sends a notice. The Network page shows its names, its validity and its SHA-256 fingerprint: compare the fingerprint before a device trusts it.
"Add your own certificate" takes a certificate chain and its private key in PEM form:
- the server certificate first, then the intermediate certificates;
- a key without a passphrase (nginx starts unattended);
- the key must belong to the first certificate, each certificate must be signed by the next, none may have expired, and the first one must name its hosts as subject alternative names.
The pair is stored in etc/tls/ in the data folder with mode 600. When an uploaded certificate expires or stops passing these checks, nginx serves the self-signed one instead and a notice says why. An uploaded certificate with 30 days or less left sends a notice too. "New self-signed certificate" makes a new self-signed pair and switches back to it.
Clients that come through a proxy see the proxy's certificate, not this one.
Download the certificate on the Network page saves the certificate the panel serves, without its key, as <name>.crt. Add it to a device's trusted certificates so that the device's browsers trust the panel; the editor's extension views need that (Extensions). A new self-signed certificate has to be trusted again.
Trusted proxies
A list of IP addresses or CIDR ranges, such as 192.168.1.5, the address of the proxy in front of the NAS. Only for a request whose TCP connection comes from one of them does nginx take:
- the client's address from
X-Forwarded-For; - the scheme the client used from
X-Forwarded-Proto(onlyhttporhttpscount).
A request from any other address counts as coming straight from its client, and nginx replaces its X-Forwarded-* headers with its own values. A range makes every host in it trusted: any of them can then set the client address and the scheme, so list the proxy's own address. A range of every address (0.0.0.0/0, ::/0) is refused.
Public URL
The address alone, such as https://git.example.com, without a path. The module sections say which links start with it.
Behind a reverse proxy (Plesk)
The usual setup puts TLS on a proxy with a public certificate and lets it talk plain HTTP to port 9950 on the LAN. The examples below use bvs.example.com for the public name, 192.168.1.20 for the NAS and 192.168.1.5 for the Plesk server; put your own in their place.
On the NAS
- On the Network page, add the Plesk server's address (as the NAS sees it) to Trusted proxies.
- Plain HTTP can stay on "Serve". "Redirect to HTTPS" works too once the Plesk server is a trusted proxy.
In Plesk
Put these directives into the domain's Apache and nginx settings, in the field "Additional nginx directives":
location ~ ^/ {
proxy_pass http://192.168.1.20:9950;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket: the editor and its terminal
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
# large pushes and uploads: no size limit, streamed, an hour of silence allowed
client_max_body_size 0;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location ~ ^/ is a regular expression location. nginx prefers a matching regular expression to a plain location / (Plesk writes one of its own), so this block serves every path of the site, and proxy_pass without a path passes the request's path unchanged. On the same settings page, turn off "Serve static files directly by nginx": with it on, Plesk answers requests for .js, .css and image files from the domain's own folder, and the panel does not load.
In the domain's Hosting Settings, turn on "Permanent SEO-safe 301 redirect from HTTP to HTTPS". The additional nginx directives also apply to the domain's plain HTTP site on port 80; without the redirect, the site answers there too, and passwords cross the internet unencrypted.
Why each line matters:
| Directive | What breaks without it |
|---|---|
Host $http_host | nginx on the NAS picks the server block by Host, so a module hostname never matches. bvsd compares the page's Origin with Host, port included, so every change in the panel fails with 403 csrf; $host would drop a public port other than 443 and fail the same way. |
X-Forwarded-For | every client counts as the Plesk server: one person's five wrong passwords lock everyone out for 15 minutes |
X-Forwarded-Proto $scheme | the session cookie is not marked Secure, and in "Redirect to HTTPS" mode every request is sent to port 9951 of the public name |
Upgrade, Connection | the editor's page opens but the editor and its terminal never connect |
client_max_body_size 0, proxy_request_buffering off, the timeouts | the proxy's own body limit and timeouts apply to large pushes and uploads |
X-Forwarded-For and X-Forwarded-Proto count only when the Plesk server is in Trusted proxies. When it is not, nginx on the NAS ignores them.
Git over SSH
An HTTP proxy does not carry SSH. Git over SSH goes to port 9952 on the NAS directly, or through a TCP port forward on the router. "SSH port in clone URLs" on the Network page sets the port the clone addresses show, and "SSH host in clone URLs" the name or address, when the public name reaches only the proxy.
Checking the setup
- The panel opens at
https://bvs.example.com/, and signing in and saving a setting work. - The browser's developer tools show the session cookie
bvs_sessionwith theSecureflag. - The Logs page shows
nginx-access: the first field of each line is the client's own address, not the Plesk server's.
Hostnames for the modules
A module can have its own hostname (Network page, Hostnames). nginx then serves that module alone at the root of the name. The name must point at the NAS, or at the proxy in front of it. On a proxy, each hostname needs its own site, with the same directives as above.
| Module | At the root of its hostname | Also there |
|---|---|---|
| Git | Forgejo | /git/ on every other name answers 302 to the Git hostname, with the same path, query and port |
| Editor | code-server | the panel's sign-in page and /api/, so a user can sign in on that name |
| SVN and Bucket | httpd | repositories stay at /svn/<name> and buckets at /bucket/<name> |
A module that is off answers 503 with a sentence saying so, on its paths and on its hostname.
One sign-in for every hostname
The session cookie belongs to the name it was set on, unless the setting network.cookie_domain names a parent domain of every module hostname. With example.com set, a sign-in on bvs.example.com also reaches git.example.com and ide.example.com. bvsd refuses a public suffix (com, com.pl, co.uk, myqnapcloud.com), a domain that is not a parent of every module hostname, and any domain while no module hostname is set. Every site under that domain receives the cookie, so set the narrowest common parent.
The Network page does not show this setting. The NAS admin sets it over the CLI socket with the package's curl:
/usr/bin/bucketvs/bin/curl --unix-socket /usr/bin/bucketvs/var/run/bvsd-cli.sock \
-X PUT -H 'Content-Type: application/json' \
-d '{"network":{"cookie_domain":"example.com"}}' \
http://bvsd/api/v1/settings
An empty value ("") keeps the cookie on each name again.
Publishing on the internet (Cloudflare Tunnel)
The Publish page (Administration, admins only) puts the package on the internet under names of a domain whose DNS is at Cloudflare. cloudflared, which the package ships, dials out from the NAS to Cloudflare: no router port is opened, and Cloudflare holds the certificates for the names. Every request passes the package's nginx: the panel and its API need a sign-in, and only what you made public (public repositories, their web view, the package repository) opens without an account.
There are two types of name, one of each at most:
| Type | Example | What it serves |
|---|---|---|
| Main hostname | code.example.com | the panel and its API, Forgejo at /git/, the web view at /svn-web/ and /bucket-web/, the package repository at /repo/ (read only) and the guide. /svn/ and /bucket/ answer 404 there, and so does /ide/ until you publish the editor (below) |
| Packages hostname | qpkg.example.com | GET and HEAD of /repo/ only, for App Center on other NAS units; everything else answers 404 |
What the token needs
Make an API token at dash.cloudflare.com (My Profile, API Tokens) with these permissions, for the zone of the names:
- Account, Cloudflare Tunnel, Edit
- Zone, DNS, Edit
- Zone, Cache Rules, Edit
- Zone, Cache Purge, Purge
The package keeps the token on the NAS in a file only root reads, and never shows it again.
If the token has Client IP Address Filtering, put both of the NAS's public addresses in it, IPv4 and IPv6: the NAS calls Cloudflare over IPv6 when it has an IPv6 address. Otherwise the Publish page refuses with token_ip_filter and names the address Cloudflare saw.
Publishing
Type the names and the token, then Publish. The package first checks everything at Cloudflare and changes nothing there when a check fails; the message says what to fix:
- a permission the token lacks, by its name;
- a name in a zone the token does not see;
- a DNS record the name already has (its type and where it points): delete it at Cloudflare, or pick another name;
- a main hostname while Public browsing (Git page) is off, since its visitors have no account;
- names in zones of two Cloudflare accounts.
Then it makes the tunnel (named bucketvs-<NAS name> unless you give a name; a tunnel of that name kept by Cloudflare is taken over), points each name at it with a proxied CNAME record, adds the cache rules and starts cloudflared. Publish again with the same values changes nothing. Set the public URL (Network) to the main hostname's https:// address, so Forgejo's links and HTTPS clone addresses name it; the page reminds you while they differ.
Git over SSH does not pass the tunnel: Cloudflare carries only HTTP to a published name. The SSH clone address is therefore the NAS's LAN address and its Git over SSH port (9952), in the panel and in Forgejo's own clone box, and the Git page and the Overview say "SSH clone works on the local network; from the internet clone over HTTPS." For SSH from the internet, forward the SSH port on the router and set SSH host in clone URLs (Network) to the router's public address or to a DNS-only name that points at it, with SSH port in clone URLs when the router uses another port.
The main hostname answers /ide/ with 404 until an admin turns on Publish the editor on the main hostname on the Publish page. The editor's terminal is a root shell on the NAS, and whoever signs in there as a developer or admin from the internet gets it, so turn on two-factor authentication for those accounts first. With the switch on, the editor passes the same sign-in as on the LAN, its port proxy (/ide/proxy/<port>/) stays off there, and Cloudflare never keeps its pages in the cache. While it is on, the Overview shows Editor on the internet.
While the CI module is on, a workflow pushed to any repository of this Forgejo runs as root on the NAS through the NAS's own runner, whether it was pushed from the internet or from the LAN: the same power as the editor's terminal. The Publish page says so under What the internet reaches. Give push access only to people you would give the NAS's admin password.
The cache
With Cache presets on, Cloudflare keeps packages (*.qpkg) for a year, icons for a day, repo.xml for 5 minutes and the panel's files for a year; it never keeps the API, Forgejo's pages, the sign-in pages or the web view. A publish into the package repository clears repo.xml (and a changed icon or a deleted version's files) from Cloudflare's cache right away. The package adds its own rules at the end of the zone's cache rules and never changes the others.
Sign-in from the internet
The first password can never be set through a published name: the first-run setup takes only a local client. Wrong passwords count per internet client, as on the LAN. A password sent with git to Forgejo counts too; a Forgejo access token works as always. Cloudflare takes requests up to 100 MB, so a larger push or upload goes over the LAN address.
Stopping and removing
Stop ends cloudflared; the names then show Cloudflare's own error page, and nothing at Cloudflare changes. Remove from Cloudflare deletes the DNS records, the cache rules and the tunnel the package made (a tunnel it took over stays) and forgets the token. While names are published, the Overview shows an Internet pill with the tunnel's state.
Modules
The Overview shows each module with its processes, memory and CPU, and switches it on and off (admin only). A module that is off says what it gives; an admin turns it on with its button (Turn on Git, Turn on Editor and so on) on its tile, on its own page or, for the Editor, in the Editor tab.
- Switching a module on runs a job: the module prepares itself, the switch is saved, and its processes start. The job waits up to 30 seconds for them.
- CI needs Git: Git cannot be switched off while CI is on. While Git is off, CI's button reads Turn on Git and CI and turns Git on first.
- A module that is off runs no process and uses no memory.
- Core is always on.
| State | Meaning |
|---|---|
| running | every process of the module runs |
| starting | a process started less than a second ago |
| degraded | a process waits to be restarted, the module's files could not be written, or its health check fails |
| restarting in a loop | a process exited 5 times within 10 minutes; bvsd starts it again every 15 minutes, and one notice went out |
| stopped | the module's processes are not running (a data move stopped them, or they failed to start) |
| off | the module is off |
A process that exits is started again after 1 second, then 2, 4 and so on up to 60 seconds. After 10 minutes of running the wait starts again at 1 second. A process that is restarting in a loop starts again at once when its module is switched off and on.
Git
The Git module runs Forgejo, a web interface for git repositories with issues, pull requests, releases, wikis, LFS, mirrors and webhooks. An admin turns it on with Turn on Git on the Overview or the Git page. The job prepares Forgejo's folders and database in <data>/forgejo, adds every panel account to Forgejo, and starts it. Forgejo then opens at /git/ on the panel's address, or at / of the Git hostname when one is set (see Hostnames for the modules).
New repository on the Git page, and on the Overview's Git tile while there is none yet, opens Forgejo's form for a repository. The panel offers it, and Migrate or mirror a repository, from the developer role up: Forgejo lets a viewer or a member create no repository.
Repositories and accounts
- One sign-in. Forgejo has no sign-in of its own:
/git/user/logingoes to the panel's sign-in, with the authenticator code when the account has one, and back to Forgejo after it. - Accounts come from the panel. Every panel account has a Forgejo account of the same name and the same password. The
adminrole is a Forgejo site admin. A disabled account, or one that must change its password first, cannot sign in to Forgejo either. A change in the panel reaches Forgejo within seconds. The Users and Git pages show each account's state in Forgejo, and Sync every account now on the Git page runs the sync again. - Deleting an account in the panel retires its Forgejo account: it can no longer sign in, and its tokens and SSH keys are removed, but its repositories stay. The name cannot be given to a new panel account until an admin renames or deletes the old Forgejo account in Forgejo's site administration (User accounts).
- An account made in Forgejo itself is never changed by the panel. A panel account of the same name shows the reason and waits until one of them is renamed.
- Two-factor authentication and git. Forgejo takes no password for an account with two-factor authentication on. git over HTTP or HTTPS then needs a Forgejo access token (Forgejo's Settings, Applications) in place of the password. SSH keys work as before.
- Repositories are created, imported and mirrored in Forgejo's own pages. Imports from addresses on the LAN are allowed by default (Settings:
git.allow_local_networks), because a NAS usually mirrors from its own network.
Clone over HTTPS and SSH
On the Git page, Clone a repository writes both commands for an owner and a repository:
| Way | Address | Sign-in |
|---|---|---|
| HTTPS (or HTTP) | https://<NAS address>:9951/git/<owner>/<repo>.git | the panel's username and password, or an access token |
| SSH | ssh://git@<NAS address>:9952/<owner>/<repo>.git | an SSH key added in Forgejo's Settings, SSH / GPG Keys |
- With a public URL (behind a proxy, Public URL) the HTTPS address uses it; with a Git hostname the path is
/<owner>/<repo>.giton that name. - The SSH port is
network.ssh_port(9952), ornetwork.public_ssh_portwhen a router forwards another port to it. The proxy does not carry SSH: forward the port on the router (see Git over SSH). - The SSH host is
network.public_ssh_hostwhen set, else the Git hostname or the public URL's host. A name published through the Cloudflare Tunnel carries no SSH, so for it the address is the NAS's LAN address on the Git over SSH port (Publishing on the internet). - Large pushes are not cut: nginx sets no body size limit on
/git/.
Showing repositories to people without an account
Public browsing on the Git page lets anyone who reaches Forgejo read its public repositories without an account. In Forgejo each repository is public or private (its Settings, Visibility). With the switch on, a visitor without an account can:
- open a public repository, its folders and files, and download a file as it is;
- read the history, open a commit with its diff (also as a
.diffor.patchfile) and compare two commits; - see its releases, download an archive, and clone it over HTTP (
git clone https://<NAS address>:9951/git/<owner>/<repo>.git); - find public repositories under Explore and on their owner's profile.
A private repository stays hidden: its address answers "not found". While the switch is on, Forgejo also lists no accounts (Explore's Users tab and the user search are off, for signed-in users too), so a visitor cannot collect the account names. Pushing, issues, pull requests and settings still need an account. With the switch off, every page asks the visitor to sign in first.
To share a repository, make it public in Forgejo, turn Public browsing on and send the address. On a Git hostname the address is https://<Git hostname>/<owner>/<repo>.
New project
New project is a page that takes a project from nothing to a built, published first version, step by step. It opens in its own tab from the command palette (Ctrl+Shift+P, New project), the top of the Editor's side bar, the Git side bar's header, the Git page and the Projects page; an example under Editor projects on the Examples page has New project from it. It needs the developer role, the Git and Editor modules, and your account in Forgejo (the Git page shows its sync state). While Git or the Editor is off, the page says so above the steps, and an admin turns them on there with one button.
- Template. The templates are grouped: Programs (C, C++ with CMake, Go, Rust, Node, Python, Java), Packages for QTS (a QPKG package, an App Builder app, a package from source), Kernel (a kernel module, the ch341 USB-serial driver), Websites (a static website: pages in
src/, a Makefile that buildspublic/, a link check asmake test) and Examples. Each card says what a build makes and marks what this NAS still lacks (QDK, a builder environment, a module that is off), with a link to the page that adds it. - Name and visibility. The name names the repository and the folder
projects/<name>; the page checks it as you type. Then the owner (your account, or an organization where Forgejo lets you create repositories), private or public, and a description if you want one. For an App Builder app, the preset it starts from and the target. - Service, for a QPKG package and a package from source: how the package starts and stops, built from blocks. Choose a preset (Command-line tool links the program into
/usr/bin; Background service adds an account, a data folder on the volume, the program started as that account, a wait for its port and a stop with a timeout; Web app adds App Center's Open button; Empty has only the verbs), then change its blocks. Each block's card shows the lines it writes into the script while you type, and a refused value is marked under its field. Start never switches App Center's Enable on, and stop never touches it. The Project view changes the blocks later. - Build. The environment the project builds in, kept for the project (the Project view changes it later), or none, and bvsd picks one at each build. The step first offers the one bvsd picks itself, where
bvs qbuildand CI build: the Ubuntu 24.04 builder, else the Debian 12 builder, else another Debian or Ubuntu environment. When the template's Makefile gives each app its environment on an ENV line, the step says so and asks for none, and the project keeps none. For a package from source the step also says that the starter recipe's source address and sha256 are placeholders: the first Build fails until the recipe names your program's real source and that file's sha256. The page shows the workflow CI runs at each push. Build right after creating is on unless you turn it off; for a package from source it starts off, and you can turn it on. With no Debian or Ubuntu environment for the NAS, the step says so: an admin creates the builder there with Create this environment, and then the step offers it; otherwise turn off Build right after creating and build the project later. - Publish, for a QPKG package, an App Builder app or a website: Test deploy on this NAS (admin, signed packages only; for an app, Activate on this NAS), Publish to this NAS's App Center repository and Publish to my web server (admin) with one of the web targets; Add a target saves a new one there. Each is optional. The publish choice also becomes the project's switch Publish to the App Center repository (Project view, Settings): while it is on, the project's CI publishes there at each run.
- Review lists every answer and the steps that will run. Create project sends them.
- Progress follows each step's job: the repository and its first commit, the first build, then the test deploy, the App Center publish and the web publish, one after the other. A row shows its state and the end of its log; a click shows its log. A failed step says why and offers Retry, and the steps after it wait. At the end, Open the project, Open in editor and Open in Forgejo. Reloading the page or coming back to its tab reads the jobs again and sends no step twice. Leaving the page stops the chain; the Project view has the same steps.
When bvsd refuses the project (a name taken by a folder, an app or a repository, an owner you cannot create in), the page goes back to the step that holds that answer, shows the reason under its field and keeps every other answer. A module that is off (an admin turns it on from the page), no Forgejo account yet and no Debian or Ubuntu environment for an App Builder app are explained on Review.
What the first job does:
- It makes the repository as you, private unless you chose public.
- It makes the folder
projects/<name>of the data folder with the first commit: every file of the template and the workflow.forgejo/workflows/build.yml, onmain. The commit is yours (your name and address are set as the author in the folder), and the template's scripts are executable in it. It pushes that commit to the repository as you.originis the repository's HTTPS address, so your next push goes there. The commit also holds the skill for AI assistants,.claude/skills/qnap-dev/andAGENTS.md. The folder is in the editor's shared folder: every developer's editor opensprojects/, whether the repository is private or public. - It keeps the environment you chose for the project, unless the template's Makefile gives each app its environment. For an App Builder app, it makes the app of the project's name with the preset's recipes and ties it to the project, so the app can also build recipes you add to the project's
recipes/folder. It also keeps your publish choice as the project's switch Publish to the App Center repository, before the workflow's first run reads it.
The workflow runs on the NAS's runner at every push once the CI module is on, in the environment the workflow names. The QPKG workflow publishes the package into the package repository, which takes code-signed packages only. If the first commit or its push fails after the repository was made, the job removes the folder, deletes the repository again and says why. While the project's switch Publish to the App Center repository is off, bvs repo publish in the workflow prints a notice instead of publishing, and the run goes on.
The Project view
Every folder of projects/ in the data folder has a page of its own: a project the wizard made, a copy of an example from Editor projects, or a repository you cloned there. It opens from the Projects branch of the Editor's side bar, from the Projects page, from Open the project at the end of New project, and from the command palette (Open the project …, or Ctrl+P and its name). It needs the developer role. A project's menu on the Projects page has Build, Rebuild, Publish and Publish to the web targets too. The same menu turns CI publishing to the App Center on or off.
The page reads the folder and changes nothing in it on its own. It works out the project's type from its files: an App Builder app of the folder's name, a qpkg.cfg at the top (a QPKG package), a Makefile at the top, or none of these, when the project's CI workflow builds it. When an App Builder app has the folder's name, a project with a qpkg.cfg at the top, or with a Makefile and a qpkg.cfg in qpkg/, keeps its own build, and Test deploy and Publish still take its QPKG.
The strip at the top holds the steps. A step that cannot run keeps its button, disabled, and says why beside it and under the strip. A step the project cannot take at all leaves the strip, and one sentence under it says what the project makes and where the build's result goes: the project's folder for a program, the Kernels page for a kernel module, each app's page for a Makefile that only builds App Builder apps.
- Build runs as a job in the bottom panel. A QPKG package builds with QDK's
qbuild, a Makefile project withmake, both in the project's builder environment. The log names every.qpkgthe build wrote tobuild/and whether it carries a code signature. An App Builder app builds as on its own page. When a Makefile's default goal runs only./bvsapp.sh createand./bvsapp.sh buildlines, Build does the same through the App Builder: it makes each app acreateline names when that app is missing, then builds the apps one after the other. Acreateline is./bvsapp.sh create NAME RECIPES-FILE [ENV] [LINK] [TARGET] [OTHERS]. Such a Makefile names its default goal and the goal's prerequisites in.PHONYand holds only plain assignments, each variable set once, and plain rules; one with anything else (a condition, an include) builds with make. TARGET isnative,aarch64orarmv7, so one Build can make an app for this NAS and one for ARM 64-bit. OTHERS says what a static app does with recipes that cannot link static (refuseorbundle). An app made this way also takes the project's own recipes fromrecipes/, unless the folder's name is not a project name (a name with a space, for example). When an app of that name is already there with another target, or with another project while the folder has recipes of its own, Build stays off and says how to fix it. Before the first build the step names the apps it builds. A Makefile project with its package inqpkg/makes the package file withmake qpkgin the editor's terminal; until it is there, Test deploy, Publish and Publish to web say "Needs a package file". - Rebuild builds from scratch:
make clean, thenmake; for a QPKG package the old.qpkgfiles ofbuild/go first. A Makefile without acleantarget cannot rebuild, and the page says so. An App Builder app has no Rebuild, and neither has a Makefile that only makes and builds App Builder apps. - Export as QPKG (admin) is there for an App Builder app, as on the app's page. Publish then waits for an export, as on the app's page, and Open the app in the header opens that page.
- Test deploy is for admins. It installs the newest
.qpkgon this NAS withqpkg_cli, the way App Center installs a file, and the job waits until App Center confirms it. The package stays installed until you remove it in App Center. A package without a code signature is refused: a test deploy installs only signed packages and never skips App Center's signature check (in App Center a NAS's owner may allow unsigned apps; this page does not). Sign it with a certificate as files (case 1 in Code signing a QPKG). The package of Bucket & Code Server IDE itself is refused, because installing it would replace the running package. For an App Builder app, Test deploy activates its build on this NAS. - Publish puts the newest
.qpkginto the package repository, with the repository's rules: an admin's publish becomes the listed version, a developer's waits for an admin, and a package without a code signature is refused unless the repository takes unsigned packages. A line under it says whether the project's CI publishes to the App Center repository too; this button publishes either way. - Publish to web (admin) copies the newest
.qpkg, or the website folderpublic/(elsedist/), to a web target and reads it back over the target's public address. For a website the wizard made, the step waits for the first build, which writespublic/.
A Makefile project picks a target beside Build (make's default, its first target, unless you choose another); a CMake project builds, or builds and runs its tests. A kernel module project builds in the kernel workshop's workspace of the running kernel, and that build is an admin's. A running step has Cancel, and the job usually ends at once. Run the workflow runs the project's CI workflow by hand when the workflow has a workflow_dispatch trigger; otherwise the button is off and the page says why. Every template's workflow has that trigger. Installed on this NAS lists the packages the project's test deploys installed, with Remove from this NAS for an admin. Delete the project, in the menu beside the header's buttons or in the project's menu on the Projects page, is for an admin or for the developer whose Forgejo account holds the repository. You type the project's name first; the folder goes, and the Forgejo repository, an App Builder app of the name and installed packages stay. The page then links the repository.
Settings holds what bvsd keeps for the project outside its folder. For a Makefile, CMake or QPKG project it shows the environment Build and Rebuild run in and changes it. For a Makefile that only makes and builds App Builder apps, it lists each app with the environment its line in the Makefile names. A project that makes a package, or whose workflow runs bvs repo publish, has the switch Publish to the App Center repository there: while it is on, bvs repo publish in the project's CI adds its packages to the repository; while it is off, the CI run skips that step with a notice. A project with no choice kept (an example's copy, a clone) has it on. Git shows the branch, how far it is ahead of or behind its remote as the last fetch left it (the page never fetches), the last commit and the changed paths. Files lists one folder at a time; pick a file to read it, and a .qpkg downloads. Editing stays in the editor, and the .git folder is left out.
Recipes, for a project with its own recipes/ folder, lists the recipes its apps take: each with its version, the library version it replaces when it has the name of a library recipe, and what the recipe check found, file and line, under it. A recipe bvsd leaves out has a row with the reason; past three left out the same way, one row counts the rest. What the check found about the recipes folder as a whole has a row of its own. A click opens the recipe's folder in the editor; the menu also copies the command that runs the same check in a terminal of the NAS, bvs recipe lint with the project's folder. The card also shows when the project's recipes is a link, a file, a folder bvsd cannot read or a folder holding only links, and says why no recipe of it is read.
Service and package (a QPKG package, or a Makefile project with its package in qpkg/, such as a package from source) holds the package's start/stop script as blocks, the fields of its qpkg.cfg (Package) and the files written from them (Script). Save shows each file's diff, then commits it to the project's repository; if the project changed meanwhile, plan again. Edit by hand turns the blocks off, so you edit the script in the editor; Attach the blocks again shows what writing it again would replace. Update the library brings this Bucket & Code Server IDE's bvs-qpkg.sh into the package, with its diff. A project made before the blocks offers Use blocks for this script.
Versions lists what the project can be switched to, and Switch to this version does it:
| Version | What switching does |
|---|---|
A .qpkg in build/, or an App Builder export | a test deploy of that file (admin) |
| The App Builder's build | activates it (admin); the App Builder keeps one build |
| A version in the package repository | makes it the listed version (admin) |
| A git tag | checks the tag out in the folder |
A click picks a version; its menu also tests, publishes, publishes to a web target or downloads a .qpkg. A tag is checked out as a detached HEAD, and Back to main in the Git card returns to the branch. The switch is refused while the folder has changes that are not committed; the message lists them, and you commit or stash them in the editor first. Tick two versions and Compare the two lists the files one has and the other lacks, and the files that changed. Two tags, or two packages, are compared by content; a package against a commit only by size. On the Packages page an admin also installs a repository version on this NAS (Install on this NAS), with the test deploy's checks.
Your own web server (web targets)
A web target is where an admin publishes a package or a website to their own web server: a folder of this NAS (QTS's Web share, which QTS's Web Server serves, or another shared folder) or an rclone remote (SFTP, FTP, WebDAV, S3, set up in the backups' rclone.conf), with the public address that serves it. They are on the Publish page, under Web targets: Add a target, Change, Remove and Test, which copies a small file there, fetches it from the public address and deletes it again.
A folder target is a folder of its own inside a share (/share/Web/mysite, never a whole share or a volume), outside the data folder, because a website publish removes from the target what the site no longer holds. The address starts with http:// or https:// and has no user or password.
Each publish reads the file back over the public address: a package's .qpkg with its .sha256, compared by sha256, or a website's index.html, compared byte for byte. When the address does not serve what was copied, the job fails with web_check: and both values.
CI runners
The CI module runs Forgejo Actions: a workflow file in a repository's .forgejo/workflows/ runs on a runner when something is pushed. It needs the Git module. An admin turns it on under Modules, CI, Enable. The job registers the NAS's own runner with Forgejo, restarts Forgejo with Actions on, and starts the runner; it ends when the runner has reached Forgejo.
The runner on the NAS
- Its labels are
bucketvs,qnap-x86_64(orqnap-aarch64) andself-hosted, plus the extra labels an admin sets on the CI page. A job whoseruns-onnames one of them runs here, for exampleruns-on: bucketvs. - It runs every job on the NAS itself, as admin, not in a container. A job has the package's tools (git, curl, make and the rest), Node for JavaScript actions such as
actions/checkout, andbvs env run <env> -- <command>for a build inside an environment. Whoever can push a workflow into a repository can therefore run commands as admin on the NAS. A member or a viewer cannot create, fork or import repositories, or create organizations, in Forgejo; developers and admins can. A viewer never pushes. While CI is on, a member's push into a repository whose Actions are on is refused with the reason, and so is a push with a deploy key. A member's push, or one with a deploy key, into a repository that an account below developer owns or administers is refused too, also while CI is off: such an account could turn Actions on itself. bvsd turns Actions off, within a minute, in every repository a member or a viewer owns or can write to, and before the runner starts when CI is turned on. - What is left is a developer's or an admin's own act: turning Actions on, or dispatching, in a repository a member wrote to earlier, or giving such an account admin access to a repository it wrote to earlier. A developer who merges or builds a member's commits runs them as admin too.
- Jobs at once is
ci.capacity(1 by default). A job that no online runner can take waits in Forgejo. - Repositories made before CI was on have Forgejo's Actions switched off, so a pushed workflow starts nothing: turn it on in the repository's Settings, Units (Actions). Repositories made while CI is on have it.
- Actions such as
actions/checkout@v4are downloaded fromhttps://data.forgejo.org, so the NAS needs internet access for workflows that use them. The runner has no cache:actions/cachefinds nothing and the job goes on. - Artifacts (
actions/upload-artifact) are uploaded to Forgejo's own address (the one on the Git page), so the NAS must reach that address, and trust its certificate when it is HTTPS. - The runner's state is in
<data>/runner; its log is runner under Logs. Turning the module off stops the runner and switches Actions off in Forgejo.
External runners
A build machine elsewhere (a server, a VM) can take jobs from this Forgejo too. On the CI page, under Register an external runner, an admin gives it a name and gets the commands for Linux x86_64 and aarch64. They download forgejo-runner 13.2.0 from code.forgejo.org, check its sha256, keep the runner's token in runner-token (readable by its owner only) and start the runner with the label linux-amd64 (or linux-arm64). The runner shows in the list at once, offline until the commands run. The token is shown only then. Such a runner runs its jobs on its own machine, as the user who started it, and reaches Forgejo at its public address.
SVN and Bucket
The SVN and Bucket module serves Subversion repositories at /svn/<name> and buckets at /bucket/<name>. A bucket is a network drive (WebDAV) in which every saved file becomes a revision, so any earlier version of a file can be read or put back. An admin turns the module on under Modules; the SVN & Bucket page then creates, lists and deletes both types.
- Accounts are the panel's. Permissions are set per repository: read and write, read, or none, for each account, plus a default for every account not listed. Admins always read and write; members and developers write where they have read and write; viewers read at most. A new repository gives nobody but the admins any access until an admin sets it (the people icon on its row, Permissions for
<name>). Below admin, the page shows each person their own access to each repository and bucket. - Wrong passwords count as at the panel's sign-in: 5 wrong passwords within 15 minutes lock that account from that address, and the address itself, for 15 minutes.
- Two-factor authentication. An account with two-factor authentication does not use its password for SVN or WebDAV. It makes one app password on its account page (App password for WebDAV and SVN): 25 random characters, shown once. A new one replaces the old; a password change or turning two-factor authentication off removes it.
- Deleting a repository moves it to
<data>/trash, where thetrashjob removes it after 7 days.
SVN repositories
svn checkout "https://<NAS address>:9951/svn/<name>"
svn commit -m "…"
The page shows the checkout command with the repository's address. Behind a proxy the address follows the public URL, and with an SVN hostname it is /svn/<name> on that name. The History of a repository lists its revisions; a file can be read at any revision, compared with another, and put back as a new revision (Restore this version, for accounts with read and write).
Buckets
- Saving is committing. A client that saves a file makes a revision with the account's name and the time; the revision is marked as made through WebDAV. Many clients write an empty file first and the data after it, so one save can make two revisions.
- macOS files. A bucket created with Refuse macOS's ._\* and .DS_Store files (the default) does not store the
._*and.DS_Storefiles Finder writes next to what it copies. The client gets no error, but the file never becomes a revision. Deleting such a file is allowed. - Going back. The bucket's history shows each file's versions; Restore this version saves the old content as a new revision, so nothing is lost.
WebDAV clients and their limits
| Client | How to connect | Notes |
|---|---|---|
| macOS Finder | Go, Connect to Server, https://<NAS address>:9951/bucket/<name> | Finder locks a file while it writes it; the bucket answers WebDAV locks |
| rclone | a webdav remote with vendor = other and the bucket's address | suits scripted copies |
| davfs2 (Linux) | mount -t davfs https://<NAS address>:9951/bucket/<name> /mnt/<dir> | the panel's username and the password or app password |
| Cyberduck, WinSCP | a WebDAV (HTTPS) connection to port 9951, path /bucket/<name> | |
| curl | curl -T file -u <user> https://<NAS address>:9951/bucket/<name>/file | one upload, one revision (or two, see above) |
| Windows Explorer | Map network drive | Windows' WebClient sends a password only over HTTPS with a certificate Windows trusts: put the bucket behind a proxy with a real certificate (Behind a reverse proxy); the self-signed certificate is not enough |
- Large files are not cut: nginx sets no body size limit on
/bucket/and passes the upload through as it arrives. - A file that the bucket refuses to store (a macOS file above) is answered with
201by the WebDAV server all the same, so a client shows no error; the file is not in the bucket.
Showing a repository or bucket to people without an account
An admin can make an SVN repository or a bucket public with the switch in its row on the SVN & Bucket page (On the web). Anyone who has the address can then read it in a browser, without an account:
| Address | What it shows |
|---|---|
https://<NAS address>:9951/svn-web/<name>/ | an SVN repository |
https://<NAS address>:9951/bucket-web/<name>/ | a bucket |
The row's View on the web link opens that page. It lists the files and folders at the newest revision or an older one, shows a text file with line numbers (up to 1 MiB) or a picture, and offers every file as a download. The History lists the revisions with their authors, messages and changed files. A revision's page shows the diff of every file it changed (up to 20 files of at most 1 MiB; the others are links); any file can be compared between two revisions.
- Read only. The pages cannot change anything. WebDAV clients and
svnstill need an account, also for a public repository. - Only what is public. A repository that is not public, and a name that does not exist, both answer "There is no public repository at this address", so the pages do not tell which names exist.
- Limits. One address may open 60 pages at once and 2 a second after that, and download 2 files at a time; beyond that the page asks to try again in a moment. A diff is cut at 2 MiB.
- Search engines are asked not to index the pages. The pages are in English. They have the panel's look, dark or light as the visitor's browser is set.
- Behind a proxy or with an SVN hostname, the address follows the repository's own (the public URL, or the SVN hostname with the same paths).
Editor and AI tools
The Editor module runs VS Code in the browser (code-server) for each developer and admin account. An admin turns it on under Modules. Each account gets its own instance, started when the account opens the editor and stopped after a time without traffic.
The editor's terminal is a root shell on the NAS. On QTS the package's processes run as admin (uid 0), except Forgejo and httpd. Give the developer role only to people you would give the NAS's admin password.
Opening the editor
- Open the editor on the Editor settings page (or
/ide/on the panel's address, or the Editor hostname) opens the workbench in a new tab. The first request starts the instance; the panel's sign-in comes first when there is no session. - Through the published main hostname, while the editor is not published there, the Editor settings page, the Editor tab, the Overview and Open a shell say that the editor opens only from the local network and link to its LAN address (
https://<NAS address>:<HTTPS port>/ide/); for an admin they also link to the switch on the Publish page. - The editor opens
<data>/projects, where the examples under Editor projects put their projects. The account's settings, extensions and terminal history are kept in<data>/ide/<account>and its home folder in<data>/home/<account>. - The terminal has the package's tools first on
PATH: git, svn, node, npm, rsync, curl, bash and the rest.bvs env shell <name>enters a Toolchains environment andeval "$(bvs use <id>)"sets up a toolchain or runtime. Open a shell on the Toolchains page opens the editor with a terminal already inside that environment. - The Editor activity, and the command palette, open the editor with a shell in an environment or with an app's folder. The editor shows inside the panel's Editor tab; on a phone it opens in its own browser window. Only pages of the panel's own address may show the editor inside them: the package tells browsers so on every answer of the editor, so another site cannot put the editor's root terminal under a page of its own.
- Idle time (default 30 minutes) stops an instance without browser traffic, but never while a program started from its terminals, an AI agent that an extension started (Claude Code for VS Code's
claude, Codex, Gemini CLI) or something left running from them still runs. 0 turns the idle stop off. An account can stop its own instance on the Editor settings page; an admin can stop any. - Memory and processes on the Editor settings page shows what an instance runs and how much memory it takes against the NAS's, marks what keeps it running, ends one process with End, and says why the last stop came and what it ended. An AI agent waiting at its prompt keeps the instance running too: end it there when it is no longer needed.
- VS Code's Ports view reaches a development server on the NAS at
/ide/proxy/<port>/; the package's own loopback ports are refused there. - New recipe, on the menu of a project's
recipesfolder, or of a recipe's folder in it, in the Explorer, asks for a name and writes a new recipe there:recipe.jsonandbuild.shfrom the package's starter recipe. In the command palette it is Bucket: New recipe, which works while a file of a project with arecipesfolder is open. Each time you save one of a recipe's files, the editor checks the project's recipes withbvs recipe lintand lists the findings in the Problems view. It warns about the starter recipe's sha256 until you put in your source's real one.
The editor answers only its own pages. A WebSocket, a form or a script's request that another page sends with the account's session, from another site or from another port of the NAS, gets 403 and starts no instance, so another page cannot reach a development server behind the Ports view either.
Extensions
- Install extensions in the editor's Extensions view (Ctrl+Shift+X). They come from Open VSX: the browser searches open-vsx.org and shows each extension's page and icon from openvsx.eclipsecontent.org, and the NAS downloads the extension itself from open-vsx.org. The browser and the NAS both need to reach the internet for that.
- The first extension of each publisher asks "Do you trust the publisher …?". Trust Publisher & Install installs it; until then the extension shows "Installing".
- Each account keeps its extensions in
<data>/ide/<account>/extensions, and a package update keeps them. - The package brings a set of extensions and formatters, which the editor installs for each account at its first start, and again after a package update that brings a new set. With it the editor highlights and formats (Format Document in the command palette) Markdown, shell scripts, C, C++, Rust, Go, Pascal, Python, Perl, PHP, JSON, YAML, TOML, nginx configuration, CSS, HTML, JavaScript and TypeScript. ShellCheck marks problems in shell scripts, systemd unit files get highlighting and completion, a project's
.editorconfigsets the indentation, colour codes such as#ff8800orrgb(0, 128, 0)show in their colour in every file, and Hex Editor: Open Active File in Hex Editor in the command palette shows a file's bytes. - Rust is formatted once the Rust runtime is installed on the Toolchains page and the account's editor has started again; until then Format Document in a Rust file reports that the request failed.
- On an ARM NAS (the aarch64 package) Pascal, Perl and PHP are highlighted but not formatted: the package has no build of Free Pascal, Perl or Mago for it.
- An extension of the set that the account removes in the Extensions view stays removed, also after a package update, and one it updates keeps the newer version. A default formatter chosen for a language (Format Document With…, then Configure Default Formatter…) stays the account's choice.
- Every
.conffile opens as nginx configuration. The language name in the status bar switches a file to another language. - Ruff, which formats Python, says once that no Python environment extension is installed. It formats without one.
- In the editor's terminal,
code-server --install-extension <publisher.name>installs an extension from Open VSX too. - Some extensions draw their own views, such as the panel of Claude Code for VS Code, the hex editor and the Markdown preview. The browser shows these views only when it trusts the panel's certificate. With the self-signed certificate accepted only through the browser's warning page they stay empty, and the Editor tab and the Editor settings page say so. Any of these fixes it:
- trust the NAS's certificate on the device: Download the certificate on the Network page or in that notice, then add it to the system's or the browser's trusted certificates (The certificate);
- an admin adds a certificate for the NAS's name on the Network page;
- open the editor through the published main hostname, once an admin publishes it there.
- Claude Code for VS Code (publisher Anthropic) installs from the Extensions view, and its panel asks how to sign in to Anthropic. Claude Code: Open in Terminal in the command palette runs Claude Code in a terminal, which works with any certificate. VS Code's own chat features start off (
chat.disableAIFeatures); that setting does not affect this extension.
Claude Code, Gemini CLI and Codex CLI
Under AI command-line tools on the Editor settings page each account installs, updates and removes these tools for itself:
| Tool | Installed with | Command |
|---|---|---|
| Claude Code | Anthropic's installer (claude.ai/install.sh), npm if that fails | claude |
| Gemini CLI | npm install -g @google/gemini-cli | gemini |
| Codex CLI | npm install -g @openai/codex | codex |
- They go into the account's home folder (
~/.local/bin,~/.npm-global/bin), which is on the terminal'sPATH; another account does not see them. - Each install is a job with its log on the Jobs page. Signing in to each tool (an account or an API key) happens in the tool itself, in the editor's terminal.
Coding with an AI assistant
Every project that New project makes carries a skill for AI assistants: .claude/skills/qnap-dev/ and an AGENTS.md that points to it. The skill, in English, holds guidelines for everything about a QNAP NAS: what it is at the shell (a RAM-disk root, BusyBox, no systemd, no package manager, getcfg and setcfg), storage and paths, how to build, sign and publish with this package (bvs env run, bvs ldd, bvs qbuild, the App Builder, the kernel workshop, the package repository), ports, backups, and what it must not suggest, such as apt on the NAS or allowing unsigned apps in App Center. It says plainly that signing with a hardware token is not supported and not tested.
- Start the assistant in the project's folder (
cd <data>/projects/<name>), not inprojects/: each tool looks in the folder it starts in. - Claude Code loads the skill from
.claude/skills/when a task needs it. Codex CLI readsAGENTS.md. Gemini CLI readsAGENTS.mdonce the project's.gemini/settings.jsonholds{"context": {"fileName": ["AGENTS.md", "GEMINI.md"]}}. - The package keeps the current skill in
/usr/bin/bucketvs/share/skills/qnap-dev. A project made before the package had it, or a project copied from the Examples gallery, gets it by copying that folder into the project's.claude/skills/. - The skill cites QNAP Club Poland's knowledge base (forum.qnap.net.pl) for what the NAS has and lacks.
- The package's build also writes the skill as
qnap-skill-<version>.zip, the same files, for use outside the editor: unzip it into a project's.claude/skills/or into~/.claude/skills/. The skill's text is licensed CC BY 4.0 (itsLICENSE): share and adapt it with the credit line "QNAP guidelines skill by QNAP Club Poland / qnaputilities.com, CC BY 4.0".
Toolchains and environments
The Toolchains page downloads what is needed to build software on the NAS:
| Type | What it is | Examples |
|---|---|---|
| Root filesystem | a Linux system to unpack into an environment | Debian 12 and 13, Ubuntu 22.04 and 24.04, Alpine 3.22 |
| Cross toolchain | a compiler that builds for another processor | Bootlin toolchains for aarch64, x86-64 and armv7, with glibc or musl |
| Runtime | a language or build tool | Python, Go, Node.js, Zig, Rust, CMake, Ninja, Java (JDK) |
| QNAP | QNAP's own tools | QDK 2.5.0, for bvs qbuild and Export as QPKG |
| Kernel source | a Linux kernel tree for the kernel workshop | Linux 6.6.129 from kernel.org; QNAP's GPL kernel release of QTS 5.2.3 |
Entries for another processor are hidden; "Show entries for other processors" lists them, but they cannot be installed. The Bootlin toolchains run on x86_64, so an ARM NAS has no cross toolchain.
The catalog
The package carries a built-in catalog. The signed remote catalog at https://pub.qnaputilities.com/bucketvs/catalog.json can add entries without a new package. bvsd uses it only when its ed25519 signature matches the key built into the package and it is not older than the catalog in use. "Refresh the catalog" fetches it at once, and the built-in cron job "Toolchain catalog refresh" fetches it every day at 04:15.
Installing and removing
Installing is an admin's job, shown with its progress on the Jobs page:
- The archive is downloaded into
toolchains/cache/in the data folder. An interrupted download resumes when the install is started again. - Its sha256 is checked against the catalog. A mismatch deletes the file.
- A cross toolchain or a runtime is unpacked into
toolchains/<kind>/<id>and its archive deleted. A root filesystem or a kernel source stays as its archive; each environment or kernel workspace unpacks its own copy. QNAP publishes its GPL kernel release in two parts: both are downloaded, each checked by its own sha256, and joined.
A full volume fails the install with "no space left on" and the mount point of the volume.
"Remove" deletes an installed entry. Environments made from a root filesystem are copies and stay.
Using a toolchain or runtime
bvs use <id> prints the export lines (such as PATH) that put an installed cross toolchain or runtime to use in a shell:
eval "$(bvs use bootlin-aarch64-glibc-stable)"
The Toolchains page shows the same lines under "bvs use" for each installed entry.
Environments
An environment is a Linux system unpacked from a root filesystem into envs/<name> in the data folder. It is changeable: apt install build-essential works inside a Debian or Ubuntu one, and what you install stays there.
- "New environment" (admin) downloads the root filesystem first when it is not installed, then unpacks it.
- While there is no environment, the list offers the Ubuntu 24.04 builder, the one the app examples build in, with one button (admin): its job shows in the list, and the environment appears when the job is done. Nothing is created by itself.
- A name has 1 to 64 characters: lower-case letters, digits,
.,_or-, starting with a letter or a digit. - A preset from the Examples gallery also installs packages with apt-get before the environment appears: the Ubuntu 24.04 builder (the default builder) and the Debian 12 builder, both full, with the App Builder's build tools and patchelf; Clang/LLVM (LLVM 18), Fortran (gfortran 13), Kernel builder, .NET 8 SDK and Kotlin (Kotlin 1.3.31), all on Ubuntu 24.04. Every preset's glibc is at most 2.39, so what it builds runs on every NAS the package runs on (Which NAS models an app runs on). When apt-get fails, no environment is left behind. From a shell:
bvs env create <name> --from <rootfs-id> --package <name> --package <name>. - "Clone" makes a full copy under a new name, for example to try a change without touching the original.
- "Export" packs the environment into
toolchains/exports/<name>-<time>.tar.zst. - "Delete" removes it. bvsd refuses while a shell or a process runs inside it. Files in
projects/are not in the environment and stay.
Inside an environment:
projects/of the data folder is visible as/projects, and a shell starts there;HOMEis/rootof the environment, andLANGisC.UTF-8;/proc, a read-only/sysand a small/devof its own are mounted, and the NAS'sresolv.confandhostsare copied in;- the mounts belong to the shell's own mount namespace and vanish when it ends, so nothing stays mounted on the NAS.
The same from a shell (the examples call bvs by name; while the package runs, a cron job and an SSH session on the NAS can do the same):
bvs env list
bvs env create deb12 --from debian-12-x86_64
bvs env shell deb12
bvs env run deb12 -- make -C /projects/hello
bvs env shell and bvs env run start in the folder you run them from. A folder under the data folder's projects/ is the same folder under /projects inside the environment. Any other folder (a CI job's checkout, a folder in a share) is mounted at /work and the command starts there. From / they start in /projects.
Building a QPKG with bvs qbuild
bvs qbuild builds a QPKG from the folder you run it in (a folder with qpkg.cfg, as the QPKG template of New project makes). It runs QNAP's qbuild inside a Debian or Ubuntu environment. qbuild calls system programs by their full paths, and in the environment those paths lead to the environment's programs instead of the NAS's own. The package lands in build/ of the folder.
bvs tc install qdk-2.5.0-x86_64 # once; qdk-2.5.0-aarch64 on an ARM NAS
cd /share/CACHEDEV1_DATA/BucketVS/projects/MyTool
bvs qbuild # --env <name> picks the environment
It uses the environment ubuntu2404-builder (the example "Ubuntu 24.04 builder"), else debian12-builder, else another Debian or Ubuntu one, and needs rsync in it. When something is missing, it stops and names the command that adds it, for example bvs env run ubuntu2404-builder -- apt-get install -y rsync. Arguments after bvs qbuild go to qbuild. Before qbuild runs, bvs qbuild says which code-signing case applies to the folder.
An environment has its own files and tools. Everything in it runs as root, sees every process on the NAS and uses the NAS's network.
Code signing a QPKG
App Center checks a package's code signature when it installs it. Signing needs a code-signing certificate that QNAP issued to you. bvs qbuild names one of three cases on its first line, for example bvs: code signing, case 1 (a certificate as files): …:
| Case | The folder holds | What happens |
|---|---|---|
| 1, a certificate as files | certificate and private_key, optionally ca_certs (the chain) | qbuild signs the package with openssl; the environment needs openssl and bsdextrautils |
| 2, a certificate on a hardware token | certificate_hsm | not supported and not tested: bvs qbuild stops before anything is built |
| 3, no certificate | none of those files | the package is built unsigned |
- Case 1. Put the three files next to
qpkg.cfg. When the environment lacks a program,bvs qbuildnames the command, for examplebvs env run ubuntu2404-builder -- apt-get install -y openssl bsdextrautils. It refuses a folder with only one ofcertificateandprivate_key(qbuild would warn and build an unsigned package) and one where either file is empty. Keep the files out of git: the QPKG template's.gitignorelists them. In CI, keep them as the repository's secrets and write them into the folder for the build only. - Case 2. qbuild would read the key's place on the token from variables that do not reach it inside the environment, and the package has not been tested with a token. Build without
certificate_hsmand sign the package on the machine that holds the token with QDK's own tools; the QNAP skill for AI assistants (references/signing.md) describes QDK's route, which this package has never run. - Case 3. On a QTS 6.1 unit,
qpkg_cli -mof an unsigned package installed nothing, whilesh <file>.qpkgin a shared folder installed and started it. The package repository takes it only while an admin allows unsigned packages, and App Center on another NAS installs it only if that NAS's owner allows apps without a valid digital signature, which this package never recommends. Export as QPKG always makes this case. qbuild --signadds a GnuPG signature, which is not the code signature App Center and the repository read.
App Builder
The App Builder compiles chosen programs into one folder, the app's prefix (by default /usr/app/my_app_prefix), inside an environment.
The Examples tab of Apps holds ready apps, environments, CI workflows, cron jobs and Editor projects. View the code on a card shows what the example makes before you use it, for any role: an Editor project's files, the workflow file, the cron job's command, or for an app its recipes in build order with each recipe's build.sh and recipe.json. The viewer is read only and shows text files; an environment has no file, so its card has no such button. Using an example needs the developer role, and an environment the admin role: a developer's card of an environment has no Use this example.
Recipes
Each program is built from a recipe. A recipe names its source with a sha256, the recipes it depends on, the build tools the environment needs, and a check that proves the result works (such as bin/bash --version). There are recipes for libraries (zlib, openssl, ncurses, readline and others), shells (bash, zsh, busybox), tools (coreutils, curl, git, jq, rsync, tmux, htop, nano and others), servers (nginx, redis, openssh, dropbear) and a language (lua).
- A recipe is built only after the recipes it depends on.
- A rebuild reuses a recipe's result while its inputs are unchanged (the source, the recipe, its dependencies, the environment's compiler and C library, the prefix, and whether it links dynamic or static) and every file it installed is still in the prefix.
- The link mode decides how programs find their libraries. dynamic gives every program a RUNPATH of
<prefix>/lib. relocatable writes it relative to the program's own folder ($ORIGIN/../lib), so the tree finds its libraries in any folder; paths compiled into programs, such as the terminfo folder, still name the prefix. bundle is relocatable and also copies intolibthe libraries the programs load from the build environment (such as libstdc++), so the app needs only the NAS's C library. static links the recipes that support it statically. Switching among dynamic, relocatable and bundle rebuilds nothing. - A static app refuses recipes that cannot link static (openssh, dropbear and tmux), naming each with the reason, unless the app is set to build them as bundle. The New app page and Change offer all four modes and, for a static app, that choice.
- A static program that looks up users, groups or host names reads them only from the NAS's own
/etc/passwd,/etc/group,/etc/hostsand DNS, whatever/etc/nsswitch.confnames, so accounts from a domain or LDAP are unknown to it. Build such programs dynamic when those accounts matter. The panel warns when a static app includes one. - After the last recipe, every recipe's check runs again over the finished prefix.
- Config files under the prefix's
etc/survive a rebuild: an edited file stays, and the new default goes next to it as<file>.bvs-new.
Recipe updates
New and updated recipes also come as a signed catalog from pub.qnaputilities.com, so they reach the NAS before the next release of Bucket & Code Server IDE. bvsd checks for a newer catalog once a day. It uses one only when its signature matches the key built into bvsd, every file matches the catalog's list, and its recipes are newer than the ones that came with the package; a catalog that fails a check is never used. While a catalog is in use, New app marks the recipes it brings new in the catalog or updated by the catalog.
The Recipe library card on the Apps page shows which library builds use (Package library with the package's version, or Catalog with the catalog's version) and the last check with its result. An admin has two buttons there; the second one appears while a catalog is in use:
- Check for recipe updates checks now instead of waiting for the daily check.
- Use the package's library goes back to the recipes that came with Bucket & Code Server IDE. The catalog stays set aside until a newer one arrives, and builds then use the newer one.
After a switch, a rebuild reuses every recipe whose files did not change. Before each build bvsd checks the catalog's files again. If one changed on the NAS, the build stops before anything runs, and builds use the previous catalog or the package's library.
Building an app
Apps, New app (developer role) asks for a name, the prefix (/usr/app/<name> unless you choose another), the environment the app builds in (a Debian or Ubuntu environment; the Examples gallery makes the "Ubuntu 24.04 builder", which the page chooses first), the target (this NAS's architecture, or aarch64 or armv7 as a cross build), which NAS models it runs on, the link mode and the recipes. The recipes they depend on are added in build order.
An app can also take the recipes of one project that has its own recipes/ folder. Choose the project in Recipes from a project in the Packages step. Its recipes are listed first, under the project's name and marked project, and the library's follow. A project recipe with the name of a library recipe replaces that recipe in this app and shows the library version it replaces. A recipe the project leaves out is named with the reason. A recipe you chose that the project's list lacks is named under the select and left out of the app; choosing a list that has it brings it back. The app's page names the project, and a developer opens its Project view from that name. A viewer sees the recipes of the app's last build in the order its log names them, each with its state and log; before the first build there is no order to show.
The link mode is a card each, with a picture of where the programs find their libraries, what the mode costs in size, in updates and in where the app runs, and which recipes take it. Before you choose recipes the cards count the whole recipe library; when you come back to the first step they count the recipes of your build. For a static app you choose what happens to recipes that cannot link static: build them as bundle, or leave them out.
- Build (or Rebuild) runs a job: it downloads each source and checks its sha256, installs the build tools the recipes need in the environment, builds in a copy of the app's previous build, and checks every program.
- A build that fails, is cancelled or is interrupted leaves the app as it was: activation, download and export keep using the newest build that passed.
- One build of an app runs at a time, and a build waits while programs of the active app run (stop them first).
- The app's page shows the build order, each recipe's state and log, the files under the prefix, and the archive of the newest successful build (Download, a
.tar.zst). - Change on the app's page sets another environment, which NAS models it runs on, the link mode, the project it takes recipes from, or the recipes; the next build applies it.
- Dependencies, a tab of the app's page, shows what every ELF file of the newest build needs and where the NAS's loader finds it, read without running anything. A missing library, a version the library does not define, or a RUNPATH that points outside the app is listed as a finding with its fix. A developer runs a fix with its button: Add … and build, Switch to … and build, Build again, Patch the RUNPATH, Set the interpreter, Choose another environment or Build for this NAS only. A fix with no button (installing a package into an environment, building for another machine) is a sentence to act on. Below the findings, every file and what it needs is drawn as a graph (programs, the app's libraries, the NAS's libraries and the missing ones in columns; a dashed line is a version the library lacks, a dotted line a library found nowhere) or as a tree; choose a file for its facts. On the NAS,
bvs deps <app>prints the same. - ELF inspector (Toolchains, developer role) checks one file: from an app's build, from the projects folder or from an environment, and for an admin any file on the NAS, as it would run on this NAS or inside an environment.
bvs ldd <file>does the same on the NAS (--env <name>for an environment). - Every build runs the same check over its result before it replaces the app, and fails on a problem that stops a program on this NAS, or on a program that needs a newer glibc than the app is for, with the problem and its fix in the job's message.
- Patch tool (the Dependencies tab) sets the RUNPATH (the app's
lib, the$ORIGINform, or a folder of the app) or the program interpreter of chosen files of the newest build, with the environment's patchelf, on a copy that replaces the build only when it succeeds. Preview shows each file before and after and which libraries the new RUNPATH does not find in the app; Apply the patch runs it. The next build writes the RUNPATHs in the app's link mode again.
A Qt build runs Qt's own tools (moc, rcc, and Linguist's lrelease for translations). In a cross build the Qt recipes (qtbase, qttools) therefore first build those tools for this NAS, into host/ under the prefix, and then Qt for the target; the recipes built on Qt run the tools from there. Nothing on the target runs host/. It is part of the app's build, so the archive and Export as QPKG carry it too.
Your own recipes
When the recipe library has no recipe for a program, a project can carry its own. The recipes go into the project's recipes/ folder, in the library's format, and only the apps that name the project build them. Making a package this way takes five steps:
- Make the project. In New project, the template Package from source makes a folder with a starter recipe named after the project, a Makefile for two apps (one for this NAS's processor, one for aarch64, a cross build on an x86_64 NAS) and the package's start and stop script from the Service step.
- Write the recipe. New recipe in the editor adds another recipe from the same starter (Opening the editor). Put the program's real source address and its sha256 into
recipe.json(curl -fsSL <address> | sha256sumin the editor's terminal prints the sum), and its build steps intobuild.sh. A recipe folder holdsrecipe.json,build.shand the vendor filerecipe.jsonnames, and nothing else, so a patch goes intobuild.shas a here-document. - Check it. Every save of a recipe's file checks the project's recipes and lists the findings in the Problems view;
make lintin the terminal and the Recipes card of the Project view give the same findings. The check readsrecipe.jsonand parsesbuild.shwithbash -n. It never runs the recipe. - Build. Build in the Project view makes the two apps when they are missing and builds them one after the other. A build reads the project's recipes once, when it starts, so a file saved later waits for the next build. A project recipe with the name of a library recipe replaces that recipe in this project's apps only, and the build's log says so.
- Pack and install.
make qpkgin the editor's terminal packs the newest successful build of each app into the package with QDK from Toolchains, and leaves out an app without one. With the code-signing files inqpkg/besideqpkg.cfg, or in a folder named byQPKG_CERT_DIR, the package is signed (Code signing a QPKG), and Test deploy in the Project view (admin) installs it on this NAS.
A few rules apply to project recipes:
- A recipe the project leaves out (an extra file in its folder, a link, a file over its size limit) is named with the reason on the Recipes card and by the check, and a build that needs it is refused.
- When the project's folder is renamed or deleted, its apps do not build: the build answers "No project named …" until Change on the app's page sets the project's new name or None: the library only.
- Project recipes run as root in the builder environment, as the library's do. Any developer can write and build them, as any developer can run a project's Makefile through Build. A viewer sees the names of an app's recipes in its build log, never the recipe files.
- A project recipe stays in its project. It joins the recipe library, and the signed catalog every NAS takes, only through a reviewed change to Bucket & Code Server IDE itself.
- A recipe whose build needs npm, Go or Cargo packages lists them in a vendor file.
npm-vendor.py,go-vendor.pyandcargo-vendor.pyin/usr/bin/bucketvs/share/bucketvs/tools/write that list; they need a Python runtime from Toolchains.
Which NAS models an app runs on
A program built against glibc starts only on a NAS whose glibc is at least as new as the newest version the program asks for. The package counts on glibc 2.39 or newer on every NAS it runs on (QTS 6.0, QuTS hero 6.0 and newer), its glibc floor. 2.39 is the glibc of Ubuntu 24.04 and of QuTS hero 6.0.1, read from that firmware's own /lib/libc.so.6; QNAP publishes no glibc version, and its release notes list no QTS 6.0. The test units on QTS 6.1.0 and QuTS hero 6.1.0 run 2.43.
Runs on in New app and in Change chooses one of two:
- Every NAS the package runs on (the default): no program may need a glibc above 2.39. Build such an app in an environment whose glibc is 2.39 or older, such as the Ubuntu 24.04 builder (2.39) or the Debian 12 builder (2.36). This is the choice for Export as QPKG and the package repository.
- This NAS only: programs may need up to this NAS's own glibc, for software that needs a newer one. Such an app is built for this NAS's architecture and is never exported as a QPKG.
A cross build (aarch64 or armv7) is always for every NAS. New app warns when the chosen environment's glibc is above what the app allows. After each build, bvsd reads the glibc version every program and library asks for, which works for a cross build too. A file above the limit fails the build, and the fixes are named: another environment (Choose another environment), the static link mode, or Build for this NAS only. The app's page says which glibc its newest successful build needs, and the Dependencies tab lists each program above the limit.
On the NAS, bvs tree-deps <prefix> --tree <folder> --glibc-max 2.39 runs the same check over any app tree.
Activating an app on the NAS
Activate (admin) makes the prefix a link to the app's newest successful build, so its programs run on the NAS from /usr/app/<name>/bin. Activation never replaces anything it did not make: a folder, a file or another link at the prefix is refused with the reason. The QTS system disk is rebuilt at each boot, so the package makes the links again at every start. Deactivate (admin) removes the link; deleting the app deactivates it first and moves it to the trash for 7 days. A developer can delete an app only while it is not active. An app built for another architecture cannot be activated here; download its archive for a NAS of that kind.
Exporting an app
Export as QPKG (admin) packs the newest successful build as a QPKG of its own, which installs the app on another NAS without this package. It refuses an app built for this NAS only, and it stops before packing when a program of the build needs a glibc above 2.39 (see Which NAS models an app runs on). It needs QDK: install "QNAP QDK 2.5.0" on the Toolchains page, QNAP tab. Until QDK is installed the button is not shown and the app's page says where to get it. The export runs qbuild inside the app's environment, the same way as bvs qbuild. Every build installs rsync there; for an app last built with an older version of the package, the refusal names the command that adds it. The exported package links its prefix at start and removes the link at stop. It is not code-signed: its job log names case 3, and so does a line under the button. In an SSH session, /etc/init.d/<name>.sh disable and enable set App Center's switch for the exported package and leave the prefix link as it is. At every start the exported package also adds a line to /etc/profile and /root/.profile that puts the app's bin and sbin on the PATH of an SSH login, after the NAS's own commands, for admin and every other account that may log in; a removal takes the line out again.
Package repository
The package keeps an App Center repository: QPKG packages published into it can be installed on other NAS units from App Center, like any repository's apps.
- The address.
bvs repo listprints it (andGET /api/v1/repo):<base>/repo/repo.xml, where<base>is the published packages hostname (else the published main hostname) while the package is published on the internet (Publishing on the internet), else the public URL when one is set, elsehttp://<NAS address>:9950(https://…:9951when plain HTTP only redirects). On the other NAS: App Center, Settings, App Repository, Add, and that address. - Publishing.
bvs repo publish <file.qpkg>(on the NAS, as root) orPOST /api/v1/repo/packageswith the file as the body (developer role). The architecture comes from the QDK file name<name>_<version>_<arch>.qpkg(x86_64,arm_64,arm-x41), or--arch. The name, version, description and author come from the package itself;PUT /api/v1/repo/packages/<name>/metaand…/icon(a PNG, admin) change what App Center shows. A package cannot be nameddefault: that is the name of the repository's own icon. - Signed packages only. A package without a code signature is refused, because App Center installs such packages only on a NAS set to allow them. The repository takes a package as signed when its code-signing block is a PKCS#7 signature with a certificate and a signer. It does not check that signature or its certificates against QNAP's; App Center does that when it installs the package. An admin can turn on
repo.accept_unsignedin the settings for a network where that is known; those packages are marked in their description. Packages the App Builder exports are not signed, so they need this setting. - Versions. Every version stays downloadable, but App Center sees one version per package, the one an admin lists. An admin's first version of a package is listed at once; a later one when the admin moves it (
POST /api/v1/repo/packages/<name>/latest, orbvs repo publish --latest). A developer's version, a new package's first one included, is listed nowhere until an admin makes a version the latest: every NAS that added the repository installs the listed version as root. Deleting a version (admin) removes all its architectures; when it was the listed one, no version is listed until an admin picks another. - Uploads. Two uploads are copied at a time; the repository refuses one that would leave less than 256 MiB free on the data volume. The repository is not part of a backup: after a restore, publish the packages again.
- In the panel. The Packages page shows the address to copy, the packages with their versions and whether they are signed; a developer uploads a QPKG there, and an admin lists a version as the latest and deletes a version. Its Code signing card shows the three cases, how many listed versions carry no signature, and the admin's switch for unsigned packages, which asks before it turns on. An app's page shows its pipeline (build, export as QPKG, publish) with a Publish button.
Kernel workshop
The kernel workshop builds kernel modules for the kernel the NAS runs, checks each one against that kernel, and loads it. QNAP has not published the source of its current kernels (its GPL releases end at QTS 5.2.3), so the workshop reads what the running kernel shows about itself and configures a kernel tree to match it.
What you need
- The Kernel builder environment from the Examples gallery (Ubuntu 24.04 with GCC 13 and the kernel build tools). A module must be built with the same GCC major version as the kernel; the workshop compares them.
- The admin role, and root in a terminal: the
bvs kernelcommands use bvsd's CLI socket or load modules.
The profile
bvs kernel profile shows what the running kernel is: its release (6.6.129-qnap), the compiler QNAP built it with, the vermagic of QNAP's own modules, the size of struct module in them and where init and exit sit, whether modules must be signed, and the settings a kernel tree gets to match it.
The workspace
bvs kernel prepare makes the workspace of the running kernel in kernels/<release> of the data folder:
- It takes the kernel source the catalog has for this version (
bvs kernel sources): QNAP's own GPL tree when QNAP published one, else the tree from kernel.org, which lacks QNAP's own changes.--sourcepicks another. - It configures the tree: with QNAP's configuration for this model when the tree is QNAP's, else with the settings of the profile.
--option NAME=madds a setting, for example a driver. - It prepares the tree for module builds (
--fullbuilds the whole kernel instead, which takes much longer and lets the check see the kernel's exported symbols). - It checks that the tree builds for exactly the running kernel's release.
It runs as a job on the Jobs page. A failed prepare keeps the tree of the previous one.
Building a module
- Your own module is a folder in
projects/with aKbuildfile.bvs kernel makein that folder (orbvs kernel make --project <folder>) builds it. The example "Kernel module" is such a project. - A driver of the kernel that QNAP does not ship is built from its directory of the kernel tree:
bvs kernel make --dir drivers/usb/serial. The example "USB serial driver ch341" builds the ch341 driver this way; its setting comes first withbvs kernel prepare --option USB_SERIAL=m --option USB_SERIAL_CH341=m.
The modules a build makes are kept in kernels/<release>/modules/. A module QNAP ships for this kernel is not kept: QNAP's own copy stays the one that loads. bvs kernel make --build-only keeps nothing there: the module stays where it was built. The CI workflows of New project's kernel templates build this way.
The check and loading
bvs kernel check <module> compares a module with the running kernel: the vermagic, the size of struct module and where init and exit sit in it (against QNAP's own modules), whether a signature is required, the modules it depends on, and every symbol it needs. Each finding says what does not match and what to do. Exit status 3 means the module must not be loaded.
bvs kernel load <module> runs the check again and loads the module, the modules it depends on first (QNAP's own from the NAS, or yours). A module with an error in the check is never loaded. bvs kernel unload <module> removes it; a module in use stays loaded. Every load and unload is written to log/kernel-modules.log.
QNAP's kernel does not list its data symbols, so the check cannot look up symbols such as jiffies; it names them as warnings. If one of them is missing, the kernel refuses the module when it loads.
Modules in an app
An app can name modules of the workspace. Activating the app loads them before its link is made, and every start of the package loads them again once the panel is up; the Kernels page shows a load that is still going. Deactivating it unloads them first; a module in use keeps the app active.
- If the NAS restarted while modules were loading, the next start loads none and shows a notice: load them by hand or activate the app again.
- If the NAS stopped without the package's ordered stop (a crash, a power cut or a hard reset) in a boot in which the package loaded modules, the next start does not load those modules, since one of them may have caused the crash. The notice and the Kernels page name them. Loading one by hand, or activating its app again, lets the start load it again. A normal shutdown or restart of the NAS stops the package in order and changes nothing.
- A module whose start never ends (a driver waiting for its hardware) leaves the panel working. A notice names it after a minute; other loads and unloads wait for it, and a restart of the NAS ends it.
After a QTS update to another kernel, the modules must be built again in the new kernel's workspace. The same holds after a restore onto another data folder: kernels/ is not part of a backup.
In the panel
The Kernels page shows the running kernel's profile, the kernel sources of the catalog, the workspace and its modules. An admin prepares the workspace there (a source, the environment, extra settings, the whole kernel or not) and builds modules from a project folder or a directory of the tree; both run as jobs. Check (developer) shows each finding with its fix; Load and Unload (admin) do what bvs kernel load and unload do, and a refused load shows the check that stopped it. A .ko file anywhere under the data folder can be checked by its path.
Cron jobs and operations
The Jobs page has two lists: scheduled jobs (cron) and operations (the long jobs bvsd runs, such as installs and moves).
Scheduled jobs
A developer creates and changes their own jobs; an admin changes all of them. A job has:
- Schedule: five cron fields (
0 3 * * *), six with seconds first, or a descriptor (@daily,@hourly,@every 6h). ACRON_TZ=<zone>prefix picks another time zone; without it the job runs in the NAS's time zone, shown at the top of the page. - Command: run as root by the package's
bash -lc. Several lines run as one script, and its output becomes the run's log. - Where it runs: on the NAS, or inside an environment (with the environment's
bash, or/bin/shwhere it has none). - Working folder: relative to
projects/in the data folder, or a full path. Inside an environment onlyprojects/is visible, as/projects. - Timeout: in seconds (0: none). A run still going after it is stopped and marked timed out.
- While a run is going: the next one is skipped, or it waits (at most one waits).
- On failure: a failed or timed-out run sends a notice.
Each job keeps its last 100 runs with the end of their output (4 KB). The full log of a run is kept 30 days.
A job runs as root on its owner's behalf, with the owner's home folder as HOME, so it runs only while its owner is an enabled developer or admin. While the owner is disabled or has the member or viewer role, each of the job's times is recorded as a skipped run that says why, such as "its owner ola is disabled", and Run now answers with the same reason. No notice is sent. A run that is going when the owner is disabled or given a role below developer is stopped. The job runs again once the account is enabled, or a developer again. Deleting an account deletes its jobs. Built-in jobs run whatever happens to an account.
When a command ends, processes it started in the background keep running. On a timeout, a delete of the job or a stop of the package, the run's whole process group gets SIGTERM, then SIGKILL after 5 seconds.
On the nights the clocks change, the jobs follow Debian cron's rule. A job with * at the start of its minute or hour field (also */N), @hourly or @every follows elapsed time: in autumn it runs in both passes of the repeated hour, and in spring the skipped hour does not come. Any other job runs each of its times once: a time in the repeated hour runs once, and a time in the skipped hour runs right after the change.
Every signed-in account, a viewer too, reads the jobs' commands and their runs' output. Keep passwords and tokens out of a command: have it read them from a file under <data>/etc that only root reads (chmod 600).
Built-in jobs
| Job | When | What it does |
|---|---|---|
| Maintenance | 03:30 daily | removes what nothing changed for 10 days from tmp/ in the data folder; rotates log/audit.log over 10 MB |
| Empty the trash | 03:45 daily | removes what was moved to trash/ in the data folder (such as a deleted account's home folder) more than 7 days ago |
| Toolchain catalog refresh | 04:15 daily | fetches the signed toolchain catalog |
| Rotate nginx's logs | every hour, at minute 7 | renames nginx-access.log or nginx-error.log to .1 once it is over 20 MB (five old files kept) and tells nginx to reopen its logs |
An admin can change a built-in job's schedule, timeout, policy and notice, and switch it off. Its name and command stay.
Operations
Installs, environment jobs, data moves and switching a module on run as operations. The list shows the last 50, newest first, with their progress and log. Operations and their logs are kept 30 days. An operation that was running when the package stopped shows as interrupted. A developer cancels their own operations; an admin cancels any.
Backups
The Backups page (admin) sets what is backed up, when, where the copies go and how many are kept. Back up now runs a backup at any time. Scheduled backups are off on a new installation; once Scheduled backups is on, a backup runs daily by default (the built-in cron job backup). It keeps the newest run of each of the last 7 days, 4 weeks and 6 months, and always the newest good run. One backup, verify, restore or delete runs at a time.
Two runs on one day count as one day: when the newer finishes, the earlier run of that day is removed. A Back up now in the afternoon therefore replaces that morning's scheduled run; copy a run elsewhere first when that morning's state matters.
What a backup holds
A run is a folder <data>/backups/<id>/ with a manifest.json that lists every file with its sha256:
| Part | What |
|---|---|
| settings | etc/: settings, accounts, TOTP secrets, the TLS key, the backup keys |
| Git | Forgejo's dump: repositories, wikis, LFS objects, attachments and its database |
| SVN, buckets | a copy of every repository, made incrementally: a revision already copied is not copied again |
| VS Code settings | each account's VS Code settings, key bindings, snippets and the list of its extensions (not the extensions' files: the Extensions view installs them again) |
| environments | the Toolchains environments (off by default: they can be made again) |
| projects | every project folder with its Git history and the files not committed yet, without node_modules |
| HOME folders | each account's HOME folder, without caches and the AI tools' own installs (their Install button puts them back) |
| App Builder | each app's settings and the history of its builds; a build makes the app's files again |
| package repository | the published packages, their icons and the index App Center reads |
The last four are in every backup, whatever is ticked on the page. A folder that has not changed since the previous backup is linked to that backup's copy and takes no space.
The run becomes visible only when every part is written and checked; a failure or a full volume removes the unfinished folder and leaves the earlier runs as they were. Verify reads a run back completely: every archive's sha256, svnadmin verify of every revision, git fsck of every repository and an integrity check of Forgejo's database.
Targets
Every run is copied to each target on the page:
- Local folder on this NAS: the run as it is, with unchanged files linked to the previous copy, so each copy costs only what changed. Not encrypted. On the same disks as the data, it guards against mistakes, not against a failed volume.
- rsync (
user@host:path) over SSH. Sign in with the package's backup key: the page shows the public key to add to the other machine'sauthorized_keys. - rclone (
remote:path, a remote from<data>/etc/rclone.conf): cloud storage and anything rclone reaches.
A copy that leaves the NAS (rsync, rclone) must be encrypted: turn on Encrypt copies that leave the NAS and Create the key. The copies are encrypted with age to its public half; the secret half is shown once and bvsd does not keep it. Without it no NAS, this one included, can restore an encrypted copy: store it away from this NAS. A new key encrypts the runs from then on; older copies need the old secret key.
A target that fails is named in the run's result; the run itself stays good.
rclone.conf holds the remotes' credentials. Before a copy or a Test uses it, bvsd makes it (and the backup key) readable by root alone (mode 600) when it is not, and the job's log says so.
Restoring
Restore on a run puts back either everything, one repository (an SVN repository, a bucket or a Git repository) or one project folder:
- Nothing is replaced before it is checked: every archive against its sha256, every SVN copy with
svnadmin verify. A copy that fails stops the restore before anything live changes. - The live data that is replaced goes to
<data>/trashfor 7 days, so a restore can itself be undone. - One repository or one project folder is put back while the modules keep running. A project folder you deleted by mistake comes back with its history and its files not committed yet.
- Everything stops the modules, swaps the data, and restarts bvsd on the restored settings: every session ends, and the accounts and passwords are the ones in the backup. A project or HOME folder made after the backup stays.
Notices
bvsd writes every notice to the QTS System Logs. It can also send them to chat and mail services through shoutrrr URLs (Telegram, Discord, Slack, e-mail, a generic webhook and others).
| Notice | When | Switch for the URLs |
|---|---|---|
| job failure | a cron run of a job with a notice on failure failed or timed out; an operation failed | notify.on_job_failure |
| backup | the result of every backup, verify and restore, the good ones too; a scheduled backup that could not start | notify.on_backup |
| restart loop | a process started restarting in a loop | notify.on_crashloop |
| certificate | an uploaded certificate has 30 days or less; nginx fell back to the self-signed certificate; a new self-signed certificate was made | notify.on_cert_expiry |
| system | settings refused at start, an unfinished data move found, leftover processes ended, the configuration could not be written | always sent |
- The switches decide only what goes to the URLs. The System Logs get every notice.
- The same notice goes out at most once in 10 minutes. The next one after that says how many were held back.
- Each URL gets its own
shoutrrr sendwith a 10-second limit. A failure is logged with the URL's scheme only, because the rest of a URL holds its token.
The URLs and the switches are under Notifications on the System page; the switches are on by default. An admin edits them there. Other accounts see the switches and only the scheme of each URL, because the rest of a URL holds its token. The NAS admin can also set them over the CLI socket:
/usr/bin/bucketvs/bin/curl --unix-socket /usr/bin/bucketvs/var/run/bvsd-cli.sock \
-X PUT -H 'Content-Type: application/json' \
-d '{"notify":{"shoutrrr_urls":["telegram://<token>@telegram?chats=<chat id>"]}}' \
http://bvsd/api/v1/settings
Logs and the support bundle
Each log is log/<name>.log in the data folder. The logs of bvsd and of every process it supervises are rotated at 20 MB, and five old files are kept. nginx-access.log and nginx-error.log have the same limits, checked at every start of bvsd and every hour by the "Rotate nginx's logs" job, so one of them can pass 20 MB by up to an hour's traffic. The "Maintenance" job rotates audit.log once it is over 10 MB and keeps three old files. The Logs page (admin) shows the last lines of any log, filters them and follows new lines as they come:
| Log | What writes it |
|---|---|
bvsd | bvsd itself: starts, stops, applied settings, problems |
nginx | nginx's own output |
nginx-access, nginx-error | every request, and nginx's errors |
audit | every request on the CLI socket |
| one per module process | each module's program |
The System page shows the version, the platform, the time zone and every process bvsd supervises. "Download the support bundle" (admin) makes one archive for a bug report:
- the settings with the SMTP password and each shoutrrr URL masked;
- the rendered
nginx.conf, the certificate's details without its key, the volumes; - the state of every module, process and recent operation;
- the last 2000 lines of each log, with every query string in them replaced by
?(query removed).
It never holds passwords, sessions, tokens or keys.
Updates
bvsd reads https://pub.qnaputilities.com/bucketvs/latest.json at start and every 24 hours. When it names a newer version, the System page names it, with the link the feed gives. bvsd only reports the new version: it never downloads or installs a package. A failed check changes nothing in the panel.
Installing a new version
Install the new package over the old one, from App Center or with Install Manually. The installation stops bvsd, replaces the program files and starts it again; the data folder, the settings, the accounts and every repository stay, and so do the panel's sessions. The editor instances stop with bvsd, so the editors' open terminals end; an instance starts again when its account opens the editor.
From a version that used ports 6050, 6051 and 6052. Earlier builds listened on 6050 (HTTP), 6051 (HTTPS) and 6052 (Git over SSH). The first start of the new version moves each of these to 9950, 9951 and 9952 and says so in a notice; a port you chose yourself stays. Change what names the old ports: bookmarks, a Plesk proxy to 6050, and git remotes such as ssh://git@<NAS>:6052/… (git remote set-url).
Removing, reinstalling and a broken package
Your work stays when the package is removed, installed again or broken. Everything you make lives in the data folder: accounts and settings, Git and SVN repositories, buckets, project folders, HOME folders, VS Code settings and extensions, App Builder apps, environments, the package repository and the backups. The install folder (<volume>/.qpkg/bucketvs) holds only the programs and one file that names the data folder.
- Remove, then install again. Removing the package deletes the install folder and keeps the data folder and
/etc/config/bucketvs-data-root, the file that names it. The next installation reads that file and starts on the same data folder: the same accounts and passwords, the same projects and repositories, signed-in sessions included. - Install over the installed package. The same version or a newer one replaces the program files; the data folder is not touched.
- A broken package. When the install folder is damaged (a program missing, the folder deleted by hand), install the package again from App Center or with Install Manually. The installation finds the data folder the same way. A bvsd that still runs from deleted files is stopped at the start, and the installed one starts.
- What deletes the data. Only deleting the data folder. To start empty on purpose, remove the package, delete the data folder, then install: the first start creates a new data folder, as Where the data folder starts describes.
A failing disk is a different matter: the data folder is on the NAS's own disks. Keep backups on another machine (Backups); every backup holds your project folders, HOME folders, App Builder settings and the package repository.
The bvs command
bvs is the package's command line, in /usr/bin/bucketvs/bin. It is on the PATH of every process bvsd starts, cron jobs included. While the package runs, /usr/bin/bvs links to it, so an SSH session on the NAS calls it by name too; stopping the package takes the link away.
Every command that talks to bvsd over its CLI socket needs root, because the socket is root's only: user, jobs, and, while bvsd runs, tc install, tc remove and env create, clone, export and delete. env shell and env run and qbuild need root too: they start bvs-chroot. As another user, a command that needs the socket stops with "bvsd's CLI socket … is open to root only: run bvs as root".
bvs env list
bvs env create <name> --from <rootfs-id> [--package <name>]...
bvs env shell <name>
bvs env run <name> -- <command> [<arg>...]
bvs env clone <name> <new-name>
bvs env export <name>
bvs env delete <name>
bvs tc list
bvs tc install <id>
bvs tc remove <id>
bvs use <id>
bvs jobs
bvs user list
bvs user password <name>
bvs user totp-reset <name>
bvs guide [--path] [en|pl|de|fr|it|nl|sv|ja|zh-TW]
bvs qbuild [--env <name>] [<qbuild argument>...]
bvs kernel profile [--json]
bvs kernel sources
bvs kernel prepare [--source <id>] [--env <name>] [--option NAME=VALUE]... [--full]
bvs kernel make [--project <folder> | --dir <tree directory>] [--build-only] [<target>...]
bvs kernel check <file.ko | module>
bvs kernel load <module>
bvs kernel unload <module>
- While bvsd runs, installs and environment jobs go to bvsd over its CLI socket, so the panel shows them, and
bvsfollows them until they end. Without bvsd they run in thebvsprocess itself;--localbefore the command does that on purpose. bvs jobsand theusercommands need bvsd.bvs guideopens this guide with the package'slesson a terminal, and prints the path of its file otherwise or with--path. Without a language it follows the first ofLC_ALL,LC_MESSAGESandLANGthat is set:de_DE.UTF-8opens the German guide,zh_TWandzh_HKthe Traditional Chinese one, and a language the guide is not written in (C,zh_CN) the English one.
Files and folders
| Path | What |
|---|---|
/usr/bin/bucketvs | the package's programs (a link to its install folder) |
/usr/bin/bucketvs/etc/data-root | the path of the data folder |
/usr/bin/bucketvs/var/run/bvsd.sock | the panel's API socket (nginx uses it) |
/usr/bin/bucketvs/var/run/bvsd-cli.sock | the socket of the bvs command, root only |
<data folder>/etc/ | settings, accounts, sessions, tokens, the certificates, the rendered nginx.conf (secrets with mode 600) |
<data folder>/log/ | the logs |
<data folder>/tmp/ | temporary files of every process |
<data folder>/home/<user>/ | the home folder of each account |
<data folder>/jobs/ | operations, cron jobs and their runs, with logs |
<data folder>/toolchains/ | the download cache, installed toolchains and runtimes, exports |
<data folder>/envs/ | environments |
<data folder>/projects/ | workspaces, visible as /projects inside environments |
<data folder>/trash/ | deleted items, kept 7 days |
The modules add folders of their own; their sections name them.