feat: add nosudo

This commit is contained in:
2026-06-25 10:46:07 +08:00
parent 0e7fe55474
commit e8aae9738b
9 changed files with 358 additions and 55 deletions
+53 -51
View File
@@ -20,7 +20,7 @@
"vim.normalModeKeyBindingsNonRecursive": [
{
"before": ["<space>"],
"commands": ["vspacecode.space"],
"commands": ["vspacecode.space"]
},
{
"before": [","],
@@ -28,15 +28,15 @@
"vspacecode.space",
{
"command": "whichkey.triggerKey",
"args": "m",
},
],
},
"args": "m"
}
]
}
],
"vim.visualModeKeyBindingsNonRecursive": [
{
"before": ["<space>"],
"commands": ["vspacecode.space"],
"commands": ["vspacecode.space"]
},
{
"before": [","],
@@ -44,18 +44,18 @@
"vspacecode.space",
{
"command": "whichkey.triggerKey",
"args": "m",
},
],
"args": "m"
}
]
},
{
"before": [">"],
"commands": ["editor.action.indentLines"],
"commands": ["editor.action.indentLines"]
},
{
"before": ["<"],
"commands": ["editor.action.outdentLines"],
},
"commands": ["editor.action.outdentLines"]
}
],
"workbench.panel.location": "right",
"gitHistory.hideCommitViewExplorer": true,
@@ -67,16 +67,16 @@
"sync.gist": "3c4e02a02a0d110622006b8c8b058fa5",
"go.languageServerFlags": ["-rpc.trace"],
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features",
"editor.defaultFormatter": "vscode.json-language-features"
},
"files.associations": {
"*.html": "html",
"*.json": "jsonc",
"NOTES.txt": "helm",
"Dockerfile": "dockerfile",
"Dockerfile": "dockerfile"
},
"[helm]": {
"editor.defaultFormatter": "redhat.vscode-yaml",
"editor.defaultFormatter": "redhat.vscode-yaml"
// "editor.formatOnSave": false // alternatively, force it to leave you alone
},
"gitlens.codeLens.recentChange.enabled": false,
@@ -87,19 +87,19 @@
// "terminal.integrated.shell.osx": "/bin/zsh",
"editor.fontFamily": "Fira Code, Sarasa Fixed SC Nerd Font, Menlo, Monaco, 'Courier New', monospace",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"search.followSymlinks": false,
"go.autocompleteUnimportedPackages": true,
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"vscode-neovim.mouseSelectionStartVisualMode": true,
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--config=~/.dotfile/golang/golangci.yml"],
"go.liveErrors": {
"enabled": false,
"delay": 500,
"delay": 500
},
"python.languageServer": "Pylance",
"go.overwriteGoplsMiddleware": {},
@@ -107,20 +107,20 @@
"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.organizeImports": "explicit"
},
"editor.snippetSuggestions": "none",
"editor.defaultFormatter": "golang.go",
"editor.defaultFormatter": "golang.go"
},
"[go.mod]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
},
"source.organizeImports": "explicit"
}
},
"gopls": {
"usePlaceholders": true,
"staticcheck": false,
"staticcheck": false
},
"go.testFlags": ["-v"],
"vscode-neovim.neovimExecutablePaths.darwin": "/usr/local/bin/nvim",
@@ -136,22 +136,22 @@
"security.workspace.trust.untrustedFiles": "open",
"workbench.tree.indent": 4,
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features",
"editor.defaultFormatter": "vscode.html-language-features"
},
"python.defaultInterpreterPath": "/usr/local/bin/python3",
"[python]": {
"editor.formatOnType": true,
"editor.defaultFormatter": "ms-python.python",
"editor.defaultFormatter": "ms-python.python"
},
"[go][go.mod]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
},
"source.organizeImports": "explicit"
}
},
"update.showReleaseNotes": false,
"terminal.integrated.defaultProfile.osx": "fish",
"extensions.experimental.affinity": {
"asvetliakov.vscode-neovim": 1,
"asvetliakov.vscode-neovim": 1
},
"makefile.configureOnOpen": false,
"python.terminal.activateEnvInCurrentTerminal": true,
@@ -159,10 +159,12 @@
"remote.SSH.defaultForwardedPorts": [],
"remote.SSH.useLocalServer": false,
"remote.SSH.connectTimeout": 30,
"remote.SSH.localServerDownload": "always",
"remote.SSH.remotePlatform": {
"box1": "linux",
"hx80": "linux",
"hx20": "linux",
"pc30": "linux"
},
"editor.formatOnSave": true,
"pylint.enabled": false,
@@ -170,50 +172,50 @@
"python.analysis.packageIndexDepths": [
{
"name": "sklearn",
"depth": 2,
"depth": 2
},
{
"name": "matplotlib",
"depth": 2,
"depth": 2
},
{
"name": "scipy",
"depth": 2,
"depth": 2
},
{
"name": "django",
"depth": 2,
"depth": 2
},
{
"name": "flask",
"depth": 2,
"depth": 2
},
{
"name": "fastapi",
"depth": 2,
},
"depth": 2
}
],
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format",
"editor.defaultFormatter": "foxundermoon.shell-format"
},
// "[dockerfile]": {
// "editor.defaultFormatter": "foxundermoon.shell-format"
// },
"[ignore]": {
"editor.defaultFormatter": "foxundermoon.shell-format",
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"[properties]": {
"editor.defaultFormatter": "foxundermoon.shell-format",
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"editor.codeActionsOnSave": {
// "source.organizeImports": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.eslint": "explicit"
},
"[proto3]": {
"editor.defaultFormatter": "zxh404.vscode-proto3",
"editor.defaultFormatter": "zxh404.vscode-proto3"
},
"[dotenv]": {
"editor.defaultFormatter": "foxundermoon.shell-format",
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"typescript.preferences.quoteStyle": "single",
"javascript.preferences.quoteStyle": "single",
@@ -221,7 +223,7 @@
"pretty-formatter.quoteConvert": "single",
"html.completion.attributeDefaultValue": "singlequotes",
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.jsxSingleQuote": true,
"go.buildFlags": ["-tags=wireinject"],
@@ -231,7 +233,7 @@
"editor.tabSize": 2,
"editor.autoIndent": "advanced",
"diffEditor.ignoreTrimWhitespace": false,
"editor.defaultColorDecorators": "never",
"editor.defaultColorDecorators": "never"
},
"html.format.unformatted": "",
"editor.defaultFormatter": "esbenp.prettier-vscode",
@@ -243,34 +245,33 @@
"name": "Space (Global Leader)",
"type": "bind",
"command": "vspacecode.space",
"when": "editorTextFocus || explorerViewletVisible || terminalFocus",
"when": "editorTextFocus || explorerViewletVisible || terminalFocus"
},
{
"keys": "w.t",
"name": "Toggle Terminal",
"type": "command",
"command": "workbench.action.terminal.toggleTerminal",
"command": "workbench.action.terminal.toggleTerminal"
},
{
"keys": "w.w",
"name": "Toggle AI Sidebar",
"type": "command",
"command": "workbench.action.toggleAuxiliaryBar",
"command": "workbench.action.toggleAuxiliaryBar"
},
{
"keys": "w.e",
"name": "Focus First Editor Group",
"type": "command",
"command": "workbench.action.focusFirstEditorGroup",
},
"command": "workbench.action.focusFirstEditorGroup"
}
],
"[java]": {
"editor.suggest.snippetsPreventQuickSuggestions": false,
"editor.suggest.snippetsPreventQuickSuggestions": false
},
"git.confirmSync": false,
"editor.dropIntoEditor.preferences": [],
"whichkey.bindingOverrides": [],
"remote.SSH.localServerDownload": "always",
"workbench.colorCustomizations": {},
"javascript.format.enable": false,
"typescript.format.enable": false,
@@ -282,8 +283,9 @@
"ANTHROPIC_DEFAULT_SONNET_MODEL": "MiniMax-M2.1",
"ANTHROPIC_MODEL": "MiniMax-M2.1",
"API_TIMEOUT_MS": "3000000",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": 1,
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": 1
},
"claudeCode.preferredLocation": "panel",
"window.autoDetectColorScheme": false,
"vim.disableExtension": true
}
+1 -1
View File
@@ -103,7 +103,7 @@ end
#################################################################
# git
# alias
alias gitb="git branch --show-current | tr -d '\n' | pbcopy"
alias gitb="git branch --show-current | tr -d '\n' | clip-copy.sh"
alias gci="golangci-lint run -c=~/.dotfile/golang/golangci.yml ./..."
alias gmt="go mod tidy"
alias gtc="go test -gcflags=all=-l -cover ./..."
+7
View File
@@ -0,0 +1,7 @@
function pwdc --description 'Copy current directory path to clipboard'
set -l dir (pwd -P)
if not printf '%s' $dir | clip-copy.sh
return 1
end
echo "Copied: "(string replace $HOME '~' $dir)
end
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Copy stdin to the system clipboard (macOS / Linux X11 / Linux Wayland).
set -euo pipefail
if command -v pbcopy >/dev/null 2>&1; then
pbcopy
elif [ "${XDG_SESSION_TYPE:-}" = wayland ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then
if command -v wl-copy >/dev/null 2>&1; then
wl-copy --no-newline
else
echo "clip-copy: wl-copy not found (Wayland session)" >&2
exit 1
fi
elif [ -n "${DISPLAY:-}" ]; then
if command -v xclip >/dev/null 2>&1; then
xclip -selection clipboard
elif command -v xsel >/dev/null 2>&1; then
xsel --clipboard --input
else
echo "clip-copy: xclip or xsel not found (DISPLAY=$DISPLAY)" >&2
exit 1
fi
elif command -v xclip >/dev/null 2>&1; then
xclip -selection clipboard
elif command -v xsel >/dev/null 2>&1; then
xsel --clipboard --input
else
echo "clip-copy: no clipboard tool (pbcopy / wl-copy / xclip / xsel)" >&2
exit 1
fi
+76
View File
@@ -0,0 +1,76 @@
#!/bin/bash
# 用法:
# ./dns-clean.sh # 清理 macOS / Linux 系统 DNS 缓存
set -e
SUDO=""
[ "$(id -u)" -ne 0 ] && SUDO="sudo"
flush_macos() {
echo "macOS: 清理 DNS 缓存..."
$SUDO dscacheutil -flushcache
$SUDO killall -HUP mDNSResponder 2>/dev/null || true
$SUDO killall mDNSResponderHelper 2>/dev/null || true
echo "macOS DNS 缓存已清理"
}
flush_linux() {
local flushed=0
echo "Linux: 清理 DNS 缓存..."
if command -v resolvectl &>/dev/null; then
if $SUDO resolvectl flush-caches 2>/dev/null; then
echo " - systemd-resolved (resolvectl) 缓存已清理"
flushed=1
fi
elif command -v systemd-resolve &>/dev/null; then
if $SUDO systemd-resolve --flush-caches 2>/dev/null; then
echo " - systemd-resolved (systemd-resolve) 缓存已清理"
flushed=1
fi
fi
if command -v systemctl &>/dev/null; then
if systemctl is-active --quiet nscd 2>/dev/null; then
$SUDO systemctl restart nscd
echo " - nscd 已重启"
flushed=1
fi
if systemctl is-active --quiet dnsmasq 2>/dev/null; then
$SUDO systemctl restart dnsmasq
echo " - dnsmasq 已重启"
flushed=1
fi
if systemctl is-active --quiet unbound 2>/dev/null; then
if command -v rndc &>/dev/null; then
$SUDO rndc flush 2>/dev/null && echo " - unbound 缓存已清理" && flushed=1
else
$SUDO systemctl restart unbound
echo " - unbound 已重启"
flushed=1
fi
fi
fi
if [ "$flushed" -eq 0 ]; then
echo "未检测到常见 DNS 缓存服务 (systemd-resolved / nscd / dnsmasq / unbound)"
echo "若仍有问题,可尝试重启 NetworkManager 或检查本地 DNS 配置"
exit 1
fi
echo "Linux DNS 缓存已清理"
}
case "$(uname -s)" in
Darwin) flush_macos ;;
Linux) flush_linux ;;
*)
echo "错误: 不支持的操作系统: $(uname -s)"
exit 1
;;
esac
+51
View File
@@ -0,0 +1,51 @@
#!/bin/bash
# 将用户加入 docker 组(免 sudo 使用 docker)
# 用法: sudo docker-group <user>
# 示例: sudo docker-group xuser
set -euo pipefail
usage() {
cat <<'EOF'
用法: sudo docker-group <user>
将 <user> 加入 docker 组,之后可直接运行 docker(无需 sudo)。
示例:
sudo docker-group xuser
说明:
- 用户需重新登录或 newgrp docker 后生效
- 安装 Docker 时 docker-install.sh 只会把当时的 $USER 加入组
EOF
exit 1
}
[[ "${1:-}" == "-h" || "${1:-}" == "--help" ]] && usage
[[ $# -eq 1 ]] || usage
if [[ $EUID -ne 0 ]]; then
echo "错误: 请使用 sudo 运行,例如: sudo $0 $1"
exit 1
fi
user="$1"
if ! id "$user" &>/dev/null; then
echo "错误: 用户不存在: $user"
exit 1
fi
if ! getent group docker &>/dev/null; then
echo "错误: docker 组不存在,请先安装 Docker(例如运行 docker-install.sh)"
exit 1
fi
if id -nG "$user" | tr ' ' '\n' | grep -qx docker; then
echo "用户 $user 已在 docker 组中"
exit 0
fi
usermod -aG docker "$user"
echo "已将 $user 加入 docker 组"
echo "请让用户重新登录,或执行: su - $user -c 'newgrp docker'"
+97
View File
@@ -0,0 +1,97 @@
#!/bin/bash
# 为指定用户配置某命令的免密 sudo(写入 /etc/sudoers.d/)
# 用法: sudo nosudo <user> <command>
# 示例: sudo nosudo xuser docker
set -euo pipefail
usage() {
cat <<'EOF'
用法: sudo nosudo <user> <command>
为 <user> 配置对 <command> 的免密 sudo(NOPASSWD)。
示例:
sudo nosudo xuser docker
sudo nosudo xuser /usr/bin/systemctl
说明:
- 每个命令单独一个文件(nosudo-<user>-<命令名>),可多次执行、互不覆盖
例如 nosudo xuser kubectl 与 nosudo xuser helm 会各写一条规则,同时生效
- 仅允许免密执行解析到的那个可执行文件路径(含子命令参数仍受 sudo 规则约束)
- 对 docker,更推荐: sudo docker-group <user>(无需 sudo 即可用 docker)
- 删除规则: sudo rm /etc/sudoers.d/nosudo-<user>-<name>
EOF
exit 1
}
[[ "${1:-}" == "-h" || "${1:-}" == "--help" ]] && usage
[[ $# -eq 2 ]] || usage
if [[ $EUID -ne 0 ]]; then
echo "错误: 请使用 sudo 运行,例如: sudo $0 $*"
exit 1
fi
if [[ ! -d /etc/sudoers.d ]]; then
echo "错误: 未找到 /etc/sudoers.d(当前系统可能不支持此方式)"
exit 1
fi
user="$1"
cmd="$2"
if ! id "$user" &>/dev/null; then
echo "错误: 用户不存在: $user"
exit 1
fi
if [[ "$cmd" == /* ]]; then
cmd_path="$cmd"
else
cmd_path="$(command -v "$cmd" 2>/dev/null || true)"
fi
if [[ -z "$cmd_path" || ! -x "$cmd_path" ]]; then
echo "错误: 找不到可执行文件: $cmd"
echo "提示: 可先安装该命令,或传入绝对路径,例如 /usr/bin/docker"
exit 1
fi
name="$(basename "$cmd_path")"
file="/etc/sudoers.d/nosudo-${user}-${name}"
if [[ -f "$file" ]]; then
if grep -qF "NOPASSWD: ${cmd_path}" "$file" 2>/dev/null; then
echo "已存在相同规则: $file"
exit 0
fi
fi
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
cat >"$tmp" <<EOF
# Managed by nosudo ($0)
# user=$user command=$cmd_path
${user} ALL=(ALL) NOPASSWD: ${cmd_path}
EOF
if ! visudo -cf "$tmp" >/dev/null; then
echo "错误: sudoers 语法校验失败"
visudo -cf "$tmp" || true
exit 1
fi
install -m 0440 -o root -g root "$tmp" "$file"
echo "已写入: $file"
echo "内容: ${user} ALL=(ALL) NOPASSWD: ${cmd_path}"
echo ""
echo "验证(切换到该用户后):"
echo " sudo -u ${user} sudo -n ${cmd_path} version 2>/dev/null || sudo -u ${user} sudo -n ${cmd_path} --help | head -1"
if [[ "$name" == "docker" ]]; then
echo ""
echo "提示: 若希望直接运行 docker(不用 sudo),请执行:"
echo " sudo docker-group ${user}"
fi
+29 -3
View File
@@ -2,14 +2,40 @@
# 用法:
# ./scan-ip.sh # ICMP 扫描本网段在线 IP
# ./scan-ip.sh 10.0.0 # ICMP 扫描指定网段(10.0.0.1-254)
# ./scan-ip.sh 22 # 扫描本网段中 22 端口开放的 IP
# ./scan-ip.sh 80,443,8080 # 扫描本网段中任一指定端口开放的 IP
# ./scan-ip.sh 22 10.0.0 # 指定网段前缀(不写则使用本机所在网段)
# ./scan-ip.sh 22 10.0.0 # 指定网段前缀 + 端口(顺序不限)
# ./scan-ip.sh 10.0.0 22 # 同上
PORT_SPEC="$1"
PREFIX_ARG="$2"
TIMEOUT=1
is_port_spec() {
[[ "$1" =~ ^[0-9]+$ || "$1" =~ ^[0-9]+-[0-9]+$ || "$1" =~ ^[0-9,]+$ ]]
}
is_ip_prefix() {
[[ "$1" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){1,3}$ ]]
}
PORT_SPEC=""
PREFIX_ARG=""
if [ -n "$1" ] && is_ip_prefix "$1"; then
PREFIX_ARG="$1"
[ -n "$2" ] && is_port_spec "$2" && PORT_SPEC="$2"
elif [ -n "$1" ]; then
is_port_spec "$1" && PORT_SPEC="$1"
[ -n "$2" ] && is_ip_prefix "$2" && PREFIX_ARG="$2"
fi
if [ -n "$1" ] && [ -z "$PORT_SPEC" ] && [ -z "$PREFIX_ARG" ]; then
echo "错误: 无法识别参数: $1"
echo "端口支持: 单端口(22) / 列表(80,443) / 范围(8000-8100)"
echo "网段支持: 前缀(10.0.0 或 192.168.1.0)"
exit 1
fi
if command -v ip &>/dev/null; then
LOCAL_IP=$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d/ -f1 | head -n 1)
elif command -v ifconfig &>/dev/null; then
+13
View File
@@ -14,5 +14,18 @@ else
echo "[zsh] Oh My Zsh 未安装,请运行: bash ~/.dotfile/script/zsh-init.sh" >&2
fi
# 复制当前 git 分支名到剪贴板(macOS / Linux)
gitb() {
git branch --show-current | tr -d '\n' | clip-copy.sh
}
# 复制当前目录到剪贴板(macOS / Linux)
pwdc() {
local dir
dir="$(pwd -P)"
printf '%s' "$dir" | clip-copy.sh || return 1
echo "Copied: ${dir/#$HOME/~}"
}
# 本机私有配置(不纳入 dotfile)
[[ -f "$HOME/.zshrc.local" ]] && source "$HOME/.zshrc.local"