From faf3f4341353d5267a1eaba51a326b458d380113 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Tue, 26 Aug 2025 13:26:22 +0200 Subject: [PATCH] Create share-common.func --- scripts/global/share-common.func | 252 +++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 scripts/global/share-common.func diff --git a/scripts/global/share-common.func b/scripts/global/share-common.func new file mode 100644 index 0000000..b98d19a --- /dev/null +++ b/scripts/global/share-common.func @@ -0,0 +1,252 @@ +#!/usr/bin/env bash +# ========================================================== +# ProxMenux - Global Share Functions (reusable) +# File: scripts/global/share_common.func +# ========================================================== +# All console-visible strings MUST be wrapped with $(translate "..."). +# Comments and code are in English, as requested. +# Requires: utils.sh (show_proxmenux_logo, msg_info, msg_ok, msg_warn, msg_error, msg_title, etc.) +# ========================================================== + +# Guard against multiple sourcing +if [[ -n "${__PROXMENUX_SHARE_COMMON__}" ]]; then + return 0 +fi +__PROXMENUX_SHARE_COMMON__=1 + +# Default group name used for shared folders (can be overridden by caller) +: "${PROXMENUX_DEFAULT_SHARE_GROUP:=sharedfiles}" + +# Where to store simple mappings (dir -> group) to remember choices (optional) +: "${PROXMENUX_SHARE_MAP_DB:=/usr/local/share/proxmenux/share-map.db}" + +# Ensure the mapping DB exists (best-effort) +mkdir -p "$(dirname "$PROXMENUX_SHARE_MAP_DB")" 2>/dev/null || true +touch "$PROXMENUX_SHARE_MAP_DB" 2>/dev/null || true + +# Read mapping: returns group name for a given path +pmx_share_map_get() { + # comments in English + # Usage: pmx_share_map_get "/mnt/myshare" + local key="$1" + awk -F'=' -v k="$key" '$1==k {print $2}' "$PROXMENUX_SHARE_MAP_DB" 2>/dev/null | tail -n1 +} + +# Write mapping: persist association between directory and group +pmx_share_map_set() { + # comments in English + # Usage: pmx_share_map_set "/mnt/myshare" "sharedfiles" + local key="$1" val="$2" + # Remove any existing line for this key + sed -i "\|^${key}=|d" "$PROXMENUX_SHARE_MAP_DB" 2>/dev/null || true + echo "${key}=${val}" >> "$PROXMENUX_SHARE_MAP_DB" +} + +# Ask user to reuse an existing group or create a new one +pmx_choose_or_create_group() { + # comments in English + # OUT: echoes chosen group name + local default_group="${1:-$PROXMENUX_DEFAULT_SHARE_GROUP}" + local choice group_name + choice=$(whiptail --title "$(translate "Shared Group")" --menu "$(translate "Choose a group policy for this shared directory:")" 16 78 3 \ + "reuse" "$(translate "Use existing group (recommended)")" \ + "new" "$(translate "Create a new group for isolation")" \ + "custom" "$(translate "Enter a custom existing group name")" 3>&1 1>&2 2>&3) || { echo ""; return 1; } + + case "$choice" in + reuse) + # Ensure default group exists or create it + pmx_ensure_host_group "$default_group" || return 1 + echo "$default_group" + ;; + new) + group_name=$(whiptail --inputbox "$(translate "Enter new group name:")" 10 70 "sharedfiles-project" --title "$(translate "New Group")" 3>&1 1>&2 2>&3) || { echo ""; return 1; } + [[ -z "$group_name" ]] && { msg_error "$(translate "Group name cannot be empty.")"; echo ""; return 1; } + pmx_ensure_host_group "$group_name" || return 1 + echo "$group_name" + ;; + custom) + group_name=$(whiptail --inputbox "$(translate "Enter existing group name:")" 10 70 "$default_group" --title "$(translate "Group Name")" 3>&1 1>&2 2>&3) || { echo ""; return 1; } + [[ -z "$group_name" ]] && { msg_error "$(translate "Group name cannot be empty.")"; echo ""; return 1; } + pmx_ensure_host_group "$group_name" || return 1 + echo "$group_name" + ;; + *) echo ""; return 1;; + esac +} + +# Ensure group exists on host and return its GID +pmx_ensure_host_group() { + # comments in English + # IN: group name + # OUT: echoes GID to stdout, return 0 on success + local group_name="$1" + if ! getent group "$group_name" >/dev/null; then + msg_info "$(translate "Creating group") $group_name" + if groupadd "$group_name" >/dev/null 2>&1; then + msg_ok "$(translate "Group created:") $group_name" + else + msg_error "$(translate "Failed to create group:") $group_name" + return 1 + fi + fi + local gid + gid="$(getent group "$group_name" | cut -d: -f3)" + if [[ -z "$gid" ]]; then + msg_error "$(translate "Failed to resolve group GID for") $group_name" + return 1 + fi + echo "$gid" + return 0 +} + +# Prepare directory on host with shared permissions (root:group, 2775, default ACLs) +pmx_prepare_host_shared_dir() { + # comments in English + # IN: dir path, group name + local dir="$1" group_name="$2" + [[ -z "$dir" || -z "$group_name" ]] && { msg_error "$(translate "Internal error: missing arguments in pmx_prepare_host_shared_dir")"; return 1; } + + if [[ ! -d "$dir" ]]; then + if mkdir -p "$dir" 2>/dev/null; then + msg_ok "$(translate "Created directory on host:") $dir" + else + msg_error "$(translate "Failed to create directory on host:") $dir" + return 1 + fi + fi + + chown -R root:"$group_name" "$dir" 2>/dev/null || true + chmod -R 2775 "$dir" 2>/dev/null || true + + if command -v setfacl >/dev/null 2>&1; then + setfacl -R -m d:g:"$group_name":rwx -m d:o::rx -m g:"$group_name":rwx "$dir" 2>/dev/null || true + msg_ok "$(translate "Default ACLs applied for group inheritance.")" + else + msg_warn "$(translate "setfacl not found; default ACLs were not applied.")" + fi + return 0 +} + +# Common selector for host mountpoint base (used by SMB/NFS/Local) +pmx_select_host_mount_point() { + # comments in English + # OUT: echoes selected mount point path + local title="${1:-$(translate "Select Mount Point")}" + local default_path="${2:-/mnt/shared}" + local choice folder_name result + + while true; do + choice=$(whiptail --title "$title" --menu "$(translate "Where do you want the host folder?")" 16 76 4 \ + "mnt" "$(translate "Create folder in /mnt")" \ + "srv" "$(translate "Create folder in /srv")" \ + "media" "$(translate "Create folder in /media")" \ + "custom" "$(translate "Enter custom path")" 3>&1 1>&2 2>&3) || { echo ""; return 1; } + + case "$choice" in + mnt) + folder_name=$(whiptail --inputbox "$(translate "Enter folder name for /mnt:")" 10 70 "shared" --title "$(translate "Folder Name")" 3>&1 1>&2 2>&3) || { echo ""; return 1; } + [[ -z "$folder_name" ]] && continue + echo "/mnt/$folder_name"; return 0;; + srv) + folder_name=$(whiptail --inputbox "$(translate "Enter folder name for /srv:")" 10 70 "shared" --title "$(translate "Folder Name")" 3>&1 1>&2 2>&3) || { echo ""; return 1; } + [[ -z "$folder_name" ]] && continue + echo "/srv/$folder_name"; return 0;; + media) + folder_name=$(whiptail --inputbox "$(translate "Enter folder name for /media:")" 10 70 "shared" --title "$(translate "Folder Name")" 3>&1 1>&2 2>&3) || { echo ""; return 1; } + [[ -z "$folder_name" ]] && continue + echo "/media/$folder_name"; return 0;; + custom) + result=$(whiptail --inputbox "$(translate "Enter full path:")" 10 80 "$default_path" --title "$(translate "Custom Path")" 3>&1 1>&2 2>&3) || { echo ""; return 1; } + [[ -z "$result" ]] && continue + echo "$result"; return 0;; + esac + done +} + +# Compute next free mp index for a CT config (mp0..mpN) +pmx_compute_next_mp_index() { + # comments in English + # IN: CTID + local ctid="$1" conf="/etc/pve/lxc/${ctid}.conf" + [[ ! -f "$conf" ]] && { echo "0"; return 0; } + local used idx next=0 + used=$(awk -F: '/^mp[0-9]+:/ {print $1}' "$conf" | sed 's/mp//' | sort -n) + for idx in $used; do + [[ "$idx" -ge "$next" ]] && next=$((idx+1)) + done + echo "$next" +} + +# Add bind mount host->CT (handles privileged/unprivileged, running state) +pmx_add_bind_mount_to_ct() { + # comments in English + # IN: CTID, host_path, ct_path + local ctid="$1" host_path="$2" ct_path="$3" + local conf="/etc/pve/lxc/${ctid}.conf" + [[ -z "$ctid" || -z "$host_path" || -z "$ct_path" ]] && { msg_error "$(translate "Internal error: missing arguments in pmx_add_bind_mount_to_ct")"; return 1; } + [[ ! -f "$conf" ]] && { msg_error "$(translate "Container config not found:") $conf"; return 1; } + + # Avoid duplicates + if grep -qE "^mp[0-9]+:\s*${host_path}," "$conf"; then + msg_warn "$(translate "This host path is already present in CT config.")" + return 0 + fi + + local unpriv running mpidx mpkey + unpriv=$(awk '/^unprivileged:/ {print $2}' "$conf") + running=$(pct status "$ctid" 2>/dev/null | grep -qi running && echo "yes" || echo "no") + mpidx="$(pmx_compute_next_mp_index "$ctid")" + mpkey="mp${mpidx}" + + if [[ "$unpriv" == "1" ]]; then + # Unprivileged: append to config; requires restart to take effect + echo "${mpkey}: ${host_path},mp=${ct_path}" >> "$conf" + msg_ok "$(translate "Bind mount appended to config (unprivileged).")" + if [[ "$running" == "yes" ]]; then + msg_warn "$(translate "Restart the container to apply the mount.")" + fi + else + # Privileged: try hot-apply via pct set; fallback to config + if [[ "$running" == "yes" ]]; then + if pct set "$ctid" -mp${mpidx} "${host_path},mp=${ct_path},create=dir" >/dev/null 2>&1; then + msg_ok "$(translate "Bind mount applied live to running container (privileged).")" + else + echo "${mpkey}: ${host_path},mp=${ct_path},create=dir" >> "$conf" + msg_warn "$(translate "pct set failed; entry appended to config.")" + fi + else + echo "${mpkey}: ${host_path},mp=${ct_path},create=dir" >> "$conf" + msg_ok "$(translate "Bind mount appended to config (privileged).")" + fi + fi + return 0 +} + +# Ensure same shared group (name + GID) inside CT and add CT users to it +pmx_sync_group_in_ct() { + # comments in English + # IN: CTID, group_name, host_gid, [ct_user1 ct_user2 ...] + local ctid="$1" group_name="$2" host_gid="$3"; shift 3 + local users=("$@") + + pct exec "$ctid" -- sh -lc " + getent group ${group_name} >/dev/null || \ + (addgroup --gid ${host_gid} ${group_name} 2>/dev/null || groupadd -g ${host_gid} ${group_name} 2>/dev/null) + " >/dev/null 2>&1 + + local u + for u in "${users[@]}"; do + pct exec "$ctid" -- sh -lc "id -u \"$u\" >/dev/null 2>&1 && usermod -aG ${group_name} \"$u\" 2>/dev/null || true" >/dev/null 2>&1 + done + msg_ok "$(translate "Group synchronized inside CT and users added (if present).")" +} + +# Ensure target path exists inside CT with group+2775 +pmx_prepare_ct_target_path() { + # comments in English + # IN: CTID, ct_path, group_name + local ctid="$1" ct_path="$2" group_name="$3" + pct exec "$ctid" -- sh -lc "mkdir -p \"$ct_path\" && chgrp \"$group_name\" \"$ct_path\" && chmod 2775 \"$ct_path\"" >/dev/null 2>&1 || true + msg_ok "$(translate "Prepared CT target path with group and 2775.")" +}