#!/usr/bin/env bash set -euo pipefail ENV_FILE=".env" SERVICE_ACCOUNT_NAME="FileShare_ServiceAccount" BOOTSTRAP_ENV_FILE="" ORIGINAL_ENV_BACKUP="" BOOTSTRAP_ENV_WRITTEN=0 FINAL_ENV_WRITTEN=0 ENV_PREEXISTED=0 cleanup() { if [[ -n "$BOOTSTRAP_ENV_FILE" && -f "$BOOTSTRAP_ENV_FILE" ]]; then rm -f "$BOOTSTRAP_ENV_FILE" fi if [[ "$BOOTSTRAP_ENV_WRITTEN" -eq 1 && "$FINAL_ENV_WRITTEN" -eq 0 ]]; then if [[ "$ENV_PREEXISTED" -eq 1 && -n "$ORIGINAL_ENV_BACKUP" && -f "$ORIGINAL_ENV_BACKUP" ]]; then cp "$ORIGINAL_ENV_BACKUP" "$ENV_FILE" chmod 600 "$ENV_FILE" printf "Restored original %s after setup failure.\n" "$ENV_FILE" >&2 else rm -f "$ENV_FILE" printf "Removed temporary %s after setup failure.\n" "$ENV_FILE" >&2 fi fi if [[ -n "$ORIGINAL_ENV_BACKUP" && -f "$ORIGINAL_ENV_BACKUP" ]]; then rm -f "$ORIGINAL_ENV_BACKUP" fi } trap cleanup EXIT sanitize_netbios_name() { local raw_name="$1" 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="ADSAMBAFSRV" fi printf '%s' "${cleaned_name:0:15}" } sanitize_sam_account_name() { local raw_name="$1" local cleaned_name cleaned_name="$(printf '%s' "$raw_name" | tr -cd 'A-Za-z0-9._-')" if [[ "$cleaned_name" == "FileShare_ServiceAccount" ]]; then printf '%s' "FileShare_ServiceAcc" return fi if [[ -z "$cleaned_name" ]]; then cleaned_name="FileShareSvc" fi printf '%s' "${cleaned_name:0:20}" } parse_route_source_ip() { local route="$1" local previous="" local token for token in $route; do if [[ "$previous" == "src" ]]; then printf '%s\n' "$token" return 0 fi previous="$token" done return 1 } detect_ad_dns_ip() { local lookup_name="$1" local target_ip="" local route="" if ! command -v ip >/dev/null 2>&1; then return 1 fi if [[ -n "$lookup_name" ]] && command -v getent >/dev/null 2>&1; then while read -r target_ip _; do if [[ ! "$target_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then continue fi route="$(ip -o -4 route get "$target_ip" 2>/dev/null || true)" parse_route_source_ip "$route" && return 0 done < <(getent ahostsv4 "$lookup_name" 2>/dev/null) fi route="$(ip -o -4 route get 1.1.1.1 2>/dev/null || true)" parse_route_source_ip "$route" } prompt_value() { local var_name="$1" local prompt_text="$2" local is_secret="${3:-false}" local value="" while [[ -z "$value" ]]; do if [[ "$is_secret" == "true" ]]; then read -r -s -p "$prompt_text: " value printf "\n" else read -r -p "$prompt_text: " value fi done printf -v "$var_name" '%s' "$value" } write_env_file() { local realm="" local workgroup="" local domain="" local admin_user="" local admin_password="" local domain_users_sid="" local domain_admins_sid="" local fslogix_group_sid="" local backup_destination="" local backup_start_hour="2" local backup_retention_daily="3" local backup_retention_weekly="2" local backup_retention_monthly="2" local backup_retention_yearly="1" local samba_hostname="adsambafsrv" local netbios_name="ADSAMBAFSRV" local ad_dns_ip="" local ad_dns_name="" local ad_dns_ip_auto="0" local service_password="" local service_account_sam="" local fslogix_group_prompt="" local samba_hostname_input="" local netbios_name_input="" local ad_dns_ip_input="" local ad_dns_name_input="" local detected_ad_dns_ip="" local sanitized_netbios_name="" prompt_value realm "REALM (e.g. EXAMPLE.COM)" prompt_value workgroup "WORKGROUP (NetBIOS, e.g. EXAMPLE)" prompt_value domain "DOMAIN (AD DNS name or reachable DC FQDN)" prompt_value admin_user "Initial admin user (for provisioning service account)" prompt_value admin_password "Initial admin password" true prompt_value domain_users_sid "DOMAIN_USERS_SID (e.g. ...-513)" prompt_value domain_admins_sid "DOMAIN_ADMINS_SID (e.g. ...-512)" fslogix_group_prompt="FSLOGIX_GROUP_SID (press Enter to reuse DOMAIN_USERS_SID)" read -r -p "${fslogix_group_prompt}: " fslogix_group_sid if [[ -z "$fslogix_group_sid" ]]; then fslogix_group_sid="$domain_users_sid" fi read -r -p "SAMBA_HOSTNAME [adsambafsrv]: " samba_hostname_input if [[ -n "${samba_hostname_input:-}" ]]; then samba_hostname="$samba_hostname_input" fi read -r -p "NETBIOS_NAME [ADSAMBAFSRV]: " netbios_name_input if [[ -n "${netbios_name_input:-}" ]]; then netbios_name="$netbios_name_input" fi sanitized_netbios_name="$(sanitize_netbios_name "$netbios_name")" if [[ "$sanitized_netbios_name" != "$netbios_name" ]]; then printf "Using sanitized NETBIOS_NAME: %s\n" "$sanitized_netbios_name" fi netbios_name="$sanitized_netbios_name" detected_ad_dns_ip="$(detect_ad_dns_ip "$domain" || true)" if [[ -n "$detected_ad_dns_ip" ]]; then read -r -p "AD_DNS_IP (host LAN IP to publish in AD DNS) [${detected_ad_dns_ip}]: " ad_dns_ip_input if [[ -n "$ad_dns_ip_input" ]]; then ad_dns_ip="$ad_dns_ip_input" else ad_dns_ip="$detected_ad_dns_ip" ad_dns_ip_auto="1" fi else prompt_value ad_dns_ip "AD_DNS_IP (host LAN IP to publish in AD DNS)" fi ad_dns_name="${samba_hostname}.${domain}" read -r -p "AD_DNS_NAME [${ad_dns_name}]: " ad_dns_name_input if [[ -n "$ad_dns_name_input" ]]; then ad_dns_name="$ad_dns_name_input" fi read -r -p "BACKUP_DESTINATION (optional URL, press Enter to disable): " backup_destination read -r -p "BACKUP_START_HOUR [2]: " backup_start_hour backup_start_hour="${backup_start_hour:-2}" read -r -p "BACKUP_RETENTION_DAILY [3]: " backup_retention_daily backup_retention_daily="${backup_retention_daily:-3}" read -r -p "BACKUP_RETENTION_WEEKLY [2]: " backup_retention_weekly backup_retention_weekly="${backup_retention_weekly:-2}" read -r -p "BACKUP_RETENTION_MONTHLY [2]: " backup_retention_monthly backup_retention_monthly="${backup_retention_monthly:-2}" read -r -p "BACKUP_RETENTION_YEARLY [1]: " backup_retention_yearly backup_retention_yearly="${backup_retention_yearly:-1}" service_account_sam="$(sanitize_sam_account_name "$SERVICE_ACCOUNT_NAME")" if [[ "$service_account_sam" != "$SERVICE_ACCOUNT_NAME" ]]; then printf "Using sAMAccountName '%s' (AD limit is 20 chars; requested '%s').\n" "$service_account_sam" "$SERVICE_ACCOUNT_NAME" fi service_password="$(python3 - <<'PY' import secrets import string alphabet = string.ascii_letters + string.digits + '@#%+=:_-' print(''.join(secrets.choice(alphabet) for _ in range(48))) PY )" if [[ -f "$ENV_FILE" ]]; then ENV_PREEXISTED=1 ORIGINAL_ENV_BACKUP="$(mktemp)" cp "$ENV_FILE" "$ORIGINAL_ENV_BACKUP" chmod 600 "$ORIGINAL_ENV_BACKUP" fi BOOTSTRAP_ENV_FILE="$(mktemp)" chmod 600 "$BOOTSTRAP_ENV_FILE" cat > "$BOOTSTRAP_ENV_FILE" < /tmp/bootstrap-smb.conf < /tmp/bootstrap.auth < "$ENV_FILE" <