Compare commits

...
4 Commits
Author SHA1 Message Date
Jakob Borg 2ca95cf149 build: give macos security set-key-partition-list the correct password
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-09-08 08:57:55 +02:00
Jakob Borg 62f6060ceb Merge branch 'infrastructure'
* infrastructure:
  chore(discosrv): add debug headers to ping response
  chore(stupgrades): allow templating the asset URL
2026-09-08 08:07:35 +02:00
Jakob Borg c10408263e chore(discosrv): add debug headers to ping response
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-09-08 08:06:31 +02:00
Jakob Borg 6c25cdebf6 chore(stupgrades): allow templating the asset URL
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-09-08 08:06:31 +02:00
3 changed files with 50 additions and 24 deletions

No files matched your search

+1 -1
View File
@@ -445,7 +445,7 @@ jobs:
CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12
echo "$DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d -o "$CERTIFICATE_PATH"
security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_PATH" -P "$DEVELOPER_ID_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign
security set-key-partition-list -S apple-tool:,apple: -s -k actions "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Set the codesign identity for following steps
echo "CODESIGN_IDENTITY=$CODESIGN_IDENTITY" >> $GITHUB_ENV
+26 -9
View File
@@ -7,6 +7,7 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
@@ -20,6 +21,7 @@ import (
"strconv"
"strings"
"sync"
"text/template"
"time"
"github.com/alecthomas/kong"
@@ -31,11 +33,12 @@ import (
)
type cli struct {
Listen string `default:":8080" help:"Listen address"`
MetricsListen string `default:":8082" help:"Listen address for metrics"`
URL string `short:"u" default:"https://api.github.com/repos/syncthing/syncthing/releases?per_page=25" help:"GitHub releases url"`
Forward []string `short:"f" help:"Forwarded pages, format: /path->https://example/com/url"`
CacheTime time.Duration `default:"15m" help:"Cache time"`
Listen string `default:":8080" help:"Listen address"`
MetricsListen string `default:":8082" help:"Listen address for metrics"`
URL string `short:"u" default:"https://api.github.com/repos/syncthing/syncthing/releases?per_page=25" help:"GitHub releases url"`
Forward []string `short:"f" help:"Forwarded pages, format: /path->https://example/com/url"`
CacheTime time.Duration `default:"15m" help:"Cache time"`
AssetURLTemplate string `help:"Template for asset URLs, blank to use GitHub default" env:"ASSET_URL_TEMPLATE"`
}
func main() {
@@ -69,7 +72,7 @@ func server(params *cli) error {
}()
}
cache := &cachedReleases{url: params.URL}
cache := &cachedReleases{url: params.URL, assetURLTemplate: params.AssetURLTemplate}
if err := cache.Update(context.Background()); err != nil {
return fmt.Errorf("initial cache update: %w", err)
} else {
@@ -266,6 +269,7 @@ func filterForCompatibility(rels []upgrade.Release, ua, osv string) []upgrade.Re
type cachedReleases struct {
url string
assetURLTemplate string
mut sync.RWMutex
current []upgrade.Release
latestRel, latestPre string
@@ -278,7 +282,7 @@ func (c *cachedReleases) Releases() []upgrade.Release {
}
func (c *cachedReleases) Update(ctx context.Context) error {
rels, err := fetchGithubReleases(ctx, c.url)
rels, err := fetchGithubReleases(ctx, c.url, c.assetURLTemplate)
if err != nil {
return err
}
@@ -306,7 +310,7 @@ func (c *cachedReleases) Update(ctx context.Context) error {
return nil
}
func fetchGithubReleases(ctx context.Context, url string) ([]upgrade.Release, error) {
func fetchGithubReleases(ctx context.Context, url, assetURLTemplate string) ([]upgrade.Release, error) {
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url, nil)
if err != nil {
metricHTTPRequests.WithLabelValues("github-releases", "error").Inc()
@@ -325,12 +329,25 @@ func fetchGithubReleases(ctx context.Context, url string) ([]upgrade.Release, er
}
metricHTTPRequests.WithLabelValues("github-releases", "success").Inc()
tpl, err := template.New("asset").Parse(assetURLTemplate)
if err != nil {
return nil, err
}
// Move the URL used for browser downloads to the URL field, and remove
// the browser URL field. This avoids going via the GitHub API for
// downloads, since Syncthing uses the URL field.
for _, rel := range rels {
for j, asset := range rel.Assets {
rel.Assets[j].URL = asset.BrowserURL
if assetURLTemplate != "" {
buf := new(bytes.Buffer)
if err := tpl.Execute(buf, map[string]any{"Release": rel, "Asset": asset}); err != nil {
return nil, err
}
rel.Assets[j].URL = buf.String()
} else {
rel.Assets[j].URL = asset.BrowserURL
}
rel.Assets[j].BrowserURL = ""
}
}
+23 -14
View File
@@ -23,6 +23,7 @@ import (
"net"
"net/http"
"net/url"
"os"
"slices"
"strconv"
"strings"
@@ -154,21 +155,10 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
slog.Debug("Handling request", "id", reqID, "method", req.Method, "url", req.URL, "proto", req.Proto)
remoteAddr := &net.TCPAddr{
IP: nil,
Port: -1,
}
var remoteAddr *net.TCPAddr
if s.useHTTP {
// X-Forwarded-For can have multiple client IPs; split using the comma separator
forwardIP, _, _ := strings.Cut(req.Header.Get("X-Forwarded-For"), ",")
// net.ParseIP will return nil if leading/trailing whitespace exists; use strings.TrimSpace()
remoteAddr.IP = net.ParseIP(strings.TrimSpace(forwardIP))
if parsedPort, err := strconv.ParseInt(req.Header.Get("X-Client-Port"), 10, 0); err == nil {
remoteAddr.Port = int(parsedPort)
}
remoteAddr = forwardedRemoteAddr(req)
} else {
var err error
remoteAddr, err = net.ResolveTCPAddr("tcp", req.RemoteAddr)
@@ -191,6 +181,21 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
}
}
func forwardedRemoteAddr(req *http.Request) *net.TCPAddr {
forwardIP, _, _ := strings.Cut(req.Header.Get("X-Forwarded-For"), ",")
remoteAddr := &net.TCPAddr{
IP: net.ParseIP(strings.TrimSpace(forwardIP)),
Port: -1,
}
if parsedPort, err := strconv.ParseInt(req.Header.Get("X-Client-Port"), 10, 0); err == nil {
remoteAddr.Port = int(parsedPort)
}
return remoteAddr
}
func (s *apiSrv) handleGET(w http.ResponseWriter, req *http.Request) {
reqID := req.Context().Value(idKey).(requestID)
@@ -327,7 +332,11 @@ func (s *apiSrv) handleAnnounce(deviceID protocol.DeviceID, addresses []string)
return s.db.merge(&deviceID, dbAddrs, seen)
}
func handlePing(w http.ResponseWriter, _ *http.Request) {
func handlePing(w http.ResponseWriter, req *http.Request) {
hostname, _ := os.Hostname()
w.Header().Set("Discovery-Server-Instance", hostname)
w.Header().Set("Discovery-Client-Address", req.RemoteAddr)
w.Header().Set("Discovery-Client-Remote", forwardedRemoteAddr(req).String())
w.WriteHeader(http.StatusNoContent)
}