diff --git a/website/public/robots.txt b/website/public/robots.txt index 825d7e9..ef35a4b 100644 --- a/website/public/robots.txt +++ b/website/public/robots.txt @@ -2,3 +2,5 @@ User-agent: * Allow: / Sitemap: https://xenvsync.softexforge.io/sitemap.xml +# LLM/GEO hint +# https://xenvsync.softexforge.io/llms.txt diff --git a/website/public/sitemap.xml b/website/public/sitemap.xml deleted file mode 100644 index ae64269..0000000 --- a/website/public/sitemap.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - https://xenvsync.softexforge.io/ - 1.0 - - - https://xenvsync.softexforge.io/roadmap - 0.8 - - - https://xenvsync.softexforge.io/docs/getting-started - 0.9 - - - https://xenvsync.softexforge.io/docs/installation - 0.8 - - - https://xenvsync.softexforge.io/docs/commands - 0.8 - - - https://xenvsync.softexforge.io/docs/security - 0.7 - - diff --git a/website/src/app/blog/developer-workflow/page.tsx b/website/src/app/blog/developer-workflow/page.tsx new file mode 100644 index 0000000..60dd1e8 --- /dev/null +++ b/website/src/app/blog/developer-workflow/page.tsx @@ -0,0 +1,247 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Callout, Card, PageHeader, Section } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "The Secret-Safe Developer Workflow: Local to CI Without Leaks", + description: + "A complete developer workflow for keeping .env secrets out of Git history, build logs, and container images — from laptop initialization through production CI/CD using xenvsync push, pull, run, and verify.", + openGraph: { + title: "The Secret-Safe Developer Workflow: Local to CI Without Leaks", + description: "A repeatable four-phase pattern for secret-safe development with xenvsync.", + url: "https://xenvsync.softexforge.io/blog/developer-workflow", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/blog/developer-workflow" }, +}; + +export default function DeveloperWorkflowPost() { + return ( +
+ + +
+ +

+ A 2025 GitHub analysis found that over 10 million secrets were accidentally committed to public repositories in a single year. The vast majority came from developers who copy-pasted a .env file into the wrong place, forgot a .gitignore rule, or pushed a branch without realizing it contained credentials. +

+

+ The solution is not more careful developers — it is a workflow that makes the insecure path harder than the secure one. When the default action is "encrypt first," accidental exposure becomes structurally unlikely. +

+

+ This post describes a four-phase workflow using xenvsync that makes the secure path the easy path, from your first git init through production deployment. +

+
+
+ +
+ +

+ Start every new project — or retrofit an existing one — with two commands. xenvsync init generates a 256-bit cryptographically random key and writes it with 0600 permissions. It also updates .gitignore to exclude both the key and your plaintext .env. +

+
+ +{`# Install once per machine +$ npm install -g @nasimstg/xenvsync +# or: brew install nasimstg/tap/xenvsync +# or: go install github.com/nasimstg/xenvsync@latest + +# Initialize the project +$ xenvsync init +# ✓ Generated .xenvsync.key (256-bit, mode 0600) +# ✓ Added .xenvsync.key to .gitignore +# ✓ Added .env to .gitignore + +# Create your .env file normally +$ cat .env +DATABASE_URL=postgres://localhost:5432/myapp +API_SECRET=dev-secret-key +REDIS_URL=redis://localhost:6379 + +# Encrypt it +$ xenvsync push +# ✓ Encrypted .env → .env.vault + +# Commit the vault (never the plaintext) +$ git add .env.vault +$ git commit -m "add encrypted vault"`} + + + The .xenvsync.key file is your decryption material. Back it up somewhere secure (password manager, secrets store) before relying on the vault. If the key is lost, the vault cannot be decrypted. + +
+ +
+ +

+ The daily loop is simple: edit secrets in .env as needed, push to update the vault, and use xenvsync run to start your app. The key insight is that xenvsync run decrypts the vault in memory and injects the variables directly into the child process — plaintext never reaches the filesystem. +

+
+ +{`# Pull latest vault from repo +$ git pull +$ xenvsync pull # restores .env from vault + +# Edit secrets as needed +$ vim .env + +# Re-encrypt after changes +$ xenvsync push +$ git add .env.vault && git commit -m "update secrets" + +# Start app — secrets live only in process memory +$ xenvsync run -- npm start +$ xenvsync run -- python manage.py runserver +$ xenvsync run -- go run ./cmd/server + +# Check what changed since last push +$ xenvsync diff + +# See full sync state +$ xenvsync status`} + + +

+ Pro tip: Add the pre-commit hook from examples/hooks/pre-commit to your repo. It blocks commits when the vault is stale or when a plaintext .env file is staged. This turns security into an automatic guard rail rather than a remembered step. +

+
+ +{`$ cp examples/hooks/pre-commit .git/hooks/pre-commit +$ chmod +x .git/hooks/pre-commit`} + +
+ +
+ +

+ CI jobs need secrets but should never store them in pipeline YAML, environment variable UI, or build artifacts. The pattern: store the raw key value as a CI secret, write it to a file at runtime, use xenvsync run to inject secrets in-memory. +

+
+ +{`name: CI +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Inject xenvsync key + run: | + echo "\${{ secrets.XENVSYNC_KEY }}" > .xenvsync.key + chmod 600 .xenvsync.key + + - name: Audit vault integrity + run: | + xenvsync doctor + xenvsync verify + + - name: Run tests with secrets + run: xenvsync run -- npm test + + - name: Build + run: xenvsync run -- npm run build`} + + +{`test: + script: + - echo "$XENVSYNC_KEY" > .xenvsync.key && chmod 600 .xenvsync.key + - xenvsync doctor && xenvsync verify + - xenvsync run -- npm test`} + + +{`jobs: + build: + steps: + - checkout + - run: + name: Inject secrets + command: | + echo "$XENVSYNC_KEY" > .xenvsync.key + chmod 600 .xenvsync.key + - run: xenvsync run -- npm test`} + +
+ +
+ +

+ For projects with separate staging and production configurations, use named environments. Each gets its own vault file and its own key (or its own team roster slot for V2 vaults). The --env flag is consistent across all commands. +

