Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,24 @@ 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 moreutils yq
RUN apk add --no-cache webhook 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/

Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

* __\<name\>.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://\<server\>/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.

3 changes: 3 additions & 0 deletions config/runit/crond/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh -x
sleep 14
exec crond -f
5 changes: 5 additions & 0 deletions config/runit/webhook/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh -x

sleep 14

exec webhook -hooks /container/config/webhook/hooks.json -port $SAMBA_DYNAMIC_WEBHOOK_PORT
8 changes: 8 additions & 0 deletions config/webhook/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"id": "refresh",
"execute-command": "/container/scripts/register-dynamic.sh",
"command-working-directory": "/container/scripts",
"include-command-output-in-response": true
}
]
48 changes: 46 additions & 2 deletions scripts/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 ####################################"
Expand All @@ -320,6 +320,50 @@ else
echo ">> CONTAINER: already initialized - direct start of samba"
fi

##################################################
# START - DYNAMIC VOLUMES
##################################################

if [ -z "$SAMBA_DYNAMIC_WEBHOOK_PORT" ]; then
SAMBA_DYNAMIC_WEBHOOK_PORT=9000
fi

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 [ "$SAMBA_DYNAMIC_INTERVAL" -lt 2 ]; then
LABEL="minute"
fi

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
# 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

##################################################
# END - DYNAMIC VOLUMES
##################################################

##
# CMD
##
Expand Down
165 changes: 165 additions & 0 deletions scripts/ini-functions-crudini.include
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#!/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_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"
if crudini --get $CRUDINI_OPTIONS "$SOURCE" | grep "$SECTION" > /dev/null 2>&1; then
crudini --get $CRUDINI_OPTIONS "$SOURCE" "$SECTION"
fi
}

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
}
Loading