#!/bin/bash # Generated from the reviewed release manifest. Installs code/configuration only; no API calls. install_mcpbytes_vault() ( set -euo pipefail umask 077 fail() { printf 'MCPBytes Vault: %s\n' "$*" >&2; exit 1; } json_string() { local value="$1" value=${value//\\/\\\\}; value=${value//\"/\\\"} printf '"%s"' "$value" } hash_file() { if command -v sha256sum >/dev/null 2>&1; then sha256sum < "$1" | awk '{print $1}' else shasum -a 256 < "$1" | awk '{print $1}'; fi } private_dir() { [[ ! -L "$1" ]] || fail 'Refusing a symlink in an installation/data path.' mkdir -p -- "$1" [[ -d "$1" && -O "$1" ]] || fail 'Installation directories must belong to this user.' chmod 700 "$1" local bits if [[ "$os" == Darwin ]]; then bits=$(stat -f '%Lp' "$1"); else bits=$(stat -c '%a' "$1"); fi (( (8#$bits & 077) == 0 )) || fail 'The filesystem must support private directory permissions.' } version='0.1.0' store=native; prefix=''; local_archive='' while [[ $# -gt 0 ]]; do case "$1" in --store) [[ $# -ge 2 ]] || fail '--store requires native or file'; store="$2"; shift 2 ;; --prefix) [[ $# -ge 2 ]] || fail '--prefix requires an absolute dedicated directory'; prefix="$2"; shift 2 ;; --archive) [[ $# -ge 2 ]] || fail '--archive requires the matching release ZIP'; local_archive="$2"; shift 2 ;; --help|-h) printf '%s\n' 'Usage: bash vault.sh [--store native|file] [--prefix /absolute/dedicated/directory] [--archive release.zip]' \ 'Per-user install; no sudo. Existing configuration, pins, state and keys are preserved.' \ '--store applies only when creating a new configuration. No API key is collected.' exit 0 ;; *) fail "Unknown option: $1" ;; esac done [[ "$store" == native || "$store" == file ]] || fail '--store must be native or file' [[ $(id -u) != 0 ]] || fail 'Run as your normal user, without sudo.' [[ -n "${HOME:-}" && -d "$HOME" ]] || fail 'A user home directory is required.' os=$(uname -s); architecture=$(uname -m) if [[ "$os" == Darwin && "$architecture" == x86_64 && "$(sysctl -n hw.optional.arm64 2>/dev/null || true)" == 1 ]]; then architecture=arm64; fi case "$os/$architecture" in Linux/x86_64) url='https://mcpbytes.com/downloads/mcpbytes-vault/0.1.0/mcpbytes-vault-0.1.0-linux-x64.zip'; archive_sha='e694efa36f906634a77fb3836af530ec8a10d93f667bc43b2cde26e4acba1594'; binary_sha='09119dcca31da932fb1bad6d031e1aad01f7e805afe599bbe7b23ac16270f101' backend=linux_secret_service; default_root="${XDG_DATA_HOME:-$HOME/.local/share}/mcpbytes-vault" ;; Darwin/arm64) url='https://mcpbytes.com/downloads/mcpbytes-vault/0.1.0/mcpbytes-vault-0.1.0-macos-arm64.zip'; archive_sha='5f920c65ad93defa207e5a1241b5274be2ce0346278f815f7a6824503f9cc6f0'; binary_sha='3849452eb02bc4e765afeb8c07f4de7f71b9b9c6fc12e61552d87c54990bf2a4' backend=macos_keychain; default_root="$HOME/Library/Application Support/MCPBytes/Vault" ;; *) fail "Unsupported platform: $os/$architecture. Available: Linux x64 and macOS Apple silicon." ;; esac command -v unzip >/dev/null 2>&1 || fail 'Install unzip, then rerun this installer.' command -v sha256sum >/dev/null 2>&1 || command -v shasum >/dev/null 2>&1 || fail 'SHA-256 utility required.' if [[ -z "$local_archive" ]]; then command -v curl >/dev/null 2>&1 || fail 'curl is required.' else [[ -f "$local_archive" && ! -L "$local_archive" ]] || fail 'Offline archive must be a regular file.'; fi vault_root=${prefix:-$default_root} [[ "$vault_root" == /* && "$vault_root" != / && "$vault_root" != "$HOME" && ! "$vault_root" =~ [[:cntrl:]] ]] || fail 'Choose an absolute dedicated installation directory.' case "$vault_root/" in *'/../'*|*'/./'*|*'//'*) fail 'Directory must not contain dot segments or duplicate slashes.' ;; esac vault_root=${vault_root%/} [[ ! -L "$vault_root" ]] || fail 'Installation root must not be a symlink.' if [[ -e "$vault_root" && ! -f "$vault_root/.mcpbytes-vault-managed" && ! -f "$vault_root/config.json" ]]; then [[ -d "$vault_root" && -z $(ls -A "$vault_root") ]] || fail 'Refusing a nonempty unmanaged directory.' fi mkdir -p -- "$vault_root" vault_root=$(cd -- "$vault_root" && pwd -P) [[ "$vault_root" != / && "$vault_root" != "$(cd -- "$HOME" && pwd -P)" ]] || fail 'Choose a dedicated directory, not your home or filesystem root.' private_dir "$vault_root" for name in .mcpbytes-vault-managed config.json mcp-server.json install.json bin versions state keys; do [[ ! -L "$vault_root/$name" ]] || fail "Refusing a symlink: $name" done [[ ! -e "$vault_root/config.json" || ( -f "$vault_root/config.json" && -O "$vault_root/config.json" ) ]] || fail 'Existing configuration must be an owner-controlled regular file.' [[ ! -e "$vault_root/mcp-server.json" || ( -f "$vault_root/mcp-server.json" && -O "$vault_root/mcp-server.json" ) ]] || fail 'Existing MCP settings must be an owner-controlled regular file.' if [[ -e "$vault_root/.mcpbytes-vault-managed" ]]; then [[ "$(cat "$vault_root/.mcpbytes-vault-managed")" == 'MCPBytes Vault installer v1' ]] || fail 'Unrecognized installation marker.' fi mkdir -- "$vault_root/.install-lock" 2>/dev/null || fail 'Another installation is active; inspect .install-lock before retrying.' scratch='' cleanup() { if [[ -n "$scratch" && "$scratch" == "$vault_root"/.install-tmp.* && ! -L "$scratch" ]]; then rm -rf -- "$scratch"; fi rmdir -- "$vault_root/.install-lock" 2>/dev/null || true } trap cleanup EXIT printf '%s\n' 'MCPBytes Vault installer v1' > "$vault_root/.mcpbytes-vault-managed" private_dir "$vault_root/bin"; private_dir "$vault_root/versions" scratch=$(mktemp -d "$vault_root/.install-tmp.XXXXXX") if [[ -n "$local_archive" ]]; then cp -- "$local_archive" "$scratch/release.zip" else printf 'Downloading MCPBytes Vault %s…\n' "$version" curl -q --proto '=https' --proto-redir '=https' --tlsv1.2 -fLsS --max-redirs 3 \ --connect-timeout 15 --max-time 120 --retry 2 -o "$scratch/release.zip" "$url" fi [[ "$(hash_file "$scratch/release.zip")" == "$archive_sha" ]] || fail 'Archive checksum mismatch; nothing was activated.' unzip -Z1 "$scratch/release.zip" | while IFS= read -r entry; do case "$entry" in /*|../*|*/../*|*/..|..|*\\*) fail 'Unsafe archive path.' ;; esac done mkdir "$scratch/unpacked" unzip -q "$scratch/release.zip" -d "$scratch/unpacked" [[ -z $(find "$scratch/unpacked" -type l -print) ]] || fail 'Symlinks are not allowed in the release archive.' [[ -f "$scratch/unpacked/mcpbytes-vault" && "$(hash_file "$scratch/unpacked/mcpbytes-vault")" == "$binary_sha" ]] || fail 'Executable checksum mismatch.' version_dir="$vault_root/versions/$version" [[ ! -L "$version_dir" ]] || fail 'Version directory must not be a symlink.' if [[ -e "$version_dir" ]]; then [[ -d "$version_dir" && -f "$version_dir/mcpbytes-vault" && ! -L "$version_dir/mcpbytes-vault" && "$(hash_file "$version_dir/mcpbytes-vault")" == "$binary_sha" ]] || fail 'Existing version differs from the release; preserve it for inspection.' else chmod 700 "$scratch/unpacked/mcpbytes-vault" mv -- "$scratch/unpacked" "$version_dir" fi config_path="$vault_root/config.json" if [[ ! -e "$config_path" ]]; then private_dir "$vault_root/state" if [[ "$store" == file ]]; then private_dir "$vault_root/keys"; backend=private_file; fi { printf '{\n "state_dir": %s,\n "store": {"backend": "%s"' "$(json_string "$vault_root/state")" "$backend" if [[ "$store" == file ]]; then printf ', "directory": %s' "$(json_string "$vault_root/keys")"; fi printf '},\n "label_prefix": "agent-",\n "mode": "remote_required",\n' printf ' "remote": {"url": "https://api.mcpbytes.com/v1/sealed-random", "api_key_env": "MCPBYTES_API_KEY",\n' printf ' "pins": [{"public_key": "ZeUTSjyq45pq5CLe8ab0jc1e4L0QTD1CvQpchQiASzI", "firmware_digests": ["fUNTPVs4pZ_AyDDV7J06eyMOQ-y8l56z3CEt9GNnAQM"]}]}\n}\n' } > "$scratch/config.json" # An exclusive hard link refuses a config created concurrently; never overwrite owner policy. ln "$scratch/config.json" "$config_path" printf 'Created private configuration (%s).\n' "$backend" else printf '%s\n' 'Existing configuration, pins and state preserved; --store is only used on first install.'; fi launcher="$vault_root/bin/mcpbytes-vault" [[ ! -L "$launcher" ]] || fail 'Launcher must not be a symlink.' if [[ -e "$launcher" ]]; then [[ -f "$launcher" && -O "$launcher" ]] || fail 'Existing launcher is not an owner-controlled file.' head -n 2 "$launcher" | tail -n 1 | grep -Fx '# Managed by MCPBytes Vault installer' >/dev/null || fail 'Refusing to overwrite an unmanaged launcher.' fi { printf '#!/bin/bash\n# Managed by MCPBytes Vault installer\nexec '; printf '%q ' "$version_dir/mcpbytes-vault" --config "$config_path"; printf '"$@"\n'; } > "$scratch/launcher" chmod 700 "$scratch/launcher"; mv -f -- "$scratch/launcher" "$launcher" if [[ ! -e "$vault_root/mcp-server.json" ]]; then printf '{"mcpServers":{"mcpbytes-vault":{"command":%s,"args":[]}}}\n' "$(json_string "$launcher")" > "$scratch/mcp-server.json" ln "$scratch/mcp-server.json" "$vault_root/mcp-server.json" fi printf '{"version":"%s","archive_sha256":"%s","binary_sha256":"%s"}\n' "$version" "$archive_sha" "$binary_sha" > "$scratch/install.json" mv -f -- "$scratch/install.json" "$vault_root/install.json" printf '\nInstalled MCPBytes Vault %s\nCommand: %s\nArguments: none\nMCP settings: %s\n' "$version" "$launcher" "$vault_root/mcp-server.json" printf '\nClaude Code:\nclaude mcp add --transport stdio --scope user mcpbytes-vault -- '; printf '%q\n' "$launcher" printf '\nCodex:\ncodex mcp add mcpbytes-vault -- '; printf '%q\n' "$launcher" printf '%s\n' 'Then add env_vars = ["MCPBYTES_API_KEY"] to [mcp_servers.mcpbytes-vault] in ~/.codex/config.toml.' printf '%s\n' 'Cursor, VS Code, Gemini, OpenCode, Pi and other local clients: https://mcpbytes.com/docs/random-bytes#connect' printf '%s\n' 'Set MCPBYTES_API_KEY privately in your MCP client environment, then connect this local stdio server.' \ 'No API key was collected, no key material was generated, and no client settings or shell profile were changed.' ) install_mcpbytes_vault "$@"