diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 645983194..3ed84ff9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -164,11 +164,6 @@ jobs: printf "\n\nSystem environment:\n\n" env - - name: Fetch GUI - run: make fetch-gui - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Build rclone run: | make diff --git a/bin/fetch-gui-dist.sh b/bin/fetch-gui-dist.sh index e15a3fe94..9346102ba 100755 --- a/bin/fetch-gui-dist.sh +++ b/bin/fetch-gui-dist.sh @@ -1,16 +1,22 @@ #!/bin/bash -# Fetch the latest GUI dist from rclone/rclone-web GitHub releases. +# Fetch the latest GUI dist.zip from rclone/rclone-web GitHub releases. # -# Downloads dist.zip from the latest release and extracts it to -# cmd/gui/dist/. Skips the download if the local tag matches. +# Downloads dist.zip from the latest release to cmd/gui/dist.zip and +# records the tag in cmd/gui/dist.tag. Both files are committed to the +# repo so that builds are reproducible and `go build` works on a fresh +# clone without needing to fetch anything. # -# Requires: curl, unzip +# Skips the download when both dist.zip and dist.tag exist and the tag +# matches the latest release. +# +# Requires: curl set -euo pipefail REPO="rclone/rclone-web" -DEST="cmd/gui/dist" -TAG_FILE="${DEST}/.tag" +DEST_DIR="cmd/gui" +ZIP_FILE="${DEST_DIR}/dist.zip" +TAG_FILE="${DEST_DIR}/dist.tag" CURL_OPTS=(-fSs --retry 5 --retry-delay 2 --retry-all-errors) @@ -49,14 +55,16 @@ sys.exit(1) echo "Latest release: ${TAG}" -# Check if we already have this version -if [ -f "${TAG_FILE}" ] && [ "$(cat "${TAG_FILE}")" = "${TAG}" ]; then +# Skip only when both the zip and the tag are present and the tag matches. +# If only the tag exists (e.g. someone deleted the zip), force a re-download. +if [ -f "${ZIP_FILE}" ] && [ -f "${TAG_FILE}" ] && [ "$(cat "${TAG_FILE}")" = "${TAG}" ]; then echo "Already up to date (${TAG})" exit 0 fi -# Download dist.zip -TMPFILE=$(mktemp /tmp/rclone-gui-dist.XXXXXX.zip) +# Download dist.zip directly to its final location, via a temp file so a +# failed download doesn't leave a partial file behind. +TMPFILE=$(mktemp "${DEST_DIR}/.dist.zip.XXXXXX") trap 'rm -f "${TMPFILE}"' EXIT echo "Downloading dist.zip from ${TAG}..." @@ -65,17 +73,8 @@ curl -L "${CURL_OPTS[@]}" "${AUTH_HEADER[@]}" -o "${TMPFILE}" "${ASSET_URL}" || exit 1 } -# Extract -echo "Extracting to ${DEST}/..." -rm -rf "${DEST}" -mkdir -p "${DEST}" -unzip -q "${TMPFILE}" -d "${DEST}" - -# Restore marker files -git checkout "${DEST}"/.gitignore -git checkout "${DEST}"/README.md - -# Write tag for cache comparison +mv "${TMPFILE}" "${ZIP_FILE}" +chmod 644 "${ZIP_FILE}" echo -n "${TAG}" > "${TAG_FILE}" -echo "Done. GUI dist updated to ${TAG}" +echo "Done. ${ZIP_FILE} updated to ${TAG}" diff --git a/cmd/gui/dist.tag b/cmd/gui/dist.tag new file mode 100644 index 000000000..a5ba93251 --- /dev/null +++ b/cmd/gui/dist.tag @@ -0,0 +1 @@ +1.1.7 \ No newline at end of file diff --git a/cmd/gui/dist.zip b/cmd/gui/dist.zip new file mode 100644 index 000000000..bfb41323f Binary files /dev/null and b/cmd/gui/dist.zip differ diff --git a/cmd/gui/dist/.gitignore b/cmd/gui/dist/.gitignore deleted file mode 100644 index 58bbeb139..000000000 --- a/cmd/gui/dist/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# This directory gets the web gui release which we ignore -* -# Except this file which we use to keep the directory available -!.gitignore diff --git a/cmd/gui/dist/README.md b/cmd/gui/dist/README.md deleted file mode 100644 index 45a48533b..000000000 --- a/cmd/gui/dist/README.md +++ /dev/null @@ -1 +0,0 @@ -Use `make fetch-gui` to populate this directory. diff --git a/cmd/gui/gui.go b/cmd/gui/gui.go index a21108efe..0a7ea42d4 100644 --- a/cmd/gui/gui.go +++ b/cmd/gui/gui.go @@ -3,8 +3,9 @@ package gui import ( "archive/zip" + "bytes" "context" - "embed" + _ "embed" "fmt" iofs "io/fs" "net/http" @@ -24,8 +25,11 @@ import ( "github.com/spf13/cobra" ) -//go:embed dist -var assets embed.FS +//go:embed dist.zip +var distZip []byte + +//go:embed dist.tag +var distTag string var ( guiAddr []string @@ -192,7 +196,11 @@ For more help see [the GUI docs](/gui/). guiServer.Serve() guiURL := guiServer.URLs()[0] - fs.Logf(nil, "Serving GUI on %s", guiURL) + guiSource := fmt.Sprintf("version %s", strings.TrimSpace(distTag)) + if srcPath != "" { + guiSource = fmt.Sprintf("from %s", srcPath) + } + fs.Logf(nil, "Serving GUI %s on %s", guiSource, guiURL) // Open browser loginURL := buildLoginURL(guiURL, rcURL, opt.Auth.BasicUser, opt.Auth.BasicPass, opt.NoAuth) @@ -231,20 +239,20 @@ func originFromURL(rawURL string) string { } // guiSourceFS opens the GUI bundle at the given path. An empty path -// returns the embedded bundle. The returned cleanup func must be -// called on shutdown (no-op for embedded/DirFS, Close for the zip -// reader). +// returns the embedded bundle (read from the zip embedded in the binary). +// The returned cleanup func must be called on shutdown (no-op for the +// embedded bundle and DirFS, Close for an external zip reader). func guiSourceFS(path string) (iofs.FS, func() error, error) { noop := func() error { return nil } if path == "" { - sub, err := iofs.Sub(assets, "dist") + zr, err := zip.NewReader(bytes.NewReader(distZip), int64(len(distZip))) if err != nil { - return nil, nil, fmt.Errorf("embedded GUI dir not found: was `make fetch-gui` run before building?: %w", err) + return nil, nil, fmt.Errorf("failed to read embedded GUI zip: was `make fetch-gui` run before building?: %w", err) } - if _, err := iofs.Stat(sub, "index.html"); err != nil { - return nil, nil, fmt.Errorf("embedded GUI not found: was `make fetch-gui` run before building?: %w", err) + if _, err := iofs.Stat(zr, "index.html"); err != nil { + return nil, nil, fmt.Errorf("embedded GUI has no index.html: was `make fetch-gui` run before building?: %w", err) } - return sub, noop, nil + return zr, noop, nil } info, err := os.Stat(path) if err != nil { diff --git a/docs/content/gui.md b/docs/content/gui.md index 8011739ee..72e32f20b 100644 --- a/docs/content/gui.md +++ b/docs/content/gui.md @@ -120,4 +120,6 @@ Tailscale (all free). ## History -In v1.74 the GUI was redone and embedded within rclone for ease of use. +In v1.74 the GUI was redone and embedded within rclone for ease of +use. The GUI bundle ships as a compressed zip embedded in the rclone +binary and is served from the zip at runtime.