본문으로 건너뛰기

업무 모듈 배치 스크립트 만들기

도메인 업무에 필요한 나만의 업무 모듈 (예: domain_business)을 개발할 때 사용하는 프로그램 실행과 관리에 대한 task.batrun, handstack 명령을 작성하는 방법을 설명합니다.

task.bat는 단순히 프로그램 하나를 실행하는 파일이 아니라, 아래 반복적인 여러 명령을 묶어서 처리하는 자동화를 위한 작업 스크립트입니다.

업무 모듈을 개발하다 보면 필연적으로 2가지 작업이 반복됩니다.

  • run: 이미 준비된 ACK 런타임을 현재 domain_business 설정으로 실행
  • handstack: HandStack 소스를 다시 빌드해서 로컬 런타임을 갱신하고, 그 위에 현재 domain_business 모듈을 다시 빌드

왜 배치 스크립트를 쓰나?

개발자 입장에서는 "명령 몇 개를 직접 치면 되지 않나?"라고 생각할 수 있습니다. 하지만 실제 실행 절차는 생각보다 순서가 중요하고, 단순 반복적인 작업을 매번 손으로 입력하면 무언가 빠뜨리기 쉽고, 시간도 더 걸립니다.

배치 스크립트를 쓰는 이유는 아래와 같습니다.

  • 여러 명령을 항상 같은 순서로 빠르게 실행할 수 있다.
  • 런타임 경로, 설정 파일, 복사 대상 경로를 사람이 직접 기억하지 않아도 된다.
  • run, handstack처럼 자주 쓰는 작업을 짧은 명령으로 반복할 수 있다.
  • .csproj 프로젝트 빌드 스크립트와 통합하기 쉽다.

task.bat는 "복잡한 로컬 실행 절차를 한 번에 묶어 두는 작업 버튼"이라고 보면 됩니다.

task.bat 전체 스크립트 예시

현재 문서가 설명하는 실제 배치 파일 스크립트는 아래와 같습니다.

@echo off
setlocal EnableDelayedExpansion
chcp 65001

set TASK_COMMAND=%1
if "%TASK_COMMAND%" == "" set TASK_COMMAND=

set TASK_SETTING=%2
if "%TASK_SETTING%" == "" set TASK_SETTING=localhost

set TASK_ARGUMENTS=%3
if "%TASK_ARGUMENTS%" == "" set TASK_ARGUMENTS=

set "MODULE_ID=!TASK_SETTING!"
if "!MODULE_ID!"=="localhost" set "MODULE_ID=handstack"

set RUNTIME_HOME=C:/projects/ack-runtimes/domain_business/handstack
set WORKING_PATH=%cd%
set RUNTIME_ACK=%RUNTIME_HOME%/app/ack.exe
set RUNTIME_CLI=%RUNTIME_HOME%/app/cli/handstack/handstack
set HANDSTACK_ACK=%HANDSTACK_HOME%/app/ack.exe
set HANDSTACK_CLI=%HANDSTACK_HOME%/app/cli/handstack/handstack

echo WORKING_PATH: %WORKING_PATH%
echo RUNTIME_HOME: %RUNTIME_HOME%
echo RUNTIME_CLI: %RUNTIME_CLI%
echo HANDSTACK_SRC: %HANDSTACK_SRC%
echo HANDSTACK_HOME: %HANDSTACK_HOME%
echo HANDSTACK_CLI: %HANDSTACK_CLI%
echo TASK_COMMAND: %TASK_COMMAND%
echo TASK_SETTING: %TASK_SETTING%
echo MODULE_ID: %MODULE_ID%

if "%TASK_COMMAND%"=="run" (
%RUNTIME_CLI% configuration --ack=!RUNTIME_ACK! --appsettings=%WORKING_PATH%/Settings/ack.%TASK_SETTING%.json

for %%I in ("%RUNTIME_HOME%\..") do set "PARENT_PATH=%%~fI"
copy /Y "%WORKING_PATH%\Settings\synconfigs\ack.%TASK_SETTING%.json" "!PARENT_PATH!\%MODULE_ID%\modules\arha\wwwroot\arha\syn.config.json"
%RUNTIME_ACK%
)

if "%TASK_COMMAND%"=="handstack" (
cd /d %HANDSTACK_SRC%
publish.bat win build Debug x64 "C:\projects\ack-runtimes\domain_business"
cd /d "C:\projects\ack-runtimes\domain_business\handstack"
install.bat
cd /d %WORKING_PATH%
dotnet build
)

위 스크립트는 Windows 에서 실행하는 개발 환경을 전제로 작성되었습니다. 실제로 무슨 일을 하는지 순서대로 풀어보죠.

