Skip to content

Use OS keyring for bundles #1170

@l2ysho

Description

@l2ysho

The OS keyring storage from #1115 works for npm install -g apify-cli but not for bundle distributions (install-cli.sh / install-cli.ps1). Bun's --compile doesn't include the @napi-rs/keyring native module, so bundle users silently fall back to plaintext file storage.

No crash, no regression — the fallback in loadKeyringModule() handles it gracefully and the success message tells bundle users to npm-install if they want keyring storage. But bundle install is the recommended path in the README, so we should make it work properly.

Reproduction

  1. Run a recent bundle binary (e.g. apify-1.6.2-linux-arm64) with APIFY_CLI_DEBUG=1 on a Linux box with gnome-keyring running.
  2. stderr shows:
    [credentials] failed to load @napi-rs/keyring error: Cannot find module '@napi-rs/keyring' from '/$bunfs/root/apify.js'
    
  3. ~/.apify/auth.json ends up with "secretsBackend": "file" and the token in plaintext, despite a working keyring being present.

What we tried (and why it didn't work)

We attempted to embed all platform .node binaries via Bun's with { type: "file" } import attribute, with pnpm.supportedArchitectures forcing all sub-packages into node_modules at build time. The .node files visibly ended up in the compiled binary (strings confirmed it), but no runtime API could read them back:

Attempt Result
fs.readFileSync(importedPath) ENOENT (six path variants tried)
Bun.file(importedPath).bytes() ENOENT
Bun.embeddedFiles lookup Array is empty

Root cause hypothesis: Bun recognizes .node as a native-module extension and handles it specially during --compile. The with { type: "file" } asset pipeline doesn't apply to .node files — they're relocated or stripped via a mechanism not exposed to user code.

Proposed future approach (per-target shims)

Instead of embedding all platforms in every bundle and picking at runtime, generate a per-target shim at build time. Each compile target gets a shim that statically imports exactly the one sub-package matching that target:

// scripts/build-cli-bundles.ts (conceptual)
const targetToSubpackage = {
    'bun-linux-x64':        '@napi-rs/keyring-linux-x64-gnu',
    'bun-linux-x64-musl':   '@napi-rs/keyring-linux-x64-musl',
    'bun-linux-arm64':      '@napi-rs/keyring-linux-arm64-gnu',
    'bun-linux-arm64-musl': '@napi-rs/keyring-linux-arm64-musl',
    'bun-darwin-x64':       '@napi-rs/keyring-darwin-x64',
    'bun-darwin-arm64':     '@napi-rs/keyring-darwin-arm64',
    'bun-windows-x64':      '@napi-rs/keyring-win32-x64-msvc',
    'bun-windows-arm64':    '@napi-rs/keyring-win32-arm64-msvc',
};

for (const target of targets) {
    const subpkg = targetToSubpackage[target.replace(/-baseline$/, '')];
    await writeFile('src/lib/keyring-bundle-shim.ts',
        `export { Entry } from '${subpkg}';\n`);
    // run bun build --compile as before
}

This avoids with { type: "file" } entirely — the hope is that a regular static import lets Bun's --compile resolve the native module the same way it resolves any other dependency.

Open questions to validate before/during implementation

  • Does Bun's --compile actually bundle .node modules from a normal static import? (Untested — needs a small experiment.)
  • Does the napi.binaryName indirection in @napi-rs/keyring-<platform>/package.json need special handling?
  • Musl/glibc detection: since we ship separate musl bundles anyway, no runtime detection needed — just make sure the labeling is right.

Acceptance criteria

  1. Bundle binary stores the token in the OS keyring on systems with a working Secret Service / Keychain / Credential Manager (auth.json shows secretsBackend: "keyring" and no token field).
  2. secret-tool search service com.apify.cli (Linux) / Keychain Access (macOS) / Credential Manager (Windows) finds the entry.
  3. On systems without a working keyring, the bundle still falls back to file storage gracefully — no crash.
  4. The npm install path keeps working as today.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    t-dxIssues owned by the DX team.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions