From 8dab891155d01b544d2ff9cc6711b55fdecd9a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 24 Aug 2026 18:42:29 -0400 Subject: [PATCH] Add dev-mirror manager for npm, pip, cargo, go, gem, docker Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp --- bin/omarchy-cn-dev-mirror-apply | 36 +++++++++ bin/omarchy-cn-dev-mirror-doctor | 32 ++++++++ bin/omarchy-cn-dev-mirror-list | 14 ++++ bin/omarchy-cn-dev-mirror-set | 20 +++++ cn/dev-mirrors.json | 26 +++++++ cn/lib/dev-mirror.sh | 126 +++++++++++++++++++++++++++++++ 6 files changed, 254 insertions(+) create mode 100755 bin/omarchy-cn-dev-mirror-apply create mode 100755 bin/omarchy-cn-dev-mirror-doctor create mode 100755 bin/omarchy-cn-dev-mirror-list create mode 100755 bin/omarchy-cn-dev-mirror-set create mode 100644 cn/dev-mirrors.json create mode 100644 cn/lib/dev-mirror.sh diff --git a/bin/omarchy-cn-dev-mirror-apply b/bin/omarchy-cn-dev-mirror-apply new file mode 100755 index 00000000..0f0232c0 --- /dev/null +++ b/bin/omarchy-cn-dev-mirror-apply @@ -0,0 +1,36 @@ +#!/bin/bash +# omarchy:summary=Apply china or official registry profile to development tools +# omarchy:args= [--target npm,pip,cargo,go,gem,docker] +# omarchy:examples=omarchycn dev-mirror apply china | omarchycn dev-mirror apply official --target npm,pip + +set -euo pipefail + +source "$OMARCHY_PATH/cn/lib/dev-mirror.sh" + +profile="${1:?usage: omarchycn dev-mirror apply [--target a,b]}" +shift + +if [[ $profile != "china" && $profile != "official" ]]; then + echo "Unknown profile: $profile (expected china or official)" >&2 + exit 1 +fi + +targets=("${CN_DM_TARGETS[@]}") +if [[ ${1:-} == "--target" ]]; then + IFS=',' read -ra targets <<<"${2:?--target needs a comma-separated list}" +fi + +stamp=$(date +%Y%m%d-%H%M%S) + +for target in "${targets[@]}"; do + if [[ ! " ${CN_DM_TARGETS[*]} " == *" $target "* ]]; then + echo "Unknown target: $target (known: ${CN_DM_TARGETS[*]})" >&2 + exit 1 + fi + url=$(cn_dm_url "$target" "$profile") + cn_dm_backup "$(cn_dm_config_file "$target")" "$stamp" + "cn_dm_set_$target" "$url" + echo "$target -> ${url:-}" +done + +echo "Backups (if any): $CN_DM_BACKUP_ROOT/$stamp" diff --git a/bin/omarchy-cn-dev-mirror-doctor b/bin/omarchy-cn-dev-mirror-doctor new file mode 100755 index 00000000..07f548fc --- /dev/null +++ b/bin/omarchy-cn-dev-mirror-doctor @@ -0,0 +1,32 @@ +#!/bin/bash +# omarchy:summary=Check reachability of each configured development registry +# omarchy:examples=omarchycn dev-mirror doctor + +set -euo pipefail + +source "$OMARCHY_PATH/cn/lib/dev-mirror.sh" + +failures=0 + +for target in "${CN_DM_TARGETS[@]}"; do + current=$("cn_dm_get_$target") + if [[ -z $current ]]; then + echo "INFO $target: using tool default" + continue + fi + + # Reduce to a probe-able https URL + probe="${current#sparse+}" + probe="${probe%%,*}" + if curl -sSf -o /dev/null -m 8 --connect-timeout 5 "$probe" 2>/dev/null; then + echo "PASS $target: $current" + elif curl -sS -o /dev/null -m 8 --connect-timeout 5 -w '%{http_code}' "$probe" 2>/dev/null | grep -qE '^[34]'; then + # Registry roots often answer 3xx/404 to bare GET while the service works + echo "PASS $target: $current" + else + echo "FAIL $target: $current unreachable" + failures=$((failures + 1)) + fi +done + +exit $((failures > 0 ? 1 : 0)) diff --git a/bin/omarchy-cn-dev-mirror-list b/bin/omarchy-cn-dev-mirror-list new file mode 100755 index 00000000..45c8b354 --- /dev/null +++ b/bin/omarchy-cn-dev-mirror-list @@ -0,0 +1,14 @@ +#!/bin/bash +# omarchy:summary=Show configured registry for each development ecosystem +# omarchy:examples=omarchycn dev-mirror list + +set -euo pipefail + +source "$OMARCHY_PATH/cn/lib/dev-mirror.sh" + +printf '%-8s %-52s %s\n' "TARGET" "CURRENT" "CONFIG" + +for target in "${CN_DM_TARGETS[@]}"; do + current=$("cn_dm_get_$target") + printf '%-8s %-52s %s\n' "$target" "${current:-}" "$(cn_dm_config_file "$target")" +done diff --git a/bin/omarchy-cn-dev-mirror-set b/bin/omarchy-cn-dev-mirror-set new file mode 100755 index 00000000..2a09f247 --- /dev/null +++ b/bin/omarchy-cn-dev-mirror-set @@ -0,0 +1,20 @@ +#!/bin/bash +# omarchy:summary=Point one development ecosystem at a custom registry URL +# omarchy:args= +# omarchy:examples=omarchycn dev-mirror set npm https://registry.example.com + +set -euo pipefail + +source "$OMARCHY_PATH/cn/lib/dev-mirror.sh" + +target="${1:?usage: omarchycn dev-mirror set }" +url="${2:?usage: omarchycn dev-mirror set }" + +if [[ ! " ${CN_DM_TARGETS[*]} " == *" $target "* ]]; then + echo "Unknown target: $target (known: ${CN_DM_TARGETS[*]})" >&2 + exit 1 +fi + +cn_dm_backup "$(cn_dm_config_file "$target")" "$(date +%Y%m%d-%H%M%S)" +"cn_dm_set_$target" "$url" +echo "$target -> $url" diff --git a/cn/dev-mirrors.json b/cn/dev-mirrors.json new file mode 100644 index 00000000..b172483e --- /dev/null +++ b/cn/dev-mirrors.json @@ -0,0 +1,26 @@ +{ + "npm": { + "china": "https://registry.npmmirror.com", + "official": "https://registry.npmjs.org/" + }, + "pip": { + "china": "https://pypi.tuna.tsinghua.edu.cn/simple", + "official": "https://pypi.org/simple" + }, + "cargo": { + "china": "sparse+https://rsproxy.cn/index/", + "official": "sparse+https://index.crates.io/" + }, + "go": { + "china": "https://goproxy.cn,direct", + "official": "https://proxy.golang.org,direct" + }, + "gem": { + "china": "https://mirrors.tuna.tsinghua.edu.cn/rubygems/", + "official": "https://rubygems.org/" + }, + "docker": { + "china": "https://docker.m.daocloud.io", + "official": "" + } +} diff --git a/cn/lib/dev-mirror.sh b/cn/lib/dev-mirror.sh new file mode 100644 index 00000000..66adb8eb --- /dev/null +++ b/cn/lib/dev-mirror.sh @@ -0,0 +1,126 @@ +# shellcheck shell=bash +# Sourced helpers for omarchy-cn-dev-mirror-* commands + +CN_DEV_MIRRORS_JSON="$OMARCHY_PATH/cn/dev-mirrors.json" +CN_DM_TARGETS=(npm pip cargo go gem docker) +CN_DM_BACKUP_ROOT="$HOME/.local/state/omarchycn/backups/dev-mirror" + +cn_dm_url() { + jq -re --arg t "$1" --arg p "$2" '.[$t][$p]' "$CN_DEV_MIRRORS_JSON" +} + +cn_dm_backup() { + local file="$1" + local stamp="$2" + + if [[ -f $file ]]; then + mkdir -p "$CN_DM_BACKUP_ROOT/$stamp" + cp "$file" "$CN_DM_BACKUP_ROOT/$stamp/${file##*/}" + fi +} + +# Replace-or-append one key=value style line matched by a regex +cn_dm_set_line() { + local file="$1" match="$2" line="$3" + + mkdir -p "${file%/*}" + touch "$file" + if grep -qE "$match" "$file"; then + sed -i -E "s|$match.*|$line|" "$file" + else + echo "$line" >> "$file" + fi +} + +cn_dm_get_npm() { grep -sE '^registry=' "$HOME/.npmrc" | cut -d= -f2- || true; } +cn_dm_set_npm() { cn_dm_set_line "$HOME/.npmrc" '^registry=' "registry=$1"; } + +cn_dm_get_pip() { + python3 - <<'EOF' +import configparser, os +c = configparser.ConfigParser() +c.read(os.path.expanduser("~/.config/pip/pip.conf")) +print(c.get("global", "index-url", fallback="")) +EOF +} + +cn_dm_set_pip() { + CN_DM_PIP_URL="$1" python3 - <<'EOF' +import configparser, os +path = os.path.expanduser("~/.config/pip/pip.conf") +c = configparser.ConfigParser() +c.read(path) +if not c.has_section("global"): + c.add_section("global") +c.set("global", "index-url", os.environ["CN_DM_PIP_URL"]) +os.makedirs(os.path.dirname(path), exist_ok=True) +with open(path, "w") as f: + c.write(f) +EOF +} + +cn_dm_get_cargo() { + local f="$HOME/.cargo/config.toml" + grep -sA1 '^\[source\.omarchycn\]' "$f" | grep -sE '^registry' | cut -d\" -f2 || true +} + +cn_dm_set_cargo() { + local url="$1" f="$HOME/.cargo/config.toml" + + mkdir -p "${f%/*}" + touch "$f" + sed -i '/^# OmarchyCN dev-mirror begin$/,/^# OmarchyCN dev-mirror end$/d' "$f" + if [[ -n $url ]]; then + cat >> "$f" < "$HOME/.gemrc" </dev/null || true +} + +cn_dm_set_docker() { + local url="$1" current="{}" + + if sudo test -f /etc/docker/daemon.json; then + current=$(sudo cat /etc/docker/daemon.json) + fi + if [[ -n $url ]]; then + jq --arg u "$url" '."registry-mirrors" = [$u]' <<<"$current" | sudo tee /etc/docker/daemon.json > /dev/null + else + jq 'del(."registry-mirrors")' <<<"$current" | sudo tee /etc/docker/daemon.json > /dev/null + fi + echo "docker: restart the docker daemon to take effect" +} + +cn_dm_config_file() { + case "$1" in + npm) echo "$HOME/.npmrc" ;; + pip) echo "$HOME/.config/pip/pip.conf" ;; + cargo) echo "$HOME/.cargo/config.toml" ;; + go) echo "$HOME/.config/go/env" ;; + gem) echo "$HOME/.gemrc" ;; + docker) echo "/etc/docker/daemon.json" ;; + esac +}