먼저 큰 그림부터

이 모듈은 혼자 실행되지 않습니다. 실제로는 아래 구조를 전제로 합니다.

  1. HandStack 소스 코드가 호스트 내에 이미 있다.
  2. HandStack을 빌드하면 로컬 실행용 런타임 폴더가 만들어진다.
  3. domain_business은 그 런타임의 modules/domain_business 아래에 복사되어 올라간다.
  4. 마지막으로 ack.exe를 실행하면 HandStack + domain_business 모듈이 함께 구동된다.

즉, task.bat는 "현재 domain_business 프로젝트를 로컬 HandStack 런타임에 올리고 실행하는 진입점"이라고 보면 됩니다.

이 스크립트가 기대하는 경로

task.bat는 몇몇 경로를 전제로 동작합니다.

  • 현재 프로젝트 경로: C:\projects\company\modules\domain_business
  • 로컬 런타임 경로: C:\projects\ack-runtimes\domain_business\handstack
  • HandStack 소스 경로: 환경변수 HANDSTACK_SRC

현재 코드 기준으로 runHANDSTACK_HOME 환경변수를 쓰지 않고, 아래 고정 경로를 사용합니다.

C:/projects/ack-runtimes/domain_business/handstack

그래서 개발자가 가장 먼저 이해해야 할 점은 이것입니다.

  • run은 "이미 만들어진 런타임"을 실행한다.
  • handstack은 "그 런타임 자체를 다시 만든다".

명령 형식

기본 형식은 아래와 같습니다.

task.bat [명령] [설정이름] [추가인자]

실제 예시는 아래와 같습니다.

task.bat run
task.bat run localhost
task.bat handstack

각 인자의 의미는 다음과 같습니다.

  • 첫 번째 인자 명령
    • run 또는 handstack
  • 두 번째 인자 설정이름
    • 비우면 기본값은 localhost
  • 세 번째 인자 추가인자
    • run일 때만 사용
    • 기본 ack.exe 경로 대신 다른 실행 파일 경로를 강제로 넣고 싶을 때 사용

run 명령 설명

가장 많이 쓰는 명령은 아래입니다.

task.bat run

이 명령은 "현재 domain_business 프로젝트의 Settigns/ 디렉토리에 있는 설정으로 ACK 런타임을 실행"하는 용도입니다.

실행 순서 확인하기

task.bat run이 실제로 하는 일은 아래 순서입니다.

  1. 명령행 인자를 읽는다.
  2. 설정 이름이 없으면 localhost를 사용한다.
  3. 런타임 경로를 C:\projects\ack-runtimes\domain_business\handstack로 잡는다.
  4. handstack CLI의 configuration 명령을 호출한다.
  5. Settings\synconfigs\ack.localhost.json을 런타임 쪽 syn.config.json 위치로 복사한다.
  6. 마지막으로 ack.exe를 실행한다.

핵심은 4번과 5번입니다.

configuration 명령은 왜 필요한가

HandStack 의 ack 프로그램이 실행 전에 "지금 어떤 설정으로 뜰지"를 맞춰 주는 과정이 필요하며 영향을 주는 환경 설정은 다음과 같이 4개가 있습니다.

  • ack.settings.json
  • module.json
  • node.config.json
  • syn.config.json

배치 파일 안에는 아래 호출이 들어 있습니다.

%RUNTIME_CLI% configuration --ack=!RUNTIME_ACK! --appsettings=%WORKING_PATH%/Settings/ack.%TASK_SETTING%.json

이 단계는 "ACK 런타임이 지금 어떤 앱 설정으로 실행될지"를 맞추는 단계입니다. %TASK_SETTING% 값에 따라 개발 PC (localhost), 개발 (development), 테스트 서버 (staging), 운영 서버 (production) 과 같이 환경 설정에 대한 사전 설정들을 관리 합니다.

  • --ack: 실행할 ack.exe 경로
  • --appsettings: 사용할 ACK 설정 파일 (Settings/ack.localhost.json)

Settings 디렉터리에는 이 파일 하나만 있는 것이 아니라, 환경별 실행에 필요한 설정 파일들이 함께 들어 있어야 합니다. 프로젝트에서 사용하는 업무 모듈에 따라 설정을 구성합니다.

Settings/
│ ack.localhost.json - ACK 호스트의 메인 서버 설정 파일입니다. 포트, 모듈 로드 목록, 세션, CORS, Serilog, Kestrel 같은 "서버가 어떻게 뜰지"를 정합니다.

