Compare commits

..

10 Commits

Author SHA1 Message Date
community-scripts-pr-app[bot]
632bfa43dc Update CHANGELOG.md (#17792)
Some checks are pending
Create Changelog Pull Request / update-changelog-pull-request (push) Waiting to run
Set state to is_deleted in pocketbase / delete-pocketbase-entry (push) Waiting to run
Sync ct/install to Incus / dispatch (push) Waiting to run
Update script timestamp on .sh changes / update-script-timestamp (push) Waiting to run
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-08 13:05:23 +00:00
community-scripts-pr-app[bot]
3d7c129646 Update CHANGELOG.md (#17786)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-08 11:49:57 +00:00
push-app-to-main[bot]
6be105b961 Unifi-OS-Server VM (#17752)
* Add unifi-os-server-vm (vm)

* Refactor UniFi OS Server VM setup script

Updated the script to set default OS and version, improved error handling, and modified the first-boot installer process.

---------

Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
Co-authored-by: CanbiZ (MickLesk) <47820557+MickLesk@users.noreply.github.com>
2026-10-08 13:49:27 +02:00
community-scripts-pr-app[bot]
de430e8131 Update CHANGELOG.md (#17781)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-08 09:50:22 +00:00
CanbiZ (MickLesk)
0de4640699 Refactor: Ubuntu VM (#17776)
* Refactor: Ubuntu VM

Updated licensing information and improved error handling. Refactored OS selection logic and cloud-init prompts.

* Update license URL in ubuntu-vm.sh
2026-10-08 11:49:54 +02:00
community-scripts-pr-app[bot]
ba752598eb Update CHANGELOG.md (#17780)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-08 09:47:35 +00:00
CanbiZ (MickLesk)
84d2c964e6 Immich: Pin to 3.3.0 / Update ML Python to 3.13 (#17770)
* Immich: pin v3.3.0 again

Reapplies #17759, reverted in #17767 because machine learning broke on 3.3.0; the fixes follow.

* Immich: run machine learning on Python 3.13

3.3.0 requires Python >=3.12 for machine learning and upstream builds both its CPU and OpenVINO images on 3.13. The CPU path still pinned 3.11, so uv sync failed on every update and install (#17762).

* Immich: stop when uv sync keeps failing

After three failed attempts the loop still reported the machine-learning step as done, so an update ended in "Updated successfully" with no usable venv and immich-ml failing on start. Exit with an error instead.

* Immich: only mention the HEIC patch when it applies

On 3.3.0 the sharp call it rewrites is gone, so every run printed "pattern not found, skipped" into the spinner line and then "Patched". Check for the pattern first and stay silent otherwise.

* BookOrbit: retry the client build when the bundler crashes

Since rolldown 1.2.8, vite build dies at random with SIGSEGV or SIGBUS
(rolldown#10860, closed upstream), which stopped the 3.3.0 update with exit
139 (#17753). Retry the client build up to three times before giving up.
2026-10-08 11:47:06 +02:00
community-scripts-pr-app[bot]
6ffb433d5d Update CHANGELOG.md (#17779)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-08 09:25:42 +00:00
push-app-to-main[bot]
a2474baaf1 Add zimaos-vm (vm) (#17778)
Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
2026-10-08 11:25:15 +02:00
community-scripts-pr-app[bot]
005cdcdb2b Update CHANGELOG.md (#17777)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-08 09:13:30 +00:00
7 changed files with 618 additions and 48 deletions

View File

@@ -561,17 +561,32 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
## 2026-10-08 ## 2026-10-08
### 🆕 New Scripts
- Unifi-OS-Server VM ([#17752](https://github.com/community-scripts/ProxmoxVE/pull/17752))
- ZimaOS VM ([#17778](https://github.com/community-scripts/ProxmoxVE/pull/17778))
### 🚀 Updated Scripts ### 🚀 Updated Scripts
- Revert "Immich: Pin to v3.3.0" [@MickLesk](https://github.com/MickLesk) ([#17767](https://github.com/community-scripts/ProxmoxVE/pull/17767)) - Revert "Immich: Pin to v3.3.0" [@MickLesk](https://github.com/MickLesk) ([#17767](https://github.com/community-scripts/ProxmoxVE/pull/17767))
- #### ✨ New Features
- Immich: Pin to 3.3.0 / Update ML Python to 3.13 [@MickLesk](https://github.com/MickLesk) ([#17770](https://github.com/community-scripts/ProxmoxVE/pull/17770))
- #### 🔧 Refactor
- Refactor: Ubuntu VM [@MickLesk](https://github.com/MickLesk) ([#17776](https://github.com/community-scripts/ProxmoxVE/pull/17776))
### 💾 Core ### 💾 Core
- VM's: run first-boot units without debconf dialogs [@MickLesk](https://github.com/MickLesk) ([core#121](https://github.com/community-scripts/core/pull/121)) - Tolerate a missing datacenter.cfg in vm_keyboard_default [@MickLesk](https://github.com/MickLesk) ([core#124](https://github.com/community-scripts/core/pull/124))
- Choose the VM keyboard layout and carry the host's timezone into prepared images [@MickLesk](https://github.com/MickLesk) ([core#120](https://github.com/community-scripts/core/pull/120)) - Take the ISO storage from the disk pool when it can hold ISOs [@MickLesk](https://github.com/MickLesk) ([core#122](https://github.com/community-scripts/core/pull/122))
- Remember a kept cached image until the upstream changes [@MickLesk](https://github.com/MickLesk) ([core#119](https://github.com/community-scripts/core/pull/119))
- tools: forge truncated release list [@MickLesk](https://github.com/MickLesk) ([core#118](https://github.com/community-scripts/core/pull/118))
- Shared VM helpers: disk import, releases, checksums, first boot, IP by MAC [@MickLesk](https://github.com/MickLesk) ([core#117](https://github.com/community-scripts/core/pull/117)) - Shared VM helpers: disk import, releases, checksums, first boot, IP by MAC [@MickLesk](https://github.com/MickLesk) ([core#117](https://github.com/community-scripts/core/pull/117))
- tools: forge truncated release list [@MickLesk](https://github.com/MickLesk) ([core#118](https://github.com/community-scripts/core/pull/118))
- Remember a kept cached image until the upstream changes [@MickLesk](https://github.com/MickLesk) ([core#119](https://github.com/community-scripts/core/pull/119))
- Choose the VM keyboard layout and carry the host's timezone into prepared images [@MickLesk](https://github.com/MickLesk) ([core#120](https://github.com/community-scripts/core/pull/120))
- VM's: run first-boot units without debconf dialogs [@MickLesk](https://github.com/MickLesk) ([core#121](https://github.com/community-scripts/core/pull/121))
## 2026-10-07 ## 2026-10-07

View File

@@ -48,7 +48,12 @@ function update_script() {
$STD corepack prepare "pnpm@${PNPM_VERSION}" --activate $STD corepack prepare "pnpm@${PNPM_VERSION}" --activate
$STD pnpm install --frozen-lockfile $STD pnpm install --frozen-lockfile
$STD pnpm --filter client run build-only # vite's bundler (rolldown) crashes at random on some builds, rolldown#10860.
for attempt in 1 2 3; do
$STD pnpm --filter client run build-only && break
[[ $attempt -eq 3 ]] && { msg_error "Client build failed three times"; exit 1; }
msg_warn "Client build failed (attempt $attempt), retrying"
done
$STD pnpm --filter server run build $STD pnpm --filter server run build
cp -r /opt/bookorbit/client/dist /opt/bookorbit/server/public cp -r /opt/bookorbit/client/dist /opt/bookorbit/server/public
mkdir -p /opt/bookorbit/server/migrations mkdir -p /opt/bookorbit/server/migrations

View File

@@ -77,7 +77,7 @@ EOF
BASE_DIR=${STAGING_DIR}/base-images BASE_DIR=${STAGING_DIR}/base-images
SOURCE_DIR=${STAGING_DIR}/image-source SOURCE_DIR=${STAGING_DIR}/image-source
cd /tmp cd /tmp
RELEASE="v3.2.4" RELEASE="v3.3.0"
if [[ -f ~/.intel_version ]]; then if [[ -f ~/.intel_version ]]; then
INTEL_SRC="https://raw.githubusercontent.com/immich-app/immich/${RELEASE}/machine-learning" INTEL_SRC="https://raw.githubusercontent.com/immich-app/immich/${RELEASE}/machine-learning"
curl -fsSL "${INTEL_SRC}/scripts/install-intel-runtime.sh" -o ./intel-runtime 2>/dev/null || curl -fsSL "${INTEL_SRC}/scripts/install-intel-runtime.sh" -o ./intel-runtime 2>/dev/null ||
@@ -274,12 +274,13 @@ EOF
msg_info "Updating Intel OpenVINO machine-learning" msg_info "Updating Intel OpenVINO machine-learning"
for attempt in $(seq 1 3); do for attempt in $(seq 1 3); do
$STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -Pnu immich uv sync --extra openvino --no-dev --active --link-mode copy -n -p "${ML_PYTHON}" --managed-python && break $STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -Pnu immich uv sync --extra openvino --no-dev --active --link-mode copy -n -p "${ML_PYTHON}" --managed-python && break
[[ $attempt -lt 3 ]] && msg_warn "uv sync attempt $attempt failed, retrying..." && sleep 10 [[ $attempt -eq 3 ]] && { msg_error "uv sync failed three times, the machine-learning environment was not built"; exit 1; }
msg_warn "uv sync attempt $attempt failed, retrying..." && sleep 10
done done
patchelf --clear-execstack "${VIRTUAL_ENV}/lib/python3.13/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-$(arch_resolve "x86_64" "aarch64")-linux-gnu.so" patchelf --clear-execstack "${VIRTUAL_ENV}/lib/python3.13/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-$(arch_resolve "x86_64" "aarch64")-linux-gnu.so"
msg_ok "Updated Intel OpenVINO machine-learning" msg_ok "Updated Intel OpenVINO machine-learning"
else else
ML_PYTHON="python3.11" ML_PYTHON="python3.13"
msg_info "Pre-installing Python ${ML_PYTHON} for machine-learning" msg_info "Pre-installing Python ${ML_PYTHON} for machine-learning"
for attempt in $(seq 1 3); do for attempt in $(seq 1 3); do
$STD sudo --preserve-env=VIRTUAL_ENV -Pnu immich uv python install "${ML_PYTHON}" && break $STD sudo --preserve-env=VIRTUAL_ENV -Pnu immich uv python install "${ML_PYTHON}" && break
@@ -289,7 +290,8 @@ EOF
msg_info "Updating machine-learning" msg_info "Updating machine-learning"
for attempt in $(seq 1 3); do for attempt in $(seq 1 3); do
$STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -Pnu immich uv sync --extra cpu --no-dev --active --link-mode copy -n -p "${ML_PYTHON}" --managed-python && break $STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -Pnu immich uv sync --extra cpu --no-dev --active --link-mode copy -n -p "${ML_PYTHON}" --managed-python && break
[[ $attempt -lt 3 ]] && msg_warn "uv sync attempt $attempt failed, retrying..." && sleep 10 [[ $attempt -eq 3 ]] && { msg_error "uv sync failed three times, the machine-learning environment was not built"; exit 1; }
msg_warn "uv sync attempt $attempt failed, retrying..." && sleep 10
done done
msg_ok "Updated machine-learning" msg_ok "Updated machine-learning"
fi fi
@@ -333,6 +335,10 @@ EOF
sed -i -e '$a\' "$INSTALL_DIR"/.env sed -i -e '$a\' "$INSTALL_DIR"/.env
echo "IMMICH_HELMET_FILE=true" >>"$INSTALL_DIR"/.env echo "IMMICH_HELMET_FILE=true" >>"$INSTALL_DIR"/.env
fi fi
if ! grep -q 'MODEL_REVISION' "$INSTALL_DIR"/.env; then
sed -i -e '$a\' "$INSTALL_DIR"/.env
echo "MACHINE_LEARNING_MODEL_REVISION=v2" >>"$INSTALL_DIR"/.env
fi
if grep -q 'ExecStart=/usr/bin/node' /etc/systemd/system/immich-web.service; then if grep -q 'ExecStart=/usr/bin/node' /etc/systemd/system/immich-web.service; then
sed -i '/^EnvironmentFile=/d' /etc/systemd/system/immich-web.service sed -i '/^EnvironmentFile=/d' /etc/systemd/system/immich-web.service
@@ -341,25 +347,12 @@ EOF
fi fi
# MickLesk temporary patch for HEIC thumbnail gen # MickLesk temporary patch for HEIC thumbnail gen
msg_info "Patching media.repository.js"
MEDIA_REPO_JS="/opt/immich/app/dist/repositories/media.repository.js" MEDIA_REPO_JS="/opt/immich/app/dist/repositories/media.repository.js"
if [[ -f "$MEDIA_REPO_JS" ]]; then if grep -qF "(0, sharp_1.default)(input).metadata()" "$MEDIA_REPO_JS" 2>/dev/null; then
python3 - <<'PY' msg_info "Patching media.repository.js"
from pathlib import Path sed -i '0,/(0, sharp_1\.default)(input)\.metadata()/s//(0, sharp_1.default)(input, { unlimited: true, limitInputPixels: false }).metadata()/' "$MEDIA_REPO_JS"
p = Path('/opt/immich/app/dist/repositories/media.repository.js') msg_ok "Patched media.repository.js"
s = p.read_text()
old = "(0, sharp_1.default)(input).metadata()"
new = "(0, sharp_1.default)(input, { unlimited: true, limitInputPixels: false }).metadata()"
if new in s:
print('hotfix already there')
elif old in s:
p.write_text(s.replace(old, new, 1))
print('hotfix applied')
else:
print('pattern not found, skipped')
PY
fi fi
msg_ok "Patched media.repository.js"
# chown excluding upload dir contents (may be a mount with restricted permissions) # chown excluding upload dir contents (may be a mount with restricted permissions)
chown immich:immich "$INSTALL_DIR" chown immich:immich "$INSTALL_DIR"

View File

@@ -33,7 +33,12 @@ PNPM_VERSION=$(jq -r '.packageManager | ltrimstr("pnpm@")' /opt/bookorbit/packag
$STD corepack prepare "pnpm@${PNPM_VERSION}" --activate $STD corepack prepare "pnpm@${PNPM_VERSION}" --activate
$STD pnpm install --frozen-lockfile $STD pnpm install --frozen-lockfile
$STD pnpm --filter client run build-only # vite's bundler (rolldown) crashes at random on some builds, rolldown#10860.
for attempt in 1 2 3; do
$STD pnpm --filter client run build-only && break
[[ $attempt -eq 3 ]] && { msg_error "Client build failed three times"; exit 1; }
msg_warn "Client build failed (attempt $attempt), retrying"
done
$STD pnpm --filter server run build $STD pnpm --filter server run build
cp -r /opt/bookorbit/client/dist /opt/bookorbit/server/public cp -r /opt/bookorbit/client/dist /opt/bookorbit/server/public
mkdir -p /opt/bookorbit/server/migrations mkdir -p /opt/bookorbit/server/migrations

View File

@@ -13,7 +13,7 @@ setting_up_container
network_check network_check
update_os update_os
RELEASE="v3.2.4" RELEASE="v3.3.0"
if lscpu | grep -q 'GenuineIntel'; then if lscpu | grep -q 'GenuineIntel'; then
echo "" echo ""
echo "" echo ""
@@ -442,12 +442,13 @@ if [[ -f ~/.openvino ]]; then
msg_info "Installing Intel OpenVINO machine-learning" msg_info "Installing Intel OpenVINO machine-learning"
for attempt in $(seq 1 3); do for attempt in $(seq 1 3); do
$STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -Pnu immich uv sync --extra openvino --no-dev --active --link-mode copy -n -p "${ML_PYTHON}" --managed-python && break $STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -Pnu immich uv sync --extra openvino --no-dev --active --link-mode copy -n -p "${ML_PYTHON}" --managed-python && break
[[ $attempt -lt 3 ]] && msg_warn "uv sync attempt $attempt failed, retrying..." && sleep 10 [[ $attempt -eq 3 ]] && { msg_error "uv sync failed three times, the machine-learning environment was not built"; exit 1; }
msg_warn "uv sync attempt $attempt failed, retrying..." && sleep 10
done done
patchelf --clear-execstack "${VIRTUAL_ENV}/lib/python3.13/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-$(arch_resolve "x86_64" "aarch64")-linux-gnu.so" patchelf --clear-execstack "${VIRTUAL_ENV}/lib/python3.13/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-$(arch_resolve "x86_64" "aarch64")-linux-gnu.so"
msg_ok "Installed Intel OpenVINO machine-learning" msg_ok "Installed Intel OpenVINO machine-learning"
else else
ML_PYTHON="python3.11" ML_PYTHON="python3.13"
msg_info "Pre-installing Python ${ML_PYTHON} for machine-learning" msg_info "Pre-installing Python ${ML_PYTHON} for machine-learning"
for attempt in $(seq 1 3); do for attempt in $(seq 1 3); do
$STD sudo --preserve-env=VIRTUAL_ENV -Pnu immich uv python install "${ML_PYTHON}" && break $STD sudo --preserve-env=VIRTUAL_ENV -Pnu immich uv python install "${ML_PYTHON}" && break
@@ -457,7 +458,8 @@ else
msg_info "Installing machine-learning" msg_info "Installing machine-learning"
for attempt in $(seq 1 3); do for attempt in $(seq 1 3); do
$STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -Pnu immich uv sync --extra cpu --no-dev --active --link-mode copy -n -p "${ML_PYTHON}" --managed-python && break $STD sudo --preserve-env=VIRTUAL_ENV,UV_HTTP_TIMEOUT -Pnu immich uv sync --extra cpu --no-dev --active --link-mode copy -n -p "${ML_PYTHON}" --managed-python && break
[[ $attempt -lt 3 ]] && msg_warn "uv sync attempt $attempt failed, retrying..." && sleep 10 [[ $attempt -eq 3 ]] && { msg_error "uv sync failed three times, the machine-learning environment was not built"; exit 1; }
msg_warn "uv sync attempt $attempt failed, retrying..." && sleep 10
done done
msg_ok "Installed machine-learning" msg_ok "Installed machine-learning"
fi fi
@@ -488,25 +490,12 @@ ln -s "$GEO_DIR" "$APP_DIR"
msg_ok "Installed GeoNames data" msg_ok "Installed GeoNames data"
# MickLesk temporary patch for HEIC thumbnail gen # MickLesk temporary patch for HEIC thumbnail gen
msg_info "Patching media.repository.js"
MEDIA_REPO_JS="/opt/immich/app/dist/repositories/media.repository.js" MEDIA_REPO_JS="/opt/immich/app/dist/repositories/media.repository.js"
if [[ -f "$MEDIA_REPO_JS" ]]; then if grep -qF "(0, sharp_1.default)(input).metadata()" "$MEDIA_REPO_JS" 2>/dev/null; then
python3 - <<'PY' msg_info "Patching media.repository.js"
from pathlib import Path sed -i '0,/(0, sharp_1\.default)(input)\.metadata()/s//(0, sharp_1.default)(input, { unlimited: true, limitInputPixels: false }).metadata()/' "$MEDIA_REPO_JS"
p = Path('/opt/immich/app/dist/repositories/media.repository.js') msg_ok "Patched media.repository.js"
s = p.read_text()
old = "(0, sharp_1.default)(input).metadata()"
new = "(0, sharp_1.default)(input, { unlimited: true, limitInputPixels: false }).metadata()"
if new in s:
print('hotfix already there')
elif old in s:
p.write_text(s.replace(old, new, 1))
print('hotfix applied')
else:
print('pattern not found, skipped')
PY
fi fi
msg_ok "Patched media.repository.js"
mkdir -p /var/log/immich mkdir -p /var/log/immich
touch /var/log/immich/{web.log,ml.log} touch /var/log/immich/{web.log,ml.log}
@@ -537,6 +526,7 @@ MACHINE_LEARNING_CACHE_FOLDER=${INSTALL_DIR}/cache
## - inference speed while reducing accuracy ## - inference speed while reducing accuracy
## - Default is FP32 ## - Default is FP32
# MACHINE_LEARNING_OPENVINO_PRECISION=FP16 # MACHINE_LEARNING_OPENVINO_PRECISION=FP16
MACHINE_LEARNING_MODEL_REVISION=v2
IMMICH_MEDIA_LOCATION=${UPLOAD_DIR} IMMICH_MEDIA_LOCATION=${UPLOAD_DIR}
EOF EOF

436
vm/unifi-os-server-vm.sh Normal file
View File

@@ -0,0 +1,436 @@
#!/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/ProxmoxVE/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/pve/vm-core.func")
load_functions
APP="UniFi OS Server"
APP_TYPE="vm"
NSAPP="unifi-os-server-vm"
var_os="debian"
var_version="13"
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=""
CLOUDINIT_REQUIRED=1
OS_TYPE=""
OS_VERSION=""
OS_CODENAME=""
OS_DISPLAY=""
THIN="discard=on,ssd=1,"
header_info
echo -e "\n Loading..."
set -Eeuo pipefail
shopt -s inherit_errexit
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
vm_require_arch amd64
TEMP_DIR=$(mktemp -d)
pushd "$TEMP_DIR" >/dev/null
vm_preflight
vm_require_tools curl jq virt-customize
function select_os() {
if [[ -n "${1:-}" ]]; then
OS_CHOICE="$1"
elif [[ "${VM_UNATTENDED:-0}" == "1" ]]; then
OS_CHOICE="${VM_OS_VERSION:-debian13}"
elif vm_dialog radiolist "SELECT OS" "Choose Operating System for UniFi OS VM" 12 68 2 \
"debian13" "Debian 13 (Trixie) - Latest" ON \
"ubuntu2404" "Ubuntu 24.04 LTS (Noble)" OFF; then
OS_CHOICE="$VM_DIALOG_RESULT"
else
exit_script
fi
case $OS_CHOICE in
debian13)
OS_TYPE="debian"
OS_VERSION="13"
OS_CODENAME="trixie"
OS_DISPLAY="Debian 13 (Trixie)"
var_os="debian"
var_version="13"
;;
ubuntu2404)
OS_TYPE="ubuntu"
OS_VERSION="24.04"
OS_CODENAME="noble"
OS_DISPLAY="Ubuntu 24.04 LTS"
var_os="ubuntu"
var_version="24.04"
;;
*)
msg_error "Unsupported OS '${OS_CHOICE}' (expected debian13 or ubuntu2404)"
exit 1
;;
esac
}
function get_image_url() {
local arch
arch=$(vm_arch_resolve amd64 arm64)
case $OS_TYPE in
debian)
# Always use Cloud-Init variant for UniFi OS
echo "https://cloud.debian.org/images/cloud/${OS_CODENAME}/latest/debian-${OS_VERSION}-generic-${arch}.qcow2"
;;
ubuntu)
# Ubuntu only has cloudimg variant (always with Cloud-Init support)
echo "https://cloud-images.ubuntu.com/${OS_CODENAME}/current/${OS_CODENAME}-server-cloudimg-${arch}.img"
;;
esac
}
function default_settings() {
vm_apply_machine_type "q35"
select_os "${VM_OS_VERSION:-}"
# Set defaults for other settings
VMID=$(get_valid_nextid)
DISK_CACHE=""
DISK_SIZE="32G"
HN="unifi-server-os"
CPU_TYPE=" -cpu host"
CORE_COUNT="2"
RAM_SIZE="6144"
BRG="vmbr0"
MAC="$GEN_MAC"
VLAN=""
MTU=""
START_VM="yes"
METHOD="default"
vm_echo_default_settings
}
function advanced_settings() {
METHOD="advanced"
select_os
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 "unifi-server-os"
vm_prompt_cpu_model "host"
vm_prompt_cpu_cores "2"
vm_prompt_ram "6144"
vm_prompt_bridge "vmbr0"
vm_prompt_mac "$GEN_MAC"
vm_prompt_vlan
vm_prompt_mtu
vm_prompt_keyboard
vm_prompt_verbose "no"
vm_prompt_start_vm "yes"
if vm_confirm_advanced_settings "Ready to create a UniFi OS Server VM?"; then
echo -e "${CREATING}${BOLD}${DGN}Creating a UniFi OS Server VM using the above advanced settings${CL}"
else
header_info
echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}"
advanced_settings
fi
}
vm_start_script "Use Default Settings?\n\nDefaults:\n• 2 CPU Cores\n• 6 GB RAM\n• 32 GB Disk\n• Cloud-Init enabled" 14 58
if [[ "${VM_UNATTENDED:-0}" == "1" ]]; then
CLOUDINIT_PASSWORD="${CLOUDINIT_PASSWORD:-${VM_ROOT_PASSWORD:-}}"
fi
vm_prompt_cloud_init "root"
if [[ "${USE_CLOUD_INIT:-no}" != "yes" ]]; then
msg_error "UniFi OS Server requires Cloud-Init"
exit 1
fi
if ! declare -f setup_cloud_init >/dev/null; then
msg_error "Required Cloud-Init helpers are unavailable"
exit 1
fi
if [[ "${VM_UNATTENDED:-0}" == "1" && -n "${VM_SSH_KEYS:-}" && -z "${CLOUDINIT_SSH_KEYS:-}" ]]; then
if [[ -f "$VM_SSH_KEYS" ]]; then
cp "$VM_SSH_KEYS" "$TEMP_DIR/ssh-keys-input"
else
printf '%s\n' "$VM_SSH_KEYS" >"$TEMP_DIR/ssh-keys-input"
fi
CLOUDINIT_SSH_KEYS="$TEMP_DIR/ssh-keys.pub"
_ci_ssh_extract_keys_from_file "$TEMP_DIR/ssh-keys-input" >"$CLOUDINIT_SSH_KEYS"
if [[ ! -s "$CLOUDINIT_SSH_KEYS" ]] || ! ssh-keygen -lf "$CLOUDINIT_SSH_KEYS" >/dev/null; then
msg_error "VM_SSH_KEYS must contain valid SSH public keys or name a public-key file"
exit 1
fi
fi
post_to_api_vm
msg_info "Checking system resources"
SYSTEM_RAM_GB=$(grep MemTotal /proc/meminfo | awk '{printf "%.0f", $2 / 1024 / 1024}')
SYSTEM_SWAP_GB=$(grep SwapTotal /proc/meminfo | awk '{printf "%.0f", $2 / 1024 / 1024}')
SYSTEM_FREE_DISK_GB=$(df -BG / | awk 'NR==2 {print $4}' | sed 's/G//')
if [[ ${SYSTEM_RAM_GB} -lt 4 ]]; then
msg_error "Warning: Less than 4GB RAM detected (${SYSTEM_RAM_GB}GB). Install may be slow."
sleep 3
fi
if [[ ${SYSTEM_FREE_DISK_GB} -lt 10 ]]; then
msg_error "Warning: Less than 10GB free disk detected. Install may fail."
sleep 3
fi
msg_ok "System resources: ${SYSTEM_RAM_GB}GB RAM, ${SYSTEM_FREE_DISK_GB}GB free disk"
if command -v ufw &>/dev/null; then
if ufw status verbose | grep -q "Status: active"; then
msg_info "Setting up firewall rules for UniFi OS Server ports"
ufw allow 11443/tcp 2>/dev/null
ufw allow 8080/tcp 2>/dev/null
ufw allow 3478/tcp 2>/dev/null
ufw allow 3478/udp 2>/dev/null
msg_ok "Firewall rules configured"
fi
fi
vm_select_storage "$HN"
# Fetch latest UniFi OS Server version and download URL
msg_info "Fetching latest UniFi OS Server version"
# Download firmware list from Ubiquiti API
API_URL="https://fw-update.ui.com/api/firmware-latest"
TEMP_JSON=$(mktemp)
if ! curl -fsSL "$API_URL" -o "$TEMP_JSON"; then
rm -f "$TEMP_JSON"
msg_error "Failed to fetch data from Ubiquiti API"
exit 1
fi
# Parse JSON to find latest unifi-os-server linux-x64 version
LATEST=$(jq -r '
._embedded.firmware
| map(select(.product == "unifi-os-server"))
| map(select(.platform == "linux-x64"))
| sort_by(.version_major, .version_minor, .version_patch)
| last
' "$TEMP_JSON")
UOS_VERSION=$(echo "$LATEST" | jq -r '.version' | sed 's/^v//')
UOS_URL=$(echo "$LATEST" | jq -r '._links.data.href')
# Cleanup temp file
rm -f "$TEMP_JSON"
if [[ -z "$UOS_URL" || "$UOS_URL" == "null" || -z "$UOS_VERSION" || "$UOS_VERSION" == "null" ]]; then
msg_error "Failed to parse UniFi OS Server version or download URL"
exit 1
fi
UOS_INSTALLER="unifi-os-server-${UOS_VERSION}.bin"
msg_ok "Found UniFi OS Server ${UOS_VERSION}"
# --- Download Cloud Image ---
msg_info "Downloading ${OS_DISPLAY} Cloud Image"
URL=$(get_image_url)
sleep 2
msg_ok "${CL}${BL}${URL}${CL}"
CACHE_FILE="$(vm_image_cache_path "$URL")"
vm_fetch_image "$URL" "$CACHE_FILE" --cache --min-bytes $((100 * 1024 * 1024)) || exit 115
FILE="$(basename "$CACHE_FILE")"
# Work on a copy: image preparation rewrites the image, which would poison the cache for every later VM.
cp -f "$CACHE_FILE" "$FILE"
# Resize the imported disk with vm_resize_disk; Cloud-Init grows the actual root
# filesystem before the first-boot installer runs, without another offline copy.
# --- Download UniFi OS installer on the host ---
msg_info "Downloading UniFi OS Server ${UOS_VERSION} installer"
curl -fsSL "${UOS_URL}" -o "unifi-os-server.bin"
chmod +x "unifi-os-server.bin"
msg_ok "Downloaded UniFi OS Server installer"
# --- Pre-install packages and setup first-boot installer ---
msg_info "Customizing disk image (installing packages, staging installer)"
# Create the first-boot installer script
FIRSTBOOT_SCRIPT="$TEMP_DIR/unifi-os-firstboot.sh"
cat >"$FIRSTBOOT_SCRIPT" <<'FBEOF'
#!/usr/bin/env bash
set -Eeuo pipefail
LOG="/var/log/unifi-os-install.log"
exec > >(tee -a "$LOG") 2>&1
echo "[$(date)] Starting UniFi OS Server first-boot setup..."
trap 'echo "[$(date)] ERROR: First-boot setup failed at line $LINENO"' ERR
if ! systemctl start qemu-guest-agent; then
echo "[$(date)] WARNING: Preinstalled guest agent could not start; retrying after package installation"
fi
# Sync clock before apt (fresh VMs have clock skew that breaks GPG signature validation)
echo "[$(date)] Syncing system clock..."
if ! timedatectl set-ntp true; then
echo "[$(date)] WARNING: Could not enable NTP; checking existing clock synchronization"
fi
# Try NTP first
for attempt in {1..6}; do
if timedatectl show -p NTPSynchronized --value 2>/dev/null | grep -q "yes"; then
echo "[$(date)] Clock synchronized via NTP"
break
fi
sleep 5
done
# Fallback: sync from HTTP header if NTP didn't work
if ! timedatectl show -p NTPSynchronized --value 2>/dev/null | grep -q "yes"; then
if HTTP_DATE=$(curl -fsSI --max-time 10 https://deb.debian.org | sed -n 's/^[Dd]ate: //p' | tr -d '\r') &&
[ -n "$HTTP_DATE" ] && date -s "$HTTP_DATE" >/dev/null; then
echo "[$(date)] Clock synchronized via HTTP"
else
echo "[$(date)] WARNING: Clock synchronization unavailable"
fi
fi
# Install required packages
export DEBIAN_FRONTEND=noninteractive
echo "[$(date)] Installing packages..."
for attempt in {1..3}; do
if apt-get update -qq 2>&1; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "[$(date)] apt-get update failed after 3 attempts"
exit 1
fi
echo "[$(date)] apt-get update failed (attempt $attempt/3), retrying in 10s..."
sleep 10
done
for attempt in {1..3}; do
if apt-get install -y -qq qemu-guest-agent podman uidmap slirp4netns curl wget; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "[$(date)] apt-get install failed after 3 attempts"
exit 1
fi
echo "[$(date)] apt-get install failed (attempt $attempt/3), retrying in 10s..."
sleep 10
done
systemctl enable --now qemu-guest-agent
echo "[$(date)] Packages installed"
# Setup swap (2GB)
if [ ! -f /swapfile ]; then
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
echo "[$(date)] Swap file created"
fi
# Run UniFi OS installer
if [ -f /opt/unifi-os-server.bin ]; then
cd /opt
./unifi-os-server.bin <<<'y'
rm -f /opt/unifi-os-server.bin
echo "[$(date)] UniFi OS Server installed successfully"
else
echo "[$(date)] ERROR: /opt/unifi-os-server.bin not found"
exit 1
fi
echo "[$(date)] First-boot setup complete"
FBEOF
vm_prepare_cloud_image "$FILE" "$HN"
vm_customize "UniFi OS installer" "$FILE" \
--upload "unifi-os-server.bin:/opt/unifi-os-server.bin" \
--chmod 0755:/opt/unifi-os-server.bin \
--run-command "systemctl enable ssh" || exit 1
vm_firstboot_unit "$FILE" "unifi-os-firstboot" "$FIRSTBOOT_SCRIPT" \
--description "UniFi OS Server First Boot Installer" \
--after qemu-guest-agent.service \
--requires-path /opt/unifi-os-server.bin \
--cloud-init yes || exit 1
rm -f "$FIRSTBOOT_SCRIPT" "unifi-os-server.bin"
msg_ok "Disk image customized (UniFi OS ${UOS_VERSION} staged for first-boot install)"
vm_claim_vmid
msg_info "Creating UniFi OS VM"
qm create "$VMID" -agent 1${MACHINE} -tablet 0 -bios ovmf \
${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
vm_mark_created
vm_import_disk "$VMID" "$FILE" "$STORAGE" "$DISK_IMPORT_FORMAT"
qm set "$VMID" \
-efidisk0 "${STORAGE}:0,efitype=4m" \
-scsi0 "${VM_IMPORTED_DISK},${DISK_CACHE}size=${DISK_SIZE}" \
-boot order=scsi0 -serial0 socket >/dev/null
vm_resize_disk
qm set "$VMID" --agent enabled=1 >/dev/null
vm_provision "$VMID"
# Core currently suppresses SSH-key write errors; keep them fatal for this VM.
if [[ -n "${CLOUDINIT_SSH_KEYS:-}" ]]; then
qm set "$VMID" --sshkeys "$CLOUDINIT_SSH_KEYS" >/dev/null
fi
set_description
msg_ok "Created a UniFi OS VM ${CL}${BL}(${HN})"
VM_IP=""
UNIFI_READY=""
FIRSTBOOT_DONE=""
if [ "$START_VM" == "yes" ]; then
vm_start_vm "UniFi OS VM"
vm_wait_for_ip 360 || true
if [[ -n "${VM_FIRSTBOOT_MARKER:-}" ]] && vm_guest_exec "$VMID" 10 test -f "$VM_FIRSTBOOT_MARKER" >/dev/null; then
FIRSTBOOT_DONE="yes"
else
msg_warn "UniFi OS first-boot installation has not completed yet"
fi
if [[ -n "${VM_IP:-}" ]] && vm_wait_http "https://${VM_IP}:11443" 300 --insecure; then
UNIFI_READY="yes"
msg_ok "UniFi OS is up at https://${VM_IP}:11443"
elif [[ -n "${VM_IP:-}" ]]; then
msg_warn "UniFi OS is not ready; first-boot installation may still be running or may have failed"
fi
else
msg_info "Start VM ${VMID} to run the UniFi OS first-boot installation"
fi
vm_print_summary \
"Operating System=${OS_DISPLAY}" \
"UniFi OS Version=${UOS_VERSION}" \
"Web Interface=https://${VM_IP:-<VM-IP>}:11443" \
"Console Login=${CLOUDINIT_USER:-root}"
if [[ "$UNIFI_READY" == "yes" ]]; then
vm_next_steps \
"Open https://${VM_IP}:11443 and complete UniFi OS setup."
FINISH_MESSAGE="UniFi OS Server VM is ready."
else
vm_next_steps \
"Wait for first-boot installation to complete in the VM: journalctl -u unifi-os-firstboot.service" \
"Follow progress in /var/log/unifi-os-install.log inside the VM." \
"Open https://${VM_IP:-<VM-IP>}:11443 after the installer finishes."
if [[ "$FIRSTBOOT_DONE" == "yes" ]]; then
FINISH_MESSAGE="VM provisioning completed; UniFi OS is installed, but web readiness was not confirmed yet."
else
FINISH_MESSAGE="VM provisioning completed; UniFi OS installation continues in the VM on first boot."
fi
fi
vm_finish "$FINISH_MESSAGE"

126
vm/zimaos-vm.sh Normal file
View File

@@ -0,0 +1,126 @@
#!/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/IceWhaleTech/ZimaOS
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/pve/vm-core.func")
load_functions
APP="ZimaOS"
APP_TYPE="vm"
NSAPP="zimaos-vm"
var_os="zimaos"
var_version=" "
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=""
THIN="discard=on,ssd=1,"
header_info
echo -e "\n Loading..."
set -Eeo pipefail
shopt -s inherit_errexit
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
vm_require_arch amd64
TEMP_DIR=$(mktemp -d)
pushd "$TEMP_DIR" >/dev/null
vm_preflight
function default_settings() {
VMID=$(get_valid_nextid)
vm_apply_machine_type "q35"
DISK_SIZE="64G"
DISK_CACHE=""
HN="zimaos"
CPU_TYPE=" -cpu host"
CORE_COUNT="4"
RAM_SIZE="4096"
BRG="vmbr0"
MAC="$GEN_MAC"
VLAN=""
MTU=""
START_VM="yes"
METHOD="default"
vm_echo_default_settings
}
function advanced_settings() {
METHOD="advanced"
vm_prompt_vmid "${VMID:-$(get_valid_nextid)}"
vm_prompt_machine_type "q35"
vm_prompt_disk_size "64G" "Set Disk Size in GiB (Recommended: 64+ for apps)"
vm_prompt_disk_cache "none"
vm_prompt_hostname "zimaos"
vm_prompt_cpu_model "host"
vm_prompt_cpu_cores "4"
vm_prompt_ram "4096"
vm_prompt_bridge "vmbr0"
vm_prompt_mac "$GEN_MAC"
vm_prompt_vlan
vm_prompt_mtu
vm_prompt_keyboard
vm_prompt_verbose "no"
vm_prompt_start_vm "yes"
if vm_confirm_advanced_settings "Ready to create a ZimaOS VM?"; then
echo -e "${CREATING}${BOLD}${DGN}Creating a ZimaOS VM using the above advanced settings${CL}"
else
header_info
echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}"
advanced_settings
fi
}
vm_start_script "Use Default Settings?\n\nDefaults:\n• 4 CPU Cores (Host model)\n• 4 GB RAM\n• 64 GB Disk\n• Q35 Machine Type" 14 58
post_to_api_vm
vm_select_storage "$HN"
msg_info "Retrieving the URL for the ZimaOS installer"
if ! vm_release_asset github IceWhaleTech/ZimaOS 'zimaos-x86_64-.*_installer\.iso$'; then
exit 1
fi
URL="$VM_RELEASE_URL"
FILENAME="$VM_RELEASE_ASSET"
ZIMAOS_VERSION="$VM_RELEASE_VERSION"
var_version="$ZIMAOS_VERSION"
vm_select_iso_storage "$FILENAME" "$HN"
CACHE_FILE="$ISO_PATH"
msg_ok "ZimaOS ${CL}${BL}${ZIMAOS_VERSION}${CL}"
[[ -s "$CACHE_FILE" ]] || msg_warn "Downloading the ZimaOS installer (approximately 2 GB, this may take a while)"
# Official installers such as 1.8.0-beta2 are smaller than 2 GiB.
MIN_ISO_BYTES=$((1024 * 1024 * 1024))
vm_fetch_image "$URL" "$CACHE_FILE" --cache --min-bytes "$MIN_ISO_BYTES" --sha256 "$VM_RELEASE_SHA256" || exit 115
vm_claim_vmid
msg_info "Creating a ZimaOS VM"
$STD qm create $VMID -agent 1${MACHINE} -tablet 0 -bios ovmf${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-single \
-efidisk0 ${STORAGE}:1,efitype=4m,pre-enrolled-keys=0 -scsi0 ${STORAGE}:${DISK_SIZE%G},${DISK_CACHE}${THIN%,} \
-cdrom "$ISO_VOLUME" -boot order='scsi0;ide2' -vga std -serial0 socket
vm_mark_created
set_description
msg_ok "Created a ZimaOS VM ${CL}${BL}(${HN})"
vm_start_vm "ZimaOS VM"
vm_print_summary "Version=${ZIMAOS_VERSION}" "ISO=${FILENAME}" "Discovery=https://find.zimaspace.com"
vm_next_steps \
"Open the VM Console in Proxmox." \
"Follow the installer and select scsi0 as the target disk." \
"When prompted to remove the disk and reboot, reboot; the boot order prefers the disk." \
"Detach the ISO afterwards (Hardware -> CD/DVD -> Remove)." \
"Read the IP from the Proxmox summary once the guest agent is up, or use https://find.zimaspace.com." \
"For NAS use, add a second disk in Proxmox and let ZimaOS manage it."
vm_finish "VM created; complete the ZimaOS installation in the Proxmox console."