Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions cmd/fj-bellows/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,12 +513,23 @@ func bootWGStack(ctx context.Context, cfg *config.Config, prov provider.Provider
return nil, func() {}, nil
}
cache := cacheRenderInputs(ctx, cfg, prov)
// Only run the FJB-99 bootstrap poll when the operator hasn't
// statically configured a peer. Skipping the 3-minute poll under
// static config keeps daemon startup fast and matches the
// back-compat path the FJB-91 e2e harness exercises against the
// persistent test cache.
var cachePubkey, cacheEndpoint string
if cfg.Transport.WG != nil && (cfg.Transport.WG.Peer.PublicKey == "" || cfg.Transport.WG.Peer.Endpoint == "") {
cachePubkey, cacheEndpoint = discoverCachePeer(ctx, prov, log)
}
stack, err := wgboot.Boot(ctx, wgboot.Config{
Transport: cfg.Transport,
ForgejoURL: cfg.Forgejo.URL,
ACLSink: aclSinkFor(prov),
Cache: cache,
Logger: log,
Transport: cfg.Transport,
ForgejoURL: cfg.Forgejo.URL,
ACLSink: aclSinkFor(prov),
Cache: cache,
Logger: log,
CachePubkey: cachePubkey,
CacheEndpoint: cacheEndpoint,
})
if err != nil {
return nil, func() {}, fmt.Errorf("transport bootstrap: %w", err)
Expand All @@ -530,6 +541,46 @@ func bootWGStack(ctx context.Context, cfg *config.Config, prov provider.Provider
}, nil
}

// discoverCachePeer runs the orchestrator side of the FJB-99 bootstrap
// loop: poll the managed cache's Object Storage bucket for the cache's
// WG pubkey (published by cache cloud-init's fjb-wg-bootstrap.sh) and
// read the cache's public IPv4 from the Linode API to build the WG
// peer endpoint. Returns empty strings (no error) when the provider
// can't supply these — wgboot then falls back to the static
// transport.wg.peer.{public_key,endpoint} config knobs and errors at
// boot if those are also empty.
//
// Bounded at 8 minutes — cache cloud-init's apt update + wireguard +
// awscli install dominates the time-to-pubkey, and on a cold Linode
// (no package cache, slow mirror) it can stretch past 5 min; the
// bound is generous so we don't fail a healthy bootstrap on a slow
// mirror day.
func discoverCachePeer(ctx context.Context, prov provider.Provider, log *slog.Logger) (pubkey, endpoint string) {
type pubkeyWaiter interface {
WaitForCacheWGPubkey(ctx context.Context, timeout time.Duration) (string, error)
}
type endpointReader interface {
CachePublicEndpoint(ctx context.Context) (string, error)
}
if pw, ok := prov.(pubkeyWaiter); ok {
start := time.Now()
if pk, err := pw.WaitForCacheWGPubkey(ctx, 8*time.Minute); err == nil {
log.Info("wgboot: cache wg-pubkey discovered", "elapsed", time.Since(start))
pubkey = pk
} else {
log.Warn("wgboot: cache wg-pubkey discovery failed; falling back to static config", "err", err)
}
}
if er, ok := prov.(endpointReader); ok {
if ep, err := er.CachePublicEndpoint(ctx); err == nil {
endpoint = ep
} else {
log.Warn("wgboot: cache public endpoint discovery failed; falling back to static config", "err", err)
}
}
return pubkey, endpoint
}

