Unofficial
On this page
  1. 01Before you start
  2. 02Create the project
  3. 03Sign every build
  4. 04Every push publishes
  5. 05Add the repository on your other NAS units
  6. 06Over the internet with Cloudflare Tunnel
  7. 07What the internet reaches

Your own App Center: ship your QPKG packages from Git to every NAS

Bucket & Code Server IDE keeps an App Center repository on your NAS. A QPKG project made with New project builds at every Git push and publishes the signed package into it. Your other NAS units add the repository's address in App Center once, then install and update your packages like any other app. Cloudflare Tunnel can publish the repository, and optionally Git, without opening a router port.

  1. Preparemodules, QDK, a certificate
  2. ProjectNew project, QPKG package
  3. Signtwo secrets in Forgejo
  4. Repositoryits address in App Center
  5. Interneta packages hostname

The pictures come from the panel with each field filled in as this guide describes. The addresses are those of a NAS at 192.168.1.20, and the published names are examples under example.com.

01Before you start

  • Git, CI and the Editor must be on: an admin turns each on with its button on the Overview (Turn on Git, Turn on Git and CI, Turn on Editor). The New project wizard needs Git and the Editor, and CI runs the project's workflow at every push.
  • QDK must be installed: on the Toolchains page, QNAP tab, install QNAP QDK 2.5.0.
  • You need a code-signing certificate issued to you by QNAP, as files: certificate, private_key and, for the chain, ca_certs. App Center checks a package's code signature when it installs it, and the repository on the Packages page takes code-signed packages only. A certificate on a hardware token is not supported.
The Code signing card on the Packages page: 1, a certificate as files, supported; 2, a certificate on a hardware token, not supported; 3, no certificate, unsigned

02Create the project

Open New project from the Git page or the command palette (developer role).

  1. Template. Under Packages for QTS choose QPKG package and press Next. Packages for QTS: QPKG package chosen, beside App Builder app
  2. Name and visibility. The name is the repository's, the folder's and the package's: 1 to 20 letters, digits, dashes or underscores. The owner is your account or an organization; leave the project Private. Name nas-notes, Owner ola, Visibility Private
  3. Service. Choose how the package starts and stops: Command-line tool links the program into /usr/bin, Background service runs it as an account of its own, Web app also gives it App Center's Open button, and Empty has only the bare service verbs (start, stop and so on). You can change these blocks later in Project view. Start from: Command-line tool chosen; Background service, Web app and Empty
  4. Build. Pick the environment the project builds in and leave Build right after creating on. Build environment: ubuntu2404-builder, ubuntu-24.04-x86_64, glibc 2.39, and the note that the build runs in this environment through bvs-chroot Build right after creating, turned on
  5. Publish. Turn on Publish to this NAS’s App Center repository. The same switch then stays in the project's settings and lets CI publish at every run. Publish: Test deploy on this NAS off, signed packages only; Publish to this NAS's App Center repository on; Publish to my web server off
  6. Review. Check the steps that will run and press Create project. What will run: Forgejo repository and first commit, First build, Publish to the App Center repository; Test deploy and Publish to my web server not chosen; the Create project button

The first job makes the repository in Forgejo and the folder projects/<name> with the first commit: the template's files and the workflow .forgejo/workflows/build.yml. The first build has no certificate yet, so its publish step stops and says the package carries no code signature. The switch stays on, and once step 03 is done every push publishes.

03Sign every build

On the NAS, put the certificate's files next to qpkg.cfg in projects/<name>. The template's .gitignore keeps them out of git. A build in that folder then signs the package, and the first line of bvs qbuild says case 1. When the environment lacks openssl or bsdextrautils, it stops and names the command that adds them.

CI checks the repository out without those files, so it gets them from two secrets. In Forgejo open the repository's Settings → Actions → Secrets and press Add secret twice: QPKG_CERTIFICATE with the output of base64 -w0 certificate, and QPKG_PRIVATE_KEY with that of base64 -w0 private_key. Forgejo itself asks for Base64 when a value's line breaks must survive. A certificate with a chain needs a third secret, QPKG_CA_CERTS, and a third line in the first step that writes it to ca_certs.

Forgejo's Add secret dialog: Name QPKG_CERTIFICATE, Value a Base64 line, the Cancel and Confirm buttons

Then add two steps to .forgejo/workflows/build.yml: one that writes the files before the build, right after the checkout, and one at the end of the list that removes them whatever the result. The template's own steps stay as they are.

# .forgejo/workflows/build.yml: the two new steps, in the template's steps list
    steps:
      - uses: actions/checkout@v4
      - name: certificate from the secrets
        run: |
          printf '%s' "$CERT" | base64 -d > certificate
          printf '%s' "$KEY" | base64 -d > private_key
          chmod 600 private_key
        env:
          CERT: ${{ secrets.QPKG_CERTIFICATE }}
          KEY: ${{ secrets.QPKG_PRIVATE_KEY }}
      # the template's build and publish step follows here, unchanged
      - name: remove the certificate
        if: always()
        run: rm -f certificate private_key ca_certs

