Compare commits
17
Commits
@@ -11,13 +11,26 @@ matching guide before starting:
|
||||
- [`agents/skills/visual-verification.md`](agents/skills/visual-verification.md) - verifying any change with a visual effect in the running UI
|
||||
- [`agents/skills/migrations.md`](agents/skills/migrations.md) - creating or changing migrations under `migrations/`
|
||||
|
||||
# OmarchyCN Layer
|
||||
|
||||
This fork carries the OmarchyCN China-integration layer on top of upstream `basecamp/omarchy`:
|
||||
|
||||
- `bin/omarchycn` routes to `omarchy cn <command>`; all cn commands are `bin/omarchy-cn-*` and follow the upstream bin conventions (metadata, helpers, `$OMARCHY_PATH` — sole exception: the overlay installer bootstraps by resolving its own checkout)
|
||||
- `cn/` holds the data layer: `mirrors.json`, `dev-mirrors.json`, `apps.json`, `registry/` (AI providers/harnesses/compatibility), `fcitx5/`, `fontconfig/`, `keys/`, `lib/` (sourced helpers), `release` (cn release number)
|
||||
- cn migrations live in `cn/migrations/*.sh`, run by `omarchy-cn-update` with per-file completion markers under `~/.local/state/omarchycn/`
|
||||
- Packaging: `packages/omarchy-pkgs-cn.patch` must be applied to the sibling `omarchy-pkgs` checkout so `omarchy-dev` ships `cn/`; keyring in `packages/omarchycn-keyring/`
|
||||
- ISO: `packages/omarchy-iso-cn.patch` must be applied to the sibling `omarchy-iso` checkout — Chinese installer (cage+foot graphical console with English VT fallback) and live-env packages
|
||||
- Release process: `docs/release-checklist.md`; signing: `docs/release-signing.md`; pacman repo: `docs/pacman-repo.md`
|
||||
- Upstream sync: `.gitea/workflows/upstream-sync.yml` opens a PR per upstream change; keep upstream file edits minimal (currently: one `GROUP_DESCRIPTIONS[cn]` line in `bin/omarchy`, the OmarchyCN section in `default/omarchy/omarchy-menu.jsonc`, a rewritten `README.md` (known recurring sync conflict, resolve toward ours), `AGENTS.md` additions, and two `.gitignore` lines)
|
||||
- cn tests: `test/shell.d/omarchycn-test.sh`, `test/shell.d/omarchycn-ai-test.sh`
|
||||
|
||||
# Documentation Layout
|
||||
|
||||
Three documentation trees, split by genre and audience:
|
||||
|
||||
- `agents/skills/` - task procedure ("do this when doing X"), for anyone working on the codebase
|
||||
- `docs/` - reference on how the system is shaped (file layout, update pipeline, theming, shell architecture), for anyone working on the codebase; skills link here for depth
|
||||
- `manual/` - end-user documentation for using Omarchy, published; never codebase internals
|
||||
- `manual/` - end-user documentation for using Omarchy, published; never codebase internals. Chinese user manual for the cn layer lives in `manual/zh-cn/`
|
||||
|
||||
# Style
|
||||
|
||||
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Show or set the OmarchyCN release channel
|
||||
# omarchy:args=[stable|beta|nightly]
|
||||
# omarchy:examples=omarchycn channel | omarchycn channel beta
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CHANNEL_FILE="$HOME/.config/omarchycn/channel"
|
||||
|
||||
if (( $# == 0 )); then
|
||||
if [[ -f $CHANNEL_FILE ]]; then
|
||||
cat "$CHANNEL_FILE"
|
||||
else
|
||||
echo "beta (default)"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
stable | beta | nightly)
|
||||
mkdir -p "${CHANNEL_FILE%/*}"
|
||||
echo "$1" > "$CHANNEL_FILE"
|
||||
echo "Channel: $1"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown channel: $1 (stable|beta|nightly)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
+41
-2
@@ -19,10 +19,49 @@ if [[ ! -d $src/.git ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
channel=$(omarchy-cn-channel)
|
||||
channel=${channel%% *}
|
||||
|
||||
old=$(git -C "$src" rev-parse --short HEAD)
|
||||
git -C "$src" pull --ff-only
|
||||
git -C "$src" fetch -q --tags origin
|
||||
|
||||
case "$channel" in
|
||||
nightly)
|
||||
git -C "$src" checkout -q quattro
|
||||
git -C "$src" pull --ff-only origin quattro
|
||||
;;
|
||||
beta)
|
||||
tag=$(git -C "$src" tag -l "*-cn.*" | sort -V | tail -1)
|
||||
if [[ -z $tag ]]; then
|
||||
echo "beta 通道无可用发布 tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
git -C "$src" checkout -q "$tag"
|
||||
;;
|
||||
stable)
|
||||
tag=$(git -C "$src" tag -l "*-cn.*" | grep -vE "alpha|beta|rc" | sort -V | tail -1 || true)
|
||||
if [[ -z $tag ]]; then
|
||||
echo "当前尚无 stable 发布(omarchycn channel beta 可跟随预发布)" >&2
|
||||
exit 1
|
||||
fi
|
||||
git -C "$src" checkout -q "$tag"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown channel: $channel" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Never move onto a tree that predates the channel mechanism: its update
|
||||
# command cannot switch back, stranding the install
|
||||
if [[ ! -f $src/bin/omarchy-cn-channel ]]; then
|
||||
git -C "$src" checkout -q "$old"
|
||||
echo "$channel 通道的目标发布早于通道机制,已回退;请使用 nightly 或更新的发布" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
new=$(git -C "$src" rev-parse --short HEAD)
|
||||
echo "Source: $old -> $new"
|
||||
echo "Channel: $channel, source: $old -> $new"
|
||||
|
||||
"$src/bin/omarchy-cn-install-overlay"
|
||||
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
# no prompt. lazydocker needs the root-owned Docker socket, so when the group is
|
||||
# absent, gate that access behind a polkit prompt. If the user has opted into
|
||||
# sudoless Docker (omarchy-setup-security-sudoless-docker), the socket is already
|
||||
# reachable, so run lazydocker directly. pkexec sanitizes the environment, so
|
||||
# carry TERM through for the TUI to render and run lazydocker from root's PATH.
|
||||
if id -nG 2>/dev/null | grep -qw docker; then
|
||||
exec lazydocker
|
||||
else
|
||||
# reachable, so run lazydocker directly — omarchy-sudo-docker answers that for
|
||||
# this session, so the prompt stays until the reboot that grants the group.
|
||||
# pkexec sanitizes the environment, so carry TERM through for the TUI to render
|
||||
# and run lazydocker from root's PATH.
|
||||
if omarchy-sudo-docker; then
|
||||
exec pkexec /usr/bin/env TERM="${TERM:-xterm-256color}" lazydocker
|
||||
else
|
||||
exec lazydocker
|
||||
fi
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
|
||||
set -e
|
||||
|
||||
if ! id -nG "$USER" 2>/dev/null | grep -qw docker; then
|
||||
# Ask about the configured groups, not this session's: right after enabling,
|
||||
# sudoless Docker is on for the account even though the running session still
|
||||
# needs a prompt, and this command is what turns it back off.
|
||||
if omarchy-sudo-docker --configured; then
|
||||
echo "Sudoless Docker is not enabled: $USER is not in the docker group."
|
||||
exit 0
|
||||
fi
|
||||
@@ -13,13 +16,19 @@ fi
|
||||
echo "Removing $USER from the docker group..."
|
||||
sudo gpasswd -d "$USER" docker >/dev/null
|
||||
|
||||
# Group membership is fixed at login, so the running session keeps its docker
|
||||
# access until it ends. Flag a reboot so omarchy-update-restart prompts for one
|
||||
# (and the bar shows it pending); a plain log out and back in works too.
|
||||
# Group membership is only re-read by a fresh session, and in practice logging
|
||||
# out or newgrp isn't enough — only a reboot reliably applies it. Record it so a
|
||||
# later `omarchy update` still prompts (omarchy-update-restart reads this), then
|
||||
# offer to do it now.
|
||||
omarchy-state set reboot-required
|
||||
|
||||
echo ""
|
||||
echo "Sudoless Docker DISABLED. Reboot (or log out and back in) for the change to take effect."
|
||||
echo "Docker access now goes through a polkit/sudo prompt again: the Docker TUI"
|
||||
echo "(Super + Shift + D) and the Windows VM will ask when they need it, and the"
|
||||
echo "plain 'docker' CLI runs under sudo."
|
||||
echo "Sudoless Docker DISABLED. Docker access goes through a polkit/sudo prompt"
|
||||
echo "again: the Docker TUI (Super + Shift + D) and the Windows VM ask when they"
|
||||
echo "need it, and the plain 'docker' CLI runs under sudo. It takes effect after a reboot."
|
||||
echo ""
|
||||
# The migration reuses this command during 'omarchy update' and defers the
|
||||
# reboot to omarchy-update-restart, so it doesn't cut the update short.
|
||||
if [[ -z ${OMARCHY_DEFER_REBOOT:-} ]] && gum confirm "Reboot now to apply?"; then
|
||||
omarchy-system-reboot
|
||||
fi
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
set -e
|
||||
|
||||
if id -nG "$USER" 2>/dev/null | grep -qw docker; then
|
||||
# Ask about the configured groups, not this session's: once enabled it stays
|
||||
# enabled for the account even before the reboot that lets this session use it.
|
||||
if ! omarchy-sudo-docker --configured; then
|
||||
echo "Sudoless Docker is already enabled: $USER is in the docker group."
|
||||
echo "To disable it again, run: omarchy-remove-security-sudoless-docker"
|
||||
exit 0
|
||||
@@ -28,14 +30,20 @@ echo ""
|
||||
|
||||
if gum confirm "Enable sudoless Docker? This gives anything running as you passwordless root."; then
|
||||
sudo usermod -aG docker "$USER"
|
||||
# Group membership is fixed at login, so docker won't be reachable without a
|
||||
# prompt until the session restarts. Flag a reboot so omarchy-update-restart
|
||||
# prompts for one (and the bar shows it pending).
|
||||
# A new docker group membership is only picked up by a fresh session, and in
|
||||
# practice logging out or newgrp isn't enough — only a reboot reliably applies
|
||||
# it. Record it so a later `omarchy update` still prompts
|
||||
# (omarchy-update-restart reads this), then offer to do it now.
|
||||
omarchy-state set reboot-required
|
||||
echo ""
|
||||
echo "Sudoless Docker ENABLED. Reboot, or log out and back in (or run 'newgrp docker'),"
|
||||
echo "for the new group membership to take effect."
|
||||
echo "To disable it again, run: omarchy-remove-security-sudoless-docker"
|
||||
echo "Sudoless Docker ENABLED. It takes effect after a reboot."
|
||||
echo "To disable it again: Setup > Security > Sudoless Docker."
|
||||
echo ""
|
||||
# The migration reuses this command during 'omarchy update' and defers the
|
||||
# reboot to omarchy-update-restart, so it doesn't cut the update short.
|
||||
if [[ -z ${OMARCHY_DEFER_REBOOT:-} ]] && gum confirm "Reboot now to apply?"; then
|
||||
omarchy-system-reboot
|
||||
fi
|
||||
else
|
||||
echo "Aborted. No changes made. Docker access still goes through a prompt."
|
||||
fi
|
||||
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Succeed when Docker needs sudo, fail when it can be used directly
|
||||
# omarchy:args=[--configured]
|
||||
# omarchy:examples=omarchy-sudo-docker && echo "needs sudo" | omarchy-sudo-docker --configured
|
||||
# omarchy:hidden=true
|
||||
|
||||
# The docker group is root-equivalent, so Omarchy leaves users out of it by
|
||||
# default and reaches the daemon through a prompt instead. Everything that has
|
||||
# to make that choice asks here rather than testing group membership itself.
|
||||
#
|
||||
# Two questions, because they have different answers between toggling sudoless
|
||||
# Docker and the reboot that applies it (group membership is fixed when the
|
||||
# session is created):
|
||||
#
|
||||
# (default) Does Docker need sudo *right now*? Answered by whether this
|
||||
# process can actually reach the socket, which is what decides
|
||||
# if a command must elevate. Still true in the window after
|
||||
# sudoless Docker is enabled but before the reboot.
|
||||
# --configured Will it need sudo once the account's groups take effect?
|
||||
# Answered from the account's configured groups, so the menu
|
||||
# offers the toggle that can actually change state.
|
||||
#
|
||||
# Succeeds (exit 0) when sudo is needed, so it reads as `if omarchy-sudo-docker`.
|
||||
|
||||
DOCKER_SOCKET="${OMARCHY_DOCKER_SOCKET:-/var/run/docker.sock}"
|
||||
|
||||
case "${1:-}" in
|
||||
--configured)
|
||||
# An account in the docker group will not need sudo after the next login.
|
||||
id -nG "$USER" 2>/dev/null | grep -qw docker && exit 1
|
||||
exit 0
|
||||
;;
|
||||
"")
|
||||
# A socket we can write is a daemon we can drive without elevating. A missing
|
||||
# socket counts as needing sudo: reaching it means starting it as root anyway.
|
||||
[[ -w $DOCKER_SOCKET ]] && exit 1
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Usage: omarchy-sudo-docker [--configured]" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -31,8 +31,11 @@ CONTAINER="omarchy-windows"
|
||||
|
||||
# --- privilege helpers -------------------------------------------------------
|
||||
|
||||
# True when the user can reach the Docker socket directly (sudoless Docker on).
|
||||
in_docker_group() { id -nG 2>/dev/null | grep -qw docker; }
|
||||
# True when this session can reach the Docker socket directly (sudoless Docker
|
||||
# on and in effect). Asking about the socket rather than the configured groups
|
||||
# keeps the prompt in place through the window where sudoless Docker is enabled
|
||||
# but the reboot that grants the group has not happened yet.
|
||||
docker_needs_sudo() { omarchy-sudo-docker; }
|
||||
|
||||
# The command to hand pkexec for the privileged re-exec. pkexec runs whatever
|
||||
# executable it is given (after authorization) and only shows the path in the
|
||||
@@ -64,7 +67,7 @@ priv_target() {
|
||||
priv() {
|
||||
local action="$1"
|
||||
shift
|
||||
if [[ $action != write_compose ]] && in_docker_group; then
|
||||
if [[ $action != write_compose ]] && ! docker_needs_sudo; then
|
||||
"__priv_$action" "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2
|
||||
3
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
"setup.security.fido2": {"icon":"","label":"Fido2","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"},
|
||||
"setup.security.sshd": {"icon":"","label":"SSHD","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sshd"},
|
||||
"setup.security.passwordless-sudo": {"icon":"","label":"Passwordless Sudo","action":"omarchy-launch-floating-terminal-with-presentation omarchy-sudo-passwordless"},
|
||||
"setup.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sudoless-docker"},
|
||||
"setup.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","when":"omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sudoless-docker"},
|
||||
"setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""},
|
||||
"setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"},
|
||||
"setup.config.xcompose": {"icon":"","label":"XCompose","action":"omarchy-launch-config-editor ~/.XCompose && omarchy-restart-xcompose"},
|
||||
@@ -291,7 +291,7 @@
|
||||
"remove.security.fingerprint": {"icon":"","label":"Fingerprint","when":"omarchy-pkg-present fprintd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fingerprint"},
|
||||
"remove.security.fido2": {"icon":"","label":"Fido2","when":"omarchy-pkg-present pam-u2f","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fido2"},
|
||||
"remove.security.sshd": {"icon":"","label":"SSHD","when":"systemctl is-enabled --quiet sshd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sshd"},
|
||||
"remove.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","when":"id -nG | grep -qw docker","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sudoless-docker"},
|
||||
"remove.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","when":"! omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sudoless-docker"},
|
||||
"remove.browser.chrome": {"icon":"","label":"Chrome","when":"omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser chrome'"},
|
||||
"remove.browser.edge": {"icon":"","label":"Edge","when":"omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser edge'"},
|
||||
"remove.browser.brave": {"icon":"","label":"Brave","when":"omarchy-pkg-present brave-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser brave'"},
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,30 @@
|
||||
# Release Checklist
|
||||
|
||||
每次发布 `<upstream>-cn.<n>` 按序执行,任一步失败即停止:
|
||||
|
||||
1. `./test/all`:相对上游基线零新增失败;`test/shell.d/omarchycn-*.sh` 全绿
|
||||
2. 用 `--local-source` 重建 ISO(`packages/omarchy-pkgs-cn.patch` 已应用到 omarchy-pkgs)
|
||||
3. 验证 omarchy-dev 包含 cn 层(`tar -tf … | grep usr/share/omarchy/cn/`)
|
||||
4. QEMU OVMF UEFI 冒烟:进入安装器欢迎屏
|
||||
5. `sha256sum` → `SHA256SUMS.txt`;签名 SUMS 与 ISO(发布子钥)
|
||||
6. SBOM 两份:syft(live airootfs)+ 离线仓库 .PKGINFO 采集
|
||||
7. `release.json`(版本、双向提交、包版本表、迁移列表、min_compatible、产物清单)→ 签名 release.json
|
||||
8. 建 tag 与 Release,上传全部产物,Release Notes 写明上游基线与已知问题
|
||||
9. 匿名回读已发布 ISO 并 sha256 复核 == 本地构建值
|
||||
10. `cn/release` 数字 +1,提交
|
||||
|
||||
## 版本规则
|
||||
|
||||
`<omarchy-upstream-version>-cn.<n>`;`min_compatible` 只在有破坏性迁移时前移。
|
||||
|
||||
# 上游同步 SLA
|
||||
|
||||
- `upstream-sync.yml` 每日拉取 basecamp/omarchy quattro,自动开同步 PR(含试合并冲突标注)
|
||||
- 常规变更:7 天内完成审查合并;上游安全修复:48 小时内
|
||||
- 合并后必须重跑第 1 步测试门禁
|
||||
|
||||
# 安全响应
|
||||
|
||||
- 接报渠道见 `SECURITY.md`,72 小时内确认
|
||||
- 涉及发布密钥泄露:按 `docs/release-signing.md` 轮换流程处理,吊销并公告
|
||||
- 修复发布走本清单完整流程,不走捷径
|
||||
@@ -24,6 +24,49 @@
|
||||
OMARCHY_FORM_BACK=1
|
||||
OMARCHY_FORM_SIGNAL=130
|
||||
|
||||
# Prompt strings; the ISO's graphical console exports OMARCHY_INSTALL_LANG=zh
|
||||
if [[ ${OMARCHY_INSTALL_LANG:-} == zh ]]; then
|
||||
OMARCHY_L_KEYBOARD_HEADER="选择键盘布局"
|
||||
OMARCHY_L_USERNAME_PROMPT="用户名> "
|
||||
OMARCHY_L_USERNAME_HINT="仅限字母数字、不含空格(如 dhh)"
|
||||
OMARCHY_L_USERNAME_RESERVED="该用户名已被系统保留"
|
||||
OMARCHY_L_USERNAME_TAKEN="该用户名在本机已存在"
|
||||
OMARCHY_L_USERNAME_INVALID="用户名必须为字母数字且不含空格"
|
||||
OMARCHY_L_PASSWORD_PROMPT="密码> "
|
||||
OMARCHY_L_PASSWORD_HINT="用于用户与 root,启用加密时也用于磁盘解锁"
|
||||
OMARCHY_L_CONFIRM_PROMPT="确认密码> "
|
||||
OMARCHY_L_CONFIRM_HINT="需与刚输入的密码一致"
|
||||
OMARCHY_L_PASSWORD_BLANK="密码不能为空!"
|
||||
OMARCHY_L_PASSWORD_MISMATCH="两次输入的密码不一致!"
|
||||
OMARCHY_L_FULLNAME_PROMPT="姓名> "
|
||||
OMARCHY_L_IDENTITY_HINT="用于 git 身份(回车跳过)"
|
||||
OMARCHY_L_EMAIL_PROMPT="邮箱> "
|
||||
OMARCHY_L_HOSTNAME_PROMPT="主机名> "
|
||||
OMARCHY_L_HOSTNAME_HINT="字母、数字与连字符(回车默认 omarchy)"
|
||||
OMARCHY_L_HOSTNAME_INVALID="主机名须为 1-63 位字母/数字/连字符,且不能以连字符开头或结尾"
|
||||
OMARCHY_L_TIMEZONE_HEADER="时区"
|
||||
else
|
||||
OMARCHY_L_KEYBOARD_HEADER="Select keyboard layout"
|
||||
OMARCHY_L_USERNAME_PROMPT="Username> "
|
||||
OMARCHY_L_USERNAME_HINT="Alphanumeric without spaces (like dhh)"
|
||||
OMARCHY_L_USERNAME_RESERVED="Username is reserved for system"
|
||||
OMARCHY_L_USERNAME_TAKEN="That username already exists on this machine"
|
||||
OMARCHY_L_USERNAME_INVALID="Username must be alphanumeric with no spaces"
|
||||
OMARCHY_L_PASSWORD_PROMPT="Password> "
|
||||
OMARCHY_L_PASSWORD_HINT="Used for user + root, and disk encryption when enabled"
|
||||
OMARCHY_L_CONFIRM_PROMPT="Confirm> "
|
||||
OMARCHY_L_CONFIRM_HINT="Must match the password you just typed"
|
||||
OMARCHY_L_PASSWORD_BLANK="Your password can't be blank!"
|
||||
OMARCHY_L_PASSWORD_MISMATCH="Passwords didn't match!"
|
||||
OMARCHY_L_FULLNAME_PROMPT="Full name> "
|
||||
OMARCHY_L_IDENTITY_HINT="Used for git authentication (hit return to skip)"
|
||||
OMARCHY_L_EMAIL_PROMPT="Email address> "
|
||||
OMARCHY_L_HOSTNAME_PROMPT="Hostname> "
|
||||
OMARCHY_L_HOSTNAME_HINT="Letters, digits, and dashes (or return for 'omarchy')"
|
||||
OMARCHY_L_HOSTNAME_INVALID="Hostname must be 1-63 letters, digits, or dashes, and cannot start or end with a dash"
|
||||
OMARCHY_L_TIMEZONE_HEADER="Timezone"
|
||||
fi
|
||||
|
||||
# The English layouts lead, then everything else alphabetically. gum choose
|
||||
# paginates in --height-sized pages and jumps to the page holding --selected,
|
||||
# so an alphabetical English (US) landed deep enough to sit alone at the edge
|
||||
@@ -94,7 +137,7 @@ omarchy_username_taken() { return 1; }
|
||||
omarchy_prompt_keyboard() {
|
||||
local choice status
|
||||
choice=$(printf '%s\n' "$OMARCHY_KEYBOARD_LAYOUTS" | cut -d'|' -f1 |
|
||||
gum choose --height 10 --selected "English (US)" --header "Select keyboard layout") && status=0 || status=$?
|
||||
gum choose --height 10 --selected "English (US)" --header "$OMARCHY_L_KEYBOARD_HEADER") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
|
||||
keyboard_label="$choice"
|
||||
@@ -104,19 +147,19 @@ omarchy_prompt_keyboard() {
|
||||
omarchy_prompt_username() {
|
||||
local status
|
||||
while true; do
|
||||
username=$(gum input --placeholder "Alphanumeric without spaces (like dhh)" --prompt.foreground="#845DF9" --prompt "Username> ") && status=0 || status=$?
|
||||
username=$(gum input --placeholder "$OMARCHY_L_USERNAME_HINT" --prompt.foreground="#845DF9" --prompt "$OMARCHY_L_USERNAME_PROMPT") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
|
||||
if [[ "$username" =~ $OMARCHY_USERNAME_PATTERN ]]; then
|
||||
if [[ "$username" =~ $OMARCHY_RESERVED_USERNAMES ]]; then
|
||||
notice "Username is reserved for system" 1
|
||||
notice "$OMARCHY_L_USERNAME_RESERVED" 1
|
||||
elif omarchy_username_taken "$username"; then
|
||||
notice "That username already exists on this machine" 1
|
||||
notice "$OMARCHY_L_USERNAME_TAKEN" 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
notice "Username must be alphanumeric with no spaces" 1
|
||||
notice "$OMARCHY_L_USERNAME_INVALID" 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
@@ -124,17 +167,17 @@ omarchy_prompt_username() {
|
||||
omarchy_prompt_password() {
|
||||
local status
|
||||
while true; do
|
||||
password=$(gum input --placeholder "Used for user + root, and disk encryption when enabled" --prompt.foreground="#845DF9" --password --prompt "Password> ") && status=0 || status=$?
|
||||
password=$(gum input --placeholder "$OMARCHY_L_PASSWORD_HINT" --prompt.foreground="#845DF9" --password --prompt "$OMARCHY_L_PASSWORD_PROMPT") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
password_confirmation=$(gum input --placeholder "Must match the password you just typed" --prompt.foreground="#845DF9" --password --prompt "Confirm> ") && status=0 || status=$?
|
||||
password_confirmation=$(gum input --placeholder "$OMARCHY_L_CONFIRM_HINT" --prompt.foreground="#845DF9" --password --prompt "$OMARCHY_L_CONFIRM_PROMPT") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
|
||||
if [[ -n "$password" && "$password" == "$password_confirmation" ]]; then
|
||||
return 0
|
||||
elif [[ -z "$password" ]]; then
|
||||
notice "Your password can't be blank!" 1
|
||||
notice "$OMARCHY_L_PASSWORD_BLANK" 1
|
||||
else
|
||||
notice "Passwords didn't match!" 1
|
||||
notice "$OMARCHY_L_PASSWORD_MISMATCH" 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
@@ -143,16 +186,16 @@ omarchy_prompt_password() {
|
||||
# only Esc/Ctrl+C end the prompt early.
|
||||
omarchy_prompt_identity() {
|
||||
local status
|
||||
full_name=$(gum input --placeholder "Used for git authentication (hit return to skip)" --prompt.foreground="#845DF9" --prompt "Full name> ") && status=0 || status=$?
|
||||
full_name=$(gum input --placeholder "$OMARCHY_L_IDENTITY_HINT" --prompt.foreground="#845DF9" --prompt "$OMARCHY_L_FULLNAME_PROMPT") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
email_address=$(gum input --placeholder "Used for git authentication (hit return to skip)" --prompt.foreground="#845DF9" --prompt "Email address> ") && status=0 || status=$?
|
||||
email_address=$(gum input --placeholder "$OMARCHY_L_IDENTITY_HINT" --prompt.foreground="#845DF9" --prompt "$OMARCHY_L_EMAIL_PROMPT") && status=0 || status=$?
|
||||
return $status
|
||||
}
|
||||
|
||||
omarchy_prompt_hostname() {
|
||||
local status
|
||||
while true; do
|
||||
hostname=$(gum input --placeholder "Letters, digits, and dashes (or return for 'omarchy')" --prompt.foreground="#845DF9" --prompt "Hostname> ") && status=0 || status=$?
|
||||
hostname=$(gum input --placeholder "$OMARCHY_L_HOSTNAME_HINT" --prompt.foreground="#845DF9" --prompt "$OMARCHY_L_HOSTNAME_PROMPT") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
|
||||
if [[ -z $hostname ]]; then
|
||||
@@ -161,7 +204,7 @@ omarchy_prompt_hostname() {
|
||||
elif [[ "$hostname" =~ $OMARCHY_HOSTNAME_PATTERN ]]; then
|
||||
return 0
|
||||
else
|
||||
notice "Hostname must be 1-63 letters, digits, or dashes, and cannot start or end with a dash" 1
|
||||
notice "$OMARCHY_L_HOSTNAME_INVALID" 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
@@ -173,9 +216,9 @@ omarchy_prompt_timezone() {
|
||||
guess=$(tzupdate -p 2>/dev/null) || guess=""
|
||||
|
||||
if [[ -n $guess ]]; then
|
||||
timezone=$(timedatectl list-timezones | gum choose --height 10 --selected "$guess" --header "Timezone") && status=0 || status=$?
|
||||
timezone=$(timedatectl list-timezones | gum choose --height 10 --selected "$guess" --header "$OMARCHY_L_TIMEZONE_HEADER") && status=0 || status=$?
|
||||
else
|
||||
timezone=$(timedatectl list-timezones | gum filter --height 10 --header "Timezone") && status=0 || status=$?
|
||||
timezone=$(timedatectl list-timezones | gum filter --height 10 --header "$OMARCHY_L_TIMEZONE_HEADER") && status=0 || status=$?
|
||||
fi
|
||||
((status == 0)) || return $status
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
▄▄▄
|
||||
▄█████▄ ▄███████████▄ ▄███████ ▄███████ ▄███████ ▄█ █▄ ▄█ █▄
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ▄███▄▄▄███ ▄███▄▄▄██▀ ███ ▄███▄▄▄███▄ ███▄▄▄███
|
||||
███ ███ ███ ███ ███ ▀███▀▀▀███ ▀███▀▀▀▀ ███ ▀▀███▀▀▀███ ▀▀▀▀▀▀███
|
||||
███ ███ ███ ███ ███ ███ ███ ██████████ ███ █▄ ███ ███ ▄██ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
▀█████▀ ▀█ ███ █▀ ███ █▀ ███ ███ ███████▀ ███ █▀ ▀█████▀
|
||||
▄█████▄ ▄███████████▄ ▄███████ ▄███████ ▄███████ ▄█ █▄ ▄█ █▄ ▄███████ ▄█▄ ▄█▄
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▄ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███ ███ ███ █▀ ████▄ ███
|
||||
███ ███ ███ ███ ███ ▄███▄▄▄███ ▄███▄▄▄██▀ ███ ▄███▄▄▄███▄ ███▄▄▄███ ███ ███▀█▄███
|
||||
███ ███ ███ ███ ███ ▀███▀▀▀███ ▀███▀▀▀▀ ███ ▀▀███▀▀▀███ ▀▀▀▀▀▀███ ███ ███ ▀████
|
||||
███ ███ ███ ███ ███ ███ ███ ██████████ ███ █▄ ███ ███ ▄██ ███ ███ █▄ ███ ▀███
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
▀█████▀ ▀█ ███ █▀ ███ █▀ ███ ███ ███████▀ ███ █▀ ▀█████▀ ███████▀ ▀█▀ ▀█▀
|
||||
███ █▀
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# 安装 OmarchyCN
|
||||
|
||||
## 下载与校验
|
||||
|
||||
从 [Releases](https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases) 下载最新 ISO 及 `SHA256SUMS.txt`、`SHA256SUMS.txt.asc`:
|
||||
|
||||
```bash
|
||||
curl -sSf https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/cn/keys/omarchycn-release.asc | gpg --import
|
||||
gpg --verify SHA256SUMS.txt.asc SHA256SUMS.txt
|
||||
sha256sum -c SHA256SUMS.txt
|
||||
```
|
||||
|
||||
签名主钥指纹应为 `04490F065F6ADD262A7243506EDF7B8603B5D247`(详见 `docs/release-signing.md`)。
|
||||
|
||||
## 写盘与启动
|
||||
|
||||
Linux 用 `caligula` 或 `dd`,Mac/Windows 用 balenaEtcher 写入 U 盘,UEFI 模式启动,按提示完成 Omarchy 安装(磁盘、用户、加密等流程与上游一致)。
|
||||
|
||||
## 首次进入桌面后
|
||||
|
||||
```bash
|
||||
omarchycn setup
|
||||
```
|
||||
|
||||
向导依次配置:语言、时区、显示缩放、中文 locale、中文字体、Fcitx5+Rime 输入法、输入法切换键、pacman 镜像、开发工具镜像、国内应用、AI Hub、隐私说明。每一步都可跳过,中断后重跑会从未完成的步骤继续。
|
||||
|
||||
## 现有 Omarchy 叠加安装(Overlay)
|
||||
|
||||
```bash
|
||||
git clone https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn.git
|
||||
cd omarchycn && ./bin/omarchy-cn-install-overlay
|
||||
omarchy cn version # 验证
|
||||
```
|
||||
|
||||
卸载:`omarchy-cn-install-overlay --uninstall`。更新:`omarchycn update`。
|
||||
@@ -0,0 +1,37 @@
|
||||
# 镜像管理
|
||||
|
||||
## pacman 镜像
|
||||
|
||||
```bash
|
||||
omarchycn mirror benchmark # 测速:延迟、吞吐、同步新鲜度
|
||||
omarchycn mirror apply china # 自动选最优中国镜像(1 主 + 2 备)
|
||||
omarchycn mirror apply official # 回到官方全球源
|
||||
omarchycn mirror pin tuna # 固定单一镜像
|
||||
omarchycn mirror status # 当前策略与镜像列表
|
||||
omarchycn mirror restore # 恢复最近一次备份
|
||||
```
|
||||
|
||||
每次写入 `/etc/pacman.d/mirrorlist` 前自动生成带时间戳备份。候选列表在 `cn/mirrors.json`。
|
||||
|
||||
主镜像失效时:
|
||||
|
||||
```bash
|
||||
omarchycn doctor mirror --fix # 检测并自动切换到健康镜像
|
||||
```
|
||||
|
||||
## 开发工具镜像
|
||||
|
||||
覆盖 npm、pip、cargo、go、gem、docker 六个生态:
|
||||
|
||||
```bash
|
||||
omarchycn dev-mirror list # 当前各生态源
|
||||
omarchycn dev-mirror apply china # 全部切国内
|
||||
omarchycn dev-mirror apply official --target npm # 指定生态切官方
|
||||
omarchycn dev-mirror set npm https://my.registry/ # 自定义
|
||||
omarchycn dev-mirror doctor # 连通性检查
|
||||
```
|
||||
|
||||
写入前备份到 `~/.local/state/omarchycn/backups/dev-mirror/<时间戳>/`,
|
||||
恢复:`omarchycn restore list` + `omarchycn restore config <时间戳>`(仅用户级文件)。
|
||||
docker 目标写 `/etc/docker/daemon.json`(需 sudo,重启 docker 生效;系统文件不参与
|
||||
自动恢复,回退用 `omarchycn dev-mirror apply official --target docker`)。
|
||||
@@ -0,0 +1,34 @@
|
||||
# 中文输入与字体
|
||||
|
||||
## 一键配置
|
||||
|
||||
```bash
|
||||
omarchycn locale apply # 生成 zh_CN.UTF-8 / en_US.UTF-8
|
||||
omarchycn font apply # 思源黑体/宋体 + emoji + fallback 优先级
|
||||
omarchycn ime apply # Fcitx5 + Rime,默认拼音
|
||||
```
|
||||
|
||||
字体配置保证中文优先落到简体字形(含无语言标签的 Chromium/Electron 路径),
|
||||
日文/韩文标签内容仍使用各自原生变体。验证:`omarchycn font status`。
|
||||
|
||||
## 切换键
|
||||
|
||||
默认 `Ctrl+Space`(Fcitx5 原生)。改用 `Super+Space`:
|
||||
|
||||
```bash
|
||||
omarchycn ime hotkey super-space
|
||||
```
|
||||
|
||||
此时 Omarchy 主菜单自动迁移到 `Super+Alt+Space`(原 Apps 菜单快捷键让位,
|
||||
可从主菜单进入 Apps 或在 `~/.config/hypr/bindings.lua` 自行改绑)。
|
||||
切回:`omarchycn ime hotkey ctrl-space`(自动移除迁移块)。
|
||||
|
||||
## 排查
|
||||
|
||||
```bash
|
||||
omarchycn ime status # 包、profile、切换键冲突、进程状态
|
||||
omarchycn doctor ime
|
||||
```
|
||||
|
||||
上游已内置 fcitx 环境变量(`INPUT_METHOD/QT_IM_MODULE/XMODIFIERS/SDL_IM_MODULE`)
|
||||
与 `omarchy-fcitx5` 用户服务,OmarchyCN 不重复配置。
|
||||
@@ -0,0 +1,36 @@
|
||||
# AI Hub
|
||||
|
||||
Harness(工具)与 Provider(模型服务)分层管理,配置数据在 `cn/registry/`。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
omarchycn ai setup # 向导:Harness → Provider → 模型 → API Key → 测试
|
||||
omarchycn ai launch # 以默认 Profile 启动
|
||||
```
|
||||
|
||||
支持组合(stable = 有 mock 回归覆盖的适配器):
|
||||
|
||||
| Harness | DeepSeek | Kimi | Z.AI/GLM |
|
||||
|---|---|---|---|
|
||||
| Claude Code | stable | stable | stable |
|
||||
| Codex | stable | — | — |
|
||||
| OpenCode | stable | — | — |
|
||||
| Kimi Code | — | 官方 CLI 自管 | — |
|
||||
| Deep Code | 官方 CLI 自管 | — | — |
|
||||
|
||||
## 手动操作
|
||||
|
||||
```bash
|
||||
omarchycn ai secret set deepseek # 存 Key(Secret Service → pass → 0600 文件)
|
||||
omarchycn ai profile create work claude-code deepseek default-coding
|
||||
omarchycn ai profile use work
|
||||
omarchycn ai test work # 真实连通/鉴权/模型调用测试
|
||||
omarchycn ai doctor # 安装、凭据、端点诊断
|
||||
omarchycn ai default work # 映射到 Super+Shift+Ctrl+A / omarchy agent
|
||||
```
|
||||
|
||||
`ai default` 把配置持久化进 Harness 自身文件(Claude Code 的 settings.json /
|
||||
Codex 的 config.toml,均 0600 并保留你的其它设置),上游快捷键即以国内模型启动。
|
||||
|
||||
注意:`ai test` 消耗一次极小请求(max_tokens=8),需有效 API Key 与账户额度。
|
||||
@@ -0,0 +1,36 @@
|
||||
# 更新与恢复
|
||||
|
||||
## 更新
|
||||
|
||||
- ISO 安装:系统随上游 `omarchy update`;cn 层当前随新版 ISO 迭代([omarchycn] pacman 仓库暂只分发 keyring,omarchy-dev 包上仓后将改为 pacman 更新)
|
||||
- Overlay 安装:`omarchycn update`(拉取源码 → 重装 overlay → 执行未跑过的 cn 迁移)
|
||||
|
||||
## 备份位置
|
||||
|
||||
| 内容 | 位置 |
|
||||
|---|---|
|
||||
| pacman mirrorlist | `/etc/pacman.d/mirrorlist.omarchycn-bak-<时间戳>` |
|
||||
| 开发工具镜像配置 | `~/.local/state/omarchycn/backups/dev-mirror/<时间戳>/` |
|
||||
| 输入法 profile | `~/.local/state/omarchycn/backups/ime/<时间戳>/` |
|
||||
| 显示缩放 | `~/.config/hypr/monitors.lua.omarchycn-prev` |
|
||||
| Codex/Claude 配置 | 同目录 `*.omarchycn-bak-<时间戳>` |
|
||||
|
||||
## 恢复
|
||||
|
||||
```bash
|
||||
omarchycn restore list # 列出全部备份
|
||||
omarchycn restore config <时间戳> # 恢复开发工具镜像配置
|
||||
omarchycn mirror restore # 恢复 pacman mirrorlist
|
||||
```
|
||||
|
||||
系统级快照与回滚沿用上游 Btrfs + Snapper 机制(见上游手册 System snapshots)。
|
||||
|
||||
## 诊断
|
||||
|
||||
```bash
|
||||
omarchycn doctor # network + mirror + dev-mirror + ime
|
||||
omarchycn ai doctor
|
||||
omarchycn status # 版本与配置概览
|
||||
```
|
||||
|
||||
诊断只在本地输出,不上传任何数据。
|
||||
@@ -2,13 +2,15 @@ echo "Move this install to the opt-in docker group default (the group is root-eq
|
||||
|
||||
# The docker group grants passwordless root (a container can bind-mount / and
|
||||
# rewrite the host), so Omarchy no longer puts users in it by default. Bring
|
||||
# existing installs in line: remove this user from the group if present. It takes
|
||||
# effect at next login, and the current session keeps working until then. Anyone
|
||||
# who wants passwordless docker back can opt in, behind a warning, with
|
||||
# existing installs in line: remove this user from the group if present. The
|
||||
# change applies after a reboot, so it stays reachable until then. Anyone who
|
||||
# wants passwordless docker back can opt in, behind a warning, with
|
||||
# Setup > Security > Sudoless Docker. Reuses the removal command so there is one
|
||||
# source of truth for the privileged change and its notice.
|
||||
# source of truth for the privileged change and its notice; DEFER_REBOOT keeps
|
||||
# it from prompting mid-update — omarchy-update-restart handles the reboot once
|
||||
# the whole update has finished.
|
||||
if id -nG "$USER" | grep -qw docker; then
|
||||
omarchy-remove-security-sudoless-docker
|
||||
OMARCHY_DEFER_REBOOT=1 omarchy-remove-security-sudoless-docker
|
||||
fi
|
||||
|
||||
# The Docker app entry copied into ~/.local/share/applications used to run
|
||||
|
||||
@@ -0,0 +1,787 @@
|
||||
diff --git a/builder/build-iso.sh b/builder/build-iso.sh
|
||||
index db8daa2..5e4ca44 100755
|
||||
--- a/builder/build-iso.sh
|
||||
+++ b/builder/build-iso.sh
|
||||
@@ -118,7 +118,7 @@ cp "/tmp/$NODE_FILENAME" "$build_cache_dir/airootfs/opt/packages/"
|
||||
# The selected omarchy-settings package is needed here so its post_install hook
|
||||
# drops Omarchy's plymouthd.conf into /etc/plymouth before mkarchiso builds the
|
||||
# live initramfs.
|
||||
-arch_packages=(linux-t2 git gum jq openssl plymouth ttfx tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted)
|
||||
+arch_packages=(linux-t2 git gum jq openssl plymouth ttfx tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted cage foot noto-fonts-cjk)
|
||||
printf '%s\n' "${arch_packages[@]}" >> "$build_cache_dir/packages.x86_64"
|
||||
|
||||
# The live ISO boots linux-t2 (see airootfs/etc/mkinitcpio.d/linux-t2.preset), so
|
||||
diff --git a/configs/airootfs/root/.automated_script.sh b/configs/airootfs/root/.automated_script.sh
|
||||
index 16904f8..f1ec36e 100644
|
||||
--- a/configs/airootfs/root/.automated_script.sh
|
||||
+++ b/configs/airootfs/root/.automated_script.sh
|
||||
@@ -10,7 +10,29 @@
|
||||
# - COLUMNS/LINES so gum picks up real terminal size
|
||||
set -euo pipefail
|
||||
|
||||
-[[ $(tty) == /dev/tty1 ]] || exit 0
|
||||
+[[ $(tty) == /dev/tty1 || -n ${OMARCHY_CN_GUI:-} ]] || exit 0
|
||||
+
|
||||
+# OmarchyCN: run the installer in a CJK-capable graphical console when KMS
|
||||
+# allows; otherwise fall back to the original English VT flow.
|
||||
+if [[ -z ${OMARCHY_CN_GUI:-} ]] && command -v cage > /dev/null 2>&1; then
|
||||
+ export XDG_RUNTIME_DIR=/run/omarchy-gui
|
||||
+ mkdir -p "$XDG_RUNTIME_DIR"
|
||||
+ chmod 700 "$XDG_RUNTIME_DIR"
|
||||
+ rm -f "$XDG_RUNTIME_DIR/gui-started"
|
||||
+ gui_status=0
|
||||
+ OMARCHY_CN_GUI=1 OMARCHY_INSTALL_LANG=zh WLR_RENDERER=pixman \
|
||||
+ cage -- foot -F "$0" || gui_status=$?
|
||||
+ if [[ -f $XDG_RUNTIME_DIR/gui-started ]]; then
|
||||
+ # GUI 已运行:退出码属于安装器,不再回退英文流程
|
||||
+ exit "$gui_status"
|
||||
+ fi
|
||||
+ echo "图形安装环境不可用,回退英文控制台 (falling back to the English VT installer)"
|
||||
+ unset OMARCHY_INSTALL_LANG XDG_RUNTIME_DIR
|
||||
+fi
|
||||
+
|
||||
+if [[ -n ${OMARCHY_CN_GUI:-} ]]; then
|
||||
+ touch "$XDG_RUNTIME_DIR/gui-started"
|
||||
+fi
|
||||
|
||||
export OMARCHY_MIRROR="$(cat /root/omarchy_mirror)"
|
||||
if [[ -f /root/omarchy_iso_ref ]]; then
|
||||
diff --git a/configs/airootfs/root/configurator b/configs/airootfs/root/configurator
|
||||
index 4cd3db3..24a1bf8 100644
|
||||
--- a/configs/airootfs/root/configurator
|
||||
+++ b/configs/airootfs/root/configurator
|
||||
@@ -9,6 +9,165 @@ OMARCHY_SETTINGS_PACKAGE="${OMARCHY_SETTINGS_PACKAGE:-omarchy-settings}"
|
||||
|
||||
LOGO_PATH="$OMARCHY_PATH/logo.txt"
|
||||
|
||||
+# Installer strings; the graphical console stage exports OMARCHY_INSTALL_LANG=zh
|
||||
+if [[ ${OMARCHY_INSTALL_LANG:-} == zh ]]; then
|
||||
+ L_TAGLINE='华丽、现代、观点鲜明的 Linux —— 中国版'
|
||||
+ L_HINT='按回车开始安装'
|
||||
+ L_ABORTED='安装已中止'
|
||||
+ L_RETRY='稍后可重新运行: ./.automated_script.sh'
|
||||
+ L_OWNER_PREP='这将把本机准备给另一位使用者。'
|
||||
+ L_OWNER_PREP2='系统现在安装,但初始设置延迟到首次开机进行。'
|
||||
+ L_OWNER_CONFIRM='为另一位使用者准备这台机器?'
|
||||
+ L_OWNER_YES='是,为他人准备'
|
||||
+ L_OWNER_NO='否,继续本人设置'
|
||||
+ L_USER_STEP='开始设置你的用户账户……'
|
||||
+ L_SUMMARY_OK='以上信息正确吗?'
|
||||
+ L_SUMMARY_NO='不对,去修改'
|
||||
+ L_F_FIELD='项目'
|
||||
+ L_F_VALUE='值'
|
||||
+ L_F_USER='用户名'
|
||||
+ L_F_PASS='密码'
|
||||
+ L_F_NAME='姓名'
|
||||
+ L_F_EMAIL='邮箱'
|
||||
+ L_F_HOST='主机名'
|
||||
+ L_F_TZ='时区'
|
||||
+ L_F_KB='键盘'
|
||||
+ L_F_SKIP='[已跳过]'
|
||||
+ L_NOSPACE_STEP='磁盘 %s 可用空间不足'
|
||||
+ L_NOSPACE_1='%s 可用空间 %s;Omarchy 至少需要 32GB。'
|
||||
+ L_NOSPACE_2='请用分区工具在该盘腾出至少 32GB,然后重试。'
|
||||
+ L_NOSPACE_CONFIRM='返回安装方式选择?'
|
||||
+ L_BACK='返回'
|
||||
+ L_OPEN_PT='打开分区工具'
|
||||
+ L_BL_STEP='检查 %s 上的 BitLocker'
|
||||
+ L_BL_1='检测到 %s 存在 BitLocker 签名。'
|
||||
+ L_BL_2='请在 Windows 中关闭 BitLocker 并等待解密完成后重试。'
|
||||
+ L_BL_3='仅暂停 BitLocker 不够——磁盘仍处于加密状态。'
|
||||
+ L_BL_ABORT='已中止:该磁盘启用了 BitLocker。'
|
||||
+ L_EFI_STEP='检查 %s 上已有的 EFI 分区'
|
||||
+ L_EFI_WIN='发现 Windows ESP 位于 %s——保持原样不动。'
|
||||
+ L_EFI_OWN='Omarchy 将在空闲空间中创建自己的 EFI 分区。'
|
||||
+ L_EFI_NEW='将在空闲空间中新建 EFI 分区。'
|
||||
+ L_FREE_STEP='分析 %s 的空闲空间'
|
||||
+ L_MKPART_STEP='在 %s 上创建分区'
|
||||
+ L_LUKS_STEP='在 %s 上配置 LUKS2 加密'
|
||||
+ L_BTRFS_STEP='创建 Btrfs 文件系统与子卷'
|
||||
+ L_FREESAY='在 %s 空闲空间中安装 Omarchy。'
|
||||
+ L_CTRLC_UNENC='按 Ctrl+C 改为不加密安装。'
|
||||
+ L_YES_INSTALL='是,开始安装'
|
||||
+ L_YES_NOENC='是,不加密安装'
|
||||
+ L_NO_CHANGE='否,去修改'
|
||||
+ L_CONFIRM_ON='确认安装到 %s'
|
||||
+ L_DISK_STEP='选择要安装 Omarchy 的磁盘……'
|
||||
+ L_DISK_HEADER='选择安装磁盘'
|
||||
+ L_PT_STEP='%s 的分区工具'
|
||||
+ L_PT_1='为 Omarchy 腾出未分配空间,然后写入更改并退出。'
|
||||
+ L_PT_2='不要在这里创建 Omarchy 分区——目标区域保持为空闲空间即可。'
|
||||
+ L_MODE_FULL='整盘安装'
|
||||
+ L_MODE_FREE='空闲空间安装(保留现有数据)'
|
||||
+ L_MODE_OTHER='换一块磁盘'
|
||||
+ L_MODE_STEP='选择 Omarchy 的安装方式……'
|
||||
+ L_MODE_HEADER='选择 %s 上的安装方式'
|
||||
+ L_WIPE_WARN='磁盘将被完全覆写,数据无法恢复。'
|
||||
+ L_WIPE_CONFIRM='确认覆写 %s'
|
||||
+ L_TAGLINE_W=38
|
||||
+ L_HINT_W=14
|
||||
+ L_WELCOME='开始设置你的机器……'
|
||||
+ L_OWNER_HINT='按 Ctrl+C 可改为「为他人准备这台机器」。'
|
||||
+ L_CFDISK_CONFIRM='打开 %s 的分区工具?'
|
||||
+ L_OPEN_CFDISK='打开 cfdisk'
|
||||
+ L_DS_GPT='初始化 %s 的 GPT'
|
||||
+ L_DS_ESPFLAG='标记分区 %s 为 ESP'
|
||||
+ L_DS_WIPE='清除 %s 的残留签名'
|
||||
+ L_DS_MKBTRFS='在 %s 创建 Btrfs 文件系统'
|
||||
+ L_DS_MOUNT='挂载 %s'
|
||||
+ L_DS_SUBVOL='创建子卷 %s'
|
||||
+ L_DS_MOUNTROOT='挂载目标根分区'
|
||||
+ L_DS_MOUNTHOME='挂载 /home'
|
||||
+ L_DS_MOUNTLOG='挂载 /var/log'
|
||||
+ L_DS_MOUNTCACHE='挂载软件包缓存'
|
||||
+ L_DS_MKESP='在 %s 创建 ESP 文件系统'
|
||||
+ L_DS_MOUNTESP='挂载 ESP'
|
||||
+else
|
||||
+ L_TAGLINE='Beautiful, Modern & Opinionated Linux by DHH'
|
||||
+ L_HINT='Press Return to Start Install'
|
||||
+ L_ABORTED='Aborted installation'
|
||||
+ L_RETRY='You can retry later by running: ./.automated_script.sh'
|
||||
+ L_OWNER_PREP='This prepares the machine for another owner.'
|
||||
+ L_OWNER_PREP2='The system installs now, but setup is delayed until first boot.'
|
||||
+ L_OWNER_CONFIRM='Prepare this machine for another owner?'
|
||||
+ L_OWNER_YES='Yes, prepare for another owner'
|
||||
+ L_OWNER_NO='No, keep setting up'
|
||||
+ L_USER_STEP='Let'\''s setup your user account...'
|
||||
+ L_SUMMARY_OK='Does this look right?'
|
||||
+ L_SUMMARY_NO='No, change it'
|
||||
+ L_F_FIELD='Field'
|
||||
+ L_F_VALUE='Value'
|
||||
+ L_F_USER='Username'
|
||||
+ L_F_PASS='Password'
|
||||
+ L_F_NAME='Full name'
|
||||
+ L_F_EMAIL='Email address'
|
||||
+ L_F_HOST='Hostname'
|
||||
+ L_F_TZ='Timezone'
|
||||
+ L_F_KB='Keyboard'
|
||||
+ L_F_SKIP='[Skipped]'
|
||||
+ L_NOSPACE_STEP='Not enough free space on %s'
|
||||
+ L_NOSPACE_1='%s has %s of usable free space; Omarchy needs at least 32GB.'
|
||||
+ L_NOSPACE_2='Open the partition tool to free at least 32GB, then try again.'
|
||||
+ L_NOSPACE_CONFIRM='Return to installation mode?'
|
||||
+ L_BACK='Back'
|
||||
+ L_OPEN_PT='Open partition tool'
|
||||
+ L_BL_STEP='Checking for BitLocker on %s'
|
||||
+ L_BL_1='BitLocker signature detected on %s.'
|
||||
+ L_BL_2='Turn BitLocker off in Windows and wait for the drive to finish decrypting, then try again.'
|
||||
+ L_BL_3='Suspending BitLocker is not enough — the drive stays encrypted.'
|
||||
+ L_BL_ABORT='Aborted: BitLocker is enabled on this disk.'
|
||||
+ L_EFI_STEP='Checking existing EFI partitions on %s'
|
||||
+ L_EFI_WIN='Found a Windows ESP at %s — leaving it untouched.'
|
||||
+ L_EFI_OWN='Omarchy will create its own dedicated EFI partition in free space.'
|
||||
+ L_EFI_NEW='A new EFI partition will be created in free space.'
|
||||
+ L_FREE_STEP='Analyzing free space on %s'
|
||||
+ L_MKPART_STEP='Creating partitions on %s'
|
||||
+ L_LUKS_STEP='Setting up LUKS2 on %s'
|
||||
+ L_BTRFS_STEP='Creating Btrfs filesystem and subvolumes'
|
||||
+ L_FREESAY='Install Omarchy in the %s of free space.'
|
||||
+ L_CTRLC_UNENC='Press Ctrl+C for unencrypted install.'
|
||||
+ L_YES_INSTALL='Yes, install'
|
||||
+ L_YES_NOENC='Yes, install without encryption'
|
||||
+ L_NO_CHANGE='No, change it'
|
||||
+ L_CONFIRM_ON='Confirm installing on %s'
|
||||
+ L_DISK_STEP='Let'\''s select where to install Omarchy...'
|
||||
+ L_DISK_HEADER='Select install disk'
|
||||
+ L_PT_STEP='Partition tool for %s'
|
||||
+ L_PT_1='Create unallocated free space for Omarchy, then write changes and quit.'
|
||||
+ L_PT_2='Do not create an Omarchy partition here — leave the target area as Free space.'
|
||||
+ L_MODE_FULL='Full disk install'
|
||||
+ L_MODE_FREE='Free space install (alongside existing data)'
|
||||
+ L_MODE_OTHER='Choose a different disk'
|
||||
+ L_MODE_STEP='Let'\''s select how to install Omarchy...'
|
||||
+ L_MODE_HEADER='Select installation mode on %s'
|
||||
+ L_WIPE_WARN='Everything will be overwritten. There is no recovery possible.'
|
||||
+ L_WIPE_CONFIRM='Confirm overwriting %s'
|
||||
+ L_TAGLINE_W=44
|
||||
+ L_HINT_W=29
|
||||
+ L_WELCOME="Let's setup your machine..."
|
||||
+ L_OWNER_HINT='Press Ctrl+C to prepare this machine for another owner.'
|
||||
+ L_CFDISK_CONFIRM='Open partition tool for %s?'
|
||||
+ L_OPEN_CFDISK='Open cfdisk'
|
||||
+ L_DS_GPT='initializing GPT on %s'
|
||||
+ L_DS_ESPFLAG='flagging partition %s as ESP'
|
||||
+ L_DS_WIPE='clearing stale signatures on %s'
|
||||
+ L_DS_MKBTRFS='creating the Btrfs filesystem on %s'
|
||||
+ L_DS_MOUNT='mounting %s'
|
||||
+ L_DS_SUBVOL='creating subvolume %s'
|
||||
+ L_DS_MOUNTROOT='mounting the target root'
|
||||
+ L_DS_MOUNTHOME='mounting /home'
|
||||
+ L_DS_MOUNTLOG='mounting /var/log'
|
||||
+ L_DS_MOUNTCACHE='mounting the package cache'
|
||||
+ L_DS_MKESP='creating the ESP filesystem on %s'
|
||||
+ L_DS_MOUNTESP='mounting the ESP'
|
||||
+fi
|
||||
+
|
||||
# The setup form — keyboard, account, hostname, and timezone questions, plus the
|
||||
# rules their answers are checked against — shared verbatim with the first-boot
|
||||
# owner setup that finishes a deferred install. build-iso.sh vendors it out of
|
||||
@@ -122,12 +281,12 @@ greeter() {
|
||||
printf '\033[%d;1H' "$logo_row"
|
||||
gum style --foreground 2 --padding "0 0 0 $PADDING_LEFT" "$(<"$LOGO_PATH")"
|
||||
|
||||
- tagline="Beautiful, Modern & Opinionated Linux by DHH"
|
||||
- tpad=$(((cols - ${#tagline}) / 2)); (( tpad < 0 )) && tpad=0
|
||||
+ tagline="$L_TAGLINE"
|
||||
+ tpad=$(((cols - L_TAGLINE_W) / 2)); (( tpad < 0 )) && tpad=0
|
||||
printf '\033[%d;%dH%s' "$tagline_row" "$((tpad + 1))" "$tagline"
|
||||
|
||||
- hint="Press Return to Start Install"
|
||||
- hpad=$(((cols - ${#hint}) / 2)); (( hpad < 0 )) && hpad=0
|
||||
+ hint="$L_HINT"
|
||||
+ hpad=$(((cols - L_HINT_W) / 2)); (( hpad < 0 )) && hpad=0
|
||||
printf '\033[%d;%dH\033[2m%s\033[0m' "$hint_row" "$((hpad + 1))" "$hint"
|
||||
|
||||
# ColorShift the logo: a green base (indexed color 2) with a cyan accent (6)
|
||||
@@ -176,9 +335,9 @@ greeter() {
|
||||
}
|
||||
|
||||
abort() {
|
||||
- gum style "${1:-Aborted installation}"
|
||||
+ gum style "${1:-$L_ABORTED}"
|
||||
echo
|
||||
- gum style "You can retry later by running: ./.automated_script.sh"
|
||||
+ gum style "$L_RETRY"
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -212,8 +371,8 @@ keyboard_form() {
|
||||
while true; do
|
||||
clear_logo
|
||||
echo
|
||||
- say "Let's setup your machine..."
|
||||
- say --foreground 8 "Press Ctrl+C to prepare this machine for another owner."
|
||||
+ say "$L_WELCOME"
|
||||
+ say --foreground 8 "$L_OWNER_HINT"
|
||||
echo
|
||||
|
||||
omarchy_prompt_keyboard && status=0 || status=$?
|
||||
@@ -245,11 +404,11 @@ keyboard_form() {
|
||||
confirm_prepare_for_another_owner() {
|
||||
clear_logo
|
||||
echo
|
||||
- say "This prepares the machine for another owner."
|
||||
- say --foreground 8 "The system installs now, but setup is delayed until first boot."
|
||||
+ say "$L_OWNER_PREP"
|
||||
+ say --foreground 8 "$L_OWNER_PREP2"
|
||||
echo
|
||||
- gum confirm --affirmative "Yes, prepare for another owner" --negative "No, keep setting up" \
|
||||
- "Prepare this machine for another owner?"
|
||||
+ gum confirm --affirmative "$L_OWNER_YES" --negative "$L_OWNER_NO" \
|
||||
+ "$L_OWNER_CONFIRM"
|
||||
}
|
||||
|
||||
# The user step. Deferred-provisioning installs skip it entirely — the machine's first owner
|
||||
@@ -257,7 +416,7 @@ confirm_prepare_for_another_owner() {
|
||||
# operator sets nothing user-specific.
|
||||
|
||||
user_form() {
|
||||
- step "Let's setup your user account..."
|
||||
+ step "$L_USER_STEP"
|
||||
|
||||
# Each prompt reports 0 (set), OMARCHY_FORM_BACK (Esc), or OMARCHY_FORM_SIGNAL
|
||||
# (Ctrl+C). Both non-zero cases unwind to user_step, which decides what they
|
||||
@@ -291,18 +450,18 @@ user_step() {
|
||||
fi
|
||||
|
||||
# Add manual padding since gum table -p doesn't respect padding
|
||||
- echo -e "Field,Value
|
||||
-Username,$username
|
||||
-Password,$(printf "%${#password}s" | tr ' ' '*')
|
||||
-Full name,${full_name:-[Skipped]}
|
||||
-Email address,${email_address:-[Skipped]}
|
||||
-Hostname,$hostname
|
||||
-Timezone,$timezone
|
||||
-Keyboard,$keyboard" |
|
||||
+ echo -e "$L_F_FIELD,$L_F_VALUE
|
||||
+$L_F_USER,$username
|
||||
+$L_F_PASS,$(printf "%${#password}s" | tr ' ' '*')
|
||||
+$L_F_NAME,${full_name:-$L_F_SKIP}
|
||||
+$L_F_EMAIL,${email_address:-$L_F_SKIP}
|
||||
+$L_F_HOST,$hostname
|
||||
+$L_F_TZ,$timezone
|
||||
+$L_F_KB,$keyboard" |
|
||||
gum table -s "," -p | sed "s/^/${PADDING_LEFT_SPACES}/"
|
||||
|
||||
echo
|
||||
- if gum confirm --negative "No, change it" "Does this look right?"; then
|
||||
+ if gum confirm --negative "$L_SUMMARY_NO" "$L_SUMMARY_OK"; then
|
||||
break
|
||||
else
|
||||
keyboard_form
|
||||
@@ -504,11 +663,11 @@ print_dry_run_files() {
|
||||
# action so users have a way to actually fix the situation.
|
||||
not_enough_space() {
|
||||
local available="${1:-0}"
|
||||
- step "Not enough free space on $disk"
|
||||
- say --foreground 1 "$disk has $(to_gb $available) of usable free space; Omarchy needs at least 32GB."
|
||||
- say "Open the partition tool to free at least 32GB on $disk, then try again."
|
||||
+ step "$(printf "$L_NOSPACE_STEP" "$disk")"
|
||||
+ say --foreground 1 "$(printf "$L_NOSPACE_1" "$disk" "$(to_gb $available)")"
|
||||
+ say "$L_NOSPACE_2"
|
||||
echo
|
||||
- if ! gum confirm --affirmative "Back" --negative "Open partition tool" "Return to installation mode?"; then
|
||||
+ if ! gum confirm --affirmative "$L_BACK" --negative "$L_OPEN_PT" "$L_NOSPACE_CONFIRM"; then
|
||||
open_partition_tool
|
||||
fi
|
||||
return 1
|
||||
@@ -518,14 +677,14 @@ not_enough_space() {
|
||||
# encryption confirm. Sets the globals run_partition_execute needs. Returns 1
|
||||
# to fall back to the install-mode picker.
|
||||
run_partition_decide() {
|
||||
- step "Checking for BitLocker on $disk"
|
||||
+ step "$(printf "$L_BL_STEP" "$disk")"
|
||||
local bl_part
|
||||
bl_part=$(detect_bitlocker || true)
|
||||
if [[ -n "$bl_part" ]]; then
|
||||
- say --foreground 1 "BitLocker signature detected on $bl_part."
|
||||
- say "Turn BitLocker off in Windows and wait for the drive to finish decrypting, then try again."
|
||||
- say "Suspending BitLocker is not enough — the drive stays encrypted."
|
||||
- abort "Aborted: BitLocker is enabled on this disk."
|
||||
+ say --foreground 1 "$(printf "$L_BL_1" "$bl_part")"
|
||||
+ say "$L_BL_2"
|
||||
+ say "$L_BL_3"
|
||||
+ abort "$L_BL_ABORT"
|
||||
fi
|
||||
|
||||
# Omarchy always creates its own dedicated ESP in free space and never
|
||||
@@ -534,17 +693,17 @@ run_partition_decide() {
|
||||
# Windows ESP is far too small for our Unified Kernel Images, and a shared
|
||||
# ESP forces the install unencrypted. A separate Linux ESP keeps Windows
|
||||
# and Omarchy fully isolated and keeps LUKS on the table.
|
||||
- step "Checking existing EFI partitions on $disk"
|
||||
+ step "$(printf "$L_EFI_STEP" "$disk")"
|
||||
local detected_win_esp
|
||||
detected_win_esp=$(detect_windows_esp || true)
|
||||
if [[ -n "$detected_win_esp" ]]; then
|
||||
- say "Found a Windows ESP at $detected_win_esp — leaving it untouched."
|
||||
- say "Omarchy will create its own dedicated EFI partition in free space."
|
||||
+ say "$(printf "$L_EFI_WIN" "$detected_win_esp")"
|
||||
+ say "$L_EFI_OWN"
|
||||
else
|
||||
- say "A new EFI partition will be created in free space."
|
||||
+ say "$L_EFI_NEW"
|
||||
fi
|
||||
|
||||
- step "Analyzing free space on $disk"
|
||||
+ step "$(printf "$L_FREE_STEP" "$disk")"
|
||||
partprobe "$disk" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
@@ -619,21 +778,21 @@ run_partition_decide() {
|
||||
while true; do
|
||||
clear_logo
|
||||
echo
|
||||
- say "Install Omarchy in the $(to_gb $INSTALL_MAX_B) of free space."
|
||||
+ say "$(printf "$L_FREESAY" "$(to_gb $INSTALL_MAX_B)")"
|
||||
|
||||
case $mode in
|
||||
encrypted)
|
||||
- say --foreground 8 "Press Ctrl+C for unencrypted install."
|
||||
- affirmative="Yes, install"
|
||||
+ say --foreground 8 "$L_CTRLC_UNENC"
|
||||
+ affirmative="$L_YES_INSTALL"
|
||||
;;
|
||||
unencrypted)
|
||||
- affirmative="Yes, install without encryption"
|
||||
+ affirmative="$L_YES_NOENC"
|
||||
;;
|
||||
esac
|
||||
echo
|
||||
|
||||
- gum confirm --affirmative "$affirmative" --negative "No, change it" \
|
||||
- "Confirm installing on $disk"
|
||||
+ gum confirm --affirmative "$affirmative" --negative "$L_NO_CHANGE" \
|
||||
+ "$(printf "$L_CONFIRM_ON" "$disk")"
|
||||
confirm_status=$?
|
||||
|
||||
case $confirm_status in
|
||||
@@ -691,9 +850,9 @@ run_partition_execute() {
|
||||
say "[dry] partition numbers are assigned by parted at creation, then read back"
|
||||
say "[dry] encrypted=$encrypt_installation kernel=$kernel_choice"
|
||||
else
|
||||
- step "Creating partitions on $disk"
|
||||
+ step "$(printf "$L_MKPART_STEP" "$disk")"
|
||||
if $needs_mklabel; then
|
||||
- disk_step "initializing GPT on $disk" parted --script "$disk" mklabel gpt
|
||||
+ disk_step "$(printf "$L_DS_GPT" "$disk")" parted --script "$disk" mklabel gpt
|
||||
partprobe "$disk" 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
@@ -709,7 +868,7 @@ run_partition_execute() {
|
||||
disk_abort_hook "Could not create the root partition on $disk"
|
||||
root_part_num="$created_partition_number"
|
||||
|
||||
- disk_step "flagging partition $efi_part_num as ESP" \
|
||||
+ disk_step "$(printf "$L_DS_ESPFLAG" "$efi_part_num")" \
|
||||
parted --script "$disk" set "$efi_part_num" esp on
|
||||
|
||||
efi_dev=$(partition_path "$disk" "$efi_part_num")
|
||||
@@ -725,11 +884,11 @@ run_partition_execute() {
|
||||
|
||||
# Both partitions are ours and brand new, but the space they occupy may
|
||||
# carry signatures from whatever was deleted to free it.
|
||||
- disk_step "clearing stale signatures on $efi_dev" wipefs -af "$efi_dev"
|
||||
- disk_step "clearing stale signatures on $root_partition_device" wipefs -af "$root_partition_device"
|
||||
+ disk_step "$(printf "$L_DS_WIPE" "$efi_dev")" wipefs -af "$efi_dev"
|
||||
+ disk_step "$(printf "$L_DS_WIPE" "$root_partition_device")" wipefs -af "$root_partition_device"
|
||||
|
||||
if [[ "$encrypt_installation" == "true" ]]; then
|
||||
- step "Setting up LUKS2 on $root_partition_device"
|
||||
+ step "$(printf "$L_LUKS_STEP" "$root_partition_device")"
|
||||
# Kept as pipes rather than routed through disk_step: the passphrase must
|
||||
# not become an argv the process table can show. Folding stderr into
|
||||
# stdout still lands any error in the install log.
|
||||
@@ -746,31 +905,31 @@ run_partition_execute() {
|
||||
root_mapper="$root_partition_device"
|
||||
fi
|
||||
|
||||
- step "Creating Btrfs filesystem and subvolumes"
|
||||
- disk_step "creating the Btrfs filesystem on $root_mapper" \
|
||||
+ step "$L_BTRFS_STEP"
|
||||
+ disk_step "$(printf "$L_DS_MKBTRFS" "$root_mapper")" \
|
||||
mkfs.btrfs -f -L OMARCHY "$root_mapper"
|
||||
wait_for_device "$root_mapper" || disk_abort_hook "Root device $root_mapper never appeared"
|
||||
|
||||
mkdir -p /mnt/btrfs-root
|
||||
- disk_step "mounting $root_mapper" mount "$root_mapper" /mnt/btrfs-root
|
||||
+ disk_step "$(printf "$L_DS_MOUNT" "$root_mapper")" mount "$root_mapper" /mnt/btrfs-root
|
||||
for subvol in @ @home @log @pkg; do
|
||||
- disk_step "creating subvolume $subvol" btrfs subvolume create "/mnt/btrfs-root/$subvol"
|
||||
+ disk_step "$(printf "$L_DS_SUBVOL" "$subvol")" btrfs subvolume create "/mnt/btrfs-root/$subvol"
|
||||
done
|
||||
umount /mnt/btrfs-root
|
||||
rmdir /mnt/btrfs-root
|
||||
|
||||
- disk_step "mounting the target root" \
|
||||
+ disk_step "$L_DS_MOUNTROOT" \
|
||||
mount -o noatime,compress=zstd,subvol=@ "$root_mapper" /mnt
|
||||
mkdir -p /mnt/home /mnt/var/log /mnt/var/cache/pacman/pkg /mnt"$esp_mount_in_target"
|
||||
- disk_step "mounting /home" \
|
||||
+ disk_step "$L_DS_MOUNTHOME" \
|
||||
mount -o noatime,compress=zstd,subvol=@home "$root_mapper" /mnt/home
|
||||
- disk_step "mounting /var/log" \
|
||||
+ disk_step "$L_DS_MOUNTLOG" \
|
||||
mount -o noatime,compress=zstd,subvol=@log "$root_mapper" /mnt/var/log
|
||||
- disk_step "mounting the package cache" \
|
||||
+ disk_step "$L_DS_MOUNTCACHE" \
|
||||
mount -o noatime,compress=zstd,subvol=@pkg "$root_mapper" /mnt/var/cache/pacman/pkg
|
||||
|
||||
- disk_step "creating the ESP filesystem on $efi_dev" mkfs.fat -F32 -n OMARCHY_EFI "$efi_dev"
|
||||
- disk_step "mounting the ESP" mount "$efi_dev" /mnt"$esp_mount_in_target"
|
||||
+ disk_step "$(printf "$L_DS_MKESP" "$efi_dev")" mkfs.fat -F32 -n OMARCHY_EFI "$efi_dev"
|
||||
+ disk_step "$L_DS_MOUNTESP" mount "$efi_dev" /mnt"$esp_mount_in_target"
|
||||
|
||||
# The orchestrator's first act is to verify this handoff. Check it here so
|
||||
# a failure names the step that broke rather than surfacing later as
|
||||
@@ -862,7 +1021,7 @@ _EOF_
|
||||
}
|
||||
|
||||
disk_form() {
|
||||
- step "Let's select where to install Omarchy..."
|
||||
+ step "$L_DISK_STEP"
|
||||
|
||||
# Don't offer the install media as an option (Arch ISO mounts it here)
|
||||
local boot_source
|
||||
@@ -888,19 +1047,19 @@ disk_form() {
|
||||
fi
|
||||
done <<<"$available_disks"
|
||||
|
||||
- selected_display=$(echo "$disk_options" | gum choose --header "Select install disk") || abort
|
||||
+ selected_display=$(echo "$disk_options" | gum choose --header "$L_DISK_HEADER") || abort
|
||||
disk=$(echo "$selected_display" | awk '{print $1}')
|
||||
}
|
||||
|
||||
# STEP 4: INSTALL MODE
|
||||
|
||||
open_partition_tool() {
|
||||
- step "Partition tool for $disk"
|
||||
- gum style "Create unallocated free space for Omarchy, then write changes and quit."
|
||||
- gum style --foreground 8 "Do not create an Omarchy partition here — leave the target area as Free space."
|
||||
+ step "$(printf "$L_PT_STEP" "$disk")"
|
||||
+ gum style "$L_PT_1"
|
||||
+ gum style --foreground 8 "$L_PT_2"
|
||||
gum style --foreground 8 "Tip: free-space install needs at least 32GB unallocated, plus 2GB more if no ESP already exists."
|
||||
echo
|
||||
- gum confirm --affirmative "Open cfdisk" --negative "Back" "Open partition tool for $disk?" || return 0
|
||||
+ gum confirm --affirmative "$L_OPEN_CFDISK" --negative "$L_BACK" "$(printf "$L_CFDISK_CONFIRM" "$disk")" || return 0
|
||||
|
||||
clear
|
||||
cfdisk "$disk" || true
|
||||
@@ -933,17 +1092,17 @@ requires_full_disk_install() {
|
||||
}
|
||||
|
||||
install_mode_form() {
|
||||
- local choices=("Full disk install")
|
||||
+ local choices=("$L_MODE_FULL")
|
||||
|
||||
# The free-space/protected path registers an EFI boot entry and is UEFI-only.
|
||||
# Full-disk installs still support BIOS through the orchestrator's BIOS branch.
|
||||
if [[ -d /sys/firmware/efi ]] && ! $full_disk_only; then
|
||||
- choices+=("Free space install (alongside existing data)")
|
||||
+ choices+=("$L_MODE_FREE")
|
||||
fi
|
||||
- choices+=("Choose a different disk")
|
||||
+ choices+=("$L_MODE_OTHER")
|
||||
|
||||
- step "Let's select how to install Omarchy..."
|
||||
- install_mode=$(gum choose --header "Select installation mode on $disk" "${choices[@]}") || abort
|
||||
+ step "$L_MODE_STEP"
|
||||
+ install_mode=$(gum choose --header "$(printf "$L_MODE_HEADER" "$disk")" "${choices[@]}") || abort
|
||||
}
|
||||
|
||||
confirm_disk_overwrite() {
|
||||
@@ -953,17 +1112,17 @@ confirm_disk_overwrite() {
|
||||
while true; do
|
||||
clear_logo
|
||||
echo
|
||||
- say "Everything will be overwritten. There is no recovery possible."
|
||||
+ say "$L_WIPE_WARN"
|
||||
|
||||
if [[ $mode == "encrypted" ]]; then
|
||||
- say --foreground 8 "Press Ctrl+C for unencrypted install."
|
||||
- affirmative="Yes, install"
|
||||
+ say --foreground 8 "$L_CTRLC_UNENC"
|
||||
+ affirmative="$L_YES_INSTALL"
|
||||
else
|
||||
- affirmative="Yes, install without encryption"
|
||||
+ affirmative="$L_YES_NOENC"
|
||||
fi
|
||||
|
||||
echo
|
||||
- gum confirm --affirmative "$affirmative" --negative "No, change it" "Confirm overwriting ${disk}"
|
||||
+ gum confirm --affirmative "$affirmative" --negative "No, change it" "$(printf "$L_WIPE_CONFIRM" "${disk}")"
|
||||
confirm_status=$?
|
||||
|
||||
case $confirm_status in
|
||||
@@ -1002,13 +1161,13 @@ select_installation() {
|
||||
# With no free-space option to offer, a full-disk install is the only
|
||||
# mode, so skip the picker and go straight to the overwrite confirm.
|
||||
if $full_disk_only; then
|
||||
- install_mode="Full disk install"
|
||||
+ install_mode="$L_MODE_FULL"
|
||||
else
|
||||
install_mode_form
|
||||
fi
|
||||
|
||||
case "$install_mode" in
|
||||
- "Full disk install")
|
||||
+ "$L_MODE_FULL")
|
||||
if confirm_disk_overwrite; then
|
||||
install_target="full_disk"
|
||||
return 0
|
||||
@@ -1017,13 +1176,13 @@ select_installation() {
|
||||
# wants a different disk.
|
||||
$full_disk_only && disk_form
|
||||
;;
|
||||
- "Free space install"*)
|
||||
+ "$L_MODE_FREE")
|
||||
if run_partition_decide; then
|
||||
install_target="free_space"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
- "Choose a different disk")
|
||||
+ "$L_MODE_OTHER")
|
||||
disk_form
|
||||
;;
|
||||
esac
|
||||
diff --git a/configs/airootfs/usr/local/bin/omarchy-install-dashboard b/configs/airootfs/usr/local/bin/omarchy-install-dashboard
|
||||
index 91e960b..54d331d 100755
|
||||
--- a/configs/airootfs/usr/local/bin/omarchy-install-dashboard
|
||||
+++ b/configs/airootfs/usr/local/bin/omarchy-install-dashboard
|
||||
@@ -53,6 +53,44 @@ export GUM_CONFIRM_UNSELECTED_BACKGROUND="${GUM_CONFIRM_UNSELECTED_BACKGROUND:-0
|
||||
child_pid=""
|
||||
child_pgid=""
|
||||
|
||||
+if [[ ${OMARCHY_INSTALL_LANG:-} == zh ]]; then
|
||||
+tips=(
|
||||
+ "Super + Space 打开 Omarchy 菜单:应用、设置与更多"
|
||||
+ "Super + K 查看全部快捷键"
|
||||
+ "Super 就是键盘上的 Windows / Command 键"
|
||||
+ "用 Xournal++ 给 PDF 签名"
|
||||
+ "用 LocalSend 与手机、电脑互传文件"
|
||||
+ "菜单 Install > Web App 可把任意网站变成应用"
|
||||
+ "Super + Return 打开终端,Super + Shift + Return 打开浏览器"
|
||||
+ "图片用 Pinta,视频用 Kdenlive,文档用 LibreOffice"
|
||||
+ "Super + Ctrl + Print 用 OCR 抓取屏幕文字"
|
||||
+ "Print 截屏,Alt + Print 录屏"
|
||||
+ "Super + Ctrl + R 设置提醒"
|
||||
+ "菜单 Style > Theme 一键换主题"
|
||||
+ "双击顶栏可切换透明"
|
||||
+ "菜单 Install > Windows 可运行完整 Windows 虚拟机"
|
||||
+ "Super + Ctrl + V 打开剪贴板管理器"
|
||||
+ "Super + 1 到 0 切换工作区,加 Shift 带窗口一起"
|
||||
+ "Super + Print 拾取屏幕任意位置的颜色"
|
||||
+ "菜单里的 Update 保持系统常新"
|
||||
+)
|
||||
+L_INSTALLING="正在安装 Omarchy CN"
|
||||
+L_TIP="提示:"
|
||||
+L_DONE_IN="安装完成,用时 %s"
|
||||
+L_STOPPED="Omarchy 安装已停止"
|
||||
+L_EXIT_STATUS="安装器退出,状态码 %s"
|
||||
+L_ENTER="按回车继续……"
|
||||
+L_REBOOT_NOW="立即重启"
|
||||
+L_FAIL_HEADER="接下来做什么?"
|
||||
+L_C_UPLOAD="上传日志求助"
|
||||
+L_C_VIEWLOG="查看完整日志"
|
||||
+L_C_SHELL="进入 Shell"
|
||||
+L_C_REBOOT="重启"
|
||||
+L_C_POWEROFF="关机"
|
||||
+L_NO_UPLOADER="当前环境没有可用的日志上传工具。"
|
||||
+L_LOG_AT="安装日志: %s"
|
||||
+L_TARGET_LOG="目标系统日志: /mnt/var/log/omarchy-install.log"
|
||||
+else
|
||||
tips=(
|
||||
"Super + Space opens the Omarchy menu for apps, settings, and more"
|
||||
"Super + K shows all the key bindings"
|
||||
@@ -73,6 +111,23 @@ tips=(
|
||||
"Super + Print picks a color from anywhere on screen"
|
||||
"Keep the system fresh with Update in the Omarchy menu"
|
||||
)
|
||||
+L_INSTALLING="Installing Omarchy"
|
||||
+L_TIP="Tip:"
|
||||
+L_DONE_IN="Installed Omarchy in %s"
|
||||
+L_STOPPED="Omarchy installation stopped"
|
||||
+L_EXIT_STATUS="Installer exited with status %s"
|
||||
+L_ENTER="Press Enter to continue…"
|
||||
+L_REBOOT_NOW="Reboot Now"
|
||||
+L_FAIL_HEADER="What would you like to do?"
|
||||
+L_C_UPLOAD="Upload log for support"
|
||||
+L_C_VIEWLOG="View full log"
|
||||
+L_C_SHELL="Drop to shell"
|
||||
+L_C_REBOOT="Reboot"
|
||||
+L_C_POWEROFF="Power off"
|
||||
+L_NO_UPLOADER="No log uploader is available in this environment."
|
||||
+L_LOG_AT="Install log: %s"
|
||||
+L_TARGET_LOG="Target log: /mnt/var/log/omarchy-install.log"
|
||||
+fi
|
||||
|
||||
cleanup() {
|
||||
printf '%s%s' "$RESET" "$SHOW_CURSOR" >"$TTY_PATH" 2>/dev/null || true
|
||||
@@ -452,13 +507,13 @@ render_dynamic() {
|
||||
LAST_PM=$pm
|
||||
|
||||
printf '%s%d;1H' "$CSI" "$DYNAMIC_ROW"
|
||||
- center "Installing Omarchy" "$CONTENT_WIDTH"
|
||||
+ center "$L_INSTALLING" "$CONTENT_WIDTH"
|
||||
blank_line
|
||||
line_at "$CONTENT_WIDTH" $(( (CONTENT_WIDTH - 34) / 2 )) ""
|
||||
progress_bar "$pm" 34
|
||||
printf '\n'
|
||||
blank_line
|
||||
- center "${DIM}Tip:${RESET} ${GREEN}$(current_tip)${RESET}" "$CONTENT_WIDTH"
|
||||
+ center "${DIM}${L_TIP}${RESET} ${GREEN}$(current_tip)${RESET}" "$CONTENT_WIDTH"
|
||||
printf '%s' "$CLEAR_TO_END"
|
||||
}
|
||||
|
||||
@@ -511,7 +566,7 @@ render_finish() {
|
||||
printf '%s%s%s%d;1H' "$SHOW_CURSOR" "$CLEAR" "$CSI" "$FINISH_TOP_ROW"
|
||||
render_logo
|
||||
blank_line
|
||||
- center "Installed Omarchy in ${duration}" "$CONTENT_WIDTH"
|
||||
+ center "$(printf "$L_DONE_IN" "${duration}")" "$CONTENT_WIDTH"
|
||||
blank_line
|
||||
} >"$TTY_PATH"
|
||||
|
||||
@@ -556,7 +611,7 @@ reboot_prompt() {
|
||||
--padding "0 0 0 $prompt_pad" \
|
||||
--show-help=false \
|
||||
--default \
|
||||
- --affirmative "Reboot Now" \
|
||||
+ --affirmative "$L_REBOOT_NOW" \
|
||||
--negative "" \
|
||||
"" <"$TTY_PATH" >"$TTY_PATH" 2>&1
|
||||
}
|
||||
@@ -617,7 +672,7 @@ find_log_uploader() {
|
||||
}
|
||||
|
||||
prompt_enter() {
|
||||
- printf '\nPress Enter to continue…' >"$TTY_PATH"
|
||||
+ printf '\n%s' "$L_ENTER" >"$TTY_PATH"
|
||||
IFS= read -r _ <"$TTY_PATH" || true
|
||||
}
|
||||
|
||||
@@ -628,9 +683,9 @@ upload_failure_log() {
|
||||
"$uploader" install >"$TTY_PATH" 2>&1 || true
|
||||
else
|
||||
{
|
||||
- echo "No log uploader is available in this environment."
|
||||
- echo "Install log: $LOG_FILE"
|
||||
- [[ -f /mnt/var/log/omarchy-install.log ]] && echo "Target log: /mnt/var/log/omarchy-install.log"
|
||||
+ echo "$L_NO_UPLOADER"
|
||||
+ printf "$L_LOG_AT\n" "$LOG_FILE"
|
||||
+ [[ -f /mnt/var/log/omarchy-install.log ]] && echo "$L_TARGET_LOG"
|
||||
} >"$TTY_PATH"
|
||||
fi
|
||||
prompt_enter
|
||||
@@ -696,8 +751,8 @@ render_failure() {
|
||||
blank_line
|
||||
render_logo
|
||||
blank_line
|
||||
- center "${RED}Omarchy installation stopped${RESET}" "$CONTENT_WIDTH"
|
||||
- center "Installer exited with status $status" "$CONTENT_WIDTH"
|
||||
+ center "${RED}${L_STOPPED}${RESET}" "$CONTENT_WIDTH"
|
||||
+ center "$(printf "$L_EXIT_STATUS" "$status")" "$CONTENT_WIDTH"
|
||||
|
||||
render_media_diagnosis "$media_diagnosis"
|
||||
|
||||
@@ -730,40 +785,40 @@ failure_menu() {
|
||||
if [[ -n $uploader ]]; then
|
||||
choice=$(gum choose \
|
||||
--height 6 \
|
||||
- --header "What would you like to do?" \
|
||||
- "Upload log for support" \
|
||||
- "View full log" \
|
||||
- "Drop to shell" \
|
||||
- "Reboot" \
|
||||
- "Power off" \
|
||||
- <"$TTY_PATH" 2>"$TTY_PATH") || choice="Drop to shell"
|
||||
+ --header "$L_FAIL_HEADER" \
|
||||
+ "$L_C_UPLOAD" \
|
||||
+ "$L_C_VIEWLOG" \
|
||||
+ "$L_C_SHELL" \
|
||||
+ "$L_C_REBOOT" \
|
||||
+ "$L_C_POWEROFF" \
|
||||
+ <"$TTY_PATH" 2>"$TTY_PATH") || choice="$L_C_SHELL"
|
||||
else
|
||||
choice=$(gum choose \
|
||||
--height 5 \
|
||||
- --header "What would you like to do?" \
|
||||
- "View full log" \
|
||||
- "Drop to shell" \
|
||||
- "Reboot" \
|
||||
- "Power off" \
|
||||
- <"$TTY_PATH" 2>"$TTY_PATH") || choice="Drop to shell"
|
||||
+ --header "$L_FAIL_HEADER" \
|
||||
+ "$L_C_VIEWLOG" \
|
||||
+ "$L_C_SHELL" \
|
||||
+ "$L_C_REBOOT" \
|
||||
+ "$L_C_POWEROFF" \
|
||||
+ <"$TTY_PATH" 2>"$TTY_PATH") || choice="$L_C_SHELL"
|
||||
fi
|
||||
|
||||
case "$choice" in
|
||||
- "Upload log for support")
|
||||
+ "$L_C_UPLOAD")
|
||||
upload_failure_log
|
||||
render_failure "${failure_status:-1}" "${failure_summary:-}" "${failure_media_diagnosis:-}" >"$TTY_PATH" 2>/dev/null || true
|
||||
;;
|
||||
- "View full log")
|
||||
+ "$L_C_VIEWLOG")
|
||||
view_failure_log
|
||||
render_failure "${failure_status:-1}" "${failure_summary:-}" "${failure_media_diagnosis:-}" >"$TTY_PATH" 2>/dev/null || true
|
||||
;;
|
||||
- "Reboot")
|
||||
+ "$L_C_REBOOT")
|
||||
reboot 2>/dev/null || systemctl reboot 2>/dev/null || true
|
||||
;;
|
||||
- "Power off")
|
||||
+ "$L_C_POWEROFF")
|
||||
poweroff 2>/dev/null || systemctl poweroff 2>/dev/null || true
|
||||
;;
|
||||
- "Drop to shell"|"")
|
||||
+ "$L_C_SHELL"|"")
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
@@ -39,16 +39,30 @@ cat >"$stub_bin/gpasswd" <<'STUB'
|
||||
#!/bin/bash
|
||||
echo "$@" >>"${GPASSWD_CALLS:?}"
|
||||
STUB
|
||||
chmod +x "$stub_bin/id" "$stub_bin/sudo" "$stub_bin/gpasswd"
|
||||
# gum confirm always says yes, and reboot records that it fired: the migration
|
||||
# must still NOT reboot (it defers to omarchy-update-restart), so neither should
|
||||
# be reached.
|
||||
cat >"$stub_bin/gum" <<'STUB'
|
||||
#!/bin/bash
|
||||
[[ $1 == confirm ]] && exit 0
|
||||
exit 0
|
||||
STUB
|
||||
cat >"$stub_bin/omarchy-system-reboot" <<'STUB'
|
||||
#!/bin/bash
|
||||
touch "${REBOOT_CALLED:?}"
|
||||
STUB
|
||||
chmod +x "$stub_bin/id" "$stub_bin/sudo" "$stub_bin/gpasswd" "$stub_bin/gum" "$stub_bin/omarchy-system-reboot"
|
||||
|
||||
reboot_flag="$home/.local/state/omarchy/reboot-required"
|
||||
gpasswd_calls="$test_dir/gpasswd-calls"
|
||||
reboot_called="$test_dir/reboot-called"
|
||||
launcher="$home/.local/share/applications/Docker.desktop"
|
||||
|
||||
run_migration() {
|
||||
rm -f "$gpasswd_calls" "$reboot_flag"
|
||||
rm -f "$gpasswd_calls" "$reboot_flag" "$reboot_called"
|
||||
HOME="$home" OMARCHY_PATH="$omarchy_path" USER="tester" STUB_GROUPS="$1" \
|
||||
GPASSWD_CALLS="$gpasswd_calls" PATH="$stub_bin:$ROOT/bin:$PATH" \
|
||||
GPASSWD_CALLS="$gpasswd_calls" REBOOT_CALLED="$reboot_called" \
|
||||
PATH="$stub_bin:$ROOT/bin:$PATH" \
|
||||
bash -euo pipefail "$migration" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
@@ -56,6 +70,7 @@ run_migration() {
|
||||
run_migration "wheel input docker" || fail "migration runs when the user is in the docker group"
|
||||
grep -q -- "-d tester docker" "$gpasswd_calls" || fail "migration removes the user from the docker group"
|
||||
[[ -f $reboot_flag ]] || fail "migration flags a reboot so the group change takes effect"
|
||||
[[ ! -f $reboot_called ]] || fail "migration must defer the reboot (not reboot mid-update)"
|
||||
[[ $(cat "$launcher") == "NEW-LAUNCHER" ]] || fail "migration refreshes the stale Docker launcher entry"
|
||||
pass "migration removes the group, flags a reboot, and refreshes the launcher"
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# omarchy-sudo-docker is the single answer to "does Docker need sudo", and it
|
||||
# answers two different questions on purpose. The default asks whether this
|
||||
# session can reach the socket, which is what decides if a command must elevate.
|
||||
# --configured asks whether the account is set up for sudoless Docker, which is
|
||||
# what the menu needs so it offers the toggle that can change state. Between
|
||||
# enabling sudoless Docker and the reboot that grants the group, those disagree.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
command="$ROOT/bin/omarchy-sudo-docker"
|
||||
|
||||
# Stub id so the configured groups are controllable.
|
||||
mkdir -p "$TMPDIR/bin"
|
||||
cat >"$TMPDIR/bin/id" <<'STUB'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "${STUB_GROUPS:-wheel input}"
|
||||
STUB
|
||||
chmod +x "$TMPDIR/bin/id"
|
||||
|
||||
# A writable stand-in means the socket is reachable; an unwritable one means it
|
||||
# is not. Test the file mode rather than a live daemon.
|
||||
reachable_socket="$TMPDIR/reachable.sock"
|
||||
blocked_socket="$TMPDIR/blocked.sock"
|
||||
touch "$reachable_socket" "$blocked_socket"
|
||||
chmod 600 "$reachable_socket"
|
||||
chmod 400 "$blocked_socket"
|
||||
|
||||
run() { # SOCKET GROUPS [--configured]
|
||||
env PATH="$TMPDIR/bin:$PATH" OMARCHY_DOCKER_SOCKET="$1" STUB_GROUPS="$2" USER=tester \
|
||||
bash "$command" ${3:+"$3"}
|
||||
}
|
||||
|
||||
# Default mode follows the socket, not the group list.
|
||||
run "$blocked_socket" "wheel input" || fail "an unreachable socket means Docker needs sudo"
|
||||
run "$reachable_socket" "wheel input" && fail "a reachable socket means Docker does not need sudo"
|
||||
pass "default mode answers from the socket this session can reach"
|
||||
|
||||
# A socket that isn't there at all still needs elevation (starting it is root work).
|
||||
run "$TMPDIR/absent.sock" "wheel input docker" || fail "a missing socket means Docker needs sudo"
|
||||
pass "a missing socket counts as needing sudo"
|
||||
|
||||
# --configured follows the account's groups, not the socket.
|
||||
run "$blocked_socket" "wheel input docker" --configured && fail "a configured docker group means no sudo is needed"
|
||||
run "$reachable_socket" "wheel input" --configured || fail "no docker group means sudo is needed"
|
||||
pass "--configured answers from the account's groups"
|
||||
|
||||
# The window this split exists for: sudoless Docker has just been enabled, so the
|
||||
# account carries the group while the running session still cannot use it. The
|
||||
# menu must offer Remove (--configured says no sudo) while lazydocker and the
|
||||
# Windows VM must still prompt (default says sudo).
|
||||
run "$blocked_socket" "wheel input docker" || fail "the session still needs sudo before the reboot"
|
||||
run "$blocked_socket" "wheel input docker" --configured && fail "the account is already configured for sudoless Docker"
|
||||
pass "the two modes disagree between enabling sudoless Docker and the reboot"
|
||||
|
||||
# An unknown argument is a usage error, not a silent answer either way.
|
||||
run "$reachable_socket" "wheel input" --bogus 2>/dev/null && fail "an unknown flag exits non-zero"
|
||||
status=0
|
||||
run "$reachable_socket" "wheel input" --bogus >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) || fail "an unknown flag exits 2, not the boolean 1"
|
||||
pass "an unknown flag is a usage error"
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Toggling sudoless Docker changes the docker group, which only takes effect on a
|
||||
# reboot. The setup/remove commands must flag the reboot and offer to do it now
|
||||
# (gum confirm), but defer it when OMARCHY_DEFER_REBOOT is set (the migration
|
||||
# reuses them inside `omarchy update`, where omarchy-update-restart handles it).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
test_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
home="$test_dir/home"
|
||||
stub_bin="$test_dir/bin"
|
||||
mkdir -p "$home" "$stub_bin"
|
||||
|
||||
cat >"$stub_bin/id" <<'STUB'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "${STUB_GROUPS:-wheel input}"
|
||||
STUB
|
||||
cat >"$stub_bin/sudo" <<'STUB'
|
||||
#!/bin/bash
|
||||
exec "$@"
|
||||
STUB
|
||||
cat >"$stub_bin/usermod" <<'STUB'
|
||||
#!/bin/bash
|
||||
echo "$@" >>"${USERMOD_CALLS:?}"
|
||||
STUB
|
||||
cat >"$stub_bin/gpasswd" <<'STUB'
|
||||
#!/bin/bash
|
||||
echo "$@" >>"${GPASSWD_CALLS:?}"
|
||||
STUB
|
||||
cat >"$stub_bin/gum" <<'STUB'
|
||||
#!/bin/bash
|
||||
touch "${GUM_CALLED:?}"
|
||||
exit "${GUM_ANSWER:-0}"
|
||||
STUB
|
||||
cat >"$stub_bin/omarchy-system-reboot" <<'STUB'
|
||||
#!/bin/bash
|
||||
touch "${REBOOT_CALLED:?}"
|
||||
STUB
|
||||
chmod +x "$stub_bin"/*
|
||||
|
||||
reboot_flag="$home/.local/state/omarchy/reboot-required"
|
||||
gum_called="$test_dir/gum-called"
|
||||
reboot_called="$test_dir/reboot-called"
|
||||
gpasswd_calls="$test_dir/gpasswd-calls"
|
||||
usermod_calls="$test_dir/usermod-calls"
|
||||
|
||||
run() { # command STUB_GROUPS GUM_ANSWER DEFER(0|1)
|
||||
rm -f "$reboot_flag" "$gum_called" "$reboot_called" "$gpasswd_calls" "$usermod_calls"
|
||||
local defer_env=()
|
||||
[[ ${4:-0} == 1 ]] && defer_env=(OMARCHY_DEFER_REBOOT=1)
|
||||
env HOME="$home" USER="tester" STUB_GROUPS="$2" GUM_ANSWER="$3" \
|
||||
GUM_CALLED="$gum_called" REBOOT_CALLED="$reboot_called" \
|
||||
GPASSWD_CALLS="$gpasswd_calls" USERMOD_CALLS="$usermod_calls" \
|
||||
PATH="$stub_bin:$ROOT/bin:$PATH" "${defer_env[@]}" \
|
||||
bash "$ROOT/bin/$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Remove, interactive, reboot confirmed -> group removed, flag set, reboot fired.
|
||||
run omarchy-remove-security-sudoless-docker "wheel input docker" 0 0
|
||||
grep -q -- "-d tester docker" "$gpasswd_calls" || fail "remove drops the user from the docker group"
|
||||
[[ -f $reboot_flag ]] || fail "remove flags a reboot"
|
||||
[[ -f $reboot_called ]] || fail "remove reboots when the prompt is confirmed"
|
||||
pass "remove drops the group, flags a reboot, and reboots on confirm"
|
||||
|
||||
# Remove, interactive, reboot declined -> flag set, but no reboot.
|
||||
run omarchy-remove-security-sudoless-docker "wheel input docker" 1 0
|
||||
[[ -f $reboot_flag ]] || fail "remove still flags a reboot when the prompt is declined"
|
||||
[[ ! -f $reboot_called ]] || fail "remove does not reboot when the prompt is declined"
|
||||
pass "remove leaves the reboot to the user when declined"
|
||||
|
||||
# Remove, deferred (migration/update) -> flag set, prompt never shown.
|
||||
run omarchy-remove-security-sudoless-docker "wheel input docker" 0 1
|
||||
[[ -f $reboot_flag ]] || fail "deferred remove still flags a reboot"
|
||||
[[ ! -f $gum_called ]] || fail "deferred remove must not prompt to reboot"
|
||||
[[ ! -f $reboot_called ]] || fail "deferred remove must not reboot"
|
||||
pass "deferred remove flags the reboot without prompting"
|
||||
|
||||
# Remove, already out of the group -> no-op, nothing flagged.
|
||||
run omarchy-remove-security-sudoless-docker "wheel input" 0 0
|
||||
[[ ! -f $gpasswd_calls ]] || fail "remove is a no-op when the user is not in the docker group"
|
||||
[[ ! -f $reboot_flag ]] || fail "remove does not flag a reboot when nothing changed"
|
||||
pass "remove is a no-op when sudoless Docker is already off"
|
||||
|
||||
# Setup, enable confirmed then reboot confirmed -> group added, flag set, reboot.
|
||||
run omarchy-setup-security-sudoless-docker "wheel input" 0 0
|
||||
grep -q -- "-aG docker tester" "$usermod_calls" || fail "setup adds the user to the docker group"
|
||||
[[ -f $reboot_flag ]] || fail "setup flags a reboot"
|
||||
[[ -f $reboot_called ]] || fail "setup reboots when the prompt is confirmed"
|
||||
pass "setup adds the group, flags a reboot, and reboots on confirm"
|
||||
Reference in New Issue
Block a user