get_logs needs a unit from an allowlist (router-*, kea, unbound, networkd, nftables, ddclient, acme, sshd, kernel). API tokens are valid for 1, 7, 30 (default) or 90 days, or without limit. The overview and the Changes page say which token made the unapplied changes. options.json (router://options) no longer holds the options' source paths: they differ between builds of a release, so a router rebuilding itself offline had to build the file and failed (found by the update test). Web UI: panels flow into columns on wide screens and the width is no longer capped; on phones the navigation is a menu button; the Advanced page uses panels; VLAN dropdowns show their chosen names; the duplicate WireGuard heading is gone. basic: a subtest for wan -> local rules. SPECS.md is now the only document (decisions, how each part works, and what is not done yet); PLAN.md is gone. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
139 lines
8.8 KiB
HTML
139 lines
8.8 KiB
HTML
{{define "tunnelform"}}
|
|
{{$t := .T}}{{$e := $t.Errors}}{{$csrf := .CSRF}}
|
|
<form method="post" action="/wireguard/save">
|
|
<input type="hidden" name="csrf" value="{{$csrf}}">
|
|
<fieldset>
|
|
<legend>{{if .New}}New tunnel{{else}}{{$t.Name}}{{end}}</legend>
|
|
<label>Name <input name="name" value="{{$t.Name}}" {{if .New}}placeholder="family"{{else}}readonly{{end}} size="14"></label>
|
|
{{with index $e "name"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>Listen port <input name="listenPort" value="{{$t.ListenPort}}" size="6" inputmode="numeric" placeholder="none (dial-out)"></label>
|
|
{{with index $e "listenPort"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>Addresses <input name="addresses" value="{{$t.Addresses}}" size="34" placeholder="10.100.0.1/24, fd42:100::1/64"></label>
|
|
{{with index $e "addresses"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label><input type="checkbox" name="internet" {{if $t.Internet}}checked{{end}}> Peers may reach the internet</label>
|
|
<label><input type="checkbox" name="allowManagement" {{if $t.AllowManagement}}checked{{end}}> Peers may reach SSH on the router</label>
|
|
<label><input type="checkbox" name="dns" {{if $t.DNS}}checked{{end}}> The router answers DNS on the tunnel address</label>
|
|
<label><input type="checkbox" name="masquerade" {{if $t.Masquerade}}checked{{end}}> Source-NAT IPv4 leaving through this tunnel</label>
|
|
<label><input type="checkbox" name="killSwitch" {{if $t.KillSwitch}}checked{{end}}> Kill switch: drop the peers' networks instead of letting them leave via the uplink</label>
|
|
<details>
|
|
<summary>More</summary>
|
|
<label>Interface <input name="interface" value="{{$t.Interface}}" size="12" placeholder="wg-{{if $t.Name}}{{$t.Name}}{{else}}name{{end}}"></label>
|
|
{{with index $e "interface"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>Firewall zone <input name="zone" value="{{$t.Zone}}" size="12" placeholder="{{if $t.Name}}{{$t.Name}}{{else}}name{{end}}"></label>
|
|
{{with index $e "zone"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>MTU <input name="mtu" value="{{$t.MTU}}" size="5" inputmode="numeric" placeholder="1420"></label>
|
|
{{with index $e "mtu"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>Source addresses <input name="sourceAddresses" value="{{$t.SourceAddresses}}" size="24" placeholder="192.168.10.1"></label>
|
|
{{with index $e "sourceAddresses"}}<span class="field-error">{{.}}</span>{{end}}
|
|
</details>
|
|
|
|
<p>
|
|
{{if $t.KeyPresent}}Public key: <code>{{$t.PublicKey}}</code>{{else if not .New}}<span class="field-error">No private key.</span>{{else}}A private key is generated for it unless you paste one.{{end}}
|
|
</p>
|
|
<label>Private key <input type="password" name="privateKey" autocomplete="off" size="46"
|
|
placeholder="{{if $t.KeyPresent}}stored; paste to replace{{else}}paste an existing key (optional){{end}}"></label>
|
|
{{with index $e "privateKey"}}<span class="field-error">{{.}}</span>{{end}}
|
|
{{if not .New}}<label><input type="checkbox" name="newKey"> Generate a new private key (every peer must be given the new public key)</label>{{end}}
|
|
</fieldset>
|
|
|
|
<fieldset>
|
|
<legend>Peers of {{if .New}}the new tunnel{{else}}{{$t.Name}}{{end}}</legend>
|
|
<input type="hidden" name="peers" value="{{len $t.Peers}}">
|
|
<div class="scroll">
|
|
<table>
|
|
<tr><th>Name</th><th>Public key</th><th>Endpoint</th><th>Allowed IPs</th><th>Keepalive</th><th>Preshared key</th><th>Now</th><th>Remove</th></tr>
|
|
{{range $i, $p := $t.Peers}}
|
|
{{$st := index $t.Live $p.PublicKey}}
|
|
<tr>
|
|
<td><input name="peer.{{$i}}.name" value="{{$p.Name}}" size="10" placeholder="new"></td>
|
|
<td><input name="peer.{{$i}}.publicKey" value="{{$p.PublicKey}}" size="30" spellcheck="false"></td>
|
|
<td><input name="peer.{{$i}}.endpoint" value="{{$p.Endpoint}}" size="18" placeholder="host:port"></td>
|
|
<td><input name="peer.{{$i}}.allowed" value="{{$p.Allowed}}" size="22"></td>
|
|
<td><input name="peer.{{$i}}.keepalive" value="{{$p.Keepalive}}" size="4" inputmode="numeric"></td>
|
|
<td><input type="password" name="peer.{{$i}}.psk" autocomplete="off" size="12" placeholder="{{if $p.PSKSet}}stored{{end}}">
|
|
<label><input type="checkbox" name="peer.{{$i}}.newPsk"> new</label></td>
|
|
<td>{{if $p.PublicKey}}{{if not $st.Handshake.IsZero}}<span class="ok">{{ago $st.Handshake}} ago</span>
|
|
<span class="muted">{{bytes $st.RX}} in, {{bytes $st.TX}} out</span>{{else if $st.RX}}<span class="muted">no handshake</span>{{else if $t.Live}}<span class="muted">never</span>{{end}}{{end}}</td>
|
|
<td>{{if $p.Name}}<input type="checkbox" name="peer.{{$i}}.remove">{{end}}</td>
|
|
</tr>
|
|
{{range $f := list "name" "publicKey" "endpoint" "allowed" "keepalive" "psk"}}{{with index $e (printf "peers[%d].%s" $i $f)}}<tr><td colspan="8" class="field-error">{{$f}}: {{.}}</td></tr>{{end}}{{end}}
|
|
{{end}}
|
|
</table>
|
|
</div>
|
|
<p class="hint">Fill in the last row to add a peer. Allowed IPs are the destinations sent into the tunnel
|
|
(and what the router accepts from the peer). Pasted preshared keys are stored on the router and not shown again.</p>
|
|
</fieldset>
|
|
<button>Save</button>
|
|
</form>
|
|
{{if not .New}}
|
|
<form method="post" action="/wireguard/delete" class="inline">
|
|
<input type="hidden" name="csrf" value="{{$csrf}}"><input type="hidden" name="name" value="{{$t.Name}}">
|
|
<label><input type="checkbox" name="confirm"> delete {{$t.Name}}</label> <button>Delete tunnel</button>
|
|
<span class="hint">The key file stays on the router.</span>
|
|
</form>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{define "content"}}
|
|
{{with .Data}}
|
|
<p class="hint">Changes are saved here and take effect when applied. Keys are made on the router and stay on it.
|
|
{{if not .Live}}Live status is not available.{{end}}</p>
|
|
|
|
{{range .Tunnels}}
|
|
<section>
|
|
<h2>{{.Name}} <span class="muted">{{.Device}}</span></h2>
|
|
{{template "tunnelform" (tform . $.CSRF false)}}
|
|
</section>
|
|
{{end}}
|
|
|
|
<section>
|
|
<h2>Add a phone or laptop</h2>
|
|
{{if .Config}}
|
|
<div class="flash">
|
|
<p><strong>{{.Config.Name}}</strong>: scan this with the WireGuard app or save the text as a config file.
|
|
It contains the client's private key and is shown only now; apply the settings so the router accepts it.</p>
|
|
{{if .Config.QR}}<div class="qr">{{.Config.QR}}</div>{{end}}
|
|
<pre>{{.Config.Text}}</pre>
|
|
</div>
|
|
{{end}}
|
|
{{if .Tunnels}}
|
|
{{$c := .Client}}{{$e := $c.Errors}}
|
|
<form method="post" action="/wireguard/client" class="narrow">
|
|
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
|
<label>Tunnel <select name="tunnel">{{range .Tunnels}}<option value="{{.Name}}" {{if eq .Name $c.Tunnel}}selected{{end}}>{{.Name}}</option>{{end}}</select></label>
|
|
{{with index $e "tunnel"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>Name <input name="name" value="{{$c.Name}}" placeholder="nils-phone"></label>
|
|
{{with index $e "name"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>Client address <input name="address" value="{{$c.Address}}" placeholder="10.100.0.2"></label>
|
|
{{with index $e "address"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>Router's public address <input name="endpoint" value="{{$c.Endpoint}}" placeholder="vpn.example.org:51820" spellcheck="false"></label>
|
|
{{with index $e "endpoint"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>Send through the tunnel <input name="allowed" value="{{$c.Allowed}}"></label>
|
|
{{with index $e "allowed"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<label>DNS server <input name="dns" value="{{$c.DNS}}" placeholder="optional"></label>
|
|
{{with index $e "dns"}}<span class="field-error">{{.}}</span>{{end}}
|
|
<button>Add and show the configuration</button>
|
|
</form>
|
|
{{else}}<p class="muted">Add a tunnel first.</p>{{end}}
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Import a tunnel</h2>
|
|
<p class="hint">Paste an existing WireGuard config (a <code>wg-quick</code> file or <code>wg showconf</code> output).
|
|
Its keys are stored on the router as root-only files and not shown again. Options the router has no
|
|
equivalent for (DNS, PostUp, ...) are skipped, and a peer that sends everything (<code>0.0.0.0/0</code>) has to be
|
|
changed to the networks you want through the tunnel.</p>
|
|
<form method="post" action="/wireguard/import">
|
|
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
|
<label>Tunnel name <input name="importName" value="{{.ImportName}}" placeholder="family" size="14"></label>
|
|
<textarea name="importText" rows="12" spellcheck="false" autocomplete="off" placeholder="[Interface] PrivateKey = ... Address = 10.100.0.1/24 ListenPort = 51820 [Peer] PublicKey = ... AllowedIPs = 10.100.0.2/32">{{.ImportText}}</textarea>
|
|
<button>Import</button>
|
|
</form>
|
|
</section>
|
|
|
|
<section>
|
|
{{template "tunnelform" (tform .New $.CSRF true)}}
|
|
</section>
|
|
{{end}}
|
|
{{end}}
|