├─modules
│ ack.dbclient.localhost.json - `dbclient` 모듈 설정 파일입니다. 기본 데이터소스, DB 연결 정보, 명령 타임아웃, 프로파일 로그 설정을 담습니다.
│ ack.function.localhost.json - `function` 모듈 설정 파일입니다. Node/C#/Python 함수 실행 환경, 실행 파일 경로, 감시 파일, 함수 소스 연결 정보를 담습니다.
│ ack.logger.localhost.json - `logger` 모듈 설정 파일입니다. 로그 저장소, 보관 주기, 로그 DB 연결 정보를 정합니다.
│ ack.repository.localhost.json - `repository` 모듈 설정 파일입니다. 파일 서버 URL, 계약 경로, 보안 헤더, 저장소 관련 요청 처리 기준을 정합니다.
│ ack.domain_business.localhost.json - 현재 모듈인 `domain_business` 전용 설정 파일입니다. 사용자 파일 경로, DB 공급자/연결 문자열, 수집 주기, 세션 만료, Agent 호스트 목록을 정합니다.
│ ack.transact.localhost.json - `transact` 모듈 설정 파일입니다. 트랜잭션 라우팅, 인증 우회 IP, 로그 처리, 계약 파일 감시 같은 요청 중계 규칙을 담습니다.
│ ack.wwwroot.localhost.json - `wwwroot` 모듈 설정 파일입니다. 정적 웹 자산 경로, 계약 파일 경로, 모듈 로그 경로를 정합니다.

├─nodeconfigs
│ ack.localhost.json - 서버에서 실행하는 Node.js 기반 거래 함수가 참고하는 설정 파일입니다. 자산 경로, API 서버 주소, 모듈 ID, 로케일, 디버그 모드 같은 화면 실행 설정이 들어 있습니다.

└─synconfigs
ack.localhost.json - 브라우저 화면이 참고하는 클라이언트 설정 파일입니다. 자산 경로, API 서버 주소, 모듈 ID, 로케일, 디버그 모드 같은 화면 실행 설정이 들어 있습니다.

정리하면 configuration 명령은 Settings/ack.localhost.json을 중심으로 ACK 호스트 설정을 맞추고, 그 뒤 실행 과정에서 같은 Settings 아래의 modules, synconfigs, nodeconfigs 설정 파일들이 ack 실행 파일이 있는 디렉토리를 기준으로 복사됩니다.

예를 들어 ack 실행 파일이 있는 전체 경로가 'C:\projects\ack-runtimes\domain_business\handstack\app\ack.exe' 이렇게 되어 있으면 다음과 같이 복사 됩니다.

# ack.settings.json
ack.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\app\ack.settings.json

# module.json
modules/ack.dbclient.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\modules\dbclient\module.json
modules/ack.function.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\modules\function\module.json
modules/ack.logger.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\modules\logger\module.json
modules/ack.repository.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\modules\repository\module.json
modules/ack.domain_business.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\modules\domain_business\module.json
modules/ack.transact.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\modules\transact\module.json
modules/ack.wwwroot.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\modules\wwwroot\module.json

# node.config.json
nodeconfigs/ack.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\modules\function\node.config.json

# syn.config.json
synconfigs/ack.localhost.json > C:\projects\ack-runtimes\domain_business\handstack\modules\wwwroot\wwwroot\syn.config.json

언제 run만 하면 되나

아래 상황이면 보통 run만 하면 됩니다.

  • domain_business 코드만 수정했다.
  • 이미 C:\projects\ack-runtimes\domain_business\handstack 런타임이 준비되어 있다.
  • HandStack 플랫폼 자체는 다시 빌드할 필요가 없다.

Visual Studio 실행 프로필도 이 흐름을 사용합니다. Properties/launchSettings.json에 다음과 같이 설정하고 F5 누르면 디버깅 모드로 task.bat run이 실행됩니다.

{
"profiles": {
"domain_business": {
"commandName": "Executable",
"executablePath": "C:/Windows/System32/cmd.exe",
"commandLineArgs": "/c task.bat run",
"workingDirectory": "$(ProjectDir)"
}
}
}

handstack 명령 설명

명령은 아래와 같습니다. 이 명령은 "로컬 개발용 HandStack 런타임을 다시 만드는 작업"입니다.

task.bat handstack

domain_business.csproj에 빌드 구성 통합하기

domain_business 모듈 개발을 하다 보면, HandStack 런타임 자체를 다시 빌드해야 하는 상황이 종종 생깁니다. 예를 들어 HandStack 공통 프레임워크 소스를 수정했다거나, 런타임이 깨졌거나, 새로 받아와야 할 때가 그렇습니다.

