Repository navigation
Expand file tree
/
Copy pathbuild.func
More file actions
340 lines (306 loc) · 13.9 KB
/
Copy pathbuild.func
File metadata and controls
340 lines (306 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: tteck (tteckster) | MickLesk | michelroegl-brunner
# License: MIT | https://raw.githubusercontent.com/community-scripts/core/main/LICENSE
# ==============================================================================
# BUILD.FUNC — TWO-ROOT ENTRY POINT
# ==============================================================================
# The engine (this repo, community-scripts/core) and the scripts (ProxmoxVE,
# DevScripts, Incus) live in SEPARATE repositories, so a single origin can no
# longer address both. Everything below distinguishes two roots:
#
# ENGINE — shared/ pve/ incus/ → core
# SCRIPTS — ct/ install/ vm/ → whichever script repo invoked us
#
# Each root resolves independently, so a fork/branch of core can be tested
# against production scripts and vice versa:
#
# COMMUNITY_SCRIPTS_CORE_URL / COMMUNITY_SCRIPTS_CORE_DIR → engine
# COMMUNITY_SCRIPTS_URL / COMMUNITY_SCRIPTS_ROOT → scripts
#
# Paths are folder-qualified: "core/core.func", "pve/backend.func",
# "incus/build.func" for the engine, "ct/…" and "install/…" for the scripts.
# ==============================================================================
[[ -n "${_LXC_BUILD_DISPATCHED:-}" ]] && return
_LXC_BUILD_DISPATCHED=1
_CS_DEFAULT_URL="${_CS_DEFAULT_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}"
_CS_CORE_DEFAULT_URL="${_CS_CORE_DEFAULT_URL:-https://raw.githubusercontent.com/community-scripts/core/main}"
# ── Engine layout ─────────────────────────────────────────────────────────────
# Paths are folder-qualified ("incus/backend.func"), so the folder itself says
# which root a path belongs to and no name→folder map is needed. That is also
# what lets the files drop their platform prefix: incus/build.func and
# core/build.func can coexist because nothing resolves by basename.
_cs_is_engine_path() {
case "${1:-}" in
core/* | lxc/* | host/* | api/* | lib/* | ui/* | vm/* | pve/* | incus/*) return 0 ;;
*) return 1 ;;
esac
}
# ── Engine root ───────────────────────────────────────────────────────────────
# This file lives at <core>/core/build.func, so the engine root is its parent.
if [[ -z "${COMMUNITY_SCRIPTS_CORE_DIR:-}" && -n "${BASH_SOURCE[0]:-}" && -f "${BASH_SOURCE[0]}" ]]; then
_cs_this_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
if [[ "${_cs_this_dir##*/}" == "core" && -d "${_cs_this_dir}/../pve" ]]; then
COMMUNITY_SCRIPTS_CORE_DIR="$(cd "${_cs_this_dir}/.." >/dev/null 2>&1 && pwd)"
fi
unset _cs_this_dir
fi
# Back-compat: callers that still set COMMUNITY_SCRIPTS_DIR meant the engine.
if [[ -z "${COMMUNITY_SCRIPTS_CORE_DIR:-}" && -n "${COMMUNITY_SCRIPTS_DIR:-}" ]]; then
COMMUNITY_SCRIPTS_CORE_DIR="${COMMUNITY_SCRIPTS_DIR}"
fi
[[ -n "${COMMUNITY_SCRIPTS_CORE_DIR:-}" ]] && export COMMUNITY_SCRIPTS_CORE_DIR
# ── Scripts root ──────────────────────────────────────────────────────────────
# Derived from the CT script that invoked us, NOT from the engine location.
# Conflating the two is what silently broke local install/ lookups before.
if [[ -z "${COMMUNITY_SCRIPTS_ROOT:-}" ]]; then
_cs_caller="${_CS_ENTRY_SCRIPT:-${BASH_SOURCE[-1]:-}}"
if [[ -n "$_cs_caller" && -f "$_cs_caller" ]]; then
_cs_dir="$(cd "$(dirname "$_cs_caller")" >/dev/null 2>&1 && pwd)"
# Walk up to the first directory holding both ct/ and install/. Checking a
# single level would miss anything nested, e.g. ct/deferred/foo.sh.
while [[ -n "$_cs_dir" && "$_cs_dir" != "/" ]]; do
if [[ -d "${_cs_dir}/ct" && -d "${_cs_dir}/install" ]]; then
COMMUNITY_SCRIPTS_ROOT="$_cs_dir"
break
fi
_cs_dir="$(cd "${_cs_dir}/.." >/dev/null 2>&1 && pwd)"
done
fi
unset _cs_caller _cs_dir
fi
[[ -n "${COMMUNITY_SCRIPTS_ROOT:-}" ]] && export COMMUNITY_SCRIPTS_ROOT
# ── Origin detection (per root, independently) ────────────────────────────────
# A fork/branch of core and a fork/branch of the scripts resolve separately, so
# either can be tested against production of the other.
_cs_load_origin_helper() {
local base="${COMMUNITY_SCRIPTS_CORE_DIR:-}"
[[ -z "$base" ]] && return 1
local helper="${base}/host/source-origin.func"
[[ -f "$helper" ]] || return 1
# shellcheck disable=SC1090
source "$helper"
}
if command -v git >/dev/null 2>&1 && _cs_load_origin_helper 2>/dev/null; then
if [[ -z "${COMMUNITY_SCRIPTS_CORE_URL:-}" && -n "${COMMUNITY_SCRIPTS_CORE_DIR:-}" && -d "${COMMUNITY_SCRIPTS_CORE_DIR}/.git" ]]; then
_cs_url="$(cs_detect_origin_from_git "$COMMUNITY_SCRIPTS_CORE_DIR" 2>/dev/null || true)"
[[ -n "${_cs_url:-}" ]] && COMMUNITY_SCRIPTS_CORE_URL="$_cs_url"
unset _cs_url
fi
if [[ -z "${COMMUNITY_SCRIPTS_URL:-}" && -n "${COMMUNITY_SCRIPTS_ROOT:-}" && -d "${COMMUNITY_SCRIPTS_ROOT}/.git" ]]; then
_cs_url="$(cs_detect_origin_from_git "$COMMUNITY_SCRIPTS_ROOT" 2>/dev/null || true)"
[[ -n "${_cs_url:-}" ]] && COMMUNITY_SCRIPTS_URL="$_cs_url"
unset _cs_url
fi
fi
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-$_CS_DEFAULT_URL}"
COMMUNITY_SCRIPTS_CORE_URL="${COMMUNITY_SCRIPTS_CORE_URL:-$_CS_CORE_DEFAULT_URL}"
export COMMUNITY_SCRIPTS_URL COMMUNITY_SCRIPTS_CORE_URL
# ── Resolution ────────────────────────────────────────────────────────────────
# Local file for a virtual path, or non-zero when it must be fetched remotely.
# Every branch is -f guarded: an unguarded assignment used to shadow the
# fallbacks and send existing files to the network.
_cs_local_path() {
local rel="${1:?relative path}"
local candidate
if _cs_is_engine_path "$rel"; then
[[ -z "${COMMUNITY_SCRIPTS_CORE_DIR:-}" ]] && return 1
candidate="${COMMUNITY_SCRIPTS_CORE_DIR}/${rel}"
if [[ -f "$candidate" ]]; then
echo "$candidate"
return 0
fi
return 1
fi
[[ -z "${COMMUNITY_SCRIPTS_ROOT:-}" ]] && return 1
candidate="${COMMUNITY_SCRIPTS_ROOT}/${rel}"
if [[ -f "$candidate" ]]; then
echo "$candidate"
return 0
fi
return 1
}
# Remote URL for a virtual path — engine and scripts use their own base.
_cs_remote_url() {
local rel="${1:?relative path}"
if _cs_is_engine_path "$rel"; then
echo "${COMMUNITY_SCRIPTS_CORE_URL}/${rel}"
else
echo "${COMMUNITY_SCRIPTS_URL}/${rel}"
fi
}
# dev_mode=net records every engine fetch. Read from $dev_mode directly, not
# from DEV_MODE_NET: parse_dev_mode runs much later, and the first fetches --
# the ones worth seeing -- happen before it.
_cs_net_log() {
[[ ",${dev_mode:-}," == *,net,* ]] || return 0
printf ' [net] %-3s %6s ms %s
' "$1" "$2" "$3" >&2
}
# Retry options shared by every engine fetch. Fourteen days of telemetry showed
# roughly 240 failures that were the engine unable to download its own parts,
# each given a single attempt. curl's own --retry rather than a loop, because it
# already knows which failures are worth repeating and leaves a 404 alone.
#
# The wget fallbacks stay single-attempt: --tries/--waitretry are GNU options,
# and that path exists for the BusyBox systems where they are a hard error.
_CS_CURL_RETRY=(--retry 3 --retry-delay 1 --retry-connrefused --connect-timeout 10)
# curl -S prints one line per failed attempt, so a download that recovers on the
# second try would show the user two errors and then quietly work — during a
# spinner, no less. Hold stderr back and release it only if every attempt failed.
_cs_curl_retry() {
local url="${1:?url}" err rc=0
err="$(mktemp 2>/dev/null)" || {
curl -fsSL "${_CS_CURL_RETRY[@]}" "$url"
return
}
curl -fsSL "${_CS_CURL_RETRY[@]}" "$url" 2>"$err" || rc=$?
((rc != 0)) && cat "$err" >&2
rm -f "$err"
return "$rc"
}
_cs_download() {
local url="${1:?url}"
local t0 code
if [[ ",${dev_mode:-}," == *,net,* ]] && command -v curl >/dev/null 2>&1; then
t0=${EPOCHREALTIME/[.,]/}
local body
body="$(curl -fsSL "${_CS_CURL_RETRY[@]}" -w '
%{http_code}' "$url" 2>/dev/null)"
code="${body##*$'
'}"
_cs_net_log "$code" "$((( ${EPOCHREALTIME/[.,]/} - t0 ) / 1000))" "$url"
printf '%s' "${body%$'
'*}"
return 0
fi
if command -v curl >/dev/null 2>&1; then
_cs_curl_retry "$url"
else
wget -qO- "$url"
fi
}
# Fetch text: prefer a local checkout, else download from the matching base.
_cs_fetch_text() {
local rel="${1:?relative path e.g. lxc/install.func}"
local file
if file="$(_cs_local_path "$rel")" && [[ -n "$file" ]]; then
cat "$file"
return 0
fi
_cs_download "$(_cs_remote_url "$rel")"
}
_cs_source_func() {
local rel="${1:?relative path}"
local file
if file="$(_cs_local_path "$rel")" && [[ -n "$file" ]]; then
# shellcheck disable=SC1090
source "$file"
return 0
fi
# shellcheck disable=SC1090
source <(_cs_download "$(_cs_remote_url "$rel")")
}
# ── Prefetch ─────────────────────────────────────────────────────────────────
# Without a checkout every engine file is its own HTTPS request — 22 of them on
# the Proxmox path, ~5.5s before the first prompt. curl fetches them
# concurrently over one connection instead (~0.4s), into tmpfs, for this run
# only. No cache, nothing to go stale.
#
# The list is checked against the repository by .github/workflows/verify-split-files.yml,
# so a new .func cannot be forgotten here. A file missing from it would still
# work — _cs_local_path falls through to the network — just slower.
_CS_ENGINE_FILES=(
lib/alpine.func api/api.func
api/errorlog.func api/exitcodes.func api/sysinfo.func
api/telemetry.func
ui/build-ui.func ui/advanced.func ui/defaults.func
ui/menu.func ui/validate.func vm/cloud-init.func vm/common.func
core/core.func core/dev-mode.func core/error_handler.func
lxc/install.func lxc/platform.func host/preflight.func
host/source-origin.func lib/tools.func lib/db.func
lib/forge.func lib/hwaccel.func lib/runtime.func
lib/system.func host/validate.func
pve/backend.func pve/vm-app.func pve/vm-core.func
incus/backend.func incus/build.func incus/core.func incus/tools.func
incus/vm-core.func
)
_cs_curl_has_parallel() {
local ver major minor
ver="$(curl --version 2>/dev/null | head -1 | awk '{print $2}')" || return 1
major="${ver%%.*}"
minor="${ver#*.}"
minor="${minor%%.*}"
[[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]] || return 1
((major > 7 || (major == 7 && minor >= 66)))
}
_cs_prefetch_engine() {
[[ -n "${COMMUNITY_SCRIPTS_CORE_DIR:-}" ]] && return 0
[[ "${COMMUNITY_SCRIPTS_NO_PREFETCH:-0}" == "1" ]] && return 1
command -v curl >/dev/null 2>&1 || return 1
_cs_curl_has_parallel || return 1
local base dir=""
for base in /dev/shm "${TMPDIR:-/tmp}" /tmp; do
[[ -d "$base" && -w "$base" ]] || continue
# An EXIT trap would be the obvious cleanup, but core.func and api.func
# install their own later and the last one wins. Sweep instead.
find "$base" -maxdepth 1 \( -name 'cs-engine.*' -o -name 'cs-pveam.*' -o -name 'cs-pveversion.*' \) -mmin +60 -exec rm -rf {} + 2>/dev/null || true
dir="$(mktemp -d "${base}/cs-engine.XXXXXX" 2>/dev/null)" && break
dir=""
done
[[ -z "$dir" ]] && return 1
local _cs_t0=${EPOCHREALTIME/[.,]/}
local args=() rel
for rel in "${_CS_ENGINE_FILES[@]}"; do
mkdir -p "${dir}/${rel%/*}" 2>/dev/null || true
args+=(-o "${dir}/${rel}" "${COMMUNITY_SCRIPTS_CORE_URL}/${rel}")
done
# --fail so a 404 leaves no file rather than an error page that would be
# sourced as shell.
curl -fsSL --parallel --parallel-immediate --parallel-max 16 "${_CS_CURL_RETRY[@]}" --max-time 60 "${args[@]}" 2>/dev/null
local kept=0 missing=0
for rel in "${_CS_ENGINE_FILES[@]}"; do
if [[ -s "${dir}/${rel}" ]]; then
kept=$((kept + 1))
else
rm -f "${dir}/${rel}" 2>/dev/null
missing=$((missing + 1))
fi
done
_cs_net_log "200" "$(( ( ${EPOCHREALTIME/[.,]/} - _cs_t0 ) / 1000 ))" "prefetch: ${kept} files, ${missing} missing -> ${dir}"
if ((kept == 0)); then
rm -rf "$dir" 2>/dev/null
return 1
fi
COMMUNITY_SCRIPTS_CORE_DIR="$dir"
export COMMUNITY_SCRIPTS_CORE_DIR
return 0
}
_cs_prefetch_engine || true
_cs_source_func "lxc/platform.func"
# Before anything asks which platform this is: the client has to be resolvable,
# or an Incus host detects as a plain container and the ct script below runs its
# update path against the host.
declare -f ensure_incus_on_path >/dev/null 2>&1 && ensure_incus_on_path
true
# Inside a container the OS is a fact, not a choice. A dual-OS script branches
# on var_os near the top of the file and only offers the menu on a host, so an
# update took the Debian arm and told an Alpine container it needed 6 GB and
# four cores. base_settings() derives this too, but long after that branch.
# var_version comes along or the branch's static default would survive and be
# read as a version mismatch. detect_lxc_platform, not `command -v pveversion`:
# an Incus host has none either, and guessing its OS would break creation.
case "$(detect_lxc_platform)" in
container | incus-container)
if [[ -r /etc/os-release ]]; then
[[ -z "${var_os:-}" ]] && var_os="$(. /etc/os-release 2>/dev/null && echo "${ID:-}")"
[[ -z "${var_version:-}" ]] && var_version="$(. /etc/os-release 2>/dev/null && echo "${VERSION_ID:-}")"
fi
;;
esac
if is_incus_lxc_backend; then
_cs_source_func "incus/build.func"
return 0
fi
_cs_source_func "ui/build-ui.func"
_cs_source_func "pve/backend.func"