{"id":44139172,"url":"https://github.com/championswimmer/env.sync.local","last_synced_at":"2026-02-14T04:02:11.330Z","repository":{"id":336981494,"uuid":"1151908842","full_name":"championswimmer/env.sync.local","owner":"championswimmer","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-12T23:13:41.000Z","size":2274,"stargazers_count":26,"open_issues_count":3,"forks_count":5,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-13T10:28:09.568Z","etag":null,"topics":["encrypted","environment-variables","key-value","password-manager","secrets-management","ssh-client","ssh-key"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/championswimmer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-07T04:13:21.000Z","updated_at":"2026-02-13T09:10:23.000Z","dependencies_parsed_at":"2026-02-13T03:02:05.196Z","dependency_job_id":null,"html_url":"https://github.com/championswimmer/env.sync.local","commit_stats":null,"previous_names":["championswimmer/env.sync.local"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/championswimmer/env.sync.local","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/championswimmer%2Fenv.sync.local","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/championswimmer%2Fenv.sync.local/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/championswimmer%2Fenv.sync.local/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/championswimmer%2Fenv.sync.local/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/championswimmer","download_url":"https://codeload.github.com/championswimmer/env.sync.local/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/championswimmer%2Fenv.sync.local/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29435378,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T03:34:37.767Z","status":"ssl_error","status_checked_at":"2026-02-14T03:34:09.092Z","response_time":53,"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":["encrypted","environment-variables","key-value","password-manager","secrets-management","ssh-client","ssh-key"],"created_at":"2026-02-09T00:18:19.682Z","updated_at":"2026-02-14T04:02:11.283Z","avatar_url":"https://github.com/championswimmer.png","language":"Go","readme":"# env-sync\n\n[![Go](https://img.shields.io/badge/Go-1.24%2B-00ADD8?logo=go\u0026logoColor=white)](https://go.dev/)\n[![SSH](https://img.shields.io/badge/SSH-Secure%20Transfer-green?logo=openssh\u0026logoColor=white)](https://www.openssh.com/)\n[![AGE](https://img.shields.io/badge/AGE-Encryption-orange?logo=age\u0026logoColor=white)](https://age-encryption.org/)\n\n[![Linux](https://img.shields.io/badge/Linux-Supported-FCC624?logo=linux\u0026logoColor=white)](https://kernel.org/)\n[![macOS](https://img.shields.io/badge/macOS-Supported-000000?logo=apple\u0026logoColor=white)](https://apple.com/macos/)\n[![Windows](https://img.shields.io/badge/Windows%20(WSL2)-Supported-0078D6?logo=windows11\u0026logoColor=white)](https://docs.microsoft.com/en-us/windows/wsl/)\n[![BATS Tests](https://github.com/championswimmer/env.sync.local/actions/workflows/bats-tests.yml/badge.svg)](https://github.com/championswimmer/env.sync.local/actions/workflows/bats-tests.yml)\n\nDistributed secrets synchronization tool for local networks with **AGE encryption**. Sync your `.env` style secrets securely across multiple machines using SCP/SSH with at-rest encryption.\n\n![](./docs/cover.png)\n\n## 🆕 What's New in v2.0\n\n**Major Rewrite in Go!**\n\n- **Single Binary**: No more bash scripts - everything is now a single, statically compiled Go binary\n- **Built-in AGE Encryption**: AGE encryption library is built-in, no need to install separate `age` package\n- **Improved Performance**: Faster sync operations and better resource usage\n- **Better Cross-Platform**: More consistent behavior across Linux, macOS, and Windows (WSL2)\n- **Easier Installation**: Just build and install one binary instead of multiple scripts\n- **Backward Compatible**: v2.0 can sync with v1.x bash-based installations\n\n**Legacy Support**: The bash-based v1.x version is still available in the `legacy/` directory and can be installed with `./install.sh --legacy`\n\n## 🆕 What's New in v1.0 - Encryption Support\n\n- **AGE Encryption**: Secrets are encrypted at rest using AGE encryption\n- **Multi-Recipient Encryption**: Each machine has its own key, encrypted to all authorized recipients\n- **Transparent Decryption**: Automatic decryption during sync, shell loading, and cron jobs\n- **Zero-Config Machine Addition**: Add new machines without modifying existing ones\n- **Remote Trigger**: New machines can trigger re-encryption remotely via SSH\n\n## ⚠️ Security Model\n\n**Two Layers of Security:**\n\n1. **Transport Security (SCP/SSH)** - Default\n   - Uses SCP over SSH for encrypted, authenticated file transfer\n   - Requires SSH keys between machines\n   - Prevents eavesdropping during sync\n\n2. **At-Rest Encryption (AGE)** - Optional but Recommended\n   - Secrets encrypted on disk using AGE\n   - Each machine has its own key pair\n   - Multi-recipient encryption (encrypted to all authorized machines)\n   - If a machine is compromised, other machines' secrets remain safe\n\n## Features\n\n- ✅ **Secure by Default**: SCP/SSH transport + AGE encryption\n- ✅ **Distributed**: No master server, all machines are equal\n- ✅ **Automatic Discovery**: Uses mDNS/Bonjour to find peers\n- ✅ **Easy Expansion**: Add new machines without touching existing ones\n- ✅ **Zero-Config Addition**: New machines trigger re-encryption remotely\n- ✅ **Transparent Decryption**: Works seamlessly in shell, cron, and manual sync\n- ✅ **Version Control**: Built-in versioning and conflict resolution\n- ✅ **Backup System**: Automatic backups before overwriting\n- ✅ **Cross-Platform**: Works on Linux, macOS, and Windows (WSL2)\n\n## Quick Start\n\n### Prerequisites\n\nEnsure SSH keys are set up between your machines:\n\n```bash\n# On each machine, copy your SSH key to other machines\nssh-copy-id beelink.local\nssh-copy-id mbp16.local\nssh-copy-id razer.local\n```\n\nAGE encryption is built into the Go binary, so no additional package install is required.\nIf you want to troubleshoot encryption manually, you can optionally install the `age` CLI.\n\n### Installation\n\n**Quick Install (Web-based)**\n\nDownload and install the latest release directly:\n\n```bash\n# Install to /usr/local/bin (requires sudo)\ncurl -fsSL https://raw.githubusercontent.com/championswimmer/env.sync.local/main/install.sh | sudo bash\n\n# Or install to ~/.local/bin (user-only, no sudo)\ncurl -fsSL https://raw.githubusercontent.com/championswimmer/env.sync.local/main/install.sh | bash -s -- --user\n```\n\n**Install from Source**\n\n```bash\n# Clone or download the repository\ngit clone https://github.com/championswimmer/env.sync.local.git\ncd env.sync.local\n\n# Install v2.0 (Go binary) to /usr/local/bin (requires sudo)\nsudo ./install.sh\n\n# Or install to ~/.local/bin (user-only)\n./install.sh --user\n\n# For legacy bash version (v1.x)\nsudo ./install.sh --legacy\n```\n\n**Note**: The installation script automatically handles running services. If env-sync is running as a background service (via `env-sync serve -d`), the installer will:\n1. Stop the service before installation\n2. Replace the binary\n3. Restart the service automatically\n\nThis ensures seamless upgrades without manual intervention.\n\n### Initial Setup\n\n**On each machine:**\n\n```bash\n# 1. Initialize secrets file with encryption\nenv-sync init --encrypted\n\n# 2. Edit secrets\nnano ~/.secrets.env\n\n# 3. Sync with peers (they'll learn your public key)\nenv-sync\n\n# 4. Set up periodic sync (optional)\nenv-sync cron --install\n```\n\nThat's it! The machines will automatically discover each other and sync encrypted secrets.\n\n## Usage\n\n### Commands\n\n```bash\n# Sync (auto-decrypts if encrypted, re-encrypts to all recipients)\nenv-sync\nenv-sync sync mbp16.local                   # Sync from specific host\nenv-sync sync --force-pull mbp16.local      # Force overwrite all local secrets from specific host\n\n# Key management\nenv-sync key show                           # Show your public key\nenv-sync key list                           # List known peer keys\nenv-sync key import age1xyz... hostname     # Import peer's key\nenv-sync key request-access --trigger-all   # Request access on new machine\n\n# Load secrets for shell\nenv-sync load                               # Output: export KEY=value\n\n# Secret management\nenv-sync add KEY=\"value\"                    # Add or update a secret\nenv-sync add OPENAI_API_KEY=\"sk-...\"        # Example: add API key\nenv-sync remove KEY                         # Remove a secret\nenv-sync list                               # List all keys (values hidden)\nenv-sync show KEY                           # Show value of specific key\n\n# Service management\nenv-sync service stop                       # Stop the background service\nenv-sync service restart                    # Restart the background service\nenv-sync service uninstall                  # Uninstall the service completely\n\n# Other commands\nenv-sync serve -d          # Start HTTP server as a background service (HTTP mode only)\nenv-sync discover          # Find peers on network\nenv-sync status            # Show current status\nenv-sync init              # Create new secrets file\nenv-sync init --encrypted  # Create with encryption\nenv-sync restore [n]       # Restore from backup (n=1-5)\nenv-sync cron --install    # Setup 30-min sync cron job\nenv-sync cron --install --interval 10  # Setup cron with custom interval (minutes)\nenv-sync --help            # Show full help\n```\n\n### Running as a background service\n\n`env-sync serve -d` installs a user-level service that keeps the HTTP server running, advertises `_envsync._tcp` via mDNS/Bonjour, and performs a sync every 30 minutes. The service restarts automatically after login or reboot.\n\n- Linux (systemd user): `systemctl --user status env-sync` (logs: `journalctl --user -u env-sync`)\n- macOS (LaunchAgent): `launchctl print gui/$(id -u)/env-sync` (restart: `launchctl kickstart -k gui/$(id -u)/env-sync`)\n\n**Managing the service:**\n```bash\nenv-sync service stop        # Stop the service\nenv-sync service restart     # Restart the service\nenv-sync service uninstall   # Remove the service completely\n```\n\nThe service commands use the native OS service manager (systemd on Linux, launchd on macOS), ensuring proper lifecycle management across platforms.\n\n### Adding a New Machine (Machine D joining A, B, C)\n\n**On the new machine (D) only:**\n\n```bash\n# 1. Install env-sync\ncurl -fsSL .../install.sh | bash\n\n# 2. Initialize with encryption\nenv-sync init --encrypted\n\n# 3. Discover peers and collect their pubkeys\nenv-sync discover --collect-keys\n\n# 4. Request access (triggers re-encryption on existing machines)\nenv-sync key request-access --trigger beelink.local\n# OR trigger all online machines:\n# env-sync key request-access --trigger-all\n\n# 5. Sync to get encrypted secrets\nenv-sync\n```\n\n**What happens:**\n1. D generates its AGE key pair\n2. D SSHes into beelink.local and adds its AGE public key to the public key list\n3. beelink.local re-encrypts secrets to include D\n4. D syncs and can now decrypt the secrets\n\n**No changes needed on A, B, or C!**\n\n### Managing Secrets\n\nenv-sync provides commands to add, remove, list, and view secrets without manually editing the file.\n\n#### Adding Secrets\n\n```bash\n# Add a new secret key-value pair\nenv-sync add OPENAI_API_KEY=\"sk-abc123xyz\"\n\n# Values can include spaces (use quotes)\nenv-sync add DATABASE_URL=\"postgres://user:pass@localhost/db\"\n\n# Updates existing key if it already exists\nenv-sync add API_KEY=\"new-value\"\n```\n\n**Features:**\n- Works with both encrypted and plaintext files\n- Automatically creates backup before modification\n- Updates metadata (timestamp, checksum)\n- Properly handles quotes in values\n\n#### Removing Secrets\n\n```bash\n# Remove a secret by key name\nenv-sync remove OPENAI_API_KEY\n\n# Safe to run - warns if key doesn't exist\nenv-sync remove NONEXISTENT_KEY\n```\n\n#### Listing Secrets\n\n```bash\n# List all secret keys (values are hidden for security)\nenv-sync list\n\n# Output:\n# Secrets keys:\n#   • OPENAI_API_KEY\n#   • DATABASE_URL\n#   • AWS_ACCESS_KEY\n#\n# Total: 3 keys\n```\n\n#### Viewing Secrets\n\n```bash\n# Show the value of a specific key\nenv-sync show OPENAI_API_KEY\n\n# Output: sk-abc123xyz\n```\n\n**Full Example Workflow:**\n\n```bash\n# Initialize with encryption\nenv-sync init --encrypted\n\n# Add your secrets\nenv-sync add OPENAI_API_KEY=\"sk-...\"\nenv-sync add DATABASE_URL=\"postgres://...\"\nenv-sync add AWS_ACCESS_KEY=\"AKIA...\"\n\n# Verify what you have\nenv-sync list\n\n# View a specific value when needed\nenv-sync show DATABASE_URL\n\n# Remove if needed\nenv-sync remove OLD_API_KEY\n\n# Changes are automatically backed up\nls ~/.config/env-sync/backups/\n```\n\n### Managing Periodic Sync with Cron\n\nenv-sync can automatically sync your secrets at regular intervals using cron. This ensures your secrets stay up-to-date across all machines without manual intervention.\n\n#### Installing Cron Job\n\n```bash\n# Install with default 30-minute interval\nenv-sync cron --install\n\n# Install with custom interval (in minutes)\nenv-sync cron --install --interval 10   # Sync every 10 minutes\nenv-sync cron --install --interval 60   # Sync every 60 minutes\n```\n\n**Features:**\n- Only accepts minute values (1-59 for practical use)\n- Automatically removes any existing env-sync cron job before installing\n- Ensures you never have duplicate cron entries\n- Runs in quiet mode to avoid cron email spam\n\n#### Viewing Current Cron Job\n\n```bash\n# Show the currently installed cron job\nenv-sync cron --show\n```\n\n#### Removing Cron Job\n\n```bash\n# Remove the cron job\nenv-sync cron --remove\n```\n\n**Important Notes:**\n- Installing a new cron job with `--install` automatically removes the old one first\n- You can safely change the interval by running `--install --interval \u003cnew\u003e` - it will replace the existing cron\n- The cron job runs `env-sync --quiet sync` to suppress output\n- All cron activity is logged to `~/.config/env-sync/logs/`\n\n### Force Pull from a Specific Host\n\nSometimes you want to completely overwrite your local secrets with those from a specific machine, ignoring timestamps and local changes. This is useful when:\n- You want to reset your local secrets to match a trusted source\n- You've made incorrect local changes and want to revert\n- You want to ensure exact consistency with a specific machine\n\n```bash\n# Force pull all secrets from a specific host (overwrites local)\nenv-sync sync --force-pull nodeA.local\n\n# This will:\n# 1. Create a backup of your local secrets\n# 2. Download secrets from nodeA.local\n# 3. Completely overwrite local file (no merging)\n# 4. Ignore local timestamps and version comparisons\n```\n\n**Important Notes:**\n- Requires a specific hostname (won't work without it)\n- Creates a backup before overwriting (can restore with `env-sync restore`)\n- All local changes will be lost (replaced with remote values)\n- Use with caution - normal sync is safer as it merges changes\n\n### Shell Integration\n\nAdd to `~/.bashrc` or `~/.zshrc`:\n\n```bash\n# Auto-load secrets (decrypts automatically if encrypted)\neval \"$(env-sync load 2\u003e/dev/null)\"\n\n# Auto-sync on shell startup (background)\nif command -v env-sync \u0026\u003e /dev/null; then\n    (env-sync --quiet \u0026)\nfi\n```\n\n### Encrypted File Format\n\n```bash\n# === ENV_SYNC_METADATA ===\n# VERSION: 1.2.3\n# TIMESTAMP: 2025-02-07T15:30:45Z\n# HOST: beelink.local\n# MODIFIED: 2025-02-07T15:30:45Z\n# ENCRYPTED: true\n# PUBLIC_KEYS: beelink.local:age1xyz...,mbp16.local:age1abc...,nuc.local:age1def...\n# === END_METADATA ===\n\nOPENAI_API_KEY=\"YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOS...\" # ENVSYNC_UPDATED_AT=2025-02-07T15:30:45Z\nDATABASE_URL=\"YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOS...\" # ENVSYNC_UPDATED_AT=2025-02-07T15:30:45Z\n\n# === ENV_SYNC_FOOTER ===\n# VERSION: 1.2.3\n# TIMESTAMP: 2025-02-07T15:30:45Z\n# HOST: beelink.local\n# === END_FOOTER ===\n```\n\n**Metadata stays plaintext** (for discovery/versioning).\n**Public keys are AGE keys** (not SSH keys), and **values are individually encrypted** using AGE.\nTimestamps track when each key was last updated for granular merging.\n\n## How It Works\n\n### 1. Encryption Model\n\nEach machine has its own AGE key pair:\n- **Private Key**: `~/.config/env-sync/keys/age_key` (chmod 600)\n- **Public Key**: Shared with peers, cached locally\n\n**Multi-Recipient Encryption:**\n```\nMachine A encrypts to: [A_pubkey, B_pubkey, C_pubkey]\nMachine B encrypts to: [A_pubkey, B_pubkey, C_pubkey]\nMachine C encrypts to: [A_pubkey, B_pubkey, C_pubkey]\n```\n\nWhen Machine D joins:\n1. D generates its key pair\n2. D triggers re-encryption on A/B/C via SSH\n3. A/B/C re-encrypt to [A, B, C, D]\n4. D can now decrypt\n\n### 2. Sync Process\n\n1. **Discovery**: Find peers via mDNS\n2. **Fetch**: Download encrypted secrets via SCP\n3. **Decrypt**: Decrypt using local private key (if in recipient list)\n4. **Compare**: Check versions\n5. **Merge**: Use newest version\n6. **Re-encrypt**: Encrypt to all known recipients\n7. **Save**: Store encrypted file\n\n### 3. Adding New Machines\n\n**Remote Trigger (Preferred):**\n```bash\n# On new machine D:\nenv-sync key request-access --trigger beelink.local\n```\n\nThis:\n- SSHes into beelink.local\n- Adds D's pubkey to beelink's cache\n- Triggers sync (re-encrypts with D as recipient)\n- D can immediately sync and decrypt\n\n**No manual approval needed** - works because D must have SSH access anyway.\n\n### 4. Transparent Decryption\n\n```bash\n# During sync (automatic)\nenv-sync\n\n# During shell load\neval \"$(env-sync load)\"\n\n# During cron\n*/30 * * * * env-sync sync --quiet \u0026\u0026 eval \"$(env-sync load --quiet)\"\n```\n\n## File Locations\n\n```\n~/.config/env-sync/\n├── config                          # Config file\n├── .secrets.env                    # Encrypted secrets\n├── .secrets.env.backup.1..5        # Encrypted backups\n├── keys/\n│   ├── age_key                     # Private key (chmod 600)\n│   ├── age_key.pub                 # Public key\n│   ├── known_hosts/                # Cached peer pubkeys\n│   │   ├── beelink.local.pub\n│   │   └── mbp16.local.pub\n│   └── cache/\n│       └── pubkey_cache.json       # Metadata about known keys\n└── logs/                           # Sync logs\n```\n\n## Troubleshooting\n\n### Cannot decrypt - not in recipient list\n```bash\n# Request access from an existing machine\nenv-sync key request-access --trigger beelink.local\n\n# Or manually add your key on any existing machine:\n# On existing machine:\nenv-sync key import $(new_machine_pubkey) new_machine.local\nenv-sync  # Re-encrypts with new recipient\n```\n\n### SSH connection fails\n```bash\n# Test SSH connectivity\nssh -v hostname.local\n\n# Copy SSH key again\nssh-copy-id hostname.local\n```\n\n### Sync not working\n```bash\n# Check status\nenv-sync status\n\n# View logs\ntail -f ~/.config/env-sync/logs/env-sync.log\n\n# Test encryption (optional, if age CLI is installed)\necho \"test\" | age -r $(env-sync key show) | age -d -i ~/.config/env-sync/keys/age_key\n```\n\n## Security Considerations\n\n### Current Implementation\n\n**SCP Mode (Default - Recommended)**\n- ✅ Encrypted transmission via SSH\n- ✅ Requires SSH key authentication\n- ✅ File permissions: 600\n- ✅ AGE encryption at rest\n- ⚠️ SSH host keys are auto-accepted on first connect (StrictHostKeyChecking=accept-new)\n  - This is TOFU behavior and can enable MITM attacks on first connection\n  - Set `ENV_SYNC_STRICT_SSH=true` and pre-populate known_hosts for production\n\n**HTTP Mode (Fallback - Insecure)**\n- ❌ Secrets transmitted in plaintext\n- ❌ Accessible to any device on network\n- ⚠️ Use only for testing or completely trusted networks\n\n### Key Management\n\n1. **Private Key Protection**\n   - Never transmit private key\n   - File permissions: 600\n   - Backup securely (offline or encrypted)\n\n2. **Revoking Access**\n   ```bash\n   # Remove compromised machine\n   env-sync key revoke compromised.local\n   # Re-encrypts without their key\n   ```\n\n3. **Lost Key Recovery**\n   - If you lose your private key, you cannot decrypt\n   - Must get re-encrypted file from another machine\n   - Or restore from backup with old key\n\n## Development\n\n### Project Structure\n```\nenv-sync/\n├── src/                       # Go source code\n│   ├── cmd/env-sync/          # Main entry point\n│   ├── internal/              # Internal packages\n│   │   ├── cli/               # CLI interface\n│   │   ├── sync/              # Sync logic\n│   │   ├── discovery/         # mDNS discovery\n│   │   ├── crypto/age/        # AGE encryption\n│   │   ├── transport/         # SSH/HTTP transport\n│   │   └── ...\n│   └── go.mod                 # Go module definition\n├── target/\n│   └── env-sync               # Built Go binary\n├── legacy/                    # Legacy bash v1.x (for reference)\n│   ├── bin/                   # Bash scripts\n│   └── lib/                   # Bash libraries\n├── install.sh                 # Installation script\n├── Makefile                   # Build automation\n├── README.md                  # This file\n└── AGENTS.md                  # Developer documentation\n```\n\n### Building from Source\n\n```bash\n# Build the Go binary\nmake build\n\n# Run tests\nmake test\n\n# Install locally\nmake install\n\n# Or use install.sh\n./install.sh --user\n```\n\n### Legacy Bash Version\n\nTo use the legacy bash-based version (v1.x):\n\n```bash\n# Install legacy version\n./install.sh --legacy\n\n# Or force use of bash scripts even if Go binary is present\nENV_SYNC_USE_BASH=true env-sync status\n```\n\n## License\n\nMIT License\n\n## Roadmap\n\n- [x] AGE encryption (v1.0)\n- [x] Multi-recipient encryption\n- [x] Remote trigger for machine addition\n- [x] Transparent decryption\n- [x] CLI secret management (add/remove/list/show)\n- [ ] Hardware key support (YubiKey)\n- [ ] Web UI for management\n- [ ] Key rotation\n- [ ] Selective sync (whitelist/blacklist)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchampionswimmer%2Fenv.sync.local","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchampionswimmer%2Fenv.sync.local","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchampionswimmer%2Fenv.sync.local/lists"}