<Project Sdk="Microsoft.NET.Sdk.Razor">
<!-- ... -->

<PropertyGroup Label="개발 HANDSTACK_HOME 경로 하드 코딩">
<HANDSTACK_HOME>C:\projects\ack-runtimes\domain_business\handstack</HANDSTACK_HOME>
</PropertyGroup>

<PropertyGroup Condition="'$(OS)' == 'Windows_NT'">
<AckExePath>$(HANDSTACK_HOME)\app\ack.exe</AckExePath>
</PropertyGroup>

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Condition="'$(OS)' == 'Windows_NT' and Exists('$(AckExePath)')"
Command="powershell -NoProfile -ExecutionPolicy Bypass -Command &quot;$ackPath = [System.IO.Path]::GetFullPath('$(AckExePath)'); $procs = Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -and [string]::Equals([System.IO.Path]::GetFullPath($_.ExecutablePath), $ackPath, [System.StringComparison]::OrdinalIgnoreCase) }; foreach ($p in $procs) { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue }&quot;"
IgnoreExitCode="true" />
<RemoveDir Directories="$(TargetDir)" Condition="Exists('$(TargetDir)')" />
</Target>

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<PropertyGroup>
<AppSourceDir>$(HANDSTACK_HOME)/app</AppSourceDir>
</PropertyGroup>

<ItemGroup>
<AppSourceFiles Include="$(AppSourceDir)/**/*.*" />
<TargetFilesToCheck Include="$(TargetDir)%(AppSourceFiles.Filename)%(AppSourceFiles.Extension)" Condition="Exists('$(TargetDir)%(AppSourceFiles.Filename)%(AppSourceFiles.Extension)')" />
</ItemGroup>
<Delete Files="@(TargetFilesToCheck)" ContinueOnError="WarnAndContinue" />
<PropertyGroup>
<ModuleDestinationDir>$(HANDSTACK_HOME)/modules/$(MSBuildProjectName)</ModuleDestinationDir>
</PropertyGroup>

<RemoveDir Directories="$(ModuleDestinationDir)" Condition="Exists('$(ModuleDestinationDir)')" />
<MakeDir Directories="$(ModuleDestinationDir)" Condition="!Exists('$(ModuleDestinationDir)')" />

<ItemGroup>
<SourceFilesForMirroring Include="$(TargetDir)**\*" />
</ItemGroup>
<Copy SourceFiles="@(SourceFilesForMirroring)" DestinationFiles="@(SourceFilesForMirroring->'$(ModuleDestinationDir)/%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
</Project>

실행 순서

handstack 명령은 아래 순서로 동작합니다.

  1. HANDSTACK_SRC 경로로 이동한다.
  2. HandStack 소스에서 publish.bat win build Debug x64 "C:\projects\ack-runtimes\domain_business"를 실행한다.
  3. 생성된 런타임 폴더 C:\projects\ack-runtimes\domain_business\handstack으로 이동한다.
  4. install.bat를 실행한다.
  5. 다시 현재 domain_business 프로젝트 폴더로 돌아온다.
  6. dotnet build를 실행한다.

이 명령이 필요한 상황

아래 경우에는 run보다 handstack이 먼저 필요할 가능성이 큽니다.

  • C:\projects\ack-runtimes\domain_business\handstack 폴더가 아직 없다.
  • HandStack 공통 프레임워크 소스를 수정했다.
  • 런타임이 깨졌거나 다시 설치가 필요하다.
  • ack.exe, CLI, 기본 앱 파일을 새로 받아와야 한다.

마지막 dotnet build가 중요한 이유

여기서 끝에 dotnet build를 다시 하는 이유는 현재 domain_business 모듈을 새 런타임에 올리기 위해서입니다.

domain_business.csproj에는 빌드 후 아래 동작이 들어 있습니다.

  • C:\projects\ack-runtimes\domain_business\handstack\modules\domain_business 폴더를 지우고
  • 빌드 결과물을 그 위치로 다시 복사

task.bat handstack은 아래를 한 번에 해 줍니다.

  1. HandStack 런타임 재생성
  2. 런타임 설치
  3. domain_business 모듈 재빌드 및 런타임에 배포

runhandstack 차이 한눈에 보기

명령목적보통 언제 사용하나
task.bat run준비된 런타임을 현재 설정으로 실행일상적인 로컬 실행
task.bat handstack런타임 자체를 다시 빌드/설치하고 domain_business도 다시 빌드초기 세팅, 런타임 갱신, 공통 코드 변경

추천 작업 순서

