블로그 이미지
LanSaid

calendar

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28

Recent Post

Recent Comment

Recent Trackback

Archive

2026. 2. 2. 14:41 AI/AI Orchestration

원격으로 프로젝트 자동화 관련 제어를 위해 slack + n8n 을 이용한 로컬 시스템들의 wol 부팅, 원격 시스템 종료를 구성하는데 있어 windows11 환경의 보안정책 때문에 cmd shutdown 등의 명령으로는 권한 문제로 불가능함에 따라 우회 방법을 구축한 후기를 공유 합니다.

http 웹요청 시 포트는 본인이 원하는 것으로 수정하시면 됩니다.(저는 편의성 8081)

 

 

본 매뉴얼은 윈도우의 강화된 보안 정책(Remote UAC)을 우회하여, 외부(n8n 등)에서 안정적으로 원격 종료를 수행하는 [사이드카 리스너] 방식을 다룹니다.

1. 기술적 배경

  • [UAC 원격 제한]: 윈도우 11 Pro는 네트워크 로그온 사용자에게 시스템 제어 권한을 주지 않습니다.
  • [해결책]: 시스템 내부에서 SYSTEM 권한으로 실행되는 경량 HTTP 리스너를 통해 명령을 로컬에서 실행하게 합니다.

2. Windows 11 Pro 측 설정 (대상 머신)

Step 1: 사이드카 리스너 스크립트 작성

아래 경로에 스크립트를 생성합니다. 윈도우 내부의 SYSTEM 권한으로 실행되어 모든 보안 제약을 우회합니다.

  • 권장 경로: C:\Users\<윈도우_계정_이름>\studio-core\shutdown_listener.ps1
PowerShell
 
# Perspective: Lightweight Control Plane Listener (Port 8081)
# <윈도우_계정_이름> 부분을 실제 환경에 맞게 수정하십시오.

$port = 8081
$listener = [System.Net.HttpListener]::new()
$listener.Prefixes.Add("http://+:$port/shutdown/")

try {
    $listener.Start()
    Write-Host "Control Plane is active on port $port..."
    
    while($listener.IsListening) {
        $context = $listener.GetContext()
        $res = $context.Response
        
        # 신호 수신 즉시 200 OK 응답 후 종료 시퀀스 진입
        $res.StatusCode = 200
        $res.Close()
        
        Write-Host "$(Get-Date): Shutdown signal received. Executing..."
        # 로컬 SYSTEM 권한으로 실행되므로 모든 원격 UAC 제한을 무시합니다.
        shutdown.exe /s /f /t 0
    }
} finally {
    $listener.Stop()
}

Step 2: 부팅 시 자동 실행 등록 (작업 스케줄러)

관리자 권한의 파워쉘에서 실행하여 리스너를 상주 서비스로 만듭니다.

PowerShell
 
# <윈도우_계정_이름> 부분을 실제 환경에 맞게 수정하십시오.
$taskName = "AI_Studio_Shutdown_Listener"
$scriptPath = "C:\Users\<윈도우_계정_이름>\studio-core\shutdown_listener.ps1"

# 기존 작업 삭제 후 재등록 (중복 방지)
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue

$action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File $scriptPath"
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType Service -RunLevel Highest

Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Force
Start-ScheduledTask -TaskName $taskName

3. n8n 워크플로우 설정 (제어 서버)

Step 1: Dynamic Node Selector (JavaScript 전체 버전)

WOL 파이프라인의 무결성을 유지하면서 OS별로 셧다운 방식을 분기합니다.

JavaScript
 
/**
 * AI Orchestrator: Multi-OS Control Plane
 * Feature: OS-based Logic Splitting (Linux SSH vs Windows HTTP)
 */
const inventory = $("Local_Computers").first().json.nodes;
const aiNode = $("Message a model").first();
const aiJson = JSON.parse(aiNode.json.content.parts[0].text);

const targets = (aiJson.target || "").toString().split(',').map(t => t.trim().toUpperCase());
const rawAction = (aiJson.action || "WOL").toUpperCase();

let selected = inventory.filter(node => {
    const nName = (node.name || "").toUpperCase();
    const nRole = (node.role || "").toUpperCase();
    return targets.some(t => {
        if (t === "ALL_COMPUTERS") return true;
        if (t === "DEV_NODES" && (nRole.includes("DEV") || nRole.includes("DEVELOPMENT"))) return true;
        return nName.includes(t) || nRole.includes(t);
    });
});

return selected.map(node => {
    const isLinux = (node.role || "").toUpperCase().includes("DEVELOPMENT");
    let res = { json: { ...node, action: rawAction, msg: aiJson.msg || `${node.name} 시퀀스 가동` } };

    if (/SHUTDOWN/i.test(rawAction)) {
        if (isLinux) {
            res.json.os_type = "LINUX";
            res.json.shutdown_command = "sudo -n /usr/sbin/shutdown -h now";
        } else {
            res.json.os_type = "WINDOWS";
            res.json.shutdown_url = `http://${node.ip}:8081/shutdown/`;
        }
    }
    return res;
});

Step 2: Slack 알림 메시지 통합 수식

SSH와 HTTP 응답을 모두 수용하는 통합 보고 체계입니다.

JavaScript
 
{{ ( ( $json.code === 0 ) || ( $json.statusCode === 200 ) ) ? "✅" : "❌" }} [SHUTDOWN] {{ $("Dynamic Node Selector").item.json.name }}
- 결과: {{ ( ( $json.code === 0 ) || ( $json.statusCode === 200 ) ) ? "명령 전달 성공" : "명령 전달 실패" }}
- 상세: {{ $json.stderr || $json.statusMessage || "정상 응답 수신" }}
posted by LanSaid