Skip to content

Conversation

@leaanthony
Copy link
Member

@leaanthony leaanthony commented Dec 20, 2025

Summary

Adds a new selfupdate service that enables Wails v3 applications to check for and apply updates from GitHub, GitLab, or Gitea releases.

  • Multi-source support: GitHub, GitLab, and Gitea release sources
  • Secure updates: ECDSA and PGP signature verification, plus checksum validation
  • Themable UI: Customizable progress window with colors, icons, and styling
  • Frontend integration: Progress events and full API access from JavaScript
  • CLI tooling: Key generation, signing, and verification commands

New Files

  • v3/pkg/services/selfupdate/selfupdate.go - Main service implementation
  • v3/internal/commands/selfupdate.go - CLI commands for keygen/sign/verify
  • v3/examples/selfupdate/ - Example application with themed UI
  • docs/src/content/docs/guides/selfupdate.mdx - Comprehensive documentation

Usage Example

app := application.New(application.Options{
    Services: []application.Service{
        application.NewService(selfupdate.NewWithConfig(&selfupdate.Config{
            CurrentVersion: version,
            Repository:     "myorg/myapp",
            Signature: &selfupdate.SignatureConfig{
                Type:      selfupdate.SignatureECDSA,
                PublicKey: publicKey,
            },
        })),
    },
})
const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/services/selfupdate");
const info = await Selfupdate.Check();
if (info.updateAvailable) {
    await Selfupdate.DownloadAndInstall();
    await Selfupdate.Restart();
}

Closes #1178

Test plan

  • Build and run the example application
  • Test key generation: wails3 tool selfupdate keygen -o ./keys
  • Test signing: wails3 tool selfupdate sign -key ./keys/update_private.pem -file ./binary
  • Test verification: wails3 tool selfupdate verify -key ./keys/update_public.pem -file ./binary
  • Test update check against a real GitHub repository
  • Test download and install flow
  • Verify progress events are emitted correctly
  • Test on Windows, Linux, and macOS

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Self-update service with support for GitHub, GitLab, and Gitea releases
    • Configurable signature verification options (ECDSA, PGP, checksum)
    • Themeable progress UI with frontend event handling
    • CLI commands for managing update signatures (keygen, sign, verify)
    • New example application demonstrating self-update functionality
    • Comprehensive self-update configuration and integration guide

✏️ Tip: You can customize this high-level summary in your review settings.

Add a new selfupdate service that enables Wails v3 applications to
check for and apply updates from GitHub, GitLab, or Gitea releases.

Features:
- Support for GitHub, GitLab, and Gitea release sources
- ECDSA and PGP signature verification for secure updates
- Checksum verification option
- Themable progress UI with customizable colors and icons
- Progress events for download tracking in frontend
- Automatic platform detection (OS/arch)
- CLI commands for key generation, signing, and verification

Closes #1178

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This PR introduces a comprehensive self-update feature for Wails v3 applications, including a service for checking and installing updates from GitHub/GitLab/Gitea, CLI tools for cryptographic signing and verification, a full example application, and detailed documentation with configuration options, signing workflows, and UI customization.

Changes

Cohort / File(s) Summary
Documentation
docs/src/content/docs/guides/selfupdate.mdx
Adds comprehensive guide covering setup, configuration, update checking, update installation, release signing, UI customization, frontend event handling, asset naming conventions, CI/CD integration, and troubleshooting.
Changelog & CLI Extensions
v3/UNRELEASED_CHANGELOG.md, v3/cmd/wails3/main.go
Changelog documents new self-update service and CLI tool commands. CLI adds selfupdate subcommand group with keygen, sign, and verify subcommands for release signing.
Core Self-Update Service
v3/pkg/services/selfupdate/selfupdate.go
Implements self-update service with support for multiple sources (GitHub, GitLab, Gitea), configurable signature verification (ECDSA, PGP, checksum), UI theming, progress event emission, and version/platform detection. Includes Check, DownloadAndInstall, Restart methods and related configuration types.
Cryptographic Utilities
v3/internal/commands/selfupdate.go
Implements signing/verification tooling with ECDSA key generation (P-256), SHA-256 based signing, and verification logic with PEM key handling and error reporting.
Example Application
v3/examples/selfupdate/main.go, v3/examples/selfupdate/assets/index.html
Example Wails app demonstrating self-update service configuration with GitHub as source, embedded assets, and HTML/JavaScript UI for update checks, progress visualization, release info display, and app restart controls.
Dependency Updates
v3/go.mod
Go version updated to 1.24.11; added go-selfupdate v1.5.2 and related transitive dependencies (Gitea SDK, GitHub API client, OAuth2); updated golang.org/x/\* modules and other stdlib/utility packages.

