detached 43baf04

Add global HTTP proxy configuration

commit
43baf04e7cf498cd1f0f9559faab7ee1bd358ccf
author
patriceckhart · · 2026-08-06 06:01 UTC
parent
c420b4e
signature
unsigned
5 files · +125 -0
M README.md +12 -0
@@ -150,6 +150,18 @@ $ZOT_HOME/
150150
151151 Drop a `SYSTEM.md` in `$ZOT_HOME` to replace the built-in identity and guidelines for every run. `--system-prompt` still wins per-invocation. Delete the file to revert to the default.
152152
153+### HTTP proxy
154+
155+To route zot-managed HTTP and HTTPS requests through one proxy, add `http_proxy` to `$ZOT_HOME/config.json`:
156+
157+```json
158+{
159+ "http_proxy": "http://127.0.0.1:7890"
160+}
161+```
162+
163+The setting is applied at startup to both HTTP and HTTPS traffic. Existing `HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, and `https_proxy` environment variables take precedence for their corresponding protocol. `NO_PROXY` and `no_proxy` continue to control bypasses. Restart zot after changing the config file. If the URL contains proxy credentials, prefer protected environment variables because `config.json` is not a credential store.
164+
153165 ## Persistent instructions (AGENTS.md)
154166
155167 Use `AGENTS.md` to give zot standing instructions that layer **on top of** the default system prompt, without replacing it. This is the friendliest way to shape behavior (for example, taming local models that jump straight to code edits) because it adds guidance rather than taking over the base identity the way `SYSTEM.md` does.
@@ -150,6 +150,18 @@ $ZOT_HOME/
150 150
151 Drop a `SYSTEM.md` in `$ZOT_HOME` to replace the built-in identity and guidelines for every run. `--system-prompt` still wins per-invocation. Delete the file to revert to the default.151 Drop a `SYSTEM.md` in `$ZOT_HOME` to replace the built-in identity and guidelines for every run. `--system-prompt` still wins per-invocation. Delete the file to revert to the default.
152 152
153+### HTTP proxy
154+
155+To route zot-managed HTTP and HTTPS requests through one proxy, add `http_proxy` to `$ZOT_HOME/config.json`:
156+
157+```json
158+{
159+ "http_proxy": "http://127.0.0.1:7890"
160+}
161+```
162+
163+The setting is applied at startup to both HTTP and HTTPS traffic. Existing `HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, and `https_proxy` environment variables take precedence for their corresponding protocol. `NO_PROXY` and `no_proxy` continue to control bypasses. Restart zot after changing the config file. If the URL contains proxy credentials, prefer protected environment variables because `config.json` is not a credential store.
164+
153 ## Persistent instructions (AGENTS.md)165 ## Persistent instructions (AGENTS.md)
154 166
155 Use `AGENTS.md` to give zot standing instructions that layer **on top of** the default system prompt, without replacing it. This is the friendliest way to shape behavior (for example, taming local models that jump straight to code edits) because it adds guidance rather than taking over the base identity the way `SYSTEM.md` does.167 Use `AGENTS.md` to give zot standing instructions that layer **on top of** the default system prompt, without replacing it. This is the friendliest way to shape behavior (for example, taming local models that jump straight to code edits) because it adds guidance rather than taking over the base identity the way `SYSTEM.md` does.
M docs/providers.md +14 -0
@@ -4,6 +4,20 @@ zot ships with built-in providers and a model catalog. You can select models
44 with `/model`, list them with `zot --list-models`, and add private models in
55 `$ZOT_HOME/models.json`.
66
7+## HTTP proxy
8+
9+Set one global proxy for zot-managed HTTP and HTTPS traffic with the `http_proxy` key in `$ZOT_HOME/config.json`:
10+
11+```json
12+{
13+ "http_proxy": "http://127.0.0.1:7890"
14+}
15+```
16+
17+zot applies this setting at startup. Existing `HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, and `https_proxy` environment variables take precedence for their corresponding protocol. Standard `NO_PROXY` and `no_proxy` bypass lists remain effective. Restart zot after changing the setting.
18+
19+`config.json` is not a credential store. If the proxy URL contains a username or password, prefer setting the proxy environment variables in a protected shell or service configuration rather than saving those credentials in `config.json`.
20+
721 ## Login methods
822
923 Use `/login` in interactive mode. Type in either provider picker to filter the list by provider ID or display name.
@@ -4,6 +4,20 @@ zot ships with built-in providers and a model catalog. You can select models
4 with `/model`, list them with `zot --list-models`, and add private models in4 with `/model`, list them with `zot --list-models`, and add private models in
5 `$ZOT_HOME/models.json`.5 `$ZOT_HOME/models.json`.
6 6
7+## HTTP proxy
8+
9+Set one global proxy for zot-managed HTTP and HTTPS traffic with the `http_proxy` key in `$ZOT_HOME/config.json`:
10+
11+```json
12+{
13+ "http_proxy": "http://127.0.0.1:7890"
14+}
15+```
16+
17+zot applies this setting at startup. Existing `HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, and `https_proxy` environment variables take precedence for their corresponding protocol. Standard `NO_PROXY` and `no_proxy` bypass lists remain effective. Restart zot after changing the setting.
18+
19+`config.json` is not a credential store. If the proxy URL contains a username or password, prefer setting the proxy environment variables in a protected shell or service configuration rather than saving those credentials in `config.json`.
20+
7 ## Login methods21 ## Login methods
8 22
9 Use `/login` in interactive mode. Type in either provider picker to filter the list by provider ID or display name.23 Use `/login` in interactive mode. Type in either provider picker to filter the list by provider ID or display name.
M packages/agent/cli.go +4 -0
@@ -193,6 +193,10 @@ func fanoutAgentEvent(mgr *extensions.Manager, ev core.AgentEvent) {
193193
194194 // Run is the top-level entrypoint for the zot binary.
195195 func Run(rawArgs []string, version string) error {
196+ // Apply network configuration before any subcommand can make an HTTP
197+ // request. Standard proxy environment variables retain precedence.
198+ applyConfiguredHTTPProxy()
199+
196200 // Subcommand router: `zot bot ...` is handled separately so the
197201 // generic flag parser doesn't reject "bot" as a positional arg.
198202 if handled, err := runBotCommand(rawArgs, version); handled {
@@ -193,6 +193,10 @@ func fanoutAgentEvent(mgr *extensions.Manager, ev core.AgentEvent) {
193 193
194 // Run is the top-level entrypoint for the zot binary.194 // Run is the top-level entrypoint for the zot binary.
195 func Run(rawArgs []string, version string) error {195 func Run(rawArgs []string, version string) error {
196+ // Apply network configuration before any subcommand can make an HTTP
197+ // request. Standard proxy environment variables retain precedence.
198+ applyConfiguredHTTPProxy()
199+
196 // Subcommand router: `zot bot ...` is handled separately so the200 // Subcommand router: `zot bot ...` is handled separately so the
197 // generic flag parser doesn't reject "bot" as a positional arg.201 // generic flag parser doesn't reject "bot" as a positional arg.
198 if handled, err := runBotCommand(rawArgs, version); handled {202 if handled, err := runBotCommand(rawArgs, version); handled {
M packages/agent/config.go +24 -0
@@ -108,6 +108,10 @@ type Config struct {
108108 // Insecure skips TLS verification for custom inference endpoints.
109109 Insecure bool `json:"insecure,omitempty"`
110110
111+ // HTTPProxy is a global proxy URL used for HTTP and HTTPS requests when
112+ // the corresponding standard proxy environment variable is not already set.
113+ HTTPProxy string `json:"http_proxy,omitempty"`
114+
111115 // LastChangelogShown is the version whose release-notes
112116 // dialog the user has already seen. When the running binary's
113117 // version differs, the next interactive run shows the
@@ -224,6 +228,26 @@ func SaveConfig(c Config) error {
224228 return os.WriteFile(ConfigPath(), b, 0o644)
225229 }
226230
231+// applyConfiguredHTTPProxy makes the persisted proxy available to Go's
232+// standard HTTP transport and to child processes. Explicit environment
233+// variables take precedence over config.json.
234+func applyConfiguredHTTPProxy() {
235+ cfg, err := LoadConfig()
236+ if err != nil {
237+ return
238+ }
239+ proxy := strings.TrimSpace(cfg.HTTPProxy)
240+ if proxy == "" {
241+ return
242+ }
243+ if os.Getenv("HTTP_PROXY") == "" && os.Getenv("http_proxy") == "" {
244+ _ = os.Setenv("HTTP_PROXY", proxy)
245+ }
246+ if os.Getenv("HTTPS_PROXY") == "" && os.Getenv("https_proxy") == "" {
247+ _ = os.Setenv("HTTPS_PROXY", proxy)
248+ }
249+}
250+
227251 // AuthStoreFor returns the auth.Store backed by AuthPath().
228252 func AuthStoreFor() *auth.Store { return auth.NewStore(AuthPath()) }
229253
@@ -108,6 +108,10 @@ type Config struct {
108 // Insecure skips TLS verification for custom inference endpoints.108 // Insecure skips TLS verification for custom inference endpoints.
109 Insecure bool `json:"insecure,omitempty"`109 Insecure bool `json:"insecure,omitempty"`
110 110
111+ // HTTPProxy is a global proxy URL used for HTTP and HTTPS requests when
112+ // the corresponding standard proxy environment variable is not already set.
113+ HTTPProxy string `json:"http_proxy,omitempty"`
114+
111 // LastChangelogShown is the version whose release-notes115 // LastChangelogShown is the version whose release-notes
112 // dialog the user has already seen. When the running binary's116 // dialog the user has already seen. When the running binary's
113 // version differs, the next interactive run shows the117 // version differs, the next interactive run shows the
@@ -224,6 +228,26 @@ func SaveConfig(c Config) error {
224 return os.WriteFile(ConfigPath(), b, 0o644)228 return os.WriteFile(ConfigPath(), b, 0o644)
225 }229 }
226 230
231+// applyConfiguredHTTPProxy makes the persisted proxy available to Go's
232+// standard HTTP transport and to child processes. Explicit environment
233+// variables take precedence over config.json.
234+func applyConfiguredHTTPProxy() {
235+ cfg, err := LoadConfig()
236+ if err != nil {
237+ return
238+ }
239+ proxy := strings.TrimSpace(cfg.HTTPProxy)
240+ if proxy == "" {
241+ return
242+ }
243+ if os.Getenv("HTTP_PROXY") == "" && os.Getenv("http_proxy") == "" {
244+ _ = os.Setenv("HTTP_PROXY", proxy)
245+ }
246+ if os.Getenv("HTTPS_PROXY") == "" && os.Getenv("https_proxy") == "" {
247+ _ = os.Setenv("HTTPS_PROXY", proxy)
248+ }
249+}
250+
227 // AuthStoreFor returns the auth.Store backed by AuthPath().251 // AuthStoreFor returns the auth.Store backed by AuthPath().
228 func AuthStoreFor() *auth.Store { return auth.NewStore(AuthPath()) }252 func AuthStoreFor() *auth.Store { return auth.NewStore(AuthPath()) }
229 253
A packages/agent/config_proxy_test.go +71 -0
@@ -0,0 +1,71 @@
1+package agent
2+
3+import (
4+ "os"
5+ "testing"
6+)
7+
8+func TestApplyConfiguredHTTPProxy(t *testing.T) {
9+ t.Setenv("ZOT_HOME", t.TempDir())
10+ for _, key := range []string{"HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"} {
11+ t.Setenv(key, "")
12+ }
13+ if err := SaveConfig(Config{HTTPProxy: " http://proxy.example:8080 "}); err != nil {
14+ t.Fatal(err)
15+ }
16+
17+ applyConfiguredHTTPProxy()
18+
19+ if got := getenvForTest("HTTP_PROXY"); got != "http://proxy.example:8080" {
20+ t.Fatalf("HTTP_PROXY = %q", got)
21+ }
22+ if got := getenvForTest("HTTPS_PROXY"); got != "http://proxy.example:8080" {
23+ t.Fatalf("HTTPS_PROXY = %q", got)
24+ }
25+}
26+
27+func TestApplyConfiguredHTTPProxyPreservesEnvironment(t *testing.T) {
28+ t.Setenv("ZOT_HOME", t.TempDir())
29+ t.Setenv("HTTP_PROXY", "http://http-env.example:8080")
30+ t.Setenv("http_proxy", "")
31+ t.Setenv("HTTPS_PROXY", "")
32+ t.Setenv("https_proxy", "http://https-env.example:8080")
33+ if err := SaveConfig(Config{HTTPProxy: "http://config.example:8080"}); err != nil {
34+ t.Fatal(err)
35+ }
36+
37+ applyConfiguredHTTPProxy()
38+
39+ if got := getenvForTest("HTTP_PROXY"); got != "http://http-env.example:8080" {
40+ t.Fatalf("HTTP_PROXY = %q", got)
41+ }
42+ if got := getenvForTest("HTTPS_PROXY"); got != "" {
43+ t.Fatalf("HTTPS_PROXY = %q, want empty because lowercase environment setting takes precedence", got)
44+ }
45+ if got := getenvForTest("https_proxy"); got != "http://https-env.example:8080" {
46+ t.Fatalf("https_proxy = %q", got)
47+ }
48+}
49+
50+func TestApplyConfiguredHTTPProxyIgnoresEmptySetting(t *testing.T) {
51+ t.Setenv("ZOT_HOME", t.TempDir())
52+ for _, key := range []string{"HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"} {
53+ t.Setenv(key, "")
54+ }
55+ if err := SaveConfig(Config{HTTPProxy: " "}); err != nil {
56+ t.Fatal(err)
57+ }
58+
59+ applyConfiguredHTTPProxy()
60+
61+ if got := getenvForTest("HTTP_PROXY"); got != "" {
62+ t.Fatalf("HTTP_PROXY = %q", got)
63+ }
64+ if got := getenvForTest("HTTPS_PROXY"); got != "" {
65+ t.Fatalf("HTTPS_PROXY = %q", got)
66+ }
67+}
68+
69+func getenvForTest(key string) string {
70+ return os.Getenv(key)
71+}
@@ -0,0 +1,71 @@
1+package agent
2+
3+import (
4+ "os"
5+ "testing"
6+)
7+
8+func TestApplyConfiguredHTTPProxy(t *testing.T) {
9+ t.Setenv("ZOT_HOME", t.TempDir())
10+ for _, key := range []string{"HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"} {
11+ t.Setenv(key, "")
12+ }
13+ if err := SaveConfig(Config{HTTPProxy: " http://proxy.example:8080 "}); err != nil {
14+ t.Fatal(err)
15+ }
16+
17+ applyConfiguredHTTPProxy()
18+
19+ if got := getenvForTest("HTTP_PROXY"); got != "http://proxy.example:8080" {
20+ t.Fatalf("HTTP_PROXY = %q", got)
21+ }
22+ if got := getenvForTest("HTTPS_PROXY"); got != "http://proxy.example:8080" {
23+ t.Fatalf("HTTPS_PROXY = %q", got)
24+ }
25+}
26+
27+func TestApplyConfiguredHTTPProxyPreservesEnvironment(t *testing.T) {
28+ t.Setenv("ZOT_HOME", t.TempDir())
29+ t.Setenv("HTTP_PROXY", "http://http-env.example:8080")
30+ t.Setenv("http_proxy", "")
31+ t.Setenv("HTTPS_PROXY", "")
32+ t.Setenv("https_proxy", "http://https-env.example:8080")
33+ if err := SaveConfig(Config{HTTPProxy: "http://config.example:8080"}); err != nil {
34+ t.Fatal(err)
35+ }
36+
37+ applyConfiguredHTTPProxy()
38+
39+ if got := getenvForTest("HTTP_PROXY"); got != "http://http-env.example:8080" {
40+ t.Fatalf("HTTP_PROXY = %q", got)
41+ }
42+ if got := getenvForTest("HTTPS_PROXY"); got != "" {
43+ t.Fatalf("HTTPS_PROXY = %q, want empty because lowercase environment setting takes precedence", got)
44+ }
45+ if got := getenvForTest("https_proxy"); got != "http://https-env.example:8080" {
46+ t.Fatalf("https_proxy = %q", got)
47+ }
48+}
49+
50+func TestApplyConfiguredHTTPProxyIgnoresEmptySetting(t *testing.T) {
51+ t.Setenv("ZOT_HOME", t.TempDir())
52+ for _, key := range []string{"HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"} {
53+ t.Setenv(key, "")
54+ }
55+ if err := SaveConfig(Config{HTTPProxy: " "}); err != nil {
56+ t.Fatal(err)
57+ }
58+
59+ applyConfiguredHTTPProxy()
60+
61+ if got := getenvForTest("HTTP_PROXY"); got != "" {
62+ t.Fatalf("HTTP_PROXY = %q", got)
63+ }
64+ if got := getenvForTest("HTTPS_PROXY"); got != "" {
65+ t.Fatalf("HTTPS_PROXY = %q", got)
66+ }
67+}
68+
69+func getenvForTest(key string) string {
70+ return os.Getenv(key)
71+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close