fix: publish host IP in AD DNS
This commit is contained in:
@@ -6,6 +6,9 @@ JOIN_PASSWORD=ReplaceWithLongRandomPassword
|
||||
DOMAIN_USERS_SID=S-1-5-21-1111111111-2222222222-3333333333-513
|
||||
DOMAIN_ADMINS_SID=S-1-5-21-1111111111-2222222222-3333333333-512
|
||||
FSLOGIX_GROUP_SID=S-1-5-21-1111111111-2222222222-3333333333-513
|
||||
# AD_DNS_IP=192.0.2.10
|
||||
# AD_DNS_NAME=adsambafsrv.example.com
|
||||
# AD_DNS_IP_AUTO=1
|
||||
# SAMBA_HOSTNAME=adsambafsrv
|
||||
# NETBIOS_NAME=ADSAMBAFSRV
|
||||
# LDAP_URI=ldaps://example.com
|
||||
|
||||
17
README.md
17
README.md
@@ -16,6 +16,7 @@ This repository provides a production-oriented Samba file server container that
|
||||
- inactive/deleted groups: `/data/groups/archive/<groupName>`
|
||||
- Samba machine trust/key material is persisted in `/var/lib/samba` to survive container recreation.
|
||||
- Container hostname is fixed (`SAMBA_HOSTNAME`) to keep AD computer identity stable.
|
||||
- In bridge-mode Docker networking, startup can publish the host LAN IP in AD DNS with `AD_DNS_IP`/`AD_DNS_NAME` instead of the container bridge IP.
|
||||
- NetBIOS name defaults to `ADSAMBAFSRV` and is clamped to 15 characters (`NETBIOS_NAME` override supported).
|
||||
- Setup prompts for well-known authorization groups by SID (`DOMAIN_USERS_SID`, `DOMAIN_ADMINS_SID`) to avoid localized group names.
|
||||
- `FSLOGIX_GROUP_SID` controls who can access the default FSLogix share (defaults to `DOMAIN_USERS_SID`).
|
||||
@@ -71,6 +72,9 @@ CREATE TABLE shares (
|
||||
- Container must resolve AD DNS records (especially SRV records for domain controllers).
|
||||
- `DOMAIN` should resolve from inside the container.
|
||||
- Preferred setup: Docker host uses AD-integrated DNS or forwards to AD DNS.
|
||||
- If Docker bridge networking is used, set `AD_DNS_IP` to the Docker host LAN IP that clients should use for SMB, not the container `172.x` address.
|
||||
- When `AD_DNS_IP` is set, domain join uses `--no-dns-updates`; each container startup unregisters/re-registers `AD_DNS_NAME -> AD_DNS_IP` in AD DNS using the machine account.
|
||||
- `./setup` and `./redeploy` auto-refresh `AD_DNS_IP` from the host route when `AD_DNS_IP_AUTO=1`.
|
||||
|
||||
## Time Sync Requirements
|
||||
|
||||
@@ -104,6 +108,8 @@ Kerberos requires close time alignment.
|
||||
- `REALM`
|
||||
- `WORKGROUP`
|
||||
- `DOMAIN`
|
||||
- `AD_DNS_IP` (host LAN IP to publish in AD DNS)
|
||||
- optional `AD_DNS_NAME` (defaults to `SAMBA_HOSTNAME.DOMAIN`)
|
||||
- initial admin credentials (used once for provisioning)
|
||||
- `DOMAIN_USERS_SID`
|
||||
- `DOMAIN_ADMINS_SID`
|
||||
@@ -124,6 +130,7 @@ Kerberos requires close time alignment.
|
||||
- uses a valid AD `sAMAccountName` (max 20 chars); default effective value is `FileShare_ServiceAcc`
|
||||
- always sets a long random password
|
||||
- writes only service-account credentials to `.env` (initial admin credentials are not stored)
|
||||
- writes `AD_DNS_IP` so container restarts keep AD DNS pointed at the host LAN IP
|
||||
|
||||
4. The setup script then starts the service with:
|
||||
|
||||
@@ -239,6 +246,16 @@ docker compose exec samba sh -lc 'tail -n 200 /var/log/backup.log'
|
||||
docker compose exec samba testparm -s | grep -i 'netbios name'
|
||||
```
|
||||
|
||||
### AD DNS points at 172.x
|
||||
|
||||
- This means Samba registered the container bridge IP.
|
||||
- Ensure `.env` contains `AD_DNS_IP=<host LAN IP>` and `AD_DNS_NAME=<server FQDN>`.
|
||||
- Recreate/restart the container so startup re-registers AD DNS:
|
||||
|
||||
```bash
|
||||
docker compose up -d --force-recreate samba
|
||||
```
|
||||
|
||||
### Winbind user/group resolution fails
|
||||
|
||||
- Check trust:
|
||||
|
||||
53
app/init.sh
53
app/init.sh
@@ -73,6 +73,30 @@ derive_backup_start_hour() {
|
||||
printf '2\n'
|
||||
}
|
||||
|
||||
derive_ad_dns_name() {
|
||||
local raw_name="${AD_DNS_NAME:-}"
|
||||
local host_name="${SAMBA_HOSTNAME:-}"
|
||||
|
||||
if [[ -z "$host_name" ]]; then
|
||||
host_name="$(hostname -s 2>/dev/null || true)"
|
||||
fi
|
||||
if [[ -z "$host_name" ]]; then
|
||||
host_name="${NETBIOS_NAME,,}"
|
||||
fi
|
||||
|
||||
if [[ -n "$raw_name" ]]; then
|
||||
if [[ "$raw_name" == *.* ]]; then
|
||||
AD_DNS_NAME="$raw_name"
|
||||
else
|
||||
AD_DNS_NAME="${raw_name}.${DOMAIN}"
|
||||
fi
|
||||
else
|
||||
AD_DNS_NAME="${host_name}.${DOMAIN}"
|
||||
fi
|
||||
|
||||
export AD_DNS_NAME="${AD_DNS_NAME,,}"
|
||||
}
|
||||
|
||||
resolve_sid_to_group() {
|
||||
local sid="$1"
|
||||
local resolved_name=""
|
||||
@@ -164,6 +188,10 @@ write_runtime_env_file() {
|
||||
printf 'export WORKGROUP=%q\n' "$WORKGROUP"
|
||||
printf 'export DOMAIN=%q\n' "$DOMAIN"
|
||||
printf 'export NETBIOS_NAME=%q\n' "$NETBIOS_NAME"
|
||||
printf 'export AD_DNS_NAME=%q\n' "$AD_DNS_NAME"
|
||||
if [[ -n "${AD_DNS_IP:-}" ]]; then
|
||||
printf 'export AD_DNS_IP=%q\n' "$AD_DNS_IP"
|
||||
fi
|
||||
printf 'export DOMAIN_USERS_SID=%q\n' "$DOMAIN_USERS_SID"
|
||||
printf 'export DOMAIN_ADMINS_SID=%q\n' "$DOMAIN_ADMINS_SID"
|
||||
printf 'export FSLOGIX_GROUP_SID=%q\n' "$FSLOGIX_GROUP_SID"
|
||||
@@ -211,10 +239,29 @@ join_domain_if_needed() {
|
||||
require_env JOIN_USER
|
||||
require_env JOIN_PASSWORD
|
||||
|
||||
local join_options=()
|
||||
if [[ -n "${AD_DNS_IP:-}" ]]; then
|
||||
join_options=(--no-dns-updates "dnshostname=${AD_DNS_NAME}")
|
||||
fi
|
||||
|
||||
log "Joining AD domain ${REALM}"
|
||||
if ! printf '%s\n' "$JOIN_PASSWORD" | net ads join -U "$JOIN_USER" -S "$DOMAIN"; then
|
||||
if ! printf '%s\n' "$JOIN_PASSWORD" | net ads join "${join_options[@]}" -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"
|
||||
printf '%s\n' "$JOIN_PASSWORD" | net ads join "${join_options[@]}" -U "$JOIN_USER"
|
||||
fi
|
||||
}
|
||||
|
||||
register_ad_dns_record() {
|
||||
if [[ -z "${AD_DNS_IP:-}" ]]; then
|
||||
log 'AD_DNS_IP is unset; skipping explicit AD DNS registration.'
|
||||
return
|
||||
fi
|
||||
|
||||
log "Registering AD DNS ${AD_DNS_NAME} -> ${AD_DNS_IP}"
|
||||
net ads dns unregister -P "$AD_DNS_NAME" >/dev/null 2>&1 || true
|
||||
if ! net ads dns register -P "$AD_DNS_NAME" "$AD_DNS_IP" >/dev/null; then
|
||||
printf '[init] ERROR: failed to register AD DNS %s -> %s\n' "$AD_DNS_NAME" "$AD_DNS_IP" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -273,6 +320,7 @@ touch /var/log/reconcile.log /var/log/backup.log
|
||||
append_winbind_to_nss
|
||||
require_vfs_modules
|
||||
derive_netbios_name
|
||||
derive_ad_dns_name
|
||||
render_krb5_conf
|
||||
render_smb_conf
|
||||
join_domain_if_needed
|
||||
@@ -283,6 +331,7 @@ winbindd -F --no-process-group &
|
||||
|
||||
wait_for_winbind
|
||||
|
||||
register_ad_dns_record
|
||||
resolve_share_groups_from_sids
|
||||
render_smb_conf
|
||||
write_runtime_env_file
|
||||
|
||||
129
redeploy
129
redeploy
@@ -5,11 +5,140 @@ log() {
|
||||
printf '[redeploy] %s\n' "$*"
|
||||
}
|
||||
|
||||
read_env_value() {
|
||||
local key="$1"
|
||||
local line=""
|
||||
|
||||
if [[ ! -f .env ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" == "${key}="* ]]; then
|
||||
printf '%s\n' "${line#*=}"
|
||||
return 0
|
||||
fi
|
||||
done < .env
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
upsert_env_value() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local temp_file=""
|
||||
local line=""
|
||||
local found=0
|
||||
|
||||
temp_file="$(mktemp)"
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" == "${key}="* ]]; then
|
||||
printf '%s=%s\n' "$key" "$value" >> "$temp_file"
|
||||
found=1
|
||||
else
|
||||
printf '%s\n' "$line" >> "$temp_file"
|
||||
fi
|
||||
done < .env
|
||||
|
||||
if [[ "$found" -eq 0 ]]; then
|
||||
printf '%s=%s\n' "$key" "$value" >> "$temp_file"
|
||||
fi
|
||||
|
||||
chmod --reference=.env "$temp_file" 2>/dev/null || chmod 600 "$temp_file"
|
||||
mv "$temp_file" .env
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
refresh_ad_dns_env() {
|
||||
local auto=""
|
||||
local domain=""
|
||||
local detected_ip=""
|
||||
local samba_hostname=""
|
||||
local netbios_name=""
|
||||
local host_part=""
|
||||
|
||||
if [[ ! -f .env ]]; then
|
||||
log '.env missing; skipping AD DNS IP refresh'
|
||||
return
|
||||
fi
|
||||
|
||||
auto="$(read_env_value AD_DNS_IP_AUTO || true)"
|
||||
case "${auto,,}" in
|
||||
0|false|no)
|
||||
log 'AD_DNS_IP_AUTO disabled; keeping configured AD_DNS_IP'
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
domain="$(read_env_value DOMAIN || true)"
|
||||
if [[ -z "$domain" ]]; then
|
||||
log 'DOMAIN missing in .env; skipping AD DNS IP refresh'
|
||||
return
|
||||
fi
|
||||
|
||||
detected_ip="$(detect_ad_dns_ip "$domain" || true)"
|
||||
if [[ -z "$detected_ip" ]]; then
|
||||
log 'Unable to detect host LAN IP; keeping configured AD_DNS_IP'
|
||||
return
|
||||
fi
|
||||
|
||||
upsert_env_value AD_DNS_IP "$detected_ip"
|
||||
if ! read_env_value AD_DNS_NAME >/dev/null; then
|
||||
samba_hostname="$(read_env_value SAMBA_HOSTNAME || true)"
|
||||
netbios_name="$(read_env_value NETBIOS_NAME || true)"
|
||||
host_part="${samba_hostname:-${netbios_name,,}}"
|
||||
host_part="${host_part:-adsambafsrv}"
|
||||
upsert_env_value AD_DNS_NAME "${host_part}.${domain}"
|
||||
fi
|
||||
upsert_env_value AD_DNS_IP_AUTO "1"
|
||||
log "Updated AD_DNS_IP=${detected_ip}"
|
||||
}
|
||||
|
||||
if [[ ! -d .git ]]; then
|
||||
printf '[redeploy] ERROR: run this script from the repository root.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
refresh_ad_dns_env
|
||||
|
||||
OLD_COMPOSE_FILE="$(mktemp)"
|
||||
|
||||
cleanup() {
|
||||
|
||||
70
setup
70
setup
@@ -57,6 +57,45 @@ sanitize_sam_account_name() {
|
||||
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"
|
||||
@@ -92,11 +131,17 @@ write_env_file() {
|
||||
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)"
|
||||
@@ -128,6 +173,25 @@ write_env_file() {
|
||||
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}"
|
||||
@@ -176,6 +240,9 @@ SERVICE_ACCOUNT_PASSWORD=${service_password}
|
||||
DOMAIN_USERS_SID=${domain_users_sid}
|
||||
DOMAIN_ADMINS_SID=${domain_admins_sid}
|
||||
FSLOGIX_GROUP_SID=${fslogix_group_sid}
|
||||
AD_DNS_IP=${ad_dns_ip}
|
||||
AD_DNS_NAME=${ad_dns_name}
|
||||
AD_DNS_IP_AUTO=${ad_dns_ip_auto}
|
||||
BACKUP_DESTINATION=${backup_destination}
|
||||
BACKUP_START_HOUR=${backup_start_hour}
|
||||
BACKUP_RETENTION_DAILY=${backup_retention_daily}
|
||||
@@ -233,6 +300,9 @@ JOIN_PASSWORD=${service_password}
|
||||
DOMAIN_USERS_SID=${domain_users_sid}
|
||||
DOMAIN_ADMINS_SID=${domain_admins_sid}
|
||||
FSLOGIX_GROUP_SID=${fslogix_group_sid}
|
||||
AD_DNS_IP=${ad_dns_ip}
|
||||
AD_DNS_NAME=${ad_dns_name}
|
||||
AD_DNS_IP_AUTO=${ad_dns_ip_auto}
|
||||
BACKUP_DESTINATION=${backup_destination}
|
||||
BACKUP_START_HOUR=${backup_start_hour}
|
||||
BACKUP_RETENTION_DAILY=${backup_retention_daily}
|
||||
|
||||
Reference in New Issue
Block a user