Sequence Diagram(s)

sequenceDiagram
    participant Frontend as Frontend/UI
    participant Service as Self-Update Service
    participant Source as GitHub/GitLab/Gitea
    participant FS as File System
    participant App as Application

    rect rgb(200, 220, 240)
    note over Frontend,Source: Check for Updates
    Frontend->>Service: Check(ctx)
    Service->>Source: Query latest release
    Source-->>Service: Release info
    Service->>Service: Validate version & signature
    Service-->>Frontend: UpdateInfo (available, version, notes)
    end

    rect rgb(240, 220, 200)
    note over Service,App: Download & Install
    Frontend->>Service: DownloadAndInstall(ctx)
    Service->>Source: Download release binary
    Source-->>Service: Binary stream
    Service->>Service: emit DownloadProgress event
    Frontend->>Frontend: Update progress bar
    Service->>FS: Extract & replace binary
    Service->>Service: Verify installation
    Service->>Service: Clear pending update state
    Service-->>Frontend: Success
    end

    rect rgb(220, 240, 200)
    note over Service,App: Restart Application
    Frontend->>Service: Restart()
    Service->>App: Relaunch updated app
    App-->>App: Exit & restart
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Service implementation (v3/pkg/services/selfupdate/selfupdate.go): Multi-source initialization, validator creation with multiple signature strategies, concurrent progress emission, and update state management require careful logic review.
  • Cryptographic utilities (v3/internal/commands/selfupdate.go): ECDSA key generation, certificate handling, PEM encoding/decoding, and signature verification logic need security-focused attention.
  • Frontend integration (v3/examples/selfupdate/assets/index.html): Complex JavaScript with service binding, event subscription, state management, and progress tracking should be reviewed for correctness and edge cases.
  • Dependency management (v3/go.mod): New transitive dependencies from go-selfupdate ecosystem (GitHub/GitLab/Gitea SDK clients) and version updates to x/crypto, x/oauth2 should be validated for compatibility and security.

Possibly related PRs

Suggested labels

v3-alpha, v3, Documentation, size:XXL, Windows, MacOS, Linux, lgtm

Poem

🐰 A self that updates, how grand!
GitHub releases now in our hand,
ECDSA keys keep signatures tight,
Progress bars dancing, oh what a sight!
Fresh versions installed, the app takes flight! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(v3): add selfupdate service for application self-updating' accurately and concisely summarizes the main change - adding a new selfupdate service to Wails v3.
Description check ✅ Passed The description is largely complete with clear summary, feature list, new files, usage examples, test plan, and issue reference. However, several template sections are not filled in: Type of change checkboxes, Test Configuration/wails doctor output, and several Checklist items remain unchecked.
Linked Issues check ✅ Passed The PR successfully implements self-updating functionality from multiple sources (GitHub, GitLab, Gitea) with signature verification, CLI tooling, and frontend integration. While #1178 focused on investigation of the minio/selfupdate repository, this PR goes well beyond that scope and delivers a complete implementation.
Out of Scope Changes check ✅ Passed All changes are directly related to the self-update feature implementation: new service, CLI commands, examples, documentation, dependency updates (go-selfupdate), and go.mod/go.sum updates. No unrelated or extraneous changes detected.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
docs/src/content/docs/guides/selfupdate.mdx (1)

391-397: Consider adding cleanup trap for CI/CD example.

The example writes the private key to a temporary file and removes it after signing. If the wails3 tool selfupdate sign command fails, the key file remains on disk. Consider suggesting a trap for cleanup:

run: |
  trap 'rm -f /tmp/key.pem' EXIT
  echo "$SIGNING_KEY" > /tmp/key.pem
  wails3 tool selfupdate sign -key /tmp/key.pem -file ./build/myapp

This is a minor documentation improvement for security best practices.

v3/examples/selfupdate/assets/index.html (1)

469-475: Consider re-enabling update button on failure.

On error, only checkBtn is re-enabled (line 473), leaving updateBtn disabled. This means users must re-check for updates before retrying the download. This may be intentional (to refresh update info), but consider if allowing immediate retry would provide better UX.

