mirror of
https://github.com/rclone/rclone.git
synced 2026-05-12 10:03:35 -04:00
gui: embed compressed dist.zip in the binary for smaller, reproducible builds
Previously `make fetch-gui` extracted the GUI release into cmd/gui/dist/ and the unpacked tree was embedded uncompressed via `//go:embed dist`. This commits and embeds the GUI bundle (dist.zip) and its release tag (dist.tag) to the repo so: - the rclone binary is smaller - `go build` works on a fresh clone without first running fetch-gui - a given commit pins an exact GUI version The "Fetch GUI" step was removed from .github/workflows/build.yml.
This commit is contained in:
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
1
cmd/gui/dist.tag
Normal file
1
cmd/gui/dist.tag
Normal file
@@ -0,0 +1 @@
|
||||
1.1.7
|
||||
BIN
cmd/gui/dist.zip
Normal file
BIN
cmd/gui/dist.zip
Normal file
Binary file not shown.
4
cmd/gui/dist/.gitignore
vendored
4
cmd/gui/dist/.gitignore
vendored
@@ -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
|
||||
1
cmd/gui/dist/README.md
vendored
1
cmd/gui/dist/README.md
vendored
@@ -1 +0,0 @@
|
||||
Use `make fetch-gui` to populate this directory.
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user