Skip to content

Commit b311d32

Browse files
docs: update templates.md and run-json.md for new features
templates.md: - env var access section ({{ env.VAR }}) - |split filter entry in filter table - strip_json5 toggle documentation - {% extends %} + {% block %} pattern with full example - Clarify deep-merge fallback is only used without {% extends %} run-json.md: - Custom executable checks: any non-built-in check name runs as binary with HCH_PROXY_ADDR + HCH_TIMEOUT env vars - strip_json5 field documentation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 194da05 commit b311d32

2 files changed

Lines changed: 121 additions & 22 deletions

File tree

docs/run-json.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ Empty = run locally. See [ssh-deploy.md](ssh-deploy.md).
188188

189189
## `checks` (array of strings)
190190

191+
Built-in checks:
192+
191193
| Value | What runs |
192194
|---|---|
193195
| `dns` | UDP DNS query via the proxy |
@@ -199,10 +201,45 @@ Empty = run locally. See [ssh-deploy.md](ssh-deploy.md).
199201
| `upload` | Upload throughput |
200202
| `speedtest` | Alias: `download` + `upload` + `ping` |
201203

204+
**Custom executable check** — any path that is not a built-in name is
205+
executed as a binary. Exit code 0 = PASS, non-zero = FAIL.
206+
207+
```json5
208+
"checks": [
209+
"dns", "http",
210+
// "./my-tester.sh", // uncomment to enable custom check
211+
// "/usr/local/bin/my-check" // absolute path also works
212+
]
213+
```
214+
215+
The binary receives these env vars:
216+
217+
| Var | Value |
218+
|---|---|
219+
| `HCH_PROXY_ADDR` | `socks5://host:port` (the client SOCKS proxy) |
220+
| `HCH_TIMEOUT` | timeout in seconds |
221+
222+
Stdout/stderr from the binary appears in the result `Extra` field (truncated to 200 chars).
223+
202224
Default: `["dns", "http"]`.
203225

204226
---
205227

228+
## `strip_json5` (bool, default `true`)
229+
230+
When `true` (default), JSON5 extensions (comments, trailing commas) are stripped
231+
from rendered config files before passing them to the core binary.
232+
233+
Set to `false` for cores that natively accept JSON5, or to debug rendered output:
234+
235+
```json5
236+
{
237+
"strip_json5": false
238+
}
239+
```
240+
241+
---
242+
206243
## `before_start` / `after_stop` (array of strings)
207244

208245
Shell commands (`sh -c`). `before_start` runs before any process starts;

docs/templates.md

Lines changed: 84 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
Config files are rendered through a two-stage pipeline:
44

55
```
6-
template source → Pongo2 render → JSON5 strip → [base merge] → valid JSON → core process
6+
template source → Pongo2 render → JSON5 strip → valid JSON → core process
7+
↑ ↑
8+
extends/block/include skipped when strip_json5:false
9+
resolves automatically
710
```
811