🔎 Optional: Allow retry without re-check
             } catch (err) {
                 setStatus('error', 'Update Failed');
                 log('Error during update: ' + err.message, 'error');
                 checkBtn.disabled = false;
+                updateBtn.disabled = false; // Allow retry
             }
v3/pkg/services/selfupdate/selfupdate.go (4)

541-556: CanUpdate check may not work correctly on Windows.

On Windows, the running executable is typically locked and cannot be opened for writing. This check would always return false even if the update mechanism (which replaces the executable via rename) would succeed. Also, file.Close() error is ignored.

🔎 Consider platform-specific checks or removing this heuristic
 // CanUpdate returns true if the current process has permission to update itself.
-// This checks if the executable is writable.
+// On Windows, this checks if we can write to the executable's directory.
+// On other platforms, this checks if the executable itself is writable.
 func (s *Service) CanUpdate() bool {
 	exe, err := selfupdate.ExecutablePath()
 	if err != nil {
 		return false
 	}

+	if runtime.GOOS == "windows" {
+		// On Windows, check if we can create a file in the same directory
+		dir := filepath.Dir(exe)
+		testFile := filepath.Join(dir, ".wails-update-test")
+		f, err := os.Create(testFile)
+		if err != nil {
+			return false
+		}
+		f.Close()
+		os.Remove(testFile)
+		return true
+	}
+
 	// Check if we can write to the executable.
 	file, err := os.OpenFile(exe, os.O_WRONLY, 0)
 	if err != nil {
 		return false
 	}
-	file.Close()
+	_ = file.Close()
 	return true
 }

This would require adding "path/filepath" to imports.


558-586: Restart method could benefit from a brief delay before quitting.

The current implementation starts a new process and immediately calls Quit(). On some systems (especially Windows), this could cause issues if the new process tries to access resources (files, ports, sockets) still held by the exiting process.

🔎 Proposed fix: add a small delay before quitting
+	// Give the new process a moment to start before we exit.
+	// This helps avoid resource contention on some platforms.
+	time.Sleep(100 * time.Millisecond)
+
 	// Quit the current application.
 	s.app.Quit()

This would require adding "time" to imports.


598-642: Consider extracting shared logic between Check and GetLatestRelease.

GetLatestRelease duplicates ~40 lines from Check. The only difference is that Check stores pendingUpdate. Consider extracting the common release-fetching and info-building logic to reduce duplication.


486-503: Struct documents "progress" state that is never emitted during download.

The DownloadProgress struct documents a "progress" state, but only "started" and "finished" are emitted. No progress callbacks are supported by the creativeprojects/go-selfupdate library, so intermediate updates cannot be wired in without a different approach—either replacing the library or implementing custom download tracking outside the library's UpdateTo method.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6184898 and 30af005.

