Polski Deutsch Français Italiano Nederlands Svenska 日本語 繁體中文 · Markdown

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

PartWhat it doesWhere it listens
nginxthe only door on the LAN: serves the panel and passes each path to its module9950 (HTTP), 9951 (HTTPS)
bvsdstarts and watches every other process, keeps the accounts and settings, runs jobs and cron jobs, answers the paneltwo unix sockets in /usr/bin/bucketvs/var/run/
Git (Forgejo)Git hosting with a web interfacea 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 history127.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 accounta unix socket per account in /usr/bin/bucketvs/var/run/ide/
CIthe Forgejo Actions runnerno 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 package starts with no account; Set the administrator password 60 minutes, local network only; administrator signed in; Restart the package in App Center: a new 60-minute window

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.

Accounts

The panel has its own accounts. QTS accounts do not sign in to it.

Roles

RoleWhat it may do
viewerreads 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
memberreads 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
developeralso 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
admineverything: 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.

Passwords and sessions

Wrong passwords

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.

API tokens

Scripts and CI use API tokens instead of a password. Under Your account, "New token" makes one:

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

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:

VariableValue
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_BUNDLEthe package's CA bundle, /usr/bin/bucketvs/etc/ssl/cacert.pem
TZthe NAS's time zone, as set in QTS
SHELL, EDITORthe package's bash and nano
LANG, LESSCHARSETC.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:

Check the target again free space: the data + 10 %; Stop the modules the panel stays on; Copy with rsync -aHAX --numeric-ids; Verify the copy files, bytes, owners, modes, times, sha256 of up to 1000 files; Switch the data folder in /usr/bin/bucketvs/etc/data-root; bvsd starts again on the new root and starts the modules; The old copy stays, marked MOVED-TO, until you remove it; The old root stays in use, the modules start again

While the move runs:

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:

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

SettingDefaultWhat it is
HTTPS9951the panel and the modules over TLS
HTTP9950the panel and the modules over plain HTTP
Git over SSH9952Git over SSH
SSH port in clone URLs0the 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 URLsemptythe 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

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:

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 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:

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.

Browser, git, WebDAV client; Plesk nginx bvs.example.com public certificate; nginx on the NAS 192.168.1.20; / panel, /api/ bvsd; /git/, /svn/, /bucket/, /ide/; git over SSH

On the NAS

  1. On the Network page, add the Plesk server's address (as the NAS sees it) to Trusted proxies.
  2. 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:

DirectiveWhat breaks without it
Host $http_hostnginx 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-Forevery client counts as the Plesk server: one person's five wrong passwords lock everyone out for 15 minutes
X-Forwarded-Proto $schemethe session cookie is not marked Secure, and in "Redirect to HTTPS" mode every request is sent to port 9951 of the public name
Upgrade, Connectionthe editor's page opens but the editor and its terminal never connect
client_max_body_size 0, proxy_request_buffering off, the timeoutsthe 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

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.

ModuleAt the root of its hostnameAlso there
GitForgejo/git/ on every other name answers 302 to the Git hostname, with the same path, query and port
Editorcode-serverthe panel's sign-in page and /api/, so a user can sign in on that name
SVN and Buckethttpdrepositories 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:

TypeExampleWhat it serves
Main hostnamecode.example.comthe 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 hostnameqpkg.example.comGET 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:

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:

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.

StateMeaning
runningevery process of the module runs
startinga process started less than a second ago
degradeda process waits to be restarted, the module's files could not be written, or its health check fails
restarting in a loopa process exited 5 times within 10 minutes; bvsd starts it again every 15 minutes, and one notice went out
stoppedthe module's processes are not running (a data move stopped them, or they failed to start)
offthe 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

Clone over HTTPS and SSH

On the Git page, Clone a repository writes both commands for an owner and a repository:

WayAddressSign-in
HTTPS (or HTTP)https://<NAS address>:9951/git/<owner>/<repo>.gitthe panel's username and password, or an access token
SSHssh://git@<NAS address>:9952/<owner>/<repo>.gitan SSH key added in Forgejo's Settings, SSH / GPG Keys

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:

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.

  1. 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 builds public/, a link check as make 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.
  2. 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.
  3. 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.
  4. 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 qbuild and 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.
  5. 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.
  6. Review lists every answer and the steps that will run. Create project sends them.
  7. 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:

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.

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:

VersionWhat switching does
A .qpkg in build/, or an App Builder exporta test deploy of that file (admin)
The App Builder's buildactivates it (admin); the App Builder keeps one build
A version in the package repositorymakes it the listed version (admin)
A git tagchecks 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

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.

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

WebDAV clients and their limits

ClientHow to connectNotes
macOS FinderGo, Connect to Server, https://<NAS address>:9951/bucket/<name>Finder locks a file while it writes it; the bucket answers WebDAV locks
rclonea webdav remote with vendor = other and the bucket's addresssuits 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, WinSCPa WebDAV (HTTPS) connection to port 9951, path /bucket/<name>
curlcurl -T file -u <user> https://<NAS address>:9951/bucket/<name>/fileone upload, one revision (or two, see above)
Windows ExplorerMap network driveWindows' 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

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:

