From 95606191d8be5c878ddac784aa88848808c90c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 23 Sep 2024 22:12:19 +0800 Subject: [PATCH] Add completions for linux packages --- .goreleaser.fury.yaml | 9 + .goreleaser.yaml | 53 +- Makefile | 4 +- cmd/sing-box/cmd.go | 68 ++ cmd/sing-box/generate_completions.go | 28 + cmd/sing-box/main.go | 69 +- release/completions/sing-box.bash | 1549 ++++++++++++++++++++++++++ release/completions/sing-box.fish | 235 ++++ release/completions/sing-box.zsh | 212 ++++ release/config/openwrt.conf | 5 + release/config/openwrt.init | 31 + release/config/sing-box.initd | 18 + 12 files changed, 2202 insertions(+), 79 deletions(-) create mode 100644 cmd/sing-box/cmd.go create mode 100644 cmd/sing-box/generate_completions.go create mode 100644 release/completions/sing-box.bash create mode 100644 release/completions/sing-box.fish create mode 100644 release/completions/sing-box.zsh create mode 100644 release/config/openwrt.conf create mode 100644 release/config/openwrt.init create mode 100644 release/config/sing-box.initd diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml index 6c7dbc27..fbd1ae42 100644 --- a/.goreleaser.fury.yaml +++ b/.goreleaser.fury.yaml @@ -49,10 +49,19 @@ nfpms: - src: release/config/config.json dst: /etc/sing-box/config.json type: config + - src: release/config/sing-box.service dst: /usr/lib/systemd/system/sing-box.service - src: release/config/sing-box@.service dst: /usr/lib/systemd/system/sing-box@.service + + - src: release/completions/sing-box.bash + dst: /usr/share/bash-completion/completions/sing-box.bash + - src: release/completions/sing-box.fish + dst: /usr/share/fish/vendor_completions.d/sing-box.fish + - src: release/completions/sing-box.zsh + dst: /usr/share/zsh/site-functions/_sing-box + - src: LICENSE dst: /usr/share/licenses/sing-box/LICENSE deb: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b1964628..2d0b234f 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,3 +1,4 @@ +version: 2 project_name: sing-box builds: - &template @@ -25,7 +26,6 @@ builds: targets: - linux_386 - linux_amd64_v1 - - linux_amd64_v3 - linux_arm64 - linux_arm_6 - linux_arm_7 @@ -33,7 +33,6 @@ builds: - linux_riscv64 - linux_mips64le - windows_amd64_v1 - - windows_amd64_v3 - windows_386 - windows_arm64 - darwin_amd64_v1 @@ -90,8 +89,6 @@ builds: - android_arm64 - android_386 - android_amd64 -snapshot: - name_template: "{{ .Version }}.{{ .ShortCommit }}" archives: - &template id: archive @@ -105,7 +102,7 @@ archives: wrap_in_directory: true files: - LICENSE - name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' + name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' - id: archive-legacy <<: *template builds: @@ -114,7 +111,7 @@ archives: nfpms: - id: package package_name: sing-box - file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' + file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' builds: - main homepage: https://sing-box.sagernet.org/ @@ -125,15 +122,26 @@ nfpms: - deb - rpm - archlinux +# - apk +# - ipk priority: extra contents: - src: release/config/config.json dst: /etc/sing-box/config.json type: config + - src: release/config/sing-box.service dst: /usr/lib/systemd/system/sing-box.service - src: release/config/sing-box@.service dst: /usr/lib/systemd/system/sing-box@.service + + - src: release/completions/sing-box.bash + dst: /usr/share/bash-completion/completions/sing-box.bash + - src: release/completions/sing-box.fish + dst: /usr/share/fish/vendor_completions.d/sing-box.fish + - src: release/completions/sing-box.zsh + dst: /usr/share/zsh/site-functions/_sing-box + - src: LICENSE dst: /usr/share/licenses/sing-box/LICENSE deb: @@ -145,13 +153,34 @@ nfpms: signature: key_file: "{{ .Env.NFPM_KEY_PATH }}" overrides: - deb: - conflicts: - - sing-box-beta - rpm: - conflicts: - - sing-box-beta + apk: + contents: + - src: release/config/config.json + dst: /etc/sing-box/config.json + type: config + - src: release/config/sing-box.initd + dst: /etc/init.d/sing-box + + - src: release/completions/sing-box.bash + dst: /usr/share/bash-completion/completions/sing-box.bash + - src: release/completions/sing-box.fish + dst: /usr/share/fish/vendor_completions.d/sing-box.fish + - src: release/completions/sing-box.zsh + dst: /usr/share/zsh/site-functions/_sing-box + + - src: LICENSE + dst: /usr/share/licenses/sing-box/LICENSE + ipk: + contents: + - src: release/config/config.json + dst: /etc/sing-box/config.json + type: config + + - src: release/config/openwrt.init + dst: /etc/init.d/sing-box + - src: release/config/openwrt.conf + dst: /etc/config/sing-box source: enabled: false name_template: '{{ .ProjectName }}-{{ .Version }}.source' diff --git a/Makefile b/Makefile index 6ede7648..4918bf25 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,9 @@ ci_build: go build $(PARAMS) $(MAIN) go build $(MAIN_PARAMS) $(MAIN) +generate_completions: + go run -v --tags generate,generate_completions $(MAIN) + install: go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) @@ -71,7 +74,6 @@ release: dist/*.deb \ dist/*.rpm \ dist/*_amd64.pkg.tar.zst \ - dist/*_amd64v3.pkg.tar.zst \ dist/*_arm64.pkg.tar.zst \ dist/release ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go new file mode 100644 index 00000000..f6c37ff7 --- /dev/null +++ b/cmd/sing-box/cmd.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "os" + "os/user" + "strconv" + "time" + + _ "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/service/filemanager" + + "github.com/spf13/cobra" +) + +var ( + globalCtx context.Context + configPaths []string + configDirectories []string + workingDir string + disableColor bool +) + +var mainCommand = &cobra.Command{ + Use: "sing-box", + PersistentPreRun: preRun, +} + +func init() { + mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path") + mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path") + mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory") + mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output") +} + +func preRun(cmd *cobra.Command, args []string) { + globalCtx = context.Background() + sudoUser := os.Getenv("SUDO_USER") + sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID")) + sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID")) + if sudoUID == 0 && sudoGID == 0 && sudoUser != "" { + sudoUserObject, _ := user.Lookup(sudoUser) + if sudoUserObject != nil { + sudoUID, _ = strconv.Atoi(sudoUserObject.Uid) + sudoGID, _ = strconv.Atoi(sudoUserObject.Gid) + } + } + if sudoUID > 0 && sudoGID > 0 { + globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID) + } + if disableColor { + log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger()) + } + if workingDir != "" { + _, err := os.Stat(workingDir) + if err != nil { + filemanager.MkdirAll(globalCtx, workingDir, 0o777) + } + err = os.Chdir(workingDir) + if err != nil { + log.Fatal(err) + } + } + if len(configPaths) == 0 && len(configDirectories) == 0 { + configPaths = append(configPaths, "config.json") + } +} diff --git a/cmd/sing-box/generate_completions.go b/cmd/sing-box/generate_completions.go new file mode 100644 index 00000000..6ab0cade --- /dev/null +++ b/cmd/sing-box/generate_completions.go @@ -0,0 +1,28 @@ +//go:build generate && generate_completions + +package main + +import "github.com/sagernet/sing-box/log" + +func main() { + err := generateCompletions() + if err != nil { + log.Fatal(err) + } +} + +func generateCompletions() error { + err := mainCommand.GenBashCompletionFile("release/completions/sing-box.bash") + if err != nil { + return err + } + err = mainCommand.GenFishCompletionFile("release/completions/sing-box.fish", true) + if err != nil { + return err + } + err = mainCommand.GenZshCompletionFile("release/completions/sing-box.zsh") + if err != nil { + return err + } + return nil +} diff --git a/cmd/sing-box/main.go b/cmd/sing-box/main.go index 66b7daa1..fd55c7d3 100644 --- a/cmd/sing-box/main.go +++ b/cmd/sing-box/main.go @@ -1,74 +1,11 @@ +//go:build !generate + package main -import ( - "context" - "os" - "os/user" - "strconv" - "time" - - _ "github.com/sagernet/sing-box/include" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/service/filemanager" - - "github.com/spf13/cobra" -) - -var ( - globalCtx context.Context - configPaths []string - configDirectories []string - workingDir string - disableColor bool -) - -var mainCommand = &cobra.Command{ - Use: "sing-box", - PersistentPreRun: preRun, -} - -func init() { - mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path") - mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path") - mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory") - mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output") -} +import "github.com/sagernet/sing-box/log" func main() { if err := mainCommand.Execute(); err != nil { log.Fatal(err) } } - -func preRun(cmd *cobra.Command, args []string) { - globalCtx = context.Background() - sudoUser := os.Getenv("SUDO_USER") - sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID")) - sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID")) - if sudoUID == 0 && sudoGID == 0 && sudoUser != "" { - sudoUserObject, _ := user.Lookup(sudoUser) - if sudoUserObject != nil { - sudoUID, _ = strconv.Atoi(sudoUserObject.Uid) - sudoGID, _ = strconv.Atoi(sudoUserObject.Gid) - } - } - if sudoUID > 0 && sudoGID > 0 { - globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID) - } - if disableColor { - log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger()) - } - if workingDir != "" { - _, err := os.Stat(workingDir) - if err != nil { - filemanager.MkdirAll(globalCtx, workingDir, 0o777) - } - err = os.Chdir(workingDir) - if err != nil { - log.Fatal(err) - } - } - if len(configPaths) == 0 && len(configDirectories) == 0 { - configPaths = append(configPaths, "config.json") - } -} diff --git a/release/completions/sing-box.bash b/release/completions/sing-box.bash new file mode 100644 index 00000000..ab102977 --- /dev/null +++ b/release/completions/sing-box.bash @@ -0,0 +1,1549 @@ +# bash completion for sing-box -*- shell-script -*- + +__sing-box_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__sing-box_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__sing-box_index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__sing-box_contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__sing-box_handle_go_custom_completion() +{ + __sing-box_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" + + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + + local out requestComp lastParam lastChar comp directive args + + # Prepare the command to request completions for the program. + # Calling ${words[0]} instead of directly sing-box allows handling aliases + args=("${words[@]:1}") + # Disable ActiveHelp which is not supported for bash completion v1 + requestComp="SING_BOX_ACTIVE_HELP=0 ${words[0]} __completeNoDesc ${args[*]}" + + lastParam=${words[$((${#words[@]}-1))]} + lastChar=${lastParam:$((${#lastParam}-1)):1} + __sing-box_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" + + if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __sing-box_debug "${FUNCNAME[0]}: Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __sing-box_debug "${FUNCNAME[0]}: calling ${requestComp}" + # Use eval to handle any environment variables and such + out=$(eval "${requestComp}" 2>/dev/null) + + # Extract the directive integer at the very end of the output following a colon (:) + directive=${out##*:} + # Remove the directive + out=${out%:*} + if [ "${directive}" = "${out}" ]; then + # There is not directive specified + directive=0 + fi + __sing-box_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" + __sing-box_debug "${FUNCNAME[0]}: the completions are: ${out}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + # Error code. No completion. + __sing-box_debug "${FUNCNAME[0]}: received error from custom completion go code" + return + else + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __sing-box_debug "${FUNCNAME[0]}: activating no space" + compopt -o nospace + fi + fi + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __sing-box_debug "${FUNCNAME[0]}: activating no file completion" + compopt +o default + fi + fi + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local fullFilter filter filteringCmd + # Do not use quotes around the $out variable or else newline + # characters will be kept. + for filter in ${out}; do + fullFilter+="$filter|" + done + + filteringCmd="_filedir $fullFilter" + __sing-box_debug "File filtering command: $filteringCmd" + $filteringCmd + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subdir + # Use printf to strip any trailing newline + subdir=$(printf "%s" "${out}") + if [ -n "$subdir" ]; then + __sing-box_debug "Listing directories in $subdir" + __sing-box_handle_subdirs_in_dir_flag "$subdir" + else + __sing-box_debug "Listing directories in ." + _filedir -d + fi + else + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${out}" -- "$cur") + fi +} + +__sing-box_handle_reply() +{ + __sing-box_debug "${FUNCNAME[0]}" + local comp + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${allflags[*]}" -- "$cur") + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%=*}" + __sing-box_index_of_word "${flag}" "${flags_with_completion[@]}" + COMPREPLY=() + if [[ ${index} -ge 0 ]]; then + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION:-}" ]; then + # zsh completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + + if [[ -z "${flag_parsing_disabled}" ]]; then + # If flag parsing is enabled, we have completed the flags and can return. + # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough + # to possibly call handle_go_custom_completion. + return 0; + fi + ;; + esac + + # check if we are handling a flag with special work handling + local index + __sing-box_index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions+=("${must_have_one_noun[@]}") + elif [[ -n "${has_completion_function}" ]]; then + # if a go completion function is provided, defer to that function + __sing-box_handle_go_custom_completion + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${completions[*]}" -- "$cur") + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${noun_aliases[*]}" -- "$cur") + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + if declare -F __sing-box_custom_func >/dev/null; then + # try command name qualified custom func + __sing-box_custom_func + else + # otherwise fall back to unqualified for compatibility + declare -F __custom_func >/dev/null && __custom_func + fi + fi + + # available in bash-completion >= 2, not always present on macOS + if declare -F __ltrim_colon_completions >/dev/null; then + __ltrim_colon_completions "$cur" + fi + + # If there is only 1 completion and it is a flag with an = it will be completed + # but we don't want a space after the = + if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then + compopt -o nospace + fi +} + +# The arguments should be in the form "ext1|ext2|extn" +__sing-box_handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__sing-box_handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return +} + +__sing-box_handle_flag() +{ + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue="" + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __sing-box_debug "${FUNCNAME[0]}: looking for ${flagname}" + if __sing-box_contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __sing-box_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + # flaghash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + fi + + # skip the argument to a two word flag + if [[ ${words[c]} != *"="* ]] && __sing-box_contains_word "${words[c]}" "${two_word_flags[@]}"; then + __sing-box_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__sing-box_handle_noun() +{ + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __sing-box_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __sing-box_contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__sing-box_handle_command() +{ + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_sing-box_root_command" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __sing-box_debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F "$next_command" >/dev/null && $next_command +} + +__sing-box_handle_word() +{ + if [[ $c -ge $cword ]]; then + __sing-box_handle_reply + return + fi + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __sing-box_handle_flag + elif __sing-box_contains_word "${words[c]}" "${commands[@]}"; then + __sing-box_handle_command + elif [[ $c -eq 0 ]]; then + __sing-box_handle_command + elif __sing-box_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __sing-box_handle_command + else + __sing-box_handle_noun + fi + else + __sing-box_handle_noun + fi + __sing-box_handle_word +} + +_sing-box_check() +{ + last_command="sing-box_check" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_format() +{ + last_command="sing-box_format" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_ech-keypair() +{ + last_command="sing-box_generate_ech-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--pq-signature-schemes-enabled") + local_nonpersistent_flags+=("--pq-signature-schemes-enabled") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_rand() +{ + last_command="sing-box_generate_rand" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--base64") + local_nonpersistent_flags+=("--base64") + flags+=("--hex") + local_nonpersistent_flags+=("--hex") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_reality-keypair() +{ + last_command="sing-box_generate_reality-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_tls-keypair() +{ + last_command="sing-box_generate_tls-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--months=") + two_word_flags+=("--months") + two_word_flags+=("-m") + local_nonpersistent_flags+=("--months") + local_nonpersistent_flags+=("--months=") + local_nonpersistent_flags+=("-m") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_uuid() +{ + last_command="sing-box_generate_uuid" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_vapid-keypair() +{ + last_command="sing-box_generate_vapid-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_wg-keypair() +{ + last_command="sing-box_generate_wg-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate() +{ + last_command="sing-box_generate" + + command_aliases=() + + commands=() + commands+=("ech-keypair") + commands+=("rand") + commands+=("reality-keypair") + commands+=("tls-keypair") + commands+=("uuid") + commands+=("vapid-keypair") + commands+=("wg-keypair") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip_export() +{ + last_command="sing-box_geoip_export" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip_list() +{ + last_command="sing-box_geoip_list" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip_lookup() +{ + last_command="sing-box_geoip_lookup" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip() +{ + last_command="sing-box_geoip" + + command_aliases=() + + commands=() + commands+=("export") + commands+=("list") + commands+=("lookup") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite_export() +{ + last_command="sing-box_geosite_export" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite_list() +{ + last_command="sing-box_geosite_list" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite_lookup() +{ + last_command="sing-box_geosite_lookup" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite() +{ + last_command="sing-box_geosite" + + command_aliases=() + + commands=() + commands+=("export") + commands+=("list") + commands+=("lookup") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_merge() +{ + last_command="sing-box_merge" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_compile() +{ + last_command="sing-box_rule-set_compile" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_convert() +{ + last_command="sing-box_rule-set_convert" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--type=") + two_word_flags+=("--type") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--type") + local_nonpersistent_flags+=("--type=") + local_nonpersistent_flags+=("-t") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_decompile() +{ + last_command="sing-box_rule-set_decompile" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_format() +{ + last_command="sing-box_rule-set_format" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_match() +{ + last_command="sing-box_rule-set_match" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--format=") + two_word_flags+=("--format") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + local_nonpersistent_flags+=("-f") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_upgrade() +{ + last_command="sing-box_rule-set_upgrade" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set() +{ + last_command="sing-box_rule-set" + + command_aliases=() + + commands=() + commands+=("compile") + commands+=("convert") + commands+=("decompile") + commands+=("format") + commands+=("match") + commands+=("upgrade") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_run() +{ + last_command="sing-box_run" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools_connect() +{ + last_command="sing-box_tools_connect" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--network=") + two_word_flags+=("--network") + two_word_flags+=("-n") + local_nonpersistent_flags+=("--network") + local_nonpersistent_flags+=("--network=") + local_nonpersistent_flags+=("-n") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools_fetch() +{ + last_command="sing-box_tools_fetch" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools_synctime() +{ + last_command="sing-box_tools_synctime" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--format=") + two_word_flags+=("--format") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + local_nonpersistent_flags+=("-f") + flags+=("--server=") + two_word_flags+=("--server") + two_word_flags+=("-s") + local_nonpersistent_flags+=("--server") + local_nonpersistent_flags+=("--server=") + local_nonpersistent_flags+=("-s") + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools() +{ + last_command="sing-box_tools" + + command_aliases=() + + commands=() + commands+=("connect") + commands+=("fetch") + commands+=("synctime") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_version() +{ + last_command="sing-box_version" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--name") + flags+=("-n") + local_nonpersistent_flags+=("--name") + local_nonpersistent_flags+=("-n") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_root_command() +{ + last_command="sing-box" + + command_aliases=() + + commands=() + commands+=("check") + commands+=("format") + commands+=("generate") + commands+=("geoip") + commands+=("geosite") + commands+=("merge") + commands+=("rule-set") + commands+=("run") + commands+=("tools") + commands+=("version") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +__start_sing-box() +{ + local cur prev words cword split + declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __sing-box_init_completion -n "=" || return + fi + + local c=0 + local flag_parsing_disabled= + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("sing-box") + local command_aliases=() + local must_have_one_flag=() + local must_have_one_noun=() + local has_completion_function="" + local last_command="" + local nouns=() + local noun_aliases=() + + __sing-box_handle_word +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_sing-box sing-box +else + complete -o default -o nospace -F __start_sing-box sing-box +fi + +# ex: ts=4 sw=4 et filetype=sh diff --git a/release/completions/sing-box.fish b/release/completions/sing-box.fish new file mode 100644 index 00000000..2aec1e1d --- /dev/null +++ b/release/completions/sing-box.fish @@ -0,0 +1,235 @@ +# fish completion for sing-box -*- shell-script -*- + +function __sing_box_debug + set -l file "$BASH_COMP_DEBUG_FILE" + if test -n "$file" + echo "$argv" >> $file + end +end + +function __sing_box_perform_completion + __sing_box_debug "Starting __sing_box_perform_completion" + + # Extract all args except the last one + set -l args (commandline -opc) + # Extract the last arg and escape it in case it is a space + set -l lastArg (string escape -- (commandline -ct)) + + __sing_box_debug "args: $args" + __sing_box_debug "last arg: $lastArg" + + # Disable ActiveHelp which is not supported for fish shell + set -l requestComp "SING_BOX_ACTIVE_HELP=0 $args[1] __complete $args[2..-1] $lastArg" + + __sing_box_debug "Calling $requestComp" + set -l results (eval $requestComp 2> /dev/null) + + # Some programs may output extra empty lines after the directive. + # Let's ignore them or else it will break completion. + # Ref: https://github.com/spf13/cobra/issues/1279 + for line in $results[-1..1] + if test (string trim -- $line) = "" + # Found an empty line, remove it + set results $results[1..-2] + else + # Found non-empty line, we have our proper output + break + end + end + + set -l comps $results[1..-2] + set -l directiveLine $results[-1] + + # For Fish, when completing a flag with an = (e.g., -n=) + # completions must be prefixed with the flag + set -l flagPrefix (string match -r -- '-.*=' "$lastArg") + + __sing_box_debug "Comps: $comps" + __sing_box_debug "DirectiveLine: $directiveLine" + __sing_box_debug "flagPrefix: $flagPrefix" + + for comp in $comps + printf "%s%s\n" "$flagPrefix" "$comp" + end + + printf "%s\n" "$directiveLine" +end + +# this function limits calls to __sing_box_perform_completion, by caching the result behind $__sing_box_perform_completion_once_result +function __sing_box_perform_completion_once + __sing_box_debug "Starting __sing_box_perform_completion_once" + + if test -n "$__sing_box_perform_completion_once_result" + __sing_box_debug "Seems like a valid result already exists, skipping __sing_box_perform_completion" + return 0 + end + + set --global __sing_box_perform_completion_once_result (__sing_box_perform_completion) + if test -z "$__sing_box_perform_completion_once_result" + __sing_box_debug "No completions, probably due to a failure" + return 1 + end + + __sing_box_debug "Performed completions and set __sing_box_perform_completion_once_result" + return 0 +end + +# this function is used to clear the $__sing_box_perform_completion_once_result variable after completions are run +function __sing_box_clear_perform_completion_once_result + __sing_box_debug "" + __sing_box_debug "========= clearing previously set __sing_box_perform_completion_once_result variable ==========" + set --erase __sing_box_perform_completion_once_result + __sing_box_debug "Successfully erased the variable __sing_box_perform_completion_once_result" +end + +function __sing_box_requires_order_preservation + __sing_box_debug "" + __sing_box_debug "========= checking if order preservation is required ==========" + + __sing_box_perform_completion_once + if test -z "$__sing_box_perform_completion_once_result" + __sing_box_debug "Error determining if order preservation is required" + return 1 + end + + set -l directive (string sub --start 2 $__sing_box_perform_completion_once_result[-1]) + __sing_box_debug "Directive is: $directive" + + set -l shellCompDirectiveKeepOrder 32 + set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) % 2) + __sing_box_debug "Keeporder is: $keeporder" + + if test $keeporder -ne 0 + __sing_box_debug "This does require order preservation" + return 0 + end + + __sing_box_debug "This doesn't require order preservation" + return 1 +end + + +# This function does two things: +# - Obtain the completions and store them in the global __sing_box_comp_results +# - Return false if file completion should be performed +function __sing_box_prepare_completions + __sing_box_debug "" + __sing_box_debug "========= starting completion logic ==========" + + # Start fresh + set --erase __sing_box_comp_results + + __sing_box_perform_completion_once + __sing_box_debug "Completion results: $__sing_box_perform_completion_once_result" + + if test -z "$__sing_box_perform_completion_once_result" + __sing_box_debug "No completion, probably due to a failure" + # Might as well do file completion, in case it helps + return 1 + end + + set -l directive (string sub --start 2 $__sing_box_perform_completion_once_result[-1]) + set --global __sing_box_comp_results $__sing_box_perform_completion_once_result[1..-2] + + __sing_box_debug "Completions are: $__sing_box_comp_results" + __sing_box_debug "Directive is: $directive" + + set -l shellCompDirectiveError 1 + set -l shellCompDirectiveNoSpace 2 + set -l shellCompDirectiveNoFileComp 4 + set -l shellCompDirectiveFilterFileExt 8 + set -l shellCompDirectiveFilterDirs 16 + + if test -z "$directive" + set directive 0 + end + + set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2) + if test $compErr -eq 1 + __sing_box_debug "Received error directive: aborting." + # Might as well do file completion, in case it helps + return 1 + end + + set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2) + set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2) + if test $filefilter -eq 1; or test $dirfilter -eq 1 + __sing_box_debug "File extension filtering or directory filtering not supported" + # Do full file completion instead + return 1 + end + + set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2) + set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2) + + __sing_box_debug "nospace: $nospace, nofiles: $nofiles" + + # If we want to prevent a space, or if file completion is NOT disabled, + # we need to count the number of valid completions. + # To do so, we will filter on prefix as the completions we have received + # may not already be filtered so as to allow fish to match on different + # criteria than the prefix. + if test $nospace -ne 0; or test $nofiles -eq 0 + set -l prefix (commandline -t | string escape --style=regex) + __sing_box_debug "prefix: $prefix" + + set -l completions (string match -r -- "^$prefix.*" $__sing_box_comp_results) + set --global __sing_box_comp_results $completions + __sing_box_debug "Filtered completions are: $__sing_box_comp_results" + + # Important not to quote the variable for count to work + set -l numComps (count $__sing_box_comp_results) + __sing_box_debug "numComps: $numComps" + + if test $numComps -eq 1; and test $nospace -ne 0 + # We must first split on \t to get rid of the descriptions to be + # able to check what the actual completion will be. + # We don't need descriptions anyway since there is only a single + # real completion which the shell will expand immediately. + set -l split (string split --max 1 \t $__sing_box_comp_results[1]) + + # Fish won't add a space if the completion ends with any + # of the following characters: @=/:., + set -l lastChar (string sub -s -1 -- $split) + if not string match -r -q "[@=/:.,]" -- "$lastChar" + # In other cases, to support the "nospace" directive we trick the shell + # by outputting an extra, longer completion. + __sing_box_debug "Adding second completion to perform nospace directive" + set --global __sing_box_comp_results $split[1] $split[1]. + __sing_box_debug "Completions are now: $__sing_box_comp_results" + end + end + + if test $numComps -eq 0; and test $nofiles -eq 0 + # To be consistent with bash and zsh, we only trigger file + # completion when there are no other completions + __sing_box_debug "Requesting file completion" + return 1 + end + end + + return 0 +end + +# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves +# so we can properly delete any completions provided by another script. +# Only do this if the program can be found, or else fish may print some errors; besides, +# the existing completions will only be loaded if the program can be found. +if type -q "sing-box" + # The space after the program name is essential to trigger completion for the program + # and not completion of the program name itself. + # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. + complete --do-complete "sing-box " > /dev/null 2>&1 +end + +# Remove any pre-existing completions for the program since we will be handling all of them. +complete -c sing-box -e + +# this will get called after the two calls below and clear the $__sing_box_perform_completion_once_result global +complete -c sing-box -n '__sing_box_clear_perform_completion_once_result' +# The call to __sing_box_prepare_completions will setup __sing_box_comp_results +# which provides the program's completion choices. +# If this doesn't require order preservation, we don't use the -k flag +complete -c sing-box -n 'not __sing_box_requires_order_preservation && __sing_box_prepare_completions' -f -a '$__sing_box_comp_results' +# otherwise we use the -k flag +complete -k -c sing-box -n '__sing_box_requires_order_preservation && __sing_box_prepare_completions' -f -a '$__sing_box_comp_results' diff --git a/release/completions/sing-box.zsh b/release/completions/sing-box.zsh new file mode 100644 index 00000000..96200556 --- /dev/null +++ b/release/completions/sing-box.zsh @@ -0,0 +1,212 @@ +#compdef sing-box +compdef _sing-box sing-box + +# zsh completion for sing-box -*- shell-script -*- + +__sing-box_debug() +{ + local file="$BASH_COMP_DEBUG_FILE" + if [[ -n ${file} ]]; then + echo "$*" >> "${file}" + fi +} + +_sing-box() +{ + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + local shellCompDirectiveKeepOrder=32 + + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder + local -a completions + + __sing-box_debug "\n========= starting completion logic ==========" + __sing-box_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") + __sing-box_debug "Truncated words[*]: ${words[*]}," + + lastParam=${words[-1]} + lastChar=${lastParam[-1]} + __sing-box_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" + + # For zsh, when completing a flag with an = (e.g., sing-box -n=) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[1]} __complete ${words[2,-1]}" + if [ "${lastChar}" = "" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go completion code. + __sing-box_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __sing-box_debug "About to call: eval ${requestComp}" + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + __sing-box_debug "completion output: ${out}" + + # Extract the directive integer following a : from the last line + local lastLine + while IFS='\n' read -r line; do + lastLine=${line} + done < <(printf "%s\n" "${out[@]}") + __sing-box_debug "last line: ${lastLine}" + + if [ "${lastLine[1]}" = : ]; then + directive=${lastLine[2,-1]} + # Remove the directive including the : and the newline + local suffix + (( suffix=${#lastLine}+2)) + out=${out[1,-$suffix]} + else + # There is no directive specified. Leave $out as is. + __sing-box_debug "No directive found. Setting do default" + directive=0 + fi + + __sing-box_debug "directive: ${directive}" + __sing-box_debug "completions: ${out}" + __sing-box_debug "flagPrefix: ${flagPrefix}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + __sing-box_debug "Completion received error. Ignoring completions." + return + fi + + local activeHelpMarker="_activeHelp_ " + local endIndex=${#activeHelpMarker} + local startIndex=$((${#activeHelpMarker}+1)) + local hasActiveHelp=0 + while IFS='\n' read -r comp; do + # Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker) + if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then + __sing-box_debug "ActiveHelp found: $comp" + comp="${comp[$startIndex,-1]}" + if [ -n "$comp" ]; then + compadd -x "${comp}" + __sing-box_debug "ActiveHelp will need delimiter" + hasActiveHelp=1 + fi + + continue + fi + + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + + local tab="$(printf '\t')" + comp=${comp//$tab/:} + + __sing-box_debug "Adding completion: ${comp}" + completions+=${comp} + lastComp=$comp + fi + done < <(printf "%s\n" "${out[@]}") + + # Add a delimiter after the activeHelp statements, but only if: + # - there are completions following the activeHelp statements, or + # - file completion will be performed (so there will be choices after the activeHelp) + if [ $hasActiveHelp -eq 1 ]; then + if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then + __sing-box_debug "Adding activeHelp delimiter" + compadd -x "--" + hasActiveHelp=0 + fi + fi + + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + __sing-box_debug "Activating nospace." + noSpace="-S ''" + fi + + if [ $((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then + __sing-box_debug "Activating keep order." + keepOrder="-V" + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local filteringCmd + filteringCmd='_files' + for filter in ${completions[@]}; do + if [ ${filter[1]} != '*' ]; then + # zsh requires a glob pattern to do file filtering + filter="\*.$filter" + fi + filteringCmd+=" -g $filter" + done + filteringCmd+=" ${flagPrefix}" + + __sing-box_debug "File filtering command: $filteringCmd" + _arguments '*:filename:'"$filteringCmd" + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subdir + subdir="${completions[1]}" + if [ -n "$subdir" ]; then + __sing-box_debug "Listing directories in $subdir" + pushd "${subdir}" >/dev/null 2>&1 + else + __sing-box_debug "Listing directories in ." + fi + + local result + _arguments '*:dirname:_files -/'" ${flagPrefix}" + result=$? + if [ -n "$subdir" ]; then + popd >/dev/null 2>&1 + fi + return $result + else + __sing-box_debug "Calling _describe" + if eval _describe $keepOrder "completions" completions $flagPrefix $noSpace; then + __sing-box_debug "_describe found some completions" + + # Return the success of having called _describe + return 0 + else + __sing-box_debug "_describe did not find completions." + __sing-box_debug "Checking if we should do file completion." + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __sing-box_debug "deactivating file completion" + + # We must return an error code here to let zsh know that there were no + # completions found by _describe; this is what will trigger other + # matching algorithms to attempt to find completions. + # For example zsh can match letters in the middle of words. + return 1 + else + # Perform file completion + __sing-box_debug "Activating file completion" + + # We must return the result of this command, so it must be the + # last command, or else we must store its result to return it. + _arguments '*:filename:_files'" ${flagPrefix}" + fi + fi + fi +} + +# don't run the completion function when being source-ed or eval-ed +if [ "$funcstack[1]" = "_sing-box" ]; then + _sing-box +fi diff --git a/release/config/openwrt.conf b/release/config/openwrt.conf new file mode 100644 index 00000000..1ce4c77d --- /dev/null +++ b/release/config/openwrt.conf @@ -0,0 +1,5 @@ +config sing-box 'main' + option enabled '1' + option conffile '/etc/sing-box/config.json' + option workdir '/usr/share/sing-box' + option log_stderr '1' diff --git a/release/config/openwrt.init b/release/config/openwrt.init new file mode 100644 index 00000000..bae70e4b --- /dev/null +++ b/release/config/openwrt.init @@ -0,0 +1,31 @@ +#!/bin/sh /etc/rc.common + +PROG="/usr/bin/sing-box" + +start_service() { + config_load "sing-box" + + local enabled config_file working_directory + local log_stdout log_stderr + config_get_bool enabled "main" "enabled" "0" + [ "$enabled" -eq "1" ] || return 0 + + config_get config_file "main" "conffile" "/etc/sing-box/config.json" + config_get working_directory "main" "workdir" "/usr/share/sing-box" + config_get_bool log_stdout "main" "log_stdout" "1" + config_get_bool log_stderr "main" "log_stderr" "1" + + procd_open_instance + procd_swet_param command "$PROG" run -c "$conffile" -D "$workdir" + procd_set_param file "$conffile" + procd_set_param stderr "$log_stderr" + procd_set_param limits core="unlimited" + sprocd_set_param limits nofile="1000000 1000000" + procd_set_param respawn + + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "sing-box" +} \ No newline at end of file diff --git a/release/config/sing-box.initd b/release/config/sing-box.initd new file mode 100644 index 00000000..db96e478 --- /dev/null +++ b/release/config/sing-box.initd @@ -0,0 +1,18 @@ +#!/sbin/openrc-run + +name=$RC_SVCNAME +description="sing-box service" +supervisor="supervise-daemon" +command="/usr/bin/sing-box" +command_args="-D /var/lib/sing-box -C /etc/sing-box run" +extra_started_commands="reload" + +depend() { + after net dns +} + +reload() { + ebegin "Reloading $RC_SVCNAME" + $supervisor "$RC_SVCNAME" --signal HUP + eend $? +} \ No newline at end of file