mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-08-26 02:04:49 +00:00
* Move the top 25 scripts onto the core engine The engine work of the last few days reaches 30 of 561 ct scripts, about 5% of ProxmoxVE traffic: retry on engine downloads, exit 227 instead of a misfiled dpkg error, the umask fix that stops a hardened host producing containers apt cannot resolve in, the TMPDIR guard, the toolchain restore. All of it has been sitting where almost nobody runs it. All eighteen at once rather than in waves. A slow rollout does not exercise the paths only some scripts take, and broad exposure is what surfaces bugs -- a deliberate call about release risk. Checked before touching anything, because "migrate" meant far more than a line swap last time: - None of the eighteen has an alpine-* variant, so there is no merge to do. - No script references misc/ outside its bootstrap line. - Of the 61 functions that exist only in misc/, none is called by any of them. So it is one line per script, and every head is now byte-identical to the ones migrated earlier. With these, ProxmoxVE goes from 30 scripts on the core engine to 48 -- and from roughly 5% of traffic to the majority, since these are the ones people actually install. Two to watch: immich sits at 44.7% success and vaultwarden at 42.1% before this. If their numbers move, the engine is one of two changed variables rather than the only one. * Move update-apps onto the core engine Entry 11 of the list and the only one that is not a ct script, so it was left out of the previous commit. It is a host tool: it never used build.func at all, it sources misc/core.func and misc/api.func directly. The swap is therefore two lines rather than one, and worth checking rather than assuming. It uses exactly five engine functions -- header_info, init_tool_telemetry, msg_info, msg_ok, msg_error -- all present in the core, and both files load standalone, which they had not had to do before: everywhere else they arrive through build.func. That completes the list. All 25 now run on the core engine. Fixing this one matters beyond the migration: update-apps is what drives unattended updates across every container on a host, and it is the path where PHS_SILENT was being ignored (#16593). It now gets the engine that honours it.
295 lines
14 KiB
Bash
295 lines
14 KiB
Bash
#!/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 tteck
|
||
# Author: tteck (tteckster)
|
||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||
# Source: https://docs.paperless-ngx.com/ | Github: https://github.com/paperless-ngx/paperless-ngx
|
||
|
||
APP="Paperless-ngx"
|
||
var_tags="${var_tags:-document;management}"
|
||
var_cpu="${var_cpu:-2}"
|
||
var_ram="${var_ram:-3072}"
|
||
var_disk="${var_disk:-12}"
|
||
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 [[ ! -d /opt/paperless ]]; then
|
||
msg_error "No ${APP} Installation Found!"
|
||
exit
|
||
fi
|
||
|
||
# Check for old data structure and prompt migration (exclude symlinks)
|
||
if [[ -f /opt/paperless/paperless.conf ]]; then
|
||
local OLD_DIRS=()
|
||
[[ -d /opt/paperless/consume && ! -L /opt/paperless/consume ]] && OLD_DIRS+=("consume")
|
||
[[ -d /opt/paperless/data && ! -L /opt/paperless/data ]] && OLD_DIRS+=("data")
|
||
[[ -d /opt/paperless/media && ! -L /opt/paperless/media ]] && OLD_DIRS+=("media")
|
||
|
||
if [[ ${#OLD_DIRS[@]} -gt 0 ]]; then
|
||
msg_error "Old data structure detected in /opt/paperless/"
|
||
msg_custom "📂" "Found directories: ${OLD_DIRS[*]}"
|
||
echo -e ""
|
||
msg_custom "🔄" "Migration required to new data structure (/opt/paperless_data/)"
|
||
msg_custom "📖" "Please follow the migration guide:"
|
||
echo -e "${GATEWAY}${BGN}https://github.com/community-scripts/ProxmoxVE/pull/9223${CL}"
|
||
echo -e ""
|
||
msg_custom "⚠️" "Update aborted. Please migrate your data first."
|
||
exit 253
|
||
fi
|
||
fi
|
||
|
||
local PAPERLESS_INSTALLED_VERSION="" BRIDGE_UPDATE=0
|
||
[[ -f ~/.paperless ]] && PAPERLESS_INSTALLED_VERSION="$(<~/.paperless)"
|
||
PAPERLESS_INSTALLED_VERSION="${PAPERLESS_INSTALLED_VERSION#v}"
|
||
if [[ "$PAPERLESS_INSTALLED_VERSION" == 2.* && "$PAPERLESS_INSTALLED_VERSION" != "2.20.15" ]]; then
|
||
BRIDGE_UPDATE=1
|
||
fi
|
||
|
||
if check_for_gh_release "paperless" "paperless-ngx/paperless-ngx"; then
|
||
if [[ "$PAPERLESS_INSTALLED_VERSION" == "2.20.15" ]]; then
|
||
msg_warn "Paperless-ngx v3 does not support encrypted documents anymore."
|
||
echo -e "${GATEWAY}${BGN}https://docs.paperless-ngx.com/migration-v3/#encryption-support${CL}"
|
||
msg_warn "Before continuing make sure that you do not use encryption or have decrypted all documents."
|
||
echo ""
|
||
read -rp "Do you want to continue with the update? (y/N): " MIGRATE
|
||
echo
|
||
if [[ ! "$MIGRATE" =~ ^[Yy]$ ]]; then
|
||
msg_custom "⚠️" "Update aborted. Decrypt all documents before upgrading to v3."
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
msg_info "Stopping all Paperless-ngx Services"
|
||
systemctl stop paperless-consumer paperless-webserver paperless-scheduler paperless-task-queue
|
||
msg_ok "Stopped all Paperless-ngx Services"
|
||
|
||
if grep -q "uv run" /etc/systemd/system/paperless-webserver.service; then
|
||
msg_info "Backing up configuration"
|
||
local BACKUP_DIR="/opt/paperless_backup_$$"
|
||
mkdir -p "$BACKUP_DIR"
|
||
[[ -f /opt/paperless/paperless.conf ]] && cp /opt/paperless/paperless.conf "$BACKUP_DIR/"
|
||
msg_ok "Backup completed to $BACKUP_DIR"
|
||
|
||
PYTHON_VERSION="3.13" setup_uv
|
||
if ((BRIDGE_UPDATE)); then
|
||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "paperless" "paperless-ngx/paperless-ngx" "prebuild" "v2.20.15" "/opt/paperless" "paperless*tar.xz"
|
||
else
|
||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "paperless" "paperless-ngx/paperless-ngx" "prebuild" "latest" "/opt/paperless" "paperless*tar.xz"
|
||
fi
|
||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "jbig2enc" "ie13/jbig2enc" "tarball" "latest" "/opt/jbig2enc"
|
||
|
||
. /etc/os-release
|
||
if [ "$VERSION_CODENAME" = "bookworm" ]; then
|
||
setup_gs
|
||
else
|
||
ensure_dependencies ghostscript
|
||
fi
|
||
ensure_dependencies gnupg
|
||
|
||
cp -r "$BACKUP_DIR"/* /opt/paperless/
|
||
if ((BRIDGE_UPDATE == 0)) && [[ "$PAPERLESS_INSTALLED_VERSION" == 2.* ]]; then
|
||
msg_info "Migrating Paperless-ngx v2 configuration to v3"
|
||
PAPERLESS_CONF="/opt/paperless/paperless.conf"
|
||
|
||
SECRET_KEY_CURRENT="$(sed -n 's|^PAPERLESS_SECRET_KEY=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
DBENGINE="$(sed -n 's|^PAPERLESS_DBENGINE=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
DB_OPTIONS_DEPRECATED="$(sed -n '/^PAPERLESS_DBSSLMODE=/p;/^PAPERLESS_DBSSLROOTCERT=/p;/^PAPERLESS_DBSSLCERT=/p;/^PAPERLESS_DBSSLKEY=/p;/^PAPERLESS_DB_POOLSIZE=/p;/^PAPERLESS_DB_TIMEOUT=/p' "$PAPERLESS_CONF")"
|
||
CONSUMER_POLLING="$(sed -n 's|^PAPERLESS_CONSUMER_POLLING=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
CONSUMER_POLLING_INTERVAL="$(sed -n 's|^PAPERLESS_CONSUMER_POLLING_INTERVAL=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
CONSUMER_INOTIFY_DELAY="$(sed -n 's|^PAPERLESS_CONSUMER_INOTIFY_DELAY=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
CONSUMER_STABILITY_DELAY="$(sed -n 's|^PAPERLESS_CONSUMER_STABILITY_DELAY=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
OCR_MODE="$(sed -n 's|^PAPERLESS_OCR_MODE=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
OCR_SKIP_ARCHIVE="$(sed -n 's|^PAPERLESS_OCR_SKIP_ARCHIVE_FILE=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
ARCHIVE_FILE_GENERATION="$(sed -n 's|^PAPERLESS_ARCHIVE_FILE_GENERATION=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
CONSUMER_DELETE_DUPLICATES="$(sed -n 's|^PAPERLESS_CONSUMER_DELETE_DUPLICATES=||p' "$PAPERLESS_CONF" | tail -n1)"
|
||
|
||
sed -i \
|
||
-e '/^PAPERLESS_CONSUMER_POLLING=/d' \
|
||
-e '/^PAPERLESS_CONSUMER_INOTIFY_DELAY=/d' \
|
||
-e '/^PAPERLESS_CONSUMER_POLLING_DELAY=/d' \
|
||
-e '/^PAPERLESS_CONSUMER_POLLING_RETRY_COUNT=/d' \
|
||
-e '/^PAPERLESS_CONSUMER_BARCODE_SCANNER=/d' \
|
||
-e '/^PAPERLESS_PASSPHRASE=/d' \
|
||
-e '/^PAPERLESS_OCR_SKIP_ARCHIVE_FILE=/d' \
|
||
-e 's|^PAPERLESS_OCR_MODE="\?skip"\?$|PAPERLESS_OCR_MODE=auto|' \
|
||
-e 's|^PAPERLESS_OCR_MODE="\?skip_noarchive"\?$|PAPERLESS_OCR_MODE=auto|' \
|
||
"$PAPERLESS_CONF"
|
||
|
||
if [[ -n "$DB_OPTIONS_DEPRECATED" ]]; then
|
||
msg_warn "Deprecated Paperless DB options detected; migrate them manually to PAPERLESS_DB_OPTIONS."
|
||
echo -e "${GATEWAY}${BGN}https://docs.paperless-ngx.com/migration-v3/#database-advanced-options${CL}"
|
||
fi
|
||
if [[ -z "$SECRET_KEY_CURRENT" || "$SECRET_KEY_CURRENT" == "change-me" ]]; then
|
||
SECRET_KEY="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
||
sed -i \
|
||
-e '/^#\?PAPERLESS_SECRET_KEY=/d' \
|
||
-e "\$a\\PAPERLESS_SECRET_KEY=$SECRET_KEY" \
|
||
"$PAPERLESS_CONF"
|
||
printf "Paperless-ngx Secret Key: %s\n" "$SECRET_KEY" >>~/paperless-ngx.creds
|
||
fi
|
||
[[ -z "$DBENGINE" ]] && sed -i '$a\PAPERLESS_DBENGINE=postgresql' "$PAPERLESS_CONF"
|
||
[[ -n "$CONSUMER_POLLING" && -z "$CONSUMER_POLLING_INTERVAL" ]] &&
|
||
sed -i "\$a\\PAPERLESS_CONSUMER_POLLING_INTERVAL=$CONSUMER_POLLING" "$PAPERLESS_CONF"
|
||
[[ -n "$CONSUMER_INOTIFY_DELAY" && -z "$CONSUMER_STABILITY_DELAY" ]] &&
|
||
sed -i "\$a\\PAPERLESS_CONSUMER_STABILITY_DELAY=$CONSUMER_INOTIFY_DELAY" "$PAPERLESS_CONF"
|
||
[[ -z "$CONSUMER_DELETE_DUPLICATES" ]] &&
|
||
sed -i '$a\PAPERLESS_CONSUMER_DELETE_DUPLICATES=true' "$PAPERLESS_CONF"
|
||
if [[ -z "$ARCHIVE_FILE_GENERATION" ]]; then
|
||
if [[ "$OCR_MODE" == "skip_noarchive" || "$OCR_MODE" == "\"skip_noarchive\"" ]]; then
|
||
sed -i '$a\PAPERLESS_ARCHIVE_FILE_GENERATION=never' "$PAPERLESS_CONF"
|
||
elif [[ "$OCR_SKIP_ARCHIVE" == "never" || "$OCR_SKIP_ARCHIVE" == "\"never\"" ]]; then
|
||
sed -i '$a\PAPERLESS_ARCHIVE_FILE_GENERATION=always' "$PAPERLESS_CONF"
|
||
elif [[ "$OCR_SKIP_ARCHIVE" == "with_text" || "$OCR_SKIP_ARCHIVE" == "\"with_text\"" ]]; then
|
||
sed -i '$a\PAPERLESS_ARCHIVE_FILE_GENERATION=auto' "$PAPERLESS_CONF"
|
||
elif [[ "$OCR_SKIP_ARCHIVE" == "always" || "$OCR_SKIP_ARCHIVE" == "\"always\"" ]]; then
|
||
sed -i '$a\PAPERLESS_ARCHIVE_FILE_GENERATION=never' "$PAPERLESS_CONF"
|
||
fi
|
||
fi
|
||
[[ -n "$(sed -n '/^PAPERLESS_CONSUMER_IGNORE_PATTERNS=/p' "$PAPERLESS_CONF")" ]] &&
|
||
msg_warn "PAPERLESS_CONSUMER_IGNORE_PATTERNS now uses regex patterns; please verify custom values."
|
||
[[ -n "$(sed -n '/^PAPERLESS_PRE_CONSUME_SCRIPT=/p;/^PAPERLESS_POST_CONSUME_SCRIPT=/p' "$PAPERLESS_CONF")" ]] &&
|
||
msg_warn "Pre/post consume scripts no longer receive positional arguments in v3; please verify custom scripts."
|
||
msg_ok "Migrated Paperless-ngx configuration"
|
||
fi
|
||
|
||
msg_info "Updating Paperless-ngx"
|
||
if ((BRIDGE_UPDATE == 0)); then
|
||
sed -i 's|^ExecStart=.*|ExecStart=uv run --no-sync -- granian --interface asginl --ws --loop uvloop "paperless.asgi:application"|' /etc/systemd/system/paperless-webserver.service
|
||
grep -q "document_index reindex" /etc/systemd/system/paperless-webserver.service ||
|
||
sed -i '/^ExecStart=/i ExecStartPre=uv run -- python manage.py document_index reindex --if-needed --no-progress-bar' /etc/systemd/system/paperless-webserver.service
|
||
$STD systemctl daemon-reload
|
||
fi
|
||
cd /opt/paperless
|
||
$STD uv sync --all-extras
|
||
cd /opt/paperless/src
|
||
$STD uv run -- python manage.py migrate
|
||
msg_ok "Updated Paperless-ngx"
|
||
|
||
if ((BRIDGE_UPDATE == 0)) && [[ "$PAPERLESS_INSTALLED_VERSION" == "2.20.15" ]]; then
|
||
$STD apt -y purge libzbar0t64 libzbar0 2>/dev/null || true
|
||
$STD apt -y autoremove 2>/dev/null || true
|
||
fi
|
||
|
||
rm -rf "$BACKUP_DIR"
|
||
|
||
else
|
||
BRIDGE_UPDATE=1
|
||
msg_warn "You are about to migrate your Paperless-ngx installation to uv!"
|
||
msg_custom "🔒" "It is strongly recommended to take a Proxmox snapshot first:"
|
||
echo -e " 1. Stop the container: pct stop <CTID>"
|
||
echo -e " 2. Create a snapshot: pct snapshot <CTID> pre-paperless-uv-migration"
|
||
echo -e " 3. Start the container again\n"
|
||
|
||
read -rp "Have you created a snapshot? [y/N]: " confirm
|
||
if [[ ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||
msg_error "Migration aborted. Please create a snapshot first."
|
||
exit
|
||
fi
|
||
msg_info "Migrating old Paperless-ngx installation to uv"
|
||
rm -rf /opt/paperless/venv
|
||
find /opt/paperless -name "__pycache__" -type d -exec rm -rf {} +
|
||
|
||
msg_info "Backing up configuration"
|
||
local BACKUP_DIR="/opt/paperless_backup_$$"
|
||
mkdir -p "$BACKUP_DIR"
|
||
[[ -f /opt/paperless/paperless.conf ]] && cp /opt/paperless/paperless.conf "$BACKUP_DIR/"
|
||
msg_ok "Backup completed to $BACKUP_DIR"
|
||
|
||
declare -A PATCHES=(
|
||
["paperless-consumer.service"]="ExecStart=uv run --no-sync -- python manage.py document_consumer"
|
||
["paperless-scheduler.service"]="ExecStart=uv run --no-sync -- celery --app paperless beat --loglevel INFO"
|
||
["paperless-task-queue.service"]="ExecStart=uv run --no-sync -- celery --app paperless worker --loglevel INFO"
|
||
["paperless-webserver.service"]="ExecStart=uv run --no-sync -- granian --interface asgi --ws \"paperless.asgi:application\""
|
||
)
|
||
|
||
for svc in "${!PATCHES[@]}"; do
|
||
path=$(systemctl show -p FragmentPath "$svc" | cut -d= -f2)
|
||
if [[ -n "$path" && -f "$path" ]]; then
|
||
sed -i "s|^ExecStart=.*|${PATCHES[$svc]}|" "$path"
|
||
if [[ "$svc" == "paperless-webserver.service" ]]; then
|
||
grep -q "^Environment=GRANIAN_HOST=" "$path" ||
|
||
sed -i '/^\[Service\]/a Environment=GRANIAN_HOST=::' "$path"
|
||
grep -q "^Environment=GRANIAN_PORT=" "$path" ||
|
||
sed -i '/^\[Service\]/a Environment=GRANIAN_PORT=8000' "$path"
|
||
grep -q "^Environment=GRANIAN_WORKERS=" "$path" ||
|
||
sed -i '/^\[Service\]/a Environment=GRANIAN_WORKERS=1' "$path"
|
||
fi
|
||
msg_ok "Patched $svc"
|
||
else
|
||
msg_error "Service file for $svc not found!"
|
||
fi
|
||
done
|
||
|
||
$STD systemctl daemon-reload
|
||
msg_info "Backing up configuration"
|
||
BACKUP_DIR="/opt/paperless_backup_$$"
|
||
mkdir -p "$BACKUP_DIR"
|
||
[[ -f /opt/paperless/paperless.conf ]] && cp /opt/paperless/paperless.conf "$BACKUP_DIR/"
|
||
msg_ok "Backup completed to $BACKUP_DIR"
|
||
|
||
PYTHON_VERSION="3.13" setup_uv
|
||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "paperless" "paperless-ngx/paperless-ngx" "prebuild" "v2.20.15" "/opt/paperless" "paperless*tar.xz"
|
||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "jbig2enc" "ie13/jbig2enc" "tarball" "latest" "/opt/jbig2enc"
|
||
|
||
. /etc/os-release
|
||
if [ "$VERSION_CODENAME" = "bookworm" ]; then
|
||
setup_gs
|
||
else
|
||
msg_info "Installing Ghostscript"
|
||
ensure_dependencies ghostscript
|
||
msg_ok "Installed Ghostscript"
|
||
fi
|
||
ensure_dependencies gnupg
|
||
|
||
msg_info "Updating Paperless-ngx to v2.20.15"
|
||
cp -r "$BACKUP_DIR"/* /opt/paperless/
|
||
cd /opt/paperless
|
||
$STD uv sync --all-extras
|
||
cd /opt/paperless/src
|
||
$STD uv run -- python manage.py migrate
|
||
msg_ok "Migrated to uv and updated to v2.20.15 (required before v3)"
|
||
|
||
rm -rf "$BACKUP_DIR"
|
||
if [[ -d /opt/paperless/backup ]]; then
|
||
rm -rf /opt/paperless/backup
|
||
msg_ok "Removed old backup directory"
|
||
fi
|
||
fi
|
||
|
||
setup_nltk "snowball_data stopwords punkt_tab" "/usr/share/nltk_data"
|
||
|
||
msg_info "Starting all Paperless-ngx Services"
|
||
systemctl start paperless-consumer paperless-webserver paperless-scheduler paperless-task-queue
|
||
sleep 1
|
||
msg_ok "Started all Paperless-ngx Services"
|
||
if ((BRIDGE_UPDATE)); then
|
||
msg_custom "ℹ️" "${YW}" "Paperless-ngx is now on v2.20.15. Run the update again to upgrade to v3."
|
||
exit
|
||
fi
|
||
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}Access it using the following URL:${CL}"
|
||
echo -e "${GATEWAY}${BGN}http://${IP}:8000${CL}"
|