Compare commits

..
Author SHA1 Message Date
MickLesk 049f2dd151 Name the admin account the way the docs do
createsuperuser was given admin, but the documented default user is archivebox -
which is what 0.7.4 produced, because its expect block sent an empty username and
Django then fell back to the OS user the command ran as.
2026-09-25 18:12:51 +02:00
MickLesk 57c299d068 Treat extractors as optional
@postlight/parser depends on a git URL and npm refuses to fetch those, so one
unreachable extractor aborted the whole install; readability and trafilatura cover
the same job and provision fine.
2026-09-25 16:14:52 +02:00
MickLesk 959a86983c Build the venv on the distro python and drop the obsolete extractors
A uv-managed interpreter lives where the service user cannot execute it, so every
archivebox call as that user died with 'Permission denied' while 0.9 drops privileges
on import; Debian 13 ships python3.13 in apt, so the venv now points at /usr/bin.
0.9 also provisions single-file and readability itself through abx-dl - the two npm
modules no longer exist - and it needs BASE_URL pinned plus tesseract, imagemagick,
ffmpeg, unzip and wget present, or archivebox install reaches for apt as a user that
has no root.
2026-09-25 14:30:30 +02:00
MickLesk eb1005e9b5 minor fixes 2026-09-25 13:43:44 +02:00
MickLesk 6a33fc45ad Let ArchiveBox reach 0.9 instead of staying on 0.7.4
uv pip install --system targets the container's Python 3.11, and every ArchiveBox
release from 0.9 on requires 3.13, so the resolver silently fell back to 0.7.4.
The package now lives in its own 3.13 venv, with the interpreter outside root's
home because ArchiveBox drops privileges while importing. The expect block is
gone: init and createsuperuser both run unattended now, and the server moved to
its new default port.
2026-09-25 11:30:16 +02:00
10 changed files with 662 additions and 304 deletions
+1 -11
View File
@@ -548,25 +548,15 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
## 2026-09-25
### 🆕 New Scripts
- RustFS ([#17499](https://github.com/community-scripts/ProxmoxVE/pull/17499))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Ignis: take the Obsidian version from release tag [@MickLesk](https://github.com/MickLesk) ([#17503](https://github.com/community-scripts/ProxmoxVE/pull/17503))
- #### 🔧 Refactor
- Refactor: Umbrel OS VM [@MickLesk](https://github.com/MickLesk) ([#17505](https://github.com/community-scripts/ProxmoxVE/pull/17505))
- Docker-LXC: remove Portainer installation from install [@MickLesk](https://github.com/MickLesk) ([#17493](https://github.com/community-scripts/ProxmoxVE/pull/17493))
### 💾 Core
- Incus: make prompts visible for end-user [@MickLesk](https://github.com/MickLesk) ([core#73](https://github.com/community-scripts/core/pull/73))
- incus: do not let an install script block on an invisible prompt [@MickLesk](https://github.com/MickLesk) ([core#71](https://github.com/community-scripts/core/pull/71))
- incus: do not let an install script block on an invisible prompt [@MickLesk](https://github.com/MickLesk) ([core#71](https://github.com/community-scripts/core/pull/71))
- tel: report progress from installs only, not updates [@MickLesk](https://github.com/MickLesk) ([core#72](https://github.com/community-scripts/core/pull/72))
## 2026-09-24
+48 -11
View File
@@ -13,7 +13,7 @@ var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-12}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
@@ -31,24 +31,61 @@ function update_script() {
exit
fi
NODE_VERSION="22" NODE_MODULE="@postlight/parser@latest,single-file-cli@latest" setup_nodejs
PYTHON_VERSION="3.13" setup_uv
NODE_VERSION="22" setup_nodejs
setup_uv
ensure_dependencies chromium
ensure_dependencies chromium ripgrep tesseract-ocr tesseract-ocr-eng imagemagick ffmpeg unzip wget
msg_info "Stopping Service"
systemctl stop archivebox
msg_ok "Stopped Service"
msg_info "Upgrading Playwright"
$STD uv pip install playwright --system --break-system-packages
$STD playwright install-deps chromium
msg_ok "Upgraded Playwright"
# Earlier installs used python3.11, which caps ArchiveBox at 0.7.4.
if [[ ! -x /opt/archivebox/venv/bin/archivebox ]]; then
msg_info "Moving ArchiveBox to its own Python 3.13 environment"
# The distro interpreter first: a uv-managed one lands where the service user cannot exec it.
local py="/usr/bin/python3.13"
if [[ ! -x "$py" ]]; then
ensure_dependencies python3.13 || true
fi
if [[ ! -x "$py" ]]; then
$STD uv python install --install-dir /opt/archivebox/python 3.13
py="$(UV_PYTHON_INSTALL_DIR=/opt/archivebox/python uv python find 3.13)"
fi
$STD uv venv --python "$py" /opt/archivebox/venv
# Keep whatever port this container already answers on, or a reverse proxy in front
# of it would silently stop resolving.
local port
port="$(awk -F: '/^ExecStart=/ {print $NF}' /etc/systemd/system/archivebox.service 2>/dev/null)"
[[ "$port" =~ ^[0-9]+$ ]] || port=5797
cat <<EOF >/etc/systemd/system/archivebox.service
[Unit]
Description=ArchiveBox Server
After=network.target
[Service]
User=archivebox
WorkingDirectory=/opt/archivebox/data
ExecStart=/opt/archivebox/venv/bin/archivebox server 0.0.0.0:${port}
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
msg_ok "Moved ArchiveBox to its own Python 3.13 environment (port ${port})"
fi
msg_info "Updating ArchiveBox"
$STD uv pip install --python /opt/archivebox/venv/bin/python --upgrade archivebox
chown -R archivebox:archivebox /opt/archivebox
# Without this a shell still reaches the old 0.7.4 entry point against a 0.9 collection.
ln -sf /opt/archivebox/venv/bin/archivebox /usr/local/bin/archivebox
cd /opt/archivebox/data
$STD uv pip install --system --break-system-packages --upgrade --no-reinstall archivebox
sudo -u archivebox archivebox init
$STD sudo -u archivebox /opt/archivebox/venv/bin/archivebox init
# npm refuses the git dependency behind @postlight/parser, so treat extractors as optional.
$STD sudo -u archivebox /opt/archivebox/venv/bin/archivebox install ||
msg_warn "Some extractors stayed unavailable - ArchiveBox runs without them"
msg_ok "Updated ArchiveBox"
msg_info "Starting Service"
@@ -65,4 +102,4 @@ description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW}Access it using the following URL:${CL}"
echo -e "${GATEWAY}${BGN}http://${IP}:8000/admin/login${CL}"
echo -e "${GATEWAY}${BGN}http://${IP}:5797/admin/${CL}"
-6
View File
@@ -1,6 +0,0 @@
____ __ ___________
/ __ \__ _______/ /_/ ____/ ___/
/ /_/ / / / / ___/ __/ /_ \__ \
/ _, _/ /_/ (__ ) /_/ __/ ___/ /
/_/ |_|\__,_/____/\__/_/ /____/
+3 -8
View File
@@ -49,14 +49,9 @@ function update_script() {
msg_ok "Built Ignis"
msg_info "Checking Obsidian Web Assets"
# From the release tag (0.8.13+obsidian.1.13.7): upstream renamed the Dockerfile variable.
OBSIDIAN_VERSION=""
[[ "${IGNIS_BUILD:-}" == *obsidian.* ]] && OBSIDIAN_VERSION="${IGNIS_BUILD##*obsidian.}"
if [[ ! "$OBSIDIAN_VERSION" =~ ^[0-9]+(\.[0-9]+)+$ ]]; then
OBSIDIAN_VERSION="$(grep -ohP '(OBSIDIAN_VERSION|IGNIS_OBSIDIAN_PIN)=\K[0-9.]+' /opt/ignis/apps/ignis-server/Dockerfile 2>/dev/null | head -n1 || true)"
fi
[[ "$OBSIDIAN_VERSION" =~ ^[0-9]+(\.[0-9]+)+$ ]] || msg_warn "Could not determine the paired Obsidian version - keeping the installed assets"
if [[ "$OBSIDIAN_VERSION" =~ ^[0-9]+(\.[0-9]+)+$ && "$OBSIDIAN_VERSION" != "$(cat /opt/ignis_data/obsidian.version 2>/dev/null)" ]]; then
OBSIDIAN_VERSION=$(grep -oP 'OBSIDIAN_VERSION=\K[0-9.]+' /opt/ignis/apps/ignis-server/Dockerfile | head -n1)
[[ -z "$OBSIDIAN_VERSION" ]] && OBSIDIAN_VERSION="$(cat /opt/ignis_data/obsidian.version 2>/dev/null)"
if [[ -n "$OBSIDIAN_VERSION" && "$OBSIDIAN_VERSION" != "$(cat /opt/ignis_data/obsidian.version 2>/dev/null)" ]]; then
rm -rf /opt/ignis_data/obsidian-app
mkdir -p /opt/ignis_data/obsidian-app
curl -fsSL -o /tmp/obsidian.asar.gz "https://github.com/obsidianmd/obsidian-releases/releases/download/v${OBSIDIAN_VERSION}/obsidian-${OBSIDIAN_VERSION}.asar.gz"
-59
View File
@@ -1,59 +0,0 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_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")
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/rustfs/rustfs
APP="RustFS"
var_tags="${var_tags:-storage;s3}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /etc/default/rustfs ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "rustfs" "rustfs/rustfs"; then
msg_info "Stopping Service"
systemctl stop rustfs
msg_ok "Stopped Service"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "rustfs" "rustfs/rustfs" "prebuild" "latest" "/opt/rustfs" "rustfs-linux-$(arch_resolve x86_64 aarch64)-gnu-*.zip"
chmod +x /opt/rustfs/rustfs
msg_info "Starting Service"
systemctl start rustfs
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW}Console:${CL}"
echo -e "${GATEWAY}${BGN}http://${IP}:9001${CL}"
echo -e "${INFO}${YW}S3 API on port 9000 - keys are in /etc/default/rustfs${CL}"
+40 -46
View File
@@ -14,63 +14,57 @@ network_check
update_os
msg_info "Installing Dependencies"
# ArchiveBox resolves these through its env provider, so it never has to reach for apt itself.
$STD apt-get install -y \
git \
expect \
libssl-dev \
libldap2-dev \
libsasl2-dev \
procps \
dnsutils \
chromium \
ripgrep \
chromium
tesseract-ocr \
tesseract-ocr-eng \
imagemagick \
ffmpeg \
unzip \
wget \
python3.13
msg_ok "Installed Dependencies"
msg_info "Installing Python Dependencies"
$STD apt-get install -y \
python3-ldap \
python3-msgpack \
python3-regex
msg_ok "Installed Python Dependencies"
NODE_VERSION="22" NODE_MODULE="@postlight/parser@latest,single-file-cli@latest" setup_nodejs
PYTHON_VERSION="3.13" setup_uv
msg_info "Installing Playwright"
$STD uv pip install playwright --system --break-system-packages
$STD playwright install-deps chromium
msg_ok "Installed Playwright"
# No NODE_MODULE: 0.9 pulls single-file and readability itself, and postlight-parser is gone.
NODE_VERSION="22" setup_nodejs
setup_uv
msg_info "Installing ArchiveBox"
mkdir -p /opt/archivebox/{data,.npm,.cache,.local}
mkdir -p /opt/archivebox/data
$STD adduser --system --shell /bin/bash --gecos 'Archive Box User' --group --disabled-password --home /home/archivebox archivebox
chown -R archivebox:archivebox /opt/archivebox/{data,.npm,.cache,.local}
chmod -R 755 /opt/archivebox/data
$STD uv pip install archivebox --system --break-system-packages
cd /opt/archivebox/data
expect <<EOF
set timeout -1
log_user 0
spawn sudo -u archivebox playwright install chromium
spawn sudo -u archivebox archivebox setup
expect "Username"
send "\r"
expect "Email address"
send "\r"
expect "Password"
send "community-scripts.org\r"
expect "Password (again)"
send "community-scripts.org\r"
expect eof
EOF
# The distro interpreter: a uv-managed one lands where the service user cannot execute it.
$STD uv venv --python /usr/bin/python3.13 /opt/archivebox/venv
$STD uv pip install --python /opt/archivebox/venv/bin/python archivebox
ln -sf /opt/archivebox/venv/bin/archivebox /usr/local/bin/archivebox
chown -R archivebox:archivebox /opt/archivebox
msg_ok "Installed ArchiveBox"
msg_info "Initializing ArchiveBox"
cd /opt/archivebox/data
$STD sudo -u archivebox /opt/archivebox/venv/bin/archivebox init
$STD sudo -u archivebox env \
DJANGO_SUPERUSER_USERNAME=archivebox \
DJANGO_SUPERUSER_EMAIL=archivebox@archivebox.local \
DJANGO_SUPERUSER_PASSWORD=community-scripts.org \
/opt/archivebox/venv/bin/archivebox manage createsuperuser --noinput
# Without a pinned canonical URL the admin greets every visitor with a red banner.
$STD sudo -u archivebox /opt/archivebox/venv/bin/archivebox config --set "BASE_URL=http://$(get_ip):5797"
msg_ok "Initialized ArchiveBox"
msg_info "Installing Extractors"
# One unreachable extractor must not sink the install: @postlight/parser pulls a git
# dependency that npm refuses, and readability covers the same job.
if $STD sudo -u archivebox /opt/archivebox/venv/bin/archivebox install; then
msg_ok "Installed Extractors"
else
msg_warn "Some extractors stayed unavailable - ArchiveBox runs without them"
fi
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/archivebox.service
[Unit]
@@ -80,7 +74,7 @@ After=network.target
[Service]
User=archivebox
WorkingDirectory=/opt/archivebox/data
ExecStart=/usr/local/bin/archivebox server 0.0.0.0:8000
ExecStart=/opt/archivebox/venv/bin/archivebox server 0.0.0.0:5797
Restart=always
[Install]
+2 -10
View File
@@ -32,16 +32,8 @@ msg_ok "Built Ignis"
msg_info "Downloading Obsidian Web Assets"
mkdir -p /opt/ignis_data/{obsidian-app,vaults,data}
# From the release tag (0.8.13+obsidian.1.13.7): upstream renamed the Dockerfile variable.
OBSIDIAN_VERSION=""
[[ "${IGNIS_BUILD:-}" == *obsidian.* ]] && OBSIDIAN_VERSION="${IGNIS_BUILD##*obsidian.}"
if [[ ! "$OBSIDIAN_VERSION" =~ ^[0-9]+(\.[0-9]+)+$ ]]; then
OBSIDIAN_VERSION="$(grep -ohP '(OBSIDIAN_VERSION|IGNIS_OBSIDIAN_PIN)=\K[0-9.]+' /opt/ignis/apps/ignis-server/Dockerfile 2>/dev/null | head -n1 || true)"
fi
if [[ ! "$OBSIDIAN_VERSION" =~ ^[0-9]+(\.[0-9]+)+$ ]]; then
msg_error "Could not determine which Obsidian version this Ignis build pairs with"
exit 1
fi
OBSIDIAN_VERSION=$(grep -oP 'OBSIDIAN_VERSION=\K[0-9.]+' /opt/ignis/apps/ignis-server/Dockerfile | head -n1)
[[ -z "$OBSIDIAN_VERSION" ]] && OBSIDIAN_VERSION="1.12.7"
curl -fsSL -o /tmp/obsidian.asar.gz "https://github.com/obsidianmd/obsidian-releases/releases/download/v${OBSIDIAN_VERSION}/obsidian-${OBSIDIAN_VERSION}.asar.gz"
gunzip -f /tmp/obsidian.asar.gz
$STD asar extract /tmp/obsidian.asar /opt/ignis_data/obsidian-app
-61
View File
@@ -1,61 +0,0 @@
#!/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: https://github.com/rustfs/rustfs
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
mkdir -p /opt/rustfs_data/{data,logs}
fetch_and_deploy_gh_release "rustfs" "rustfs/rustfs" "prebuild" "latest" "/opt/rustfs" "rustfs-linux-$(arch_resolve x86_64 aarch64)-gnu-*.zip"
chmod +x /opt/rustfs/rustfs
msg_info "Configuring RustFS"
RUSTFS_ACCESS_KEY=$(openssl rand -hex 8)
RUSTFS_SECRET_KEY=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
cat <<EOF >/etc/default/rustfs
RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY}
RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY}
RUSTFS_VOLUMES=/opt/rustfs_data/data
RUSTFS_ADDRESS=:9000
RUSTFS_CONSOLE_ADDRESS=:9001
RUSTFS_CONSOLE_ENABLE=true
RUSTFS_OBS_LOGGER_LEVEL=error
RUSTFS_OBS_LOG_DIRECTORY=/opt/rustfs_data/logs/
EOF
chmod 600 /etc/default/rustfs
msg_ok "Configured RustFS"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/rustfs.service
[Unit]
Description=RustFS Object Storage
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/rustfs_data
EnvironmentFile=/etc/default/rustfs
ExecStart=/opt/rustfs/rustfs
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now rustfs
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
-6
View File
@@ -1,6 +0,0 @@
__ __ __ __ ____ _____
/ / / /___ ___ / /_ ________ / / / __ \/ ___/
/ / / / __ `__ \/ __ \/ ___/ _ \/ / / / / /\__ \
/ /_/ / / / / / / /_/ / / / __/ / / /_/ /___/ /
\____/_/ /_/ /_/_.___/_/ \___/_/ \____//____/
+568 -86
View File
@@ -1,43 +1,280 @@
#!/usr/bin/env bash
#!/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
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/pve/vm-core.func")
load_functions
source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)
function header_info {
clear
cat <<"EOF"
__ __ __ __ ____ _____ _ ____ ___
/ / / /___ ___ / /_ ________ / / / __ \/ ___/ | | / / |/ /
/ / / / __ `__ \/ __ \/ ___/ _ \/ / / / / /\__ \ | | / / /|_/ /
/ /_/ / / / / / / /_/ / / / __/ / / /_/ /___/ / | |/ / / / /
\____/_/ /_/ /_/_.___/_/ \___/_/ \____//____/ |___/_/ /_/
EOF
}
header_info
echo -e "\n Loading..."
GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//')
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)"
METHOD=""
APP="Umbrel OS"
APP_TYPE="vm"
NSAPP="umbrel-os-vm"
var_os="umbrel-os"
var_version="n.d."
YW=$(echo "\033[33m")
BL=$(echo "\033[36m")
HA=$(echo "\033[1;34m")
RD=$(echo "\033[01;31m")
BGN=$(echo "\033[4;92m")
GN=$(echo "\033[1;92m")
DGN=$(echo "\033[32m")
CL=$(echo "\033[m")
BOLD=$(echo "\033[1m")
BFR="\\r\\033[K"
HOLD=" "
TAB=" "
CM="${TAB}✔️${TAB}${CL}"
CROSS="${TAB}✖️${TAB}${CL}"
INFO="${TAB}💡${TAB}${CL}"
OS="${TAB}🖥️${TAB}${CL}"
CONTAINERTYPE="${TAB}📦${TAB}${CL}"
DISKSIZE="${TAB}💾${TAB}${CL}"
CPUCORE="${TAB}🧠${TAB}${CL}"
RAMSIZE="${TAB}🛠️${TAB}${CL}"
CONTAINERID="${TAB}🆔${TAB}${CL}"
HOSTNAME="${TAB}🏠${TAB}${CL}"
BRIDGE="${TAB}🌉${TAB}${CL}"
GATEWAY="${TAB}🌐${TAB}${CL}"
DEFAULT="${TAB}⚙️${TAB}${CL}"
MACADDRESS="${TAB}🔗${TAB}${CL}"
VLANTAG="${TAB}🏷️${TAB}${CL}"
CREATING="${TAB}🚀${TAB}${CL}"
ADVANCED="${TAB}🧩${TAB}${CL}"
THIN="discard=on,ssd=1,"
header_info
echo -e "\n Loading..."
set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT
trap 'post_update_to_api "failed" "130"' SIGINT
trap 'post_update_to_api "failed" "143"' SIGTERM
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
function error_handler() {
local exit_code="$?"
local line_number="$1"
local command="$2"
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
post_update_to_api "failed" "${exit_code}"
echo -e "\n$error_message\n"
cleanup_vmid
}
vm_require_arch amd64
function get_valid_nextid() {
local try_id
try_id=$(pvesh get /cluster/nextid)
while true; do
if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then
try_id=$((try_id + 1))
continue
fi
if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then
try_id=$((try_id + 1))
continue
fi
break
done
echo "$try_id"
}
function cleanup_vmid() {
if qm status $VMID &>/dev/null; then
qm stop $VMID &>/dev/null
qm destroy $VMID &>/dev/null
fi
}
function cleanup() {
local exit_code=$?
popd >/dev/null
if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
if [[ $exit_code -eq 0 ]]; then
post_update_to_api "done" "none"
else
post_update_to_api "failed" "$exit_code"
fi
fi
rm -rf $TEMP_DIR
}
TEMP_DIR=$(mktemp -d)
pushd $TEMP_DIR >/dev/null
if whiptail --backtitle "Proxmox VE Helper Scripts" --title "Umbrel OS VM" --yesno "This will create a New Umbrel OS VM. Proceed?" 10 58; then
:
else
header_info && echo -e "${CROSS}${RD}User exited script${CL}\n" && exit
fi
function msg_info() {
local msg="$1"
echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}"
}
function msg_ok() {
local msg="$1"
echo -e "${BFR}${CM}${GN}${msg}${CL}"
}
function msg_error() {
local msg="$1"
echo -e "${BFR}${CROSS}${RD}${msg}${CL}"
}
function check_root() {
if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then
clear
msg_error "Please run this script as root."
echo -e "\nExiting..."
sleep 2
exit
fi
}
# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.
# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.2
pve_check() {
local PVE_VER
PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')"
# Check for Proxmox VE 8.x: allow 8.0–8.9
if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then
local MINOR="${BASH_REMATCH[1]}"
if ((MINOR < 0 || MINOR > 9)); then
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 8.0 – 8.9"
exit 105
fi
return 0
fi
# Check for Proxmox VE 9.x: allow 9.0 and 9.2
if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then
local MINOR="${BASH_REMATCH[1]}"
if ((MINOR < 0 || MINOR > 2)); then
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 9.0 – 9.2"
exit 105
fi
return 0
fi
# All other unsupported versions
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.2"
exit 105
}
function arch_check() {
if [ "$(dpkg --print-architecture)" != "amd64" ]; then
echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n"
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
echo -e "Exiting..."
sleep 2
exit
fi
}
function ssh_check() {
if command -v pveversion >/dev/null 2>&1; then
if [ -n "${SSH_CLIENT:+x}" ]; then
if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then
echo "you've been warned"
else
clear
exit
fi
fi
fi
}
function exit-script() {
clear
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
exit
}
# Ensure pv is installed or abort with instructions
function ensure_pv() {
if ! command -v pv &>/dev/null; then
msg_info "Installing required package: pv"
if ! apt-get update -qq &>/dev/null || ! apt-get install -y pv &>/dev/null; then
msg_error "Failed to install pv automatically."
echo -e "\nPlease run manually on the Proxmox host:\n apt install pv\n"
exit 237
fi
msg_ok "Installed pv"
fi
}
# Download an .xz file and validate it
# Args: $1=url $2=cache_file
function download_and_validate_xz() {
local url="$1"
local file="$2"
# If file exists, check validity
if [[ -s "$file" ]]; then
if xz -t "$file" &>/dev/null; then
msg_ok "Using cached image $(basename "$file")"
return 0
else
msg_error "Cached file $(basename "$file") is corrupted. Deleting and retrying download..."
rm -f "$file"
fi
fi
# Download fresh file
msg_info "Downloading image: $(basename "$file")"
if ! curl -fSL -o "$file" "$url"; then
msg_error "Download failed: $url"
rm -f "$file"
exit 115
fi
# Validate again
if ! xz -t "$file" &>/dev/null; then
msg_error "Downloaded file $(basename "$file") is corrupted. Please try again later."
rm -f "$file"
exit 115
fi
msg_ok "Downloaded and validated $(basename "$file")"
}
# Extract .xz with pv
# Args: $1=cache_file $2=target_img
function extract_xz_with_pv() {
set -o pipefail
local file="$1"
local target="$2"
msg_info "Decompressing $(basename "$file") to $target"
if ! xz -dc "$file" | pv -N "Extracting" >"$target"; then
msg_error "Failed to extract $file"
rm -f "$target"
exit 115
fi
msg_ok "Decompressed to $target"
}
function default_settings() {
vm_apply_machine_type "q35"
VMID=$(get_valid_nextid)
MACHINE=" -machine q35"
FORMAT=""
DISK_SIZE="32G"
HN="umbrelos"
CPU_TYPE=""
@@ -49,27 +286,223 @@ function default_settings() {
MTU=""
START_VM="yes"
METHOD="default"
vm_echo_default_settings
echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}"
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}"
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}"
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}"
echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}"
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}"
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}"
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}"
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}"
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}"
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}"
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}"
echo -e "${CREATING}${BOLD}${DGN}Creating a Umbrel OS VM using the above default settings${CL}"
}
function advanced_settings() {
METHOD="advanced"
vm_prompt_vmid "${VMID:-$(get_valid_nextid)}"
vm_prompt_machine_type "q35"
vm_prompt_disk_size "32G"
vm_prompt_disk_cache "none"
vm_prompt_hostname "umbrelos"
vm_prompt_cpu_model "kvm64"
vm_prompt_cpu_cores "2"
vm_prompt_ram "4096"
vm_prompt_bridge "vmbr0"
vm_prompt_mac "$GEN_MAC"
vm_prompt_vlan
vm_prompt_mtu
vm_prompt_verbose "no"
vm_prompt_start_vm "yes"
[ -z "${VMID:-}" ] && VMID=$(get_valid_nextid)
while true; do
if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 $VMID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$VMID" ]; then
VMID=$(get_valid_nextid)
fi
if pct status "$VMID" &>/dev/null || qm status "$VMID" &>/dev/null; then
echo -e "${CROSS}${RD} ID $VMID is already in use${CL}"
sleep 2
continue
fi
echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}"
break
else
exit-script
fi
done
if vm_confirm_advanced_settings "Ready to create a Umbrel OS VM?"; then
if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Machine Type" 10 58 2 \
"q35" "Modern (PCIe, UEFI, default)" ON \
"i440fx" "Legacy (older compatibility)" OFF \
3>&1 1>&2 2>&3); then
if [ "$MACH" = "q35" ]; then
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}"
FORMAT=""
MACHINE=" -machine q35"
else
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}"
FORMAT=",efitype=4m"
MACHINE=""
fi
else
exit-script
fi
if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GiB (e.g., 10, 20)" 8 58 "$DISK_SIZE" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ')
if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then
DISK_SIZE="${DISK_SIZE}G"
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}"
elif [[ "$DISK_SIZE" =~ ^[0-9]+G$ ]]; then
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}"
else
echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}"
exit-script
fi
else
exit-script
fi
if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \
"0" "None (Default)" ON \
"1" "Write Through" OFF \
3>&1 1>&2 2>&3); then
if [ $DISK_CACHE = "1" ]; then
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}"
DISK_CACHE="cache=writethrough,"
else
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}"
DISK_CACHE=""
fi
else
exit-script
fi
if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 umbrelos --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $VM_NAME ]; then
HN="umbrelos"
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
else
HN=$(echo "${VM_NAME,,}" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')
if [ "$HN" != "${VM_NAME,,}" ]; then
whiptail --backtitle "Proxmox VE Helper Scripts" --title "HOSTNAME ADJUSTED" --msgbox "Invalid characters detected. Hostname has been adjusted to:\n\n $HN" 10 58
fi
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
fi
else
exit-script
fi
if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose CPU Model" --cancel-button Exit-Script 10 58 2 \
"KVM64" "Default – safe for migration/compatibility" ON \
"Host" "Use host CPU features (faster, no migration)" OFF \
3>&1 1>&2 2>&3); then
case "$CPU_TYPE1" in
"Host")
echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}"
CPU_TYPE=" -cpu host"
;;
*)
echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}"
CPU_TYPE=""
;;
esac
else
exit-script
fi
while true; do
if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 2 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$CORE_COUNT" ]; then CORE_COUNT="2"; fi
if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "CPU Cores must be a positive integer (e.g., 2)." 8 58
else
exit-script
fi
done
while true; do
if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 2048 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$RAM_SIZE" ]; then RAM_SIZE="2048"; fi
if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "RAM Size must be a positive integer in MiB (e.g., 2048)." 8 58
else
exit-script
fi
done
if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Bridge" 8 58 vmbr0 --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $BRG ]; then
BRG="vmbr0"
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
else
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
fi
else
exit-script
fi
while true; do
if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address" 8 58 $GEN_MAC --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$MAC1" ]; then
MAC="$GEN_MAC"
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}"
break
fi
if [[ "$MAC1" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then
MAC="$MAC1"
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF)." 8 58
else
exit-script
fi
done
while true; do
if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for default)" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$VLAN1" ]; then
VLAN1="Default"
VLAN=""
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}"
break
fi
if [[ "$VLAN1" =~ ^[0-9]+$ ]] && [ "$VLAN1" -ge 1 ] && [ "$VLAN1" -le 4094 ]; then
VLAN=",tag=$VLAN1"
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58
else
exit-script
fi
done
while true; do
if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default)" 8 58 --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$MTU1" ]; then
MTU1="Default"
MTU=""
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}"
break
fi
if [[ "$MTU1" =~ ^[0-9]+$ ]] && [ "$MTU1" -ge 576 ] && [ "$MTU1" -le 65520 ]; then
MTU=",mtu=$MTU1"
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58
else
exit-script
fi
done
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}"
START_VM="yes"
else
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}"
START_VM="no"
fi
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create a Umbrel OS VM?" --no-button Do-Over 10 58); then
echo -e "${CREATING}${BOLD}${DGN}Creating a Umbrel OS VM using the above advanced settings${CL}"
else
header_info
@@ -78,79 +511,128 @@ function advanced_settings() {
fi
}
function start_script() {
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "Use Default Settings?" --no-button Advanced 10 58); then
header_info
echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings${CL}"
default_settings
else
header_info
echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}"
advanced_settings
fi
}
vm_preflight
vm_start_script "Use Default Settings?\n\nDefaults:\n• 2 CPU Cores\n• 4 GB RAM\n• 32 GB Disk" 13 58
check_root
arch_check
pve_check
ssh_check
ensure_pv
start_script
post_to_api_vm
vm_select_storage "$HN"
msg_info "Validating Storage"
STORAGE_MENU=()
while read -r tag type free; do
ITEM="Type: $type Free: $free"
STORAGE_MENU+=("$tag" "$ITEM" "OFF")
done < <(pvesm status -content images | awk 'NR>1 {printf "%s %s %s\n", $1, $2, $6}')
if [ ${#STORAGE_MENU[@]} -eq 0 ]; then
msg_error "Unable to detect a valid storage location."
exit 119
elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then
STORAGE=${STORAGE_MENU[0]}
else
while [ -z "${STORAGE:+x}" ]; do
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
"Which storage pool would you like to use for ${HN}?\nTo make a selection, use the Spacebar.\n" \
16 70 6 \
"${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3)
done
fi
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
msg_info "Retrieving the URL for the Umbrel OS installer ISO"
UMBREL_RELEASE="$(curl -fsSL --max-time 20 https://api.umbrel.com/latest-release 2>/dev/null |
sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
[[ -z "$UMBREL_RELEASE" ]] && UMBREL_RELEASE="latest"
var_version="$UMBREL_RELEASE"
msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}."
URL="https://download.umbrel.com/release/${UMBREL_RELEASE}/umbrelos-amd64-usb-installer.iso"
# The upstream file name is the same for every release, so the version goes into
# the cached name -- otherwise the cache serves 1.7.4 to someone asking for 2.0.
ISO_NAME="umbrelos-${UMBREL_RELEASE}-amd64-usb-installer.iso"
CACHE_DIR="/var/lib/vz/template/iso"
CACHE_FILE="${CACHE_DIR}/${ISO_NAME}"
mkdir -p "$CACHE_DIR"
msg_ok "${CL}${BL}${URL}${CL}"
URL="https://download.umbrel.com/release/latest/umbrelos-amd64.img.xz"
CACHE_DIR="/var/lib/vz/template/cache"
CACHE_FILE="$CACHE_DIR/$(basename "$URL")"
FILE_IMG="/var/lib/vz/template/tmp/${CACHE_FILE##*/%.xz}"
# download.umbrel.com answers 307 for any name at all, so a redirect proves
# nothing about the file existing. Size is what separates an ISO from a 404 page.
msg_info "Downloading the Umbrel OS installer ISO (approximately 1.8 GB)"
vm_fetch_image "$URL" "$CACHE_FILE" --cache --min-bytes $((1024 * 1024 * 1024)) || exit 115
mkdir -p "$CACHE_DIR" "$(dirname "$FILE_IMG")"
msg_info "Creating a Umbrel OS VM"
# Umbrel requires EFI: the installer ISO has no legacy boot path. Its own
# console runs on tty1, so this VM is driven through noVNC, not the serial line.
qm create "$VMID"${MACHINE} -bios ovmf -agent enabled=1 -tablet 0 -localtime 1 ${CPU_TYPE} \
download_and_validate_xz "$URL" "$CACHE_FILE"
qm create $VMID${MACHINE} -bios ovmf -agent 1 -tablet 0 -localtime 1 ${CPU_TYPE} \
-cores "$CORE_COUNT" -memory "$RAM_SIZE" -name "$HN" -tags community-script \
-net0 "virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU" -onboot 1 -ostype l26 -scsihw virtio-scsi-pci \
-efidisk0 "${STORAGE}:1,efitype=4m,pre-enrolled-keys=0" \
-scsi0 "${STORAGE}:${DISK_SIZE%G},${DISK_CACHE:-}${THIN%,}" \
-cdrom "local:iso/${ISO_NAME}" -boot order='scsi0;ide2' >/dev/null
-net0 "virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU" -onboot 1 -ostype l26 -scsihw virtio-scsi-pci >/dev/null
extract_xz_with_pv "$CACHE_FILE" "$FILE_IMG"
if qm disk import --help >/dev/null 2>&1; then
IMPORT_CMD=(qm disk import)
else
IMPORT_CMD=(qm importdisk)
fi
IMPORT_OUT="$("${IMPORT_CMD[@]}" "$VMID" "$FILE_IMG" "$STORAGE" --format raw 2>&1 || true)"
DISK_REF="$(printf '%s\n' "$IMPORT_OUT" | sed -n "s/.*imported disk '\([^']\+\)'.*/\1/p" | tr -d "\r\"'")"
[[ -z "$DISK_REF" ]] && DISK_REF="$(pvesm list "$STORAGE" | awk -v id="$VMID" '$5 ~ ("vm-"id"-disk-") {print $1":"$5}' | sort | tail -n1)"
qm set $VMID \
--efidisk0 ${STORAGE}:0,efitype=4m \
--scsi0 ${DISK_REF},ssd=1,discard=on \
--boot order=scsi0 \
--serial0 socket >/dev/null
qm set $VMID --agent enabled=1 >/dev/null
qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null
DESCRIPTION=$(
cat <<EOF
<div align='center'>
<a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a>
<h2 style='font-size: 24px; margin: 20px 0;'>Umbrel OS VM</h2>
<p style='margin: 16px 0;'>
<a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />
</a>
</p>
<span style='margin: 0 10px;'>
<i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>
</span>
</div>
EOF
)
qm set $VMID -description "$DESCRIPTION" >/dev/null
if whiptail --backtitle "Proxmox VE Helper Scripts" --title "Image Cache" \
--yesno "Keep downloaded Umbrel OS image for future VMs?\n\nFile: $CACHE_FILE" 10 70; then
msg_ok "Keeping cached image"
else
rm -f "$CACHE_FILE"
msg_ok "Deleted cached image"
fi
rm -f "$FILE_IMG"
set_description
msg_ok "Created a Umbrel OS VM ${CL}${BL}(${HN})"
if [[ "${VM_UNATTENDED:-0}" == "1" ]]; then
KEEP_IMAGE="${VM_KEEP_IMAGE:-yes}"
elif vm_dialog yesno "Image Cache" \
"Keep downloaded Umbrel OS installer ISO for future VMs?\n\nFile: $CACHE_FILE" 10 70; then
KEEP_IMAGE="yes"
else
KEEP_IMAGE="no"
fi
if [[ "$KEEP_IMAGE" == "yes" ]]; then
msg_ok "Keeping cached ISO"
else
msg_warn "The ISO is still attached to the VM, so it is removed after the install"
KEEP_IMAGE="no"
fi
if [ "$START_VM" == "yes" ]; then
msg_info "Starting Umbrel OS VM"
$STD qm start $VMID
qm start $VMID
msg_ok "Started Umbrel OS VM"
fi
post_update_to_api "done" "none"
echo -e "\n${INFO}${BOLD}${YW}Next Steps:${CL}"
echo -e "${TAB}1. Open the VM console in Proxmox (noVNC)"
echo -e "${TAB}2. The installer asks which storage device to install umbrelOS on."
echo -e "${TAB} Pick the ${BL}sda${CL} entry -- ${BL}sr0${CL} is the installer ISO itself"
echo -e "${TAB}3. Confirm, wait for it to finish, then press a key to power off"
echo -e "${TAB}4. Detach the ISO (${BL}qm set ${VMID} --ide2 none${CL}) and start the VM"
echo -e "${TAB}5. umbrelOS is then reachable at ${BL}http://umbrel.local${CL}"
if [[ "$KEEP_IMAGE" == "no" ]]; then
echo -e "${TAB} Delete ${BL}${CACHE_FILE}${CL} once the ISO is detached"
fi
msg_ok "Completed successfully!\n"