#cloud-config # Minimal user-data: download bootstrap.sh from your file server and run it at first boot. # # 1. Host bootstrap.sh at a URL reachable from the device on first boot (e.g. your # provisioning portal or file server). Example: http://10.20.50.1:5000/files/bootstrap.sh # 2. Copy this file to the boot partition as "user-data" (with meta-data and optional network-config). # 3. Edit BOOTSTRAP_URL below to match your server (or set it once in the runcmd section). package_update: true package_upgrade: false # Keep /etc/hosts in sync with hostname (from meta-data or set below) manage_etc_hosts: true # DNS is managed by systemd-resolved; we do not overwrite /etc/resolv.conf manage_resolv_conf: false packages: - curl # Ensure SSH is enabled and password auth allowed so you can log in after first boot write_files: - path: /etc/ssh/sshd_config.d/99-cloud-init.conf content: | PasswordAuthentication yes PermitRootLogin no # Push current DHCP DNS into systemd-resolved (for dhcpcd/dhclient when NM doesn't feed resolved). # With no args: discover DNS from lease or resolvectl and push to resolved for default IF. # NetworkManager feeds resolved automatically; this covers first boot and non-NM setups. - path: /usr/local/bin/update-resolv-from-dhcp.sh content: | #!/bin/sh # Push DHCP DNS to systemd-resolved so resolv.conf (stub) uses it. IF="${IFACE:-$(ip -o -4 route show to default 2>/dev/null | awk '{print $5}' | head -1)}" [ -z "$IF" ] && exit 0 DNS="" if [ -s /run/systemd/resolve/resolv.conf ]; then DNS=$(grep -E '^nameserver\s+' /run/systemd/resolve/resolv.conf | awk '{print $2}' | tr '\n' ' ') fi if [ -z "$DNS" ]; then DNS=$(resolvectl dns "$IF" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | tr '\n' ' ') fi if [ -z "$DNS" ]; then LEASE=$(ls /var/lib/dhcp/dhclient.*.leases 2>/dev/null | head -1) [ -n "$LEASE" ] && DNS=$(grep -oP 'option domain-name-servers \K[^;]+' "$LEASE" 2>/dev/null | tr ',' '\n' | tr -d ' ' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | tr '\n' ' ') fi [ -n "$DNS" ] && resolvectl dns "$IF" $DNS permissions: '0755' # dhclient: feed systemd-resolved on every lease acquire/renew (DHCP provides new_domain_name_servers) - path: /etc/dhcp/dhclient-exit-hooks.d/zzz-update-resolv-conf content: | #!/bin/sh # Run by dhclient on exit; push DHCP DNS into systemd-resolved. [ -z "$new_domain_name_servers" ] && exit 0 [ -z "$interface" ] && exit 0 resolvectl dns "$interface" $new_domain_name_servers permissions: '0755' # NetworkManager: resolved is fed by NM by default; this only runs our script as fallback (e.g. if resolved started late). - path: /etc/NetworkManager/dispatcher.d/99-update-resolv-from-dhcp content: | #!/bin/sh [ "$2" = "up" ] || [ "$2" = "dhcp4-change" ] || exit 0 export IFACE="$1" /usr/local/bin/update-resolv-from-dhcp.sh permissions: '0755' # Tell NetworkManager to send DHCP DNS to systemd-resolved (so every DHCP update is applied). - path: /etc/NetworkManager/conf.d/99-use-resolved.conf content: | [main] dns=systemd-resolved rc-manager=unmanaged # Fallback: push DHCP DNS to resolved once when network is up (e.g. dhcpcd-only or first boot). - path: /etc/systemd/system/update-resolv-from-dhcp.service content: | [Unit] Description=Push DHCP DNS to systemd-resolved After=network-online.target systemd-resolved.service WantedBy=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/update-resolv-from-dhcp.sh RemainAfterExit=yes runcmd: # Use systemd-resolved for DNS; /etc/resolv.conf -> stub so all lookups go through resolved (DHCP DNS applied by NM/hooks). - systemctl enable systemd-resolved.service - systemctl start systemd-resolved.service - rm -f /etc/resolv.conf && ln -s /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf # Push current DHCP DNS into resolved once at first boot (in case NM hasn't applied yet). - /usr/local/bin/update-resolv-from-dhcp.sh - systemctl enable update-resolv-from-dhcp.service - systemctl enable ssh - systemctl start ssh # Download and run bootstrap script (edit URL to match your file server) - | BOOTSTRAP_URL="http://10.20.50.1:5000/files/bootstrap.sh" LOG="/var/log/cloud-init-bootstrap.log" if ! curl -fsSL "$BOOTSTRAP_URL" -o /tmp/bootstrap.sh 2>>"$LOG" || [ ! -s /tmp/bootstrap.sh ]; then echo "$(date -Iseconds) ERROR: Failed to download bootstrap.sh from $BOOTSTRAP_URL (file missing or empty)" >> "$LOG" exit 0 fi chmod +x /tmp/bootstrap.sh /tmp/bootstrap.sh - cloud-init single --name cc_final_message