{"id":38740430,"url":"https://github.com/napalu/gosafedate","last_synced_at":"2026-01-17T11:37:56.432Z","repository":{"id":327412885,"uuid":"1109185517","full_name":"napalu/gosafedate","owner":"napalu","description":"gosafedate provides a tiny, dependency-free, trust-preserving way for your Go apps to update themselves safely","archived":false,"fork":false,"pushed_at":"2025-12-05T18:21:15.000Z","size":33,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-06T17:45:46.940Z","etag":null,"topics":["golang","self-update"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/napalu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-03T13:06:56.000Z","updated_at":"2025-12-05T18:21:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/napalu/gosafedate","commit_stats":null,"previous_names":["napalu/gosafedate"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/napalu/gosafedate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/napalu%2Fgosafedate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/napalu%2Fgosafedate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/napalu%2Fgosafedate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/napalu%2Fgosafedate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/napalu","download_url":"https://codeload.github.com/napalu/gosafedate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/napalu%2Fgosafedate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28507980,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T10:25:30.148Z","status":"ssl_error","status_checked_at":"2026-01-17T10:25:29.718Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","self-update"],"created_at":"2026-01-17T11:37:56.367Z","updated_at":"2026-01-17T11:37:56.424Z","avatar_url":"https://github.com/napalu.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🛡️ gosafedate\n\n\u003e **Self-updates that won’t leave you on the rocks 🍸**  \n\u003e No frills, secure, signed, atomic updates for Go binaries — with zero heartbreaks.\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/napalu/gosafedate.svg)](https://pkg.go.dev/github.com/napalu/gosafedate)\n[![Go Report Card](https://goreportcard.com/badge/github.com/napalu/gosafedate)](https://goreportcard.com/report/github.com/napalu/gosafedate)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n\n---\n\n### ✨ Why gosafedate?\n\nBecause `curl | bash` is not a security model.  \n**gosafedate** makes it simple — and safe — for your Go apps to update themselves *without* breaking trust or atomicity.\n\n- 🔐 **Ed25519‑signed updates**\n- 🧾 **Checksum verification (SHA‑256)**\n- 💣 **Atomic binary replacement**\n- 🚀 **Optional auto‑restart**\n- 🧘 **Zero dependencies**\n- 🧱 **Tiny API surface**\n\nPerfect when you want *safe updates without introducing a whole framework*.\n\n---\n\n## 📦 Install the CLI\n\nTo use `gosafedate` as a standalone signing / metadata tool:\n\n```bash\ngo install github.com/napalu/gosafedate/cmd/gosafedate@latest\n```\n\nThis installs the gosafedate binary into your $GOBIN (often $(go env GOPATH)/bin).\nMake sure that directory is on your PATH, then you can run:\n\n```bash\ngosafedate --help\n```\n\n## 🚀 Quick Start\n\n```bash\ngo get github.com/napalu/gosafedate\n```\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"github.com/napalu/gosafedate/self\"\n\t\"github.com/napalu/myapp/version\"\n)\n\nfunc maybeUpdate() {\n\terr := self.UpdateIfNewer(self.Config{\n\t\tURL:         \"https://repo.example.com/myapp/metadata.json\",\n\t\tPubKey:      version.PublicKey, // raw Ed25519 key (see below)\n\t\tCurrentVer:  version.Version,\n\t\tAutoRestart: true,\n\t})\n\n\tif err != nil {\n\t\tlog.Printf(\"update check failed: %v\", err)\n\t}\n}\n```\n\n---\n\n## Advanced: Modular Update Flow\n\nThe API also exposes low‑level primitives so you can control the workflow:\n\n```go\ncfg := self.Config{\n\tURL:        \"https://repo.example.com/myapp/metadata.json\",\n\tPubKey:     version.PublicKey,\n\tCurrentVer: version.Version,\n}\n\nnewer, meta, err := self.HasNewer(cfg)\nif err != nil {\n\tlog.Fatalf(\"update check failed: %v\", err)\n}\n\nif newer {\n\tlog.Printf(\"new version %s available\", meta.Version)\n\t_ = self.UpdateFromMetadata(cfg, meta)\n}\n```\n\n`HasNewer` only performs a remote version check and does not download anything.\n`UpdateFromMetadata` performs the actual verified download and installation.\n\nThis is useful for applications that:\n\n- want to prompt users before upgrading\n- want custom logging or upgrade policies\n- want to integrate UI/UX around available updates\n\n---\n\n## Windows: Helper Setup (required for self-update)\n\nOn Windows, a running `.exe` cannot overwrite itself.  \ngosafedate therefore uses a small **helper mode** to finalize updates.\n\nTo enable this, call `MaybeRunUpdateHelper` at the very start of your `main` function:\n\n```go\nfunc main() {\n    // On Windows: finalize any pending update\n    // On all other OS: no-op\n    self.MaybeRunUpdateHelper(version.PublicKey)\n\n    // ... rest of your application ...\n}\n```\n\nIf you omit this call, Windows updates will download and verify correctly,\nbut will never be installed automatically.\n\nOn non-Windows platforms this call is a no-op and completely safe.\n\n---\n\t\n## Embedding the Public Key (required)\n\ngosafedate uses **raw 32‑byte Ed25519 public keys**, not PEM.  \nThe CLI converts PEM → byte slice:\n\n```bash\ngosafedate pubkey-bytes --pub myapp.key.pub\n```\n\nOutput:\n\n```go\n[]byte{0x12, 0x34, 0xab, 0xcd, /* ... */ }\n```\n\nEmbed that into your binary:\n\n```go\npackage version\n\nvar PublicKey = []byte{\n\t0x12, 0x34, 0xab, 0xcd, // ...\n}\n```\n\nThis ensures updates cannot be forged without compromising your signing key.\n\n---\n\n## CLI Overview\nThe `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.\n\nInstall:\n\n```bash\ngo install github.com/napalu/gosafedate/cmd/gosafedate@latest\n```\n\n### Generate signing keys\n\n```bash\ngosafedate keygen myapp.key\n```\n\nProduces:\n\n```\nmyapp.key\nmyapp.key.pub\n```\n\n### Sign `{version}+{sha256}`\n\n```bash\ngosafedate sign --key myapp.key \"v1.2.3+ce9f2b63e4c7e2b8...\"\n```\n\n### Verify a signature\n\n```bash\ngosafedate verify --pub myapp.key.pub \"v1.2.3+ce9f2b63e4c7e2b8...\" \u003csignature\u003e\n```\n\n### Export raw public key bytes\n\n```bash\ngosafedate pubkey-bytes --pub myapp.key.pub\n```\n\n---\n\n## Metadata Format\n\nEach release is described by a small JSON file:\n\n```json\n{\n  \"version\": \"v1.2.3\",\n  \"sha256\": \"ce9f2b63e4c7e2b8...\",\n  \"signature\": \"mLr4Q1...==\",\n  \"downloadUrl\": \"myapp-v1.2.3.gz\"\n}\n```\n\n`downloadUrl` may be:\n\n- an absolute URL, or\n- relative to the metadata URL:\n\nExample:\n\n```\nhttps://repo.example.com/myapp/metadata.json\ndownloadUrl: \"myapp-v1.2.3.gz\"\n```\n\nResolves to:\n\n```\nhttps://repo.example.com/myapp/myapp-v1.2.3.gz\n```\n\n---\n\n## How Signing Works\n\ngosafedate signs:\n\n```\n\"{version}+{sha256}\"\n```\n\nThis means an attacker must compromise:\n\n1. **The binary**, and\n2. **The metadata**, and\n3. **The signature**, and\n4. **Your private key**\n\nWithout all four, the update is rejected.\n\n---\n\n## Update Flow\n\n1. Fetch metadata\n2. Parse semantic versions\n3. Resolve download URL\n4. Download `.gz`\n5. Decompress to a temporary file\n6. Verify SHA‑256\n7. Verify Ed25519 signature\n8. Atomically replace the running binary\n9. Restore original permissions\n10. Optionally restart the process\n\nIf *anything* fails: the running binary stays untouched.\n\n---\n\n## CI Example (Jenkins)\n\n```groovy\nwithCredentials([string(credentialsId: 'myapp_ed25519_key', variable: 'APP_KEY')]) {\n  def hash = sha256 file: \"./myapp\"\n  def sig = sh(\n    script: \"gosafedate sign --key myapp.key \\\"${APP_VERSION}+${hash}\\\"\",\n    returnStdout: true\n  ).trim()\n\n  writeJSON(file: \"./metadata.json\", json: [\n    version: \"${APP_VERSION}\",\n    sha256: hash,\n    downloadUrl: \"https://repo.example.com/myapp/myapp-${APP_VERSION}.gz\",\n    signature: sig\n  ])\n}\n```\n\n---\n\n## Compatibility \u0026 Guarantees\n\n- **Atomic updates**: new binary fully written and verified before replacing the old one\n- **Crash‑safe**: failed updates leave the current binary untouched\n- **OS support**:\n    - Linux, macOS, BSD: native atomic `rename(2)` replacement\n    - Windows: supported via a secure helper process (no in-place overwrite)\n- **Requirements**: the executable must have write permissions to its own directory\n\n### Windows permissions\n\ngosafedate requires that the **running process has write access to its own executable directory**.\n\n- ✅ Per-user CLIs installed under user-writable locations\n  (e.g. `%LOCALAPPDATA%\\bin`) work out of the box.\n- ✅ Services running as SYSTEM or a service account with write access to\n  `C:\\Program Files\\YourApp` can self-update safely.\n- ❌ GUI applications installed in `C:\\Program Files` and run by standard users\n  **cannot self-update** without an external elevated updater.\n\ngosafedate does **not** attempt UAC elevation, privilege escalation,\nor background services.\nOn permission errors, the update fails safely and the existing binary\nremains untouched.\n\n### Windows helper security model\n\nOn Windows, gosafedate never trusts environment variables for file paths.\nThe helper process:\n\n- locates itself via `os.Executable()`,\n- loads trusted metadata from `\u003cexe\u003e.new.meta`,\n- re-verifies SHA-256 and Ed25519 signatures using the embedded public key,\n- only then performs the final atomic rename.\n\nThis prevents hijacking or privilege escalation through crafted environment\nvariables or path injection.\n\n---\n\n## Building from Source\n\n```bash\ngit clone https://github.com/napalu/gosafedate.git\ncd gosafedate\ngo build -o bin/gosafedate ./cmd/gosafedate\n```\n\nRun tests:\n\n```bash\ngo test ./...\n```\n\n---\n\n## License\n\nMIT  \nCopyright ©\n\n---\n\nIf you use gosafedate in your project, feel free to open an issue or PR — feedback welcome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnapalu%2Fgosafedate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnapalu%2Fgosafedate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnapalu%2Fgosafedate/lists"}