From 01ec867c2f43892fc82e9843eed3dec1de6d593b1e43415fb426b9d1a4d8b7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9F=E9=85=8C=20=E9=B5=AC=E5=85=84?= Date: Wed, 25 Mar 2026 18:05:42 +0800 Subject: [PATCH] Can now create a node worker --- alpine/build-rootfs.sh | 2 +- alpine/configure-system.sh | 1 - alpine/install-packages.sh | 3 - alpine/rootfs-extra/etc/fancontrol | 2 +- .../opt/scripts/apply-node-config.sh | 149 ++++++ .../opt/scripts/bootstrap-cluster.sh | 436 ++++++++++++++++++ .../opt/scripts/bootstrap-control-plane.sh | 377 --------------- configs/cluster.env.default | 18 + configs/node.env.default | 7 + initramfs/rootfs-extra/bin/flash-emmc.sh.tmpl | 140 +++++- kernel-build/dts/mono-gateway-dk.dts | 3 + makefile | 17 +- 12 files changed, 747 insertions(+), 408 deletions(-) create mode 100644 alpine/rootfs-extra/opt/scripts/apply-node-config.sh create mode 100755 alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh delete mode 100755 alpine/rootfs-extra/opt/scripts/bootstrap-control-plane.sh create mode 100644 configs/node.env.default diff --git a/alpine/build-rootfs.sh b/alpine/build-rootfs.sh index 99933b0..8031868 100755 --- a/alpine/build-rootfs.sh +++ b/alpine/build-rootfs.sh @@ -51,7 +51,7 @@ rm -r "$ROOTFS/build" echo "##################################################### Packaging RootFS "$( du -sh "$ROOTFS/" ) IMG=output.img -SIZE=2048MB +SIZE=3072MB dd if=/dev/zero of="$IMG" bs=1 count=0 seek=$SIZE diff --git a/alpine/configure-system.sh b/alpine/configure-system.sh index f7e8636..9d931d1 100755 --- a/alpine/configure-system.sh +++ b/alpine/configure-system.sh @@ -11,4 +11,3 @@ rc-update add fancontrol boot rc-update add loopback boot rc-update add hostname boot rc-update add localmount boot -rc-update add crio default diff --git a/alpine/install-packages.sh b/alpine/install-packages.sh index 11855cb..08c8630 100755 --- a/alpine/install-packages.sh +++ b/alpine/install-packages.sh @@ -31,9 +31,6 @@ if [ $? -ne 0 ]; then exit $? fi -mv /etc/cni/net.d/10-crio-bridge.conflist.disabled \ - /etc/cni/net.d/10-crio-bridge.conflist - sed -i "s/default_runtime = \"crun\"/\0\ncgroup_manager = \"cgroupfs\"/g" /etc/crio/crio.conf.d/10-crio.conf grep cgroup_manager /etc/crio/crio.conf.d/10-crio.conf || exit 1 diff --git a/alpine/rootfs-extra/etc/fancontrol b/alpine/rootfs-extra/etc/fancontrol index b378156..143d6eb 100644 --- a/alpine/rootfs-extra/etc/fancontrol +++ b/alpine/rootfs-extra/etc/fancontrol @@ -3,7 +3,7 @@ DEVPATH=hwmon0=devices/platform/soc/2180000.i2c/i2c-0/i2c-7/7-002e hwmon1=device DEVNAME=hwmon0=emc2305 hwmon1=ddr_thermal FCTEMPS=hwmon0/pwm2=hwmon1/temp1_input FCFANS= hwmon0/pwm2=hwmon0/fan1_input -MINTEMP=hwmon0/pwm2=40 +MINTEMP=hwmon0/pwm2=35 MAXTEMP=hwmon0/pwm2=60 MINSTART=hwmon0/pwm2=60 MINSTOP=hwmon0/pwm2=45 diff --git a/alpine/rootfs-extra/opt/scripts/apply-node-config.sh b/alpine/rootfs-extra/opt/scripts/apply-node-config.sh new file mode 100644 index 0000000..74391e0 --- /dev/null +++ b/alpine/rootfs-extra/opt/scripts/apply-node-config.sh @@ -0,0 +1,149 @@ +#!/bin/bash +set -euo pipefail + +CONFIG_DIR="${CONFIG_DIR:-/opt/monok8s/config}" +NODE_ENV="${NODE_ENV:-$CONFIG_DIR/node.env}" + +log() { + echo "[monok8s-node] $*" +} + +fail() { + echo "[monok8s-node] ERROR: $*" >&2 + exit 1 +} + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1" +} + +require_file() { + [ -f "$1" ] || fail "required file not found: $1" +} + +load_config() { + require_file "$NODE_ENV" + + # shellcheck disable=SC1090 + . "$NODE_ENV" + + HOSTNAME="${HOSTNAME:-}" + MGMT_IFACE="${MGMT_IFACE:-}" + MGMT_ADDRESS="${MGMT_ADDRESS:-}" + MGMT_GATEWAY="${MGMT_GATEWAY:-}" +} + +validate_config() { + [ -n "$HOSTNAME" ] || fail "HOSTNAME is required" + [ -n "$MGMT_IFACE" ] || fail "MGMT_IFACE is required" + [ -n "$MGMT_ADDRESS" ] || fail "MGMT_ADDRESS is required" + + case "$MGMT_ADDRESS" in + */*) + ;; + *) + fail "MGMT_ADDRESS must include a CIDR prefix, example: 10.0.0.13/24" + ;; + esac +} + +check_prereqs() { + need_cmd ip + need_cmd hostname + need_cmd grep + need_cmd awk + need_cmd cut + need_cmd mkdir + need_cmd printf + need_cmd cat +} + +configure_mgmt_interface() { + local addr_ip + + ip link show "$MGMT_IFACE" >/dev/null 2>&1 || fail "interface not found: $MGMT_IFACE" + + log "bringing up interface: $MGMT_IFACE" + ip link set "$MGMT_IFACE" up + + addr_ip="${MGMT_ADDRESS%/*}" + + if ip -o addr show dev "$MGMT_IFACE" | awk '{print $4}' | cut -d/ -f1 | grep -Fx "$addr_ip" >/dev/null 2>&1; then + log "address already present on $MGMT_IFACE: $MGMT_ADDRESS" + else + log "assigning $MGMT_ADDRESS to $MGMT_IFACE" + ip addr add "$MGMT_ADDRESS" dev "$MGMT_IFACE" + fi + + if [ -n "${MGMT_GATEWAY:-}" ]; then + log "ensuring default route via $MGMT_GATEWAY" + ip route replace default via "$MGMT_GATEWAY" dev "$MGMT_IFACE" + fi +} + +set_hostname_if_needed() { + local current_hostname + + current_hostname="$(hostname 2>/dev/null || true)" + + if [ "$current_hostname" != "$HOSTNAME" ]; then + log "setting hostname to $HOSTNAME" + + hostname "$HOSTNAME" + + mkdir -p /etc + printf '%s\n' "$HOSTNAME" > /etc/hostname + + if [ -f /etc/hosts ]; then + if ! grep -Eq "[[:space:]]$HOSTNAME([[:space:]]|$)" /etc/hosts; then + printf '127.0.0.1\tlocalhost %s\n' "$HOSTNAME" >> /etc/hosts + fi + else + cat > /etc/hosts < /proc/sys/net/ipv4/ip_forward + fi + + mkdir -p /etc/sysctl.d + cat > /etc/sysctl.d/99-monok8s.conf <<'EOF' +net.ipv4.ip_forward = 1 +EOF +} + +print_summary() { + log "node configuration applied" + log "hostname: $HOSTNAME" + log "interface: $MGMT_IFACE" + log "address: $MGMT_ADDRESS" + if [ -n "${MGMT_GATEWAY:-}" ]; then + log "gateway: $MGMT_GATEWAY" + else + log "gateway: " + fi +} + +main() { + load_config + validate_config + check_prereqs + + ensure_ip_forward + configure_mgmt_interface + set_hostname_if_needed + print_summary +} + +main "$@" \ No newline at end of file diff --git a/alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh b/alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh new file mode 100755 index 0000000..9752bd2 --- /dev/null +++ b/alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh @@ -0,0 +1,436 @@ +#!/bin/sh +set -eu + +CONFIG_DIR="${CONFIG_DIR:-/opt/monok8s/config}" +CLUSTER_ENV="${CONFIG_DIR}/cluster.env" +KUBEADM_CONFIG_OUT="${KUBEADM_CONFIG_OUT:-/tmp/kubeadm-init.yaml}" + +log() { + echo "[monok8s] $*" +} + +fail() { + echo "[monok8s] ERROR: $*" >&2 + exit 1 +} + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1" +} + +require_file() { + [ -f "$1" ] || fail "required file not found: $1" +} + +load_config() { + require_file "$CLUSTER_ENV" + + # shellcheck disable=SC1090 + . "$CLUSTER_ENV" + + : "${KUBERNETES_VERSION:?KUBERNETES_VERSION is required}" + : "${NODE_NAME:?NODE_NAME is required}" + : "${APISERVER_ADVERTISE_ADDRESS:?APISERVER_ADVERTISE_ADDRESS is required}" + + POD_SUBNET="${POD_SUBNET:-10.244.0.0/16}" + SERVICE_SUBNET="${SERVICE_SUBNET:-10.96.0.0/12}" + CLUSTER_NAME="${CLUSTER_NAME:-monok8s}" + CLUSTER_DOMAIN="${CLUSTER_DOMAIN:-cluster.local}" + CONTAINER_RUNTIME_ENDPOINT="${CONTAINER_RUNTIME_ENDPOINT:-unix:///var/run/crio/crio.sock}" + SANS="${SANS:-}" + ALLOW_SCHEDULING_ON_CONTROL_PLANE="${ALLOW_SCHEDULING_ON_CONTROL_PLANE:-yes}" + SKIP_IMAGE_CHECK="${SKIP_IMAGE_CHECK:-no}" + KUBECONFIG_USER_HOME="${KUBECONFIG_USER_HOME:-/root}" + + BOOTSTRAP_MODE="${BOOTSTRAP_MODE:-init}" + JOIN_KIND="${JOIN_KIND:-worker}" + API_SERVER_ENDPOINT="${API_SERVER_ENDPOINT:-}" + BOOTSTRAP_TOKEN="${BOOTSTRAP_TOKEN:-}" + DISCOVERY_TOKEN_CA_CERT_HASH="${DISCOVERY_TOKEN_CA_CERT_HASH:-}" + CONTROL_PLANE_CERT_KEY="${CONTROL_PLANE_CERT_KEY:-}" + CNI_PLUGIN="${CNI_PLUGIN:-none}" +} + +validate_config() { + case "$BOOTSTRAP_MODE" in + init) + ;; + join) + : "${API_SERVER_ENDPOINT:?API_SERVER_ENDPOINT is required for join mode}" + : "${BOOTSTRAP_TOKEN:?BOOTSTRAP_TOKEN is required for join mode}" + : "${DISCOVERY_TOKEN_CA_CERT_HASH:?DISCOVERY_TOKEN_CA_CERT_HASH is required for join mode}" + + case "$JOIN_KIND" in + worker|control-plane) + ;; + *) + fail "JOIN_KIND must be 'worker' or 'control-plane'" + ;; + esac + + if [ "$JOIN_KIND" = "control-plane" ]; then + : "${CONTROL_PLANE_CERT_KEY:?CONTROL_PLANE_CERT_KEY is required for JOIN_KIND=control-plane}" + fi + ;; + *) + fail "BOOTSTRAP_MODE must be 'init' or 'join'" + ;; + esac +} + +check_prereqs() { + need_cmd kubeadm + need_cmd kubelet + need_cmd kubectl + need_cmd crictl + need_cmd rc-service + need_cmd awk + need_cmd ip + need_cmd grep + need_cmd sed + need_cmd hostname +} + +check_apiserver_reachable() { + host="${API_SERVER_ENDPOINT%:*}" + port="${API_SERVER_ENDPOINT##*:}" + + need_cmd nc + + log "checking API server reachability: ${host}:${port}" + + for _ in $(seq 1 20); do + if nc -z "$host" "$port" >/dev/null 2>&1; then + log "API server is reachable" + return 0 + fi + sleep 1 + done + + fail "cannot reach API server at ${host}:${port}" +} + +start_crio() { + rc-service crio start +} + +check_crio_running() { + log "waiting for CRI-O to become ready..." + + last_status="unknown" + + for _ in $(seq 1 30); do + if rc-service crio status >/dev/null 2>&1; then + last_status="service-running" + if crictl --runtime-endpoint "$CONTAINER_RUNTIME_ENDPOINT" info >/dev/null 2>&1; then + log "CRI-O is up" + return 0 + fi + last_status="service-running-but-runtime-not-ready" + else + last_status="service-not-running" + fi + sleep 1 + done + + fail "CRI-O did not become ready in time (${last_status})" +} + +image_present() { + wanted="$1" + + repo="${wanted%:*}" + tag="${wanted##*:}" + + crictl --runtime-endpoint "$CONTAINER_RUNTIME_ENDPOINT" images \ + | awk 'NR>1 { print $1 ":" $2 }' \ + | grep -Fx "$repo:$tag" >/dev/null 2>&1 +} + +check_required_images() { + [ "$SKIP_IMAGE_CHECK" = "yes" ] && { + log "skipping image check (SKIP_IMAGE_CHECK=yes)" + return 0 + } + + log "checking required Kubernetes images for $KUBERNETES_VERSION..." + + missing_any=0 + + for img in $(kubeadm config images list --kubernetes-version "$KUBERNETES_VERSION"); do + if image_present "$img"; then + log "found image: $img" + else + echo "[monok8s] MISSING image: $img" >&2 + missing_any=1 + fi + done + + [ "$missing_any" -eq 0 ] || fail "preload the Kubernetes images before bootstrapping" + + log "all required images are present" +} + +check_not_already_bootstrapped() { + case "$BOOTSTRAP_MODE" in + init) + if [ -f /etc/kubernetes/admin.conf ]; then + fail "cluster already appears initialized (/etc/kubernetes/admin.conf exists)" + fi + ;; + join) + if [ -f /etc/kubernetes/kubelet.conf ]; then + fail "node already appears joined (/etc/kubernetes/kubelet.conf exists)" + fi + ;; + esac +} + +run_kubeadm_join() { + log "running kubeadm join..." + + case "$JOIN_KIND" in + worker) + kubeadm join "${API_SERVER_ENDPOINT}" \ + --token "${BOOTSTRAP_TOKEN}" \ + --discovery-token-ca-cert-hash "${DISCOVERY_TOKEN_CA_CERT_HASH}" \ + --node-name "${NODE_NAME}" \ + --cri-socket "${CONTAINER_RUNTIME_ENDPOINT}" + ;; + control-plane) + kubeadm join "${API_SERVER_ENDPOINT}" \ + --token "${BOOTSTRAP_TOKEN}" \ + --discovery-token-ca-cert-hash "${DISCOVERY_TOKEN_CA_CERT_HASH}" \ + --control-plane \ + --certificate-key "${CONTROL_PLANE_CERT_KEY}" \ + --apiserver-advertise-address "${APISERVER_ADVERTISE_ADDRESS}" \ + --node-name "${NODE_NAME}" \ + --cri-socket "${CONTAINER_RUNTIME_ENDPOINT}" + ;; + esac +} + +generate_kubeadm_config() { + log "generating kubeadm config at $KUBEADM_CONFIG_OUT..." + + SAN_LINES="" + if [ -n "${SANS:-}" ]; then + old_ifs="$IFS" + IFS=',' + for san in $SANS; do + san_trimmed="$(echo "$san" | sed 's/^ *//;s/ *$//')" + [ -n "$san_trimmed" ] && SAN_LINES="${SAN_LINES} - \"${san_trimmed}\" +" + done + IFS="$old_ifs" + fi + + cat > "$KUBEADM_CONFIG_OUT" </dev/null 2>&1 \ + || fail "required local IP is not present on any interface: $wanted_ip" +} + +validate_network_requirements() { + case "$BOOTSTRAP_MODE" in + init) + require_local_ip "$APISERVER_ADVERTISE_ADDRESS" + ;; + join) + require_local_ip "$APISERVER_ADVERTISE_ADDRESS" + check_apiserver_reachable + ;; + *) + fail "unsupported BOOTSTRAP_MODE: $BOOTSTRAP_MODE" + ;; + esac +} + +setup_local_kubectl() { + kube_dir="${KUBECONFIG_USER_HOME}/.kube" + log "setting up local kubectl config in ${kube_dir}/config..." + + mkdir -p "$kube_dir" + cp /etc/kubernetes/admin.conf "${kube_dir}/config" + chmod 600 "${kube_dir}/config" + + if [ "$KUBECONFIG_USER_HOME" = "/root" ]; then + mkdir -p /etc/profile.d + cat > /etc/profile.d/kubeconfig.sh <<'EOF' +export KUBECONFIG=/root/.kube/config +EOF + chmod 644 /etc/profile.d/kubeconfig.sh + fi +} + +wait_for_node() { + log "waiting for node registration: $NODE_NAME" + + for _ in $(seq 1 60); do + if kubectl --kubeconfig /etc/kubernetes/admin.conf get node "$NODE_NAME" >/dev/null 2>&1; then + return 0 + fi + sleep 1 + done + + fail "node $NODE_NAME did not register in time" +} + +apply_local_node_metadata_if_possible() { + if [ "$BOOTSTRAP_MODE" != "init" ]; then + log "skipping node labels/annotations from this node (not control-plane init mode)" + return 0 + fi + + wait_for_node + + if [ -n "${NODE_ANNOTATIONS:-}" ]; then + kubectl --kubeconfig /etc/kubernetes/admin.conf annotate node "$NODE_NAME" $(printf '%s' "$NODE_ANNOTATIONS" | tr ',' ' ') --overwrite + fi + if [ -n "${NODE_LABELS:-}" ]; then + kubectl --kubeconfig /etc/kubernetes/admin.conf label node "$NODE_NAME" $(printf '%s' "$NODE_LABELS" | tr ',' ' ') --overwrite + fi +} + +install_cni_if_requested() { + case "${CNI_PLUGIN}" in + none) + if [ -f /etc/cni/net.d/10-crio-bridge.conflist ]; then + mv /etc/cni/net.d/10-crio-bridge.conflist \ + /etc/cni/net.d/10-crio-bridge.conflist.disabled + fi + log "bootstrap bridge CNI disabled; install a cluster CNI (e.g., flannel) for pod networking" + ;; + bridge) + if [ -f /etc/cni/net.d/10-crio-bridge.conflist.disabled ]; then + mv /etc/cni/net.d/10-crio-bridge.conflist.disabled \ + /etc/cni/net.d/10-crio-bridge.conflist + fi + log "bootstrap bridge CNI enabled" + ;; + *) + fail "unsupported CNI_PLUGIN: ${CNI_PLUGIN}" + ;; + esac +} + +allow_single_node_scheduling() { + if [ "$ALLOW_SCHEDULING_ON_CONTROL_PLANE" != "yes" ]; then + log "leaving control-plane taint in place" + return 0 + fi + + log "removing control-plane taint so this single node can schedule workloads..." + kubectl --kubeconfig /etc/kubernetes/admin.conf taint nodes "$NODE_NAME" node-role.kubernetes.io/control-plane- >/dev/null 2>&1 || true +} + +print_next_steps() { + echo + echo "[monok8s] bootstrap complete" + echo + + case "$BOOTSTRAP_MODE" in + init) + cat <&2 - exit 1 -} - -need_cmd() { - command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1" -} - -require_file() { - [ -f "$1" ] || fail "required file not found: $1" -} - -load_config() { - require_file "$CLUSTER_ENV" - - # shellcheck disable=SC1090 - . "$CLUSTER_ENV" - - : "${KUBERNETES_VERSION:?KUBERNETES_VERSION is required}" - : "${NODE_NAME:?NODE_NAME is required}" - : "${APISERVER_ADVERTISE_ADDRESS:?APISERVER_ADVERTISE_ADDRESS is required}" - - POD_SUBNET="${POD_SUBNET:-10.244.0.0/16}" - SERVICE_SUBNET="${SERVICE_SUBNET:-10.96.0.0/12}" - CLUSTER_NAME="${CLUSTER_NAME:-monok8s}" - CLUSTER_DOMAIN="${CLUSTER_DOMAIN:-cluster.local}" - CONTAINER_RUNTIME_ENDPOINT="${CONTAINER_RUNTIME_ENDPOINT:-unix:///var/run/crio/crio.sock}" - SANS="${SANS:-}" - ALLOW_SCHEDULING_ON_CONTROL_PLANE="${ALLOW_SCHEDULING_ON_CONTROL_PLANE:-yes}" - SKIP_IMAGE_CHECK="${SKIP_IMAGE_CHECK:-no}" - KUBECONFIG_USER_HOME="${KUBECONFIG_USER_HOME:-/root}" -} - -check_prereqs() { - need_cmd kubeadm - need_cmd kubelet - need_cmd kubectl - need_cmd crictl - need_cmd rc-service - need_cmd awk - need_cmd grep - need_cmd sed - need_cmd hostname - - [ -S /var/run/crio/crio.sock ] || fail "CRI-O socket not found at /var/run/crio/crio.sock" -} - -set_hostname_if_needed() { - current_hostname="$(hostname 2>/dev/null || true)" - - if [ "$current_hostname" != "$NODE_NAME" ]; then - log "setting hostname to $NODE_NAME" - - hostname "$NODE_NAME" - - mkdir -p /etc - printf '%s\n' "$NODE_NAME" > /etc/hostname - - # Keep localhost mapping sane even without DNS. - if [ -f /etc/hosts ]; then - if ! grep -Eq "[[:space:]]$NODE_NAME([[:space:]]|\$)" /etc/hosts; then - printf '127.0.0.1\tlocalhost %s\n' "$NODE_NAME" >> /etc/hosts - fi - else - cat > /etc/hosts < /proc/sys/net/ipv4/ip_forward - fi - - mkdir -p /etc/sysctl.d - cat > /etc/sysctl.d/99-monok8s.conf <<'EOF' -net.ipv4.ip_forward = 1 -EOF -} - -ensure_kubelet_binary() { - if command -v kubelet >/dev/null 2>&1; then - return 0 - fi - - if [ -x /usr/local/bin/kubelet ]; then - export PATH="/usr/local/bin:$PATH" - return 0 - fi - - if [ -x /opt/cni/bin/kubelet ]; then - export PATH="/opt/cni/bin:$PATH" - return 0 - fi - - fail "kubelet binary not found; install kubelet into /usr/local/bin or make sure PATH includes it" -} - -ensure_kubelet_service() { - if ! rc-service kubelet status >/dev/null 2>&1; then - if [ ! -f /etc/init.d/kubelet ]; then - fail "kubelet OpenRC service does not exist at /etc/init.d/kubelet" - fi - - log "starting kubelet service" - rc-service kubelet start || fail "failed to start kubelet" - fi -} - -check_crio_running() { - log "checking CRI-O status..." - - if ! rc-service crio status >/dev/null 2>&1; then - fail "crio service is not running" - fi - - if ! crictl --runtime-endpoint "$CONTAINER_RUNTIME_ENDPOINT" info >/dev/null 2>&1; then - fail "crictl cannot talk to CRI-O via $CONTAINER_RUNTIME_ENDPOINT" - fi - - log "CRI-O is up" -} - -normalize_image_ref() { - # Add docker.io/library/ for bare images like pause:3.10 if needed. - # kubeadm usually emits full refs already, so this is just defensive. - ref="$1" - case "$ref" in - */* ) echo "$ref" ;; - * ) echo "docker.io/library/$ref" ;; - esac -} - -image_present() { - wanted="$1" - - repo="${wanted%:*}" - tag="${wanted##*:}" - - crictl --runtime-endpoint "$CONTAINER_RUNTIME_ENDPOINT" images \ - | awk 'NR>1 { print $1 ":" $2 }' \ - | grep -Fx "$repo:$tag" >/dev/null 2>&1 -} - -check_required_images() { - [ "$SKIP_IMAGE_CHECK" = "yes" ] && { - log "skipping image check (SKIP_IMAGE_CHECK=yes)" - return 0 - } - - log "checking required Kubernetes images for $KUBERNETES_VERSION..." - - missing_any=0 - - for img in $(kubeadm config images list --kubernetes-version "$KUBERNETES_VERSION"); do - if image_present "$img"; then - log "found image: $img" - else - echo "[monok8s] MISSING image: $img" >&2 - missing_any=1 - fi - done - - [ "$missing_any" -eq 0 ] || fail "preload the Kubernetes images before bootstrapping" - - log "all required images are present" -} - -check_node_name() { - current_hostname="$(hostname)" - if [ "$current_hostname" != "$NODE_NAME" ]; then - log "warning: hostname is '$current_hostname' but NODE_NAME is '$NODE_NAME'" - log "kubeadm will use NODE_NAME from config" - fi -} - -check_already_initialized() { - if [ -f /etc/kubernetes/admin.conf ]; then - fail "cluster already appears initialized (/etc/kubernetes/admin.conf exists)" - fi -} - -generate_kubeadm_config() { - log "generating kubeadm config at $KUBEADM_CONFIG_OUT..." - - SAN_LINES="" - if [ -n "${SANS:-}" ]; then - old_ifs="$IFS" - IFS=',' - for san in $SANS; do - san_trimmed="$(echo "$san" | sed 's/^ *//;s/ *$//')" - [ -n "$san_trimmed" ] && SAN_LINES="${SAN_LINES} - \"${san_trimmed}\" -" - done - IFS="$old_ifs" - fi - - cat > "$KUBEADM_CONFIG_OUT" </dev/null 2>&1; then - log "API server advertise address already present: $APISERVER_ADVERTISE_ADDRESS" - return 0 - fi - - warn_dummy_interface - - if ! command -v modprobe >/dev/null 2>&1; then - fail "modprobe not found; cannot create dummy interface" - fi - - log "loading dummy kernel module..." - modprobe dummy || fail "failed to load dummy kernel module" - - if ! ip link show "$DUMMY_IFACE" >/dev/null 2>&1; then - log "creating dummy interface: $DUMMY_IFACE" - ip link add "$DUMMY_IFACE" type dummy || fail "failed to create dummy interface $DUMMY_IFACE" - else - log "dummy interface already exists: $DUMMY_IFACE" - fi - - log "assigning ${APISERVER_ADVERTISE_ADDRESS}/${DUMMY_PREFIX_LEN} to $DUMMY_IFACE" - ip addr add "${APISERVER_ADVERTISE_ADDRESS}/${DUMMY_PREFIX_LEN}" dev "$DUMMY_IFACE" 2>/dev/null || true - - log "bringing up $DUMMY_IFACE" - ip link set "$DUMMY_IFACE" up || fail "failed to bring up $DUMMY_IFACE" - - if ! ip -o addr show dev "$DUMMY_IFACE" | awk '{print $4}' | cut -d/ -f1 | grep -Fx "$APISERVER_ADVERTISE_ADDRESS" >/dev/null 2>&1; then - fail "dummy interface came up, but ${APISERVER_ADVERTISE_ADDRESS} is still not present" - fi - - log "dummy interface ready: $DUMMY_IFACE -> ${APISERVER_ADVERTISE_ADDRESS}/${DUMMY_PREFIX_LEN}" -} - -setup_local_kubectl() { - kube_dir="${KUBECONFIG_USER_HOME}/.kube" - log "setting up local kubectl config in ${kube_dir}/config..." - - mkdir -p "$kube_dir" - cp /etc/kubernetes/admin.conf "${kube_dir}/config" - chmod 600 "${kube_dir}/config" - - if [ "$KUBECONFIG_USER_HOME" = "/root" ]; then - mkdir -p /etc/profile.d - cat > /etc/profile.d/kubeconfig.sh <<'EOF' -export KUBECONFIG=/root/.kube/config -EOF - chmod 644 /etc/profile.d/kubeconfig.sh - fi -} - -allow_single_node_scheduling() { - if [ "$ALLOW_SCHEDULING_ON_CONTROL_PLANE" != "yes" ]; then - log "leaving control-plane taint in place" - return 0 - fi - - log "removing control-plane taint so this single node can schedule workloads..." - kubectl --kubeconfig /etc/kubernetes/admin.conf taint nodes "$NODE_NAME" node-role.kubernetes.io/control-plane- >/dev/null 2>&1 || true -} - -print_next_steps() { - cat </dev/null || true + umount "$MNT" 2>/dev/null || true +} + +log() { + echo "[*] $*" +} + +warn() { + echo "[!] $*" >&2 +} + +die() { + echo "[!] ERROR: $*" >&2 + exit 1 +} + +reread_partitions() { + log "Re-reading partition table..." + blockdev --rereadpt "$EMMC_DEV" 2>/dev/null || true + partprobe "$EMMC_DEV" 2>/dev/null || true + partx -u "$EMMC_DEV" 2>/dev/null || true + mdev -s 2>/dev/null || true +} + +get_sectors() { + devname="${1##*/}" + cat "/sys/class/block/$devname/size" 2>/dev/null || echo 0 +} + +wait_for_partition_ready() { + part="$1" + timeout="${2:-15}" + + i=0 + while [ "$i" -lt "$timeout" ]; do + if [ -b "$part" ]; then + sectors="$(get_sectors "$part")" + if [ "${sectors:-0}" -gt 0 ]; then + log "Partition $part is present with $sectors sectors" + return 0 + fi + fi + i=$((i + 1)) + sleep 1 + done + + return 1 +} + +mkdir -p "$MNT" "$ROOT_MNT" +trap cleanup EXIT + +log "Mounting USB..." mount "$USB_DEV" "$MNT" # Find a default image (first match) @@ -23,45 +79,83 @@ else fi echo -# Prompt user printf "Image Location (%s): " "${DEFAULT_IMG:-/mnt/...}" read -r IMG -# Use default if empty if [ -z "$IMG" ]; then IMG="$DEFAULT_IMG" fi -# Validate -if [ -z "$IMG" ] || [ ! -f "$IMG" ]; then - echo "Invalid image: $IMG" - umount "$MNT" || true - exit 1 -fi +[ -n "$IMG" ] || die "No image selected" +[ -f "$IMG" ] || die "Invalid image: $IMG" echo +echo "Looking for env files on USB..." +ENV_FILES="$(find "$MNT" -maxdepth 1 -type f -name '*.env' | sort || true)" + +if [ -n "$ENV_FILES" ]; then + echo "Found env files:" + echo "$ENV_FILES" | sed 's/^/ /' +else + echo "No *.env files found in $MNT" +fi +echo + echo "About to write:" -echo " Image: $IMG" +echo " Image: $IMG" echo " Target: $EMMC_DEV" echo printf "Type 'YES' to continue: " read -r CONFIRM -if [ "$CONFIRM" != "YES" ]; then - echo "Aborted." - umount "$MNT" || true - exit 1 -fi +[ "$CONFIRM" = "YES" ] || die "Aborted." -echo "[*] Flashing..." +log "Flashing image to eMMC..." gunzip -c "$IMG" > "$EMMC_DEV" -echo "[*] Syncing..." +log "Syncing flash write..." sync -echo "[*] Done." -umount "$MNT" || true +reread_partitions -echo "[*] Rebooting..." -reboot -f +log "Waiting for partition nodes..." +if ! wait_for_partition_ready "$EMMC_ROOT_PART" 15; then + warn "Initial partition scan did not settle; retrying after another reread..." + reread_partitions + sleep 2 + wait_for_partition_ready "$EMMC_ROOT_PART" 15 || die "root partition not ready: $EMMC_ROOT_PART" +fi + +log "Partition sizes:" +echo " $(basename "$EMMC_DEV"): $(get_sectors "$EMMC_DEV") sectors" +echo " $(basename "$EMMC_ROOT_PART"): $(get_sectors "$EMMC_ROOT_PART") sectors" + +if [ -n "$ENV_FILES" ]; then + log "Mounting flashed root partition..." + mount "$EMMC_ROOT_PART" "$ROOT_MNT" || { + warn "Mount failed; retrying once after partition reread..." + reread_partitions + sleep 2 + mount "$EMMC_ROOT_PART" "$ROOT_MNT" + } + + log "Creating config dir: $CONFIG_DIR" + mkdir -p "$ROOT_MNT$CONFIG_DIR" + + log "Copying env files..." + for f in $ENV_FILES; do + base="$(basename "$f")" + cp "$f" "$ROOT_MNT$CONFIG_DIR/$base" + echo " copied: $base" + done + + log "Syncing copied config..." + sync + + umount "$ROOT_MNT" +fi + +log "Done." +log "Rebooting..." +reboot -f \ No newline at end of file diff --git a/kernel-build/dts/mono-gateway-dk.dts b/kernel-build/dts/mono-gateway-dk.dts index eaafe8e..d2d7aea 100644 --- a/kernel-build/dts/mono-gateway-dk.dts +++ b/kernel-build/dts/mono-gateway-dk.dts @@ -482,18 +482,21 @@ ethernet@e8000 { phy-handle = <&sgmii_phy0>; phy-connection-type = "sgmii"; + managed = "in-band-status"; status = "okay"; }; ethernet@ea000 { phy-handle = <&sgmii_phy1>; phy-connection-type = "sgmii"; + managed = "in-band-status"; status = "okay"; }; ethernet@e2000 { phy-handle = <&sgmii_phy2>; phy-connection-type = "sgmii"; + managed = "in-band-status"; status = "okay"; }; diff --git a/makefile b/makefile index 01285b6..2fa068c 100644 --- a/makefile +++ b/makefile @@ -20,6 +20,8 @@ CONFIGS_DIR := configs SCRIPTS_DIR := scripts CLUSTER_ENV_DEFAULT := $(CONFIGS_DIR)/cluster.env.default CLUSTER_ENV := $(OUT_DIR)/cluster.env +NODE_ENV_DEFAULT := configs/node.env.default +NODE_ENV := $(OUT_DIR)/node.env BOARD_ITB := $(OUT_DIR)/board.itb INITRAMFS := $(OUT_DIR)/initramfs.cpio.gz @@ -211,14 +213,25 @@ check-functions: in_main && /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*$$/ { \ gsub(/^[ \t]+/, "", $$0); \ print $$0 \ - }' ./alpine/rootfs-extra/opt/scripts/bootstrap-control-plane.sh \ + }' ./alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh \ | sort -u > /tmp/called.txt - @grep -E '^[a-zA-Z_][a-zA-Z0-9_]*\(\)' ./alpine/rootfs-extra/opt/scripts/bootstrap-control-plane.sh \ + @grep -E '^[a-zA-Z_][a-zA-Z0-9_]*\(\)' ./alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh \ | sed 's/().*//' \ | sort -u > /tmp/defined.txt @echo "Missing functions:" @comm -23 /tmp/called.txt /tmp/defined.txt || true +# ---- node targets ------------------------------------------------------------ + +node-config: $(NODE_ENV_DEFAULT) $(SCRIPTS_DIR)/merge-env.sh | $(OUT_DIR) + sh $(SCRIPTS_DIR)/merge-env.sh $(NODE_ENV_DEFAULT) $(NODE_ENV) + +node-defconfig: $(NODE_ENV_DEFAULT) | $(OUT_DIR) + cp $(NODE_ENV_DEFAULT) $(NODE_ENV) + +node-print: + @cat $(NODE_ENV) + # ---- cluster targets ------------------------------------------------------------ cluster-config: $(CLUSTER_ENV_DEFAULT) $(SCRIPTS_DIR)/merge-env.sh | $(OUT_DIR)