Add connect-on-launch and notarized desktop release packaging
This commit is contained in:
@@ -76,7 +76,9 @@ what we take vs. skip). Never vendor it into the build.
|
|||||||
needs CGO). It runs the same `internal/client` service in-process and uses
|
needs CGO). It runs the same `internal/client` service in-process and uses
|
||||||
`~/.config/tunnel/config.json`. If the CLI service is already running, stop
|
`~/.config/tunnel/config.json`. If the CLI service is already running, stop
|
||||||
it first — the app shows a banner and offers to stop it. `--autostart`
|
it first — the app shows a banner and offers to stop it. `--autostart`
|
||||||
connects on launch. On macOS, `scripts/build-desktop-app.sh <version>` builds
|
connects on launch; Settings has **Connect on launch** for the same behavior
|
||||||
|
when opened normally. Add the app to macOS Login Items for start-at-login.
|
||||||
|
On macOS, `scripts/build-desktop-app.sh <version>` builds
|
||||||
and ad-hoc signs `Tunnel.app`; the installed copy is
|
and ad-hoc signs `Tunnel.app`; the installed copy is
|
||||||
`~/Applications/Tunnel.app`. Cross-OS builds need that OS's CGO toolchain;
|
`~/Applications/Tunnel.app`. Cross-OS builds need that OS's CGO toolchain;
|
||||||
`CGO_ENABLED=0` does not work for Fyne.
|
`CGO_ENABLED=0` does not work for Fyne.
|
||||||
|
|||||||
@@ -113,8 +113,9 @@ open "$HOME/Applications/Tunnel.app" --args --autostart
|
|||||||
It uses `~/.config/tunnel/config.json` like the CLI, shows tunnel status and
|
It uses `~/.config/tunnel/config.json` like the CLI, shows tunnel status and
|
||||||
certificate expiry, routes with copy/open/remove buttons, live logs, settings
|
certificate expiry, routes with copy/open/remove buttons, live logs, settings
|
||||||
and profile import, and has a start/stop button that runs the service
|
and profile import, and has a start/stop button that runs the service
|
||||||
in-process. If the CLI service is already running, a banner offers to stop it
|
in-process. The Settings dialog has **Connect on launch**; add the app to
|
||||||
first.
|
macOS Login Items (or the Linux/Windows equivalent) to reconnect at login. If
|
||||||
|
the CLI service is already running, a banner offers to stop it first.
|
||||||
|
|
||||||
Fyne needs CGO: the macOS build is tested; Linux and Windows must be built on
|
Fyne needs CGO: the macOS build is tested; Linux and Windows must be built on
|
||||||
those platforms (or with a native CGO cross toolchain), not with
|
those platforms (or with a native CGO cross toolchain), not with
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func TestNormalizeTarget(t *testing.T) {
|
|||||||
func TestConfigRoundTrip(t *testing.T) {
|
func TestConfigRoundTrip(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
path := filepath.Join(dir, "config.json")
|
path := filepath.Join(dir, "config.json")
|
||||||
cfg := &Config{API: "https://tunnel.example", Routes: map[string]Route{}}
|
cfg := &Config{API: "https://tunnel.example", AutoConnect: true, Routes: map[string]Route{}}
|
||||||
if err := cfg.AddRoute("api", "4096", ""); err != nil {
|
if err := cfg.AddRoute("api", "4096", ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func TestConfigRoundTrip(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if loaded.API != cfg.API || loaded.Routes["api"].Target != "127.0.0.1:4096" {
|
if loaded.API != cfg.API || !loaded.AutoConnect || loaded.Routes["api"].Target != "127.0.0.1:4096" {
|
||||||
t.Errorf("round trip: %+v", loaded)
|
t.Errorf("round trip: %+v", loaded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type Config struct {
|
|||||||
Hostname string `json:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty"`
|
||||||
Token string `json:"token,omitempty"`
|
Token string `json:"token,omitempty"`
|
||||||
CertificateExpiry string `json:"certificate_expiry,omitempty"`
|
CertificateExpiry string `json:"certificate_expiry,omitempty"`
|
||||||
|
AutoConnect bool `json:"auto_connect,omitempty"`
|
||||||
Routes map[string]Route `json:"routes,omitempty"`
|
Routes map[string]Route `json:"routes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func Run(version string, autostart bool) {
|
|||||||
fyne.Do(a.refresh)
|
fyne.Do(a.refresh)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if autostart {
|
if autostart || a.cfg.AutoConnect {
|
||||||
a.start()
|
a.start()
|
||||||
}
|
}
|
||||||
a.app.Run()
|
a.app.Run()
|
||||||
@@ -370,8 +370,13 @@ func (a *App) settingsDialog() {
|
|||||||
api := widget.NewEntry()
|
api := widget.NewEntry()
|
||||||
api.SetText(a.cfg.API)
|
api.SetText(a.cfg.API)
|
||||||
api.SetPlaceHolder("https://tunnel.example.com")
|
api.SetPlaceHolder("https://tunnel.example.com")
|
||||||
|
autoConnect := widget.NewCheck("Connect on launch", nil)
|
||||||
|
autoConnect.SetChecked(a.cfg.AutoConnect)
|
||||||
dialog.NewForm("Settings", "Save", "Cancel",
|
dialog.NewForm("Settings", "Save", "Cancel",
|
||||||
[]*widget.FormItem{widget.NewFormItem("API", api)},
|
[]*widget.FormItem{
|
||||||
|
widget.NewFormItem("API", api),
|
||||||
|
widget.NewFormItem("Startup", autoConnect),
|
||||||
|
},
|
||||||
func(ok bool) {
|
func(ok bool) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@@ -382,6 +387,7 @@ func (a *App) settingsDialog() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.cfg.API = normalized
|
a.cfg.API = normalized
|
||||||
|
a.cfg.AutoConnect = autoConnect.Checked
|
||||||
if err := a.cfg.Save(a.configPath); err != nil {
|
if err := a.cfg.Save(a.configPath); err != nil {
|
||||||
a.showError(err)
|
a.showError(err)
|
||||||
return
|
return
|
||||||
|
|||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build, Developer ID sign, notarize, staple and zip the macOS app.
|
||||||
|
# Distribution must not skip this step; an ad-hoc build is not a release.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
VERSION="${1:-0.2.0}"
|
||||||
|
BUILD="${BUILD:-1}"
|
||||||
|
APP="Tunnel.app"
|
||||||
|
OUT=".local/release"
|
||||||
|
ZIP="$OUT/Tunnel-$VERSION.zip"
|
||||||
|
KEY="$HOME/.config/kapture/AuthKey_K5KXMMSB28.p8"
|
||||||
|
KEY_ID="K5KXMMSB28"
|
||||||
|
ISSUER="3e25e5dc-8b75-453f-a71c-c7f3feaac628"
|
||||||
|
|
||||||
|
FYNE="$(command -v fyne || true)"
|
||||||
|
[ -n "$FYNE" ] || FYNE="$(go env GOPATH)/bin/fyne"
|
||||||
|
[ -x "$FYNE" ] || { echo "install fyne: go install fyne.io/tools/cmd/fyne@latest"; exit 1; }
|
||||||
|
[ -f "$KEY" ] || { echo "missing notary key: $KEY"; exit 1; }
|
||||||
|
|
||||||
|
IDENT="$(security find-identity -v -p codesigning | awk '/Developer ID Application:/{print $2; exit}')"
|
||||||
|
[ -n "$IDENT" ] || { echo "no Developer ID Application identity"; exit 1; }
|
||||||
|
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
go build -o .local/Tunnel -ldflags "-X main.version=$VERSION" ./cmd/tunnel-desktop
|
||||||
|
rm -rf "$APP"
|
||||||
|
"$FYNE" package -os darwin \
|
||||||
|
-executable .local/Tunnel \
|
||||||
|
-name Tunnel -app-id dev.tzgyn.tunnel \
|
||||||
|
-app-version "$VERSION" -app-build "$BUILD" \
|
||||||
|
-icon assets/icon.png -release
|
||||||
|
|
||||||
|
codesign --force --deep --options runtime --timestamp --sign "$IDENT" "$APP"
|
||||||
|
codesign --verify --deep --strict --verbose=2 "$APP"
|
||||||
|
|
||||||
|
ditto -c -k --keepParent "$APP" "$ZIP"
|
||||||
|
xcrun notarytool submit "$ZIP" --key "$KEY" --key-id "$KEY_ID" --issuer "$ISSUER" \
|
||||||
|
--wait --output-format json
|
||||||
|
|
||||||
|
xcrun stapler staple "$APP"
|
||||||
|
xcrun stapler validate "$APP"
|
||||||
|
spctl -a -vv "$APP" 2>&1 | tee "$OUT/spctl.txt"
|
||||||
|
grep -q "source=Notarized Developer ID" "$OUT/spctl.txt" || {
|
||||||
|
echo "notarization did not produce a Notarized Developer ID app; not releasing"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -f "$ZIP"
|
||||||
|
ditto -c -k --keepParent "$APP" "$ZIP"
|
||||||
|
shasum -a 256 "$ZIP" | tee "$OUT/Tunnel-$VERSION.zip.sha256"
|
||||||
|
echo "Release app: $ZIP"
|
||||||
Reference in New Issue
Block a user