clientupdate,net/tstun: add support for OpenWrt 25.12.0 using apk (#18545)

OpenWrt is changing to using alpine like `apk` for package installation
over its previous opkg. Additionally, they are not using the same repo
files as alpine making installation fail.

Add support for the new repository files and ensure that the required
package detection system uses apk.

Updates #18535

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
This commit is contained in:
Claus Lensbøl
2026-03-05 13:39:07 -05:00
committed by GitHub
parent d82e478dbc
commit 1b53c00f2b
3 changed files with 188 additions and 29 deletions
+124
View File
@@ -6,9 +6,12 @@ package clientupdate
import (
"archive/tar"
"compress/gzip"
"encoding/json"
"fmt"
"io/fs"
"maps"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"slices"
@@ -299,6 +302,127 @@ tailscale-1.58.2-r0 installed size:
}
}
func TestCheckOutdatedAlpineRepo(t *testing.T) {
anyToString := func(a any) string {
str, ok := a.(string)
if !ok {
panic("failed to parse param as string")
}
return str
}
tests := []struct {
name string
fileContent string
latestHTTPVersion string
latestApkVersion string
wantHTTPVersion string
wantApkVersion string
wantAlpineVersion string
track string
}{
{
name: "Up to date",
fileContent: "https://dl-cdn.alpinelinux.org/alpine/v3.20/main",
latestHTTPVersion: "1.95.3",
latestApkVersion: "1.95.3",
track: "unstable",
},
{
name: "Behind unstable",
fileContent: "https://dl-cdn.alpinelinux.org/alpine/v3.20/main",
latestHTTPVersion: "1.95.4",
latestApkVersion: "1.95.3",
wantHTTPVersion: "1.95.4",
wantApkVersion: "1.95.3",
wantAlpineVersion: "v3.20",
track: "unstable",
},
{
name: "Behind stable",
fileContent: "https://dl-cdn.alpinelinux.org/alpine/v2.40/main",
latestHTTPVersion: "1.94.3",
latestApkVersion: "1.92.1",
wantHTTPVersion: "1.94.3",
wantApkVersion: "1.92.1",
wantAlpineVersion: "v2.40",
track: "stable",
},
{
name: "Nothing in dist file",
fileContent: "",
latestHTTPVersion: "1.94.3",
latestApkVersion: "1.92.1",
wantHTTPVersion: "1.94.3",
wantApkVersion: "1.92.1",
track: "stable",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir, err := os.MkdirTemp("", "example")
if err != nil {
t.Fatalf("error creating temp dir: %v", err)
}
t.Cleanup(func() { os.RemoveAll(dir) }) // clean up
file := filepath.Join(dir, "distfile")
if err := os.WriteFile(file, []byte(tt.fileContent), 0o666); err != nil {
t.Fatalf("error creating dist file: %v", err)
}
testServ := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, _ *http.Request) {
version := trackPackages{
MSIsVersion: tt.latestHTTPVersion,
MacZipsVersion: tt.latestHTTPVersion,
TarballsVersion: tt.latestHTTPVersion,
SPKsVersion: tt.latestHTTPVersion,
}
jsonData, err := json.Marshal(version)
if err != nil {
t.Errorf("failed to marshal version string: %v", err)
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(jsonData); err != nil {
t.Errorf("failed to write json blob: %v", err)
}
},
))
defer testServ.Close()
oldEndpoint := tailscaleHTTPEndpoint
tailscaleHTTPEndpoint = testServ.URL
defer func() { tailscaleHTTPEndpoint = oldEndpoint }()
var paramLatest string
var paramApkVer string
var paramAlpineVer string
logf := func(_ string, params ...any) {
paramLatest = anyToString(params[0])
paramApkVer = anyToString(params[1])
if len(params) > 2 {
paramAlpineVer = anyToString(params[2])
}
}
err = checkOutdatedAlpineRepo(logf, []string{file}, tt.latestApkVersion, tt.track)
if err != nil {
t.Errorf("did not expect error, got: %v", err)
}
if paramLatest != tt.wantHTTPVersion {
t.Errorf("expected HTTP version '%s', got '%s'", tt.wantHTTPVersion, paramLatest)
}
if paramApkVer != tt.wantApkVersion {
t.Errorf("expected APK version '%s', got '%s'", tt.wantApkVersion, paramApkVer)
}
if paramAlpineVer != tt.wantAlpineVersion {
t.Errorf("expected alpine version '%s', got '%s'", tt.wantAlpineVersion, paramAlpineVer)
}
})
}
}
func TestSynoArch(t *testing.T) {
tests := []struct {
goarch string