@aion0/forge 0.2.30 → 0.2.32
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/app/login/page.tsx +16 -0
- package/bin/forge-server.mjs +62 -0
- package/components/Dashboard.tsx +7 -3
- package/components/SettingsModal.tsx +3 -0
- package/package.json +1 -1
package/app/login/page.tsx
CHANGED
|
@@ -8,6 +8,7 @@ export default function LoginPage() {
|
|
|
8
8
|
const [sessionCode, setSessionCode] = useState('');
|
|
9
9
|
const [error, setError] = useState('');
|
|
10
10
|
const [isRemote, setIsRemote] = useState(false);
|
|
11
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
11
12
|
|
|
12
13
|
useEffect(() => {
|
|
13
14
|
const host = window.location.hostname;
|
|
@@ -76,6 +77,21 @@ export default function LoginPage() {
|
|
|
76
77
|
Session code is generated when tunnel starts. Use /tunnel_code in Telegram to get it.
|
|
77
78
|
</p>
|
|
78
79
|
)}
|
|
80
|
+
<div className="text-center">
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
onClick={() => setShowHelp(v => !v)}
|
|
84
|
+
className="text-[10px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
85
|
+
>
|
|
86
|
+
Forgot password?
|
|
87
|
+
</button>
|
|
88
|
+
{showHelp && (
|
|
89
|
+
<p className="text-[10px] text-[var(--text-secondary)] mt-1 bg-[var(--bg-tertiary)] rounded p-2">
|
|
90
|
+
Run in terminal:<br />
|
|
91
|
+
<code className="text-[var(--accent)]">forge server start --reset-password</code>
|
|
92
|
+
</p>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
79
95
|
</form>
|
|
80
96
|
|
|
81
97
|
</div>
|
package/bin/forge-server.mjs
CHANGED
|
@@ -59,6 +59,7 @@ const isStop = process.argv.includes('--stop');
|
|
|
59
59
|
const isRestart = process.argv.includes('--restart');
|
|
60
60
|
const isRebuild = process.argv.includes('--rebuild');
|
|
61
61
|
const resetTerminal = process.argv.includes('--reset-terminal');
|
|
62
|
+
const resetPassword = process.argv.includes('--reset-password');
|
|
62
63
|
|
|
63
64
|
const webPort = parseInt(getArg('--port')) || 3000;
|
|
64
65
|
const terminalPort = parseInt(getArg('--terminal-port')) || 3001;
|
|
@@ -89,6 +90,67 @@ process.env.PORT = String(webPort);
|
|
|
89
90
|
process.env.TERMINAL_PORT = String(terminalPort);
|
|
90
91
|
process.env.FORGE_DATA_DIR = DATA_DIR;
|
|
91
92
|
|
|
93
|
+
// ── Password setup (first run or --reset-password) ──
|
|
94
|
+
if (!isStop) {
|
|
95
|
+
const YAML = await import('yaml');
|
|
96
|
+
const settingsFile = join(DATA_DIR, 'settings.yaml');
|
|
97
|
+
let settings = {};
|
|
98
|
+
try { settings = YAML.parse(readFileSync(settingsFile, 'utf-8')) || {}; } catch {}
|
|
99
|
+
|
|
100
|
+
const hasPassword = !!settings.telegramTunnelPassword;
|
|
101
|
+
|
|
102
|
+
if (resetPassword || !hasPassword) {
|
|
103
|
+
if (resetPassword) {
|
|
104
|
+
console.log('[forge] Password reset requested');
|
|
105
|
+
} else {
|
|
106
|
+
console.log('[forge] First run — please set an admin password');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const readline = await import('node:readline');
|
|
110
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
111
|
+
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
112
|
+
|
|
113
|
+
let pw = '';
|
|
114
|
+
while (true) {
|
|
115
|
+
pw = await ask(' Enter admin password: ');
|
|
116
|
+
if (!pw || pw.length < 4) {
|
|
117
|
+
console.log(' Password must be at least 4 characters');
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const confirm = await ask(' Confirm password: ');
|
|
121
|
+
if (pw !== confirm) {
|
|
122
|
+
console.log(' Passwords do not match, try again');
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
rl.close();
|
|
128
|
+
|
|
129
|
+
// Encrypt and save
|
|
130
|
+
const crypto = await import('node:crypto');
|
|
131
|
+
const KEY_FILE = join(DATA_DIR, '.encrypt-key');
|
|
132
|
+
let encKey;
|
|
133
|
+
if (existsSync(KEY_FILE)) {
|
|
134
|
+
encKey = Buffer.from(readFileSync(KEY_FILE, 'utf-8').trim(), 'hex');
|
|
135
|
+
} else {
|
|
136
|
+
encKey = crypto.randomBytes(32);
|
|
137
|
+
writeFileSync(KEY_FILE, encKey.toString('hex'), { mode: 0o600 });
|
|
138
|
+
}
|
|
139
|
+
const iv = crypto.randomBytes(12);
|
|
140
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', encKey, iv);
|
|
141
|
+
const encrypted = Buffer.concat([cipher.update(pw, 'utf-8'), cipher.final()]);
|
|
142
|
+
const tag = cipher.getAuthTag();
|
|
143
|
+
settings.telegramTunnelPassword = `enc:${iv.toString('base64')}.${tag.toString('base64')}.${encrypted.toString('base64')}`;
|
|
144
|
+
if (!existsSync(dirname(settingsFile))) mkdirSync(dirname(settingsFile), { recursive: true });
|
|
145
|
+
writeFileSync(settingsFile, YAML.stringify(settings), 'utf-8');
|
|
146
|
+
console.log('[forge] Admin password saved');
|
|
147
|
+
|
|
148
|
+
if (resetPassword && !isDev && !isBackground && !isRestart) {
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
92
154
|
// ── Reset terminal server (kill port + tmux sessions) ──
|
|
93
155
|
if (resetTerminal) {
|
|
94
156
|
console.log(`[forge] Resetting terminal server (port ${terminalPort})...`);
|
package/components/Dashboard.tsx
CHANGED
|
@@ -239,14 +239,18 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
239
239
|
+ New Task
|
|
240
240
|
</button>
|
|
241
241
|
)}
|
|
242
|
-
{/*
|
|
243
|
-
<TunnelToggle />
|
|
242
|
+
{/* Preview + Tunnel */}
|
|
244
243
|
<button
|
|
245
244
|
onClick={() => setViewMode('preview')}
|
|
246
|
-
className={`text-[10px]
|
|
245
|
+
className={`text-[10px] px-2 py-0.5 border rounded transition-colors ${
|
|
246
|
+
viewMode === 'preview'
|
|
247
|
+
? 'border-[var(--accent)] text-[var(--accent)]'
|
|
248
|
+
: 'border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--text-secondary)]'
|
|
249
|
+
}`}
|
|
247
250
|
>
|
|
248
251
|
Preview
|
|
249
252
|
</button>
|
|
253
|
+
<TunnelToggle />
|
|
250
254
|
{onlineCount.total > 0 && (
|
|
251
255
|
<span className="text-[10px] text-[var(--text-secondary)] flex items-center gap-1" title={`${onlineCount.total} online${onlineCount.remote > 0 ? `, ${onlineCount.remote} remote` : ''}`}>
|
|
252
256
|
<span className="text-green-500">●</span>
|
|
@@ -795,6 +795,9 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
|
|
|
795
795
|
isSet={!!secretStatus.telegramTunnelPassword}
|
|
796
796
|
onEdit={() => setEditingSecret({ field: 'telegramTunnelPassword', label: 'Admin Password' })}
|
|
797
797
|
/>
|
|
798
|
+
<p className="text-[9px] text-[var(--text-secondary)]">
|
|
799
|
+
Forgot? Run: <code className="text-[var(--accent)]">forge server start --reset-password</code>
|
|
800
|
+
</p>
|
|
798
801
|
</div>
|
|
799
802
|
|
|
800
803
|
{/* Actions */}
|
package/package.json
CHANGED