diff --git a/README.md b/README.md index 63a4f4e..a00b972 100644 --- a/README.md +++ b/README.md @@ -1,159 +1,126 @@ -# 小米 Raphael 设备 Linux 系统镜像构建项目 +
-本项目为小米 Raphael(Redmi K20 Pro)专属 Linux 镜像构建项目,提供完整的 Debian / Ubuntu 系统镜像构建脚本与 GitHub Actions 自动化工作流,支持多内核、多桌面、服务器版,开箱即用,适配性完善。 +# Device Linux -## 📌 项目概述 +# 📱 小米 Raphael Linux 镜像构建项目 -项目集成全套自动化构建工具链,支持内核编译、系统镜像打包,覆盖桌面端与服务器端系统,所有镜像均预装适配设备的专属驱动、固件及常用工具,无需额外配置即可正常使用。 +### Redmi K20 Pro · 一键构建 Debian / Ubuntu 系统镜像 -支持构建系统类型: +[![Build](https://img.shields.io/github/actions/workflow/status/GengWei1997/linux-xiaomi-raphael-uboot/build-system.yml?style=flat-square&label=Build&logo=github-actions&logoColor=white)](https://github.com/GengWei1997/linux-xiaomi-raphael-uboot/actions) +[![Release](https://img.shields.io/github/v/release/GengWei1997/linux-xiaomi-raphael-uboot?style=flat-square&label=Release&logo=github&logoColor=white)](https://github.com/GengWei1997/linux-xiaomi-raphael-uboot/releases) +[![Stars](https://img.shields.io/github/stars/GengWei1997/linux-xiaomi-raphael-uboot?style=flat-square&logo=github&logoColor=white)](https://github.com/GengWei1997/linux-xiaomi-raphael-uboot/stargazers) -- **Debian 系列**:GNOME 桌面版、Phosh 移动桌面版、Server 无图形服务器版 +--- -- **Ubuntu 系列**:GNOME 桌面版、Phosh 移动桌面版、Server 无图形服务器版 +
-- **定制内核**:编译适配设备 Linux 内核 +## ✨ 项目简介 -## ✅ 设备适配状态 +本项目为 **小米 Raphael(Redmi K20 Pro)** 专属 Linux 镜像构建方案,提供完整的 Debian / Ubuntu 系统镜像构建脚本与 GitHub Actions 自动化工作流。支持多内核版本、多桌面环境,开箱即用。 -当前设备硬件适配完整,主流功能全部可用: +## 📋 支持系统类型 -- 网络:2.4G/5G 双频 Wi-Fi、USB NCM 网络 +| 系统标识 | 桌面环境 | 发行版 | 镜像大小 | +|:---:|:---:|:---:|:---:| +| `debian-server` | 无(纯命令行) | Debian trixie | 3G | +| `debian-gnome` | GNOME | Debian trixie | 6G | +| `debian-phosh` | Phosh 移动端桌面 | Debian trixie | 6G | +| `ubuntu-server` | 无(纯命令行) | Ubuntu resolute | 3G | +| `ubuntu-gnome` | GNOME | Ubuntu resolute | 6G | +| `ubuntu-phosh` | Phosh 移动端桌面 | Ubuntu resolute | 6G | -- 外设:蓝牙(文件传输/音频输出)、USB SSH/OTG 功能、触摸屏、手电筒(支持亮度调节) +## 🖥️ 设备硬件适配 -- 基础硬件:屏幕显示、电池检测、实时时钟、GPU 渲染、FDE 加密、音频(耳机/扬声器输出) - -## 📊 版本支持矩阵 - -### 系统类型对照表 - -|系统标识|桌面环境|基础发行版| -|---|---|---| -|debian-server|无(纯命令行)|Debian| -|debian-gnome|GNOME|Debian| -|debian-phosh|Phosh 移动端桌面|Debian| -|ubuntu-server|无(纯命令行)|Ubuntu| -|ubuntu-gnome|GNOME|Ubuntu| -|ubuntu-phosh|Phosh 移动端桌面|Ubuntu| - -### 系统与内核版本 - -- **Debian 版本**:trixie(默认最新) - -- **Ubuntu 版本**:resolute(默认最新) - -- **内核版本**:6.18、7.0(双版本可选) - -### Phosh 桌面变体 - -- `phosh-core`:基础 Phosh 环境 - -- `phosh-full`:完整的 Phosh 环境 - -- `phosh-phone`:手机优化的 Phosh 环境 +| 分类 | 支持项目 | 状态 | +|:---:|:---|:---:| +| 🌐 **网络** | 2.4G / 5G 双频 Wi-Fi | ✅ | +| 📡 **蓝牙** | 文件传输 · 音频输出 | ✅ | +| 📶 **蜂窝** | 网络支持 | ❌ | +| 🔌 **USB** | NCM 网络共享 · OTG 功能 | ✅ | +| 🖥️ **显示** | 屏幕输出 · GPU 渲染 | ✅ | +| 🔊 **音频** | 扬声器 · 耳机输出 | ✅ | +| 👆 **输入** | 触摸屏 · 闪光灯(手电筒) | ✅ | +| 🔋 **系统** | 电池检测 · 实时时钟 · FDE 加密 | ✅ | ## 🚀 快速上手 ### 方式一:下载预构建镜像 -项目持续自动构建最新镜像,可直接前往 [Releases](https://github.com/GengWei1997/linux-xiaomi-raphael-uboot/releases) 页面下载,无需本地编译。 +前往 [Releases](https://github.com/GengWei1997/linux-xiaomi-raphael-uboot/releases) 页面下载最新镜像,无需本地编译。 -> **⚠️ 大文件提示**:`ubuntu-gnome-6.18` / `ubuntu-gnome-7.0` 镜像体积超过 2GB,未上传至 Releases,需前往项目 Artifacts 下载。 -> -> +> ⚠️ `ubuntu-gnome` 镜像体积超过 2GB,请前往项目 [Actions → Artifacts](https://github.com/GengWei1997/linux-xiaomi-raphael-uboot/actions) 下载。 ### 方式二:GitHub Actions 自定义构建(推荐) -1. Fork 本仓库至个人 GitHub 账号 - -2. 进入仓库 **Actions** 页面,选择「构建系统镜像」工作流 - -3. 点击 **Run workflow**,自定义构建参数: - +1. **Fork** 本仓库至个人 GitHub 账号 +2. 进入 **Actions** 页面 → 选择「构建系统镜像」工作流 +3. 点击 **Run workflow**,自定义参数: - - **构建模式**:`parallel`并行构建全部镜像(默认) / `single` 单独构建指定镜像 +| 参数 | 说明 | 默认值 | +|:---:|:---|:---:| +| 构建模式 | `parallel` 并行构建 / `single` 单独构建 | `parallel` | +| 系统类型 | 支持逗号分隔,留空则全量构建 | 全部 | +| 内核版本 | `6.18` 或 `7.0` | `7.0` | +| 构建工具 | `mmdebstrap` / `debootstrap` | `mmdebstrap` | +| Phosh 变体 | 仅 Phosh 镜像生效 | `phosh-core` | - - **系统类型**:支持多类型逗号分隔,默认全量构建 +4. 等待构建完成,镜像自动发布至 Releases - - **内核版本**:支持 `7.0`(默认7.0可选6.18) +## 📦 镜像特性 - - **构建工具**:`mmdebstrap`(默认) / `debootstrap` - - - **Phosh 变体**:仅 Phosh 桌面镜像生效,默认`phosh-core` - - - **系统版本**:默认 Debian: trixie、Ubuntu: resolute - -4. 等待工作流执行完成,镜像自动打包发布至仓库 Releases - -## 📦 镜像通用特性 - -### 全版本通用 - -- 默认配置**清华软件源** - -- 预装简体中文语言包、中国标准时区,开箱汉化 +### 🌐 全版本通用 +- 默认配置 **清华软件源** +- 预装简体中文语言包 + 中国标准时区,开箱汉化 - 支持 USB NCM 网络共享,电脑直连设备 SSH - - 内置 SSH 服务,支持 root / 普通用户远程登录 +- 支持 **一键内核更新脚本**,在线升级定制内核 -- 支持**一键内核更新脚本**,可在线升级定制内核 - -- 默认账号密码: - +| 账户 | 用户名 | 密码 | +|:---:|:---:|:---:| +| 普通用户 | `user` | `1234` | +| 超级用户 | `root` | `1234` | - - 普通用户:`user` / `1234` +> 📌 USB NCM 连接时,设备 IP:`172.16.42.1`,SSH 连接:`ssh user@172.16.42.1` - - 超级用户:`root` / `1234` - -- 设备默认 IP:`172.16.42.1`,SSH 连接命令:`ssh user@172.16.42.1` - -### 桌面版专属特性 +### 🎨 桌面版专属 - GNOME / Phosh 双桌面环境可选,适配桌面、移动两种使用场景 -- 已知问题:**GNOME 桌面电源键无法息屏**,后续版本持续修复 - -### 服务器版专属特性 +### 🖥️ 服务器版专属 - 内置网络管理器,支持有线、Wi-Fi、USB 多种联网方式 - - 开机 15 秒自动熄屏,降低设备功耗 +- 自定义快捷命令:`leijun` 关闭屏幕 · `jinfan` 点亮屏幕 -- 自定义快捷命令:`leijun` 关闭屏幕、`jinfan` 点亮屏幕 - -## ⬆️ 内核更新教程(只做内核调试用,非必要无需更新) +## ⬆️ 内核更新 -项目提供一键内核升级脚本,建议**root 权限**执行,快速更新设备定制内核: +
+⚠️ 仅做内核调试用,非必要无需更新 -官方原始链接: - -```Plain Text -sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/GengWei1997/kernel-deb/refs/heads/main/Update-kernel.sh)" -``` - -国内加速链接: - -```Plain Text -sudo bash -c "$(curl -fsSL https://ghfast.top/https://raw.githubusercontent.com/GengWei1997/kernel-deb/refs/heads/main/ghproxy-Update-kernel.sh)" -``` +建议 **root 权限** 执行,完成后重启设备即可生效。 -脚本执行完成后,重启设备即可生效新内核。 +| 链接 | 命令 | +|:---:|:---| +| 🌏 国内加速 | `sudo bash -c "$(curl -fsSL https://ghfast.top/https://raw.githubusercontent.com/GengWei1997/kernel-deb/refs/heads/main/ghproxy-Update-kernel.sh)"` | +| 🌐 原始链接 | `sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/GengWei1997/kernel-deb/refs/heads/main/Update-kernel.sh)"` | -## 🔧 设备安装教程 +
-### 前置准备 +## 🔧 安装教程 -1. 设备已完成 **Bootloader 解锁** +### 前置条件 -2. 在电脑上安装 `adb`、`fastboot` 刷机工具,并配置环境变量 - -3. 解压下载的 `.7z` 镜像压缩包,获取 `rootfs.img`、`xiaomi-k20pro-boot.img` 下载[u-boot.img](https://github.com/GengWei1997/linux-xiaomi-raphael-uboot/releases/tag/v1.0.0)(选择最近日期版本) +- ✅ 设备已完成 **Bootloader 解锁** +- ✅ 电脑已安装 `adb` / `fastboot` 工具 +- ✅ 下载镜像压缩包,解压获取: + - `rootfs.img` — 系统镜像 + - `xiaomi-k20pro-boot.img` — 内核镜像 + - [u-boot.img](https://github.com/GengWei1997/linux-xiaomi-raphael-uboot/releases/tag/v1.0.0) — U-Boot 引导 ### 刷机命令 -```Plain Text +```bash # 1. 进入 Fastboot 模式 adb reboot bootloader @@ -163,56 +130,110 @@ fastboot erase boot fastboot erase cache fastboot erase userdata -# 3. 刷入 boot 镜像 +# 3. 刷入镜像 fastboot flash cache xiaomi-k20pro-boot.img fastboot flash boot u-boot.img - -# 4. 刷入系统镜像(需要先解压 rootfs.7z) fastboot flash userdata rootfs.img -# 5. 重启设备 +# 4. 重启设备 fastboot reboot ``` -## ❓ 常见问题 FAQ - -- 什么破玩意儿,开机就卡死了???(请拔掉sim卡) - -- **Windows 无法连接设备 CDC NCM 驱动**:参考解决方案视频[BV1tW4y1A79V](https://www.bilibili.com/video/BV1tW4y1A79V/) - -- **Server 版如何联网**: - - - 1. OTG 外接网线,系统自动识别联网 +## ❓ 常见问题 - 2. OTG 外接键盘,终端输入 `nmtui` 可视化连接 Wi-Fi +| 问题 | 解决方案 | +|:---|:---| +| 蜂窝网络支持情况 | 联通 / 电信已支持,移动正在修复中 *[@GavinLiuOnline](https://github.com/GavinLiuOnline)* | +| Windows 无法连接设备 | 参考 [NCM 驱动教程](https://www.bilibili.com/video/BV1tW4y1A79V/) | +| Server 版如何联网 | OTG 外接网线自动联网 · OTG 外接键盘输入 `nmtui` 连接 Wi-Fi | - 3. USB 连接电脑,安装 NCM 驱动后,通过 `nmtui` 配置网络 +--- ## 🙏 致谢 -本项目基于众多开源项目与开发者成果开发,排名不分先后,特此致谢: - -- Linux 内核官方开发团队、Debian / Ubuntu 开源社区、Phosh 桌面开发团队 - -- [@degdag](https://github.com/degdag) - -- [@dabao1955](https://github.com/dabao1955) - -- [@qaz6750](https://github.com/qaz6750) - -- [@GavinLiuOnline](https://github.com/GavinLiuOnline) - -- [@umeiko](https://github.com/umeiko) - -- [@yuweiyuan8](https://github.com/yuweiyuan8) - -- [@璀璨梦星](https://github.com/ccmx200) - -- [@map220v](https://github.com/map220v) - -- [@Pc1598](https://github.com/Pc1598) - -- [Aospa\-raphael\-unofficial/linux](https://github.com/Aospa-raphael-unofficial/linux)、[sm8150\-mainline/linux](https://gitlab.postmarketos.org/soc/qualcomm-sm8150/linux):内核源码支持 - -- 所有开源贡献者与项目使用者 +感谢以下开源项目与开发者的支持(排名不分先后): + + + + + + + + + + + + + + + + + +
+ +
+ degdag +
+
+ +
+ dabao1955 +
+
+ +
+ qaz6750 +
+
+ +
+ GavinLiuOnline +
+
+ +
+ umeiko +
+
+ +
+ yuweiyuan8 +
+
+ +
+ 璀璨梦星 +
+
+ +
+ map220v +
+
+ +
+ Pc1598 +
+
+ +
+ Aospa-raphael +
+
+ +
+ sm8150-mainline +
+
+ +

