Created
June 15, 2026 18:10
-
-
Save dobesv/e19d7a14b4ab8f8176beeb700d5feb45 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # install-ev-serial.sh | |
| # | |
| # Give a DIY / no-factory-serial Linux box a unique system serial number (and, | |
| # optionally, a model name) for Google Workspace Endpoint Verification — | |
| # WITHOUT touching the BIOS/flash. | |
| # | |
| # How it works: | |
| # * Endpoint Verification's helper (device_state.sh) reads the serial from | |
| # /sys/class/dmi/id/product_serial and the model from product_name. | |
| # * We bind-mount a file containing the chosen value(s) over those sysfs | |
| # entries (the kernel-exported DMI data; nothing is written to flash). | |
| # * A systemd unit re-applies the bind-mount on every boot, ordered BEFORE | |
| # endpoint-verification.service so EV caches the right value. | |
| # * We then refresh EV's cache so the change takes effect immediately. | |
| # | |
| # Usage: | |
| # ./install-ev-serial.sh <SERIAL> [MODEL] # re-execs under sudo if needed | |
| # sudo ./install-ev-serial.sh <SERIAL> [MODEL] | |
| # sudo ./install-ev-serial.sh --remove # uninstall / roll back | |
| # | |
| # SERIAL is required. A UUID makes a good, guaranteed-unique value; generate | |
| # one with: uuidgen | |
| # | |
| # Examples: | |
| # ./install-ev-serial.sh "$(uuidgen)" | |
| # ./install-ev-serial.sh "$(uuidgen)" "ASUS PRIME X670-P WIFI" | |
| # | |
| set -euo pipefail | |
| # ---- configuration (paths are hard-coded in the unit too; keep in sync) ---- | |
| OVERRIDE_DIR=/etc/dmi-override | |
| UNIT_NAME=dmi-serial-override.service | |
| UNIT_PATH=/etc/systemd/system/${UNIT_NAME} | |
| DMI_DIR=/sys/class/dmi/id | |
| EV_SERVICE=endpoint-verification.service | |
| EV_ATTRS=/opt/google/endpoint-verification/var/lib/device_attrs | |
| # ---- pretty output (no color if not a tty) ---- | |
| if [ -t 1 ]; then C_OK=$'\033[1;32m'; C_WARN=$'\033[1;33m'; C_ERR=$'\033[1;31m'; C_OFF=$'\033[0m' | |
| else C_OK=; C_WARN=; C_ERR=; C_OFF=; fi | |
| say() { printf '%s==>%s %s\n' "$C_OK" "$C_OFF" "$*"; } | |
| warn() { printf '%s[warn]%s %s\n' "$C_WARN" "$C_OFF" "$*" >&2; } | |
| die() { printf '%s[error]%s %s\n' "$C_ERR" "$C_OFF" "$*" >&2; exit 1; } | |
| usage() { | |
| cat <<'USAGE' | |
| install-ev-serial.sh — set a unique system serial for Google Endpoint | |
| Verification without flashing the BIOS. | |
| Usage: | |
| install-ev-serial.sh <SERIAL> [MODEL] # SERIAL is required | |
| install-ev-serial.sh --remove # uninstall / roll back | |
| install-ev-serial.sh --help | |
| A UUID makes a good, guaranteed-unique serial. Generate one with: uuidgen | |
| Examples: | |
| install-ev-serial.sh "$(uuidgen)" | |
| install-ev-serial.sh "$(uuidgen)" "ASUS PRIME X670-P WIFI" | |
| USAGE | |
| } | |
| # ---- handle --help before anything that needs root ---- | |
| case "${1:-}" in | |
| -h|--help) usage; exit 0 ;; | |
| esac | |
| # ---- require an argument before elevating (so we don't prompt for a password | |
| # just to error out on a missing serial) ---- | |
| if [ "$#" -eq 0 ]; then | |
| usage >&2 | |
| die "SERIAL is required — generate one with: $0 \"\$(uuidgen)\"" | |
| fi | |
| # ---- re-exec under sudo if not root ---- | |
| if [ "$(id -u)" -ne 0 ]; then | |
| say "Elevating with sudo..." | |
| SELF="$(readlink -f "$0")" | |
| exec sudo bash "$SELF" "$@" | |
| fi | |
| # ---- parse action ---- | |
| ACTION=install | |
| SERIAL="" | |
| MODEL="" | |
| case "${1:-}" in | |
| -r|--remove|--uninstall) ACTION=remove ;; | |
| "") usage >&2; die "SERIAL is required — generate one with: $0 \"\$(uuidgen)\"" ;; | |
| -*) die "Unknown option: $1" ;; | |
| *) SERIAL="$1"; MODEL="${2:-}" ;; | |
| esac | |
| # =========================================================================== | |
| # Remove / roll back | |
| # =========================================================================== | |
| if [ "$ACTION" = remove ]; then | |
| say "Removing DMI serial override" | |
| systemctl disable --now "$UNIT_NAME" >/dev/null 2>&1 || true | |
| for n in product_serial product_name; do | |
| if mountpoint -q "$DMI_DIR/$n"; then | |
| umount "$DMI_DIR/$n" || warn "could not umount $DMI_DIR/$n" | |
| fi | |
| done | |
| rm -f "$UNIT_PATH" | |
| rm -rf "$OVERRIDE_DIR" | |
| systemctl daemon-reload | |
| if systemctl list-unit-files 2>/dev/null | grep -q "^${EV_SERVICE}"; then | |
| say "Refreshing Endpoint Verification cache" | |
| systemctl restart "$EV_SERVICE" || warn "could not restart $EV_SERVICE" | |
| fi | |
| say "Done. sysfs serial is back to: $(cat "$DMI_DIR/product_serial" 2>/dev/null || echo '<unreadable>')" | |
| exit 0 | |
| fi | |
| # =========================================================================== | |
| # Install | |
| # =========================================================================== | |
| # ---- preflight + validation ---- | |
| [ -e "$DMI_DIR/product_serial" ] || die "$DMI_DIR/product_serial does not exist (no SMBIOS/DMI on this system?)." | |
| case "$SERIAL" in *\"*) warn "Serial contains a double-quote; EV strips those, so the reported value will differ." ;; esac | |
| [ "${#SERIAL}" -le 128 ] || warn "Serial is ${#SERIAL} chars; EV truncates to 128." | |
| say "Serial to report: \"$SERIAL\"" | |
| [ -n "$MODEL" ] && say "Model to report: \"$MODEL\"" | |
| # ---- write the override value(s) ---- | |
| say "Writing override values to $OVERRIDE_DIR" | |
| install -d -m 0755 "$OVERRIDE_DIR" | |
| printf '%s\n' "$SERIAL" > "$OVERRIDE_DIR/product_serial" | |
| chmod 0444 "$OVERRIDE_DIR/product_serial" | |
| if [ -n "$MODEL" ]; then | |
| printf '%s\n' "$MODEL" > "$OVERRIDE_DIR/product_name" | |
| chmod 0444 "$OVERRIDE_DIR/product_name" | |
| else | |
| # If re-running without a model, drop any previous model override. | |
| rm -f "$OVERRIDE_DIR/product_name" | |
| fi | |
| # ---- install the persistence unit (binds every file in OVERRIDE_DIR) ---- | |
| say "Installing systemd unit $UNIT_PATH" | |
| cat > "$UNIT_PATH" <<'UNIT_EOF' | |
| [Unit] | |
| Description=Override DMI product_serial/product_name for Endpoint Verification (DIY board, no factory serial) | |
| DefaultDependencies=no | |
| After=local-fs.target | |
| Before=endpoint-verification.service | |
| ConditionPathExists=/etc/dmi-override/product_serial | |
| [Service] | |
| Type=oneshot | |
| RemainAfterExit=yes | |
| ExecStart=/bin/sh -c 'for f in /etc/dmi-override/*; do n=$(basename "$f"); mountpoint -q "/sys/class/dmi/id/$n" || mount --bind "$f" "/sys/class/dmi/id/$n"; done' | |
| ExecStop=/bin/sh -c 'for f in /etc/dmi-override/*; do n=$(basename "$f"); ! mountpoint -q "/sys/class/dmi/id/$n" || umount "/sys/class/dmi/id/$n"; done' | |
| [Install] | |
| WantedBy=multi-user.target | |
| UNIT_EOF | |
| # ---- (re)apply the bind-mount(s) now and on every boot ---- | |
| systemctl daemon-reload | |
| systemctl enable "$UNIT_NAME" >/dev/null 2>&1 || true | |
| say "Applying bind-mount(s)" | |
| systemctl restart "$UNIT_NAME" | |
| # ---- refresh Endpoint Verification's cached attributes ---- | |
| if systemctl list-unit-files 2>/dev/null | grep -q "^${EV_SERVICE}"; then | |
| say "Refreshing Endpoint Verification cache" | |
| systemctl restart "$EV_SERVICE" || warn "could not restart $EV_SERVICE" | |
| else | |
| warn "$EV_SERVICE not found — skipping EV refresh (override is still active)." | |
| fi | |
| # ---- report result ---- | |
| echo | |
| say "Result" | |
| printf ' %-34s = %s\n' "$DMI_DIR/product_serial" "$(cat "$DMI_DIR/product_serial")" | |
| [ -e "$OVERRIDE_DIR/product_name" ] && printf ' %-34s = %s\n' "$DMI_DIR/product_name" "$(cat "$DMI_DIR/product_name")" | |
| if [ -r "$EV_ATTRS" ]; then | |
| echo " Endpoint Verification cache ($EV_ATTRS):" | |
| sed 's/^/ /' "$EV_ATTRS" | |
| fi | |
| cat <<EOF | |
| Done. Notes: | |
| * \`dmidecode\` will still show the old placeholder — expected; EV reads sysfs, not dmidecode. | |
| * The new value uploads on EV's next sync (~10 min) or when you open the | |
| Endpoint Verification extension. Confirm in Admin console > Devices. | |
| * Roll back any time: sudo $0 --remove | |
| EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment