Live auf echter Hardware gefunden (01.09.2026): "Error: Partition(s) 1 on /dev/sda have been written, but we have been unable to inform the Kernel of the change, probably because it/they are in use." - parted schreibt die neue GPT-Tabelle zwar trotzdem, meldet aber einen Fehler, wenn eine alte Partition (Swap, gemountetes Dateisystem vom vorherigen Betriebssystem auf dieser Platte) noch belegt ist. In QEMU mit stets frischen Testplatten nie aufgetreten - reale Kundenhardware hat aber fast immer schon ein Vorleben auf der Platte. Zwei neue Bausteine in image_deploy_partition(): - _image_deploy_release_disk(): unmountet/deaktiviert Swap auf allen vorhandenen Partitionen der Zielplatte, bevor ueberhaupt mklabel aufgerufen wird. - _image_deploy_settle_partitions(): wiederholt partprobe/udevadm settle bis zu 5x statt nach einem einzigen Versuch aufzugeben, sowohl nach mklabel als auch nach dem Anlegen der eigentlichen Partitionen. Zusaetzlich wird nach mklabel explizit verifiziert, dass wirklich ein gueltiges GPT-Label vorliegt (parted print), statt parteds Exit-Code fuer den Kernel-Info-Schritt blind als Gesamtfehlschlag zu werten. Lokal verifiziert: einmal komplett durchinstalliert (frische Platte), dann OHNE die Platte zurueckzusetzen ein zweites Mal auf dieselbe, bereits mit einem echten System belegte Platte installiert - lief sauber durch, keine Kernel-Fehlermeldung mehr.
396 lines
17 KiB
Bash
396 lines
17 KiB
Bash
#!/usr/bin/env bash
|
|
# Kernmechanik des Golden-Image-Deployments (siehe ADR-0024 und
|
|
# platform-docs-Plan "Golden-Image-Deployment statt Ubiquity-
|
|
# Automatisierung"). Wird von source eingebunden - nicht eigenstaendig
|
|
# ausfuehrbar. Ersetzt den bisherigen Ansatz "nativen Distributions-
|
|
# Installer automatisieren" (Ubiquity/Anaconda) durch das curtin/FAI-
|
|
# Muster: Zieldatentraeger direkt partitionieren, ein fertiges
|
|
# Root-Filesystem-Image entpacken, per chroot nacharbeiten
|
|
# (fstab/initramfs/machine-id/SSH-Hostkeys/Bootloader).
|
|
#
|
|
# Bewusst als eigenstaendige Funktionsbibliothek (wie lib/initrd.sh,
|
|
# lib/checks.sh) statt einer einzigen "mach alles"-Funktion - sowohl der
|
|
# isolierte Phase-1-Testtreiber als auch das spaetere
|
|
# backends/mint-image/backend.sh rufen dieselben Bausteine in derselben
|
|
# Reihenfolge auf.
|
|
|
|
image_deploy_log() {
|
|
printf '[image_deploy] %s\n' "$*" >&2
|
|
}
|
|
|
|
image_deploy_fatal() {
|
|
printf '[image_deploy] FEHLER: %s\n' "$*" >&2
|
|
return 1
|
|
}
|
|
|
|
# Nvme/mmcblk-Geraete brauchen ein "p" vor der Partitionsnummer
|
|
# (/dev/nvme0n1p1), sd/vd-Geraete nicht (/dev/sda1) - Heuristik: Geraete-
|
|
# name endet auf eine Ziffer -> braucht "p".
|
|
_image_deploy_part_suffix() {
|
|
local disk="$1"
|
|
if [[ "${disk}" =~ [0-9]$ ]]; then
|
|
echo "p"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
image_deploy_partition_path() {
|
|
local disk="$1"
|
|
local num="$2"
|
|
echo "${disk}$(_image_deploy_part_suffix "${disk}")${num}"
|
|
}
|
|
|
|
# Partitioniert den Zieldatentraeger komplett neu (GPT, wie schon in
|
|
# _tuxflotte_render_partman_recipe() begruendet - auch ohne EFI legt GPT
|
|
# an, siehe dortiger Kommentar zu bios_grub). Gibt "BOOT_PART ROOT_PART"
|
|
# auf stdout aus - BOOT_PART ist bei BIOS leer (die biosgrub-Partition
|
|
# braucht kein Dateisystem/keinen Mountpunkt).
|
|
# Vorhandene Partitionen der Zielplatte vor dem Neupartitionieren freigeben
|
|
# (unmounten, Swap deaktivieren) - live auf echter Hardware gefunden
|
|
# (01.09.2026): parted schreibt eine neue Partitionstabelle zwar trotzdem
|
|
# auf die Platte, meldet aber einen Fehler ("...have been unable to inform
|
|
# the Kernel of the change... probably because it/they are in use"), wenn
|
|
# eine alte Partition (z.B. Swap oder ein gemountetes Dateisystem vom
|
|
# vorherigen Betriebssystem auf dieser Platte) noch belegt ist. In QEMU mit
|
|
# stets frischen Testplatten nie aufgetreten - reale Kundenhardware hat
|
|
# aber fast immer schon ein Vorleben auf der Platte.
|
|
_image_deploy_release_disk() {
|
|
local disk="$1"
|
|
local part
|
|
|
|
for part in "${disk}"?*; do
|
|
[[ -b "${part}" ]] || continue
|
|
swapoff "${part}" 2>/dev/null || true
|
|
umount --recursive "${part}" 2>/dev/null || true
|
|
done
|
|
}
|
|
|
|
# Manche Kernel-/Platten-Kombinationen brauchen mehrere Anlaeufe, bis das
|
|
# Neueinlesen der Partitionstabelle (BLKRRPART) greift, gerade direkt nach
|
|
# einem mklabel auf einer zuvor belegten Platte - deshalb mehrfach
|
|
# probieren statt nach einem einzigen partprobe-Aufruf aufzugeben.
|
|
_image_deploy_settle_partitions() {
|
|
local disk="$1"
|
|
local attempt
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if partprobe "${disk}" 2>/dev/null; then
|
|
udevadm settle
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
udevadm settle
|
|
return 1
|
|
}
|
|
|
|
image_deploy_partition() {
|
|
local disk="$1"
|
|
local is_efi="$2"
|
|
|
|
_image_deploy_release_disk "${disk}"
|
|
|
|
if ! parted --script "${disk}" mklabel gpt; then
|
|
image_deploy_log "parted meldete einen Fehler beim Anlegen der Partitionstabelle auf ${disk} (haeufig: eine alte Partition war noch belegt) - pruefe, ob der Kernel die neue Tabelle trotzdem uebernimmt."
|
|
fi
|
|
|
|
_image_deploy_settle_partitions "${disk}" ||
|
|
{ image_deploy_fatal "Kernel konnte die neue Partitionstabelle auf ${disk} auch nach mehreren Versuchen nicht uebernehmen."; return 1; }
|
|
|
|
parted --script "${disk}" print 2>/dev/null | grep -q "Partition Table: gpt" ||
|
|
{ image_deploy_fatal "Nach dem Partitionieren liegt kein gueltiges GPT-Label auf ${disk} vor."; return 1; }
|
|
|
|
if [[ "${is_efi}" == "true" ]]; then
|
|
parted --script "${disk}" mkpart ESP fat32 1MiB 513MiB &&
|
|
parted --script "${disk}" set 1 esp on &&
|
|
parted --script "${disk}" mkpart root ext4 513MiB 100% ||
|
|
{ image_deploy_fatal "Partitionierung (UEFI) fehlgeschlagen auf ${disk}"; return 1; }
|
|
else
|
|
parted --script "${disk}" mkpart biosgrub 1MiB 2MiB &&
|
|
parted --script "${disk}" set 1 bios_grub on &&
|
|
parted --script "${disk}" mkpart root ext4 2MiB 100% ||
|
|
{ image_deploy_fatal "Partitionierung (BIOS) fehlgeschlagen auf ${disk}"; return 1; }
|
|
fi
|
|
|
|
_image_deploy_settle_partitions "${disk}" ||
|
|
{ image_deploy_fatal "Kernel konnte die neuen Partitionen auf ${disk} nicht uebernehmen."; return 1; }
|
|
|
|
local part1 part2
|
|
part1="$(image_deploy_partition_path "${disk}" 1)"
|
|
part2="$(image_deploy_partition_path "${disk}" 2)"
|
|
|
|
[[ -b "${part1}" && -b "${part2}" ]] ||
|
|
{ image_deploy_fatal "Partitionen nach partprobe nicht gefunden (${part1}, ${part2})"; return 1; }
|
|
|
|
if [[ "${is_efi}" == "true" ]]; then
|
|
echo "${part1} ${part2}"
|
|
else
|
|
echo " ${part2}"
|
|
fi
|
|
}
|
|
|
|
image_deploy_format() {
|
|
local boot_part="$1"
|
|
local root_part="$2"
|
|
local root_fs="$3"
|
|
|
|
if [[ -n "${boot_part}" ]]; then
|
|
mkfs.vfat -F32 -n ESP "${boot_part}" ||
|
|
{ image_deploy_fatal "mkfs.vfat auf ${boot_part} fehlgeschlagen"; return 1; }
|
|
fi
|
|
|
|
case "${root_fs}" in
|
|
ext4) mkfs.ext4 -F -L root "${root_part}" ;;
|
|
btrfs) mkfs.btrfs -f -L root "${root_part}" ;;
|
|
*) image_deploy_fatal "Nicht unterstuetztes Root-Dateisystem: ${root_fs}"; return 1 ;;
|
|
esac || { image_deploy_fatal "mkfs auf ${root_part} fehlgeschlagen"; return 1; }
|
|
}
|
|
|
|
image_deploy_mount() {
|
|
local target="$1"
|
|
local boot_part="$2"
|
|
local root_part="$3"
|
|
|
|
install -d "${target}"
|
|
mount "${root_part}" "${target}" ||
|
|
{ image_deploy_fatal "Root-Partition ${root_part} konnte nicht auf ${target} gemountet werden"; return 1; }
|
|
|
|
if [[ -n "${boot_part}" ]]; then
|
|
install -d "${target}/boot/efi"
|
|
mount "${boot_part}" "${target}/boot/efi" ||
|
|
{ image_deploy_fatal "ESP ${boot_part} konnte nicht gemountet werden"; return 1; }
|
|
fi
|
|
}
|
|
|
|
# package_golden_image.sh schliesst /proc, /sys, /dev, /run, /tmp, /var/tmp
|
|
# bewusst aus dem Archiv aus (Laufzeit-Zustand der Referenz-VM, nicht Teil
|
|
# eines portablen Images). Nach dem Entpacken fehlen diese Verzeichnisse
|
|
# deshalb komplett - live gefunden (31.08.2026, erster echter Lauf mit dem
|
|
# echten Referenz-VM-Archiv): erst "mount --bind /dev" ("mount point does
|
|
# not exist"), dann - nach Behebung dessen - "mktemp: failed to create
|
|
# directory via template '/var/tmp/mkinitramfs_XXXXXX'" bei
|
|
# update-initramfs. Statt jedes Fehlen einzeln zu jagen: alle sechs
|
|
# ausgeschlossenen Verzeichnisse direkt nach dem Entpacken einmalig mit
|
|
# sinnvollen Rechten wiederherstellen. /dev, /proc, /sys werden gleich
|
|
# danach ohnehin ueberbindmounted (image_deploy_bind_mounts) - schadet
|
|
# aber nicht, sie schon hier als leere Verzeichnisse anzulegen.
|
|
image_deploy_restore_excluded_dirs() {
|
|
local target="$1"
|
|
|
|
mkdir -p "${target}"/{proc,sys,dev,run,tmp,var/tmp}
|
|
chmod 1777 "${target}/tmp" "${target}/var/tmp"
|
|
}
|
|
|
|
# Entpackt das per build_golden_image.sh gebaute Golden Image direkt in
|
|
# den gemounteten Zielbaum. -p erhaelt Rechte/Eigentuemer, --zstd deckt
|
|
# sowohl .tar.zst als auch (falls spaeter gewechselt) andere
|
|
# zstd-komprimierte Varianten ab.
|
|
image_deploy_extract_image() {
|
|
local image_path="$1"
|
|
local target="$2"
|
|
|
|
[[ -r "${image_path}" ]] ||
|
|
{ image_deploy_fatal "Golden Image nicht lesbar: ${image_path}"; return 1; }
|
|
|
|
tar --zstd -xpf "${image_path}" -C "${target}" ||
|
|
{ image_deploy_fatal "Golden Image konnte nicht nach ${target} entpackt werden"; return 1; }
|
|
|
|
image_deploy_restore_excluded_dirs "${target}"
|
|
}
|
|
|
|
# Streamt das Golden Image direkt vom Server in die Extraktion, ohne es
|
|
# jemals als Ganzes zwischenzuspeichern - live gefunden (31.08.2026,
|
|
# erster echter End-to-End-Lauf mit dem vollen 2,3-GB-Referenz-VM-Archiv
|
|
# ueber das neue Boot-Medium): das bisherige "erst nach /run/tuxflotte/...
|
|
# herunterladen, dann entpacken" scheiterte an /run (RAM-Tmpfs, oft nur
|
|
# ein paar hundert MB gross, unabhaengig von der Geraete-RAM-Groesse) -
|
|
# "curl: (23) Failure writing output to destination". Direktes
|
|
# Zusammenrohren braucht dagegen nur ein paar MB Puffer, unabhaengig von
|
|
# der Archivgroesse. PIPESTATUS wird explizit geprueft statt sich auf
|
|
# "pipefail"-Semantik (rechteste fehlgeschlagene Stufe gewinnt) zu
|
|
# verlassen, damit die Fehlermeldung eindeutig zwischen Download- und
|
|
# Entpack-Fehler unterscheidet.
|
|
image_deploy_extract_image_from_url() {
|
|
local url="$1"
|
|
local target="$2"
|
|
local pipe_status
|
|
|
|
# "|| true" verhindert, dass "set -e" hier sofort abbricht - der
|
|
# eigentliche Fehlerfall wird gleich anhand von PIPESTATUS gezielt
|
|
# ausgewertet, statt die Pipeline roh durchschlagen zu lassen.
|
|
curl --silent --show-error --fail --location "${url}" |
|
|
tar --zstd -xpf - -C "${target}" || true
|
|
pipe_status=("${PIPESTATUS[@]}")
|
|
|
|
[[ "${pipe_status[0]}" -eq 0 ]] ||
|
|
{ image_deploy_fatal "Golden Image konnte nicht geladen werden: ${url}"; return 1; }
|
|
[[ "${pipe_status[1]}" -eq 0 ]] ||
|
|
{ image_deploy_fatal "Golden Image konnte nicht nach ${target} entpackt werden"; return 1; }
|
|
|
|
image_deploy_restore_excluded_dirs "${target}"
|
|
}
|
|
|
|
# /etc/fstab aus den tatsaechlichen Partitions-UUIDs neu erzeugen - kein
|
|
# genfstab auf Debian/Mint verfuegbar (das ist ein Arch-Linux-Werkzeug),
|
|
# blkid reicht fuer unseren einfachen Fall (Root + optional ESP) locker.
|
|
image_deploy_write_fstab() {
|
|
local target="$1"
|
|
local boot_part="$2"
|
|
local root_part="$3"
|
|
local root_fs="$4"
|
|
|
|
local root_uuid
|
|
root_uuid="$(blkid -s UUID -o value "${root_part}")"
|
|
[[ -n "${root_uuid}" ]] ||
|
|
{ image_deploy_fatal "Keine UUID fuer ${root_part} gefunden"; return 1; }
|
|
|
|
{
|
|
echo "# Von image_deploy.sh generiert - siehe ADR-0024."
|
|
echo "UUID=${root_uuid} / ${root_fs} defaults 0 1"
|
|
if [[ -n "${boot_part}" ]]; then
|
|
local boot_uuid
|
|
boot_uuid="$(blkid -s UUID -o value "${boot_part}")"
|
|
[[ -n "${boot_uuid}" ]] ||
|
|
{ image_deploy_fatal "Keine UUID fuer ${boot_part} gefunden"; return 1; }
|
|
echo "UUID=${boot_uuid} /boot/efi vfat umask=0077 0 1"
|
|
fi
|
|
} > "${target}/etc/fstab"
|
|
}
|
|
|
|
# /dev, /proc, /sys, /dev/pts in den Zielbaum einbinden (fuer chroot-
|
|
# Operationen, die Geraeteknoten/Kernel-Interfaces brauchen - initramfs,
|
|
# grub-install) sowie eine funktionierende DNS-Aufloesung fuers
|
|
# apt-get-basierte Bootloader-Nachinstallieren. MOUNTED_STACK wird vom
|
|
# aufrufenden Skript vorgehalten (siehe image_deploy_unbind_mounts()).
|
|
image_deploy_bind_mounts() {
|
|
local target="$1"
|
|
local -n stack_ref="$2"
|
|
|
|
# Golden Image schliesst /dev, /proc, /sys bewusst aus
|
|
# (package_golden_image.sh --exclude=dev/sys/proc) - die Zielverzeichnisse
|
|
# existieren nach dem Entpacken deshalb noch nicht und muessen vor dem
|
|
# Bind-Mount erst angelegt werden. Live gefunden (31.08.2026, erster
|
|
# echter Lauf mit der echten Mint-Referenz-VM): ohne das schlaegt
|
|
# "mount --bind" mit "mount point does not exist" fehl - und zwar
|
|
# SILENT, weil das bisherige "cmd && stack_ref+=(...)"-Muster einen
|
|
# Fehlschlag von "cmd" unter "set -e" nicht als Statement-Fehler wertet
|
|
# (wohlbekannte Bash-Ausnahme fuer &&/||-Ketten) - der fehlende
|
|
# /dev-Bind-Mount fiel dann erst beim naechsten chroot-Aufruf auf
|
|
# ("ssh-keygen -A: Couldn't open /dev/null"), weit weg von der
|
|
# eigentlichen Ursache. Jetzt: Zielverzeichnisse anlegen, jeden
|
|
# Mount-Fehler explizit und einzeln pruefen statt auf &&-Verkettung
|
|
# zu vertrauen.
|
|
mkdir -p "${target}/dev" "${target}/proc" "${target}/sys" "${target}/dev/pts"
|
|
|
|
mount --bind /dev "${target}/dev" ||
|
|
{ image_deploy_fatal "/dev konnte nicht eingebunden werden"; return 1; }
|
|
stack_ref+=("${target}/dev")
|
|
|
|
mount -t proc proc "${target}/proc" ||
|
|
{ image_deploy_fatal "/proc konnte nicht eingebunden werden"; return 1; }
|
|
stack_ref+=("${target}/proc")
|
|
|
|
mount -t sysfs sysfs "${target}/sys" ||
|
|
{ image_deploy_fatal "/sys konnte nicht eingebunden werden"; return 1; }
|
|
stack_ref+=("${target}/sys")
|
|
|
|
mount -t devpts devpts "${target}/dev/pts" ||
|
|
{ image_deploy_fatal "/dev/pts konnte nicht eingebunden werden"; return 1; }
|
|
stack_ref+=("${target}/dev/pts")
|
|
|
|
# Live gefunden (31.08.2026, erster echter Lauf mit der echten
|
|
# Mint-Referenz-VM statt eines debootstrap-Test-Images): dort ist
|
|
# /etc/resolv.conf ein von NetworkManager verwalteter Symlink (z.B. auf
|
|
# /run/NetworkManager/resolv.conf) - im gerade erst entpackten,
|
|
# ungebooteten Zielbaum zeigt der ins Leere, "cp" verweigert dann das
|
|
# Schreiben "through a dangling symlink". Ziel deshalb vorher entfernen
|
|
# (Symlink oder Datei, beides abgedeckt), erst dann kopieren.
|
|
rm -f "${target}/etc/resolv.conf"
|
|
cp /etc/resolv.conf "${target}/etc/resolv.conf" ||
|
|
{ image_deploy_fatal "resolv.conf konnte nicht nach ${target} kopiert werden"; return 1; }
|
|
}
|
|
|
|
image_deploy_unbind_mounts() {
|
|
local -n stack_ref="$1"
|
|
local i mnt
|
|
|
|
for ((i = ${#stack_ref[@]} - 1; i >= 0; i--)); do
|
|
mnt="${stack_ref[i]}"
|
|
umount --recursive "${mnt}" 2>/dev/null || umount --lazy "${mnt}" 2>/dev/null || true
|
|
done
|
|
stack_ref=()
|
|
}
|
|
|
|
# machine-id + SSH-Hostkeys werden im Golden Image bewusst NICHT
|
|
# mitgeliefert (siehe build_golden_image.sh) - hier, pro tatsaechlich
|
|
# ausgerolltem Geraet, frisch erzeugt. update-initramfs regeneriert das
|
|
# initrd fuer die tatsaechliche Zielhardware (Golden Image wurde auf
|
|
# einem anderen System gebaut).
|
|
image_deploy_chroot_fixup() {
|
|
local target="$1"
|
|
|
|
chroot "${target}" systemd-machine-id-setup ||
|
|
{ image_deploy_fatal "machine-id konnte nicht erzeugt werden"; return 1; }
|
|
chroot "${target}" ssh-keygen -A ||
|
|
{ image_deploy_fatal "SSH-Hostkeys konnten nicht erzeugt werden"; return 1; }
|
|
chroot "${target}" update-initramfs -u -k all ||
|
|
{ image_deploy_fatal "initramfs-Regenerierung fehlgeschlagen"; return 1; }
|
|
}
|
|
|
|
# Grub wird bewusst NICHT ins Golden Image eingebaut (spart Platz, das
|
|
# Image kennt beim Bauen das Zielfirmware-Schema noch nicht) - stattdessen
|
|
# hier zur Deployment-Zeit nachinstalliert, wo bereits Netzwerk verfuegbar
|
|
# ist (Stufe-1-Umgebung hat sich schon fuer die Aktivierung verbunden).
|
|
image_deploy_install_bootloader() {
|
|
local target="$1"
|
|
local disk="$2"
|
|
local is_efi="$3"
|
|
|
|
if [[ "${is_efi}" == "true" ]]; then
|
|
chroot "${target}" /bin/bash -c '
|
|
set -e
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update
|
|
apt-get install -y grub-efi-amd64
|
|
' || { image_deploy_fatal "grub-efi-amd64-Installation fehlgeschlagen"; return 1; }
|
|
chroot "${target}" grub-install --target=x86_64-efi \
|
|
--efi-directory=/boot/efi --bootloader-id=tuxflotte --recheck ||
|
|
{ image_deploy_fatal "grub-install (UEFI) fehlgeschlagen"; return 1; }
|
|
# Zusaetzlich auf den Standard-Fallback-Pfad (EFI/BOOT/BOOTX64.EFI)
|
|
# installieren - real beim Testen entdeckt: grub-install kann in
|
|
# verschachtelten/eingeschraenkten Umgebungen keinen NVRAM-
|
|
# Booteintrag setzen ("EFI variables are not supported on this
|
|
# system"), ohne --removable bleibt dann NUR der benannte
|
|
# /EFI/tuxflotte/-Pfad uebrig, den die Firmware ohne NVRAM-Eintrag
|
|
# nie findet ("No bootable option or device was found"). Der
|
|
# Fallback-Pfad wird von JEDER UEFI-Firmware ohne NVRAM-Eintrag
|
|
# automatisch versucht - robuster fuer heterogene Zielhardware
|
|
# generell, nicht nur fuer dieses Testszenario.
|
|
chroot "${target}" grub-install --target=x86_64-efi \
|
|
--efi-directory=/boot/efi --removable --recheck ||
|
|
{ image_deploy_fatal "grub-install (UEFI, removable-Fallback) fehlgeschlagen"; return 1; }
|
|
else
|
|
chroot "${target}" /bin/bash -c '
|
|
set -e
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update
|
|
apt-get install -y grub-pc
|
|
' || { image_deploy_fatal "grub-pc-Installation fehlgeschlagen"; return 1; }
|
|
chroot "${target}" grub-install --target=i386-pc --recheck "${disk}" ||
|
|
{ image_deploy_fatal "grub-install (BIOS) fehlgeschlagen"; return 1; }
|
|
fi
|
|
|
|
chroot "${target}" update-grub ||
|
|
{ image_deploy_fatal "update-grub fehlgeschlagen"; return 1; }
|
|
}
|
|
|
|
image_deploy_set_hostname() {
|
|
local target="$1"
|
|
local hostname="$2"
|
|
|
|
echo "${hostname}" > "${target}/etc/hostname"
|
|
printf '127.0.1.1\t%s\n' "${hostname}" >> "${target}/etc/hosts"
|
|
}
|