AddressWhat 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.

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

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

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:

ToolInstalled withCommand
Claude CodeAnthropic's installer (claude.ai/install.sh), npm if that failsclaude
Gemini CLInpm install -g @google/gemini-cligemini
Codex CLInpm install -g @openai/codexcodex

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.

Toolchains and environments

The Toolchains page downloads what is needed to build software on the NAS:

TypeWhat it isExamples
Root filesystema Linux system to unpack into an environmentDebian 12 and 13, Ubuntu 22.04 and 24.04, Alpine 3.22
Cross toolchaina compiler that builds for another processorBootlin toolchains for aarch64, x86-64 and armv7, with glibc or musl
Runtimea language or build toolPython, Go, Node.js, Zig, Rust, CMake, Ninja, Java (JDK)
QNAPQNAP's own toolsQDK 2.5.0, for bvs qbuild and Export as QPKG
Kernel sourcea Linux kernel tree for the kernel workshopLinux 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:

  1. The archive is downloaded into toolchains/cache/ in the data folder. An interrupted download resumes when the install is started again.
  2. Its sha256 is checked against the catalog. A mismatch deletes the file.
  3. 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.

Inside an environment:

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): …:

CaseThe folder holdsWhat happens
1, a certificate as filescertificate 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 tokencertificate_hsmnot supported and not tested: bvs qbuild stops before anything is built
3, no certificatenone of those filesthe package is built unsigned

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).

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:

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.

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:

  1. 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.
  2. 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> | sha256sum in the editor's terminal prints the sum), and its build steps into build.sh. A recipe folder holds recipe.json, build.sh and the vendor file recipe.json names, and nothing else, so a patch goes into build.sh as a here-document.
  3. Check it. Every save of a recipe's file checks the project's recipes and lists the findings in the Problems view; make lint in the terminal and the Recipes card of the Project view give the same findings. The check reads recipe.json and parses build.sh with bash -n. It never runs the recipe.
  4. 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.
  5. Pack and install. make qpkg in 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 in qpkg/ beside qpkg.cfg, or in a folder named by QPKG_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:

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:

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.

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 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:

  1. 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. --source picks another.
  2. 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=m adds a setting, for example a driver.
  3. It prepares the tree for module builds (--full builds the whole kernel instead, which takes much longer and lets the check see the kernel's exported symbols).
  4. 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

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.

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:

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

JobWhenWhat it does
Maintenance03:30 dailyremoves what nothing changed for 10 days from tmp/ in the data folder; rotates log/audit.log over 10 MB
Empty the trash03:45 dailyremoves what was moved to trash/ in the data folder (such as a deleted account's home folder) more than 7 days ago
Toolchain catalog refresh04:15 dailyfetches the signed toolchain catalog
Rotate nginx's logsevery hour, at minute 7renames 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:

PartWhat
settingsetc/: settings, accounts, TOTP secrets, the TLS key, the backup keys
GitForgejo's dump: repositories, wikis, LFS objects, attachments and its database
SVN, bucketsa copy of every repository, made incrementally: a revision already copied is not copied again
VS Code settingseach account's VS Code settings, key bindings, snippets and the list of its extensions (not the extensions' files: the Extensions view installs them again)
environmentsthe Toolchains environments (off by default: they can be made again)
projectsevery project folder with its Git history and the files not committed yet, without node_modules
HOME folderseach account's HOME folder, without caches and the AI tools' own installs (their Install button puts them back)
App Buildereach app's settings and the history of its builds; a build makes the app's files again
package repositorythe 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:

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:

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).

NoticeWhenSwitch for the URLs
job failurea cron run of a job with a notice on failure failed or timed out; an operation failednotify.on_job_failure
backupthe result of every backup, verify and restore, the good ones too; a scheduled backup that could not startnotify.on_backup
restart loopa process started restarting in a loopnotify.on_crashloop
certificatean uploaded certificate has 30 days or less; nginx fell back to the self-signed certificate; a new self-signed certificate was madenotify.on_cert_expiry
systemsettings refused at start, an unfinished data move found, leftover processes ended, the configuration could not be writtenalways sent

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:

LogWhat writes it
bvsdbvsd itself: starts, stops, applied settings, problems
nginxnginx's own output
nginx-access, nginx-errorevery request, and nginx's errors
auditevery request on the CLI socket
one per module processeach 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:

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.

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>

Files and folders

PathWhat
/usr/bin/bucketvsthe package's programs (a link to its install folder)
/usr/bin/bucketvs/etc/data-rootthe path of the data folder
/usr/bin/bucketvs/var/run/bvsd.sockthe panel's API socket (nginx uses it)
/usr/bin/bucketvs/var/run/bvsd-cli.sockthe 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.