Loading and source roots
How a script finds and loads the engine, what is prefetched, and which variables point it at a fork, a branch or a local checkout.
A script from ProxmoxVE, DevScripts or Incus contains no engine code. Its
first lines load
core/build.func,
and that file finds and loads everything else. This page follows that path.
Two roots
The engine and the scripts live in different repositories, so the loader keeps two separate locations:
| Root | Holds | Local directory | Remote base | Default |
|---|---|---|---|---|
| Engine | core/, lxc/, host/, api/, lib/, ui/, vm/, pve/, incus/, headers/ | COMMUNITY_SCRIPTS_CORE_DIR | COMMUNITY_SCRIPTS_CORE_URL | https://raw.githubusercontent.com/community-scripts/core/main |
| Scripts | ct/, install/ and everything else | COMMUNITY_SCRIPTS_ROOT | COMMUNITY_SCRIPTS_URL | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main |
Each root is resolved on its own. You can run released scripts against an engine branch, or a script fork against the released engine.
Paths are always written with their folder, such as lib/tools.func. The
folder decides the root: a path that starts with one of the nine engine
folders is an engine file, any other path is a script file. Several engine
folders contain files with the same name (core/build.func and
incus/build.func), which is why nothing is looked up by file name alone.
The loader lines in a script
Every ct script starts like
ct/vaultwarden.sh:
#!/usr/bin/env bash
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")- If
COMMUNITY_SCRIPTS_CORE_DIRis set,build.funcis read from that directory. - Otherwise the script looks for a directory named
corenext to its own repository checkout (ct/../../core). - If neither file exists,
build.funcis downloaded fromCOMMUNITY_SCRIPTS_CORE_URL, or from the default engine URL.
Scripts in DevScripts and Incus have one more line before these, which makes their own repository the default for the scripts root:
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/DevScripts/main"What build.func does
In order:
- Engine directory. If
COMMUNITY_SCRIPTS_CORE_DIRis not set andbuild.funcwas read from a file inside acorefolder that has apvefolder next to it, the parent directory becomes the engine directory. The older nameCOMMUNITY_SCRIPTS_DIRis still accepted. - Scripts directory. If
COMMUNITY_SCRIPTS_ROOTis not set and the script was started from a file, the loader walks up from that file to the first directory that contains bothct/andinstall/. - URLs from Git. Only when
gitis installed and the engine directory is a local checkout (it must containhost/source-origin.func): for each root that is a Git checkout and has no URL set, the URL is derived from theoriginremote and the current branch, ashttps://raw.githubusercontent.com/<owner>/<repo>/<branch>. A detached checkout uses the short commit hash. Only GitHub remotes are recognised. - Defaults. URLs that are still empty get the defaults from the table above. Both URLs are exported.
- Host probes. On a Proxmox VE host, as root,
pveversion,pvesm status,pveam availableandpvesh get /cluster/nextidare started in the background so their answers are ready when the checks and dialogs need them. - Prefetch, see below.
- Platform.
lxc/platform.funcis loaded anddetect_lxc_platformdecides where the script is running. - Backend. On Incus (host or container)
incus/build.funcis loaded. Everywhere elseui/build-ui.funcandpve/backend.funcare loaded.
ui/build-ui.func in turn loads api/api.func, core/core.func,
core/error_handler.func and core/dev-mode.func, calls load_functions and
catch_errors, and then loads host/validate.func, ui/validate.func,
ui/defaults.func, ui/advanced.func and ui/menu.func. lib/tools.func is
loaded later, by start.
build.func runs once per shell (guard _LXC_BUILD_DISPATCHED). It also
unsets SUDO_USER, SUDO_UID, SUDO_GID, SUDO_HOME and SUDO_COMMAND.
How one file is resolved
Engine code loads other engine files through two functions that build.func
defines:
| Function | Result |
|---|---|
_cs_source_func "<folder>/<file>" | sources the file into the current shell |
_cs_fetch_text "<folder>/<file>" | prints the file's content |
Both use the same rule:
- If the root's local directory is set and the file exists there, use it.
- Otherwise download
<remote base>/<path>.
Downloads use curl -fsSL --retry 3 --retry-delay 1 --retry-connrefused --connect-timeout 10,
or wget -qO- when curl is missing.
The Proxmox VE backend uses _cs_fetch_text for the two files that go into
the container:
export FUNCTIONS_FILE_PATH="$(_cs_fetch_text "lxc/install.func")"
_install_script="$(_cs_fetch_text "install/${var_install}.sh")"The first is an engine path and comes from the engine root; the second comes from the scripts root.
Use _cs_source_func in engine code instead of a raw URL. It is the only
thing that honours a local checkout, a fork and the prefetched copy at the
same time. Application scripts do not call it; they get their functions from
the loader.
Prefetch
Without a local engine directory every engine file would be a separate HTTPS
request. _cs_prefetch_engine downloads all files listed in _CS_ENGINE_FILES
with one parallel curl call into a temporary directory and then sets
COMMUNITY_SCRIPTS_CORE_DIR to it. From then on the resolution rule above
finds every file locally.
| Detail | Value |
|---|---|
| Skipped when | COMMUNITY_SCRIPTS_CORE_DIR is already set, COMMUNITY_SCRIPTS_NO_PREFETCH=1, curl is missing, or curl is older than 7.66 |
| Directory | cs-engine.XXXXXX in the first writable of /dev/shm, $TMPDIR, /tmp |
| Request | curl --parallel --parallel-max 16 --max-time 60, with the retry options above |
| A file that failed | is deleted, and fetched on its own when it is needed |
| Nothing downloaded | the directory is removed and every file is fetched on demand |
The prefetched directory is for one run. It is not a cache: the next run
downloads again, so there is nothing that can go stale. Each run also deletes
cs-engine.*, cs-pveam.*, cs-pveversion.* and cs-probe.* entries older
than 60 minutes from the same temporary locations.
Skipping the prefetch only makes loading slower:
COMMUNITY_SCRIPTS_NO_PREFETCH=1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)"What is cached
| Data | Where | Lifetime |
|---|---|---|
| Engine files | cs-engine.XXXXXX | this run |
pveversion and pveam available output | cs-pveversion.<pid>, cs-pveam.<pid> | this run |
| Host probe results | cs-probe.XXXXXX | this run |
| Header banners | /usr/local/community-scripts/headers/ | until deleted |
Banners are the only thing kept between runs. Delete that folder to fetch them again. See Headers.
Platform detection
detect_lxc_platform in
lxc/platform.func
returns the first match:
| Order | Test | Result |
|---|---|---|
| 1 | LXC_PLATFORM is set | its value |
| 2 | /dev/incus/sock is a socket, or /etc/profile.d/incus-motd.sh exists | incus-container |
| 3 | pveversion exists | pve |
| 4 | an incus client is found and one of: incus info works, /var/lib/incus exists, /var/run/incus/unix.socket exists | incus |
| 5 | none of the above | container |
The Incus host test does not require a reachable daemon; the files on disk are
enough. That is why the container markers are tested first. The incus client
is also searched in /usr/bin, /usr/local/bin, /snap/bin and
/opt/incus/bin, and its directory is added to PATH when it was not there.
container means the script runs its update path. If detection is wrong,
override it:
LXC_PLATFORM=pve bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)"Accepted values are pve, incus, incus-container and container.
Inside a container, build.func also fills var_os and var_version from
/etc/os-release when they are empty, so a script that supports two operating
systems takes the right branch during an update.
Inside the container
The host's engine directory is not visible in a new container, so the container loads the engine again:
-
The install script's first line sources
lxc/install.funcfrom the environment variable the host exported:source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" -
Its bootstrap installs
curlif the template has none, then prefetches the files in_CS_CT_ENGINE_FILES(core/core.func,core/error_handler.func,api/exitcodes.func,api/errorlog.func,lib/tools.func,lib/alpine.funcand the fivelib/parts) the same way the host does. -
It sources
core/core.func,api/exitcodes.func,api/errorlog.funcandcore/error_handler.func, andcore/dev-mode.funcwhendev_modeis set. After each file it checks that an expected function exists. -
update_oslater sourceslib/alpine.funcon Alpine andlib/tools.funceverywhere else.
A file that cannot be downloaded, or that arrives incomplete, ends the install with exit code 115 and a message naming the file, the URL and the HTTP status.
The container uses the COMMUNITY_SCRIPTS_URL and COMMUNITY_SCRIPTS_CORE_URL
values the host exported, so a fork or branch chosen on the host also applies
inside the container.
Other entry points
Not every script goes through core/build.func:
| Script type | What it sources |
|---|---|
tools/addon/*.sh | core/core.func, lib/tools.func, core/error_handler.func and api/api.func directly from COMMUNITY_SCRIPTS_CORE_URL |
vm/*.sh | pve/vm-core.func from COMMUNITY_SCRIPTS_CORE_URL; on an Incus host that file hands over to incus/vm-core.func |
These honour COMMUNITY_SCRIPTS_CORE_URL, and the loaders inside the engine
files (api/api.func, lib/tools.func) also use COMMUNITY_SCRIPTS_CORE_DIR
when it is set.
Variables
| Variable | Effect |
|---|---|
COMMUNITY_SCRIPTS_CORE_DIR | Local engine directory. Disables the prefetch |
COMMUNITY_SCRIPTS_DIR | Older name for the same; used when the one above is not set |
COMMUNITY_SCRIPTS_CORE_URL | Raw base URL of the engine, without a trailing path |
COMMUNITY_SCRIPTS_ROOT | Local scripts directory (the one that holds ct/ and install/) |
COMMUNITY_SCRIPTS_URL | Raw base URL of the scripts repository |
COMMUNITY_SCRIPTS_NO_PREFETCH | 1 skips the prefetch on the host and in the container |
LXC_PLATFORM | Forces the detected platform |
_CS_DEFAULT_URL | Default scripts URL, set by scripts outside ProxmoxVE on their second line |
A set variable always wins over detection.
Running a fork or a branch
A script started with curl cannot tell the engine where it came from. If you
run a ct script from a fork without saying so, the engine looks for
install/<app>-install.sh in the default repository and stops with exit 115
and Could not fetch install/<app>-install.sh. Set the scripts URL:
COMMUNITY_SCRIPTS_URL=https://raw.githubusercontent.com/YOU/ProxmoxVE/your-branch \
bash -c "$(curl -fsSL https://raw.githubusercontent.com/YOU/ProxmoxVE/your-branch/ct/debian.sh)"An engine branch with the released scripts:
COMMUNITY_SCRIPTS_CORE_URL=https://raw.githubusercontent.com/YOU/core/your-branch \
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)"The engine's runner sets both and starts the script. The third argument is optional and defaults to the released engine:
curl -fsSL https://raw.githubusercontent.com/community-scripts/core/main/tools/run.sh |
bash -s -- https://raw.githubusercontent.com/YOU/ProxmoxVE/your-branch \
ct/debian.sh \
https://raw.githubusercontent.com/YOU/core/your-branchWhen either root is not the default, the settings summary before the build
says so in a line such as Engine branch: community-scripts/core@your-branch,
Engine fork: YOU/core@your-branch or Scripts fork: YOU/ProxmoxVE@your-branch.
Local checkouts
Clone the scripts repository and core into the same parent directory and run
the script from its checkout:
git clone https://github.com/community-scripts/core.git
git clone https://github.com/community-scripts/ProxmoxVE.git
bash ProxmoxVE/ct/debian.shThe loader finds core next to ProxmoxVE, reads every engine file from it,
and reads install/debian-install.sh from the ProxmoxVE checkout. The
container cannot see either directory, so the loader derives both URLs from
the checkouts' origin remotes and current branches. Push your branch before
you test: the container downloads the engine files it needs (core/core.func,
lib/tools.func and the others listed above) from the engine URL, and the
generated update command records both URLs.
A checkout somewhere else works with the variable:
COMMUNITY_SCRIPTS_CORE_DIR=/root/src/core bash ProxmoxVE/ct/debian.shKeeping the lists complete
_CS_ENGINE_FILES and _CS_CT_ENGINE_FILES are checked against the
repository by the engine's verify-split-files.yml workflow. A file missing
from a list still loads, one request later. When you add an engine file, add
it to the list in the same change.