CLI tool
daeuniverse/gg avatar
daeuniverse/gg

gg wraps one command in a proxy instead of the whole machine

一个支持节点与订阅链接的 Linux 命令行代理工具 | A command-line tool for one-click proxy in your research and development without installing v2ray or anything else (only for linux)

1,936 stars152 forksGoAGPL-3.0

At a glance

What is it?
gg is a Linux command line proxy client written in Go that prefixes a single command, or a whole shell, to route its traffic through a node or subscription link without installing v2ray. The trade is that it works on statically linked Go binaries where proxychains cannot, and the repository identity shifted from mzz2017 to daeuniverse while the module path did not.
Who is it for?
Adopt gg on a Linux workstation or build server where you need one command proxied now and cannot install a system wide proxy stack, and where the target is a statically linked Go binary that proxychains cannot hook. Do not adopt it as a general purpose VPN, and read the AGPL-3.0 terms first if you intend to modify it, since the licence is network copyleft rather than permissive.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 108 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Prefix the command instead of reconfiguring the host

gg is described as a command-line tool for one-click proxy in research and development, and the mechanism is a prefix. You put `gg` in front of another command and that program's traffic goes through your proxy, with nothing else installed:

bash
gg python -m pip install torch

The stated motivation is specific. The author was tired of poor network conditions during research and development and did not want to install v2ray on working servers because it is too heavy. What was wanted was a light and portable tool for downloading dependencies and software on various servers. That is the whole scope: research and development machines, not a desktop VPN client.

The scope widens by one level with a shell:

bash
gg bash

git clone --depth=1 https://github.com/torvalds/linux.git
curl ipv4.appspot.com

Everything run inside that shell is proxied until it exits, which is the practical way to work in a session.

It is a pure Go implementation inspired by graftcp, and the README claims three advantages over proxychains and graftcp: it works without any other proxy utility installed, it supports UDP, and it supports Go programs. That last one is the substantive claim, linked to a proxychains-ng issue reporting that applications built by Go cannot be hooked.

The install script, the sudo question, and the PATH warning

Installation is one command that downloads and runs a shell script:

bash
sudo sh -c "$(curl -L https://github.com/mzz2017/gg/raw/main/release/go.sh)"

Run without `sudo`, gg installs into the user directory instead. The README then warns that if the `gg` command fails after installation, check your `$PATH`, and offers a symbolic link as the fix:

bash
sudo ln -s /usr/local/bin/gg /usr/bin/gg

The version check is:

bash
gg --version

whose documented output is `gg version 0.1.1`, which is from the README's own example and does not correspond to any current tag.

Two things to notice about that command. It pipes a remote script straight into a shell as root, which is the normal practice for this class of installer and is also the reason to read the script before running it. And it points at `mzz2017/gg` while the repository you are reading is `daeuniverse/gg`. The Go module path in `go.mod` is also `github.com/mzz2017/gg`. The project moved owners without moving its identity, so a self build reports the old path in its stack traces and errors.

Three ways to hand it a proxy: share link, node, or subscription

There are three input forms for the proxy itself, and they differ mainly in whether you persist anything.

With no configuration written before, gg prompts for a share link when it needs one:

bash
gg wget -O frp.tar.gz https://github.com/fatedier/frp/releases/download/v0.38.0/frp_0.38.0_linux_amd64.tar.gz

The prompt is `Enter the share-link of your proxy:`. You can also pass a link inline with `--node`, as in the speedtest example `gg --node ss://[email protected]:17247 speedtest`.

A subscription is the long term answer. Given a URL, gg selects a node for you, choosing the first available one by default:

bash
gg --subscription https://example.com/path/to/sub docker pull caddy

Add `--select` to pick one by hand, which tests the nodes and opens a selector showing each node's tag, latency, protocol and whether UDP is supported, navigable with the arrow keys and searchable with `/`.

The detail pane in that selector shows the fields the tool tracks per node: Name, Protocol, Support UDP and Latency. The example output shows protocol values of `shadowsocks` and `trojan` in the list, and the config file shows a `trojan-go://` link cached as the last node, so several protocols are in play.

Whether a node is tested before use is a config flag, `test_node_before_use`, shown in the config dump as `true`.

The config file is six keys, editable by flag or by hand

Long term setup writes config values with `-w`. Both a subscription and a node can be persisted the same way:

bash
gg config -w subscription=https://example.com/path/to/sub
gg config -w node=vmess://MY_VMESS_SERVER_SHARE_LINK

Reading and editing are symmetric. `gg config` with no arguments prints every key, `gg config node` reads one, and `gg config -u node` unsets one, printing `node=` as the confirmation that it is now empty.

The full dump in the README shows the complete key set, which is short enough to memorise:

text
node=
subscription.link=https://example.com/path/to/sub
subscription.select=first
subscription.cache_last_node=true
cache.subscription.last_node=trojan-go://MY_TROJAN_GO_SERVER_SHARE_LINK
no_udp=false
test_node_before_use=true

Reading that as a behaviour description: `subscription.select` is how a node gets chosen, `subscription.cache_last_node` means the last working node is remembered so a later run skips the selection, `cache.subscription.last_node` holds that remembered link, `no_udp` disables UDP, and `test_node_before_use` makes gg probe the node before sending traffic through it.

