Compare commits

...

26 Commits

Author SHA1 Message Date
imbytecat 8e411c4c97 feat(base): 添加 Adobe 思源黑体和宋体中文字体 2026-04-17 14:21:14 +08:00
imbytecat e94eda2a5f feat(fish): 绑定 Up 键到 Atuin 前缀搜索 2026-04-10 16:59:15 +08:00
imbytecat ac66aadea2 feat(dev): 添加 oxlint 全局包 2026-04-10 11:15:09 +08:00
imbytecat 04bf913d35 feat(base): 添加 chromium 浏览器 2026-04-10 10:54:41 +08:00
imbytecat d2b2c7376f fix(mise): 添加 usage 工具以支持 completions 2026-04-10 09:45:42 +08:00
imbytecat b84368259d fix(fish): 将 \e\e 绑定语法更新为 Fish 4.0+ 的 escape,escape 2026-04-10 09:37:29 +08:00
imbytecat 3895221c48 fix(fish): 修复 Ctrl+Space 绑定的语法错误,将 \c@ 替换为 ctrl-space 2026-04-10 09:34:42 +08:00
imbytecat 27d4c68f23 fix(docker): 改用 SourceError 处理组信息查询失败 2026-04-09 16:05:04 +08:00
imbytecat af42eb913f fix(fish): 改用 SourceError 处理 shell 查询失败 2026-04-09 15:51:45 +08:00
imbytecat 33f4beb72a refactor(zsh): 移除 zsh 模块与配置 2026-04-09 15:50:34 +08:00
imbytecat 193cbdb11b feat(fish): 添加 fish 模块与交互配置 2026-04-09 15:50:34 +08:00
imbytecat 99f9608282 feat(base): 添加 fastfetch 系统信息工具 2026-04-09 12:09:54 +08:00
imbytecat b96e144e82 refactor(modules): 添加类型注解并优化 WSL 检测
- 全模块补全类型注解 (files, after_update, on_change)
- docker: 仅在 WSL 环境禁用 networkd-wait-online
- docker: 添加 _is_wsl() 自动检测函数
- zsh/docker: 改进错误输出使用 decman.error
- wsl-init: 统一使用 [[ ]] 并格式化代码
2026-04-09 12:09:00 +08:00
imbytecat 4182b3c300 docs(agents): 完善 AGENTS.md 并添加别名依赖表
- 添加 .zshrc 别名与包的绑定表,防止误删依赖
- 简化 Agent 须知第5、6条表述
- 修正仓库结构描述
2026-04-09 11:52:40 +08:00
imbytecat ff31160602 feat(base): 精简 WSL 开发环境包列表
移除 WSL 下冗余的媒体处理和工具包:
- chafa, fastfetch, ffmpeg, imagemagick, poppler, resvg, tealdeer, xh

保留核心开发工具:
- micro (友好编辑器) - .zshrc 通过 alias vi=nvim 使用
- gomi-bin (回收站) - .zshrc 通过 alias rm=gomi 使用

同时移除 .zshrc 中的 http 别名 (xh 已移除)

优化后:78 个包 (原 87 个)
2026-04-09 11:51:53 +08:00
imbytecat 34e71deed0 feat(zsh): 优化补全匹配与 eza 别名 2026-04-09 11:10:23 +08:00
imbytecat e34d7da3ea feat(zsh): WSL 下让 Windows Terminal 新标签继承当前目录 2026-04-08 13:34:57 +08:00
imbytecat e9a77f907e feat(base): 补齐 yazi 预览依赖
yazi 的预览能力依赖外部工具,按官方推荐补齐:

- ffmpeg: 视频缩略图
- poppler: PDF 预览
- imagemagick: 图片预览(含 HEIC/JPEG XL/字体)
- resvg: SVG 预览
- chafa: 终端不支持图片协议时的 ASCII fallback
  (Windows Terminal 旧版本无 sixel 时的兜底)
2026-04-08 13:26:03 +08:00
imbytecat 77d51c65b3 feat(zsh): 现代化 zshrc 配置并删除冗余 vim
zshrc 配置更新:
- 新增 EDITOR/VISUAL/MANPAGER/PAGER 环境变量,让 git commit、
  visudo、man 等工具默认使用 nvim 与 bat
