@reegaviljoen/eldlock 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +285 -0
  2. package/bin/eldlock +11 -0
  3. package/docs/architecture.md +164 -0
  4. package/docs/threat-model.md +47 -0
  5. package/eldlock-cli/README.md +56 -0
  6. package/eldlock-cli/bin/eldlock +3 -0
  7. package/eldlock-cli/package-lock.json +805 -0
  8. package/eldlock-cli/package.json +71 -0
  9. package/eldlock-cli/src/api.ts +250 -0
  10. package/eldlock-cli/src/cli.ts +490 -0
  11. package/eldlock-cli/src/main.ts +10 -0
  12. package/eldlock-cli/src/tui.ts +676 -0
  13. package/eldlock-cli/tsconfig.json +13 -0
  14. package/eldlock-cli/vendor/npm/ansi-regex-6.2.2.tgz +0 -0
  15. package/eldlock-cli/vendor/npm/bun-ffi-structs-0.2.2.tgz +0 -0
  16. package/eldlock-cli/vendor/npm/diff-9.0.0.tgz +0 -0
  17. package/eldlock-cli/vendor/npm/emoji-regex-10.6.0.tgz +0 -0
  18. package/eldlock-cli/vendor/npm/esbuild-0.28.0.tgz +0 -0
  19. package/eldlock-cli/vendor/npm/esbuild-darwin-arm64-0.28.0.tgz +0 -0
  20. package/eldlock-cli/vendor/npm/esbuild-darwin-x64-0.28.0.tgz +0 -0
  21. package/eldlock-cli/vendor/npm/esbuild-linux-arm64-0.28.0.tgz +0 -0
  22. package/eldlock-cli/vendor/npm/esbuild-linux-x64-0.28.0.tgz +0 -0
  23. package/eldlock-cli/vendor/npm/fsevents-2.3.3.tgz +0 -0
  24. package/eldlock-cli/vendor/npm/get-east-asian-width-1.6.0.tgz +0 -0
  25. package/eldlock-cli/vendor/npm/marked-17.0.1.tgz +0 -0
  26. package/eldlock-cli/vendor/npm/opentui-core-0.3.1.tgz +0 -0
  27. package/eldlock-cli/vendor/npm/opentui-core-darwin-arm64-0.3.1.tgz +0 -0
  28. package/eldlock-cli/vendor/npm/opentui-core-darwin-x64-0.3.1.tgz +0 -0
  29. package/eldlock-cli/vendor/npm/opentui-core-linux-arm64-0.3.1.tgz +0 -0
  30. package/eldlock-cli/vendor/npm/opentui-core-linux-x64-0.3.1.tgz +0 -0
  31. package/eldlock-cli/vendor/npm/string-width-7.2.0.tgz +0 -0
  32. package/eldlock-cli/vendor/npm/strip-ansi-7.1.2.tgz +0 -0
  33. package/eldlock-cli/vendor/npm/tsx-4.22.4.tgz +0 -0
  34. package/eldlock-cli/vendor/npm/types-node-22.19.19.tgz +0 -0
  35. package/eldlock-cli/vendor/npm/typescript-5.9.3.tgz +0 -0
  36. package/eldlock-cli/vendor/npm/undici-types-6.21.0.tgz +0 -0
  37. package/eldlock-cli/vendor/npm/web-tree-sitter-0.25.10.tgz +0 -0
  38. package/eldlock-cli/vendor/npm/yoga-layout-3.2.1.tgz +0 -0
  39. package/eldlock-server/cmd/eldlock-server/main.go +132 -0
  40. package/eldlock-server/go.mod +10 -0
  41. package/eldlock-server/go.sum +11 -0
  42. package/eldlock-server/internal/api/README.md +14 -0
  43. package/eldlock-server/internal/api/core.go +126 -0
  44. package/eldlock-server/internal/api/exec.go +97 -0
  45. package/eldlock-server/internal/api/secrets.go +358 -0
  46. package/eldlock-server/internal/api/server.go +72 -0
  47. package/eldlock-server/internal/api/service_test.go +416 -0
  48. package/eldlock-server/internal/api/types.go +48 -0
  49. package/eldlock-server/internal/api/vault.go +69 -0
  50. package/eldlock-server/internal/api/vendor.go +44 -0
  51. package/eldlock-server/internal/libfido2/LICENSE +21 -0
  52. package/eldlock-server/internal/libfido2/README.md +127 -0
  53. package/eldlock-server/internal/libfido2/examples_test.go +614 -0
  54. package/eldlock-server/internal/libfido2/fido2.go +1234 -0
  55. package/eldlock-server/internal/libfido2/fido2_darwin.go +7 -0
  56. package/eldlock-server/internal/libfido2/fido2_other.go +9 -0
  57. package/eldlock-server/internal/libfido2/fido2_test.go +101 -0
  58. package/eldlock-server/internal/libfido2/go.mod +10 -0
  59. package/eldlock-server/internal/libfido2/go.sum +16 -0
  60. package/eldlock-server/internal/libfido2/log.go +87 -0
  61. package/eldlock-server/internal/store/README.md +7 -0
  62. package/eldlock-server/internal/store/store.go +434 -0
  63. package/eldlock-server/internal/store/store_test.go +125 -0
  64. package/eldlock-server/internal/yubikey/README.md +25 -0
  65. package/eldlock-server/internal/yubikey/default_fido2.go +7 -0
  66. package/eldlock-server/internal/yubikey/default_stub.go +7 -0
  67. package/eldlock-server/internal/yubikey/fido2_disabled.go +9 -0
  68. package/eldlock-server/internal/yubikey/fido2_libfido2.go +225 -0
  69. package/eldlock-server/internal/yubikey/fido2_libfido2_test.go +66 -0
  70. package/eldlock-server/internal/yubikey/passkey.go +139 -0
  71. package/eldlock-server/internal/yubikey/passkey_test.go +36 -0
  72. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/LICENSE +21 -0
  73. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/README.md +127 -0
  74. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/fido2.go +1234 -0
  75. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/fido2_darwin.go +7 -0
  76. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/fido2_other.go +9 -0
  77. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/log.go +87 -0
  78. package/eldlock-server/vendor/github.com/pkg/errors/.travis.yml +10 -0
  79. package/eldlock-server/vendor/github.com/pkg/errors/LICENSE +23 -0
  80. package/eldlock-server/vendor/github.com/pkg/errors/Makefile +44 -0
  81. package/eldlock-server/vendor/github.com/pkg/errors/README.md +59 -0
  82. package/eldlock-server/vendor/github.com/pkg/errors/appveyor.yml +32 -0
  83. package/eldlock-server/vendor/github.com/pkg/errors/errors.go +288 -0
  84. package/eldlock-server/vendor/github.com/pkg/errors/go113.go +38 -0
  85. package/eldlock-server/vendor/github.com/pkg/errors/stack.go +177 -0
  86. package/eldlock-server/vendor/modules.txt +7 -0
  87. package/examples/eldlock.toml +17 -0
  88. package/install.sh +66 -0
  89. package/package.json +66 -0
  90. package/scripts/build-production.mjs +177 -0
  91. package/scripts/postinstall-production.mjs +23 -0
