Self-updates that won’t leave you on the rocks 🍸
No frills, secure, signed, atomic updates for Go binaries — with zero heartbreaks.
Because curl | bash is not a security model.
gosafedate makes it simple — and safe — for your Go apps to update themselves without breaking trust or atomicity.
- 🔐 Ed25519‑signed updates
- 🧾 Checksum verification (SHA‑256)
- 💣 Atomic binary replacement
- 🚀 Optional auto‑restart
- 🧘 Zero dependencies
- 🧱 Tiny API surface
Perfect when you want safe updates without introducing a whole framework.
To use gosafedate as a standalone signing / metadata tool:
go install github.com/napalu/gosafedate/cmd/gosafedate@latestThis installs the gosafedate binary into your
gosafedate --helpgo get github.com/napalu/gosafedatepackage main
import (
"log"
"github.com/napalu/gosafedate/self"
"github.com/napalu/myapp/version"
)
func maybeUpdate() {
err := self.UpdateIfNewer(self.Config{
URL: "https://repo.example.com/myapp/metadata.json",
PubKey: version.PublicKey, // raw Ed25519 key (see below)
CurrentVer: version.Version,
AutoRestart: true,
})
if err != nil {
log.Printf("update check failed: %v", err)
}
}The API also exposes low‑level primitives so you can control the workflow:
cfg := self.Config{
URL: "https://repo.example.com/myapp/metadata.json",
PubKey: version.PublicKey,
CurrentVer: version.Version,
}
newer, meta, err := self.HasNewer(cfg)
if err != nil {
log.Fatalf("update check failed: %v", err)
}
if newer {
log.Printf("new version %s available", meta.Version)
_ = self.UpdateFromMetadata(cfg, meta)
}HasNewer only performs a remote version check and does not download anything.
UpdateFromMetadata performs the actual verified download and installation.
This is useful for applications that:
- want to prompt users before upgrading
- want custom logging or upgrade policies
- want to integrate UI/UX around available updates
On Windows, a running .exe cannot overwrite itself.
gosafedate therefore uses a small helper mode to finalize updates.
To enable this, call MaybeRunUpdateHelper at the very start of your main function:
func main() {
// On Windows: finalize any pending update
// On all other OS: no-op
self.MaybeRunUpdateHelper(version.PublicKey)
// ... rest of your application ...
}If you omit this call, Windows updates will download and verify correctly, but will never be installed automatically.
On non-Windows platforms this call is a no-op and completely safe.
gosafedate uses raw 32‑byte Ed25519 public keys, not PEM.
The CLI converts PEM → byte slice:
gosafedate pubkey-bytes --pub myapp.key.pubOutput:
[]byte{0x12, 0x34, 0xab, 0xcd, /* ... */ }Embed that into your binary:
package version
var PublicKey = []byte{
0x12, 0x34, 0xab, 0xcd, // ...
}This ensures updates cannot be forged without compromising your signing key.
The gosafedate CLI is a small companion tool for key generation, signing, verification and public-key export. It’s installable via go install and can be used independently of the self-update library.
Install:
go install github.com/napalu/gosafedate/cmd/gosafedate@latestgosafedate keygen myapp.keyProduces:
myapp.key
myapp.key.pub
gosafedate sign --key myapp.key "v1.2.3+ce9f2b63e4c7e2b8..."gosafedate verify --pub myapp.key.pub "v1.2.3+ce9f2b63e4c7e2b8..." <signature>gosafedate pubkey-bytes --pub myapp.key.pubEach release is described by a small JSON file:
{
"version": "v1.2.3",
"sha256": "ce9f2b63e4c7e2b8...",
"signature": "mLr4Q1...==",
"downloadUrl": "myapp-v1.2.3.gz"
}downloadUrl may be:
- an absolute URL, or
- relative to the metadata URL:
Example:
https://repo.example.com/myapp/metadata.json
downloadUrl: "myapp-v1.2.3.gz"
Resolves to:
https://repo.example.com/myapp/myapp-v1.2.3.gz
gosafedate signs:
"{version}+{sha256}"
This means an attacker must compromise:
- The binary, and
- The metadata, and
- The signature, and
- Your private key
Without all four, the update is rejected.
- Fetch metadata
- Parse semantic versions
- Resolve download URL
- Download
.gz - Decompress to a temporary file
- Verify SHA‑256
- Verify Ed25519 signature
- Atomically replace the running binary
- Restore original permissions
- Optionally restart the process
If anything fails: the running binary stays untouched.
withCredentials([string(credentialsId: 'myapp_ed25519_key', variable: 'APP_KEY')]) {
def hash = sha256 file: "./myapp"
def sig = sh(
script: "gosafedate sign --key myapp.key \"${APP_VERSION}+${hash}\"",
returnStdout: true
).trim()
writeJSON(file: "./metadata.json", json: [
version: "${APP_VERSION}",
sha256: hash,
downloadUrl: "https://repo.example.com/myapp/myapp-${APP_VERSION}.gz",
signature: sig
])
}- Atomic updates: new binary fully written and verified before replacing the old one
- Crash‑safe: failed updates leave the current binary untouched
- OS support:
- Linux, macOS, BSD: native atomic
rename(2)replacement - Windows: supported via a secure helper process (no in-place overwrite)
- Linux, macOS, BSD: native atomic
- Requirements: the executable must have write permissions to its own directory
gosafedate requires that the running process has write access to its own executable directory.
- ✅ Per-user CLIs installed under user-writable locations
(e.g.
%LOCALAPPDATA%\bin) work out of the box. - ✅ Services running as SYSTEM or a service account with write access to
C:\Program Files\YourAppcan self-update safely. - ❌ GUI applications installed in
C:\Program Filesand run by standard users cannot self-update without an external elevated updater.
gosafedate does not attempt UAC elevation, privilege escalation, or background services. On permission errors, the update fails safely and the existing binary remains untouched.
On Windows, gosafedate never trusts environment variables for file paths. The helper process:
- locates itself via
os.Executable(), - loads trusted metadata from
<exe>.new.meta, - re-verifies SHA-256 and Ed25519 signatures using the embedded public key,
- only then performs the final atomic rename.
This prevents hijacking or privilege escalation through crafted environment variables or path injection.
git clone https://github.com/napalu/gosafedate.git
cd gosafedate
go build -o bin/gosafedate ./cmd/gosafedateRun tests:
go test ./...MIT
Copyright ©
If you use gosafedate in your project, feel free to open an issue or PR — feedback welcome!