로컬 환경을 처음 맞출 때는 아래 순서를 권장합니다.

  1. HANDSTACK_SRC 환경변수가 올바른지 확인
  2. task.bat handstack
  3. task.bat run

일반 개발 중에는 보통 아래만 반복합니다.

  1. 코드 수정
  2. dotnet build 또는 IDE 빌드
  3. task.bat run

자주 헷갈리는 포인트

runHANDSTACK_HOME을 안 쓰는 이유

현재 task.bat 기준으로 run은 고정 경로 C:\projects\ack-runtimes\domain_business\handstack를 사용합니다.
즉 환경변수 HANDSTACK_HOME이 맞아도, 그 경로로 실행하지는 않습니다.

팁) AI 채팅 프롬프트 예시

아래처럼 AI 채팅에 요청하면, task.bat를 기준으로 Windows, Linux, macOS에서 공통으로 실행 가능한 PowerShell 스크립트를 만드는 데 도움이 됩니다.

아래 task.bat와 동일한 동작을 하는 task.ps1 스크립트를 만들어 줘.

조건:
- Windows, Linux, macOS에서 `pwsh ./task.ps1` 형태로 실행 가능해야 함
- `run`, `handstack` 명령 구조와 인자 의미는 유지
- Windows에서는 `ack.exe`, `handstack.exe`를 우선 찾고, Linux/macOS에서는 `ack`, `handstack`을 우선 찾기
- 경로가 없으면 초보자도 이해할 수 있는 오류 메시지 출력
- `task.bat`의 설정 파일 반영 순서와 `syn.config.json` 복사 흐름은 유지
- 가능하면 `-RuntimeHome`, `-HandStackSource` 같은 옵션도 추가

---

task.bat:
@echo off
setlocal EnableDelayedExpansion
chcp 65001

set TASK_COMMAND=%1
if "%TASK_COMMAND%" == "" set TASK_COMMAND=

set TASK_SETTING=%2
if "%TASK_SETTING%" == "" set TASK_SETTING=localhost

set TASK_ARGUMENTS=%3
if "%TASK_ARGUMENTS%" == "" set TASK_ARGUMENTS=

set "MODULE_ID=!TASK_SETTING!"
if "!MODULE_ID!"=="localhost" set "MODULE_ID=handstack"

set RUNTIME_HOME=C:/projects/ack-runtimes/domain_business/handstack
set WORKING_PATH=%cd%
set RUNTIME_ACK=%RUNTIME_HOME%/app/ack.exe
set RUNTIME_CLI=%RUNTIME_HOME%/app/cli/handstack/handstack
set HANDSTACK_ACK=%HANDSTACK_HOME%/app/ack.exe
set HANDSTACK_CLI=%HANDSTACK_HOME%/app/cli/handstack/handstack

echo WORKING_PATH: %WORKING_PATH%
echo RUNTIME_HOME: %RUNTIME_HOME%
echo RUNTIME_CLI: %RUNTIME_CLI%
echo HANDSTACK_SRC: %HANDSTACK_SRC%
echo HANDSTACK_HOME: %HANDSTACK_HOME%
echo HANDSTACK_CLI: %HANDSTACK_CLI%
echo TASK_COMMAND: %TASK_COMMAND%
echo TASK_SETTING: %TASK_SETTING%
echo MODULE_ID: %MODULE_ID%

if "%TASK_COMMAND%"=="run" (
%RUNTIME_CLI% configuration --ack=!RUNTIME_ACK! --appsettings=%WORKING_PATH%/Settings/ack.%TASK_SETTING%.json

for %%I in ("%RUNTIME_HOME%\..") do set "PARENT_PATH=%%~fI"
copy /Y "%WORKING_PATH%\Settings\synconfigs\ack.%TASK_SETTING%.json" "!PARENT_PATH!\%MODULE_ID%\modules\arha\wwwroot\arha\syn.config.json"
%RUNTIME_ACK%
)

if "%TASK_COMMAND%"=="handstack" (
cd /d %HANDSTACK_SRC%
publish.bat win build Debug x64 "C:\projects\ack-runtimes\domain_business"
cd /d "C:\projects\ack-runtimes\domain_business\handstack"
install.bat
cd /d %WORKING_PATH%
dotnet build
)

PowerShell은 배치 파일보다 아래 점에서 유리합니다.

  • Windows뿐 아니라 Linux, macOS에서도 동일한 pwsh 문법으로 실행할 수 있다.
  • 문자열 처리, 경로 계산, 조건문, 함수 작성이 가능하며 예외 처리와 오류 메시지 구성이 쉬워서 유지보수가 편하다.