@@ -0,0 +1,177 @@
1
+ package errors
2
+
3
+ import (
4
+ "fmt"
5
+ "io"
6
+ "path"
7
+ "runtime"
8
+ "strconv"
9
+ "strings"
10
+ )
11
+
12
+ // Frame represents a program counter inside a stack frame.
13
+ // For historical reasons if Frame is interpreted as a uintptr
14
+ // its value represents the program counter + 1.
15
+ type Frame uintptr
16
+
17
+ // pc returns the program counter for this frame;
18
+ // multiple frames may have the same PC value.
19
+ func (f Frame) pc() uintptr { return uintptr(f) - 1 }
20
+
21
+ // file returns the full path to the file that contains the
22
+ // function for this Frame's pc.
23
+ func (f Frame) file() string {
24
+ fn := runtime.FuncForPC(f.pc())
25
+ if fn == nil {
26
+ return "unknown"
27
+ }
28
+ file, _ := fn.FileLine(f.pc())
29
+ return file
30
+ }
31
+
32
+ // line returns the line number of source code of the
33
+ // function for this Frame's pc.
34
+ func (f Frame) line() int {
35
+ fn := runtime.FuncForPC(f.pc())
36
+ if fn == nil {
37
+ return 0
38
+ }
39
+ _, line := fn.FileLine(f.pc())
40
+ return line
41
+ }
42
+
43
+ // name returns the name of this function, if known.
44
+ func (f Frame) name() string {
45
+ fn := runtime.FuncForPC(f.pc())
46
+ if fn == nil {
47
+ return "unknown"
48
+ }
49
+ return fn.Name()
50
+ }
51
+
52
+ // Format formats the frame according to the fmt.Formatter interface.
53
+ //
54
+ // %s source file
55
+ // %d source line
56
+ // %n function name
57
+ // %v equivalent to %s:%d
58
+ //
59
+ // Format accepts flags that alter the printing of some verbs, as follows:
60
+ //
61
+ // %+s function name and path of source file relative to the compile time
62
+ // GOPATH separated by \n\t (<funcname>\n\t<path>)
63
+ // %+v equivalent to %+s:%d
64
+ func (f Frame) Format(s fmt.State, verb rune) {
65
+ switch verb {
66
+ case 's':
67
+ switch {
68
+ case s.Flag('+'):
69
+ io.WriteString(s, f.name())
70
+ io.WriteString(s, "\n\t")
71
+ io.WriteString(s, f.file())
72
+ default:
73
+ io.WriteString(s, path.Base(f.file()))
74
+ }
75
+ case 'd':
76
+ io.WriteString(s, strconv.Itoa(f.line()))
77
+ case 'n':
78
+ io.WriteString(s, funcname(f.name()))
79
+ case 'v':
80
+ f.Format(s, 's')
81
+ io.WriteString(s, ":")
82
+ f.Format(s, 'd')
83
+ }
84
+ }
85
+
86
+ // MarshalText formats a stacktrace Frame as a text string. The output is the
87
+ // same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
88
+ func (f Frame) MarshalText() ([]byte, error) {
89
+ name := f.name()
90
+ if name == "unknown" {
91
+ return []byte(name), nil
92
+ }
93
+ return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
94
+ }
95
+
96
+ // StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
97
+ type StackTrace []Frame
98
+
99
+ // Format formats the stack of Frames according to the fmt.Formatter interface.
100
+ //
101
+ // %s lists source files for each Frame in the stack
102
+ // %v lists the source file and line number for each Frame in the stack
103
+ //
104
+ // Format accepts flags that alter the printing of some verbs, as follows:
105
+ //
106
+ // %+v Prints filename, function, and line number for each Frame in the stack.
107
+ func (st StackTrace) Format(s fmt.State, verb rune) {
108
+ switch verb {
109
+ case 'v':
110
+ switch {
111
+ case s.Flag('+'):
112
+ for _, f := range st {
113
+ io.WriteString(s, "\n")
114
+ f.Format(s, verb)
115
+ }
116
+ case s.Flag('#'):
117
+ fmt.Fprintf(s, "%#v", []Frame(st))
118
+ default:
119
+ st.formatSlice(s, verb)
120
+ }
121
+ case 's':
122
+ st.formatSlice(s, verb)
123
+ }
124
+ }
125
+
126
+ // formatSlice will format this StackTrace into the given buffer as a slice of
127
+ // Frame, only valid when called with '%s' or '%v'.
128
+ func (st StackTrace) formatSlice(s fmt.State, verb rune) {
129
+ io.WriteString(s, "[")
130
+ for i, f := range st {
131
+ if i > 0 {
132
+ io.WriteString(s, " ")
133
+ }
134
+ f.Format(s, verb)
135
+ }
136
+ io.WriteString(s, "]")
137
+ }
138
+
139
+ // stack represents a stack of program counters.
140
+ type stack []uintptr
141
+
142
+ func (s *stack) Format(st fmt.State, verb rune) {
143
+ switch verb {
144
+ case 'v':
145
+ switch {
146
+ case st.Flag('+'):
147
+ for _, pc := range *s {
148
+ f := Frame(pc)
149
+ fmt.Fprintf(st, "\n%+v", f)
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ func (s *stack) StackTrace() StackTrace {
156
+ f := make([]Frame, len(*s))
157
+ for i := 0; i < len(f); i++ {
158
+ f[i] = Frame((*s)[i])
159
+ }
160
+ return f
161
+ }
162
+
163
+ func callers() *stack {
164
+ const depth = 32
165
+ var pcs [depth]uintptr
166
+ n := runtime.Callers(3, pcs[:])
167
+ var st stack = pcs[0:n]
168
+ return &st
169
+ }
170
+
171
+ // funcname removes the path prefix component of a function's name reported by func.Name().
172
+ func funcname(name string) string {
173
+ i := strings.LastIndex(name, "/")
174
+ name = name[i+1:]
175
+ i = strings.Index(name, ".")
176
+ return name[i+1:]
177
+ }
@@ -0,0 +1,7 @@
1
+ # github.com/keys-pub/go-libfido2 v1.5.3 => ./internal/libfido2
2
+ ## explicit; go 1.13
3
+ github.com/keys-pub/go-libfido2
4
+ # github.com/pkg/errors v0.9.1
5
+ ## explicit
6
+ github.com/pkg/errors
7
+ # github.com/keys-pub/go-libfido2 => ./internal/libfido2
@@ -0,0 +1,17 @@
1
+ # Example per-project Eldlock config.
2
+ # This file maps normal env var names to secrets stored in the local vault.
3
+
4
+ [env]
5
+ OPENAI_API_KEY = "personal/openai/api_key"
6
+ GITHUB_TOKEN = "personal/github/token"
7
+ DATABASE_URL = "work/project/database_url"
8
+
9
+ [policy]
10
+ approval = "per-session"
11
+ ttl = "15m"
12
+ require_yubikey = true
13
+
14
+ [allow]
15
+ cwd = "."
16
+ processes = ["bin/dev", "rails", "bundle", "npm", "node", "python"]
17
+
package/install.sh ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ repo="${ELDLOCK_REPO:-reeganviljoen/eldlock}"
5
+ ref="${ELDLOCK_REF:-main}"
6
+ install_dir="${ELDLOCK_INSTALL_DIR:-$HOME/.local/share/eldlock}"
7
+ bin_dir="${ELDLOCK_BIN_DIR:-$HOME/.local/bin}"
8
+
9
+ need() {
10
+ if ! command -v "$1" >/dev/null 2>&1; then
11
+ echo "eldlock install: missing required command: $1" >&2
12
+ exit 1
13
+ fi
14
+ }
15
+
16
+ need curl
17
+ need tar
18
+ need node
19
+ need npm
20
+ if ! command -v go >/dev/null 2>&1 && [ -z "${ELDLOCK_GO_PATH:-}" ]; then
21
+ echo "eldlock install: missing required command: go" >&2
22
+ echo "Set ELDLOCK_GO_PATH=/path/to/go if Go is installed outside PATH." >&2
23
+ exit 1
24
+ fi
25
+
26
+ case "$(uname -s)" in
27
+ Darwin|Linux) ;;
28
+ *)
29
+ echo "eldlock install: only macOS and Linux are supported right now" >&2
30
+ exit 1
31
+ ;;
32
+ esac
33
+
34
+ tmp="$(mktemp -d)"
35
+ cleanup() {
36
+ rm -rf "$tmp"
37
+ }
38
+ trap cleanup EXIT
39
+
40
+ archive="$tmp/eldlock.tar.gz"
41
+ src="$tmp/src"
42
+ mkdir -p "$src"
43
+
44
+ url="https://api.github.com/repos/$repo/tarball/$ref"
45
+ echo "Downloading eldlock from $repo@$ref"
46
+
47
+ if [ -n "${GITHUB_TOKEN:-}" ]; then
48
+ curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" "$url" -o "$archive"
49
+ else
50
+ curl -fsSL "$url" -o "$archive"
51
+ fi
52
+
53
+ tar -xzf "$archive" -C "$src" --strip-components=1
54
+
55
+ cd "$src"
56
+ node scripts/build-production.mjs --install-dir "$install_dir" --bin-dir "$bin_dir"
57
+
58
+ echo ""
59
+ echo "Eldlock installed."
60
+ echo ""
61
+ echo "Add this to your shell profile if it is not already there:"
62
+ echo " export PATH=\"$bin_dir:\$PATH\""
63
+ echo ""
64
+ echo "Then run:"
65
+ echo " eldlock start"
66
+ echo " eldlock init"
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@reegaviljoen/eldlock",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "bin": {
6
+ "eldlock": "bin/eldlock"
7
+ },
8
+ "files": [
9
+ "README.md",
10
+ "install.sh",
11
+ "bin/",
12
+ "docs/",
13
+ "examples/",
14
+ "scripts/build-production.mjs",
15
+ "scripts/postinstall-production.mjs",
16
+ "eldlock-cli/bin/",
17
+ "eldlock-cli/package.json",
18
+ "eldlock-cli/package-lock.json",
19
+ "eldlock-cli/src/",
20
+ "eldlock-cli/tsconfig.json",
21
+ "eldlock-cli/vendor/npm/ansi-regex-6.2.2.tgz",
22
+ "eldlock-cli/vendor/npm/bun-ffi-structs-0.2.2.tgz",
23
+ "eldlock-cli/vendor/npm/diff-9.0.0.tgz",
24
+ "eldlock-cli/vendor/npm/emoji-regex-10.6.0.tgz",
25
+ "eldlock-cli/vendor/npm/esbuild-0.28.0.tgz",
26
+ "eldlock-cli/vendor/npm/esbuild-darwin-arm64-0.28.0.tgz",
27
+ "eldlock-cli/vendor/npm/esbuild-darwin-x64-0.28.0.tgz",
28
+ "eldlock-cli/vendor/npm/esbuild-linux-arm64-0.28.0.tgz",
29
+ "eldlock-cli/vendor/npm/esbuild-linux-x64-0.28.0.tgz",
30
+ "eldlock-cli/vendor/npm/fsevents-2.3.3.tgz",
31
+ "eldlock-cli/vendor/npm/get-east-asian-width-1.6.0.tgz",
32
+ "eldlock-cli/vendor/npm/marked-17.0.1.tgz",
33
+ "eldlock-cli/vendor/npm/opentui-core-0.3.1.tgz",
34
+ "eldlock-cli/vendor/npm/opentui-core-darwin-arm64-0.3.1.tgz",
35
+ "eldlock-cli/vendor/npm/opentui-core-darwin-x64-0.3.1.tgz",
36
+ "eldlock-cli/vendor/npm/opentui-core-linux-arm64-0.3.1.tgz",
37
+ "eldlock-cli/vendor/npm/opentui-core-linux-x64-0.3.1.tgz",
38
+ "eldlock-cli/vendor/npm/string-width-7.2.0.tgz",
39
+ "eldlock-cli/vendor/npm/strip-ansi-7.1.2.tgz",
40
+ "eldlock-cli/vendor/npm/tsx-4.22.4.tgz",
41
+ "eldlock-cli/vendor/npm/types-node-22.19.19.tgz",
42
+ "eldlock-cli/vendor/npm/typescript-5.9.3.tgz",
43
+ "eldlock-cli/vendor/npm/undici-types-6.21.0.tgz",
44
+ "eldlock-cli/vendor/npm/web-tree-sitter-0.25.10.tgz",
45
+ "eldlock-cli/vendor/npm/yoga-layout-3.2.1.tgz",
46
+ "eldlock-server/cmd/",
47
+ "eldlock-server/go.mod",
48
+ "eldlock-server/go.sum",
49
+ "eldlock-server/internal/",
50
+ "eldlock-server/vendor/"
51
+ ],
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "scripts": {
56
+ "build": "npm --prefix eldlock-cli run build --",
57
+ "build:prod": "node scripts/build-production.mjs",
58
+ "build:dev": "npm --prefix eldlock-cli run build:dev --",
59
+ "dev": "npm --prefix eldlock-cli run dev --",
60
+ "install:local": "node scripts/build-production.mjs --install-dir \"$HOME/.local/share/eldlock\" --bin-dir \"$HOME/.local/bin\"",
61
+ "install:vendored": "npm --prefix eldlock-cli run install:vendored",
62
+ "postinstall": "node scripts/postinstall-production.mjs",
63
+ "release": "node scripts/release.mjs",
64
+ "typecheck": "npm --prefix eldlock-cli run typecheck"
65
+ }
66
+ }
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import process from "node:process";
7
+
8
+ const repoDir = path.resolve(import.meta.dirname, "..");
9
+ const serverDir = path.join(repoDir, "eldlock-server");
10
+ const cliDir = path.join(repoDir, "eldlock-cli");
11
+ const installDir = path.resolve(valueAfter("--install-dir") ?? path.join(repoDir, "dist", "eldlock"));
12
+ const binDir = path.resolve(valueAfter("--bin-dir") ?? path.join(installDir, "bin"));
13
+ const runtimeDir = path.join(installDir, "lib", "eldlock-cli");
14
+ const serverBin = path.join(installDir, "bin", "eldlock-server");
15
+ const commandPath = path.join(binDir, "eldlock");
16
+
17
+ assertPlatform();
18
+ const goBin = resolveCommand("go", process.env.ELDLOCK_GO_PATH);
19
+ resolveCommand("node", process.env.ELDLOCK_NODE_PATH);
20
+ const npmBin = resolveCommand("npm", process.env.ELDLOCK_NPM_PATH);
21
+ const env = buildEnv();
22
+
23
+ console.log("==> installing vendored npm dependencies");
24
+ run(npmBin, ["ci", "--offline"], { cwd: cliDir, env });
25
+
26
+ console.log("==> building eldlock-cli");
27
+ run(path.join(cliDir, "node_modules", ".bin", "tsc"), [], { cwd: cliDir, env });
28
+
29
+ console.log("==> building eldlock-server");
30
+ fs.mkdirSync(path.dirname(serverBin), { recursive: true });
31
+ run(goBin, ["build", "-mod=vendor", "-o", serverBin, "./cmd/eldlock-server"], { cwd: serverDir, env });
32
+
33
+ console.log("==> installing runtime files");
34
+ fs.rmSync(runtimeDir, { recursive: true, force: true });
35
+ fs.mkdirSync(runtimeDir, { recursive: true });
36
+ copyDir(path.join(cliDir, "dist"), path.join(runtimeDir, "dist"));
37
+ copyDir(path.join(cliDir, "node_modules"), path.join(runtimeDir, "node_modules"));
38
+ fs.copyFileSync(path.join(cliDir, "package.json"), path.join(runtimeDir, "package.json"));
39
+
40
+ console.log("==> installing eldlock command");
41
+ fs.mkdirSync(binDir, { recursive: true });
42
+ fs.writeFileSync(commandPath, commandScript({ installDir }), { mode: 0o755 });
43
+ fs.chmodSync(serverBin, 0o755);
44
+
45
+ console.log("");
46
+ console.log(`Installed eldlock to ${commandPath}`);
47
+ console.log(`Installed eldlock-server to ${serverBin}`);
48
+ console.log("");
49
+ console.log("Make sure this is on PATH:");
50
+ console.log(` export PATH="${binDir}:$PATH"`);
51
+
52
+ function valueAfter(flag) {
53
+ const index = process.argv.indexOf(flag);
54
+ if (index < 0) return undefined;
55
+ const value = process.argv[index + 1];
56
+ if (!value || value.startsWith("--")) {
57
+ throw new Error(`${flag} requires a value`);
58
+ }
59
+ return value;
60
+ }
61
+
62
+ function assertPlatform() {
63
+ if (!["darwin", "linux"].includes(process.platform)) {
64
+ throw new Error(`production install supports macOS and Linux, got ${process.platform}`);
65
+ }
66
+ }
67
+
68
+ function buildEnv() {
69
+ const env = { ...process.env };
70
+ if (!env.ELDLOCK_PASSKEY_PROVIDER) {
71
+ env.ELDLOCK_PASSKEY_PROVIDER = env.ELDLOCK_STUB_PASSKEY ? "stub" : "fido";
72
+ }
73
+ if (env.ELDLOCK_PASSKEY_PROVIDER === "fido") {
74
+ requireLibfido2();
75
+ env.GOFLAGS = addGoBuildTag(env.GOFLAGS, "fido2");
76
+ env.PKG_CONFIG_PATH = [
77
+ env.PKG_CONFIG_PATH,
78
+ "/opt/homebrew/opt/libfido2/lib/pkgconfig",
79
+ "/opt/homebrew/opt/openssl@3/lib/pkgconfig",
80
+ ].filter(Boolean).join(":");
81
+ }
82
+ return env;
83
+ }
84
+
85
+ function requireLibfido2() {
86
+ const result = spawnSync("pkg-config", ["--exists", "libfido2"], { encoding: "utf8" });
87
+ if (result.status !== 0) {
88
+ const hint = process.platform === "darwin"
89
+ ? "Install libfido2 with: brew install keys-pub/tap/libfido2"
90
+ : "Install libfido2 development headers with your package manager, for example: sudo apt install libfido2-dev pkg-config";
91
+ throw new Error(`libfido2 was not found by pkg-config. ${hint}`);
92
+ }
93
+ }
94
+
95
+ function resolveCommand(name, configured) {
96
+ if (configured) {
97
+ if (isExecutable(configured)) return configured;
98
+ throw new Error(`${name} path is not executable: ${configured}`);
99
+ }
100
+ for (const dir of (process.env.PATH ?? "").split(path.delimiter)) {
101
+ if (!dir) continue;
102
+ const candidate = path.join(dir, name);
103
+ if (isExecutable(candidate)) return candidate;
104
+ }
105
+ if (name === "go") {
106
+ const miseGo = findMiseGo();
107
+ if (miseGo) return miseGo;
108
+ }
109
+ throw new Error(`could not find ${name} on PATH`);
110
+ }
111
+
112
+ function findMiseGo() {
113
+ const installsDir = path.join(process.env.HOME ?? "", ".local", "share", "mise", "installs", "go");
114
+ if (!fs.existsSync(installsDir)) {
115
+ return undefined;
116
+ }
117
+ const versions = fs.readdirSync(installsDir).sort().reverse();
118
+ for (const version of versions) {
119
+ const candidate = path.join(installsDir, version, "bin", "go");
120
+ if (isExecutable(candidate)) {
121
+ return candidate;
122
+ }
123
+ }
124
+ return undefined;
125
+ }
126
+
127
+ function isExecutable(candidate) {
128
+ try {
129
+ fs.accessSync(candidate, fs.constants.X_OK);
130
+ return fs.statSync(candidate).isFile();
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ function addGoBuildTag(goFlags = "", tag) {
137
+ if (goFlags.includes(`-tags=${tag}`) || goFlags.includes(`-tags ${tag}`)) {
138
+ return goFlags;
139
+ }
140
+ return `${goFlags} -tags=${tag}`.trim();
141
+ }
142
+
143
+ function run(command, args, options) {
144
+ const result = spawnSync(command, args, {
145
+ cwd: options.cwd,
146
+ env: options.env,
147
+ stdio: "inherit",
148
+ });
149
+ if (result.status !== 0) {
150
+ throw new Error(`${command} ${args.join(" ")} exited with ${result.status}`);
151
+ }
152
+ }
153
+
154
+ function copyDir(from, to) {
155
+ fs.rmSync(to, { recursive: true, force: true });
156
+ fs.cpSync(from, to, { recursive: true });
157
+ }
158
+
159
+ function commandScript({ installDir }) {
160
+ return `#!/usr/bin/env sh
161
+ set -eu
162
+ export ELDLOCK_SERVER_PATH="${installDir}/bin/eldlock-server"
163
+ export ELDLOCK_STATE_DIR="\${ELDLOCK_STATE_DIR:-$HOME/.eldlock}"
164
+ if [ "\${1:-}" = "" ] || [ "\${1:-}" = "tui" ]; then
165
+ if [ -n "\${ELDLOCK_TUI_RUNTIME:-}" ]; then
166
+ exec "\$ELDLOCK_TUI_RUNTIME" "${installDir}/lib/eldlock-cli/dist/main.js" "\$@"
167
+ fi
168
+ if command -v bun >/dev/null 2>&1; then
169
+ exec bun "${installDir}/lib/eldlock-cli/dist/main.js" "\$@"
170
+ fi
171
+ fi
172
+ if [ -n "\${ELDLOCK_NODE_PATH:-}" ]; then
173
+ exec "\$ELDLOCK_NODE_PATH" "${installDir}/lib/eldlock-cli/dist/main.js" "\$@"
174
+ fi
175
+ exec node "${installDir}/lib/eldlock-cli/dist/main.js" "\$@"
176
+ `;
177
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+
7
+ const packageDir = path.resolve(import.meta.dirname, "..");
8
+ const installDir = path.join(packageDir, ".eldlock-runtime");
9
+ const binDir = path.join(packageDir, "bin");
10
+
11
+ const result = spawnSync(
12
+ "node",
13
+ ["scripts/build-production.mjs", "--install-dir", installDir, "--bin-dir", binDir],
14
+ {
15
+ cwd: packageDir,
16
+ env: process.env,
17
+ stdio: "inherit",
18
+ },
19
+ );
20
+
21
+ if (result.status !== 0) {
22
+ throw new Error(`build production runtime exited with ${result.status}`);
23
+ }