mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-15 01:53:08 -04:00
Adds a CLI subcommand that downloads a signed Tailscale appliance image (Gokrazy archive format, GAF) from pkgs.tailscale.com, constructs a fresh GPT-partitioned disk from it (mbr.img + a synthesized partition table + boot.img + root.img), formats /perm as ext4 in pure Go via go-diskfs, and ejects the disk so a user running on a regular workstation can flash an SD card or homelab VM disk in one command without installing e2fsprogs. On macOS the target disk is auto-discovered via diskutil, skipping the boot disk and anything bigger than 256 GB out of paranoia. On Linux the user passes --disk=/dev/sdX explicitly. Windows is not supported yet and the command returns an error. The GPT layout matches monogok's full-disk layout via the new public github.com/bradfitz/monogok/disklayout package; a drift- guard test inside monogok asserts the two implementations stay byte-identical so OTA updates against monogok-built images keep working. Behind a ts_omit_flashappliance build tag (on by default). Updates #1866 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: Ic1a8cd185e7039edccb7702ab4104544fcb58d29
43 lines
1.2 KiB
Go
43 lines
1.2 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package distsign
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"tailscale.com/types/logger"
|
|
)
|
|
|
|
// DownloadVerified is a convenience wrapper around [Client.Download]
|
|
// for callers that have a full URL (e.g.
|
|
// https://pkgs.tailscale.com/unstable/foo.gaf) rather than a base URL
|
|
// plus path. It splits srcURL into a base ("scheme://host") and a path,
|
|
// constructs a [Client] for the base, and downloads with signature
|
|
// verification to dstPath.
|
|
func DownloadVerified(ctx context.Context, logf logger.Logf, srcURL, dstPath string) error {
|
|
if logf == nil {
|
|
logf = logger.Discard
|
|
}
|
|
u, err := url.Parse(srcURL)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing URL %q: %w", srcURL, err)
|
|
}
|
|
if u.Scheme == "" || u.Host == "" {
|
|
return fmt.Errorf("URL %q is missing scheme or host", srcURL)
|
|
}
|
|
base := &url.URL{Scheme: u.Scheme, User: u.User, Host: u.Host}
|
|
path := strings.TrimPrefix(u.Path, "/")
|
|
if path == "" {
|
|
return fmt.Errorf("URL %q has no path component", srcURL)
|
|
}
|
|
c, err := NewClient(logf, base.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.Download(ctx, path, dstPath)
|
|
}
|