This commit is contained in:
ai-dev
2025-10-11 12:14:55 +02:00
parent 0085404253
commit e8681cdead
93 changed files with 3680 additions and 671 deletions

View File

@@ -0,0 +1,11 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Take down the S6 supervision tree based on service exit code
# ==============================================================================
if [[ "${1}" -ne 0 ]] && [[ "${1}" -ne 256 ]]; then
bashio::log.warning "avahi crashed, halting add-on"
exec /run/s6/basedir/bin/halt
fi
bashio::log.info "avahi stopped"

View File

@@ -0,0 +1,12 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Start avahi service
# ==============================================================================
declare SMB_HOST
SMB_HOST=$(grep -i '^\s*netbios name\s*=' /etc/samba/smb.conf | cut -f2 -d= | tr -d '[:blank:]')
bashio::log.info "Starting the AVAHI for ${SMB_HOST%.*}..."
exec avahi-publish-service -v -f -s "${SMB_HOST%.*}" _smb._tcp 445

View File

@@ -0,0 +1 @@
longrun

View File

@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/cifs-supervisor-mount/finish

View File

@@ -0,0 +1,17 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Take down the S6 supervision tree based on service exit code
# ==============================================================================
if [ -f /tmp/cifs_network ]; then
bashio::log.info "Umount Network Automount Shares..."
available_shares=$(awk '/\[(.*)\]/{ DISK=substr($1,2,length($1)-2); next } /.*path =(.*)/{ printf "%s\n",DISK,$0 }' /etc/samba/smb.conf)
while read -r -a device; do
[[ "share config addons ssl backup media all_addon_configs homeassistant" =~ ${device,,} ]] && continue
status=$(bashio::api.supervisor DELETE /mounts/${device})
bashio::log.info "Return from Umount ${status}"
done <<<"${available_shares}"
fi

View File

@@ -0,0 +1,57 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Amoutomount Shares with new system!
# ==============================================================================
if [ -f /tmp/cifs_network ]; then
#bashio::log.level all
bashio::log.info "Automount Shares..."
ipaddress=$(bashio::addon.ip_address)
#username=$(bashio::config 'username')
#password=$(bashio::config 'password')
username=$(jq -r '.username' </tmp/auth.json)
password=$(jq -r '.password' </tmp/auth.json)
available_shares=$(awk '/\[(.*)\]/{ DISK=substr($1,2,length($1)-2); next } /.*path =(.*)/{ printf "%s\n",DISK,$0 }' /etc/samba/smb.conf)
#info=$(bashio::api.supervisor GET /host/info false)
#bashio::log "Info: ${info}"
#mounts=$(bashio::api.supervisor GET /mounts false)
#bashio::log "Mounts: ${mounts}"
#status=$(smbcontrol smbd ping)
#bashio::log "Samba Ready: ${status}"
#bashio::log "Children: $(smbcontrol smbd num-children)"
bashio::log.info "Wait Samba Server to going up..(max 60s)"
# timeout 30 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' ${ipaddress/\/*/} 445
bashio::net.wait_for 445 ${ipaddress/\/*/} 60
# smbstatus
while read -r -a device; do
[[ ${device,,} == @(share|config|addons|ssl|backup|media|addon_configs|homeassistant) ]] && continue
usage=$(jq -r --arg xshare "$device" '.acl[] | select(.share==$xshare) | .usage // "media"' <<<"$(bashio::addon.config)")
cmdshare=$(jq -nrc --arg usage "${usage:-media}" --arg share "$device" --arg ip "${ipaddress/\/*/}" --arg user "$username" --arg pwd "$password" '.name=$share|.usage=$usage|.type="cifs"|.server=$ip|.share=$share|.username=$user|.password=$pwd')
#bashio::log.info $(jq '.password="********"' <<<${cmdshare})
#bashio::log.info $(bashio::api.supervisor GET /mounts)
#bashio::log.info $(bashio::api.supervisor GET /mounts | jq -r --arg xshare "$device" '.mounts[] | select(.name == $xshare ) // empty')
if [[ ! -z $(bashio::api.supervisor GET /mounts | jq -r --arg xshare "$device" '.mounts[] | select(.name == $xshare ) // empty') ]]; then
bashio::log.info "Share Already Found ${device} - Remove!"
bashio::api.supervisor DELETE /mounts/${device} || true
fi
for rt in {1..3}; do
status=$(bashio::api.supervisor POST /mounts "${cmdshare}") && break
bashio::log.warning "Retry ${rt}/3 Error Automount ${device} Msg: $(jq -c '.password="********"' <<<${cmdshare})"
sleep 3
done
done <<<"${available_shares}"
fi

View File

@@ -0,0 +1 @@
oneshot

View File

@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/cifs-supervisor-mount/run

View File

@@ -0,0 +1,10 @@
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Take down the S6 supervision tree based on service exit code
# ==============================================================================
if [[ "${1}" -ne 0 ]] && [[ "${1}" -ne 256 ]]; then
bashio::log.warning "hd-idle crashed, halting add-on"
exec /run/s6/basedir/bin/halt
fi
bashio::log.info "hd-idle stopped"

View File

@@ -0,0 +1,13 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Start hd-idle service
# ==============================================================================
if bashio::config.has_value 'hdd_idle_seconds' && ! bashio::config.equals 'hdd_idle_seconds' '0'; then
bashio::log.info "Enabling HDD IDLE after $(bashio::config 'hdd_idle_seconds')sec"
bashio::log.warning "HDD IDLE is subject to host file-handle policy. So severals minutes can be wait before real IDLE can be performed!"
mkfifo /tmp/hdidle.events || true
exec hd-idle -i "$(bashio::config 'hdd_idle_seconds')" | tee /tmp/hdidle.events
else
exec sleep infinity
fi

View File

@@ -0,0 +1 @@
longrun

View File

@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-automount/finish

View File

