@clazic/urban 0.2.15 → 0.2.17
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/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/install/daemon.js +46 -28
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -64,7 +64,7 @@ async function run() {
|
|
|
64
64
|
console.log(`✅ ${installResult.message}`);
|
|
65
65
|
}
|
|
66
66
|
const result = await startDaemon();
|
|
67
|
-
console.log(result.ok ? `✅ ${result.message}` : `❌
|
|
67
|
+
console.log(result.ok ? `✅ ${result.message}` : `❌ ${result.message}`);
|
|
68
68
|
break;
|
|
69
69
|
}
|
|
70
70
|
case 'stop': {
|
package/src/install/daemon.js
CHANGED
|
@@ -68,35 +68,45 @@ WantedBy=default.target
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* 순수 함수: Windows
|
|
71
|
+
* 순수 함수: Windows start-daemon.ps1 내용 생성
|
|
72
72
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* - oWsh.Run True(동기) + RestartCount 충돌 → 0xC000013A CTRL+BREAK 강제 종료
|
|
76
|
-
* - stdout/stderr 버려져 진단 불가
|
|
77
|
-
*
|
|
78
|
-
* 새 방식: cmd.exe /c + S4U Principal + -Hidden
|
|
79
|
-
* - S4U(Service For User): 비밀번호 없이 비대화형 세션에서 실행 → 콘솔 창 절대 안 뜸
|
|
80
|
-
* - cmd.exe로 URBAN_HOME 환경변수 주입 + 로그 리다이렉트
|
|
81
|
-
* - -Hidden: 작업 자체를 숨김
|
|
73
|
+
* ~/.urban/start-daemon.ps1 로 저장 후 Task Scheduler가 이 파일을 실행함.
|
|
74
|
+
* 직접 편집 가능 → 경로·환경변수 문제 발생 시 육안 확인 가능.
|
|
82
75
|
*/
|
|
83
|
-
export function
|
|
76
|
+
export function buildStartScript({ nodePath, daemonJs, urbanHome, logsDir }) {
|
|
84
77
|
const psQ = (s) => String(s).replace(/'/g, "''");
|
|
85
|
-
|
|
86
78
|
const outLog = join(logsDir, 'out.log');
|
|
87
79
|
const errLog = join(logsDir, 'err.log');
|
|
80
|
+
return `# Urban daemon start script (urban install 자동 생성)
|
|
81
|
+
$env:URBAN_HOME = '${psQ(urbanHome)}'
|
|
82
|
+
New-Item -ItemType Directory -Force -Path '${psQ(logsDir)}' | Out-Null
|
|
83
|
+
Set-Location -Path '${psQ(urbanHome)}'
|
|
84
|
+
& '${psQ(nodePath)}' '${psQ(daemonJs)}' 1>> '${psQ(outLog)}' 2>> '${psQ(errLog)}'
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
88
87
|
|
|
88
|
+
/**
|
|
89
|
+
* 순수 함수: Windows Task Scheduler 등록 PowerShell 커맨드 생성
|
|
90
|
+
*
|
|
91
|
+
* VBScript / cmd.exe 방식 폐기 이유:
|
|
92
|
+
* - cmd.exe: Set-Content -Encoding ASCII가 한글 경로 깨뜨림 (조용히 실패)
|
|
93
|
+
* - VBScript oWsh.Run True(동기) + RestartCount 충돌 → 0xC000013A CTRL+BREAK 강제 종료
|
|
94
|
+
*
|
|
95
|
+
* 새 방식: powershell.exe -WindowStyle Hidden -File + S4U Principal
|
|
96
|
+
* - S4U(Service For User): 비대화형 세션 → 콘솔 창 절대 안 뜸
|
|
97
|
+
* - -WindowStyle Hidden: 이중 방어 (Interactive 폴백에서도 숨김)
|
|
98
|
+
* - 래퍼 스크립트를 파일로 저장 → 직접 편집·디버그 가능
|
|
99
|
+
*/
|
|
100
|
+
export function buildPowerShellCmd({ urbanHome, startScriptPath }) {
|
|
101
|
+
const psQ = (s) => String(s).replace(/'/g, "''");
|
|
89
102
|
return `
|
|
90
|
-
$
|
|
91
|
-
$
|
|
92
|
-
$
|
|
93
|
-
$
|
|
94
|
-
$
|
|
95
|
-
$
|
|
96
|
-
$
|
|
97
|
-
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
|
|
98
|
-
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Limited
|
|
99
|
-
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1) -Hidden
|
|
103
|
+
$scriptPath = '${psQ(startScriptPath)}'
|
|
104
|
+
$urbanHome = '${psQ(urbanHome)}'
|
|
105
|
+
$argument = '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + $scriptPath + '"'
|
|
106
|
+
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $argument -WorkingDirectory $urbanHome
|
|
107
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
|
|
108
|
+
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Limited
|
|
109
|
+
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1) -Hidden
|
|
100
110
|
Unregister-ScheduledTask -TaskName 'Urban' -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
|
101
111
|
Register-ScheduledTask -TaskName 'Urban' -Action $action -Trigger $trigger -Principal $principal -Settings $settings | Out-Null
|
|
102
112
|
`;
|
|
@@ -139,10 +149,15 @@ export async function installDaemon() {
|
|
|
139
149
|
return { ok: true, message: `macOS 데몬 등록 완료: ${plistPath}` };
|
|
140
150
|
} else if (isWindows) {
|
|
141
151
|
// Windows: Task Scheduler (PowerShell)
|
|
142
|
-
|
|
152
|
+
// 1. start-daemon.ps1 생성 — 사용자가 직접 열어 경로 확인 가능
|
|
153
|
+
const startScriptPath = join(URBAN_HOME, 'start-daemon.ps1');
|
|
154
|
+
const startScript = buildStartScript({ nodePath, daemonJs, urbanHome: URBAN_HOME, logsDir });
|
|
155
|
+
writeFileSync(startScriptPath, startScript, 'utf8');
|
|
156
|
+
|
|
157
|
+
// 2. Task Scheduler 등록
|
|
158
|
+
const ps = buildPowerShellCmd({ urbanHome: URBAN_HOME, startScriptPath });
|
|
143
159
|
try {
|
|
144
160
|
// -EncodedCommand: Base64(UTF-16LE) — 따옴표/특수문자 인젝션 방지
|
|
145
|
-
// stdio: pipe로 PowerShell 에러 메시지 캡처해 진단 가능하게
|
|
146
161
|
const encoded = Buffer.from(ps, 'utf16le').toString('base64');
|
|
147
162
|
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand ${encoded}`, {
|
|
148
163
|
shell: false,
|
|
@@ -156,7 +171,7 @@ export async function installDaemon() {
|
|
|
156
171
|
try {
|
|
157
172
|
execSync(`powershell -NoProfile -Command "Start-ScheduledTask -TaskName 'Urban'"`, { shell: true, stdio: 'ignore' });
|
|
158
173
|
} catch { /* 이미 실행 중이면 무시 */ }
|
|
159
|
-
return { ok: true, message: `Windows 데몬 등록 완료 (
|
|
174
|
+
return { ok: true, message: `Windows 데몬 등록 완료 (start-daemon.ps1: ${startScriptPath})` };
|
|
160
175
|
} else if (isLinux) {
|
|
161
176
|
// Linux: systemd user service
|
|
162
177
|
const serviceDir = join(homedir(), '.config', 'systemd', 'user');
|
|
@@ -214,6 +229,8 @@ export async function uninstallDaemon() {
|
|
|
214
229
|
} catch (err) {
|
|
215
230
|
// 작업이 없거나 권한 부족 → 무시
|
|
216
231
|
}
|
|
232
|
+
const startScriptPath = join(URBAN_HOME, 'start-daemon.ps1');
|
|
233
|
+
if (existsSync(startScriptPath)) rmSync(startScriptPath);
|
|
217
234
|
return { ok: true, message: `Windows 데몬 제거 완료` };
|
|
218
235
|
} else if (isLinux) {
|
|
219
236
|
try {
|
|
@@ -331,10 +348,11 @@ export async function statusDaemon() {
|
|
|
331
348
|
}
|
|
332
349
|
} else if (isWindows) {
|
|
333
350
|
try {
|
|
334
|
-
//
|
|
351
|
+
// ?. 연산자는 PS 7+ 전용 — PS 5.1(Windows 기본)에서 파싱 오류 발생
|
|
352
|
+
// if 분기로 대체: 미등록 시 빈 문자열 출력
|
|
335
353
|
const result = execSync(
|
|
336
|
-
`powershell -NoProfile -Command "
|
|
337
|
-
{ encoding: 'utf8', shell: true, stdio: 'pipe' }
|
|
354
|
+
`powershell -NoProfile -Command "$t = Get-ScheduledTask -TaskName 'Urban' -ErrorAction SilentlyContinue; if ($t) { $t.State } else { '' }"`,
|
|
355
|
+
{ encoding: 'utf8', shell: true, stdio: ['ignore', 'pipe', 'pipe'] }
|
|
338
356
|
);
|
|
339
357
|
const state = result.trim();
|
|
340
358
|
if (!state) return { running: false, message: '데몬 미등록' };
|