{"id":46212628,"url":"https://github.com/heatxsink/x","last_synced_at":"2026-04-18T23:13:59.214Z","repository":{"id":250383988,"uuid":"670359928","full_name":"heatxsink/x","owner":"heatxsink","description":"This is my `x` repo, there are many others like it but this one is mine.","archived":false,"fork":false,"pushed_at":"2026-01-07T06:55:14.000Z","size":155,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-03T11:32:49.432Z","etag":null,"topics":["discord","gcs","golang","iot","pushover","shell","ssh","systemd","term","webhook"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/heatxsink.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":"2023-07-24T22:01:55.000Z","updated_at":"2026-01-07T06:52:57.000Z","dependencies_parsed_at":"2026-01-08T08:07:59.801Z","dependency_job_id":null,"html_url":"https://github.com/heatxsink/x","commit_stats":null,"previous_names":["heatxsink/x"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/heatxsink/x","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heatxsink%2Fx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heatxsink%2Fx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heatxsink%2Fx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heatxsink%2Fx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/heatxsink","download_url":"https://codeload.github.com/heatxsink/x/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heatxsink%2Fx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30164538,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T04:43:31.446Z","status":"ssl_error","status_checked_at":"2026-03-06T04:40:30.133Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5: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":["discord","gcs","golang","iot","pushover","shell","ssh","systemd","term","webhook"],"created_at":"2026-03-03T09:33:07.909Z","updated_at":"2026-04-18T23:13:59.205Z","avatar_url":"https://github.com/heatxsink.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# x\n\nA collection of Go utility packages and experimental modules for various applications.\n\n## Description\n\nThis is my `x` repository - a curated collection of reusable Go packages that provide common functionality for web applications, IoT devices, cloud services, and system administration tasks. The repository is organized into stable production-ready modules and experimental work-in-progress features.\n\n## Installation\n\n```bash\ngo get github.com/heatxsink/x\n```\n\n## Architecture\n\nThe repository is organized into two main categories:\n\n### Stable Modules (Production Ready)\nThese modules are well-tested, stable, and ready for production use.\n\n### Experimental Modules (`exp/`)\nThese modules are work-in-progress and should be considered experimental. Use with caution in production environments.\n\n## Stable Modules\n\n### `dotenv/` - Environment File Parser\nZero-dependency `.env` file parser with full feature support.\n\n**Features:**\n- Load, Overload, Read, Parse, Unmarshal, UnmarshalBytes for reading\n- Marshal, Write for serialization\n- Exec for running commands with loaded environment\n- Single/double/backtick quoting with proper escape handling\n- Variable expansion (`$VAR`, `${VAR}`)\n- Export prefix, inline comments, CRLF normalization\n\n**Example:**\n```go\n// Load .env file (does not override existing env vars)\nerr := dotenv.Load()\n\n// Load and override existing env vars\nerr := dotenv.Overload(\".env.local\")\n\n// Read into map without modifying os.Environ\nenvMap, err := dotenv.Read(\".env\")\n```\n\n### `gcs/` - Google Cloud Storage (deprecated)\nThin wrapper around `cloud.google.com/go/storage` for bucket and object operations.\n\n**Deprecated** in favor of `exp/storage` with `gs://bucket/key` URIs. The new package pools the GCS client across calls and lets callers swap to local filesystem or in-memory backends without changing call sites. See the Deprecations section below.\n\n### `gravatar/` - Gravatar Integration\nFull Gravatar URL API client with functional options pattern. Supports avatar images, profile URLs (JSON/XML/VCF), and QR codes.\n\n**Features:**\n- `AvatarURL` with configurable size (1-2048px), default image, rating, and force default\n- `ProfileURL` for JSON, XML, and vCard response formats\n- `QRCodeURL` for the v3 QR code endpoint\n- All 8 default image types (`404`, `mp`, `identicon`, `monsterid`, `wavatar`, `retro`, `robohash`, `blank`)\n- SHA256 email hashing per current Gravatar spec\n\n**Example:**\n```go\nurl := gravatar.AvatarURL(\"user@example.com\",\n    gravatar.WithSize(200),\n    gravatar.WithDefault(gravatar.DefaultIdenticon),\n    gravatar.WithRating(gravatar.RatingPG),\n)\n\nprofileJSON := gravatar.ProfileURL(\"user@example.com\", gravatar.FormatJSON)\nqrCode := gravatar.QRCodeURL(\"user@example.com\")\n```\n\n### `progressbar/` - Progress Bar\nMinimal, zero-dependency terminal progress bar implementing `io.Writer`.\n\n**Features:**\n- `DefaultBytes(total, description)` constructor\n- Implements `io.Writer` for use with `io.TeeReader`, `io.Copy`, etc.\n- Throttled redraws (100ms) to avoid terminal spam\n- Human-readable byte formatting (B, KB, MB, GB)\n\n**Example:**\n```go\nbar := progressbar.DefaultBytes(fileSize, \"Uploading\")\nteeReader := io.TeeReader(file, bar)\nio.Copy(dst, teeReader)\nbar.Close()\n```\n\n### `shell/` - Shell Command Execution\nSafe utilities for executing shell commands with proper error handling and output capture.\n\n### `ssh/` - SSH Client Utilities\nSSH client wrapper with connection management and remote command execution capabilities.\n\n### `systemd/` - systemd Service Management\nTools for managing systemd services, including start, stop, status, and configuration operations.\n\n### `term/` - Terminal Utilities\nTerminal and console utilities for interactive command-line applications.\n\n### `times/` - Time Manipulation\nEnhanced time and date manipulation utilities beyond the standard library.\n\n### `webhook/` - HTTP Webhook Client\nHTTP client specifically designed for sending webhook payloads with retry logic, timeouts, and context support.\n\n### `xdg/` - XDG/Platform Path Resolution\nZero-dependency application path resolver following platform conventions.\n\n**Features:**\n- XDG Base Directory spec compliance on Linux (`XDG_CONFIG_HOME`, `XDG_DATA_HOME`, etc.)\n- macOS `~/Library/` conventions (Preferences, Application Support, Caches, Logs)\n- Windows `%LOCALAPPDATA%` / `%PROGRAMDATA%` support\n- User, System, and CustomHome scope types\n- Vendor prefix support\n- Config, Data, Cache, and Log path resolution\n- File lookup across priority-ordered directories\n\n**Example:**\n```go\nscope := xdg.NewScope(xdg.User, \"myapp\")\n\nconfigPath, err := scope.ConfigPath(\"config.yaml\")\nlogPath, err := scope.LogPath(\"app.log\")\ncacheDir, err := scope.CacheDir()\n\n// With vendor prefix\nscope := xdg.NewVendorScope(xdg.User, \"mycompany\", \"myapp\")\n```\n\n## Experimental Modules (`exp/`)\n\n### `config/` - Configuration Management\nMulti-source configuration loader supporting file, Google Cloud Storage, and Google Secret Manager backends via URI-based configuration.\n\n**Features:**\n- File-based configuration (`file://`)\n- Google Cloud Storage configuration (`gs://`)\n- Google Secret Manager integration (`secret://`)\n- Unified URI-based interface\n\n**Example:**\n```go\nctx := context.Background()\nconfig, err := config.FromURI(ctx, \"gs://my-bucket/config.json\")\nif err != nil {\n    log.Fatal(err)\n}\n```\n\n### `discord/` - Discord Bot Utilities\nDiscord bot integration utilities for creating and managing Discord bots.\n\n### `epub/` - EPUB Metadata\nEPUB metadata parsing, cover image extraction, and word counting. Parses OPF metadata directly instead of relying on third-party libraries that panic on optional-element gaps. Supports EPUB 2, EPUB 3, and Calibre custom metadata.\n\n**Features:**\n- `Metadata`: title, authors, ISBN, publisher, subjects, description, language, series, edition, publish date\n- Cover image extraction\n- Word-count estimation across spine items\n\n### `storage/` - URI-Addressable Blob Storage\nURI-dispatched `Store` abstraction over three backends: Google Cloud Storage, local filesystem, and an in-memory store for tests. Callers switch backends by changing a URI; the surface never exposes backend-specific types.\n\n**Schemes:**\n- `gs://bucket/key` - Google Cloud Storage, with a lazily-initialized `*storage.Client` reused across calls via `sync.Once` and package-level memoization.\n- `file:///abs/path` - Local filesystem. Preserves `ContentType` via a `\u003cpath\u003e.meta.json` sidecar. Rejects path traversal (`..` / `.` segments, non-empty host, non-absolute paths). POSIX-only; Windows file URIs are not handled in this version.\n- `mem://namespace/key` - Process-global in-memory backend keyed by full URI. Intended for tests; callers isolate with a per-test namespace (e.g., `mem://\u003ct.Name()\u003e/...`).\n\n**Features:**\n- `Store` interface with `Get`, `PutFile`, `PutBytes`, `Delete`, `List`.\n- `For(uri)` resolves and memoizes one `Store` per scheme so the GCS HTTP/gRPC pool is reused across calls within a process.\n- Package-level helpers (`storage.Get`, `storage.PutBytes`, etc.) dispatch by URI scheme.\n- `List` returns a backend-neutral `[]Object` with `URI`, `Size`, `ContentType`, `Updated`, `Generation`, `Metageneration`.\n- Sentinels: `ErrUnsupportedScheme`, `ErrInvalidURI`, `ErrNotExist` (aliases `io/fs.ErrNotExist`).\n- Integration tests against real GCS run via `mage integration` with `STORAGE_TEST_BUCKET` and ADC.\n\n**Example:**\n```go\n// Same call, different backend — only the URI changes.\ndata, err := storage.Get(ctx, \"gs://my-bucket/configs/app.yaml\")\ndata, err := storage.Get(ctx, \"file:///var/lib/myapp/configs/app.yaml\")\ndata, err := storage.Get(ctx, \"mem://test/configs/app.yaml\")\n\n// Or bind once:\ns, _ := storage.For(os.Getenv(\"STORAGE_URI\"))\ndata, err := s.Get(ctx, uri)\n\n// Detect missing objects uniformly across backends:\nif errors.Is(err, storage.ErrNotExist) { /* ... */ }\n```\n\n### `http/` - HTTP Server Utilities\nComprehensive HTTP server middleware and utilities.\n\n#### `http/clients/` - HTTP Client Configurations\nPre-configured HTTP clients with sensible defaults, timeouts, and proxy support.\n\n#### `http/handlers/` - HTTP Middleware\nProduction-ready HTTP middleware including:\n- **CORS**: Cross-origin resource sharing with configurable policies\n- **Recovery**: Panic recovery with structured logging\n- **AccessLog**: Structured HTTP access logging (method, path, status, bytes, client IP, user-agent, duration)\n- **Compression**: Gzip compression with configurable levels\n- **Minification**: HTML, CSS, and JavaScript minification\n- **Dump**: Full request dump logging for debugging\n- **Rate Limiting**: Request throttling and rate limiting\n\n**Example:**\n```go\nmux := http.NewServeMux()\nmux.HandleFunc(\"/api\", apiHandler)\n\n// Patch applies: Recover -\u003e AccessLog -\u003e Compress -\u003e Minify -\u003e CORS\nhandler := handlers.Patch(mux,\n    []string{\"https://example.com\"}, // allowed origins\n    handlers.DefaultAllowedMethods,   // allowed methods\n    handlers.DefaultAllowedHeaders,   // allowed headers\n)\n\nlog.Fatal(http.ListenAndServe(\":8080\", handler))\n\n// Or use AccessLog standalone\nhandler := handlers.AccessLog(myHandler)\n```\n\n#### `http/healthz/` - Health Check Endpoints\nStandard health check endpoints for monitoring and load balancing.\n\n#### `http/responses/` - HTTP Response Utilities\nStandardized HTTP response helpers for JSON, errors, and common status codes.\n\n#### `http/throttled/` - Rate Limiting\nAdvanced rate limiting middleware with multiple algorithms and storage backends.\n\n#### `http/tracer/` - Request Tracing\nRequest tracing and correlation ID management for distributed systems.\n\n### `iot/` - IoT Device Management\nInternet of Things device integration and management utilities.\n\n#### `iot/ezplug/` - Smart Plug Control\nControl and monitoring for EZPlug smart outlets and power management devices.\n\n#### `iot/wled/` - WLED Lighting Control\nIntegration with WLED (WiFi LED) controllers for managing addressable LED strips and matrices.\n\n### `logger/` - Structured Logging\nAdvanced logging utilities built on top of Uber's Zap logger.\n\n**Features:**\n- File-based logging with rotation\n- stderr logging for development\n- HTTP middleware integration\n- Context-aware logging\n- Structured and sugared logger interfaces\n\n**Example:**\n```go\n// Create a file-based logger\nlogger := logger.File(\"/var/log/app.log\")\nlogger.Info(\"Application started\")\n\n// Use with HTTP middleware\nhandler := logger.WithLogger(logger)(http.HandlerFunc(myHandler))\n\n// Get logger from HTTP request context\nfunc myHandler(w http.ResponseWriter, r *http.Request) {\n    log := logger.FromRequest(r)\n    log.Info(\"Processing request\")\n}\n```\n\n### `loom/` - Remote Service Deployment\nAutomated deployment and management utilities for remote Linux services over SSH.\n\n**Features:**\n- SSH-based remote command execution\n- systemd service management\n- File upload and directory setup\n- Service file generation\n- Support for SSH agent and password authentication\n- Environment-based configuration via `.env` files\n\n**Environment Variables:**\n- `LOOM_SSH_LOGIN`: SSH username\n- `LOOM_SSH_PASSWORD`: SSH password (when not using agent)\n- `LOOM_SSH_HOSTNAME`: Target hostname\n- `LOOM_SSH_PORT`: SSH port (defaults to 22)\n- `LOOM_SSH_DESTINATION`: Remote upload destination\n\n**Example:**\n```go\n// Create a new loom instance\nloom, err := loom.New(\"my-service\", true) // use SSH agent\nif err != nil {\n    log.Fatal(err)\n}\n\n// Generate and setup systemd service\nserviceFile, err := loom.ServiceFile(\"/opt/my-service/bin/my-service\")\nif err != nil {\n    log.Fatal(err)\n}\n\n// Deploy the service\nerr = loom.Setup(serviceFile)\nif err != nil {\n    log.Fatal(err)\n}\n\n// Control the service\nerr = loom.Service(\"start\")\nif err != nil {\n    log.Fatal(err)\n}\n```\n\n### `manifest/` - Application Manifest\nVersioned manifest storage for static-asset deploys, built on `exp/storage`. Tracks published `Item` entries (timestamp, `major.minor.point` version, content prefix) and prunes stale content prefixes.\n\n**Features:**\n- URI-based storage root — works against any `exp/storage` backend (`gs://`, `file://`, `mem://`)\n- `Save` / `Load` round-trip to `manifest.json` under the root URI\n- `Init` returns the next `Item` plus a rolling window of prior versions (returns `storage.ErrNotExist` when the manifest is missing or empty)\n- `Clean` prunes objects whose URIs don't match any current-version prefix or caller-provided allow-list; matching is exact-or-trailing-slash, not a raw `HasPrefix`\n\n**Example:**\n```go\nm := manifest.New(\"gs://my-bucket\", \"2024-01-01\")\nitem, history, err := m.Init(ctx)\n// ... upload assets under gs://my-bucket/\u003citem.Prefix\u003e/ ...\n_ = m.Save(ctx, history)\n_ = m.Clean(ctx, history, []string{\"manifest.json\"})\n```\n\n### `paths/` - Path Manipulation\nEnhanced path manipulation utilities using the `xdg` module for platform-appropriate config, log, and data paths.\n\n### `pushover/` - Push Notifications\nPushover notification service integration for sending push notifications to mobile devices.\n\n## Deprecations\n\nThe following APIs are deprecated. Each continues to work; callers should migrate to the replacement.\n\n| Deprecated | Replacement | Why |\n|---|---|---|\n| `gcs` package (entire package: `Get`, `PutFile`, `PutBytes`, `Delete`, `List`) | `exp/storage` with `gs://bucket/key` URIs | URI-based dispatch, GCS client reuse, backend-neutral `List` (no `cloud.google.com/go/storage` types leak through the API) |\n| `dotenv.Exec` | `dotenv.ExecContext` | Lets the caller cancel or set a deadline on the spawned process |\n| `shell.Execute` | `shell.ExecuteContext` | Context-aware execution |\n| `shell.ExecuteWith` | `shell.ExecuteWithContext` | Context-aware execution |\n| `ssh.NewWithAgent` | `ssh.NewWithAgentContext` | Lets the caller bound the agent-socket dial |\n| `term.PasswordPrompt` | `term.PasswordPromptContext` | Returns errors instead of terminating the process; caller wires signal handling |\n\n`staticcheck` / `golangci-lint` flag calls to any of these with `SA1019`.\n\n## Testing\n\nThe repository includes test suites for all modules. Run tests with:\n\n```bash\n# Run all tests\ngo test ./...\n\n# Run tests for a specific module\ngo test ./webhook\ngo test ./exp/logger\n\n# Run tests with verbose output\ngo test -v ./...\n```\n\n### Mage targets\n\nA `magefile.go` at the repo root exposes convenience targets (requires `mage` — `go install github.com/magefile/mage@latest`):\n\n```bash\nmage test         # go test -race -count=1 ./...\nmage lint         # golangci-lint run --timeout=5m ./...\nmage sec          # gosec ./...\nmage integration  # go test -tags=integration -run=Integration ./exp/storage/...\n                  # requires STORAGE_TEST_BUCKET and Application Default Credentials\n```\n\n## Key Dependencies\n\n- **Logging**: `go.uber.org/zap` with `lumberjack` for log rotation\n- **HTTP**: `gorilla/handlers`, `rs/cors` for CORS handling\n- **Cloud**: `cloud.google.com/go/storage` and `cloud.google.com/go/secretmanager` for GCP integration\n- **Minification**: `tdewolff/minify` for HTML/CSS/JS minification\n- **IoT**: `eclipse/paho.mqtt.golang` for MQTT communication\n- **Notifications**: `gregdel/pushover` for push notifications\n- **YAML**: `gopkg.in/yaml.v3` for configuration file parsing\n\nNotable: `dotenv`, `gravatar`, `progressbar`, and `xdg` are zero-dependency, stdlib-only implementations.\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add some amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## Caveats\n\n1. **Experimental modules**: Anything in `exp/` is to be considered experimental/work-in-progress\n2. **Stable modules**: Everything else (not in `exp/`) can be relied upon for production use\n3. **Breaking changes**: Experimental modules may have breaking changes without notice\n4. **Pull requests**: PRs are always welcome!\n\n## License\n\nCopyright 2026 Nick Granado \u003cngranado@gmail.com\u003e\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheatxsink%2Fx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheatxsink%2Fx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheatxsink%2Fx/lists"}