@aion0/forge 0.10.81 → 0.10.83
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/RELEASE_NOTES.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Forge v0.10.
|
|
1
|
+
# Forge v0.10.83
|
|
2
2
|
|
|
3
|
-
Released: 2026-06-
|
|
3
|
+
Released: 2026-06-15
|
|
4
4
|
|
|
5
|
-
## Changes since v0.10.
|
|
5
|
+
## Changes since v0.10.82
|
|
6
6
|
|
|
7
7
|
### Other
|
|
8
|
-
- feat(
|
|
8
|
+
- feat(settings): disable admin password change in container mode
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.
|
|
11
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.82...v0.10.83
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
2
3
|
import { loadSettings, loadSettingsMasked, saveSettings, type Settings } from '@/lib/settings';
|
|
3
4
|
import { restartTelegramBot } from '@/lib/init';
|
|
4
5
|
import { SECRET_FIELDS } from '@/lib/crypto';
|
|
5
6
|
import { verifyAdmin } from '@/lib/password';
|
|
6
7
|
|
|
8
|
+
// Container deployments manage the admin password out-of-band (control plane /
|
|
9
|
+
// `make change-password`), which also re-pairs the browser-extension bridge
|
|
10
|
+
// token. Same detection signal as app/api/onboarding/route.ts.
|
|
11
|
+
function inContainer(): boolean {
|
|
12
|
+
if (process.env.FORGE_CONTAINER === '1') return true;
|
|
13
|
+
try { return existsSync('/.dockerenv'); } catch { return false; }
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export async function GET() {
|
|
8
|
-
return NextResponse.json(loadSettingsMasked());
|
|
17
|
+
return NextResponse.json({ ...loadSettingsMasked(), _inContainer: inContainer() });
|
|
9
18
|
}
|
|
10
19
|
|
|
11
20
|
export async function PUT(req: Request) {
|
|
@@ -34,6 +43,21 @@ export async function PUT(req: Request) {
|
|
|
34
43
|
&& !existingSettings.telegramTunnelPassword
|
|
35
44
|
&& !!newValue;
|
|
36
45
|
|
|
46
|
+
// In container mode the admin password is managed out-of-band (the
|
|
47
|
+
// platform rotation also re-pairs the browser extension's bridge token).
|
|
48
|
+
// Changing it here would desync that token and break the agent's browser,
|
|
49
|
+
// with no UI re-pair path. Block rotations — but still allow the very first
|
|
50
|
+
// set (isFirstAdminSet), which happens before any extension pairing.
|
|
51
|
+
if (field === 'telegramTunnelPassword' && !isFirstAdminSet && inContainer()) {
|
|
52
|
+
return NextResponse.json({
|
|
53
|
+
ok: false,
|
|
54
|
+
error:
|
|
55
|
+
'Password is managed by the platform in container mode. Change it from ' +
|
|
56
|
+
'the admin console / self-service portal (it also re-pairs the browser ' +
|
|
57
|
+
'extension), or on the host run: make change-password U=<user>.',
|
|
58
|
+
}, { status: 403 });
|
|
59
|
+
}
|
|
60
|
+
|
|
37
61
|
if (!isFirstAdminSet && !verifyAdmin(adminPassword)) {
|
|
38
62
|
return NextResponse.json({ ok: false, error: 'Wrong password' }, { status: 403 });
|
|
39
63
|
}
|
|
@@ -367,6 +367,9 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
|
|
|
367
367
|
maxConcurrentPipelines: 5,
|
|
368
368
|
});
|
|
369
369
|
const [secretStatus, setSecretStatus] = useState<Record<string, boolean>>({});
|
|
370
|
+
// Container deployments manage the admin password out-of-band (it also
|
|
371
|
+
// re-pairs the browser extension); UI change is disabled when set.
|
|
372
|
+
const [inContainer, setInContainer] = useState(false);
|
|
370
373
|
const [newRoot, setNewRoot] = useState('');
|
|
371
374
|
const [newDocRoot, setNewDocRoot] = useState('');
|
|
372
375
|
const [pickerFor, setPickerFor] = useState<null | 'project' | 'doc'>(null);
|
|
@@ -390,7 +393,9 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
|
|
|
390
393
|
const fetchSettings = useCallback(() => {
|
|
391
394
|
fetch('/api/settings').then(r => r.json()).then((data: Settings) => {
|
|
392
395
|
const status = data._secretStatus || {};
|
|
396
|
+
setInContainer(!!(data as any)._inContainer);
|
|
393
397
|
delete data._secretStatus;
|
|
398
|
+
delete (data as any)._inContainer;
|
|
394
399
|
setSettings(data);
|
|
395
400
|
origSettingsRef.current = JSON.stringify(data);
|
|
396
401
|
setSecretStatus(status);
|
|
@@ -983,14 +988,30 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
|
|
|
983
988
|
<p className="text-[10px] text-[var(--text-secondary)]">
|
|
984
989
|
Used for local login, tunnel start, secret changes, and Telegram commands. Remote login requires admin password + session code (generated on tunnel start).
|
|
985
990
|
</p>
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
991
|
+
{inContainer && secretStatus.telegramTunnelPassword ? (
|
|
992
|
+
<>
|
|
993
|
+
<div className="flex items-center justify-between gap-2 px-2 py-1.5 bg-[var(--bg-tertiary)] border border-[var(--border)] rounded">
|
|
994
|
+
<span className="text-xs font-mono text-[var(--text-secondary)]">••••••••</span>
|
|
995
|
+
<span className="text-[9px] text-[var(--text-secondary)] italic">Managed by platform</span>
|
|
996
|
+
</div>
|
|
997
|
+
<p className="text-[9px] text-[var(--text-secondary)]">
|
|
998
|
+
Password is managed by the platform. Change it from the admin console
|
|
999
|
+
or your self-service portal — it also re-pairs the browser extension.
|
|
1000
|
+
(Host CLI: <code className="text-[var(--accent)]">make change-password U=<user></code>.)
|
|
1001
|
+
</p>
|
|
1002
|
+
</>
|
|
1003
|
+
) : (
|
|
1004
|
+
<>
|
|
1005
|
+
<SecretField
|
|
1006
|
+
label="Admin Password"
|
|
1007
|
+
isSet={!!secretStatus.telegramTunnelPassword}
|
|
1008
|
+
onEdit={() => setEditingSecret({ field: 'telegramTunnelPassword', label: 'Admin Password' })}
|
|
1009
|
+
/>
|
|
1010
|
+
<p className="text-[9px] text-[var(--text-secondary)]">
|
|
1011
|
+
Forgot? Run: <code className="text-[var(--accent)]">forge --reset-password</code>
|
|
1012
|
+
</p>
|
|
1013
|
+
</>
|
|
1014
|
+
)}
|
|
994
1015
|
</div>
|
|
995
1016
|
|
|
996
1017
|
{/* API Key */}
|
|
@@ -130,7 +130,16 @@ async function runHttpProbe(
|
|
|
130
130
|
const t0 = Date.now();
|
|
131
131
|
let res: Response;
|
|
132
132
|
try {
|
|
133
|
-
|
|
133
|
+
// Honour connector-level http.verify_tls — self-signed appliances (Jenkins,
|
|
134
|
+
// NAC, ESXi …) need undici with rejectUnauthorized:false, same as http.ts.
|
|
135
|
+
const fetchInit = { method, headers, body, signal: ctrl.signal };
|
|
136
|
+
if (def.http?.verify_tls === false) {
|
|
137
|
+
const { fetch: undiciFetch, Agent } = await import('undici');
|
|
138
|
+
const dispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
|
139
|
+
res = await undiciFetch(url, { ...fetchInit, dispatcher } as any) as unknown as Response;
|
|
140
|
+
} else {
|
|
141
|
+
res = await fetch(url, fetchInit);
|
|
142
|
+
}
|
|
134
143
|
} catch (e) {
|
|
135
144
|
clearTimeout(timer);
|
|
136
145
|
const err = e as Error & { cause?: unknown };
|
package/package.json
CHANGED