+
+ +{`# Push separate vaults per environment +$ xenvsync push --env staging +$ xenvsync push --env production + +# Named vault files created: +# .env.staging.vault +# .env.production.vault + +# Discover all environments +$ xenvsync envs + +# Pull a specific environment +$ xenvsync pull --env staging + +# Run against a specific environment +$ xenvsync run --env production -- node server.js + +# Audit a specific environment +$ xenvsync verify --env production +$ xenvsync log --env production`} + + + Use environment fallback merging to share common variables: put shared values in .env.shared, environment-specific overrides in .env.staging, and local machine overrides in .env.local. xenvsync merges all three when you push. + +
+ +
+ + + + + + + + + + + {[ + ["Setup", "xenvsync init", "Generate key, update .gitignore"], + ["Encrypt", "xenvsync push", "Encrypt .env → .env.vault"], + ["Run locally", "xenvsync run -- ", "Inject secrets in-memory"], + ["Restore", "xenvsync pull", "Decrypt vault → .env"], + ["Audit", "xenvsync doctor + verify", "Check health and integrity"], + ["Diff", "xenvsync diff", "Preview changes before push"], + ["History", "xenvsync log", "Key-level change history"], + ["Rotate", "xenvsync rotate", "Cycle key material"], + ].map(([phase, cmd, desc], i) => ( + + + + + + ))} + +
PhaseCommandWhat it does
{phase}{cmd}{desc}
+
+
+ → Full command reference + → CI/CD recipes +
+
+
+ ); +} diff --git a/website/src/app/blog/migration-playbook/page.tsx b/website/src/app/blog/migration-playbook/page.tsx new file mode 100644 index 0000000..9ab8464 --- /dev/null +++ b/website/src/app/blog/migration-playbook/page.tsx @@ -0,0 +1,274 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Callout, Card, PageHeader, Section } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "Migration Playbook: From dotenv / git-crypt / sops to xenvsync", + description: + "A phased migration playbook to move from plaintext .env files, dotenv-vault, git-crypt, or sops to xenvsync. Includes inventory steps, CI transition, team key setup, rollback strategy, and a go/no-go checklist.", + openGraph: { + title: "Migration Playbook: From dotenv / git-crypt / sops to xenvsync", + description: "A low-risk, phased migration guide to xenvsync with rollback options at every step.", + url: "https://xenvsync.softexforge.io/blog/migration-playbook", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/blog/migration-playbook" }, +}; + +export default function MigrationPlaybookPost() { + return ( +
+ + +
+ +

+ Migrating secret management is high-stakes work. The goal of this playbook is to make each phase independently reversible. You should be able to complete Phase 1 and stop, run both systems in parallel during Phase 2, and only cut over fully in Phase 4 once you have validated that xenvsync works end-to-end in your environment. +

+

+ Estimated timeline: One sprint (one week) for most teams. The bottleneck is usually CI validation and getting all team members to generate identities — not the technical setup. +

+
+
+ {[ + { label: "From plaintext .env", time: "1–2 days", note: "Fastest path — nothing to replace, just add encryption." }, + { label: "From dotenv-vault", time: "2–3 days", note: "Need to export secrets from service and re-encrypt locally." }, + { label: "From git-crypt / sops", time: "3–5 days", note: "Decrypt existing vault, re-encrypt with xenvsync, validate CI." }, + ].map(({ label, time, note }) => ( + +

{label}

+

{time}

+

{note}

+
+ ))} +
+
+ +
+ +

+ Before touching any secrets, understand what you have. List every .env file, vault artifact, and CI secret in use. This inventory becomes your test checklist in Phase 4. +

+
+ +{`# Find all .env files in the repo (excluding node_modules) +$ find . -name ".env*" -not -path "*/node_modules/*" -not -path "*/.git/*" + +# List all CI secrets (check your provider's UI or CLI) +# GitHub: gh secret list +# GitLab: glab variable list + +# Note: environments in use +$ ls .env* | grep -v ".vault" | grep -v ".example"`} + + +{`# npm (all platforms) +$ npm install -g @nasimstg/xenvsync + +# macOS / Linux (Homebrew) +$ brew install nasimstg/tap/xenvsync + +# Windows (Scoop) +$ scoop install xenvsync + +# Verify +$ xenvsync version`} + +
+ +
+ +

+ Start with staging or dev — never production first. This lets you validate the full push/pull/run cycle with low risk. Run the old and new systems in parallel until you are confident. +

+
+ +{`# Initialize in the project root +$ xenvsync init +# ✓ Generated .xenvsync.key (mode 0600) +# ✓ Updated .gitignore + +# Encrypt the staging environment +$ xenvsync push --env staging + +# Validate: decrypt and check contents +$ xenvsync pull --env staging +$ diff .env.staging .env.staging.backup # compare to known-good + +# Verify vault integrity +$ xenvsync verify --env staging +$ xenvsync doctor + +# Commit the vault +$ git add .env.staging.vault +$ git commit -m "migrate: add xenvsync encrypted staging vault"`} + + + Keep your old secret management running in parallel. Do not delete dotenv-vault files, git-crypt keys, or sops configs until Phase 4 validation is complete. + +
+ +
+ +

+ Update one CI job to use xenvsync. Store the key value as a CI secret and write it to a file at runtime. Validate that the job passes before updating all jobs. +

+
+ +{`# Add XENVSYNC_STAGING_KEY to repo secrets +# Settings → Secrets → Actions → New repository secret + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # New: inject xenvsync key + - name: Inject key + run: | + echo "\${{ secrets.XENVSYNC_STAGING_KEY }}" > .xenvsync.key + chmod 600 .xenvsync.key + + # Remove: old env var injection or dotenv-vault step + # - run: dotenv-vault pull ... + + - name: Run tests + run: xenvsync run --env staging -- npm test`} + + +{`test: + variables: + # Add XENVSYNC_STAGING_KEY as a masked CI/CD variable + script: + - echo "$XENVSYNC_STAGING_KEY" > .xenvsync.key && chmod 600 .xenvsync.key + - xenvsync run --env staging -- npm test`} + +
+ +
+ +

+ If you are moving away from a shared-key model, have each team member generate their X25519 identity now. Collect all public keys before re-pushing the vault. +

+
+ +{`# Each team member (once per machine) +$ xenvsync keygen +$ xenvsync whoami # share this output with the maintainer + +# Maintainer: build roster from collected public keys +$ xenvsync team add alice +$ xenvsync team add bob +# ... add all members + +# Re-push as V2 vault +$ xenvsync push --env staging +$ xenvsync push --env production +$ git add .xenvsync-team.json *.vault +$ git commit -m "migrate: switch to V2 per-member vaults"`} + +
+ +
+ +

Run this checklist before removing the old system:

+
+ + {[ + "xenvsync pull works for every team member on their machine.", + "xenvsync run works in local dev for all services.", + "CI passes on all pipelines using xenvsync key injection.", + "xenvsync doctor reports no failures or security warnings.", + "xenvsync verify passes on all vaults.", + "All team members have generated identities and pulled successfully (for V2).", + "Pre-commit hook installed and tested — blocks plaintext .env staging.", + ].map((item, i) => ( +
+ + {item} +
+ ))} +
+ +{`# Encrypt production environment +$ xenvsync push --env production + +# Run full audit +$ xenvsync doctor +$ xenvsync verify --env staging +$ xenvsync verify --env production + +# Commit everything +$ git add . +$ git commit -m "migrate: complete xenvsync migration across all environments" + +# Now safe to remove old artifacts: +# - .env.vault (dotenv-vault format if different) +# - .git-crypt keys +# - sops .decrypted files +# - old CI secret variables`} + +
+ +
+ +

+ Because each phase is additive (you never delete the old system until Phase 4), rollback at any phase is straightforward: +

+
    + {[ + "Phase 1 rollback: Nothing changed — just delete .xenvsync.key and the vault files.", + "Phase 2 rollback: Revert the vault commit. The old system is still functional.", + "Phase 3 rollback: Revert the CI job change. Old secrets variables are still there.", + "Phase 4 rollback: If you did not delete old artifacts, revert the commit and restore the old CI variables.", + ].map((item, i) => ( +
  • + + {item} +
  • + ))} +
+
+ + xenvsync vaults are format-stable and forward-compatible. V1 and V2 vaults are both supported in all current and future xenvsync versions. You will not be locked in to a specific version after migration. + +
+ +
+
+ +

From plaintext .env files

+

+ Fastest path. Just run xenvsync init and xenvsync push. The .gitignore update happens automatically. Run Phase 2–4 in a single day. +

+
+ +

From dotenv-vault

+

+ Export your decrypted secrets from the dotenv-vault service into a local .env file. Then run xenvsync init and push. The key difference: xenvsync never calls an external service — the vault is self-contained in your repo. +

+
+ +

From git-crypt

+

+ Run git-crypt unlock to decrypt the repo, copy the plaintext secrets to a temporary .env file, then run xenvsync init and push. After validating, remove the git-crypt filter configuration from .gitattributes. +

+
+ +

From sops

+

+ Run sops --decrypt secrets.yaml to extract plaintext values. Convert to KEY=VALUE format in a .env file. Then follow the standard xenvsync init and push flow. For teams already using age keys, note that X25519 key derivation in xenvsync is independent of age key format. +

+
+
+
+ → Full migration docs + → Tool comparison +
+
+
+ ); +} diff --git a/website/src/app/blog/page.tsx b/website/src/app/blog/page.tsx new file mode 100644 index 0000000..580d800 --- /dev/null +++ b/website/src/app/blog/page.tsx @@ -0,0 +1,142 @@ +import Link from "next/link"; +import { Card, PageHeader, Section } from "@/components/DocsComponents"; + +export const metadata = { + title: "Blog - xenvsync", + description: + "The xenvsync blog — deep dives into .env secret management, AES-256-GCM cryptography, team key sharing, CI/CD injection patterns, tool comparisons, and migration guides for developers.", + openGraph: { + title: "xenvsync Blog", + description: "Engineering content on secure .env workflows, cryptography, team secrets, and developer tooling.", + url: "https://xenvsync.softexforge.io/blog", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/blog" }, +}; + +const posts = [ + { + href: "/blog/tool-comparison", + title: "xenvsync vs dotenv-vault vs sops — A Practical Comparison", + category: "Tool Comparison", + date: "2026-04-01", + readTime: "8 min read", + excerpt: "A detailed comparison across security model, team key sharing, CI ergonomics, and operational overhead. When each tool wins and when it doesn't.", + tags: ["security", "dotenv-vault", "sops", "comparison"], + }, + { + href: "/blog/tool-ranking", + title: "Best .env Secret Management Tools for 2026", + category: "Tool Ranking", + date: "2026-04-01", + readTime: "6 min read", + excerpt: "Ranking xenvsync, sops, dotenv-vault, and git-crypt across local-first security, developer experience, team access control, and 12-month operational cost.", + tags: ["ranking", "security", "developer experience"], + }, + { + href: "/blog/use-case-story", + title: "How a 7-Person Startup Encrypted Their Secrets in One Sprint", + category: "Case Study", + date: "2026-03-30", + readTime: "7 min read", + excerpt: "From scattered Slack-shared .env files to encrypted vaults with per-member keys in a single week. A real migration story with the commands that made it happen.", + tags: ["startup", "team", "migration", "case study"], + }, + { + href: "/blog/developer-workflow", + title: "The Secret-Safe Developer Workflow: Local to CI Without Leaks", + category: "Developer Workflow", + date: "2026-03-29", + readTime: "9 min read", + excerpt: "A repeatable four-step pattern for keeping plaintext secrets out of repos, build logs, and container images — from laptop setup through production deployment.", + tags: ["workflow", "CI/CD", "docker", "best practices"], + }, + { + href: "/blog/technical-deep-dive", + title: "Inside xenvsync: AES-256-GCM Encryption and X25519 Team Sharing", + category: "Technical Deep Dive", + date: "2026-03-28", + readTime: "12 min read", + excerpt: "A detailed look at the vault format, nonce generation, GCM authentication, and the X25519 ECDH key exchange that enables per-member team vaults with no shared secrets.", + tags: ["cryptography", "AES-256-GCM", "X25519", "security"], + }, + { + href: "/blog/migration-playbook", + title: "Migration Playbook: From dotenv / git-crypt to xenvsync", + category: "Migration Guide", + date: "2026-03-27", + readTime: "10 min read", + excerpt: "A phased, low-risk migration plan for teams moving off plaintext .env files, dotenv-vault, sops, or git-crypt. Includes rollback strategy and CI transition steps.", + tags: ["migration", "dotenv-vault", "git-crypt", "sops"], + }, +]; + +const categoryColors: Record = { + "Tool Comparison": "text-blue-400 border-blue-400/30 bg-blue-400/5", + "Tool Ranking": "text-purple-400 border-purple-400/30 bg-purple-400/5", + "Case Study": "text-amber-400 border-amber-400/30 bg-amber-400/5", + "Developer Workflow": "text-green-400 border-green-400/30 bg-green-400/5", + "Technical Deep Dive": "text-rose-400 border-rose-400/30 bg-rose-400/5", + "Migration Guide": "text-cyan-400 border-cyan-400/30 bg-cyan-400/5", +}; + +export default function BlogIndexPage() { + return ( +
+ + +
+
+ {posts.map((post) => ( + +
+ + {post.category} + + {post.date} + · + {post.readTime} +
+

+ {post.title} +

+

{post.excerpt}

+
+
+ {post.tags.map((tag) => ( + + #{tag} + + ))} +
+ + Read article → + +
+
+ ))} +
+
+ +
+
+ {[ + { label: "Cryptography", detail: "AES-256-GCM, X25519, nonce safety" }, + { label: "Team Secrets", detail: "Per-member keys, rotation, revocation" }, + { label: "CI/CD", detail: "GitHub Actions, GitLab, Docker, runtime injection" }, + { label: "Tool Comparisons", detail: "xenvsync vs dotenv-vault vs sops vs git-crypt" }, + { label: "Migration Guides", detail: "Step-by-step transitions with rollback plans" }, + { label: "Developer Workflow", detail: "Local dev to production, secret-safe patterns" }, + ].map((topic) => ( + +

{topic.label}

+

{topic.detail}

+
+ ))} +
+
+
+ ); +} diff --git a/website/src/app/blog/technical-deep-dive/page.tsx b/website/src/app/blog/technical-deep-dive/page.tsx new file mode 100644 index 0000000..db51cb9 --- /dev/null +++ b/website/src/app/blog/technical-deep-dive/page.tsx @@ -0,0 +1,219 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Card, PageHeader, Section } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "Inside xenvsync: AES-256-GCM Encryption and X25519 Team Sharing", + description: + "A technical deep dive into xenvsync's cryptographic model: AES-256-GCM vault format, nonce generation, GCM authentication tags, X25519 ECDH key exchange for team vaults, passphrase-based key encryption with scrypt, and memory zeroing.", + openGraph: { + title: "Inside xenvsync: AES-256-GCM Encryption and X25519 Team Sharing", + description: "The vault format, cryptographic primitives, and security design decisions behind xenvsync.", + url: "https://xenvsync.softexforge.io/blog/technical-deep-dive", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/blog/technical-deep-dive" }, +}; + +export default function TechnicalDeepDivePost() { + return ( +
+ + +
+ +

+ xenvsync was built around three non-negotiable security properties: +

+
    + {[ + "Authenticated encryption — any tampering with the vault must be detectable before decryption proceeds.", + "Fresh randomness — encrypting the same plaintext twice must produce different ciphertext, with no nonce reuse.", + "Local key custody — decryption material must never leave the local machine or pass through a third-party service.", + ].map((item, i) => ( +
  1. + {i + 1}. + {item} +
  2. + ))} +
+

+ AES-256-GCM satisfies all three. The team sharing layer adds a fourth property: per-member key isolation using X25519 ECDH, so no symmetric secret is ever distributed across a team. +

+
+
+ +
+ +

+ For solo use (no team roster), xenvsync uses V1: a single AES-256-GCM symmetric key stored at .xenvsync.key with 0600 permissions. The vault file is a base64-encoded blob wrapped in ASCII header and footer markers. +

+

+ The binary layout inside the base64 encoding is: +

+
+ +{`[ 12-byte nonce ][ ciphertext (variable length) ][ 16-byte GCM tag ] + +Total overhead: 28 bytes + plaintext length +Base64-encoded and wrapped with: + -----BEGIN XENVSYNC VAULT----- + + -----END XENVSYNC VAULT-----`} + + +

+ Nonce: 12 bytes generated from crypto/rand — Go's cryptographically secure random source backed by the operating system. A fresh nonce is generated on every xenvsync push, ensuring ciphertext is non-deterministic even when the plaintext has not changed. +

+

+ AES-256-GCM: The 256-bit key encrypts the serialized .env content. GCM (Galois/Counter Mode) is an authenticated encryption with associated data (AEAD) scheme — it produces both a ciphertext and a 16-byte authentication tag. The tag covers the full ciphertext and nonce. If any byte of the vault is modified, decryption fails with an authentication error rather than silently returning corrupted plaintext. +

+

+ Key size: The symmetric key is exactly 32 bytes (256 bits), generated by crypto/rand.Read. Keys shorter than this are rejected by doctor. +

+
+
+ +
+ +

+ When a .xenvsync-team.json roster exists, xenvsync upgrades to V2 format. The vault now contains a JSON header with per-member key slots, followed by the same AES-256-GCM encrypted payload. +

+

+ The core idea: a single symmetric data encryption key (DEK) encrypts the payload. For each team member, an ephemeral X25519 key pair performs an ECDH exchange with the member's long-term public key. The shared secret derived from this exchange encrypts the DEK. Each member gets their own slot containing the ephemeral public key and the encrypted DEK. +

+
+ +{`-----BEGIN XENVSYNC V2 VAULT----- +[JSON key slot array] +{"slots": [ + { + "name": "alice", + "ephemeralPublicKey": "", + "encryptedKey": "" + }, + { + "name": "bob", + "ephemeralPublicKey": "", + "encryptedKey": "" + } +]} +---DATA--- + +-----END XENVSYNC V2 VAULT-----`} + + +

+ X25519 ECDH key exchange: X25519 is the Diffie-Hellman function over Curve25519. It takes a private key and a public key and produces a 32-byte shared secret. For each push operation, xenvsync generates a fresh ephemeral private key, computes ECDH(ephemeral_private, member_public), and uses the result (via HKDF expansion) to encrypt the DEK with AES-256-GCM. +

+

+ Why ephemeral keys? Using a fresh ephemeral keypair per push means that even if the DEK slot is somehow decrypted, it provides no information about previous vault versions. Each push generates completely independent key material for every slot. This is forward secrecy at the vault level. +

+

+ Decryption: A member runs xenvsync pull. Their identity's private key is at ~/.xenvsync/identity (0600 permissions). xenvsync scans the slot list for their name, computes ECDH(identity_private, slot.ephemeralPublicKey) to recover the shared secret, decrypts the DEK, then uses the DEK to decrypt the payload. The DEK never leaves the member's machine. +

+
+
+ +
+ +

+ For additional protection of the key file itself, xenvsync init --passphrase wraps the symmetric key with a key-encryption-key (KEK) derived from a user passphrase. +

+
+ +{`KEK = scrypt(passphrase, salt, N=32768, r=8, p=1, keyLen=32) +encrypted_key = AES-256-GCM(KEK, key_material) + +Stored as: "enc:" + base64(salt || nonce || ciphertext || tag) + +Runtime: XENVSYNC_PASSPHRASE env var → derive KEK → decrypt key → proceed`} + + +

+ scrypt parameters: N=32768 (2^15) work factor, r=8 block size, p=1 parallelization. These parameters require ~32 MB of memory per derivation attempt, making offline dictionary attacks expensive. The salt is generated fresh per key file. +

+

+ This provides defense in depth: even if .xenvsync.key is leaked, it cannot be used without the passphrase. +

+
+
+ +
+ +

+ xenvsync zeroes out key material after use via crypto.ZeroBytes, which overwrites the byte slice with zeros before letting the garbage collector reclaim it. This limits the window during which key material exists in process memory and reduces the risk of it persisting in a core dump or memory snapshot. +

+

+ xenvsync run is the most security-sensitive command: it decrypts the vault in memory, merges the env pairs into the child process environment, spawns the child, and immediately zeroes the key material. The child process inherits only the environment variables — never the key. +

+
+ +{`// ZeroBytes overwrites a byte slice with zeros +// to limit key material residency in memory. +func ZeroBytes(b []byte) { + for i := range b { + b[i] = 0 + } +}`} + +
+ +
+ +

+ xenvsync rotate decrypts the current vault, generates fresh key material, and re-encrypts in one atomic step. The ordering matters: the vault is written first, then the key file. If the key write fails, the old key can still decrypt the old vault. This prevents a scenario where the vault has been updated to use a new key that was never saved. +

+

+ In V2 mode, rotation generates fresh ephemeral X25519 keys for all current roster members. The --revoke <name> flag removes a member from the roster before re-encryption, effectively excluding them from all future decryption. Their existing private key can still decrypt historical vaults committed before the rotation — which is why immediate rotation after offboarding is essential. +

+
+
+ +
+
+ {[ + { + threat: "Vault committed to public repo", + mitigation: "AES-256-GCM ciphertext is computationally infeasible to decrypt without the key. GCM tag ensures tamper detection.", + }, + { + threat: "Key file leaked (no passphrase)", + mitigation: "Attacker can decrypt all vaults encrypted with that key. Immediate rotation invalidates the key for future vaults.", + }, + { + threat: "Key file leaked (passphrase protected)", + mitigation: "Attacker must brute-force scrypt(N=32768) — expensive. Rotate key after confirmed exposure.", + }, + { + threat: "Team member with access leaks secrets", + mitigation: "Per-member key slots limit blast radius to that member's identity. Rotate to exclude compromised identity.", + }, + { + threat: "Vault file tampered in transit", + mitigation: "GCM authentication tag detects any modification. xenvsync verify catches this explicitly.", + }, + { + threat: "Nonce reuse producing repeated ciphertext", + mitigation: "12-byte nonce from crypto/rand per operation. Probability of collision across 2^48 operations is negligible.", + }, + ].map(({ threat, mitigation }) => ( + +

{threat}

+

{mitigation}

+
+ ))} +
+
+ +
+
+ Security Model docs + Tool comparison +
+
+
+ ); +} diff --git a/website/src/app/blog/tool-comparison/page.tsx b/website/src/app/blog/tool-comparison/page.tsx new file mode 100644 index 0000000..05d05b6 --- /dev/null +++ b/website/src/app/blog/tool-comparison/page.tsx @@ -0,0 +1,206 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Callout, Card, PageHeader, Section } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "xenvsync vs dotenv-vault vs sops — Practical Comparison 2026", + description: + "A detailed technical comparison of xenvsync, dotenv-vault, and sops for .env secret management. Covers security model, team key sharing, CI/CD ergonomics, vault format, and operational overhead.", + openGraph: { + title: "xenvsync vs dotenv-vault vs sops — Practical Comparison 2026", + description: "Which secret management tool fits your team? An honest comparison across security, DX, and ops.", + url: "https://xenvsync.softexforge.io/blog/tool-comparison", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/blog/tool-comparison" }, +}; + +export default function ToolComparisonPost() { + return ( +
+ + +
+ +

+ Every serious project eventually faces the same question: how do we keep .env secrets out of Git without creating a workflow nightmare? Three tools dominate this space in 2026 — xenvsync, dotenv-vault, and sops — and they make fundamentally different tradeoffs around cloud dependency, team key distribution, and developer friction. +

+

+ This comparison is practical, not marketing. Each tool has a real place. The goal is to help you pick the right one for your team size, trust model, and operational appetite. +

+
+
+ +
+ + + + + + + + + + + + {[ + ["Cloud dependency", "None — fully local", "Required for sync service", "Optional (KMS / age)"], + ["Encryption algorithm", "AES-256-GCM", "AES-256-GCM (service-managed)", "AES-256-GCM, PGP, age"], + ["Team key model", "X25519 per member", "Shared service key", "PGP / KMS / age recipients"], + ["In-memory run", "xenvsync run (built-in)", "Limited support", "No native run command"], + ["Vault format", "Git-safe base64 file", "Proprietary .env.vault", "JSON/YAML with inline ciphertext"], + ["Multi-environment", "--env flag, auto-detection", "Environment tiers", "Separate files or --config"], + ["Audit trail", "xenvsync log (Git-native)", "Dashboard (cloud)", "Git history only"], + ["Key rotation", "xenvsync rotate (atomic)", "Service-managed", "Manual re-encrypt"], + ["Access revocation", "rotate --revoke (instant)", "Remove from service", "Re-encrypt with new key list"], + ["Installation", "npm, brew, scoop, go, AUR, nix", "npm only", "package manager / binary"], + ["Single binary", "Yes, no runtime deps", "No (Node.js required)", "Yes"], + ["Setup time", "< 2 minutes", "Account + CLI setup", "Key infrastructure required"], + ["Free to self-host", "Yes (MIT license)", "Limited free tier", "Yes (open source)"], + ].map(([dim, xenv, dotenv, sops], i) => ( + + + + + + + ))} + +
Dimensionxenvsyncdotenv-vaultsops
{dim}{xenv}{dotenv}{sops}
+
+
+ +
+
+ +

xenvsync

+

+ xenvsync owns the full encryption pipeline locally. It uses AES-256-GCM with a fresh 12-byte random nonce per operation and a 16-byte GCM authentication tag that detects any vault tampering. In V1 mode, a 256-bit key lives on disk at .xenvsync.key (0600 permissions, excluded from Git). In V2 team mode, each member has an X25519 keypair — the vault contains an encrypted key slot per member derived from ephemeral ECDH. No symmetric secret is ever shared across the team. +

+
+ +

dotenv-vault

+

+ dotenv-vault syncs secrets through a hosted service. The encryption happens on their servers, and you pull decryption keys via a DOTENV_KEY token tied to your account. This means you have strong ergonomics and a managed key lifecycle, but you are also dependent on their service availability, their security practices, and their terms of service. The vault file is a local artifact, but the decryption path runs through the cloud. +

+
+ +

sops

+

+ sops is the most flexible of the three. It supports PGP keys, AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault, and the modern age format. The encrypted values are embedded inline in YAML or JSON, making diffs readable at the key level. The tradeoff is setup complexity — you need a working key management infrastructure before anyone can encrypt or decrypt. For small teams without existing KMS, that overhead is significant. +

+
+
+
+ +
+ +

This is where the differences become most operational.

+

+ xenvsync V2: Each developer generates a keypair once (xenvsync keygen), shares their public key, and gets added to the roster. The maintainer runs xenvsync push and the vault contains an encrypted key slot for every member. Onboarding a new developer is three commands. Revoking an ex-employee is one command with immediate effect on the next push. +

+

+ dotenv-vault: Access is managed at the service level. Team members connect their CLI to the service account. Access control depends on the service's permission model. Revoking access requires going through the dashboard or API. +

+

+ sops: Each team member needs a PGP key or KMS role. Adding a member means updating the recipients list and re-encrypting. This is powerful and auditable but operationally heavier than xenvsync's roster model, especially for teams without existing PGP infrastructure. +

+
+ +{`# New member runs once +$ xenvsync keygen && xenvsync whoami + +# Maintainer adds and re-encrypts +$ xenvsync team add newdev && xenvsync push`} + +
+ +
+ +

All three tools work in CI, but with different friction levels.

+

+ xenvsync: Store the raw key value as a CI secret. Write it to .xenvsync.key at runtime. Run xenvsync run -- <command>. The vault is already in the repo. No service calls, no network dependency. +

+

+ dotenv-vault: Store your DOTENV_KEY as a CI secret. The CLI calls the service to decrypt. This requires network access to dotenv-vault's service during the build — a hard dependency that can break your pipeline if the service is unavailable. +

+

+ sops: Works well with KMS-backed roles (IAM in GitHub Actions, workload identity in GCP). For PGP-based setups, the private key must be imported into the CI environment. The setup is well-documented but requires more CI configuration than xenvsync. +

+
+ +{`- run: echo "\${{ secrets.XENVSYNC_KEY }}" > .xenvsync.key && chmod 600 .xenvsync.key +- run: xenvsync run -- npm run build`} + +
+ +
+
+ {[ + { + tool: "xenvsync", + color: "text-[var(--color-accent)]", + wins: [ + "You want zero cloud dependency — vault + key lifecycle stays entirely on your infrastructure.", + "You need per-member access without distributing a shared symmetric key.", + "Your team values simple, memorable commands over deep configurability.", + "You need in-memory secret injection (xenvsync run) for local dev and CI.", + "You want a Git-native audit log of secret changes (xenvsync log).", + ], + }, + { + tool: "dotenv-vault", + color: "text-yellow-400", + wins: [ + "Your team is already deep in the dotenv ecosystem and values managed UX.", + "You want a web dashboard for secret management without CLI expertise.", + "Cloud dependency is acceptable or preferred for your compliance posture.", + ], + }, + { + tool: "sops", + color: "text-blue-400", + wins: [ + "You have existing KMS infrastructure (AWS, GCP, Azure, HashiCorp Vault).", + "You need fine-grained recipient control with enterprise key management.", + "You want encrypted values visible inline in YAML/JSON for partial secret diffs.", + "Your team has PGP/age expertise and wants maximum cryptographic flexibility.", + ], + }, + ].map(({ tool, color, wins }) => ( + +

{tool}

+
    + {wins.map((w, i) => ( +
  • + + {w} +
  • + ))} +
+
+ ))} +
+
+ +
+ +

+ For teams that want local-first security, a simple mental model, and commands that work identically on any laptop or CI runner without external services — xenvsync is the best fit. +

+

+ For teams that already have KMS infrastructure and need enterprise-grade recipient management, sops is the right choice. For teams that want a managed product with a web UI and are comfortable with cloud dependency, dotenv-vault is viable. +

+
+ + See also:{" "} + Best .env Tools Ranking for 2026 + {" "}and{" "} + Migration Playbook. + +
+
+ ); +} diff --git a/website/src/app/blog/tool-ranking/page.tsx b/website/src/app/blog/tool-ranking/page.tsx new file mode 100644 index 0000000..e45a131 --- /dev/null +++ b/website/src/app/blog/tool-ranking/page.tsx @@ -0,0 +1,251 @@ +import { Card, PageHeader, Section } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "Best .env Secret Management Tools for 2026", + description: + "An honest ranking of xenvsync, sops, dotenv-vault, and git-crypt for .env secret management in 2026. Evaluated on local-first security, developer experience, team access control, CI/CD ergonomics, and operational cost.", + openGraph: { + title: "Best .env Secret Management Tools for 2026", + description: "Ranking secret management tools by security posture, DX, and operational overhead for modern teams.", + url: "https://xenvsync.softexforge.io/blog/tool-ranking", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/blog/tool-ranking" }, +}; + +const criteria = [ + { + name: "Local-first security", + weight: "25%", + detail: "Does the tool work without a cloud service? Are keys stored and controlled locally? Can you encrypt and decrypt with zero network calls?", + }, + { + name: "Developer experience", + weight: "25%", + detail: "Time from zero to encrypted. Command memorability. How painful is onboarding a new developer? How smooth is the daily push/pull/run loop?", + }, + { + name: "Team access control", + weight: "25%", + detail: "Can members use individual keys instead of a shared secret? How easy is access revocation? Is offboarding a single command or a multi-step process?", + }, + { + name: "Operational overhead", + weight: "25%", + detail: "Infrastructure required to operate. Ongoing maintenance of keys, dependencies, and service accounts. What breaks when a team member leaves?", + }, +]; + +const rankings = [ + { + rank: 1, + tool: "xenvsync", + version: "v1.12.0", + tagline: "Best overall for developer teams that want local-first security with minimal overhead.", + scores: { + "Local-first security": { score: 10, note: "Entirely local — no external calls. AES-256-GCM + X25519. Key stays on your machine." }, + "Developer experience": { score: 9, note: "< 2 min setup. Commands like push/pull/run are intuitive. doctor and verify catch mistakes early." }, + "Team access control": { score: 9, note: "Per-member X25519 keys. Single-command revocation with rotate --revoke. No shared secrets." }, + "Operational overhead": { score: 9, note: "Single binary, no runtime deps. Vault in Git. Only the key needs protecting." }, + }, + highlights: [ + "Zero cloud dependency — works offline, works in air-gapped environments", + "xenvsync run injects secrets in-memory, plaintext never hits disk", + "V2 team vaults with per-member X25519 key slots", + "Git-native audit log via xenvsync log", + "doctor + verify make security posture visible and actionable", + "MIT license, single static binary, 8 install methods", + ], + limitations: [ + "Younger ecosystem than sops (first release March 2026)", + "No web dashboard or service-level secret sharing", + ], + }, + { + rank: 2, + tool: "sops", + version: "v3.x", + tagline: "Most powerful and flexible, best for teams with existing KMS infrastructure.", + scores: { + "Local-first security": { score: 8, note: "Local with age/PGP; cloud-dependent with KMS. Full control when configured correctly." }, + "Developer experience": { score: 6, note: "Powerful but steep setup curve. Requires PGP/age key infra or KMS role setup before first use." }, + "Team access control": { score: 8, note: "Flexible recipient model (PGP, age, KMS). Re-encryption required when roster changes." }, + "Operational overhead": { score: 6, note: "Needs key infra. Re-encrypting after roster changes is manual. KMS costs money." }, + }, + highlights: [ + "Supports PGP, age, AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault", + "Inline encrypted values in YAML/JSON — partial diffs without decryption", + "Strong enterprise adoption and audit tooling", + "Mature project with large community", + ], + limitations: [ + "Significant setup time for teams without existing key infrastructure", + "No built-in run command for in-memory injection", + "Re-encryption after team changes is not atomic", + ], + }, + { + rank: 3, + tool: "dotenv-vault", + version: "latest", + tagline: "Easiest onboarding for dotenv users who accept cloud dependency.", + scores: { + "Local-first security": { score: 5, note: "Decryption requires service call. Outage or account issue can block your workflow." }, + "Developer experience": { score: 8, note: "Very smooth for existing dotenv users. Web dashboard is a plus for non-CLI teams." }, + "Team access control": { score: 6, note: "Managed at service level. No per-member keys — team shares a service-bound DOTENV_KEY." }, + "Operational overhead": { score: 7, note: "Service manages keys. Low local overhead, but you're dependent on their SLA." }, + }, + highlights: [ + "Zero setup friction for dotenv users", + "Web dashboard for visual secret management", + "Handles key rotation via service", + ], + limitations: [ + "Cloud dependency — build breaks if service is unavailable", + "Shared team key model — not per-member", + "Free tier limits may affect larger teams", + "Less suitable for air-gapped or strict compliance environments", + ], + }, + { + rank: 4, + tool: "git-crypt", + version: "0.7.x", + tagline: "Simple symmetric encryption for small repos, but limited for modern secret operations.", + scores: { + "Local-first security": { score: 7, note: "Local GnuPG-based symmetric encryption. No cloud dependency." }, + "Developer experience": { score: 5, note: "Transparent encryption via Git filters, but GPG setup is friction-heavy." }, + "Team access control": { score: 5, note: "GPG-based recipients. Re-encrypting the repository key for new members requires re-cloning." }, + "Operational overhead": { score: 5, note: "GPG ecosystem maintenance. No built-in rotation or audit tooling." }, + }, + highlights: [ + "Transparent — files look normal after checkout", + "No external services", + "Works with existing GPG key infrastructure", + ], + limitations: [ + "No per-file key rotation", + "Revoking access is not straightforward — requires re-keying the repo", + "GPG UX is notoriously painful", + "No built-in audit log, diff, or verify commands", + ], + }, +]; + +const ScoreBar = ({ score }: { score: number }) => ( +
+
+ {Array.from({ length: 10 }).map((_, i) => ( +
+ ))} +
+ {score}/10 +
+); + +export default function ToolRankingPost() { + return ( +
+ + +
+ +

+ This ranking evaluates four tools across four equally weighted criteria. Scores reflect practical use — not theoretical capability. A tool with powerful features that require two days of infrastructure setup scores lower on developer experience than a tool that works in two minutes. +

+
+
+ {criteria.map((c) => ( + +
+

{c.name}

+ {c.weight} +
+

{c.detail}

+
+ ))} +
+
+ +
+
+ {rankings.map((r) => ( + +
+ #{r.rank} +

{r.tool}

+ {r.version} + {r.rank === 1 && ( + + Editor's Pick + + )} +
+

{r.tagline}

+ +
+ {Object.entries(r.scores).map(([criterion, { score, note }]) => ( +
+
+

{criterion}

+
+ +

{note}

+
+ ))} +
+ +
+
+

Highlights

+
    + {r.highlights.map((h, i) => ( +
  • + + {h} +
  • + ))} +
+
+
+

Limitations

+
    + {r.limitations.map((l, i) => ( +
  • + {l} +
  • + ))} +
+
+
+
+ ))} +
+
+ +
+ +

+ If you are starting a new project or team in 2026 and want the best combination of security, simplicity, and zero operational overhead — xenvsync is the clear first choice. It handles solo workflows, team sharing, multi-environment, CI/CD, Docker, and audit trails from a single binary. +

+

+ sops remains the strongest option for teams with existing KMS infrastructure who need enterprise-grade recipient management. dotenv-vault wins on managed UX at the cost of cloud dependency. git-crypt has limited utility for modern workflows. +

+
+
+ + → Full tool comparison + + + → Migration playbook + +
+
+
+ ); +} diff --git a/website/src/app/blog/use-case-story/page.tsx b/website/src/app/blog/use-case-story/page.tsx new file mode 100644 index 0000000..0ef5825 --- /dev/null +++ b/website/src/app/blog/use-case-story/page.tsx @@ -0,0 +1,211 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Callout, Card, PageHeader, Section } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "How a 7-Person Startup Encrypted Their Secrets in One Sprint", + description: + "A real migration case study: how a 7-person startup moved from plaintext .env files shared over Slack to xenvsync encrypted vaults with per-member X25519 keys, working CI/CD, and a revocation workflow — in under a week.", + openGraph: { + title: "How a 7-Person Startup Encrypted Their Secrets in One Sprint", + description: "From Slack-shared .env files to encrypted team vaults in one sprint. A xenvsync migration case study.", + url: "https://xenvsync.softexforge.io/blog/use-case-story", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/blog/use-case-story" }, +}; + +export default function UseCaseStoryPost() { + return ( +
+ + +
+ +

+ Picture a typical seed-stage startup: seven engineers, three environments (dev, staging, production), and a secrets strategy that could generously be described as "ad hoc." The staging database password lived in a Slack DM from six months ago. The production API keys were in a Google Doc that three people had bookmarked. Two developers had slightly different local .env files and nobody was quite sure which one was correct. +

+

+ A contractor who had left two months earlier still theoretically had access to everything they had ever been messaged. Nobody had rotated the keys because nobody knew which keys they had. +

+

+ This is not unusual. Most early-stage teams prioritize shipping over secret hygiene, and that is a reasonable tradeoff — until it isn't. +

+
+
+ +
+ +

+ A senior engineer noticed that .env files were appearing in branches that contributors had pushed to forks. No secrets were exposed publicly — all the repos were private — but the close call was enough. The team decided to fix the problem properly before their Series A audit. +

+

+ Requirements they agreed on: +

+
    + {[ + "Secrets must be encrypted before they touch version control.", + "Each developer should use their own key — no shared team password.", + "CI/CD must work without passing secrets through environment variable UI.", + "When someone leaves, their access must be revocable in under five minutes.", + "The solution cannot require a cloud account or subscription.", + ].map((req, i) => ( +
  • + + {req} +
  • + ))} +
+
+
+ +
+ +

+ The lead engineer installed xenvsync via npm and ran the first encryption on the staging environment. Total time from reading the docs to a committed vault: eleven minutes. +

+
+ +{`# Install +$ npm install -g @nasimstg/xenvsync + +# Initialize key for the project +$ xenvsync init +# Generated .xenvsync.key +# Updated .gitignore: added .xenvsync.key, .env + +# Encrypt staging secrets +$ xenvsync push --env staging +# Encrypted .env.staging → .env.staging.vault + +$ git add .env.staging.vault +$ git commit -m "add encrypted staging vault"`} + + + The .xenvsync.key was shared with the rest of the team over a secure channel (1Password) as a temporary measure. The team planned to move to per-member X25519 keys on Day 3. + +
+ +
+ +

+ The team stored the staging key value in GitHub Actions Secrets and updated the pipeline to inject it at runtime. The plaintext environment variable blocks in the YAML were removed. +

+
+ +{`jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up xenvsync key + run: | + echo "\${{ secrets.XENVSYNC_STAGING_KEY }}" > .xenvsync.key + chmod 600 .xenvsync.key + + - name: Run tests with secrets + run: xenvsync run --env staging -- npm test + + - name: Verify vault integrity + run: xenvsync verify --env staging`} + + + One developer noted that removing the explicit env: block from the pipeline YAML was the most satisfying part of the day. The pipeline went from 14 environment variables to two: the key and the passphrase. + +
+ +
+ +

+ Each of the seven developers ran xenvsync keygen on their own machine and shared their public key with the lead. The lead added all seven to the team roster and re-pushed the vault. From that point on, everyone used their own identity to decrypt — the shared key from Day 1 was deleted and the locks changed. +

+
+ +{`# Each developer (once on their machine) +$ xenvsync keygen +$ xenvsync whoami +# Public key: ABC123... ← share this + +# Lead engineer builds roster +$ xenvsync team add alice +$ xenvsync team add bob +$ xenvsync team add carol +$ xenvsync team add dave +$ xenvsync team add eve +$ xenvsync team add frank +$ xenvsync team add grace + +# Re-push as V2 vault — each member gets their own encrypted slot +$ xenvsync push --env staging +$ xenvsync push --env production +$ git add .env.staging.vault .env.production.vault .xenvsync-team.json +$ git commit -m "migrate to V2 per-member vaults" + +# Immediately delete the shared symmetric key +$ rm .xenvsync.key`} + +
+ +
+ +

+ The team spent the second week rolling the same pattern out to their other three services. Each repo got its own vault. The shared V1 key for CI was replaced with the lead's own identity key injected from GitHub Secrets (since CI acts as a "robot team member" in their roster model). +

+

+ They also set up the pre-commit hook from examples/hooks/pre-commit to prevent plaintext .env files from being staged accidentally — the problem that had triggered the whole migration. +

+
+ +{`$ cp examples/hooks/pre-commit .git/hooks/pre-commit +$ chmod +x .git/hooks/pre-commit + +# Hook now blocks: +# - Staging of plaintext .env files +# - Commits when vault is older than .env`} + +
+ +
+ +

+ Three weeks after the migration, a contractor finished their engagement. The lead tested the revocation flow in a staging environment first: +

+
+ +{`# Revoke contractor access across all environments +$ xenvsync rotate --revoke contractor --env staging +$ xenvsync rotate --revoke contractor --env production + +# Confirm they are no longer in the roster +$ xenvsync team list + +# Push updated vaults +$ xenvsync push --env staging +$ xenvsync push --env production`} + + + Total time: four minutes and twenty seconds. The contractor's previous local identity files were worthless against the re-encrypted vaults. + +
+ +
+ +

+ The team ran the migration past their Series A security audit. The auditor flagged the use of AES-256-GCM with fresh nonces, Git-committed encrypted vaults, per-member key isolation, and a documented rotation workflow as positives. The pre-existing shared-key approach was noted as resolved. +

+

+ Developer feedback after three months was uniformly positive. The daily workflow — git pull, xenvsync pull, xenvsync run -- npm start — became muscle memory within a week. The pre-commit hook caught two accidental staging attempts in the first month, both from developers who had forgotten to push their vault after editing .env. +

+
+ + Want to run the same migration? See the{" "} + Migration Playbook + {" "}for a phased guide with rollback options. + +
+
+ ); +} diff --git a/website/src/app/consent/page.tsx b/website/src/app/consent/page.tsx new file mode 100644 index 0000000..9695762 --- /dev/null +++ b/website/src/app/consent/page.tsx @@ -0,0 +1,82 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Card, PageHeader, Section } from "@/components/DocsComponents"; +import { ConsentPreferencesManager } from "@/components/ConsentPreferencesManager"; +import Link from "next/link"; + +export const metadata = { + title: "Consent Preferences - xenvsync", + description: + "Consent and preference controls for xenvsync website storage, including essential-only and accept-all options.", + openGraph: { + title: "Consent Preferences - xenvsync", + description: "Understand what website preferences are stored and manage your choices.", + url: "https://xenvsync.softexforge.io/consent", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/consent" }, +}; + +const lastUpdated = "April 2, 2026"; + +export default function ConsentPage() { + return ( +
+ + +
+ +

xenvsync uses a minimal consent model focused on essential website behavior and preference memory.

+

There is no mandatory account login for documentation access, and no project-specific ad tracking requirement.

+

Your selected consent preference is stored locally so you are not repeatedly prompted.

+
+
+ +
+ + + + + + + + + + + + + + + + +
Storage keyPurposeData
xenvsync-consent-v1Stores your consent choicechoice and updatedAt timestamp
+
+
+ +
+ +
+ +
+ +{`localStorage.removeItem("xenvsync-consent-v1")`} + +
+ +
+ +

+ See Privacy for broader data handling notes. +

+

+ See Terms for website usage terms. +

+

+ Questions about consent can be sent via Contact. +

+
+
+
+ ); +} diff --git a/website/src/app/contact/page.tsx b/website/src/app/contact/page.tsx new file mode 100644 index 0000000..b84004e --- /dev/null +++ b/website/src/app/contact/page.tsx @@ -0,0 +1,109 @@ +import { Card, PageHeader, Section } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "Contact - xenvsync", + description: + "Contact and support page for xenvsync: bug reports, feature requests, security disclosures, sponsorship, and collaboration.", + openGraph: { + title: "Contact - xenvsync", + description: "Reach xenvsync maintainers through the right channel for support and security.", + url: "https://xenvsync.softexforge.io/contact", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/contact" }, +}; + +const supportChannels = [ + { + title: "Bug reports", + description: "Use GitHub Issues for reproducible bugs and regressions.", + href: "https://github.com/nasimstg/xenvsync/issues", + label: "Open an issue", + }, + { + title: "Feature ideas", + description: "Use GitHub Discussions for roadmap ideas and design discussion.", + href: "https://github.com/nasimstg/xenvsync/discussions", + label: "Start a discussion", + }, + { + title: "Security vulnerabilities", + description: "Report security issues privately through GitHub Security Advisories.", + href: "https://github.com/nasimstg/xenvsync/security/advisories/new", + label: "Report privately", + }, + { + title: "Partnerships and support", + description: "For business support, sponsorship, and collaboration requests.", + href: "mailto:contact@nasimstg.dev", + label: "contact@nasimstg.dev", + }, +]; + +export default function ContactPage() { + return ( +
+ + +
+
+ {supportChannels.map((channel) => ( + +

{channel.title}

+

+ {channel.description} +

+ + {channel.label} + +
+ ))} +
+
+ +
+ +

To speed up triage, include:

+

1. Exact command run and full error output.

+

2. OS, shell, and xenvsync version.

+

3. Output from doctor and verify when relevant.

+

4. Minimal reproduction steps.

+
+
+ +
+ +

+ Contribution process and expectations are documented in{" "} + + Contributing + + . +

+

+ Community behavior standards follow the Contributor Covenant in the project Code of Conduct. +

+

+ Legal usage terms are available on the{" "} + + Terms + + {" "}and{" "} + + License + + {" "}pages. +

+
+
+
+ ); +} diff --git a/website/src/app/docs/changelog/page.tsx b/website/src/app/docs/changelog/page.tsx new file mode 100644 index 0000000..c11517e --- /dev/null +++ b/website/src/app/docs/changelog/page.tsx @@ -0,0 +1,378 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Callout, Card, PageHeader, Section, StaggerItem, StaggerList } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "Changelog - xenvsync", + description: + "Full release history for xenvsync — security fixes, new commands, vault format changes, and upgrade guidance from v1.0.0 through v1.12.0.", + openGraph: { + title: "Changelog - xenvsync", + description: "Release history and upgrade guidance for xenvsync.", + url: "https://xenvsync.softexforge.io/docs/changelog", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/docs/changelog" }, +}; + +const releases = [ + { + version: "v1.12.0", + date: "2026-04-01", + tag: "Latest", + tagColor: "text-emerald-400", + sections: [ + { + label: "Security", + color: "text-red-400", + items: [ + "Fix shell injection in export --format=shell — switched from Go %q (allows $() expansion) to single-quote escaping", + "Fix path traversal via --env flag — validate environment names contain no slashes or ..", + "Fix V2 vault separator collision — data separator now searched after header, not globally", + ], + }, + { + label: "Fixed", + color: "text-yellow-400", + items: [ + "Fix rotate writing key before vault — vault is now written first so old key still works on failure", + "Fix doctor falsely failing on passphrase-protected keys (enc: prefix)", + "Fix pre-commit hook not blocking stale vaults (verify only warns on staleness)", + "Fix Docker/CI examples using XENVSYNC_KEY env var instead of key file mount", + "Fix GitLab CI template deleting key in before_script before script runs", + "Fix entrypoint.sh using unsafe eval on shell export — now uses xenvsync run", + "Fix migration guide deleting .env.vault before git add", + "Fix YAML export missing YAML 1.1 boolean quoting (yes, no, on, off)", + ], + }, + { + label: "Improved", + color: "text-blue-400", + items: [ + "Website: responsive prev/next navigation (stacks on mobile)", + "Website: sidebar FAB hidden when drawer is open", + "Website: single-column footer on small screens", + "Website: smoother hero text scaling across breakpoints", + "Website: prefers-reduced-motion support for all animations", + "Website: breadcrumb truncation on narrow screens", + "Website: roadmap updated to show all 12 phases complete", + ], + }, + ], + }, + { + version: "v1.11.0", + date: "2026-04-01", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "Migration guides from dotenv-vault, sops, and git-crypt (#18)", + "Feature comparison table on migration docs page", + "Migration page in sidebar navigation and search index", + ], + }, + ], + }, + { + version: "v1.10.0", + date: "2026-04-01", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "Homebrew tap: brew install nasimstg/tap/xenvsync (auto-published by GoReleaser) (#17)", + "Scoop bucket: scoop install xenvsync for Windows (auto-published by GoReleaser)", + "Nix flake: nix run github:nasimstg/xenvsync", + "AUR PKGBUILD for Arch Linux", + ], + }, + ], + }, + { + version: "v1.9.0", + date: "2026-04-01", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "xenvsync doctor command — audit local setup for security issues (permissions, gitignore, key strength, vault integrity, identity) (#14)", + "Passphrase protection for key files with init --passphrase and XENVSYNC_PASSPHRASE env var (scrypt + AES-256-GCM key-encryption-key)", + "Memory zeroing for key material after use (crypto.ZeroBytes)", + ], + }, + ], + }, + { + version: "v1.8.0", + date: "2026-04-01", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "xenvsync verify command — validate vault integrity with structural checks, GCM authentication, duplicate key detection, and stale vault warnings (#13)", + "Pre-commit hook script (examples/hooks/pre-commit) that blocks commits when vault is stale or .env is staged", + "--env flag support on verify for named environments", + "Duplicate key detection warns about repeated keys in .env files", + ], + }, + ], + }, + { + version: "v1.7.0", + date: "2026-04-01", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "xenvsync log command — display vault change history from Git commits with key-level diffs (#12)", + "--show-values flag on diff and log for explicit opt-in to display secret values", + "-n/--limit flag on log to control how many commits are shown (default 10)", + "Shared diff engine (diffutil.go) with computeKeyChanges and formatKeyChanges", + "decryptVaultBytes helper for decrypting vault data from non-file sources", + ], + }, + { + label: "Changed", + color: "text-orange-400", + items: [ + "diff now hides values by default — only key names and change types are shown", + "diff output includes a summary line with counts of added/modified/removed keys", + ], + }, + ], + }, + { + version: "v1.6.0", + date: "2026-03-30", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "xenvsync rotate command — rotate encryption key and re-encrypt the vault in one atomic step (#11)", + "V1 mode: generates new symmetric key and re-encrypts with it", + "V2 mode: re-encrypts for all current roster members with fresh ephemeral keys", + "--revoke flag to remove a team member and rotate in one step", + "--env flag support for rotating named environment vaults", + ], + }, + ], + }, + { + version: "v1.5.0", + date: "2026-03-30", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "Docker integration: Alpine Dockerfile, multi-stage app example, docker-compose, and entrypoint script (#8)", + "CI provider templates: GitHub Actions, GitLab CI, CircleCI, and Bitbucket Pipelines (#10)", + "All examples in examples/docker/ and examples/ci/", + ], + }, + ], + }, + { + version: "v1.4.0", + date: "2026-03-30", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "xenvsync team add/remove/list commands — manage team members' X25519 public keys (#5)", + "Team roster stored in .xenvsync-team.json (project-local, committed to repo)", + "V2 vault format with per-member key slots using X25519 ECDH (#6)", + "Each team member can decrypt vaults using their own private key", + "Automatic V2 encryption when a team roster exists", + "V1 vault backward compatibility — existing vaults remain readable", + "Shared decryptVault() helper auto-detects V1/V2 format across all commands", + ], + }, + ], + }, + { + version: "v1.3.0", + date: "2026-03-30", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "xenvsync keygen command — generate an X25519 keypair and store identity at ~/.xenvsync/identity (#4)", + "xenvsync whoami command — display your public key and identity path", + "--force flag on keygen to overwrite existing identity", + "internal/crypto package with X25519 key generation, encoding, and decoding", + "Cross-platform identity support (Linux, macOS, Windows)", + ], + }, + ], + }, + { + version: "v1.2.0", + date: "2026-03-30", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "Multi-environment support with --env flag on push, pull, run, diff, status, and export commands (#1)", + "xenvsync envs command — discover and list all environments with sync status (#3)", + "XENVSYNC_ENV environment variable as fallback for --env flag", + "Named environment file convention: .env. / .env..vault", + "Environment fallback merging: .env.shared < .env. < .env.local (#2)", + "--no-fallback flag on push to disable merging for strict isolation", + "npm README for package page on npmjs.com", + ], + }, + ], + }, + { + version: "v1.1.1", + date: "2026-03-30", + sections: [ + { + label: "Fixed", + color: "text-yellow-400", + items: [ + "npm postinstall failing with ENOENT — tar extraction used --strip-components=1 but GoReleaser archives have no wrapper directory", + ], + }, + ], + }, + { + version: "v1.1.0", + date: "2026-03-29", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "xenvsync export command — decrypt vault and output as JSON, YAML, shell, tfvars, or dotenv to stdout (#9)", + "xenvsync completion command — generate shell completions for bash, zsh, fish, and powershell (#15)", + ], + }, + ], + }, + { + version: "v1.0.0", + date: "2026-03-21", + tag: "Initial Release", + tagColor: "text-[var(--color-accent)]", + sections: [ + { + label: "Added", + color: "text-green-400", + items: [ + "AES-256-GCM encryption/decryption of .env files", + "xenvsync init — generate encryption key with 0600 permissions", + "xenvsync push — encrypt .env → .env.vault", + "xenvsync pull — decrypt .env.vault → .env", + "xenvsync run — inject secrets into child process (in-memory only)", + "xenvsync diff — preview changes between .env and vault", + "xenvsync status — show sync state of xenvsync files", + "Cross-platform builds via GoReleaser (Linux, macOS, Windows)", + "npm package wrapper for npm install -g @nasimstg/xenvsync", + "CI pipeline with test matrix, linting, and automated releases", + ], + }, + ], + }, +]; + +export default function ChangelogPage() { + return ( +
+ + +
+ +{`# 1. Check current version +$ xenvsync version + +# 2. Audit your local setup +$ xenvsync doctor + +# 3. Verify vault integrity +$ xenvsync verify + +# 4. Optionally rotate keys when revoking member access or after security events +$ xenvsync rotate`} + + + Always review security-tagged entries before upgrading production pipelines. Pay close attention to vault format or encryption changes. + +
+ + GitHub Releases + + + Installation Guide + + + Migration Guides + +
+
+ +
+ +
+ {releases.map((release) => ( + + +
+

{release.version}

+ {release.tag && ( + + {release.tag} + + )} + {release.date} +
+
+ {release.sections.map((section) => ( +
+

+ {section.label} +

+
    + {section.items.map((item, i) => ( +
  • + + {item} +
  • + ))} +
+
+ ))} +
+
+
+ ))} +
+
+
+
+ ); +} diff --git a/website/src/app/docs/ci-cd/page.tsx b/website/src/app/docs/ci-cd/page.tsx new file mode 100644 index 0000000..42e1a29 --- /dev/null +++ b/website/src/app/docs/ci-cd/page.tsx @@ -0,0 +1,124 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Callout, Card, PageHeader, Section } from "@/components/DocsComponents"; + +export const metadata = { + title: "CI/CD Recipes - xenvsync", + description: + "Production-ready xenvsync CI/CD recipes for GitHub Actions, GitLab CI, CircleCI, and Bitbucket Pipelines.", + openGraph: { + title: "CI/CD Recipes - xenvsync", + description: "Secure CI/CD patterns for decrypting and injecting secrets with xenvsync.", + url: "https://xenvsync.softexforge.io/docs/ci-cd", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/docs/ci-cd" }, +}; + +export default function CiCdPage() { + return ( +
+ + +
+ +

1. Store the key in your CI secret manager, never in the repository.

+

2. Install xenvsync in pipeline jobs.

+

3. Write the key at runtime with restricted permissions.

+

4. Run pull or run only in jobs that actually need secrets.

+
+ + Prefer environment-scoped vaults (for example, staging and production) to reduce blast radius. + +
+ +
+
+ +{`name: build +on: [push] + +jobs: + app: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: bash scripts/install-latest-xenvsync.sh + - run: | + echo "$XENVSYNC_KEY" > .xenvsync.key + chmod 600 .xenvsync.key + env: + XENVSYNC_KEY: \${{ secrets.XENVSYNC_KEY }} + - run: xenvsync pull --env staging + - run: npm ci && npm test`} + +
+
+ +
+
+ +{`build: + image: ubuntu:24.04 + script: + - bash scripts/install-latest-xenvsync.sh + - echo "$XENVSYNC_KEY" > .xenvsync.key + - chmod 600 .xenvsync.key + - xenvsync pull --env staging + - npm ci + - npm test`} + +
+
+ +
+
+ +{`jobs: + build: + docker: + - image: cimg/base:stable + steps: + - checkout + - run: bash scripts/install-latest-xenvsync.sh + - run: | + echo "$XENVSYNC_KEY" > .xenvsync.key + chmod 600 .xenvsync.key + - run: xenvsync pull --env staging + - run: npm ci && npm test`} + +
+
+ +
+
+ +{`pipelines: + default: + - step: + image: atlassian/default-image:4 + script: + - bash scripts/install-latest-xenvsync.sh + - echo "$XENVSYNC_KEY" > .xenvsync.key + - chmod 600 .xenvsync.key + - xenvsync pull --env staging + - npm ci + - npm test`} + +
+
+ +
+ +

+ Use xenvsync pull when downstream tools require an actual .env file. +

+

+ Use xenvsync run -- your-command when you want in-memory injection and no plaintext file output. +

+
+
+
+ ); +} diff --git a/website/src/app/docs/contributing/page.tsx b/website/src/app/docs/contributing/page.tsx new file mode 100644 index 0000000..703f234 --- /dev/null +++ b/website/src/app/docs/contributing/page.tsx @@ -0,0 +1,242 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Callout, Card, PageHeader, Section, StaggerItem, StaggerList } from "@/components/DocsComponents"; + +export const metadata = { + title: "Contributing - xenvsync", + description: + "How to contribute to xenvsync: development setup with Go 1.25+, branch workflow, quality checks, commit conventions, issue reporting, and security disclosure guidelines.", + openGraph: { + title: "Contributing - xenvsync", + description: "Contribute code, docs, and feedback to xenvsync.", + url: "https://xenvsync.softexforge.io/docs/contributing", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/docs/contributing" }, +}; + +const prChecklist = [ + "Add or update tests for any new or changed behavior.", + "Ensure all tests pass with race detection enabled.", + "Run the local CI-equivalent checks script before pushing.", + "Keep docs aligned with user-visible changes (README, INSTALL, ARCHITECTURE).", + "Include migration notes for breaking changes.", + "Use focused, atomic commits with imperative mood messages.", + "Reference the relevant issue in your PR description.", +]; + +const codeStyleRules = [ + { rule: "gofmt", detail: "All code must be formatted with gofmt. CI enforces this." }, + { rule: "go vet", detail: "No vet warnings allowed. Run go vet ./... before pushing." }, + { rule: "golangci-lint", detail: "Optionally install golangci-lint for broader static analysis." }, + { rule: "govulncheck", detail: "Scan for known vulnerabilities with govulncheck ./..." }, + { rule: "Small functions", detail: "Keep functions focused and well-named. Avoid long multi-purpose functions." }, + { rule: "Comments", detail: "Only add comments where the logic is not self-evident. Avoid redundant comments." }, +]; + +const highRiskAreas = [ + { area: "run command", note: "Child process exit code propagation and signal forwarding (SIGINT/SIGTERM) must work correctly on Linux, macOS, and Windows." }, + { area: "rotate command", note: "Vault is written before key to ensure rollback safety. Do not change this ordering." }, + { area: "Fallback merge precedence", note: ".env.shared < .env. < .env.local. Ensure layer order is preserved." }, + { area: "V1/V2 vault detection", note: "decryptVault() auto-detects format. Changes must not break backward compatibility with V1 vaults." }, + { area: "Key permissions", note: "Key files must be written with mode 0600. Identity files must also be 0600." }, + { area: "Memory zeroing", note: "Key material must be zeroed after use via crypto.ZeroBytes." }, +]; + +export default function ContributingPage() { + return ( +
+ + + + Before writing code, review the{" "} + Development Guide + {" "}and{" "} + Architecture docs + {" "}to understand the codebase structure and design decisions. + + +
+ +

+ Prerequisites: Go 1.25+, Git. Optionally install golangci-lint and govulncheck for richer local checks. +

+
+ +{`# 1. Fork and clone +$ git clone https://github.com/nasimstg/xenvsync.git +$ cd xenvsync + +# 2. Download dependencies +$ go mod download + +# 3. Run tests with race detection +$ go test -race ./... + +# 4. (Recommended) Install quality tools +$ go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +$ go install golang.org/x/vuln/cmd/govulncheck@latest`} + +
+ +
+ +{`# Create a feature branch from main +$ git checkout -b feature/my-change + +# Make changes, then run checks +$ go test -race ./... +$ go vet ./... + +# Run local CI-equivalent before pushing +$ ./scripts/ci-check.sh + +# Open a pull request against main`} + + + All changes must be against the main branch. Keep commits focused and atomic — one logical change per commit. + +
+ +
+ + {prChecklist.map((item, i) => ( +
+ {i + 1}. + {item} +
+ ))} +
+
+ +
+ +
+ {codeStyleRules.map((r) => ( + + + + {r.rule} + +

{r.detail}

+
+
+ ))} +
+
+
+ +
+ +

+ Use the imperative mood in the first line. Keep it under 72 characters. Reference issues when applicable. +

+
+ +{`# Good — imperative mood, concise, references issue +Add --revoke flag to rotate command (#11) +Fix path traversal via --env flag +Update YAML export to quote YAML 1.1 booleans + +# Bad — past tense or vague +Added rotation support +Fixed stuff +Update`} + +
+ +
+ +

+ xenvsync uses table-driven tests. New functionality must include tests covering normal paths, malformed input, and negative/error paths. +

+

+ Command tests in cmd/*_test.go use temporary directories and invoke Cobra directly. Do not depend on global state. +

+
+ +{`# Full suite with race detection (required before PR) +$ go test -race ./... + +# With coverage report +$ go test -race -coverprofile=coverage.out ./... +$ go tool cover -html=coverage.out + +# Run a specific package +$ go test -race ./internal/crypto/... +$ go test -race ./cmd/...`} + +
+ +
+

+ These areas require extra care and thorough testing before any changes: +

+ +
+ {highRiskAreas.map((h) => ( + + +

{h.area}

+

{h.note}

+
+
+ ))} +
+
+
+ +
+ +

When making user-visible behavior changes, update the following files alongside code:

+
    + {["README.md — feature overview and command reference", "CONTRIBUTING.md — if contributing workflow changes", "docs/INSTALL.md — if installation steps change", "docs/ARCHITECTURE.md — if design boundaries shift", "CHANGELOG.md — add entry under the next version"].map((doc, i) => ( +
  • + + {doc} +
  • + ))} +
+
+
+ +
+ +

Use GitHub Issues to report bugs or request features.

+

Include:

+
    + {["Steps to reproduce the issue", "Expected vs. actual behavior", "Your OS and Go version (xenvsync version output)", "Relevant error output"].map((item, i) => ( +
  • + + {item} +
  • + ))} +
+
+
+ +
+ + Do not open a public issue for security vulnerabilities. Report them privately via{" "} + + GitHub Security Advisories + + . This ensures responsible disclosure before a fix is released. + +
+ +
+ + By contributing, you agree that your contributions will be licensed under the{" "} + MIT License. + +
+
+ ); +} diff --git a/website/src/app/docs/faq/page.tsx b/website/src/app/docs/faq/page.tsx new file mode 100644 index 0000000..a06b4ee --- /dev/null +++ b/website/src/app/docs/faq/page.tsx @@ -0,0 +1,239 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Card, PageHeader, Section, StaggerItem, StaggerList } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "FAQ - xenvsync", + description: + "Frequently asked questions about xenvsync: security model, Git safety, team sharing, key rotation, CI/CD usage, multi-environment, passphrase protection, migration, and vault formats.", + openGraph: { + title: "FAQ - xenvsync", + description: "Common xenvsync setup, security, team, and workflow questions answered.", + url: "https://xenvsync.softexforge.io/docs/faq", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/docs/faq" }, +}; + +const faqs: { category: string; items: { q: string; a: string; code?: string; codeTitle?: string }[] }[] = [ + { + category: "Security & Encryption", + items: [ + { + q: "What encryption does xenvsync use?", + a: "xenvsync uses AES-256-GCM for authenticated encryption. Each push operation generates a fresh 12-byte random nonce, so identical plaintext produces different ciphertext on every encryption. The 16-byte GCM authentication tag protects against tampering — if the vault is modified, decryption will fail with an authentication error.", + }, + { + q: "Is .env.vault safe to commit to Git?", + a: "Yes. The vault is the encrypted artifact and is intentionally designed for version control. The symmetric key (.xenvsync.key) or your private identity (~/.xenvsync/identity) must never be committed. xenvsync init automatically adds both .xenvsync.key and .env to .gitignore.", + }, + { + q: "Can xenvsync detect vault tampering?", + a: "Yes. AES-256-GCM includes an authentication tag that covers both the ciphertext and the nonce. Any modification to the vault — even a single bit — will cause decryption to fail. Use xenvsync verify to explicitly check vault integrity without modifying any files.", + }, + { + q: "What does passphrase protection do?", + a: "With xenvsync init --passphrase, the symmetric key is itself encrypted using a scrypt-derived key (N=32768, r=8, p=1) plus AES-256-GCM. This means even if .xenvsync.key is obtained, it cannot be used without the passphrase. Set XENVSYNC_PASSPHRASE in your environment before running xenvsync commands.", + code: `$ xenvsync init --passphrase +$ export XENVSYNC_PASSPHRASE="your-passphrase" +$ xenvsync push`, + codeTitle: "Passphrase-protected init", + }, + { + q: "Does xenvsync store anything in the cloud?", + a: "No. xenvsync is completely local-first. The encrypted vault lives in your Git repository. Your decryption key lives on your local filesystem. There is no external service, API call, or network dependency at any point in the encrypt/decrypt/run workflow.", + }, + ], + }, + { + category: "Team Sharing", + items: [ + { + q: "How does team access work without sharing one global key?", + a: "xenvsync V2 vaults use X25519 asymmetric cryptography. Each team member generates their own keypair with xenvsync keygen. When you push with a team roster in place, xenvsync creates a per-member encrypted key slot inside the vault using the member's public key. Each member decrypts their slot with their own private key — no global shared secret is ever distributed.", + code: `# Each member runs once on their machine +$ xenvsync keygen +$ xenvsync whoami # prints public key to share + +# Maintainer adds members and pushes +$ xenvsync team add alice +$ xenvsync team add bob +$ xenvsync push`, + codeTitle: "Team onboarding", + }, + { + q: "What happens when a team member leaves?", + a: "Remove them from the roster and rotate immediately. The rotate command re-encrypts the vault excluding the revoked member. Any vault encrypted before rotation is accessible to the removed member — rotation ensures future vaults are not.", + code: `# Revoke and rotate in one step +$ xenvsync rotate --revoke former-member +$ xenvsync push + +# Confirm new roster +$ xenvsync team list`, + codeTitle: "Revoking access", + }, + { + q: "Where is the team roster stored?", + a: "The roster is stored in .xenvsync-team.json in the project root. It is safe to commit to the repository — it contains only member names and their public keys, not any secret material. All team members should commit this file so everyone works with the same roster.", + }, + { + q: "Can I use xenvsync without a team roster?", + a: "Yes. Without .xenvsync-team.json, xenvsync uses V1 format: a single symmetric key encrypts the vault. This is ideal for solo developers or projects where secret management is handled by one person. V1 and V2 vaults are automatically detected on pull.", + }, + ], + }, + { + category: "Multi-Environment", + items: [ + { + q: "How do I use different secrets per environment?", + a: "Use the --env flag to name your environment. xenvsync creates .env. and .env..vault files. You can have as many environments as needed — staging, production, ci, preview, etc.", + code: `$ xenvsync push --env staging +$ xenvsync push --env production +$ xenvsync pull --env staging +$ xenvsync run --env production -- node server.js`, + codeTitle: "Named environments", + }, + { + q: "What is environment fallback merging?", + a: "When you push with --env , xenvsync merges three layers in order: .env.shared (base values for all environments) → .env. (environment-specific overrides) → .env.local (local machine overrides, never committed). Use --no-fallback to disable this and encrypt only the named file.", + }, + { + q: "How do I discover which environments exist in a project?", + a: "Run xenvsync envs. It scans for all .env.* and .env.*.vault files and shows their sync status — which have been pushed, which are stale, and which are missing vaults.", + code: `$ xenvsync envs`, + codeTitle: "List all environments", + }, + ], + }, + { + category: "CI/CD", + items: [ + { + q: "How do I use xenvsync in CI without committing the key?", + a: "Store the key value in your CI provider's secrets store. At runtime, write it to .xenvsync.key with 0600 permissions before running pull or run. The vault file is already in the repository.", + code: `# GitHub Actions example +- name: Inject xenvsync key + run: | + echo "\${{ secrets.XENVSYNC_KEY }}" > .xenvsync.key + chmod 600 .xenvsync.key + +- name: Run with secrets + run: xenvsync run -- npm run build`, + codeTitle: "GitHub Actions", + }, + { + q: "Should I use xenvsync pull or xenvsync run in CI?", + a: "Prefer xenvsync run -- when possible. It injects secrets directly into the child process environment without writing plaintext to disk. Use xenvsync pull only when you need the .env file to exist on disk for tools that require it.", + }, + { + q: "How do I validate vault integrity as a CI quality gate?", + a: "Add xenvsync verify and xenvsync doctor as steps before your build. verify checks structural and cryptographic integrity; doctor audits permissions, gitignore state, and key strength.", + code: `$ xenvsync doctor +$ xenvsync verify +$ xenvsync run -- npm test`, + codeTitle: "CI quality gate", + }, + ], + }, + { + category: "Key Rotation & Audit", + items: [ + { + q: "How does key rotation work?", + a: "xenvsync rotate decrypts the current vault, generates new key material, and re-encrypts in one atomic step. For V1 (single-key) mode, a new symmetric key is generated and written. For V2 (team) mode, fresh ephemeral X25519 keys are used for all current roster members. The vault is written before the key to ensure rollback safety.", + }, + { + q: "How do I see what changed in the vault over time?", + a: "Use xenvsync log to view vault change history from Git commits. It shows key-level diffs (added/modified/removed key names) across the last N commits, without exposing values by default.", + code: `# Show last 10 commits that touched the vault +$ xenvsync log + +# Show more commits +$ xenvsync log -n 25 + +# Opt in to showing values (use with care) +$ xenvsync log --show-values`, + codeTitle: "Vault audit log", + }, + ], + }, + { + category: "Installation & Compatibility", + items: [ + { + q: "What operating systems does xenvsync support?", + a: "xenvsync supports Linux (x86_64, arm64), macOS (x86_64, arm64), and Windows (x86_64, arm64). It is distributed as a single static binary with no runtime dependencies.", + }, + { + q: "How do I install xenvsync?", + a: "xenvsync is available via npm, Homebrew, Scoop (Windows), AUR (Arch Linux), Nix flake, go install, and direct binary download. See the installation guide for all options.", + code: `# npm (works on all platforms) +$ npm install -g @nasimstg/xenvsync + +# Homebrew (macOS / Linux) +$ brew install nasimstg/tap/xenvsync + +# Scoop (Windows) +$ scoop install xenvsync + +# Go +$ go install github.com/nasimstg/xenvsync@latest`, + codeTitle: "Install options", + }, + { + q: "Are V1 and V2 vaults compatible?", + a: "Yes. xenvsync automatically detects vault format on pull, run, diff, verify, and other read commands. V1 vaults remain readable after upgrading. V2 is used automatically when a .xenvsync-team.json roster exists.", + }, + ], + }, +]; + +export default function FaqPage() { + return ( +
+ + + {faqs.map((group) => ( +
+ +
+ {group.items.map((item) => ( + + +

{item.q}

+

{item.a}

+ {item.code && ( + + {item.code} + + )} +
+
+ ))} +
+
+
+ ))} + +
+
+ + Troubleshooting Guide + + + Command Reference + + + Security Model + + + GitHub Discussions + +
+
+
+ ); +} diff --git a/website/src/app/docs/getting-started/page.tsx b/website/src/app/docs/getting-started/page.tsx index 0b63577..dba9ea3 100644 --- a/website/src/app/docs/getting-started/page.tsx +++ b/website/src/app/docs/getting-started/page.tsx @@ -27,10 +27,10 @@ export default function GettingStarted() {
  • Homebrew,{" "} npm,{" "} - Go 1.22+, or a{" "} - + Go 1.25+, or a{" "} + prebuilt binary - +
  • diff --git a/website/src/app/docs/installation/page.tsx b/website/src/app/docs/installation/page.tsx index 4f96f8e..554d714 100644 --- a/website/src/app/docs/installation/page.tsx +++ b/website/src/app/docs/installation/page.tsx @@ -1,5 +1,6 @@ import { CodeBlock } from "@/components/CodeBlock"; -import { Section, PageHeader, Card } from "@/components/DocsComponents"; +import { Section, PageHeader } from "@/components/DocsComponents"; +import Link from "next/link"; export const metadata = { title: "Installation - xenvsync", @@ -46,7 +47,7 @@ $ scoop install xenvsync`}
    -

    Requires Go 1.22+.

    +

    Requires Go 1.25+.

    {`$ go install github.com/nasimstg/xenvsync@latest`} @@ -57,10 +58,10 @@ $ scoop install xenvsync`}

    - Prebuilt binaries are available on{" "} - - GitHub Releases - + Prebuilt binaries are available from the release channels listed in{" "} + + Changelog + .

    diff --git a/website/src/app/docs/integrations/page.tsx b/website/src/app/docs/integrations/page.tsx new file mode 100644 index 0000000..bef8d63 --- /dev/null +++ b/website/src/app/docs/integrations/page.tsx @@ -0,0 +1,5 @@ +import { permanentRedirect } from "next/navigation"; + +export default function DocsIntegrations() { + permanentRedirect("/integrations"); +} diff --git a/website/src/app/docs/roadmap/page.tsx b/website/src/app/docs/roadmap/page.tsx index 6b500fe..4491a96 100644 --- a/website/src/app/docs/roadmap/page.tsx +++ b/website/src/app/docs/roadmap/page.tsx @@ -1,7 +1,7 @@ -import { redirect } from "next/navigation"; +import { permanentRedirect } from "next/navigation"; export const metadata = { title: "Roadmap - xenvsync" }; export default function DocsRoadmap() { - redirect("/roadmap"); + permanentRedirect("/roadmap"); } diff --git a/website/src/app/docs/troubleshooting/page.tsx b/website/src/app/docs/troubleshooting/page.tsx new file mode 100644 index 0000000..fb25209 --- /dev/null +++ b/website/src/app/docs/troubleshooting/page.tsx @@ -0,0 +1,338 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Callout, Card, PageHeader, Section, StaggerItem, StaggerList } from "@/components/DocsComponents"; + +export const metadata = { + title: "Troubleshooting - xenvsync", + description: + "Comprehensive xenvsync troubleshooting guide covering decrypt failures, key permission errors, passphrase issues, stale vaults, team access problems, CI injection failures, export errors, and doctor/verify diagnostics.", + openGraph: { + title: "Troubleshooting - xenvsync", + description: "Fix common xenvsync setup, decrypt, team, and CI issues quickly.", + url: "https://xenvsync.softexforge.io/docs/troubleshooting", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/docs/troubleshooting" }, +}; + +const issues = [ + { + category: "Decryption Failures", + items: [ + { + title: "authentication failed — vault decryption error", + cause: "The key used to decrypt does not match the key that encrypted the vault. This happens when the wrong .xenvsync.key is present, the vault came from a different project, or the vault was tampered with.", + fix: `# Run the full diagnostic +$ xenvsync doctor +$ xenvsync verify + +# Check which key file is present +$ ls -la .xenvsync.key + +# Compare key fingerprint against team roster if using V2 +$ xenvsync whoami + +# If key is wrong, obtain the correct key from the vault owner +# then re-run: +$ xenvsync pull`, + }, + { + title: "vault file not found / no such file .env.vault", + cause: "The vault has not been created yet, or you are running xenvsync from the wrong directory.", + fix: `# Verify you are in the repo root +$ ls .env.vault + +# If missing, create the vault by encrypting your .env +$ xenvsync push + +# For named environments, specify --env +$ xenvsync push --env staging`, + }, + { + title: "no key file found — .xenvsync.key missing", + cause: "The symmetric key file is absent. It was never initialized, was gitignored and not restored, or was deleted.", + fix: `# Initialize a new key (only if starting fresh — this invalidates the old vault) +$ xenvsync init + +# If vault already exists, obtain the key from the person who created it +# Place it at: +$ ls .xenvsync.key +# Set correct permissions +$ chmod 600 .xenvsync.key`, + }, + { + title: "passphrase required — enc: prefix detected", + cause: "The key file was created with --passphrase and requires XENVSYNC_PASSPHRASE to be set before xenvsync can use it.", + fix: `# Export the passphrase before running any xenvsync command +$ export XENVSYNC_PASSPHRASE="your-passphrase" +$ xenvsync pull + +# In CI, inject via secrets manager: +# GitHub Actions example +# env: +# XENVSYNC_PASSPHRASE: \${{ secrets.XENVSYNC_PASSPHRASE }}`, + }, + ], + }, + { + category: "Key & Permission Errors", + items: [ + { + title: "key file readable by others — permission warning", + cause: "xenvsync expects .xenvsync.key and ~/.xenvsync/identity to be owner-only (mode 0600). Broader permissions are a security risk.", + fix: `# Fix key file permissions (Linux / macOS) +$ chmod 600 .xenvsync.key + +# Fix identity file permissions +$ chmod 600 ~/.xenvsync/identity + +# Confirm with doctor +$ xenvsync doctor`, + }, + { + title: "key not in .gitignore — doctor warning", + cause: "The .xenvsync.key file is not listed in .gitignore. Committing it would expose the decryption key.", + fix: `# Add to .gitignore manually +$ echo ".xenvsync.key" >> .gitignore +$ echo ".env" >> .gitignore +$ git add .gitignore + +# Or re-run init which auto-updates .gitignore +$ xenvsync init --force`, + }, + { + title: "key strength warning — all-zeros or weak key", + cause: "doctor detected a key that is all zeros or appears too weak to provide security.", + fix: `# Generate a fresh strong key +$ xenvsync init --force + +# Then re-encrypt the vault with the new key +$ xenvsync push`, + }, + ], + }, + { + category: "Stale Vault & Sync Issues", + items: [ + { + title: ".env is newer than .env.vault — stale vault warning", + cause: "The plaintext .env was edited after the last push. The vault does not reflect current secrets.", + fix: `# Preview what changed +$ xenvsync diff + +# Re-encrypt with current .env contents +$ xenvsync push + +# Verify the updated vault +$ xenvsync verify`, + }, + { + title: "diff shows unexpected changes", + cause: "A diff that shows changes you did not make may indicate that another team member pushed changes, or that the vault was rotated.", + fix: `# Show full diff (key names only by default — no values) +$ xenvsync diff + +# To see values explicitly (use with care) +$ xenvsync diff --show-values + +# Check vault Git history for recent changes +$ xenvsync log`, + }, + ], + }, + { + category: "Team & V2 Vault Access", + items: [ + { + title: "no identity file found — team member cannot decrypt", + cause: "The member has not generated their X25519 identity, or the identity file was deleted.", + fix: `# Generate identity (run on the member's machine) +$ xenvsync keygen + +# Print public key to share with vault maintainer +$ xenvsync whoami`, + }, + { + title: "recipient not in roster — V2 decryption failed", + cause: "The member's public key was not included in the roster when the vault was last encrypted. Either they were never added, or rotation was performed without them.", + fix: `# Maintainer: add the member and rotate +$ xenvsync team add alice +$ xenvsync rotate +$ xenvsync push + +# Member: pull after maintainer rotates +$ xenvsync pull`, + }, + { + title: "member removed but can still decrypt old vault", + cause: "Removing from roster does not retroactively re-encrypt. Rotation is required to exclude the revoked identity.", + fix: `# Revoke access and rotate in one step +$ xenvsync rotate --revoke former-member + +# Confirm roster +$ xenvsync team list`, + }, + { + title: ".xenvsync-team.json not committed", + cause: "The team roster file is needed by all members. It must be committed to the repository.", + fix: `$ git add .xenvsync-team.json +$ git commit -m "Add team roster"`, + }, + ], + }, + { + category: "CI/CD Injection Failures", + items: [ + { + title: "xenvsync pull fails in CI — key not available", + cause: "The key was not injected into the CI environment. CI jobs do not have the .xenvsync.key file by default.", + fix: `# Inject key from CI secret into file +# GitHub Actions example: +# - run: echo "\${{ secrets.XENVSYNC_KEY }}" > .xenvsync.key && chmod 600 .xenvsync.key + +# For passphrase-protected keys, also set: +# env: +# XENVSYNC_PASSPHRASE: \${{ secrets.XENVSYNC_PASSPHRASE }} + +# Then pull or run as normal +$ xenvsync pull +$ xenvsync run -- npm run build`, + }, + { + title: "run command exits with unexpected code", + cause: "xenvsync run preserves child exit codes. A non-zero exit comes from the child process, not xenvsync itself.", + fix: `# Debug by running the child process directly after pulling +$ xenvsync pull +$ npm run build # run child independently to see its error + +# Alternatively, inspect the child environment +$ xenvsync run -- env | grep YOUR_VAR`, + }, + ], + }, + { + category: "Export Errors", + items: [ + { + title: "export --format=shell produces invalid output", + cause: "Values are single-quote escaped to prevent shell expansion. If a value itself contains a single quote, the escaping handles it. This is the secure behavior.", + fix: `# Verify export output looks correct +$ xenvsync export --format=shell + +# Supported formats: dotenv, json, yaml, shell, tfvars +$ xenvsync export --format=json +$ xenvsync export --format=yaml`, + }, + { + title: "YAML export has unquoted boolean-like values", + cause: "YAML 1.1 treats yes/no/on/off as booleans. xenvsync v1.12.0+ quotes these automatically.", + fix: `# Upgrade to v1.12.0 or later +$ xenvsync version + +# Re-export after upgrading +$ xenvsync export --format=yaml`, + }, + ], + }, + { + category: "Multi-Environment Issues", + items: [ + { + title: "wrong environment loaded — unexpected variables", + cause: "Fallback merging layers .env.shared → .env. → .env.local. A variable from .env.shared may be overriding your expectation.", + fix: `# Disable fallback to use only the named env file +$ xenvsync push --no-fallback --env production +$ xenvsync pull --no-fallback --env production + +# Discover all environments in the project +$ xenvsync envs + +# Check status of a specific env +$ xenvsync status --env staging`, + }, + { + title: "invalid environment name — path traversal error", + cause: "Environment names must not contain slashes or .. characters. This is a security check.", + fix: `# Valid environment names +$ xenvsync push --env staging +$ xenvsync push --env prod +$ xenvsync push --env feature-auth + +# Invalid (will error) +# xenvsync push --env ../etc +# xenvsync push --env prod/v2`, + }, + ], + }, +]; + +export default function TroubleshootingPage() { + return ( +
    + + +
    + +{`# Check version and environment +$ xenvsync version + +# Show sync state of all xenvsync files +$ xenvsync status + +# Full security audit (permissions, gitignore, key strength, vault integrity) +$ xenvsync doctor + +# Structural + cryptographic vault validation +$ xenvsync verify`} + + + Always run diagnostics from the repository root that contains your target vault and key files. Running from a subdirectory is a common source of "file not found" errors. + +
    + + {issues.map((group) => ( +
    + +
    + {group.items.map((issue) => ( + + +

    + {issue.title} +

    +

    + Cause: {issue.cause} +

    + + {issue.fix} + +
    +
    + ))} +
    +
    +
    + ))} + +
    + +

    If none of the above resolves your issue, collect diagnostic output and open a GitHub issue:

    + +{`$ xenvsync version +$ xenvsync doctor +$ xenvsync verify +$ xenvsync status`} + +

    + File a bug at{" "} + + github.com/nasimstg/xenvsync/issues + + {" "}with the output from the commands above and your OS / Go version. +

    +
    +
    +
    + ); +} diff --git a/website/src/app/docs/use-cases/page.tsx b/website/src/app/docs/use-cases/page.tsx new file mode 100644 index 0000000..2c3c165 --- /dev/null +++ b/website/src/app/docs/use-cases/page.tsx @@ -0,0 +1,218 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Callout, Card, PageHeader, Section, StaggerItem, StaggerList } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "Use Cases - xenvsync Docs", + description: + "How xenvsync fits into real workflows: solo developer local setup, startup team secret sharing, enterprise CI/CD pipelines, open-source maintainer credential isolation, and Docker/container workflows.", + openGraph: { + title: "Use Cases - xenvsync", + description: "Real-world xenvsync workflows by team size and delivery model.", + url: "https://xenvsync.softexforge.io/docs/use-cases", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/docs/use-cases" }, +}; + +export default function DocsUseCasesPage() { + return ( +
    + + +
    + +

    Goal: Keep secrets out of Git history without cloud tooling or complicated setup.

    +

    A solo developer initializes a key once, encrypts their .env, and uses xenvsync run to start their app without plaintext ever hitting disk again. The vault is committed to Git — the key stays local.

    +
    + +{`# One-time setup +$ xenvsync init + +# Encrypt your .env into a vault +$ xenvsync push +$ git add .env.vault && git commit -m "add encrypted vault" + +# Start the app with secrets injected in-memory +$ xenvsync run -- npm start + +# On a new machine: restore secrets from vault +$ xenvsync pull`} + +
    + +
    + +

    Goal: Share staging and production secrets across the team without a central vault service or a single shared password.

    +

    Each developer generates their own X25519 keypair. The maintainer adds public keys to the roster and pushes a V2 vault where each member has their own encrypted key slot. No one shares a symmetric key — each person decrypts with their private identity.

    +
    + +{`# Each team member runs once on their machine +$ xenvsync keygen +$ xenvsync whoami # copy this public key and send to maintainer + +# Maintainer: build the roster +$ xenvsync team add alice +$ xenvsync team add bob +$ xenvsync team list + +# Push — automatically uses V2 multi-key format +$ xenvsync push --env staging +$ xenvsync push --env production +$ git add .env.staging.vault .env.production.vault .xenvsync-team.json +$ git commit -m "update encrypted vaults" + +# Any team member can now pull +$ xenvsync pull --env staging`} + + + When a team member leaves, run xenvsync rotate --revoke <name> immediately to exclude them from future vault encryption. + +
    + +
    + +

    Goal: Inject secrets into CI jobs without storing plaintext in environment variables, pipeline YAML, or build logs.

    +

    Store only the raw key value as a CI secret. At runtime, write it to .xenvsync.key with correct permissions, then use xenvsync run or xenvsync pull before the build step.

    +
    + +{`jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Inject xenvsync key + run: | + echo "\${{ secrets.XENVSYNC_KEY }}" > .xenvsync.key + chmod 600 .xenvsync.key + + - name: Build with secrets + run: xenvsync run -- npm run build + + - name: Audit vault integrity + run: | + xenvsync doctor + xenvsync verify`} + + +{`build: + script: + - echo "$XENVSYNC_KEY" > .xenvsync.key && chmod 600 .xenvsync.key + - xenvsync run -- npm run build`} + +
    + +
    + +

    Goal: Standardize secret handling across many services with audit trails, automatic permission checks, and access revocation workflows.

    +

    Platform teams run xenvsync doctor and xenvsync verify as CI gates. The log command provides a Git-native audit trail of every vault change with key-level diffs. Rotation after offboarding is enforced as a policy.

    +
    + +{`# Add vault health checks to all pipelines +$ xenvsync doctor # permissions, gitignore, key strength +$ xenvsync verify # structural + GCM integrity + staleness + +# Review vault change history +$ xenvsync log --env production +$ xenvsync log -n 50 --env staging + +# Diff current .env against encrypted vault +$ xenvsync diff --env production + +# Revoke departing team member and rotate +$ xenvsync rotate --revoke former-member --env production +$ xenvsync rotate --revoke former-member --env staging +$ xenvsync push --env production +$ xenvsync push --env staging`} + +
    + +
    + +

    Goal: Keep release signing keys, npm tokens, and deployment credentials encrypted in the repo without exposing them to contributors or forks.

    +

    Use a named ci environment for release credentials that contributors never see. The release pipeline injects the key at runtime. Contributor builds use a different environment with limited secrets.

    +
    + +{`# Separate environments for contributors vs. releases +$ xenvsync push --env dev # general dev secrets, shared with team +$ xenvsync push --env ci # release credentials, maintainer only + +# CI release job +$ echo "$RELEASE_XENVSYNC_KEY" > .xenvsync.key && chmod 600 .xenvsync.key +$ xenvsync run --env ci -- make release + +# Contributors run normally +$ xenvsync pull --env dev +$ xenvsync run --env dev -- make test`} + +
    + +
    + +

    Goal: Inject secrets into containers at startup without baking them into the image or using cloud secret managers.

    +

    Mount the key as a Docker secret or environment variable. Use xenvsync run as the container entrypoint so secrets are injected into the app process in-memory and never written to the container filesystem.

    +
    + +{`# Build image without secrets +$ docker build -t myapp . + +# Run with key mounted +$ docker run \ + -v \$(pwd)/.xenvsync.key:/app/.xenvsync.key:ro \ + -v \$(pwd)/.env.vault:/app/.env.vault:ro \ + myapp \ + xenvsync run -- node server.js + +# Or inject key via environment + passphrase +$ docker run \ + -e XENVSYNC_KEY="\$(cat .xenvsync.key)" \ + -e XENVSYNC_PASSPHRASE="secret" \ + myapp`} + +
    + +
    + +
    + {[ + "You want encrypted vaults in Git with no always-on cloud dependency.", + "Your team needs per-member key access without sharing one global secret.", + "You prefer a single CLI that works identically in local and CI flows.", + "You need in-memory secret injection so plaintext never hits disk.", + "You want Git-native audit trails via xenvsync log.", + "You need to support multiple named environments (staging, production, ci) from one repo.", + "You want simple commands with a predictable, understandable security model.", + ].map((item, i) => ( + + + + {item} + + + ))} +
    +
    +
    + +
    +
    + + Getting Started + + + Command Reference + + + CI/CD Recipes + + + Security Model + +
    +
    +
    + ); +} diff --git a/website/src/app/donate/page.tsx b/website/src/app/donate/page.tsx new file mode 100644 index 0000000..b78fbf3 --- /dev/null +++ b/website/src/app/donate/page.tsx @@ -0,0 +1,158 @@ +import { Card, PageHeader, Section } from "@/components/DocsComponents"; + +export const metadata = { + title: "Support xenvsync Development", + description: + "Support xenvsync open-source development through GitHub Sponsors, one-time contributions, or business support. Help fund maintenance, new features, documentation, and CI infrastructure.", + openGraph: { + title: "Support xenvsync Development", + description: "Help sustain open-source development of xenvsync.", + url: "https://xenvsync.softexforge.io/donate", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/donate" }, +}; + +export default function DonatePage() { + return ( +
    + + +
    + +

    + xenvsync is built and maintained as a solo open-source project. Every line of code, every test, every documentation page, and every release is produced without a corporate sponsor or VC backing. +

    +

    + Sponsorship directly funds the time required to: +

    +
      + {[ + "Review and merge community contributions", + "Maintain packaging across npm, Homebrew, Scoop, AUR, and Nix", + "Run CI pipelines across Linux, macOS, and Windows", + "Write and update documentation", + "Respond to security advisories promptly", + "Develop new features requested by the community", + ].map((item, i) => ( +
    • + + {item} +
    • + ))} +
    +
    +
    + +
    +
    + +

    GitHub Sponsors

    +

    + Become a recurring sponsor through GitHub Sponsors. Monthly sponsorships help with long-term planning and prioritizing roadmap work. Every tier directly supports development time. +

    +
      +
    • $5/mo– Community supporter
    • +
    • $15/mo– Regular contributor tier
    • +
    • $50/mo– Team supporter
    • +
    + + Sponsor on GitHub → + +
    + + +

    Business Support

    +

    + Using xenvsync in production at your company? Priority support is available for teams that need implementation guidance, architecture review, security consultation, or custom integrations. +

    +

    + Business support includes: +

    +
      + {[ + "Direct email support with SLA", + "Migration assistance from existing tooling", + "Security review of your xenvsync setup", + "Custom CI/CD integration help", + ].map((item, i) => ( +
    • + + {item} +
    • + ))} +
    + + Contact for business support → + +
    +
    +
    + +
    +
    + {[ + { + title: "Star on GitHub", + detail: "Stars increase visibility and help more developers discover xenvsync.", + href: "https://github.com/nasimstg/xenvsync", + label: "Star the repo", + }, + { + title: "Share and Write", + detail: "Write about your xenvsync setup, share the project with your team, or post on social media.", + href: null, + label: null, + }, + { + title: "Contribute Code", + detail: "Bug fixes, new features, and documentation improvements are always welcome.", + href: "https://github.com/nasimstg/xenvsync/blob/main/CONTRIBUTING.md", + label: "Read contributing guide", + }, + ].map(({ title, detail, href, label }) => ( + +

    {title}

    +

    {detail}

    + {href && label && ( + + {label} → + + )} +
    + ))} +
    +
    + +
    + +

    + xenvsync is built by{" "} + + Md Nasim Sheikh + + {" "}at{" "} + + SoftexForge + . The project is MIT licensed and will remain free and open-source. Sponsorship supports the developer's time, not a locked-down feature tier. +

    +
    +
    +
    + ); +} diff --git a/website/src/app/examples/page.tsx b/website/src/app/examples/page.tsx new file mode 100644 index 0000000..9b72e73 --- /dev/null +++ b/website/src/app/examples/page.tsx @@ -0,0 +1,137 @@ +import Link from "next/link"; +import { CodeBlock } from "@/components/CodeBlock"; +import { Card, PageHeader, Section } from "@/components/DocsComponents"; + +export const metadata = { + title: "Examples - xenvsync", + description: + "Real xenvsync examples across solo, startup, and enterprise teams with end-to-end command playbooks.", + openGraph: { + title: "Examples - xenvsync", + description: "Team-based xenvsync examples with practical command flows.", + url: "https://xenvsync.softexforge.io/examples", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/examples" }, +}; + +const teamExamples = [ + { + team: "Solo Developer", + scale: "1-2 engineers", + primaryGoal: "Prevent accidental plaintext leaks without adding process overhead.", + painPoint: "Local .env is edited constantly and can be committed by mistake.", + flow: `$ xenvsync init +$ xenvsync push +$ git add .env.vault +$ git commit -m "vault update" +$ xenvsync run -- npm run dev`, + }, + { + team: "Startup Team", + scale: "3-15 engineers", + primaryGoal: "Share staging and production secrets with per-member access and quick onboarding.", + painPoint: "Team members share credentials over chat and CI environments drift from local setups.", + flow: `$ xenvsync keygen +$ xenvsync whoami +$ xenvsync team add alice +$ xenvsync team add bob +$ xenvsync push --env staging +$ xenvsync push --env production`, + }, + { + team: "Enterprise Platform Team", + scale: "15+ engineers / multiple services", + primaryGoal: "Standardize secret controls with repeatable CI gates, audits, and revocation flow.", + painPoint: "Inconsistent secret handling per repository and unclear offboarding confidence.", + flow: `$ xenvsync doctor +$ xenvsync verify --env production +$ xenvsync log --env production -n 30 +$ xenvsync rotate --revoke former-member --env production +$ xenvsync push --env production`, + }, +]; + +export default function ExamplesPage() { + return ( +
    + + +
    + + + + + + + + + + + {teamExamples.map((item) => ( + + + + + + ))} + +
    TeamTypical ScalePrimary Outcome
    {item.team}{item.scale}{item.primaryGoal}
    +
    +
    + +
    +
    + {teamExamples.map((item) => ( + +

    {item.team}

    +

    + Pain point: {item.painPoint} +

    +

    + Target outcome: {item.primaryGoal} +

    + + {item.flow} + +
    + ))} +
    +
    + +
    +
    + +

    Workflow Library

    +

    + Expanded end-to-end workflows for local, CI, release, and incident scenarios. +

    + + Open workflows + +
    + +

    Usage Cookbook

    +

    + Command recipes by intent: bootstrap, audit, migration, export, and recovery. +

    + + Open usages + +
    + +

    Narrative Use Cases

    +

    + Deeper context and tradeoff analysis across solo, startup, and enterprise teams. +

    + + Open use-cases + +
    +
    +
    +
    + ); +} diff --git a/website/src/app/examples/usages/page.tsx b/website/src/app/examples/usages/page.tsx new file mode 100644 index 0000000..ef1e56a --- /dev/null +++ b/website/src/app/examples/usages/page.tsx @@ -0,0 +1,155 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Card, PageHeader, Section } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "Usage Examples - xenvsync", + description: + "Command cookbook examples for xenvsync across solo development, startup collaboration, and enterprise operations.", + openGraph: { + title: "Usage Examples - xenvsync", + description: "Task-focused xenvsync command recipes by team type and operational intent.", + url: "https://xenvsync.softexforge.io/examples/usages", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/examples/usages" }, +}; + +const commandIntents = [ + { + intent: "Bootstrap a secure project", + commands: "xenvsync init + xenvsync push", + team: "Solo / Startup", + }, + { + intent: "Run app without plaintext on disk", + commands: "xenvsync run -- ", + team: "All", + }, + { + intent: "Team key onboarding", + commands: "xenvsync keygen + whoami + team add", + team: "Startup / Enterprise", + }, + { + intent: "Pipeline safety gate", + commands: "xenvsync doctor + verify", + team: "Enterprise", + }, + { + intent: "Access revocation", + commands: "xenvsync rotate --revoke", + team: "Startup / Enterprise", + }, +]; + +export default function UsagesExamplesPage() { + return ( +
    + + +
    + + + + + + + + + + + {commandIntents.map((row) => ( + + + + + + ))} + +
    IntentPrimary commandsBest fit team
    {row.intent}{row.commands}{row.team}
    +
    +
    + +
    + +

    + Encrypt local env, catch drift early, and run app in-memory with no plaintext file writes. +

    + +{`$ xenvsync init +$ xenvsync push +$ xenvsync diff +$ xenvsync run -- go run ./cmd/api +$ xenvsync status`} + +
    +
    + +
    + +

    + Onboard member keys, keep CI keys runtime-only, and align staging workflows across local and pipeline runs. +

    + +{`$ xenvsync keygen && xenvsync whoami +$ xenvsync team add alice +$ xenvsync team add bob +$ xenvsync push --env staging +$ xenvsync run --env staging -- npm test`} + +
    +
    + +
    + +

    + Enforce health gates, export for infra pipelines, and run revocation rotation when offboarding or exposure is suspected. +

    + +{`$ xenvsync doctor --env production +$ xenvsync verify --env production +$ xenvsync export --env production --format=tfvars > secrets.auto.tfvars +$ terraform apply +$ xenvsync rotate --revoke former-member --env production +$ xenvsync push --env production`} + +
    +
    + +
    +
    + +

    Use-Case Deep Dive

    +

    + Narrative paths across team maturity and responsibility boundaries. +

    + + Open use-cases + +
    + +

    Workflow Playbooks

    +

    + Complete pipelines for onboarding, promotion, and rotation. +

    + + Open workflows + +
    + +

    Command Reference

    +

    + Full command and flag semantics for deeper customization. +

    + + Open commands + +
    +
    +
    +
    + ); +} diff --git a/website/src/app/examples/workflows/page.tsx b/website/src/app/examples/workflows/page.tsx new file mode 100644 index 0000000..6d35570 --- /dev/null +++ b/website/src/app/examples/workflows/page.tsx @@ -0,0 +1,134 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { Card, PageHeader, Section } from "@/components/DocsComponents"; +import Link from "next/link"; + +export const metadata = { + title: "Workflow Examples - xenvsync", + description: + "Team-ready xenvsync workflow examples for local development, CI/CD, promotion, rotation, and incident response.", + openGraph: { + title: "Workflow Examples - xenvsync", + description: "Detailed xenvsync workflows for solo, startup, and enterprise delivery flows.", + url: "https://xenvsync.softexforge.io/examples/workflows", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/examples/workflows" }, +}; + +const workflows = [ + { + title: "Solo: Local Development Loop", + team: "Solo", + summary: "Initialize once, encrypt local env, and run app with in-memory secret injection.", + script: `$ xenvsync init +$ xenvsync push +$ git add .env.vault && git commit -m "vault update" +$ xenvsync run -- npm run dev +$ xenvsync diff`, + }, + { + title: "Startup: Team Onboarding + Staging", + team: "Startup", + summary: "Add team members, publish staging vault, and align local and CI workflows.", + script: `$ xenvsync keygen +$ xenvsync whoami +$ xenvsync team add alice +$ xenvsync team add bob +$ xenvsync push --env staging +$ xenvsync pull --env staging +$ xenvsync run --env staging -- npm test`, + }, + { + title: "Enterprise: CI Gate + Promotion", + team: "Enterprise", + summary: "Enforce doctor/verify in pipeline, then promote vetted config from staging to production.", + script: `$ xenvsync doctor --env staging +$ xenvsync verify --env staging +$ xenvsync pull --env staging +$ xenvsync push --env production +$ xenvsync verify --env production`, + }, + { + title: "Enterprise: Offboarding + Rotation", + team: "Enterprise", + summary: "Revoke member access and rotate production/staging material with an audit-friendly command chain.", + script: `$ xenvsync team remove contractor-1 +$ xenvsync rotate --revoke contractor-1 --env staging +$ xenvsync rotate --revoke contractor-1 --env production +$ xenvsync push --env staging +$ xenvsync push --env production +$ xenvsync log --env production -n 20`, + }, + { + title: "Incident Response: Suspected Key Leak", + team: "All", + summary: "Contain, rotate, and re-issue working vaults with minimal downtime.", + script: `$ xenvsync doctor +$ xenvsync verify --env production +$ xenvsync rotate --env production +$ xenvsync push --env production +$ xenvsync pull --env production +$ xenvsync run --env production -- npm run smoke`, + }, +]; + +export default function WorkflowsExamplesPage() { + return ( +
    + + +
    +
    + {workflows.map((workflow) => ( + +
    +

    {workflow.title}

    + + {workflow.team} + +
    +

    {workflow.summary}

    + + {workflow.script} + +
    + ))} +
    +
    + +
    +
    + +

    Use-Cases Narrative

    +

    + Strategic context for solo, startup, and enterprise adoption paths. +

    + + Open use-cases + +
    + +

    Usage Cookbook

    +

    + Task-driven recipes for migration, auditing, export, and operations. +

    + + Open usages + +
    + +

    Troubleshooting

    +

    + Error signatures, warning classes, and targeted remediation patterns. +

    + + Open troubleshooting + +
    +
    +
    +
    + ); +} diff --git a/website/src/app/globals.css b/website/src/app/globals.css index e2ad0a1..edc6003 100644 --- a/website/src/app/globals.css +++ b/website/src/app/globals.css @@ -4,7 +4,7 @@ --color-bg: #06060b; --color-bg-card: #0d0d14; --color-bg-elevated: #14141f; - --color-bg-glass: rgba(13, 13, 20, 0.6); + --color-bg-glass: rgba(10, 10, 18, 0.8); --color-border: #1e1e2e; --color-border-bright: #2e2e44; --color-text: #e8e8f0; @@ -40,17 +40,19 @@ body { /* Glass card */ .glass { - background: var(--color-bg-glass); - backdrop-filter: blur(16px); - -webkit-backdrop-filter: blur(16px); - border: 1px solid var(--color-border); + background: linear-gradient(180deg, rgba(16, 16, 26, 0.88), var(--color-bg-glass)); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + border: 1px solid rgba(62, 62, 94, 0.75); + box-shadow: 0 10px 28px rgba(0, 0, 0, 0.3); } .glass-bright { - background: rgba(20, 20, 31, 0.7); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 1px solid var(--color-border-bright); + background: rgba(10, 10, 18, 0.92); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + border: 1px solid rgba(76, 76, 112, 0.9); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03), 0 16px 30px rgba(0, 0, 0, 0.35); } /* Gradient text */ diff --git a/website/src/app/integrations/page.tsx b/website/src/app/integrations/page.tsx new file mode 100644 index 0000000..f6cddd0 --- /dev/null +++ b/website/src/app/integrations/page.tsx @@ -0,0 +1,88 @@ +import Link from "next/link"; +import { CodeBlock } from "@/components/CodeBlock"; +import { Card, PageHeader, Section } from "@/components/DocsComponents"; + +export const metadata = { + title: "Integrations - xenvsync", + description: + "Integrate xenvsync with Node.js, Python, Docker Compose, Kubernetes, Terraform, and CI pipelines.", + openGraph: { + title: "Integrations - xenvsync", + description: "Integration patterns for app runtimes, containers, IaC, and CI workflows.", + url: "https://xenvsync.softexforge.io/integrations", + }, + alternates: { canonical: "https://xenvsync.softexforge.io/integrations" }, +}; + +export default function IntegrationsPage() { + return ( +
    + + +
    +
    + +{`$ xenvsync run -- npm run dev +$ xenvsync run -- npm test`} + +
    +
    + +
    +
    + +{`$ xenvsync run -- python app.py +$ xenvsync run -- pytest`} + +
    +
    + +
    +
    + +{`$ xenvsync run -- docker compose up --build`} + + + Prefer in-memory injection for local workflows when possible to avoid writing plaintext .env files. + +
    +
    + +
    +
    + +{`$ xenvsync export --format=yaml > secrets.yaml +$ kubectl apply -f secrets.yaml`} + +
    +
    + +
    +
    + +{`$ xenvsync export --format=tfvars > secrets.auto.tfvars +$ terraform apply`} + +
    +
    + +
    + +

    + For production pipelines, use the provider-specific recipes in{" "} + + CI/CD Recipes + + . +

    +

    + Keep decryption keys in CI secret stores and load them only at runtime for jobs that need secrets. +

    +
    +
    +
    + ); +} diff --git a/website/src/app/layout.tsx b/website/src/app/layout.tsx index fe08c0b..a57175c 100644 --- a/website/src/app/layout.tsx +++ b/website/src/app/layout.tsx @@ -1,11 +1,39 @@ import type { Metadata } from "next"; import { Header } from "@/components/Header"; import { Footer } from "@/components/Footer"; +import { ConsentBanner } from "@/components/ConsentBanner"; import { SearchHighlight } from "@/components/SearchHighlight"; import "./globals.css"; +const structuredData = [ + { + "@context": "https://schema.org", + "@type": "SoftwareApplication", + name: "xenvsync", + applicationCategory: "DeveloperApplication", + operatingSystem: "Linux, macOS, Windows", + description: + "Encrypt, commit, and inject .env secrets with AES-256-GCM and X25519 team sharing.", + url: "https://xenvsync.softexforge.io", + sameAs: ["https://github.com/nasimstg/xenvsync"], + offers: { + "@type": "Offer", + price: "0", + priceCurrency: "USD", + }, + }, + { + "@context": "https://schema.org", + "@type": "WebSite", + name: "xenvsync", + url: "https://xenvsync.softexforge.io", + inLanguage: "en", + }, +]; + export const metadata: Metadata = { metadataBase: new URL("https://xenvsync.softexforge.io"), + applicationName: "xenvsync", title: "xenvsync - Encrypted Environment Variables for Teams", description: "Encrypt, commit, and inject .env secrets with AES-256-GCM. No cloud required. A fast, cross-platform CLI tool for secure local development.", @@ -27,11 +55,32 @@ export const metadata: Metadata = { type: "website", url: "https://xenvsync.softexforge.io", siteName: "xenvsync", + locale: "en_US", + images: [ + { + url: "/opengraph-image", + width: 1200, + height: 630, + alt: "xenvsync - Encrypt .env, commit with confidence", + }, + ], }, twitter: { card: "summary_large_image", title: "xenvsync", description: "Encrypt, commit, and inject .env secrets. No cloud required.", + images: ["/twitter-image"], + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-image-preview": "large", + "max-snippet": -1, + "max-video-preview": -1, + }, }, icons: { icon: "/favicon.ico", @@ -49,10 +98,15 @@ export default function RootLayout({ return ( +