@striae-org/striae 5.1.1 → 5.2.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.
- package/.env.example +20 -1
- package/app/utils/data/permissions.ts +4 -2
- package/package.json +4 -4
- package/scripts/deploy-config/modules/env-utils.sh +322 -0
- package/scripts/deploy-config/modules/keys.sh +404 -0
- package/scripts/deploy-config/modules/prompt.sh +372 -0
- package/scripts/deploy-config/modules/scaffolding.sh +336 -0
- package/scripts/deploy-config/modules/validation.sh +365 -0
- package/scripts/deploy-config.sh +47 -1572
- package/scripts/deploy-worker-secrets.sh +100 -5
- package/worker-configuration.d.ts +6 -3
- package/workers/audit-worker/package.json +1 -1
- package/workers/audit-worker/src/audit-worker.example.ts +188 -6
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +1 -1
- package/workers/data-worker/src/data-worker.example.ts +344 -32
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/package.json +1 -1
- package/workers/image-worker/src/image-worker.example.ts +190 -5
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/package.json +1 -1
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/package.json +1 -1
- package/workers/pdf-worker/src/pdf-worker.example.ts +0 -1
- package/workers/pdf-worker/wrangler.jsonc.example +1 -5
- package/workers/user-worker/package.json +17 -17
- package/workers/user-worker/src/encryption-utils.ts +244 -0
- package/workers/user-worker/src/user-worker.example.ts +333 -31
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
package/.env.example
CHANGED
|
@@ -54,6 +54,17 @@ KEYS_AUTH=your_custom_keys_auth_token_here
|
|
|
54
54
|
USER_WORKER_NAME=your_user_worker_name_here
|
|
55
55
|
USER_WORKER_DOMAIN=your_user_worker_domain_here
|
|
56
56
|
KV_STORE_ID=your_kv_store_id_here
|
|
57
|
+
USER_KV_ENCRYPTION_PRIVATE_KEY=your_user_kv_encryption_private_key_here
|
|
58
|
+
USER_KV_ENCRYPTION_KEY_ID=your_user_kv_encryption_key_id_here
|
|
59
|
+
USER_KV_ENCRYPTION_PUBLIC_KEY=your_user_kv_encryption_public_key_here
|
|
60
|
+
# Optional write toggle for USER_DB mutation endpoints.
|
|
61
|
+
# true (default): require USER_KV_ENCRYPTION_PUBLIC_KEY and USER_KV_ENCRYPTION_KEY_ID for encrypt-on-write.
|
|
62
|
+
# false: allow read-only deployments using private key material (legacy key or key registry) without write-path keys.
|
|
63
|
+
USER_KV_WRITE_ENDPOINTS_ENABLED=true
|
|
64
|
+
# Optional key registry for rotation-safe USER_DB reads.
|
|
65
|
+
# JSON shape: {"activeKeyId":"kid_current","keys":{"kid_current":"-----BEGIN PRIVATE KEY-----\\n...","kid_previous":"-----BEGIN PRIVATE KEY-----\\n..."}}
|
|
66
|
+
USER_KV_ENCRYPTION_KEYS_JSON='{"activeKeyId":"your_user_kv_active_encryption_key_id_here","keys":{"your_user_kv_active_encryption_key_id_here":"your_user_kv_encryption_private_key_here"}}'
|
|
67
|
+
USER_KV_ENCRYPTION_ACTIVE_KEY_ID=your_user_kv_active_encryption_key_id_here
|
|
57
68
|
|
|
58
69
|
# ================================
|
|
59
70
|
# DATA WORKER ENVIRONMENT VARIABLES
|
|
@@ -69,10 +80,18 @@ MANIFEST_SIGNING_PUBLIC_KEY=your_manifest_signing_public_key_here
|
|
|
69
80
|
EXPORT_ENCRYPTION_PRIVATE_KEY=your_export_encryption_private_key_here
|
|
70
81
|
EXPORT_ENCRYPTION_KEY_ID=your_export_encryption_key_id_here
|
|
71
82
|
EXPORT_ENCRYPTION_PUBLIC_KEY=your_export_encryption_public_key_here
|
|
83
|
+
# Optional key registry for export decrypt compatibility.
|
|
84
|
+
# JSON shape: {"activeKeyId":"kid_current","keys":{"kid_current":"-----BEGIN PRIVATE KEY-----\\n...","kid_previous":"-----BEGIN PRIVATE KEY-----\\n..."}}
|
|
85
|
+
EXPORT_ENCRYPTION_KEYS_JSON='{"activeKeyId":"your_export_encryption_active_key_id_here","keys":{"your_export_encryption_active_key_id_here":"your_export_encryption_private_key_here"}}'
|
|
86
|
+
EXPORT_ENCRYPTION_ACTIVE_KEY_ID=your_export_encryption_active_key_id_here
|
|
87
|
+
DATA_AT_REST_ENCRYPTION_ENABLED=true
|
|
72
88
|
DATA_AT_REST_ENCRYPTION_PRIVATE_KEY=your_data_at_rest_encryption_private_key_here
|
|
73
89
|
DATA_AT_REST_ENCRYPTION_KEY_ID=your_data_at_rest_encryption_key_id_here
|
|
74
90
|
DATA_AT_REST_ENCRYPTION_PUBLIC_KEY=your_data_at_rest_encryption_public_key_here
|
|
75
|
-
|
|
91
|
+
# Optional key registry for data/files/audit decryption compatibility.
|
|
92
|
+
# JSON shape: {"activeKeyId":"kid_current","keys":{"kid_current":"-----BEGIN PRIVATE KEY-----\\n...","kid_previous":"-----BEGIN PRIVATE KEY-----\\n..."}}
|
|
93
|
+
DATA_AT_REST_ENCRYPTION_KEYS_JSON='{"activeKeyId":"your_data_at_rest_active_encryption_key_id_here","keys":{"your_data_at_rest_active_encryption_key_id_here":"your_data_at_rest_encryption_private_key_here"}}'
|
|
94
|
+
DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID=your_data_at_rest_active_encryption_key_id_here
|
|
76
95
|
|
|
77
96
|
# ================================
|
|
78
97
|
# AUDIT WORKER ENVIRONMENT VARIABLES
|
|
@@ -42,8 +42,10 @@ export const getUserData = async (user: User): Promise<UserData | null> => {
|
|
|
42
42
|
if (response.status === 404) {
|
|
43
43
|
return null; // User not found
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
|
|
46
|
+
const responseBody = await response.text().catch(() => '');
|
|
47
|
+
const detail = responseBody ? `: ${responseBody}` : '';
|
|
48
|
+
throw new Error(`Failed to fetch user data (${response.status} ${response.statusText})${detail}`);
|
|
47
49
|
} catch (error) {
|
|
48
50
|
console.error('Error fetching user data:', error);
|
|
49
51
|
throw error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@striae-org/striae",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Striae is a specialized, cloud-native platform designed to streamline forensic firearms identification by providing an intuitive environment for digital comparison image annotation, authenticated confirmations, and automated report generation.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"workers/*/src/*.example.ts",
|
|
55
55
|
"workers/*/src/*.example.js",
|
|
56
56
|
"workers/*/src/*.ts",
|
|
57
|
-
"workers/pdf-worker/scripts/*.js",
|
|
57
|
+
"workers/pdf-worker/scripts/*.js",
|
|
58
58
|
"!workers/*/src/*worker.ts",
|
|
59
59
|
"workers/pdf-worker/src/assets/generated-assets.example.ts",
|
|
60
60
|
"workers/pdf-worker/src/formats/format-striae.ts",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"vite.config.ts",
|
|
68
68
|
"worker-configuration.d.ts",
|
|
69
69
|
"wrangler.toml.example",
|
|
70
|
-
"LICENSE"
|
|
70
|
+
"LICENSE"
|
|
71
71
|
],
|
|
72
72
|
"sideEffects": false,
|
|
73
73
|
"type": "module",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"deploy-workers:image": "cd workers/image-worker && npm run deploy",
|
|
107
107
|
"deploy-workers:keys": "cd workers/keys-worker && npm run deploy",
|
|
108
108
|
"deploy-workers:pdf": "cd workers/pdf-worker && npm run deploy",
|
|
109
|
-
"deploy-workers:user": "cd workers/user-worker && npm run deploy"
|
|
109
|
+
"deploy-workers:user": "cd workers/user-worker && npm run deploy"
|
|
110
110
|
},
|
|
111
111
|
"dependencies": {
|
|
112
112
|
"@react-router/cloudflare": "^7.13.2",
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
escape_for_sed_pattern() {
|
|
4
|
+
printf '%s' "$1" | sed -e 's/[][\\.^$*+?{}|()]/\\&/g'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dedupe_env_var_entries() {
|
|
8
|
+
local var_name=$1
|
|
9
|
+
local expected_count=1
|
|
10
|
+
local escaped_var_name
|
|
11
|
+
|
|
12
|
+
escaped_var_name=$(escape_for_sed_pattern "$var_name")
|
|
13
|
+
|
|
14
|
+
if [ -f ".env.example" ]; then
|
|
15
|
+
expected_count=$(grep -c "^$escaped_var_name=" .env.example || true)
|
|
16
|
+
|
|
17
|
+
if [ "$expected_count" -lt 1 ]; then
|
|
18
|
+
expected_count=1
|
|
19
|
+
fi
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
awk -v key="$var_name" -v keep="$expected_count" '
|
|
23
|
+
BEGIN { seen = 0 }
|
|
24
|
+
{
|
|
25
|
+
if (index($0, key "=") == 1) {
|
|
26
|
+
seen++
|
|
27
|
+
|
|
28
|
+
if (seen > keep) {
|
|
29
|
+
next
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
print
|
|
33
|
+
}
|
|
34
|
+
' .env > .env.tmp && mv .env.tmp .env
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
normalize_domain_value() {
|
|
38
|
+
local domain="$1"
|
|
39
|
+
|
|
40
|
+
domain=$(printf '%s' "$domain" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
|
41
|
+
domain="${domain#http://}"
|
|
42
|
+
domain="${domain#https://}"
|
|
43
|
+
domain="${domain%/}"
|
|
44
|
+
|
|
45
|
+
printf '%s' "$domain"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
normalize_worker_label_value() {
|
|
49
|
+
local label="$1"
|
|
50
|
+
|
|
51
|
+
label=$(normalize_domain_value "$label")
|
|
52
|
+
label="${label#.}"
|
|
53
|
+
label="${label%.}"
|
|
54
|
+
label=$(printf '%s' "$label" | tr '[:upper:]' '[:lower:]')
|
|
55
|
+
|
|
56
|
+
printf '%s' "$label"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
normalize_worker_subdomain_value() {
|
|
60
|
+
local subdomain="$1"
|
|
61
|
+
|
|
62
|
+
subdomain=$(normalize_domain_value "$subdomain")
|
|
63
|
+
subdomain="${subdomain#.}"
|
|
64
|
+
subdomain="${subdomain%.}"
|
|
65
|
+
subdomain=$(printf '%s' "$subdomain" | tr '[:upper:]' '[:lower:]')
|
|
66
|
+
|
|
67
|
+
printf '%s' "$subdomain"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
is_valid_worker_label() {
|
|
71
|
+
local label="$1"
|
|
72
|
+
|
|
73
|
+
[[ "$label" =~ ^[a-z0-9-]+$ ]]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
is_valid_worker_subdomain() {
|
|
77
|
+
local subdomain="$1"
|
|
78
|
+
|
|
79
|
+
[[ "$subdomain" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$ ]]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
strip_carriage_returns() {
|
|
83
|
+
printf '%s' "$1" | tr -d '\r'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
read_env_var_from_file() {
|
|
87
|
+
local env_file=$1
|
|
88
|
+
local var_name=$2
|
|
89
|
+
|
|
90
|
+
if [ ! -f "$env_file" ]; then
|
|
91
|
+
return 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
awk -v key="$var_name" '
|
|
95
|
+
index($0, key "=") == 1 {
|
|
96
|
+
value = substr($0, length(key) + 2)
|
|
97
|
+
}
|
|
98
|
+
END {
|
|
99
|
+
if (value != "") {
|
|
100
|
+
gsub(/\r/, "", value)
|
|
101
|
+
gsub(/^"/, "", value)
|
|
102
|
+
gsub(/"$/, "", value)
|
|
103
|
+
print value
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
' "$env_file"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
resolve_existing_domain_value() {
|
|
110
|
+
local var_name=$1
|
|
111
|
+
local current_value=$2
|
|
112
|
+
local preserved_value=""
|
|
113
|
+
|
|
114
|
+
current_value=$(normalize_domain_value "$current_value")
|
|
115
|
+
|
|
116
|
+
if [ "$current_value" = "$var_name" ]; then
|
|
117
|
+
current_value=""
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [ -n "$current_value" ] && ! is_placeholder "$current_value"; then
|
|
121
|
+
printf '%s' "$current_value"
|
|
122
|
+
return 0
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [ -n "$preserved_domain_env_file" ] && [ -f "$preserved_domain_env_file" ]; then
|
|
126
|
+
preserved_value=$(read_env_var_from_file "$preserved_domain_env_file" "$var_name")
|
|
127
|
+
preserved_value=$(normalize_domain_value "$preserved_value")
|
|
128
|
+
|
|
129
|
+
if [ "$preserved_value" = "$var_name" ]; then
|
|
130
|
+
preserved_value=""
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
if [ -n "$preserved_value" ] && ! is_placeholder "$preserved_value"; then
|
|
134
|
+
printf '%s' "$preserved_value"
|
|
135
|
+
return 0
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
printf '%s' "$current_value"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
restore_env_var_from_backup_if_missing() {
|
|
143
|
+
local var_name=$1
|
|
144
|
+
local current_value="${!var_name}"
|
|
145
|
+
local preserved_value=""
|
|
146
|
+
|
|
147
|
+
if [ "$update_env" != "true" ]; then
|
|
148
|
+
return 0
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
if [ -z "$preserved_domain_env_file" ] || [ ! -f "$preserved_domain_env_file" ]; then
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
current_value=$(strip_carriage_returns "$current_value")
|
|
156
|
+
|
|
157
|
+
if [ -n "$current_value" ] && ! is_placeholder "$current_value"; then
|
|
158
|
+
return 0
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
preserved_value=$(read_env_var_from_file "$preserved_domain_env_file" "$var_name")
|
|
162
|
+
preserved_value=$(strip_carriage_returns "$preserved_value")
|
|
163
|
+
|
|
164
|
+
if [ -z "$preserved_value" ] || is_placeholder "$preserved_value"; then
|
|
165
|
+
return 0
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
printf -v "$var_name" '%s' "$preserved_value"
|
|
169
|
+
export "$var_name"
|
|
170
|
+
write_env_var "$var_name" "$preserved_value"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
confirm_key_pair_regeneration() {
|
|
174
|
+
local key_pair_label=$1
|
|
175
|
+
local impact_warning=$2
|
|
176
|
+
local regenerate_choice=""
|
|
177
|
+
|
|
178
|
+
if [ "$force_rotate_keys" = "true" ]; then
|
|
179
|
+
echo -e "${YELLOW}⚠️ Auto-confirmed regeneration for $key_pair_label key pair (--force-rotate-keys).${NC}"
|
|
180
|
+
return 0
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
echo -e "${GREEN}Current $key_pair_label key pair: [HIDDEN]${NC}"
|
|
184
|
+
|
|
185
|
+
if [ -n "$impact_warning" ]; then
|
|
186
|
+
echo -e "${YELLOW}⚠️ $impact_warning${NC}"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
read -p "Regenerate $key_pair_label key pair? (press Enter to keep current, or type 'y' to regenerate): " regenerate_choice
|
|
190
|
+
regenerate_choice=$(strip_carriage_returns "$regenerate_choice")
|
|
191
|
+
|
|
192
|
+
if [ "$regenerate_choice" = "y" ] || [ "$regenerate_choice" = "Y" ]; then
|
|
193
|
+
return 0
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
return 1
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
generate_worker_subdomain_label() {
|
|
200
|
+
node -e "const { randomInt } = require('crypto'); const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'; let value = ''; for (let index = 0; index < 10; index += 1) { value += alphabet[randomInt(alphabet.length)]; } process.stdout.write(value);" 2>/dev/null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
compose_worker_domain() {
|
|
204
|
+
local worker_name=$1
|
|
205
|
+
local worker_subdomain=$2
|
|
206
|
+
|
|
207
|
+
worker_name=$(normalize_worker_label_value "$worker_name")
|
|
208
|
+
worker_subdomain=$(normalize_worker_subdomain_value "$worker_subdomain")
|
|
209
|
+
|
|
210
|
+
if [ -z "$worker_name" ] || [ -z "$worker_subdomain" ]; then
|
|
211
|
+
return 1
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
if ! is_valid_worker_label "$worker_name" || ! is_valid_worker_subdomain "$worker_subdomain"; then
|
|
215
|
+
return 1
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
printf '%s.%s' "$worker_name" "$worker_subdomain"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
infer_worker_subdomain_from_domain() {
|
|
222
|
+
local worker_name=$1
|
|
223
|
+
local worker_domain=$2
|
|
224
|
+
local worker_subdomain=""
|
|
225
|
+
|
|
226
|
+
worker_name=$(normalize_worker_label_value "$worker_name")
|
|
227
|
+
worker_domain=$(normalize_domain_value "$worker_domain")
|
|
228
|
+
worker_domain=$(printf '%s' "$worker_domain" | tr '[:upper:]' '[:lower:]')
|
|
229
|
+
|
|
230
|
+
if [ -z "$worker_name" ] || [ -z "$worker_domain" ] || is_placeholder "$worker_name" || is_placeholder "$worker_domain"; then
|
|
231
|
+
printf '%s' ""
|
|
232
|
+
return 0
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
case "$worker_domain" in
|
|
236
|
+
"$worker_name".*)
|
|
237
|
+
worker_subdomain="${worker_domain#${worker_name}.}"
|
|
238
|
+
worker_subdomain=$(normalize_worker_subdomain_value "$worker_subdomain")
|
|
239
|
+
|
|
240
|
+
if is_valid_worker_subdomain "$worker_subdomain"; then
|
|
241
|
+
printf '%s' "$worker_subdomain"
|
|
242
|
+
return 0
|
|
243
|
+
fi
|
|
244
|
+
;;
|
|
245
|
+
esac
|
|
246
|
+
|
|
247
|
+
printf '%s' ""
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
write_env_var() {
|
|
251
|
+
local var_name=$1
|
|
252
|
+
local var_value=$2
|
|
253
|
+
local env_file_value="$var_value"
|
|
254
|
+
|
|
255
|
+
var_value=$(strip_carriage_returns "$var_value")
|
|
256
|
+
env_file_value="$var_value"
|
|
257
|
+
|
|
258
|
+
if [ "$var_name" = "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" ] || [ "$var_name" = "MANIFEST_SIGNING_PRIVATE_KEY" ] || [ "$var_name" = "MANIFEST_SIGNING_PUBLIC_KEY" ] || [ "$var_name" = "EXPORT_ENCRYPTION_PRIVATE_KEY" ] || [ "$var_name" = "EXPORT_ENCRYPTION_PUBLIC_KEY" ] || [ "$var_name" = "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" ] || [ "$var_name" = "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" ] || [ "$var_name" = "USER_KV_ENCRYPTION_PRIVATE_KEY" ] || [ "$var_name" = "USER_KV_ENCRYPTION_PUBLIC_KEY" ] || [ "$var_name" = "EXPORT_ENCRYPTION_KEYS_JSON" ] || [ "$var_name" = "DATA_AT_REST_ENCRYPTION_KEYS_JSON" ] || [ "$var_name" = "USER_KV_ENCRYPTION_KEYS_JSON" ]; then
|
|
259
|
+
# Store as a quoted string so sourced .env preserves escaped newline markers (\n)
|
|
260
|
+
env_file_value=${env_file_value//\"/\\\"}
|
|
261
|
+
env_file_value="\"$env_file_value\""
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
local escaped_var_name
|
|
265
|
+
local replacement_line
|
|
266
|
+
escaped_var_name=$(escape_for_sed_pattern "$var_name")
|
|
267
|
+
replacement_line=$(escape_for_sed_replacement "$var_name=$env_file_value")
|
|
268
|
+
|
|
269
|
+
if grep -q "^$escaped_var_name=" .env; then
|
|
270
|
+
# Replace all occurrences so intentional duplicates in .env.example stay in sync.
|
|
271
|
+
sed -i "s|^$escaped_var_name=.*|$replacement_line|g" .env
|
|
272
|
+
dedupe_env_var_entries "$var_name"
|
|
273
|
+
else
|
|
274
|
+
echo "$var_name=$env_file_value" >> .env
|
|
275
|
+
fi
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
update_private_key_registry() {
|
|
279
|
+
local registry_var_name=$1
|
|
280
|
+
local active_key_var_name=$2
|
|
281
|
+
local current_key_id=$3
|
|
282
|
+
local private_key_value=$4
|
|
283
|
+
local registry_label=$5
|
|
284
|
+
local existing_registry_json=""
|
|
285
|
+
local updated_registry_json=""
|
|
286
|
+
local registry_entry_count=""
|
|
287
|
+
|
|
288
|
+
if [ -z "$registry_var_name" ] || [ -z "$active_key_var_name" ] || [ -z "$current_key_id" ] || [ -z "$private_key_value" ]; then
|
|
289
|
+
echo -e "${YELLOW}⚠️ Skipping $registry_label key registry update due to missing inputs${NC}"
|
|
290
|
+
return 0
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
existing_registry_json="${!registry_var_name}"
|
|
294
|
+
existing_registry_json=$(strip_carriage_returns "$existing_registry_json")
|
|
295
|
+
|
|
296
|
+
if [ -z "$existing_registry_json" ] || is_placeholder "$existing_registry_json"; then
|
|
297
|
+
existing_registry_json="{}"
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
updated_registry_json=$(node -e "const raw = process.argv[1] || '{}'; const keyId = process.argv[2] || ''; const privateKey = process.argv[3] || ''; if (!keyId || !privateKey) process.exit(1); const normalized = { activeKeyId: null, keys: {} }; try { const parsed = JSON.parse(raw); if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { if (parsed.keys && typeof parsed.keys === 'object' && !Array.isArray(parsed.keys)) { normalized.keys = Object.fromEntries(Object.entries(parsed.keys).filter(([id, pem]) => typeof id === 'string' && id.trim().length > 0 && typeof pem === 'string' && pem.trim().length > 0)); if (typeof parsed.activeKeyId === 'string' && parsed.activeKeyId.trim().length > 0) normalized.activeKeyId = parsed.activeKeyId.trim(); } else { normalized.keys = Object.fromEntries(Object.entries(parsed).filter(([id, pem]) => id !== 'activeKeyId' && id !== 'keys' && typeof id === 'string' && id.trim().length > 0 && typeof pem === 'string' && pem.trim().length > 0)); if (typeof parsed.activeKeyId === 'string' && parsed.activeKeyId.trim().length > 0) normalized.activeKeyId = parsed.activeKeyId.trim(); } } } catch (_) {} normalized.keys[keyId] = privateKey; normalized.activeKeyId = keyId; process.stdout.write(JSON.stringify(normalized));" "$existing_registry_json" "$current_key_id" "$private_key_value" 2>/dev/null || true)
|
|
301
|
+
|
|
302
|
+
if [ -z "$updated_registry_json" ]; then
|
|
303
|
+
echo -e "${RED}❌ Error: Failed to update $registry_label key registry JSON${NC}"
|
|
304
|
+
exit 1
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
printf -v "$registry_var_name" '%s' "$updated_registry_json"
|
|
308
|
+
export "$registry_var_name"
|
|
309
|
+
write_env_var "$registry_var_name" "$updated_registry_json"
|
|
310
|
+
|
|
311
|
+
printf -v "$active_key_var_name" '%s' "$current_key_id"
|
|
312
|
+
export "$active_key_var_name"
|
|
313
|
+
write_env_var "$active_key_var_name" "$current_key_id"
|
|
314
|
+
|
|
315
|
+
registry_entry_count=$(node -e "const raw = process.argv[1] || '{}'; try { const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { process.stdout.write('0'); process.exit(0); } const keys = parsed.keys && typeof parsed.keys === 'object' && !Array.isArray(parsed.keys) ? parsed.keys : parsed; const count = Object.entries(keys).filter(([id, pem]) => id !== 'activeKeyId' && id !== 'keys' && typeof id === 'string' && id.trim().length > 0 && typeof pem === 'string' && pem.trim().length > 0).length; process.stdout.write(String(count)); } catch (_) { process.stdout.write('0'); }" "$updated_registry_json")
|
|
316
|
+
echo -e "${GREEN}✅ Updated $registry_label key registry ($registry_entry_count key IDs tracked)${NC}"
|
|
317
|
+
echo -e "${GREEN}✅ $active_key_var_name: $current_key_id${NC}"
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
escape_for_sed_replacement() {
|
|
321
|
+
printf '%s' "$1" | sed -e 's/[&|\\]/\\&/g'
|
|
322
|
+
}
|