04Every push publishes

Each push to the repository runs the workflow on the NAS's own runner. It builds the package and publishes it with --latest, so repo.xml lists the new version at once and App Center offers it at its next check. An unsigned build is not published: the run ends with a warning that names the reason.

The project's page (Projects, or the Editor's side bar) runs the same steps by hand: Build, Rebuild, Test deploy (an admin's, signed packages only) and Publish.

The Pipeline of a QPKG project: Build in ubuntu2404-builder with Build and Rebuild; Test deploy needs a code-signed package; Publish says the build is unsigned and the repository accepts signed packages only, and that CI publishes to the App Center repository too; Publish to web cannot run here

Its Settings card holds the switch Publish to the App Center repository. While it is off, a CI run skips the publish with a notice.

Publish to the App Center repository, turned on: the project's CI adds its packages to this NAS's App Center repository

The Packages page lists every package with its latest version and signature. A version a developer uploads there by hand waits until an admin makes it the latest.

Published packages: name, latest version, architectures, signed, versions and when each was published

05Add the repository on your other NAS units

  1. Copy the address from the App Center address card on the Packages page. App Center address: http://192.168.1.20:9950/repo/repo.xml
  2. On the other NAS: App Center → ⚙ Settings → App Repository → Add. Give it a name, paste the address and press Add.
  3. Your packages are under that name in App Center's left rail. Install them there. When the repository lists a newer version, App Center offers it as an update.

This LAN address works for NAS units in your network. For a NAS outside it, publish the repository on the internet.

06Over the internet with Cloudflare Tunnel

The package ships cloudflared, which dials out from the NAS to Cloudflare: no router port is opened, and Cloudflare holds the certificates for the names. You need a domain whose DNS is at Cloudflare and an API token for that zone (dash.cloudflare.com, My Profile → API Tokens) with four permissions: Account, Cloudflare Tunnel, Edit; Zone, DNS, Edit; Zone, Cache Rules, Edit; Zone, Cache Purge, Purge. If the token has Client IP Address Filtering, put both public addresses of the NAS in it, IPv4 and IPv6.

NameWhat it serves
Packages hostname
qpkg.example.com
only GET and HEAD of /repo/, which is all App Center on your other NAS units needs
Main hostname
code.example.com
the panel, Forgejo at /git/ for clone and push over HTTPS, the repository read only, the web view of public repositories and the user guide
  1. For a main hostname, first turn on Public browsing on the Git page and press Save and restart Forgejo: the Publish page refuses a main hostname while it is off. Repositories marked private stay hidden. The Public browsing switch on the Git page, turned on
  2. Open Publish under Administration (admins only). Type the names and the token, keep Cache presets on and press Publish. The package checks everything at Cloudflare first, changes nothing there when a check fails, and says what to fix. Hostnames and token: Main hostname code.example.com, Packages hostname qpkg.example.com, the token as dots, Tunnel name empty, Cache presets on, the four permissions the token needs, and the Publish button
  3. Then it makes the tunnel, points each name at it and starts cloudflared. The Tunnel card shows the connections. The Tunnel card: 4 connections to Cloudflare, cloudflared running, the tunnel bucketvs-nas-lab, and the published names code.example.com and qpkg.example.com
  4. The App Center address on the Packages page now names the packages hostname. Add that address on NAS units outside your network. App Center address: https://qpkg.example.com/repo/repo.xml
  5. With a main hostname, set Public URL on the Network page to its address, so that Forgejo's links and HTTPS clone addresses use it. Public URL set to https://code.example.com on the Network page
  • Git over SSH does not go through the tunnel. From outside, clone and push over HTTPS, or forward a router port to the Git over SSH port (9952) and set SSH host in clone URLs on the Network page.
  • Cloudflare takes requests up to 100 MB. Push or upload anything larger over the LAN address.
  • A publish into the repository clears repo.xml from Cloudflare's cache at once, so App Center sees the new version at its next check.

07What the internet reaches

The Publish page lists what each name serves and the cache rules beside it.

What the internet reaches: the main hostname serves the panel, Forgejo, the web view and the package repository, the editor only while its switch is on, never WebDAV; the packages hostname serves GET and HEAD of /repo/ only. Cache presets: packages a year, icons a day, repo.xml 5 minutes and purged at every publish, the API, Forgejo and the sign-in never cached
  • The main hostname answers /ide/ with 404 until an admin turns on Publish the editor. The editor's terminal is a root shell on the NAS, so turn on two-factor authentication for developer and admin accounts first. The editor on the internet: the switch Publish the editor, off, with its warning about the root shell and two-factor authentication
  • While CI is on, a workflow pushed to any repository runs as root on the NAS, whether pushed from the internet or the LAN. Give push access only to people you would trust with the NAS's admin password.
  • The first administrator password is never set through a published name: the first-run screen accepts only a client on the local network.
  • Stop ends cloudflared and changes nothing at Cloudflare. Remove from Cloudflare deletes the DNS records, the cache rules and the tunnel the package made, and forgets the token.