Merge branch 'develop' into release
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "autonomy-studio-launcher",
|
"name": "autonomy-studio-launcher",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "Autonomy Studio v2 Frontend",
|
"description": "Autonomy Studio v2 Frontend",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
+36
-24
@@ -512,6 +512,38 @@ function tailscaleCommandEnv(loginServer: string): NodeJS.ProcessEnv | undefined
|
|||||||
return /^http:\/\//i.test(loginServer.trim()) ? { TS_DEBUG_ALLOW_HTTP: '1' } : undefined
|
return /^http:\/\//i.test(loginServer.trim()) ? { TS_DEBUG_ALLOW_HTTP: '1' } : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLauncherOsUser(): string {
|
||||||
|
const user = (process.env.USER ?? process.env.LOGNAME ?? '').trim()
|
||||||
|
return /^[a-z_][a-z0-9_-]*\$?$/i.test(user) ? user : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureTailscaleOperator(): Promise<CommandResult> {
|
||||||
|
const operator = getLauncherOsUser()
|
||||||
|
if (!operator) {
|
||||||
|
return { ok: false, message: '현재 OS 사용자를 확인하지 못했습니다.' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = ['set', `--operator=${operator}`]
|
||||||
|
try {
|
||||||
|
await runCommand('tailscale', args, 15000)
|
||||||
|
return { ok: true, message: 'Tailscale 제어 권한이 준비되었습니다.' }
|
||||||
|
} catch (error) {
|
||||||
|
if (!isTailscalePermissionError(error)) {
|
||||||
|
return { ok: false, message: commandErrorMessage(error, 'Tailscale 제어 권한 설정 실패') }
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCommand('pkexec', ['/usr/bin/tailscale', ...args], 30000)
|
||||||
|
return { ok: true, message: 'Tailscale 제어 권한이 준비되었습니다.' }
|
||||||
|
} catch (pkexecError) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
message: commandErrorMessage(pkexecError, commandErrorMessage(error, 'Tailscale 제어 권한 설정 실패')),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function installTailscale(): Promise<CommandResult> {
|
async function installTailscale(): Promise<CommandResult> {
|
||||||
const installScript = [
|
const installScript = [
|
||||||
'set -e',
|
'set -e',
|
||||||
@@ -590,13 +622,6 @@ async function resetTailscaleSession(commandEnv?: NodeJS.ProcessEnv): Promise<vo
|
|||||||
await runCommandIfPossible('tailscale', ['down'], 10000, commandEnv)
|
await runCommandIfPossible('tailscale', ['down'], 10000, commandEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetTailscaleSessionWithPkexec(commandEnv?: NodeJS.ProcessEnv): Promise<void> {
|
|
||||||
const loggedOut = await runCommandIfPossible('pkexec', ['/usr/bin/tailscale', 'logout'], 30000, commandEnv)
|
|
||||||
if (!loggedOut) {
|
|
||||||
await runCommandIfPossible('pkexec', ['/usr/bin/tailscale', 'down'], 30000, commandEnv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readTailscaleStatus(): Promise<TailscaleStatusResult> {
|
async function readTailscaleStatus(): Promise<TailscaleStatusResult> {
|
||||||
const attempts: Array<[string, string[]]> = [
|
const attempts: Array<[string, string[]]> = [
|
||||||
['tailscale', ['status', '--json']],
|
['tailscale', ['status', '--json']],
|
||||||
@@ -714,25 +739,11 @@ async function loginTailscale(
|
|||||||
await waitForTailscaleConnected()
|
await waitForTailscaleConnected()
|
||||||
return { ok: true, message: 'Tailscale 연결 완료' }
|
return { ok: true, message: 'Tailscale 연결 완료' }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isTailscalePermissionError(error)) {
|
|
||||||
try {
|
|
||||||
if (forceReset) {
|
|
||||||
await resetTailscaleSessionWithPkexec(commandEnv)
|
|
||||||
}
|
|
||||||
await runCommand('pkexec', ['/usr/bin/tailscale', ...tailscaleArgs], 60000, commandEnv)
|
|
||||||
await waitForTailscaleConnected()
|
|
||||||
return { ok: true, message: 'Tailscale 연결 완료' }
|
|
||||||
} catch (pkexecError) {
|
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
message: commandErrorMessage(pkexecError, commandErrorMessage(error, 'Tailscale 제어 권한이 없습니다.')),
|
message: isTailscalePermissionError(error)
|
||||||
}
|
? commandErrorMessage(error, 'Tailscale 제어 권한이 없습니다. 다시 연결해 주세요.')
|
||||||
}
|
: commandErrorMessage(error, 'Tailscale 연결 실패'),
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
message: commandErrorMessage(error, 'Tailscale 연결 실패'),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1027,6 +1038,7 @@ function createWindow(): void {
|
|||||||
})
|
})
|
||||||
ipcMain.handle('launcher-tailscale-status', () => readTailscaleStatus())
|
ipcMain.handle('launcher-tailscale-status', () => readTailscaleStatus())
|
||||||
ipcMain.handle('launcher-tailscale-install', () => installTailscale())
|
ipcMain.handle('launcher-tailscale-install', () => installTailscale())
|
||||||
|
ipcMain.handle('launcher-tailscale-ensure-operator', () => ensureTailscaleOperator())
|
||||||
ipcMain.handle('launcher-ssh-key-ensure', () => ensureLauncherSshKey())
|
ipcMain.handle('launcher-ssh-key-ensure', () => ensureLauncherSshKey())
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
'launcher-tailscale-login',
|
'launcher-tailscale-login',
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const appWindowMeta = { isAppWindow, appName, url: appUrl }
|
|||||||
const launcherTailscale = {
|
const launcherTailscale = {
|
||||||
status: () => ipcRenderer.invoke('launcher-tailscale-status'),
|
status: () => ipcRenderer.invoke('launcher-tailscale-status'),
|
||||||
install: () => ipcRenderer.invoke('launcher-tailscale-install'),
|
install: () => ipcRenderer.invoke('launcher-tailscale-install'),
|
||||||
|
ensureOperator: () => ipcRenderer.invoke('launcher-tailscale-ensure-operator'),
|
||||||
login: (authKey: string, loginServer: string, hostname?: string, forceReset?: boolean) =>
|
login: (authKey: string, loginServer: string, hostname?: string, forceReset?: boolean) =>
|
||||||
ipcRenderer.invoke('launcher-tailscale-login', authKey, loginServer, hostname, forceReset),
|
ipcRenderer.invoke('launcher-tailscale-login', authKey, loginServer, hostname, forceReset),
|
||||||
logout: () => ipcRenderer.invoke('launcher-tailscale-logout'),
|
logout: () => ipcRenderer.invoke('launcher-tailscale-logout'),
|
||||||
|
|||||||
Vendored
+4
@@ -40,6 +40,10 @@ declare global {
|
|||||||
ok: boolean
|
ok: boolean
|
||||||
message: string
|
message: string
|
||||||
}>
|
}>
|
||||||
|
ensureOperator: () => Promise<{
|
||||||
|
ok: boolean
|
||||||
|
message: string
|
||||||
|
}>
|
||||||
login: (authKey: string, loginServer: string, hostname?: string, forceReset?: boolean) => Promise<{
|
login: (authKey: string, loginServer: string, hostname?: string, forceReset?: boolean) => Promise<{
|
||||||
ok: boolean
|
ok: boolean
|
||||||
message: string
|
message: string
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const LMS_WEB_BASE_URL = `${(
|
|||||||
).replace(/\/+$/, '')}/`
|
).replace(/\/+$/, '')}/`
|
||||||
const NOTICE_READ_STORAGE_KEY = 'atnmy.notice.readSignatures'
|
const NOTICE_READ_STORAGE_KEY = 'atnmy.notice.readSignatures'
|
||||||
const LAST_LAUNCHED_APP_STORAGE_KEY = 'atnmy.launcher.lastLaunchedApp'
|
const LAST_LAUNCHED_APP_STORAGE_KEY = 'atnmy.launcher.lastLaunchedApp'
|
||||||
|
const TAILSCALE_DEVICE_ID_STORAGE_KEY = 'atnmy.launcher.tailscale-device-id'
|
||||||
const IS_DEV = import.meta.env.DEV
|
const IS_DEV = import.meta.env.DEV
|
||||||
const TAILSCALE_BACKGROUND_TIMEOUT_MS = 20_000
|
const TAILSCALE_BACKGROUND_TIMEOUT_MS = 20_000
|
||||||
|
|
||||||
@@ -102,6 +103,21 @@ function isSameTailscaleAccount(statusAccount: string | undefined, loginAccount:
|
|||||||
return normalizeHeadscaleAccount(statusAccount) === normalizeHeadscaleAccount(loginAccount)
|
return normalizeHeadscaleAccount(statusAccount) === normalizeHeadscaleAccount(loginAccount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTailscaleDeviceId(): string {
|
||||||
|
try {
|
||||||
|
const stored = window.localStorage.getItem(TAILSCALE_DEVICE_ID_STORAGE_KEY)?.trim()
|
||||||
|
if (stored && /^[a-z0-9-]{6,24}$/i.test(stored)) return stored
|
||||||
|
|
||||||
|
const next = typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
||||||
|
? crypto.randomUUID().replace(/-/g, '').slice(0, 10)
|
||||||
|
: `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`
|
||||||
|
window.localStorage.setItem(TAILSCALE_DEVICE_ID_STORAGE_KEY, next)
|
||||||
|
return next
|
||||||
|
} catch {
|
||||||
|
return 'launcher-client'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isTailscaleMissingStatus(status: Awaited<ReturnType<NonNullable<typeof window.launcherTailscale>['status']>> | null): boolean {
|
function isTailscaleMissingStatus(status: Awaited<ReturnType<NonNullable<typeof window.launcherTailscale>['status']>> | null): boolean {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
status?.backendState === 'Unavailable' &&
|
status?.backendState === 'Unavailable' &&
|
||||||
@@ -272,11 +288,24 @@ function Dashboard({ authSession, onSessionUpdate, onLogout, onSessionExpired }:
|
|||||||
const sshKey = await window.launcherSshKey?.ensure().catch(() => null)
|
const sshKey = await window.launcherSshKey?.ensure().catch(() => null)
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
|
|
||||||
|
const operator = await launcherTailscale.ensureOperator()
|
||||||
|
if (cancelled) return
|
||||||
|
if (!operator.ok) {
|
||||||
|
setTailscaleIssue({
|
||||||
|
state: 'warning',
|
||||||
|
title: '원격 연결 권한 필요',
|
||||||
|
message: operator.message || 'Tailscale 제어 권한을 설정하지 못했습니다.',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetchWithAuthRefreshRef.current(`${LAUNCHER_API_BASE_URL}/api/provision/session`, {
|
const response = await fetchWithAuthRefreshRef.current(`${LAUNCHER_API_BASE_URL}/api/provision/session`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
hostname: window.navigator.userAgent.includes('Electron') ? 'launcher-client' : 'client',
|
hostname: window.navigator.userAgent.includes('Electron')
|
||||||
|
? `launcher-client-${getTailscaleDeviceId()}`
|
||||||
|
: `client-${getTailscaleDeviceId()}`,
|
||||||
usageType: 'launcher-client',
|
usageType: 'launcher-client',
|
||||||
publicSshKey: sshKey?.publicKey,
|
publicSshKey: sshKey?.publicKey,
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user