⛔ Files ignored due to path filters (1)
  • v3/go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • docs/src/content/docs/guides/selfupdate.mdx (1 hunks)
  • v3/UNRELEASED_CHANGELOG.md (1 hunks)
  • v3/cmd/wails3/main.go (1 hunks)
  • v3/examples/selfupdate/assets/index.html (1 hunks)
  • v3/examples/selfupdate/main.go (1 hunks)
  • v3/go.mod (6 hunks)
  • v3/internal/commands/selfupdate.go (1 hunks)
  • v3/pkg/services/selfupdate/selfupdate.go (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-09-30T06:13:46.595Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/examples/window/main.go:472-475
Timestamp: 2024-09-30T06:13:46.595Z
Learning: In `v3/examples/window/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.

Applied to files:

  • v3/examples/selfupdate/main.go
📚 Learning: 2024-09-30T06:14:32.602Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/internal/commands/appimage_testfiles/main.go:295-299
Timestamp: 2024-09-30T06:14:32.602Z
Learning: In `v3/internal/commands/appimage_testfiles/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.

Applied to files:

  • v3/examples/selfupdate/main.go
📚 Learning: 2025-02-04T23:59:43.956Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4045
File: v3/internal/generator/render/info.go:28-68
Timestamp: 2025-02-04T23:59:43.956Z
Learning: In the Wails v3 project, internal functions are designed to panic on nil parameters as they represent contract violations, rather than adding defensive nil checks.

Applied to files:

  • v3/go.mod
📚 Learning: 2025-04-29T23:54:07.488Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4256
File: v2/internal/frontend/desktop/linux/notifications.go:27-28
Timestamp: 2025-04-29T23:54:07.488Z
Learning: In Wails v2, unlike v3-alpha which has a `ServiceShutdown` method for services, there is no standardized teardown pattern for frontend implementations. When implementing features that require cleanup (like goroutines or resources), add explicit cleanup methods (e.g., `CleanupNotifications()`) that handle resource release, context cancellation, and connection closure.

Applied to files:

  • v3/pkg/services/selfupdate/selfupdate.go
🧬 Code graph analysis (3)
v3/internal/commands/selfupdate.go (1)
v3/internal/term/term.go (2)
  • Println (109-111)
  • Success (80-82)
v3/examples/selfupdate/main.go (1)
v3/pkg/services/selfupdate/selfupdate.go (7)
  • New (196-198)
  • Service (185-192)
  • NewWithConfig (202-208)
  • Config (60-92)
  • Source (34-34)
  • SourceGitHub (38-38)
  • UIConfig (111-137)
v3/pkg/services/selfupdate/selfupdate.go (1)
v3/pkg/application/services.go (2)
  • ServiceStartup (98-100)
  • ServiceOptions (17-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
  • GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (go)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (22)
v3/UNRELEASED_CHANGELOG.md (1)

20-29: LGTM!

The changelog entries are well-structured, following the Keep a Changelog format. They accurately describe the new selfupdate service features and CLI commands, and correctly reference the linked issue #1178.

v3/cmd/wails3/main.go (1)

106-128: LGTM!

The selfupdate CLI command wiring follows the established patterns in this file. The three subcommands (keygen, sign, verify) are correctly structured with their respective options structs and action handlers, consistent with other tool commands like sign at lines 99-104.

v3/internal/commands/selfupdate.go (4)

43-68: LGTM - Secure key generation implementation.

The ECDSA P-256 key generation is well-implemented:

  • Directory creation with appropriate permissions (0755)
  • Proper check for existing keys to prevent accidental overwrites
  • Force flag option for intentional overwrites

70-86: LGTM - Appropriate certificate configuration for code signing.

The certificate template correctly specifies KeyUsageDigitalSignature and ExtKeyUsageCodeSigning, which is appropriate for update signing. The configurable validity period via ValidYears is a nice touch.


170-182: LGTM - Signing implementation is correct.

The signing flow correctly reads the file, computes SHA-256 hash, and produces an ASN.1-encoded ECDSA signature. For typical application binaries, loading the file into memory is acceptable.


214-266: LGTM - Verification implementation is secure.

The verification correctly:

  • Parses the X.509 certificate to extract the ECDSA public key
  • Validates the key type with a clear error message
  • Uses VerifyASN1 which properly handles ASN.1-encoded signatures
  • Returns distinct error vs success states
v3/examples/selfupdate/main.go (1)

18-57: LGTM - Well-structured example application.

The example effectively demonstrates:

  • Service registration with NewWithConfig
  • All major configuration options (version, source, repository, prerelease, UI theming)
  • Commented-out signature config showing optional security setup
  • UI colors that match the frontend CSS variables

The placeholder repository "example/myapp" is appropriate for demo code.

v3/examples/selfupdate/assets/index.html (2)

385-407: LGTM - Progress event handling.

The event listener correctly handles all three progress states (started, progress, finished) and updates the UI appropriately. The progress bar animation and text updates provide good user feedback.


1-256: LGTM - Clean and well-organized UI implementation.

The HTML/CSS implementation is well-structured with:

  • CSS custom properties for theming that align with the Go UIConfig
  • Accessible status indicators with both visual (dot color) and text feedback
  • Responsive layout with proper spacing
  • Good visual hierarchy for the card-based design
v3/go.mod (2)

3-3: Go version 1.24.11 is valid — Go 1.24 is a major release that arrived in February 2025, making versions like 1.24.11 entirely legitimate and well-supported.


13-13: New dependency: go-selfupdate v1.5.2.

This library enables the self-update functionality with no known vulnerabilities. The implementation supports secure update mechanisms including SHA256 hash and ECDSA signature validation, aligning with the PR objectives.

v3/pkg/services/selfupdate/selfupdate.go (11)

1-31: LGTM!

The package documentation is clear and the imports are appropriate for the functionality.


33-57: LGTM!

Well-documented type definitions with clear constants for update sources and signature types.


59-92: LGTM!

The Config struct is well-organized with clear documentation. The optional fields have sensible defaults mentioned in the comments.


139-182: LGTM!

Well-structured data types with appropriate JSON tags for frontend integration.


210-232: LGTM!

Service lifecycle methods follow the Wails v3 ServiceStartup/ServiceShutdown pattern correctly. The lock in ServiceStartup is appropriate since initSource doesn't perform blocking network operations.


265-339: LGTM!

Good implementation with proper error wrapping and handling of all source types. The guard clause for nil config is appropriate.


341-385: LGTM!

Good defensive programming with panic recovery for the underlying library. All signature types are properly handled with appropriate validation.


387-394: LGTM!

Simple and effective parsing logic for the repository format.


396-450: LGTM!

Proper use of read locks for accessing configuration and appropriate write lock for storing pending updates. The version comparison logic correctly handles both upgrade and downgrade scenarios.


513-518: LGTM!

Clean helper with appropriate nil check for the app reference.


644-657: LGTM!

Correctly returns a copy of the UI configuration to prevent external modification, with proper nil handling.

Comment on lines 73 to 76
```javascript
// Get the selfupdate service
const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/services/selfupdate");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Verify the service path for frontend binding.

The service path "github.com/wailsapp/wails/v3/services/selfupdate" appears to be missing the pkg segment. Based on the Go imports in the example code (line 38-39), the actual package path is github.com/wailsapp/wails/v3/pkg/services/selfupdate.

🔎 Suggested fix
-const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/services/selfupdate");
+const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/pkg/services/selfupdate");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```javascript
// Get the selfupdate service
const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/services/selfupdate");
🤖 Prompt for AI Agents
In docs/src/content/docs/guides/selfupdate.mdx around lines 73 to 76, the
frontend service binding uses the path
"github.com/wailsapp/wails/v3/services/selfupdate" which omits the required
"pkg" segment; update the service path to
"github.com/wailsapp/wails/v3/pkg/services/selfupdate" so it matches the actual
Go package import and allows the frontend to correctly bind to the selfupdate
service.
Comment on lines 329 to 331
// Service binding - adjust the path based on your generated bindings
const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/services/selfupdate");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same service path issue as documentation.

The service binding path appears to be missing the pkg segment, same as in the documentation. This should be consistent with the actual Go package path.

🔎 Suggested fix
-        const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/services/selfupdate");
+        const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/pkg/services/selfupdate");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Service binding - adjust the path based on your generated bindings
const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/services/selfupdate");
// Service binding - adjust the path based on your generated bindings
const Selfupdate = wails.Service("github.com/wailsapp/wails/v3/pkg/services/selfupdate");
🤖 Prompt for AI Agents
In v3/examples/selfupdate/assets/index.html around lines 329-331, the Wails
service binding path is missing the pkg segment; update the binding to use the
correct Go package path by inserting "pkg" after "v3" (i.e. change the service
path to the package path that includes pkg) so it matches the actual Go package
location.
Comment on lines 200 to 208
// NewWithConfig creates a new selfupdate service with the given configuration.
// If config is nil, you must call Configure before using the service.
func NewWithConfig(config *Config) *Service {
s := &Service{}
if config != nil {
_ = s.Configure(config)
}
return s
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Configuration errors silently ignored in constructor.

When NewWithConfig is called with an invalid config (e.g., missing Repository or CurrentVersion), the error from Configure is discarded. Users may not realize their configuration is invalid until ServiceStartup fails.

Consider returning the error or at minimum documenting this behavior.

🔎 Option 1: Return error from constructor
-// NewWithConfig creates a new selfupdate service with the given configuration.
-// If config is nil, you must call Configure before using the service.
-func NewWithConfig(config *Config) *Service {
+// NewWithConfig creates a new selfupdate service with the given configuration.
+// If config is nil, you must call Configure before using the service.
+// Returns an error if the provided config is invalid.
+func NewWithConfig(config *Config) (*Service, error) {
 	s := &Service{}
 	if config != nil {
-		_ = s.Configure(config)
+		if err := s.Configure(config); err != nil {
+			return nil, err
+		}
 	}
-	return s
+	return s, nil
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In v3/pkg/services/selfupdate/selfupdate.go around lines 200 to 208, the
constructor NewWithConfig swallows the error returned by Configure which hides
invalid configuration; change the API to return (*Service, error) and propagate
Configure's error (i.e., call Configure and if it returns an error return nil
and that error, otherwise return the service and nil); update any callers to
handle the new error return (or alternatively, add clear doc comment if you
choose not to change the signature), and run tests to ensure call sites are
updated accordingly.
Comment on lines 249 to 261
// Clone to prevent changes from the outside.
clone := new(Config)
*clone = *config

// Set defaults.
if clone.Source == "" {
clone.Source = SourceGitHub
}

s.lock.Lock()
defer s.lock.Unlock()

s.config = clone
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Shallow clone doesn't protect nested pointer fields.

The clone operation *clone = *config performs a shallow copy. If the caller modifies config.Signature or config.UI fields after calling Configure, those changes will affect the service's internal state since the pointers are copied by value.

🔎 Proposed fix for deep cloning nested structs
 	// Clone to prevent changes from the outside.
 	clone := new(Config)
 	*clone = *config
+	if config.Signature != nil {
+		sig := *config.Signature
+		clone.Signature = &sig
+	}
+	if config.UI != nil {
+		ui := *config.UI
+		clone.UI = &ui
+	}

 	// Set defaults.
 	if clone.Source == "" {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Clone to prevent changes from the outside.
clone := new(Config)
*clone = *config
// Set defaults.
if clone.Source == "" {
clone.Source = SourceGitHub
}
s.lock.Lock()
defer s.lock.Unlock()
s.config = clone
// Clone to prevent changes from the outside.
clone := new(Config)
*clone = *config
if config.Signature != nil {
sig := *config.Signature
clone.Signature = &sig
}
if config.UI != nil {
ui := *config.UI
clone.UI = &ui
}
// Set defaults.
if clone.Source == "" {
clone.Source = SourceGitHub
}
s.lock.Lock()
defer s.lock.Unlock()
s.config = clone
🤖 Prompt for AI Agents
In v3/pkg/services/selfupdate/selfupdate.go around lines 249 to 261, the current
clone uses a shallow copy (*clone = *config) which leaves nested pointer fields
(e.g., config.Signature, config.UI or any other pointer members) shared with the
caller; update Configure to perform a deep clone instead: create a new Config,
copy primitive fields as before, and for each pointer field allocate new
instances and copy their values (or call a Config.Clone method that deep-copies
all nested structs), ensuring s.config receives independent copies before
assigning under the lock.
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 20, 2025

Deploying wails with  Cloudflare Pages  Cloudflare Pages

Latest commit: 053bd7f
Status: ✅  Deploy successful!
Preview URL: https://9716f314.wails.pages.dev
Branch Preview URL: https://feature-selfupdate-service.wails.pages.dev

View logs

@leaanthony leaanthony marked this pull request as draft December 20, 2025 23:51
- Fix JavaScript API in docs and example to use ES module imports
  instead of non-existent wails.Service() function
- Use correct binding path with pkg segment
- Store configuration errors from NewWithConfig for proper error
  reporting in ServiceStartup
- Deep clone nested pointer fields (Signature, UI) in Configure to
  prevent external modifications affecting service state

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
5.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@symball
Copy link
Contributor

symball commented Dec 21, 2025

I've had a look through this and like the implementation but don't believe creativeprojects/go-selfupdate is a good choice for wails applications due to needing variations of the same build (webkit2_41) with it only supporting {cmd}{goos}{goarch}{.ext}.

I would also suggest the practise of directly self updating self from the binary should be avoided, if anything goes wrong (Windows doesn't support it properly to start with) users are up the creek. I would like to suggest at update, using an intermediary program the performs the replace such as this.

Would you be open to an alternative implementation?

@leaanthony
Copy link
Member Author

I've had a look through this and like the implementation but don't believe creativeprojects/go-selfupdate is a good choice for wails applications due to needing variations of the same build (webkit2_41) with it only supporting {cmd}{goos}{goarch}{.ext}.

I would also suggest the practise of directly self updating self from the binary should be avoided, if anything goes wrong (Windows doesn't support it properly to start with) users are up the creek. I would like to suggest at update, using an intermediary program the performs the replace such as this.

Would you be open to an alternative implementation?

Absolutely!

@symball
Copy link
Contributor

symball commented Jan 2, 2026

@leaanthony i have just made a small push to gophorth including a full github-release example if you are still curious about alternatives for self-updating.

i'm going to write tests, documentation, a tiny github site, and then park this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants