Files
maas-samba-ad/Makefile
ilkermanap cf07702535 Initial commit: MAAS-deployable Samba Active Directory domain controller
Builds a Debian image that MAAS deploys to bare metal as an Active Directory
domain controller, with first-boot automation that either creates a domain or
joins an existing one without anyone logging in. To a Windows client the result
is an AD domain: same Kerberos, same LDAP, same Group Policy, same domain join.

The request was for "primary and backup domain controllers", which is NT 4
terminology. Active Directory has no such split — every DC holds a writable copy
and any of them can service a logon. What lives on one DC at a time are the five
FSMO roles, one confusingly named "PDC Emulator". The image therefore offers
provision (create the domain) and join (add another equal DC), which is the
distinction that actually exists.

Contents:

  * customize-samba-ad.sh, run inside the packer-maas build VM: installs samba,
    winbind, Kerberos, chrony and rsync, swaps the cloud kernel for the generic
    one, and deletes every trace of a domain so one image can produce many DCs
  * adc-maas-init, a first-boot state machine covering /etc/hosts, node-unique
    identifiers, time, the resolver, provisioning or joining, the service
    switchover and a self-test that proves Kerberos issues a ticket
  * adc-sysvol-sync, a systemd timer implementing the SYSVOL workaround
  * a MAAS curtin preseed, cloud-init examples, and the release pipeline

Two findings shaped the design.

Debian builds Samba against its bundled Heimdal rather than system MIT Kerberos
— samba-ad-dc does not depend on krb5-kdc — so the AD DC role is in Samba's
supported configuration, not the experimental MIT one. The build asserts this
and fails if it ever changes.

Samba implements neither DFS-R nor FRS, so SYSVOL — where Group Policy lives —
does not replicate between DCs. Left alone, a policy created on one DC never
reaches the others and clients behave differently depending on which DC answered
them, with nothing reporting an error. adc-sysvol-sync applies the Samba wiki's
rsync workaround: sync idmap.ldb once so SID-to-uid mappings agree, rsync the
tree, then samba-tool ntacl sysvolreset because rsync carries POSIX bits while
the Windows ACLs live in AD. Without an SSH key to the source DC it exits with
an explanation rather than letting Group Policy diverge quietly.

Also carried over from maas-proxmox, where they were verified on real hardware:
the curtin-hooks that skip the kernel install and pin interface names by MAC,
Type=simple on the first-boot unit to avoid the systemd ordering cycle, and
shipping networking.service disabled so MAAS owns the network on first boot.
Specific to this image, the Debian cloud kernel is replaced with the generic one
— a bare-metal node booted with the cloud kernel can come up with no disk.

Nothing here has been built or deployed. The README says so at the top and in a
Verified status section that separates what was checked from what was not.

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

271 lines
12 KiB
Makefile

# Copyright (C) 2026 Ilker Manap
# SPDX-License-Identifier: AGPL-3.0-or-later
# maas-samba-ad - Samba Active Directory domain controller image for MAAS
#
# Hizli baslangic (build host uzerinde, root olarak):
# sudo ./scripts/install-deps.sh
# sudo make image
# make preseed
# make upload MAAS_PROFILE=admin
#
SHELL := /bin/bash
.DEFAULT_GOAL := help
# ---------------------------------------------------------------- version settings
# Samba's AD DC comes straight from Debian - no third-party repository is
# involved, so there is nothing to pin beyond the Debian release itself.
DEBIAN_SERIES ?= trixie
DEBIAN_VERSION ?= 13
# Packages beyond the domain controller itself.
AD_EXTRA_PACKAGES ?= acl attr net-tools ldap-utils python3-gpg bind9-dnsutils \
ethtool ipmitool nvme-cli lsscsi sudo
# ---------------------------------------------------------------- imaj ayarlari
# amd64 | arm64. arm64 is wired up but has never been built or deployed -
# see "Verified status" in the README. Building arm64 on an x86_64 host means
# TCG emulation with no KVM, which is very slow; prefer a native arm64 builder.
ARCH ?= amd64
SUBARCH ?= generic
BOOT ?= uefi
TIMEOUT ?= 3h
PACKER_LOG ?= 0
# Build VM kaynaklari. Upstream sablon 4G/2CPU/2GB ile gelir; Debian cloud image
# (~3G) plus Samba and its dependencies does not fit in 4G, so we patch it.
# Sonuc tgz yalnizca kullanilan dosyalari icerdiginden buyuk disk imaji sismez.
DISK_SIZE ?= 16G
BUILD_CPUS ?= 4
BUILD_MEM ?= 4096
# --- derleme hizi -------------------------------------------------------------
# stable | daily
# stable: cloud.debian.org/.../trixie/latest (sabit URL -> packer onbellegi
# calisir, tekrarlanan derlemelerde ~350MB indirme yok, tekrarlanabilir)
# daily : upstream sablonun varsayilani (her gun degisir, onbellek isabet etmez)
DEBIAN_IMAGE_CHANNEL ?= stable
# Tarball sikistirma seviyesi. Upstream --best (9) kullaniyor; 6 belirgin daha
# hizli ve imaj yalnizca ~%2-3 buyuyor. pigz varsa cok cekirdekli calisir.
GZIP_LEVEL ?= 6
# Yerel APT onbellegi (or. apt-cacher-ng): http://10.0.2.2:3142
# Packer'in user-mode aginda build VM host'u 10.0.2.2 olarak gorur.
# Bos birakilirsa proxy kullanilmaz. Bkz: make deps-cache
APT_PROXY ?=
IMAGE_NAME ?= samba-ad-dc
IMAGE_TITLE ?= Samba AD Domain Controller (Debian $(DEBIAN_VERSION))
# ---------------------------------------------------------------- yollar
WORKDIR ?= $(CURDIR)/build
PM_REPO ?= https://github.com/canonical/packer-maas.git
# Test edilmis upstream commit'e sabitlenmistir. 'main' birakmak, upstream'de
# yapilan bir degisikligin derlemeyi haber vermeden bozmasi anlamina gelir.
# Ileri tasimak icin: PM_REF=main ile derleyip test edin, sonra yeni SHA'yi buraya yazin.
PM_REF ?= c23d5dd985f52b2eaccc893e886213a424a243a5
PM := $(WORKDIR)/packer-maas
TPL := $(PM)/debian
OVERLAY_TGZ := $(WORKDIR)/adc-maas-overlay.tar.gz
CUSTOMIZE := $(WORKDIR)/customize-samba-ad.sh
OUTPUT ?= $(WORKDIR)/$(IMAGE_NAME).tar.gz
PRESEED := $(WORKDIR)/curtin_userdata_custom_$(ARCH)_$(SUBARCH)_$(IMAGE_NAME)
# ---------------------------------------------------------------- MAAS ayarlari
MAAS_PROFILE ?= admin
MAAS_ARCH ?= $(ARCH)/$(SUBARCH)
MAAS_IMAGE_NAME ?= custom/$(IMAGE_NAME)
# MAAS snap kurulumu icin: /var/snap/maas/current/preseeds
MAAS_PRESEED_DIR ?= /var/snap/maas/current/preseeds
# UEFI firmware, keyed on the TARGET architecture - an aarch64 guest needs
# AAVMF whatever the build host is. (Upstream packer-maas keys this on the host
# architecture instead, which only works when host and target match.)
ifeq ($(strip $(ARCH)),arm64)
FW_DIR ?= /usr/share/AAVMF
FW ?= AAVMF
else
FW_DIR ?= /usr/share/OVMF
FW ?= OVMF
endif
FW_SFX ?= $(shell test -f $(FW_DIR)/$(FW)_CODE.fd && echo "" || echo "_4M")
# KVM is only usable when the host and the guest share an architecture.
# Otherwise QEMU falls back to TCG emulation, which is very slow.
HOST_IS_ARM := $(shell test "$$(uname -m)" = aarch64 && echo true || echo false)
# ---------------------------------------------------------------- hedefler
.PHONY: help deps deps-cache check-upstream print-var checkout overlay customize image verify preseed install-preseed upload clean distclean lint
help:
@echo "maas-samba-ad - Samba AD Domain Controller MAAS image"
@echo
@echo " make deps Build host bagimliliklarini kur (root gerekir)"
@echo " make deps-cache Yerel APT onbellegi kur (derlemeyi hizlandirir)"
@echo " make image Imaji derle -> $(OUTPUT) (root gerekir)"
@echo " make verify Uretilen imajin icerigini dogrula"
@echo " make check-upstream Depodaki surumleri elimizdeki imajla karsilastir"
@echo " make preseed MAAS curtin preseed dosyasini uret"
@echo " make install-preseed Preseed'i $(MAAS_PRESEED_DIR) altina kopyala (root)"
@echo " make upload Imaji MAAS'a yukle (MAAS_PROFILE=$(MAAS_PROFILE))"
@echo " make clean Ara dosyalari sil"
@echo " make distclean build/ dizinini tamamen sil"
@echo
@echo "Onemli degiskenler:"
@echo " DEBIAN_SERIES=$(DEBIAN_SERIES) IMAGE_NAME=$(IMAGE_NAME)"
@echo " ARCH=$(ARCH) BOOT=$(BOOT)"
@echo " OUTPUT=$(OUTPUT)"
deps:
./scripts/install-deps.sh
# --- packer-maas deposunu getir -----------------------------------------------
$(PM)/.git:
@mkdir -p $(WORKDIR)
git clone $(PM_REPO) $(PM)
# PM_REF bir dal, etiket ya da commit SHA olabilir.
checkout: $(PM)/.git
@cd $(PM) && git fetch -q --all --tags && git checkout -q -f $(PM_REF) \
&& (git symbolic-ref -q HEAD >/dev/null && git pull -q --ff-only || true)
@echo "packer-maas: $$(cd $(PM) && git rev-parse --short HEAD) ($(PM_REF))"
# --- imaja gomulecek overlay ---------------------------------------------------
overlay: $(OVERLAY_TGZ)
$(OVERLAY_TGZ): $(shell find overlay -type f 2>/dev/null)
@mkdir -p $(WORKDIR)
COPYFILE_DISABLE=1 tar czf $@ -C overlay --no-xattrs --exclude='.keep' .
@echo "overlay: $@ ($$(du -h $@ | cut -f1))"
# --- packer'in VM icinde calistiracagi customize script -------------------------
customize: $(CUSTOMIZE)
$(CUSTOMIZE): scripts/customize-samba-ad.sh.in $(OVERLAY_TGZ)
@mkdir -p $(WORKDIR)
@sed -e 's|@@DEBIAN_SERIES@@|$(DEBIAN_SERIES)|g' \
-e 's|@@AD_EXTRA_PACKAGES@@|$(AD_EXTRA_PACKAGES)|g' \
-e 's|@@PM_REF@@|$(PM_REF)|g' \
$< > $@
@echo '__ADC_MAAS_OVERLAY__' >> $@
@base64 < $(OVERLAY_TGZ) >> $@
@chmod +x $@
@echo "customize script: $@"
# --- imaj derleme ---------------------------------------------------------------
image: checkout $(CUSTOMIZE)
@if [ "$$(id -u)" -ne 0 ]; then echo "HATA: 'make image' root gerektirir (sudo make image)"; exit 1; fi
@command -v packer >/dev/null || { echo "HATA: packer yok, once 'make deps'"; exit 1; }
@echo "==> Sablon yamalaniyor: disk=$(DISK_SIZE) cpus=$(BUILD_CPUS) mem=$(BUILD_MEM)"
sed -i -E 's|^([[:space:]]*disk_size[[:space:]]*=[[:space:]]*).*|\1"$(DISK_SIZE)"|' $(TPL)/debian-cloudimg.pkr.hcl
sed -i -E 's|^([[:space:]]*cpus[[:space:]]*=[[:space:]]*).*|\1$(BUILD_CPUS)|' $(TPL)/debian-cloudimg.pkr.hcl
sed -i -E 's|^([[:space:]]*memory[[:space:]]*=[[:space:]]*).*|\1$(BUILD_MEM)|' $(TPL)/debian-cloudimg.pkr.hcl
ifeq ($(strip $(DEBIAN_IMAGE_CHANNEL)),stable)
@echo "==> Kararli Debian cloud image kullanilacak (packer onbellegi isabet eder)"
sed -i -E 's|/daily/latest/|/latest/|g; s|-daily\.qcow2|.qcow2|g' $(TPL)/debian-cloudimg.pkr.hcl
@grep -nE 'iso_url|iso_checksum' $(TPL)/debian-cloudimg.pkr.hcl
endif
sed -i -E 's|--best --force|-$(GZIP_LEVEL) --force|' $(PM)/scripts/fuse-tar-root
@grep -nE 'disk_size|^ cpus|^ memory' $(TPL)/debian-cloudimg.pkr.hcl
cp -v $(FW_DIR)/$(FW)_CODE$(FW_SFX).fd $(TPL)/OVMF_CODE.fd
cp -v $(FW_DIR)/$(FW)_VARS$(FW_SFX).fd $(TPL)/OVMF_VARS.fd
ifeq ($(strip $(ARCH)),arm64)
# AAVMF images must be padded to 64 MiB for QEMU's arm64 "virt" machine.
truncate -s 64m $(TPL)/OVMF_CODE.fd
truncate -s 64m $(TPL)/OVMF_VARS.fd
endif
rm -rf $(TPL)/output-cloudimg $(TPL)/seeds-cloudimg.iso
cd $(TPL) && PACKER_LOG=$(PACKER_LOG) packer init .
cd $(TPL) && PACKER_LOG=$(PACKER_LOG) packer build \
-var debian_series=$(DEBIAN_SERIES) \
-var debian_version=$(DEBIAN_VERSION) \
-var architecture=$(ARCH) \
-var boot_mode=$(BOOT) \
-var ovmf_suffix=$(FW_SFX) \
-var host_is_arm=$(HOST_IS_ARM) \
-var timeout=$(TIMEOUT) \
-var customize_script=$(CUSTOMIZE) \
-var filename=$(OUTPUT) \
-var http_proxy=$(APT_PROXY) \
.
@ls -lh $(OUTPUT)
# --- MAAS preseed ---------------------------------------------------------------
preseed: $(PRESEED)
$(PRESEED): maas/curtin_userdata_custom.in
@mkdir -p $(WORKDIR)
@sed -e 's|@@IMAGE_NAME@@|$(IMAGE_NAME)|g' \
-e 's|@@ARCH@@|$(ARCH)|g' \
$< > $@
@echo "preseed: $@"
@echo " -> MAAS region controller uzerinde $(MAAS_PRESEED_DIR)/ altina kopyalayin"
install-preseed: $(PRESEED)
@if [ "$$(id -u)" -ne 0 ]; then echo "HATA: root gerekir"; exit 1; fi
install -D -m 0644 $(PRESEED) $(MAAS_PRESEED_DIR)/$(notdir $(PRESEED))
@echo "kuruldu: $(MAAS_PRESEED_DIR)/$(notdir $(PRESEED))"
# --- MAAS'a yukleme -------------------------------------------------------------
upload:
@test -f $(OUTPUT) || { echo "HATA: $(OUTPUT) yok, once 'make image'"; exit 1; }
maas $(MAAS_PROFILE) boot-resources create \
name='$(MAAS_IMAGE_NAME)' \
title='$(IMAGE_TITLE)' \
architecture='$(MAAS_ARCH)' \
filetype='tgz' \
content@=$(OUTPUT)
verify:
@test -f $(OUTPUT) || { echo "HATA: $(OUTPUT) yok, once 'make image'"; exit 1; }
./scripts/verify-image.sh $(OUTPUT)
# Depoda hangi surumler var, elimizdeki imaj hangi surumde?
check-upstream:
@echo "==> Available in Debian $(DEBIAN_SERIES):"
@curl -fsS http://deb.debian.org/debian/dists/$(DEBIAN_SERIES)/main/binary-$(ARCH)/Packages.gz 2>/dev/null \
| gunzip \
| awk '/^Package: (samba|samba-ad-dc|winbind)$$/{p=$$2; next} \
/^Version: /{if(p!=""){print p" "$$2; p=""}}' \
| sort -V | awk '{v[$$1]=$$2} END{for(k in v) printf " %-18s %s\n", k, v[k]}' \
|| echo " (could not reach the archive)"
@echo "==> In the local image ($(OUTPUT)):"
@if [ -f $(OUTPUT) ]; then \
tar xzf $(OUTPUT) -O ./etc/adc-maas/image-info 2>/dev/null | sed 's/^/ /' \
|| echo " (image-info unreadable)"; \
else echo " (no image - run 'make image' first)"; fi
# Yerel APT onbellegi - tekrarlanan derlemelerde ~700MB indirmeyi ortadan kaldirir.
# Kurduktan sonra: sudo make image APT_PROXY=http://10.0.2.2:3142
deps-cache:
@if [ "$$(id -u)" -ne 0 ]; then echo "HATA: root gerekir (sudo make deps-cache)"; exit 1; fi
DEBIAN_FRONTEND=noninteractive apt-get install -y apt-cacher-ng
systemctl enable --now apt-cacher-ng
@echo
@echo "Hazir. Derlemede kullanmak icin:"
@echo " sudo make image APT_PROXY=http://10.0.2.2:3142"
@echo "(10.0.2.2 = packer user-mode aginda build host'un adresi)"
# Tek bir degiskenin degerini bas - CI script'leri bunu kullanir.
# make -s print-var VAR=OUTPUT
print-var:
@echo "$($(VAR))"
lint:
@bash -n scripts/customize-samba-ad.sh.in && echo "customize-samba-ad.sh.in: OK"
@bash -n overlay/usr/local/sbin/adc-maas-init && echo "adc-maas-init: OK"
@bash -n overlay/usr/local/sbin/adc-sysvol-sync && echo "adc-sysvol-sync: OK"
@command -v shellcheck >/dev/null && shellcheck -S warning \
overlay/usr/local/sbin/adc-maas-init overlay/usr/local/sbin/adc-sysvol-sync \
scripts/install-deps.sh || true
clean:
rm -f $(OVERLAY_TGZ) $(CUSTOMIZE) $(PRESEED)
rm -rf $(TPL)/output-cloudimg $(TPL)/seeds-cloudimg.iso \
$(TPL)/OVMF_CODE.fd $(TPL)/OVMF_VARS.fd
distclean:
rm -rf $(WORKDIR)