mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-07-22 22:02:53 +02:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2919410887 | |||
| 3a7305a8cf | |||
| 737d99ec9d | |||
| 0576b7c61f | |||
| 56e2170f6c | |||
| d5f9db35f3 | |||
| 6b068bc9bf | |||
| 04dbf97672 | |||
| a339c08bca | |||
| e4d0e743e4 | |||
| b2d54f2a72 | |||
| fe01d670f6 | |||
| e0df8a80bf | |||
| 3f7283ab66 |
@@ -505,6 +505,30 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## 2026-07-22
|
||||||
|
|
||||||
|
### 🆕 New Scripts
|
||||||
|
|
||||||
|
- pve-tool: storage share helper script for Proxmox [@MickLesk](https://github.com/MickLesk) ([#15869](https://github.com/community-scripts/ProxmoxVE/pull/15869))
|
||||||
|
|
||||||
|
### 🚀 Updated Scripts
|
||||||
|
|
||||||
|
- #### 🐞 Bug Fixes
|
||||||
|
|
||||||
|
- InvoiceNinja: preserve and Re-download snappdf Chromium [@MickLesk](https://github.com/MickLesk) ([#15956](https://github.com/community-scripts/ProxmoxVE/pull/15956))
|
||||||
|
- Anytype: preserve default mongod.conf when configuring Anytype replica set [@MickLesk](https://github.com/MickLesk) ([#15954](https://github.com/community-scripts/ProxmoxVE/pull/15954))
|
||||||
|
- Nametag: fix missing tailwindcss module [@MickLesk](https://github.com/MickLesk) ([#15955](https://github.com/community-scripts/ProxmoxVE/pull/15955))
|
||||||
|
|
||||||
|
- #### ✨ New Features
|
||||||
|
|
||||||
|
- OPNSense: Bump FreeBSD to 15 and OPNSense to 26.7 [@MickLesk](https://github.com/MickLesk) ([#15943](https://github.com/community-scripts/ProxmoxVE/pull/15943))
|
||||||
|
|
||||||
|
### 💾 Core
|
||||||
|
|
||||||
|
- #### ✨ New Features
|
||||||
|
|
||||||
|
- core: add OS mismatch guard for container updates [@MickLesk](https://github.com/MickLesk) ([#15948](https://github.com/community-scripts/ProxmoxVE/pull/15948))
|
||||||
|
|
||||||
## 2026-07-21
|
## 2026-07-21
|
||||||
|
|
||||||
### 🚀 Updated Scripts
|
### 🚀 Updated Scripts
|
||||||
|
|||||||
+7
-1
@@ -35,12 +35,18 @@ function update_script() {
|
|||||||
systemctl stop supervisor nginx php8.4-fpm
|
systemctl stop supervisor nginx php8.4-fpm
|
||||||
msg_ok "Stopped Services"
|
msg_ok "Stopped Services"
|
||||||
|
|
||||||
create_backup /opt/invoiceninja/.env /opt/invoiceninja/storage /opt/invoiceninja/public/storage
|
create_backup /opt/invoiceninja/.env /opt/invoiceninja/storage /opt/invoiceninja/public/storage /opt/invoiceninja/vendor/beganovich/snappdf/versions
|
||||||
|
|
||||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "invoiceninja" "invoiceninja/invoiceninja" "prebuild" "latest" "/opt/invoiceninja" "invoiceninja.tar.gz"
|
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "invoiceninja" "invoiceninja/invoiceninja" "prebuild" "latest" "/opt/invoiceninja" "invoiceninja.tar.gz"
|
||||||
|
|
||||||
restore_backup
|
restore_backup
|
||||||
|
|
||||||
|
msg_info "Verifying Chromium for PDF Generation"
|
||||||
|
cd /opt/invoiceninja
|
||||||
|
$STD ./vendor/bin/snappdf download
|
||||||
|
chown -R www-data:www-data /opt/invoiceninja/vendor/beganovich/snappdf/versions
|
||||||
|
msg_ok "Verified Chromium for PDF Generation"
|
||||||
|
|
||||||
msg_info "Running Migrations"
|
msg_info "Running Migrations"
|
||||||
cd /opt/invoiceninja
|
cd /opt/invoiceninja
|
||||||
$STD php artisan migrate --force
|
$STD php artisan migrate --force
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ function update_script() {
|
|||||||
|
|
||||||
msg_info "Rebuilding Application"
|
msg_info "Rebuilding Application"
|
||||||
cd /opt/nametag
|
cd /opt/nametag
|
||||||
$STD npm ci
|
$STD npm ci --include=dev
|
||||||
set -a
|
set -a
|
||||||
source /opt/nametag/.env
|
source /opt/nametag/.env
|
||||||
set +a
|
set +a
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ update_os
|
|||||||
setup_mongodb
|
setup_mongodb
|
||||||
|
|
||||||
msg_info "Configuring MongoDB Replica Set"
|
msg_info "Configuring MongoDB Replica Set"
|
||||||
cat <<EOF >/etc/mongod.conf
|
cat <<EOF >>/etc/mongod.conf
|
||||||
|
|
||||||
replication:
|
replication:
|
||||||
replSetName: "rs0"
|
replSetName: "rs0"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ fetch_and_deploy_gh_release "nametag" "mattogodoy/nametag" "tarball" "latest" "/
|
|||||||
|
|
||||||
msg_info "Setting up Application"
|
msg_info "Setting up Application"
|
||||||
cd /opt/nametag
|
cd /opt/nametag
|
||||||
$STD npm ci
|
$STD npm ci --include=dev
|
||||||
DATABASE_URL="postgresql://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}" $STD npx prisma generate
|
DATABASE_URL="postgresql://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}" $STD npx prisma generate
|
||||||
DATABASE_URL="postgresql://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}" $STD npx prisma migrate deploy
|
DATABASE_URL="postgresql://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}" $STD npx prisma migrate deploy
|
||||||
msg_ok "Set up Application"
|
msg_ok "Set up Application"
|
||||||
|
|||||||
+87
-2
@@ -3831,6 +3831,78 @@ runtime_script_status_guard() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# check_container_os_guard()
|
||||||
|
#
|
||||||
|
# - Compares the container OS (/etc/os-release) with the script's recommended
|
||||||
|
# var_os/var_version before running update_script
|
||||||
|
# - On mismatch: interactive runs ask whether to continue (default: no);
|
||||||
|
# headless runs (PHS_SILENT=1 / no tty) abort instead of updating on an
|
||||||
|
# unsupported base and leaving the app broken mid-update
|
||||||
|
# - Bypass via var_ignore_os_mismatch=1|yes|true|on (still warns, but continues)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
check_container_os_guard() {
|
||||||
|
local rec_os="${var_os:-}" rec_ver="${var_version:-}"
|
||||||
|
rec_os="${rec_os,,}"
|
||||||
|
[[ -z "$rec_os" || -z "$rec_ver" ]] && return 0
|
||||||
|
[[ -r /etc/os-release ]] || return 0
|
||||||
|
|
||||||
|
local cur_os cur_ver
|
||||||
|
cur_os="$(. /etc/os-release 2>/dev/null; echo "${ID:-}")"
|
||||||
|
cur_ver="$(. /etc/os-release 2>/dev/null; echo "${VERSION_ID:-}")"
|
||||||
|
cur_os="${cur_os,,}"
|
||||||
|
[[ -z "$cur_os" || -z "$cur_ver" ]] && return 0
|
||||||
|
|
||||||
|
# Exact version or prefix on a dot boundary (e.g. alpine 3.22 matches 3.22.1)
|
||||||
|
if [[ "$cur_os" == "$rec_os" ]] && [[ "$cur_ver" == "$rec_ver" || "$cur_ver" == "$rec_ver".* ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${var_ignore_os_mismatch:-}" in
|
||||||
|
1 | yes | true | on)
|
||||||
|
msg_warn "Container OS is ${cur_os} ${cur_ver} but the script recommends ${rec_os} ${rec_ver} — continuing via var_ignore_os_mismatch (may break, no support)."
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Persistent opt-out: stores the ignored target version, so the question
|
||||||
|
# comes back once the script targets a newer OS again
|
||||||
|
local ignore_file="/usr/local/community-scripts/ignore-os-mismatch"
|
||||||
|
if [[ -f "$ignore_file" && "$(cat "$ignore_file" 2>/dev/null)" == "${rec_os} ${rec_ver}" ]]; then
|
||||||
|
msg_warn "Container OS is ${cur_os} ${cur_ver} but the script recommends ${rec_os} ${rec_ver} — continuing (previously ignored via ${ignore_file}, may break, no support)."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${PHS_SILENT:-0}" != "1" ]] && command -v whiptail &>/dev/null && [ -t 0 ] && [[ "$TERM" != "dumb" ]]; then
|
||||||
|
local choice
|
||||||
|
choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "OS VERSION MISMATCH" --menu \
|
||||||
|
"This container runs ${cur_os} ${cur_ver}, but this script now targets ${rec_os} ${rec_ver}.\n\nUpdating on the older base OS may fail or leave ${APP:-the application} broken (e.g. required runtime versions are not available).\n\nRecommended: upgrade the container OS to ${rec_os} ${rec_ver} first, then run this update again.\n\nIf you continue anyway, it may break — no support is provided in that case.\n\nContinue anyway?" \
|
||||||
|
20 70 3 \
|
||||||
|
"1" "No (cancel update)" \
|
||||||
|
"2" "Yes (continue this time)" \
|
||||||
|
"3" "Yes (continue and don't ask again)" \
|
||||||
|
--nocancel --default-item "1" 3>&1 1>&2 2>&3)
|
||||||
|
case "$choice" in
|
||||||
|
2)
|
||||||
|
msg_warn "Continuing update on ${cur_os} ${cur_ver} despite recommended ${rec_os} ${rec_ver} — may break, no support."
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
mkdir -p "${ignore_file%/*}"
|
||||||
|
echo "${rec_os} ${rec_ver}" >"$ignore_file"
|
||||||
|
msg_warn "Continuing update on ${cur_os} ${cur_ver}; OS check for ${rec_os} ${rec_ver} disabled via ${ignore_file} — may break, no support."
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
msg_error "Update cancelled: container OS ${cur_os} ${cur_ver} does not match the recommended ${rec_os} ${rec_ver}."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_error "Container OS ${cur_os} ${cur_ver} does not match the recommended ${rec_os} ${rec_ver} — skipping update."
|
||||||
|
msg_error "Upgrade the container OS to ${rec_os} ${rec_ver} first, then run this update again — or bypass this check (may break, no support) with: echo \"${rec_os} ${rec_ver}\" > ${ignore_file}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# start()
|
# start()
|
||||||
#
|
#
|
||||||
@@ -3852,6 +3924,7 @@ start() {
|
|||||||
ensure_profile_loaded
|
ensure_profile_loaded
|
||||||
get_lxc_ip
|
get_lxc_ip
|
||||||
runtime_script_status_guard update || return 0
|
runtime_script_status_guard update || return 0
|
||||||
|
check_container_os_guard || return 0
|
||||||
update_script
|
update_script
|
||||||
run_addon_updates
|
run_addon_updates
|
||||||
update_motd_ip
|
update_motd_ip
|
||||||
@@ -3863,6 +3936,7 @@ start() {
|
|||||||
ensure_profile_loaded
|
ensure_profile_loaded
|
||||||
get_lxc_ip
|
get_lxc_ip
|
||||||
runtime_script_status_guard update || return 0
|
runtime_script_status_guard update || return 0
|
||||||
|
check_container_os_guard || return 0
|
||||||
update_script
|
update_script
|
||||||
run_addon_updates
|
run_addon_updates
|
||||||
update_motd_ip
|
update_motd_ip
|
||||||
@@ -3893,6 +3967,7 @@ start() {
|
|||||||
ensure_profile_loaded
|
ensure_profile_loaded
|
||||||
get_lxc_ip
|
get_lxc_ip
|
||||||
runtime_script_status_guard update || return 0
|
runtime_script_status_guard update || return 0
|
||||||
|
check_container_os_guard || return 0
|
||||||
update_script
|
update_script
|
||||||
run_addon_updates
|
run_addon_updates
|
||||||
update_motd_ip
|
update_motd_ip
|
||||||
@@ -5140,7 +5215,10 @@ EOF
|
|||||||
echo -en "${YW}Select option [1-${max_option}] (default: 1, auto-remove in 60s): ${CL}"
|
echo -en "${YW}Select option [1-${max_option}] (default: 1, auto-remove in 60s): ${CL}"
|
||||||
|
|
||||||
local response=""
|
local response=""
|
||||||
if read -t 60 -r response; then
|
local read_rc
|
||||||
|
read -t 60 -r response
|
||||||
|
read_rc=$?
|
||||||
|
if [[ $read_rc -eq 0 ]]; then
|
||||||
case "${response:-1}" in
|
case "${response:-1}" in
|
||||||
1)
|
1)
|
||||||
# Remove container
|
# Remove container
|
||||||
@@ -5359,13 +5437,20 @@ EOF
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
else
|
elif [[ $read_rc -gt 128 ]]; then
|
||||||
# Timeout - auto-remove
|
# Timeout - auto-remove
|
||||||
echo ""
|
echo ""
|
||||||
msg_info "No response - removing container ${CTID}"
|
msg_info "No response - removing container ${CTID}"
|
||||||
pct stop "$CTID" &>/dev/null || true
|
pct stop "$CTID" &>/dev/null || true
|
||||||
pct destroy "$CTID" &>/dev/null || true
|
pct destroy "$CTID" &>/dev/null || true
|
||||||
msg_ok "Container ${CTID} removed"
|
msg_ok "Container ${CTID} removed"
|
||||||
|
else
|
||||||
|
# read itself failed (e.g. broken/closed stdin, I/O error) rather than
|
||||||
|
# timing out - don't guess and destroy the container on a read we
|
||||||
|
# couldn't actually capture; keep it since that's the reversible choice.
|
||||||
|
echo ""
|
||||||
|
msg_error "Could not read your response (stdin error) - keeping container ${CTID} for safety."
|
||||||
|
msg_error "Remove it manually if not needed: pct destroy ${CTID}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Force one final status update attempt after cleanup
|
# Force one final status update attempt after cleanup
|
||||||
|
|||||||
+17
-2
@@ -497,7 +497,10 @@ error_handler() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local response=""
|
local response=""
|
||||||
if read -t 60 -r response; then
|
local read_rc
|
||||||
|
read -t 60 -r response
|
||||||
|
read_rc=$?
|
||||||
|
if [[ $read_rc -eq 0 ]]; then
|
||||||
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
|
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
if declare -f msg_info >/dev/null 2>&1; then
|
if declare -f msg_info >/dev/null 2>&1; then
|
||||||
@@ -520,7 +523,7 @@ error_handler() {
|
|||||||
echo -e "${YW}Container ${CTID} kept for debugging${CL}"
|
echo -e "${YW}Container ${CTID} kept for debugging${CL}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
elif [[ $read_rc -gt 128 ]]; then
|
||||||
# Timeout - auto-remove
|
# Timeout - auto-remove
|
||||||
echo ""
|
echo ""
|
||||||
if declare -f msg_info >/dev/null 2>&1; then
|
if declare -f msg_info >/dev/null 2>&1; then
|
||||||
@@ -535,6 +538,18 @@ error_handler() {
|
|||||||
else
|
else
|
||||||
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
# read itself failed (e.g. broken/closed stdin, I/O error) rather than
|
||||||
|
# timing out - don't guess and destroy the container on a read we
|
||||||
|
# couldn't actually capture; keep it since that's the reversible choice.
|
||||||
|
echo ""
|
||||||
|
if declare -f msg_error >/dev/null 2>&1; then
|
||||||
|
msg_error "Could not read your response (stdin error) - keeping container ${CTID} for safety."
|
||||||
|
msg_error "Remove it manually if not needed: pct destroy ${CTID}"
|
||||||
|
else
|
||||||
|
echo -e "${YW}Could not read your response (stdin error) - keeping container ${CTID} for safety.${CL}"
|
||||||
|
echo -e "${YW}Remove it manually if not needed: pct destroy ${CTID}${CL}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
+9
-9
@@ -2689,7 +2689,7 @@ fetch_and_deploy_gh_tag() {
|
|||||||
|
|
||||||
local tmpdir
|
local tmpdir
|
||||||
tmpdir=$(mktemp -d) || return 1
|
tmpdir=$(mktemp -d) || return 1
|
||||||
trap 'rm -rf "$tmpdir"' RETURN
|
trap 'rm -rf "$tmpdir"; trap - RETURN' RETURN
|
||||||
local tarball_url="https://github.com/${repo}/archive/refs/tags/${version}.tar.gz"
|
local tarball_url="https://github.com/${repo}/archive/refs/tags/${version}.tar.gz"
|
||||||
local filename="${app_lc}-${version}.tar.gz"
|
local filename="${app_lc}-${version}.tar.gz"
|
||||||
|
|
||||||
@@ -2866,7 +2866,7 @@ fetch_and_deploy_gl_tag() {
|
|||||||
|
|
||||||
local tmpdir
|
local tmpdir
|
||||||
tmpdir=$(mktemp -d) || return 1
|
tmpdir=$(mktemp -d) || return 1
|
||||||
trap 'rm -rf "$tmpdir"' RETURN
|
trap 'rm -rf "$tmpdir"; trap - RETURN' RETURN
|
||||||
local filename="${app_lc}-${version_safe}.tar.gz"
|
local filename="${app_lc}-${version_safe}.tar.gz"
|
||||||
|
|
||||||
msg_info "Fetching GitLab tag: ${app} (${resolved_tag})"
|
msg_info "Fetching GitLab tag: ${app} (${resolved_tag})"
|
||||||
@@ -2974,7 +2974,7 @@ check_for_gh_release() {
|
|||||||
|
|
||||||
local gh_check_json=""
|
local gh_check_json=""
|
||||||
gh_check_json=$(mktemp /tmp/tools-gh-check-XXXXXX) || return 73
|
gh_check_json=$(mktemp /tmp/tools-gh-check-XXXXXX) || return 73
|
||||||
trap 'rm -f "${gh_check_json:-}"' RETURN
|
trap 'rm -f "${gh_check_json:-}"; trap - RETURN' RETURN
|
||||||
|
|
||||||
# Build auth header if token is available
|
# Build auth header if token is available
|
||||||
local header_args=()
|
local header_args=()
|
||||||
@@ -3574,7 +3574,7 @@ fetch_and_deploy_codeberg_release() {
|
|||||||
|
|
||||||
local tmpdir
|
local tmpdir
|
||||||
tmpdir=$(mktemp -d) || return 252
|
tmpdir=$(mktemp -d) || return 252
|
||||||
trap 'rm -rf "$tmpdir"' RETURN
|
trap 'rm -rf "$tmpdir"; trap - RETURN' RETURN
|
||||||
|
|
||||||
msg_info "Fetching Codeberg tag: $app ($tag_name)"
|
msg_info "Fetching Codeberg tag: $app ($tag_name)"
|
||||||
|
|
||||||
@@ -3616,7 +3616,7 @@ fetch_and_deploy_codeberg_release() {
|
|||||||
|
|
||||||
local codeberg_rel_json
|
local codeberg_rel_json
|
||||||
codeberg_rel_json=$(mktemp /tmp/tools-codeberg-rel-XXXXXX) || return 73
|
codeberg_rel_json=$(mktemp /tmp/tools-codeberg-rel-XXXXXX) || return 73
|
||||||
trap 'rm -f "$codeberg_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"' RETURN
|
trap 'rm -f "$codeberg_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"; trap - RETURN' RETURN
|
||||||
|
|
||||||
local attempt=0 success=false resp http_code
|
local attempt=0 success=false resp http_code
|
||||||
|
|
||||||
@@ -4113,7 +4113,7 @@ fetch_and_deploy_gh_release() {
|
|||||||
|
|
||||||
local tmpdir
|
local tmpdir
|
||||||
tmpdir=$(mktemp -d) || return 1
|
tmpdir=$(mktemp -d) || return 1
|
||||||
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"' RETURN
|
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"; trap - RETURN' RETURN
|
||||||
local filename="" url=""
|
local filename="" url=""
|
||||||
|
|
||||||
msg_info "Fetching GitHub release: $app ($version)"
|
msg_info "Fetching GitHub release: $app ($version)"
|
||||||
@@ -9218,7 +9218,7 @@ fetch_and_deploy_from_url() {
|
|||||||
msg_error "Failed to create temporary directory"
|
msg_error "Failed to create temporary directory"
|
||||||
return 252
|
return 252
|
||||||
}
|
}
|
||||||
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"' RETURN
|
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"; trap - RETURN' RETURN
|
||||||
|
|
||||||
curl -fsSL -o "$tmpdir/$filename" "$url" || {
|
curl -fsSL -o "$tmpdir/$filename" "$url" || {
|
||||||
msg_error "Download failed: $url"
|
msg_error "Download failed: $url"
|
||||||
@@ -9434,7 +9434,7 @@ check_for_gl_release() {
|
|||||||
|
|
||||||
local gl_check_json
|
local gl_check_json
|
||||||
gl_check_json=$(mktemp /tmp/tools-gl-check-XXXXXX) || return 73
|
gl_check_json=$(mktemp /tmp/tools-gl-check-XXXXXX) || return 73
|
||||||
trap 'rm -f "$gl_check_json"' RETURN
|
trap 'rm -f "$gl_check_json"; trap - RETURN' RETURN
|
||||||
|
|
||||||
local repo_encoded
|
local repo_encoded
|
||||||
repo_encoded=$(printf '%s' "$source" | sed 's|/|%2F|g')
|
repo_encoded=$(printf '%s' "$source" | sed 's|/|%2F|g')
|
||||||
@@ -9724,7 +9724,7 @@ fetch_and_deploy_gl_release() {
|
|||||||
|
|
||||||
local gl_rel_json
|
local gl_rel_json
|
||||||
gl_rel_json=$(mktemp /tmp/tools-gl-rel-XXXXXX) || return 73
|
gl_rel_json=$(mktemp /tmp/tools-gl-rel-XXXXXX) || return 73
|
||||||
trap 'rm -f "$gl_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"' RETURN
|
trap 'rm -f "$gl_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"; trap - RETURN' RETURN
|
||||||
|
|
||||||
local repo_encoded
|
local repo_encoded
|
||||||
repo_encoded=$(printf '%s' "$repo" | sed 's|/|%2F|g')
|
repo_encoded=$(printf '%s' "$repo" | sed 's|/|%2F|g')
|
||||||
|
|||||||
@@ -0,0 +1,693 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
|
# Author: MickLesk (CanbiZ)
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
|
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/refs/heads/main/misc/core.func)
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||||
|
load_functions
|
||||||
|
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "storage-share-helper" "pve"
|
||||||
|
|
||||||
|
set -eEuo pipefail
|
||||||
|
|
||||||
|
function header_info() {
|
||||||
|
clear
|
||||||
|
cat <<"EOF"
|
||||||
|
_____ __ ___ _________
|
||||||
|
/ ___// /_____ _________ _____ ____ / | / _/ __ \
|
||||||
|
\__ \/ __/ __ \/ ___/ __ `/ __ `/ _ \ / /| | / // / / /
|
||||||
|
___/ / /_/ /_/ / / / /_/ / /_/ / __/ / ___ |_/ // /_/ /
|
||||||
|
/____/\__/\____/_/ \__,_/\__, /\___/ /_/ |_/___/_____/
|
||||||
|
/____/
|
||||||
|
|
||||||
|
Proxmox Storage Allrounder
|
||||||
|
SMB | NFS | iSCSI | LVM-on-iSCSI | LXC Mountpoints | Host Shares
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
pause() {
|
||||||
|
read -r -p "Press Enter to continue..." _
|
||||||
|
}
|
||||||
|
|
||||||
|
require_pve() {
|
||||||
|
if ! command -v pct >/dev/null 2>&1 || ! command -v pvesm >/dev/null 2>&1; then
|
||||||
|
msg_error "This script must run on a Proxmox VE host (pct/pvesm missing)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_packages() {
|
||||||
|
local packages=()
|
||||||
|
|
||||||
|
command -v whiptail >/dev/null 2>&1 || packages+=("whiptail")
|
||||||
|
command -v mount.cifs >/dev/null 2>&1 || packages+=("cifs-utils")
|
||||||
|
command -v showmount >/dev/null 2>&1 || packages+=("nfs-common")
|
||||||
|
command -v iscsiadm >/dev/null 2>&1 || packages+=("open-iscsi")
|
||||||
|
|
||||||
|
if [[ ${#packages[@]} -gt 0 ]]; then
|
||||||
|
msg_info "Installing required packages: ${packages[*]}"
|
||||||
|
apt update >/dev/null 2>&1
|
||||||
|
apt install -y "${packages[@]}" >/dev/null 2>&1
|
||||||
|
msg_ok "Dependencies installed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm_start() {
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Share Allrounder" \
|
||||||
|
--yesno "This AIO wizard can test and configure SMB/NFS/iSCSI, create/remove Proxmox storages, manage LXC mountpoints and optionally create host shares. Proceed?" 13 100
|
||||||
|
}
|
||||||
|
|
||||||
|
read_input() {
|
||||||
|
local title="$1"
|
||||||
|
local prompt="$2"
|
||||||
|
local default_value="${3:-}"
|
||||||
|
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||||
|
--inputbox "$prompt" 11 100 "$default_value" 3>&1 1>&2 2>&3
|
||||||
|
}
|
||||||
|
|
||||||
|
read_password() {
|
||||||
|
local title="$1"
|
||||||
|
local prompt="$2"
|
||||||
|
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||||
|
--passwordbox "$prompt" 11 100 3>&1 1>&2 2>&3
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm_yes_no() {
|
||||||
|
local title="$1"
|
||||||
|
local prompt="$2"
|
||||||
|
local height="${3:-11}"
|
||||||
|
local width="${4:-100}"
|
||||||
|
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||||
|
--yesno "$prompt" "$height" "$width"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Destructive confirmation: defaults to "No" so a stray Enter never removes anything.
|
||||||
|
confirm_danger() {
|
||||||
|
local title="$1"
|
||||||
|
local prompt="$2"
|
||||||
|
local height="${3:-15}"
|
||||||
|
local width="${4:-100}"
|
||||||
|
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||||
|
--defaultno --yesno "$prompt" "$height" "$width"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Shown before slow data gathering. Goes to stderr: inside $(...) captures, stdout is
|
||||||
|
# swallowed — stderr is the real terminal.
|
||||||
|
show_loading() {
|
||||||
|
echo -e "${YW}Loading... gathering data, this can take a moment (CTRL+C to abort)${CL}" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Info popup that stays visible inside $(...) captures: whiptail draws its UI on
|
||||||
|
# stdout, so without 1>&2 the dialog would be captured and the script appears hung.
|
||||||
|
notice_box() {
|
||||||
|
local title="$1"
|
||||||
|
local text="$2"
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||||
|
--msgbox "$text" 10 80 1>&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Interactive container picker (read-only): returns the chosen CTID on stdout.
|
||||||
|
pick_container() {
|
||||||
|
local title="$1"
|
||||||
|
local -a rows=()
|
||||||
|
local ctid status name label
|
||||||
|
|
||||||
|
show_loading
|
||||||
|
while IFS=$'\t' read -r ctid status name; do
|
||||||
|
[[ -z "$ctid" ]] && continue
|
||||||
|
label=$(printf '%-58s' "${name:-<no-name>} [${status}]")
|
||||||
|
rows+=("$ctid" "$label")
|
||||||
|
done < <(pct list 2>/dev/null | awk 'NR>1 {print $1"\t"$2"\t"$NF}')
|
||||||
|
|
||||||
|
if [[ ${#rows[@]} -eq 0 ]]; then
|
||||||
|
notice_box "$title" "No LXC containers found on this host."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||||
|
--menu "Select a container:" 24 80 16 "${rows[@]}" 3>&1 1>&2 2>&3
|
||||||
|
}
|
||||||
|
|
||||||
|
# Interactive multi-select picker over ALL mountpoints across ALL containers (read-only).
|
||||||
|
# Returns chosen entries as "<ctid>:<mpX>" tokens on stdout.
|
||||||
|
# Reads /etc/pve/lxc/*.conf directly (one 'pct config' per CT is far too slow);
|
||||||
|
# awk stops at the first '[section]' so snapshot mp entries are not listed.
|
||||||
|
pick_all_mountpoints_multi() {
|
||||||
|
local title="$1"
|
||||||
|
local -a rows=()
|
||||||
|
local conf ctid name line key def label
|
||||||
|
|
||||||
|
show_loading
|
||||||
|
for conf in /etc/pve/lxc/*.conf; do
|
||||||
|
[[ -e "$conf" ]] || continue
|
||||||
|
ctid="$(basename "$conf" .conf)"
|
||||||
|
name=$(awk '/^\[/{exit} $1=="hostname:"{print $2; exit}' "$conf")
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
key="${line%%:*}"
|
||||||
|
def="${line#*: }"
|
||||||
|
label=$(printf '%-86s' "CT ${ctid} ${name:-<no-name>} | ${key}: ${def}")
|
||||||
|
rows+=("${ctid}:${key}" "$label" "OFF")
|
||||||
|
done < <(awk '/^\[/{exit} /^mp[0-9]+:/{print}' "$conf")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#rows[@]} -eq 0 ]]; then
|
||||||
|
notice_box "$title" "No LXC mountpoints found on any container."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||||
|
--checklist "Select mountpoint(s) to remove across all containers (Space to toggle, Enter to confirm):" \
|
||||||
|
24 112 16 "${rows[@]}" 3>&1 1>&2 2>&3
|
||||||
|
}
|
||||||
|
|
||||||
|
# Interactive multi-select storage picker (read-only): returns chosen storage IDs on stdout.
|
||||||
|
pick_storages_multi() {
|
||||||
|
local title="$1"
|
||||||
|
local -a rows=()
|
||||||
|
local stype sid label
|
||||||
|
|
||||||
|
show_loading
|
||||||
|
while read -r stype sid; do
|
||||||
|
[[ -z "$sid" ]] && continue
|
||||||
|
label=$(printf '%-58s' "[${stype}]")
|
||||||
|
rows+=("$sid" "$label" "OFF")
|
||||||
|
done < <(awk -F': ' '/^[a-z]+: /{print $1, $2}' /etc/pve/storage.cfg 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ ${#rows[@]} -eq 0 ]]; then
|
||||||
|
notice_box "$title" "No storages found in /etc/pve/storage.cfg."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||||
|
--checklist "Select storage(s) to remove (Space to toggle, Enter to confirm):" \
|
||||||
|
24 80 16 "${rows[@]}" 3>&1 1>&2 2>&3
|
||||||
|
}
|
||||||
|
|
||||||
|
# Interactive multi-select container picker (read-only): returns chosen CTIDs on stdout.
|
||||||
|
pick_containers_multi() {
|
||||||
|
local title="$1"
|
||||||
|
local -a rows=()
|
||||||
|
local ctid status name label
|
||||||
|
|
||||||
|
show_loading
|
||||||
|
while IFS=$'\t' read -r ctid status name; do
|
||||||
|
[[ -z "$ctid" ]] && continue
|
||||||
|
label=$(printf '%-58s' "${name:-<no-name>} [${status}]")
|
||||||
|
rows+=("$ctid" "$label" "OFF")
|
||||||
|
done < <(pct list 2>/dev/null | awk 'NR>1 {print $1"\t"$2"\t"$NF}')
|
||||||
|
|
||||||
|
if [[ ${#rows[@]} -eq 0 ]]; then
|
||||||
|
notice_box "$title" "No LXC containers found on this host."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||||
|
--checklist "Select one or more containers (Space to toggle, Enter to confirm):" \
|
||||||
|
24 80 16 "${rows[@]}" 3>&1 1>&2 2>&3
|
||||||
|
}
|
||||||
|
|
||||||
|
manual_smb_test() {
|
||||||
|
header_info
|
||||||
|
local server share username password domain vers mount_dir mount_opts
|
||||||
|
|
||||||
|
server=$(read_input "SMB Test" "SMB server/IP (e.g. 10.0.1.9)") || return
|
||||||
|
share=$(read_input "SMB Test" "Share name (without leading //)" "Proxmox") || return
|
||||||
|
username=$(read_input "SMB Test" "Username" "proxmox") || return
|
||||||
|
password=$(read_password "SMB Test" "Password for ${username}") || return
|
||||||
|
domain=$(read_input "SMB Test" "Domain/Workgroup (optional, leave empty if not needed)") || return
|
||||||
|
vers=$(read_input "SMB Test" "SMB version (e.g. 3.0, 3.1.1)" "3.1.1") || return
|
||||||
|
mount_dir="/mnt/test-smb"
|
||||||
|
|
||||||
|
mkdir -p "$mount_dir"
|
||||||
|
|
||||||
|
mount_opts="username=${username},password=${password},vers=${vers},sec=ntlmssp"
|
||||||
|
if [[ -n "$domain" ]]; then
|
||||||
|
mount_opts+=";domain=${domain}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_info "Testing SMB mount on ${mount_dir}"
|
||||||
|
if mount -t cifs "//${server}/${share}" "$mount_dir" -o "${mount_opts//;/,}" >/dev/null 2>&1; then
|
||||||
|
touch "${mount_dir}/smb-test-$(date +%s)" >/dev/null 2>&1 || true
|
||||||
|
umount "$mount_dir" >/dev/null 2>&1 || true
|
||||||
|
msg_ok "SMB test successful"
|
||||||
|
else
|
||||||
|
msg_error "SMB test failed. Check network/firewall/credentials/share permissions."
|
||||||
|
fi
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
manual_nfs_test() {
|
||||||
|
header_info
|
||||||
|
local server export_path mount_dir
|
||||||
|
|
||||||
|
server=$(read_input "NFS Test" "NFS server/IP (e.g. 10.0.6.159)") || return
|
||||||
|
export_path=$(read_input "NFS Test" "NFS export path (e.g. /srv/proxmox-nfs)") || return
|
||||||
|
mount_dir="/mnt/test-nfs"
|
||||||
|
|
||||||
|
mkdir -p "$mount_dir"
|
||||||
|
|
||||||
|
msg_info "Testing NFS mount on ${mount_dir}"
|
||||||
|
if mount -t nfs "${server}:${export_path}" "$mount_dir" >/dev/null 2>&1; then
|
||||||
|
touch "${mount_dir}/nfs-test-$(date +%s)" >/dev/null 2>&1 || true
|
||||||
|
umount "$mount_dir" >/dev/null 2>&1 || true
|
||||||
|
msg_ok "NFS test successful"
|
||||||
|
else
|
||||||
|
msg_error "NFS test failed. Check export/firewall/network permissions."
|
||||||
|
fi
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
manual_iscsi_discovery() {
|
||||||
|
header_info
|
||||||
|
local portal
|
||||||
|
|
||||||
|
portal=$(read_input "iSCSI Discovery" "iSCSI portal IP/FQDN (e.g. 10.0.1.20)") || return
|
||||||
|
msg_info "Running iSCSI target discovery on ${portal}"
|
||||||
|
if iscsiadm -m discovery -t sendtargets -p "$portal"; then
|
||||||
|
msg_ok "iSCSI discovery completed"
|
||||||
|
else
|
||||||
|
msg_error "iSCSI discovery failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
add_smb_storage() {
|
||||||
|
header_info
|
||||||
|
local storage_id server share username password content nodes options
|
||||||
|
|
||||||
|
storage_id=$(read_input "Add SMB/CIFS Storage" "Storage ID (unique, e.g. smb-media)") || return
|
||||||
|
server=$(read_input "Add SMB/CIFS Storage" "SMB server/IP") || return
|
||||||
|
share=$(read_input "Add SMB/CIFS Storage" "Share name (without //)") || return
|
||||||
|
username=$(read_input "Add SMB/CIFS Storage" "Username") || return
|
||||||
|
password=$(read_password "Add SMB/CIFS Storage" "Password for ${username}") || return
|
||||||
|
content=$(read_input "Add SMB/CIFS Storage" "Content types (comma separated)" "backup,iso,vztmpl,snippets") || return
|
||||||
|
nodes=$(read_input "Add SMB/CIFS Storage" "Nodes (optional, comma separated)") || return
|
||||||
|
options=$(read_input "Add SMB/CIFS Storage" "Mount options (optional, e.g. vers=3.1.1,domain=WORKGROUP)") || return
|
||||||
|
|
||||||
|
local cmd=(pvesm add cifs "$storage_id" --server "$server" --share "$share" --username "$username" --password "$password" --content "$content")
|
||||||
|
[[ -n "$nodes" ]] && cmd+=(--nodes "$nodes")
|
||||||
|
[[ -n "$options" ]] && cmd+=(--options "$options")
|
||||||
|
|
||||||
|
if "${cmd[@]}" >/dev/null 2>&1; then
|
||||||
|
msg_ok "SMB storage '${storage_id}' added"
|
||||||
|
else
|
||||||
|
msg_error "Failed to add SMB storage '${storage_id}'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
add_nfs_storage() {
|
||||||
|
header_info
|
||||||
|
local storage_id server export_path content nodes options
|
||||||
|
|
||||||
|
storage_id=$(read_input "Add NFS Storage" "Storage ID (unique, e.g. nfs-vmdata)") || return
|
||||||
|
server=$(read_input "Add NFS Storage" "NFS server/IP") || return
|
||||||
|
export_path=$(read_input "Add NFS Storage" "Export path") || return
|
||||||
|
content=$(read_input "Add NFS Storage" "Content types (comma separated)" "images,rootdir") || return
|
||||||
|
nodes=$(read_input "Add NFS Storage" "Nodes (optional, comma separated)") || return
|
||||||
|
options=$(read_input "Add NFS Storage" "Mount options (optional)") || return
|
||||||
|
|
||||||
|
local cmd=(pvesm add nfs "$storage_id" --server "$server" --export "$export_path" --content "$content")
|
||||||
|
[[ -n "$nodes" ]] && cmd+=(--nodes "$nodes")
|
||||||
|
[[ -n "$options" ]] && cmd+=(--options "$options")
|
||||||
|
|
||||||
|
if "${cmd[@]}" >/dev/null 2>&1; then
|
||||||
|
msg_ok "NFS storage '${storage_id}' added"
|
||||||
|
else
|
||||||
|
msg_error "Failed to add NFS storage '${storage_id}'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
add_iscsi_storage() {
|
||||||
|
header_info
|
||||||
|
local storage_id portal target nodes
|
||||||
|
|
||||||
|
storage_id=$(read_input "Add iSCSI Storage" "Storage ID (unique, e.g. iscsi-synology)") || return
|
||||||
|
portal=$(read_input "Add iSCSI Storage" "Portal IP/FQDN") || return
|
||||||
|
target=$(read_input "Add iSCSI Storage" "Target IQN (e.g. iqn.2000-01.com.synology:...)") || return
|
||||||
|
nodes=$(read_input "Add iSCSI Storage" "Nodes (optional, comma separated)") || return
|
||||||
|
|
||||||
|
local cmd=(pvesm add iscsi "$storage_id" --portal "$portal" --target "$target")
|
||||||
|
[[ -n "$nodes" ]] && cmd+=(--nodes "$nodes")
|
||||||
|
|
||||||
|
if "${cmd[@]}" >/dev/null 2>&1; then
|
||||||
|
msg_ok "iSCSI storage '${storage_id}' added"
|
||||||
|
else
|
||||||
|
msg_error "Failed to add iSCSI storage '${storage_id}'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
add_lvm_on_base_storage() {
|
||||||
|
header_info
|
||||||
|
local storage_id base_storage vgname content shared
|
||||||
|
|
||||||
|
storage_id=$(read_input "Add LVM Storage" "LVM Storage ID (unique, e.g. lvm-iscsi01)") || return
|
||||||
|
base_storage=$(read_input "Add LVM Storage" "Base storage ID (usually iSCSI storage ID)") || return
|
||||||
|
vgname=$(read_input "Add LVM Storage" "Volume Group name on target") || return
|
||||||
|
content=$(read_input "Add LVM Storage" "Content types (comma separated)" "images,rootdir") || return
|
||||||
|
shared=$(read_input "Add LVM Storage" "Shared across nodes? 1=yes, 0=no" "1") || return
|
||||||
|
|
||||||
|
local cmd=(pvesm add lvm "$storage_id" --base "$base_storage" --vgname "$vgname" --content "$content" --shared "$shared")
|
||||||
|
if "${cmd[@]}" >/dev/null 2>&1; then
|
||||||
|
msg_ok "LVM storage '${storage_id}' added"
|
||||||
|
else
|
||||||
|
msg_error "Failed to add LVM storage '${storage_id}'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_storage() {
|
||||||
|
header_info
|
||||||
|
local selection storage_id
|
||||||
|
local -a results=()
|
||||||
|
|
||||||
|
selection=$(pick_storages_multi "Remove Storage") || return
|
||||||
|
if [[ -z "$selection" ]]; then
|
||||||
|
msg_warn "No storage selected."
|
||||||
|
pause
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Confirm each selected storage separately (defaults to No).
|
||||||
|
for storage_id in $selection; do
|
||||||
|
storage_id="${storage_id//\"/}"
|
||||||
|
[[ -z "$storage_id" ]] && continue
|
||||||
|
|
||||||
|
if confirm_danger "Remove Storage" \
|
||||||
|
"Really remove storage '${storage_id}' from the Proxmox config?
|
||||||
|
|
||||||
|
This removes the storage definition only.
|
||||||
|
Data on the underlying share/target is NOT deleted."; then
|
||||||
|
if pvesm remove "$storage_id" >/dev/null 2>&1; then
|
||||||
|
results+=("removed ${storage_id}")
|
||||||
|
else
|
||||||
|
results+=("FAILED ${storage_id}")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
results+=("skipped ${storage_id}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
header_info
|
||||||
|
echo -e "${BL}Remove storage — result${CL}\n"
|
||||||
|
printf ' %s\n' "${results[@]}"
|
||||||
|
echo
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
find_next_mp_slot() {
|
||||||
|
local ctid="$1"
|
||||||
|
local used
|
||||||
|
|
||||||
|
used=$(pct config "$ctid" | awk -F: '/^mp[0-9]+:/ {gsub("mp", "", $1); print $1}')
|
||||||
|
for i in $(seq 0 255); do
|
||||||
|
if ! grep -qx "$i" <<<"$used"; then
|
||||||
|
echo "$i"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
add_lxc_mountpoint() {
|
||||||
|
header_info
|
||||||
|
local ctid host_path ct_path mp_slot
|
||||||
|
|
||||||
|
ctid=$(pick_container "LXC: add mountpoint") || return
|
||||||
|
host_path=$(read_input "LXC Mountpoint" "Host path (must exist, e.g. /mnt/pve/smb-media)") || return
|
||||||
|
ct_path=$(read_input "LXC Mountpoint" "Container path (e.g. /mnt/media)") || return
|
||||||
|
|
||||||
|
if [[ ! -d "$host_path" ]]; then
|
||||||
|
msg_error "Host path does not exist: ${host_path}"
|
||||||
|
pause
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
mp_slot=$(find_next_mp_slot "$ctid")
|
||||||
|
if [[ -z "$mp_slot" ]]; then
|
||||||
|
msg_error "No free mp slot available for container ${ctid}"
|
||||||
|
pause
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
confirm_yes_no "LXC: add mountpoint" \
|
||||||
|
"Add mp${mp_slot} to CT ${ctid}?
|
||||||
|
|
||||||
|
${host_path} -> ${ct_path}" 13 100 || return
|
||||||
|
|
||||||
|
if pct set "$ctid" -mp"$mp_slot" "$host_path",mp="$ct_path" >/dev/null 2>&1; then
|
||||||
|
msg_ok "Added mp${mp_slot}: ${host_path} -> ${ct_path} on CT ${ctid}"
|
||||||
|
msg_warn "If CT is unprivileged, ensure UID/GID mapping and filesystem permissions fit your workload."
|
||||||
|
else
|
||||||
|
msg_error "Failed to add mountpoint to CT ${ctid}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_lxc_mountpoint() {
|
||||||
|
header_info
|
||||||
|
local selection entry ctid mp_key mp_def
|
||||||
|
local -a results=()
|
||||||
|
|
||||||
|
selection=$(pick_all_mountpoints_multi "LXC: remove mountpoint") || return
|
||||||
|
if [[ -z "$selection" ]]; then
|
||||||
|
msg_warn "No mountpoint selected."
|
||||||
|
pause
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Confirm each selected mountpoint separately (defaults to No).
|
||||||
|
for entry in $selection; do
|
||||||
|
entry="${entry//\"/}"
|
||||||
|
[[ -z "$entry" ]] && continue
|
||||||
|
ctid="${entry%%:*}"
|
||||||
|
mp_key="${entry#*:}"
|
||||||
|
mp_def=$(pct config "$ctid" | awk -F': ' -v k="$mp_key" '$1==k{print $2}')
|
||||||
|
|
||||||
|
if confirm_danger "LXC: remove mountpoint" \
|
||||||
|
"Remove ${mp_key} from CT ${ctid}?
|
||||||
|
|
||||||
|
${mp_key}: ${mp_def}
|
||||||
|
|
||||||
|
This only detaches the mountpoint from the container.
|
||||||
|
Data on the host is NOT deleted."; then
|
||||||
|
if pct set "$ctid" -delete "$mp_key" >/dev/null 2>&1; then
|
||||||
|
results+=("removed CT ${ctid} ${mp_key}")
|
||||||
|
else
|
||||||
|
results+=("FAILED CT ${ctid} ${mp_key}")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
results+=("skipped CT ${ctid} ${mp_key}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
header_info
|
||||||
|
echo -e "${BL}Remove mountpoint — result${CL}\n"
|
||||||
|
printf ' %s\n' "${results[@]}"
|
||||||
|
echo
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
list_lxc_mountpoints() {
|
||||||
|
header_info
|
||||||
|
local selection ctid mps
|
||||||
|
|
||||||
|
selection=$(pick_containers_multi "LXC: list mountpoints") || return
|
||||||
|
if [[ -z "$selection" ]]; then
|
||||||
|
msg_warn "No container selected."
|
||||||
|
pause
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
for ctid in $selection; do
|
||||||
|
ctid="${ctid//\"/}"
|
||||||
|
[[ -z "$ctid" ]] && continue
|
||||||
|
echo -e "${BL}Mountpoints for CT ${ctid}${CL}"
|
||||||
|
mps=$(pct config "$ctid" | awk '/^mp[0-9]+:/{print}')
|
||||||
|
if [[ -n "$mps" ]]; then
|
||||||
|
echo "$mps"
|
||||||
|
else
|
||||||
|
echo " (no mountpoints configured)"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
host_create_samba_share() {
|
||||||
|
header_info
|
||||||
|
local share_name share_path user_name user_pass
|
||||||
|
|
||||||
|
share_name=$(read_input "Host Samba Share" "Share name (e.g. data)") || return
|
||||||
|
share_path=$(read_input "Host Samba Share" "Share path on host (e.g. /srv/samba/data)" "/srv/samba/${share_name}") || return
|
||||||
|
user_name=$(read_input "Host Samba Share" "Linux/Samba username") || return
|
||||||
|
user_pass=$(read_password "Host Samba Share" "Password for ${user_name}") || return
|
||||||
|
|
||||||
|
msg_info "Installing Samba on host if needed"
|
||||||
|
apt update >/dev/null 2>&1
|
||||||
|
apt install -y samba >/dev/null 2>&1
|
||||||
|
|
||||||
|
mkdir -p "$share_path"
|
||||||
|
getent group sambashare >/dev/null 2>&1 || groupadd sambashare
|
||||||
|
id "$user_name" >/dev/null 2>&1 || useradd -M -s /usr/sbin/nologin -G sambashare "$user_name"
|
||||||
|
usermod -aG sambashare "$user_name"
|
||||||
|
chown -R root:sambashare "$share_path"
|
||||||
|
chmod 2775 "$share_path"
|
||||||
|
|
||||||
|
if ! (
|
||||||
|
echo "$user_pass"
|
||||||
|
echo "$user_pass"
|
||||||
|
) | smbpasswd -s -a "$user_name" >/dev/null 2>&1; then
|
||||||
|
msg_error "Failed to set Samba password for ${user_name}"
|
||||||
|
pause
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "^\[${share_name}\]" /etc/samba/smb.conf; then
|
||||||
|
cat <<EOF >>/etc/samba/smb.conf
|
||||||
|
|
||||||
|
[${share_name}]
|
||||||
|
comment = Proxmox Host Share (${share_name})
|
||||||
|
path = ${share_path}
|
||||||
|
browseable = yes
|
||||||
|
read only = no
|
||||||
|
guest ok = no
|
||||||
|
create mask = 0664
|
||||||
|
directory mask = 2775
|
||||||
|
valid users = @sambashare
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
testparm -s >/dev/null 2>&1 || {
|
||||||
|
msg_error "Samba config validation failed (testparm). Check /etc/samba/smb.conf"
|
||||||
|
pause
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
systemctl enable --now smbd nmbd >/dev/null 2>&1
|
||||||
|
systemctl restart smbd nmbd >/dev/null 2>&1
|
||||||
|
|
||||||
|
msg_ok "Host SMB share created: //$(hostname -I | awk '{print $1}')/${share_name}"
|
||||||
|
msg_warn "Best practice: prefer running Samba in a dedicated LXC/VM for cleaner host separation."
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
host_create_nfs_export() {
|
||||||
|
header_info
|
||||||
|
local export_path subnet options
|
||||||
|
|
||||||
|
export_path=$(read_input "Host NFS Export" "Export path on host (e.g. /srv/proxmox-nfs)" "/srv/proxmox-nfs") || return
|
||||||
|
subnet=$(read_input "Host NFS Export" "Allowed subnet/CIDR (e.g. 10.0.0.0/16)") || return
|
||||||
|
options=$(read_input "Host NFS Export" "Export options" "rw,sync,no_subtree_check,no_root_squash") || return
|
||||||
|
|
||||||
|
msg_info "Installing NFS server on host if needed"
|
||||||
|
apt update >/dev/null 2>&1
|
||||||
|
apt install -y nfs-kernel-server >/dev/null 2>&1
|
||||||
|
|
||||||
|
mkdir -p "$export_path"
|
||||||
|
chmod 0770 "$export_path"
|
||||||
|
|
||||||
|
if ! grep -qE "^${export_path//\//\/}[[:space:]]+${subnet//\//\/}\(" /etc/exports; then
|
||||||
|
echo "${export_path} ${subnet}(${options})" >>/etc/exports
|
||||||
|
fi
|
||||||
|
|
||||||
|
exportfs -ra >/dev/null 2>&1
|
||||||
|
systemctl enable --now nfs-kernel-server >/dev/null 2>&1
|
||||||
|
|
||||||
|
msg_ok "Host NFS export created: ${export_path} ${subnet}(${options})"
|
||||||
|
msg_warn "Best practice: use host exports carefully; dedicated storage VM/LXC is often cleaner."
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
header_info
|
||||||
|
echo -e "${BL}pvesm status${CL}"
|
||||||
|
pvesm status || true
|
||||||
|
echo
|
||||||
|
echo -e "${BL}Mounted /mnt/pve paths${CL}"
|
||||||
|
mount | grep /mnt/pve || true
|
||||||
|
echo
|
||||||
|
echo -e "${BL}Mounted CIFS/NFS/iSCSI related paths${CL}"
|
||||||
|
mount | grep -E ' type (cifs|nfs|nfs4)' || true
|
||||||
|
echo
|
||||||
|
echo -e "${BL}iSCSI sessions${CL}"
|
||||||
|
iscsiadm -m session 2>/dev/null || true
|
||||||
|
echo
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
main_menu() {
|
||||||
|
while true; do
|
||||||
|
local choice
|
||||||
|
choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Share Allrounder" \
|
||||||
|
--menu "Select action (read-only actions are safe; write/remove actions ask for confirmation):" 30 116 22 \
|
||||||
|
" " "────────── READ-ONLY (safe, no changes) ──────────" \
|
||||||
|
"1" "Test | SMB: manual mount test" \
|
||||||
|
"2" "Test | NFS: manual mount test" \
|
||||||
|
"3" "Test | iSCSI: discovery test" \
|
||||||
|
"4" "Read | LXC: list mountpoints" \
|
||||||
|
"5" "Read | Show storage/mount/iSCSI status" \
|
||||||
|
" " "────────── WRITE (modifies config / host) ──────────" \
|
||||||
|
"6" "Write | Proxmox: add SMB/CIFS storage" \
|
||||||
|
"7" "Write | Proxmox: add NFS storage" \
|
||||||
|
"8" "Write | Proxmox: add iSCSI storage" \
|
||||||
|
"9" "Write | Proxmox: add LVM on base storage (e.g. iSCSI)" \
|
||||||
|
"10" "Write | LXC: add bind mountpoint (pct set -mpX)" \
|
||||||
|
"11" "Write | Host: install Samba + create SMB share" \
|
||||||
|
"12" "Write | Host: install NFS server + create export" \
|
||||||
|
" " "────────── REMOVE (destructive, defaults to No) ──────────" \
|
||||||
|
"13" "Remove | Proxmox: remove storage definition" \
|
||||||
|
"14" "Remove | LXC: remove mountpoint (pct set -delete mpX)" \
|
||||||
|
"0" "Exit" 3>&1 1>&2 2>&3) || break
|
||||||
|
|
||||||
|
# '|| true' so a cancelled sub-dialog (whiptail returns non-zero) returns to the
|
||||||
|
# menu instead of tripping 'set -e' and exiting the whole script.
|
||||||
|
case "$choice" in
|
||||||
|
1) manual_smb_test || true ;;
|
||||||
|
2) manual_nfs_test || true ;;
|
||||||
|
3) manual_iscsi_discovery || true ;;
|
||||||
|
4) list_lxc_mountpoints || true ;;
|
||||||
|
5) show_status || true ;;
|
||||||
|
6) add_smb_storage || true ;;
|
||||||
|
7) add_nfs_storage || true ;;
|
||||||
|
8) add_iscsi_storage || true ;;
|
||||||
|
9) add_lvm_on_base_storage || true ;;
|
||||||
|
10) add_lxc_mountpoint || true ;;
|
||||||
|
11) host_create_samba_share || true ;;
|
||||||
|
12) host_create_nfs_export || true ;;
|
||||||
|
13) remove_storage || true ;;
|
||||||
|
14) remove_lxc_mountpoint || true ;;
|
||||||
|
0) break ;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
header_info
|
||||||
|
root_check
|
||||||
|
pve_check
|
||||||
|
require_pve
|
||||||
|
ensure_packages
|
||||||
|
confirm_start || exit 0
|
||||||
|
main_menu
|
||||||
|
|
||||||
|
header_info
|
||||||
|
msg_ok "Finished."
|
||||||
+83
-19
@@ -25,6 +25,7 @@ METHOD=""
|
|||||||
NSAPP="opnsense-vm"
|
NSAPP="opnsense-vm"
|
||||||
var_os="opnsense"
|
var_os="opnsense"
|
||||||
var_version="26.7"
|
var_version="26.7"
|
||||||
|
FREEBSD_MAJOR="15"
|
||||||
#
|
#
|
||||||
GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//')
|
GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//')
|
||||||
GEN_MAC_LAN=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//')
|
GEN_MAC_LAN=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//')
|
||||||
@@ -490,7 +491,7 @@ function advanced_settings() {
|
|||||||
fi
|
fi
|
||||||
echo -e "${DGN}Using LAN GATEWAY ADDRESS: ${BGN}$LAN_GW${CL}"
|
echo -e "${DGN}Using LAN GATEWAY ADDRESS: ${BGN}$LAN_GW${CL}"
|
||||||
fi
|
fi
|
||||||
if NETMASK=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a LAN netmmask (24 for example)" 8 58 $NETMASK --title "LAN NETMASK" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
if NETMASK=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a LAN netmask (24 for example)" 8 58 $NETMASK --title "LAN NETMASK" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||||
if [ -z $NETMASK ]; then
|
if [ -z $NETMASK ]; then
|
||||||
echo -e "${DGN}Netmask needs to be set if ip is not dhcp${CL}"
|
echo -e "${DGN}Netmask needs to be set if ip is not dhcp${CL}"
|
||||||
fi
|
fi
|
||||||
@@ -558,7 +559,7 @@ function advanced_settings() {
|
|||||||
else
|
else
|
||||||
exit-script
|
exit-script
|
||||||
fi
|
fi
|
||||||
if WAN_NETMASK=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a WAN netmmask (24 for example)" 8 58 $WAN_NETMASK --title "WAN NETMASK" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
if WAN_NETMASK=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a WAN netmask (24 for example)" 8 58 $WAN_NETMASK --title "WAN NETMASK" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||||
if [ -z $WAN_NETMASK ]; then
|
if [ -z $WAN_NETMASK ]; then
|
||||||
echo -e "${DGN}WAN Netmask needs to be set if ip is not dhcp${CL}"
|
echo -e "${DGN}WAN Netmask needs to be set if ip is not dhcp${CL}"
|
||||||
fi
|
fi
|
||||||
@@ -574,7 +575,7 @@ function advanced_settings() {
|
|||||||
else
|
else
|
||||||
exit-script
|
exit-script
|
||||||
fi
|
fi
|
||||||
if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a WAN MAC Address" 8 58 $GEN_MAC --title "WAN MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a LAN MAC Address" 8 58 $GEN_MAC --title "LAN MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||||
if [ -z $MAC1 ]; then
|
if [ -z $MAC1 ]; then
|
||||||
MAC="$GEN_MAC"
|
MAC="$GEN_MAC"
|
||||||
else
|
else
|
||||||
@@ -585,7 +586,7 @@ function advanced_settings() {
|
|||||||
exit-script
|
exit-script
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if MAC2=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a LAN MAC Address" 8 58 $GEN_MAC_LAN --title "LAN MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
if MAC2=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a WAN MAC Address" 8 58 $GEN_MAC_LAN --title "WAN MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||||
if [ -z $MAC2 ]; then
|
if [ -z $MAC2 ]; then
|
||||||
WAN_MAC="$GEN_MAC_LAN"
|
WAN_MAC="$GEN_MAC_LAN"
|
||||||
else
|
else
|
||||||
@@ -652,23 +653,26 @@ fi
|
|||||||
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
|
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
|
||||||
msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}."
|
msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}."
|
||||||
msg_info "Retrieving the URL for the OPNsense Qcow2 Disk Image"
|
msg_info "Retrieving the URL for the OPNsense Qcow2 Disk Image"
|
||||||
# Use latest stable FreeBSD amd64 qcow2 VM image (generic, not UFS/ZFS)
|
# Use latest stable FreeBSD amd64 qcow2 VM image matching FREEBSD_MAJOR
|
||||||
RELEASE_LIST="$(curl -s https://download.freebsd.org/releases/VM-IMAGES/ |
|
RELEASE_LIST="$(curl -s https://download.freebsd.org/releases/VM-IMAGES/ |
|
||||||
grep -Eo '[0-9]+\.[0-9]+-RELEASE' |
|
grep -Eo "${FREEBSD_MAJOR}\.[0-9]+-RELEASE" |
|
||||||
sort -Vr |
|
sort -Vr |
|
||||||
uniq)"
|
uniq)"
|
||||||
URL=""
|
URL=""
|
||||||
FREEBSD_VER=""
|
FREEBSD_VER=""
|
||||||
for ver in $RELEASE_LIST; do
|
for ver in $RELEASE_LIST; do
|
||||||
candidate="https://download.freebsd.org/releases/VM-IMAGES/${ver}/amd64/Latest/FreeBSD-${ver}-amd64.qcow2.xz"
|
# FreeBSD 15+ publishes separate -ufs/-zfs images instead of a generic one
|
||||||
if curl -fsI "$candidate" >/dev/null 2>&1; then
|
for variant in "" "-ufs" "-zfs"; do
|
||||||
FREEBSD_VER="$ver"
|
candidate="https://download.freebsd.org/releases/VM-IMAGES/${ver}/amd64/Latest/FreeBSD-${ver}-amd64${variant}.qcow2.xz"
|
||||||
URL="$candidate"
|
if curl -fsI "$candidate" >/dev/null 2>&1; then
|
||||||
break
|
FREEBSD_VER="$ver"
|
||||||
fi
|
URL="$candidate"
|
||||||
|
break 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
done
|
done
|
||||||
if [ -z "$URL" ]; then
|
if [ -z "$URL" ]; then
|
||||||
msg_error "Could not find generic FreeBSD amd64 qcow2 image (non-UFS/ZFS)."
|
msg_error "Could not find a FreeBSD ${FREEBSD_MAJOR}.x amd64 qcow2 image."
|
||||||
exit 115
|
exit 115
|
||||||
fi
|
fi
|
||||||
msg_ok "Download URL: ${CL}${BL}${URL}${CL}"
|
msg_ok "Download URL: ${CL}${BL}${URL}${CL}"
|
||||||
@@ -768,7 +772,7 @@ DESCRIPTION=$(
|
|||||||
cat <<EOF
|
cat <<EOF
|
||||||
<div align='center'>
|
<div align='center'>
|
||||||
<a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
|
<a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
|
||||||
<img src='https://raw.githubusercontent.com/michelroegl-brunner/ProxmoxVE/refs/heads/develop/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
|
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<h2 style='font-size: 24px; margin: 20px 0;'>OPNsense VM</h2>
|
<h2 style='font-size: 24px; margin: 20px 0;'>OPNsense VM</h2>
|
||||||
@@ -814,10 +818,70 @@ if [ -n "$WAN_BRG" ]; then
|
|||||||
msg_ok "WAN interface added"
|
msg_ok "WAN interface added"
|
||||||
sleep 5 # Brief pause after adding network interface
|
sleep 5 # Brief pause after adding network interface
|
||||||
fi
|
fi
|
||||||
send_line_to_vm "sh ./opnsense-bootstrap.sh.in -y -f -r 26.7"
|
# FreeBSD 15+ VM images ship the base system as pkgbase packages; the bootstrap's
|
||||||
|
# "delete all packages" step would remove the running base system (/bin/rm etc.)
|
||||||
|
# and brick the VM. Deregister them from the pkg db first - the files stay in
|
||||||
|
# place and OPNsense replaces base and kernel with its own sets afterwards.
|
||||||
|
send_line_to_vm "echo \"PRAGMA foreign_keys=ON; DELETE FROM packages WHERE name LIKE 'FreeBSD-%';\" | pkg shell"
|
||||||
|
sleep 5
|
||||||
|
send_line_to_vm "sh ./opnsense-bootstrap.sh.in -y -f -r ${var_version}"
|
||||||
msg_ok "OPNsense VM is being installed, do not close the terminal, or the installation will fail."
|
msg_ok "OPNsense VM is being installed, do not close the terminal, or the installation will fail."
|
||||||
#We need to wait for the OPNsense build proccess to finish, this takes a few minutes
|
# The bootstrap ends with an automatic reboot into OPNsense. While it runs the
|
||||||
sleep 1000
|
# console keeps changing (download progress, package installs); once the VM has
|
||||||
|
# settled at the login prompt the screen stays static. Poll a screendump hash
|
||||||
|
# and continue after 3 minutes without change, bounded by a floor (the build
|
||||||
|
# never finishes faster) and a ceiling for slow machines. If no screendump can
|
||||||
|
# be captured at all, fall back to a fixed wait.
|
||||||
|
SCREEN_PPM="${TEMP_DIR}/screen-${VMID}.ppm"
|
||||||
|
|
||||||
|
function screen_hash() {
|
||||||
|
# Remove the previous dump first: a stale file from an earlier successful
|
||||||
|
# dump must not simulate a static screen when later dumps start failing.
|
||||||
|
# Note: "qm monitor" is unusable here - its readline attaches to /dev/tty
|
||||||
|
# even with piped stdin and captures the terminal, so use the API instead.
|
||||||
|
rm -f "$SCREEN_PPM"
|
||||||
|
timeout 10 pvesh create /nodes/$(hostname -s)/qemu/$VMID/monitor --command "screendump ${SCREEN_PPM}" >/dev/null 2>&1 || true
|
||||||
|
md5sum "$SCREEN_PPM" 2>/dev/null | cut -d' ' -f1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
build_elapsed=300
|
||||||
|
build_stable=0
|
||||||
|
screen_ok=0
|
||||||
|
hash_a=""
|
||||||
|
hash_b=""
|
||||||
|
sleep 300
|
||||||
|
while [ $build_stable -lt 6 ] && [ $build_elapsed -lt 2400 ]; do
|
||||||
|
sleep 30
|
||||||
|
build_elapsed=$((build_elapsed + 30))
|
||||||
|
new_hash=$(screen_hash)
|
||||||
|
if [ -n "$new_hash" ]; then
|
||||||
|
screen_ok=1
|
||||||
|
# The login prompt cursor may blink: a screen alternating between the same
|
||||||
|
# two frames (A/B/A/B) counts as stable, anything new resets the counter
|
||||||
|
if [ "$new_hash" = "$hash_a" ] || [ "$new_hash" = "$hash_b" ]; then
|
||||||
|
build_stable=$((build_stable + 1))
|
||||||
|
else
|
||||||
|
build_stable=0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
build_stable=0
|
||||||
|
fi
|
||||||
|
hash_b="$hash_a"
|
||||||
|
hash_a="$new_hash"
|
||||||
|
if [ -n "$new_hash" ]; then
|
||||||
|
echo -e "${DGN}Waiting for OPNsense build: ${YW}$((build_elapsed / 60))min elapsed, screen ${new_hash:0:8}, stable ${build_stable}/6${CL}"
|
||||||
|
else
|
||||||
|
echo -e "${DGN}Waiting for OPNsense build: ${YW}$((build_elapsed / 60))min elapsed, screendump failed${CL}"
|
||||||
|
fi
|
||||||
|
# No working screendump after several attempts: fixed wait instead
|
||||||
|
if [ $screen_ok -eq 0 ] && [ $build_elapsed -ge 480 ]; then
|
||||||
|
msg_error "Console screendump not available on this system - falling back to a fixed wait (12 minutes)."
|
||||||
|
sleep 720
|
||||||
|
build_elapsed=$((build_elapsed + 720))
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
msg_ok "OPNsense build finished after $((build_elapsed / 60)) minutes"
|
||||||
send_line_to_vm "root"
|
send_line_to_vm "root"
|
||||||
send_line_to_vm "opnsense"
|
send_line_to_vm "opnsense"
|
||||||
send_line_to_vm "2"
|
send_line_to_vm "2"
|
||||||
@@ -855,8 +919,8 @@ if [ -n "$WAN_BRG" ] && [ "$WAN_IP_ADDR" != "" ]; then
|
|||||||
send_line_to_vm "2"
|
send_line_to_vm "2"
|
||||||
send_line_to_vm "n"
|
send_line_to_vm "n"
|
||||||
send_line_to_vm "${WAN_IP_ADDR}"
|
send_line_to_vm "${WAN_IP_ADDR}"
|
||||||
send_line_to_vm "${NETMASK}"
|
send_line_to_vm "${WAN_NETMASK}"
|
||||||
send_line_to_vm "${LAN_GW}"
|
send_line_to_vm "${WAN_GW}"
|
||||||
send_line_to_vm "n"
|
send_line_to_vm "n"
|
||||||
send_line_to_vm " "
|
send_line_to_vm " "
|
||||||
send_line_to_vm "n"
|
send_line_to_vm "n"
|
||||||
|
|||||||
Reference in New Issue
Block a user