From 8721703ae99ff626220782325aa2cf119e1e5195 Mon Sep 17 00:00:00 2001 From: Fawzib Rojas Date: Mon, 28 Sep 2026 15:45:16 +0000 Subject: [PATCH 1/9] Added support for dynamic volumes --- Dockerfile | 16 ++- config/runit/crond/run | 3 + config/runit/webhook/run | 8 ++ config/webhook/hooks.json | 8 ++ scripts/entrypoint.sh | 29 +++++- scripts/ini-functions.include | 111 +++++++++++++++++++++ scripts/register-dynamic.sh | 180 ++++++++++++++++++++++++++++++++++ scripts/register-static.sh | 31 ++++++ 8 files changed, 383 insertions(+), 3 deletions(-) create mode 100755 config/runit/crond/run create mode 100755 config/runit/webhook/run create mode 100755 config/webhook/hooks.json create mode 100755 scripts/ini-functions.include create mode 100755 scripts/register-dynamic.sh create mode 100755 scripts/register-static.sh diff --git a/Dockerfile b/Dockerfile index 9656d1a..cd38894 100755 --- a/Dockerfile +++ b/Dockerfile @@ -24,9 +24,23 @@ RUN apk add --no-cache runit \ && touch /external/avahi/not-mounted \ && echo done +########################### +# DYNAMIC VOLUMES STUFF +########################### +RUN mkdir -p /dynamic-volumes +RUN apk add --no-cache webhook jq moreutils py3-pip +RUN pip install crudini --break-system-packages +########################### + VOLUME ["/shares"] -EXPOSE 137/udp 139 445 +# udp port 137: Netbios Name Service +# udp port 138: Netbios Datagram Service +# tcp port 139: SMB for Windows NT or older +# tcp port 445: SMB for Windows 2000 or newer +# tcp port 9000: Webhook + +EXPOSE 137/udp 138/udp 139 445 9000 COPY . /container/ diff --git a/config/runit/crond/run b/config/runit/crond/run new file mode 100755 index 0000000..e85c534 --- /dev/null +++ b/config/runit/crond/run @@ -0,0 +1,3 @@ +#!/bin/sh -x +sleep 14 +exec crond -f diff --git a/config/runit/webhook/run b/config/runit/webhook/run new file mode 100755 index 0000000..4df4ef0 --- /dev/null +++ b/config/runit/webhook/run @@ -0,0 +1,8 @@ +#!/bin/sh -x +sleep 14 + +if [ -z "$SAMBA_DYNAMIC_WEBHOOK_PORT" ]; then + SAMBA_DYNAMIC_WEBHOOK_PORT=9000 +fi + +exec webhook -hooks /container/config/webhook/hooks.json -port $SAMBA_DYNAMIC_WEBHOOK_PORT diff --git a/config/webhook/hooks.json b/config/webhook/hooks.json new file mode 100755 index 0000000..4cf8b38 --- /dev/null +++ b/config/webhook/hooks.json @@ -0,0 +1,8 @@ +[ + { + "id": "refresh", + "execute-command": "/container/scripts/register-dynamic.sh", + "command-working-directory": "/container/scripts", + "include-command-output-in-response": true + } +] diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index ccacf7d..63ed066 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -149,7 +149,7 @@ if [ ! -f "$INITALIZED" ]; then echo -e "$ACCOUNT_PASSWORD\n$ACCOUNT_PASSWORD" | passwd "$ACCOUNT_NAME" echo -e "$ACCOUNT_PASSWORD\n$ACCOUNT_PASSWORD" | smbpasswd "$ACCOUNT_NAME" fi - + smbpasswd -e "$ACCOUNT_NAME" done @@ -300,7 +300,7 @@ if [ ! -f "$INITALIZED" ]; then echo ">> EXTERNAL AVAHI: list of services" ls -l /external/avahi/*.service fi - + echo "" echo ">> SAMBA: check smb.conf file using 'testparm -s'" echo "############################### START ####################################" @@ -320,6 +320,31 @@ else echo ">> CONTAINER: already initialized - direct start of samba" fi +################################################## +# START - DYNAMIC VOLUMES +################################################## + +LABEL="minutes" +if [ -z "$SAMBA_DYNAMIC_INTERVAL" ] || [ "$SAMBA_DYNAMIC_INTERVAL" -lt 2 ]; then + # interval not set or less than 2, check every minute + SAMBA_DYNAMIC_INTERVAL=1 + LABEL="minute" +fi + +if echo "$SAMBA_DYNAMIC_VOLUMES" | grep -i -E "^(yes|y|true)$" > /dev/null; then + echo ">> DYNAMIC-VOLUMES: Enabled, checking every $SAMBA_DYNAMIC_INTERVAL $LABEL" + # register static shares defined in docker compose file + /container/scripts/register-static.sh + # set cron job to monitor /dynamic-volumes directory for shares + echo "*/$SAMBA_DYNAMIC_INTERVAL * * * * /container/scripts/register-dynamic.sh" >> /etc/crontabs/root +else + echo ">> DYNAMIC-VOLUMES: Disabled" +fi + +################################################## +# END - DYNAMIC VOLUMES +################################################## + ## # CMD ## diff --git a/scripts/ini-functions.include b/scripts/ini-functions.include new file mode 100755 index 0000000..010de02 --- /dev/null +++ b/scripts/ini-functions.include @@ -0,0 +1,111 @@ +#!/bin/sh + +############################ +# FILENAME FUNCTIONS +############################ + +CRUDINI_OPTIONS="--ini-options=ignoreindent" + +filename(){ + local FILENAME=$(basename "$1") + echo "${FILENAME%.*}" +} + +extname(){ + local FILENAME=$(basename "$1") + local EXTNAME="${FILENAME##*.}" + # + if [ "$FILENAME" = "$EXTNAME" ]; then + echo "" + else + echo "$EXTNAME" + fi +} + +############################ +# INI FUNCTIONS +############################ + +ini_list_sections(){ + local SOURCE="$1" + crudini --get $CRUDINI_OPTIONS "$SOURCE" +} + +ini_has_section(){ + local SOURCE="$1" + local SECTION="$2" + + if crudini --get $CRUDINI_OPTIONS "$SOURCE" | grep "$SECTION" > /dev/null; then + return 0 + else + return 1 + fi +} + +ini_print_section(){ + local SOURCE="$1" + local SECTION="$2" + + crudini --get $CRUDINI_OPTIONS --format=ini "$SOURCE" "$SECTION" | tail -n +2 +} + +ini_del_section(){ + local SOURCE="$1" + local SECTION="$2" + crudini --del $CRUDINI_OPTIONS "$SOURCE" "$SECTION" +} + +ini_list_keys(){ + local SOURCE="$1" + local SECTION="$2" + crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION" +} + +ini_get_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local DEF="$4" + # + if ! crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" 2> /dev/null; then + echo "$DEF" + fi +} + +ini_set_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local VALUE="$4" + # + crudini --set $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" "$VALUE" +} + +ini_del_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local VALUE="$4" + # + crudini --del $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" +} + +ini_copy_section(){ + local FROM_FILE="$1" + local FROM_SECTION="$2" + local TO_FILE="$3" + local TO_SECTION="$4" + local EXCLUDE="$5" + # + if [ -f "$FROM_FILE" ] && [ -z "$EXCLUDE" ]; then + ini_list_keys "$FROM_FILE" "$FROM_SECTION" | while IFS= read -r KEY ; do + VALUE=$(ini_get_key "$FROM_FILE" "$FROM_SECTION" "$KEY") + ini_set_key "$TO_FILE" "$TO_SECTION" "$KEY" "$VALUE" + done + elif [ -f "$FROM_FILE" ]; then + ini_list_keys "$FROM_FILE" "$FROM_SECTION" | grep -v -E "$EXCLUDE" | while IFS= read -r KEY ; do + VALUE=$(ini_get_key "$FROM_FILE" "$FROM_SECTION" "$KEY") + ini_set_key "$TO_FILE" "$TO_SECTION" "$KEY" "$VALUE" + done + fi +} diff --git a/scripts/register-dynamic.sh b/scripts/register-dynamic.sh new file mode 100755 index 0000000..c74b4af --- /dev/null +++ b/scripts/register-dynamic.sh @@ -0,0 +1,180 @@ +#!/bin/sh + +. "/container/scripts/ini-functions.include" + +remove_line(){ + sed -i "/$1/d; /^[[:space:]]*$/d" "$DYNAMIC_REGISTRY" +} + +register_directory(){ + local S_SOURCE="$1" + local S_DIR=$(dirname "$S_SOURCE") + local S_NAME=$(filename "$S_SOURCE") + local S_TYPE=$(ini_get_key "$VOLUME_REGISTRY" "$S_NAME" "type") + local S_CSUM=$(ini_get_key "$VOLUME_REGISTRY" "$S_NAME" "checksum") + local S_TEMPLATE="$S_DIR/directory.template" + local S_USR="nobody" + local S_GRP="nogroup" + local S_WRITABLE="no" + local CURR_CSUM=$(cat "$S_TEMPLATE" 2> /dev/null | md5sum | cut -f 1 -d " ") + # check if valid name + if echo "$S_NAME" | grep -E "global|home|printers" > /dev/null; then + # reserved section name + return 1 + elif [ "$S_TYPE" = "static" ]; then + # already registered as static + return 1 + fi + # check if already registered + if ini_has_section "$VOLUME_REGISTRY" "$S_NAME"; then + # check if checkums match + if [ "$S_CSUM" = "$CURR_CSUM" ]; then + # they match so no change, remove from delete list and return ok + remove_line "$S_NAME" + return 0 + fi + fi + # get directory information + local S_SOURCE_STAT=$(stat -L "$S_SOURCE" -c "%A %G %U") + S_USR=$(echo "$S_SOURCE_STAT" | cut -f 2 -d " ") + S_GRP=$(echo "$S_SOURCE_STAT" | cut -f 3 -d " ") + if echo "$S_SOURCE_STAT" | grep -E "^........w." > /dev/null; then + S_WRITABLE="yes" + elif echo "$S_SOURCE_STAT" | grep -E "^.....w...." > /dev/null && [ "$S_GRP" = "$SAMBA_DYNAMIC_GROUP" ]; then + S_WRITABLE="yes" + elif [ "$S_USR" = "$SAMBA_DYNAMIC_USER" ]; then + S_WRITABLE="yes" + fi + # add to registry + ini_set_key "$VOLUME_REGISTRY" "$S_NAME" "type" "dynamic" + ini_set_key "$VOLUME_REGISTRY" "$S_NAME" "checksum" "$CURR_CSUM" + # add to samba + ini_set_key "$SAMBA_CONFIG" "$S_NAME" "path" "$S_SOURCE" + ini_set_key "$SAMBA_CONFIG" "$S_NAME" "comment" "Share for $S_SOURCE in %L" + ini_set_key "$SAMBA_CONFIG" "$S_NAME" "writeable" "$S_WRITABLE" + # if templare exists add its keys + if [ -f "$S_TEMPLATE" ]; then + # copy default keys (no section) + ini_copy_section "$S_TEMPLATE" "" "$SAMBA_CONFIG" "$S_NAME" "path|comment|writeable|readable" + # copy keys for name if found + if ini_has_section "$S_TEMPLATE" "$S_NAME"; then + ini_copy_section "$S_TEMPLATE" "$S_NAME" "$SAMBA_CONFIG" "$S_NAME" "path|comment|writeable|readable" + fi + fi + # registered, remove from deleted list + remove_line "$S_NAME" + # set samba changed flag + SAMBA_CONFIG_CHANGED="yes" + echo ">> DYNAMIC-VOLUMES: Register directory [$S_NAME]" + ini_print_section "$SAMBA_CONFIG" "$S_NAME" | while IFS= read -r LINE; do + echo " $LINE" + done + # + return 0 +} + + +register_file(){ + local S_SOURCE="$1" + local S_DIR=$(dirname "$S_SOURCE") + local S_NAME=$(filename "$S_SOURCE") + local S_EXT=$(extname "$S_SOURCE") + local S_USR="nobody" + local S_GRP="nogroup" + local S_WRITABLE="no" + local CURR_CSUM=$(cat "$S_SOURCE" 2> /dev/null | md5sum | cut -f 1 -d " ") + # check if valid name + if echo "$S_NAME" | grep -E "global|home|printers" > /dev/null; then + # reserved section name + return 1 + elif [ "$S_TYPE" = "static" ]; then + # already registered as static + return 1 + fi + # read all sections in share file and add to samba + ini_list_sections "$S_SOURCE" | while IFS= read -r S_SECTION; do + local S_TYPE=$(ini_get_key "$VOLUME_REGISTRY" "$S_SECTION" "type") + local S_PATH=$(ini_get_key "$S_SOURCE" "$S_SECTION" "path") + local S_CSUM=$(ini_get_key "$VOLUME_REGISTRY" "$S_SECTION" "checksum") + # check if valid name + if echo "$S_SECTION" | grep -E "global|home|printers" > /dev/null; then + echo ">> DYNAMIC-VOLUMES: Error adding directory [$S_SECTION], reserved name " + elif [ "$S_TYPE" = "static" ]; then + echo ">> DYNAMIC-VOLUMES: Error adding directory [$S_SECTION], already exists as static" + elif df --output=source "$S_PATH" 2> /dev/null | tail -n 1 | grep -E "^\/$|^overlay$|^shm$" > /dev/null; then + echo ">> DYNAMIC-VOLUMES: Error adding directory [$S_SECTION], path to internal file" + elif [ "$S_CSUM" = "$CURR_CSUM" ]; then + # checksum same, no change, remove from delete list and continue + remove_line "$S_SECTION" + else + # add to registry + ini_set_key "$VOLUME_REGISTRY" "$S_SECTION" "type" "dynamic" + ini_set_key "$VOLUME_REGISTRY" "$S_SECTION" "checksum" "$CURR_CSUM" + # add to samba + ini_copy_section "$S_SOURCE" "$S_SECTION" "$SAMBA_CONFIG" "$S_SECTION" "" + # registered, remove from deleted list + remove_line "$S_SECTION" + # set samba changed flag + SAMBA_CONFIG_CHANGED="yes" + echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" + ini_print_section "$SAMBA_CONFIG" "$S_NAME" | while IFS= read -r LINE; do + echo " $LINE" + done + fi + done + return 0 +} + +############################ +# MAIN +############################ + +# variables + +VOLUME_DIR="/dynamic-volumes" +VOLUME_REGISTRY="/tmp/volumes.ini" +DYNAMIC_REGISTRY="/tmp/dynamic-shares.txt" +SAMBA_CONFIG="/etc/samba/smb.conf" +SAMBA_CONFIG_CHANGED="" + +# save dynamic share names + +echo "" > "$DYNAMIC_REGISTRY" +ini_list_sections "$VOLUME_REGISTRY" | while IFS= read -r S_SECTION; do + S_TYPE=$(ini_get_key "$VOLUME_REGISTRY" "$S_NAME" "type") + if [ "$S_TYPE" = "dynamic" ]; then + echo "$S_SECTION" >> "$DYNAMIC_REGISTRY" + fi +done + +# register dynamic volumes + +for S_NAME in $(find "$VOLUME_DIR" -mindepth 1 -maxdepth 1 -type f -name "*.share" -o -type d); do + if [ -d "$S_NAME" ]; then + register_directory "$S_NAME" + else + register_file "$S_NAME" + fi +done + +# remove inactive shares + +for S_NAME in $(cat "$DYNAMIC_REGISTRY"); do + echo ">> DYNAMIC-VOLUMES: Unregister directory $S_NAME" + # remove from samba + ini_del_section "$SAMBA_CONFIG" "$S_NAME" + # remove from registry + ini_del_section "$VOLUME_REGISTRY" "$S_NAME" +done + +# reload samba if needed + +if [ -z "$SAMBA_CONFIG_CHANGED" ]; then + : # do nothing, smb.conf not changed +elif ! which smbcontrol > /dev/null; then + echo ">> DYNAMIC-VOLUMES: Samba configuration changed, smbcontrol missing!!" +elif ! smbcontrol all reload-config; then + echo ">> DYNAMIC-VOLUMES: Samba configuration changed, error reloading!!" +else + echo ">> DYNAMIC-VOLUMES: Samba configuration changed, reload" +fi diff --git a/scripts/register-static.sh b/scripts/register-static.sh new file mode 100755 index 0000000..17b897e --- /dev/null +++ b/scripts/register-static.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "/container/scripts/ini-functions.include" + +############################ +# MAIN +############################ + +# variables + +VOLUME_REGISTRY="/tmp/volumes.ini" +DYNAMIC_REGISTRY="/tmp/dynamic-shares.txt" +SAMBA_CONFIG="/etc/samba/smb.conf" + +# create files + +touch "$VOLUME_REGISTRY" +touch "$DYNAMIC_REGISTRY" + +# register static volumes, only runs on start + +ini_list_sections "$SAMBA_CONFIG" | while IFS= read -r S_NAME; do + # check if valid name + if echo "$S_NAME" | grep -E "global|home|printers" > /dev/null; then + # reserved section name + continue + else + echo ">> DYNAMIC-VOLUMES: Register static directory [$S_NAME]" + ini_set_key "$VOLUME_REGISTRY" "$S_NAME" "type" "static" + fi +done From 64e76829e0c031d14742ff3ddc446adf32d5d83a Mon Sep 17 00:00:00 2001 From: Fawzib Rojas Date: Mon, 28 Sep 2026 16:54:40 +0000 Subject: [PATCH 2/9] Added option to disable monitoring and reload config with webhook --- Dockerfile | 2 +- scripts/entrypoint.sh | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index cd38894..fad4f92 100755 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ RUN apk add --no-cache runit \ # DYNAMIC VOLUMES STUFF ########################### RUN mkdir -p /dynamic-volumes -RUN apk add --no-cache webhook jq moreutils py3-pip +RUN apk add --no-cache webhook moreutils py3-pip RUN pip install crudini --break-system-packages ########################### diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 63ed066..64ea88e 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -324,19 +324,35 @@ fi # START - DYNAMIC VOLUMES ################################################## + +case "$SAMBA_DYNAMIC_INTERVAL" in + "") + # interval not set, check every minute + SAMBA_DYNAMIC_INTERVAL=1 + ;; + *[!0-9]*) + # interval not a positive number, check every minute + SAMBA_DYNAMIC_INTERVAL=1 + ;; +esac + LABEL="minutes" -if [ -z "$SAMBA_DYNAMIC_INTERVAL" ] || [ "$SAMBA_DYNAMIC_INTERVAL" -lt 2 ]; then - # interval not set or less than 2, check every minute - SAMBA_DYNAMIC_INTERVAL=1 +if [ "$SAMBA_DYNAMIC_INTERVAL" -lt 2 ]; then LABEL="minute" fi -if echo "$SAMBA_DYNAMIC_VOLUMES" | grep -i -E "^(yes|y|true)$" > /dev/null; then - echo ">> DYNAMIC-VOLUMES: Enabled, checking every $SAMBA_DYNAMIC_INTERVAL $LABEL" +if echo "$SAMBA_DYNAMIC_VOLUMES" | grep -i -E "^(enabled|yes|y|true)$" > /dev/null; then + echo ">> DYNAMIC-VOLUMES: Enabled" # register static shares defined in docker compose file /container/scripts/register-static.sh - # set cron job to monitor /dynamic-volumes directory for shares - echo "*/$SAMBA_DYNAMIC_INTERVAL * * * * /container/scripts/register-dynamic.sh" >> /etc/crontabs/root + # check interval value + if [ "$SAMBA_DYNAMIC_INTERVAL" -eq 0 ]; then + echo ">> DYNAMIC-VOLUMES: Monitoring disabled, use webhook to reload config" + else + echo ">> DYNAMIC-VOLUMES: Monitoring enabled, checking every $SAMBA_DYNAMIC_INTERVAL $LABEL" + # set cron job to monitor /dynamic-volumes directory for shares + echo "*/$SAMBA_DYNAMIC_INTERVAL * * * * /container/scripts/register-dynamic.sh" >> /etc/crontabs/root + fi else echo ">> DYNAMIC-VOLUMES: Disabled" fi From da09cf4fedfee87e52e6fd63cdc3fda10aa5bb38 Mon Sep 17 00:00:00 2001 From: Fawzib Rojas Date: Tue, 29 Sep 2026 17:40:37 +0000 Subject: [PATCH 3/9] Added validation for ini files --- config/runit/webhook/run | 5 +-- scripts/entrypoint.sh | 3 ++ scripts/ini-functions.include | 31 ++++++++++++-- scripts/register-dynamic.sh | 76 ++++++++++++++++++----------------- 4 files changed, 72 insertions(+), 43 deletions(-) diff --git a/config/runit/webhook/run b/config/runit/webhook/run index 4df4ef0..1c96060 100755 --- a/config/runit/webhook/run +++ b/config/runit/webhook/run @@ -1,8 +1,5 @@ #!/bin/sh -x -sleep 14 -if [ -z "$SAMBA_DYNAMIC_WEBHOOK_PORT" ]; then - SAMBA_DYNAMIC_WEBHOOK_PORT=9000 -fi +sleep 14 exec webhook -hooks /container/config/webhook/hooks.json -port $SAMBA_DYNAMIC_WEBHOOK_PORT diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 64ea88e..f2ce562 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -324,6 +324,9 @@ fi # START - DYNAMIC VOLUMES ################################################## +if [ -z "$SAMBA_DYNAMIC_WEBHOOK_PORT" ]; then + SAMBA_DYNAMIC_WEBHOOK_PORT=9000 +fi case "$SAMBA_DYNAMIC_INTERVAL" in "") diff --git a/scripts/ini-functions.include b/scripts/ini-functions.include index 010de02..91d2faf 100755 --- a/scripts/ini-functions.include +++ b/scripts/ini-functions.include @@ -26,16 +26,41 @@ extname(){ # INI FUNCTIONS ############################ +ini_valid(){ + local SOURCE="$1" + local SECTIONS="" + local RESULT="" + # save sections to file + if ! crudini --get $CRUDINI_OPTIONS "$SOURCE" > /tmp/sections.txt 2> /dev/null; then + RESULT="error" + else + # check sections + while IFS= read -r CURR ; do + if [ -n "$RESULT" ]; then + : # HAS ERROR + elif ! crudini --get $CRUDINI_OPTIONS "$SOURCE" "$CURR" > /dev/null 2>&1; then + RESULT="error" + fi + done < /tmp/sections.txt + fi + # + if [ -n "$RESULT" ]; then + return 1 + else + return 0 + fi +} + ini_list_sections(){ local SOURCE="$1" - crudini --get $CRUDINI_OPTIONS "$SOURCE" + crudini --get $CRUDINI_OPTIONS "$SOURCE" 2> /dev/null } ini_has_section(){ local SOURCE="$1" local SECTION="$2" - - if crudini --get $CRUDINI_OPTIONS "$SOURCE" | grep "$SECTION" > /dev/null; then + # + if crudini --get $CRUDINI_OPTIONS "$SOURCE" | grep "$SECTION" > /dev/null 2>&1; then return 0 else return 1 diff --git a/scripts/register-dynamic.sh b/scripts/register-dynamic.sh index c74b4af..6c03cc8 100755 --- a/scripts/register-dynamic.sh +++ b/scripts/register-dynamic.sh @@ -2,10 +2,6 @@ . "/container/scripts/ini-functions.include" -remove_line(){ - sed -i "/$1/d; /^[[:space:]]*$/d" "$DYNAMIC_REGISTRY" -} - register_directory(){ local S_SOURCE="$1" local S_DIR=$(dirname "$S_SOURCE") @@ -30,7 +26,7 @@ register_directory(){ # check if checkums match if [ "$S_CSUM" = "$CURR_CSUM" ]; then # they match so no change, remove from delete list and return ok - remove_line "$S_NAME" + echo "$S_NAME" >> "$DYNAMIC_REGISTRY" return 0 fi fi @@ -53,7 +49,13 @@ register_directory(){ ini_set_key "$SAMBA_CONFIG" "$S_NAME" "comment" "Share for $S_SOURCE in %L" ini_set_key "$SAMBA_CONFIG" "$S_NAME" "writeable" "$S_WRITABLE" # if templare exists add its keys - if [ -f "$S_TEMPLATE" ]; then + echo ">> DYNAMIC-VOLUMES: Register directory [$S_NAME]" + if [ ! -f "$S_TEMPLATE" ]; then + : # no template found + elif ! ini_valid "$S_TEMPLATE"; then + # file not a valid INI file + echo " ERROR: Template file $(basename $S_TEMPLATE) is malformed." + else # copy default keys (no section) ini_copy_section "$S_TEMPLATE" "" "$SAMBA_CONFIG" "$S_NAME" "path|comment|writeable|readable" # copy keys for name if found @@ -62,10 +64,9 @@ register_directory(){ fi fi # registered, remove from deleted list - remove_line "$S_NAME" + echo "$S_NAME" >> "$DYNAMIC_REGISTRY" # set samba changed flag SAMBA_CONFIG_CHANGED="yes" - echo ">> DYNAMIC-VOLUMES: Register directory [$S_NAME]" ini_print_section "$SAMBA_CONFIG" "$S_NAME" | while IFS= read -r LINE; do echo " $LINE" done @@ -83,12 +84,11 @@ register_file(){ local S_GRP="nogroup" local S_WRITABLE="no" local CURR_CSUM=$(cat "$S_SOURCE" 2> /dev/null | md5sum | cut -f 1 -d " ") - # check if valid name - if echo "$S_NAME" | grep -E "global|home|printers" > /dev/null; then - # reserved section name - return 1 - elif [ "$S_TYPE" = "static" ]; then - # already registered as static + # check if valid INI file + if ! ini_valid "$S_SOURCE"; then + # file not a valid INI file + echo ">> DYNAMIC-VOLUMES: Register directories from $(basename $S_SOURCE)" + echo " ERROR: Share file $(basename $S_SOURCE) is malformed." return 1 fi # read all sections in share file and add to samba @@ -96,16 +96,20 @@ register_file(){ local S_TYPE=$(ini_get_key "$VOLUME_REGISTRY" "$S_SECTION" "type") local S_PATH=$(ini_get_key "$S_SOURCE" "$S_SECTION" "path") local S_CSUM=$(ini_get_key "$VOLUME_REGISTRY" "$S_SECTION" "checksum") + local S_FS=$(df -P "$S_PATH" 2> /dev/null | tail -n 1 | awk '{print $1}') # check if valid name if echo "$S_SECTION" | grep -E "global|home|printers" > /dev/null; then - echo ">> DYNAMIC-VOLUMES: Error adding directory [$S_SECTION], reserved name " + echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" + echo " ERROR: [$S_SECTION] is reserved" elif [ "$S_TYPE" = "static" ]; then - echo ">> DYNAMIC-VOLUMES: Error adding directory [$S_SECTION], already exists as static" - elif df --output=source "$S_PATH" 2> /dev/null | tail -n 1 | grep -E "^\/$|^overlay$|^shm$" > /dev/null; then - echo ">> DYNAMIC-VOLUMES: Error adding directory [$S_SECTION], path to internal file" + echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" + echo " ERROR: [$S_SECTION] already registered as static" + elif echo "$S_FS" | grep -E "^\/$|^overlay$|^shm$" > /dev/null; then + echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" + echo " ERROR: [$S_SECTION] points to an internal container path" elif [ "$S_CSUM" = "$CURR_CSUM" ]; then # checksum same, no change, remove from delete list and continue - remove_line "$S_SECTION" + echo "$S_SECTION" >> "$DYNAMIC_REGISTRY" else # add to registry ini_set_key "$VOLUME_REGISTRY" "$S_SECTION" "type" "dynamic" @@ -113,11 +117,11 @@ register_file(){ # add to samba ini_copy_section "$S_SOURCE" "$S_SECTION" "$SAMBA_CONFIG" "$S_SECTION" "" # registered, remove from deleted list - remove_line "$S_SECTION" + echo "$S_SECTION" >> "$DYNAMIC_REGISTRY" # set samba changed flag SAMBA_CONFIG_CHANGED="yes" echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" - ini_print_section "$SAMBA_CONFIG" "$S_NAME" | while IFS= read -r LINE; do + ini_print_section "$SAMBA_CONFIG" "$S_SECTION" | while IFS= read -r LINE; do echo " $LINE" done fi @@ -137,15 +141,7 @@ DYNAMIC_REGISTRY="/tmp/dynamic-shares.txt" SAMBA_CONFIG="/etc/samba/smb.conf" SAMBA_CONFIG_CHANGED="" -# save dynamic share names - echo "" > "$DYNAMIC_REGISTRY" -ini_list_sections "$VOLUME_REGISTRY" | while IFS= read -r S_SECTION; do - S_TYPE=$(ini_get_key "$VOLUME_REGISTRY" "$S_NAME" "type") - if [ "$S_TYPE" = "dynamic" ]; then - echo "$S_SECTION" >> "$DYNAMIC_REGISTRY" - fi -done # register dynamic volumes @@ -157,14 +153,22 @@ for S_NAME in $(find "$VOLUME_DIR" -mindepth 1 -maxdepth 1 -type f -name "*.shar fi done -# remove inactive shares +# remove disabled volumes -for S_NAME in $(cat "$DYNAMIC_REGISTRY"); do - echo ">> DYNAMIC-VOLUMES: Unregister directory $S_NAME" - # remove from samba - ini_del_section "$SAMBA_CONFIG" "$S_NAME" - # remove from registry - ini_del_section "$VOLUME_REGISTRY" "$S_NAME" +ini_list_sections "$VOLUME_REGISTRY" | while IFS= read -r S_SECTION; do + S_TYPE=$(ini_get_key "$VOLUME_REGISTRY" "$S_SECTION" "type") + if [ "$S_TYPE" = "static" ]; then + # skip static ones + continue + elif ! grep -E "$S_SECTION" "$DYNAMIC_REGISTRY" > /dev/null; then + echo ">> DYNAMIC-VOLUMES: Unregister directory $S_SECTION" + # remove from samba + ini_del_section "$SAMBA_CONFIG" "$S_SECTION" + # remove from registry + ini_del_section "$VOLUME_REGISTRY" "$S_SECTION" + # set samba changed flag + SAMBA_CONFIG_CHANGED="yes" + fi done # reload samba if needed From 38eb6924704ac5a36dfb489ef302962b92e3db85 Mon Sep 17 00:00:00 2001 From: Fawzib Rojas Date: Wed, 30 Sep 2026 17:22:01 +0000 Subject: [PATCH 4/9] Changed how volumes are monitored --- Dockerfile | 1 + scripts/ini-functions-crudini.include | 150 ++++++++++++++++++++++++++ scripts/ini-functions-yq.include | 142 ++++++++++++++++++++++++ scripts/register-dynamic.sh | 81 +++++++------- scripts/register-static.sh | 4 +- 5 files changed, 336 insertions(+), 42 deletions(-) create mode 100755 scripts/ini-functions-crudini.include create mode 100755 scripts/ini-functions-yq.include diff --git a/Dockerfile b/Dockerfile index fad4f92..88eb4f6 100755 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,7 @@ RUN apk add --no-cache runit \ # DYNAMIC VOLUMES STUFF ########################### RUN mkdir -p /dynamic-volumes +#RUN apk add --no-cache webhook moreutils yq RUN apk add --no-cache webhook moreutils py3-pip RUN pip install crudini --break-system-packages ########################### diff --git a/scripts/ini-functions-crudini.include b/scripts/ini-functions-crudini.include new file mode 100755 index 0000000..84226f6 --- /dev/null +++ b/scripts/ini-functions-crudini.include @@ -0,0 +1,150 @@ +#!/bin/sh + +############################ +# FILENAME FUNCTIONS +############################ + +CRUDINI_OPTIONS="--ini-options=ignoreindent" + +filename(){ + local FILENAME=$(basename "$1") + echo "${FILENAME%.*}" +} + +extname(){ + local FILENAME=$(basename "$1") + local EXTNAME="${FILENAME##*.}" + # + if [ "$FILENAME" = "$EXTNAME" ]; then + echo "" + else + echo "$EXTNAME" + fi +} + +############################ +# INI FUNCTIONS +############################ + +ini_valid(){ + local SOURCE="$1" + local SECTIONS="" + local RESULT="" + # save sections to file + if ! crudini --get $CRUDINI_OPTIONS "$SOURCE" > /tmp/sections.txt 2> /dev/null; then + RESULT="error" + else + # check sections + while IFS= read -r CURR ; do + if [ -n "$RESULT" ]; then + : # HAS ERROR + elif ! crudini --get $CRUDINI_OPTIONS "$SOURCE" "$CURR" > /dev/null 2>&1; then + RESULT="error" + fi + done < /tmp/sections.txt + fi + # + if [ -n "$RESULT" ]; then + return 1 + else + return 0 + fi +} + +ini_list_sections(){ + local SOURCE="$1" + crudini --get $CRUDINI_OPTIONS "$SOURCE" 2> /dev/null +} + +ini_has_section(){ + local SOURCE="$1" + local SECTION="$2" + # + if crudini --get $CRUDINI_OPTIONS "$SOURCE" | grep "$SECTION" > /dev/null 2>&1; then + return 0 + else + return 1 + fi +} + +ini_print_section(){ + local SOURCE="$1" + local SECTION="$2" + + crudini --get $CRUDINI_OPTIONS --format=ini "$SOURCE" "$SECTION" | tail -n +2 +} + +ini_del_section(){ + local SOURCE="$1" + local SECTION="$2" + crudini --del $CRUDINI_OPTIONS "$SOURCE" "$SECTION" +} + +ini_list_keys(){ + local SOURCE="$1" + local SECTION="$2" + crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION" +} + +ini_get_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local DEF="$4" + # + if ! crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" 2> /dev/null; then + echo "$DEF" + fi +} + +ini_is_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local TEST_VALUE="$4" + local CURR_VALUE=$(crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" 2> /dev/null) + # + if [ "$TEST_VALUE" = "$CURR_VALUE" ]; then + return 0 + else + return 1 + fi +} + +ini_set_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local VALUE="$4" + # + crudini --set $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" "$VALUE" +} + +ini_del_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local VALUE="$4" + # + crudini --del $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" +} + +ini_copy_section(){ + local FROM_FILE="$1" + local FROM_SECTION="$2" + local TO_FILE="$3" + local TO_SECTION="$4" + local EXCLUDE="$5" + # + if [ -f "$FROM_FILE" ] && [ -z "$EXCLUDE" ]; then + ini_list_keys "$FROM_FILE" "$FROM_SECTION" | while IFS= read -r KEY ; do + VALUE=$(ini_get_key "$FROM_FILE" "$FROM_SECTION" "$KEY") + ini_set_key "$TO_FILE" "$TO_SECTION" "$KEY" "$VALUE" + done + elif [ -f "$FROM_FILE" ]; then + ini_list_keys "$FROM_FILE" "$FROM_SECTION" | grep -v -E "$EXCLUDE" | while IFS= read -r KEY ; do + VALUE=$(ini_get_key "$FROM_FILE" "$FROM_SECTION" "$KEY") + ini_set_key "$TO_FILE" "$TO_SECTION" "$KEY" "$VALUE" + done + fi +} diff --git a/scripts/ini-functions-yq.include b/scripts/ini-functions-yq.include new file mode 100755 index 0000000..41fc0e3 --- /dev/null +++ b/scripts/ini-functions-yq.include @@ -0,0 +1,142 @@ +#!/bin/sh + +# yq has a problem when INI keys have a colon in the name +# when they fix it I'll replace crudini with yq + +############################ +# FILENAME FUNCTIONS +############################ + +INI_PARSER="yq --input-format ini --output-format ini" + +filename(){ + local FILENAME=$(basename "$1") + echo "${FILENAME%.*}" +} + +extname(){ + local FILENAME=$(basename "$1") + local EXTNAME="${FILENAME##*.}" + # + if [ "$FILENAME" = "$EXTNAME" ]; then + echo "" + else + echo "$EXTNAME" + fi +} + +############################ +# INI FUNCTIONS +############################ + +ini_valid(){ + local SOURCE="$1" + # save sections to file + if ! $INI_PARSER "." "$SOURCE" > /dev/null 2>&1; then + return 1 + else + return 0 + fi +} + +ini_list_sections(){ + local SOURCE="$1" + $INI_PARSER ".[] | key" "$SOURCE" 2> /dev/null +} + +ini_has_section(){ + local SOURCE="$1" + local SECTION="$2" + # + if $INI_PARSER ". | has(\"$SECTION\")" "$SOURCE" | grep "true" > /dev/null 2>&1; then + return 0 + else + return 1 + fi +} + +ini_print_section(){ + local SOURCE="$1" + local SECTION="$2" + # + $INI_PARSER ".[\"$SECTION\"]" "$SOURCE" +} + +ini_del_section(){ + local SOURCE="$1" + local SECTION="$2" + # + $INI_PARSER -i "del(.[\"$SECTION\"])" "$SOURCE" +} + +ini_list_keys(){ + local SOURCE="$1" + local SECTION="$2" + # + $INI_PARSER ".[\"$SECTION\"].[] | key" "$SOURCE" +} + +ini_get_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local DEF="$4" + local VALUE=$($INI_PARSER ".[\"$SECTION\"].[\"$KEY\"]" "$SOURCE") + if [ "$VALUE" = "null" ]; then + echo "$DEF" + else + echo "$VALUE" + fi +} + +ini_is_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local TEST_VALUE="$4" + local CURR_VALUE=$($INI_PARSER ".[\"$SECTION\"].[\"$KEY\"]" "$SOURCE") + # + if [ "$TEST_VALUE" = "$CURR_VALUE" ]; then + return 0 + else + return 1 + fi +} + +ini_set_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local VALUE="$4" + # + $INI_PARSER -i ".[\"$SECTION\"].[\"$KEY\"]=\"$VALUE\"" "$SOURCE" +} + +ini_del_key(){ + local SOURCE="$1" + local SECTION="$2" + local KEY="$3" + local VALUE="$4" + # + $INI_PARSER -i "del(.[\"$SECTION\"].[\"$KEY\"])" "$SOURCE" +} + +ini_copy_section(){ + local FROM_FILE="$1" + local FROM_SECTION="$2" + local TO_FILE="$3" + local TO_SECTION="$4" + local EXCLUDE="$5" + # + if [ -f "$FROM_FILE" ] && [ -z "$EXCLUDE" ]; then + ini_list_keys "$FROM_FILE" "$FROM_SECTION" | while IFS= read -r KEY ; do + VALUE=$(ini_get_key "$FROM_FILE" "$FROM_SECTION" "$KEY") + ini_set_key "$TO_FILE" "$TO_SECTION" "$KEY" "$VALUE" + done + elif [ -f "$FROM_FILE" ]; then + ini_list_keys "$FROM_FILE" "$FROM_SECTION" | grep -v -E "$EXCLUDE" | while IFS= read -r KEY ; do + VALUE=$(ini_get_key "$FROM_FILE" "$FROM_SECTION" "$KEY") + ini_set_key "$TO_FILE" "$TO_SECTION" "$KEY" "$VALUE" + done + fi +} diff --git a/scripts/register-dynamic.sh b/scripts/register-dynamic.sh index 6c03cc8..dc0639a 100755 --- a/scripts/register-dynamic.sh +++ b/scripts/register-dynamic.sh @@ -1,13 +1,11 @@ #!/bin/sh -. "/container/scripts/ini-functions.include" +. "/container/scripts/ini-functions-crudini.include" register_directory(){ local S_SOURCE="$1" local S_DIR=$(dirname "$S_SOURCE") local S_NAME=$(filename "$S_SOURCE") - local S_TYPE=$(ini_get_key "$VOLUME_REGISTRY" "$S_NAME" "type") - local S_CSUM=$(ini_get_key "$VOLUME_REGISTRY" "$S_NAME" "checksum") local S_TEMPLATE="$S_DIR/directory.template" local S_USR="nobody" local S_GRP="nogroup" @@ -17,18 +15,13 @@ register_directory(){ if echo "$S_NAME" | grep -E "global|home|printers" > /dev/null; then # reserved section name return 1 - elif [ "$S_TYPE" = "static" ]; then + elif ini_is_key "$VOLUME_REGISTRY" "$S_NAME" "type" "static"; then # already registered as static return 1 - fi - # check if already registered - if ini_has_section "$VOLUME_REGISTRY" "$S_NAME"; then - # check if checkums match - if [ "$S_CSUM" = "$CURR_CSUM" ]; then - # they match so no change, remove from delete list and return ok - echo "$S_NAME" >> "$DYNAMIC_REGISTRY" - return 0 - fi + elif ini_is_key "$VOLUME_REGISTRY" "$S_NAME" "checksum" "$CURR_CSUM"; then + # registered and same checksum, nothing changed + ini_set_key "$VOLUME_STATE" "volumes" "$S_NAME" "added" + return 0 fi # get directory information local S_SOURCE_STAT=$(stat -L "$S_SOURCE" -c "%A %G %U") @@ -57,16 +50,16 @@ register_directory(){ echo " ERROR: Template file $(basename $S_TEMPLATE) is malformed." else # copy default keys (no section) - ini_copy_section "$S_TEMPLATE" "" "$SAMBA_CONFIG" "$S_NAME" "path|comment|writeable|readable" + ini_copy_section "$S_TEMPLATE" "global" "$SAMBA_CONFIG" "$S_NAME" "path|comment|writeable|readable" # copy keys for name if found if ini_has_section "$S_TEMPLATE" "$S_NAME"; then ini_copy_section "$S_TEMPLATE" "$S_NAME" "$SAMBA_CONFIG" "$S_NAME" "path|comment|writeable|readable" fi fi - # registered, remove from deleted list - echo "$S_NAME" >> "$DYNAMIC_REGISTRY" - # set samba changed flag - SAMBA_CONFIG_CHANGED="yes" + # registered, update state + ini_set_key "$VOLUME_STATE" "flags" "changed" "true" + ini_set_key "$VOLUME_STATE" "volumes" "$S_NAME" "added" + # print section ini_print_section "$SAMBA_CONFIG" "$S_NAME" | while IFS= read -r LINE; do echo " $LINE" done @@ -100,27 +93,27 @@ register_file(){ # check if valid name if echo "$S_SECTION" | grep -E "global|home|printers" > /dev/null; then echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" - echo " ERROR: [$S_SECTION] is reserved" - elif [ "$S_TYPE" = "static" ]; then + echo " ERROR: [$S_SECTION] is a reserved name" + elif ini_is_key "$VOLUME_REGISTRY" "$S_SECTION" "type" "static"; then echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" echo " ERROR: [$S_SECTION] already registered as static" + elif ini_is_key "$VOLUME_REGISTRY" "$S_SECTION" "checksum" "$CURR_CSUM"; then + # checksum same, no change, add to registered directories + ini_set_key "$VOLUME_STATE" "volumes" "$S_SECTION" "added" elif echo "$S_FS" | grep -E "^\/$|^overlay$|^shm$" > /dev/null; then echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" echo " ERROR: [$S_SECTION] points to an internal container path" - elif [ "$S_CSUM" = "$CURR_CSUM" ]; then - # checksum same, no change, remove from delete list and continue - echo "$S_SECTION" >> "$DYNAMIC_REGISTRY" else + echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" # add to registry ini_set_key "$VOLUME_REGISTRY" "$S_SECTION" "type" "dynamic" ini_set_key "$VOLUME_REGISTRY" "$S_SECTION" "checksum" "$CURR_CSUM" # add to samba ini_copy_section "$S_SOURCE" "$S_SECTION" "$SAMBA_CONFIG" "$S_SECTION" "" - # registered, remove from deleted list - echo "$S_SECTION" >> "$DYNAMIC_REGISTRY" - # set samba changed flag - SAMBA_CONFIG_CHANGED="yes" - echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" + # registered, update state + ini_set_key "$VOLUME_STATE" "flags" "changed" "true" + ini_set_key "$VOLUME_STATE" "volumes" "$S_SECTION" "added" + # print section ini_print_section "$SAMBA_CONFIG" "$S_SECTION" | while IFS= read -r LINE; do echo " $LINE" done @@ -137,11 +130,22 @@ register_file(){ VOLUME_DIR="/dynamic-volumes" VOLUME_REGISTRY="/tmp/volumes.ini" -DYNAMIC_REGISTRY="/tmp/dynamic-shares.txt" +VOLUME_STATE="/tmp/state.ini" SAMBA_CONFIG="/etc/samba/smb.conf" -SAMBA_CONFIG_CHANGED="" -echo "" > "$DYNAMIC_REGISTRY" +# clear volumes state + +touch "$VOLUME_STATE" +ini_del_key "$VOLUME_STATE" "flags" "changed" +ini_del_section "$VOLUME_STATE" "volumes" + +# get current volumes + +ini_list_sections "$VOLUME_REGISTRY" | while IFS= read -r S_SECTION; do + if ! ini_is_key "$VOLUME_REGISTRY" "$S_SECTION" "type" "static"; then + ini_set_key "$VOLUME_STATE" "volumes" "$S_SECTION" + fi +done # register dynamic volumes @@ -155,25 +159,24 @@ done # remove disabled volumes -ini_list_sections "$VOLUME_REGISTRY" | while IFS= read -r S_SECTION; do - S_TYPE=$(ini_get_key "$VOLUME_REGISTRY" "$S_SECTION" "type") - if [ "$S_TYPE" = "static" ]; then - # skip static ones +ini_list_keys "$VOLUME_STATE" "volumes" | while IFS= read -r S_SECTION; do + if ini_is_key "$VOLUME_STATE" "volumes" "$S_SECTION" "added"; then + # skip added ones continue - elif ! grep -E "$S_SECTION" "$DYNAMIC_REGISTRY" > /dev/null; then + else echo ">> DYNAMIC-VOLUMES: Unregister directory $S_SECTION" # remove from samba ini_del_section "$SAMBA_CONFIG" "$S_SECTION" # remove from registry ini_del_section "$VOLUME_REGISTRY" "$S_SECTION" - # set samba changed flag - SAMBA_CONFIG_CHANGED="yes" + # set changed flag + ini_set_key "$VOLUME_STATE" "flags" "changed" "true" fi done # reload samba if needed -if [ -z "$SAMBA_CONFIG_CHANGED" ]; then +if ! ini_is_key "$VOLUME_STATE" "flags" "changed" "true"; then : # do nothing, smb.conf not changed elif ! which smbcontrol > /dev/null; then echo ">> DYNAMIC-VOLUMES: Samba configuration changed, smbcontrol missing!!" diff --git a/scripts/register-static.sh b/scripts/register-static.sh index 17b897e..5c6a203 100755 --- a/scripts/register-static.sh +++ b/scripts/register-static.sh @@ -1,6 +1,6 @@ #!/bin/sh -. "/container/scripts/ini-functions.include" +. "/container/scripts/ini-functions-crudini.include" ############################ # MAIN @@ -9,13 +9,11 @@ # variables VOLUME_REGISTRY="/tmp/volumes.ini" -DYNAMIC_REGISTRY="/tmp/dynamic-shares.txt" SAMBA_CONFIG="/etc/samba/smb.conf" # create files touch "$VOLUME_REGISTRY" -touch "$DYNAMIC_REGISTRY" # register static volumes, only runs on start From 77a1f7cc725803e19910bdbb17e5d79272f4821f Mon Sep 17 00:00:00 2001 From: Fawzib Rojas Date: Fri, 2 Oct 2026 15:46:57 +0000 Subject: [PATCH 5/9] Deleted unused file --- scripts/ini-functions.include | 136 ---------------------------------- 1 file changed, 136 deletions(-) delete mode 100755 scripts/ini-functions.include diff --git a/scripts/ini-functions.include b/scripts/ini-functions.include deleted file mode 100755 index 91d2faf..0000000 --- a/scripts/ini-functions.include +++ /dev/null @@ -1,136 +0,0 @@ -#!/bin/sh - -############################ -# FILENAME FUNCTIONS -############################ - -CRUDINI_OPTIONS="--ini-options=ignoreindent" - -filename(){ - local FILENAME=$(basename "$1") - echo "${FILENAME%.*}" -} - -extname(){ - local FILENAME=$(basename "$1") - local EXTNAME="${FILENAME##*.}" - # - if [ "$FILENAME" = "$EXTNAME" ]; then - echo "" - else - echo "$EXTNAME" - fi -} - -############################ -# INI FUNCTIONS -############################ - -ini_valid(){ - local SOURCE="$1" - local SECTIONS="" - local RESULT="" - # save sections to file - if ! crudini --get $CRUDINI_OPTIONS "$SOURCE" > /tmp/sections.txt 2> /dev/null; then - RESULT="error" - else - # check sections - while IFS= read -r CURR ; do - if [ -n "$RESULT" ]; then - : # HAS ERROR - elif ! crudini --get $CRUDINI_OPTIONS "$SOURCE" "$CURR" > /dev/null 2>&1; then - RESULT="error" - fi - done < /tmp/sections.txt - fi - # - if [ -n "$RESULT" ]; then - return 1 - else - return 0 - fi -} - -ini_list_sections(){ - local SOURCE="$1" - crudini --get $CRUDINI_OPTIONS "$SOURCE" 2> /dev/null -} - -ini_has_section(){ - local SOURCE="$1" - local SECTION="$2" - # - if crudini --get $CRUDINI_OPTIONS "$SOURCE" | grep "$SECTION" > /dev/null 2>&1; then - return 0 - else - return 1 - fi -} - -ini_print_section(){ - local SOURCE="$1" - local SECTION="$2" - - crudini --get $CRUDINI_OPTIONS --format=ini "$SOURCE" "$SECTION" | tail -n +2 -} - -ini_del_section(){ - local SOURCE="$1" - local SECTION="$2" - crudini --del $CRUDINI_OPTIONS "$SOURCE" "$SECTION" -} - -ini_list_keys(){ - local SOURCE="$1" - local SECTION="$2" - crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION" -} - -ini_get_key(){ - local SOURCE="$1" - local SECTION="$2" - local KEY="$3" - local DEF="$4" - # - if ! crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" 2> /dev/null; then - echo "$DEF" - fi -} - -ini_set_key(){ - local SOURCE="$1" - local SECTION="$2" - local KEY="$3" - local VALUE="$4" - # - crudini --set $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" "$VALUE" -} - -ini_del_key(){ - local SOURCE="$1" - local SECTION="$2" - local KEY="$3" - local VALUE="$4" - # - crudini --del $CRUDINI_OPTIONS "$SOURCE" "$SECTION" "$KEY" -} - -ini_copy_section(){ - local FROM_FILE="$1" - local FROM_SECTION="$2" - local TO_FILE="$3" - local TO_SECTION="$4" - local EXCLUDE="$5" - # - if [ -f "$FROM_FILE" ] && [ -z "$EXCLUDE" ]; then - ini_list_keys "$FROM_FILE" "$FROM_SECTION" | while IFS= read -r KEY ; do - VALUE=$(ini_get_key "$FROM_FILE" "$FROM_SECTION" "$KEY") - ini_set_key "$TO_FILE" "$TO_SECTION" "$KEY" "$VALUE" - done - elif [ -f "$FROM_FILE" ]; then - ini_list_keys "$FROM_FILE" "$FROM_SECTION" | grep -v -E "$EXCLUDE" | while IFS= read -r KEY ; do - VALUE=$(ini_get_key "$FROM_FILE" "$FROM_SECTION" "$KEY") - ini_set_key "$TO_FILE" "$TO_SECTION" "$KEY" "$VALUE" - done - fi -} From 3fbd68a8877e7607c33e47747f879bab41679e75 Mon Sep 17 00:00:00 2001 From: Fawzib Rojas Date: Tue, 6 Oct 2026 14:47:58 +0000 Subject: [PATCH 6/9] Changed how volumes are detected, again --- scripts/register-dynamic.sh | 95 +++++++++++++++++++++++-------------- scripts/register-static.sh | 2 +- 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/scripts/register-dynamic.sh b/scripts/register-dynamic.sh index dc0639a..13b8404 100755 --- a/scripts/register-dynamic.sh +++ b/scripts/register-dynamic.sh @@ -2,6 +2,34 @@ . "/container/scripts/ini-functions-crudini.include" +register_action(){ + local SECTION="$1" + local ACTION="$2" + ini_set_key "$VOLUME_REGISTRY" "global" "$SECTION" "$ACTION" + # + if [ "$ACTION" = "add" ] || [ "$ACTION" = "del" ]; then + ini_set_key "$VOLUME_REGISTRY" "global" "changed" "true" + fi +} + +register_isaction(){ + local SECTION="$1" + local ACTION="$2" + if ini_is_key "$VOLUME_REGISTRY" "global" "$SECTION" "$ACTION"; then + return 0 + else + return 1 + fi +} + +register_ischanged(){ + if ini_is_key "$VOLUME_REGISTRY" "global" "changed" "true"; then + return 0 + else + return 1 + fi +} + register_directory(){ local S_SOURCE="$1" local S_DIR=$(dirname "$S_SOURCE") @@ -20,7 +48,7 @@ register_directory(){ return 1 elif ini_is_key "$VOLUME_REGISTRY" "$S_NAME" "checksum" "$CURR_CSUM"; then # registered and same checksum, nothing changed - ini_set_key "$VOLUME_STATE" "volumes" "$S_NAME" "added" + register_action "$S_NAME" "none" return 0 fi # get directory information @@ -34,6 +62,8 @@ register_directory(){ elif [ "$S_USR" = "$SAMBA_DYNAMIC_USER" ]; then S_WRITABLE="yes" fi + # remove existing + ini_del_section "$SAMBA_CONFIG" "$S_NAME" # add to registry ini_set_key "$VOLUME_REGISTRY" "$S_NAME" "type" "dynamic" ini_set_key "$VOLUME_REGISTRY" "$S_NAME" "checksum" "$CURR_CSUM" @@ -41,7 +71,7 @@ register_directory(){ ini_set_key "$SAMBA_CONFIG" "$S_NAME" "path" "$S_SOURCE" ini_set_key "$SAMBA_CONFIG" "$S_NAME" "comment" "Share for $S_SOURCE in %L" ini_set_key "$SAMBA_CONFIG" "$S_NAME" "writeable" "$S_WRITABLE" - # if templare exists add its keys + # if template exists add its keys echo ">> DYNAMIC-VOLUMES: Register directory [$S_NAME]" if [ ! -f "$S_TEMPLATE" ]; then : # no template found @@ -57,8 +87,7 @@ register_directory(){ fi fi # registered, update state - ini_set_key "$VOLUME_STATE" "flags" "changed" "true" - ini_set_key "$VOLUME_STATE" "volumes" "$S_NAME" "added" + register_action "$S_NAME" "add" # print section ini_print_section "$SAMBA_CONFIG" "$S_NAME" | while IFS= read -r LINE; do echo " $LINE" @@ -67,7 +96,6 @@ register_directory(){ return 0 } - register_file(){ local S_SOURCE="$1" local S_DIR=$(dirname "$S_SOURCE") @@ -99,20 +127,21 @@ register_file(){ echo " ERROR: [$S_SECTION] already registered as static" elif ini_is_key "$VOLUME_REGISTRY" "$S_SECTION" "checksum" "$CURR_CSUM"; then # checksum same, no change, add to registered directories - ini_set_key "$VOLUME_STATE" "volumes" "$S_SECTION" "added" + register_action "$S_SECTION" "none" elif echo "$S_FS" | grep -E "^\/$|^overlay$|^shm$" > /dev/null; then echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" echo " ERROR: [$S_SECTION] points to an internal container path" else echo ">> DYNAMIC-VOLUMES: Register directory [$S_SECTION] from $(basename $S_SOURCE)" + # remove existing + ini_del_section "$SAMBA_CONFIG" "$S_SECTION" # add to registry ini_set_key "$VOLUME_REGISTRY" "$S_SECTION" "type" "dynamic" ini_set_key "$VOLUME_REGISTRY" "$S_SECTION" "checksum" "$CURR_CSUM" # add to samba ini_copy_section "$S_SOURCE" "$S_SECTION" "$SAMBA_CONFIG" "$S_SECTION" "" # registered, update state - ini_set_key "$VOLUME_STATE" "flags" "changed" "true" - ini_set_key "$VOLUME_STATE" "volumes" "$S_SECTION" "added" + register_action "$S_SECTION" "add" # print section ini_print_section "$SAMBA_CONFIG" "$S_SECTION" | while IFS= read -r LINE; do echo " $LINE" @@ -130,22 +159,11 @@ register_file(){ VOLUME_DIR="/dynamic-volumes" VOLUME_REGISTRY="/tmp/volumes.ini" -VOLUME_STATE="/tmp/state.ini" SAMBA_CONFIG="/etc/samba/smb.conf" # clear volumes state -touch "$VOLUME_STATE" -ini_del_key "$VOLUME_STATE" "flags" "changed" -ini_del_section "$VOLUME_STATE" "volumes" - -# get current volumes - -ini_list_sections "$VOLUME_REGISTRY" | while IFS= read -r S_SECTION; do - if ! ini_is_key "$VOLUME_REGISTRY" "$S_SECTION" "type" "static"; then - ini_set_key "$VOLUME_STATE" "volumes" "$S_SECTION" - fi -done +ini_del_section "$VOLUME_REGISTRY" "global" # register dynamic volumes @@ -153,30 +171,37 @@ for S_NAME in $(find "$VOLUME_DIR" -mindepth 1 -maxdepth 1 -type f -name "*.shar if [ -d "$S_NAME" ]; then register_directory "$S_NAME" else - register_file "$S_NAME" + register_file "$S_NAME" fi done -# remove disabled volumes +# check removed -ini_list_keys "$VOLUME_STATE" "volumes" | while IFS= read -r S_SECTION; do - if ini_is_key "$VOLUME_STATE" "volumes" "$S_SECTION" "added"; then - # skip added ones +ini_list_sections "$SAMBA_CONFIG" | while IFS= read -r S_NAME; do + # check if valid name + if echo "$S_NAME" | grep -E "global|home|printers" > /dev/null; then + # reserved section name continue - else - echo ">> DYNAMIC-VOLUMES: Unregister directory $S_SECTION" - # remove from samba - ini_del_section "$SAMBA_CONFIG" "$S_SECTION" - # remove from registry - ini_del_section "$VOLUME_REGISTRY" "$S_SECTION" - # set changed flag - ini_set_key "$VOLUME_STATE" "flags" "changed" "true" - fi + elif ini_is_key "$VOLUME_REGISTRY" "$S_NAME" "type" "static"; then + # volume is static + continue + elif register_isaction "$S_NAME" "add"; then + # volume added + continue + elif register_isaction "$S_NAME" "none"; then + # volume not changed + continue + else + echo ">> DYNAMIC-VOLUMES: Unregister directory $S_NAME" + ini_del_section "$VOLUME_REGISTRY" "$S_NAME" + ini_del_section "$SAMBA_CONFIG" "$S_NAME" + register_action "$S_NAME" "del" + fi done # reload samba if needed -if ! ini_is_key "$VOLUME_STATE" "flags" "changed" "true"; then +if ! register_ischanged; then : # do nothing, smb.conf not changed elif ! which smbcontrol > /dev/null; then echo ">> DYNAMIC-VOLUMES: Samba configuration changed, smbcontrol missing!!" diff --git a/scripts/register-static.sh b/scripts/register-static.sh index 5c6a203..d4b27ae 100755 --- a/scripts/register-static.sh +++ b/scripts/register-static.sh @@ -11,7 +11,7 @@ VOLUME_REGISTRY="/tmp/volumes.ini" SAMBA_CONFIG="/etc/samba/smb.conf" -# create files +# create volume registry touch "$VOLUME_REGISTRY" From 7136e02e0e521ad6826dad3aa43b7f74e21ff768 Mon Sep 17 00:00:00 2001 From: Fawzib Rojas Date: Tue, 6 Oct 2026 16:22:36 +0000 Subject: [PATCH 7/9] Fixed INI functions --- scripts/ini-functions-crudini.include | 17 ++++++++++++++++- scripts/register-dynamic.sh | 2 +- scripts/register-static.sh | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/scripts/ini-functions-crudini.include b/scripts/ini-functions-crudini.include index 84226f6..05f71f2 100755 --- a/scripts/ini-functions-crudini.include +++ b/scripts/ini-functions-crudini.include @@ -80,10 +80,25 @@ ini_del_section(){ crudini --del $CRUDINI_OPTIONS "$SOURCE" "$SECTION" } +ini_clear_section(){ + local SOURCE="$1" + local SECTION="$2" + # check if section exists + if ! crudini --get $CRUDINI_OPTIONS "$SOURCE" | grep "$SECTION" > /dev/null 2>&1; then + return 1 + fi + # delete all keys + ini_list_keys "$SOURCE" "$SECTION" | while IFS= read -r KEY ; do + ini_del_key "$SOURCE" "$SECTION" "$KEY" + done +} + ini_list_keys(){ local SOURCE="$1" local SECTION="$2" - crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION" + if crudini --get $CRUDINI_OPTIONS "$SOURCE" | grep "$SECTION" > /dev/null 2>&1; then + crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION" + fi } ini_get_key(){ diff --git a/scripts/register-dynamic.sh b/scripts/register-dynamic.sh index 13b8404..3e4f730 100755 --- a/scripts/register-dynamic.sh +++ b/scripts/register-dynamic.sh @@ -163,7 +163,7 @@ SAMBA_CONFIG="/etc/samba/smb.conf" # clear volumes state -ini_del_section "$VOLUME_REGISTRY" "global" +ini_clear_section "$VOLUME_REGISTRY" "global" # register dynamic volumes diff --git a/scripts/register-static.sh b/scripts/register-static.sh index d4b27ae..09c678c 100755 --- a/scripts/register-static.sh +++ b/scripts/register-static.sh @@ -11,9 +11,9 @@ VOLUME_REGISTRY="/tmp/volumes.ini" SAMBA_CONFIG="/etc/samba/smb.conf" -# create volume registry +# create empty volume registry -touch "$VOLUME_REGISTRY" +echo "[global]" > "$VOLUME_REGISTRY" # register static volumes, only runs on start From ef1cb6968f2845d35af168cfbe516d79d5cdaceb Mon Sep 17 00:00:00 2001 From: Fawzib Rojas Date: Tue, 6 Oct 2026 17:08:12 +0000 Subject: [PATCH 8/9] Use ENV variables to register static shares --- scripts/register-static.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/register-static.sh b/scripts/register-static.sh index 09c678c..bc1b803 100755 --- a/scripts/register-static.sh +++ b/scripts/register-static.sh @@ -15,9 +15,11 @@ SAMBA_CONFIG="/etc/samba/smb.conf" echo "[global]" > "$VOLUME_REGISTRY" -# register static volumes, only runs on start +# register static volumes, get from environment values -ini_list_sections "$SAMBA_CONFIG" | while IFS= read -r S_NAME; do +for VAR_NAME in $(env | grep '^SAMBA_VOLUME_CONFIG_' | cut -d= -f1); do + eval "VAR_VALUE=\${$VAR_NAME}" + S_NAME=$(echo "$VAR_VALUE" | sed 's/^[[:space:]]*\[\(.*\)\].*/\1/') # check if valid name if echo "$S_NAME" | grep -E "global|home|printers" > /dev/null; then # reserved section name From f291db8e8497caa3256d4da8db11650d60ad9a46 Mon Sep 17 00:00:00 2001 From: fawzibr Date: Fri, 9 Oct 2026 12:40:50 -0400 Subject: [PATCH 9/9] Update README.md --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index cfebe93..eb9d181 100755 --- a/README.md +++ b/README.md @@ -320,3 +320,33 @@ Note: _This `wsdd2` service seems to need `CAP_NET_ADMIN` as a capability. (more cap_add: - CAP_NET_ADMIN ``` + +## Dynamic Volumes + +Added support to add dynamic volumes to the samba configuration. A directory mapped to volume '/dynamic-volumes' is monitored for files and directories added. Directories are automatically mounted and shared. Added this functionality to share automounted USB drives. There are 2 types of files monitored: + +* __\.share__ + * Format: INI file + * Adds content to samba config. Should only contain samba shares (check smb.conf for format) + +* __directory.template__ + * Format: INI file + * Default configuration for directories added to '/dynamic-volumes' + * Values added to the [global] section are added to all directories. If a custom config is needed for a directory just add values to a section with the same name as the directory (values in [global] will be added as well). + +Environment variables added: + +* __SAMBA\_DYNAMIC\_VOLUMES__ + * _optional_ + * default: disabled + * flag to enable dynamic volume monitoring. Valid values: enabled|true|yes +* __SAMBA\_DYNAMIC\_WEBHOOK\_PORT__ + * _optional_ + * default: 9000 + * webhook used to manually refresh dynamic volumes, address is http://\/hooks/refresh + +* __SAMBA\_DYNAMIC\_INTERVAL__ + * _optional_ + * default: 1 (0 to disable monitoring) + * Monitoring interval for checking changes to the dynamic volume directory (minutes). If monitoring is disabled the only way to refresh volumes is by calling the webhook. +