+ Linux 内核官方开发团队 · Debian / Ubuntu 开源社区 · Phosh 桌面开发团队 · 以及所有开源贡献者 +

+ +--- + +
+ +**如果这个项目对你有帮助,请给一个 ⭐ Star 支持一下!** + +
diff --git a/build.sh b/build.sh index 8849f70..e5b0e96 100644 --- a/build.sh +++ b/build.sh @@ -104,10 +104,11 @@ echo "[$(date +'%Y-%m-%d %H:%M:%S')] ========================================== "$SCRIPT_DIR/scripts/11-config-fstab.sh" "$SCRIPT_DIR/scripts/12-create-users.sh" "$SCRIPT_DIR/scripts/13-config-power.sh" -"$SCRIPT_DIR/scripts/14-config-zram.sh" -"$SCRIPT_DIR/scripts/15-config-audio.sh" -"$SCRIPT_DIR/scripts/16-cleanup.sh" -"$SCRIPT_DIR/scripts/17-finalize.sh" +"$SCRIPT_DIR/scripts/14-config-power-key.sh" +"$SCRIPT_DIR/scripts/15-config-zram.sh" +"$SCRIPT_DIR/scripts/16-config-audio.sh" +"$SCRIPT_DIR/scripts/17-cleanup.sh" +"$SCRIPT_DIR/scripts/18-finalize.sh" echo "[$(date +'%Y-%m-%d %H:%M:%S')] ========================================== 🎉 构建完成 🎉 ==========================================" echo "" diff --git a/scripts/14-config-power-key.sh b/scripts/14-config-power-key.sh new file mode 100644 index 0000000..2c10c5b --- /dev/null +++ b/scripts/14-config-power-key.sh @@ -0,0 +1,345 @@ +#!/bin/bash +set -e + +if [ "$DESKTOP_ENV" != "gnome" ]; then + echo "[$(date +'%Y-%m-%d %H:%M:%S')] [14] ⏭️ 非 GNOME 桌面,跳过电源键配置" + exit 0 +fi + +echo "[$(date +'%Y-%m-%d %H:%M:%S')] [14] 🔘 配置电源键(短按熄屏 / 长按 3s GNOME 关机菜单)" + +echo "[$(date +'%Y-%m-%d %H:%M:%S')] [14] └─ 禁用 systemd 电源键行为" +install -d rootdir/etc/systemd/logind.conf.d +cat > rootdir/etc/systemd/logind.conf.d/raphael-power-key.conf << 'EOF' +[Login] +HandlePowerKey=ignore +HandlePowerKeyLongPress=ignore +PowerKeyIgnoreInhibited=yes +EOF + +echo "[$(date +'%Y-%m-%d %H:%M:%S')] [14] └─ 安装电源键守护进程" +install -d rootdir/usr/local/sbin +cat > rootdir/usr/local/sbin/raphael-power-key-wait.sh << 'WEOF' +#!/bin/sh +USER_NAME="${USER_NAME:-user}" +RUN_UID=$(id -u "$USER_NAME" 2>/dev/null) || exit 1 +RUN="/run/user/$RUN_UID" +for i in $(seq 1 120); do + if [ -S "$RUN/bus" ] && pgrep -u "$USER_NAME" -x gnome-shell >/dev/null 2>&1; then + sleep 3 + exit 0 + fi + sleep 1 +done +echo "raphael-power-key-wait: timeout waiting for $USER_NAME session" >&2 +exit 1 +WEOF +chmod 755 rootdir/usr/local/sbin/raphael-power-key-wait.sh + +cat > rootdir/usr/local/sbin/raphael-power-key.py << 'PYEOF' +#!/usr/bin/env python3 +import fcntl +import logging +import os +import pwd +import select +import struct +import subprocess +import sys +import threading +import time +from pathlib import Path + +EV_KEY = 0x01 +KEY_POWER = 116 +EVENT_FMT = "llHHi" +EVENT_SIZE = struct.calcsize(EVENT_FMT) +LONG_PRESS_SEC = 3.0 +USER_NAME = os.environ.get("USER_NAME", "user") + +MUTTER_BUS = "org.gnome.Mutter.DisplayConfig" +MUTTER_PATH = "/org/gnome/Mutter/DisplayConfig" +MUTTER_IFACE = "org.gnome.Mutter.DisplayConfig" +POWER_PROP = "PowerSaveMode" + +logging.basicConfig( + level=logging.INFO, + format="raphael-power-key: %(message)s", + stream=sys.stdout, +) +log = logging.getLogger("raphael-power-key") + + +def find_power_input(): + base = Path("/sys/class/input") + for name_path in sorted(base.glob("input*/name")): + name = name_path.read_text().strip() + if name == "pm8941_pwrkey": + num = name_path.parent.name.replace("input", "") + dev = Path(f"/dev/input/event{num}") + if dev.exists(): + return dev + return Path("/dev/input/event0") + + +def user_env(): + uid = pwd.getpwnam(USER_NAME).pw_uid + runtime = Path(f"/run/user/{uid}") + env = os.environ.copy() + env.update({ + "HOME": f"/home/{USER_NAME}", + "USER": USER_NAME, + "LOGNAME": USER_NAME, + "XDG_RUNTIME_DIR": str(runtime), + "DBUS_SESSION_BUS_ADDRESS": f"unix:path={runtime / 'bus'}", + }) + for disp in ("wayland-0", "wayland-1"): + if (runtime / disp).exists(): + env["WAYLAND_DISPLAY"] = disp + break + return env + + +def run_as_user(args): + env = user_env() + env_cmd = [ + "env", + f"HOME={env['HOME']}", + f"USER={env['USER']}", + f"LOGNAME={env['LOGNAME']}", + f"XDG_RUNTIME_DIR={env['XDG_RUNTIME_DIR']}", + f"DBUS_SESSION_BUS_ADDRESS={env['DBUS_SESSION_BUS_ADDRESS']}", + ] + if "WAYLAND_DISPLAY" in env: + env_cmd.append(f"WAYLAND_DISPLAY={env['WAYLAND_DISPLAY']}") + return subprocess.run( + ["runuser", "-u", USER_NAME, "--", *env_cmd, *args], + check=False, + capture_output=True, + text=True, + ) + + +def get_power_save_mode(): + r = run_as_user( + ["busctl", "--user", "get-property", + MUTTER_BUS, MUTTER_PATH, MUTTER_IFACE, POWER_PROP] + ) + if r.returncode != 0: + log.warning("get PowerSaveMode failed: %s", r.stderr.strip()) + return 0 + return int(r.stdout.split()[1]) + + +def set_power_save_mode(mode): + r = run_as_user( + ["busctl", "--user", "set-property", + MUTTER_BUS, MUTTER_PATH, MUTTER_IFACE, POWER_PROP, "i", str(mode)] + ) + if r.returncode != 0: + log.warning("set PowerSaveMode failed: %s", r.stderr.strip()) + return False + return True + + +def toggle_screen(): + try: + current = get_power_save_mode() + target = 0 if current else 1 + log.info("toggle screen PowerSaveMode %s -> %s", current, target) + set_power_save_mode(target) + except Exception as exc: + log.error("toggle_screen failed: %s", exc) + + +def show_shutdown_dialog(): + log.info("show GNOME native shutdown dialog") + r = run_as_user( + ["busctl", "--user", "call", + "org.gnome.SessionManager", + "/org/gnome/SessionManager", + "org.gnome.SessionManager", + "RequestShutdown"] + ) + if r.returncode != 0: + log.warning("RequestShutdown failed: %s", r.stderr.strip()) + run_as_user(["gnome-session-quit", "--power-off"]) + + +def session_ready(): + uid = pwd.getpwnam(USER_NAME).pw_uid + runtime = Path(f"/run/user/{uid}") + bus = runtime / "bus" + if not bus.exists(): + return False + try: + subprocess.run( + ["pgrep", "-u", USER_NAME, "-x", "gnome-shell"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return True + except subprocess.CalledProcessError: + return False + + +def wait_for_session(timeout=120): + log.info("waiting for %s GNOME session", USER_NAME) + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if session_ready(): + time.sleep(3) + log.info("session ready") + return True + time.sleep(1) + log.error("session not ready after %ss", timeout) + return False + + +def grab_device(fd): + EVIOCGRAB = 0x40044590 + try: + fcntl.ioctl(fd, EVIOCGRAB, 1) + return True + except OSError as exc: + log.warning("EVIOCGRAB failed: %s", exc) + return False + + +def main(): + if not wait_for_session(): + sys.exit(1) + + dev = find_power_input() + fd = os.open(str(dev), os.O_RDONLY | os.O_NONBLOCK) + grabbed = grab_device(fd) + if grabbed: + log.info("grabbed input device") + else: + log.warning("initial grab failed, will retry") + log.info("listening on %s", dev) + + press_time = None + long_fired = False + long_timer = None + is_pressed = False + last_grab_try = time.monotonic() + + def cancel_long_timer(): + nonlocal long_timer + if long_timer is not None: + long_timer.cancel() + long_timer = None + + def on_long_press(): + nonlocal long_fired + if not is_pressed: + return + long_fired = True + show_shutdown_dialog() + + while True: + r, _, _ = select.select([fd], [], [], 1.0) + now = time.monotonic() + if not grabbed and now - last_grab_try >= 5: + last_grab_try = now + if grab_device(fd): + grabbed = True + log.info("grabbed input device (retry)") + if not r: + continue + data = os.read(fd, EVENT_SIZE) + if len(data) < EVENT_SIZE: + continue + _sec, _usec, ev_type, code, value = struct.unpack(EVENT_FMT, data) + if ev_type != EV_KEY or code != KEY_POWER: + continue + + log.info("KEY_POWER value=%s", value) + + if value == 1: + if not is_pressed: + is_pressed = True + press_time = time.monotonic() + long_fired = False + cancel_long_timer() + long_timer = threading.Timer(LONG_PRESS_SEC, on_long_press) + long_timer.daemon = True + long_timer.start() + elif value == 0 and press_time is not None: + is_pressed = False + cancel_long_timer() + if not long_fired: + duration = time.monotonic() - press_time + if duration < LONG_PRESS_SEC: + toggle_screen() + press_time = None + + +if __name__ == "__main__": + main() +PYEOF +chmod 755 rootdir/usr/local/sbin/raphael-power-key.py + +echo "[$(date +'%Y-%m-%d %H:%M:%S')] [14] └─ 创建 systemd 服务" +cat > rootdir/etc/systemd/system/raphael-power-key.service << 'EOF' +[Unit] +Description=Raphael power key handler (screen toggle / shutdown menu) +After=gdm.service network.target +Wants=gdm.service + +[Service] +Type=simple +ExecStartPre=/usr/local/sbin/raphael-power-key-wait.sh +ExecStart=/usr/bin/python3 /usr/local/sbin/raphael-power-key.py +Restart=on-failure +RestartSec=5 +TimeoutStopSec=5 +KillMode=mixed + +[Install] +WantedBy=multi-user.target +EOF + +cat > rootdir/etc/systemd/system/raphael-power-key-restart.service << 'EOF' +[Unit] +Description=Restart Raphael power key handler after desktop is up +After=gdm.service + +[Service] +Type=oneshot +ExecStart=/bin/systemctl restart raphael-power-key.service +EOF + +cat > rootdir/etc/systemd/system/raphael-power-key-restart.timer << 'EOF' +[Unit] +Description=Delayed restart of Raphael power key handler + +[Timer] +OnBootSec=45s +Unit=raphael-power-key-restart.service + +[Install] +WantedBy=timers.target +EOF + +echo "[$(date +'%Y-%m-%d %H:%M:%S')] [14] └─ 启用服务" +chroot rootdir systemctl enable raphael-power-key.service +chroot rootdir systemctl enable raphael-power-key-restart.timer + +echo "[$(date +'%Y-%m-%d %H:%M:%S')] [14] └─ 禁用 GNOME 自带电源键处理" +install -d rootdir/etc/dconf/db/local.d rootdir/etc/dconf/profile +cat > rootdir/etc/dconf/db/local.d/01-power-key << 'EOF' +[org/gnome/settings-daemon/plugins/power] +power-button-action='nothing' +EOF +if [ ! -f rootdir/etc/dconf/profile/user ]; then + cat > rootdir/etc/dconf/profile/user << 'EOF' +user-db:user +system-db:local +EOF +fi +chroot rootdir dconf update 2>/dev/null || true + +echo "[$(date +'%Y-%m-%d %H:%M:%S')] [14] ✅ 电源键配置完成" diff --git a/scripts/14-config-zram.sh b/scripts/15-config-zram.sh similarity index 100% rename from scripts/14-config-zram.sh rename to scripts/15-config-zram.sh diff --git a/scripts/15-config-audio.sh b/scripts/16-config-audio.sh similarity index 100% rename from scripts/15-config-audio.sh rename to scripts/16-config-audio.sh diff --git a/scripts/16-cleanup.sh b/scripts/17-cleanup.sh similarity index 100% rename from scripts/16-cleanup.sh rename to scripts/17-cleanup.sh diff --git a/scripts/17-finalize.sh b/scripts/18-finalize.sh similarity index 100% rename from scripts/17-finalize.sh rename to scripts/18-finalize.sh