原始信息
[cat@max2-nixos:~]$ # ① 所有唤醒位 + 当前状态(enabled=能唤醒 / disabled=不能)
for p in $(find /sys/bus/*/devices/*/power/wakeup /sys/class/power_supply/*/power/wakeup 2>/dev/null); do
printf '%-9s %s\n' "$(cat "$p")" "$p"
done | sort
echo "--- 计数 ---"
for p in $(find /sys/bus/*/devices/*/power/wakeup /sys/class/power_supply/*/power/wakeup 2>/dev/null); do cat "$p"; done | sort | uniq -c
# ② 定时器总闸(26 号核验项)
for u in $(systemctl list-units --type=timer --all --no-legend --plain | awk '{print $1}'); do
printf '%s\t%s\n' "$(systemctl show "$u" -p WakeSystem --value)" "$u"
done
# ③ 另一个口径(注意:awake list【不读】它,别拿来直接对表)
cat /proc/acpi/wakeup
disabled /sys/bus/acpi/devices/PNP0C0A:00/power/wakeup
disabled /sys/bus/acpi/devices/PNP0C0C:00/power/wakeup
disabled /sys/bus/acpi/devices/PNP0C0E:00/power/wakeup
disabled /sys/bus/i2c/devices/i2c-GXTP7385:00/power/wakeup
disabled /sys/bus/pci/devices/0000:00:01.2/power/wakeup
disabled /sys/bus/pci/devices/0000:00:02.2/power/wakeup
disabled /sys/bus/pci/devices/0000:00:02.3/power/wakeup
disabled /sys/bus/pci/devices/0000:00:02.4/power/wakeup
disabled /sys/bus/pci/devices/0000:00:04.1/power/wakeup
disabled /sys/bus/pci/devices/0000:00:08.1/power/wakeup
disabled /sys/bus/pci/devices/0000:00:08.2/power/wakeup
disabled /sys/bus/pci/devices/0000:00:08.3/power/wakeup
disabled /sys/bus/pci/devices/0000:01:00.0/power/wakeup
disabled /sys/bus/pci/devices/0000:02:00.0/power/wakeup
disabled /sys/bus/pci/devices/0000:03:00.0/power/wakeup
disabled /sys/bus/pci/devices/0000:04:00.0/power/wakeup
disabled /sys/bus/pci/devices/0000:65:00.0/power/wakeup
disabled /sys/bus/pci/devices/0000:65:00.1/power/wakeup
disabled /sys/bus/pci/devices/0000:65:00.2/power/wakeup
disabled /sys/bus/pci/devices/0000:65:00.4/power/wakeup
disabled /sys/bus/pci/devices/0000:65:00.6/power/wakeup
disabled /sys/bus/pci/devices/0000:66:00.0/power/wakeup
disabled /sys/bus/pci/devices/0000:66:00.1/power/wakeup
disabled /sys/bus/pci/devices/0000:67:00.0/power/wakeup
disabled /sys/bus/pci/devices/0000:67:00.6/power/wakeup
disabled /sys/bus/platform/devices/alarmtimer.0.auto/power/wakeup
disabled /sys/bus/platform/devices/PNP0B00:00/power/wakeup
disabled /sys/bus/thunderbolt/devices/0-0/power/wakeup
disabled /sys/bus/thunderbolt/devices/domain0/power/wakeup
disabled /sys/bus/usb/devices/1-5/power/wakeup
disabled /sys/bus/usb/devices/3-1/power/wakeup
disabled /sys/bus/usb/devices/7-1.1/power/wakeup
disabled /sys/bus/usb/devices/7-1.4/power/wakeup
disabled /sys/bus/usb/devices/7-1/power/wakeup
disabled /sys/bus/usb/devices/usb1/power/wakeup
disabled /sys/bus/usb/devices/usb2/power/wakeup
disabled /sys/bus/usb/devices/usb3/power/wakeup
disabled /sys/bus/usb/devices/usb4/power/wakeup
disabled /sys/bus/usb/devices/usb5/power/wakeup
disabled /sys/bus/usb/devices/usb6/power/wakeup
disabled /sys/bus/usb/devices/usb7/power/wakeup
disabled /sys/bus/usb/devices/usb8/power/wakeup
disabled /sys/class/power_supply/ADP1/power/wakeup
disabled /sys/class/power_supply/hid-0003:17EF:60EE.0015-battery/power/wakeup
enabled /sys/bus/acpi/devices/PNP0C0D:00/power/wakeup
enabled /sys/bus/pci/devices/0000:65:00.3/power/wakeup
enabled /sys/bus/pci/devices/0000:67:00.3/power/wakeup
enabled /sys/bus/pci/devices/0000:67:00.4/power/wakeup
enabled /sys/bus/serio/devices/serio0/power/wakeup
enabled /sys/bus/usb/devices/7-1.4.2/power/wakeup
--- 计数 ---
44 disabled
6 enabled
no fstrim.timer
no fwupd-refresh.timer
no logrotate.timer
no nix-gc.timer
no systemd-tmpfiles-clean.timer
Device S-state Status Sysfs node
GPP0 S4 *disabled
GPP6 S4 *disabled pci:0000:00:02.2
GPP7 S4 *disabled pci:0000:00:02.3
GP11 S4 *disabled
SWUS S4 *disabled
GP12 S4 *disabled pci:0000:00:04.1
SWUS S4 *disabled
XHC0 S4 *enabled pci:0000:65:00.3
XHC1 S4 *disabled pci:0000:65:00.4
XHC2 S3 *disabled pci:0000:67:00.0
XHC3 S3 *enabled pci:0000:67:00.3
XHC4 S3 *enabled pci:0000:67:00.4
NHI0 S4 *disabled
NHI1 S3 *disabled pci:0000:67:00.6
awake list 列表数据结构设计
按照 power.wakeup(enabled和yes在前,disabled和no在后,path值作为第二排序字段)
path uevent.DRIVER power.wakeup power.wakeup_last_time_ms conf.id conf.wakeup conf.desc
/sys/bus/acpi/devices/PNP0C0A:00 battery enabled yyyy-MM-dd hh:mm:ss 1自增 disable 备注描述可空
/sys/bus/platform/devices/alarmtimer.0.auto alarmtimer disabled yyyy-MM-dd hh:mm:ss 2自增 disable 备注描述可空
/sys/bus/usb/devices/1-5 usb disabled yyyy-MM-dd hh:mm:ss null null null
/sys/class/power_supply/hid-0003:17EF:60EE.0015-battery battery disabled null null null null
fstrim.timer timer no null 2自增 no Discard unused filesystem blocks once a week
awake.conf配置数据文件设计
path uevent.DRIVER conf.id conf.wakeup conf.desc
/sys/bus/acpi/devices/PNP0C0A:00 battery 1自增 disable 备注描述可空,ai处理时
fstrim.timer timer 2自增 no Discard unused filesystem blocks once a week
命令说明
awake —— 管理「谁能唤醒这台机器」的事项清单(清单文件 + 系统运行时)
awake list 运行时全量清单(含清单注解列;能唤醒的在前)
awake setting 读清单:两组(能唤醒 / 不能唤醒)
sudo awake on <id…> 设为「能唤醒」—— 只改运行时,不动清单
sudo awake off <id…> 设为「不能唤醒」—— 只改运行时,不动清单
sudo awake sync 把运行时实况写回清单(新项插入并分配 id)⇒ 下次 rebuild 成为持久策略
awake log [N] [-f] 唤醒日志(睡多久 + 唤醒原因)
机制:清单只有一个文件 logind-data/awake-<机器码>.conf
(机器码 = hardware-configuration.nix 里 fileSystems."/boot" 的 UUID,原文大小写)。
nix 读它进本世代快照、rebuild 时按它重放运行时;on/off 只改运行时;
sync 把运行时写回清单。设备写 sysfs 的 power/wakeup;timer 写 /run 下 drop-in。
需要 root 的:on / off / sync(写 sysfs、/run 与清单文件)。
/etc/nixos/modules/system/service/logind.sh
#!/usr/bin/env bash
# logind.sh —— 「谁能唤醒这台机器」的事项清单管理工具(由 logind.nix 包装成命令 awake)
#
# 三个对象:
# ① nix 配置层 logind.nix:logind 睡眠策略 + 唤醒位默认策略(udev)+ 重放单元(无轮询)
# ② 事项清单 logind-data/awake-<机器码>.conf —— 唯一数据源
# 机器码 = /etc/nixos/hardware-configuration.nix 里 fileSystems."/boot" 设备的 UUID(原文大小写)
# nix 读它(rebuild → 本世代快照 → apply 重放运行时);awake list/setting 读它;
# awake sync 把运行时实况写回它 ⇒ 下次 rebuild 成为持久策略
# ③ 系统运行时 设备 <dir>/power/wakeup(enabled/disabled);timer 的 WakeSystem(yes/no)
#
# 清单列(TAB 分隔):path | driver | id | wakeup | desc
# path 设备 = /sys/.../power/wakeup 所在的【设备目录】;timer = systemd unit 名(.timer 结尾)
# driver 设备 = /sys/.../driver 符号链接(回退 uevent 的 DRIVER=,再取不到留空);timer = 固定 timer
# id 自增编号(sync 新增行取 max+1,删行不重排)—— awake on/off 用它
# wakeup 设备 = enabled/disabled(sysfs 原值);timer = yes/no(WakeSystem 原值)
# desc 备注(设备可空;timer 默认取 unit 的 Description)
#
# 命令:
# awake list 运行时全量 + 清单注解:path | driver | 运行时 | last_time | id | conf.wakeup | conf.desc
# awake setting 清单的两组(能唤醒 / 不能唤醒)
# awake on|off <id…> 只改【运行时】:设备写 sysfs;timer 写 /run 下 drop-in + daemon-reload
# awake sync 运行时 → 清单(更新已有行;清单里没有的运行时项插入新行 + 分配 id)
# awake log [N] [-f] 看唤醒日志
# 内部动词(供 nix 侧与睡眠钩子调用):
# awake --apply 按【本世代清单快照】($AWAKE_SNAPSHOT)重放运行时
# awake --snapshot 睡前快照唤醒源计数 awake --record-wake 醒后记账
#
# 退出码:0 成功 / 1 用法或参数错 / 2 需要 root
set -uo pipefail
DATA_DIR="${AWAKE_DATA_DIR:-/etc/nixos/modules/system/service/logind-data}"
HWCONF="${AWAKE_HWCONF:-/etc/nixos/hardware-configuration.nix}"
LOG="${AWAKE_LOG:-/var/log/logind-awake.log}"
RUN="${AWAKE_RUN:-/run/logind-awake}"
SNAPSHOT="${AWAKE_SNAPSHOT:-}" # nix 注入的 store 快照;只有 --apply 用它
DROPIN=/run/systemd/system # timer 的运行时改动落这里(tmpfs ⇒ 重启自清)
die() { printf 'awake: %s\n' "$*" >&2; exit 1; }
need_root() { [ "$(id -u)" = 0 ] || { printf 'awake: %s 需要 root —— 请用 sudo awake %s\n' "$1" "$2" >&2; exit 2; }; }
usage() {
cat <<'EOF'
awake —— 管理「谁能唤醒这台机器」的事项清单(清单文件 + 系统运行时)
awake list 运行时全量清单(含清单注解列;能唤醒的在前)
awake setting 读清单:两组(能唤醒 / 不能唤醒)
sudo awake on <id…> 设为「能唤醒」—— 只改运行时,不动清单
sudo awake off <id…> 设为「不能唤醒」—— 只改运行时,不动清单
sudo awake sync 把运行时实况写回清单(新项插入并分配 id)⇒ 下次 rebuild 成为持久策略
awake log [N] [-f] 唤醒日志(睡多久 + 唤醒原因)
机制:清单只有一个文件 logind-data/awake-<机器码>.conf
(机器码 = hardware-configuration.nix 里 fileSystems."/boot" 的 UUID,原文大小写)。
nix 读它进本世代快照、rebuild 时按它重放运行时;on/off 只改运行时;
sync 把运行时写回清单。设备写 sysfs 的 power/wakeup;timer 写 /run 下 drop-in。
需要 root 的:on / off / sync(写 sysfs、/run 与清单文件)。
EOF
}
# ───────────────────────── 路径与取值 ─────────────────────────
SRC_CACHE=''
machine_id() { # 机器码 = /boot 分区设备的 UUID(原文大小写;取自同一个文件,nix/sh 同一条规则)
local id
id=$(grep -A4 'fileSystems\."/boot"' "$HWCONF" 2>/dev/null | grep -o 'by-uuid/[A-Za-z0-9-]*' | head -1 | cut -d/ -f2)
[ -n "$id" ] || die "取不到机器码:$HWCONF 里没有 fileSystems.\"/boot\" 的 by-uuid 设备"
printf '%s' "$id"
}
src() { # 源清单文件(命令面读写的那个)
[ -n "$SRC_CACHE" ] && { printf '%s' "$SRC_CACHE"; return; }
SRC_CACHE="$DATA_DIR/awake-$(machine_id).conf"
printf '%s' "$SRC_CACHE"
}
conf_rows() { grep -vE '^[[:space:]]*(#|$)' "${1:-$(src)}" 2>/dev/null; } # $1 = 文件
is_timer() { case "$1" in *.timer) return 0;; *) return 1;; esac; }
dev_wake() { cat "$1/power/wakeup" 2>/dev/null; }
dev_last_ms() { cat "$1/power/wakeup_last_time_ms" 2>/dev/null; }
dev_driver() {
local d=$1 drv=''
[ -e "$d/driver" ] && drv=$(basename "$(readlink -f "$d/driver" 2>/dev/null)" 2>/dev/null)
[ -n "$drv" ] || drv=$(awk -F= '/^DRIVER=/{print $2; exit}' "$d/uevent" 2>/dev/null)
printf '%s' "$drv"
}
timer_wake() { systemctl show "$1" -p WakeSystem --value 2>/dev/null; }
unit_loaded() { [ "$(systemctl show "$1" -p LoadState --value 2>/dev/null)" = loaded ]; }
runtime_state() { if is_timer "$1"; then timer_wake "$1"; else dev_wake "$1"; fi; } # $1 = anchor
runtime_driver() { if is_timer "$1"; then printf 'timer'; else dev_driver "$1"; fi; }
runtime_anchors() { # 运行时全量:设备目录 + timer unit
local p u
while IFS= read -r p; do [ -n "$p" ] && printf '%s\n' "${p%/power/wakeup}"; done \
< <(find /sys/bus/*/devices/*/power/wakeup /sys/class/power_supply/*/power/wakeup 2>/dev/null)
while IFS= read -r u; do [ -n "$u" ] && printf '%s\n' "$u"; done \
< <(systemctl list-units --type=timer --all --no-legend --plain 2>/dev/null | awk '{print $1}')
}
ms_to_time() { # 自开机起的毫秒 → yyyy-MM-dd HH:MM:SS;空/0 → null
local ms=$1 up now
{ [ -n "$ms" ] && [ "$ms" != 0 ]; } || { printf 'null'; return; }
up=$(cut -d' ' -f1 /proc/uptime); now=$(date +%s)
date -d "@$(awk -v n="$now" -v u="$up" -v m="$ms" 'BEGIN{printf "%d", n-u+m/1000}')" '+%Y-%m-%d %H:%M:%S'
}
conf_lookup() { # $1 = anchor, $2 = 文件 → 打印该行(TAB 分隔);无则返回 1
awk -F'\t' -v a="$1" '!/^[[:space:]]*#/ && NF>=5 && $1==a {print; found=1} END{exit found?0:1}' "$2" 2>/dev/null
}
conf_maxid() { awk -F'\t' '!/^[[:space:]]*#/ && NF>=5 {if ($3+0>m) m=$3+0} END{print m+0}' "$1" 2>/dev/null; }
conf_proj() { # 清单行投影成 '|' 分隔(空字段必须保住 —— bash 的 IFS=TAB 会把连续 TAB 折叠)
awk -F'\t' -v OFS='|' '!/^[[:space:]]*#/ && NF>=5 {print $1,$2,$3,$4,$5}' "${1:-$(src)}" 2>/dev/null
}
# ───────────────────────── awake list ─────────────────────────
cmd_list() {
local SRC; SRC=$(src)
printf '%-46s %-13s %-9s %-19s %-4s %-9s %s\n' path driver 运行时 last_time id conf.wakeup conf.desc
printf '%-46s %-13s %-9s %-19s %-4s %-9s %s\n' \
---------------------------------------------- ------------- --------- ------------------- ---- --------- --------------------------------
local a st drv last row id cw cd key line out=''
while IFS= read -r a; do
[ -n "$a" ] || continue
st=$(runtime_state "$a"); drv=$(runtime_driver "$a")
if is_timer "$a"; then last=null; else last=$(ms_to_time "$(dev_last_ms "$a")"); fi
id=null; cw=null; cd=null
if row=$(conf_lookup "$a" "$SRC"); then
id=$(printf '%s' "$row" | cut -f3); cw=$(printf '%s' "$row" | cut -f4); cd=$(printf '%s' "$row" | cut -f5)
[ -n "$id" ] || id=null; [ -n "$cw" ] || cw=null; [ -n "$cd" ] || cd=null
fi
[ -n "$st" ] || st='—(不在位)'
if [ "$st" = enabled ] || [ "$st" = yes ]; then key=0; else key=1; fi
out="$out$key|$a|$drv|$st|$last|$id|$cw|$cd"$'\n'
done < <(runtime_anchors)
printf '%s' "$out" | LC_ALL=C sort -s -t'|' -k1,1n -k2,2 | while IFS='|' read -r key a drv st last id cw cd; do
[ -n "$a" ] || continue
printf '%-46s %-13s %-9s %-19s %-4s %-9s %s\n' "$a" "$drv" "$st" "$last" "$id" "$cw" "$cd"
done
# 清单里有、运行时不在位(拔掉的设备 / 卸载的 timer)
local some=0
while IFS='|' read -r a drv id cw cd; do
[ -n "${a:-}" ] || continue
if is_timer "$a"; then unit_loaded "$a" && continue
else [ -e "$a/power/wakeup" ] && continue; fi
if [ "$some" = 0 ]; then printf '\n清单里有、运行时不在位:\n'; some=1; fi
printf '%-46s %-13s %-9s %-19s %-4s %-9s %s\n' "$a" "$drv" '—(不在位)' null "$id" "$cw" "$cd"
done < <(conf_proj "$SRC")
# 运行时能唤醒、但清单里没有(未登记)—— 单独点名,避免漏网
local miss=''
while IFS= read -r a; do
[ -n "$a" ] || continue
case "$(runtime_state "$a")" in enabled|yes) ;; *) continue;; esac
conf_lookup "$a" "$SRC" >/dev/null && continue
miss="$miss$a"$'\n'
done < <(runtime_anchors)
if [ -n "$miss" ]; then
printf '\n⚠ 运行时能唤醒、但清单里还没登记(先 sudo awake sync 登记后才能按 id 操作):\n'
printf '%s' "$miss" | while IFS= read -r a; do [ -n "$a" ] && printf ' %s\n' "$a"; done
fi
# 定时器总闸:有几个 timer 设了 WakeSystem=yes
local n=0 yes=0 u
while IFS= read -r u; do
[ -n "$u" ] || continue; n=$((n+1))
[ "$(timer_wake "$u")" = yes ] && yes=$((yes+1))
done < <(systemctl list-units --type=timer --all --no-legend --plain 2>/dev/null | awk '{print $1}')
printf '\n定时器总闸:%s 个 timer 中 %s 个能唤醒(WakeSystem=yes)%s\n' "$n" "$yes" \
"$([ "$yes" = 0 ] && printf '—— 合规(无定时任务能唤醒)' || printf '—— 不合规')"
printf '(清单:%s)\n' "$SRC"
}
# ───────────────────────── awake setting ─────────────────────────
cmd_setting() {
local SRC; SRC=$(src)
echo "清单($SRC):"
local a drv id w cd on=0 off=0
printf '\n【能唤醒】\n'
while IFS='|' read -r a drv id w cd; do
[ -n "${a:-}" ] || continue
case "$w" in enabled|yes) printf ' %-4s %-9s %s%s\n' "$id" "$w" "$a" "${cd:+ ($cd)}"; on=$((on+1));; esac
done < <(conf_proj "$SRC")
[ "$on" = 0 ] && printf ' (空)\n'
printf '\n【不能唤醒】\n'
while IFS='|' read -r a drv id w cd; do
[ -n "${a:-}" ] || continue
case "$w" in disabled|no) printf ' %-4s %-9s %s%s\n' "$id" "$w" "$a" "${cd:+ ($cd)}"; off=$((off+1));; esac
done < <(conf_proj "$SRC")
[ "$off" = 0 ] && printf ' (空)\n'
printf '\n合计 %s 项(能唤醒 %s / 不能 %s)\n' "$((on+off))" "$on" "$off"
}
# ───────────────────── 写运行时(设备 / timer)─────────────────────
set_runtime() { # $1 = anchor $2 = on|off → 0 改到 / 1 运行时不在位 / 2 真失败
local a=$1 mode=$2 target
if is_timer "$a"; then
if [ "$mode" = on ]; then target=yes; else target=no; fi
unit_loaded "$a" || { printf '运行时未在位(unit 未加载)'; return 1; }
[ "$(timer_wake "$a")" = "$target" ] && return 0 # 已一致 ⇒ 不落 drop-in、不 daemon-reload
local d="$DROPIN/$a.d"
mkdir -p "$d" || { printf '建不了 %s' "$d"; return 2; }
printf '[Timer]\nWakeSystem=%s\n' "$target" > "$d/50-awake.conf" || { printf '写 %s/50-awake.conf 失败' "$d"; return 2; }
systemctl daemon-reload >/dev/null 2>&1 || { printf 'daemon-reload 失败'; return 2; }
[ "$(timer_wake "$a")" = "$target" ] || { printf '写入后读回不是 %s(可能需 systemctl restart %s)' "$target" "$a"; return 2; }
return 0
fi
if [ "$mode" = on ]; then target=enabled; else target=disabled; fi
local f="$a/power/wakeup"
[ -e "$f" ] || { printf '运行时未在位(无 %s)' "$f"; return 1; }
[ -w "$f" ] || { printf '%s 不可写(器件不声明 remote wakeup?)' "$f"; return 1; }
printf '%s' "$target" > "$f" 2>/dev/null || { printf '写 %s 失败' "$f"; return 2; }
[ "$(dev_wake "$a")" = "$target" ] || { printf '写入后读回不是 %s' "$target"; return 2; }
return 0
}
# ───────────────────────── awake on / off ─────────────────────────
cmd_toggle() { # $1 = on|off $2.. = conf.id
local mode=$1; shift
[ $# -gt 0 ] || die "用法:sudo awake $mode <id…>(id 见 awake list / setting)"
need_root "$mode" "$mode $*"
local SRC; SRC=$(src)
local id row a note rep ok=0 bad=0 miss=0
for id in "$@"; do
case "$id" in *[!0-9]*) die "id 必须是数字:$id";; esac
row=$(awk -F'\t' -v n="$id" '!/^[[:space:]]*#/ && NF>=5 && $3+0==n {print; exit}' "$SRC")
if [ -z "$row" ]; then printf ' [%s] ✗ 清单里没有这个 id\n' "$id"; bad=$((bad+1)); continue; fi
a=$(printf '%s' "$row" | cut -f1)
note=$(set_runtime "$a" "$mode"); rep=$?
if [ "$rep" = 0 ]; then
printf ' [%s] ✓ %s → %s\n' "$id" "$a" "$([ "$mode" = on ] && printf '能唤醒' || printf '不能唤醒')"
ok=$((ok+1))
elif [ "$rep" = 1 ]; then
printf ' [%s] ↷ %s —— %s\n' "$id" "$a" "$note"; miss=$((miss+1))
else
printf ' [%s] ✗ %s —— %s\n' "$id" "$a" "$note" >&2; bad=$((bad+1))
fi
done
printf '合计:成功 %s,未在位而跳过 %s,失败 %s(清单未改动;满意后跑 sudo awake sync 固化)\n' "$ok" "$miss" "$bad"
[ "$bad" = 0 ] || return 2
}
# ───────────────────────── awake sync ─────────────────────────
cmd_sync() {
need_root sync sync
local SRC; SRC=$(src)
[ -f "$SRC" ] || die "清单不存在:$SRC"
cp -f "$SRC" "$SRC.bak-$(date +%Y%m%d-%H%M%S)" || die "备份清单失败($SRC)"
local hdr rows='' a drv id w cd st newid upd=0 add=0 same=0 have line
hdr=$(awk 'BEGIN{p=1} /^[[:space:]]*#/ || /^[[:space:]]*$/ {if(p) print; next} {p=0}' "$SRC")
newid=$(conf_maxid "$SRC")
# ① 已有行:运行时值不同则更新(保留 id / desc / 行序)
while IFS='|' read -r a drv id w cd; do
[ -n "${a:-}" ] || continue
st=$(runtime_state "$a")
if [ -z "$st" ] || [ "$st" = "$w" ]; then
printf -v line '%s\t%s\t%s\t%s\t%s' "$a" "$drv" "$id" "$w" "$cd"; rows="$rows$line"$'\n'; same=$((same+1))
else
printf ' ✎ %s:%s → %s\n' "$a" "$w" "$st"
printf -v line '%s\t%s\t%s\t%s\t%s' "$a" "$drv" "$id" "$st" "$cd"; rows="$rows$line"$'\n'; upd=$((upd+1))
fi
done < <(conf_proj "$SRC")
# ② 运行时新项:插入新行(分配 id;driver 取运行时值;desc 留空)
while IFS= read -r a; do
[ -n "$a" ] || continue
have=$(awk -F'\t' -v x="$a" '!/^[[:space:]]*#/ && NF>=5 && $1==x {print 1; exit}' "$SRC")
[ -n "$have" ] && continue
newid=$((newid+1)); drv=$(runtime_driver "$a"); st=$(runtime_state "$a")
printf ' ➕ 新登记 [%s] %s(%s)\n' "$newid" "$a" "$st"
printf -v line '%s\t%s\t%s\t%s\t' "$a" "$drv" "$newid" "$st"; rows="$rows$line"$'\n'; add=$((add+1))
done < <(runtime_anchors)
printf '%s\n' "$hdr" > "$SRC"
printf '%s' "$rows" >> "$SRC"
awk -F'\t' '!/^[[:space:]]*#/ && NF>0 && NF<5 {print "awake: 清单第 "NR" 行字段不足 5 列" > "/dev/stderr"; bad=1} END{exit bad?1:0}' "$SRC" \
|| die "写回后清单有非法行(备份在 $SRC.bak-*)"
printf '已写回:更新 %s 行,新登记 %s 行,未变 %s 行(备份 %s.bak-*)\n' "$upd" "$add" "$same" "$SRC"
}
# ───────────────────── awake --apply(按清单重放)─────────────────────
cmd_apply() {
[ "$(id -u)" = 0 ] || die "--apply 需要 root"
local f="${SNAPSHOT:-$(src)}"
[ -f "$f" ] || die "清单不存在:$f"
local a drv id w cd note rep ok=0 skip=0 bad=0
while IFS='|' read -r a w id; do
[ -n "${a:-}" ] || continue
case "$w" in
enabled|yes) note=$(set_runtime "$a" on);;
disabled|no) note=$(set_runtime "$a" off);;
*) printf 'awake: 第 %s 行状态值非法(%s)—— 跳过\n' "${id:-?}" "$w" >&2; skip=$((skip+1)); continue;;
esac
rep=$?
if [ "$rep" = 0 ]; then ok=$((ok+1))
elif [ "$rep" = 1 ]; then skip=$((skip+1)); printf 'awake: 第 %s 行跳过 —— %s\n' "$id" "$note" >&2
else bad=$((bad+1)); printf 'awake: 第 %s 行重放失败 —— %s\n' "$id" "$note" >&2
fi
done < <(awk -F'\t' -v OFS='|' '!/^[[:space:]]*#/ && NF>=5 {print $1,$4,$3}' "$f" 2>/dev/null)
printf 'awake: 已按 %s 重放运行时(生效 %s 项,运行时无对象而跳过 %s 项,失败 %s 项)\n' "$f" "$ok" "$skip" "$bad"
[ "$bad" = 0 ] || return 2
}
# ───────────────────────── 唤醒日志 ─────────────────────────
cmd_log() {
local n=30 follow=0
case "${1:-}" in
'') ;;
-f) n=10; follow=1;;
*[!0-9]*) die "用法:awake log [数字] | awake log -f";;
*) n=$1;;
esac
[ -f "$LOG" ] || { echo "还没有唤醒日志($LOG 不存在)—— 睡一次醒来就会有。"; return 0; }
if [ "$follow" = 1 ]; then tail -n "$n" -f "$LOG"; else tail -n "$n" "$LOG"; fi
}
cmd_snapshot() {
mkdir -p "$RUN" 2>/dev/null
# 唤醒源计数只在 debugfs 里;本机 debugfs 不是声明式挂载的 ⇒ 用时自挂(幂等)
if [ ! -r /sys/kernel/debug/wakeup_sources ]; then
mountpoint -q /sys/kernel/debug 2>/dev/null || mount -t debugfs none /sys/kernel/debug 2>/dev/null
fi
cp -f /sys/kernel/debug/wakeup_sources "$RUN/wakeup_sources.before" 2>/dev/null
# ACPI GPE 计数(不依赖 debugfs 的第二证据源;带名字,便于做差)
acpi_dump() {
local f
for f in /sys/firmware/acpi/interrupts/*; do
[ -r "$f" ] || continue
printf '%s %s\n' "$(basename "$f")" "$(awk 'NR==1{print $1}' "$f" 2>/dev/null)"
done
}
acpi_dump >"$RUN/acpi.before" 2>/dev/null
date +%s >"$RUN/t0" 2>/dev/null
}
cmd_record() {
local t0 slept entry irq irqname reason gpe f
t0=$(cat "$RUN/t0" 2>/dev/null || echo 0)
mkdir -p "$(dirname "$LOG")" 2>/dev/null
slept='?'
if [ "$t0" != 0 ]; then
entry=$(journalctl -b -k -o short-unix --no-pager 2>/dev/null | grep 'PM: suspend entry' | tail -1 | cut -d. -f1)
[ -n "$entry" ] && slept=$(( $(date +%s) - entry ))
fi
irq=$(cat /sys/power/pm_wakeup_irq 2>/dev/null || echo '?')
irqname=$(awk -v n="$irq" '$1 == n":" {print $NF; exit}' /proc/interrupts 2>/dev/null)
reason=$(awk '
NR==FNR { for (i=1;i<=NF;i++) b[$1" "i]=$i; next }
FNR==1 { next }
{ for (i=2;i<=NF;i++) { d=$i-(b[$1" "i]+0); if (d!=0) printf "%s%s%+d", (out++?", ":""), $1, d } }
' "$RUN/wakeup_sources.before" /sys/kernel/debug/wakeup_sources 2>/dev/null)
[ -z "$reason" ] && reason='无唤醒源事件增量'
# ACPI GPE 增量(第二证据源,标出是靠哪条 GPE 醒的)
gpe=''
if [ -r "$RUN/acpi.before" ]; then
local now=''
for f in /sys/firmware/acpi/interrupts/*; do
[ -r "$f" ] || continue
now="$now$(basename "$f") $(awk 'NR==1{print $1}' "$f" 2>/dev/null)
"
done
gpe=$(printf '%s' "$now" | awk '
NR==FNR { b[$1]=$2; next }
{ d=$2-(b[$1]+0); if (d>0 && $1 ~ /^(gpe|sci|ff_)/) printf "%s%s%+d", (o++?", ":""), $1, d }
' "$RUN/acpi.before" -)
fi
printf '%s slept=%ss wake_irq=%s%s src: %s%s\n' \
"$(date '+%Y-%m-%dT%H:%M:%S%z')" "$slept" "$irq" "${irqname:+($irqname)}" "$reason" \
"${gpe:+ gpe: $gpe}" >>"$LOG"
}
# ───────────────────────── 派发 ─────────────────────────
case "${1:-}" in
''|-h|--help|help) usage; exit 0;;
list|ls) shift; cmd_list "$@";;
setting|settings) shift; cmd_setting "$@";;
on|enable|run|start|open) shift; cmd_toggle on "$@";;
off|disable|stop|end|close) shift; cmd_toggle off "$@";;
sync) shift; cmd_sync "$@";;
log|logs) shift; cmd_log "$@";;
--apply) shift; cmd_apply "$@";;
--snapshot) shift; cmd_snapshot "$@";;
--record-wake) shift; cmd_record "$@";;
*) die "未知子命令:$1(跑 awake 看说明)";;
esac
/etc/nixos/modules/system/service/logind-data/awake-8BE3-50EF.conf
# awake-8BE3-50EF.conf —— 「谁能唤醒这台机器」的事项清单(唯一数据源,按机器码分文件)
#
# 文件名的机器码 = /etc/nixos/hardware-configuration.nix 里 fileSystems."/boot".device 的 UUID(原文大小写)。
# 两侧同一条规则:nix 侧取 config.fileSystems."/boot".device 的 basename;sh 侧解析同一个文件。
#
# 列(TAB 分隔):path | driver | id | wakeup | desc
# path 设备 = /sys/.../power/wakeup 所在的【设备目录】;timer = systemd unit 名(.timer 结尾)
# driver 设备 = /sys/.../driver 符号链接(取不到回退 uevent 的 DRIVER=,再取不到留空);timer = 固定 timer
# id 自增编号(sync 新增行取 max+1,删行不重排)—— awake on/off 用它
# wakeup 设备 = enabled/disabled(sysfs 原值);timer = yes/no(WakeSystem 原值)
# desc 备注(设备可空;timer 默认取 unit 的 Description)
#
# 语义:on/off 只改运行时(设备写 sysfs、timer 写 /run 下 drop-in);sync 把运行时实况写回本文件
# ⇒ 下次 rebuild 成为持久策略;rebuild 时 logind-awake-apply 按本文件重放到运行时。
/sys/bus/acpi/devices/PNP0C0D:00 button 1 enabled 合盖开关
/sys/bus/pci/devices/0000:65:00.3 xhci_hcd 2 enabled USB 出口 xHCI0(SoC)
/sys/bus/pci/devices/0000:67:00.3 xhci_hcd 3 enabled USB 出口 xHCI3(雷雳坞)
/sys/bus/pci/devices/0000:67:00.4 xhci_hcd 4 enabled USB 出口 xHCI4(雷雳坞)
/sys/bus/serio/devices/serio0 atkbd 5 enabled 内置键盘(PS/2)
/sys/bus/usb/devices/7-1.4.2 usb 6 enabled USB 键鼠(HID 类:键盘/鼠标,当前插在 7-1.4.2)
/sys/bus/acpi/devices/PNP0C0A:00 battery 7 disabled 电池事件
/sys/bus/acpi/devices/PNP0C0C:00 button 8 disabled 电源键(ACPI 固定事件,未必真能屏蔽)
/sys/bus/acpi/devices/PNP0C0E:00 button 9 disabled ACPI 睡眠键
/sys/bus/i2c/devices/i2c-GXTP7385:00 i2c_hid_acpi 10 disabled 触摸板 / 触摸屏
/sys/bus/pci/devices/0000:00:01.2 pcieport 11 disabled
/sys/bus/pci/devices/0000:00:02.2 pcieport 12 disabled 无线网卡根端口
/sys/bus/pci/devices/0000:00:02.3 pcieport 13 disabled 读卡器根端口
/sys/bus/pci/devices/0000:00:02.4 pcieport 14 disabled
/sys/bus/pci/devices/0000:00:04.1 pcieport 15 disabled 空根端口
/sys/bus/pci/devices/0000:00:08.1 pcieport 16 disabled
/sys/bus/pci/devices/0000:00:08.2 pcieport 17 disabled
/sys/bus/pci/devices/0000:00:08.3 pcieport 18 disabled
/sys/bus/pci/devices/0000:01:00.0 nvme 19 disabled
/sys/bus/pci/devices/0000:02:00.0 iwlwifi 20 disabled
/sys/bus/pci/devices/0000:03:00.0 sdhci-pci 21 disabled
/sys/bus/pci/devices/0000:04:00.0 nvme 22 disabled
/sys/bus/pci/devices/0000:65:00.0 amdgpu 23 disabled
/sys/bus/pci/devices/0000:65:00.1 snd_hda_intel 24 disabled
/sys/bus/pci/devices/0000:65:00.2 ccp 25 disabled
/sys/bus/pci/devices/0000:65:00.4 xhci_hcd 26 disabled xHCI1(摄像头 + 指纹所在控制器)
/sys/bus/pci/devices/0000:65:00.6 snd_hda_intel 27 disabled
/sys/bus/pci/devices/0000:66:00.0 28 disabled
/sys/bus/pci/devices/0000:66:00.1 amdxdna 29 disabled
/sys/bus/pci/devices/0000:67:00.0 30 disabled
/sys/bus/pci/devices/0000:67:00.6 thunderbolt 31 disabled 雷雳 NHI
/sys/bus/platform/devices/PNP0B00:00 rtc_cmos 32 disabled RTC 闹钟(定时唤醒)
/sys/bus/platform/devices/alarmtimer.0.auto alarmtimer 33 disabled alarmtimer(事件计时)
/sys/bus/thunderbolt/devices/0-0 34 disabled 雷雳根设备(据点 0-0)
/sys/bus/thunderbolt/devices/domain0 35 disabled 雷雳域(热插拔唤醒)
/sys/bus/usb/devices/1-5 usb 36 disabled 蓝牙适配器(Intel AX210)
/sys/bus/usb/devices/3-1 usb 37 disabled USB 集线器 3-1
/sys/bus/usb/devices/7-1 usb 38 disabled USB 集线器 7-1
/sys/bus/usb/devices/7-1.1 usb 39 disabled USB 集线器 7-1.1
/sys/bus/usb/devices/7-1.4 usb 40 disabled USB 集线器 7-1.4
/sys/bus/usb/devices/usb1 usb 41 disabled USB 根集线器 usb1
/sys/bus/usb/devices/usb2 usb 42 disabled USB 根集线器 usb2
/sys/bus/usb/devices/usb3 usb 43 disabled USB 根集线器 usb3
/sys/bus/usb/devices/usb4 usb 44 disabled USB 根集线器 usb4
/sys/bus/usb/devices/usb5 usb 45 disabled USB 根集线器 usb5
/sys/bus/usb/devices/usb6 usb 46 disabled USB 根集线器 usb6
/sys/bus/usb/devices/usb7 usb 47 disabled USB 根集线器 usb7
/sys/bus/usb/devices/usb8 usb 48 disabled USB 根集线器 usb8
/sys/class/power_supply/ADP1 49 disabled 交流适配器事件(拔插电)
/sys/class/power_supply/hid-0003:17EF:60EE.0015-battery 50 disabled HID 设备电池事件(键鼠电量)
fstrim.timer timer 51 no Discard unused filesystem blocks once a week
fwupd-refresh.timer timer 52 no Refresh fwupd metadata regularly
logrotate.timer timer 53 no
nix-gc.timer timer 54 no
systemd-tmpfiles-clean.timer timer 55 no Daily Cleanup of Temporary Directories