Compare commits
80
Commits
@@ -0,0 +1,50 @@
|
||||
name: upstream-sync
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 3 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: quattro
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch upstream and open sync PR
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
API: ${{ github.server_url }}/api/v1
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "omarchycn-sync"
|
||||
git config user.email "sync@noreply.git.zacharyzhang.com"
|
||||
git remote add upstream https://github.com/basecamp/omarchy.git
|
||||
git fetch upstream quattro
|
||||
UP=$(git rev-parse upstream/quattro)
|
||||
echo "upstream quattro: $UP"
|
||||
if git merge-base --is-ancestor "$UP" HEAD; then
|
||||
echo "Already up to date with upstream"
|
||||
exit 0
|
||||
fi
|
||||
BR="sync/upstream-${UP:0:8}"
|
||||
if git ls-remote --exit-code --heads origin "$BR" > /dev/null; then
|
||||
echo "Sync branch $BR already exists, PR pending review"
|
||||
exit 0
|
||||
fi
|
||||
# Trial merge only to report conflict status in the PR body
|
||||
MERGE="clean"
|
||||
if ! git merge --no-commit --no-ff "upstream/quattro" > /dev/null 2>&1; then
|
||||
MERGE="CONFLICTS (resolve manually)"
|
||||
fi
|
||||
git merge --abort 2> /dev/null || true
|
||||
# Branch points at upstream HEAD so the PR always gets created
|
||||
git push origin "$UP:refs/heads/$BR"
|
||||
curl -sS --fail-with-body -X POST \
|
||||
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||
"$API/repos/$REPO/pulls" \
|
||||
-d "{\"base\":\"quattro\",\"head\":\"$BR\",\"title\":\"Sync upstream omarchy ${UP:0:8}\",\"body\":\"Automated sync of basecamp/omarchy quattro @ $UP. Trial merge: $MERGE. Review, run ./test/all, then merge.\"}"
|
||||
@@ -2,3 +2,7 @@
|
||||
# Python bytecode (orchestrator)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Local working documents
|
||||
OmarchyCN PRD.md
|
||||
OmarchyCN-TASKS.md
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# 行为准则
|
||||
|
||||
参与 OmarchyCN 社区(issue、PR、讨论)时:
|
||||
|
||||
- 尊重他人,就事论事,不进行人身攻击、骚扰或歧视
|
||||
- 欢迎新手提问,回答保持耐心
|
||||
- 技术分歧用证据和代码说话
|
||||
- 不发布垃圾信息、广告或与项目无关的内容
|
||||
|
||||
违反者由维护者视情节警告、删除内容或封禁账号。
|
||||
举报渠道见 [SECURITY.md](SECURITY.md) 中的联系方式。
|
||||
@@ -0,0 +1,23 @@
|
||||
# 贡献指南
|
||||
|
||||
## 仓库结构
|
||||
|
||||
- 上游 Omarchy 代码尽量不动;OmarchyCN 改动集中在 `bin/omarchy-cn-*`、`bin/omarchycn`、`cn/`、`install/cn/`
|
||||
- 上游代码定期与 `basecamp/omarchy` 同步:同步 PR 由维护者或 CI 工作流发起,人工审查合并
|
||||
|
||||
## 提交规范
|
||||
|
||||
- 遵循 [AGENTS.md](AGENTS.md) 的全部代码与命令约定(Bash 5、`[[ ]]`/`(( ))`、两空格缩进、`#!/bin/bash`)
|
||||
- 提交保持原子:一个提交只做一件事
|
||||
- 涉及系统修改的命令必须支持 `--dry-run`,写配置前先备份
|
||||
|
||||
## 测试
|
||||
|
||||
- CLI 与 shell 改动跑 `./test/all`
|
||||
- OmarchyCN 新增测试放在 `test/shell.d/*-test.sh`
|
||||
|
||||
## 提交流程
|
||||
|
||||
1. Fork 或在 issue 中讨论
|
||||
2. 提交 PR 到 `quattro` 分支,说明动机与验证方式
|
||||
3. 通过 CI 与代码审查后合并
|
||||
@@ -0,0 +1,14 @@
|
||||
OmarchyCN
|
||||
|
||||
Copyright (c) 2026 Zachary Zhang / OmarchyCN Community
|
||||
|
||||
本发行版基于 Omarchy(https://github.com/basecamp/omarchy),
|
||||
Copyright (c) 2025 David Heinemeier Hansson,MIT License。
|
||||
上游文件保留其原始版权与许可证声明(见 LICENSE)。
|
||||
|
||||
OmarchyCN 原创代码(bin/omarchy-cn-*、cn/、install/cn/ 及相关文档)
|
||||
采用 MIT License 发布。
|
||||
|
||||
OmarchyCN is an independent community distribution based on Omarchy.
|
||||
OmarchyCN is not affiliated with or endorsed by Basecamp, 37signals,
|
||||
or the Omarchy maintainers.
|
||||
@@ -1,79 +1,44 @@
|
||||
# Omarchy
|
||||
# OmarchyCN
|
||||
|
||||
Omarchy is a beautiful, modern & opinionated Linux distribution by DHH.
|
||||
面向中国开发者的 [Omarchy](https://omarchy.org) 下游发行版:完整保留 Omarchy 的 Arch + Hyprland 桌面体验,为中国网络环境、中文使用习惯与国内 AI 服务做系统级增强。
|
||||
|
||||
Read more at [omarchy.org](https://omarchy.org).
|
||||
当前版本 `4.0.0.alpha-cn.1`(基于上游 `quattro` 分支构建的基线 ISO)。
|
||||
|
||||
## The Omarchy Manual
|
||||
## 特性
|
||||
|
||||
The manual lives in [`manual/`](manual/), which is its authoritative source. It's
|
||||
mirrored to [learn.omacom.io](https://learn.omacom.io/2/the-omarchy-manual), where
|
||||
its screenshots are also hosted.
|
||||
继承自 Omarchy:
|
||||
|
||||
- [Welcome to Omarchy!](manual/01-welcome-to-omarchy.md)
|
||||
- Hyprland 动态平铺桌面 + Quickshell 顶栏、菜单与系统面板,键盘驱动工作流
|
||||
- `omarchy` CLI 与 `Super + Space` 系统菜单,CLI 与 GUI 同构
|
||||
- 内置主题系统与一键换肤,终端 / 编辑器 / 桌面配色统一
|
||||
- AI coding agent 桌面集成(Claude Code、Codex、OpenCode 等,`mise` 按需安装)
|
||||
- Btrfs + Snapper 更新前快照与系统回滚
|
||||
- archiso 离线安装镜像,支持无人值守安装与双系统引导
|
||||
|
||||
**The Basics**
|
||||
OmarchyCN 增强(开发中,按路线图逐步交付):
|
||||
|
||||
- [Getting Started](manual/02-getting-started.md)
|
||||
- [Coming From Mac or Windows](manual/03-coming-from-mac-or-windows.md)
|
||||
- [Navigation](manual/04-navigation.md)
|
||||
- [The top bar](manual/05-the-top-bar.md)
|
||||
- [Themes](manual/06-themes.md)
|
||||
- [Hotkeys](manual/07-hotkeys.md)
|
||||
- [Unified Clipboard & History](manual/08-unified-clipboard-history.md)
|
||||
- [Reminders](manual/09-reminders.md)
|
||||
- [Notices](manual/10-notices.md)
|
||||
- [Text Extraction & Dictation](manual/11-text-extraction-dictation.md)
|
||||
- [Screenshots & Recording](manual/12-screenshots-recording.md)
|
||||
- [Toggles, idle & screensaver](manual/13-toggles-idle-screensaver.md)
|
||||
- [Omarchy CLI](manual/14-omarchy-cli.md)
|
||||
- **中国镜像管理**:Arch / npm / pip / Cargo / Go 等软件源测速、自动选择与故障切换
|
||||
- **中文环境开箱即用**:zh_CN locale、思源黑体 / 宋体字体栈、高分屏分数缩放预设
|
||||
- **中文输入法**:Fcitx5 + Rime 预配置,Wayland / GTK / Qt / Electron 全栈兼容,快捷键冲突自动处理
|
||||
- **AI Hub**:Kimi、DeepSeek、Z.AI/GLM 一等 Provider 支持,与 Claude Code、Codex、OpenCode、Kimi Code、Deep Code 等 Harness 的统一配置向导、凭据安全存储与连接诊断
|
||||
- **国内应用中心**:微信、QQ、飞书、钉钉、腾讯会议、WPS 等应用的可信安装入口
|
||||
- **Overlay 安装器**:在现有 Omarchy 上叠加 OmarchyCN,全程可逆、可卸载
|
||||
- **统一诊断**:`omarchycn doctor` 覆盖网络、镜像、输入法、显示与 AI 配置
|
||||
|
||||
**The Applications**
|
||||
## 下载与安装
|
||||
|
||||
- [Terminal](manual/15-terminal.md)
|
||||
- [Neovim](manual/16-neovim.md)
|
||||
- [AI](manual/17-ai.md)
|
||||
- [Development Tools](manual/18-development-tools.md)
|
||||
- [Shell Tools](manual/19-shell-tools.md)
|
||||
- [Shell Functions](manual/20-shell-functions.md)
|
||||
- [TUIs](manual/21-tuis.md)
|
||||
- [GUIs](manual/22-guis.md)
|
||||
- [Browsers](manual/23-browsers.md)
|
||||
- [Commercial apps/services](manual/24-commercial-apps-services.md)
|
||||
- [Web Apps](manual/25-web-apps.md)
|
||||
- [Gaming](manual/26-gaming.md)
|
||||
- [Filling out PDFs](manual/27-filling-out-pdfs.md)
|
||||
- [Windows VM](manual/28-windows-vm.md)
|
||||
- [Other Packages](manual/29-other-packages.md)
|
||||
从 [Releases](https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases) 页面下载 ISO 与 `SHA256SUMS.txt`,校验后写入 U 盘,UEFI 启动安装:
|
||||
|
||||
**Configuration**
|
||||
```bash
|
||||
sha256sum -c SHA256SUMS.txt
|
||||
```
|
||||
|
||||
- [Updates](manual/30-updates.md)
|
||||
- [Dotfiles](manual/31-dotfiles.md)
|
||||
- [Shell plugins](manual/32-shell-plugins.md)
|
||||
- [Monitors](manual/33-monitors.md)
|
||||
- [Keyboard, Mouse, Trackpad](manual/34-keyboard-mouse-trackpad.md)
|
||||
- [Networking](manual/35-networking.md)
|
||||
- [System sleep](manual/36-system-sleep.md)
|
||||
- [Hardware authentication](manual/37-hardware-authentication.md)
|
||||
- [Fonts](manual/38-fonts.md)
|
||||
- [Backgrounds](manual/39-backgrounds.md)
|
||||
- [Prompt](manual/40-prompt.md)
|
||||
- [Branding](manual/41-branding.md)
|
||||
- [Common tweaks](manual/42-common-tweaks.md)
|
||||
- [Making your own theme](manual/43-making-your-own-theme.md)
|
||||
## 上游文档
|
||||
|
||||
**The Rest**
|
||||
Omarchy 完整英文手册在 [`manual/`](manual/) 目录,亦见 [learn.omacom.io](https://learn.omacom.io/2/the-omarchy-manual)。
|
||||
|
||||
- [Mac support](manual/44-mac-support.md)
|
||||
- [Troubleshooting](manual/45-troubleshooting.md)
|
||||
- [FAQ](manual/46-faq.md)
|
||||
- [System snapshots](manual/47-system-snapshots.md)
|
||||
- [Security](manual/48-security.md)
|
||||
- [Omarchy on...](manual/49-omarchy-on.md)
|
||||
- [Dual Boot Install](manual/50-dual-boot-install.md)
|
||||
- [Unattended Installs](manual/51-unattended-installs.md)
|
||||
## 声明与许可证
|
||||
|
||||
## License
|
||||
OmarchyCN is an independent community distribution based on Omarchy. OmarchyCN is not affiliated with or endorsed by Basecamp, 37signals, or the Omarchy maintainers.
|
||||
|
||||
Omarchy is released under the [MIT License](https://opensource.org/licenses/MIT).
|
||||
代码沿用上游 [MIT License](LICENSE),保留 Omarchy 原始版权声明。
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# 安全政策
|
||||
|
||||
## 报告漏洞
|
||||
|
||||
请勿在公开 issue 中披露安全漏洞。发送邮件至:
|
||||
|
||||
**zhangyanghaha0407@outlook.com**(主题注明 [OmarchyCN Security])
|
||||
|
||||
请附:影响版本、复现步骤、影响面评估。我们在 72 小时内确认,修复后在
|
||||
Release Notes 中致谢(除非你要求匿名)。
|
||||
|
||||
## 范围
|
||||
|
||||
- OmarchyCN 软件包、ISO、安装与更新脚本、Registry 与签名链路
|
||||
- 上游 Omarchy / Arch / Hyprland 的漏洞请报给对应上游项目
|
||||
|
||||
## 签名验证
|
||||
|
||||
Release 产物的 PGP 公钥与验证方法见 [docs/release-signing.md](docs/release-signing.md)。
|
||||
+1
-1
@@ -39,8 +39,8 @@ GROUP_DESCRIPTIONS[capture]="Screenshots and screen recording"
|
||||
GROUP_DESCRIPTIONS[channel]="Omarchy release channel management"
|
||||
GROUP_DESCRIPTIONS[clipboard]="Clipboard helpers"
|
||||
GROUP_DESCRIPTIONS[cmd]="Command and shortcut helpers"
|
||||
GROUP_DESCRIPTIONS[cn]="OmarchyCN China environment integration"
|
||||
GROUP_DESCRIPTIONS[config]="System configuration helpers"
|
||||
GROUP_DESCRIPTIONS[crash]="Crash notification controls"
|
||||
GROUP_DESCRIPTIONS[debug]="Diagnostics and support logs"
|
||||
GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
|
||||
GROUP_DESCRIPTIONS[default]="Default application selection"
|
||||
|
||||
+1
-3
@@ -92,10 +92,8 @@ omp)
|
||||
;;
|
||||
ori)
|
||||
# Ori is a harness launcher, and `ori code` is the agent it runs itself.
|
||||
# A prompt alone means one headless turn there, printed after the turn ends,
|
||||
# so --interactive is what seeds the session with it and keeps the window.
|
||||
command=(ori code)
|
||||
[[ -n ${prompt:-} ]] && command+=(--interactive --prompt "$prompt")
|
||||
[[ -n ${prompt:-} ]] && command+=(--prompt "$prompt")
|
||||
;;
|
||||
pi)
|
||||
command=(pi)
|
||||
|
||||
@@ -528,7 +528,7 @@ def fetch_codex_rpc():
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[codex, "-s", "read-only", "-a", "on-request", "app-server"],
|
||||
[codex, "-s", "read-only", "-a", "untrusted", "app-server"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Map an AI profile onto Omarchy's default agent and hotkey
|
||||
# omarchy:args=[profile]
|
||||
# omarchy:examples=omarchycn ai default | omarchycn ai default work
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||
|
||||
profile="${1:-$(cn_ai_current_profile)}"
|
||||
|
||||
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
|
||||
echo "No such profile: $profile" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
harness=$(cn_ai_profile_field "$profile" harness)
|
||||
provider=$(cn_ai_profile_field "$profile" provider)
|
||||
model=$(cn_ai_profile_field "$profile" model)
|
||||
|
||||
# Only harnesses whose provider config persists in their own files can back
|
||||
# the upstream hotkey; opencode is session-env only, natives self-auth
|
||||
case "$harness" in
|
||||
claude-code) agent="claude" ;;
|
||||
codex) agent="codex" ;;
|
||||
*)
|
||||
echo "$harness 无法映射为上游默认 Agent;用 omarchycn ai launch $profile 启动" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
command=$(cn_ai_harness_field "$harness" command)
|
||||
if omarchy-cmd-missing "$command"; then
|
||||
install=$(cn_ai_harness_field "$harness" install)
|
||||
echo "Installing $harness: $install"
|
||||
$install
|
||||
fi
|
||||
|
||||
key=$(omarchy-cn-ai-secret get "$provider")
|
||||
|
||||
case "$harness" in
|
||||
claude-code)
|
||||
# Persist provider env in Claude Code's own settings so the upstream
|
||||
# Super+Shift+Ctrl+A -> omarchy-agent path launches fully configured
|
||||
settings="$HOME/.claude/settings.json"
|
||||
mkdir -p "${settings%/*}"
|
||||
base=$(cn_ai_endpoint "$provider" anthropic)
|
||||
fast=$(cn_ai_fast_model "$provider")
|
||||
current="{}"
|
||||
if [[ -s $settings ]]; then
|
||||
current=$(cat "$settings")
|
||||
cp "$settings" "$settings.omarchycn-bak-$(date +%Y%m%d-%H%M%S)"
|
||||
fi
|
||||
# Key reaches jq via environment, never the argument list
|
||||
rm -f "$settings.omarchycn-new"
|
||||
(
|
||||
umask 077
|
||||
CN_AI_KEY="$key" jq --arg base "$base" --arg model "$model" --arg fast "$fast" \
|
||||
'.env = (.env // {}) + {
|
||||
ANTHROPIC_BASE_URL: $base,
|
||||
ANTHROPIC_AUTH_TOKEN: env.CN_AI_KEY,
|
||||
ANTHROPIC_API_KEY: env.CN_AI_KEY,
|
||||
ANTHROPIC_MODEL: $model,
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: $model,
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: $model,
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: $fast
|
||||
}' <<<"$current" > "$settings.omarchycn-new"
|
||||
)
|
||||
mv "$settings.omarchycn-new" "$settings"
|
||||
;;
|
||||
codex)
|
||||
cn_ai_render_codex_config "$provider" "$model" "$key"
|
||||
;;
|
||||
esac
|
||||
|
||||
agent_file="$HOME/.config/omarchy/defaults/agent"
|
||||
mkdir -p "${agent_file%/*}"
|
||||
printf '%s\n' "$agent" > "$agent_file"
|
||||
|
||||
echo "默认 Agent: $agent($profile: $provider x $model)"
|
||||
echo "快捷键 Super+Shift+Ctrl+A 或 'omarchy agent' 将以该配置启动"
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Check AI harness installation, credentials, and endpoint health
|
||||
# omarchy:examples=omarchycn ai doctor
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||
|
||||
failures=0
|
||||
|
||||
profile=$(cn_ai_current_profile 2>/dev/null || true)
|
||||
if [[ -z $profile ]]; then
|
||||
echo "INFO ai: 未设置默认 Profile (omarchycn ai profile use <name>)"
|
||||
exit 0
|
||||
fi
|
||||
echo "INFO ai: current profile $profile"
|
||||
|
||||
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
|
||||
echo "FAIL ai: 默认 Profile $profile 的文件不存在 (omarchycn ai profile list)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
harness=$(cn_ai_profile_field "$profile" harness)
|
||||
provider=$(cn_ai_profile_field "$profile" provider)
|
||||
command=$(cn_ai_harness_field "$harness" command)
|
||||
proto=$(cn_ai_harness_field "$harness" protocol)
|
||||
|
||||
if omarchy-cmd-present "$command"; then
|
||||
echo "PASS ai: harness $harness installed ($command)"
|
||||
else
|
||||
install=$(cn_ai_harness_field "$harness" install)
|
||||
if [[ $install == doc:* ]]; then
|
||||
echo "WARN ai: harness $harness 未安装,需手动安装: ${install#doc:}"
|
||||
else
|
||||
echo "WARN ai: harness $harness 未安装,首次 launch 时自动安装"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $proto == "native" ]]; then
|
||||
echo "INFO ai: $harness 自管鉴权,跳过 OmarchyCN Key 检查"
|
||||
elif omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
|
||||
echo "PASS ai: $provider API Key stored"
|
||||
else
|
||||
echo "FAIL ai: $provider API Key 未存储 (omarchycn ai secret set $provider)"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
if [[ $proto != "native" ]]; then
|
||||
base=$(cn_ai_endpoint "$provider" "$proto")
|
||||
host=$(sed -E 's|https?://([^/]+).*|\1|' <<<"$base")
|
||||
if getent hosts "$host" > /dev/null 2>&1; then
|
||||
echo "PASS ai: endpoint DNS resolves ($host)"
|
||||
else
|
||||
echo "FAIL ai: endpoint DNS 无法解析 ($host)"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
exit $((failures > 0 ? 1 : 0))
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Launch the AI harness configured by a profile
|
||||
# omarchy:args=[profile]
|
||||
# omarchy:examples=omarchycn ai launch | omarchycn ai launch work
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||
|
||||
profile="${1:-$(cn_ai_current_profile)}"
|
||||
|
||||
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
|
||||
echo "No such profile: $profile" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
harness=$(cn_ai_profile_field "$profile" harness)
|
||||
provider=$(cn_ai_profile_field "$profile" provider)
|
||||
model=$(cn_ai_profile_field "$profile" model)
|
||||
command=$(cn_ai_harness_field "$harness" command)
|
||||
config_method=$(cn_ai_harness_field "$harness" config_method)
|
||||
|
||||
if omarchy-cmd-missing "$command"; then
|
||||
install=$(cn_ai_harness_field "$harness" install)
|
||||
if [[ $install == doc:* ]]; then
|
||||
echo "$harness 未安装,安装方法见官方文档: ${install#doc:}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Installing $harness: $install"
|
||||
$install
|
||||
fi
|
||||
|
||||
case "$config_method" in
|
||||
env)
|
||||
key=$(omarchy-cn-ai-secret get "$provider")
|
||||
if [[ $harness == "codex" ]]; then
|
||||
cn_ai_render_codex_config "$provider" "$model" "$key"
|
||||
exec "$command"
|
||||
fi
|
||||
env_exports=$(cn_ai_render_env "$harness" "$provider" "$model" "$key")
|
||||
eval "$env_exports"
|
||||
exec "$command"
|
||||
;;
|
||||
native)
|
||||
# Native harnesses (kimi-code, deep-code) manage their own auth
|
||||
exec "$command"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown config method: $config_method" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Create an AI profile binding a harness, provider, and model
|
||||
# omarchy:args=<name> <harness> <provider> <model|default-coding|fast>
|
||||
# omarchy:examples=omarchycn ai profile create work claude-code deepseek default-coding
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||
|
||||
name="${1:?usage: omarchycn ai profile create <name> <harness> <provider> <model>}"
|
||||
harness="${2:?missing harness}"
|
||||
provider="${3:?missing provider}"
|
||||
model="${4:?missing model}"
|
||||
|
||||
if [[ ! $name =~ ^[a-z0-9-]+$ ]]; then
|
||||
echo "Profile name must be lowercase alphanumeric/dashes: $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cn_ai_harness_field "$harness" command > /dev/null || {
|
||||
echo "Unknown harness: $harness (known: $(cn_ai_harness_ids | tr '\n' ' '))" >&2
|
||||
exit 1
|
||||
}
|
||||
jq -e --arg p "$provider" '.providers[$p]' "$CN_AI_PROVIDERS" > /dev/null || {
|
||||
echo "Unknown provider: $provider (known: $(cn_ai_provider_ids | tr '\n' ' '))" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
level=$(cn_ai_combo_level "$harness" "$provider")
|
||||
if [[ $level == "unsupported" ]]; then
|
||||
echo "Combo $harness x $provider is not in the compatibility matrix" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
allowlist=$(cn_ai_combo_models "$harness" "$provider")
|
||||
if [[ -n $allowlist ]]; then
|
||||
# Combo-restricted model set (e.g. codex uses its own catalog slugs)
|
||||
if ! grep -qxF "$model" <<<"$allowlist"; then
|
||||
echo "Model $model not supported for $harness x $provider" >&2
|
||||
echo "Supported: $(tr '\n' ' ' <<<"$allowlist")" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
resolved=$(cn_ai_model_by_alias "$provider" "$model" 2>/dev/null || true)
|
||||
if [[ -n $resolved ]]; then
|
||||
model="$resolved"
|
||||
elif ! cn_ai_models "$provider" | grep -qxF "$model"; then
|
||||
echo "Unknown model for $provider: $model" >&2
|
||||
echo "Available: $(cn_ai_models "$provider" | tr '\n' ' ')" >&2
|
||||
exit 1
|
||||
fi
|
||||
model_proto=$(cn_ai_model_protocol "$provider" "$model")
|
||||
harness_proto=$(cn_ai_harness_field "$harness" protocol)
|
||||
if [[ -n $model_proto && $model_proto != "$harness_proto" ]]; then
|
||||
echo "Model $model speaks $model_proto, but $harness needs $harness_proto" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$CN_AI_PROFILE_DIR"
|
||||
cat > "$CN_AI_PROFILE_DIR/$name.toml" <<EOF
|
||||
schema_version = 1
|
||||
name = "$name"
|
||||
harness = "$harness"
|
||||
provider = "$provider"
|
||||
model = "$model"
|
||||
secret_ref = "omarchycn://ai/$provider"
|
||||
EOF
|
||||
|
||||
echo "Profile $name: $harness x $provider x $model (compat: $level)"
|
||||
if ! omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
|
||||
echo "提示: 尚未存储 $provider 的 API Key,运行: omarchycn ai secret set $provider"
|
||||
fi
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=List AI profiles and the current default
|
||||
# omarchy:examples=omarchycn ai profile list
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||
|
||||
current=""
|
||||
if [[ -f $CN_AI_CURRENT ]]; then
|
||||
current=$(<"$CN_AI_CURRENT")
|
||||
fi
|
||||
|
||||
found="false"
|
||||
for f in "$CN_AI_PROFILE_DIR"/*.toml; do
|
||||
[[ -f $f ]] || continue
|
||||
found="true"
|
||||
name="${f##*/}"
|
||||
name="${name%.toml}"
|
||||
marker=" "
|
||||
[[ $name == "$current" ]] && marker="*"
|
||||
printf '%s %-14s %s x %s x %s\n' "$marker" "$name" \
|
||||
"$(cn_ai_profile_field "$name" harness)" \
|
||||
"$(cn_ai_profile_field "$name" provider)" \
|
||||
"$(cn_ai_profile_field "$name" model)"
|
||||
done
|
||||
|
||||
if [[ $found == "false" ]]; then
|
||||
echo "No profiles (create: omarchycn ai profile create <name> <harness> <provider> <model>)"
|
||||
fi
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Set the default AI profile
|
||||
# omarchy:args=<name>
|
||||
# omarchy:examples=omarchycn ai profile use work
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||
|
||||
name="${1:?usage: omarchycn ai profile use <name>}"
|
||||
|
||||
if [[ ! -f $CN_AI_PROFILE_DIR/$name.toml ]]; then
|
||||
echo "No such profile: $name (see: omarchycn ai profile list)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${CN_AI_CURRENT%/*}"
|
||||
echo "$name" > "$CN_AI_CURRENT"
|
||||
echo "Default AI profile: $name"
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Store, read, or delete AI provider API keys
|
||||
# omarchy:args=<set|get|rm> <provider>
|
||||
# omarchy:examples=omarchycn ai secret set deepseek | omarchycn ai secret get deepseek
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
action="${1:?usage: omarchycn ai secret <set|get|rm> <provider>}"
|
||||
provider="${2:?usage: omarchycn ai secret <set|get|rm> <provider>}"
|
||||
|
||||
if [[ ! $provider =~ ^[a-z0-9-]+$ ]]; then
|
||||
echo "Invalid provider name: $provider" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILE_DIR="$HOME/.local/state/omarchycn/secrets"
|
||||
FILE_PATH="$FILE_DIR/$provider"
|
||||
|
||||
secret_service_ok() {
|
||||
omarchy-cmd-present secret-tool && secret-tool search service omarchycn > /dev/null 2>&1
|
||||
}
|
||||
|
||||
pass_ok() {
|
||||
omarchy-cmd-present pass && pass ls > /dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$action" in
|
||||
set)
|
||||
if [[ -t 0 ]]; then
|
||||
read -rs -p "API key for $provider: " key
|
||||
echo
|
||||
else
|
||||
IFS= read -r key || true
|
||||
fi
|
||||
if [[ -z $key ]]; then
|
||||
echo "Empty key refused" >&2
|
||||
exit 1
|
||||
fi
|
||||
if secret_service_ok; then
|
||||
printf '%s' "$key" | secret-tool store --label "OmarchyCN AI: $provider" service omarchycn key "ai/$provider"
|
||||
# Purge stale copies in lower-priority backends so get never falls
|
||||
# through to an outdated key; a failed purge fails the set.
|
||||
# Existence check is decryption-free (store file on disk).
|
||||
if pass_ok && [[ -f ${PASSWORD_STORE_DIR:-$HOME/.password-store}/omarchycn/ai/$provider.gpg ]]; then
|
||||
if ! pass rm -f "omarchycn/ai/$provider" > /dev/null; then
|
||||
echo "存储成功但 pass 中的旧副本清除失败,请手动执行: pass rm omarchycn/ai/$provider" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
rm -f "$FILE_PATH"
|
||||
echo "Stored in Secret Service (secret-tool)"
|
||||
elif pass_ok; then
|
||||
printf '%s\n' "$key" | pass insert -m -f "omarchycn/ai/$provider" > /dev/null
|
||||
rm -f "$FILE_PATH"
|
||||
echo "Stored in pass (omarchycn/ai/$provider)"
|
||||
else
|
||||
mkdir -p "$FILE_DIR"
|
||||
chmod 700 "$FILE_DIR"
|
||||
rm -f "$FILE_PATH"
|
||||
(umask 177 && printf '%s' "$key" > "$FILE_PATH")
|
||||
echo "Stored in $FILE_PATH (0600 file fallback — 安装 libsecret 或 pass 可获得更安全的存储)"
|
||||
fi
|
||||
;;
|
||||
get)
|
||||
if secret_service_ok && secret-tool lookup service omarchycn key "ai/$provider" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
if pass_ok && pass show "omarchycn/ai/$provider" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ -f $FILE_PATH ]]; then
|
||||
cat "$FILE_PATH"
|
||||
exit 0
|
||||
fi
|
||||
echo "No secret stored for $provider (run: omarchycn ai secret set $provider)" >&2
|
||||
exit 1
|
||||
;;
|
||||
rm)
|
||||
removed=0
|
||||
if secret_service_ok && secret-tool clear service omarchycn key "ai/$provider" 2>/dev/null; then
|
||||
removed=1
|
||||
fi
|
||||
if pass_ok && pass rm -f "omarchycn/ai/$provider" > /dev/null 2>&1; then
|
||||
removed=1
|
||||
fi
|
||||
if [[ -f $FILE_PATH ]]; then
|
||||
rm -f "$FILE_PATH"
|
||||
removed=1
|
||||
fi
|
||||
if omarchy-cmd-present secret-tool && ! secret_service_ok; then
|
||||
echo "Secret Service 不可达,无法确认其中的副本已清除;服务恢复后重新执行 rm" >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( removed == 0 )); then
|
||||
echo "No secret stored for $provider" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Secret for $provider removed"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown action: $action (expected set, get, or rm)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Interactive wizard: pick harness, provider, model, store key, test
|
||||
# omarchy:examples=omarchycn ai setup
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo "omarchycn ai setup 需要交互终端;非交互场景用 omarchycn ai profile create" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
harness=$(cn_ai_harness_ids | gum choose --header "选择 Harness")
|
||||
|
||||
# Only combos whose adapter actually renders at launch
|
||||
mapfile -t providers < <(jq -r --arg h "$harness" \
|
||||
'.combos[$h] | to_entries[] | select(.value.adapter) | .key' "$CN_AI_COMPAT")
|
||||
if (( ${#providers[@]} == 0 )); then
|
||||
echo "兼容矩阵中没有 $harness 的可用 Provider 组合" >&2
|
||||
exit 1
|
||||
fi
|
||||
provider=$(printf '%s\n' "${providers[@]}" | gum choose --header "选择 Provider(兼容级别见 docs)")
|
||||
|
||||
harness_proto=$(cn_ai_harness_field "$harness" protocol)
|
||||
allowlist=$(cn_ai_combo_models "$harness" "$provider")
|
||||
if [[ -n $allowlist ]]; then
|
||||
model=$(gum choose --header "选择模型" <<<"$allowlist")
|
||||
else
|
||||
# Hide models bound to a different wire protocol than the harness speaks
|
||||
model=$(jq -r --arg p "$provider" --arg hp "$harness_proto" \
|
||||
'.providers[$p].models[] | select((.protocol // $hp) == $hp) | .id' \
|
||||
"$CN_AI_PROVIDERS" | gum choose --header "选择模型")
|
||||
fi
|
||||
|
||||
default_name="$harness-$provider"
|
||||
name=$(gum input --header "Profile 名称" --value "$default_name")
|
||||
|
||||
proto=$(cn_ai_harness_field "$harness" protocol)
|
||||
if [[ $proto == "native" ]]; then
|
||||
omarchy-cn-ai-profile-create "$name" "$harness" "$provider" "$model"
|
||||
omarchy-cn-ai-profile-use "$name"
|
||||
echo "$harness 自管鉴权:首次启动时按其官方流程登录/配置"
|
||||
echo "完成。启动: omarchycn ai launch"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
|
||||
key=$(gum input --password --header "$provider API Key(获取: $(jq -r --arg p "$provider" '.providers[$p].key_url' "$CN_AI_PROVIDERS"))")
|
||||
printf '%s' "$key" | omarchy-cn-ai-secret set "$provider"
|
||||
fi
|
||||
|
||||
# Keep any existing profile intact until the new config proves itself
|
||||
profile_file="$CN_AI_PROFILE_DIR/$name.toml"
|
||||
if [[ -f $profile_file ]]; then
|
||||
cp "$profile_file" "$profile_file.omarchycn-prev"
|
||||
fi
|
||||
|
||||
omarchy-cn-ai-profile-create "$name" "$harness" "$provider" "$model"
|
||||
|
||||
if gum confirm "运行连接测试?"; then
|
||||
if ! omarchy-cn-ai-test "$name"; then
|
||||
if [[ -f $profile_file.omarchycn-prev ]]; then
|
||||
mv "$profile_file.omarchycn-prev" "$profile_file"
|
||||
echo "测试未通过,已恢复原 Profile $name" >&2
|
||||
else
|
||||
rm -f "$profile_file"
|
||||
echo "测试未通过,Profile $name 未保留" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$profile_file.omarchycn-prev"
|
||||
omarchy-cn-ai-profile-use "$name"
|
||||
echo "完成。启动: omarchycn ai launch"
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Test connectivity, auth, and model availability for an AI profile
|
||||
# omarchy:args=[profile]
|
||||
# omarchy:examples=omarchycn ai test | omarchycn ai test work
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||
|
||||
profile="${1:-$(cn_ai_current_profile)}"
|
||||
|
||||
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
|
||||
echo "No such profile: $profile" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
harness=$(cn_ai_profile_field "$profile" harness)
|
||||
provider=$(cn_ai_profile_field "$profile" provider)
|
||||
model=$(cn_ai_profile_field "$profile" model)
|
||||
proto=$(cn_ai_harness_field "$harness" protocol)
|
||||
# Codex speaks the Responses API (wire_api=responses), not chat/completions
|
||||
[[ $harness == "codex" ]] && proto="openai-responses"
|
||||
failures=0
|
||||
|
||||
if [[ $proto == "native" ]]; then
|
||||
echo "INFO ai-test: $harness 自管鉴权,本测试仅覆盖 env/config 型 Harness"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Endpoint override for mock-server tests
|
||||
base="${OMARCHYCN_AI_TEST_BASE_URL:-$(cn_ai_endpoint "$provider" "${proto%-responses}")}"
|
||||
echo "INFO ai-test: $profile ($harness x $provider x $model) -> $base"
|
||||
|
||||
if ! key=$(omarchy-cn-ai-secret get "$provider" 2>/dev/null); then
|
||||
echo "FAIL ai-test: 未存储 $provider 的 API Key (omarchycn ai secret set $provider)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
body=$(mktemp)
|
||||
trap 'rm -f "$body"' EXIT
|
||||
|
||||
case "$proto" in
|
||||
anthropic)
|
||||
code=$(curl -sS -o "$body" -w '%{http_code}' -m 30 --connect-timeout 8 \
|
||||
-X POST "${base%/}/v1/messages" \
|
||||
-H "x-api-key: $key" -H "authorization: Bearer $key" \
|
||||
-H "anthropic-version: 2023-06-01" -H "content-type: application/json" \
|
||||
-d "{\"model\":\"$model\",\"max_tokens\":8,\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}" || true)
|
||||
;;
|
||||
openai)
|
||||
code=$(curl -sS -o "$body" -w '%{http_code}' -m 30 --connect-timeout 8 \
|
||||
-X POST "${base%/}/chat/completions" \
|
||||
-H "Authorization: Bearer $key" -H "content-type: application/json" \
|
||||
-d "{\"model\":\"$model\",\"max_tokens\":8,\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}" || true)
|
||||
;;
|
||||
openai-responses)
|
||||
code=$(curl -sS -o "$body" -w '%{http_code}' -m 30 --connect-timeout 8 \
|
||||
-X POST "${base%/}/responses" \
|
||||
-H "Authorization: Bearer $key" -H "content-type: application/json" \
|
||||
-d "{\"model\":\"$model\",\"input\":\"ping\",\"max_output_tokens\":16}" || true)
|
||||
;;
|
||||
*)
|
||||
echo "FAIL ai-test: unknown protocol $proto"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
[[ -z $code ]] && code=000
|
||||
|
||||
case "$code" in
|
||||
200)
|
||||
echo "PASS ai-test: 连接、鉴权与模型调用成功 (HTTP 200)"
|
||||
;;
|
||||
401 | 403)
|
||||
echo "FAIL ai-test: 鉴权失败 (HTTP $code),检查 API Key"
|
||||
failures=$((failures + 1))
|
||||
;;
|
||||
404 | 400 | 422)
|
||||
echo "FAIL ai-test: 端点可达但请求被拒 (HTTP $code),可能是模型名问题"
|
||||
head -c 200 "$body" 2>/dev/null && echo
|
||||
failures=$((failures + 1))
|
||||
;;
|
||||
000)
|
||||
echo "FAIL ai-test: 无法连接 $base (DNS/TLS/网络)"
|
||||
failures=$((failures + 1))
|
||||
;;
|
||||
*)
|
||||
echo "FAIL ai-test: HTTP $code"
|
||||
head -c 200 "$body" 2>/dev/null && echo
|
||||
failures=$((failures + 1))
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -f $body
|
||||
exit $((failures > 0 ? 1 : 0))
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Install a China app from the catalog with source confirmation
|
||||
# omarchy:args=<app-id> [--yes]
|
||||
# omarchy:examples=omarchycn app install wechat | omarchycn app install tencent-docs
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
APPS_JSON="$OMARCHY_PATH/cn/apps.json"
|
||||
|
||||
app="${1:?usage: omarchycn app install <app-id> (see: omarchycn app list)}"
|
||||
yes="${2:-}"
|
||||
|
||||
entry=$(jq -e --arg a "$app" '.apps[$a]' "$APPS_JSON") || {
|
||||
echo "Unknown app: $app (see: omarchycn app list)" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
name=$(jq -r '.name' <<<"$entry")
|
||||
source_type=$(jq -r '.source' <<<"$entry")
|
||||
license=$(jq -r '.license' <<<"$entry")
|
||||
|
||||
if [[ $license == "proprietary" && $yes != "--yes" ]]; then
|
||||
pkg=$(jq -r '.package' <<<"$entry")
|
||||
echo "$name 为专有软件,来源: AUR/$pkg(构建脚本公开,二进制来自厂商)"
|
||||
if [[ -t 0 ]]; then
|
||||
gum confirm "确认安装?" || exit 1
|
||||
else
|
||||
echo "非交互环境需显式加 --yes 确认专有软件安装" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$source_type" in
|
||||
aur)
|
||||
omarchy-pkg-aur-add "$(jq -r '.package' <<<"$entry")"
|
||||
;;
|
||||
webapp)
|
||||
omarchy-webapp-install "$name" "$(jq -r '.url' <<<"$entry")" "$(jq -r '.icon' <<<"$entry")"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown source type: $source_type" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$name 安装完成"
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=List China app catalog entries and their sources
|
||||
# omarchy:examples=omarchycn app list
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
APPS_JSON="$OMARCHY_PATH/cn/apps.json"
|
||||
|
||||
printf '%-14s %-10s %-8s %-12s %s\n' "ID" "NAME" "SOURCE" "LICENSE" "PACKAGE/URL"
|
||||
jq -r '.apps | to_entries[] |
|
||||
[.key, .value.name, .value.source, .value.license, (.value.package // .value.url)] | @tsv' \
|
||||
"$APPS_JSON" |
|
||||
while IFS=$'\t' read -r id name source license ref; do
|
||||
printf '%-14s %-10s %-8s %-12s %s\n' "$id" "$name" "$source" "$license" "$ref"
|
||||
done
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Apply china or official registry profile to development tools
|
||||
# omarchy:args=<china|official> [--target npm,pip,cargo,go,gem,docker]
|
||||
# omarchy:examples=omarchycn dev-mirror apply china | omarchycn dev-mirror apply official --target npm,pip
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
|
||||
|
||||
profile="${1:?usage: omarchycn dev-mirror apply <china|official> [--target a,b]}"
|
||||
shift
|
||||
|
||||
if [[ $profile != "china" && $profile != "official" ]]; then
|
||||
echo "Unknown profile: $profile (expected china or official)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
targets=("${CN_DM_TARGETS[@]}")
|
||||
if [[ ${1:-} == "--target" ]]; then
|
||||
IFS=',' read -ra targets <<<"${2:?--target needs a comma-separated list}"
|
||||
fi
|
||||
|
||||
stamp=$(date +%Y%m%d-%H%M%S)
|
||||
|
||||
for target in "${targets[@]}"; do
|
||||
if [[ ! " ${CN_DM_TARGETS[*]} " == *" $target "* ]]; then
|
||||
echo "Unknown target: $target (known: ${CN_DM_TARGETS[*]})" >&2
|
||||
exit 1
|
||||
fi
|
||||
url=$(cn_dm_url "$target" "$profile")
|
||||
cn_dm_backup "$(cn_dm_config_file "$target")" "$stamp"
|
||||
"cn_dm_set_$target" "$url"
|
||||
echo "$target -> ${url:-<default>}"
|
||||
done
|
||||
|
||||
echo "Backups (if any): $CN_DM_BACKUP_ROOT/$stamp"
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Check reachability of each configured development registry
|
||||
# omarchy:examples=omarchycn dev-mirror doctor
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
|
||||
|
||||
failures=0
|
||||
|
||||
for target in "${CN_DM_TARGETS[@]}"; do
|
||||
current=$("cn_dm_get_$target")
|
||||
if [[ -z $current ]]; then
|
||||
echo "INFO $target: using tool default"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Reduce to a probe-able https URL
|
||||
probe="${current#sparse+}"
|
||||
probe="${probe%%,*}"
|
||||
if curl -sSf -o /dev/null -m 8 --connect-timeout 5 "$probe" 2>/dev/null; then
|
||||
echo "PASS $target: $current"
|
||||
elif curl -sS -o /dev/null -m 8 --connect-timeout 5 -w '%{http_code}' "$probe" 2>/dev/null | grep -qE '^[34]'; then
|
||||
# Registry roots often answer 3xx/404 to bare GET while the service works
|
||||
echo "PASS $target: $current"
|
||||
else
|
||||
echo "FAIL $target: $current unreachable"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
exit $((failures > 0 ? 1 : 0))
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Show configured registry for each development ecosystem
|
||||
# omarchy:examples=omarchycn dev-mirror list
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
|
||||
|
||||
printf '%-8s %-52s %s\n' "TARGET" "CURRENT" "CONFIG"
|
||||
|
||||
for target in "${CN_DM_TARGETS[@]}"; do
|
||||
current=$("cn_dm_get_$target")
|
||||
printf '%-8s %-52s %s\n' "$target" "${current:-<default>}" "$(cn_dm_config_file "$target")"
|
||||
done
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Point one development ecosystem at a custom registry URL
|
||||
# omarchy:args=<target> <url>
|
||||
# omarchy:examples=omarchycn dev-mirror set npm https://registry.example.com
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
|
||||
|
||||
target="${1:?usage: omarchycn dev-mirror set <target> <url>}"
|
||||
url="${2:?usage: omarchycn dev-mirror set <target> <url>}"
|
||||
|
||||
if [[ ! " ${CN_DM_TARGETS[*]} " == *" $target "* ]]; then
|
||||
echo "Unknown target: $target (known: ${CN_DM_TARGETS[*]})" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cn_dm_backup "$(cn_dm_config_file "$target")" "$(date +%Y%m%d-%H%M%S)"
|
||||
"cn_dm_set_$target" "$url"
|
||||
echo "$target -> $url"
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Apply a display scale preset with 15s revert confirmation
|
||||
# omarchy:args=<1.0|1.25|1.5|1.6|1.75|2.0>
|
||||
# omarchy:examples=omarchycn display scale 1.6
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
preset="${1:?usage: omarchycn display scale <1.0|1.25|1.5|1.6|1.75|2.0>}"
|
||||
|
||||
case "$preset" in
|
||||
1.0 | 1.25 | 1.5 | 1.6 | 1.75 | 2.0) ;;
|
||||
*)
|
||||
echo "Unknown preset: $preset (supported: 1.0 1.25 1.5 1.6 1.75 2.0)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
monitors="$HOME/.config/hypr/monitors.lua"
|
||||
if [[ ! -f $monitors ]]; then
|
||||
omarchy-refresh-config hypr/monitors.lua
|
||||
fi
|
||||
|
||||
gdk=$(printf '%.0f' "$preset")
|
||||
backup="$monitors.omarchycn-prev"
|
||||
cp "$monitors" "$backup"
|
||||
|
||||
sed -i -E \
|
||||
-e "s|^local omarchy_monitor_scale = .*|local omarchy_monitor_scale = $preset|" \
|
||||
-e "s|^local omarchy_gdk_scale = .*|local omarchy_gdk_scale = $gdk|" \
|
||||
"$monitors"
|
||||
|
||||
if ! grep -q "omarchy_monitor_scale = $preset" "$monitors" ||
|
||||
! grep -q "omarchy_gdk_scale = $gdk" "$monitors"; then
|
||||
mv "$backup" "$monitors"
|
||||
echo "monitors.lua lacks the omarchy scale lines; reverted, edit it manually" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Monitor scale: $preset, GDK scale: $gdk"
|
||||
|
||||
if [[ -z ${HYPRLAND_INSTANCE_SIGNATURE:-} ]]; then
|
||||
rm -f "$backup"
|
||||
echo "当前不在 Hyprland 会话内,重登录后生效"
|
||||
elif ! hyprctl reload > /dev/null 2>&1; then
|
||||
mv "$backup" "$monitors"
|
||||
echo "hyprctl reload 失败,已恢复原配置" >&2
|
||||
exit 1
|
||||
elif [[ -t 0 ]]; then
|
||||
echo "15 秒内按 y 保留新缩放,超时或按其他键自动恢复"
|
||||
if read -r -t 15 -n 1 answer && [[ $answer == "y" ]]; then
|
||||
rm -f "$backup"
|
||||
echo "已保留"
|
||||
else
|
||||
mv "$backup" "$monitors"
|
||||
hyprctl reload > /dev/null 2>&1
|
||||
echo "已恢复原缩放"
|
||||
fi
|
||||
else
|
||||
omarchy-notification-send "OmarchyCN" "缩放已改为 $preset;如异常执行: mv $backup $monitors && hyprctl reload" 2> /dev/null || true
|
||||
echo "无终端交互:已应用;恢复命令: mv $backup $monitors && hyprctl reload"
|
||||
fi
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Check network, pacman mirror, and dev registries; --fix fails over mirrors
|
||||
# omarchy:args=[network|mirror|dev-mirror] [--fix]
|
||||
# omarchy:examples=omarchycn doctor | omarchycn doctor mirror --fix
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/mirror.sh"
|
||||
|
||||
module="all"
|
||||
fix="false"
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
all | network | mirror | dev-mirror | ime) module="$arg" ;;
|
||||
--fix) fix="true" ;;
|
||||
*)
|
||||
echo "Unknown argument: $arg" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
failures=0
|
||||
|
||||
check_network() {
|
||||
local host
|
||||
for host in mirrors.tuna.tsinghua.edu.cn archlinux.org; do
|
||||
if getent hosts "$host" > /dev/null 2>&1; then
|
||||
echo "PASS network: DNS resolves $host"
|
||||
else
|
||||
echo "FAIL network: cannot resolve $host"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
done
|
||||
if curl -sSf -o /dev/null -m 8 --connect-timeout 5 https://www.baidu.com 2>/dev/null; then
|
||||
echo "PASS network: HTTPS reachable (baidu.com)"
|
||||
else
|
||||
echo "FAIL network: HTTPS unreachable (baidu.com)"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
check_mirror() {
|
||||
local primary speed ttfb age
|
||||
|
||||
primary=$(grep -sE '^Server' "$CN_MIRRORLIST" | head -1 | sed -E 's/^Server = //; s|/\$repo/os/\$arch/?$||' || true)
|
||||
if [[ -z $primary ]]; then
|
||||
echo "FAIL mirror: no Server entries in $CN_MIRRORLIST"
|
||||
failures=$((failures + 1))
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "INFO mirror: primary $primary"
|
||||
read -r speed ttfb age < <(cn_mirror_probe "$primary")
|
||||
if (( speed > 0 )) && [[ $age != "stale" ]]; then
|
||||
echo "PASS mirror: primary healthy ($((speed / 1024)) KB/s, sync age ${age}s)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "FAIL mirror: primary unhealthy (speed=$speed age=$age)"
|
||||
failures=$((failures + 1))
|
||||
|
||||
if [[ $fix == "true" && -f $CN_PROFILE_FILE && $(<"$CN_PROFILE_FILE") == "china" ]]; then
|
||||
echo "INFO mirror: re-applying china profile (auto-failover)"
|
||||
if omarchy-cn-mirror-apply china; then
|
||||
failures=$((failures - 1))
|
||||
omarchy-notification-send "OmarchyCN" "pacman 镜像已自动切换" 2> /dev/null || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_dev_mirror() {
|
||||
omarchy-cn-dev-mirror-doctor || failures=$((failures + 1))
|
||||
}
|
||||
|
||||
check_ime() {
|
||||
omarchy-cn-ime-status || failures=$((failures + 1))
|
||||
}
|
||||
|
||||
case "$module" in
|
||||
network) check_network ;;
|
||||
mirror) check_mirror ;;
|
||||
dev-mirror) check_dev_mirror ;;
|
||||
ime) check_ime ;;
|
||||
all)
|
||||
check_network
|
||||
check_mirror
|
||||
check_dev_mirror
|
||||
check_ime
|
||||
;;
|
||||
esac
|
||||
|
||||
if (( failures > 0 )); then
|
||||
echo "Doctor: $failures failure(s)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Doctor: all checks passed"
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Install CJK fonts and the OmarchyCN fontconfig priority
|
||||
# omarchy:examples=omarchycn font apply
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
omarchy-pkg-add noto-fonts-cjk noto-fonts-emoji
|
||||
|
||||
conf_dir="$HOME/.config/fontconfig/conf.d"
|
||||
mkdir -p "$conf_dir"
|
||||
cp "$OMARCHY_PATH/cn/fontconfig/64-omarchycn-cjk.conf" "$conf_dir/"
|
||||
fc-cache -f > /dev/null
|
||||
|
||||
echo "CJK fonts installed, fontconfig priority applied ($conf_dir/64-omarchycn-cjk.conf)"
|
||||
omarchy-cn-font-status
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Check Chinese font availability and fallback correctness
|
||||
# omarchy:examples=omarchycn font status
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
failures=0
|
||||
|
||||
for family in "Noto Sans CJK SC" "Noto Serif CJK SC" "Noto Sans Mono CJK SC"; do
|
||||
matched=$(fc-match --format '%{family}' "$family")
|
||||
if [[ $matched == *"$family"* ]]; then
|
||||
echo "PASS font: $family"
|
||||
else
|
||||
echo "FAIL font: $family missing (got: $matched)"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
zh_sans=$(fc-match --format '%{family}' sans-serif:lang=zh-cn)
|
||||
if [[ $zh_sans == *"CJK SC"* ]]; then
|
||||
echo "PASS fallback: zh-cn sans-serif -> $zh_sans"
|
||||
else
|
||||
echo "FAIL fallback: zh-cn sans-serif -> $zh_sans (expected SC variant)"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
# Untagged requests (Chromium/Electron path) must reach SC before JP/KR
|
||||
untagged=$(fc-match --sort sans-serif | grep -m1 -oE 'CJK (SC|JP|TC|KR|HK)' || true)
|
||||
if [[ $untagged == "CJK SC" ]]; then
|
||||
echo "PASS fallback: untagged CJK -> SC"
|
||||
else
|
||||
echo "FAIL fallback: untagged CJK -> ${untagged:-none} (expected SC)"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
exit $((failures > 0 ? 1 : 0))
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Install Fcitx5 Rime and Chinese addons with a default profile
|
||||
# omarchy:examples=omarchycn ime apply
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
omarchy-pkg-add fcitx5-rime fcitx5-chinese-addons fcitx5-configtool
|
||||
|
||||
profile_dir="$HOME/.config/fcitx5"
|
||||
mkdir -p "$profile_dir"
|
||||
|
||||
if [[ -f $profile_dir/profile && "$(<"$profile_dir/profile")" != "$(<"$OMARCHY_PATH/cn/fcitx5/profile")" ]]; then
|
||||
backup_dir="$HOME/.local/state/omarchycn/backups/ime/$(date +%Y%m%d-%H%M%S)"
|
||||
mkdir -p "$backup_dir"
|
||||
cp "$profile_dir/profile" "$backup_dir/profile"
|
||||
echo "Existing profile backed up to $backup_dir"
|
||||
fi
|
||||
|
||||
cp "$OMARCHY_PATH/cn/fcitx5/profile" "$profile_dir/profile"
|
||||
|
||||
if systemctl --user is-active omarchy-fcitx5.service > /dev/null 2>&1; then
|
||||
systemctl --user restart omarchy-fcitx5.service
|
||||
echo "Fcitx5 restarted with Rime profile"
|
||||
else
|
||||
echo "Fcitx5 profile installed (takes effect at next graphical session)"
|
||||
fi
|
||||
|
||||
omarchy-cn-ime-status
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Set the input method toggle key, migrating the menu key if needed
|
||||
# omarchy:args=<ctrl-space|super-space>
|
||||
# omarchy:examples=omarchycn ime hotkey ctrl-space | omarchycn ime hotkey super-space
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
choice="${1:?usage: omarchycn ime hotkey <ctrl-space|super-space>}"
|
||||
fcitx_config="$HOME/.config/fcitx5/config"
|
||||
bindings="$HOME/.config/hypr/bindings.lua"
|
||||
|
||||
# Replace or append the [Hotkey/TriggerKeys] section in fcitx5 global config
|
||||
set_trigger() {
|
||||
local key="$1"
|
||||
|
||||
mkdir -p "${fcitx_config%/*}"
|
||||
touch "$fcitx_config"
|
||||
awk '
|
||||
/^\[Hotkey\/TriggerKeys\]$/ { skip = 1; next }
|
||||
/^\[/ { skip = 0 }
|
||||
!skip { print }
|
||||
' "$fcitx_config" > "$fcitx_config.omarchycn-tmp"
|
||||
printf '[Hotkey/TriggerKeys]\n0=%s\n' "$key" >> "$fcitx_config.omarchycn-tmp"
|
||||
mv "$fcitx_config.omarchycn-tmp" "$fcitx_config"
|
||||
}
|
||||
|
||||
remove_menu_migration() {
|
||||
if [[ -f $bindings ]]; then
|
||||
sed -i '/^-- OmarchyCN ime hotkey begin$/,/^-- OmarchyCN ime hotkey end$/d' "$bindings"
|
||||
fi
|
||||
}
|
||||
|
||||
case "$choice" in
|
||||
ctrl-space)
|
||||
set_trigger "Control+space"
|
||||
remove_menu_migration
|
||||
echo "输入法切换键: Ctrl+Space(Omarchy 菜单保持 Super+Space)"
|
||||
;;
|
||||
super-space)
|
||||
set_trigger "Super+space"
|
||||
mkdir -p "${bindings%/*}"
|
||||
touch "$bindings"
|
||||
remove_menu_migration
|
||||
cat >> "$bindings" <<'EOF'
|
||||
-- OmarchyCN ime hotkey begin
|
||||
hl.unbind("SUPER + SPACE")
|
||||
hl.unbind("SUPER + ALT + SPACE")
|
||||
o.bind("SUPER + ALT + SPACE", "Omarchy menu", "omarchy-menu toggle")
|
||||
-- OmarchyCN ime hotkey end
|
||||
EOF
|
||||
echo "输入法切换键: Super+Space"
|
||||
echo "Omarchy 菜单已迁移至 Super+Alt+Space(写入 $bindings)"
|
||||
echo "原 Super+Alt+Space 的 Apps 菜单让位,可从根菜单进入或自行改绑"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown hotkey choice: $choice (expected ctrl-space or super-space)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if systemctl --user is-active omarchy-fcitx5.service > /dev/null 2>&1; then
|
||||
systemctl --user restart omarchy-fcitx5.service
|
||||
fi
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Check Fcitx5 Rime installation and configuration
|
||||
# omarchy:examples=omarchycn ime status
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
failures=0
|
||||
|
||||
for pkg in fcitx5 fcitx5-gtk fcitx5-qt fcitx5-rime fcitx5-chinese-addons; do
|
||||
if pacman -Q "$pkg" > /dev/null 2>&1; then
|
||||
echo "PASS ime: $pkg installed"
|
||||
else
|
||||
echo "FAIL ime: $pkg missing"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if grep -sq '^Name=rime$' "$HOME/.config/fcitx5/profile"; then
|
||||
echo "PASS ime: rime in fcitx5 profile"
|
||||
else
|
||||
echo "FAIL ime: rime not in fcitx5 profile (run: omarchycn ime apply)"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
if grep -sq '^DefaultIM=rime$' "$HOME/.config/fcitx5/profile"; then
|
||||
echo "PASS ime: rime is the default input method"
|
||||
else
|
||||
echo "FAIL ime: DefaultIM is not rime"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
env_file="/usr/share/omarchy/default/environment.d/10-omarchy-fcitx.conf"
|
||||
if [[ -f $env_file ]] || [[ -f $OMARCHY_PATH/default/environment.d/10-omarchy-fcitx.conf ]]; then
|
||||
echo "PASS ime: fcitx environment file present"
|
||||
else
|
||||
echo "FAIL ime: fcitx environment file missing"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
trigger=$(grep -sA2 '^\[Hotkey/TriggerKeys\]' "$HOME/.config/fcitx5/config" | grep -sE '^0=' | cut -d= -f2- || true)
|
||||
if [[ $trigger == "Super+space" ]] && ! grep -sq 'OmarchyCN ime hotkey begin' "$HOME/.config/hypr/bindings.lua"; then
|
||||
echo "FAIL ime: Super+space 与 Omarchy 菜单冲突 (run: omarchycn ime hotkey super-space)"
|
||||
failures=$((failures + 1))
|
||||
else
|
||||
echo "PASS ime: trigger key ${trigger:-Control+space (fcitx5 default)}"
|
||||
fi
|
||||
|
||||
if pgrep -x fcitx5 > /dev/null 2>&1; then
|
||||
echo "PASS ime: fcitx5 running"
|
||||
else
|
||||
echo "INFO ime: fcitx5 not running (headless session?)"
|
||||
fi
|
||||
|
||||
exit $((failures > 0 ? 1 : 0))
|
||||
Executable
+148
@@ -0,0 +1,148 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Install or remove the OmarchyCN layer on an existing Omarchy
|
||||
# omarchy:args=[--uninstall]
|
||||
# omarchy:examples=omarchy-cn-install-overlay | omarchy-cn-install-overlay --uninstall
|
||||
# Bootstraps onto a foreign OMARCHY_PATH, so it resolves its own checkout root.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SRC=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
DEST="${OMARCHY_PATH:?OMARCHY_PATH must point at the target Omarchy install}"
|
||||
MANIFEST="$HOME/.local/state/omarchycn/overlay-manifest"
|
||||
|
||||
run_in() {
|
||||
local root="$1"
|
||||
shift
|
||||
if [[ -w $root ]]; then
|
||||
"$@"
|
||||
else
|
||||
sudo "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
valid_entry() {
|
||||
[[ $1 == "cn" || $1 =~ ^bin/(omarchycn|omarchy-cn-[a-z0-9-]+)$ ]]
|
||||
}
|
||||
|
||||
MENU_EXT="$HOME/.config/omarchy/extensions/omarchy-menu.jsonc"
|
||||
MENU_MARKER="// OmarchyCN overlay menu extension"
|
||||
|
||||
# Comments-only files (upstream ships such a template) hold no user content
|
||||
menu_ext_has_user_content() {
|
||||
[[ -f $MENU_EXT ]] || return 1
|
||||
grep -qF "$MENU_MARKER" "$MENU_EXT" && return 1
|
||||
[[ -n $(grep -vE '^[[:space:]]*(//.*)?$' "$MENU_EXT" | tr -d '[:space:]{}') ]]
|
||||
}
|
||||
|
||||
install_menu_extension() {
|
||||
if menu_ext_has_user_content; then
|
||||
echo "跳过菜单扩展:$MENU_EXT 已有用户内容,请手动合并 default/omarchy/omarchy-menu.jsonc 的 OmarchyCN 段"
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "${MENU_EXT%/*}"
|
||||
{
|
||||
echo "$MENU_MARKER"
|
||||
echo "{"
|
||||
sed -n '/^ \/\/ OmarchyCN$/,$p' "$SRC/default/omarchy/omarchy-menu.jsonc" | sed '$d'
|
||||
echo "}"
|
||||
} > "$MENU_EXT"
|
||||
echo "OmarchyCN 菜单扩展已写入 $MENU_EXT"
|
||||
}
|
||||
|
||||
remove_menu_extension() {
|
||||
if [[ -f $MENU_EXT ]] && grep -qF "$MENU_MARKER" "$MENU_EXT"; then
|
||||
rm -f "$MENU_EXT"
|
||||
fi
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
if [[ ! -f $MANIFEST ]]; then
|
||||
echo "No overlay manifest at $MANIFEST" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local dest="" rel=""
|
||||
dest=$(grep -m1 '^dest=' "$MANIFEST" | cut -d= -f2-)
|
||||
if [[ -z $dest || ! -d $dest ]]; then
|
||||
echo "Manifest has no valid dest= record" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate every entry before deleting anything
|
||||
while IFS= read -r rel; do
|
||||
[[ $rel == source=* || $rel == dest=* ]] && continue
|
||||
if ! valid_entry "$rel"; then
|
||||
echo "Refusing suspicious manifest entry: $rel (nothing deleted)" >&2
|
||||
exit 1
|
||||
fi
|
||||
done < "$MANIFEST"
|
||||
|
||||
while IFS= read -r rel; do
|
||||
[[ $rel == source=* || $rel == dest=* ]] && continue
|
||||
if [[ $rel == "cn" ]]; then
|
||||
run_in "$dest" rm -rf "$dest/cn"
|
||||
else
|
||||
run_in "$dest" rm -f "$dest/$rel"
|
||||
fi
|
||||
done < "$MANIFEST"
|
||||
|
||||
remove_menu_extension
|
||||
rm -f "$MANIFEST"
|
||||
echo "OmarchyCN overlay removed from $dest"
|
||||
}
|
||||
|
||||
install() {
|
||||
if [[ ! -f $DEST/version || ! -x $DEST/bin/omarchy ]]; then
|
||||
echo "No Omarchy install found at $DEST" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ $SRC == "$DEST" ]]; then
|
||||
echo "Source checkout and target are the same tree; nothing to overlay" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Reinstall: remove the previous overlay first so upgrades leave no residue
|
||||
if [[ -f $MANIFEST ]]; then
|
||||
uninstall
|
||||
fi
|
||||
|
||||
if [[ -e $DEST/cn ]]; then
|
||||
echo "Refusing to take over unmanaged $DEST/cn" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local f rel entries=()
|
||||
for f in "$SRC/bin/omarchycn" "$SRC"/bin/omarchy-cn-*; do
|
||||
rel="bin/${f##*/}"
|
||||
if [[ -e $DEST/$rel ]]; then
|
||||
echo "Refusing to overwrite unmanaged $DEST/$rel" >&2
|
||||
exit 1
|
||||
fi
|
||||
entries+=("$rel")
|
||||
done
|
||||
|
||||
# Manifest is the intent record, published BEFORE copying: if a copy fails
|
||||
# midway, a rerun sees the manifest, uninstalls the partial state, retries.
|
||||
mkdir -p "${MANIFEST%/*}"
|
||||
{
|
||||
echo "source=$SRC"
|
||||
echo "dest=$DEST"
|
||||
echo "cn"
|
||||
printf '%s\n' "${entries[@]}"
|
||||
} > "$MANIFEST"
|
||||
|
||||
run_in "$DEST" cp -r "$SRC/cn" "$DEST/cn"
|
||||
for rel in "${entries[@]}"; do
|
||||
run_in "$DEST" cp "$SRC/$rel" "$DEST/$rel"
|
||||
done
|
||||
|
||||
install_menu_extension
|
||||
echo "OmarchyCN overlay installed into $DEST (${#entries[@]} commands)"
|
||||
echo "Verify: omarchy cn version"
|
||||
}
|
||||
|
||||
if [[ ${1:-} == "--uninstall" ]]; then
|
||||
uninstall
|
||||
else
|
||||
install
|
||||
fi
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Generate zh_CN.UTF-8 and en_US.UTF-8 locales
|
||||
# omarchy:examples=omarchycn locale apply
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
for loc in zh_CN en_US; do
|
||||
if ! grep -qE "^${loc}\.UTF-8 UTF-8" /etc/locale.gen; then
|
||||
echo "${loc}.UTF-8 UTF-8" | sudo tee -a /etc/locale.gen > /dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
sudo locale-gen
|
||||
|
||||
for loc in zh_CN en_US; do
|
||||
if ! locale -a | grep -qiE "^${loc}\.utf-?8$"; then
|
||||
echo "FAIL locale: ${loc}.UTF-8 not generated" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "PASS locale: ${loc}.UTF-8"
|
||||
done
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Apply a mirror profile or pin one mirror for pacman
|
||||
# omarchy:args=<china|official|mirror-id>
|
||||
# omarchy:examples=omarchycn mirror apply china | omarchycn mirror apply official | omarchycn mirror apply tuna
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/mirror.sh"
|
||||
|
||||
target="${1:?usage: omarchycn mirror apply <china|official|mirror-id>}"
|
||||
|
||||
case "$target" in
|
||||
china)
|
||||
echo "Benchmarking China mirrors..."
|
||||
mapfile -t cn_ids < <(cn_mirror_ids_by_region cn)
|
||||
mapfile -t ranked < <(cn_mirror_rank "${cn_ids[@]}")
|
||||
if (( ${#ranked[@]} == 0 )); then
|
||||
echo "No healthy China mirror reachable; keeping current mirrorlist" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Primary + up to two backups
|
||||
cn_mirror_write_list china "${ranked[@]:0:3}"
|
||||
;;
|
||||
official)
|
||||
cn_mirror_write_list official geo worldwide
|
||||
;;
|
||||
*)
|
||||
url=$(cn_mirror_url "$target")
|
||||
if [[ -z $url ]]; then
|
||||
echo "Unknown mirror id: $target (see: omarchycn mirror benchmark)" >&2
|
||||
exit 1
|
||||
fi
|
||||
cn_mirror_write_list "pin:$target" "$target"
|
||||
;;
|
||||
esac
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Benchmark Arch mirrors for speed, latency, and sync freshness
|
||||
# omarchy:examples=omarchycn mirror benchmark
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/mirror.sh"
|
||||
|
||||
printf '%-11s %-22s %12s %8s %10s\n' "ID" "NAME" "SPEED" "TTFB" "SYNC"
|
||||
|
||||
for id in $(cn_mirror_ids); do
|
||||
url=$(cn_mirror_url "$id")
|
||||
read -r speed ttfb age < <(cn_mirror_probe "$url")
|
||||
|
||||
if (( speed > 0 )); then
|
||||
speed_h="$((speed / 1024)) KB/s"
|
||||
else
|
||||
speed_h="FAIL"
|
||||
fi
|
||||
case "$age" in
|
||||
stale) sync_h="STALE" ;;
|
||||
unknown) sync_h="?" ;;
|
||||
*) sync_h="$((age / 60))m ago" ;;
|
||||
esac
|
||||
|
||||
printf '%-11s %-22s %12s %7ss %10s\n' "$id" "$(cn_mirror_name "$id")" "$speed_h" "${ttfb:0:5}" "$sync_h"
|
||||
done
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Pin pacman to a single mirror
|
||||
# omarchy:args=<mirror-id>
|
||||
# omarchy:examples=omarchycn mirror pin tuna
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
exec omarchy-cn-mirror-apply "${1:?usage: omarchycn mirror pin <mirror-id>}"
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Restore the most recent mirrorlist backup
|
||||
# omarchy:examples=omarchycn mirror restore
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/mirror.sh"
|
||||
|
||||
backups=("$CN_MIRRORLIST".omarchycn-bak-*)
|
||||
if [[ ! -e ${backups[0]} ]]; then
|
||||
echo "No OmarchyCN mirrorlist backups found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
latest="${backups[-1]}"
|
||||
sudo cp "$latest" "$CN_MIRRORLIST"
|
||||
rm -f "$CN_PROFILE_FILE"
|
||||
echo "Restored $CN_MIRRORLIST from ${latest##*/}"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Show current pacman mirror profile and servers
|
||||
# omarchy:examples=omarchycn mirror status
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/mirror.sh"
|
||||
|
||||
if [[ -f $CN_PROFILE_FILE ]]; then
|
||||
echo "Profile: $(<"$CN_PROFILE_FILE")"
|
||||
else
|
||||
echo "Profile: not managed by OmarchyCN"
|
||||
fi
|
||||
|
||||
echo "Mirrorlist: $CN_MIRRORLIST"
|
||||
echo "Servers:"
|
||||
grep -E '^Server' "$CN_MIRRORLIST" | sed 's/^/ /'
|
||||
|
||||
backups=("$CN_MIRRORLIST".omarchycn-bak-*)
|
||||
if [[ -e ${backups[0]} ]]; then
|
||||
echo "Backups: ${#backups[@]} (latest: ${backups[-1]##*/})"
|
||||
fi
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Restore development registry configs from a backup stamp
|
||||
# omarchy:args=<stamp>
|
||||
# omarchy:examples=omarchycn restore config 20260824-184243
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
|
||||
|
||||
stamp="${1:?usage: omarchycn restore config <stamp> (see: omarchycn restore list)}"
|
||||
dir="$CN_DM_BACKUP_ROOT/$stamp"
|
||||
|
||||
if [[ ! -d $dir ]]; then
|
||||
echo "No backup at $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
restored=0
|
||||
for target in "${CN_DM_TARGETS[@]}"; do
|
||||
dest=$(cn_dm_config_file "$target")
|
||||
src="$dir/${dest##*/}"
|
||||
if [[ -f $src && $dest != /etc/* ]]; then
|
||||
mkdir -p "${dest%/*}"
|
||||
cp "$src" "$dest"
|
||||
echo "restored $target: $dest"
|
||||
restored=$((restored + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if (( restored == 0 )); then
|
||||
echo "Nothing restored from $dir (system files like docker are not auto-restored)" >&2
|
||||
exit 1
|
||||
fi
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=List OmarchyCN configuration backups
|
||||
# omarchy:examples=omarchycn restore list
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
|
||||
|
||||
echo "Dev-mirror backups ($CN_DM_BACKUP_ROOT):"
|
||||
if [[ -d $CN_DM_BACKUP_ROOT ]]; then
|
||||
for dir in "$CN_DM_BACKUP_ROOT"/*/; do
|
||||
[[ -d $dir ]] || continue
|
||||
stamp="${dir%/}"
|
||||
stamp="${stamp##*/}"
|
||||
echo " $stamp: $(ls "$dir" | tr '\n' ' ')"
|
||||
done
|
||||
else
|
||||
echo " (none)"
|
||||
fi
|
||||
|
||||
echo "Mirrorlist backups (/etc/pacman.d):"
|
||||
found="false"
|
||||
for f in /etc/pacman.d/mirrorlist.omarchycn-bak-*; do
|
||||
if [[ -e $f ]]; then
|
||||
echo " ${f##*/}"
|
||||
found="true"
|
||||
fi
|
||||
done
|
||||
if [[ $found == "false" ]]; then
|
||||
echo " (none)"
|
||||
fi
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=First-run wizard: language, timezone, scale, Chinese env, mirrors, apps, AI
|
||||
# omarchy:examples=omarchycn setup
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo "omarchycn setup 需要交互终端" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STATE_DIR="$HOME/.local/state/omarchycn/setup-done"
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
run_step() {
|
||||
local step="$1" title="$2"
|
||||
shift 2
|
||||
|
||||
if [[ -f $STATE_DIR/$step ]]; then
|
||||
gum confirm "「$title」已完成,重新运行?" --default=false || return 0
|
||||
elif ! gum confirm "运行「$title」?(可跳过,稍后重进)"; then
|
||||
return 0
|
||||
fi
|
||||
echo "==> $title"
|
||||
if "$@"; then
|
||||
touch "$STATE_DIR/$step"
|
||||
else
|
||||
gum confirm "「$title」失败,继续后面的步骤?" || exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
step_language() {
|
||||
local choice env_file="$HOME/.config/environment.d/90-omarchycn-lang.conf"
|
||||
choice=$(gum choose --header "界面语言 LANG" "zh_CN.UTF-8" "en_US.UTF-8" "保持当前")
|
||||
if [[ $choice == "保持当前" ]]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "${env_file%/*}"
|
||||
echo "LANG=$choice" > "$env_file"
|
||||
echo "LANG=$choice 写入 $env_file(重登录生效)"
|
||||
}
|
||||
|
||||
step_timezone() {
|
||||
local choice
|
||||
choice=$(gum choose --header "时区" "Asia/Shanghai" "保持当前" "手动输入")
|
||||
if [[ $choice == "保持当前" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ $choice == "手动输入" ]]; then
|
||||
choice=$(timedatectl list-timezones | gum filter --header "选择时区")
|
||||
fi
|
||||
sudo timedatectl set-timezone "$choice"
|
||||
echo "时区: $(timedatectl show -p Timezone --value)"
|
||||
}
|
||||
|
||||
step_scale() {
|
||||
local choice
|
||||
choice=$(gum choose --header "显示缩放" "1.0" "1.25" "1.5" "1.6" "1.75" "2.0" "保持当前")
|
||||
if [[ $choice == "保持当前" ]]; then
|
||||
return 0
|
||||
fi
|
||||
omarchy-cn-display-scale "$choice"
|
||||
}
|
||||
|
||||
step_hotkey() {
|
||||
local choice
|
||||
choice=$(gum choose --header "输入法切换键" "ctrl-space" "super-space")
|
||||
omarchy-cn-ime-hotkey "$choice"
|
||||
}
|
||||
|
||||
step_mirror() {
|
||||
local choice
|
||||
choice=$(gum choose --header "pacman 镜像策略" "china" "official")
|
||||
omarchy-cn-mirror-apply "$choice"
|
||||
}
|
||||
|
||||
step_dev_mirror() {
|
||||
local choice
|
||||
choice=$(gum choose --header "开发工具镜像(npm/pip/cargo/go/gem)" "china" "official")
|
||||
omarchy-cn-dev-mirror-apply "$choice" --target npm,pip,cargo,go,gem
|
||||
}
|
||||
|
||||
step_apps() {
|
||||
local catalog picks app rc=0
|
||||
catalog=$(omarchy-cn-app-list | tail -n +2 | awk '{print $1}') || return 1
|
||||
picks=$(gum choose --no-limit --header "选择要安装的国内应用(空格多选,回车确认)" <<<"$catalog" || true)
|
||||
for app in $picks; do
|
||||
omarchy-cn-app-install "$app" || rc=1
|
||||
done
|
||||
return $rc
|
||||
}
|
||||
|
||||
step_privacy() {
|
||||
echo "隐私状态(无可配置项,如实告知):"
|
||||
echo " - OmarchyCN 不含遥测代码,默认无任何数据上报"
|
||||
echo " - 诊断信息仅在你手动运行 doctor 时本地生成,不上传"
|
||||
gum confirm "已了解" --affirmative "了解" --negative "" || true
|
||||
}
|
||||
|
||||
run_step language "语言 Language" step_language
|
||||
run_step timezone "时区 Timezone" step_timezone
|
||||
run_step scale "显示缩放 Display Scale" step_scale
|
||||
run_step locale "中文 Locale 生成" omarchy-cn-locale-apply
|
||||
run_step fonts "中文字体与 fallback" omarchy-cn-font-apply
|
||||
run_step ime "Fcitx5 + Rime 输入法" omarchy-cn-ime-apply
|
||||
run_step hotkey "输入法切换键" step_hotkey
|
||||
run_step mirror "pacman 镜像" step_mirror
|
||||
run_step dev-mirror "开发工具镜像" step_dev_mirror
|
||||
run_step apps "国内应用" step_apps
|
||||
run_step ai "AI Hub(Harness/Provider/Key)" omarchy-cn-ai-setup
|
||||
run_step privacy "隐私与诊断" step_privacy
|
||||
|
||||
echo
|
||||
echo "==> 最终检查"
|
||||
if omarchy-cn-doctor all; then
|
||||
echo "OmarchyCN 初始化完成。随时可用 omarchycn setup 重进任一步骤。"
|
||||
else
|
||||
echo "初始化步骤已执行,但诊断存在失败项(见上),修复后可重跑 omarchycn setup" >&2
|
||||
exit 1
|
||||
fi
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Show OmarchyCN version, paths, and configured state
|
||||
# omarchy:examples=omarchycn status
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "OmarchyCN $(omarchy-cn-version)"
|
||||
echo "Omarchy upstream: $(<"$OMARCHY_PATH/version")"
|
||||
echo "Root: $OMARCHY_PATH"
|
||||
|
||||
config="$HOME/.config/omarchycn"
|
||||
state="$HOME/.local/state/omarchycn"
|
||||
echo "User config: $config $([[ -d $config ]] && echo "(present)" || echo "(not created)")"
|
||||
echo "State: $state $([[ -d $state ]] && echo "(present)" || echo "(not created)")"
|
||||
|
||||
if [[ -f $config/mirror-profile ]]; then
|
||||
echo "Mirror profile: $(<"$config/mirror-profile")"
|
||||
else
|
||||
echo "Mirror profile: not configured"
|
||||
fi
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Update an overlay install: pull source, reinstall, run migrations
|
||||
# omarchy:examples=omarchycn update
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MANIFEST="$HOME/.local/state/omarchycn/overlay-manifest"
|
||||
MIGRATION_DONE_DIR="$HOME/.local/state/omarchycn/migrations-done"
|
||||
|
||||
if [[ ! -f $MANIFEST ]]; then
|
||||
echo "Not an overlay install (no $MANIFEST)." >&2
|
||||
echo "Package-based installs update through 'omarchy update' / pacman." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
src=$(grep -m1 '^source=' "$MANIFEST" | cut -d= -f2-)
|
||||
if [[ ! -d $src/.git ]]; then
|
||||
echo "Overlay source $src is not a git checkout" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
old=$(git -C "$src" rev-parse --short HEAD)
|
||||
git -C "$src" pull --ff-only
|
||||
new=$(git -C "$src" rev-parse --short HEAD)
|
||||
echo "Source: $old -> $new"
|
||||
|
||||
"$src/bin/omarchy-cn-install-overlay"
|
||||
|
||||
# Run each not-yet-completed migration in filename order, one marker per file
|
||||
if [[ -d $src/cn/migrations ]]; then
|
||||
mkdir -p "$MIGRATION_DONE_DIR"
|
||||
for m in "$src"/cn/migrations/*.sh; do
|
||||
[[ -f $m ]] || continue
|
||||
name="${m##*/}"
|
||||
if [[ ! -f $MIGRATION_DONE_DIR/$name ]]; then
|
||||
echo "Migration: $name"
|
||||
bash -euo pipefail "$m"
|
||||
touch "$MIGRATION_DONE_DIR/$name"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "OmarchyCN update complete ($(omarchy-cn-version 2>/dev/null || echo unknown))"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Show the OmarchyCN version
|
||||
# omarchy:examples=omarchycn version
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
upstream=$(<"$OMARCHY_PATH/version")
|
||||
release=$(<"$OMARCHY_PATH/cn/release")
|
||||
|
||||
echo "${upstream}-cn.${release}"
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Silence crash notifications for one program, or list what is silenced
|
||||
# omarchy:args=[--] [<program>] [on|off|toggle]
|
||||
# omarchy:examples=omarchy crash mute | omarchy crash mute hyprland | omarchy crash mute /usr/bin/hyprland | omarchy crash mute hyprland off
|
||||
|
||||
# The flag omarchy-crash-watch reads before announcing a crash. Muting is per
|
||||
# program; Trigger > Toggle > Crash Capture is the switch for all of them.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
readonly MUTES="$HOME/.local/state/omarchy/toggles/crash-ignore"
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy crash mute [--] [<program>] [on|off|toggle]" >&2
|
||||
}
|
||||
|
||||
# Only regular files, because that is all the watcher honours: anything else in
|
||||
# there would be reported as muted while the crashes kept arriving. The dotted
|
||||
# glob is for a program legitimately called .hidden, and `.` and `..` fail the
|
||||
# same -f test that keeps them out.
|
||||
list() {
|
||||
local entry found=0
|
||||
|
||||
for entry in "$MUTES"/* "$MUTES"/.*; do
|
||||
[[ -f $entry ]] || continue
|
||||
printf '%s\n' "${entry##*/}"
|
||||
found=1
|
||||
done
|
||||
|
||||
((found)) || echo "No programs muted. Crashes all notify."
|
||||
}
|
||||
|
||||
# A program may be named -h, and the router answers that with its own help
|
||||
# before this ever runs. `omarchy crash mute -- -h` is the way through.
|
||||
[[ ${1:-} == "--" ]] && shift
|
||||
|
||||
if (($# == 0)); then
|
||||
list
|
||||
exit 0
|
||||
fi
|
||||
|
||||
program=$1
|
||||
action=${2:-on}
|
||||
|
||||
# The watcher keys the mute on the executable's basename, so accept the path it
|
||||
# reports as readily as the name, and reduce either the same way it does.
|
||||
program=${program##*/}
|
||||
|
||||
if [[ -z $program || $program == "." || $program == ".." ]]; then
|
||||
echo "Not a program name: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$action" in
|
||||
on|off|toggle) ;;
|
||||
*)
|
||||
echo "Not an action: $action" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
omarchy-toggle "crash-ignore/$program" "$action" || exit 1
|
||||
|
||||
# Report what is now true rather than what was asked for: the flag is what the
|
||||
# watcher reads, and a toggle does not say which way it went.
|
||||
if omarchy-toggle-enabled "crash-ignore/$program"; then
|
||||
echo "Muted crash notifications for $program."
|
||||
else
|
||||
echo "Crash notifications for $program are back on."
|
||||
fi
|
||||
+5
-28
@@ -48,17 +48,12 @@ announce() {
|
||||
# -n 0 so a restart does not re-announce crashes already dealt with.
|
||||
journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID" 2>/dev/null |
|
||||
while IFS= read -r entry; do
|
||||
# A dash for a field that is empty as well as one that is missing: tab is
|
||||
# IFS whitespace, so an empty field collapses into the next delimiter and
|
||||
# every field after it shifts along one. A process can set its own comm to
|
||||
# nothing, and that crash used to be read as somebody else's and dropped.
|
||||
IFS=$'\t' read -r uid comm pid exe signal < <(
|
||||
jq -r 'def field: if . == null or . == "" then "-" else . end;
|
||||
[(._UID | field),
|
||||
(.COREDUMP_COMM | field),
|
||||
(.COREDUMP_PID | field),
|
||||
(.COREDUMP_EXE | field),
|
||||
(.COREDUMP_SIGNAL_NAME | field)] | @tsv' <<<"$entry" 2>/dev/null
|
||||
jq -r '[(._UID // "-"),
|
||||
(.COREDUMP_COMM // "-"),
|
||||
(.COREDUMP_PID // "-"),
|
||||
(.COREDUMP_EXE // "-"),
|
||||
(.COREDUMP_SIGNAL_NAME // "-")] | @tsv' <<<"$entry" 2>/dev/null
|
||||
)
|
||||
|
||||
[[ $pid =~ ^[0-9]+$ ]] || continue
|
||||
@@ -76,29 +71,11 @@ journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID" 2>/dev/null |
|
||||
name=$comm
|
||||
[[ $exe == /* ]] && name=${exe##*/}
|
||||
|
||||
# A process can set its own comm to anything prctl takes, slashes included,
|
||||
# and a crash with no recorded executable falls back to it. The mute below
|
||||
# turns this name into a path, so keep it one component: a crash must not
|
||||
# reach a flag outside crash-ignore/, nor have a diagnosis write one there.
|
||||
name=${name##*/}
|
||||
|
||||
# What that leaves is not always a name. "/" leaves nothing, which is no
|
||||
# kind of array subscript and no kind of toast; a dot component names a
|
||||
# directory rather than a flag, so a mute on it would touch that directory
|
||||
# and then never match; and a dash is what the read above puts there when
|
||||
# the crash recorded no name at all.
|
||||
[[ -n $name && $name != "-" && $name != "." && $name != ".." ]] || name=unknown
|
||||
|
||||
[[ -n $ignore_pattern && $name =~ $ignore_pattern ]] && continue
|
||||
|
||||
# Never announce our own machinery, or it notifies about itself.
|
||||
[[ $name == omarchy-crash-* || $name == omarchy-agent-* ]] && continue
|
||||
|
||||
# Muted at the end of a diagnosis, when the user was offered it and said
|
||||
# yes. A flag per program rather than one list, so omarchy-crash-mute can
|
||||
# lift one without reading, rewriting and re-parsing the rest.
|
||||
omarchy-toggle-enabled "crash-ignore/$name" && continue
|
||||
|
||||
now=$EPOCHSECONDS
|
||||
(((now - ${last_notified[$name]:-0}) < dedupe_seconds)) && continue
|
||||
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Whenever this runs as root — invoked directly through the passwordless
|
||||
# sudoers rule, or re-execed by require_root below — sudo's secure_path decides
|
||||
# where a bare helper resolves, and a dev link (etc/sudoers.d/omarchy-dev-path)
|
||||
# prepends a user-writable checkout bin/ to it. Every helper this script calls
|
||||
# by bare name (dirname, install, tee, rm, nmcli, systemctl, awk) is a system
|
||||
# tool, never an omarchy-* command, so pin PATH to trusted system directories
|
||||
# and keep root from resolving one out of that checkout. The unprivileged
|
||||
# wrapper phase keeps the caller's PATH so it can still find sudo/pkexec.
|
||||
if (( EUID == 0 )); then
|
||||
export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
fi
|
||||
|
||||
NM_DNS_CONF=/etc/NetworkManager/conf.d/20-omarchy-dns.conf
|
||||
|
||||
provider_from_arg() {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Check that a git URL names a repository, not a transport helper
|
||||
# omarchy:args=<git-url>
|
||||
# omarchy:hidden=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# git picks a remote helper -- an executable it runs at clone time -- out of a URL
|
||||
# in exactly two shapes, and no others: `<helper>::<address>`, and
|
||||
# `<scheme>://<address>` for any scheme git does not handle itself. A single
|
||||
# colon is always scp-style ssh, and a bare path is always a path; neither can
|
||||
# reach a helper. So constraining those two shapes covers the whole surface.
|
||||
#
|
||||
# The `::` shape is refused outright, because no helper reachable that way is one
|
||||
# a theme or plugin URL has business naming, and `ext::` runs a shell command.
|
||||
# The `://` shape cannot be refused the same way, since it is also how every
|
||||
# legitimate URL arrives -- so it is allowlisted instead. The list is the
|
||||
# transports git still connects itself, `git+ssh` and `ssh+git` included: those
|
||||
# two are spelled like a helper but are read as plain ssh. `ext` and `fd` are
|
||||
# left out deliberately -- git ships a helper for each, and `ext` runs whatever
|
||||
# command the URL carries.
|
||||
TRANSPORTS=(ssh git git+ssh ssh+git http https ftp ftps file)
|
||||
|
||||
fail() {
|
||||
echo "omarchy-git-url-check: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
url="${1-}"
|
||||
|
||||
if [[ -z $url ]]; then
|
||||
fail "a git URL is required"
|
||||
fi
|
||||
|
||||
if [[ $url == -* || $url =~ ^[A-Za-z0-9][A-Za-z0-9+.-]*:: ]]; then
|
||||
fail "'$url' names a git option or transport helper, not a repository."
|
||||
fi
|
||||
|
||||
if [[ $url =~ ^([A-Za-z0-9][A-Za-z0-9+.-]*):// ]]; then
|
||||
scheme="${BASH_REMATCH[1]}"
|
||||
|
||||
for transport in "${TRANSPORTS[@]}"; do
|
||||
if [[ $scheme == "$transport" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
fail "'$url' names the '$scheme' transport, which Omarchy does not clone from."
|
||||
fi
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Match the Dell XPS 13 DX13260 that requires the sidecar amplifier workaround.
|
||||
|
||||
product_sku="${OMARCHY_DMI_PRODUCT_SKU:-/sys/class/dmi/id/product_sku}"
|
||||
|
||||
omarchy-hw-match "DX13260" &&
|
||||
grep -qix "0E53" "$product_sku" 2>/dev/null
|
||||
@@ -11,14 +11,6 @@ MONITOR_LUA="$HOME/.config/hypr/monitors.lua"
|
||||
|
||||
INTERNAL=$(omarchy-hyprland-monitor-laptop)
|
||||
|
||||
# INTERNAL is written into generated Lua and hyprctl eval/dispatch below, so a
|
||||
# name that is not a plain connector string could execute on the next reload.
|
||||
# Names come from hyprctl; a user-created headless output can carry anything.
|
||||
if [[ -n $INTERNAL && ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||
echo "Refusing unsafe internal monitor name" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
valid_scale() {
|
||||
[[ $1 =~ ^[0-9]+([.][0-9]+)?$ ]]
|
||||
}
|
||||
|
||||
@@ -28,13 +28,6 @@ off() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The name is written into generated Lua below, so only a plain connector
|
||||
# name may pass; anything else could execute on the next reload.
|
||||
if [[ ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||
omarchy-notification-send -g "Refusing unsafe monitor name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! omarchy-hyprland-monitor-external-active; then
|
||||
omarchy-notification-send -g "Can't disable the only active display"
|
||||
exit 1
|
||||
|
||||
@@ -22,15 +22,6 @@ on() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Both names are written into generated Lua below, so only plain connector
|
||||
# names may pass; a user-created headless output can carry any name.
|
||||
for output in "$INTERNAL" "$EXTERNAL"; do
|
||||
if [[ ! $output =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||
omarchy-notification-send -g "Refusing unsafe monitor name"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
omarchy-hyprland-toggle $DISABLE_TOGGLE off
|
||||
|
||||
if omarchy-hyprland-toggle-disabled $TOGGLE; then
|
||||
|
||||
@@ -80,14 +80,6 @@ set_scale() {
|
||||
local width="$(echo "$monitor_info" | jq -r '.width')"
|
||||
local height="$(echo "$monitor_info" | jq -r '.height')"
|
||||
local refresh_rate="$(echo "$monitor_info" | jq -r '.refreshRate')"
|
||||
|
||||
# active_monitor is written into the Lua string eval'd below, so only a plain
|
||||
# connector name may pass; a hostile output name could execute otherwise.
|
||||
if [[ ! $active_monitor =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||
echo "Refusing unsafe monitor name" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local new_scale="$(clean_scale "$requested_scale" "$width" "$height")"
|
||||
# GTK only honors integer GDK_SCALE values, so persist the nearest whole
|
||||
# factor even when the monitor scale itself is fractional.
|
||||
|
||||
@@ -10,4 +10,4 @@ echo "Enabling ONCE background service..."
|
||||
sudo systemctl enable --now once-background.service
|
||||
|
||||
echo -e "\nLaunching ONCE..."
|
||||
sudo once
|
||||
once
|
||||
|
||||
@@ -9,12 +9,10 @@
|
||||
# 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 — 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
|
||||
# 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
|
||||
exec pkexec /usr/bin/env TERM="${TERM:-xterm-256color}" lazydocker
|
||||
fi
|
||||
|
||||
@@ -93,13 +93,6 @@ if [[ -z $url ]]; then
|
||||
[[ -n $url ]] || fail "a git URL is required"
|
||||
fi
|
||||
|
||||
# Refuse a URL that names a git option or a transport helper before cloning, so
|
||||
# an untrusted URL cannot run a command before the plugin is validated or
|
||||
# enabled. The check is shared with omarchy-theme-install and explains itself; a
|
||||
# missing checker leaves this non-zero, which refuses the URL rather than
|
||||
# cloning it.
|
||||
omarchy-git-url-check "$url" || exit 1
|
||||
|
||||
if (( ! ASSUME_YES )); then
|
||||
cat >&2 <<WARN
|
||||
|
||||
|
||||
@@ -24,14 +24,9 @@ echo -e "\e[32mRemoving FIDO2 device from authentication.\n\e[0m"
|
||||
|
||||
remove_pam_config
|
||||
|
||||
authdir=/etc/fido2
|
||||
|
||||
# -d follows symlinks, so a dangling link at /etc/fido2 would survive this and
|
||||
# a later setup would install the authfile through it. rm -rf on a symlink
|
||||
# removes the link itself, never the directory it points at.
|
||||
if [[ -e $authdir || -L $authdir ]]; then
|
||||
if [[ -d /etc/fido2 ]]; then
|
||||
echo "Removing FIDO2 configuration..."
|
||||
sudo rm -rf "$authdir"
|
||||
sudo rm -rf /etc/fido2
|
||||
fi
|
||||
|
||||
echo "Removing FIDO2 packages..."
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
# 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
|
||||
if ! id -nG "$USER" 2>/dev/null | grep -qw docker; then
|
||||
echo "Sudoless Docker is not enabled: $USER is not in the docker group."
|
||||
exit 0
|
||||
fi
|
||||
@@ -16,19 +13,13 @@ fi
|
||||
echo "Removing $USER from the docker group..."
|
||||
sudo gpasswd -d "$USER" docker >/dev/null
|
||||
|
||||
# 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.
|
||||
# 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.
|
||||
omarchy-state set reboot-required
|
||||
|
||||
echo ""
|
||||
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
|
||||
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."
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
|
||||
check_fido2_hardware() {
|
||||
@@ -51,79 +50,13 @@ if ! check_fido2_hardware; then
|
||||
fi
|
||||
|
||||
# Create the pamu2fcfg file
|
||||
authdir=/etc/fido2
|
||||
authfile=/etc/fido2/fido2
|
||||
|
||||
# install -d follows a symlink here and applies the mode and ownership to
|
||||
# whatever it points at, so the credential would be staged and published inside
|
||||
# the link target and that directory reopened to root:root 755. This is the
|
||||
# threat omarchy-remove-security-fido2 already names on its side.
|
||||
if [[ -L $authdir || ( -e $authdir && ! -d $authdir ) ]]; then
|
||||
echo -e "\e[31m\n$authdir is not a FIDO2 configuration directory.\e[0m"
|
||||
echo "Run omarchy-remove-security-fido2 first, then set FIDO2 up again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# -f follows symlinks, so the already-registered check below reads a symlinked
|
||||
# authfile as a registration and leaves it in place, and is false for a
|
||||
# directory, so it tries to register over one. Only a regular file is a valid
|
||||
# pam_u2f authfile.
|
||||
if [[ -L $authfile || ( -e $authfile && ! -f $authfile ) ]]; then
|
||||
echo -e "\e[31m\n$authfile is not a FIDO2 registration file.\e[0m"
|
||||
echo "Run omarchy-remove-security-fido2 first, then set FIDO2 up again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f $authfile ]]; then
|
||||
sudo install -d -m 755 -o root -g root "$authdir"
|
||||
if [[ ! -f /etc/fido2/fido2 ]]; then
|
||||
sudo mkdir -p /etc/fido2
|
||||
echo -e "\e[32m\nLet's setup your device by confirming on the device now.\e[0m"
|
||||
echo -e "Touch your FIDO2 key when it lights up...\n"
|
||||
|
||||
# A unique sibling created by root cannot be replaced by another process
|
||||
# running as this user. Stream pamu2fcfg into it instead of asking root to
|
||||
# reopen a caller-owned path: an observed temporary name could otherwise be
|
||||
# replaced with a symlink before the privileged copy. The final rename is
|
||||
# atomic, and -T refuses a directory at the destination. Mode 644 keeps the
|
||||
# root-owned global authfile readable when pam_u2f uses openasuser; only root
|
||||
# can still rewrite it.
|
||||
stage=""
|
||||
|
||||
# mktemp's output is an operand for four privileged commands below, one of
|
||||
# them an rm. Take only the name this script asked for rather than whatever
|
||||
# came back on stdout.
|
||||
safe_stage_path() {
|
||||
local candidate=$1
|
||||
local prefix="$authfile.new."
|
||||
local suffix
|
||||
|
||||
[[ $candidate == "$prefix"* ]] || return 1
|
||||
suffix=${candidate#"$prefix"}
|
||||
[[ $suffix =~ ^[[:alnum:]]{6}$ ]]
|
||||
}
|
||||
|
||||
cleanup_stage() {
|
||||
local status=$?
|
||||
|
||||
if safe_stage_path "$stage"; then
|
||||
sudo rm -f -- "$stage" || true
|
||||
fi
|
||||
|
||||
return "$status"
|
||||
}
|
||||
|
||||
trap cleanup_stage EXIT
|
||||
stage=$(sudo mktemp "$authfile.new.XXXXXX")
|
||||
|
||||
if ! safe_stage_path "$stage" || [[ ! -f $stage || -L $stage ]]; then
|
||||
echo -e "\e[31m\nCould not create a safe staging file beside $authfile.\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if pamu2fcfg | sudo tee "$stage" >/dev/null && [[ -s $stage ]]; then
|
||||
sudo chmod 644 "$stage"
|
||||
sudo mv -Tf "$stage" "$authfile"
|
||||
stage=""
|
||||
trap - EXIT
|
||||
if pamu2fcfg >/tmp/fido2; then
|
||||
sudo mv /tmp/fido2 /etc/fido2/fido2
|
||||
echo -e "\e[32mFIDO2 device registered successfully!\e[0m"
|
||||
else
|
||||
echo -e "\e[31m\nFIDO2 registration failed. Please try again.\e[0m"
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
# 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
|
||||
if id -nG "$USER" 2>/dev/null | grep -qw docker; 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
|
||||
@@ -30,20 +28,14 @@ echo ""
|
||||
|
||||
if gum confirm "Enable sudoless Docker? This gives anything running as you passwordless root."; then
|
||||
sudo usermod -aG docker "$USER"
|
||||
# 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.
|
||||
# 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).
|
||||
omarchy-state set reboot-required
|
||||
echo ""
|
||||
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
|
||||
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"
|
||||
else
|
||||
echo "Aborted. No changes made. Docker access still goes through a prompt."
|
||||
fi
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/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
|
||||
@@ -16,10 +16,14 @@ if [[ -z $REPO_URL ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Refuse a URL that names a git option or a transport helper before cloning. The
|
||||
# check is shared with omarchy-plugin-add and explains itself; a missing checker
|
||||
# leaves this non-zero, which refuses the URL rather than cloning it.
|
||||
omarchy-git-url-check "$REPO_URL" || exit 1
|
||||
# git reads a leading dash as an option, and `<helper>::<address>` as a remote
|
||||
# helper to run. The helper name is a bare word at the very start, which is what
|
||||
# this matches; an scp-style IPv6 host such as git@[2001:db8::1]:org/repo.git
|
||||
# carries `::` too and must still clone.
|
||||
if [[ $REPO_URL == -* || $REPO_URL =~ ^[A-Za-z0-9][A-Za-z0-9+.-]*:: ]]; then
|
||||
echo "Error: '$REPO_URL' names a git option or transport helper, not a repository."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
THEMES_DIR="$HOME/.config/omarchy/themes"
|
||||
|
||||
|
||||
@@ -7,70 +7,44 @@
|
||||
KIND="${1:-}"
|
||||
ACTION="${2:-toggle}"
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy-toggle-input-device <touchpad|touchscreen> [on|off|toggle]" >&2
|
||||
}
|
||||
|
||||
case "$KIND" in
|
||||
touchpad) LABEL="Touchpad" ICON="touchpad" ;;
|
||||
touchscreen) LABEL="Touchscreen" ICON="touch" ;;
|
||||
*)
|
||||
usage
|
||||
echo "Usage: omarchy-toggle-input-device <touchpad|touchscreen> [on|off|toggle]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# The persisted disable is the device name stored as plain data; on every
|
||||
# reload default/hypr/disabled-input-device.lua reads it back and disables the
|
||||
# device. Names come from USB descriptors and must not be interpolated into
|
||||
# shell or Lua. The path is hardcoded to ~/.local/state like the sibling
|
||||
# toggle tools, so it keeps working when XDG_STATE_HOME diverges.
|
||||
NAME_FILE="$HOME/.local/state/omarchy/toggles/hypr/$KIND-disabled-name"
|
||||
# Hyprland sources this directory on reload, so the disabled state survives restarts
|
||||
STATE_FILE="$HOME/.local/state/omarchy/toggles/hypr/$KIND-disabled.lua"
|
||||
|
||||
device="$("omarchy-hw-$KIND")"
|
||||
|
||||
require_device() {
|
||||
if [[ -z $device ]]; then
|
||||
echo "No $KIND device found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $device == *[[:cntrl:]]* ]]; then
|
||||
echo "Invalid $KIND device name" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
apply_device() {
|
||||
local enabled=$1
|
||||
local quoted=${device//\\/\\\\}
|
||||
quoted=${quoted//\"/\\\"}
|
||||
hyprctl eval "hl.device({ name = \"$quoted\", enabled = $enabled })" >/dev/null
|
||||
}
|
||||
if [[ -z $device ]]; then
|
||||
echo "No $KIND device found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
enable() {
|
||||
# Clear the persisted state before requiring a usable device, so a device
|
||||
# that stops reporting a valid name can never wedge the disable in place.
|
||||
rm -f "$NAME_FILE"
|
||||
require_device
|
||||
apply_device true
|
||||
hyprctl eval "hl.device({ name = \"$device\", enabled = true })" >/dev/null
|
||||
rm -f "$STATE_FILE"
|
||||
omarchy-osd -i "$ICON" -m "$LABEL enabled"
|
||||
}
|
||||
|
||||
disable() {
|
||||
require_device
|
||||
apply_device false
|
||||
mkdir -p "$(dirname "$NAME_FILE")"
|
||||
printf '%s\n' "$device" >"$NAME_FILE"
|
||||
hyprctl eval "hl.device({ name = \"$device\", enabled = false })" >/dev/null
|
||||
mkdir -p "$(dirname "$STATE_FILE")"
|
||||
printf 'hl.device({ name = "%s", enabled = false })\n' "$device" >"$STATE_FILE"
|
||||
omarchy-osd -i "$ICON" -m "$LABEL disabled"
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
on) enable ;;
|
||||
off) disable ;;
|
||||
toggle) if [[ -f $NAME_FILE ]]; then enable; else disable; fi ;;
|
||||
toggle) if [[ -f $STATE_FILE ]]; then enable; else disable; fi ;;
|
||||
*)
|
||||
usage
|
||||
echo "Usage: omarchy-toggle-input-device <touchpad|touchscreen> [on|off|toggle]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -13,18 +13,6 @@ safe_icon_name() {
|
||||
| sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//'
|
||||
}
|
||||
|
||||
require_plain_name() {
|
||||
# The name becomes a filename. A slash would turn it into directory levels, so
|
||||
# the launcher lands somewhere omarchy-webapp-remove cannot address and the app
|
||||
# is stuck in the launcher; a leading ../ leaves the applications directory
|
||||
# altogether. Refuse rather than silently renaming what the user typed -- most
|
||||
# often it is a URL entered in the name field.
|
||||
if [[ $1 == */* ]]; then
|
||||
echo "App name cannot contain '/': $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
icon_name_from_ref() {
|
||||
local ref="$1"
|
||||
local name
|
||||
@@ -80,7 +68,6 @@ fetch_site_icon() {
|
||||
if (( $# < 3 )); then
|
||||
echo -e "\e[32mLet's create a new web app you can start with the app launcher.\n\e[0m"
|
||||
APP_NAME=$(gum input --prompt "Name> " --placeholder "My favorite web app")
|
||||
require_plain_name "$APP_NAME"
|
||||
APP_URL=$(gum input --prompt "URL> " --placeholder "https://example.com")
|
||||
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
|
||||
APP_URL="https://$APP_URL"
|
||||
@@ -117,8 +104,6 @@ if [[ -z $APP_NAME || -z $APP_URL ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_plain_name "$APP_NAME"
|
||||
|
||||
if [[ -z $ICON_REF ]]; then
|
||||
ICON_VALUE=$(safe_icon_name "$APP_NAME")
|
||||
mkdir -p "$ICON_DIR"
|
||||
@@ -147,9 +132,8 @@ fi
|
||||
EXEC_COMMAND="${CUSTOM_EXEC:-omarchy-launch-webapp $APP_URL}"
|
||||
|
||||
# Create application .desktop file
|
||||
DESKTOP_DIR="$HOME/.local/share/applications"
|
||||
DESKTOP_FILE="$DESKTOP_DIR/$APP_NAME.desktop"
|
||||
mkdir -p "$DESKTOP_DIR"
|
||||
DESKTOP_FILE="$HOME/.local/share/applications/$APP_NAME.desktop"
|
||||
mkdir -p "$(dirname "$DESKTOP_FILE")"
|
||||
|
||||
cat >"$DESKTOP_FILE" <<EOF
|
||||
[Desktop Entry]
|
||||
|
||||
@@ -9,31 +9,14 @@ ICON_DIR="$HOME/.local/share/icons/hicolor/256x256/apps"
|
||||
OLD_ICON_DIR="$HOME/.local/share/applications/icons"
|
||||
DESKTOP_DIR="$HOME/.local/share/applications/"
|
||||
|
||||
# Always index the launchers, so removal deletes the file that was found rather
|
||||
# than a path rebuilt from the displayed name. Installs predating the name
|
||||
# validation could nest the launcher inside directories, and those are exactly
|
||||
# the ones a reconstructed path cannot reach.
|
||||
WEB_APP_PATHS=()
|
||||
while IFS= read -r -d '' file; do
|
||||
if grep -q '^Exec=.*\(omarchy-launch-webapp\|omarchy-webapp-handler\).*' "$file"; then
|
||||
WEB_APPS+=("$(basename "${file%.desktop}")")
|
||||
WEB_APP_PATHS+=("$file")
|
||||
fi
|
||||
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0 2>/dev/null)
|
||||
|
||||
# The launcher matching a chosen name, or empty when nothing was indexed under
|
||||
# it (an app removed between the scan and the pick, say).
|
||||
path_for_web_app() {
|
||||
local wanted="$1" i
|
||||
for i in "${!WEB_APPS[@]}"; do
|
||||
if [[ ${WEB_APPS[$i]} == "$wanted" ]]; then
|
||||
printf '%s\n' "${WEB_APP_PATHS[$i]}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if (( $# == 0 )); then
|
||||
# Find all web apps
|
||||
while IFS= read -r -d '' file; do
|
||||
if grep -q '^Exec=.*\(omarchy-launch-webapp\|omarchy-webapp-handler\).*' "$file"; then
|
||||
WEB_APPS+=("$(basename "${file%.desktop}")")
|
||||
fi
|
||||
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0)
|
||||
|
||||
if ((${#WEB_APPS[@]})); then
|
||||
mapfile -t SORTED_WEB_APPS < <(printf '%s\n' "${WEB_APPS[@]}" | sort)
|
||||
APP_NAME=$(omarchy-menu-select "Select web app to remove" "${SORTED_WEB_APPS[@]}" -- --width 520 --maxheight 520)
|
||||
@@ -51,8 +34,7 @@ if [[ -z $APP_NAME ]]; then
|
||||
fi
|
||||
|
||||
icon_name=$(printf '%s\n' "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//')
|
||||
desktop_file=$(path_for_web_app "$APP_NAME")
|
||||
rm -f "${desktop_file:-$DESKTOP_DIR/$APP_NAME.desktop}"
|
||||
rm -f "$DESKTOP_DIR/$APP_NAME.desktop"
|
||||
rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png"
|
||||
|
||||
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then
|
||||
|
||||
@@ -31,11 +31,8 @@ CONTAINER="omarchy-windows"
|
||||
|
||||
# --- privilege helpers -------------------------------------------------------
|
||||
|
||||
# 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; }
|
||||
# True when the user can reach the Docker socket directly (sudoless Docker on).
|
||||
in_docker_group() { id -nG 2>/dev/null | grep -qw 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
|
||||
@@ -67,7 +64,7 @@ priv_target() {
|
||||
priv() {
|
||||
local action="$1"
|
||||
shift
|
||||
if [[ $action != write_compose ]] && ! docker_needs_sudo; then
|
||||
if [[ $action != write_compose ]] && in_docker_group; then
|
||||
"__priv_$action" "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# OmarchyCN command center: routes to omarchy cn <command>
|
||||
|
||||
set -o pipefail
|
||||
|
||||
OMARCHY_BIN_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
exec "$OMARCHY_BIN_DIR/omarchy" cn "$@"
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"_verified": "2026-08-24, AUR RPC 实证在维护且未 out-of-date",
|
||||
"apps": {
|
||||
"wechat": { "name": "微信", "source": "aur", "package": "wechat-universal-bwrap", "license": "proprietary" },
|
||||
"qq": { "name": "QQ", "source": "aur", "package": "linuxqq", "license": "proprietary" },
|
||||
"feishu": { "name": "飞书", "source": "aur", "package": "feishu-bin", "license": "proprietary" },
|
||||
"dingtalk": { "name": "钉钉", "source": "aur", "package": "dingtalk-bin", "license": "proprietary" },
|
||||
"wemeet": { "name": "腾讯会议", "source": "aur", "package": "wemeet-bin", "license": "proprietary" },
|
||||
"wps": { "name": "WPS Office", "source": "aur", "package": "wps-office-cn", "license": "proprietary" },
|
||||
"tencent-docs": { "name": "腾讯文档", "source": "webapp", "url": "https://docs.qq.com", "icon": "https://docs.qq.com/favicon.ico", "license": "web" },
|
||||
"yuque": { "name": "语雀", "source": "webapp", "url": "https://www.yuque.com/dashboard", "icon": "https://www.yuque.com/favicon.ico", "license": "web" },
|
||||
"shimo": { "name": "石墨文档", "source": "webapp", "url": "https://shimo.im/desktop", "icon": "https://shimo.im/favicon.ico", "license": "web" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"npm": {
|
||||
"china": "https://registry.npmmirror.com",
|
||||
"official": "https://registry.npmjs.org/"
|
||||
},
|
||||
"pip": {
|
||||
"china": "https://pypi.tuna.tsinghua.edu.cn/simple",
|
||||
"official": "https://pypi.org/simple"
|
||||
},
|
||||
"cargo": {
|
||||
"china": "sparse+https://rsproxy.cn/index/",
|
||||
"official": ""
|
||||
},
|
||||
"go": {
|
||||
"china": "https://goproxy.cn,direct",
|
||||
"official": "https://proxy.golang.org,direct"
|
||||
},
|
||||
"gem": {
|
||||
"china": "https://mirrors.tuna.tsinghua.edu.cn/rubygems/",
|
||||
"official": "https://rubygems.org/"
|
||||
},
|
||||
"docker": {
|
||||
"china": "https://docker.m.daocloud.io",
|
||||
"official": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[Groups/0]
|
||||
Name=Default
|
||||
Default Layout=us
|
||||
DefaultIM=rime
|
||||
|
||||
[Groups/0/Items/0]
|
||||
Name=keyboard-us
|
||||
Layout=
|
||||
|
||||
[Groups/0/Items/1]
|
||||
Name=rime
|
||||
Layout=
|
||||
|
||||
[GroupOrder]
|
||||
0=Default
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- 日文/韩文标签内容保持原生字形(仅约束 generic 请求,避免波及未标签路径) -->
|
||||
<match target="pattern">
|
||||
<test name="lang" compare="contains"><string>ja</string></test>
|
||||
<test name="family"><string>sans-serif</string></test>
|
||||
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK JP</string></edit>
|
||||
</match>
|
||||
<match target="pattern">
|
||||
<test name="lang" compare="contains"><string>ko</string></test>
|
||||
<test name="family"><string>sans-serif</string></test>
|
||||
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK KR</string></edit>
|
||||
</match>
|
||||
|
||||
<!-- 简体中文内容强制简体字形 -->
|
||||
<match target="pattern">
|
||||
<test name="lang" compare="contains"><string>zh-cn</string></test>
|
||||
<test name="family"><string>sans-serif</string></test>
|
||||
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK SC</string></edit>
|
||||
</match>
|
||||
<match target="pattern">
|
||||
<test name="lang" compare="contains"><string>zh-cn</string></test>
|
||||
<test name="family"><string>serif</string></test>
|
||||
<edit name="family" mode="prepend" binding="strong"><string>Noto Serif CJK SC</string></edit>
|
||||
</match>
|
||||
<match target="pattern">
|
||||
<test name="lang" compare="contains"><string>zh-cn</string></test>
|
||||
<test name="family"><string>monospace</string></test>
|
||||
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans Mono CJK SC</string></edit>
|
||||
</match>
|
||||
|
||||
<!-- 无语言标签的任意字体请求:汉字回退到 SC 而非 JP(Chromium/Electron 常见路径) -->
|
||||
<match target="pattern">
|
||||
<edit name="family" mode="append" binding="weak"><string>Noto Sans CJK SC</string></edit>
|
||||
</match>
|
||||
|
||||
<!-- generic 家族展开时 SC 优先于其他 CJK 变体 -->
|
||||
<alias>
|
||||
<family>sans-serif</family>
|
||||
<prefer>
|
||||
<family>Noto Sans</family>
|
||||
<family>Noto Sans CJK SC</family>
|
||||
<family>Noto Color Emoji</family>
|
||||
</prefer>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>serif</family>
|
||||
<prefer>
|
||||
<family>Noto Serif</family>
|
||||
<family>Noto Serif CJK SC</family>
|
||||
<family>Noto Color Emoji</family>
|
||||
</prefer>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>monospace</family>
|
||||
<prefer>
|
||||
<family>Noto Sans Mono CJK SC</family>
|
||||
<family>Noto Color Emoji</family>
|
||||
</prefer>
|
||||
</alias>
|
||||
</fontconfig>
|
||||
@@ -0,0 +1,32 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBGqMzPYBCADOcNGiPxC8AgP1kG1s6obLZi5NzfkVzlqkWH9/7JgSokRr//IO
|
||||
L8L2eMQ5bvEou4Mc+eujPI+5Tm8TwfECkjhKWbEOk09yn9kUXVlc/+iROJXF9z4u
|
||||
Cp6BIiZ5YzoadZysCqSLlYXejp38LGXdi4xyh5SfXII/VD/036MBwvwZkb6qoFsp
|
||||
1AS4BRLcE0BgD2QjL5MIFU+yVblUZl8ss1r9AVNNgAllGzFpYHFt+PknnPDldj/j
|
||||
dv5vihekC1Wy+w46w15vdBM04i6t1h72N3naFfuPkMJFuXo0NLWMNLOeDp+bsej8
|
||||
Kwf31yn6Vz0XLw8wCmLBwUtBaahvE/TqYUJPABEBAAHNDyhBcmNoIFJlZ2lzdHJ5
|
||||
KcLAuwQTAQgAbwWCaozM9gILBwkQvThgSIZ7M7Q1FAAAAAAAHAAQc2FsdEBub3Rh
|
||||
dGlvbnMub3BlbnBncGpzLm9yZwbZDf7U+gBf+H+EPPm1SmYCFQgCFgACGQECmwMC
|
||||
HgEWIQR03PV6zYErJNlZ8Ua9OGBIhnsztAAATuEH/2LQxWP9JXXjloS2HwsD/jBb
|
||||
OX/LPP4inkbUmkxaPx9hzecKPIZttzRoIXK8FxviPxwifQYPRShD5t1UwI1vwTnY
|
||||
BCWs4VUf+vMlcaKrmMhgL4z3Wfi1Cpn7GcEYaCB+9BPbNlVTy4KzQJKXk8RkrOPz
|
||||
+r2YynBH2PynjoqVqVvgAnU1vc5gdRUdZIBQIt8RqpwF/Sc0+WwWf9bdW1VzB0Pc
|
||||
QTvuCaNzQOtSTYgDko9XJSa5VNQJrYuwYg97DmTfx+PYm2dFBTeR8Ut9wWiynItZ
|
||||
4ywSm6qE9w1OuBknUx80jev2LGlNNCD0/xBTWPokLf0ZsgLG9f302raZ+WY78GzO
|
||||
wE0EaozM9gEIAN8JV920AR4+TY1v1FVenZbSq74nCe2UNtEHWf97cwZye8ivUbg2
|
||||
mbDEJp+09asoLWRGwzDvD6rUicqihvTgAOB5cZQU1D9wxXfowfG5aB/czVA0MzPU
|
||||
bj5BXS8lA0pN4zcTamCHnrfoQpCGk+eB48xwBPLfxxVtwttaBtvG2YAPdiwultJu
|
||||
13IX4lsK/LS425NYwzuGw5m7Amy/rzamAUZiymIiG45RQ8y3/1UYRMW4yXOVMrtV
|
||||
fmtaL/hzsG0sGVEyiuseR/ZPqZKZrveYeqzy3fCOyCGsPwWH9rtfDekGBGbJLoiY
|
||||
wwA3ikMifFYPr1A1PMOBR5jAXYV/VG/pQQkAEQEAAcLArAQYAQgAYAWCaozM9gkQ
|
||||
vThgSIZ7M7Q1FAAAAAAAHAAQc2FsdEBub3RhdGlvbnMub3BlbnBncGpzLm9yZyFs
|
||||
5xT4PpfIzf6Y7ke/Br8CmwwWIQR03PV6zYErJNlZ8Ua9OGBIhnsztAAATqIH/26g
|
||||
dx2lJNHIYXlS/Ld8JXCC42IctWPeESegQPmJp96GbvF4Z7dVB0e+kqy7YgNYY8dv
|
||||
ibc7GicmQrAtsNDmm+fB1nmd316HxrtiVsBKgeNWTh7EN8cH79lNB4IqwWf6zkny
|
||||
1A1Ntbtqv0tERg/fXEuY6y+9f67JBM5P20iWRij3NNODPHpy5Vr7Ftm7pQ7FgDSj
|
||||
tntrOKMvAwKsWA+avGt55eQe9kbbFhbOqQEXfiqIgIQki0yg8o5VrIz5ZEQm7AXU
|
||||
fYr6IxJPrD7KhBeXO2Z0/TdKjZ5c1kpWEC2W0x8TUH9s2v5q4UeTlyHg5fyFzte6
|
||||
oAEZesYWyQ10m7ewZTg=
|
||||
=9OK8
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -0,0 +1,16 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mDMEaozDJhYJKwYBBAHaRw8BAQdA3l2d/6gj2z96cit0NGYg419BmDDFH6SYhkzL
|
||||
3OMVCem0PU9tYXJjaHlDTiBSZWxlYXNlIFNpZ25pbmcgS2V5IDx6aGFuZ3lhbmdo
|
||||
YWhhMDQwN0BvdXRsb29rLmNvbT6ImQQTFgoAQRYhBARJDwZfat0mKnJDUG7fe4YD
|
||||
tdJHBQJqjMMmAhsBBQkDwmcABQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJ
|
||||
EG7fe4YDtdJHuSMBAI3/samJVmhfdr/HYS8y266Xq82tof9lL9izImd/YibkAQCQ
|
||||
Fjw97J4LPnPQEm93j/3kmHC+EGTQu3EDbXEoI/AUC7gzBGqMwyYWCSsGAQQB2kcP
|
||||
AQEHQCRBOFrQ9UQVtv3XawZIMm93j3IV45K8kk5AGLg+JnMUiPUEGBYKACYWIQQE
|
||||
SQ8GX2rdJipyQ1Bu33uGA7XSRwUCaozDJgIbAgUJA8JnAACBCRBu33uGA7XSR3Yg
|
||||
BBkWCgAdFiEEUarKC/SJIll7SA5+IRubguF8+2cFAmqMwyYACgkQIRubguF8+2cP
|
||||
FQD6A6Xgt7L5bXR/kSJXvDHEgjsQG5xfAEEaZ8/VZ0clWxwA/ikOgsx8VQkZhwrJ
|
||||
Hb/3iG5mb138yHf3Ug00N1GwL6gOHn0A/3OSdXKvdmQhMEUf8TpZNf/ouRal2NsI
|
||||
BB1FBGxZWCYGAQDJRR2W0FEYbEuzUj9XGCHb8O9TjabNN0QduQj78ZNNAQ==
|
||||
=Awb4
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
# shellcheck shell=bash
|
||||
# Sourced helpers for omarchy-cn-ai-* commands
|
||||
|
||||
CN_AI_PROVIDERS="$OMARCHY_PATH/cn/registry/ai-providers.json"
|
||||
CN_AI_HARNESSES="$OMARCHY_PATH/cn/registry/ai-harnesses.json"
|
||||
CN_AI_COMPAT="$OMARCHY_PATH/cn/registry/ai-compatibility.json"
|
||||
CN_AI_PROFILE_DIR="$HOME/.config/omarchycn/ai/profiles"
|
||||
CN_AI_CURRENT="$HOME/.config/omarchycn/ai/current"
|
||||
|
||||
cn_ai_provider_ids() { jq -r '.providers | keys[]' "$CN_AI_PROVIDERS"; }
|
||||
cn_ai_harness_ids() { jq -r '.harnesses | keys[]' "$CN_AI_HARNESSES"; }
|
||||
|
||||
cn_ai_endpoint() {
|
||||
jq -re --arg p "$1" --arg proto "$2" '.providers[$p].endpoints[$proto]' "$CN_AI_PROVIDERS"
|
||||
}
|
||||
|
||||
cn_ai_models() {
|
||||
jq -r --arg p "$1" '.providers[$p].models[].id' "$CN_AI_PROVIDERS"
|
||||
}
|
||||
|
||||
cn_ai_model_by_alias() {
|
||||
jq -re --arg p "$1" --arg a "$2" \
|
||||
'.providers[$p].models[] | select(.aliases // [] | index($a)) | .id' "$CN_AI_PROVIDERS"
|
||||
}
|
||||
|
||||
cn_ai_fast_model() {
|
||||
cn_ai_model_by_alias "$1" fast 2>/dev/null || cn_ai_model_by_alias "$1" default-coding
|
||||
}
|
||||
|
||||
cn_ai_harness_field() {
|
||||
jq -re --arg h "$1" --arg f "$2" '.harnesses[$h][$f]' "$CN_AI_HARNESSES"
|
||||
}
|
||||
|
||||
cn_ai_combo_level() {
|
||||
jq -r --arg h "$1" --arg p "$2" '.combos[$h][$p].level // "unsupported"' "$CN_AI_COMPAT"
|
||||
}
|
||||
|
||||
# Combo-specific model allowlist; empty output means no restriction
|
||||
cn_ai_combo_models() {
|
||||
jq -r --arg h "$1" --arg p "$2" '.combos[$h][$p].models // [] | .[]' "$CN_AI_COMPAT"
|
||||
}
|
||||
|
||||
cn_ai_model_protocol() {
|
||||
jq -r --arg p "$1" --arg m "$2" \
|
||||
'.providers[$p].models[] | select(.id == $m) | .protocol // ""' "$CN_AI_PROVIDERS"
|
||||
}
|
||||
|
||||
# Profile files are flat key="value" lines
|
||||
cn_ai_profile_field() {
|
||||
local file="$CN_AI_PROFILE_DIR/$1.toml" key="$2"
|
||||
grep -E "^$key = " "$file" | head -1 | cut -d\" -f2
|
||||
}
|
||||
|
||||
cn_ai_current_profile() {
|
||||
if [[ ! -f $CN_AI_CURRENT ]]; then
|
||||
echo "No default AI profile set (run: omarchycn ai profile use <name>)" >&2
|
||||
return 1
|
||||
fi
|
||||
cat "$CN_AI_CURRENT"
|
||||
}
|
||||
|
||||
# Print export statements for the profile's harness x provider combo.
|
||||
# Secrets are resolved at launch time only; nothing is written to disk.
|
||||
cn_ai_render_env() {
|
||||
local harness="$1" provider="$2" model="$3" key="$4"
|
||||
local base fast
|
||||
|
||||
case "$harness" in
|
||||
claude-code)
|
||||
base=$(cn_ai_endpoint "$provider" anthropic)
|
||||
fast=$(cn_ai_fast_model "$provider")
|
||||
printf 'export ANTHROPIC_BASE_URL=%q\n' "$base"
|
||||
# Docs vary between AUTH_TOKEN (deepseek/zai) and API_KEY (kimi): set both
|
||||
printf 'export ANTHROPIC_AUTH_TOKEN=%q\n' "$key"
|
||||
printf 'export ANTHROPIC_API_KEY=%q\n' "$key"
|
||||
printf 'export ANTHROPIC_MODEL=%q\n' "$model"
|
||||
printf 'export ANTHROPIC_DEFAULT_SONNET_MODEL=%q\n' "$model"
|
||||
printf 'export ANTHROPIC_DEFAULT_OPUS_MODEL=%q\n' "$model"
|
||||
printf 'export ANTHROPIC_DEFAULT_HAIKU_MODEL=%q\n' "$fast"
|
||||
;;
|
||||
opencode)
|
||||
if [[ $provider != "deepseek" ]]; then
|
||||
echo "opencode adapter renders only the deepseek combo (native provider)" >&2
|
||||
return 1
|
||||
fi
|
||||
printf 'export DEEPSEEK_API_KEY=%q\n' "$key"
|
||||
;;
|
||||
*)
|
||||
echo "No env renderer for harness: $harness" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Write ~/.codex/config.toml + models.json per DeepSeek's official codex
|
||||
# integration contract (mirrors cdn.deepseek.com codex-deepseek-setup script)
|
||||
cn_ai_render_codex_config() {
|
||||
local provider="$1" model="$2" key="$3"
|
||||
local base cfg="$HOME/.codex/config.toml"
|
||||
|
||||
if [[ $provider != "deepseek" ]]; then
|
||||
echo "codex adapter renders only the deepseek combo (official integration)" >&2
|
||||
return 1
|
||||
fi
|
||||
base="https://api.deepseek.com/"
|
||||
|
||||
mkdir -p "${cfg%/*}"
|
||||
touch "$cfg"
|
||||
|
||||
# Strip our old blocks first, then check for an unmanaged provider table
|
||||
rm -f "$cfg.omarchycn-tmp" "$cfg.omarchycn-new"
|
||||
(
|
||||
umask 077
|
||||
awk '
|
||||
/^# OmarchyCN ai (model|provider) begin$/ { skip = 1; next }
|
||||
/^# OmarchyCN ai (model|provider) end$/ { skip = 0; next }
|
||||
skip { next }
|
||||
/^[[:space:]]*\[/ { in_section = 1 }
|
||||
!in_section && /^[[:space:]]*(model|model_provider|model_catalog_json|preferred_auth_method|forced_login_method|model_reasoning_effort)[[:space:]]*=/ { next }
|
||||
{ print }
|
||||
' "$cfg" > "$cfg.omarchycn-tmp"
|
||||
)
|
||||
|
||||
if grep -qE "^[[:space:]]*\[[[:space:]]*model_providers[[:space:]]*\.[[:space:]]*\"?$provider\"?[[:space:]]*\]" "$cfg.omarchycn-tmp"; then
|
||||
rm -f "$cfg.omarchycn-tmp"
|
||||
echo "codex: $cfg 已有非托管的 [model_providers.$provider],请手动清理后重试" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -s $cfg ]]; then
|
||||
cp "$cfg" "$cfg.omarchycn-bak-$(date +%Y%m%d-%H%M%S)"
|
||||
fi
|
||||
|
||||
cp "$OMARCHY_PATH/cn/registry/codex-deepseek-models.json" "$HOME/.codex/models.json"
|
||||
|
||||
(
|
||||
umask 077
|
||||
{
|
||||
echo "# OmarchyCN ai model begin"
|
||||
echo "model = \"$model\""
|
||||
echo "model_provider = \"$provider\""
|
||||
echo "model_catalog_json = \"~/.codex/models.json\""
|
||||
echo "preferred_auth_method = \"apikey\""
|
||||
echo "forced_login_method = \"api\""
|
||||
echo "model_reasoning_effort = \"high\""
|
||||
echo "# OmarchyCN ai model end"
|
||||
cat "$cfg.omarchycn-tmp"
|
||||
echo "# OmarchyCN ai provider begin"
|
||||
echo "[model_providers.$provider]"
|
||||
echo "name = \"$provider\""
|
||||
echo "base_url = \"$base\""
|
||||
echo "wire_api = \"responses\""
|
||||
echo "experimental_bearer_token = \"$key\""
|
||||
echo "# OmarchyCN ai provider end"
|
||||
} > "$cfg.omarchycn-new"
|
||||
)
|
||||
mv "$cfg.omarchycn-new" "$cfg"
|
||||
rm -f "$cfg.omarchycn-tmp"
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
# shellcheck shell=bash
|
||||
# Sourced helpers for omarchy-cn-dev-mirror-* commands
|
||||
|
||||
CN_DEV_MIRRORS_JSON="$OMARCHY_PATH/cn/dev-mirrors.json"
|
||||
# shellcheck disable=SC2034 # consumed by sourcing commands
|
||||
CN_DM_TARGETS=(npm pip cargo go gem docker)
|
||||
CN_DM_BACKUP_ROOT="$HOME/.local/state/omarchycn/backups/dev-mirror"
|
||||
|
||||
cn_dm_url() {
|
||||
jq -re --arg t "$1" --arg p "$2" '.[$t][$p]' "$CN_DEV_MIRRORS_JSON"
|
||||
}
|
||||
|
||||
cn_dm_backup() {
|
||||
local file="$1"
|
||||
local stamp="$2"
|
||||
|
||||
if [[ -f $file ]]; then
|
||||
mkdir -p "$CN_DM_BACKUP_ROOT/$stamp"
|
||||
cp "$file" "$CN_DM_BACKUP_ROOT/$stamp/${file##*/}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Replace-or-append one key=value style line matched by a regex
|
||||
cn_dm_set_line() {
|
||||
local file="$1" match="$2" line="$3"
|
||||
|
||||
mkdir -p "${file%/*}"
|
||||
touch "$file"
|
||||
grep -vE "$match" "$file" > "$file.omarchycn-tmp" || true
|
||||
printf '%s\n' "$line" >> "$file.omarchycn-tmp"
|
||||
mv "$file.omarchycn-tmp" "$file"
|
||||
}
|
||||
|
||||
cn_dm_get_npm() { grep -sE '^registry=' "$HOME/.npmrc" | cut -d= -f2- || true; }
|
||||
cn_dm_set_npm() { cn_dm_set_line "$HOME/.npmrc" '^registry=' "registry=$1"; }
|
||||
|
||||
cn_dm_get_pip() {
|
||||
python3 - <<'EOF'
|
||||
import configparser, os
|
||||
c = configparser.ConfigParser()
|
||||
c.read(os.path.expanduser("~/.config/pip/pip.conf"))
|
||||
print(c.get("global", "index-url", fallback=""))
|
||||
EOF
|
||||
}
|
||||
|
||||
cn_dm_set_pip() {
|
||||
CN_DM_PIP_URL="$1" python3 - <<'EOF'
|
||||
import configparser, os
|
||||
path = os.path.expanduser("~/.config/pip/pip.conf")
|
||||
c = configparser.ConfigParser()
|
||||
c.read(path)
|
||||
if not c.has_section("global"):
|
||||
c.add_section("global")
|
||||
c.set("global", "index-url", os.environ["CN_DM_PIP_URL"])
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w") as f:
|
||||
c.write(f)
|
||||
EOF
|
||||
}
|
||||
|
||||
cn_dm_get_cargo() {
|
||||
local f="$HOME/.cargo/config.toml"
|
||||
grep -sA1 '^\[source\.omarchycn\]' "$f" | grep -sE '^registry' | cut -d\" -f2 || true
|
||||
}
|
||||
|
||||
cn_dm_set_cargo() {
|
||||
local url="$1" f="$HOME/.cargo/config.toml"
|
||||
|
||||
mkdir -p "${f%/*}"
|
||||
touch "$f"
|
||||
sed -i '/^# OmarchyCN dev-mirror begin$/,/^# OmarchyCN dev-mirror end$/d' "$f"
|
||||
if [[ -z $url ]]; then
|
||||
return 0
|
||||
fi
|
||||
if grep -q '^\[source\.crates-io\]' "$f"; then
|
||||
echo "cargo: $f already defines [source.crates-io]; edit it manually" >&2
|
||||
return 1
|
||||
fi
|
||||
cat >> "$f" <<EOF
|
||||
# OmarchyCN dev-mirror begin
|
||||
[source.crates-io]
|
||||
replace-with = "omarchycn"
|
||||
|
||||
[source.omarchycn]
|
||||
registry = "$url"
|
||||
# OmarchyCN dev-mirror end
|
||||
EOF
|
||||
}
|
||||
|
||||
cn_dm_get_go() { grep -sE '^GOPROXY=' "$HOME/.config/go/env" | cut -d= -f2- || true; }
|
||||
cn_dm_set_go() { cn_dm_set_line "$HOME/.config/go/env" '^GOPROXY=' "GOPROXY=$1"; }
|
||||
|
||||
cn_dm_get_gem() { grep -sE '^- ' "$HOME/.gemrc" | head -1 | sed 's/^- //' || true; }
|
||||
|
||||
cn_dm_set_gem() {
|
||||
local f="$HOME/.gemrc"
|
||||
|
||||
if [[ -s $f ]] && ! grep -q '^# OmarchyCN dev-mirror managed$' "$f"; then
|
||||
echo "gem: $f has existing user content; set :sources: manually" >&2
|
||||
return 1
|
||||
fi
|
||||
cat > "$f" <<EOF
|
||||
# OmarchyCN dev-mirror managed
|
||||
---
|
||||
:sources:
|
||||
- $1
|
||||
EOF
|
||||
}
|
||||
|
||||
cn_dm_get_docker() {
|
||||
jq -re '."registry-mirrors"[0] // ""' /etc/docker/daemon.json 2>/dev/null || true
|
||||
}
|
||||
|
||||
cn_dm_set_docker() {
|
||||
local url="$1" current="{}"
|
||||
|
||||
sudo mkdir -p /etc/docker
|
||||
if sudo test -f /etc/docker/daemon.json; then
|
||||
current=$(sudo cat /etc/docker/daemon.json)
|
||||
fi
|
||||
if [[ -n $url ]]; then
|
||||
jq --arg u "$url" '."registry-mirrors" = [$u]' <<<"$current" | sudo tee /etc/docker/daemon.json > /dev/null
|
||||
else
|
||||
jq 'del(."registry-mirrors")' <<<"$current" | sudo tee /etc/docker/daemon.json > /dev/null
|
||||
fi
|
||||
echo "docker: restart the docker daemon to take effect"
|
||||
}
|
||||
|
||||
cn_dm_config_file() {
|
||||
case "$1" in
|
||||
npm) echo "$HOME/.npmrc" ;;
|
||||
pip) echo "$HOME/.config/pip/pip.conf" ;;
|
||||
cargo) echo "$HOME/.cargo/config.toml" ;;
|
||||
go) echo "$HOME/.config/go/env" ;;
|
||||
gem) echo "$HOME/.gemrc" ;;
|
||||
docker) echo "/etc/docker/daemon.json" ;;
|
||||
esac
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
# shellcheck shell=bash
|
||||
# Sourced helpers for omarchy-cn-mirror-* commands
|
||||
|
||||
CN_MIRRORS_JSON="$OMARCHY_PATH/cn/mirrors.json"
|
||||
CN_MIRRORLIST="/etc/pacman.d/mirrorlist"
|
||||
CN_PROFILE_FILE="$HOME/.config/omarchycn/mirror-profile"
|
||||
|
||||
cn_mirror_ids() {
|
||||
jq -r '.mirrors[].id' "$CN_MIRRORS_JSON"
|
||||
}
|
||||
|
||||
cn_mirror_url() {
|
||||
jq -r --arg id "$1" '.mirrors[] | select(.id == $id) | .url' "$CN_MIRRORS_JSON"
|
||||
}
|
||||
|
||||
cn_mirror_name() {
|
||||
jq -r --arg id "$1" '.mirrors[] | select(.id == $id) | .name' "$CN_MIRRORS_JSON"
|
||||
}
|
||||
|
||||
cn_mirror_ids_by_region() {
|
||||
jq -r --arg r "$1" '.mirrors[] | select(.region == $r) | .id' "$CN_MIRRORS_JSON"
|
||||
}
|
||||
|
||||
# Probe one mirror: prints "<speed_bytes_s> <ttfb_s> <sync_age_s|stale|unknown>"
|
||||
cn_mirror_probe() {
|
||||
local url="$1"
|
||||
local out speed=0 ttfb=0 lastsync age="unknown" now
|
||||
|
||||
if out=$(curl -sSf -o /dev/null -m 12 --connect-timeout 5 \
|
||||
-w '%{speed_download} %{time_starttransfer}' \
|
||||
"$url/core/os/x86_64/core.db" 2>/dev/null); then
|
||||
read -r speed ttfb <<<"$out"
|
||||
speed="${speed%%.*}"
|
||||
fi
|
||||
|
||||
if lastsync=$(curl -sSf -m 5 --connect-timeout 5 "$url/lastsync" 2>/dev/null); then
|
||||
lastsync=$(tr -cd '0-9' <<<"$lastsync")
|
||||
if [[ -n $lastsync ]]; then
|
||||
now=$(date +%s)
|
||||
age=$((now - lastsync))
|
||||
if (( age > 86400 )); then
|
||||
age="stale"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$speed $ttfb $age"
|
||||
}
|
||||
|
||||
# Rank ids by probe speed, excluding dead (speed 0) and stale mirrors
|
||||
cn_mirror_rank() {
|
||||
local id url speed ttfb age
|
||||
|
||||
for id in "$@"; do
|
||||
url=$(cn_mirror_url "$id")
|
||||
read -r speed ttfb age < <(cn_mirror_probe "$url")
|
||||
(( speed > 0 )) || continue
|
||||
[[ $age == "stale" ]] && continue
|
||||
echo "$speed $id"
|
||||
done | sort -rn | awk '{print $2}'
|
||||
}
|
||||
|
||||
# Write mirrorlist from mirror ids (first = primary), with timestamped backup
|
||||
cn_mirror_write_list() {
|
||||
local profile="$1"
|
||||
shift
|
||||
local stamp id url content=""
|
||||
|
||||
stamp=$(date +%Y%m%d-%H%M%S)
|
||||
content="## OmarchyCN mirrorlist (profile: $profile, generated $stamp)\n"
|
||||
for id in "$@"; do
|
||||
url=$(cn_mirror_url "$id")
|
||||
content+="## $id: $(cn_mirror_name "$id")\nServer = $url/\$repo/os/\$arch\n"
|
||||
done
|
||||
|
||||
sudo cp "$CN_MIRRORLIST" "$CN_MIRRORLIST.omarchycn-bak-$stamp"
|
||||
printf '%b' "$content" | sudo tee "$CN_MIRRORLIST" > /dev/null
|
||||
|
||||
mkdir -p "${CN_PROFILE_FILE%/*}"
|
||||
echo "$profile" > "$CN_PROFILE_FILE"
|
||||
echo "Mirrorlist written ($CN_MIRRORLIST), backup: $CN_MIRRORLIST.omarchycn-bak-$stamp"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"mirrors": [
|
||||
{ "id": "tuna", "name": "清华大学 TUNA", "region": "cn", "url": "https://mirrors.tuna.tsinghua.edu.cn/archlinux" },
|
||||
{ "id": "ustc", "name": "中国科学技术大学", "region": "cn", "url": "https://mirrors.ustc.edu.cn/archlinux" },
|
||||
{ "id": "aliyun", "name": "阿里云", "region": "cn", "url": "https://mirrors.aliyun.com/archlinux" },
|
||||
{ "id": "tencent", "name": "腾讯云", "region": "cn", "url": "https://mirrors.cloud.tencent.com/archlinux" },
|
||||
{ "id": "netease", "name": "网易", "region": "cn", "url": "https://mirrors.163.com/archlinux" },
|
||||
{ "id": "sjtu", "name": "上海交通大学", "region": "cn", "url": "https://mirror.sjtu.edu.cn/archlinux" },
|
||||
{ "id": "geo", "name": "Arch 官方 Geo Mirror", "region": "global", "url": "https://geo.mirror.pkgbuild.com" },
|
||||
{ "id": "worldwide", "name": "Arch 官方 Worldwide", "region": "global", "url": "https://mirror.rackspace.com/archlinux" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"_verified": "2026-08-24. level=stable: adapter 渲染由 test/shell.d/omarchycn-ai-test.sh mock 回归覆盖; level=official-doc: 官方集成文档存在,native CLI 自管配置",
|
||||
"combos": {
|
||||
"claude-code": {
|
||||
"deepseek": {
|
||||
"level": "stable",
|
||||
"doc": "https://api-docs.deepseek.com/quick_start/agent_integrations/claude_code/",
|
||||
"adapter": true
|
||||
},
|
||||
"kimi": {
|
||||
"level": "stable",
|
||||
"doc": "https://www.kimi.com/code/docs/en/third-party-tools/claude-code",
|
||||
"adapter": true
|
||||
},
|
||||
"zai": {
|
||||
"level": "stable",
|
||||
"doc": "https://docs.z.ai/devpack/tool/claude",
|
||||
"adapter": true
|
||||
}
|
||||
},
|
||||
"codex": {
|
||||
"deepseek": {
|
||||
"level": "stable",
|
||||
"doc": "https://api-docs.deepseek.com/quick_start/agent_integrations/codex/",
|
||||
"models": [
|
||||
"deepseek-v4-flash",
|
||||
"deepseek-v4-pro",
|
||||
"deepseek-v4-flash-vision-exp"
|
||||
],
|
||||
"catalog": "codex-deepseek-models.json",
|
||||
"adapter": true
|
||||
}
|
||||
},
|
||||
"opencode": {
|
||||
"deepseek": {
|
||||
"level": "stable",
|
||||
"doc": "https://api-docs.deepseek.com/quick_start/agent_integrations/opencode/",
|
||||
"adapter": true
|
||||
},
|
||||
"zai": {
|
||||
"level": "official-doc",
|
||||
"doc": "https://docs.z.ai/scenario-example/develop-tools/opencode",
|
||||
"adapter": false
|
||||
}
|
||||
},
|
||||
"kimi-code": {
|
||||
"kimi": {
|
||||
"level": "official-doc",
|
||||
"doc": "https://www.kimi.com/code/docs/en/",
|
||||
"adapter": true
|
||||
}
|
||||
},
|
||||
"deep-code": {
|
||||
"deepseek": {
|
||||
"level": "official-doc",
|
||||
"doc": "https://api-docs.deepseek.com/quick_start/agent_integrations/deepcode/",
|
||||
"adapter": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"_verified": "2026-08-24",
|
||||
"harnesses": {
|
||||
"claude-code": {
|
||||
"display_name": "Claude Code",
|
||||
"command": "claude",
|
||||
"install": "omarchy-mise-install claude",
|
||||
"config_method": "env",
|
||||
"protocol": "anthropic"
|
||||
},
|
||||
"codex": {
|
||||
"display_name": "Codex",
|
||||
"command": "codex",
|
||||
"install": "omarchy-mise-install codex",
|
||||
"config_method": "env",
|
||||
"protocol": "openai"
|
||||
},
|
||||
"opencode": {
|
||||
"display_name": "OpenCode",
|
||||
"command": "opencode",
|
||||
"install": "omarchy-mise-install opencode",
|
||||
"config_method": "env",
|
||||
"protocol": "anthropic"
|
||||
},
|
||||
"kimi-code": {
|
||||
"display_name": "Kimi Code CLI",
|
||||
"command": "kimi",
|
||||
"install": "doc:https://www.kimi.com/code/docs/en/",
|
||||
"config_method": "native",
|
||||
"protocol": "native"
|
||||
},
|
||||
"deep-code": {
|
||||
"display_name": "Deep Code",
|
||||
"command": "deepcode",
|
||||
"install": "omarchy-mise-install npm:@vegamo/deepcode-cli deepcode",
|
||||
"config_method": "native",
|
||||
"protocol": "native"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"_verified": "2026-08-24, from official docs (see OmarchyCN PRD §27)",
|
||||
"providers": {
|
||||
"deepseek": {
|
||||
"display_name": "DeepSeek",
|
||||
"auth": ["api_key"],
|
||||
"key_url": "https://platform.deepseek.com/api_keys",
|
||||
"endpoints": {
|
||||
"anthropic": "https://api.deepseek.com/anthropic",
|
||||
"openai": "https://api.deepseek.com"
|
||||
},
|
||||
"models": [
|
||||
{ "id": "deepseek-v4-pro[1m]", "aliases": ["default-coding"], "capabilities": ["tools", "streaming", "reasoning"] },
|
||||
{ "id": "deepseek-v4-flash", "aliases": ["fast"], "capabilities": ["tools", "streaming"] }
|
||||
]
|
||||
},
|
||||
"kimi": {
|
||||
"display_name": "Kimi (Moonshot)",
|
||||
"auth": ["api_key"],
|
||||
"key_url": "https://www.kimi.com/code",
|
||||
"endpoints": {
|
||||
"anthropic": "https://api.kimi.com/coding/",
|
||||
"openai": "https://api.moonshot.cn/v1"
|
||||
},
|
||||
"models": [
|
||||
{ "id": "kimi-for-coding", "aliases": ["default-coding"], "capabilities": ["tools", "streaming", "reasoning"] },
|
||||
{ "id": "kimi-for-coding-highspeed", "aliases": ["fast"], "capabilities": ["tools", "streaming"] },
|
||||
{ "id": "kimi-k2.6", "aliases": ["general"], "capabilities": ["tools", "streaming"], "protocol": "openai" }
|
||||
]
|
||||
},
|
||||
"zai": {
|
||||
"display_name": "Z.AI / GLM",
|
||||
"auth": ["api_key"],
|
||||
"key_url": "https://z.ai/manage-apikey/apikey-list",
|
||||
"endpoints": {
|
||||
"anthropic": "https://api.z.ai/api/anthropic"
|
||||
},
|
||||
"models": [
|
||||
{ "id": "glm-5.2[1m]", "aliases": ["default-coding"], "capabilities": ["tools", "streaming", "reasoning"] },
|
||||
{ "id": "GLM-5.3", "aliases": ["latest"], "capabilities": ["tools", "streaming", "reasoning"] },
|
||||
{ "id": "GLM-4.7", "aliases": ["fast"], "capabilities": ["tools", "streaming"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
2
|
||||
@@ -87,38 +87,7 @@ ambiguous, say so rather than assembling confidence out of guesswork.
|
||||
|
||||
**Leave the system as you found it.** Diagnosis reads; it does not fix, tidy, or
|
||||
reconfigure. The one thing to clean up is your own: delete the core you extracted
|
||||
above, which is a copy of the crashed process's memory. The single change a
|
||||
diagnosis may make is the mute below, and only when the user asks for it.
|
||||
|
||||
## Offer to stop the notifications for this program
|
||||
|
||||
A crash you have explained often keeps happening anyway. Finish by offering to
|
||||
silence notifications for **that one program**, and never run it unprompted. Say
|
||||
how to lift it in the same breath, so it is not a one-way door.
|
||||
|
||||
```bash
|
||||
omarchy-crash-mute '<program>' # silence it
|
||||
omarchy-crash-mute '<program>' off # let it speak again
|
||||
omarchy-crash-mute # list what is muted
|
||||
```
|
||||
|
||||
Pass the `binary:` path from the crash facts, or the `process:` name where no
|
||||
binary was recorded; the command reduces either to the name the watcher keys on.
|
||||
A diagnosis run by hand from `omarchy agent crash <pid>` has neither, so take
|
||||
them from `coredumpctl info`. Prefer the binary: a process name is truncated to
|
||||
15 characters and a basename is not, so muting the truncated form matches
|
||||
nothing, forever, while looking like it worked.
|
||||
|
||||
Quote it. The name is whatever the crashed program's author called a file, and a
|
||||
single quote inside one closes yours and runs the rest as your shell.
|
||||
|
||||
The key is a bare name, so anything run through an interpreter is keyed as the
|
||||
interpreter: muting `python3.13` silences every Python program on the machine.
|
||||
Say so rather than quietly doing it.
|
||||
|
||||
None of this fixes anything, and a mute offered in place of a fix that was within
|
||||
reach is the wrong answer. For every program rather than one, the switch is
|
||||
_Trigger > Toggle > Crash Capture_.
|
||||
above, which is a copy of the crashed process's memory.
|
||||
|
||||
## If it is an Omarchy bug
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
-- Disable a Hyprland input device whose name was stored as data, not Lua.
|
||||
-- Device names come from USB descriptors and must never be loaded as code.
|
||||
|
||||
local paths = require("default.hypr.paths")
|
||||
|
||||
return function(kind)
|
||||
-- Hardcoded to ~/.local/state to match omarchy-toggle-input-device and the
|
||||
-- sibling bash toggle tools, which all write there regardless of
|
||||
-- XDG_STATE_HOME.
|
||||
local file = io.open(paths.home .. "/.local/state/omarchy/toggles/hypr/" .. kind .. "-disabled-name", "r")
|
||||
if not file then
|
||||
return
|
||||
end
|
||||
|
||||
local name = file:read("*l")
|
||||
file:close()
|
||||
|
||||
if name and name ~= "" then
|
||||
hl.device({ name = name, enabled = false })
|
||||
end
|
||||
end
|
||||
+3
-13
@@ -4,19 +4,9 @@
|
||||
|
||||
local home = os.getenv("HOME")
|
||||
|
||||
-- A variable that is set but empty means "unset" (XDG Base Directory spec);
|
||||
-- bash's ${VAR:-fallback} in the sibling tools treats it the same way.
|
||||
local function env_or(name, fallback)
|
||||
local value = os.getenv(name)
|
||||
if value == nil or value == "" then
|
||||
return fallback
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
return {
|
||||
home = home,
|
||||
config_home = env_or("XDG_CONFIG_HOME", home .. "/.config"),
|
||||
state_home = env_or("XDG_STATE_HOME", home .. "/.local/state"),
|
||||
omarchy_path = env_or("OMARCHY_PATH", "/usr/share/omarchy"),
|
||||
config_home = os.getenv("XDG_CONFIG_HOME") or (home .. "/.config"),
|
||||
state_home = os.getenv("XDG_STATE_HOME") or (home .. "/.local/state"),
|
||||
omarchy_path = os.getenv("OMARCHY_PATH") or "/usr/share/omarchy",
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
-- Pass a module prefix for normal package.path modules, e.g.
|
||||
-- require_all.files(paths.omarchy_path .. "/default/hypr/apps", "default.hypr.apps")
|
||||
-- Pass nil as the prefix when the directory itself has been added to package.path.
|
||||
-- Pass options.exclude as a set of base names (without ".lua") to skip; a legacy
|
||||
-- file that must never be loaded as code stays on disk for a migration to remove.
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -14,23 +12,19 @@ local function shell_quote(path)
|
||||
end
|
||||
|
||||
function M.files(dir, module_prefix, options)
|
||||
local exclude = options and options.exclude or {}
|
||||
local handle = io.popen("find " .. shell_quote(dir) .. " -maxdepth 1 -type f -name '*.lua' -printf '%f\\n' 2>/dev/null | sort")
|
||||
if handle then
|
||||
for filename in handle:lines() do
|
||||
local name = filename:gsub("%.lua$", "")
|
||||
if not exclude[name] then
|
||||
local module = name
|
||||
if module_prefix then
|
||||
module = module_prefix .. "." .. module
|
||||
end
|
||||
|
||||
if options and options.reload then
|
||||
package.loaded[module] = nil
|
||||
end
|
||||
|
||||
require(module)
|
||||
local module = filename:gsub("%.lua$", "")
|
||||
if module_prefix then
|
||||
module = module_prefix .. "." .. module
|
||||
end
|
||||
|
||||
if options and options.reload then
|
||||
package.loaded[module] = nil
|
||||
end
|
||||
|
||||
require(module)
|
||||
end
|
||||
handle:close()
|
||||
end
|
||||
|
||||
@@ -4,20 +4,6 @@ local require_all = require("default.hypr.require_all")
|
||||
local toggles_dir = paths.state_home .. "/omarchy/toggles/hypr"
|
||||
package.path = toggles_dir .. "/?.lua;" .. package.path
|
||||
|
||||
-- touchpad-disabled.lua / touchscreen-disabled.lua were generated Lua in older
|
||||
-- versions and could carry an injected USB device name. They must never be loaded
|
||||
-- as code again: exclude them so a not-yet-migrated install cannot execute a
|
||||
-- leftover payload on reload. The migration recovers the name and deletes them.
|
||||
require_all.files(toggles_dir, nil, {
|
||||
reload = true,
|
||||
exclude = {
|
||||
["touchpad-disabled"] = true,
|
||||
["touchscreen-disabled"] = true,
|
||||
},
|
||||
})
|
||||
|
||||
local disabled_input_device = require("default.hypr.disabled-input-device")
|
||||
disabled_input_device("touchpad")
|
||||
disabled_input_device("touchscreen")
|
||||
require_all.files(toggles_dir, nil, { reload = true })
|
||||
|
||||
require("default.hypr.workspace-layouts")
|
||||
|
||||
@@ -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","when":"omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sudoless-docker"},
|
||||
"setup.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","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":"! omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sudoless-docker"},
|
||||
"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.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'"},
|
||||
@@ -365,4 +365,33 @@
|
||||
"update.hardware.trackpad": {"icon":"","label":"Trackpad","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-trackpad"},
|
||||
"update.password.drive": {"icon":"","label":"Drive Encryption","action":"omarchy-launch-floating-terminal-with-presentation omarchy-drive-password"},
|
||||
"update.password.user": {"icon":"","label":"User","action":"omarchy-launch-floating-terminal-with-presentation passwd"},
|
||||
|
||||
// OmarchyCN
|
||||
"omarchycn": {"icon":"","label":"OmarchyCN"},
|
||||
"omarchycn.mirrors": {"icon":"","label":"镜像 Mirrors"},
|
||||
"omarchycn.mirrors.china": {"icon":"","label":"中国镜像 China","checked":"[[ \"$(cat ~/.config/omarchycn/mirror-profile 2>/dev/null)\" == china ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-mirror-apply china'"},
|
||||
"omarchycn.mirrors.official": {"icon":"","label":"官方镜像 Official","checked":"[[ \"$(cat ~/.config/omarchycn/mirror-profile 2>/dev/null)\" == official ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-mirror-apply official'"},
|
||||
"omarchycn.mirrors.benchmark": {"icon":"","label":"测速 Benchmark","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-mirror-benchmark"},
|
||||
"omarchycn.mirrors.dev-china": {"icon":"","label":"开发工具国内源 Dev China","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-dev-mirror-apply china'"},
|
||||
"omarchycn.mirrors.dev-official": {"icon":"","label":"开发工具官方源 Dev Official","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-dev-mirror-apply official'"},
|
||||
"omarchycn.mirrors.restore": {"icon":"","label":"恢复 Restore","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-mirror-restore"},
|
||||
"omarchycn.chinese": {"icon":"","label":"中文环境 Chinese"},
|
||||
"omarchycn.chinese.ime": {"icon":"","label":"输入法 Fcitx5+Rime","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-ime-apply"},
|
||||
"omarchycn.chinese.hotkey-ctrl": {"icon":"","label":"切换键 Ctrl+Space","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-ime-hotkey ctrl-space'"},
|
||||
"omarchycn.chinese.hotkey-super": {"icon":"","label":"切换键 Super+Space","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-ime-hotkey super-space'"},
|
||||
"omarchycn.chinese.fonts": {"icon":"","label":"中文字体 Fonts","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-font-apply"},
|
||||
"omarchycn.chinese.locale": {"icon":"","label":"中文 Locale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-locale-apply"},
|
||||
"omarchycn.display": {"icon":"","label":"显示缩放 Scale"},
|
||||
"omarchycn.display.s10": {"icon":"","label":"1.0","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.0'"},
|
||||
"omarchycn.display.s125": {"icon":"","label":"1.25","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.25'"},
|
||||
"omarchycn.display.s15": {"icon":"","label":"1.5","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.5'"},
|
||||
"omarchycn.display.s16": {"icon":"","label":"1.6","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.6'"},
|
||||
"omarchycn.display.s175": {"icon":"","label":"1.75","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.75'"},
|
||||
"omarchycn.display.s20": {"icon":"","label":"2.0","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 2.0'"},
|
||||
"omarchycn.diagnostics": {"icon":"","label":"诊断 Diagnostics"},
|
||||
"omarchycn.diagnostics.doctor": {"icon":"","label":"System Doctor","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-doctor all'"},
|
||||
"omarchycn.diagnostics.mirror-fix": {"icon":"","label":"镜像修复 Mirror Fix","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-doctor mirror --fix'"},
|
||||
"omarchycn.diagnostics.ime": {"icon":"","label":"输入法状态 IME Status","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-ime-status"},
|
||||
"omarchycn.update": {"icon":"","label":"更新 Update","when":"[[ -f ~/.local/state/omarchycn/overlay-manifest ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-update"},
|
||||
"omarchycn.about": {"icon":"","label":"关于 About","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-status"},
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ Include = /etc/pacman.d/mirrorlist
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[omarchy]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://pkgs.omarchy.org/edge/$arch
|
||||
|
||||
# Repositories for debug symbol packages.
|
||||
|
||||
@@ -26,4 +26,5 @@ Include = /etc/pacman.d/mirrorlist
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[omarchy]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://pkgs.omarchy.org/edge/$arch
|
||||
|
||||
@@ -26,4 +26,5 @@ Include = /etc/pacman.d/mirrorlist
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[omarchy]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://pkgs.omarchy.org/stable/$arch
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# [omarchycn] 软件仓库
|
||||
|
||||
OmarchyCN 的 pacman 仓库托管在 Gitea Arch package registry,每个上传的包由
|
||||
registry 密钥自动签名,数据库同源生成。
|
||||
|
||||
## 接入
|
||||
|
||||
```bash
|
||||
# 1. 导入并信任 registry 公钥(首次)
|
||||
curl -sSf https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/repository.key -o /tmp/omarchycn-reg.key
|
||||
sudo pacman-key --add /tmp/omarchycn-reg.key
|
||||
sudo pacman-key --lsign-key 74DCF57ACD812B24D959F146BD386048867B33B4
|
||||
|
||||
# 2. 添加仓库
|
||||
cat << 'EOF' | sudo tee -a /etc/pacman.conf
|
||||
[omarchycn]
|
||||
SigLevel = Required DatabaseOptional
|
||||
Server = https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchycn/x86_64
|
||||
EOF
|
||||
|
||||
# 3. 安装 keyring(含 registry 与 release 两把公钥及信任配置)
|
||||
sudo pacman -Syu omarchycn-keyring
|
||||
```
|
||||
|
||||
## 密钥
|
||||
|
||||
| 用途 | 指纹 |
|
||||
|---|---|
|
||||
| Registry 包签名(Gitea 自动) | `74DCF57ACD812B24D959F146BD386048867B33B4` |
|
||||
| Release 产物签名(ISO/清单) | `04490F065F6ADD262A7243506EDF7B8603B5D247`(见 [release-signing.md](release-signing.md)) |
|
||||
|
||||
## 发布包
|
||||
|
||||
维护者构建与上传(keyring 示例):
|
||||
|
||||
```bash
|
||||
./packages/build-keyring.sh
|
||||
curl -X PUT -H "Authorization: token <token>" \
|
||||
--upload-file packages/out/omarchycn-keyring-<ver>-any.pkg.tar.zst \
|
||||
https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchycn
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
# Release 签名与验证
|
||||
|
||||
OmarchyCN 发布产物(ISO、SHA256SUMS、release.json)使用 PGP 签名。
|
||||
|
||||
## 当前密钥
|
||||
|
||||
| 用途 | 指纹 | 有效期 |
|
||||
|---|---|---|
|
||||
| 主钥(仅认证) | `04490F065F6ADD262A7243506EDF7B8603B5D247` | 2026-08-24 → 2028-08-23 |
|
||||
| 发布签名子钥 | `51AACA0BF48922597B480E7E211B9B82E17CFB67` | 同上 |
|
||||
|
||||
公钥文件:[`cn/keys/omarchycn-release.asc`](../cn/keys/omarchycn-release.asc)
|
||||
|
||||
## 验证下载
|
||||
|
||||
```bash
|
||||
gpg --import cn/keys/omarchycn-release.asc
|
||||
gpg --verify SHA256SUMS.txt.asc SHA256SUMS.txt
|
||||
sha256sum -c SHA256SUMS.txt
|
||||
```
|
||||
|
||||
确认签名者指纹与上表一致。
|
||||
|
||||
## 密钥保管
|
||||
|
||||
- 主钥私钥离线保存(不进服务器、不进 CI),仅用于签发/吊销子钥
|
||||
- 日常签名只使用签名子钥
|
||||
- 私钥与吊销证书不入 Git;仓库只含公钥
|
||||
|
||||
## 轮换流程
|
||||
|
||||
1. 用主钥吊销旧签名子钥,签发新子钥
|
||||
2. 更新 `cn/keys/omarchycn-release.asc` 与本文档指纹表
|
||||
3. 在下一个 Release Notes 中公告轮换
|
||||
4. 主钥泄露时:发布吊销证书、启用全新主钥、公告并重签最近产物
|
||||
@@ -1 +1 @@
|
||||
%wheel ALL=(root) NOPASSWD: /usr/bin/timedatectl ^set-timezone [A-Za-z0-9_+][A-Za-z0-9_+.-]*(/[A-Za-z0-9_+][A-Za-z0-9_+.-]*)*$
|
||||
%wheel ALL=(root) NOPASSWD: /usr/bin/timedatectl set-timezone *
|
||||
|
||||
@@ -25,10 +25,6 @@ run_logged "$OMARCHY_INSTALL/hardware/intel/fred.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/intel/fix-wifi7-eht.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/intel/sof-firmware.sh"
|
||||
|
||||
# Rebuilds the boot image, so it has to follow the Panther Lake kernel swap
|
||||
# above rather than sit with the other Dell leaf at the top of this file.
|
||||
run_logged "$OMARCHY_INSTALL/hardware/dell-xps13-sidecar-amps.sh"
|
||||
|
||||
run_logged "$OMARCHY_INSTALL/hardware/asus/fix-asus-ptl-display-backlight.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/asus/fix-asus-ptl-b9406-display.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/asus/fix-asus-ptl-b9406-touchpad.sh"
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Enable the temporary sidecar amplifier workaround on the exact Dell XPS 13 model that needs it.
|
||||
#
|
||||
# Pacman registers a package even when its post_install scriptlet fails, so the
|
||||
# apply command runs explicitly here: a failed cleanup or boot-image rebuild has
|
||||
# to reach the caller rather than hide behind a successfully registered package.
|
||||
|
||||
if omarchy-hw-dell-xps13-sidecar-amps; then
|
||||
omarchy-pkg-add dell-xps13-sidecar-amps &&
|
||||
sudo dell-xps13-sidecar-amps-apply
|
||||
fi
|
||||
@@ -61,7 +61,6 @@ linux-firmware-marvell
|
||||
|
||||
# Dell laptop support packages
|
||||
dell-xps-touchpad-haptics
|
||||
dell-xps13-sidecar-amps
|
||||
|
||||
# Speaker tunings (LV2 limiter every tuning ends in)
|
||||
lsp-plugins-lv2
|
||||
|
||||
@@ -21,7 +21,7 @@ From the terminal, the same switches are `omarchy toggle <thing>`. Run `omarchy
|
||||
| Suspend | — | `omarchy toggle suspend` |
|
||||
| Hybrid GPU | — | `omarchy toggle hybrid gpu` |
|
||||
|
||||
The touchpad, touchscreen, and hybrid GPU switches live under _Trigger > Hardware_ (`Super + Ctrl + H`) rather than under Toggle, since they only show up when you actually have that hardware. The touchpad and touchscreen ones survive a Hyprland reload — the disabled device's name is saved to a small state file that Hyprland reads on startup to disable it again.
|
||||
The touchpad, touchscreen, and hybrid GPU switches live under _Trigger > Hardware_ (`Super + Ctrl + H`) rather than under Toggle, since they only show up when you actually have that hardware. The touchpad and touchscreen ones survive a Hyprland reload — the disabled state is written back out as a small Lua file that Hyprland sources on startup.
|
||||
|
||||
The Toggle menu also carries a few things that aren't `omarchy toggle` commands but behave the same: battery percentage in the bar, workspace layout (`Super + L`), window gaps (`Super + Shift + Backspace`), and the 1-window square aspect (`Super + Ctrl + Backspace`).
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ Omarchy watches systemd-coredump for process crashes. When something segfaults,
|
||||
|
||||
The watching is on by default. Turn it off under _Trigger > Toggle > Crash Capture_ (or with `omarchy toggle crash-capture`) and the notifications stop; `omarchy agent crash <pid>` still works by hand.
|
||||
|
||||
Crashes can also be silenced one program at a time, which is what the diagnosis offers you at the end. `omarchy crash mute hyprland` stops the notifications for that program only, `omarchy crash mute hyprland off` brings them back, and `omarchy crash mute` on its own lists what you've muted. It takes the binary's path as happily as its name, so `omarchy crash mute /usr/bin/hyprland` does the same thing. Quote a name with a space in it, as in `omarchy crash mute 'Some App'`. Everything else still notifies, and the muted program still crashes — this hides the reminder, it doesn't fix anything.
|
||||
|
||||
### Desktop apps
|
||||
|
||||
The _Install > AI_ menu also carries a couple of graphical AI apps: the ChatGPT desktop app, and Grok Bot for chatting with xAI's models.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user