#!/usr/bin/env bash set -euo pipefail log() { printf '[init] %s\n' "$*" } require_env() { local name="$1" if [[ -z "${!name:-}" ]]; then printf '[init] ERROR: missing required env var %s\n' "$name" >&2 exit 1 fi } append_winbind_to_nss() { sed -ri '/^passwd:/ { /winbind/! s/$/ winbind/ }' /etc/nsswitch.conf sed -ri '/^group:/ { /winbind/! s/$/ winbind/ }' /etc/nsswitch.conf } derive_netbios_name() { local raw_name="${NETBIOS_NAME:-ADSAMBAFSRV}" local upper_name="${raw_name^^}" local cleaned_name cleaned_name="$(printf '%s' "$upper_name" | tr -cd 'A-Z0-9')" if [[ -z "$cleaned_name" ]]; then cleaned_name="SAMBAFS" fi if [[ ${#cleaned_name} -gt 15 ]]; then log "NETBIOS_NAME derived from '${raw_name}' exceeds 15 chars, truncating." fi export NETBIOS_NAME="${cleaned_name:0:15}" } resolve_sid_to_group() { local sid="$1" local group_name="" local sid_output="" if sid_output="$(wbinfo --sid-to-fullname "$sid" 2>/dev/null)"; then group_name="${sid_output%%$'\t'*}" fi if [[ -z "$group_name" ]] && sid_output="$(wbinfo -s "$sid" 2>/dev/null)"; then group_name="$(printf '%s' "$sid_output" | sed -E 's/[[:space:]]+[0-9]+$//')" fi if [[ -z "$group_name" ]]; then printf '[init] ERROR: unable to resolve SID %s via winbind\n' "$sid" >&2 return 1 fi printf '%s\n' "$group_name" } resolve_share_groups_from_sids() { export DOMAIN_USERS_GROUP DOMAIN_USERS_GROUP="$(resolve_sid_to_group "$DOMAIN_USERS_SID")" export DOMAIN_ADMINS_GROUP DOMAIN_ADMINS_GROUP="$(resolve_sid_to_group "$DOMAIN_ADMINS_SID")" export PUBLIC_GROUP PUBLIC_GROUP="$(resolve_sid_to_group "$PUBLIC_GROUP_SID")" } render_krb5_conf() { cat > /etc/krb5.conf < /etc/samba/smb.conf testparm -s /etc/samba/smb.conf >/dev/null } write_runtime_env_file() { { printf 'export REALM=%q\n' "$REALM" printf 'export WORKGROUP=%q\n' "$WORKGROUP" printf 'export DOMAIN=%q\n' "$DOMAIN" printf 'export NETBIOS_NAME=%q\n' "$NETBIOS_NAME" printf 'export DOMAIN_USERS_SID=%q\n' "$DOMAIN_USERS_SID" printf 'export DOMAIN_ADMINS_SID=%q\n' "$DOMAIN_ADMINS_SID" printf 'export PUBLIC_GROUP_SID=%q\n' "$PUBLIC_GROUP_SID" printf 'export DOMAIN_USERS_GROUP=%q\n' "$DOMAIN_USERS_GROUP" printf 'export DOMAIN_ADMINS_GROUP=%q\n' "$DOMAIN_ADMINS_GROUP" printf 'export PUBLIC_GROUP=%q\n' "$PUBLIC_GROUP" if [[ -n "${JOIN_USER:-}" ]]; then printf 'export JOIN_USER=%q\n' "$JOIN_USER" fi if [[ -n "${JOIN_PASSWORD:-}" ]]; then printf 'export JOIN_PASSWORD=%q\n' "$JOIN_PASSWORD" fi if [[ -n "${LDAP_URI:-}" ]]; then printf 'export LDAP_URI=%q\n' "$LDAP_URI" fi if [[ -n "${LDAP_BASE_DN:-}" ]]; then printf 'export LDAP_BASE_DN=%q\n' "$LDAP_BASE_DN" fi } > /app/runtime.env chmod 600 /app/runtime.env } join_domain_if_needed() { if net ads testjoin >/dev/null 2>&1; then log 'Domain join already present; skipping join.' return fi require_env JOIN_USER require_env JOIN_PASSWORD log "Joining AD domain ${REALM}" if ! printf '%s\n' "$JOIN_PASSWORD" | net ads join -U "$JOIN_USER" -S "$DOMAIN"; then log 'Join using explicit server failed, retrying automatic DC discovery.' printf '%s\n' "$JOIN_PASSWORD" | net ads join -U "$JOIN_USER" fi } wait_for_winbind() { local tries=0 local max_tries=30 until wbinfo -t >/dev/null 2>&1; do tries=$((tries + 1)) if [[ "$tries" -ge "$max_tries" ]]; then printf '[init] ERROR: winbind trust test failed after %d attempts\n' "$max_tries" >&2 return 1 fi sleep 2 done return 0 } install_cron_job() { cat > /etc/cron.d/reconcile-shares <<'EOF' SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin */5 * * * * root source /app/runtime.env && /usr/bin/python3 /app/reconcile_shares.py >> /var/log/reconcile.log 2>&1 EOF chmod 0644 /etc/cron.d/reconcile-shares } require_env REALM require_env WORKGROUP require_env DOMAIN require_env DOMAIN_USERS_SID require_env DOMAIN_ADMINS_SID export REALM WORKGROUP DOMAIN export PUBLIC_GROUP_SID="${PUBLIC_GROUP_SID:-${DOMAIN_USERS_SID}}" export DOMAIN_USERS_GROUP="${DOMAIN_USERS_SID}" export DOMAIN_ADMINS_GROUP="${DOMAIN_ADMINS_SID}" export PUBLIC_GROUP="${PUBLIC_GROUP_SID}" if [[ -n "${JOIN_USER:-}" ]]; then export JOIN_USER fi if [[ -n "${JOIN_PASSWORD:-}" ]]; then export JOIN_PASSWORD fi mkdir -p /data/private /data/public /data/groups /state /etc/samba/generated /var/log/samba touch /etc/samba/generated/shares.conf /var/log/reconcile.log append_winbind_to_nss derive_netbios_name render_krb5_conf render_smb_conf join_domain_if_needed log 'Starting winbindd' winbindd -F --no-process-group & wait_for_winbind resolve_share_groups_from_sids render_smb_conf write_runtime_env_file log 'Running startup reconciliation' python3 /app/reconcile_shares.py install_cron_job log 'Starting cron daemon' cron -f & log 'Starting smbd in foreground' exec smbd -F --no-process-group