Headers
Where the ASCII banner at the top of every script comes from, how it is generated, looked up and cached.
The banner a script prints first is not part of the script. It is a small text
file in the engine repository, generated from the script's APP= line and
fetched by header_info.
______ __ _ ____
/ ____/___ / /_(_) __/_ __
/ / __/ __ \/ __/ / /_/ / / /
/ /_/ / /_/ / /_/ / __/ /_/ /
\____/\____/\__/_/_/ \__, /
/____/Where the files are
headers/ in the
core repository has one folder per script type. The file name is the slug: APP
in lower case with spaces removed, without an extension.
| Folder | Script type | Example |
|---|---|---|
headers/ct/ | container scripts | APP="Uptime Kuma" → headers/ct/uptimekuma |
headers/vm/ | virtual machine scripts | headers/vm/homeassistantos |
headers/addon/ | add-on scripts | headers/addon/portainer |
headers/tools/ | host tools | headers/tools/pve/pve-privilege-converter |
The type comes from the script's APP_TYPE variable. Container scripts do not set
it and get ct. A script that sits under a pve/ or incus/ path in its
repository is platform-specific, and its header is stored one level deeper, for
example headers/ct/pve/oci-container or headers/tools/pve/docker2lxc.
How they are generated
Nobody writes these files by hand. The workflow
generate-headers.yml
in the core repository runs every six hours, on manual dispatch, and when a script
repository sends a scripts-changed event. It
- checks out ProxmoxVE, DevScripts and Incus,
- runs
generate-headers.py, which reads theAPP="..."line of everyct/*.sh,vm/*.shandtools/**/*.shand renders it withfiglet -w 500 -f slant, - rewrites the whole
headers/tree, so banners of removed scripts disappear, - opens a pull request named
Generate app headersif anything changed.
When the same app exists in more than one repository, the first repository in
that order wins. A script whose APP is built from a variable is skipped, because
there is nothing fixed to render.
For contributors this means: set APP correctly and do nothing else. To change a
banner, change APP. Do not add header files to a script repository.
How a script finds its header
header_info clears the screen, calls get_header, and prints the result.
get_header tries two paths in order, the general one first:
headers/<type>/<slug>headers/<type>/<platform>/<slug>, where the platform ispveorincus
For each path it looks in three places:
| Order | Place | Used when |
|---|---|---|
| 1 | $COMMUNITY_SCRIPTS_CORE_DIR/headers/... | the engine runs from a local checkout, so a fork or branch shows its own banners |
| 2 | the cache, /usr/local/community-scripts/headers/... | the banner was downloaded before |
| 3 | $COMMUNITY_SCRIPTS_CORE_URL/headers/... | first run; the file is downloaded and stored in the cache |
The cache directory follows the engine's state directory, so an Incus operator
without root gets ~/.config/community-scripts/headers/. The download has a
15-second limit and is not retried, because a banner is decoration. An empty
download is deleted again rather than cached.
Inside a container the same lookup happens on every update, so containers have
their own cached copy.
When there is no header
A new script has no banner until the generator has run and its pull request is
merged. That is not an error. header_info then prints a generic
"Community Scripts" banner followed by the app name on its own line.
The cache has no expiry. If a banner changed upstream while the slug stayed the same, delete the cached file and it is downloaded again on the next run:
rm /usr/local/community-scripts/headers/ct/gotify