gokrazy, clientupdate: add start of Gokrazy auto-updates, tests

This adds support for Gokrazy GAF (Gokrazy Archive Format) zip
auto-updates, starting to wire up Tailscale's clientupdate mechanism
to Gokrazy's update mechanism.

Currently there's just a CLI command to update from a GAF URL,
with an --unsigned flag for use in a new natlab vmtest.

Next step would be publishing unstable track GAF files on
pkgs.tailscale.com, with detached signatures, and then making the
clientupdate mechanism also download those and check signatures.

Updates #20002

Change-Id: Ib03c56f17a57f8a4638398ef83549dac4813323d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-06-04 11:20:14 -07:00
committed by Brad Fitzpatrick
parent 6ff761c5f8
commit 772be1b0cc
15 changed files with 474 additions and 22 deletions
+3
View File
@@ -4,6 +4,9 @@ help:
image:
go run build.go --build
gaf:
go run build.go --gaf
qemu: image
qemu-system-x86_64 -m 1G -drive file=tsapp.img,format=raw -boot d -netdev user,id=user.0 -device virtio-net-pci,netdev=user.0 -serial mon:stdio -audio none
+26 -12
View File
@@ -30,6 +30,7 @@ var (
app = flag.String("app", "tsapp", "appliance name; one of the subdirectories of gokrazy/")
bucket = flag.String("bucket", "tskrazy-import", "S3 bucket to upload disk image to while making AMI")
build = flag.Bool("build", false, "if true, just build locally and stop, without uploading")
gaf = flag.Bool("gaf", false, "if true, build a gokrazy archive format file instead of a full disk image")
)
func findMkfsExt4() (string, error) {
@@ -95,7 +96,7 @@ func main() {
if err := buildImage(); err != nil {
log.Fatalf("build image: %v", err)
}
if *build {
if *build || *gaf {
log.Printf("built. stopping.")
return
}
@@ -122,11 +123,6 @@ func main() {
}
func buildImage() error {
mkfs, err := findMkfsExt4()
if err != nil {
return err
}
dir, err := os.Getwd()
if err != nil {
return err
@@ -134,19 +130,37 @@ func buildImage() error {
if fi, err := os.Stat(filepath.Join(dir, *app)); err != nil || !fi.IsDir() {
return fmt.Errorf("in wrong directory %v; no %q subdirectory found", dir, *app)
}
// Build the tsapp.img
args := []string{"run", "github.com/bradfitz/monogok/cmd/monogok"}
if *gaf {
args = append(args,
"overwrite",
"--gaf", filepath.Join(dir, *app+".gaf"),
)
} else {
args = append(args,
"overwrite",
"--full", filepath.Join(dir, *app+".img"),
"--target_storage_bytes=1258299392",
)
}
var buf bytes.Buffer
cmd := exec.Command("go", "run",
"github.com/bradfitz/monogok/cmd/monogok",
"overwrite",
"--full", filepath.Join(dir, *app+".img"),
"--target_storage_bytes=1258299392")
cmd := exec.Command("go", args...)
cmd.Dir = filepath.Join(dir, *app)
cmd.Stdout = io.MultiWriter(os.Stdout, &buf)
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
if *gaf {
return nil
}
mkfs, err := findMkfsExt4()
if err != nil {
return err
}
// monogok overwrite emits a line of text saying how to run mkfs.ext4
// to create the ext4 /perm filesystem. Parse that and run it.