Move Foldergram media dir; add batch CT creator
Change Foldergram installation to store media and config under /opt/foldergram_media (create media dir, write env file there, update systemd EnvironmentFile). Update ct/foldergram.sh to remove temporary backup/restore during updates. Update json/foldergram.json config_path and add an info note about where to place media. Add tools/pve/ct-batch-create.sh — a new Proxmox CT batch-creator script (interactive/unattended modes, caching, storage selection) to download and deploy multiple community-scripts CTs.
This commit is contained in:
@@ -34,11 +34,6 @@ function update_script() {
|
|||||||
systemctl stop foldergram
|
systemctl stop foldergram
|
||||||
msg_ok "Stopped Service"
|
msg_ok "Stopped Service"
|
||||||
|
|
||||||
msg_info "Backing up configuration"
|
|
||||||
cp -r /opt/foldergram/data /opt/foldergram_data
|
|
||||||
cp /opt/foldergram/foldergram.env /opt/foldergram_env
|
|
||||||
msg_ok "Backed up configuration"
|
|
||||||
|
|
||||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "foldergram" "foldergram/foldergram" "tarball"
|
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "foldergram" "foldergram/foldergram" "tarball"
|
||||||
|
|
||||||
msg_info "Installing Foldergram"
|
msg_info "Installing Foldergram"
|
||||||
@@ -47,11 +42,6 @@ function update_script() {
|
|||||||
$STD pnpm run build
|
$STD pnpm run build
|
||||||
msg_ok "Installed Foldergram"
|
msg_ok "Installed Foldergram"
|
||||||
|
|
||||||
msg_info "Restoring configuration"
|
|
||||||
mv /opt/foldergram_data /opt/foldergram/data
|
|
||||||
mv /opt/foldergram_env /opt/foldergram/foldergram.env
|
|
||||||
msg_ok "Restored configuration"
|
|
||||||
|
|
||||||
msg_info "Starting Service"
|
msg_info "Starting Service"
|
||||||
systemctl start foldergram
|
systemctl start foldergram
|
||||||
msg_ok "Started Service"
|
msg_ok "Started Service"
|
||||||
|
|||||||
@@ -27,9 +27,18 @@ $STD corepack enable
|
|||||||
cd /opt/foldergram
|
cd /opt/foldergram
|
||||||
$STD pnpm install
|
$STD pnpm install
|
||||||
$STD pnpm run build
|
$STD pnpm run build
|
||||||
cat <<EOF >/opt/foldergram/foldergram.env
|
mkdir -p /opt/foldergram_media
|
||||||
|
cat <<EOF >/opt/foldergram_media/foldergram.env
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
SERVER_PORT=4141
|
SERVER_PORT=4141
|
||||||
|
DATA_ROOT=/opt/foldergram_media
|
||||||
|
GALLERY_ROOT=/opt/foldergram_media/gallery
|
||||||
|
DB_DIR=/opt/foldergram_media/db
|
||||||
|
THUMBNAILS_DIR=/opt/foldergram_media/thumbnails
|
||||||
|
PREVIEWS_DIR=/opt/foldergram_media/previews
|
||||||
|
IMAGE_DETAIL_SOURCE=preview
|
||||||
|
DERIVATIVE_MODE=eager
|
||||||
|
GALLERY_EXCLUDED_FOLDERS=
|
||||||
EOF
|
EOF
|
||||||
msg_ok "Configured Foldergram"
|
msg_ok "Configured Foldergram"
|
||||||
|
|
||||||
@@ -43,7 +52,7 @@ After=network.target
|
|||||||
WorkingDirectory=/opt/foldergram
|
WorkingDirectory=/opt/foldergram
|
||||||
ExecStart=/usr/bin/pnpm start
|
ExecStart=/usr/bin/pnpm start
|
||||||
Restart=always
|
Restart=always
|
||||||
EnvironmentFile=/opt/foldergram/foldergram.env
|
EnvironmentFile=/opt/foldergram_media/foldergram.env
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
{
|
{
|
||||||
"type": "default",
|
"type": "default",
|
||||||
"script": "ct/foldergram.sh",
|
"script": "ct/foldergram.sh",
|
||||||
"config_path": "/opt/foldergram/foldergram.env",
|
"config_path": "/opt/foldergram_media/foldergram.env",
|
||||||
"resources": {
|
"resources": {
|
||||||
"cpu": 1,
|
"cpu": 1,
|
||||||
"ram": 2048,
|
"ram": 2048,
|
||||||
@@ -31,5 +31,10 @@
|
|||||||
"username": null,
|
"username": null,
|
||||||
"password": null
|
"password": null
|
||||||
},
|
},
|
||||||
"notes": []
|
"notes": [
|
||||||
}
|
{
|
||||||
|
"text": "Media data (gallery, database, thumbnails, previews) is stored under /opt/foldergram_media. Place your photos and videos in /opt/foldergram_media/gallery.",
|
||||||
|
"type": "info"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
274
tools/pve/ct-batch-create.sh
Normal file
274
tools/pve/ct-batch-create.sh
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
|
# Author: GitHub Copilot
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
||||||
|
|
||||||
|
set -eEuo pipefail
|
||||||
|
|
||||||
|
BL='\033[36m'
|
||||||
|
GN='\033[1;92m'
|
||||||
|
YW='\033[1;93m'
|
||||||
|
RD='\033[01;31m'
|
||||||
|
CL='\033[m'
|
||||||
|
|
||||||
|
var_repo="${var_repo:-}"
|
||||||
|
var_mode="${var_mode:-}"
|
||||||
|
var_apps="${var_apps:-}"
|
||||||
|
var_refresh_cache="${var_refresh_cache:-no}"
|
||||||
|
var_cache_ttl="${var_cache_ttl:-21600}"
|
||||||
|
var_template_storage="${var_template_storage:-}"
|
||||||
|
var_container_storage="${var_container_storage:-}"
|
||||||
|
|
||||||
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||||
|
CACHE_DIR="/tmp/community-scripts-ct-batch-cache"
|
||||||
|
|
||||||
|
header_info() {
|
||||||
|
clear
|
||||||
|
cat <<"EOF"
|
||||||
|
______ _______ ____ __ __ ______ __
|
||||||
|
/ ____//_ __/ | / / /_ ____/ /______/ /_ / ____/_______ ____ _/ /____ _____
|
||||||
|
/ / / / | |/ / __ \/ __ / ___/ __ \ / / / ___/ _ \/ __ `/ __/ _ \/ ___/
|
||||||
|
/ /___ / / | / /_/ / /_/ (__ ) / / / / /___/ / / __/ /_/ / /_/ __/ /
|
||||||
|
\____/ /_/ |__/_.___/\__,_/____/_/ /_/ \____/_/ \___/\__,_/\__/\___/_/
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_info() { echo -e "${BL}[INFO]${CL} $1"; }
|
||||||
|
msg_ok() { echo -e "${GN}[OK]${CL} $1"; }
|
||||||
|
msg_warn() { echo -e "${YW}[WARN]${CL} $1"; }
|
||||||
|
msg_error() { echo -e "${RD}[ERROR]${CL} $1"; }
|
||||||
|
|
||||||
|
ensure_dependencies() {
|
||||||
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
|
apt update >/dev/null 2>&1
|
||||||
|
apt install -y curl >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v whiptail >/dev/null 2>&1; then
|
||||||
|
apt update >/dev/null 2>&1
|
||||||
|
apt install -y whiptail >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_root() {
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
msg_error "Run this script as root."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
select_repo() {
|
||||||
|
if [[ -n "$var_repo" ]]; then
|
||||||
|
case "${var_repo,,}" in
|
||||||
|
ve | proxmoxve) REPO_NAME="ProxmoxVE" ;;
|
||||||
|
ved | proxmoxved) REPO_NAME="ProxmoxVED" ;;
|
||||||
|
*)
|
||||||
|
msg_error "Invalid var_repo='$var_repo'. Use: ve|ved"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local choice
|
||||||
|
choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||||||
|
--title "Repository" \
|
||||||
|
--menu "Choose script source:" 14 60 2 \
|
||||||
|
"ProxmoxVE" "community-scripts/ProxmoxVE" \
|
||||||
|
"ProxmoxVED" "community-scripts/ProxmoxVED" \
|
||||||
|
3>&1 1>&2 2>&3) || exit 0
|
||||||
|
|
||||||
|
REPO_NAME="$choice"
|
||||||
|
}
|
||||||
|
|
||||||
|
select_mode() {
|
||||||
|
if [[ -n "$var_mode" ]]; then
|
||||||
|
case "${var_mode,,}" in
|
||||||
|
generated | mydefaults) INSTALL_MODE="${var_mode,,}" ;;
|
||||||
|
*)
|
||||||
|
msg_error "Invalid var_mode='$var_mode'. Use: generated|mydefaults"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local choice
|
||||||
|
choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||||||
|
--title "Install Mode" \
|
||||||
|
--menu "Choose unattended mode:" 13 66 2 \
|
||||||
|
"generated" "Auto-generated defaults" \
|
||||||
|
"mydefaults" "Use /usr/local/community-scripts/default.vars" \
|
||||||
|
3>&1 1>&2 2>&3) || exit 0
|
||||||
|
|
||||||
|
INSTALL_MODE="$choice"
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_file_for_repo() {
|
||||||
|
echo "${CACHE_DIR}/${REPO_NAME,,}-apps-sorted.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_cache_valid() {
|
||||||
|
local cache_file="$1"
|
||||||
|
[[ -f "$cache_file" ]] || return 1
|
||||||
|
|
||||||
|
local now age
|
||||||
|
now=$(date +%s)
|
||||||
|
age=$((now - $(stat -c %Y "$cache_file")))
|
||||||
|
[[ "$age" -lt "$var_cache_ttl" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_default_storage() {
|
||||||
|
local content="$1"
|
||||||
|
local selected=""
|
||||||
|
|
||||||
|
selected=$(pvesm status -content "$content" 2>/dev/null | awk 'NR>1 && $1=="local" {print $1; exit}')
|
||||||
|
[[ -z "$selected" ]] && selected=$(pvesm status -content "$content" 2>/dev/null | awk 'NR>1 && $1=="local-lvm" {print $1; exit}')
|
||||||
|
[[ -z "$selected" ]] && selected=$(pvesm status -content "$content" 2>/dev/null | awk 'NR>1 {print $1; exit}')
|
||||||
|
|
||||||
|
echo "$selected"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_unattended_storage() {
|
||||||
|
if [[ -z "$var_template_storage" ]]; then
|
||||||
|
var_template_storage=$(pick_default_storage vztmpl)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$var_container_storage" ]]; then
|
||||||
|
var_container_storage=$(pick_default_storage rootdir)
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "$var_template_storage" ]] && msg_info "Template storage: ${var_template_storage}"
|
||||||
|
[[ -n "$var_container_storage" ]] && msg_info "Container storage: ${var_container_storage}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch_ct_list() {
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
|
||||||
|
local cache_file
|
||||||
|
cache_file=$(cache_file_for_repo)
|
||||||
|
|
||||||
|
if [[ "$var_refresh_cache" != "yes" ]] && is_cache_valid "$cache_file"; then
|
||||||
|
msg_ok "Using cached app list (${cache_file})"
|
||||||
|
cp "$cache_file" "$TEMP_DIR/apps-sorted.txt"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local api_url="https://api.github.com/repos/community-scripts/${REPO_NAME}/contents/ct?ref=main"
|
||||||
|
|
||||||
|
msg_info "Refreshing CT list from ${REPO_NAME}..."
|
||||||
|
curl -fsSL "$api_url" >"$TEMP_DIR/ct.json"
|
||||||
|
|
||||||
|
sed -n 's/.*"name": "\([^"]*\.sh\)".*/\1/p' "$TEMP_DIR/ct.json" |
|
||||||
|
sort -f >"$TEMP_DIR/slugs.txt"
|
||||||
|
|
||||||
|
if [[ ! -s "$TEMP_DIR/slugs.txt" ]]; then
|
||||||
|
msg_error "No CT scripts found in ${REPO_NAME}."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
: >"$TEMP_DIR/apps.txt"
|
||||||
|
local total index
|
||||||
|
total=$(wc -l <"$TEMP_DIR/slugs.txt")
|
||||||
|
index=0
|
||||||
|
|
||||||
|
while IFS= read -r script_file; do
|
||||||
|
index=$((index + 1))
|
||||||
|
local slug="${script_file%.sh}"
|
||||||
|
local raw_url="https://raw.githubusercontent.com/community-scripts/${REPO_NAME}/main/ct/${script_file}"
|
||||||
|
local app_name
|
||||||
|
|
||||||
|
msg_info "[${index}/${total}] Reading app name for ${slug}"
|
||||||
|
app_name=$(curl -fsSL "$raw_url" | sed -n 's/^APP="\([^"]*\)".*/\1/p' | head -n1 || true)
|
||||||
|
[[ -z "$app_name" ]] && app_name="$slug"
|
||||||
|
|
||||||
|
printf '%s|%s\n' "$slug" "$app_name" >>"$TEMP_DIR/apps.txt"
|
||||||
|
done <"$TEMP_DIR/slugs.txt"
|
||||||
|
|
||||||
|
sort -f -t '|' -k2,2 -k1,1 "$TEMP_DIR/apps.txt" >"$TEMP_DIR/apps-sorted.txt"
|
||||||
|
cp "$TEMP_DIR/apps-sorted.txt" "$cache_file"
|
||||||
|
msg_ok "Cached app list written to ${cache_file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
select_apps() {
|
||||||
|
if [[ -n "$var_apps" ]]; then
|
||||||
|
SELECTED_APPS=$(echo "$var_apps" | tr ',' ' ')
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local menu_items=()
|
||||||
|
while IFS='|' read -r slug app_name; do
|
||||||
|
menu_items+=("$slug" "$app_name" "OFF")
|
||||||
|
done <"$TEMP_DIR/apps-sorted.txt"
|
||||||
|
|
||||||
|
if [[ ${#menu_items[@]} -eq 0 ]]; then
|
||||||
|
msg_error "No app entries available for selection."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local choice
|
||||||
|
choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||||||
|
--title "CT Batch Creator (${REPO_NAME})" \
|
||||||
|
--checklist "Select one or more CT scripts (alphabetical by app name):" 30 90 20 \
|
||||||
|
"${menu_items[@]}" 3>&1 1>&2 2>&3) || exit 0
|
||||||
|
|
||||||
|
SELECTED_APPS=$(echo "$choice" | tr -d '"')
|
||||||
|
|
||||||
|
if [[ -z "$SELECTED_APPS" ]]; then
|
||||||
|
msg_warn "No apps selected."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_selected_apps() {
|
||||||
|
local selected_count
|
||||||
|
selected_count=$(echo "$SELECTED_APPS" | wc -w)
|
||||||
|
|
||||||
|
msg_info "Starting ${selected_count} deployment(s) from ${REPO_NAME} with mode=${INSTALL_MODE}"
|
||||||
|
prepare_unattended_storage
|
||||||
|
|
||||||
|
local failed_apps=()
|
||||||
|
local done_count=0
|
||||||
|
for slug in $SELECTED_APPS; do
|
||||||
|
local script_url="https://raw.githubusercontent.com/community-scripts/${REPO_NAME}/main/ct/${slug}.sh"
|
||||||
|
local script_file="$TEMP_DIR/${slug}.sh"
|
||||||
|
done_count=$((done_count + 1))
|
||||||
|
|
||||||
|
msg_info "[${done_count}/${selected_count}] Downloading ${slug}"
|
||||||
|
curl -fsSL "$script_url" >"$script_file"
|
||||||
|
|
||||||
|
msg_info "[${done_count}/${selected_count}] Deploying ${slug}"
|
||||||
|
if MODE="$INSTALL_MODE" mode="$INSTALL_MODE" PHS_SILENT=1 \
|
||||||
|
var_template_storage="$var_template_storage" \
|
||||||
|
var_container_storage="$var_container_storage" \
|
||||||
|
bash "$script_file"; then
|
||||||
|
msg_ok "Finished ${slug}"
|
||||||
|
else
|
||||||
|
msg_error "Failed ${slug}"
|
||||||
|
failed_apps+=("$slug")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#failed_apps[@]} -gt 0 ]]; then
|
||||||
|
msg_warn "Completed with failures: ${failed_apps[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_ok "All selected CTs were processed successfully."
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
header_info
|
||||||
|
check_root
|
||||||
|
ensure_dependencies
|
||||||
|
select_repo
|
||||||
|
select_mode
|
||||||
|
fetch_ct_list
|
||||||
|
select_apps
|
||||||
|
run_selected_apps
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user