Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
640c0d4291
|
||
|
|
ed9ef8bc04
|
||
|
|
653656e439
|
||
|
|
8715c20af2
|
||
|
|
f9c4085982
|
||
|
|
074247c79a
|
||
|
|
dc0513a519
|
||
|
|
495b24cedf
|
||
|
|
9ce9979ce5
|
||
|
|
f0672772d2 | ||
|
|
62eb5182d0 | ||
|
|
06174bbd06
|
||
|
|
6b10dbf191 | ||
|
|
5925929cb6 | ||
|
|
7d58bb9a62 | ||
|
|
6dd9aa55cc | ||
|
|
468b511249 | ||
|
|
101a3db393
|
||
|
|
64a531f999
|
||
|
|
7f9236777f
|
||
|
|
92ebed0f34
|
||
|
|
e62584b31f
|
||
|
|
39026fd41e
|
||
|
|
81c253b86e
|
||
|
|
f4d5fa4013
|
||
|
|
7d22dc14a5
|
||
|
|
9f15d42c2c
|
||
|
|
06e32d243d | ||
|
|
f9c9b3c364
|
||
|
|
f72a48f747
|
||
|
|
c51df9977b
|
||
|
|
1535e7871f
|
||
|
|
c4dcd2b63a
|
||
|
|
b183be2832
|
||
|
|
fc9afae2b6
|
||
|
|
111b857cda
|
||
|
|
4b280a1767
|
||
|
|
d1845245d3 | ||
|
|
9d8c0176d1 | ||
|
|
602a82621a
|
||
|
|
87b4cc4da1
|
||
|
|
83881e979b | ||
|
|
9ece53cede | ||
|
|
946704f309 | ||
|
|
c5a5e14e99 | ||
|
|
4be440b501 | ||
|
|
7026ede90b | ||
|
|
9d02bb08f8 | ||
|
|
b68d4142d7 | ||
|
|
ea6ee9440a | ||
|
|
eeb4206c7b | ||
|
|
8d14869689 | ||
|
|
eb7ecd13f3 | ||
|
|
0260d2accb | ||
|
|
e428dc2627 | ||
|
|
6e962b4466 | ||
|
|
3af7675a10 | ||
|
|
70895d4f1c
|
||
|
|
0ae1694830 | ||
|
|
77305ed3b9 | ||
|
|
45749c5b68 | ||
|
|
bafc9a1000 | ||
|
|
44a186afe4 | ||
|
|
bebe19bc70 | ||
|
|
87dfa14c56 | ||
|
|
b0e6611c70 | ||
|
|
95b791af16 | ||
|
|
2c93e66b0c | ||
|
|
9301092404 | ||
|
|
4cd8a081cb | ||
|
|
e53548fae2 | ||
|
|
fe56d68e90 | ||
|
|
23dab9ec4d | ||
|
|
9285b19d6a | ||
|
|
4637735aa2 | ||
|
|
68ab12f77d | ||
|
|
30471bf35a | ||
|
|
597f57a198 | ||
|
|
e66c27f1e7 | ||
|
|
44b00a4e80 | ||
|
|
7c896d3521 | ||
|
|
94a7b70ed4 | ||
|
|
ec9da050ee |
@@ -0,0 +1,24 @@
|
||||
name: pkg-repo-sync
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "17 */6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: quattro
|
||||
|
||||
- name: Mirror upstream [omarchy] stable into the Gitea Arch registry
|
||||
env:
|
||||
PKG_SYNC_TOKEN: ${{ secrets.PKG_SYNC_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v zstd > /dev/null; then
|
||||
apt-get update -qq && apt-get install -y -qq zstd
|
||||
fi
|
||||
bash packages/sync-omarchy-repo.sh
|
||||
@@ -15,16 +15,19 @@ matching guide before starting:
|
||||
|
||||
This fork carries the OmarchyCN China-integration layer on top of upstream `basecamp/omarchy`:
|
||||
|
||||
- `bin/omarchycn` routes to `omarchy cn <command>`; all cn commands are `bin/omarchy-cn-*` and follow the upstream bin conventions (metadata, helpers, `$OMARCHY_PATH` — sole exception: the overlay installer bootstraps by resolving its own checkout)
|
||||
- `bin/omarchycn` routes to `omarchy cn <command>`; all cn commands are `bin/omarchy-cn-*` and follow the upstream bin conventions (metadata, helpers, `$OMARCHY_PATH` — bootstrap exceptions: the overlay installer resolves its own checkout, and `omarchy-cn-convert` / `omarchy-cn-revert` are curl-able standalone scripts that default `OMARCHY_PATH` because they run before/while the cn tree exists)
|
||||
- Convert/revert: `omarchy-cn-convert` turns a vanilla package-based Omarchy into OmarchyCN (registry key + [omarchycn] repo, `--ask=4` package swap to the cn-built omarchy-dev/omarchy-settings-dev published by `packages/publish-cn-packages.sh`, [omarchy] mirror line, zh_CN locale, fcitx5 seeding, plymouth rebrand) recording pre-state in `/var/lib/omarchycn/convert-state`; `omarchy-cn-revert` restores that state; checkout installs are refused toward the overlay
|
||||
- `cn/` holds the data layer: `mirrors.json`, `dev-mirrors.json`, `apps.json`, `registry/` (AI providers/harnesses/compatibility), `fcitx5/`, `fontconfig/`, `keys/`, `lib/` (sourced helpers), `release` (cn release number)
|
||||
- cn migrations live in `cn/migrations/*.sh`, run by `omarchy-cn-update` with per-file completion markers under `~/.local/state/omarchycn/`
|
||||
- cn migrations live in `cn/migrations/*.sh`, run by `omarchy-cn-migrate` with per-file completion markers under `~/.local/state/omarchycn/`; `bin/omarchy-migrate` calls it after the upstream migrations, and `omarchy-cn-update` serves both install forms (overlay: git pull + reinstall + migrate; package: exec `omarchy-update`)
|
||||
- Packaging: `packages/omarchy-pkgs-cn.patch` must be applied to the sibling `omarchy-pkgs` checkout so `omarchy-dev` ships `cn/`; keyring in `packages/omarchycn-keyring/`
|
||||
- Upstream repo mirror: `packages/sync-omarchy-repo.sh` + `.gitea/workflows/pkg-repo-sync.yml` mirror the upstream `[omarchy]` stable channel into the Gitea Arch registry every 6h; `pacman-stable.conf` lists the mirror first (upstream fallback), clients must trust the registry key (install import + cn migration), `omarchycn doctor mirror --fix` re-heals the line; see `docs/pacman-repo.md`
|
||||
- ISO: `packages/omarchy-iso-cn.patch` must be applied to the sibling `omarchy-iso` checkout — Chinese installer (cage+foot graphical console with English VT fallback) and live-env packages
|
||||
- Release process: `docs/release-checklist.md`; signing: `docs/release-signing.md`; pacman repo: `docs/pacman-repo.md`
|
||||
- Release process: `docs/release-checklist.md`; signing: `docs/release-signing.md`; pacman repo: `docs/pacman-repo.md`; ISO distribution: Cloudflare R2 bucket behind `dl.zacharyzhang.com` via `packages/upload-release-r2.sh` (Gitea releases carry only the small artifacts)
|
||||
- Site: `site/` is the omarchycn.zacharyzhang.com landing page (Vite 8 vanilla + GSAP + self-hosted Fusion Pixel + reicon, kami palette on a 12-col grid); deploy with `npx wrangler deploy` from `site/`, rendered-state checks via `node shots.mjs` against a preview or the live URL
|
||||
- Chinese-first defaults: OmarchyCN ships Simplified Chinese as the default UX. User-visible strings in `default/omarchy/omarchy-menu.jsonc` (all labels), `default/hypr/bindings/*.lua` (bind descriptions, including the generated workspace/group/panel loops), `bin/omarchy-menu-keybindings` (its merge list and priority patterns must match the shipped Chinese descriptions), `bin/omarchy-update-confirm`, and `install/user/first-run/*` notifications are Chinese with brand names kept in English; menu search stays reachable in English through leaf ids. When syncing upstream, translate new strings in these files and resolve string conflicts toward our Chinese text.
|
||||
- Default set changes vs upstream: `install/omarchy-base.packages` adds fcitx5-rime/chinese-addons/configtool and drops aether/libreoffice-fresh/obs-studio; the Basecamp/Discord/HEY/Google/WhatsApp/X launchers, their webapp keybindings, the whatsapp-slim extension, and the HEY mailto handler are removed (preinstall add/remove lists, launcher.hides, chromium flags, and mimeapps stay in sync); `install/user/cn-chinese.sh` seeds IME/font config on zh_CN systems
|
||||
- AI: `cn/registry/` drives providers (DeepSeek/Kimi/Z.AI/MiniMax) with zero-code adapters; `omarchy-default-agent` also accepts kimi (official installer script, not mise) and deepcode (mise npm); the Default Agent menu carries them plus an AI Hub combo entry
|
||||
- Upstream sync: `.gitea/workflows/upstream-sync.yml` opens a PR per upstream change; keep upstream file edits minimal and inventoried (currently: one `GROUP_DESCRIPTIONS[cn]` line in `bin/omarchy`, the localized menu/bindings/update/first-run surfaces above, the western-app removals above, a rewritten `README.md` (known recurring sync conflict, resolve toward ours), `AGENTS.md` additions, and two `.gitignore` lines)
|
||||
- AI: `cn/registry/` drives providers (DeepSeek/Kimi/Z.AI/MiniMax/Ollama-local) with zero-code adapters; Ollama uses a fixed `static_token` and runtime-listed models (`models_dynamic`); `omarchy-default-agent` also accepts kimi (official installer script, not mise), deepcode, dim, and dsh (mise npm); the Default Agent menu carries them plus an AI Hub combo entry
|
||||
- Upstream sync: `.gitea/workflows/upstream-sync.yml` opens a PR per upstream change; keep upstream file edits minimal and inventoried (currently: one `GROUP_DESCRIPTIONS[cn]` line in `bin/omarchy`, the `omarchy-cn-migrate` line in `bin/omarchy-migrate`, the localized menu/bindings/update/first-run surfaces above, the western-app removals above, the cn agent roster in `bin/omarchy-default-agent` / `bin/omarchy-agent` (kimi/deepcode/dim/dsh), the `[omarchy]` mirror line in `default/pacman/pacman-stable.conf`, the registry-key import in `install/post-install/pacman.sh`, a rewritten `README.md` (known recurring sync conflict, resolve toward ours), `AGENTS.md` additions, and two `.gitignore` lines)
|
||||
- cn tests: `test/shell.d/omarchycn-test.sh`, `test/shell.d/omarchycn-ai-test.sh`; localized UI expectations live in the upstream suites (menu, keybindings-menu, hyprland-default-config, binding-conflicts, clock, screenrecording, update-disk-space)
|
||||
|
||||
# Documentation Layout
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
面向中国开发者的 [Omarchy](https://omarchy.org) 下游发行版:完整保留 Omarchy 的 Arch + Hyprland 桌面体验,为中国网络环境、中文使用习惯与国内 AI 服务做系统级增强。
|
||||
|
||||
当前版本 `4.0.0.alpha-cn.4`(基于上游 `quattro` 分支构建的基线 ISO)。
|
||||
当前版本 `4.0.0.alpha-cn.7`(基于上游 `quattro` 分支构建的基线 ISO)。
|
||||
|
||||
## 特性
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
- **中国镜像管理**: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 的统一配置向导、凭据安全存储与连接诊断
|
||||
- **AI Hub**:Kimi、DeepSeek、Z.AI/GLM、MiniMax 与本地 Ollama 的一等 Provider 支持,与 Claude Code、Codex、OpenCode、Kimi Code、Deep Code 等 Harness 的统一配置向导、凭据安全存储与连接诊断;另有 Dim、DSH 等自管登录的 Agent CLI 一键安装
|
||||
- **国内应用中心**:微信、QQ、飞书、钉钉、腾讯会议、WPS 等应用的可信安装入口
|
||||
- **Overlay 安装器**:在现有 Omarchy 上叠加 OmarchyCN,全程可逆、可卸载
|
||||
- **一键转换**:原版 Omarchy(包安装)一条命令转成 OmarchyCN,logo/菜单/键位/输入法/软件源全套切换,`omarchy cn revert` 一键还原
|
||||
- **Overlay 安装器**:在现有 Omarchy checkout 上叠加 OmarchyCN,全程可逆、可卸载
|
||||
- **统一诊断**:`omarchycn doctor` 覆盖网络、镜像、输入法、显示与 AI 配置
|
||||
|
||||
## 下载与安装
|
||||
@@ -31,6 +32,22 @@
|
||||
sha256sum -c SHA256SUMS.txt
|
||||
```
|
||||
|
||||
## 已有原版 Omarchy?一键转换
|
||||
|
||||
包安装的原版 Omarchy(quattro)可直接转换,无需重装系统:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/bin/omarchy-cn-convert | bash
|
||||
```
|
||||
|
||||
转换内容:换装 OmarchyCN 构建的系统包(中文菜单/键位/更新界面、OMARCHY CN 品牌、AI 全家桶)、启用 [omarchy] 自建镜像与 [omarchycn] 仓库、zh_CN 语言与 Fcitx5+Rime 输入法、国内 Arch 镜像自动测速。重新登录后生效。
|
||||
|
||||
随时一键还原(按转换时记录的状态逐项回退):
|
||||
|
||||
```bash
|
||||
omarchy cn revert
|
||||
```
|
||||
|
||||
## 上游文档
|
||||
|
||||
Omarchy 完整英文手册在 [`manual/`](manual/) 目录,亦见 [learn.omacom.io](https://learn.omacom.io/2/the-omarchy-manual)。
|
||||
|
||||
@@ -41,6 +41,7 @@ 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"
|
||||
|
||||
+11
-1
@@ -92,8 +92,10 @@ 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+=(--prompt "$prompt")
|
||||
[[ -n ${prompt:-} ]] && command+=(--interactive --prompt "$prompt")
|
||||
;;
|
||||
pi)
|
||||
command=(pi)
|
||||
@@ -115,6 +117,14 @@ dim)
|
||||
command=(dim)
|
||||
fi
|
||||
;;
|
||||
dsh)
|
||||
# dsh has no TUI: `dsh web` is the interactive form, headless the one-shot
|
||||
if [[ -n ${prompt:-} ]]; then
|
||||
command=(dsh --profile headless "$prompt")
|
||||
else
|
||||
command=(dsh web)
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported default agent: $agent" >&2
|
||||
exit 1
|
||||
|
||||
@@ -528,7 +528,7 @@ def fetch_codex_rpc():
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[codex, "-s", "read-only", "-a", "untrusted", "app-server"],
|
||||
[codex, "-s", "read-only", "-a", "on-request", "app-server"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
# omarchy:args=[--no-osd] [+N%|N%-|N%]
|
||||
# omarchy:examples=omarchy brightness display apple | omarchy brightness display apple +5% | omarchy brightness display apple --no-osd 50%
|
||||
|
||||
device_cache="${XDG_RUNTIME_DIR:-/tmp}/omarchy-brightness-display-apple.device"
|
||||
# Only cache under the user-private runtime dir. With no XDG_RUNTIME_DIR we skip
|
||||
# caching (detect every run) rather than fall back to a predictable, world-writable
|
||||
# /tmp path another user could pre-create.
|
||||
device_cache=""
|
||||
if [[ -n ${XDG_RUNTIME_DIR:-} ]]; then
|
||||
device_cache="$XDG_RUNTIME_DIR/omarchy-brightness-display-apple.device"
|
||||
fi
|
||||
no_osd=0
|
||||
if [[ ${1:-} == "--no-osd" ]]; then
|
||||
no_osd=1
|
||||
@@ -28,9 +34,14 @@ find_apple_display_device() {
|
||||
local cached=""
|
||||
local device=""
|
||||
|
||||
if [[ -r $device_cache ]]; then
|
||||
if [[ -n $device_cache && -r $device_cache ]]; then
|
||||
read -r cached <"$device_cache" || true
|
||||
if [[ -n $cached && -e $cached ]]; then
|
||||
# Trust a cached value only if it still names a hiddev character device. A
|
||||
# stale or unexpected cache (a regular file, a non-hiddev node) is ignored and
|
||||
# we re-detect instead of handing an arbitrary path to asdcontrol. The globs
|
||||
# are left unquoted on purpose: [[ ]] pattern-matches an unquoted right side,
|
||||
# and quoting them would turn the match into a literal string comparison.
|
||||
if [[ ( $cached == /dev/hiddev* || $cached == /dev/usb/hiddev* ) && -c $cached ]]; then
|
||||
printf '%s\n' "$cached"
|
||||
return 0
|
||||
fi
|
||||
@@ -39,7 +50,9 @@ find_apple_display_device() {
|
||||
device="$(detect_apple_display_device)" || return 1
|
||||
[[ -n $device ]] || return 1
|
||||
|
||||
printf '%s\n' "$device" >"$device_cache"
|
||||
if [[ -n $device_cache ]]; then
|
||||
printf '%s\n' "$device" >"$device_cache"
|
||||
fi
|
||||
printf '%s\n' "$device"
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ if omarchy-cmd-missing "$command"; then
|
||||
$install
|
||||
fi
|
||||
|
||||
key=$(omarchy-cn-ai-secret get "$provider")
|
||||
key=$(cn_ai_resolve_key "$provider")
|
||||
|
||||
case "$harness" in
|
||||
claude-code)
|
||||
@@ -45,7 +45,8 @@ claude-code)
|
||||
settings="$HOME/.claude/settings.json"
|
||||
mkdir -p "${settings%/*}"
|
||||
base=$(cn_ai_endpoint "$provider" anthropic)
|
||||
fast=$(cn_ai_fast_model "$provider")
|
||||
# Dynamic-model providers have no fast alias: every tier maps to the model
|
||||
fast=$(cn_ai_fast_model "$provider" 2>/dev/null || printf '%s' "$model")
|
||||
current="{}"
|
||||
if [[ -s $settings ]]; then
|
||||
current=$(cat "$settings")
|
||||
|
||||
@@ -38,6 +38,8 @@ fi
|
||||
|
||||
if [[ $proto == "native" ]]; then
|
||||
echo "INFO ai: $harness 自管鉴权,跳过 OmarchyCN Key 检查"
|
||||
elif cn_ai_static_token "$provider" > /dev/null 2>&1; then
|
||||
echo "PASS ai: $provider 使用内置 token,无需存储 Key"
|
||||
elif omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
|
||||
echo "PASS ai: $provider API Key stored"
|
||||
else
|
||||
@@ -47,7 +49,7 @@ fi
|
||||
|
||||
if [[ $proto != "native" ]]; then
|
||||
base=$(cn_ai_endpoint "$provider" "$proto")
|
||||
host=$(sed -E 's|https?://([^/]+).*|\1|' <<<"$base")
|
||||
host=$(sed -E 's|https?://([^/:]+).*|\1|' <<<"$base")
|
||||
if getent hosts "$host" > /dev/null 2>&1; then
|
||||
echo "PASS ai: endpoint DNS resolves ($host)"
|
||||
else
|
||||
|
||||
@@ -32,7 +32,7 @@ fi
|
||||
|
||||
case "$config_method" in
|
||||
env)
|
||||
key=$(omarchy-cn-ai-secret get "$provider")
|
||||
key=$(cn_ai_resolve_key "$provider")
|
||||
if [[ $harness == "codex" ]]; then
|
||||
cn_ai_render_codex_config "$provider" "$model" "$key"
|
||||
exec "$command"
|
||||
|
||||
@@ -33,7 +33,9 @@ if [[ $level == "unsupported" ]]; then
|
||||
fi
|
||||
|
||||
allowlist=$(cn_ai_combo_models "$harness" "$provider")
|
||||
if [[ -n $allowlist ]]; then
|
||||
if cn_ai_models_dynamic "$provider"; then
|
||||
: # runtime-listed models; omarchycn ai test validates against the live server
|
||||
elif [[ -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
|
||||
@@ -68,6 +70,6 @@ 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
|
||||
if ! cn_ai_resolve_key "$provider" > /dev/null 2>&1; then
|
||||
echo "提示: 尚未存储 $provider 的 API Key,运行: omarchycn ai secret set $provider"
|
||||
fi
|
||||
|
||||
+26
-2
@@ -24,7 +24,31 @@ provider=$(printf '%s\n' "${providers[@]}" | gum choose --header "选择 Provide
|
||||
|
||||
harness_proto=$(cn_ai_harness_field "$harness" protocol)
|
||||
allowlist=$(cn_ai_combo_models "$harness" "$provider")
|
||||
if [[ -n $allowlist ]]; then
|
||||
if cn_ai_models_dynamic "$provider"; then
|
||||
base=$(cn_ai_endpoint "$provider" "$harness_proto")
|
||||
if [[ $provider == "ollama" ]]; then
|
||||
if omarchy-cmd-missing ollama; then
|
||||
gum confirm "Ollama 未安装,现在安装本地服务?" || exit 1
|
||||
if omarchy-cmd-present nvidia-smi; then
|
||||
ollama_pkg=ollama-cuda
|
||||
elif omarchy-cmd-present rocminfo; then
|
||||
ollama_pkg=ollama-rocm
|
||||
else
|
||||
ollama_pkg=ollama
|
||||
fi
|
||||
omarchy-pkg-add "$ollama_pkg"
|
||||
fi
|
||||
if ! curl -sf --connect-timeout 3 "$base/api/tags" > /dev/null; then
|
||||
sudo systemctl enable --now ollama
|
||||
fi
|
||||
fi
|
||||
models=$(curl -sf --connect-timeout 3 "$base/api/tags" | jq -r '.models[].name' || true)
|
||||
if [[ -z $models ]]; then
|
||||
echo "无法列出本地模型(服务未运行或尚未拉取):先 ollama pull <模型>(如 qwen3-coder)再重试" >&2
|
||||
exit 1
|
||||
fi
|
||||
model=$(gum choose --header "选择本地模型" <<<"$models")
|
||||
elif [[ -n $allowlist ]]; then
|
||||
model=$(gum choose --header "选择模型" <<<"$allowlist")
|
||||
else
|
||||
# Hide models bound to a different wire protocol than the harness speaks
|
||||
@@ -48,7 +72,7 @@ if [[ $proto == "native" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
|
||||
if ! cn_ai_resolve_key "$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
|
||||
|
||||
@@ -31,7 +31,7 @@ fi
|
||||
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
|
||||
if ! key=$(cn_ai_resolve_key "$provider" 2>/dev/null); then
|
||||
echo "FAIL ai-test: 未存储 $provider 的 API Key (omarchycn ai secret set $provider)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Convert a vanilla package-based Omarchy install into OmarchyCN
|
||||
# omarchy:examples=omarchy cn convert
|
||||
# Curl-able bootstrap (like the overlay installer): everything before the
|
||||
# package swap must run without the cn tree, so no $OMARCHY_PATH until then.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GITEA=${GITEA:-https://git.zacharyzhang.com}
|
||||
OWNER=${OWNER:-ZacharyZhang-NY}
|
||||
REGISTRY="$GITEA/api/packages/$OWNER/arch"
|
||||
REG_KEY_FPR=74DCF57ACD812B24D959F146BD386048867B33B4
|
||||
STATE_DIR=/var/lib/omarchycn
|
||||
STATE=$STATE_DIR/convert-state
|
||||
# Bootstrap exception: curl|bash runs outside a session, so default the path
|
||||
export OMARCHY_PATH=${OMARCHY_PATH:-/usr/share/omarchy}
|
||||
|
||||
main() {
|
||||
command -v pacman > /dev/null || { echo "需要 Arch Linux(未找到 pacman)" >&2; exit 1; }
|
||||
if (( EUID == 0 )); then
|
||||
echo "请以普通用户运行(脚本内部按需 sudo)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local prev_omarchy prev_settings
|
||||
prev_omarchy=$(pacman -Q omarchy 2> /dev/null || pacman -Q omarchy-dev 2> /dev/null || true)
|
||||
prev_settings=$(pacman -Q omarchy-settings 2> /dev/null || pacman -Q omarchy-settings-dev 2> /dev/null || true)
|
||||
if [[ -z $prev_omarchy || -z $prev_settings ]]; then
|
||||
if [[ -d $HOME/.local/share/omarchy/.git ]]; then
|
||||
echo "检测到 checkout 安装(非包安装):请改用 overlay(bin/omarchycn-install-overlay)" >&2
|
||||
else
|
||||
echo "未检测到 Omarchy 包安装(需要 omarchy 与 omarchy-settings)" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
[[ -f $STATE ]] && echo "已转换过($STATE 在案):本次仅同步更新,不覆盖回退状态"
|
||||
|
||||
echo "==> 信任 OmarchyCN registry 公钥并配置 [omarchycn] 仓库"
|
||||
local tmpkey
|
||||
tmpkey=$(mktemp)
|
||||
curl -fsSL "$REGISTRY/repository.key" -o "$tmpkey"
|
||||
sudo pacman-key --add "$tmpkey"
|
||||
sudo pacman-key --lsign-key "$REG_KEY_FPR"
|
||||
rm -f "$tmpkey"
|
||||
if ! grep -q '^\[omarchycn\]' /etc/pacman.conf; then
|
||||
printf '\n[omarchycn]\nSigLevel = Required DatabaseOptional\nServer = %s/omarchycn/x86_64\n' \
|
||||
"$REGISTRY" | sudo tee -a /etc/pacman.conf > /dev/null
|
||||
fi
|
||||
|
||||
if [[ ! -f $STATE ]]; then
|
||||
echo "==> 记录转换前状态(供 omarchy cn revert 还原)"
|
||||
sudo mkdir -p "$STATE_DIR"
|
||||
if [[ -f /etc/locale.conf ]]; then
|
||||
sudo cp /etc/locale.conf "$STATE_DIR/locale.conf.pre"
|
||||
fi
|
||||
sudo cp /etc/pacman.d/mirrorlist "$STATE_DIR/mirrorlist.pre"
|
||||
local p new_pkgs=""
|
||||
for p in fcitx5-rime fcitx5-chinese-addons fcitx5-configtool omarchycn-keyring; do
|
||||
pacman -Q "$p" > /dev/null 2>&1 || new_pkgs+="$p "
|
||||
done
|
||||
# Values are quoted because revert sources this file; publish atomically
|
||||
{
|
||||
echo "prev_omarchy=\"${prev_omarchy%% *}\""
|
||||
echo "prev_settings=\"${prev_settings%% *}\""
|
||||
echo "new_pkgs=\"${new_pkgs% }\""
|
||||
echo "converted_at=\"$(date +%s)\""
|
||||
} | sudo tee "$STATE.new" > /dev/null
|
||||
sudo mv "$STATE.new" "$STATE"
|
||||
fi
|
||||
|
||||
echo "==> 系统更新并换装 OmarchyCN 软件包(替换 ${prev_omarchy%% *}/${prev_settings%% *})"
|
||||
sudo pacman -Syu --noconfirm
|
||||
sudo pacman -S --noconfirm omarchycn/omarchycn-keyring
|
||||
# --ask=4 auto-confirms removing the conflicting upstream omarchy packages
|
||||
sudo pacman -S --noconfirm --ask=4 omarchycn/omarchy-dev omarchycn/omarchy-settings-dev
|
||||
sudo pacman -S --needed --noconfirm fcitx5-rime fcitx5-chinese-addons fcitx5-configtool
|
||||
|
||||
echo "==> [omarchy] 仓库启用自建镜像(Cloudflare 上游兜底)"
|
||||
source /usr/share/omarchy/cn/lib/mirror.sh
|
||||
cn_mirror_omarchy_repo_fix
|
||||
|
||||
echo "==> 系统语言 zh_CN.UTF-8"
|
||||
grep -q '^zh_CN.UTF-8 UTF-8' /etc/locale.gen ||
|
||||
echo 'zh_CN.UTF-8 UTF-8' | sudo tee -a /etc/locale.gen > /dev/null
|
||||
sudo locale-gen > /dev/null
|
||||
if grep -q '^LANG=' /etc/locale.conf 2> /dev/null; then
|
||||
sudo sed -i 's/^LANG=.*/LANG=zh_CN.UTF-8/' /etc/locale.conf
|
||||
else
|
||||
echo 'LANG=zh_CN.UTF-8' | sudo tee -a /etc/locale.conf > /dev/null
|
||||
fi
|
||||
|
||||
echo "==> 中文输入法与字体(用户级)"
|
||||
OMARCHY_PATH=/usr/share/omarchy bash /usr/share/omarchy/install/user/cn-chinese.sh
|
||||
systemctl --user restart omarchy-fcitx5.service 2> /dev/null || true
|
||||
|
||||
echo "==> Arch 官方仓库切换国内镜像(自动测速,失败则保留现状)"
|
||||
omarchy-cn-mirror-apply china || echo "国内镜像不可达,保留当前 mirrorlist"
|
||||
|
||||
echo "==> OMARCHY CN 开机画面"
|
||||
omarchy-refresh-plymouth
|
||||
|
||||
omarchy-notification-send "OmarchyCN" "转换完成:重新登录后菜单/键位/输入法全中文" 2> /dev/null || true
|
||||
echo
|
||||
echo "转换完成(重新登录生效)。回退: omarchy cn revert"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
+28
-1
@@ -69,6 +69,29 @@ check_mirror() {
|
||||
fi
|
||||
}
|
||||
|
||||
# refresh-pacman restores the checkout's template, which on overlay installs
|
||||
# has no OmarchyCN mirror line; this check makes that loss visible and fixable
|
||||
check_omarchy_repo() {
|
||||
local conf="${OMARCHYCN_PACMAN_CONF:-/etc/pacman.conf}"
|
||||
|
||||
if ! grep -q "^Server = https://pkgs.omarchy.org/stable/" "$conf"; then
|
||||
echo "INFO omarchy-repo: 非 stable 通道,跳过 OmarchyCN 镜像检查"
|
||||
return 0
|
||||
fi
|
||||
# --fix runs the helper even when the line is present: it also heals a missing key
|
||||
if [[ $fix == "true" ]]; then
|
||||
if cn_mirror_omarchy_repo_fix && cn_mirror_omarchy_repo_ok; then
|
||||
echo "PASS omarchy-repo: OmarchyCN 镜像行与 registry 公钥在位"
|
||||
return 0
|
||||
fi
|
||||
elif cn_mirror_omarchy_repo_ok; then
|
||||
echo "PASS omarchy-repo: OmarchyCN 镜像行在位"
|
||||
return 0
|
||||
fi
|
||||
echo "FAIL omarchy-repo: [omarchy] 缺 OmarchyCN 镜像行(omarchycn doctor mirror --fix 可修复)"
|
||||
failures=$((failures + 1))
|
||||
}
|
||||
|
||||
check_dev_mirror() {
|
||||
omarchy-cn-dev-mirror-doctor || failures=$((failures + 1))
|
||||
}
|
||||
@@ -79,12 +102,16 @@ check_ime() {
|
||||
|
||||
case "$module" in
|
||||
network) check_network ;;
|
||||
mirror) check_mirror ;;
|
||||
mirror)
|
||||
check_mirror
|
||||
check_omarchy_repo
|
||||
;;
|
||||
dev-mirror) check_dev_mirror ;;
|
||||
ime) check_ime ;;
|
||||
all)
|
||||
check_network
|
||||
check_mirror
|
||||
check_omarchy_repo
|
||||
check_dev_mirror
|
||||
check_ime
|
||||
;;
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Run pending OmarchyCN migrations
|
||||
# omarchy:examples=omarchycn migrate
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MIGRATIONS_DIR="$OMARCHY_PATH/cn/migrations"
|
||||
DONE_DIR="$HOME/.local/state/omarchycn/migrations-done"
|
||||
|
||||
[[ -d $MIGRATIONS_DIR ]] || exit 0
|
||||
|
||||
mkdir -p "$DONE_DIR"
|
||||
for m in "$MIGRATIONS_DIR"/*.sh; do
|
||||
[[ -f $m ]] || continue
|
||||
name="${m##*/}"
|
||||
if [[ ! -f $DONE_DIR/$name ]]; then
|
||||
echo "OmarchyCN migration: $name"
|
||||
bash -euo pipefail "$m"
|
||||
touch "$DONE_DIR/$name"
|
||||
fi
|
||||
done
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Revert an OmarchyCN conversion back to vanilla Omarchy
|
||||
# omarchy:examples=omarchy cn revert
|
||||
# Curl-able and self-deleting-safe: main() is fully parsed before the package
|
||||
# swap removes this file from the cn tree it may be running from.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
STATE_DIR=/var/lib/omarchycn
|
||||
STATE=$STATE_DIR/convert-state
|
||||
MIRROR_LINE="Server = https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64"
|
||||
REG_KEY_FPR=74DCF57ACD812B24D959F146BD386048867B33B4
|
||||
# Bootstrap exception: curl|bash runs outside a session, so default the path
|
||||
export OMARCHY_PATH=${OMARCHY_PATH:-/usr/share/omarchy}
|
||||
|
||||
main() {
|
||||
if [[ ! -f $STATE ]]; then
|
||||
echo "未发现转换状态($STATE):本机不是由 omarchy cn convert 转换而来" >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( EUID == 0 )); then
|
||||
echo "请以普通用户运行(脚本内部按需 sudo)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local prev_omarchy="" prev_settings="" new_pkgs=""
|
||||
# State is trusted root-owned key=value lines written by convert
|
||||
source "$STATE"
|
||||
[[ -n $prev_omarchy && -n $prev_settings ]] || { echo "转换状态损坏: $STATE" >&2; exit 1; }
|
||||
|
||||
echo "==> 清理转换时播种的用户配置(与 cn 层原件一致才删除)"
|
||||
local seeded
|
||||
for seeded in \
|
||||
"$HOME/.config/fcitx5/profile:/usr/share/omarchy/cn/fcitx5/profile" \
|
||||
"$HOME/.config/fontconfig/conf.d/64-omarchycn-cjk.conf:/usr/share/omarchy/cn/fontconfig/64-omarchycn-cjk.conf"; do
|
||||
if cmp -s "${seeded%%:*}" "${seeded#*:}" 2> /dev/null; then
|
||||
rm -f "${seeded%%:*}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "==> 还原 mirrorlist 与系统语言"
|
||||
sudo cp "$STATE_DIR/mirrorlist.pre" /etc/pacman.d/mirrorlist
|
||||
if [[ -f $STATE_DIR/locale.conf.pre ]]; then
|
||||
sudo cp "$STATE_DIR/locale.conf.pre" /etc/locale.conf
|
||||
fi
|
||||
|
||||
echo "==> pacman.conf 移除 [omarchycn] 与 [omarchy] 镜像行"
|
||||
sudo sed -i "\|^$MIRROR_LINE\$|d" /etc/pacman.conf
|
||||
# Delete the section header and body, but keep any section that follows
|
||||
sudo sed -i '/^\[omarchycn\]$/,/^\[/{ /^\[omarchycn\]$/d; /^\[/!d; }' /etc/pacman.conf
|
||||
|
||||
echo "==> 换回上游软件包($prev_omarchy/$prev_settings)"
|
||||
sudo pacman -Sy
|
||||
# --ask=4 auto-confirms removing the conflicting cn packages
|
||||
sudo pacman -S --noconfirm --ask=4 "omarchy/$prev_omarchy" "omarchy/$prev_settings"
|
||||
if [[ -n $new_pkgs ]]; then
|
||||
# Only what is still installed: a user may have removed some already,
|
||||
# and a -R failure here would strand the swap-completed system
|
||||
local p still=""
|
||||
for p in $new_pkgs; do
|
||||
# grep without -q consumes the whole list: -q's early exit would
|
||||
# SIGPIPE pacman and read as "not installed" under pipefail
|
||||
pacman -Qq | grep -xF "$p" > /dev/null && still+="$p "
|
||||
done
|
||||
if [[ -n $still ]]; then
|
||||
echo "==> 移除转换时新装的包: ${still% }"
|
||||
# shellcheck disable=SC2086
|
||||
sudo pacman -R --noconfirm ${still% }
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "==> 移除 registry 公钥信任"
|
||||
sudo pacman-key --delete "$REG_KEY_FPR" > /dev/null 2>&1 || true
|
||||
|
||||
# The system is vanilla again: clear state before the cosmetic tail so a
|
||||
# plymouth failure cannot strand a reverted machine in "converted" state
|
||||
systemctl --user restart omarchy-fcitx5.service 2> /dev/null || true
|
||||
sudo rm -rf "$STATE_DIR"
|
||||
|
||||
echo "==> 还原上游开机画面"
|
||||
if ! omarchy-refresh-plymouth; then
|
||||
echo "开机画面还原失败:手动执行 omarchy-refresh-plymouth 即可" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
echo "已回退为原版 Omarchy(重新登录生效)。再次转换: omarchy cn convert"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
+11
-21
@@ -1,15 +1,17 @@
|
||||
#!/bin/bash
|
||||
# omarchy:summary=Update an overlay install: pull source, reinstall, run migrations
|
||||
# omarchy:summary=Full OmarchyCN update: system, cn data, and 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
|
||||
# Package install: the pipeline brings the new cn tree and runs cn migrations
|
||||
if [[ -d $OMARCHY_PATH/cn ]]; then
|
||||
exec omarchy-update
|
||||
fi
|
||||
echo "未检测到 OmarchyCN($OMARCHY_PATH/cn 不存在,也没有 overlay manifest)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -52,11 +54,11 @@ stable)
|
||||
;;
|
||||
esac
|
||||
|
||||
# Never move onto a tree that predates the channel mechanism: its update
|
||||
# command cannot switch back, stranding the install
|
||||
if [[ ! -f $src/bin/omarchy-cn-channel ]]; then
|
||||
# Never move onto a tree that predates the channel or migrate mechanism:
|
||||
# it cannot switch back or finish this update's migration step
|
||||
if [[ ! -f $src/bin/omarchy-cn-channel || ! -f $src/bin/omarchy-cn-migrate ]]; then
|
||||
git -C "$src" checkout -q "$old"
|
||||
echo "$channel 通道的目标发布早于通道机制,已回退;请使用 nightly 或更新的发布" >&2
|
||||
echo "$channel 通道的目标发布早于当前更新机制,已回退;请使用 nightly 或更新的发布" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -65,18 +67,6 @@ echo "Channel: $channel, 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
|
||||
"$src/bin/omarchy-cn-migrate"
|
||||
|
||||
echo "OmarchyCN update complete ($(omarchy-cn-version 2>/dev/null || echo unknown))"
|
||||
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/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
|
||||
+28
-5
@@ -48,12 +48,17 @@ 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 '[(._UID // "-"),
|
||||
(.COREDUMP_COMM // "-"),
|
||||
(.COREDUMP_PID // "-"),
|
||||
(.COREDUMP_EXE // "-"),
|
||||
(.COREDUMP_SIGNAL_NAME // "-")] | @tsv' <<<"$entry" 2>/dev/null
|
||||
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
|
||||
)
|
||||
|
||||
[[ $pid =~ ^[0-9]+$ ]] || continue
|
||||
@@ -71,11 +76,29 @@ 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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Set and launch the default coding agent
|
||||
# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim]
|
||||
# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim|dsh]
|
||||
# omarchy:examples=omarchy default agent | omarchy default agent codex | omarchy default agent kimi
|
||||
|
||||
installing=false
|
||||
@@ -37,8 +37,9 @@ copilot | github-copilot) agent="copilot"; name="GitHub Copilot" ;;
|
||||
kimi | kimi-code) agent="kimi"; name="Kimi Code" ;;
|
||||
deepcode | deep-code) agent="deepcode"; name="Deep Code"; agent_package="npm:@vegamo/deepcode-cli" ;;
|
||||
dim | dimcode | dim-agent) agent="dim"; name="Dim Agent"; agent_package="npm:dimcode" ;;
|
||||
dsh | deepseek-harness) agent="dsh"; name="DSH"; agent_package="npm:@deepseek-ai/dsh" ;;
|
||||
*)
|
||||
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim>"
|
||||
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim|dsh>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -6,6 +6,18 @@
|
||||
|
||||
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() {
|
||||
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/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
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/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,6 +11,14 @@ 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,6 +28,13 @@ 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,6 +22,15 @@ 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,6 +80,14 @@ 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.
|
||||
|
||||
+11
-17
@@ -6,9 +6,10 @@
|
||||
|
||||
set -e
|
||||
|
||||
setup_policy_directory() {
|
||||
sudo mkdir -p "$1"
|
||||
sudo chmod a+rw "$1"
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
|
||||
setup_chromium_policy_directory() {
|
||||
browser_policy_setup_dir "$1"
|
||||
}
|
||||
|
||||
announce_browser_installed() {
|
||||
@@ -23,13 +24,6 @@ copy_chromium_flags() {
|
||||
omarchy-install-chromium-ytdlp
|
||||
}
|
||||
|
||||
setup_firefox_preferences() {
|
||||
local distribution_dir="$1"
|
||||
|
||||
setup_policy_directory "$distribution_dir"
|
||||
sudo cp -f "$OMARCHY_PATH/default/firefox/policies.json" "$distribution_dir/policies.json"
|
||||
}
|
||||
|
||||
setup_firefox_wayland() {
|
||||
mkdir -p ~/.config/environment.d
|
||||
echo "MOZ_ENABLE_WAYLAND=1" > ~/.config/environment.d/omarchy-firefox-wayland.conf
|
||||
@@ -40,7 +34,7 @@ chromium)
|
||||
echo "Installing Chromium..."
|
||||
omarchy-pkg-add chromium
|
||||
|
||||
setup_policy_directory /etc/chromium/policies/managed
|
||||
setup_chromium_policy_directory /etc/chromium/policies/managed
|
||||
copy_chromium_flags ~/.config/chromium-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Chromium"
|
||||
@@ -49,7 +43,7 @@ chrome)
|
||||
echo "Installing Chrome..."
|
||||
omarchy-pkg-aur-add google-chrome || exit 1
|
||||
|
||||
setup_policy_directory /etc/opt/chrome/policies/managed
|
||||
setup_chromium_policy_directory /etc/opt/chrome/policies/managed
|
||||
copy_chromium_flags ~/.config/chrome-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Chrome"
|
||||
@@ -58,7 +52,7 @@ edge)
|
||||
echo "Installing Edge..."
|
||||
omarchy-pkg-aur-add microsoft-edge-stable-bin || exit 1
|
||||
|
||||
setup_policy_directory /etc/opt/edge/policies/managed
|
||||
setup_chromium_policy_directory /etc/opt/edge/policies/managed
|
||||
copy_chromium_flags ~/.config/microsoft-edge-stable-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Edge"
|
||||
@@ -67,7 +61,7 @@ brave)
|
||||
echo "Installing Brave..."
|
||||
omarchy-pkg-aur-add brave-bin || exit 1
|
||||
|
||||
setup_policy_directory /etc/brave/policies/managed
|
||||
setup_chromium_policy_directory /etc/brave/policies/managed
|
||||
copy_chromium_flags ~/.config/brave-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Brave"
|
||||
@@ -76,7 +70,7 @@ brave-origin)
|
||||
echo "Installing Brave Origin..."
|
||||
omarchy-pkg-aur-add brave-origin-bin || exit 1
|
||||
|
||||
setup_policy_directory /etc/brave/policies/managed
|
||||
setup_chromium_policy_directory /etc/brave/policies/managed
|
||||
copy_chromium_flags ~/.config/brave-origin-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Brave Origin"
|
||||
@@ -85,7 +79,7 @@ firefox)
|
||||
echo "Installing Firefox..."
|
||||
omarchy-pkg-add firefox || exit 1
|
||||
|
||||
setup_firefox_preferences /usr/lib/firefox/distribution
|
||||
browser_policy_setup_firefox_distribution /usr/lib/firefox/distribution
|
||||
setup_firefox_wayland
|
||||
announce_browser_installed "Firefox"
|
||||
;;
|
||||
@@ -93,7 +87,7 @@ zen)
|
||||
echo "Installing Zen..."
|
||||
omarchy-pkg-aur-add zen-browser-bin || exit 1
|
||||
|
||||
setup_firefox_preferences /opt/zen-browser/distribution
|
||||
browser_policy_setup_firefox_distribution /opt/zen-browser/distribution
|
||||
setup_firefox_wayland
|
||||
announce_browser_installed "Zen"
|
||||
;;
|
||||
|
||||
@@ -10,4 +10,4 @@ echo "Enabling ONCE background service..."
|
||||
sudo systemctl enable --now once-background.service
|
||||
|
||||
echo -e "\nLaunching ONCE..."
|
||||
once
|
||||
sudo once
|
||||
|
||||
@@ -96,6 +96,9 @@ while IFS=$'\t' read -r name file marker; do
|
||||
fi
|
||||
done < <(migration_entries)
|
||||
|
||||
# Here (not omarchy-update) so the first update from a pre-cn package runs them
|
||||
OMARCHY_PATH="$OMARCHY_PATH" omarchy-cn-migrate
|
||||
|
||||
# Clear a login-time notification the user left sitting there and then resolved
|
||||
# by running migrations some other way. The substring matches both the current
|
||||
# and legacy notification titles.
|
||||
|
||||
@@ -93,6 +93,13 @@ 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
|
||||
|
||||
|
||||
@@ -742,6 +742,12 @@ create_user() {
|
||||
# for specific commands), and a duplicate grant is harmless.
|
||||
echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-omarchy-wheel
|
||||
chmod 440 /etc/sudoers.d/00-omarchy-wheel
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
||||
[[ -d $dir || -L $dir ]] || continue
|
||||
browser_policy_setup_dir "$dir"
|
||||
done
|
||||
}
|
||||
|
||||
install_authorized_keys() {
|
||||
|
||||
@@ -24,9 +24,14 @@ echo -e "\e[32mRemoving FIDO2 device from authentication.\n\e[0m"
|
||||
|
||||
remove_pam_config
|
||||
|
||||
if [[ -d /etc/fido2 ]]; then
|
||||
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
|
||||
echo "Removing FIDO2 configuration..."
|
||||
sudo rm -rf /etc/fido2
|
||||
sudo rm -rf "$authdir"
|
||||
fi
|
||||
|
||||
echo "Removing FIDO2 packages..."
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
|
||||
check_fido2_hardware() {
|
||||
@@ -50,13 +51,79 @@ if ! check_fido2_hardware; then
|
||||
fi
|
||||
|
||||
# Create the pamu2fcfg file
|
||||
if [[ ! -f /etc/fido2/fido2 ]]; then
|
||||
sudo mkdir -p /etc/fido2
|
||||
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"
|
||||
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"
|
||||
|
||||
if pamu2fcfg >/tmp/fido2; then
|
||||
sudo mv /tmp/fido2 /etc/fido2/fido2
|
||||
# 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
|
||||
echo -e "\e[32mFIDO2 device registered successfully!\e[0m"
|
||||
else
|
||||
echo -e "\e[31m\nFIDO2 registration failed. Please try again.\e[0m"
|
||||
|
||||
@@ -16,14 +16,10 @@ if [[ -z $REPO_URL ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 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
|
||||
# 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
|
||||
|
||||
THEMES_DIR="$HOME/.config/omarchy/themes"
|
||||
|
||||
|
||||
@@ -3,23 +3,15 @@
|
||||
# omarchy:summary=Apply the current theme color to Chromium, Chrome, Edge, and Brave
|
||||
# omarchy:hidden=true
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
|
||||
CHROMIUM_THEME=$HOME/.local/state/omarchy/current/theme/chromium.theme
|
||||
THEME_HEX_COLOR=$BROWSER_POLICY_DEFAULT_COLOR
|
||||
|
||||
if [[ -f $CHROMIUM_THEME ]]; then
|
||||
THEME_RGB_COLOR=$(<$CHROMIUM_THEME)
|
||||
THEME_HEX_COLOR=$(printf '#%02x%02x%02x' ${THEME_RGB_COLOR//,/ })
|
||||
else
|
||||
# Use a default, neutral grey if theme doesn't have a color
|
||||
THEME_HEX_COLOR="#1c2027"
|
||||
THEME_HEX_COLOR=$(browser_policy_theme_hex "$(<$CHROMIUM_THEME)")
|
||||
fi
|
||||
|
||||
set_browser_policy() {
|
||||
local policy_dir="$1"
|
||||
|
||||
[[ -d $policy_dir ]] || return
|
||||
echo "{\"BrowserThemeColor\": \"$THEME_HEX_COLOR\", \"BrowserColorScheme\": \"device\"}" | tee "$policy_dir/color.json" >/dev/null
|
||||
}
|
||||
|
||||
refresh_running_browser() {
|
||||
local process="$1"
|
||||
local command="$2"
|
||||
@@ -30,17 +22,15 @@ refresh_running_browser() {
|
||||
fi
|
||||
}
|
||||
|
||||
set_browser_policy /etc/chromium/policies/managed
|
||||
failed=0
|
||||
omarchy-theme-set-browser-policy "${THEME_HEX_COLOR#\#}" || failed=1
|
||||
|
||||
refresh_running_browser chromium chromium
|
||||
|
||||
set_browser_policy /etc/opt/chrome/policies/managed
|
||||
refresh_running_browser chrome google-chrome-stable || refresh_running_browser chrome google-chrome
|
||||
|
||||
set_browser_policy /etc/opt/edge/policies/managed
|
||||
refresh_running_browser msedge microsoft-edge-stable
|
||||
|
||||
set_browser_policy /etc/brave/policies/managed
|
||||
refresh_running_browser brave brave
|
||||
# Match on the binary path: the running process is named plain "brave", and a
|
||||
# bare -f brave-origin pattern would also match the installer's own terminal.
|
||||
refresh_running_browser /opt/brave-origin-bin/ brave-origin -f
|
||||
|
||||
exit "$failed"
|
||||
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Write the current theme color into the browser policy directories
|
||||
# omarchy:args=<rrggbb>
|
||||
# omarchy:hidden=true
|
||||
|
||||
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 (printf's builtin aside: install, mktemp, rm) 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
|
||||
|
||||
# Enterprise policy trust roots. The list is fixed here rather than taken from
|
||||
# the caller: the caller chooses a color, never a path.
|
||||
POLICY_DIRS=(
|
||||
/etc/chromium/policies/managed
|
||||
/etc/opt/chrome/policies/managed
|
||||
/etc/opt/edge/policies/managed
|
||||
/etc/brave/policies/managed
|
||||
)
|
||||
|
||||
# The path etc/sudoers.d/omarchy-theme-browser names. The privileged half always
|
||||
# runs from there rather than from whichever copy was invoked, so the rule
|
||||
# matches even where $OMARCHY_PATH points at a checkout.
|
||||
PACKAGED_PATH=/usr/bin/omarchy-theme-set-browser-policy
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy-theme-set-browser-policy <rrggbb>" >&2
|
||||
}
|
||||
|
||||
if (( $# != 1 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
color="$1"
|
||||
|
||||
# Six lowercase hex digits is the whole of what this accepts. The leading "#"
|
||||
# is added when the JSON is written rather than passed in: "#" opens a comment
|
||||
# in sudoers, and keeping it out of argv lets the sudoers rule spell the
|
||||
# argument as a plain six-character glob.
|
||||
if [[ ! $color =~ ^[0-9a-f]{6}$ ]]; then
|
||||
echo "omarchy-theme-set-browser-policy: expected six lowercase hex digits, got '$color'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# True when sudo would run this exact command without stopping for a password.
|
||||
# `sudo -l` on its own reports whether a command is permitted, which the blanket
|
||||
# %wheel rule answers yes to for everything; the long listing prints the matched
|
||||
# entry's tags, so !authenticate is the grant in
|
||||
# etc/sudoers.d/omarchy-theme-browser and nothing else. Listing runs nothing
|
||||
# and, under -n, prompts for nothing.
|
||||
sudo_grants_passwordless() {
|
||||
sudo -n -l -l "$PACKAGED_PATH" "$@" 2>/dev/null | grep -q '!authenticate'
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if (( EUID == 0 )); then
|
||||
return
|
||||
elif [[ -t 0 ]] || sudo_grants_passwordless "$@"; then
|
||||
exec sudo "$PACKAGED_PATH" "$@"
|
||||
else
|
||||
exec pkexec "$PACKAGED_PATH" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
require_root "$color"
|
||||
|
||||
failed=0
|
||||
staged=""
|
||||
# Bash 5.3 makes the EXIT trap's last command decide the script's exit status,
|
||||
# so this handler must not end on a false test. Every successful run clears
|
||||
# staged, and a trailing `[[ -n $staged ]] && ...` would report that as failure.
|
||||
cleanup() {
|
||||
if [[ -n $staged ]]; then
|
||||
rm -f "$staged"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
for policy_dir in "${POLICY_DIRS[@]}"; do
|
||||
# Only browsers Omarchy has installed have a policy directory. Creating one
|
||||
# here would hand a browser a managed-policy root it does not otherwise have.
|
||||
[[ -d $policy_dir && ! -L $policy_dir ]] || continue
|
||||
|
||||
dest=$policy_dir/color.json
|
||||
staged=$(mktemp) || {
|
||||
failed=1
|
||||
continue
|
||||
}
|
||||
printf '{"BrowserThemeColor": "#%s", "BrowserColorScheme": "device"}\n' "$color" >"$staged"
|
||||
|
||||
if [[ -L $dest || -d $dest ]]; then
|
||||
if ! rm -rf -- "$dest"; then
|
||||
rm -f "$staged"
|
||||
staged=""
|
||||
echo "omarchy-theme-set-browser-policy: cannot replace $dest" >&2
|
||||
failed=1
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! install -m 0644 -o root -g root -T "$staged" "$dest"; then
|
||||
rm -f "$staged"
|
||||
staged=""
|
||||
echo "omarchy-theme-set-browser-policy: cannot write $dest" >&2
|
||||
failed=1
|
||||
continue
|
||||
fi
|
||||
|
||||
rm -f "$staged"
|
||||
staged=""
|
||||
done
|
||||
|
||||
exit "$failed"
|
||||
@@ -7,44 +7,70 @@
|
||||
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" ;;
|
||||
*)
|
||||
echo "Usage: omarchy-toggle-input-device <touchpad|touchscreen> [on|off|toggle]" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Hyprland sources this directory on reload, so the disabled state survives restarts
|
||||
STATE_FILE="$HOME/.local/state/omarchy/toggles/hypr/$KIND-disabled.lua"
|
||||
# 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"
|
||||
|
||||
device="$("omarchy-hw-$KIND")"
|
||||
|
||||
if [[ -z $device ]]; then
|
||||
echo "No $KIND device found" >&2
|
||||
exit 1
|
||||
fi
|
||||
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
|
||||
}
|
||||
|
||||
enable() {
|
||||
hyprctl eval "hl.device({ name = \"$device\", enabled = true })" >/dev/null
|
||||
rm -f "$STATE_FILE"
|
||||
# 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
|
||||
omarchy-osd -i "$ICON" -m "$LABEL enabled"
|
||||
}
|
||||
|
||||
disable() {
|
||||
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"
|
||||
require_device
|
||||
apply_device false
|
||||
mkdir -p "$(dirname "$NAME_FILE")"
|
||||
printf '%s\n' "$device" >"$NAME_FILE"
|
||||
omarchy-osd -i "$ICON" -m "$LABEL disabled"
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
on) enable ;;
|
||||
off) disable ;;
|
||||
toggle) if [[ -f $STATE_FILE ]]; then enable; else disable; fi ;;
|
||||
toggle) if [[ -f $NAME_FILE ]]; then enable; else disable; fi ;;
|
||||
*)
|
||||
echo "Usage: omarchy-toggle-input-device <touchpad|touchscreen> [on|off|toggle]" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1312,7 +1312,21 @@ apply_system_transition() {
|
||||
/usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg
|
||||
as_root gtk-update-icon-cache /usr/share/icons/Yaru >/dev/null 2>&1 || true
|
||||
|
||||
as_root install -d -m 0777 /etc/chromium/policies/managed
|
||||
local browser_policy_helper=/usr/share/omarchy/install/helpers/browser-policy.sh
|
||||
if ! as_root test -f "$browser_policy_helper"; then
|
||||
warn "$browser_policy_helper is unavailable; Chromium policy directories were not hardened."
|
||||
else
|
||||
as_root env OMARCHY_PATH=/usr/share/omarchy \
|
||||
bash -euo pipefail -c '
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
browser_policy_setup_dir /etc/chromium/policies/managed
|
||||
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
||||
[[ $dir == "/etc/chromium/policies/managed" ]] && continue
|
||||
[[ -d $dir || -L $dir ]] || continue
|
||||
browser_policy_setup_dir "$dir"
|
||||
done
|
||||
'
|
||||
fi
|
||||
as_root install -d -m 0755 /usr/lib/chromium
|
||||
printf '%s\n' '{"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' | \
|
||||
as_root tee /usr/lib/chromium/initial_preferences >/dev/null
|
||||
@@ -2302,6 +2316,11 @@ refresh_current_theme_after_upgrade() {
|
||||
# hooks because one of them runs `hyprctl reload`. Still poke terminal
|
||||
# emulators so the active upgrade terminal picks up generated theme files.
|
||||
run_as_user_omarchy omarchy-restart-terminal >/dev/null 2>&1 || true
|
||||
|
||||
# apply_system_transition purged user-owned color.json. Headless theme-set
|
||||
# skipped omarchy-theme-set-browser, so rewrite the colour here.
|
||||
run_as_user_omarchy omarchy-theme-set-browser >/dev/null 2>&1 ||
|
||||
warn "Could not apply browser theme colour. Run 'omarchy theme set \"$theme_name\"' after reboot if Chromium's theme looks stale."
|
||||
}
|
||||
|
||||
# Everything below mutates the system, so a non-zero exit from here on leaves a
|
||||
|
||||
@@ -13,6 +13,18 @@ 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
|
||||
@@ -68,6 +80,7 @@ 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"
|
||||
@@ -104,6 +117,8 @@ 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"
|
||||
@@ -132,8 +147,9 @@ fi
|
||||
EXEC_COMMAND="${CUSTOM_EXEC:-omarchy-launch-webapp $APP_URL}"
|
||||
|
||||
# Create application .desktop file
|
||||
DESKTOP_FILE="$HOME/.local/share/applications/$APP_NAME.desktop"
|
||||
mkdir -p "$(dirname "$DESKTOP_FILE")"
|
||||
DESKTOP_DIR="$HOME/.local/share/applications"
|
||||
DESKTOP_FILE="$DESKTOP_DIR/$APP_NAME.desktop"
|
||||
mkdir -p "$DESKTOP_DIR"
|
||||
|
||||
cat >"$DESKTOP_FILE" <<EOF
|
||||
[Desktop Entry]
|
||||
|
||||
@@ -9,14 +9,31 @@ ICON_DIR="$HOME/.local/share/icons/hicolor/256x256/apps"
|
||||
OLD_ICON_DIR="$HOME/.local/share/applications/icons"
|
||||
DESKTOP_DIR="$HOME/.local/share/applications/"
|
||||
|
||||
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)
|
||||
# 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
|
||||
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)
|
||||
@@ -34,7 +51,8 @@ if [[ -z $APP_NAME ]]; then
|
||||
fi
|
||||
|
||||
icon_name=$(printf '%s\n' "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//')
|
||||
rm -f "$DESKTOP_DIR/$APP_NAME.desktop"
|
||||
desktop_file=$(path_for_web_app "$APP_NAME")
|
||||
rm -f "${desktop_file:-$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
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- 每条 lang 规则成对出现:50-omarchy.conf 先以 assign 把 generic 名整体替换成
|
||||
Liberation Sans/Serif 与 JetBrainsMono Nerd Font(且先于本文件加载),
|
||||
所以除 generic 名外还必须测试替换后的具体家族,否则规则静默失效(Issue #4) -->
|
||||
|
||||
<!-- 日文/韩文标签内容保持原生字形(仅约束 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>ja</string></test>
|
||||
<test name="family"><string>Liberation Sans</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>ko</string></test>
|
||||
<test name="family"><string>Liberation Sans</string></test>
|
||||
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK KR</string></edit>
|
||||
</match>
|
||||
|
||||
<!-- 简体中文内容强制简体字形 -->
|
||||
<match target="pattern">
|
||||
@@ -19,16 +33,31 @@
|
||||
<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>Liberation Sans</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>Liberation 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>
|
||||
<match target="pattern">
|
||||
<test name="lang" compare="contains"><string>zh-cn</string></test>
|
||||
<test name="family"><string>JetBrainsMono Nerd Font</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">
|
||||
|
||||
+17
-1
@@ -31,6 +31,21 @@ cn_ai_harness_field() {
|
||||
jq -re --arg h "$1" --arg f "$2" '.harnesses[$h][$f]' "$CN_AI_HARNESSES"
|
||||
}
|
||||
|
||||
# Providers with a fixed token (local servers) need no stored secret.
|
||||
# `// empty` keeps stdout clean when absent (bare -re would print "null").
|
||||
cn_ai_static_token() {
|
||||
jq -re --arg p "$1" '.providers[$p].static_token // empty' "$CN_AI_PROVIDERS"
|
||||
}
|
||||
|
||||
cn_ai_resolve_key() {
|
||||
cn_ai_static_token "$1" 2>/dev/null || omarchy-cn-ai-secret get "$1"
|
||||
}
|
||||
|
||||
# Dynamic-model providers list models at runtime, not in the registry
|
||||
cn_ai_models_dynamic() {
|
||||
jq -e --arg p "$1" '.providers[$p].models_dynamic == true' "$CN_AI_PROVIDERS" > /dev/null
|
||||
}
|
||||
|
||||
cn_ai_combo_level() {
|
||||
jq -r --arg h "$1" --arg p "$2" '.combos[$h][$p].level // "unsupported"' "$CN_AI_COMPAT"
|
||||
}
|
||||
@@ -68,7 +83,8 @@ cn_ai_render_env() {
|
||||
case "$harness" in
|
||||
claude-code)
|
||||
base=$(cn_ai_endpoint "$provider" anthropic)
|
||||
fast=$(cn_ai_fast_model "$provider")
|
||||
# Dynamic-model providers have no fast alias: every tier maps to the model
|
||||
fast=$(cn_ai_fast_model "$provider" 2>/dev/null || printf '%s' "$model")
|
||||
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"
|
||||
|
||||
@@ -4,6 +4,22 @@
|
||||
CN_MIRRORS_JSON="$OMARCHY_PATH/cn/mirrors.json"
|
||||
CN_MIRRORLIST="/etc/pacman.d/mirrorlist"
|
||||
CN_PROFILE_FILE="$HOME/.config/omarchycn/mirror-profile"
|
||||
CN_OMARCHY_MIRROR="https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64"
|
||||
|
||||
# [omarchy] mirror line management (stable channel only); shared by the
|
||||
# rollout migration and doctor --fix so refresh-pacman resets stay healable
|
||||
cn_mirror_omarchy_repo_ok() {
|
||||
grep -qxF "Server = $CN_OMARCHY_MIRROR" "${OMARCHYCN_PACMAN_CONF:-/etc/pacman.conf}"
|
||||
}
|
||||
|
||||
cn_mirror_omarchy_repo_fix() {
|
||||
local conf="${OMARCHYCN_PACMAN_CONF:-/etc/pacman.conf}"
|
||||
# [omarchy] requires signed packages; the mirror re-signs with the registry key
|
||||
sudo pacman-key --add "$OMARCHY_PATH/cn/keys/omarchycn-registry.asc" > /dev/null || return 1
|
||||
sudo pacman-key --lsign-key 74DCF57ACD812B24D959F146BD386048867B33B4 > /dev/null || return 1
|
||||
cn_mirror_omarchy_repo_ok && return 0
|
||||
sudo sed -i "s|^Server = https://pkgs.omarchy.org/stable/|Server = $CN_OMARCHY_MIRROR\nServer = https://pkgs.omarchy.org/stable/|" "$conf"
|
||||
}
|
||||
|
||||
cn_mirror_ids() {
|
||||
jq -r '.mirrors[].id' "$CN_MIRRORS_JSON"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
echo "Trust the OmarchyCN registry key and put the [omarchy] mirror first"
|
||||
|
||||
# Only the stable channel is mirrored; edge/rc systems keep upstream directly
|
||||
source "$OMARCHY_PATH/cn/lib/mirror.sh"
|
||||
cn_mirror_omarchy_repo_fix
|
||||
@@ -0,0 +1,8 @@
|
||||
echo "Refresh the CJK fontconfig priority so zh-cn rules survive 50-omarchy.conf (Issue #4)"
|
||||
|
||||
# Only users who applied it before carry the broken copy
|
||||
conf="$HOME/.config/fontconfig/conf.d/64-omarchycn-cjk.conf"
|
||||
if [[ -f $conf ]]; then
|
||||
cp "$OMARCHY_PATH/cn/fontconfig/64-omarchycn-cjk.conf" "$conf"
|
||||
fc-cache -f > /dev/null
|
||||
fi
|
||||
@@ -21,6 +21,11 @@
|
||||
"level": "stable",
|
||||
"doc": "https://platform.minimax.io/docs/token-plan/claude-code",
|
||||
"adapter": true
|
||||
},
|
||||
"ollama": {
|
||||
"level": "stable",
|
||||
"doc": "https://docs.ollama.com/api/anthropic-compatibility",
|
||||
"adapter": true
|
||||
}
|
||||
},
|
||||
"codex": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"_verified": "2026-08-25, from official docs (see OmarchyCN PRD §27; minimax: platform.minimax.io/docs/token-plan/claude-code)",
|
||||
"_verified": "2026-08-27, from official docs (see OmarchyCN PRD §27; minimax: platform.minimax.io/docs/token-plan/claude-code; ollama: docs.ollama.com/api/anthropic-compatibility, fixed token \"ollama\", models are whatever is pulled locally)",
|
||||
"providers": {
|
||||
"deepseek": {
|
||||
"display_name": "DeepSeek",
|
||||
@@ -124,6 +124,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ollama": {
|
||||
"display_name": "Ollama 本地",
|
||||
"auth": [
|
||||
"none"
|
||||
],
|
||||
"static_token": "ollama",
|
||||
"endpoints": {
|
||||
"anthropic": "http://localhost:11434"
|
||||
},
|
||||
"models": [],
|
||||
"models_dynamic": true
|
||||
},
|
||||
"minimax": {
|
||||
"display_name": "MiniMax",
|
||||
"auth": [
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
5
|
||||
8
|
||||
|
||||
@@ -87,7 +87,38 @@ 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.
|
||||
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_.
|
||||
|
||||
## If it is an Omarchy bug
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
o.window(".*[Rr]esolve.*", {
|
||||
float = true,
|
||||
stay_focused = true,
|
||||
-- Prevent modal dialog pointer warps when focus follows the mouse.
|
||||
no_follow_mouse = true,
|
||||
tag = "-default-opacity",
|
||||
opacity = "1 1",
|
||||
})
|
||||
|
||||
o.window({ class = ".*[Rr]esolve.*", title = "^DaVinci Resolve( Studio)? - .+$" }, { fullscreen = true })
|
||||
o.window({ class = ".*[Rr]esolve.*", title = "^(DaVinci Resolve( Studio)? - .+|Project Manager)$" }, { stay_focused = false })
|
||||
-- Resolve exposes the Voiceover panel under the generic "Dialog" title.
|
||||
o.window({ class = ".*[Rr]esolve.*", title = "^(DaVinci Resolve( Studio)? - .+|Project Manager|Preferences|Find Directory|Dialog)$" }, { stay_focused = false })
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
-- 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
|
||||
+13
-3
@@ -4,9 +4,19 @@
|
||||
|
||||
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 = 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",
|
||||
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"),
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
-- 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 = {}
|
||||
|
||||
@@ -12,19 +14,23 @@ 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 module = filename:gsub("%.lua$", "")
|
||||
if module_prefix then
|
||||
module = module_prefix .. "." .. module
|
||||
end
|
||||
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
|
||||
if options and options.reload then
|
||||
package.loaded[module] = nil
|
||||
end
|
||||
|
||||
require(module)
|
||||
require(module)
|
||||
end
|
||||
end
|
||||
handle:close()
|
||||
end
|
||||
|
||||
@@ -4,6 +4,20 @@ local require_all = require("default.hypr.require_all")
|
||||
local toggles_dir = paths.state_home .. "/omarchy/toggles/hypr"
|
||||
package.path = toggles_dir .. "/?.lua;" .. package.path
|
||||
|
||||
require_all.files(toggles_dir, nil, { reload = true })
|
||||
-- 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("default.hypr.workspace-layouts")
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
"setup.default.agent.crush": {"icon":"","label":"Crush","checked":"[[ \"$(omarchy-default-agent)\" == \"crush\" ]]","action":"omarchy-default-agent crush"},
|
||||
"setup.default.agent.deepcode": {"icon":"","label":"Deep Code","checked":"[[ \"$(omarchy-default-agent)\" == \"deepcode\" ]]","action":"omarchy-default-agent deepcode"},
|
||||
"setup.default.agent.dim": {"icon":"","label":"Dim Agent","checked":"[[ \"$(omarchy-default-agent)\" == \"dim\" ]]","action":"omarchy-default-agent dim"},
|
||||
"setup.default.agent.dsh": {"icon":"","label":"DSH","checked":"[[ \"$(omarchy-default-agent)\" == \"dsh\" ]]","action":"omarchy-default-agent dsh"},
|
||||
"setup.default.agent.grok": {"icon":"","iconFont":"omarchy","label":"Grok","checked":"[[ \"$(omarchy-default-agent)\" == \"grok\" ]]","action":"omarchy-default-agent grok"},
|
||||
"setup.default.agent.kimi": {"icon":"","label":"Kimi Code","checked":"[[ \"$(omarchy-default-agent)\" == \"kimi\" ]]","action":"omarchy-default-agent kimi"},
|
||||
"setup.default.agent.omp": {"icon":"","iconFont":"omarchy","label":"omp","checked":"[[ \"$(omarchy-default-agent)\" == \"omp\" ]]","action":"omarchy-default-agent omp"},
|
||||
@@ -244,6 +245,7 @@
|
||||
"install.ai.deep-code": {"icon":"","label":"Deep Code","disabled":"omarchy-cmd-present deepcode","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-mise-install npm:@vegamo/deepcode-cli deepcode && deepcode'"},
|
||||
"install.ai.dictation": {"icon":"","label":"语音听写","disabled":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
|
||||
"install.ai.dim": {"icon":"","label":"Dim Agent","disabled":"omarchy-cmd-present dim","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-mise-install npm:dimcode dim && dim'"},
|
||||
"install.ai.dsh": {"icon":"","label":"DSH","disabled":"omarchy-cmd-present dsh","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-mise-install npm:@deepseek-ai/dsh dsh && dsh web'"},
|
||||
"install.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","disabled":"omarchy-pkg-present grok-bot","action":"omarchy-install-and-launch 'Grok Bot' grok-bot grok-bot"},
|
||||
"install.ai.kimi-code": {"icon":"","label":"Kimi Code","disabled":"omarchy-cmd-present kimi","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-kimi-install && kimi'"},
|
||||
"install.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","disabled":"omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
|
||||
@@ -415,6 +417,7 @@
|
||||
"omarchycn.diagnostics.doctor": {"icon":"","label":"系统诊断","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-doctor all'"},
|
||||
"omarchycn.diagnostics.mirror-fix": {"icon":"","label":"镜像修复","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-doctor mirror --fix'"},
|
||||
"omarchycn.diagnostics.ime": {"icon":"","label":"输入法状态","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-ime-status"},
|
||||
"omarchycn.update": {"icon":"","label":"更新","when":"[[ -f ~/.local/state/omarchycn/overlay-manifest ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-update"},
|
||||
"omarchycn.update": {"icon":"","label":"更新","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-update"},
|
||||
"omarchycn.revert": {"icon":"","label":"回退为原版 Omarchy","when":"[[ -f /var/lib/omarchycn/convert-state ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-revert"},
|
||||
"omarchycn.about": {"icon":"","label":"关于","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-status"},
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ 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,5 +26,4 @@ Include = /etc/pacman.d/mirrorlist
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[omarchy]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://pkgs.omarchy.org/edge/$arch
|
||||
|
||||
@@ -26,5 +26,6 @@ Include = /etc/pacman.d/mirrorlist
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[omarchy]
|
||||
SigLevel = Optional TrustAll
|
||||
# OmarchyCN mirror first (registry-signed db and packages, key via cn/keys), upstream fallback
|
||||
Server = https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64
|
||||
Server = https://pkgs.omarchy.org/stable/$arch
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
OmarchyCN 的 pacman 仓库托管在 Gitea Arch package registry,每个上传的包由
|
||||
registry 密钥自动签名,数据库同源生成。
|
||||
|
||||
# [omarchy] 上游仓库镜像
|
||||
|
||||
上游 `pkgs.omarchy.org`(Cloudflare 托管)的 stable 通道被完整镜像到同一 Gitea registry 的 `omarchy` 仓库。`packages/sync-omarchy-repo.sh` 按文件名对上游与镜像 db 做差集,缺失包转传、上游移除的按 name/version/arch 精确删除;`.gitea/workflows/pkg-repo-sync.yml` 每 6 小时调度一次(幂等,可手动 dispatch)。正常情况下镜像滞后不超过一个周期;调度失败或 runner 掉线时滞后会持续到下一次成功运行。注意 pacman 无法感知"陈旧":只要镜像可达,客户端就使用镜像的 db,看到的是滞后版本;只有镜像无法提供所请求的 db/包文件(宕机、404)时才回退到上游 Server。
|
||||
|
||||
客户端接线(`default/pacman/pacman-stable.conf`):`[omarchy]` 段镜像行在前、上游为兜底。镜像 db 由 registry 密钥签名,因此系统必须信任该公钥——安装期由 `install/post-install/pacman.sh` 导入,既有系统由 cn 迁移 `1787861071.sh` 处理。仅 stable 被镜像:Gitea 仓库名必须等于 pacman 段名(客户端固定请求 `omarchy.db`),edge/rc 通道保持上游直连。
|
||||
|
||||
`omarchy-refresh-pacman` 会用检出内模板覆写 `/etc/pacman.conf`:ISO/包安装系统的模板自带镜像行;overlay 系统的模板来自上游,覆写后镜像行丢失,用 `omarchycn doctor mirror --fix` 重新插入。
|
||||
|
||||
已知事实:上游仓库本身含 `omarchy`/`omarchy-dev` 包,当前版本(4.0.0.r1832)低于 cn 层自构建版本(r1955+)。常规 `pacman -Syu` 不会降级覆盖;但 `omarchy-refresh-pacman` 收尾运行的是 `pacman -Syyuu`,`-uu` 明确允许降级——在任何 cn 系统上执行都会把 `omarchy-dev` 降回上游版本、抹掉 cn 层(该风险先于本镜像存在,镜像只是如实转载上游内容)。若上游版本号超过 cn 构建,普通 `-Syu` 也会覆盖 cn 层——每次发布前需核对上游版本。
|
||||
|
||||
## 接入
|
||||
|
||||
```bash
|
||||
|
||||
+21
-11
@@ -2,26 +2,36 @@
|
||||
|
||||
每次发布 `<upstream>-cn.<n>` 按序执行,任一步失败即停止:
|
||||
|
||||
1. `./test/all`:相对上游基线零新增失败;`test/shell.d/omarchycn-*.sh` 全绿
|
||||
2. 用 `--local-source` 重建 ISO(`packages/omarchy-pkgs-cn.patch` 已应用到 omarchy-pkgs)
|
||||
3. 验证 omarchy-dev 包含 cn 层(`tar -tf … | grep usr/share/omarchy/cn/`)
|
||||
4. QEMU OVMF UEFI 冒烟:进入安装器欢迎屏
|
||||
5. `sha256sum` → `SHA256SUMS.txt`;签名 SUMS 与 ISO(发布子钥)
|
||||
6. SBOM 两份:syft(live airootfs)+ 离线仓库 .PKGINFO 采集
|
||||
7. `release.json`(版本、双向提交、包版本表、迁移列表、min_compatible、产物清单)→ 签名 release.json
|
||||
8. 建 tag 与 Release,上传全部产物,Release Notes 写明上游基线与已知问题
|
||||
9. 匿名回读已发布 ISO 并 sha256 复核 == 本地构建值
|
||||
10. `cn/release` 数字 +1,提交
|
||||
1. `cn/release` 写入本次发布号 `<n>`(上次 +1)并提交;此后所有构建产物自称 cn.`<n>`
|
||||
2. `./test/all`:相对上游基线零新增失败;`test/shell.d/omarchycn-*.sh` 全绿
|
||||
3. 用 `--local-source` 重建 ISO(`packages/omarchy-pkgs-cn.patch` 已应用到 omarchy-pkgs)
|
||||
4. 验证 omarchy-dev 包含 cn 层(`tar -tf … | grep usr/share/omarchy/cn/`)
|
||||
5. QEMU OVMF UEFI 冒烟:进入安装器欢迎屏
|
||||
6. `sha256sum` → `SHA256SUMS.txt`;签名 SUMS 与 ISO(发布子钥)
|
||||
7. SBOM 两份:syft(live airootfs)+ 离线仓库 .PKGINFO 采集
|
||||
8. `release.json`(版本、双向提交、包版本表、迁移列表、min_compatible、产物清单)→ 签名 release.json
|
||||
9. 把本次构建的 `omarchy-dev` / `omarchy-settings-dev` 发布到 [omarchycn] registry
|
||||
(`packages/publish-cn-packages.sh`;先于公开 Release,一键转换才不会装到旧包)
|
||||
10. ISO 上传 R2 下载站:`packages/upload-release-r2.sh <版本> <iso>`(脚本自带匿名回读 sha256 复核,
|
||||
并自动只保留最新 2 个版本目录;公网地址 `https://dl.zacharyzhang.com/<版本>/<文件名>`,
|
||||
凭据在 `~/omarchycn-build/.r2.env`)
|
||||
11. 建 tag 与 Release,上传其余小件产物(SUMS/签名/SBOM/release.json),Release Notes 写明上游基线、
|
||||
已知问题与 R2 ISO 下载链接;ISO 不再挂 Gitea 附件(带宽走 R2)
|
||||
12. 匿名回读 R2 ISO 与 Release 附件并 sha256 复核 == 本地构建值
|
||||
|
||||
## 版本规则
|
||||
|
||||
`<omarchy-upstream-version>-cn.<n>`;`min_compatible` 只在有破坏性迁移时前移。
|
||||
|
||||
`cn/release` 表示当前树所属的发布号:发布的第一步把它写成本次发布号,两次发布之间 HEAD 保持上一
|
||||
发布号(与上游 `version` 文件同语义)。发布之间构建并发到 [omarchycn] 的中间包因此自称上一发布号,
|
||||
不会出现自称未发布版本的产物。
|
||||
|
||||
# 上游同步 SLA
|
||||
|
||||
- `upstream-sync.yml` 每日拉取 basecamp/omarchy quattro,自动开同步 PR(含试合并冲突标注)
|
||||
- 常规变更:7 天内完成审查合并;上游安全修复:48 小时内
|
||||
- 合并后必须重跑第 1 步测试门禁
|
||||
- 合并后必须重跑第 2 步测试门禁
|
||||
|
||||
# 安全响应
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Theme switching is a menu action with no terminal to carry a password prompt,
|
||||
# and it repaints the browser accent on every switch, so this one write must not
|
||||
# stop for a password. The argument is spelled out as six hex digits rather than
|
||||
# a wildcard: the grant covers a color and nothing else, and sudoers matches a
|
||||
# command's arguments exactly, so it cannot be stretched into extra ones. The
|
||||
# helper revalidates the same shape, since the terminal path does not come
|
||||
# through this rule.
|
||||
%wheel ALL=(root) NOPASSWD: /usr/bin/omarchy-theme-set-browser-policy [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]
|
||||
@@ -1 +1 @@
|
||||
%wheel ALL=(root) NOPASSWD: /usr/bin/timedatectl set-timezone *
|
||||
%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_+.-]*)*$
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
run_logged "$OMARCHY_INSTALL/config/theme-system.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/browser-policy.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/increase-lockout-limit.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/lockscreen-pam.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/fix-powerprofilesctl-shebang.sh"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
browser_policy_setup_dir /etc/chromium/policies/managed
|
||||
@@ -6,10 +6,6 @@ ln -snf /usr/share/icons/Adwaita/symbolic/actions/go-next-symbolic.svg \
|
||||
/usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg
|
||||
gtk-update-icon-cache /usr/share/icons/Yaru &>/dev/null || true
|
||||
|
||||
# Chromium policy directory for theme
|
||||
mkdir -p /etc/chromium/policies/managed
|
||||
chmod a+rw /etc/chromium/policies/managed
|
||||
|
||||
# Default Chromium to follow system appearance ("device") instead of dark
|
||||
mkdir -p /usr/lib/chromium
|
||||
echo '{"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' > \
|
||||
|
||||
@@ -25,6 +25,10 @@ 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"
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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
|
||||
@@ -0,0 +1,7 @@
|
||||
as_root() {
|
||||
if (( EUID == 0 )); then
|
||||
"$@"
|
||||
else
|
||||
sudo "$@"
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
# Chromium-family machine policy is mandatory for every profile. Directories
|
||||
# stay 0755 root:root; omarchy-theme-set-browser-policy is the privileged
|
||||
# write for color.json.
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/as-root.sh"
|
||||
|
||||
BROWSER_POLICY_MANAGED_DIRS=(
|
||||
/etc/chromium/policies/managed
|
||||
/etc/opt/chrome/policies/managed
|
||||
/etc/opt/edge/policies/managed
|
||||
/etc/brave/policies/managed
|
||||
)
|
||||
|
||||
# Ancestors of the managed dirs, shortest first. A writable or attacker-owned
|
||||
# parent can rename the leaf aside; install -d follows a planted symlink.
|
||||
BROWSER_POLICY_PARENT_DIRS=(
|
||||
/etc/chromium
|
||||
/etc/chromium/policies
|
||||
/etc/opt/chrome
|
||||
/etc/opt/chrome/policies
|
||||
/etc/opt/edge
|
||||
/etc/opt/edge/policies
|
||||
/etc/brave
|
||||
/etc/brave/policies
|
||||
)
|
||||
|
||||
BROWSER_POLICY_FIREFOX_DIRS=(
|
||||
/usr/lib/firefox/distribution
|
||||
/opt/zen-browser/distribution
|
||||
)
|
||||
|
||||
BROWSER_POLICY_DEFAULT_COLOR="#1c2027"
|
||||
|
||||
browser_policy_purge_dir() {
|
||||
local dir=$1
|
||||
|
||||
as_root find "$dir" -mindepth 1 -maxdepth 1 ! -user root -exec rm -rf -- {} +
|
||||
}
|
||||
|
||||
browser_policy_parent_hardened() {
|
||||
local dir=$1
|
||||
|
||||
[[ -d $dir && ! -L $dir ]] || return 1
|
||||
[[ $(stat -c '%a' "$dir") == "755" ]] || return 1
|
||||
[[ $(stat -c '%U' "$dir") == "root" ]] || return 1
|
||||
}
|
||||
|
||||
browser_policy_dir_hardened() {
|
||||
browser_policy_parent_hardened "$1"
|
||||
}
|
||||
|
||||
browser_policy_parents_hardened() {
|
||||
local dir=$1
|
||||
local parent
|
||||
|
||||
for parent in "${BROWSER_POLICY_PARENT_DIRS[@]}"; do
|
||||
[[ $dir == "$parent"/* ]] || continue
|
||||
[[ -e $parent || -L $parent ]] || continue
|
||||
browser_policy_parent_hardened "$parent" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
browser_policy_setup_parent() {
|
||||
local dir=$1
|
||||
|
||||
if [[ -L $dir || ( -e $dir && ! -d $dir ) ]]; then
|
||||
as_root rm -rf -- "$dir"
|
||||
fi
|
||||
as_root install -d -m 0755 -o root -g root "$dir"
|
||||
}
|
||||
|
||||
browser_policy_setup_parents_for() {
|
||||
local dir=$1
|
||||
local parent
|
||||
|
||||
for parent in "${BROWSER_POLICY_PARENT_DIRS[@]}"; do
|
||||
[[ $dir == "$parent"/* ]] || continue
|
||||
browser_policy_setup_parent "$parent"
|
||||
done
|
||||
}
|
||||
|
||||
browser_policy_setup_dir() {
|
||||
local dir=$1
|
||||
|
||||
browser_policy_setup_parents_for "$dir"
|
||||
browser_policy_setup_parent "$dir"
|
||||
browser_policy_purge_dir "$dir"
|
||||
}
|
||||
|
||||
# Themes are user-installed. Accept only three 0-255 components.
|
||||
browser_policy_theme_hex() {
|
||||
local theme_rgb=$1
|
||||
|
||||
if [[ $theme_rgb =~ ^[[:space:]]*([0-9]{1,3})[[:space:]]*,[[:space:]]*([0-9]{1,3})[[:space:]]*,[[:space:]]*([0-9]{1,3})[[:space:]]*$ ]] &&
|
||||
(( 10#${BASH_REMATCH[1]} < 256 && 10#${BASH_REMATCH[2]} < 256 && 10#${BASH_REMATCH[3]} < 256 )); then
|
||||
printf '#%02x%02x%02x' "$((10#${BASH_REMATCH[1]}))" "$((10#${BASH_REMATCH[2]}))" "$((10#${BASH_REMATCH[3]}))"
|
||||
return
|
||||
fi
|
||||
|
||||
printf '%s' "$BROWSER_POLICY_DEFAULT_COLOR"
|
||||
}
|
||||
|
||||
browser_policy_install_color() {
|
||||
local policy_dir=$1
|
||||
local hex=$2
|
||||
local dest=$policy_dir/color.json
|
||||
local tmp
|
||||
|
||||
[[ -d $policy_dir && ! -L $policy_dir ]] || return 0
|
||||
[[ $hex =~ ^#[0-9a-f]{6}$ ]] || return 1
|
||||
|
||||
tmp=$(mktemp) || return 1
|
||||
printf '{"BrowserThemeColor": "%s", "BrowserColorScheme": "device"}\n' "$hex" >"$tmp"
|
||||
|
||||
if [[ -L $dest || -d $dest ]]; then
|
||||
if ! rm -rf -- "$dest" 2>/dev/null; then
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if install -m 0644 -T "$tmp" "$dest" 2>/dev/null; then
|
||||
rm -f "$tmp"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
}
|
||||
|
||||
browser_policy_firefox_policy_file_ok() {
|
||||
local file=$1
|
||||
local mode
|
||||
local group_write
|
||||
local other_write
|
||||
|
||||
[[ -f $file && ! -L $file ]] || return 1
|
||||
[[ $(stat -c '%U' "$file") == "root" ]] || return 1
|
||||
mode=$(stat -c '%a' "$file")
|
||||
group_write=$((8#${mode: -2:1}))
|
||||
other_write=$((8#${mode: -1}))
|
||||
(( (group_write & 2) == 0 && (other_write & 2) == 0 ))
|
||||
}
|
||||
|
||||
browser_policy_firefox_hardened() {
|
||||
local dir=$1
|
||||
|
||||
[[ -d $dir && ! -L $dir ]] || return 1
|
||||
[[ $(stat -c '%a' "$dir") == "755" ]] || return 1
|
||||
[[ $(stat -c '%U' "$dir") == "root" ]] || return 1
|
||||
browser_policy_firefox_policy_file_ok "$dir/policies.json"
|
||||
}
|
||||
|
||||
browser_policy_install_firefox_policies() {
|
||||
local distribution_dir=$1
|
||||
local policies=${2:-$OMARCHY_PATH/default/firefox/policies.json}
|
||||
|
||||
as_root install -m 644 -o root -g root -T "$policies" "$distribution_dir/policies.json"
|
||||
}
|
||||
|
||||
browser_policy_setup_firefox_distribution() {
|
||||
local distribution_dir=$1
|
||||
local policies=${2:-$OMARCHY_PATH/default/firefox/policies.json}
|
||||
|
||||
browser_policy_setup_parent "$distribution_dir"
|
||||
browser_policy_purge_dir "$distribution_dir"
|
||||
browser_policy_install_firefox_policies "$distribution_dir" "$policies"
|
||||
}
|
||||
@@ -61,6 +61,7 @@ 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
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
cp -f "$OMARCHY_PATH/default/pacman/pacman-${OMARCHY_MIRROR:-stable}.conf" /etc/pacman.conf
|
||||
cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-${OMARCHY_MIRROR:-stable}" /etc/pacman.d/mirrorlist
|
||||
|
||||
# The [omarchy] mirror db is signed by the OmarchyCN registry key
|
||||
pacman-key --add "$OMARCHY_PATH/cn/keys/omarchycn-registry.asc"
|
||||
pacman-key --lsign-key 74DCF57ACD812B24D959F146BD386048867B33B4
|
||||
|
||||
# omarchy-settings skips this override until cups-browsed is actually present
|
||||
# to avoid pacman creating cups-browsed.conf.pacnew during ISO package install.
|
||||
if [[ -f $OMARCHY_PATH/etc-overrides/cups-cups-browsed.conf && -d /etc/cups ]]; then
|
||||
|
||||
@@ -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 state is written back out as a small Lua file that Hyprland sources on startup.
|
||||
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 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,6 +39,8 @@ 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.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
### Apple M1/M2 chips
|
||||
|
||||
[Asahi Alarm](https://asahi-alarm.org/) is a version of Arch for Apple M1/M2 computers built on top of [Asahi Linux](https://asahilinux.org/). You can get Omarchy running on top of that with some effort. See [the user-driven guide](https://codeberg.org/malik-na/omarchy-mac).
|
||||
[Asahi Alarm](https://asahi-alarm.org/) is a version of Arch for Apple M1/M2 computers built on top of [Asahi Linux](https://asahilinux.org/). You can get Omarchy running on top of that with some effort. See [the user-driven guide](https://github.com/omarchy-mac/omarchy-mac).
|
||||
|
||||
### Apple Virtual Machine
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 下载与校验
|
||||
|
||||
从 [Releases](https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases) 下载最新 ISO 及 `SHA256SUMS.txt`、`SHA256SUMS.txt.asc`:
|
||||
ISO 从下载站 `https://dl.zacharyzhang.com/<版本>/<文件名>` 获取([Releases](https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases) 页面给出各版本的具体链接),`SHA256SUMS.txt`、`SHA256SUMS.txt.asc` 仍从 Releases 附件下载:
|
||||
|
||||
```bash
|
||||
curl -sSf https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/cn/keys/omarchycn-release.asc | gpg --import
|
||||
@@ -24,7 +24,27 @@ omarchycn setup
|
||||
|
||||
向导依次配置:语言、时区、显示缩放、中文 locale、中文字体、Fcitx5+Rime 输入法、输入法切换键、pacman 镜像、开发工具镜像、国内应用、AI Hub、隐私说明。每一步都可跳过,中断后重跑会从未完成的步骤继续。
|
||||
|
||||
## 现有 Omarchy 叠加安装(Overlay)
|
||||
## 原版 Omarchy 一键转换(包安装)
|
||||
|
||||
quattro 的原版 Omarchy 是包安装形态(`pacman -Q omarchy` 有输出),可以整机转换成 OmarchyCN,无需重装:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/bin/omarchy-cn-convert | bash
|
||||
```
|
||||
|
||||
转换做的事:信任 OmarchyCN registry 公钥并接入 [omarchycn] 仓库;把 `omarchy`/`omarchy-settings` 换成 OmarchyCN 构建的 `omarchy-dev`/`omarchy-settings-dev`(中文菜单、中文键位描述、OMARCHY CN 品牌、AI 全家桶随包生效);给 `[omarchy]` 仓库加自建镜像(Cloudflare 上游兜底);系统语言切到 zh_CN.UTF-8 并安装配置 Fcitx5+Rime;Arch 官方仓库自动测速切换国内镜像;刷新 OMARCHY CN 开机画面。重新登录后生效。
|
||||
|
||||
转换前的包名、语言与 mirrorlist 会记录在 `/var/lib/omarchycn/convert-state`,随时一键还原:
|
||||
|
||||
```bash
|
||||
omarchy cn revert
|
||||
```
|
||||
|
||||
还原会换回上游软件包、恢复语言与 mirrorlist、移除 [omarchycn] 仓库与镜像行、卸载转换时新装的输入法包,并恢复上游开机画面。菜单 OmarchyCN 区段也有「回退为原版 Omarchy」入口。
|
||||
|
||||
## 现有 Omarchy 叠加安装(Overlay,checkout 形态)
|
||||
|
||||
老的 checkout 安装(`~/.local/share/omarchy` 是 git 检出)用 Overlay 叠加,不走一键转换:
|
||||
|
||||
```bash
|
||||
git clone https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn.git
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
## 更新
|
||||
|
||||
- ISO 安装:系统随上游 `omarchy update`;cn 层当前随新版 ISO 迭代([omarchycn] pacman 仓库暂只分发 keyring,omarchy-dev 包上仓后将改为 pacman 更新)
|
||||
- Overlay 安装:`omarchycn update`(拉取源码 → 重装 overlay → 执行未跑过的 cn 迁移)
|
||||
所有安装形态都可以用 `omarchycn update`(即 `omarchy cn update`):
|
||||
|
||||
- ISO / 一键转换等包安装:完整更新管线(系统包 + [omarchycn] 仓库里的 cn 包 → 应用目录、镜像表、AI registry 随包更新 → 上游迁移与 cn 迁移);直接跑 `omarchy update` 效果相同(cn 迁移已并入其管线)
|
||||
- Overlay 安装:只更新 cn 层(拉取源码 → 重装 overlay → 执行未跑过的 cn 迁移);底下的 Omarchy 系统仍用 `omarchy update` 更新
|
||||
|
||||
## 备份位置
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
echo "Take ownership of the FIDO2 authfile so it cannot be rewritten without root"
|
||||
|
||||
authfile="/etc/fido2/fido2"
|
||||
|
||||
# omarchy-migrate records this migration as complete whenever it exits zero, so
|
||||
# a line printed here scrolls past once in the update terminal and is never
|
||||
# shown again. The states below cannot be repaired without deciding what to do
|
||||
# with a file we do not own, and they are exactly the ones where the authfile
|
||||
# may already be under someone else's control, so say so where it outlives the
|
||||
# scrollback as well.
|
||||
report_unrepairable() {
|
||||
echo " $1"
|
||||
echo " $2"
|
||||
omarchy-notification-send -u critical -g "FIDO2 authfile needs attention" "$1 $2" || true
|
||||
}
|
||||
|
||||
# Nothing to repair on any machine that never set FIDO2 up, which is almost all
|
||||
# of them. Checked before any sudo so those machines never see a password
|
||||
# prompt. -L as well as -e: a dangling symlink is invisible to -e.
|
||||
if [[ ! -L $authfile && ! -e $authfile ]]; then
|
||||
# Absence and "cannot look" are the same answer to the tests above. The old
|
||||
# setup created /etc/fido2 with `sudo mkdir -p`, which took the union of the
|
||||
# caller's umask and sudoers' 0022, so anyone registering under `umask 077`
|
||||
# left it mode 0700 with the user-owned authfile still inside. Escalate for
|
||||
# that case alone -- a machine that never set FIDO2 up has no directory here
|
||||
# and still reaches exit 0 without a password prompt. Not through a symlink:
|
||||
# chmod would act on whatever it points at.
|
||||
authdir=${authfile%/*}
|
||||
|
||||
if [[ -L $authdir || ! -d $authdir || -x $authdir ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Ask root whether a registration is behind it before touching the directory
|
||||
# itself. An aborted setup that left an empty 0700 directory, or one an
|
||||
# administrator deliberately keeps private, must not have its mode widened
|
||||
# and its group and special bits discarded for a repair it does not need.
|
||||
if ! sudo test -e "$authfile" && ! sudo test -L "$authfile"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sudo chmod 755 "$authdir"
|
||||
fi
|
||||
|
||||
# The old privileged move could install a symlink here if its fixed staging path
|
||||
# was redirected. Reported, not repaired: chown follows symlinks and would take
|
||||
# ownership of the target instead, and removing it would strip sudo and polkit
|
||||
# from anyone whose only credential is the token.
|
||||
if [[ -L $authfile ]]; then
|
||||
report_unrepairable "$authfile is a symlink, not a regular file." \
|
||||
"Leaving it alone. If you did not create it, remove it and re-run Setup > Security > Fido2."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# A directory or a device here is no more ours to rewrite than a symlink is,
|
||||
# and changing a directory's mode would alter an object we do not own.
|
||||
if [[ ! -f $authfile ]]; then
|
||||
report_unrepairable "$authfile is not a regular file." \
|
||||
"Leaving it alone. Remove it and re-run Setup > Security > Fido2."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Migration state is per-user, so every account re-runs this. The file's own
|
||||
# ownership is the state check: the second account finds the repair already
|
||||
# done and exits without escalating.
|
||||
owner=$(stat -c %U "$authfile" 2>/dev/null) || owner=""
|
||||
group=$(stat -c %G "$authfile" 2>/dev/null) || group=""
|
||||
mode=$(stat -c %a "$authfile" 2>/dev/null) || mode=""
|
||||
if [[ $owner == "root" && $group == "root" && $mode == "644" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Setup used to `mv` this in from /tmp, which carried the invoking user's
|
||||
# ownership into /etc. Root ownership stops that user from rewriting their own
|
||||
# PAM credential without root. Mode 644 keeps the public credential mapping
|
||||
# readable when pam_u2f opens an absolute authfile as the authenticating user.
|
||||
#
|
||||
# Rename a fresh copy over the path rather than chowning in place. A descriptor
|
||||
# opened while the file was still the user's own stays writable on that inode
|
||||
# through any later chmod or chown, since permission is checked at open(2), and
|
||||
# pam_u2f resolving the path would keep landing on it. Replacing the inode
|
||||
# leaves that descriptor writing to a file nothing reads.
|
||||
stage=""
|
||||
|
||||
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 " Could not create a safe staging file beside $authfile."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sudo install -T -m 644 -o root -g root "$authfile" "$stage"
|
||||
sudo mv -Tf "$stage" "$authfile"
|
||||
stage=""
|
||||
trap - EXIT
|
||||
@@ -0,0 +1,30 @@
|
||||
echo "Stop world-writable Chromium and Firefox policy directories"
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
|
||||
repaired=0
|
||||
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
||||
[[ -d $dir || -L $dir ]] || continue
|
||||
browser_policy_setup_dir "$dir"
|
||||
repaired=1
|
||||
done
|
||||
|
||||
# Repainting the policy color is cosmetic and the next theme change redoes it.
|
||||
# Under bash -euo pipefail a failure here would abort the migration before the
|
||||
# Firefox directories below are hardened, and the marker would never be written.
|
||||
if (( repaired )); then
|
||||
omarchy-theme-set-browser || true
|
||||
fi
|
||||
|
||||
for dir in "${BROWSER_POLICY_FIREFOX_DIRS[@]}"; do
|
||||
[[ -d $dir || -L $dir ]] || continue
|
||||
if browser_policy_firefox_hardened "$dir"; then
|
||||
browser_policy_purge_dir "$dir"
|
||||
continue
|
||||
fi
|
||||
browser_policy_setup_parent "$dir"
|
||||
browser_policy_purge_dir "$dir"
|
||||
if ! browser_policy_firefox_policy_file_ok "$dir/policies.json"; then
|
||||
browser_policy_install_firefox_policies "$dir"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,20 @@
|
||||
echo "Require signed packages from the Omarchy repository"
|
||||
|
||||
# The [omarchy] repo predates the Omarchy packaging key, so existing installs
|
||||
# carry a SigLevel override that also accepts unsigned packages. Packages are
|
||||
# signed now, so drop the override and let the repo inherit the global
|
||||
# SigLevel = Required DatabaseOptional like every other repo. Machine-wide and
|
||||
# self-detecting, so another user's rerun no-ops.
|
||||
omarchy_sig_override='SigLevel = Optional TrustAll'
|
||||
|
||||
if [[ -f /etc/pacman.conf ]] &&
|
||||
sed -n '/^\[omarchy\]/,/^\[/p' /etc/pacman.conf | grep -qxF "$omarchy_sig_override"; then
|
||||
# Requiring signatures with an untrusted packaging key would fail every
|
||||
# omarchy transaction, including the one that could repair it.
|
||||
if omarchy-pkg-missing omarchy-keyring ||
|
||||
! sudo pacman-key --list-keys 40DFB630FF42BCFFB047046CF0134EE680CAC571 &>/dev/null; then
|
||||
omarchy-update-keyring
|
||||
fi
|
||||
|
||||
sudo sed -i "/^\[omarchy\]/,/^\[/{/^$omarchy_sig_override$/d}" /etc/pacman.conf
|
||||
fi
|
||||
@@ -0,0 +1,39 @@
|
||||
echo "Store Hyprland input-device names as data instead of generated Lua"
|
||||
|
||||
# omarchy-toggle-input-device used to interpolate hyprctl device names into
|
||||
# hyprctl eval and a generated Lua file. Those names come from USB descriptors,
|
||||
# so recover the plain device name as data and delete the generated Lua. A name
|
||||
# that could have broken out of the old Lua string literal is discarded, not
|
||||
# trusted. The old script wrote to ~/.local/state regardless of XDG_STATE_HOME.
|
||||
toggles_dir="$HOME/.local/state/omarchy/toggles/hypr"
|
||||
|
||||
reapply=0
|
||||
|
||||
for kind in touchpad touchscreen; do
|
||||
state_file="$toggles_dir/$kind-disabled.lua"
|
||||
name_file="$toggles_dir/$kind-disabled-name"
|
||||
|
||||
[[ -f $state_file ]] || continue
|
||||
|
||||
if [[ ! -f $name_file && -r $state_file ]]; then
|
||||
old=$(<"$state_file")
|
||||
pattern='^hl\.device\(\{ name = "([^"\\[:cntrl:]]+)", enabled = false \}\)$'
|
||||
if [[ $old =~ $pattern ]]; then
|
||||
printf '%s\n' "${BASH_REMATCH[1]}" >"$name_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$state_file"
|
||||
|
||||
if [[ -f $name_file ]]; then
|
||||
reapply=1
|
||||
fi
|
||||
done
|
||||
|
||||
# The package hook reloads Hyprland before migrations run, so this session has
|
||||
# already dropped the disable: the generated Lua is no longer loaded and the
|
||||
# name file did not exist yet to replace it. Reload once more now that it does,
|
||||
# or the device the user switched off stays on until their next login.
|
||||
if (( reapply )); then
|
||||
hyprctl reload >/dev/null 2>&1 || true
|
||||
fi
|
||||
@@ -0,0 +1,6 @@
|
||||
echo "Enable Dell XPS 13 sidecar speaker amplifiers"
|
||||
|
||||
if omarchy-hw-dell-xps13-sidecar-amps; then
|
||||
source "$OMARCHY_PATH/install/hardware/dell-xps13-sidecar-amps.sh"
|
||||
omarchy-state set reboot-required
|
||||
fi
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# Publishes cn-built packages into the Gitea [omarchycn] Arch registry.
|
||||
# usage: publish-cn-packages.sh <pkg.tar.zst>... (maintainer/release step)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GITEA=${GITEA:-https://git.zacharyzhang.com}
|
||||
OWNER=${OWNER:-ZacharyZhang-NY}
|
||||
: "${PKG_SYNC_TOKEN:?PKG_SYNC_TOKEN (Gitea package-write token) is required}"
|
||||
(($# > 0)) || { echo "usage: publish-cn-packages.sh <pkg.tar.zst>..." >&2; exit 1; }
|
||||
|
||||
for pkg in "$@"; do
|
||||
[[ -f $pkg ]] || { echo "no such file: $pkg" >&2; exit 1; }
|
||||
# Token travels via curl config on stdin, never in the process argument list
|
||||
code=$(curl -sS --retry 2 -o /tmp/publish.out -w '%{http_code}' -X PUT -K - \
|
||||
--upload-file "$pkg" "$GITEA/api/packages/$OWNER/arch/omarchycn" \
|
||||
<<< "header = \"Authorization: token $PKG_SYNC_TOKEN\"")
|
||||
case "$code" in
|
||||
201) echo "published: ${pkg##*/}" ;;
|
||||
409) echo "already present: ${pkg##*/}" ;;
|
||||
*)
|
||||
echo "upload of ${pkg##*/} failed (HTTP $code): $(head -c 300 /tmp/publish.out)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
# Mirrors the upstream [omarchy] stable repo into the OmarchyCN Gitea Arch registry.
|
||||
# Maintainer/CI script (pkg-repo-sync.yml); needs a package-write token, network.
|
||||
|
||||
set -euo pipefail
|
||||
export LC_ALL=C
|
||||
|
||||
GITEA=${GITEA:-https://git.zacharyzhang.com}
|
||||
OWNER=${OWNER:-ZacharyZhang-NY}
|
||||
REPO=${REPO:-omarchy}
|
||||
UPSTREAM=${UPSTREAM:-https://pkgs.omarchy.org/stable/x86_64}
|
||||
: "${PKG_SYNC_TOKEN:?PKG_SYNC_TOKEN (Gitea package-write token) is required}"
|
||||
|
||||
registry="$GITEA/api/packages/$OWNER/arch"
|
||||
mirror="$registry/$REPO/x86_64"
|
||||
|
||||
workdir=$(mktemp -d)
|
||||
trap 'rm -rf "$workdir"' EXIT
|
||||
|
||||
# One "filename name version arch" line per package in a repo db (zstd or gzip tar)
|
||||
db_entries() {
|
||||
local db="$1" dir="$2" desc
|
||||
mkdir -p "$dir"
|
||||
tar --zstd -xf "$db" -C "$dir" 2>/dev/null || tar -xzf "$db" -C "$dir"
|
||||
for desc in "$dir"/*/desc; do
|
||||
[[ -f $desc ]] || continue
|
||||
awk '
|
||||
/^%FILENAME%$/ { getline; f = $0 }
|
||||
/^%NAME%$/ { getline; n = $0 }
|
||||
/^%VERSION%$/ { getline; v = $0 }
|
||||
/^%ARCH%$/ { getline; a = $0 }
|
||||
END {
|
||||
if (f == "" || n == "" || v == "" || a == "") {
|
||||
print "malformed desc: " FILENAME > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
print f, n, v, a
|
||||
}
|
||||
' "$desc"
|
||||
done
|
||||
}
|
||||
|
||||
curl -sSf --retry 3 -o "$workdir/upstream.db" "$UPSTREAM/omarchy.db"
|
||||
db_entries "$workdir/upstream.db" "$workdir/upstream" | sort > "$workdir/upstream.list"
|
||||
|
||||
# Only 404 means first sync; any other failure must not fake an empty mirror,
|
||||
# which would skip deletions and report a bogus success
|
||||
code=$(curl -sS --retry 3 -o "$workdir/mirror.db" -w '%{http_code}' "$mirror/$REPO.db" || true)
|
||||
[[ -z $code ]] && code=000
|
||||
case "$code" in
|
||||
200)
|
||||
db_entries "$workdir/mirror.db" "$workdir/mirror" | sort > "$workdir/mirror.list"
|
||||
;;
|
||||
404)
|
||||
: > "$workdir/mirror.list"
|
||||
;;
|
||||
*)
|
||||
echo "mirror db fetch failed (HTTP $code): $mirror/$REPO.db" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
comm -23 "$workdir/upstream.list" "$workdir/mirror.list" > "$workdir/to-add.list"
|
||||
comm -13 "$workdir/upstream.list" "$workdir/mirror.list" > "$workdir/to-remove.list"
|
||||
echo "upstream: $(wc -l < "$workdir/upstream.list") packages," \
|
||||
"mirror: $(wc -l < "$workdir/mirror.list")," \
|
||||
"to add: $(wc -l < "$workdir/to-add.list")," \
|
||||
"to remove: $(wc -l < "$workdir/to-remove.list")"
|
||||
|
||||
added=0
|
||||
while read -r filename name version arch; do
|
||||
echo "add: $filename"
|
||||
curl -sSf --retry 3 -o "$workdir/pkg" "$UPSTREAM/$filename"
|
||||
code=$(curl -sS --retry 2 -o "$workdir/put.out" -w '%{http_code}' -X PUT \
|
||||
-H "Authorization: token $PKG_SYNC_TOKEN" \
|
||||
--upload-file "$workdir/pkg" "$registry/$REPO")
|
||||
case "$code" in
|
||||
201) added=$((added + 1)) ;;
|
||||
409) echo " already present (409)" ;;
|
||||
*)
|
||||
echo "upload of $filename failed (HTTP $code): $(head -c 300 "$workdir/put.out")" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
rm -f "$workdir/pkg"
|
||||
done < "$workdir/to-add.list"
|
||||
|
||||
removed=0
|
||||
while read -r filename name version arch; do
|
||||
echo "remove: $filename"
|
||||
code=$(curl -sS --retry 2 -o "$workdir/del.out" -w '%{http_code}' -X DELETE \
|
||||
-H "Authorization: token $PKG_SYNC_TOKEN" \
|
||||
"$registry/$REPO/$name/$version/$arch")
|
||||
if [[ $code != 2?? && $code != 404 ]]; then
|
||||
echo "removal of $filename failed (HTTP $code): $(head -c 300 "$workdir/del.out")" >&2
|
||||
exit 1
|
||||
fi
|
||||
removed=$((removed + 1))
|
||||
done < "$workdir/to-remove.list"
|
||||
|
||||
echo "Sync complete: $added added, $removed removed"
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
# Upload release artifacts to the omarchycn-releases R2 bucket, then verify each
|
||||
# by anonymous public re-read (sha256 must match the local file).
|
||||
# Usage: upload-release-r2.sh <version> <file...>
|
||||
# Public URL shape: https://dl.zacharyzhang.com/<version>/<name>
|
||||
# Credentials: ~/omarchycn-build/.r2.env (rclone S3 env config, mode 0600)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BUCKET=omarchycn-releases
|
||||
PUBLIC=https://dl.zacharyzhang.com
|
||||
|
||||
version="${1:?usage: upload-release-r2.sh <version> <file...>}"
|
||||
shift
|
||||
(($#)) || { echo "no files given" >&2; exit 1; }
|
||||
|
||||
source "$HOME/omarchycn-build/.r2.env"
|
||||
|
||||
for f in "$@"; do
|
||||
[[ -f $f ]] || { echo "no such file: $f" >&2; exit 1; }
|
||||
name=$(basename "$f")
|
||||
echo "==> $name -> $PUBLIC/$version/$name"
|
||||
rclone copyto --s3-upload-cutoff 200M --s3-chunk-size 100M --retries 6 "$f" "r2:$BUCKET/$version/$name"
|
||||
want=$(sha256sum "$f" | cut -d' ' -f1)
|
||||
got=$(curl -fsSL "$PUBLIC/$version/$name" | sha256sum | cut -d' ' -f1)
|
||||
if [[ $want != "$got" ]]; then
|
||||
echo "sha256 mismatch for $name: local $want public $got" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "verified $want"
|
||||
done
|
||||
|
||||
# Keep the newest two release prefixes (current + rollback), purge older ones
|
||||
keep=2
|
||||
mapfile -t versions < <(rclone lsf --dirs-only "r2:$BUCKET" | sed 's|/$||' | sort -V)
|
||||
if ((${#versions[@]} > keep)); then
|
||||
for old in "${versions[@]:0:${#versions[@]}-keep}"; do
|
||||
echo "==> purge old release $old"
|
||||
rclone purge "r2:$BUCKET/$old"
|
||||
done
|
||||
fi
|
||||
@@ -138,6 +138,7 @@ BorderSurface {
|
||||
radius: 0
|
||||
}
|
||||
contentItem: Text {
|
||||
textFormat: Text.PlainText
|
||||
text: root.tooltipText
|
||||
color: root.tooltipForeground
|
||||
font.family: root.fontFamily
|
||||
@@ -158,6 +159,7 @@ BorderSurface {
|
||||
spacing: Style.spacing.controlGap
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.iconText !== ""
|
||||
text: root.iconText
|
||||
color: root.selected ? root._selectedColor : root.foreground
|
||||
@@ -177,6 +179,7 @@ BorderSurface {
|
||||
}
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.text !== ""
|
||||
text: root.text
|
||||
color: root.selected ? root._selectedColor : root.foreground
|
||||
|
||||
@@ -69,6 +69,7 @@ Item {
|
||||
|
||||
Text {
|
||||
id: messageText
|
||||
textFormat: Text.PlainText
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
@@ -105,6 +106,7 @@ Item {
|
||||
radius: 0
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
color: destructive ? (selected ? Color.urgent : root.foreground) : (selected ? root.selectedText : root.foreground)
|
||||
|
||||
@@ -71,6 +71,7 @@ Item {
|
||||
spacing: Style.spacing.labelGap
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.showLabel && root.label !== ""
|
||||
text: root.label
|
||||
color: Qt.darker(root.foreground, 1.4)
|
||||
@@ -110,6 +111,7 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.left: parent.left
|
||||
anchors.right: chevron.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -214,6 +216,7 @@ Item {
|
||||
: "transparent"
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -259,6 +259,7 @@ Item {
|
||||
spacing: Style.spacing.labelGap
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.showLabel && root.label !== ""
|
||||
text: root.label
|
||||
color: Qt.darker(root.foreground, 1.4)
|
||||
@@ -298,6 +299,7 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.left: parent.left
|
||||
anchors.right: chevron.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -451,6 +453,7 @@ Item {
|
||||
: Border.controlSpec("normal", root.foreground, root.accent)
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.centerIn: parent
|
||||
text: root.loadingOptions ? "" : ""
|
||||
color: root.foreground
|
||||
@@ -486,6 +489,7 @@ Item {
|
||||
height: popup.height - searchHeader.height - Style.spacing.xxs - 1
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.centerIn: parent
|
||||
visible: resultList.count === 0
|
||||
text: root.loadingOptions ? "Loading…" : (root.optionsError !== "" ? root.optionsError : root.emptyText)
|
||||
@@ -581,6 +585,7 @@ Item {
|
||||
spacing: Style.spacing.xxs
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
text: modelData.label
|
||||
color: index === resultList.currentIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground
|
||||
font.family: root.fontFamily
|
||||
@@ -589,6 +594,7 @@ Item {
|
||||
width: parent.width
|
||||
}
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: text !== ""
|
||||
text: modelData.description
|
||||
color: Qt.darker(root.foreground, 1.5)
|
||||
|
||||
@@ -25,6 +25,7 @@ Column {
|
||||
spacing: Style.spacing.md
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.label !== ""
|
||||
text: root.label
|
||||
color: Qt.darker(root.foreground, 1.4)
|
||||
|
||||
@@ -25,6 +25,7 @@ Item {
|
||||
|
||||
Text {
|
||||
id: glyph
|
||||
textFormat: Text.PlainText
|
||||
// Keep the shared line box and baseline intact. Correcting only the
|
||||
// horizontal painted bounds avoids per-glyph vertical drift.
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -69,6 +69,7 @@ BorderSurface {
|
||||
Behavior on color { ColorAnimation { duration: 60 } }
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.centerIn: parent
|
||||
text: root.iconText
|
||||
color: root.enabled
|
||||
|
||||
@@ -48,6 +48,7 @@ Item {
|
||||
width: parent.width
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.title !== ""
|
||||
text: root.title
|
||||
width: Math.min(implicitWidth, Math.max(0, parent.width - (detailPill.visible ? detailPill.implicitWidth + Style.space(8) : 0)))
|
||||
@@ -75,6 +76,7 @@ Item {
|
||||
|
||||
Text {
|
||||
id: detailText
|
||||
textFormat: Text.PlainText
|
||||
anchors.centerIn: parent
|
||||
text: root.detail
|
||||
color: root.dim
|
||||
@@ -87,6 +89,7 @@ Item {
|
||||
|
||||
Text {
|
||||
id: metaText
|
||||
textFormat: Text.PlainText
|
||||
width: parent.width
|
||||
text: root.meta.toUpperCase()
|
||||
visible: text !== ""
|
||||
|
||||
@@ -11,6 +11,10 @@ Text {
|
||||
property string fontFamily: Style.font.family
|
||||
property real fontSize: Style.font.caption
|
||||
|
||||
// Callers bind `text` from outside this file, so the default has to be set
|
||||
// here. AutoText would let a section title that happens to carry a device or
|
||||
// network name promote itself to rich text.
|
||||
textFormat: Text.PlainText
|
||||
color: Qt.darker(foreground, 1.4)
|
||||
font.family: fontFamily
|
||||
font.pixelSize: fontSize
|
||||
|
||||
@@ -36,6 +36,7 @@ ToolTip {
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
textFormat: Text.PlainText
|
||||
text: root.text
|
||||
color: root.panelForeground
|
||||
font.family: root.fontFamily
|
||||
|
||||
@@ -93,6 +93,7 @@ Item {
|
||||
spacing: Style.spacing.labelGap
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.showLabel && root.label !== ""
|
||||
text: root.label
|
||||
color: Qt.darker(root.foreground, 1.4)
|
||||
@@ -132,6 +133,7 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.left: parent.left
|
||||
anchors.right: chevron.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -246,6 +248,7 @@ Item {
|
||||
height: popup.height - searchHeader.height - Style.spacing.xxs - 1
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.centerIn: parent
|
||||
visible: resultList.count === 0
|
||||
text: root.emptyText
|
||||
@@ -313,6 +316,7 @@ Item {
|
||||
spacing: Style.spacing.xxs
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
text: root.optionLabel(modelData)
|
||||
color: index === resultList.currentIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground
|
||||
font.family: root.fontFamily
|
||||
@@ -321,6 +325,7 @@ Item {
|
||||
width: parent.width
|
||||
}
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: text !== ""
|
||||
text: root.optionDescription(modelData)
|
||||
color: Qt.darker(root.foreground, 1.5)
|
||||
|
||||
@@ -130,6 +130,7 @@ PanelWindow {
|
||||
spacing: Style.space(16)
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.title !== ""
|
||||
text: root.title.toUpperCase()
|
||||
color: root.onScrimDim
|
||||
@@ -182,6 +183,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.failed
|
||||
text: root.error
|
||||
color: root.onScrimUrgent
|
||||
@@ -368,6 +370,7 @@ PanelWindow {
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
// Both branches go through the locale: a reading is a measurement, so
|
||||
// its separators follow the system's number conventions rather than the
|
||||
@@ -383,6 +386,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: root.unit
|
||||
color: root.onScrimDim
|
||||
@@ -394,6 +398,7 @@ PanelWindow {
|
||||
// The 90° gap at the bottom of the scale is where a cluster prints its
|
||||
// unit; here it names the direction.
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
text: dial.label
|
||||
|
||||
@@ -69,6 +69,7 @@ BorderSurface {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
text: root.label
|
||||
color: root.foreground
|
||||
font.family: root.fontFamily
|
||||
@@ -79,6 +80,7 @@ BorderSurface {
|
||||
}
|
||||
|
||||
Text {
|
||||
textFormat: Text.PlainText
|
||||
visible: root.description !== ""
|
||||
text: root.description
|
||||
color: Qt.darker(root.foreground, 1.5)
|
||||
|
||||
@@ -74,6 +74,7 @@ Item {
|
||||
|
||||
Text {
|
||||
id: label
|
||||
textFormat: Text.PlainText
|
||||
visible: root.labelVisible
|
||||
anchors.centerIn: parent
|
||||
text: root.text
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user