Compare commits
13 Commits
clean_beep
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| ae5f998d88 | |||
| 6cf28217a4 | |||
| e93e99480b | |||
| 619e919fa0 | |||
| 809f123854 | |||
| 46961040b3 | |||
| 4349413887 | |||
| 994023553d | |||
| dd0c7b8feb | |||
| 2d5d3919e2 | |||
| ac0a338b76 | |||
| 24b2b6c199 | |||
| a314a1a0d8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
/logs
|
||||
/.idea
|
||||
/.vscode
|
||||
/.qwen
|
||||
|
||||
*.mp3
|
||||
game-driver*
|
||||
43
.woodpecker.yml
Normal file
43
.woodpecker.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
when:
|
||||
- event: tag
|
||||
|
||||
clone:
|
||||
git:
|
||||
image: docker.m.daocloud.io/woodpeckerci/plugin-git
|
||||
settings:
|
||||
depth: 1
|
||||
|
||||
steps:
|
||||
# 构建多架构二进制文件
|
||||
build:
|
||||
image: docker.m.daocloud.io/golang:1.24-trixie
|
||||
environment:
|
||||
GOPROXY: https://goproxy.cn
|
||||
commands:
|
||||
- sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources
|
||||
# 启用多架构支持并安装交叉编译工具
|
||||
- dpkg --add-architecture arm64
|
||||
- apt-get update
|
||||
- apt-get install -y gcc-aarch64-linux-gnu pkg-config
|
||||
- apt-get install -y libasound2-dev libvlc-dev
|
||||
- apt-get install -y libasound2-dev:arm64 libvlc-dev:arm64
|
||||
- mkdir -p release
|
||||
# 构建 amd64 (native)
|
||||
- PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig go build -ldflags="-w -s" -o release/game-driver-linux-amd64 .
|
||||
# 构建 arm64 (cross-compile)
|
||||
- PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc go build -ldflags="-w -s" -o release/game-driver-linux-arm64 .
|
||||
- ls -lh release/
|
||||
|
||||
# 发布构建产物(可选)
|
||||
release:
|
||||
image: docker.m.daocloud.io/woodpeckerci/plugin-release
|
||||
settings:
|
||||
base-url: https://gitea.tides.top
|
||||
title: ${CI_COMMIT_TAG}
|
||||
api-key:
|
||||
from_secret: gitea_token
|
||||
files:
|
||||
- release/game-driver-linux-amd64
|
||||
- release/game-driver-linux-arm64
|
||||
depends_on:
|
||||
- build
|
||||
12
config.yml
12
config.yml
@@ -1,5 +1,5 @@
|
||||
location: wushan # 项目名称
|
||||
point: 3 # 点位
|
||||
point: 5 # 点位
|
||||
relay:
|
||||
maxTimeout: 60 # 单位 s,必须大于 0
|
||||
standbyCache: # 待机缓存文件路径
|
||||
@@ -17,9 +17,9 @@ log:
|
||||
maxAge: 30
|
||||
compress: true
|
||||
mqtt:
|
||||
url: mqtt://wushan-mqtt.chaoshengshuzi.com:1883
|
||||
clientID: wushan-3
|
||||
password: wushan@1013
|
||||
url: mqtt://mqtt.wxsxlj.com:1883
|
||||
clientID: wushan-5
|
||||
password:
|
||||
aliyun:
|
||||
accessKeyID:
|
||||
accessKeySecret:
|
||||
@@ -31,8 +31,8 @@ aliyun:
|
||||
|
||||
# 激光秀点位 osc 参数
|
||||
game:
|
||||
host: 192.168.0.167
|
||||
port: 3033
|
||||
host: 192.168.1.191
|
||||
port: 8000
|
||||
|
||||
# 待机投影仪控制参数
|
||||
#wait:
|
||||
|
||||
12
go.sum
12
go.sum
@@ -5,8 +5,6 @@ github.com/adrg/libvlc-go/v3 v3.1.6 h1:Cm22w6xNMDdzYCW8koHgAvjonYm4xbPP5TrlVTtMd
|
||||
github.com/adrg/libvlc-go/v3 v3.1.6/go.mod h1:xJK0YD8cyMDejnrTFQinStE6RYCV1nlfS8KmqTpszSc=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1376/go.mod h1:9CMdKNL3ynIGPpfTcdwTvIm8SGuAZYYC4jFVSSvE1YQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.92 h1:qespx4b6EexlXkvQUow9x0v1GnWUJYGU5FWYw3a4Wlg=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.92/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.93 h1:yHRWq/QmBJ3lC15zy1A1+TkvcAN+6dr1bgHsFghKvmk=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.93/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1 h1:LjItoNZuu5xHlsByFo+kr3nGa4LRIESCGWhfurayxBg=
|
||||
@@ -151,8 +149,6 @@ github.com/warthog618/go-gpiocdev v0.9.1 h1:pwHPaqjJfhCipIQl78V+O3l9OKHivdRDdmgX
|
||||
github.com/warthog618/go-gpiocdev v0.9.1/go.mod h1:dN3e3t/S2aSNC+hgigGE/dBW8jE1ONk9bDSEYfoPyl8=
|
||||
github.com/warthog618/go-gpiosim v0.1.1 h1:MRAEv+T+itmw+3GeIGpQJBfanUVyg0l3JCTwHtwdre4=
|
||||
github.com/warthog618/go-gpiosim v0.1.1/go.mod h1:YXsnB+I9jdCMY4YAlMSRrlts25ltjmuIsrnoUrBLdqU=
|
||||
github.com/ysmood/fetchup v0.2.4 h1:2kfWr/UrdiHg4KYRrxL2Jcrqx4DZYD+OtWu7WPBZl5o=
|
||||
github.com/ysmood/fetchup v0.2.4/go.mod h1:hbysoq65PXL0NQeNzUczNYIKpwpkwFL4LXMDEvIQq9A=
|
||||
github.com/ysmood/fetchup v0.3.0 h1:UhYz9xnLEVn2ukSuK3KCgcznWpHMdrmbsPpllcylyu8=
|
||||
github.com/ysmood/fetchup v0.3.0/go.mod h1:hbysoq65PXL0NQeNzUczNYIKpwpkwFL4LXMDEvIQq9A=
|
||||
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
|
||||
@@ -183,8 +179,6 @@ golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 h1:aWwlzYV971S4BXRS9AmqwDLAD85ouC6X+pocatKY58c=
|
||||
golang.org/x/exp v0.0.0-20250228200357-dead58393ab7/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
@@ -195,8 +189,6 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -204,13 +196,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
602
init-device.py
Executable file
602
init-device.py
Executable file
@@ -0,0 +1,602 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
设备初始化自动化脚本
|
||||
根据 init_device.md 文档自动执行设备初始化步骤
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import tempfile
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
from dataclasses import dataclass
|
||||
from contextlib import contextmanager
|
||||
|
||||
@dataclass
|
||||
class InitConfig:
|
||||
"""初始化配置"""
|
||||
install_chromium: bool = False
|
||||
username: Optional[str] = None
|
||||
dry_run: bool = False
|
||||
|
||||
class Logger:
|
||||
"""优雅的日志输出"""
|
||||
|
||||
@staticmethod
|
||||
def section(title: str):
|
||||
"""输出章节标题"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f" {title}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
@staticmethod
|
||||
def step(description: str):
|
||||
"""输出步骤说明"""
|
||||
print(f"\n🔧 {description}")
|
||||
|
||||
@staticmethod
|
||||
def success(message: str):
|
||||
"""输出成功信息"""
|
||||
print(f"✅ {message}")
|
||||
|
||||
@staticmethod
|
||||
def warning(message: str):
|
||||
"""输出警告信息"""
|
||||
print(f"⚠️ {message}")
|
||||
|
||||
@staticmethod
|
||||
def error(message: str):
|
||||
"""输出错误信息"""
|
||||
print(f"❌ {message}")
|
||||
|
||||
@staticmethod
|
||||
def info(message: str):
|
||||
"""输出信息"""
|
||||
print(f"ℹ️ {message}")
|
||||
|
||||
class CommandRunner:
|
||||
"""命令执行器"""
|
||||
|
||||
def __init__(self, dry_run: bool = False):
|
||||
self.dry_run = dry_run
|
||||
|
||||
def _execute_command(self, command: str) -> subprocess.CompletedProcess:
|
||||
"""执行命令的核心逻辑"""
|
||||
with subprocess.Popen(
|
||||
command,
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1
|
||||
) as process:
|
||||
output_lines = []
|
||||
|
||||
# 实时读取并显示输出
|
||||
for line in iter(process.stdout.readline, ''):
|
||||
print(line.rstrip())
|
||||
output_lines.append(line)
|
||||
|
||||
# 等待进程完成
|
||||
process.wait()
|
||||
|
||||
return subprocess.CompletedProcess(
|
||||
command,
|
||||
process.returncode,
|
||||
''.join(output_lines),
|
||||
""
|
||||
)
|
||||
|
||||
def run(self, command: str, description: str = "", check: bool = True) -> subprocess.CompletedProcess:
|
||||
"""执行命令"""
|
||||
Logger.step(description or command)
|
||||
|
||||
if self.dry_run:
|
||||
Logger.info(f"[预览模式] 命令: {command}")
|
||||
return subprocess.CompletedProcess(command, 0, "", "")
|
||||
|
||||
try:
|
||||
result = self._execute_command(command)
|
||||
|
||||
if check and result.returncode != 0:
|
||||
raise subprocess.CalledProcessError(result.returncode, command, result.stdout)
|
||||
|
||||
Logger.success("执行成功")
|
||||
return result
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.error(f"命令执行失败 (退出码: {e.returncode})")
|
||||
if e.stderr:
|
||||
print(f"错误详情: {e.stderr.strip()}")
|
||||
if check:
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
Logger.error(f"执行异常: {e}")
|
||||
if check:
|
||||
sys.exit(1)
|
||||
|
||||
@contextmanager
|
||||
def temp_file_with_content(content: str, suffix: str = ""):
|
||||
"""创建临时文件上下文管理器"""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix=suffix, delete=False) as f:
|
||||
f.write(content)
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
yield temp_path
|
||||
finally:
|
||||
Path(temp_path).unlink(missing_ok=True)
|
||||
|
||||
class DeviceInitializer:
|
||||
"""设备初始化器"""
|
||||
|
||||
# 常量定义
|
||||
SECTION_OPTIMIZE_BOOT = "优化系统启动时间"
|
||||
SECTION_UPDATE_SYSTEM = "更新系统包列表"
|
||||
SECTION_INSTALL_BASIC = "安装基础依赖"
|
||||
SECTION_ADD_REPOS = "添加第三方软件源"
|
||||
SECTION_INSTALL_PACKAGES = "安装主要软件包"
|
||||
SECTION_CONFIGURE_SYSTEM = "配置系统设置"
|
||||
SECTION_AUTO_STARTX = "配置图形界面自动启动"
|
||||
SECTION_AUTO_LOGIN = "配置自动登录"
|
||||
SECTION_CONFIGURE_I3 = "配置 i3 窗口管理器"
|
||||
SECTION_MANUAL_STEPS = "手动配置步骤"
|
||||
SECTION_COMPLETE = "初始化完成"
|
||||
|
||||
def __init__(self, config: InitConfig):
|
||||
self.config = config
|
||||
# 获取实际用户名(sudo 下运行时从 SUDO_USER 获取)
|
||||
self.username = config.username or os.getenv('SUDO_USER') or os.getenv('USER')
|
||||
self.user_home = Path(f"/home/{self.username}")
|
||||
self.runner = CommandRunner(config.dry_run)
|
||||
|
||||
# 软件包配置
|
||||
self.base_packages = [
|
||||
"fontconfig",
|
||||
"fonts-noto-cjk",
|
||||
"fonts-noto-color-emoji",
|
||||
"unclutter",
|
||||
"xorg",
|
||||
"i3-wm",
|
||||
"libvlc-dev",
|
||||
"vlc-plugin-base",
|
||||
"vlc-plugin-video-output",
|
||||
"libasound2-dev",
|
||||
"alsa-utils",
|
||||
"trzsz",
|
||||
"wireguard",
|
||||
"wireguard-tools"
|
||||
]
|
||||
|
||||
# 基础软件源(始终需要)
|
||||
self.repositories = [
|
||||
"ppa:trzsz/ppa"
|
||||
]
|
||||
# Chromium 仅在需要时添加的源
|
||||
self.chromium_repo = "ppa:xtradeb/apps"
|
||||
|
||||
def _edit_systemd_service(self, service_name: str, override_content: str, description: str):
|
||||
"""兼容地编辑 systemd 服务配置"""
|
||||
# 直接创建 override 目录和文件,兼容所有 systemctl 版本
|
||||
override_dir = f"/etc/systemd/system/{service_name}.d"
|
||||
override_file = f"{override_dir}/override.conf"
|
||||
|
||||
# 创建目录
|
||||
self.runner.run(f"sudo mkdir -p {override_dir}", f"创建服务 override 目录: {override_dir}")
|
||||
|
||||
# 写入配置文件
|
||||
with temp_file_with_content(override_content) as temp_file:
|
||||
self.runner.run(f"sudo cp {temp_file} {override_file}", f"创建服务配置文件: {override_file}")
|
||||
|
||||
# 重新加载 systemd 配置
|
||||
self.runner.run("sudo systemctl daemon-reload", "重新加载 systemd 配置")
|
||||
|
||||
Logger.success(f"{description} - 已完成")
|
||||
|
||||
def optimize_boot_time(self):
|
||||
"""优化 Ubuntu 24 开机时间"""
|
||||
Logger.section(self.SECTION_OPTIMIZE_BOOT)
|
||||
|
||||
override_content = "[Service]\nTimeoutStartSec=2sec\n"
|
||||
self._edit_systemd_service(
|
||||
"systemd-networkd-wait-online.service",
|
||||
override_content,
|
||||
"配置网络等待服务超时时间为2秒"
|
||||
)
|
||||
|
||||
def update_system(self):
|
||||
"""更新系统包"""
|
||||
Logger.section(self.SECTION_UPDATE_SYSTEM)
|
||||
self.runner.run("sudo apt-get update", "刷新软件包索引")
|
||||
|
||||
def install_basic_packages(self):
|
||||
"""安装基础包"""
|
||||
Logger.section(self.SECTION_INSTALL_BASIC)
|
||||
basic_packages = ["curl", "gpg"]
|
||||
packages_str = " ".join(basic_packages)
|
||||
self.runner.run(f"sudo apt-get install -y {packages_str}", f"安装基础包: {', '.join(basic_packages)}")
|
||||
|
||||
def add_repositories(self):
|
||||
"""添加软件源"""
|
||||
Logger.section(self.SECTION_ADD_REPOS)
|
||||
|
||||
# 基础源始终添加;Chromium 源仅在需要安装 Chromium 时添加
|
||||
repos = list(self.repositories)
|
||||
if self.config.install_chromium:
|
||||
repos.append(self.chromium_repo)
|
||||
Logger.info("已选择安装 Chromium,将添加其 PPA 源")
|
||||
else:
|
||||
Logger.info("未选择安装 Chromium,跳过添加 xtradeb/apps PPA")
|
||||
|
||||
for repo in repos:
|
||||
self.runner.run(f"sudo add-apt-repository -y {repo}", f"添加软件源: {repo}")
|
||||
|
||||
self.runner.run("sudo apt-get update", "更新软件包索引")
|
||||
|
||||
def install_packages(self):
|
||||
"""安装主要软件包"""
|
||||
Logger.section(self.SECTION_INSTALL_PACKAGES)
|
||||
|
||||
packages = self.base_packages.copy()
|
||||
if self.config.install_chromium:
|
||||
packages.insert(0, "ungoogled-chromium")
|
||||
Logger.info("已包含 ungoogled-chromium")
|
||||
|
||||
packages_str = " ".join(packages)
|
||||
package_list = ", ".join(packages)
|
||||
# 安装软件包,实时显示进度
|
||||
self.runner.run(f"sudo apt-get install -y {packages_str}", f"安装软件包: {package_list}")
|
||||
|
||||
def configure_system(self):
|
||||
"""配置系统设置"""
|
||||
Logger.section(self.SECTION_CONFIGURE_SYSTEM)
|
||||
|
||||
# 设置时区
|
||||
self.runner.run("sudo timedatectl set-timezone Asia/Shanghai", "设置时区为上海")
|
||||
|
||||
# 添加用户到相关组
|
||||
groups = "audio,video,dialout"
|
||||
self.runner.run(
|
||||
f"sudo usermod -aG {groups} {self.username}",
|
||||
f"将用户 {self.username} 添加到 {groups} 组"
|
||||
)
|
||||
|
||||
def setup_auto_startx(self):
|
||||
"""设置自动启动 Xorg"""
|
||||
Logger.section(self.SECTION_AUTO_STARTX)
|
||||
|
||||
bashrc_path = self.user_home / ".bashrc"
|
||||
startx_config = """
|
||||
# 自动启动 Xorg 和窗口管理器
|
||||
if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
|
||||
startx
|
||||
fi
|
||||
"""
|
||||
|
||||
if self.config.dry_run:
|
||||
Logger.step("配置自动启动 Xorg")
|
||||
Logger.info(f"[预览模式] 将添加配置到 {bashrc_path}")
|
||||
return
|
||||
|
||||
# 检查是否已存在配置
|
||||
if bashrc_path.exists():
|
||||
try:
|
||||
content = bashrc_path.read_text(encoding='utf-8')
|
||||
if 'startx' in content and 'tty1' in content:
|
||||
Logger.warning("自动启动 Xorg 配置已存在,跳过")
|
||||
return
|
||||
except Exception as e:
|
||||
Logger.warning(f"读取 .bashrc 文件失败: {e}")
|
||||
|
||||
# 添加配置
|
||||
try:
|
||||
with open(bashrc_path, 'a', encoding='utf-8') as f:
|
||||
f.write(startx_config)
|
||||
|
||||
# 设置文件所有权为实际用户
|
||||
self.runner.run(f"sudo chown {self.username}:{self.username} {bashrc_path}",
|
||||
f"设置 .bashrc 所有权为 {self.username}")
|
||||
|
||||
Logger.success(f"已配置自动启动 Xorg: {bashrc_path}")
|
||||
except Exception as e:
|
||||
Logger.error(f"配置自动启动 Xorg 失败: {e}")
|
||||
|
||||
def setup_auto_login(self):
|
||||
"""设置自动登录"""
|
||||
Logger.section(self.SECTION_AUTO_LOGIN)
|
||||
|
||||
override_content = f"""[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty --autologin {self.username} --noclear %I $TERM
|
||||
"""
|
||||
|
||||
self._edit_systemd_service(
|
||||
"getty@tty1.service",
|
||||
override_content,
|
||||
f"配置用户 {self.username} 自动登录"
|
||||
)
|
||||
|
||||
def configure_i3(self):
|
||||
"""配置 i3 窗口管理器"""
|
||||
Logger.section(self.SECTION_CONFIGURE_I3)
|
||||
|
||||
i3_config_dir = self.user_home / ".config" / "i3"
|
||||
i3_config_path = i3_config_dir / "config"
|
||||
|
||||
if self.config.dry_run:
|
||||
Logger.step("配置 i3 窗口管理器")
|
||||
Logger.info(f"[预览模式] 将配置 i3 配置文件: {i3_config_path}")
|
||||
return
|
||||
|
||||
# 创建 i3 配置目录
|
||||
i3_config_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 检查是否已有 i3 配置文件
|
||||
if not i3_config_path.exists():
|
||||
Logger.info("i3 配置文件不存在,从系统默认配置复制")
|
||||
self._copy_system_i3_config(i3_config_path)
|
||||
else:
|
||||
Logger.info("i3 配置文件已存在,直接更新配置")
|
||||
|
||||
# 无论是新生成还是已存在,都更新配置
|
||||
self._update_i3_config(i3_config_path)
|
||||
|
||||
# 设置配置文件所有权为实际用户
|
||||
self.runner.run(f"sudo chown -R {self.username}:{self.username} {i3_config_dir}", f"设置 i3 配置目录所有权为 {self.username}")
|
||||
|
||||
Logger.success(f"已配置 i3 窗口管理器: {i3_config_path}")
|
||||
|
||||
def _copy_system_i3_config(self, config_path: Path):
|
||||
"""从系统默认配置复制 i3 配置文件"""
|
||||
Logger.step("从系统默认配置复制 i3 配置文件")
|
||||
|
||||
system_config = "/etc/i3/config"
|
||||
self.runner.run(f"cp {system_config} {config_path}", f"复制系统默认 i3 配置到 {config_path}")
|
||||
|
||||
# 移除 i3-config-wizard 指令
|
||||
try:
|
||||
content = config_path.read_text(encoding='utf-8')
|
||||
if 'i3-config-wizard' in content:
|
||||
# 移除包含 i3-config-wizard 的行
|
||||
lines = content.splitlines()
|
||||
filtered_lines = [line for line in lines if 'i3-config-wizard' not in line]
|
||||
|
||||
with open(config_path, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(filtered_lines) + '\n')
|
||||
|
||||
Logger.info("已移除系统配置中的 i3-config-wizard 指令")
|
||||
|
||||
except Exception as e:
|
||||
Logger.warning(f"移除 i3-config-wizard 指令失败: {e}")
|
||||
|
||||
Logger.success("已从系统默认配置复制 i3 配置文件")
|
||||
|
||||
def _update_i3_config(self, config_path: Path):
|
||||
"""更新现有 i3 配置文件"""
|
||||
Logger.step("更新现有 i3 配置文件")
|
||||
|
||||
try:
|
||||
content = config_path.read_text(encoding='utf-8')
|
||||
except Exception as e:
|
||||
Logger.error(f"读取 i3 配置文件失败: {e}")
|
||||
return
|
||||
|
||||
# 添加启动配置项
|
||||
startup_configs = [
|
||||
"exec --no-startup-id unclutter -root # 隐藏鼠标",
|
||||
"exec --no-startup-id xset dpms 0 0 0 # 关闭屏幕自动关闭",
|
||||
"exec --no-startup-id xset s off # 关闭屏幕保护"
|
||||
]
|
||||
|
||||
config_modified = False
|
||||
for config_line in startup_configs:
|
||||
if config_line not in content:
|
||||
content += f"\n{config_line}\n"
|
||||
Logger.info(f"添加配置: {config_line}")
|
||||
config_modified = True
|
||||
|
||||
# 移除 i3bar 配置块
|
||||
if "bar {" in content:
|
||||
# 使用正则表达式直接移除整个 bar 配置块
|
||||
bar_pattern = r'bar\s*\{[^}]*\}'
|
||||
new_content = re.sub(bar_pattern, '', content, flags=re.DOTALL)
|
||||
if new_content != content:
|
||||
content = new_content
|
||||
Logger.info("已移除 i3bar 状态栏配置")
|
||||
config_modified = True
|
||||
|
||||
# 只在有修改时才写回文件
|
||||
if config_modified:
|
||||
try:
|
||||
with open(config_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
Logger.success("i3 配置文件更新完成")
|
||||
except Exception as e:
|
||||
Logger.error(f"写入 i3 配置文件失败: {e}")
|
||||
else:
|
||||
Logger.info("i3 配置无需修改")
|
||||
|
||||
def show_manual_steps(self):
|
||||
"""显示需要手动完成的步骤"""
|
||||
Logger.section(self.SECTION_MANUAL_STEPS)
|
||||
|
||||
Logger.info("请手动完成以下配置步骤:")
|
||||
|
||||
print("\n📋 WireGuard 配置:")
|
||||
print(" 1. 从服务器获取 WireGuard 配置文件")
|
||||
print(" 2. 保存到 /etc/wireguard/wg0.conf")
|
||||
print(" 3. 启用并启动 WireGuard:")
|
||||
print(" sudo systemctl enable wg-quick@wg0")
|
||||
print(" sudo systemctl start wg-quick@wg0")
|
||||
|
||||
print("\n📋 系统音量配置:")
|
||||
print(" 1. 运行 alsamixer 进入音量控制界面")
|
||||
print(" 2. 调节 master 音量到合适级别")
|
||||
print(" 3. 按 ESC 退出")
|
||||
print(" 4. 运行 sudo alsactl store 保存音量设置")
|
||||
|
||||
print("\n📋 i3 窗口管理器:")
|
||||
print(" 1. 重启系统后会自动进入 i3")
|
||||
print(" 2. 脚本已自动配置必要的设置(隐藏鼠标、禁用屏保等)")
|
||||
print(" 3. 可以根据需要进一步自定义 ~/.config/i3/config")
|
||||
|
||||
def run_initialization(self):
|
||||
"""运行完整的初始化流程"""
|
||||
Logger.section("设备初始化开始")
|
||||
|
||||
Logger.info(f"目标用户: {self.username}")
|
||||
Logger.info(f"用户主目录: {self.user_home}")
|
||||
Logger.info(f"安装 Chromium: {'是' if self.config.install_chromium else '否'}")
|
||||
Logger.info(f"预览模式: {'是' if self.config.dry_run else '否'}")
|
||||
|
||||
# 初始化步骤配置
|
||||
initialization_steps = [
|
||||
("优化系统启动", self.optimize_boot_time),
|
||||
("更新系统包", self.update_system),
|
||||
("安装基础依赖", self.install_basic_packages),
|
||||
("添加软件源", self.add_repositories),
|
||||
("安装主要软件", self.install_packages),
|
||||
("配置系统设置", self.configure_system),
|
||||
("配置自动启动X", self.setup_auto_startx),
|
||||
("配置自动登录", self.setup_auto_login),
|
||||
("配置i3窗口管理器", self.configure_i3),
|
||||
]
|
||||
|
||||
try:
|
||||
for step_name, step_func in initialization_steps:
|
||||
Logger.info(f"正在执行: {step_name}")
|
||||
step_func()
|
||||
|
||||
self.show_manual_steps()
|
||||
|
||||
Logger.section(self.SECTION_COMPLETE)
|
||||
Logger.success("🎉 所有自动化步骤已完成")
|
||||
Logger.warning("⚠️ 请重启系统以使所有更改生效")
|
||||
Logger.info("💡 重启后请完成 WireGuard 和音量的手动配置")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
Logger.error("❌ 用户中断了初始化过程")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
Logger.error(f"❌ 初始化过程中发生错误: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
class SystemValidator:
|
||||
"""系统环境验证器"""
|
||||
|
||||
@staticmethod
|
||||
def validate_environment():
|
||||
"""验证运行环境"""
|
||||
Logger.section("验证系统环境")
|
||||
|
||||
# 检查是否通过 sudo 运行
|
||||
if os.geteuid() != 0:
|
||||
Logger.error("此脚本需要使用 sudo 运行")
|
||||
Logger.info("请使用: sudo python3 init-device.py")
|
||||
sys.exit(1)
|
||||
|
||||
# 检查是否有原始用户信息
|
||||
if not os.getenv('SUDO_USER'):
|
||||
Logger.error("请不要直接以 root 用户运行此脚本")
|
||||
Logger.info("请使用普通用户通过 sudo 运行: sudo python3 init-device.py")
|
||||
sys.exit(1)
|
||||
|
||||
Logger.info(f"检测到通过 sudo 运行脚本,原用户: {os.getenv('SUDO_USER')}")
|
||||
Logger.success("系统环境验证通过")
|
||||
|
||||
def create_argument_parser() -> argparse.ArgumentParser:
|
||||
"""创建命令行参数解析器"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="设备初始化自动化脚本",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
使用示例:
|
||||
%(prog)s # 基础安装
|
||||
%(prog)s --chromium # 包含 Chromium 的完整安装
|
||||
%(prog)s --dry-run # 预览模式,不实际执行
|
||||
%(prog)s --username user1 # 指定用户名
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--chromium",
|
||||
action="store_true",
|
||||
help="安装 ungoogled-chromium 浏览器"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--username",
|
||||
type=str,
|
||||
help="指定用户名(默认使用当前用户)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="预览模式:仅显示将要执行的操作,不实际执行"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="%(prog)s 1.0.0"
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def show_preview(config: InitConfig):
|
||||
"""显示预览信息"""
|
||||
Logger.section("预览模式")
|
||||
|
||||
steps = [
|
||||
DeviceInitializer.SECTION_OPTIMIZE_BOOT,
|
||||
DeviceInitializer.SECTION_UPDATE_SYSTEM,
|
||||
f"{DeviceInitializer.SECTION_INSTALL_BASIC} (curl, gpg)",
|
||||
DeviceInitializer.SECTION_ADD_REPOS,
|
||||
f"{DeviceInitializer.SECTION_INSTALL_PACKAGES}" + (" (包含 ungoogled-chromium)" if config.install_chromium else ""),
|
||||
f"{DeviceInitializer.SECTION_CONFIGURE_SYSTEM} (时区、用户组)",
|
||||
DeviceInitializer.SECTION_AUTO_STARTX,
|
||||
DeviceInitializer.SECTION_AUTO_LOGIN,
|
||||
DeviceInitializer.SECTION_CONFIGURE_I3,
|
||||
"显示手动配置步骤"
|
||||
]
|
||||
|
||||
Logger.info("将要执行的操作步骤:")
|
||||
for i, step in enumerate(steps, 1):
|
||||
print(f" {i:2d}. {step}")
|
||||
|
||||
Logger.warning("这是预览模式,不会实际执行任何操作")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
parser = create_argument_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
# 创建配置
|
||||
config = InitConfig(
|
||||
install_chromium=args.chromium,
|
||||
username=args.username,
|
||||
dry_run=args.dry_run
|
||||
)
|
||||
|
||||
# 预览模式
|
||||
if config.dry_run:
|
||||
show_preview(config)
|
||||
return
|
||||
|
||||
# 验证环境
|
||||
SystemValidator.validate_environment()
|
||||
|
||||
# 运行初始化
|
||||
initializer = DeviceInitializer(config)
|
||||
initializer.run_initialization()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
Logger.error("用户中断操作")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
Logger.error(f"程序异常退出: {e}")
|
||||
sys.exit(1)
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
```bash
|
||||
# 在 systemd-networkd-wait-online.service Service 加入 TimeoutStartSec=2sec
|
||||
sudo vim /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service
|
||||
sudo EDITOR=vim systemctl edit systemd-networkd-wait-online.service
|
||||
# 在打开的编辑器中添加:
|
||||
# [Service]
|
||||
# TimeoutStartSec=2sec
|
||||
```
|
||||
|
||||
### 初始化设备
|
||||
@@ -10,9 +13,9 @@ sudo vim /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-o
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install curl gpg
|
||||
sudo add-apt-repository ppa:xtradeb/apps
|
||||
sudo add-apt-repository ppa:xtradeb/apps # 不安装 ungoogled-chromium 时,不要添加。可能与系统源的库冲突
|
||||
sudo add-apt-repository ppa:trzsz/ppa
|
||||
sudo apt install -y ungoogled-chromium fonts-noto-cjk fonts-noto-color-emoji unclutter xorg i3-wm libvlc-dev libasound2-dev alsa-utils trzsz wireguard wireguard-tools
|
||||
sudo apt install -y ungoogled-chromium fontconfig fonts-noto-cjk fonts-noto-color-emoji unclutter xorg i3-wm libvlc-dev vlc-plugin-base vlc-plugin-video-output libasound2-dev alsa-utils trzsz wireguard wireguard-tools
|
||||
sudo timedatectl set-timezone Asia/Shanghai
|
||||
sudo usermod -aG audio,video,dialout $USER
|
||||
```
|
||||
@@ -48,10 +51,14 @@ fi
|
||||
|
||||
### 自动登录
|
||||
|
||||
编辑 `/etc/systemd/system/getty.target.wants/getty@tty1.service` 文件,将 `ExecStart` 行修改为:
|
||||
使用 systemctl edit 修改 getty@tty1 服务:
|
||||
|
||||
```bash
|
||||
ExecStart=-/sbin/agetty --autologin <your_username> --noclear %I $TERM
|
||||
sudo EDITOR=vim systemctl edit getty@tty1.service
|
||||
# 在打开的编辑器中添加:
|
||||
# [Service]
|
||||
# ExecStart=
|
||||
# ExecStart=-/sbin/agetty --autologin <your_username> --noclear %I $TERM
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
@@ -6,14 +6,15 @@ import (
|
||||
"game-driver/leaf"
|
||||
"game-driver/pkg/utils"
|
||||
"game-driver/pkg/video"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func OnlyVideo(c *leaf.Context) {
|
||||
payload := leaf.Value[*schema.PlayModal](c, middleware.PayloadJSONKey)
|
||||
|
||||
utils.BlankOpen()
|
||||
defer utils.BlankClose()
|
||||
// utils.BlankOpen()
|
||||
// defer utils.BlankClose()
|
||||
|
||||
if url, ok := payload.Game["video"]; ok {
|
||||
path, local, err := utils.LinkVideo(url.(string))
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/pkg/utils"
|
||||
"game-driver/pkg/video"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -19,8 +20,8 @@ func Video(item schema.WaitItemModel) func(c context.Context) error {
|
||||
zap.S().Infoln("播放待机视频")
|
||||
defer zap.S().Infoln("结束待机视频")
|
||||
|
||||
utils.BlankOpen()
|
||||
defer utils.BlankClose()
|
||||
// utils.BlankOpen()
|
||||
// defer utils.BlankClose()
|
||||
|
||||
err = video.Play(c, path, local)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"game-driver/internal/schema"
|
||||
"game-driver/pkg/browser"
|
||||
"game-driver/pkg/utils"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -13,8 +13,8 @@ func Web(item schema.WaitItemModel) func(c context.Context) error {
|
||||
zap.S().Infoln("打开待机网页")
|
||||
|
||||
// 控制背光
|
||||
utils.BlankOpen()
|
||||
defer utils.BlankClose()
|
||||
// utils.BlankOpen()
|
||||
// defer utils.BlankClose()
|
||||
|
||||
browser.OpenApp(c, item.Data)
|
||||
return nil
|
||||
|
||||
@@ -83,13 +83,13 @@ func Run() {
|
||||
cls, err := logger.NewTenCls(fmt.Sprintf("game-driver-%s-%v", config.C.Location, config.C.Point))
|
||||
if err != nil {
|
||||
log.Println("初始化腾讯云日志服务异常: ", err)
|
||||
logger.InitDevLogger()
|
||||
} else {
|
||||
cls.Start()
|
||||
defer cls.Close()
|
||||
logger.InitProLogger(cls)
|
||||
}
|
||||
cls.Start()
|
||||
defer cls.Close()
|
||||
|
||||
logger.InitProLogger(cls)
|
||||
|
||||
//logger.InitDevLogger()
|
||||
// 应用退出时刷新所有缓冲日志
|
||||
defer logger.Sync()
|
||||
|
||||
@@ -101,7 +101,7 @@ func Run() {
|
||||
zap.S().Infoln("当前IP: ", addrs)
|
||||
|
||||
// 启动时关闭屏幕
|
||||
utils.BlankClose()
|
||||
// utils.BlankClose()
|
||||
|
||||
topicPrefix := fmt.Sprintf("server/%s/%v/", config.C.Location, config.C.Point)
|
||||
publishTopic := fmt.Sprintf("device/%s/%v/status", config.C.Location, config.C.Point)
|
||||
|
||||
@@ -14,51 +14,61 @@ func New(host string, port int) *Client {
|
||||
}
|
||||
}
|
||||
|
||||
// StartCue 播放节目
|
||||
func (c *Client) StartCue(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/StartCue", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
// EnableLaserOutput 打开激光
|
||||
func (c *Client) EnableLaserOutput() error {
|
||||
msg := osc.NewMessage("/beyond/general/EnableLaserOutput")
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
// DisableLaserOutput 关闭激光
|
||||
func (c *Client) DisableLaserOutput() error {
|
||||
msg := osc.NewMessage("/beyond/general/DisableLaserOutput")
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
// SetLaserOutput 设置激光输出
|
||||
func (c *Client) SetLaserOutput(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutput", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
// SetLaserOutputColor 设置激光颜色
|
||||
func (c *Client) SetLaserOutputColor(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputColor", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
// SetLaserOutputIntensity 设置激光强度
|
||||
func (c *Client) SetLaserOutputIntensity(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputIntensity", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
// SetLaserOutputPosition 设置激光位置
|
||||
func (c *Client) SetLaserOutputPosition(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputPosition", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
// SetLaserOutputSize 设置激光尺寸
|
||||
func (c *Client) SetLaserOutputSize(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputSize", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
// SetLaserOutputSpeed 设置激光速度
|
||||
func (c *Client) SetLaserOutputSpeed(data string) error {
|
||||
msg := osc.NewMessage("/beyond/general/SetLaserOutputSpeed", data)
|
||||
return c.o.Send(msg)
|
||||
}
|
||||
|
||||
// Status 获取状态
|
||||
func (c *Client) Status() error {
|
||||
msg := osc.NewMessage("/beyond/general/Status")
|
||||
return c.o.Send(msg)
|
||||
|
||||
@@ -3,12 +3,26 @@ package utils
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
xsetBin string
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
once.Do(func() {
|
||||
if found, err := exec.LookPath("xset"); err == nil {
|
||||
xsetBin = found
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BlankOpen 打开屏幕
|
||||
func BlankOpen() {
|
||||
if found, err := exec.LookPath("xset"); err == nil {
|
||||
exec.Command(found, "dpms", "force", "on").Run()
|
||||
if xsetBin != "" {
|
||||
exec.Command(xsetBin, "dpms", "force", "on").Run()
|
||||
return
|
||||
}
|
||||
os.WriteFile("/sys/class/graphics/fb0/blank", []byte("0"), 0644)
|
||||
@@ -16,8 +30,8 @@ func BlankOpen() {
|
||||
|
||||
// BlankClose 关闭屏幕
|
||||
func BlankClose() {
|
||||
if found, err := exec.LookPath("xset"); err == nil {
|
||||
exec.Command(found, "dpms", "force", "off").Run()
|
||||
if xsetBin != "" {
|
||||
exec.Command(xsetBin, "dpms", "force", "off").Run()
|
||||
return
|
||||
}
|
||||
os.WriteFile("/sys/class/graphics/fb0/blank", []byte("1"), 0644)
|
||||
|
||||
14
todo.md
14
todo.md
@@ -4,10 +4,14 @@
|
||||
|
||||
```bash
|
||||
# 在 systemd-networkd-wait-online.service Service 加入 TimeoutStartSec=2sec
|
||||
sudo vim /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service
|
||||
sudo EDITOR=vim systemctl edit systemd-networkd-wait-online.service
|
||||
# 在打开的编辑器中添加:
|
||||
# [Service]
|
||||
# TimeoutStartSec=2sec
|
||||
```
|
||||
|
||||
### 配置时区
|
||||
|
||||
```bash
|
||||
sudo timedatectl set-timezone Asia/Shanghai
|
||||
```
|
||||
@@ -107,10 +111,14 @@ fi
|
||||
|
||||
### 自动登录
|
||||
|
||||
编辑 `/etc/systemd/system/getty.target.wants/getty@tty1.service` 文件,将 `ExecStart` 行修改为:
|
||||
使用 systemctl edit 修改 getty@tty1 服务:
|
||||
|
||||
```bash
|
||||
ExecStart=-/sbin/agetty --autologin <your_username> --noclear %I $TERM
|
||||
sudo EDITOR=vim systemctl edit getty@tty1.service
|
||||
# 在打开的编辑器中添加:
|
||||
# [Service]
|
||||
# ExecStart=
|
||||
# ExecStart=-/sbin/agetty --autologin <your_username> --noclear %I $TERM
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
Reference in New Issue
Block a user