Files
maas-samba-ad/overlay/usr/local/sbin/adc-sysvol-sync
ilkermanap bc5d30d048 Fix a SYSVOL sync service that terminated itself, and record the test results
Deploying the image found a real defect. adc-sysvol-sync.service declared
Requires=samba-ad-dc.service, and the sync restarts samba-ad-dc after copying
idmap.ldb. systemd stops units that *require* a service being restarted, so the
sync killed itself with SIGTERM part way through:

  adc-sysvol-sync[2065]: syncing idmap.ldb from 10.10.10.22 (one time)
  adc-sysvol-sync.service: Main process exited, code=killed, status=15/TERM
  adc-sysvol-sync.service: Failed with result 'signal'

The timer started it again and the second run succeeded, so the end state looked
correct while the mechanism was broken — the kind of fault that stays hidden
until it matters. The dependency is Wants= now, which gives the ordering without
the stop propagation, and the restart is --no-block.

The image was tested end to end: two DCs deployed from MAAS, the first
provisioning the domain in 21 seconds and the second joining in 23, both
unattended and on the first attempt. DRS replication is healthy, the FSMO roles
sit where they should, and a file written into SYSVOL on one DC reached the other
within a timer interval, ACLs reset.

A Windows Server 2025 machine then joined the domain: it found the DC through the
_ldap._tcp.dc._msdcs SRV record, Add-Computer succeeded, and after rebooting the
secure channel tested good. nltest reports the DC with the full set of
capabilities Windows expects — PDC GC DS LDAP KDC TIMESERV GTIMESERV WRITABLE
DNS_DC DNS_DOMAIN DNS_FOREST FULL_SECRET. Group Policy refresh completed, and
LDAP queries from the client listed both DCs, the Windows machine account and the
domain users.

One genuine limitation surfaced and now has its own section: Samba does not
implement ADWS, so port 9389 is closed and the PowerShell ActiveDirectory module
cannot be used against these DCs. ADUC, ADSI and raw LDAP are unaffected, which
is what the LDAP queries above demonstrate.

Verified status now separates what was observed from what was not. Notably still
untested: applying an actual GPO, an interactive domain logon, seizing FSMO roles
after losing a DC, BIND9_DLZ, and real bare metal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:17:11 +02:00

75 lines
3.3 KiB
Bash
Executable File

#!/bin/bash
# Copyright (C) 2026 Ilker Manap
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# adc-sysvol-sync - pulls SYSVOL from another domain controller.
#
# Samba implements neither DFS-R nor FRS, so the SYSVOL share — which holds
# Group Policy objects and logon scripts — does not replicate between domain
# controllers by itself. Without something like this, a policy created on one DC
# is invisible to clients that authenticate against another, and the domain
# silently behaves differently depending on which DC a client happened to reach.
#
# This is the rsync approach from the Samba wiki: pull from the DC holding the
# PDC Emulator FSMO role, then reset the POSIX ACLs from what is stored in AD.
#
# Configuration: /etc/adc-maas/sysvol-sync.conf (SYSVOL_SOURCE=<dc fqdn or ip>)
# Authentication: root SSH key to the source DC. Provide one at deploy time;
# without it this exits with a clear message rather than failing silently.
set -uo pipefail
CONF=/etc/adc-maas/sysvol-sync.conf
SYSVOL=/var/lib/samba/sysvol
LOG_TAG=adc-sysvol-sync
log() { echo "[$LOG_TAG] $*"; logger -t "$LOG_TAG" -- "$*" 2>/dev/null || true; }
warn() { echo "[$LOG_TAG] WARN: $*" >&2; logger -t "$LOG_TAG" -p user.warning -- "$*" 2>/dev/null || true; }
[ -r "$CONF" ] || { log "no $CONF, nothing to do"; exit 0; }
# shellcheck disable=SC1090
. "$CONF"
: "${SYSVOL_SOURCE:?SYSVOL_SOURCE not set in $CONF}"
[ -d "$SYSVOL" ] || { warn "$SYSVOL missing — is this host a DC?"; exit 1; }
SSH_OPTS="-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10"
if ! ssh $SSH_OPTS "root@${SYSVOL_SOURCE}" true 2>/dev/null; then
warn "cannot reach root@${SYSVOL_SOURCE} over SSH — SYSVOL is NOT replicating"
warn "install a root SSH key on this host that is authorised on ${SYSVOL_SOURCE},"
warn "or replicate SYSVOL by some other means. Group Policy will diverge until then."
exit 1
fi
# idmap.ldb decides which POSIX uid/gid each SID maps to. If it differs between
# DCs, the same file shows different ownership depending on which DC serves it.
# The Samba wiki calls syncing it a prerequisite, not an optimisation.
if [ ! -e /var/lib/adc-maas/idmap-synced ]; then
log "syncing idmap.ldb from ${SYSVOL_SOURCE} (one time)"
if rsync -a -e "ssh $SSH_OPTS" \
"root@${SYSVOL_SOURCE}:/var/lib/samba/private/idmap.ldb" \
/var/lib/samba/private/idmap.ldb; then
mkdir -p /var/lib/adc-maas && date -Is > /var/lib/adc-maas/idmap-synced
# --no-block so this script is not waiting on a restart of a service
# it is ordered after; see the note in adc-sysvol-sync.service.
systemctl restart --no-block samba-ad-dc >/dev/null 2>&1 || true
else
warn "idmap.ldb sync failed; uid/gid mappings may differ between DCs"
fi
fi
log "pulling SYSVOL from ${SYSVOL_SOURCE}"
if rsync -aAX --delete -e "ssh $SSH_OPTS" \
"root@${SYSVOL_SOURCE}:${SYSVOL}/" "${SYSVOL}/"; then
# rsync carries POSIX bits; the Windows ACLs come from AD and have to be
# reapplied afterwards or clients get access-denied on Group Policy.
if samba-tool ntacl sysvolreset >/dev/null 2>&1; then
log "SYSVOL synced and ACLs reset"
else
warn "sysvolreset failed — clients may be denied access to Group Policy"
exit 1
fi
else
warn "rsync failed"
exit 1
fi