- zoxide init --cmd cd 直接接管 cd,删除手动 alias cd=z/cdi=zi
- 新增 yazi yy wrapper:退出时自动 cd 到最后浏览的目录
- FZF 默认配置:fd 联动 + 现代 UI(高度/边框/预览窗口)
- compinit 缓存优化:每天重建一次 zcompdump,平时复用
- eza alias 公共参数提取到 _EZA_BASE,所有列表都带 --git
- 新增 Ctrl+Space 接受 zsh-autosuggestions 建议

base.py 同步:
- 删除冗余 vim:neovim 已装且 EDITOR=nvim 已设置,
  visudo 等工具会通过 $EDITOR fallback 到 nvim
2026-04-08 13:21:59 +08:00
imbytecat 2b3544b4a5 refactor(base): 切换回收站工具 trash-cli → gomi-bin
trash-cli 是 Python 写的老牌方案,启动开销大且 5 年未发布正式
release。gomi 是 Go 实现的现代替代品:

- 完整兼容 freedesktop.org Trash 规范,与 yazi/GNOME/KDE 互通
- 提供交互式 TUI 浏览/恢复界面(gomi 无参数即进入)
- 性能比 trash-cli 快约 10 倍
- 极活跃维护,AUR 提供预编译 gomi-bin

回收站数据无缝迁移:两者都使用 ~/.local/share/Trash/。

- pacman: -trash-cli
- aur: +gomi-bin
- .zshrc: alias rm: trash-put → gomi
2026-04-08 13:21:16 +08:00
imbytecat d4cb2f8acb refactor(base): 用 ouch 替换 atool 与冗余解压器
atool 上次更新于 2016 年,且依赖外部 unzip/unrar/p7zip 二进制。
ouch 是纯 Rust 实现,所有常见格式(zip/7z/rar/tar/gz/bz2/xz/zst 等)
通过 Rust crates 原生支持,运行时零外部依赖。

- pacman: -atool -unrar -unzip +ouch
- 保留 7zip:yazi 用作存档预览/提取的可选依赖
- .zshrc: alias x: aunpack → ouch decompress
2026-04-08 13:20:55 +08:00
imbytecat 0b6830f6d8 docs: 添加 WSL 安装 Arch Linux 的说明 2026-04-08 12:56:08 +08:00
imbytecat 9befd389af docs: 修正 AGENTS.md 与重构后代码不符的描述
- zsh 模块描述: oh-my-zsh → 插件 + 自动 chsh(实际无 oh-my-zsh)
- source.py 分区描述: 删除已不存在的"系统文件→用户配置→pacman→AUR"分区
- source.py 结构: 同步删除"校验插件存在性"(对应 source.py 死代码清理)
- 模块组织原则: 删除矛盾的"直接在 source.py 用 File()"路径
- Pacman vs AUR 路径: decman.pacman.packages → @pacman_packages 装饰器
- 常见任务: 添加包/文件/dotfile 步骤更新到模块路径
2026-04-08 12:37:57 +08:00
imbytecat f626c12e49 fix(dev): hook 失败改为 raise SourceError 而非静默警告
全局包安装失败原本只 print 警告,decman 退出码仍为 0,违反声明式语义。
改为汇总后 raise decman.SourceError,符合官方 docstring 推荐(失败应被感知,
下次 sync 会重试)。保留"尝试所有包"逻辑,一次性看到全部失败。
2026-04-08 12:37:49 +08:00
imbytecat 04d517a2c2 refactor: 清理 source.py 死代码与空 __init__.py
- source.py: 删除插件存在性检查,modules import 阶段已会 ImportError
- modules/__init__.py: 删除空文件,PEP 420 namespace package 不需要
2026-04-08 12:37:41 +08:00
imbytecat 171aaa55fd fix(docker): 禁用 systemd-networkd-wait-online 解决 docker 启动卡两分钟 2026-04-08 12:18:04 +08:00
16 changed files with 275 additions and 188 deletions
+32 -22
View File
@@ -14,17 +14,17 @@
. .
├── source.py # decman 主配置入口 ├── source.py # decman 主配置入口
├── modules/ ├── modules/
│ ├── base.py # 基础模块(系统包 + 现代 CLI 工具 + 配置文件 │ ├── base.py # 基础模块(系统包 + CLI 工具 + dotfiles
│ ├── dev.py # 开发模块(语言运行时 + 编辑器 + 工具链) │ ├── dev.py # 开发模块(语言运行时 + LSP + 工具链)
│ ├── docker.py # Docker 模块(packages + systemd units │ ├── docker.py # Docker 模块(packages + systemd units
│ ├── locale.py # locale 模块(files + on_change hook │ ├── locale.py # locale 模块(on_change hook 示例
│ └── zsh.py # Zsh 模块(shell + oh-my-zsh + 插件 │ └── fish.py # Fish 模块(shell + fzf + 自动 chsh
├── system/etc/ # 系统配置文件源 → 部署到 /etc/ ├── system/etc/ # 系统配置文件源 → /etc/
├── home/ # 用户配置文件源 → 部署到 ~/ ├── home/ # 用户配置文件源 → ~/(必须指定 owner
├── scripts/ ├── scripts/
│ ├── install.sh # 引导脚本(git → decman → 首次 sync │ ├── install.sh # 引导脚本(curl | bash
│ └── wsl-init.sh # WSL 首次初始化(创建用户) │ └── wsl-init.sh # WSL 首次初始化
└── pyproject.toml # 开发依赖(decman + 插件,仅用于类型检查) └── pyproject.toml # 开发依赖(decman 插件,仅类型检查)
``` ```
## 命令 ## 命令
@@ -70,15 +70,15 @@ uv sync
files → pacman → aur → systemd files → pacman → aur → systemd
``` ```
`source.py` 中的声明分区按此排列:系统文件 → 用户配置 → modules pacman 包 → AUR 包 `source.py` 是纯模块注册入口,所有 files / packages / units 声明都在各 Module 内部实现
## 代码风格 ## 代码风格
### Pythonsource.py 及模块) ### Pythonsource.py 及模块)
**source.py 结构** **source.py 结构**
- 纯模块注册不直接声明文件或包 - 纯模块注册入口,不直接声明文件或包
- 校验 `SUDO_USER` 和必要插件存在性 - 校验 `SUDO_USER`(插件缺失时 `import modules.*` 阶段会直接 ImportError,无需运行时检查)
- 通过 `decman.modules += [...]` 注册所有模块 - 通过 `decman.modules += [...]` 注册所有模块
**模块模式**(适用于需要 hook 或跨步骤声明的场景): **模块模式**(适用于需要 hook 或跨步骤声明的场景):
@@ -101,10 +101,7 @@ class DockerModule(Module):
return {"docker.socket"} return {"docker.socket"}
``` ```
**何时用模块 vs 直接声明** **模块组织原则**:所有 files / packages / units 声明都通过 Module 封装,按领域拆分(base / dev / docker / locale / fish)。新增功能优先加到对应现有模块;跨领域、需要 lifecycle hook(`on_change` / `after_update`)或需要绑定 packages + systemd units 时再新建模块。
- 需要 `on_change` hook(如 `locale-gen`)→ Module
- 需要绑定 packages + systemd units → Module`@packages` + `@units` 装饰器)
- 纯静态文件、无副作用 → 直接在 `source.py``File()`
### Shell 脚本 ### Shell 脚本
@@ -124,19 +121,32 @@ class DockerModule(Module):
- 示例:`feat(docker): 添加 Docker 支持并重排声明顺序` - 示例:`feat(docker): 添加 Docker 支持并重排声明顺序`
- 分支:直接在 `main` 上工作 - 分支:直接在 `main` 上工作
## 关键依赖关系
`.config/fish/config.fish` 别名与包的绑定(修改前务必检查):
| 别名 | 依赖包 | 位置 |
|------|--------|------|
| `rm="gomi"` | `gomi-bin` (AUR) | `base.py` |
| `cat="bat"` | `bat` | `base.py` |
| `ls="eza"` | `eza` | `base.py` |
| `vi="nvim"` | `neovim` | `dev.py` |
| `lg="lazygit"` | `lazygit` | `dev.py` |
| `x="ouch decompress"` | `ouch` | `base.py` |
## Agent 须知 ## Agent 须知
1. **decman 是唯一真相**:不要手动装包,加到 `source.py` 或模块里,跑 `sudo decman` 1. **decman 是唯一真相**:不要手动装包,加到 `source.py` 或模块里,跑 `sudo decman`
2. **Pacman vs AUR**:用 `pacman -Ss` 确认包在官方仓库还是 AUR分别加到 `decman.pacman.packages``decman.aur.packages` 2. **Pacman vs AUR**:用 `pacman -Ss` 确认包在官方仓库还是 AUR,分别加到对应模块的 `@pacman_packages``@aur_packages` 装饰器方法返回集合
3. **系统文件**:源文件放 `system/`,目录结构对应目标路径(`system/etc/foo.conf``/etc/foo.conf`)。decman 复制(非 symlink)到目标位置。 3. **系统文件**:源文件放 `system/`,目录结构对应目标路径(`system/etc/foo.conf``/etc/foo.conf`)。decman 复制(非 symlink)到目标位置。
4. **用户配置**:源文件放 `home/`,必须指定 `owner=USERNAME` 4. **用户配置**:源文件放 `home/`,必须指定 `owner=USERNAME`
5. **Runs as root**`sudo decman` 以 root 执行 `source.py``SUDO_USER`调用 sudo 的原始用户名不要 fallback。 5. **环境变量**`sudo decman` 以 root 执行`SUDO_USER` 是原始用户名。配置内不要 fallback`os.getenv("USER")`
6. **开发环境**`pyproject.toml` + `uv sync` 管理开发依赖(decman、decman-pacman、decman-systemd),仅用于 IDE 类型检查,不影响运行时。 6. **开发依赖**`pyproject.toml` 仅用 uv 管理类型检查依赖,不影响运行时。修改依赖后运行 `uv sync`
7. **幂等性**:所有脚本和配置必须可安全重复执行。 7. **幂等性**:所有脚本和配置必须可安全重复执行。
@@ -144,11 +154,11 @@ class DockerModule(Module):
## 常见任务 ## 常见任务
**添加包**确认 pacman/AUR → 加到 `source.py` 对应集合 → `sudo decman` **添加包**:确认 pacman/AUR → 加到对应模块的 `@pacman_packages` / `@aur_packages` 方法返回集合 → `sudo decman`
**添加系统文件**`system/` → 在 `source.py``File(source_file=...)``sudo decman` **添加系统文件**:`system/` → 在对应模块的 `files()` 方法`File(source_file=...)``sudo decman`
**添加 dotfile**`home/` → 在 `source.py``File(source_file=..., owner=USERNAME)``sudo decman` **添加 dotfile**:`home/` → 在对应模块的 `files()` 方法`File(source_file=..., owner=self.user)``sudo decman`
**添加需要 systemd 服务的软件**:创建 Module 文件,用 `@packages` + `@units` 装饰器 → 在 `source.py` 注册 → `sudo decman` **添加需要 systemd 服务的软件**:创建 Module 文件,用 `@packages` + `@units` 装饰器 → 在 `source.py` 注册 → `sudo decman`
+11
View File
@@ -5,6 +5,17 @@
## 使用 ## 使用
### 安装 Arch LinuxWSL
需要 Windows 10/11 并已启用 WSL 2。在 PowerShell 中执行:
```powershell
wsl --update
wsl --install archlinux
```
安装完成后,可通过开始菜单的 `archlinux` 应用或命令 `wsl -d archlinux` 启动,首次进入默认以 `root` 身份登录。
### WSL 首次启动(默认 root 登录) ### WSL 首次启动(默认 root 登录)
1. 初始化普通用户: 1. 初始化普通用户:
+90
View File
@@ -0,0 +1,90 @@
fish_add_path "$HOME/go/bin" "$HOME/.bun/bin"
set -gx EDITOR nvim
set -gx VISUAL nvim
set -gx PAGER less
set -gx MANPAGER "sh -c 'col -bx | bat -l man -p'"
set -g fish_greeting
function __prepend_sudo
set -l buffer (commandline)
if test -z "$buffer"
commandline -f up-line
set buffer (commandline)
end
if test -n "$buffer"
if not string match -qr '^sudo\b' -- "$buffer"
commandline -r "sudo $buffer"
end
commandline -f end-of-line repaint
end
end
bind escape,escape __prepend_sudo # Fish 4.0+ 语法:双击 Escape 添加 sudo
bind ctrl-space accept-autosuggestion # Fish 4.0+ 语法
set -gx FZF_DEFAULT_OPTS '--height=50% --layout=reverse --border --preview-window=right:60%'
set -gx FZF_DEFAULT_COMMAND 'fd --type f --hidden --follow --exclude .git'
set -gx FZF_CTRL_T_COMMAND "$FZF_DEFAULT_COMMAND"
set -gx FZF_ALT_C_COMMAND 'fd --type d --hidden --follow --exclude .git'
alias ..='cd ..'
alias ...='cd ../..'
function ls
command eza --icons --group-directories-first --git $argv
end
function l
command eza -la --icons --group-directories-first --git $argv
end
function ll
command eza -l --icons --group-directories-first --git $argv
end
function la
command eza -lA --icons --group-directories-first --git $argv
end
function lt
command eza --tree --level=2 --icons --group-directories-first --git $argv
end
alias cat='bat --paging=never'
alias rm='gomi'
alias lg='lazygit'
alias vi='nvim'
alias x='ouch decompress'
if set -q WSL_DISTRO_NAME
alias pbcopy='clip.exe'
alias pbpaste='powershell.exe -noprofile -c Get-Clipboard'
function keep_current_path --on-event fish_prompt
printf '\e]9;9;%s\e\\' (wslpath -w "$PWD")
end
end
if status is-interactive
zoxide init fish --cmd cd | source
mise activate fish | source
set -g direnv_fish_mode eval_after_arrow
direnv hook fish | source
fzf --fish | source
set -gx ATUIN_NOBIND true
atuin init fish | source
bind \cr _atuin_search
bind up _atuin_bind_up
starship init fish | source
end
if test -f ~/.config/fish/config.local.fish
source ~/.config/fish/config.local.fish
end
+12
View File
@@ -0,0 +1,12 @@
function yy
set -l tmp (mktemp -t yazi-cwd.XXXXXX)
command yazi $argv --cwd-file="$tmp"
if set -l cwd (command cat -- "$tmp")
if test -n "$cwd" -a "$cwd" != "$PWD"
builtin cd -- "$cwd"
end
end
command rm -f -- "$tmp"
end
+3
View File
@@ -1,2 +1,5 @@
[settings] [settings]
trusted_config_paths = ["/"] trusted_config_paths = ["/"]
[tools]
usage = "latest"
-64
View File
@@ -1,64 +0,0 @@
# ── PATH ──
export PATH="$HOME/go/bin:$HOME/.bun/bin:$PATH"
# ── Shell 选项 ──
setopt AUTO_CD # 输目录名直接 cd
setopt INTERACTIVE_COMMENTS # 允许交互式 # 注释
setopt NO_BEEP # 关蜂鸣
# ── 补全系统(必须在 fzf-tab 之前)──
autoload -Uz compinit && compinit
# ── 外部插件 ──
source /usr/share/zsh/plugins/fzf-tab-git/fzf-tab.plugin.zsh
source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh # 必须最后
# ── 双击 ESC 自动加 sudo ──
sudo-command-line() {
[[ -z $BUFFER ]] && zle up-history
[[ $BUFFER != sudo\ * ]] && BUFFER="sudo $BUFFER"
zle end-of-line
}
zle -N sudo-command-line
bindkey '\e\e' sudo-command-line
# ── 工具初始化(顺序重要)──
eval "$(starship init zsh)"
eval "$(zoxide init zsh)"
eval "$(mise activate zsh)"
eval "$(direnv hook zsh)"
eval "$(fzf --zsh)" # Ctrl+T 搜文件, Alt+C 搜目录
eval "$(atuin init zsh)" # 必须在 fzf 之后,接管 Ctrl+R
# ── 别名 ──
# 导航
alias cd="z"
alias cdi="zi"
alias ..="cd .."
alias ...="cd ../.."
# 文件列表
alias ls="eza --icons --group-directories-first"
alias ll="eza -la --icons --git --group-directories-first"
alias la="eza -a --icons --group-directories-first"
alias lt="eza --tree --level=2 --icons"
# 工具
alias cat="bat --paging=never"
alias rm="trash-put"
alias lg="lazygit"
alias vi="nvim"
alias x="aunpack" # 万能解压(支持 tar/zip/7z/rar 等)
# 网络
alias http="xh"
# ── WSL 剪贴板 ──
if [[ -n "$WSL_DISTRO_NAME" ]]; then
alias pbcopy="clip.exe"
alias pbpaste="powershell.exe -noprofile -c Get-Clipboard"
fi
# ── Local ──
[[ -f ~/.zshrc.local ]] && source ~/.zshrc.local
View File
+6 -8
View File
@@ -8,7 +8,7 @@ class BaseModule(Module):
super().__init__("base") super().__init__("base")
self.user = user self.user = user
def files(self): def files(self) -> dict[str, File]:
return { return {
"/etc/pacman.conf": File( "/etc/pacman.conf": File(
source_file="./system/etc/pacman.conf", source_file="./system/etc/pacman.conf",
@@ -34,12 +34,14 @@ class BaseModule(Module):
def pacman_packages(self) -> set[str]: def pacman_packages(self) -> set[str]:
return { return {
"7zip", "7zip",
"atool", "adobe-source-han-sans-cn-fonts",
"adobe-source-han-serif-cn-fonts",
"atuin", "atuin",
"base-devel", "base-devel",
"base", "base",
"bat", "bat",
"btop", "btop",
"chromium",
"curl", "curl",
"direnv", "direnv",
"duf", "duf",
@@ -51,18 +53,13 @@ class BaseModule(Module):
"git", "git",
"jq", "jq",
"micro", "micro",
"ouch",
"procs", "procs",
"ripgrep", "ripgrep",
"sd", "sd",
"starship", "starship",
"sudo", "sudo",
"tealdeer",
"trash-cli",
"unrar",
"unzip",
"vim",
"wget", "wget",
"xh",
"yazi", "yazi",
"yq", "yq",
"zoxide", "zoxide",
@@ -72,4 +69,5 @@ class BaseModule(Module):
def aur_packages(self) -> set[str]: def aur_packages(self) -> set[str]:
return { return {
"decman", "decman",
"gomi-bin",
} }
+9 -8
View File
@@ -2,13 +2,14 @@ import decman
from decman import File, Module from decman import File, Module
from decman.plugins.pacman import packages as pacman_packages from decman.plugins.pacman import packages as pacman_packages
BUN_GLOBAL_PACKAGES = [ BUN_GLOBAL_PACKAGES: list[str] = [
"@vue/language-server", "@vue/language-server",
"dockerfile-language-server-nodejs", "dockerfile-language-server-nodejs",
"oxlint",
"opencode-ai", "opencode-ai",
] ]
GO_INSTALL_PACKAGES = [ GO_INSTALL_PACKAGES: list[str] = [
"github.com/code-yeongyu/go-claude-code-comment-checker/cmd/comment-checker@latest", "github.com/code-yeongyu/go-claude-code-comment-checker/cmd/comment-checker@latest",
] ]
@@ -18,7 +19,7 @@ class DevModule(Module):
super().__init__("dev") super().__init__("dev")
self.user = user self.user = user
def files(self): def files(self) -> dict[str, File]:
return { return {
f"/home/{self.user}/.config/mise/config.toml": File( f"/home/{self.user}/.config/mise/config.toml": File(
source_file="./home/.config/mise/config.toml", source_file="./home/.config/mise/config.toml",
@@ -50,7 +51,7 @@ class DevModule(Module):
"zellij", "zellij",
} }
def after_update(self, store): def after_update(self, store: object) -> None:
failures: list[str] = [] failures: list[str] = []
for pkg in BUN_GLOBAL_PACKAGES: for pkg in BUN_GLOBAL_PACKAGES:
try: try:
@@ -78,7 +79,7 @@ class DevModule(Module):
except Exception as e: except Exception as e:
failures.append(f"go: {pkg} ({e})") failures.append(f"go: {pkg} ({e})")
if failures: if failures:
print(f"\n{len(failures)} 个全局包安装失败:") raise decman.SourceError(
for f in failures: f"{len(failures)} 个全局包安装失败:\n"
print(f" - {f}") + "\n".join(f" - {f}" for f in failures)
print() )
+32 -3
View File
@@ -1,4 +1,5 @@
import subprocess import subprocess
from pathlib import Path
import decman import decman
from decman import Module from decman import Module
@@ -6,10 +7,19 @@ from decman.plugins.pacman import packages as pacman_packages
from decman.plugins.systemd import units from decman.plugins.systemd import units
def _is_wsl() -> bool:
"""检测是否在 WSL 环境"""
try:
return "microsoft" in Path("/proc/version").read_text().lower()
except OSError:
return False
class DockerModule(Module): class DockerModule(Module):
def __init__(self, user: str): def __init__(self, user: str):
super().__init__("docker") super().__init__("docker")
self.user = user self.user = user
self._is_wsl = _is_wsl()
@pacman_packages @pacman_packages
def pacman_packages(self) -> set[str]: def pacman_packages(self) -> set[str]:
@@ -19,11 +29,30 @@ class DockerModule(Module):
def units(self) -> set[str]: def units(self) -> set[str]:
return {"docker.socket"} return {"docker.socket"}
def after_update(self, store): def after_update(self, store: object) -> None:
self._ensure_user_in_docker_group()
if self._is_wsl:
self._disable_networkd_wait_online()
def _ensure_user_in_docker_group(self) -> None:
result = subprocess.run( result = subprocess.run(
["id", "-nG", self.user], capture_output=True, text=True ["id", "-nG", self.user],
capture_output=True,
text=True,
check=False,
) )
if result.returncode != 0: if result.returncode != 0:
return raise decman.SourceError(f"无法读取用户 {self.user} 的组信息")
if "docker" not in result.stdout.split(): if "docker" not in result.stdout.split():
decman.prg(["gpasswd", "-a", self.user, "docker"]) decman.prg(["gpasswd", "-a", self.user, "docker"])
def _disable_networkd_wait_online(self) -> None:
"""WSL 环境:禁用 systemd-networkd-wait-online 避免启动阻塞"""
result = subprocess.run(
["systemctl", "is-enabled", "systemd-networkd-wait-online.service"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0 and result.stdout.strip() == "enabled":
decman.prg(["systemctl", "disable", "systemd-networkd-wait-online.service"])
+44
View File
@@ -0,0 +1,44 @@
import subprocess
import decman
from decman import File, Module
from decman.plugins.pacman import packages as pacman_packages
class FishModule(Module):
def __init__(self, user: str):
super().__init__("fish")
self.user = user
def files(self) -> dict[str, File]:
return {
f"/home/{self.user}/.config/fish/config.fish": File(
source_file="./home/.config/fish/config.fish",
owner=self.user,
),
f"/home/{self.user}/.config/fish/functions/yy.fish": File(
source_file="./home/.config/fish/functions/yy.fish",
owner=self.user,
),
}
@pacman_packages
def pacman_packages(self) -> set[str]:
return {
"fish",
"fzf",
}
def after_update(self, store: object) -> None:
result = subprocess.run(
["getent", "passwd", self.user],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
raise decman.SourceError(f"无法读取用户 {self.user} 的 passwd 信息")
# passwd 格式: name:x:uid:gid:gecos:home:shell
shell = result.stdout.strip().split(":")[-1]
if shell != "/usr/bin/fish":
decman.prg(["chsh", "-s", "/usr/bin/fish", self.user])
+2 -2
View File
@@ -6,11 +6,11 @@ class LocaleModule(Module):
def __init__(self): def __init__(self):
super().__init__("locale") super().__init__("locale")
def files(self): def files(self) -> dict[str, File]:
return { return {
"/etc/locale.conf": File(content="LANG=en_US.UTF-8\n"), "/etc/locale.conf": File(content="LANG=en_US.UTF-8\n"),
"/etc/locale.gen": File(content="en_US.UTF-8 UTF-8\n"), "/etc/locale.gen": File(content="en_US.UTF-8 UTF-8\n"),
} }
def on_change(self, store): def on_change(self, store: object) -> None:
decman.prg(["locale-gen"]) decman.prg(["locale-gen"])
-47
View File
@@ -1,47 +0,0 @@
import subprocess
import decman
from decman import File, Module
from decman.plugins.aur import packages as aur_packages
from decman.plugins.pacman import packages as pacman_packages
class ZshModule(Module):
def __init__(self, user: str):
super().__init__("zsh")
self.user = user
def files(self):
return {
f"/home/{self.user}/.zshrc": File(
source_file="./home/.zshrc",
owner=self.user,
),
}
@pacman_packages
def pacman_packages(self) -> set[str]:
return {
"fzf",
"zsh",
"zsh-autosuggestions",
"zsh-completions",
"zsh-syntax-highlighting",
}
@aur_packages
def aur_packages(self) -> set[str]:
return {
"fzf-tab-git",
}
def after_update(self, store):
result = subprocess.run(
["getent", "passwd", self.user], capture_output=True, text=True
)
if result.returncode != 0:
return
# passwd 格式: name:x:uid:gid:gecos:home:shell
shell = result.stdout.strip().split(":")[-1]
if shell != "/usr/bin/zsh":
decman.prg(["chsh", "-s", "/usr/bin/zsh", self.user])
+5 -2
View File
@@ -5,7 +5,10 @@ REPO_URL="https://git.furtherverse.com/imbytecat/archlinux-config.git"
CONFIG_DIR="$HOME/.config/archlinux-config" CONFIG_DIR="$HOME/.config/archlinux-config"
echo "🔑 验证 sudo 权限..." echo "🔑 验证 sudo 权限..."
sudo -v < /dev/tty || { echo "❌ 需要 sudo 权限,请确认当前用户已配置 sudo"; exit 1; } sudo -v </dev/tty || {
echo "❌ 需要 sudo 权限,请确认当前用户已配置 sudo"
exit 1
}
echo "🔄 更新系统..." echo "🔄 更新系统..."
sudo pacman -Syu --noconfirm sudo pacman -Syu --noconfirm
@@ -36,7 +39,7 @@ echo "⚙️ 应用系统配置..."
sudo decman --source "$CONFIG_DIR/source.py" </dev/tty sudo decman --source "$CONFIG_DIR/source.py" </dev/tty
echo "" echo ""
echo "🎉 安装完成!重新登录以使用 zsh。" echo "🎉 安装完成!重新登录以使用 fish。"
echo "" echo ""
echo "后续更新配置:" echo "后续更新配置:"
echo " cd $CONFIG_DIR && git pull && sudo decman" echo " cd $CONFIG_DIR && git pull && sudo decman"
+2 -2
View File
@@ -1,13 +1,13 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
if [ "$(id -u)" -ne 0 ]; then if [[ "$(id -u)" -ne 0 ]]; then
echo "❌ 请以 root 身份运行此脚本" echo "❌ 请以 root 身份运行此脚本"
exit 1 exit 1
fi fi
USERNAME="${1:-}" USERNAME="${1:-}"
if [ -z "$USERNAME" ]; then if [[ -z "$USERNAME" ]]; then
echo "用法: wsl-init.sh <用户名>" echo "用法: wsl-init.sh <用户名>"
echo "示例: wsl-init.sh imbytecat" echo "示例: wsl-init.sh imbytecat"
exit 1 exit 1
+2 -5
View File
@@ -6,10 +6,7 @@ import modules.base
import modules.dev import modules.dev
import modules.docker import modules.docker
import modules.locale import modules.locale
import modules.zsh import modules.fish
if decman.pacman is None or decman.aur is None or decman.systemd is None:
raise decman.SourceError("缺少必要插件,请检查 decman 安装")
USERNAME = os.environ.get("SUDO_USER") USERNAME = os.environ.get("SUDO_USER")
if not USERNAME: if not USERNAME:
@@ -20,5 +17,5 @@ decman.modules += [
modules.dev.DevModule(USERNAME), modules.dev.DevModule(USERNAME),
modules.docker.DockerModule(USERNAME), modules.docker.DockerModule(USERNAME),
modules.locale.LocaleModule(), modules.locale.LocaleModule(),
modules.zsh.ZshModule(USERNAME), modules.fish.FishModule(USERNAME),
] ]