912
---
@@ -48,14 +51,30 @@ Both forms work:
4851
}
4952
```
5053

54+
### Environment variable access
55+
56+
Templates can read OS environment variables via the `env` map:
57+
58+
```json5
59+
"private_key": "{{ env.WG_SERVER_PRIVKEY }}",
60+
"api_token": "{{ env.MY_API_KEY }}",
61+
{% if env.DEBUG %}"log_level": "debug",{% endif %}
62+
```
63+
64+
Missing env vars render as empty string — no error.
65+
5166
### Loops
5267

53-
Pass a structured context via the runner (future extension) for loops:
68+
Use `|split` filter to iterate over comma-separated strings:
5469

5570
```json5
56-
"users": [
57-
{% for u in users %}
58-
{"uuid": "{{ u.uuid }}", "email": "{{ u.email }}"},
71+
// run.json.j2 — auto-generate variant list
72+
"vars": [
73+
{% for flow in ",xtls-rprx-vision"|split:"," %}
74+
{
75+
"TITLE": "{% if flow %}vless-flow{% else %}plain-tls{% endif %}",
76+
"VLESS_FLOW": "{{ flow }}"
77+
},
5978
{% endfor %}
6079
]
6180
```
@@ -64,15 +83,14 @@ Pass a structured context via the runner (future extension) for loops:
6483

6584
<https://github.com/flosch/pongo2?tab=readme-ov-file#filters>
6685

67-
Common ones:
68-
6986
| Filter | Example | Result |
7087
|---|---|---|
7188
| `default` | `{{ X\|default:"foo" }}` | `foo` if X empty |
7289
| `lower` | `{{ NAME\|lower }}` | lowercase |
7390
| `upper` | `{{ NAME\|upper }}` | UPPERCASE |
7491
| `truncatechars` | `{{ S\|truncatechars:8 }}` | first 8 chars |
7592
| `replace` | `{{ S\|replace:"a":"b" }}` | char replace |
93+
| `split` | `{{ "a,b"\|split:"," }}` | list (for `{% for %}`) |
7694

7795
---
7896

@@ -98,35 +116,79 @@ standard JSON that proxy cores accept.
98116
}
99117
```
100118

101-
### What is NOT stripped
119+
### Disabling JSON5 stripping
120+
121+
Some cores accept comments natively, or you want to debug rendered output.
122+
Set `"strip_json5": false` in `run.json`:
123+
124+
```json5
125+
{
126+
"strip_json5": false, // keep // and # comments in rendered config
127+
...
128+
}
129+
```
130+
131+
Default: `true` (strip — required by most proxy cores).
132+
133+
### What is NOT stripped (when strip_json5:true)
102134

103135
- `//` inside a string value: `"url": "https://example.com/path//foo"` → kept
104136
- `#` inside a string value: `"color": "#ff0000"` → kept
105137
- Single-quoted strings: `'value'` → normalised to `"value"`
106138

107139
---
108140

109-
## Base template composition
141+
## Template inheritance: `{% extends %}` + `{% block %}`
110142

111-
When a core has a `templates/base/<role>.json.j2` file (e.g.
112-
`examples/xray/templates/base/client.json.j2`), the runner:
143+
**Recommended pattern** — protocol templates extend the base template.
144+
Pongo2 handles composition natively; no Go-side merging.
113145

114-
1. Renders the protocol template (e.g. `vless-xhttp/client.json.j2`)
115-
2. Renders the base template with the same resolved vars
116-
3. Deep-merges them: **base = defaults, protocol = overrides**
117-
118-
This lets protocol templates be minimal (just `outbounds`) while the base
119-
provides the outer shell (`log`, `inbounds`/SOCKS, `routing`):
146+
`examples/xray/templates/base/client.json.j2`:
120147

148+
```json5
149+
{
150+
"log": {"loglevel": "{{ LOG_LEVEL }}"},
151+
"inbounds": [
152+
{"tag": "socks-in", "port": {{ SOCKS_PORT }}, "protocol": "socks"}
153+
],
154+
"outbounds": [
155+
{% block outbound %}
156+
{"protocol": "freedom", "tag": "direct"}
157+
{% endblock %}
158+
]
159+
}
121160
```
122-
examples/xray/templates/base/client.json.j2 ← log + socks inbound + routing
123-
examples/xray/vless-xhttp/client.json.j2 ← just the vless outbound
124-
↓ deep merge
125-
full xray client config
161+
162+
`examples/xray/vless-xhttp/client.json.j2`:
163+
164+
```json5
165+
{% extends "../templates/base/client.json.j2" %}
166+
167+
{% block outbound %}
168+
{
169+
"tag": "vless-out",
170+
"protocol": "vless",
171+
"streamSettings": {
172+
{% include "../templates/tls/client.tpl" %}
173+
"network": "xhttp"
174+
}
175+
},
176+
{"protocol": "freedom", "tag": "direct"}
177+
{% endblock %}
126178
```
127179

180+
- `{% extends %}` must be the first statement in the file
181+
- `{% include %}` still works inside blocks
182+
- When `{% extends %}` is detected, the Go-side deep-merge fallback is skipped
183+
184+
## Deep-merge fallback (without `{% extends %}`)
185+
186+
If a template does **not** use `{% extends %}`, the runner checks for
187+
`templates/base/<role>.json.j2` in ancestor directories and deep-merges:
188+
base = defaults, protocol = overrides.
189+
128190
**Merge rules:**
129-
- Object keys present only in base → kept
191+
- Object keys only in base → kept
130192
- Object keys in both → protocol wins (recursive for nested objects)
131193
- Arrays → protocol array replaces base array entirely
132194
- `null` in protocol → removes the key from base

0 commit comments

Comments
 (0)