The storage format is not specified in the README. The Go dependencies suggest the plumbing, since `github.com/spf13/viper` and `github.com/pelletier/go-toml` are in `go.mod`, so configuration is read and written through Viper with TOML available.

sudo, setuid and ptrace are the documented failure modes

The Q&A section is more useful than most, because it names three real failure modes.

The first is configuration disappearing under sudo. If `sudo gg xxx` still asks for a share link even though config is set, the answer is `sudo -E gg xxx`, which preserves the environment.

The second is the reverse and more damaging case. Running `gg sudo xxx` produces `sudo: effective uid is not 0, ...`, and the README explains why: `setuid` and `ptrace` cannot work together, with a Stack Overflow link on the mechanism. The fix is to put sudo first, `sudo gg xxx`, never the other way round.

That mechanism is worth understanding rather than memorising. gg hooks child processes, which means ptrace or an equivalent, and sudo already uses setuid to change privileges. Asking gg to trace a process that is in the middle of changing its effective user is asking for two privileged transitions at once, and the kernel reports the inconsistency rather than letting it happen.

The third is a shell interaction, not a gg bug. With oh-my-zsh, gg can collide with a `git gui` alias and produce `git: 'gui' is not a git command`, which the README attributes to oh-my-zsh having added an alias from gg to git gui. The fix is one line in `~/.zshrc`:

bash
unalias gg

AGPL-3.0, a Linux-only binary, and an old tag series

The project is AGPL-3.0, with the licence at the root and a `contributor-agreement` file alongside it. For a tool you install on your own machine that is not a constraint. For a tool you patch, or that you ship as part of a service, AGPL network copyleft is the part of the licence to read properly rather than assume is equivalent to GPL.

The `go.mod` file declares `go 1.18` and the module is `github.com/mzz2017/gg`. The dependency list is where the protocol support is visible: `github.com/sagernet/sing` and `github.com/sagernet/sing-shadowsocks2` for Shadowsocks, `github.com/v2rayA/shadowsocksR` for ShadowsocksR, and `github.com/mzz2017/softwind` alongside `github.com/nadoo/glider`. Configuration runs through Cobra and Viper, logging through logrus, and the interactive node selector uses two separate libraries, promptui and survey.

Repository layout follows the same split: `cmd/`, `config/`, `dialer/`, `proxy/`, `tracer/`, `common/`, `infra/` and `completion/`. The `tracer` and `dialer` directories are the mechanism the ptrace discussion above refers to.

The last push was on 2026-07-06. The newest tag, however, is v0.2.19 from 2024-08-31, with v0.2.18 and v0.2.17 both from February 2023. So the listed releases predate the last push, and the install script fetches from the default branch rather than a release. Anyone pinning a version is pinning a tag that does not contain the current work.

Where proxychains, a system wide proxy or a VPN client fits instead

The comparison the README makes is with proxychains and graftcp, and it is fair on two of three points. gg bundles what graftcp needs instead of asking you to run a separate proxy helper alongside it, and its Go static linking is a real difference for Go binaries. UDP support is the third, and it matters for name resolution rather than for downloads.

Against proxychains-ng, the documented difference is support for Go programs. The README links a proxychains-ng issue reporting that applications built by Go cannot be hooked.

Against a system wide proxy client or a VPN, gg is narrower and also simpler. It has no daemon, no routing table changes, no TUN device and no traffic rules. That narrowness is its main operational advantage on a shared build server, where installing a routing daemon is a much bigger decision than running one binary.

What you give up is coverage. Anything that does not run as a child of the gg process, including a daemon you start or a service you talk to over a Unix socket, is outside its reach. That is the boundary to check first.

Editorial conclusion

Adopt gg on a Linux workstation or build server where you need one command proxied now and cannot install a system wide proxy stack, and where the target is a statically linked Go binary that proxychains cannot hook. Do not adopt it as a general purpose VPN, and read the AGPL-3.0 terms first if you intend to modify it, since the licence is network copyleft rather than permissive. Verify first that `gg --version` runs after install and that `gg config` shows your subscription, and note that the release tags stop at v0.2.19 from 2024-08-31 while the last push was on 2026-07-06, so you are installing unreleased work from a default branch.

Frequently asked questions

How do I install gg on Linux?

Run `sudo sh -c "$(curl -L https://github.com/mzz2017/gg/raw/main/release/go.sh)"`. Without sudo it installs into the user directory. If the `gg` command is not found afterwards, check your `$PATH` or add a symbolic link to /usr/bin.

Why does sudo gg still ask for a share link?

Because sudo strips the environment that points at your config. The README's answer is to use `sudo -E gg xxx` so the environment is preserved.

What is the difference between gg and proxychains?

The README claims gg works without another proxy utility, supports UDP, and supports Go programs, which proxychains-ng cannot hook.

Which config keys does gg store?

The documented set is `node`, `subscription.link`, `subscription.select`, `subscription.cache_last_node`, `cache.subscription.last_node`, `no_udp` and `test_node_before_use`. Set them with `gg config -w`, read one with `gg config <key>` and unset with `gg config -u <key>`.

What licence is gg released under?

AGPL-3.0, with the licence text in the LICENSE file at the repository root and a contributor agreement file alongside it.

Official sources

  1. daeuniverse/gg on GitHub
  2. Issues
  3. License: AGPL-3.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/daeuniverse-gg.svg)](https://hysenlabs.com/projects/daeuniverse-gg)