fix tunnel embed, acme renew

Kim committed Mar 6, 2026 at 13:52 UTC 5e2ed3a00b09f21c7b754ac94306838642c9686c
2 files changed +39 -28
cmd/relay-server/tunnel.go
+8 -20
@@ -5,8 +5,6 @@ import (
5 "encoding/hex"
6 "fmt"
7 "net/http"
8 - "os"
9 - "path/filepath"
8 "strings"
9 )
10
@@ -199,46 +197,36 @@ func serveTunnelBinary(w http.ResponseWriter, r *http.Request) {
197 slug = strings.TrimSuffix(slug, ".sha256")
198 }
199
202 - path, ok := tunnelBinaryPathBySlug(slug)
200 + data, filename, ok := tunnelBinaryBySlug(slug)
201 if !ok {
202 http.NotFound(w, r)
203 return
204 }
207 -
208 - data, err := os.ReadFile(path)
209 - if err != nil {
210 - http.NotFound(w, r)
211 - return
212 - }
205 sum := sha256.Sum256(data)
206 checksumHex := hex.EncodeToString(sum[:])
207
208 if checksumRequest {
209 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
210 if r.Method == http.MethodGet {
219 - _, _ = fmt.Fprintf(w, "%s portal-tunnel-%s\n", checksumHex, slug)
211 + _, _ = fmt.Fprintf(w, "%s %s\n", checksumHex, filename)
212 }
213 return
214 }
215
216 w.Header().Set("Content-Type", "application/octet-stream")
217 + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
218 w.Header().Set("X-Checksum-Sha256", checksumHex)
219 if r.Method == http.MethodGet {
220 _, _ = w.Write(data)
221 }
222 }
223
231 -func tunnelBinaryPathBySlug(slug string) (string, bool) {
232 - candidates := []string{
233 - filepath.Join("dist", "tunnel", tunnelBinaryName(slug)),
234 - filepath.Join("bin", tunnelBinaryName(slug)),
235 - }
236 - for _, candidate := range candidates {
237 - if _, err := os.Stat(candidate); err == nil {
238 - return candidate, true
239 - }
224 +func tunnelBinaryBySlug(slug string) ([]byte, string, bool) {
225 + filename := tunnelBinaryName(slug)
226 + if data, err := embeddedDistFS.ReadFile("dist/tunnel/" + filename); err == nil {
227 + return data, filename, true
228 }
241 - return "", false
229 + return nil, "", false
230 }
231
232 func tunnelBinaryName(slug string) string {
portal/acme/acme.go
+31 -8
@@ -24,6 +24,7 @@ import (
24 lego "github.com/go-acme/lego/v4/lego"
25 "github.com/go-acme/lego/v4/providers/dns/cloudflare"
26 "github.com/go-acme/lego/v4/registration"
27 + "github.com/rs/zerolog/log"
28 )
29
30 const (
@@ -32,6 +33,9 @@ const (
33 accountKeyFileName = "acme-account.key"
34 registrationFileName = "acme-registration.json"
35 defaultACMEEmailPrefix = "acme@"
36 + defaultRenewInterval = 24 * time.Hour
37 + defaultDNSSyncInterval = 10 * time.Minute
38 + defaultSyncTimeout = 2 * time.Minute
39 )
40
41 type Config struct {
@@ -99,7 +103,7 @@ func (m *Manager) EnsureCertificate(ctx context.Context) (string, string, error)
103 return "", "", errors.New("cloudflare token is required for non-local relay certificates")
104 }
105
102 - if err := EnsureDNSRecords(ctx, m.cfg.BaseDomain, m.cfg.CloudflareToken); err != nil {
106 + if err := m.syncDNS(ctx); err != nil {
107 return "", "", fmt.Errorf("ensure dns records: %w", err)
108 }
109
@@ -124,7 +128,7 @@ func (m *Manager) Start(ctx context.Context) {
128
129 m.startOnce.Do(func() {
130 m.wg.Add(1)
127 - go m.renewalLoop(ctx)
131 + go m.maintenanceLoop(ctx)
132 })
133 }
134
@@ -195,11 +199,13 @@ func (m *Manager) provision(ctx context.Context) error {
199 return nil
200 }
201
198 -func (m *Manager) renewalLoop(ctx context.Context) {
202 +func (m *Manager) maintenanceLoop(ctx context.Context) {
203 defer m.wg.Done()
204
201 - ticker := time.NewTicker(24 * time.Hour)
202 - defer ticker.Stop()
205 + renewTicker := time.NewTicker(defaultRenewInterval)
206 + dnsTicker := time.NewTicker(defaultDNSSyncInterval)
207 + defer renewTicker.Stop()
208 + defer dnsTicker.Stop()
209
210 for {
211 select {
@@ -207,17 +213,34 @@ func (m *Manager) renewalLoop(ctx context.Context) {
213 return
214 case <-m.stopCh:
215 return
210 - case <-ticker.C:
216 + case <-dnsTicker.C:
217 + syncCtx, cancel := context.WithTimeout(ctx, defaultSyncTimeout)
218 + err := m.syncDNS(syncCtx)
219 + cancel()
220 + if err != nil {
221 + log.Warn().Err(err).Str("base_domain", m.cfg.BaseDomain).Msg("sync dns records")
222 + }
223 + case <-renewTicker.C:
224 if !m.shouldRenew() {
225 continue
226 }
214 - renewCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
215 - _ = m.provision(renewCtx)
227 + renewCtx, cancel := context.WithTimeout(ctx, defaultSyncTimeout)
228 + err := m.provision(renewCtx)
229 cancel()
230 + if err != nil {
231 + log.Warn().Err(err).Str("base_domain", m.cfg.BaseDomain).Msg("renew acme certificate")
232 + }
233 }
234 }
235 }
236
237 +func (m *Manager) syncDNS(ctx context.Context) error {
238 + if m == nil || isLocalhost(m.cfg.BaseDomain) || m.cfg.CloudflareToken == "" {
239 + return nil
240 + }
241 + return EnsureDNSRecords(ctx, m.cfg.BaseDomain, m.cfg.CloudflareToken)
242 +}
243 +
244 func (m *Manager) shouldRenew() bool {
245 m.mu.RLock()
246 defer m.mu.RUnlock()