// cacheRenderInputs pulls the cache-side facts the renderer needs out
// of whatever live provider state is available. Today only the Linode
// provider carries a managed cache; the function returns whatever it
Expand Down
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.26
require (
connectrpc.com/connect v1.20.0
github.com/linode/linodego v1.69.1
github.com/minio/minio-go/v7 v7.2.0
github.com/prometheus/client_golang v1.23.2
go.uber.org/goleak v1.3.0
golang.org/x/crypto v0.52.0
Expand All @@ -19,15 +20,27 @@ require (
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-resty/resty/v2 v2.17.2 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/tinylib/msgp v1.6.1 // indirect
github.com/zeebo/xxh3 v1.1.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
Expand Down
35 changes: 31 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
Expand All @@ -17,10 +19,17 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand All @@ -29,8 +38,16 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/linode/linodego v1.69.1 h1:f45N2MHR/oece2/ktTTCYmrlfse4//k3NgwcF5zbGZ0=
github.com/linode/linodego v1.69.1/go.mod h1:Fha0NYsQSx5VZK1HQNJY/z/dIxxkFp+vb5veawbmAUw=
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.2.0 h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
github.com/minio/minio-go/v7 v7.2.0/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
Expand All @@ -41,8 +58,10 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand All @@ -52,10 +71,18 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
Expand Down
18 changes: 10 additions & 8 deletions internal/config/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,16 @@ func (w *WG) validate() error {
}

func (p *WGPeer) validate() error {
if p.PublicKey == "" {
return errors.New("public_key is required")
}
if p.Endpoint == "" {
return errors.New("endpoint is required")
}
if _, _, err := net.SplitHostPort(p.Endpoint); err != nil {
return fmt.Errorf("endpoint %q is not host:port: %w", p.Endpoint, err)
// public_key + endpoint are runtime-discoverable from FJB-99's
// cache wg-bootstrap loop (the cache writes its pubkey to S3; the
// orchestrator reads the cache's public IPv4 from the Linode API).
// Either may be empty at config-validate time; wgboot's planBoot
// is the load-bearing check that both end up populated by the
// time the tunnel is brought up.
if p.Endpoint != "" {
if _, _, err := net.SplitHostPort(p.Endpoint); err != nil {
return fmt.Errorf("endpoint %q is not host:port: %w", p.Endpoint, err)
}
}
if len(p.AllowedIPs) == 0 {
return errors.New("allowed_ips must list at least one CIDR")
Expand Down
25 changes: 0 additions & 25 deletions internal/config/transport_wg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ func TestWG_DefaultKeepaliveOneSecond(t *testing.T) {
}
}

//nolint:funlen // table-driven: 11 small validation cases inline for readability.
func TestWG_Validation(t *testing.T) {
cases := []struct {
name string
Expand Down Expand Up @@ -200,30 +199,6 @@ func TestWG_Validation(t *testing.T) {
`,
wantSub: `local_addr = "not-a-cidr"`,
},
{
name: "missing peer public key",
wgBlock: `
wg:
private_key_file: /tmp/k
local_addr: 10.99.0.1/32
peer:
endpoint: 172.234.203.50:51820
allowed_ips: [10.99.0.2/32]
`,
wantSub: "public_key is required",
},
{
name: "missing peer endpoint",
wgBlock: `
wg:
private_key_file: /tmp/k
local_addr: 10.99.0.1/32
peer:
public_key: AbcDefGhiJklMnoPqrStuVwxYzAbcDefGhiJklMnoPqs=
allowed_ips: [10.99.0.2/32]
`,
wantSub: "endpoint is required",
},
{
name: "bad peer endpoint shape",
wgBlock: `
Expand Down
9 changes: 9 additions & 0 deletions internal/provider/linode/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ type managedBucket struct {
// endpoint is the S3 endpoint URL for the bucket's region, looked up
// once at ensureAtConfigure.
endpoint string

// accessKey + secretKey are the scoped Object Storage credentials
// minted at ensureAtConfigure. Stored so the orchestrator can sign
// S3 GETs against the bucket for the FJB-99 wg-pubkey discovery
// loop. Cleared on maybeCleanup.
accessKey string
secretKey string
}

func newManagedBucket(tag, region, label string, client bucketClient, log *slog.Logger) *managedBucket {
Expand Down Expand Up @@ -114,6 +121,8 @@ func (m *managedBucket) ensureAtConfigure(ctx context.Context) (bucketCreds, err
return bucketCreds{}, fmt.Errorf("create scoped object storage key: %w", err)
}
m.keyID = created.ID
m.accessKey = created.AccessKey
m.secretKey = created.SecretKey

return bucketCreds{
Bucket: m.label,
Expand Down
31 changes: 21 additions & 10 deletions internal/provider/linode/cache-cloud-init.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ packages:
{{- end}}
{{- if .OrchestratorWGPubkey}}
- wireguard
- awscli
{{- end}}
write_files:
- path: /etc/zot/config.json
Expand Down Expand Up @@ -83,7 +82,12 @@ write_files:
# boots reuse the persisted private key. wg0.conf is always
# rewritten so a config bump (peer pubkey rotation, port change)
# takes effect on reboot without operator intervention.
set -euo pipefail
#
# Logs go to /var/log/fjb-wg-bootstrap.log — read via Lish or
# cat to debug a failed bootstrap post-mortem.
exec >>/var/log/fjb-wg-bootstrap.log 2>&1
echo "=== fjb-wg-bootstrap.sh starting at $(date -u +%Y-%m-%dT%H:%M:%SZ)"
set -euxo pipefail
umask 0077
install -d -m 0700 /etc/wireguard
if [ ! -s /etc/wireguard/privatekey ]; then
Expand All @@ -105,14 +109,21 @@ write_files:
EOC
chmod 0600 /etc/wireguard/wg0.conf
systemctl enable --now wg-quick@wg0
# Publish own pubkey so the orchestrator can read it from S3 and
# complete the wg peer config on its side (FJB-99 Phase B).
AWS_ACCESS_KEY_ID='{{.AccessKey}}' \
AWS_SECRET_ACCESS_KEY='{{.SecretKey}}' \
AWS_DEFAULT_REGION='{{.Region}}' \
aws --endpoint-url '{{.Endpoint}}' \
s3 cp /etc/wireguard/publickey \
s3://{{.Bucket}}/wg-pubkey.txt
# Publish own pubkey via a pre-signed S3 PUT URL the orchestrator
# baked into cloud-init at create time. No awscli on the cache:
# awscli on Debian 13 has a NoneType-iteration bug hitting Linode
# Object Storage that breaks both `s3 cp` and `s3api put-object`,
# so we sidestep it entirely by pre-signing on the orchestrator
# (minio-go) and having the cache just curl the URL. The
# orchestrator's GET path (also minio-go) reads it back.
# --fail-with-body so a 4xx/5xx response prints the XML error
# body to the bootstrap log instead of silently failing. -sS
# silences the progress bar but keeps the error path printable.
curl --fail-with-body -sS \
--upload-file /etc/wireguard/publickey \
'{{.WGPubkeyPutURL}}' \
|| { echo "FATAL: curl PUT failed (exit=$?)"; exit 1; }
echo "wg-pubkey upload OK"
{{- end}}
- path: /etc/systemd/system/zot.service
permissions: '0644'
Expand Down
Loading