Files
maas-samba-ad/scripts/customize-samba-ad.sh.in
ilkermanap f6b241b3f7
All checks were successful
build-image / build (push) Successful in 5m41s
Force IPv4 in the build VM, where apt was losing 31s per large fetch
Every apt download over roughly 15 MB inside the build VM took exactly 31
seconds regardless of its size, while a 14.1 MB one took 1 second at 23 MB/s.
A fixed cost that ignores size is a timeout, not a bandwidth limit: QEMU's
user-mode network advertises IPv6 that does not work, so apt's parallel
connections black-holed on it and only fell back to IPv4 when the 30-second
timeout expired.

Patch the build VM's cloud-init seed from bootcmd, which runs in the
init-local stage before SSH is up, so the setting covers upstream's apt calls
as well as ours. The same 28.5 MB fetch went from 31s (914 kB/s) to 3s
(9152 kB/s).

Also fix APT_PROXY, which never worked as documented. A cache cannot see
inside a CONNECT tunnel, so the repositories have to be rewritten to plain
http first, and Debian 13 keeps the real mirror URLs in /etc/apt/mirrors/*.list
behind the mirror+file: method, which rewriting sources.list alone misses.

The README now carries the measurements rather than estimates. The headline is
that the cache barely matters: with it fully warm it saves about three seconds
of a 4m40s build, so a local Debian mirror (138 GB for trixie amd64) would buy
nothing here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:59:39 +02:00

261 lines
12 KiB
Bash

#!/bin/bash
# Copyright (C) 2026 Ilker Manap
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# customize-samba-ad.sh - runs inside the packer-maas build VM.
#
# Installs everything a Samba Active Directory domain controller needs on top of
# the Debian cloud image, then removes every trace of domain state so the image
# is generic. The domain itself is created or joined on first boot by
# adc-maas-init, driven by the cloud-init user-data MAAS supplies.
#
# This file is a template; the Makefile fills in the @@...@@ placeholders and
# appends the overlay archive, base64 encoded, after the marker at the end.
#
# Packer runs this with expect_disconnect = true.
set -euo pipefail
DEBIAN_SUITE="@@DEBIAN_SERIES@@"
AD_EXTRA_PACKAGES="@@AD_EXTRA_PACKAGES@@"
PACKER_MAAS_REF="@@PM_REF@@"
export DEBIAN_FRONTEND=noninteractive
APT="apt-get -y -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef"
use_eatmydata() {
command -v eatmydata >/dev/null 2>&1 || return 0
APT="eatmydata ${APT}"
log "eatmydata enabled (dpkg fsync calls disabled)"
}
log() { echo "==> [ad-image] $*"; }
# ---------------------------------------------------------------------------
# 1. Unpack the overlay embedded at the end of this script
# ---------------------------------------------------------------------------
log "unpacking the overlay"
sed -n '/^__ADC_MAAS_OVERLAY__$/,$p' "$0" | tail -n +2 | base64 -d \
| tar xzf - -C / --no-same-owner --no-same-permissions
chown -R root:root /usr/local/sbin/adc-maas-init /usr/local/sbin/adc-sysvol-sync \
/etc/adc-maas /etc/systemd/system/adc-maas-init.service \
/etc/systemd/system/adc-sysvol-sync.service \
/etc/systemd/system/adc-sysvol-sync.timer /curtin
chmod 0755 /usr/local/sbin/adc-maas-init /usr/local/sbin/adc-sysvol-sync
chmod 0755 /curtin /curtin/curtin-hooks
chmod 0644 /etc/adc-maas/adc-maas.conf
mkdir -p /etc/adc-maas/conf.d /var/lib/adc-maas
# ---------------------------------------------------------------------------
# 2. The build hostname has to resolve while packages configure themselves
# ---------------------------------------------------------------------------
BUILD_HOST="$(hostname -s)"
BUILD_IP="$(ip -4 -o route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)"
: "${BUILD_IP:=127.0.1.1}"
log "build hostname=${BUILD_HOST} ip=${BUILD_IP}"
cp -a /etc/hosts /etc/hosts.ad-image-backup
sed -i "/[[:space:]]${BUILD_HOST}\([[:space:]]\|$\)/d" /etc/hosts
echo "${BUILD_IP} ${BUILD_HOST}.local ${BUILD_HOST}" >> /etc/hosts
hostname -f || echo "WARNING: hostname -f does not resolve" >&2
# ---------------------------------------------------------------------------
# 3. Keep the MAAS-compatible cloud-init packer-maas installed
# ---------------------------------------------------------------------------
log "holding the cloud-init package"
apt-mark hold cloud-init || true
# If a caching proxy is configured, rewrite the repositories from https to
# plain http: a cache cannot see inside a CONNECT tunnel, so https requests are
# passed through uncached. Package signatures are still verified, so this costs
# nothing in integrity. Debian 13 keeps the real mirror URLs in
# /etc/apt/mirrors/*.list behind the "mirror+file:" method, so rewriting
# sources.list alone would miss them.
if [ -n "${http_proxy:-}" ]; then
log "APT cache in use (${http_proxy}); switching repositories to http"
sed -i 's|https://|http://|g' \
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources \
/etc/apt/sources.list.d/*.list /etc/apt/mirrors/*.list 2>/dev/null || true
fi
apt-get update
$APT install eatmydata || true
use_eatmydata
log "full-upgrade"
$APT full-upgrade
# ---------------------------------------------------------------------------
# 4. Answer the questions the packages would otherwise ask
# ---------------------------------------------------------------------------
# samba-common would offer to take WINS settings from DHCP, which is wrong for
# a DC. krb5-config wants a realm; the real one is not known until first boot,
# and adc-maas-init replaces /etc/krb5.conf with Samba's own anyway.
debconf-set-selections <<EOF
samba-common samba-common/dhcp boolean false
samba-common samba-common/do_debconf boolean false
krb5-config krb5-config/default_realm string EXAMPLE.LAN
krb5-config krb5-config/add_servers_realm string EXAMPLE.LAN
krb5-config krb5-config/read_conf boolean true
EOF
# ---------------------------------------------------------------------------
# 5. Defer initramfs regeneration until the end
# ---------------------------------------------------------------------------
log "diverting update-initramfs for the duration of the install"
dpkg-divert --local --rename --add /usr/sbin/update-initramfs >/dev/null
ln -sf /bin/true /usr/sbin/update-initramfs
# ---------------------------------------------------------------------------
# 6. The domain controller itself
#
# What an Active Directory domain actually consists of, all of it served by the
# single samba daemon unless noted:
#
# LDAP the directory: users, groups, computers, policy links
# Kerberos the KDC that issues the tickets clients authenticate with
# DNS AD is unusable without it — clients find DCs through SRV records
# SMB the SYSVOL and NETLOGON shares, where Group Policy lives
# NTP chrony; Kerberos rejects a clock skew over five minutes
# ---------------------------------------------------------------------------
log "installing the Samba AD domain controller"
$APT install \
samba samba-ad-dc samba-ad-provision samba-dsdb-modules \
winbind libnss-winbind libpam-winbind \
smbclient ldb-tools \
krb5-user \
chrony \
rsync
if [ -n "${AD_EXTRA_PACKAGES}" ]; then
log "installing extra packages: ${AD_EXTRA_PACKAGES}"
# shellcheck disable=SC2086
$APT install ${AD_EXTRA_PACKAGES}
fi
log "installed versions:"
dpkg-query -W -f='${Package} ${Version}\n' samba samba-ad-dc winbind krb5-user chrony || true
# Confirm this Samba can actually be a DC. Debian builds Samba against its
# bundled Heimdal; a build against system MIT Kerberos would make the AD DC role
# experimental, and samba-ad-dc would then pull in krb5-kdc.
if dpkg-query -W -f='${Depends}' samba-ad-dc 2>/dev/null | grep -q 'krb5-kdc'; then
echo "ERROR: this samba-ad-dc depends on the MIT KDC; the AD DC role is" >&2
echo " experimental in that configuration and is not used here." >&2
exit 1
fi
log "samba-ad-dc uses the bundled Heimdal KDC (the supported configuration)"
# ---------------------------------------------------------------------------
# 6b. Swap the cloud kernel for the generic one
#
# The Debian cloud image ships linux-image-cloud-amd64, which is built for
# virtual machines and leaves out most physical-hardware drivers. This image is
# deployed to bare metal by MAAS, so it needs the generic kernel or the node may
# come up with no disk or no network. curtin does not install a kernel here
# (see /curtin/curtin-hooks), so whatever is in the image is what boots.
# ---------------------------------------------------------------------------
DEB_ARCH="$(dpkg --print-architecture)"
log "installing the generic kernel (linux-image-${DEB_ARCH})"
$APT install "linux-image-${DEB_ARCH}"
CLOUD_KERNELS="$(dpkg-query -W -f='${Package}\n' 'linux-image-*cloud*' 2>/dev/null | grep -E '^linux-image' || true)"
if [ -n "${CLOUD_KERNELS}" ]; then
log "removing the cloud-only kernel: ${CLOUD_KERNELS}"
# shellcheck disable=SC2086
$APT purge ${CLOUD_KERNELS} || true
fi
$APT autoremove --purge || true
log "restoring update-initramfs and running it once"
rm -f /usr/sbin/update-initramfs
dpkg-divert --local --rename --remove /usr/sbin/update-initramfs >/dev/null
update-initramfs -u -k all
update-grub
# ---------------------------------------------------------------------------
# 7. Remove every trace of a domain
#
# Installing the packages leaves a default smb.conf and can leave TDB state. If
# any of it shipped in the image, every machine deployed from it would start
# from the same half-configured directory, and `samba-tool domain provision`
# would refuse to run at all.
# ---------------------------------------------------------------------------
log "clearing domain state so the image is generic"
systemctl stop samba-ad-dc smbd nmbd winbind 2>/dev/null || true
rm -f /etc/samba/smb.conf
rm -rf /var/lib/samba/private/* /var/lib/samba/sysvol/*
find /var/lib/samba -maxdepth 1 -name '*.tdb' -delete 2>/dev/null || true
find /var/cache/samba -type f -delete 2>/dev/null || true
rm -f /etc/krb5.keytab
# The standalone file-server daemons conflict with the AD DC daemon. Mask them
# now; adc-maas-init unmasks and starts samba-ad-dc once a domain exists.
systemctl disable smbd nmbd winbind 2>/dev/null || true
systemctl mask smbd nmbd winbind 2>/dev/null || true
systemctl disable samba-ad-dc 2>/dev/null || true
# Node-unique, must not be shared between machines built from this image.
rm -f /etc/iscsi/initiatorname.iscsi
rm -f /root/.ssh/known_hosts /etc/ssh/ssh_known_hosts
# ---------------------------------------------------------------------------
# 8. Leave the network to MAAS on the first boot
#
# ifupdown/ifupdown2 writes the build VM's interface name into
# /etc/network/interfaces. On a deployed node MAAS configures the network
# through netplan and systemd-networkd; if ifupdown also starts, it takes the
# real interface down using that stale definition and the node loses its network
# before any of our automation runs.
# ---------------------------------------------------------------------------
log "disabling networking.service (MAAS owns the network on first boot)"
cat > /etc/network/interfaces <<'EOF'
# Left deliberately minimal. The network on a deployed node is configured by
# MAAS through netplan and systemd-networkd.
auto lo
iface lo inet loopback
source /etc/network/interfaces.d/*
EOF
rm -f /etc/network/interfaces.d/* /etc/network/interfaces.new 2>/dev/null || true
systemctl disable networking.service 2>/dev/null || true
# ---------------------------------------------------------------------------
# 9. First-boot service, persistent logs, image metadata
# ---------------------------------------------------------------------------
log "enabling adc-maas-init.service"
systemctl daemon-reload
systemctl enable adc-maas-init.service
# The first boot can reboot once, and a volatile journal loses everything that
# happened before it — including why a stage failed.
log "enabling a persistent journal"
mkdir -p /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal 2>/dev/null || true
log "writing /etc/adc-maas/image-info"
{
echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "debian_suite=${DEBIAN_SUITE}"
echo "packer_maas_ref=${PACKER_MAAS_REF}"
dpkg-query -W -f='${Package}=${Version}\n' samba samba-ad-dc winbind krb5-user 2>/dev/null
echo "kernel=$(ls -1 /boot/vmlinuz-* 2>/dev/null | sed 's|.*/vmlinuz-||' | head -1)"
} > /etc/adc-maas/image-info
cat /etc/adc-maas/image-info
# ---------------------------------------------------------------------------
# 10. Clean up
# ---------------------------------------------------------------------------
log "cleaning up"
mv /etc/hosts.ad-image-backup /etc/hosts
$APT clean
rm -rf /var/lib/apt/lists/*
find /var/log/journal -mindepth 1 -delete 2>/dev/null || true
rm -rf /var/log/*.gz /var/log/*.1
: > /var/log/wtmp || true
: > /var/log/btmp || true
cloud-init clean --logs || true
log "image ready: Samba AD DC on Debian ${DEBIAN_SUITE}"
exit 0
# Everything after this marker is the base64 overlay archive the Makefile appends.