@@ -0,0 +1,27 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Umount all drivers.
# ==============================================================================
declare interface
declare ipaddress
interface=$(bashio::network.name)
ipaddress=$(bashio::network.ipv4_address ${interface})
if [[ -f /tmp/local_mount ]]; then
readarray -t umdev </tmp/local_mount
if [ ${#umdev[@]} -gt 0 ]; then
bashio::log.info "Unmount drivers:\n$(printf "%s\n" "${umdev[@]}")"
umount "${umdev[@]}" 2>/dev/null || true
fi
fi
if [[ -f /tmp/remote_mount ]]; then
readarray -t umdev </tmp/remote_mount
if [ ${#umdev[@]} -gt 0 ]; then
bashio::log.info "Unmount Host drivers:\n$(printf "%s\n" "${umdev[@]}")"
line=$(printf "\"%s\" " "${umdev[@]}")
ssh root@${ipaddress%/*} -p 22222 -o "StrictHostKeyChecking no" "umount $line" || true
fi
fi
bashio::log.info "Bye."

View File

@@ -0,0 +1,313 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Mounting external HD and modify the smb.conf
# ==============================================================================
declare moredisks
declare autodisks
declare tomountdisks
declare interface
declare ipaddress
declare ssh_private_key
declare remote_mount
declare network_mount
declare fstypes
declare dev
declare mntops
declare reserved_names
function disk2label() { # $1 disk return (label disk or id)
local disk=$1
if [[ $disk == id:* ]]; then
disk=${disk:3}
if [ -L /dev/disk/by-id/"$disk" ]; then
label=$(lsblk -no label /dev/disk/by-id/"$disk")
# fstype=$(lsblk -no fstype /dev/disk/by-id/"$disk")
if [[ -n "$label" && -L "/dev/disk/by-label/$label" ]]; then
bashio::log.info "Disk with id ${disk} is labeled $label so $label is used."
disk="$label"
# elif [[ $fstype == apfs ]]; then
# disk=$(apfsutil /dev/disk/by-id/"$disk" | awk -F "[, ]+" '/Name:/{print $2}')
else
disk=$1
fi
else
bashio::log.warning "Disk with id ${disk} not found."
return 1
fi
else
blkid -L "$disk" >>/dev/null || {
bashio::log.warning "Disk with label ${disk} not found."
return 1
}
fi
echo "$disk"
return 0
}
# Check for reserved mount name
function reserved_mount_name() { # $1 disk
disk=$1
reserved_names=(config addons ssl share backup media all_addon_configs homeassistant)
# Clean reserved name with disaled
for rdisk in "${reserved_names[@]}"; do
deleted=$(jq -r --arg share "${rdisk,,}" '.acl[] | select( (.share|ascii_upcase) == ($share|ascii_upcase) ) | select (.disabled) | .share' </data/options.json)
reserved_names=("${reserved_names[@]/$deleted/}")
done
# Clean tomountdisks
for rdisk in "${reserved_names[@]}"; do
if [[ ${rdisk,,} = ${disk,,} ]]; then
tomountdisks=("${tomountdisks[@]/$disk/}")
return 0
fi
done
}
# mount a disk from parameters
function mount_disk() { # $1 disk $2 path $3 remote_mount $4 mount_options
disk=$1
path=$2
remote_mount=$3
mntops=$4
if [[ $disk == id:* ]]; then
bashio::log.debug "Disk ${disk:3} is an ID"
if [ -L "/dev/disk/by-uuid/${disk:3}" ]; then
dev=/dev/disk/by-uuid/${disk:3}
disk=${disk:3}
elif [ -L "/dev/disk/by-id/${disk:3}" ]; then
dev=/dev/disk/by-id/${disk:3}
disk=${disk:3}
elif [ -L "/dev/disk/by-partuuid/${disk:3}" ]; then
dev=/dev/disk/by-partuuid/${disk:3}
disk=${disk:3}
else
unset dev
fi
else
dev=$(blkid -L "$disk")
fi
if [ ! ${dev:+1} ]; then
bashio::log.info "Disk ${disk} not found! <SKIP>"
return 0
fi
mdisk=$(printf %b "$disk")
mkdir -p "$path/$mdisk"
chmod a+rwx "$path/$mdisk"
# check with findmnt if the disk is already mounted
if findmnt -n -o TARGET "$path/$mdisk" >/dev/null 2>&1; then
bashio::log.info "Disk ${mdisk} is already mounted"
echo "$path"/"$mdisk" >>/tmp/local_mount
return 0
else
# Check FS type and set relative options
fstype=$(lsblk "$dev" -no fstype)
options="${mntops}"
type="auto"
cmd="mount"
case "$fstype" in
exfat | vfat | msdos)
bashio::log.warning "Your ${mdisk} is ${fstype}. Permissions and ACL don't works and this is an EXPERIMENTAL support"
options="${options},umask=000"
;;
ntfs)
bashio::log.warning "Your ${mdisk} is ${fstype}. This is an EXPERIMENTAL support"
options="${options},umask=000"
type="ntfs3"
;;
apfs)
bashio::log.warning "Your ${mdisk} is ${fstype}. This is an EXPERIMENTAL support and work only in RO. Mount options not supported!"
type=""
options=""
cmd="mount.apfs"
;;
esac
# Create mount arg array
m_args=()
if [ -n "$type" ]; then
m_args+=("-t" $type)
fi
if [ -n "$options" ]; then
m_args+=("-o" $options)
fi
m_args+=("${dev}")
bashio::log.debug "Mounting ${mdisk} of type ${fstype} with ${m_args[@]}"
if [ "$remote_mount" = true ]; then
ssh root@"${ipaddress%/*}" -p 22222 -o "StrictHostKeyChecking no" "if findmnt '/mnt/data/supervisor/media/$mdisk ' >/dev/null; then echo 'Disk $mdisk already mounted on host' ; else $cmd ${m_args[@]} '/mnt/data/supervisor/media/$mdisk'; fi" &&
echo "$dev" >>/tmp/remote_mount
fi || bashio::log.warning "Host Mount ${mdisk}[${fstype}] Fail!" || :
bashio::log.debug "Exec command: ${cmd} ${m_args[@]} \"${path}/${mdisk}\""
$cmd ${m_args[@]} "$path/$mdisk" &&
echo "$path"/"$mdisk" >>/tmp/local_mount &&
jq --arg dname "${mdisk}" --arg path "${path}/${mdisk}" --arg fs "${fstype}" ' . += {($dname | gsub( "-"; "_") | ascii_upcase ):{"path":$path,"fs":$fs}}' /tmp/local_mount.json >/tmp/local_mount.json.tmp &&
mv /tmp/local_mount.json.tmp /tmp/local_mount.json &&
bashio::log.info "Mount ${mdisk}[${fstype}] Success!"
fi
}
# Error for Operating System
if ! [[ "$(bashio::info.operating_system)" =~ ^Home\ Assistant\ OS.* ]]; then
bashio::log.warning "Your operating system $(bashio::info.operating_system) is not supported! "
bashio::log.red "+------------------------------------------------------------------+"
bashio::log.red "| THIS ADDON IS DESIGNED FOR HOME ASSISTANT OPERATING SYSTEM ONLY! |"
bashio::log.red "| THIS ADDON IS DESIGNED FOR HOME ASSISTANT OPERATING SYSTEM ONLY! |"
bashio::log.red "| THIS ADDON IS DESIGNED FOR HOME ASSISTANT OPERATING SYSTEM ONLY! |"
bashio::log.red "| THIS ADDON IS DESIGNED FOR HOME ASSISTANT OPERATING SYSTEM ONLY! |"
bashio::log.red "| THIS ADDON IS DESIGNED FOR HOME ASSISTANT OPERATING SYSTEM ONLY! |"
bashio::log.red "+------------------------------------------------------------------+"
if bashio::config.exists 'meaning_of_life' && [[ $(bashio::config 'meaning_of_life') -eq 42 ]]; then
bashio::log.green "Deep Thought permission accepted!"
else
bashio::exit.nok "You can force this addon to run only if you known the meaning of life!"
fi
fi
# Mount external drive
bashio::log.info "Protection Mode is $(bashio::addon.protected)"
# shellcheck disable=SC2091
if $(bashio::addon.protected) && (bashio::config.has_value 'moredisks' || bashio::config.true 'automount'); then
bashio::log.warning "MoreDisk and Automount ignored because ADDON in Protected Mode!"
bashio::config.suggest "protected" "moredisk only work when Protection mode is disabled"
elif bashio::config.has_value 'moredisks' || bashio::config.true 'automount'; then
bashio::log.info "MoreDisk or Automount option found!"
# Check supported FS
for mfs in ntfs3 exfat btrfs xfs; do
modprobe $mfs || bashio::log.warning "$mfs module not available!"
done
fstypes=$(grep -v nodev </proc/filesystems | tr -d '\n')
bashio::log.blue "---------------------------------------------------"
bashio::log.green "Supported fs: ${fstypes}"
if grep -q fuseblk </proc/filesystems; then bashio::log.green "Supported fusefs: $(find /usr/sbin -name "mount*" | cut -c 17- | tr "\n" " " | sed s/fuse.//g)"; fi
bashio::log.blue "---------------------------------------------------"
# Check Host Ssh config
remote_mount=false
network_mount=false
path=/mnt
if bashio::config.true 'medialibrary.enable'; then
bashio::log.info "MediaLibrary option found!"
if bashio::config.is_empty 'medialibrary.ssh_private_key'; then
# Check OS Capability
features=$(bashio::info 'supervisor.info.features' '.features')
#bashio::log "Features ${features}"
if grep \"mount\" <<<"${features}" >/dev/null; then
touch "/tmp/cifs_network"
else
bashio::log.warning "Unsupported Mount Feature by system!"
bashio::config.suggest "ssh_private_key" "Your host system don't upport mount feature\nSSH Private Key is required for enable medialibrary deprected feature."
fi
else
bashio::log.red "+-------------------------------------------------------------------------------------+"
bashio::log.warning "|SSH Private Key *DEPRECATED WARNING* The use of old experimental system is deprecated|"
bashio::log.warning "|Remove the key and try the new system to mound /media and /share data disks |"
bashio::log.red "+-------------------------------------------------------------------------------------+"
interface=$(bashio::network.name)
ipaddress=$(bashio::network.ipv4_address "${interface}")
ssh_private_key=$(bashio::config 'medialibrary.ssh_private_key')
mkdir -p /root/.ssh
echo "${ssh_private_key}" >/root/.ssh/id_rsa
chmod ag-rw /root/.ssh/id_rsa
if ssh root@"${ipaddress%/*}" -p 22222 -o "StrictHostKeyChecking no" "date"; then
bashio::log.info "SSH connection to ${ipaddress%/*}:22222 OK"
remote_mount=true
path=/media
else
bashio::log.warning "SSH connection to ${ipaddress%/*}:22222 FAILED"
bashio::log.warning "MediaLibrary disabled due error in config!"
fi
fi
else
bashio::log.info "MediaLibrary disabled in config. Disk are mounted only for this addon!"
fi
OIFS=$IFS
IFS=$'\n'
## List available Disk with Labels and Id
if bashio::config.true 'available_disks_log' || bashio::config.true 'automount'; then
bashio::log.blue "---------------------------------------------------"
#readarray -t autodisks < <(lsblk -E label -n -o label -i | sed -r '/^\s*$/d' | grep -v hassos)
readarray -t autodisks < <(/usr/bin/poetry -C /usr/local/bin/ run python /usr/local/bin/disklist.py)
if [ ${#autodisks[@]} -eq 0 ]; then
bashio::log.info "No Disk with labels."
else
bashio::log.info "Available Disk Labels:"
for disk in "${autodisks[@]}"; do
if [[ $disk == id:* ]]; then
bashio::log.info "\t${disk}[$(lsblk $(blkid -U "${disk:3}") -no fstype)]"
else
bashio::log.info "\t${disk}[$(lsblk $(blkid -L "$disk") -no fstype)]"
fi
done
fi
bashio::log.blue "---------------------------------------------------"
fi
mnt_ops=($(bashio::config 'mountoptions'))
mnt_ops=$(
IFS=,
echo "${mnt_ops[*]}"
)
moredisks=($(bashio::config 'moredisks'))
if [ ${#moredisks[@]} -eq 0 ]; then
bashio::log.info "No MoreDisks to mount"
else
bashio::log.info "MoreDisks to mount:\n" $(printf "\t%s\n" "${moredisks[@]}")
i=0
mmoredisks=()
for index in "${!moredisks[@]}"; do
tmpd=$(disk2label "${moredisks[$index]}") &&
mmoredisks[$i]=$tmpd &&
((i = i + 1))
done
moredisks=("${mmoredisks[@]}")
fi
if bashio::config.true 'automount' && [ ${#autodisks[@]} -gt 0 ]; then
bashio::log.info "Automount is Enabled!"
tomountdisks=("${autodisks[@]}" "${moredisks[@]}")
tomountdisks=($(sort -u <<<"${tomountdisks[*]}"))
else
tomountdisks=("${moredisks[@]}")
fi
if [ ${#tomountdisks[@]} -gt 0 ]; then
bashio::log.magenta "---------------------------------------------------"
bashio::log.info "Checking Mounting disks for reserved names:\n" $(printf "\t%s\n" "${tomountdisks[@]}")
bashio::log.magenta "---------------------------------------------------"
for disk in "${tomountdisks[@]}"; do
reserved_mount_name "$disk" || bashio::log.warning "Fail to mount ${disk} due to reserved name!"
done
fi
echo "{}" >/tmp/local_mount.json
if [ ${#tomountdisks[@]} -gt 0 ]; then
bashio::log.magenta "---------------------------------------------------"
bashio::log.info "Mounting disks:\n" $(printf "\t%s\n" "${tomountdisks[@]}")
bashio::log.magenta "---------------------------------------------------"
for disk in "${tomountdisks[@]}"; do
mount_disk "$disk" "$path" "$remote_mount" "$mnt_ops" || bashio::log.warning "Fail to mount ${disk} ${mnt_ops} !"
done
fi
IFS=$OIFS
echo "$path" >/tmp/mountpath
fi

View File

@@ -0,0 +1 @@
oneshot

View File

@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-automount/run

View File

@@ -0,0 +1,47 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Prepare the MQTT config for running
# ==============================================================================
readonly CONF="/root/.config/mosquitto_pub"
readonly CONF_SUB="/root/.config/mosquitto_sub"
declare host
declare username
declare password
declare port
declare topic
if bashio::config.true "mqtt_enable"; then
topic=$(bashio::config 'mqtt_topic' "sambanas")
host=$(bashio::config 'mqtt_host' "$(bashio::services 'mqtt' 'host')")
username=$(bashio::config 'mqtt_username' "$(bashio::services 'mqtt' 'username')")
password=$(bashio::config 'mqtt_password' "$(bashio::services 'mqtt' 'password')")
port=$(bashio::config 'mqtt_port' "$(bashio::services 'mqtt' 'port')")
topic=$(bashio::config 'mqtt_topic')
#bashio::log.info "Init MQTT config ${host}:${port} ${username}:${password}"
[ -z "$host" ] && bashio::log.warning "No MQTT Server found. Homeassistant integration can't work!"
if bashio::var.has_value "host" && ! bashio::config.false "mqtt_enable" && [ -n "$host" ]; then
{
echo "-h ${host}"
echo "--username ${username}"
echo "--pw ${password}"
echo "--port ${port}"
} >"${CONF}"
{
echo "-h ${host}"
echo "--username ${username}"
echo "--pw ${password}"
echo "--port ${port}"
} >"${CONF_SUB}"
fi
else
bashio::log.info "MQTT support not enabled in config"
fi

View File

@@ -0,0 +1 @@
oneshot

View File

@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-mqtt/run

View File

@@ -0,0 +1,99 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# vim: ft=bash
# ==============================================================================
# Prepare the Samba service for running
# ==============================================================================
declare password
declare username
declare -a interfaces=()
export HOSTNAME
# Check Login data
bashio::config.require 'username'
bashio::config.require 'password'
# Read hostname from API or setting default "hassio"
HOSTNAME=$(bashio::info.hostname)
if bashio::var.is_empty "${HOSTNAME}" || [ "${HOSTNAME}" == "null" ]; then
bashio::log.warning "Can't read hostname, using default."
HOSTNAME="homeassistant"
fi
bashio::log.info "Hostname: ${HOSTNAME}"
if bashio::config.has_value 'interfaces'; then
bashio::log.debug "Interfaces from config: $(bashio::config 'interfaces')"
for interface in $(bashio::config 'interfaces'); do
if [ -d "/sys/class/net/${interface}" ]; then
interfaces+=("${interface}")
else
bashio::log.warning "Interface ${interface} not found, skipping."
fi
done
else
# Get supported interfaces
for interface in $(bashio::network.interfaces); do
interfaces+=("${interface}")
done
fi
if [ ${#interfaces[@]} -eq 0 ]; then
bashio::exit.nok 'No supported interfaces found to bind on.'
fi
bashio::log.info "Interfaces: $(printf '%s ' "${interfaces[@]}")"
bashio::log.info "Docker Interface: $(bashio::network 'network.info.docker.inerface' '.docker.interface') $(bashio::network 'network.info.docker.network' '.docker.address')"
# Generate Samba configuration.
touch /tmp/local_mount
if [[ ! -e /tmp/local_mount.json ]]; then
echo "{}" >/tmp/local_mount.json
fi
jq ".shares = $(jq -c </tmp/local_mount.json) | .interfaces = $(jq -c -n '$ARGS.positional' --args -- "${interfaces[@]}") | .docker_interface = \"$(bashio::network 'network.info.docker.inerface' '.docker.interface')\" | .docker_net = \"$(bashio::network 'network.info.docker.network' '.docker.address')\" | .moredisks = $(jq -R -s -c 'split("\n") | map(select(length > 0)) | [ .[] | ltrimstr("/") ]' </tmp/local_mount) " /data/options.json |
tee /config/bootconfig.json |
tempio \
-template /usr/share/tempio/smb.gtpl \
-out /etc/samba/smb.conf
if [[ "${__BASHIO_LOG_LEVEL_TRACE}" -eq "${__BASHIO_LOG_LEVEL}" ]]; then
bashio::log.info "${__BASHIO_LOG_LEVEL_TRACE} ${__BASHIO_LOG_LEVEL}"
bashio::log.trace "Dump SMB.conf to ADDON_CONFIG/$(hostname) share"
cp /etc/samba/smb.conf /config/smb.conf.dump
fi
function addSambaUser() { # $1 username $2 password
username=$1
password=$2
addgroup "${username}"
adduser -D -H -G "${username}" -s /bin/false "${username}"
(
echo "$password"
echo "$password"
) |
smbpasswd -a -s -c "/etc/samba/smb.conf" "${username}"
}
# Init user
username=$(bashio::config 'username')
password=$(bashio::config 'password')
addSambaUser "${username}" "${password}"
# Init superuser
if [ -f /tmp/cifs_network ]; then
username="_ha_mount_user_"
password=$(sed 's/[-]//g' /proc/sys/kernel/random/uuid | head -c 20)
addSambaUser "${username}" "${password}"
jq -n --arg username "${username}" --arg password "${password}" '{username:$username, password:$password}' >/tmp/auth.json
fi
# Create other users
for user in $(bashio::config 'other_users'); do
username=$(echo "${user}" | jq -r '.username')
password=$(echo "${user}" | jq -r '.password')
addSambaUser "${username}" "${password}"
done
# Log exposed mounted shares
bashio::log.blue "---------------------------------------------------"
bashio::log.info "Exposed Disks Summary:\n$(awk '/\[.*\]$/{ DISK=$0; next } /.*path =(.*)/{ PATH=$0; next} /.*TM:(.*)/{ printf "%-20s %s %s#\n",DISK,PATH,$0 }' /etc/samba/smb.conf)"
bashio::log.blue "---------------------------------------------------"

View File

@@ -0,0 +1 @@
oneshot

View File

@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-samba/run

View File

@@ -0,0 +1,14 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Prepare the SMART config for disks
# ==============================================================================
if ! bashio::config.false "enable_smart"; then
smartctl --scan-open | while read -r -a device; do
bashio::log.info "Enabling S.M.A.R.T for ${device[0]}"
smartctl --smart=on --offlineauto=on --saveauto=on --quietmode=errorsonly "${device[0]}" || true
done
else
bashio::log.info "SMART support disabled in config"
fi

View File

@@ -0,0 +1 @@
oneshot

View File

@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-smartd/run

View File

@@ -1,166 +0,0 @@
#!/command/with-contenv bashio
# vim: ft=bash
# shellcheck shell=bash
# ==============================================================================
# Prepare the Samba service for running
# ==============================================================================
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
if ! bashio::supervisor.ping 2> /dev/null; then
echo "..."
exit 0
fi
bashio::log.notice "This script is used to mount local USB/SATA/SD/NVMe drives. Instructions here : https://github.com/alexbelgium/hassio-addons/wiki/Mounting-Local-Drives-in-Addons"
######################
# MOUNT LOCAL SHARES #
######################
# Mount local Share if configured
if bashio::config.has_value 'localdisks'; then
# Available devices
blkid | awk '{print substr($1, 0, length($1) - 1)}' | awk -F'/' '{print $NF}' > availabledisks
echo "NAME" >> availabledisks
## List available Disk with Labels and Id
bashio::log.blue "---------------------------------------------------"
bashio::log.info "Available Disks for mounting :"
lsblk -o name,label,size,fstype,ro | awk '$4 != "" { print $0 }' | grep -f availabledisks
bashio::log.blue "---------------------------------------------------"
rm availabledisks
# Show support fs https://github.com/dianlight/hassio-addons/blob/2e903184254617ac2484fe7c03a6e33e6987151c/sambanas/rootfs/etc/s6-overlay/s6-rc.d/init-automount/run#L106
fstypessupport=$(grep -v nodev < /proc/filesystems | awk '{$1=" "$1}1' | tr -d '\n\t')
bashio::log.green "Supported fs : ${fstypessupport}"
bashio::log.green "Inspired from : github.com/dianlight"
bashio::log.blue "---------------------------------------------------"
MOREDISKS=$(bashio::config 'localdisks')
echo "Local Disks mounting..."
# Separate comma separated values
# shellcheck disable=SC2086
for disk in ${MOREDISKS//,/ }; do
# Remove text until last slash
disk="${disk##*/}"
# Function to check what is the type of device
if [ -e /dev/"$disk" ]; then
echo "... $disk is a physical device"
devpath=/dev
elif [ -e /dev/disk/by-uuid/"$disk" ] || lsblk -o UUID | grep -q "$disk"; then
echo "... $disk is a device by UUID"
devpath=/dev/disk/by-uuid
elif [ -e /dev/disk/by-label/"$disk" ] || lsblk -o LABEL | grep -q "$disk"; then
echo "... $disk is a device by label"
devpath=/dev/disk/by-label
else
bashio::log.fatal "$disk does not match any known physical device, UUID, or label. "
continue
fi
# Creates dir
mkdir -p /mnt/"$disk"
if bashio::config.has_value 'PUID' && bashio::config.has_value 'PGID'; then
PUID="$(bashio::config 'PUID')"
PGID="$(bashio::config 'PGID')"
chown "$PUID:$PGID" /mnt/"$disk"
fi
# Check FS type and set relative options (thanks @https://github.com/dianlight/hassio-addons)
fstype=$(lsblk "$devpath"/"$disk" -no fstype)
options="nosuid,relatime,noexec"
type="auto"
# Check if supported
if [[ "${fstypessupport}" != *"${fstype}"* ]]; then
bashio::log.fatal : "${fstype} type for ${disk} is not supported"
break
fi
# Mount drive
bashio::log.info "Mounting ${disk} of type ${fstype}"
case "$fstype" in
exfat | vfat | msdos)
bashio::log.warning "${fstype} permissions and ACL don't works and this is an EXPERIMENTAL support"
options="${options},umask=000"
;;
ntfs)
bashio::log.warning "${fstype} is an EXPERIMENTAL support"
options="${options},umask=000"
type="ntfs"
;;
squashfs)
bashio::log.warning "${fstype} is an EXPERIMENTAL support"
options="loop"
type="squashfs"
;;
esac
# Legacy mounting : mount to share if still exists (avoid breaking changes)
dirpath="/mnt"
if [ -d /share/"$disk" ]; then dirpath="/share"; fi
# shellcheck disable=SC2015
mount -t $type "$devpath"/"$disk" "$dirpath"/"$disk" -o $options && bashio::log.info "Success! $disk mounted to /mnt/$disk" \
|| (
bashio::log.fatal "Unable to mount local drives! Please check the name."
rmdir /mnt/"$disk"
bashio::addon.stop
)
done
fi
declare password
declare username
declare -a interfaces=()
export HOSTNAME
# Check Login data
if ! bashio::config.has_value 'username' || ! bashio::config.has_value 'password'; then
bashio::exit.nok "Setting a username and password is required!"
fi
bashio::config.require "enabled_shares" "Samba is a tool for sharing folders. Starting it without sharing any folders defeats the purpose."
# Read hostname from API or setting default "hassio"
HOSTNAME=$(bashio::info.hostname)
if bashio::var.is_empty "${HOSTNAME}"; then
bashio::log.warning "Can't read hostname, using default."
HOSTNAME="hassio"
fi
bashio::log.info "Hostname: ${HOSTNAME}"
# Get supported interfaces
for interface in $(bashio::network.interfaces); do
interface_enabled=$(bashio::network.enabled "${interface}")
if bashio::var.true "${interface_enabled}"; then
interfaces+=("${interface}")
fi
done
if [ ${#interfaces[@]} -eq 0 ]; then
bashio::exit.nok 'No supported interfaces found to bind on.'
fi
bashio::log.info "Interfaces: $(printf '%s ' "${interfaces[@]}")"
# Generate Samba configuration.
jq ".interfaces = $(jq -c -n '$ARGS.positional' --args -- "${interfaces[@]}") |
.enabled_shares.[] |= ascii_downcase" /data/options.json \
| tempio \
-template /usr/share/tempio/smb.gtpl \
-out /etc/samba/smb.conf
# Init user
username=$(bashio::config 'username')
password=$(bashio::config 'password')
addgroup "${username}"
adduser -D -H -G "${username}" -s /bin/false "${username}"
(echo "$password"; echo "$password") \
| smbpasswd -a -s -c "/etc/samba/smb.conf" "${username}"

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-smbd/run

View File

@@ -0,0 +1,24 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Take down the S6 supervision tree based on service exit code
# ==============================================================================
declare topic
if bashio::config.false "mqtt_nexgen_entities"; then
if ! bashio::config.true "autodiscovery.disable_autoremove"; then
bashio::log.info "MQTT disk cleanup."
topic=$(bashio::config 'mqtt_topic')
if [ "$topic" = "null" ]; then topic="sambanas"; fi
mosquitto_sub -t "homeassistant/+/${topic}/+/config" --remove-retained -W 3 >/dev/null || true
mosquitto_sub -t "${topic}/state" --remove-retained -W 3 >/dev/null || true
bashio::log.info "MQTT disk cleanup Done."
fi
fi
if [[ "${1}" -ne 0 ]] && [[ "${1}" -ne 256 ]]; then
bashio::log.warning "mqtt-handler crashed, halting add-on"
exec /run/s6/basedir/bin/halt
fi
bashio::log.info "mqtt-handler stopped"

View File

@@ -0,0 +1,171 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Start mqtt service for disk only
# ==============================================================================
readonly MAX_TIMEFRAME=60
readonly MIN_TIMEFRAME=10
readonly AVG_TIMEFRAME=$(((MAX_TIMEFRAME + MIN_TIMEFRAME) / 2))
if bashio::config.true "mqtt_nexgen_entities"; then
exec sleep infinity
fi
if [ -f /root/.config/mosquitto_pub ]; then
bashio::log.info "Starting the MQTT daemon for disks info..."
# Send autodiscovery entities
topic=$(bashio::config 'mqtt_topic')
if [ "$topic" = "null" ]; then topic="sambanas"; fi
# Send discovery messages.
if ! bashio::config.true "autodiscovery.disable_persistent"; then prs="-r"; fi
jdevice=$(jq -r -c -n --arg topic "$topic" --arg smbv "$(smbd -V | sed s/Version\ //)" --arg addon "$(bashio::addon.version)" '
{device:{
identifiers:[],
name: "SambaNas Physical Disk ",
hw_version: $addon,
sw_version: $smbv,
model: "SambaNas",
manufacturer: "@Dianlight",
via_device: $topic
}}')
device_scan=$(smartctl --scan-open)
if [ -z "$device_scan" ]; then
bashio::log.warning "No disk with S.M.A.R.T support found. Disable sensor report"
exec sleep infinity
fi
while read -r -a device; do
row=$(smartctl -A -l error -l selftest "${device[0]}" -j | jq '. * (.ata_smart_attributes.table // [] | INDEX(.name)) | del(.ata_smart_attributes.table)')
#bashio::log.info "2.2 ${row}"
for entity in device.name device.type device.protocol power_on_time.hours power_cycle_count temperature.current \
Raw_Read_Error_Rate.raw.value Reallocated_Sector_Ct.raw.value Wear_Leveling_Count.raw.value UDMA_CRC_Error_Count.raw.value \
ata_smart_error_log.summary.count ata_smart_self_test_log.standard.count; do
if [[ -z $(jq ".$entity // empty" <<<"$row") ]]; then
# bashio::log.info "2.2 Missing ${entity} in ${row}"
continue
fi
exmsg={}
etype=""
base=$(jq --arg topic "$topic" --arg entity "$entity" '
{
name:($topic+" "+$entity +" "+.device.info_name),
unique_id:((.device.name | explode | join("")) + "-" + ($entity|explode|join(""))),
value_template:("{{ value_json." +$entity+ "}}"),
state_topic:($topic + "/" + (.device.name | gsub("[^A-z]";"")) + "/state" ),
oth:{
uuid:.device.name | explode | join(""),
label:.device.name
}
}' <<<"$row")
case "$entity" in
device.name | device.type | device.protocol) #TEXT
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" '
{
mode: "text",
icon:"mdi:harddisk"
}' <<<"$row")
;;
power_on_time.hours) #Time
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" '
{
unit_of_measurement: "h",
device_class: "duration"
}' <<<"$row")
;;
power_cycle_count) #number
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" '
{
_unit_of_measurement: "",
icon:"mdi:power-cycle"
}' <<<"$row")
;;
Raw_Read_Error_Rate.raw.value | Reallocated_Sector_Ct.raw.value | Wear_Leveling_Count.raw.value | UDMA_CRC_Error_Count.raw.value | ata_smart_error_log.summary.count | ata_smart_self_test_log.standard.count) #number
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" '
{
_unit_of_measurement: "",
icon:"mdi:chart-box-outline"
}' <<<"$row")
;;
temperature.current) #temperature
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" '
{
unit_of_measurement: "°C",
device_class: "temperature",
_icon:"mdi:thermometer"
}' <<<"$row")
;;
*)
bashio::log.warning "Autodiscovery for $entity missing!"
;;
esac
#bashio::log.info "2.4 $base $exmsg"
msg=$(echo "$base" "$jdevice" "$exmsg" | jq -s 'add|.device.identifiers[.device.identifiers|length]=.oth.uuid|.device.name=(.device.name + .oth.label)|del(.oth)')
#bashio::log.debug "$msg"
mosquitto_pub "${prs}" -t "homeassistant/${etype}/${topic}/$(jq -r '.device.name | explode | join("")' <<<$row)-${entity//[\.\/]/-}/config" -m "$msg"
done
# bashio::log.info "2.3"
done <<<"${device_scan}"
while read -r -a device; do
mkfifo /tmp/mqtt-hanlder-${device[0]//\//}
# Send status message process
tail -F /tmp/mqtt-hanlder-${device[0]//\//} | mosquitto_pub -l -t "${topic}/${device[0]//\//}/state" &
done <<<"${device_scan}"
while read -r -a device; do
cdevice=${device[0]//\//}
shaOldMessage="-"
sleepTime=$AVG_TIMEFRAME
while true; do
#bashio::log.info "[$cdevice] ${shaOldMessage} $sleepTime"
status=$(smartctl -A -l error -l selftest "${device[0]}" -j | jq -c '. * (.ata_smart_attributes.table // [] | INDEX(.name)) | del(.ata_smart_attributes.table) | del(.local_time) | del(.smartctl) | del (.json_format_version)')
# Debug
shaMessage=$(sha1sum <<<"$status")
# bashio::log.green "[$cdevice] SleepTimes: $sleepTime $shaOldMessage] ${status}"
if [ "${shaOldMessage}" = "$shaMessage" ]; then
sleepTime=$((sleepTime * 2))
[ ${sleepTime} -gt $MAX_TIMEFRAME ] && sleepTime=$MAX_TIMEFRAME
else
# Send status message
if [ $sleepTime -gt $AVG_TIMEFRAME ]; then
sleepTime=$((sleepTime / 2))
else
sleepTime=$((sleepTime - MIN_TIMEFRAME))
fi
[ $sleepTime -le $MIN_TIMEFRAME ] && sleepTime=$MIN_TIMEFRAME
fi
jq -c --arg st "$sleepTime" --arg sh "$shaMessage" '. +
{
ref: {
mws: $st,
sha: $sh
}
}' <<<"${status}" >/tmp/mqtt-hanlder-$cdevice
shaOldMessage=$shaMessage
# Sleep
sleep ${sleepTime}
done &
sleep $((10 / $(wc -l <<<"$device_scan")))
done <<<"${device_scan}"
wait
else
exec sleep infinity
fi

View File

@@ -0,0 +1 @@
longrun

View File

@@ -0,0 +1,24 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Take down the S6 supervision tree based on service exit code
# ==============================================================================
declare topic
if bashio::config.false "mqtt_nexgen_entities"; then
if ! bashio::config.true "autodiscovery.disable_autoremove"; then
bashio::log.info "MQTT cleanup."
topic=$(bashio::config 'mqtt_topic')
if [ "$topic" = "null" ]; then topic="sambanas"; fi
mosquitto_sub -t "homeassistant/+/${topic}/+/config" --remove-retained -W 3 >/dev/null || true
mosquitto_sub -t "${topic}/state" --remove-retained -W 3 >/dev/null || true
bashio::log.info "MQTT cleanup Done."
fi
fi
if [[ "${1}" -ne 0 ]] && [[ "${1}" -ne 256 ]]; then
bashio::log.warning "mqtt-handler crashed, halting add-on"
exec /run/s6/basedir/bin/halt
fi
bashio::log.info "mqtt-handler stopped"

View File

@@ -0,0 +1,177 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Start mqtt service
# ==============================================================================
readonly MAX_TIMEFRAME=60
readonly MIN_TIMEFRAME=5
readonly AVG_TIMEFRAME=$(((MAX_TIMEFRAME + MIN_TIMEFRAME) / 2))
if [ -f /root/.config/mosquitto_pub ]; then
bashio::log.info "Starting the MQTT daemon for partitions info..."
topic=$(bashio::config 'mqtt_topic' "sambanas")
# Send autodiscovery entities
if bashio::config.true "mqtt_nexgen_entities"; then
bashio::log.info "New MQTT integration"
host=$(bashio::config 'mqtt_host' "$(bashio::services 'mqtt' 'host')")
username=$(bashio::config 'mqtt_username' "$(bashio::services 'mqtt' 'username')")
password=$(bashio::config 'mqtt_password' "$(bashio::services 'mqtt' 'password')")
port=$(bashio::config 'mqtt_port' "$(bashio::services 'mqtt' 'port')")
log_level=$(bashio::string.lower "$(bashio::config log_level info)")
#bashio::log.info "New MQTT config ${host}:${port:-1883} ${username}:${password} ${topic}"
idleparam=""
if [ -p /tmp/hdidle.events ]; then
idleparam="-i /tmp/hdidle.events"
fi
exec /usr/bin/poetry -C /usr/local/bin/ run python /usr/local/bin/mqtt_daemon.py -b "${host}" -p "${port:-1883}" -u "${username}" -P "${password}" -t "${topic}" -v $(bashio::addon.version) ${idleparam} -l ${log_level^^}
else
bashio::log.info "MQTT integration"
# disks=$(awk 'BEGIN { ORS=""; print "["} /^ path = .*/g { printf "%s\"%s\"",separator,$3 ; separator="," } END { print "]" } ' /etc/samba/smb.conf)
disks=$(grep path /etc/samba/smb.conf | sed 's/.*path\ =\ //' | jq --raw-input --slurp 'split("\n") | map(select(. != ""))')
blk=$(lsblk -b -no PARTUUID,NAME,LABEL,FSTYPE,MOUNTPOINTS -J -y)
jdisks=$(jq --argjson disks "$disks" 'reduce (.blockdevices[].children[]? |select(.mountpoints? - $disks != .mountpoints) ) as $i ({};.[$i.name] = $i)' <<<${blk})
# Send discovery messages.
if ! bashio::config.true "autodiscovery.disable_persistent"; then prs="-r"; fi
#bashio::log.info $device
device=$(jq -r -c -n --arg topic "$topic" --arg smbv "$(smbd -V | sed s/Version\ //)" --arg addon "$(bashio::addon.version)" '
{device:{
identifiers:[],
name: "SambaNas Disk ",
hw_version: $addon,
sw_version: $smbv,
model: "SambaNas",
manufacturer: "@Dianlight",
via_device: $topic
}}')
for row in $(jq -r '.|map(.|@base64)|.[]' <<<"$jdisks"); do
for entity in name label mountpoints fssize fsused fsuse_pct fsavail fstype iostat.tps iostat.kB_read/s iostat.kB_wrtn/s iostat.kB_dscd/s iostat.kB_read iostat.kB_wrtn iostat.kB_dscd; do
exmsg={}
etype=""
base=$(jq --arg topic "$topic" --arg entity "$entity" -R '@base64d|fromjson|
{
name:($topic+" "+$entity +" "+ .label),
unique_id:(.partuuid +"-"+ ($entity|explode|join(""))),
value_template:("{{ value_json." + .name + "." +$entity+ "}}"),
state_topic:($topic + "/state"),
oth:{
partuuid:.partuuid,
name:.name,
label:.label,
root:("/dev/" + .name[:-1])
}
}' <<<"$row")
case "$entity" in
name | label | fstype | mountpoints) #TEXT
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" -R '@base64d|fromjson|
{
mode: "text",
icon:"mdi:harddisk"
}' <<<"$row")
;;
fssize | fsused | fsavail) #DATA_SIZE (Byte)
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" -R '@base64d|fromjson|
{
unit_of_measurement: "B",
device_class: "data_size",
}' <<<"$row")
;;
iostat.kB_read | iostat.kB_wrtn | iostat.kB_dscd) #DATA_SIZE (KB)
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" -R '@base64d|fromjson|
{
unit_of_measurement: "kB",
device_class: "data_size",
}' <<<"$row")
;;
iostat.kB_read/s | iostat.kB_wrtn/s | iostat.kB_dscd/s) #data_rate
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entityb "${entity%.*}" --arg entityd "${entity##*.}" -R '@base64d|fromjson|
{
unit_of_measurement: "kB/s",
device_class: "data_rate",
value_template:("{{ value_json." + .name + "." +$entityb+ "['"'"'" + $entityd + "'"'"']}}"),
icon:"mdi:database-refresh"
}' <<<"$row")
;;
iostat.tps) #TPS
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" -R '@base64d|fromjson|
{
unit_of_measurement: "tps",
icon:"mdi:database-search"
}' <<<"$row")
;;
fsuse_pct) # PERCENT
etype="sensor"
exmsg=$(jq --arg topic "$topic" --arg entity "$entity" -R '@base64d|fromjson|
{
unit_of_measurement: "%",
icon:"mdi:database-eye"
}' <<<"$row")
;;
*)
bashio::log.warning "Autodiscovery for $entity missing!"
;;
esac
msg=$(echo "$base" "$device" "$exmsg" | jq -s 'add|.device.identifiers[.device.identifiers|length]=.oth.partuuid|.device.name=(.device.name + .oth.label)|.device.via_device=(.oth.root | explode | join(""))|del(.oth)')
#bashio::log.debug "$msg"
mosquitto_pub "${prs}" -t "homeassistant/${etype}/${topic}/$(jq -R -r '@base64d|fromjson|.partuuid' <<<"$row")-${entity//[\.\/]/-}/config" -m "$msg"
done
done
mkfifo /tmp/mqtt-hanlder
# Send status message process
tail -F /tmp/mqtt-hanlder | mosquitto_pub -l -t "${topic}/state" &
sleepTime=$AVG_TIMEFRAME
shaOldMessage="-"
while true; do
blk=$(lsblk -b -no NAME,LABEL,FSSIZE,FSUSED,FSUSE%,FSAVAIL,FSTYPE,MOUNTPOINTS -J -y | jq 'walk(if type == "object" and .fsuse_pct != null then .fsuse_pct|=(rtrimstr("%")|tonumber) else . end)')
liostat=$(/usr/bin/iostat "$(jq -r '.|map(.name)|.[]' <<<"""$jdisks""")" -k -d -p -o JSON)
status=$(jq -c --argjson disks "$disks" --argjson iostat "$liostat" 'reduce (.blockdevices[].children[]? |select(.mountpoints? - $disks != .mountpoints) ) as $i ({};.[$i.name] = $i+{iostat:($iostat.sysstat.hosts[0].statistics[0].disk[] | select(.disk_device==$i.name))})' <<<"${blk}")
# Send status message
shaMessage=$(sha1sum <<<"$status")
if [ "$shaOldMessage" = "$shaMessage" ]; then
sleepTime=$((sleepTime * 2))
[ $sleepTime -gt $MAX_TIMEFRAME ] && sleepTime=$MAX_TIMEFRAME
else
# Send status message
if [ $sleepTime -gt $AVG_TIMEFRAME ]; then
sleepTime=$((sleepTime / 2))
else
sleepTime=$((sleepTime - MIN_TIMEFRAME))
fi
[ $sleepTime -le $MIN_TIMEFRAME ] && sleepTime=$MIN_TIMEFRAME
fi
jq -c --arg st "$sleepTime" --arg sh "$shaMessage" '. +
{
ref: {
mws: $st,
sha: $sh
}
}' <<<"${status}" >/tmp/mqtt-hanlder
shaOldMessage=$shaMessage
# Sleep
sleep $sleepTime
done
fi
else
exec sleep infinity
fi

View File

@@ -0,0 +1 @@
longrun

View File

@@ -1,27 +1,10 @@
#!/command/with-contenv bashio
# vim: ft=bash
# shellcheck shell=bash
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Take down the S6 supervision tree when nmbd fails
# Take down the S6 supervision tree based on service exit code
# ==============================================================================
# shellcheck disable=SC2155
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
readonly exit_code_service="${1}"
readonly exit_code_signal="${2}"
readonly service="nmbd"
bashio::log.info \
"Service ${service} exited with code ${exit_code_service}" \
"(by signal ${exit_code_signal})"
if [[ "${exit_code_service}" -eq 256 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo $((128 + exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
fi
[[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
elif [[ "${exit_code_service}" -ne 0 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
fi
if [[ "${1}" -ne 0 ]] && [[ "${1}" -ne 256 ]]; then
bashio::log.warning "nmbd crashed, halting add-on"
exec /run/s6/basedir/bin/halt
fi
bashio::log.info "nmbd stopped"

View File

@@ -1,10 +1,8 @@
#!/command/with-contenv bashio
# vim: ft=bash
# shellcheck shell=bash
#!/usr/bin/env bash
# ==============================================================================
# Start nmbd service
# ==============================================================================
exec nmbd \
--foreground \
--debug-stdout \
--no-process-group
--no-process-group

View File

@@ -1 +1 @@
longrun
longrun

View File

@@ -1,27 +1,12 @@
#!/command/with-contenv bashio
# vim: ft=bash
# shellcheck shell=bash
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Take down the S6 supervision tree when smbd fails
# Take down the S6 supervision tree based on service exit code
# ==============================================================================
# shellcheck disable=SC2155
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
readonly exit_code_service="${1}"
readonly exit_code_signal="${2}"
readonly service="smbd"
bashio::log.info \
"Service ${service} exited with code ${exit_code_service}" \
"(by signal ${exit_code_signal})"
if [[ "${exit_code_service}" -eq 256 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo $((128 + exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
fi
[[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
elif [[ "${exit_code_service}" -ne 0 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
fi
if [[ "${1}" -ne 0 ]] && [[ "${1}" -ne 256 ]]; then
bashio::log.warning "smbd crashed, halting add-on"
bashio::log.info "Dump SMB configuration:"
cat /etc/samba/smb.conf >&2
exec /run/s6/basedir/bin/halt
fi
bashio::log.info "smbd stopped"

View File

@@ -1,10 +1,8 @@
#!/command/with-contenv bashio
# vim: ft=bash
# shellcheck shell=bash
#!/usr/bin/env bash
# ==============================================================================
# Start smbd service
# ==============================================================================
exec smbd \
--foreground \
--debug-stdout \
--no-process-group
--no-process-group

View File

@@ -1 +1 @@
longrun
longrun

View File

@@ -0,0 +1,10 @@
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Take down the S6 supervision tree based on service exit code
# ==============================================================================
if [[ "${1}" -ne 0 ]] && [[ "${1}" -ne 256 ]]; then
bashio::log.warning "wsdd crashed, halting add-on"
exec /run/s6/basedir/bin/halt
fi
bashio::log.info "wsdd stopped"

View File

@@ -0,0 +1,46 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Start wsdd service
# ==============================================================================
declare SMB_GROUP
declare SMB_HOST
SMB_GROUP=$(grep -i '^\s*workgroup\s*=' /etc/samba/smb.conf | cut -f2 -d= | tr -d '[:blank:]')
SMB_HOST=$(grep -i '^\s*netbios name\s*=' /etc/samba/smb.conf | cut -f2 -d= | tr -d '[:blank:]')
if bashio::config.true 'bind_all_interfaces'; then
interfaces+=" "
elif bashio::config.has_value 'interfaces'; then
bashio::log.info "Interfaces from config: $(bashio::config 'interfaces')"
for interface in $(bashio::config 'interfaces'); do
if [ -d "/sys/class/net/${interface}" ]; then
interfaces+=("-i ${interface}")
else
bashio::log.warning "Interface ${interface} not found, skipping."
fi
done
else
# Get supported interfaces
for interface in $(bashio::network.interfaces); do
interfaces+=("-i ${interface}")
done
fi
#if [ ${#interfaces[@]} -eq 0 ]; then
# bashio::exit.nok 'No supported interfaces found to bind on.'
#fi
#bashio::log.info "Interfaces: $(printf '%s ' "${interfaces[@]}")"
if bashio::config.true 'wsdd2'; then
bashio::log.info "Starting the WSDD2 daemon $(printf '%s ' "${interfaces[@]}") for ${SMB_GROUP}/${SMB_HOST}..."
setcap CAP_NET_RAW+ep /usr/sbin/wsdd2
# shellcheck disable=SC2046
exec /usr/sbin/wsdd2 -t -u -w $(printf '%s ' "${interfaces[@]}") -H "${SMB_HOST}" -b vendor:homeassistant,model:sambanas
elif bashio::config.true 'wsdd'; then
bashio::log.info "Starting the WSDD daemon $(printf '%s ' "${interfaces[@]}") for ${SMB_GROUP}/${SMB_HOST}..."
# shellcheck disable=SC2046
exec wsdd -v $(printf '%s ' "${interfaces[@]}") -n "${SMB_HOST}" -w "${SMB_GROUP}"
else
exec sleep infinity
fi

View File

@@ -0,0 +1 @@
longrun

View File

View File

@@ -0,0 +1,23 @@
# python 3.11
# Return the list of mountable external Devices
from diskinfo import DiskInfo
di = DiskInfo()
disks = di.get_disk_list(sorting=True)
regex = r"Name:\s+(\w+)\s.*\n"
for d in disks:
if d.get_partition_table_type() == "":
continue
plist = d.get_partition_list()
for item in plist:
label = item.get_fs_label()
if label.startswith("hassos"):
continue
elif label != "":
print(item.get_fs_label())
elif item.get_fs_type() == "apfs":
print("id:{uuid}".format(uuid=item.get_fs_uuid()))
# print(item.get_fs_label()," ",item.get_fs_type()," ",item.get_part_uuid()," ",item.get_name())

View File

@@ -0,0 +1,650 @@
# python 3.11
import logging
import subprocess
import time
import argparse
import collections
import asyncio
import json
import os
import typing
import hashlib
from pySMART import DeviceList, Device
from pySMART.interface import NvmeAttributes, AtaAttributes
import psutil
from psutil._common import sdiskio, sdiskusage
from ha_mqtt_discoverable import Settings, DeviceInfo
from ha_mqtt_discoverable.sensors import Sensor, SensorInfo, BinarySensor
from ha_mqtt_discoverable.sensors import BinarySensorInfo
import signal
from typing import Any, Callable, List, Literal, NoReturn, Self, Tuple, Union
import re
import humanize
from abc import ABC, abstractmethod
from diskinfo import Disk
# config = configparser.ConfigParser( strict=False )
# config.read("/etc/samba/smb.conf")
# Get the arguments from the command-line except the filename
ap = argparse.ArgumentParser()
# Add the arguments to the parser
ap.add_argument("-b", "--broker", required=True, help="Broker")
ap.add_argument("-p", "--port", required=True, help="Broker Port")
ap.add_argument("-u", "--user", required=True, help="Broker User")
ap.add_argument("-P", "--password", required=True, help="Broker Password")
ap.add_argument("-t", "--topic", required=False, help="Topic", default="sambanas")
ap.add_argument(
"-i",
"--hdidle_log",
required=False,
help="HD_IDLE log to listen for sleeping drivers",
)
ap.add_argument(
"-T", "--discovery_topic", required=False, help="Topic", default="homeassistant"
)
ap.add_argument(
"-d", "--persist_discovery", required=False, help="Topic", default=True, type=bool
)
ap.add_argument(
"-v", "--addon_version", required=False, help="Addon Version", default="latest"
)
ap.add_argument(
"-l",
"--logLevel",
required=False,
default="WARNING",
choices=[
"ALL",
"TRACE",
"DEBUG",
"INFO",
"NOTICE",
"WARNING",
"ERROR",
"CRITICAL",
"FATAL",
"OFF",
],
)
args: dict[str, Any] = vars(ap.parse_args())
match str(args["logLevel"]).upper():
case "DEBUG" | "ALL" | "TRACE":
logging.basicConfig(level=logging.DEBUG)
case "NOTICE":
logging.basicConfig(level=logging.INFO)
case "WARNING" | "INFO":
logging.basicConfig(level=logging.WARNING)
case "ERROR":
logging.basicConfig(level=logging.ERROR)
case "CRITICAL" | "FATAL":
logging.basicConfig(level=logging.CRITICAL)
case "OFF":
logging.basicConfig(level=logging.NOTSET)
case "_":
logging.basicConfig(level=logging.WARNING)
mqtt_settings = Settings.MQTT(
host=args["broker"],
port=int(args["port"]),
username=args["user"],
password=args["password"],
discovery_prefix=args["discovery_topic"],
state_prefix=args["topic"],
)
# Global Sensors regitry
class ConfigEntity(ABC):
def __init__(
self,
sensorInfo: Union[SensorInfo, BinarySensorInfo],
state_function: Callable[[Self], Any],
attributes_function: Callable[[Self], dict[str, Any]] | None = None,
) -> None:
self.sensorInfo: SensorInfo | BinarySensorInfo = sensorInfo
self.state_function = state_function
self.attributes_function = attributes_function
self.sensor: Union[Sensor, BinarySensor] = None # type: ignore
def createSensor(self) -> Self:
if self.sensor is not None:
return self.sensor # type: ignore
settings = Settings(mqtt=mqtt_settings, entity=self.sensorInfo)
if isinstance(self.sensorInfo, BinarySensorInfo):
self.sensor = BinarySensor(settings) # type: ignore
logging.debug(
"BinarySensor '%s' created", self.sensor.generate_config()["name"]
)
elif isinstance(self.sensorInfo, SensorInfo):
self.sensor = Sensor(settings) # type: ignore
logging.debug("Sensor '%s' created", self.sensor.generate_config()["name"])
return self
@abstractmethod
def detstroy(self) -> None:
pass
class ConfigEntityAutonomous(ConfigEntity):
def detstroy(self) -> None:
self.sensor.delete()
class ConfigEntityFromDevice(ConfigEntity):
def __init__(
self,
sensorInfo: Union[SensorInfo, BinarySensorInfo],
device: Device,
state_function: Callable[[Self], Any],
attributes_function: Callable[[Self], dict[str, Any]] = None, # type: ignore
):
super().__init__(sensorInfo, state_function, attributes_function)
self.device = device
def detstroy(self) -> None:
self.sensor.delete()
class ConfigEntityFromHDIdle(ConfigEntity):
def __init__(
self,
sensorInfo: Union[SensorInfo, BinarySensorInfo],
device: Device,
state_function: Callable[[Self], Any],
attributes_function: Callable[[Self], dict[str, Any]] = None, # type: ignore
):
super().__init__(sensorInfo, state_function, attributes_function)
self.device = device
def detstroy(self) -> None:
self.sensor.delete()
class ConfigEntityFromIoStat(ConfigEntity):
def __init__(
self,
sensorInfo: Union[SensorInfo, BinarySensorInfo],
iostat_device: str,
state_function: Callable[[Self], Any],
attributes_function: Callable[[Self], dict[str, Any]] | None = None,
) -> None:
super().__init__(sensorInfo, state_function, attributes_function)
self.iostat_device: str = iostat_device
self._iostat: collections.deque[tuple[int, dict[str, sdiskio] | None]] = (
collections.deque([(0, None), (0, None)], maxlen=2)
)
@property
def iostat(self) -> tuple[int, dict[str, sdiskio] | None]:
return self._iostat[1]
@property
def h_iostat(self) -> tuple[int, dict[str, sdiskio] | None]:
return self._iostat[0]
def addIostat(self, iostat: dict) -> None:
self._iostat.append((time.time_ns(), iostat.copy()))
def detstroy(self) -> None:
self.sensor.delete()
class ConfigEntityFromSamba(ConfigEntity):
def __init__(
self,
sensorInfo: Union[SensorInfo, BinarySensorInfo],
samba: dict,
state_function: Callable[[Self], Any],
attributes_function: Callable[[Self], dict[str, Any]] | None = None,
) -> None:
super().__init__(sensorInfo, state_function, attributes_function)
self.samba = samba
def detstroy(self):
self.sensor.delete()
def sambaMetricCollector() -> dict[str, Any]:
# smbstatus gets report of current samba server connections
try:
p = subprocess.Popen(["smbstatus", "-jf"], stdout=subprocess.PIPE)
output, err = p.communicate()
jsondata = json.loads(output.decode())
logging.debug("SmbClient: %s", jsondata)
except Exception:
logging.warning("Exception on smbstat comunication!")
jsondata = {}
data = {"samba_version": jsondata.get("version", "Unknown")}
if "sessions" in jsondata:
data["users"] = len(jsondata["sessions"])
data["users_json"] = jsondata["sessions"]
else:
data["users"] = 0
data["users_json"] = {}
data["connections"] = len(jsondata["tcons"]) if "tcons" in jsondata else 0
if "open_files" in jsondata:
data["open_files"] = len(jsondata["open_files"])
else:
data["open_files"] = 0
logging.debug(data)
return data
sensorList: List[Tuple[str, ConfigEntity]] = []
devicePowerStatus: dict[str, str] = {}
# Main device SambaNas
samba: dict[str, Any] = sambaMetricCollector()
sambanas_device_info = DeviceInfo(
name="SambaNas",
model="Addon",
manufacturer="@Dianlight",
sw_version=samba["samba_version"],
hw_version=args["addon_version"],
identifiers=[os.getenv("HOSTNAME", default="local_test")],
)
devlist = DeviceList()
psdata: dict[str, sdiskio] = psutil.disk_io_counters(perdisk=True, nowrap=True)
for dev_name in psdata.keys():
if re.search(r"\d+$", dev_name):
continue
dev = Device(dev_name)
disk_device_info = DeviceInfo(
name=f"SambaNas Disk {dev_name}",
model=dev.model,
sw_version=dev.firmware,
# connections=[[dev_name,sambanas_device_info.identifiers[0]]],
identifiers=[dev.serial or "Unknown(%s)" % dev_name],
via_device=sambanas_device_info.identifiers[0], # type: ignore
)
# sambanas_device_info.connections.append([dev_name,disk_device_info.identifiers[0]])
def smartAssesmentAttribute(ce: ConfigEntityFromDevice) -> dict[str, Any]:
attributes: dict[str, Any] = {
"smart_capable": ce.device.smart_capable,
"smart_enabled": ce.device.smart_enabled,
"assessment": ce.device.assessment,
"messages": ce.device.messages,
"rotation_rate": ce.device.rotation_rate,
"_test_running": ce.device._test_running,
"_test_progress": ce.device._test_progress,
}
if ce.device.if_attributes is None:
return attributes
if isinstance(ce.device.if_attributes, AtaAttributes):
atattrs: AtaAttributes = ce.device.if_attributes
for atattr in atattrs.legacyAttributes:
if atattr is None:
continue
attributes[atattr.name] = atattr.raw
health_value: Literal["OK", "FAIL"] = (
"OK"
if (atattr.thresh is not None and atattr.worst > atattr.thresh)
else "FAIL"
)
attributes[f"{atattr.name}_Health"] = health_value
elif isinstance(ce.device.if_attributes, NvmeAttributes):
nwmattrs: NvmeAttributes = ce.device.if_attributes
for nwmattr in nwmattrs.__dict__.keys():
if nwmattrs.__dict__[nwmattr] is None:
continue
attributes[nwmattr] = nwmattrs.__dict__[nwmattr]
return attributes
if dev.smart_capable:
smartAssessment = ConfigEntityFromDevice(
sensorInfo=BinarySensorInfo(
name=f"S.M.A.R.T {dev.name}",
unique_id=hashlib.md5(
f"S.M.A.R.T {dev.name}".encode("utf-8")
).hexdigest(),
device=disk_device_info,
# enabled_by_default= dev.smart_capable
device_class="problem",
),
state_function=lambda ce: ce.device.assessment != "PASS",
attributes_function=smartAssesmentAttribute,
device=dev,
)
sensorList.append((f"smart_{dev.name}", smartAssessment.createSensor()))
if args["hdidle_log"] is not None:
hdIdleAssessment = ConfigEntityFromHDIdle(
sensorInfo=BinarySensorInfo(
name=f"POWER {dev.name}",
unique_id=hashlib.md5(f"POWER {dev.name}".encode("utf-8")).hexdigest(),
device=disk_device_info,
device_class="power",
),
state_function=lambda ce: devicePowerStatus.get(ce.device.name) != "spindown",
attributes_function=lambda ce: {"pipe_file": args["hdidle_log"] + "s"},
device=dev,
)
sensorList.append((f"power_{dev.name}", hdIdleAssessment.createSensor()))
def totalDiskRate(ce: ConfigEntityFromIoStat) -> float | Literal[0]:
if (
ce.h_iostat[0] in [0, ce.iostat[0]] or ce.iostat[1] is None or ce.h_iostat[1] is None
):
return 0
t_read: int = int(ce.iostat[1][ce.iostat_device].read_bytes) - int(
ce.h_iostat[1][ce.iostat_device].read_bytes
)
t_write: int = int(ce.iostat[1][ce.iostat_device].write_bytes) - int(
ce.h_iostat[1][ce.iostat_device].write_bytes
)
t_ns_time: int = ce.iostat[0] - ce.h_iostat[0]
logging.debug("%s %d %d %d", ce.iostat_device, t_read, t_write, t_ns_time)
return round(((t_read + t_write) * 1000000000 / t_ns_time) / 1024, 2) # kB/s
def iostatAttribute(ce: ConfigEntityFromIoStat) -> dict[str, str]:
if ce.iostat[1] is None:
return {}
attributes: dict[str, str] = {
key: getattr(ce.iostat[1][ce.iostat_device], key)
for key in ce.iostat[1][ce.iostat_device]._fields
}
return attributes
diskInfo = ConfigEntityFromIoStat(
sensorInfo=SensorInfo(
name=f"IOSTAT {dev.name}",
unique_id=hashlib.md5(f"IOSTAT {dev.name}".encode("utf-8")).hexdigest(),
device=disk_device_info,
unit_of_measurement="kB/s",
device_class="data_rate",
),
state_function=totalDiskRate,
attributes_function=iostatAttribute,
iostat_device=dev.name,
)
sensorList.append((f"iostat_{dev.name}", diskInfo.createSensor()))
partitionDevices: typing.Dict[str, DeviceInfo] = {}
for partition in Disk(dev_name).get_partition_list():
if (partition.get_fs_label() or partition.get_fs_uuid()) == "":
continue
if not partition.get_name() in partitionDevices:
partitionDevices[partition.get_name()] = DeviceInfo(
name=f"SambaNas Partition {partition.get_fs_label() or partition.get_fs_uuid()}",
model=partition.get_fs_label() or partition.get_fs_uuid(),
manufacturer=partition.get_fs_type(),
identifiers=[partition.get_fs_label() or partition.get_fs_uuid()],
via_device=disk_device_info.identifiers[0], # type: ignore
)
try:
sdiskparts = list(
filter(
lambda part: part.device.endswith(partition.get_name()),
psutil.disk_partitions(),
)
)
if sdiskparts and sdiskparts[0] and sdiskparts[0].mountpoint:
if partitionDevices[partition.get_name()].identifiers is None:
partitionDevices[partition.get_name()].identifiers = []
if isinstance(partitionDevices[partition.get_name()].identifiers, str):
partitionDevices[partition.get_name()].identifiers = [
str(partitionDevices[partition.get_name()].identifiers)
]
partitionDevices[partition.get_name()].identifiers.append( # type: ignore
sdiskparts[0].mountpoint
)
finally:
pass
logging.debug(
"Generated %d Partitiond Device for %s", len(partitionDevices), dev.name
)
for partition_device, partitionDeviceInfo in partitionDevices.items():
partitionInfo = ConfigEntityFromIoStat(
sensorInfo=SensorInfo(
name=f"IOSTAT {partitionDeviceInfo.model}",
unique_id=hashlib.md5(
f"IOSTAT {partitionDeviceInfo.model}".encode("utf-8")
).hexdigest(),
device=partitionDeviceInfo,
unit_of_measurement="kB/s",
device_class="data_rate",
),
state_function=totalDiskRate,
attributes_function=iostatAttribute,
iostat_device=partition_device,
)
sensorList.append((f"iostat_{partition_device}", partitionInfo.createSensor()))
def usageAttribute(ce: ConfigEntityAutonomous) -> dict[str, str]:
if ce.sensorInfo.device is None or ce.sensorInfo.device.identifiers is None:
return {}
usage: sdiskusage = psutil.disk_usage(ce.sensorInfo.device.identifiers[-1])
attributes: dict[str, str] = {
"used": humanize.naturalsize(usage.used),
"total": humanize.naturalsize(usage.total),
"free": humanize.naturalsize(usage.free),
}
return attributes
def partitionUsage(ce: ConfigEntityAutonomous) -> Any:
if ce.sensorInfo.device is None or ce.sensorInfo.device.identifiers is None:
return
logging.debug(
"Collecting Usage from %s [%s]",
ce.sensorInfo.device.identifiers,
ce.sensorInfo.device.identifiers[-1],
)
return psutil.disk_usage(ce.sensorInfo.device.identifiers[-1]).percent
if (
partitionDeviceInfo.identifiers is not None and len(partitionDeviceInfo.identifiers) > 1
):
partitionInfo = ConfigEntityAutonomous(
sensorInfo=SensorInfo(
name=f"Usage {partitionDeviceInfo.model}",
unique_id=hashlib.md5(
f"Usage {partitionDeviceInfo.model}".encode("utf-8")
).hexdigest(),
device=partitionDeviceInfo,
icon="mdi:harddisk",
unit_of_measurement="%",
device_class="power_factor",
),
state_function=partitionUsage,
attributes_function=usageAttribute,
)
sensorList.append(
(f"usage_{partition_device}", partitionInfo.createSensor())
)
sambaUsers = ConfigEntityFromSamba(
sensorInfo=SensorInfo(
name="Online Users",
device=sambanas_device_info,
unique_id=hashlib.md5("Online Users".encode("utf-8")).hexdigest(),
),
state_function=lambda ce: ce.samba["users"],
samba=samba,
)
sensorList.append(("samba_users", sambaUsers.createSensor()))
sambaConnections = ConfigEntityFromSamba(
sensorInfo=SensorInfo(
name="Active Connections",
device=sambanas_device_info,
unique_id=hashlib.md5("Active Connections".encode("utf-8")).hexdigest(),
),
state_function=lambda ce: ce.samba["connections"],
attributes_function=lambda ce: {"open_files": ce.samba["open_files"]},
samba=samba,
)
sensorList.append(("samba_connections", sambaConnections.createSensor()))
tasks: list[asyncio.Task] = []
async def publish_states() -> NoReturn:
while True:
for sensor in sensorList:
if isinstance(sensor[1], ConfigEntityAutonomous):
logging.info("Updating sensor %s", sensor[0])
sensor[1].sensor.set_state(sensor[1].state_function(sensor[1])) # type: ignore
if sensor[1].attributes_function is not None:
sensor[1].sensor.set_attributes(
sensor[1].attributes_function(sensor[1])
)
await asyncio.sleep(5)
async def publish_idle_states() -> NoReturn:
with open(args["hdidle_log"]) as pipe_hdidle:
while True:
line = pipe_hdidle.readline()
if len(line) == 0:
logging.error(f"Pipe Broken {args["hdidle_log"]}!")
break
# Parse line with ilde status
# Aug 8 00:14:55 enterprise hd-idle[9958]: sda spindown
# Aug 8 00:14:55 enterprise hd-idle[9958]: sdb spindown
# Aug 8 00:14:56 enterprise hd-idle[9958]: sdc spindown
# Aug 8 00:17:55 enterprise hd-idle[9958]: sdb spinup
# Aug 8 00:28:55 enterprise hd-idle[9958]: sdb spindown
disk, status = line.split(' ', maxsplit=1)
logging.info("Disk %s change to status %s", disk, status.strip())
devicePowerStatus[disk] = status.strip()
logging.debug(devicePowerStatus)
for sensor in sensorList:
if isinstance(sensor[1], ConfigEntityFromHDIdle) and sensor[1].device.name == disk:
logging.info("Updating sensor %s", sensor[0])
if isinstance(sensor[1].sensor, BinarySensor):
sensor[1].sensor.update_state(sensor[1].state_function(sensor[1]))
elif isinstance(sensor[1].sensor, Sensor):
sensor[1].sensor.set_state(sensor[1].state_function(sensor[1])) # type: ignore
if sensor[1].attributes_function is not None:
sensor[1].sensor.set_attributes(
sensor[1].attributes_function(sensor[1])
)
async def publish_device_states() -> NoReturn:
while True:
for sensor in sensorList:
if isinstance(sensor[1], ConfigEntityFromDevice):
logging.info(
"Updating Device sensor %s (pw:%s)",
sensor[0],
devicePowerStatus.get(sensor[1].device.name),
)
if devicePowerStatus.get(sensor[1].device.name) == "spindown":
continue
sensor[1].device.update()
if isinstance(sensor[1].sensor, BinarySensor):
sensor[1].sensor.update_state(sensor[1].state_function(sensor[1]))
elif isinstance(sensor[1].sensor, Sensor):
sensor[1].sensor.set_state(sensor[1].state_function(sensor[1]))
if sensor[1].attributes_function is not None:
sensor[1].sensor.set_attributes(
sensor[1].attributes_function(sensor[1])
)
await asyncio.sleep(5)
async def publish_iostate_states() -> NoReturn:
while True:
iostate: dict[str, sdiskio] = psutil.disk_io_counters(perdisk=True, nowrap=True)
for sensor in sensorList:
if isinstance(sensor[1], ConfigEntityFromIoStat):
logging.info("Updating Iostat sensor %s", sensor[0])
sensor[1].addIostat(iostate)
sensor[1].sensor.set_state(sensor[1].state_function(sensor[1])) # type: ignore
if sensor[1].attributes_function is not None:
sensor[1].sensor.set_attributes(
sensor[1].attributes_function(sensor[1])
)
await asyncio.sleep(1)
async def publish_samba_states() -> NoReturn:
while True:
samba: dict[str, Any] = sambaMetricCollector()
for sensor in sensorList:
if isinstance(sensor[1], ConfigEntityFromSamba):
logging.info("Updating Samba sensor %s", sensor[0])
sensor[1].samba = samba
sensor[1].sensor.set_state(sensor[1].state_function(sensor[1])) # type: ignore
if sensor[1].attributes_function is not None:
sensor[1].sensor.set_attributes(
sensor[1].attributes_function(sensor[1])
)
await asyncio.sleep(10)
async def run() -> None:
def doneCallback(task):
for sensor in sensorList:
logging.info("Unpublish sensor %s", sensor[0])
sensor[1].sensor.delete()
# Loop Status publish
async with asyncio.TaskGroup() as tg:
if args["hdidle_log"] is not None:
task = tg.create_task(publish_idle_states(), name="Read and Publish HD-IDLE states")
task.add_done_callback(doneCallback)
tasks.append(task)
task = tg.create_task(publish_states(), name="Publish States")
task.add_done_callback(doneCallback)
tasks.append(task)
task = tg.create_task(publish_device_states(), name="Publish Device States")
task.add_done_callback(doneCallback)
tasks.append(task)
task = tg.create_task(publish_iostate_states(), name="Publish IO States")
task.add_done_callback(doneCallback)
tasks.append(task)
task = tg.create_task(publish_samba_states(), name="Publish Samba States")
task.add_done_callback(doneCallback)
tasks.append(task)
loop = asyncio.get_event_loop()
async def ask_exit(signame):
logging.warning("Signal %x. Unpublish %d sensors", signame, len(sensorList))
loop.remove_signal_handler(signame)
for task in tasks:
try:
task.cancel()
finally:
logging.warning(f"Task {task.get_name()} cancelled!")
for signame in ('SIGINT', 'SIGTERM', 'SIGHUP'):
loop.add_signal_handler(getattr(signal, signame),
lambda signame=signame: asyncio.create_task(ask_exit(signame)))
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(run())

403
samba/rootfs/usr/local/bin/poetry.lock generated Normal file
View File

@@ -0,0 +1,403 @@
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]]
name = "annotated-types"
version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]]
name = "chardet"
version = "5.2.0"
description = "Universal encoding detector for Python 3"
optional = false
python-versions = ">=3.7"
files = [
{file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"},
{file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"},
]
[[package]]
name = "diskinfo"
version = "3.1.2"
description = "Disk information Python library for Linux"
optional = false
python-versions = ">=3.8"
files = [
{file = "diskinfo-3.1.2-py3-none-any.whl", hash = "sha256:2b8606462b5783d18188ff1a8b7c5094dbc455e8361418033ffe2a823aa3d4b7"},
{file = "diskinfo-3.1.2.tar.gz", hash = "sha256:3f60a6f7b72dbf079c7f828540d38078e977aa5f578aa80a6e50b5860ffeaaa1"},
]
[package.dependencies]
pySMART = ">=1.3.0"
[[package]]
name = "gitlike-commands"
version = "0.3.0"
description = "Makes driver scripts that enable git-like commands so that foo bar will run foo-bar"
optional = false
python-versions = ">=3.9,<4.0"
files = [
{file = "gitlike_commands-0.3.0-py3-none-any.whl", hash = "sha256:c262f8f532639ec8558369bdc2cd904bd0b65638834ed333c42a51be69578f21"},
{file = "gitlike_commands-0.3.0.tar.gz", hash = "sha256:72f4e65239cb6a4a2c614867c5f914b5d5994edd2863335515b543689b01ff70"},
]
[[package]]
name = "ha-mqtt-discoverable"
version = "0.19.2"
description = "Python library for creating MQTT entities compatible with Home Assistant"
optional = false
python-versions = "<4.0,>=3.10.0"
files = [
{file = "ha_mqtt_discoverable-0.19.2-py3-none-any.whl", hash = "sha256:84725816a53d4e64f9d81cac6493c60dbd73899300c169b978fff9fdcbae2344"},
{file = "ha_mqtt_discoverable-0.19.2.tar.gz", hash = "sha256:2c0facdfdff5573a4bae7ab40e9b66cc077e65445fb9d6f356e1c74ce00aa9d9"},
]
[package.dependencies]
gitlike-commands = ">=0.2.1,<0.4.0"
paho-mqtt = "2.1.0"
pyaml = "25.5.0"
pydantic = "2.11.5"
[[package]]
name = "humanfriendly"
version = "10.0"
description = "Human friendly output for text interfaces using Python"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"},
{file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"},
]
[package.dependencies]
pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""}
[[package]]
name = "humanize"
version = "4.12.3"
description = "Python humanize utilities"
optional = false
python-versions = ">=3.9"
files = [
{file = "humanize-4.12.3-py3-none-any.whl", hash = "sha256:2cbf6370af06568fa6d2da77c86edb7886f3160ecd19ee1ffef07979efc597f6"},
{file = "humanize-4.12.3.tar.gz", hash = "sha256:8430be3a615106fdfceb0b2c1b41c4c98c6b0fc5cc59663a5539b111dd325fb0"},
]
[package.extras]
tests = ["freezegun", "pytest", "pytest-cov"]
[[package]]
name = "paho-mqtt"
version = "2.1.0"
description = "MQTT version 5.0/3.1.1 client class"
optional = false
python-versions = ">=3.7"
files = [
{file = "paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee"},
{file = "paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834"},
]
[package.extras]
proxy = ["pysocks"]
[[package]]
name = "psutil"
version = "7.0.0"
description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7."
optional = false
python-versions = ">=3.6"
files = [
{file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"},
{file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"},
{file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"},
{file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"},
{file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"},
{file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"},
{file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"},
{file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"},
{file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"},
{file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"},
]
[package.extras]
dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"]
test = ["pytest", "pytest-xdist", "setuptools"]
[[package]]
name = "pyaml"
version = "25.5.0"
description = "PyYAML-based module to produce a bit more pretty and readable YAML-serialized data"
optional = false
python-versions = ">=3.8"
files = [
{file = "pyaml-25.5.0-py3-none-any.whl", hash = "sha256:b9e0c4e58a5e8003f8f18e802db49fd0563ada587209b13e429bdcbefa87d035"},
{file = "pyaml-25.5.0.tar.gz", hash = "sha256:5799560c7b1c9daf35a7a4535f53e2c30323f74cbd7cb4f2e715b16dd681a58a"},
]
[package.dependencies]
PyYAML = "*"
[package.extras]
anchors = ["unidecode"]
[[package]]
name = "pydantic"
version = "2.11.5"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.9"
files = [
{file = "pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7"},
{file = "pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
pydantic-core = "2.33.2"
typing-extensions = ">=4.12.2"
typing-inspection = ">=0.4.0"
[package.extras]
email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata"]
[[package]]
name = "pydantic-core"
version = "2.33.2"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.9"
files = [
{file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"},
{file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"},
{file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"},
{file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"},
{file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"},
{file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"},
{file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"},
{file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"},
{file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"},
{file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"},
{file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"},
{file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"},
{file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"},
{file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"},
{file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"},
{file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"},
{file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"},
{file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"},
{file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"},
{file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"},
{file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"},
{file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"},
{file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"},
{file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"},
{file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"},
{file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"},
{file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"},
{file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"},
{file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"},
{file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"},
{file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"},
{file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"},
{file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"},
{file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"},
{file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"},
{file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"},
{file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"},
{file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"},
{file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"},
{file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"},
{file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"},
{file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"},
]
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pyreadline3"
version = "3.5.4"
description = "A python implementation of GNU readline."
optional = false
python-versions = ">=3.8"
files = [
{file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"},
{file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"},
]
[package.extras]
dev = ["build", "flake8", "mypy", "pytest", "twine"]
[[package]]
name = "pysmart"
version = "1.4.1"
description = "Wrapper for smartctl (smartmontools)"
optional = false
python-versions = ">=3.8"
files = [
{file = "pySMART-1.4.1-py3-none-any.whl", hash = "sha256:8bb92d0bb579a073c60dca60cb69eea6516d7cf2673ad6d149d2a49e61432e5d"},
{file = "pysmart-1.4.1.tar.gz", hash = "sha256:65700612477861acd83550c33a9430933017c93d91f66973ed409696b0327723"},
]
[package.dependencies]
chardet = "*"
humanfriendly = "*"
[package.extras]
dev = ["coveralls", "pdoc", "pytest", "pytest-cov"]
[[package]]
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "typing-inspection"
version = "0.4.0"
description = "Runtime typing introspection tools"
optional = false
python-versions = ">=3.9"
files = [
{file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"},
{file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"},
]
[package.dependencies]
typing-extensions = ">=4.12.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "13b19a408fb9cc0752b3e7d593791056598fe9ab78303fef118e37a7b6a3836f"

View File

@@ -0,0 +1,21 @@
[tool.poetry]
name = "sambanas-util"
version = "1.0.1"
description = "Utility for SambaNas Addon"
authors = ["dianlight <lucio.tarantino@gmail.com>"]
license = "MIT"
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.12"
pySMART = "^1.3.0"
psutil = "^7.0.0"
humanize = "^4.9.0"
diskinfo = "^3.1.1"
ha-mqtt-discoverable = "^0.19.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,8 +1,49 @@
[global]
{{ if .compatibility_mode }}
client min protocol = NT1
server min protocol = NT1
{{ else }}
server min protocol = SMB2_10
client min protocol = SMB2_10
{{ end }}
{{if .multi_channel }}
server multi channel support = yes
aio read size = 1
aio write size = 1
{{ end }}
dns proxy = yes
ea support = yes
vfs objects = catia fruit streams_xattr{{ if .recyle_bin_enabled }} recycle{{ end }}
fruit:aapl = yes
fruit:model = MacSamba
fruit:resource = file
fruit:veto_appledouble = no
fruit:posix_rename = yes
fruit:wipe_intentionally_left_blank_rfork = yes
fruit:zero_file_id = yes
fruit:delete_empty_adfiles = yes
# cherry pick from PR#167 to Test
fruit:copyfile = yes
fruit:nfs_aces = no
# Performance Enhancements for network
socket options = TCP_NODELAY IPTOS_LOWDELAY
min receivefile size = 16384
getwd cache = yes
aio read size = 1
aio write size = 1
# End PR#167
netbios name = {{ env "HOSTNAME" }}
workgroup = {{ .workgroup }}
server string = Samba Home Assistant
server string = Samba NAS HomeAssistant config
local master = {{ .local_master | ternary "yes" "no" }}
multicast dns register = {{ if or .wsdd .wsdd2 }}no{{ else }}yes{{ end }}
security = user
ntlm auth = yes
@@ -12,125 +53,103 @@
load printers = no
disable spoolss = yes
log level = 1
{{ $log_level := dict "trace" "5" "debug" "4" "info" "3" "notice" "2" "warning" "1" "error" "1" "fatal" "1" -}}
log level = {{ .log_level | default "warning" | get $log_level }}
bind interfaces only = yes
interfaces = lo {{ .interfaces | join " " }}
hosts allow = 127.0.0.1 {{ .allow_hosts | join " " }}
{{ if .compatibility_mode }}
client min protocol = NT1
server min protocol = NT1
{{ end }}
bind interfaces only = {{ .bind_all_interfaces | default false | ternary "no" "yes" }}
interfaces = 127.0.0.1 {{ .interfaces | join " " }} {{ .docker_interface | default " "}}
hosts allow = 127.0.0.1 {{ .allow_hosts | join " " }} {{ .docker_net | default " " }}
mangled names = no
dos charset = CP850
dos charset = CP1253
unix charset = UTF-8
{{ if .apple_compatibility_mode }}
vfs objects = catia fruit streams_xattr
{{ define "SHT" }}
{{- $unsupported := list "vfat" "msdos" "f2fs" "fuseblk" "exfat" -}}
{{- $rosupported := list "apfs"}}
{{- $name := regexReplaceAll "[^A-Za-z0-9_/ ]" .share "_" | regexFind "[A-Za-z0-9_ ]+$" | upper -}}
{{- $dinfo := get .shares $name | default dict -}}
[{{- $name -}}]
browseable = yes
writeable = {{ has $dinfo.fs $rosupported | ternary "no" "yes" }}
# cherry pick from PR#167 to Test
create mask = 0664
force create mode = 0664
directory mask = 0775
force directory mode = 0775
# End PR#167
path = /{{- if eq .share "config" }}homeassistant{{- else }}{{- .share }}{{- end }}
valid users =_ha_mount_user_ {{ .users|default .username|join " " }} {{ .ro_users|join " " }}
{{ if .ro_users }}
read list = {{ .ro_users|join " " }}
{{ end }}
{{ if (has "config" .enabled_shares) }}
[config]
browseable = yes
writeable = yes
path = /homeassistant
valid users = {{ .username }}
force user = root
force group = root
veto files = /{{ .veto_files | join "/" }}/
delete veto files = {{ eq (len .veto_files) 0 | ternary "no" "yes" }}
# DEBUG: {{ toJson $dinfo }}|.share={{ .share }}|$name={{ $name }}|.shares={{ .shares }}|
{{if .recyle_bin_enabled }}
recycle:repository = .recycle/%U
recycle:keeptree = yes
recycle:versions = yes
recycle:touch = yes
recycle:touch_mtime = no
recycle:directory_mode = 0777
#recycle:subdir_mode = 0700
#recycle:exclude =
#recycle:exclude_dir =
#recycle:maxsize = 0
{{ end }}
{{ if (has "addons" .enabled_shares) }}
[addons]
browseable = yes
writeable = yes
path = /addons
# TM:{{ if has $dinfo.fs $unsupported }}unsupported{{else}}{{ .timemachine }}{{ end }} US:{{ .users|default .username|join "," }} {{ .ro_users|join "," }}{{- if .medialibrary.enable }}{{ if .usage }} CL:{{ .usage }}{{ end }} FS:{{ $dinfo.fs | default "native" }} {{ if .recyle_bin_enabled }}RECYCLEBIN{{ end }} {{ end }}
{{- if and .timemachine (has $dinfo.fs $unsupported | not ) }}
vfs objects = catia fruit streams_xattr{{ if .recyle_bin_enabled }} recycle{{ end }}
valid users = {{ .username }}
force user = root
force group = root
veto files = /{{ .veto_files | join "/" }}/
delete veto files = {{ eq (len .veto_files) 0 | ternary "no" "yes" }}
# Time Machine Settings Ref: https://github.com/markthomas93/samba.apple.templates
fruit:time machine = yes
#fruit:time machine max size = SIZE [K|M|G|T|P]
fruit:metadata = stream
{{ end }}
{{- if has $dinfo.fs $unsupported }}
vfs objects = catia{{ if .recyle_bin_enabled }} recycle{{ end }}
{{ end }}
{{ if (has "addon_configs" .enabled_shares) }}
[addon_configs]
browseable = yes
writeable = yes
path = /addon_configs
valid users = {{ .username }}
force user = root
force group = root
veto files = /{{ .veto_files | join "/" }}/
delete veto files = {{ eq (len .veto_files) 0 | ternary "no" "yes" }}
{{ end }}
{{ if (has "ssl" .enabled_shares) }}
[ssl]
browseable = yes
writeable = yes
path = /ssl
valid users = {{ .username }}
force user = root
force group = root
veto files = /{{ .veto_files | join "/" }}/
delete veto files = {{ eq (len .veto_files) 0 | ternary "no" "yes" }}
{{ end }}
{{ if (has "share" .enabled_shares) }}
[share]
browseable = yes
writeable = yes
path = /share
valid users = {{ .username }}
force user = root
force group = root
veto files = /{{ .veto_files | join "/" }}/
delete veto files = {{ eq (len .veto_files) 0 | ternary "no" "yes" }}
{{ end }}
{{ if (has "backup" .enabled_shares) }}
[backup]
browseable = yes
writeable = yes
path = /backup
valid users = {{ .username }}
force user = root
force group = root
veto files = /{{ .veto_files | join "/" }}/
delete veto files = {{ eq (len .veto_files) 0 | ternary "no" "yes" }}
{{ end }}
{{ if (has "media" .enabled_shares) }}
[media]
browseable = yes
writeable = yes
path = /media
valid users = {{ .username }}
force user = root
force group = root
veto files = /{{ .veto_files | join "/" }}/
delete veto files = {{ eq (len .veto_files) 0 | ternary "no" "yes" }}
{{ end }}
{{ if (has "sda8" .enabled_shares) }}
[sda8]
browseable = yes
writeable = yes
path = /mnt/sda8
valid users = {{ .username }}
force user = root
force group = root
veto files = /{{ .veto_files | join "/" }}/
delete veto files = {{ eq (len .veto_files) 0 | ternary "no" "yes" }}
{{ end }}
{{- $dfdisk := list "config" "addons" "ssl" "share" "backup" "media" "addon_configs" }}
{{- $disks := concat $dfdisk (compact .moredisks|default list) -}}
{{- $root := . -}}
{{- range $disk := $disks -}}
{{- $acld := false -}}
{{- range $dd := $root.acl -}}
{{- $ndisk := $disk | regexFind "[A-Za-z0-9_]+$" -}}
{{- if eq ($dd.share|upper) ($ndisk|upper) -}}
{{- $def := deepCopy $dd }}
{{- $acld = true -}}
{{- if not $dd.disabled -}}
{{- $_ := set $dd "share" $disk -}}
{{- if has $disk $dfdisk -}}
{{- $_ := set $def "timemachine" false -}}
{{- $_ := set $def "usage" "" -}}
{{- else -}}
{{- $_ := set $def "timemachine" true -}}
{{- $_ := set $def "usage" "media" -}}
{{- end }}
{{- template "SHT" deepCopy $root | mergeOverwrite $def $dd -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $acld -}}
{{- $dd := dict "share" $disk "timemachine" true -}}
{{- $_ := set $dd "usage" "media" -}}
{{- if has $disk $dfdisk -}}
{{- $_ := set $dd "timemachine" false -}}
{{- $_ := set $dd "usage" "" -}}
{{- end }}
{{- template "SHT" (deepCopy $root | mergeOverwrite $dd) -}}
{{- end -}}
{{- end -}}