⌨️

(예제로 알아보는) AppleScript와 Hammerspoon을 이용한 반복업무 자동화

태그
가이드생산성
최종 편집
Dec 30, 2022 2:30 AM
발행일
September 18, 2021
💡
- 한국신용데이터 동료였던 김장환님이 사내 위키에 쓴 글을 편집해서 옮긴 글입니다. - 모든 설명은 MacOS Catalina 기준입니다. - 예제 코드는 여기에서 내려받을 수 있습니다.

반복 작업을 좋아하는 사람은 별로 없다. 마우스를 싫어하고 단축키를 사랑하는 개발자들이라면 더욱 그러하다. 이 글은 한국신용데이터(이하 KCD)에서 개발자들의 반복 작업 중 하나였던 VPN 망 연결 및 전환AppleScriptHammerspoon을 이용해 자동화(원클릭 + 단축키 등록)하여 개선한 과정이다.

필자도 두 도구를 예전부터 사용해왔다거나, 잘 쓸줄 안다거나 하는 건 전혀 아니다. (원 필자인 김장환님은 좀 아시는듯하지만..) 어쨌든 문제 해결을 위해 노력한 결과를 공유하는 것이 목적이다. 비슷한 어려움을 겪고 있는 분들이 있다면, 또는 반복 업무를 자동화하는 데 관심이 있다면, 이 글과 코드를 참고하여 하루에 몇 초라도 절약할 수 있기를 기대한다.

0. 들어가며

KCD에서는 재택근무시 보안을 위해 VPN을 이용해야만 인터넷 접근이 가능하게 설정해 두었다. 일반적인 인터넷을 사용할 때는 외부망용(EXT), 코드 리뷰/배포 등 민감한 작업을 할 때는 외부 인터넷이 차단된 내부망용(INT) VPN을 쓴다.

VPN 연결에 쓰는 Pulse Secure는 설치시 메뉴 바로 VPN 연결을 할 수 있는데, 문제는 아래 두 가지다.

  • 현재 EXT인지 INT인지 알기 어렵다.
  • EXT와 INT를 오갈 때마다 메뉴바 클릭 + 마우스 움직임 + ID/PW 입력 등 많은 과정을 거쳐야 해서 번거롭다.

이 두 가지 문제를 자동화하여 업무 효율을 높여보자.

1. 망 접속 상태를 알아보자

우선 현재 어떤 네트워크에 접속해 있는지는 네트워크 IP 주소를 이용해 알아낼 수 있다.

EXT 연결일 때의 ifconfig
EXT 연결일 때의 ifconfig
INT 연결일 때의 ifconfig
INT 연결일 때의 ifconfig

이 IP 주소들은 사내 네트워크 구성이 변하지 않는 한 고정되어 있으므로, ifconfig 의 호출 결과로 현재 외부망인지 내부망인지를 알 수 있다:

연결 안 됨
연결 안 됨
EXT에 연결
EXT에 연결
INT에 연결
INT에 연결

☝️ 는 Hammerspoon이라는 OS X 자동화를 위한 Lua 기반 툴킷을 이용해, 1) 주기적으로 ifconfig를 호출해서 2. 그 결과값을 메뉴바에 표시해준 것이다. Hammerspoon의 사용 방법은 아래에서 천천히 알아보고, 맛보기로 VPN 상태를 감지해서 메뉴바를 업데이트하는 코드만 구경해보자:

-- query vpn connection
local function queryVpnConnection(callback)
  logger.v('queryVpnConnection')
  local test = hs.task.new("/sbin/ifconfig", function(exitCode, stdout, stderr)
    local isExternal = string.find(stdout, 'xx.xx.3.')
    local isInternal = string.find(stdout, 'xx.xx.4.')
    --
    local status = 0
    if isExternal then
      status = 1
    elseif isInternal then
      status = 2
    else
      status = 0
    end
    obj.status = status
    callback(status)
  end):start()
end

-- update the menu bar
local function updateMenuVpnStatus(status)
  logger.v('updateMenuVpnStatus: %d', status)
  if status == 1 then
    obj.menu:setTitle('EXT')
  elseif status == 2 then
    obj.menu:setTitle(hs.styledtext.new('INT', {
      font = hs.styledtext.boldSystem,
      color = { hex = 'FF0000' }
    }))
  else
    obj.menu:setTitle('')
  end
end

2. EXT ↔ INT 변경을 쉽게 해보자

현재 어떤 상태인지 알고 있다면, 다른 특정 상태로 바꾸기 위해 어떤 단계를 거쳐야 하는지도 알 수 있다. 예를 들어 INT 상태에서 EXT 상태로 바꾸려면 다음 10단계를 수행해야 한다:

image
  1. Pulse Secure 메뉴바 아이콘 클릭
  2. Employee_VPN 메뉴 클릭
  3. 연결 해제 메뉴 클릭
  4. Pulse Secure 메뉴바 아이콘 클릭
  5. 연결 메뉴 클릭
  6. Employee_VPN 창에서 KCD_EXTVPN 선택
  7. 연결 버튼 클릭
  8. ID와 PASSWORD 입력
  9. 연결 버튼 클릭
  10. OTP PASSWORD 입력

그러면 이제 "메뉴바 클릭"이라든가, (PASSWORD는 위험하니 패스하더라도) "ID 타이핑" 같은 걸 프로그램으로 수행할 수만 있다면, 반복작업 자동화에 한 발 가까워질 것이다. 이를 할 수 있는 방법은 다양하겠지만 이 글에서는 MacOS 환경에 맞춰 AppleScript를 쓴다.

AppleScript는 애플이 만든 MacOS 상에서 애플리케이션과 통신할 수 있는 스크립트 언어다. OS X 가 나오기 전인 1993년부터 있었다.

https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/introduction/ASLR_intro.html

예를 들어, AppleScript로 위의 6번과 7번(Employee_VPN 창에서 KCD_EXTVPN 선택 → 연결 버튼 클릭)을 수행하는 코드는 다음과 같다:

on click_connection_on_window(target)
  global VPN_NAME
  tell application "System Events" to tell process "PulseTray"
    set vpnwin to first window whose name is VPN_NAME
    tell first sheet of vpnwin
      set realmRows to get every row of table 1 of scroll area 1
      -- find & select row with target name
      select (first item of realmRows whose value of static text 1 is target)
      -- click "Connect"
      click button CONNECT_NAME
    end tell
  end tell
end click_connection_on_window

참고로 AppleScript 파일은 .scpt 확장자를 가졌는데 일반적인 텍스트 에디터로는 편집이 안 되고, MacOS 기본 프로그램인 "스크립트 편집기"로 편집해야 한다.

image

스크립트 편집기에서 망치 모양 아이콘을 눌러 컴파일하고 나면, 아래와 같이 osascript(애플스크립트 파일을 실행할 수 있는 스크립트 언어) 파일을 만들어서 scpt 파일에 정의된 메서드를 실행할 수 있다. 아래는 Finder 앱의 경로를 현재 파일의 경로로 두고, kcd_vpn.scpt 에 정의된 connect_to_ext() 를 실행하여 외부망으로 연결 후 현재 상태를 메뉴바에 업데이트하는 코드다.

#!/usr/bin/osascript
tell application "Finder" to set cwd to container of (path to me) as string
set kcd_vpn to (load script file (cwd & "kcd_vpn.scpt" as alias))
run kcd_vpn

--
tell kcd_vpn
	connect_to_ext()
  delay 1
	status()
end tell

3. Hammerspoon + AppleScript = ?

Hammerspoon은 menubar 아이콘과 상태를 표시하고, AppleScript는 각 메뉴를 눌렀을 때 정해진 명령을 실행한다. 열심히 삽질하고 실험해가며 코드를 짠 결과 이런 것들이 가능해졌다.

  • 메뉴바 아이콘에 연결상태(EXT, INT) 표시
  • 연결 상태에 따라 메뉴 제공
    • disconnect 일 때:
      1. image
      2. connect...: 연결창 열기
      3. connect to INT: INT로 접속하기
      4. connect to EXT: EXT로 접속하기
      5. status: disconnected: 상태 표시 (비활성)
      6. Pulse Secure: Pulse Secure 열기
    • EXT 일 때 (INT도 유사):
      1. image
      2. switch to INT: INT로 재접속하기
      3. disconnect: 연결 끊기
      4. status: connected to EXT: 상태 표시 (비활성)
      5. Pulse Secure: Pulse Secure 열기

4. Putting it all together

  1. 우선 Hammerspoon 홈페이지에 가서 다운로드 후 설치하면 ~/.hammerspoon 디렉토리가 생성된다.
    • 안 생기면 직접 만들고, Hammerspoon 구동 파일인 init.lua 도 추가하자.
  2. Hammerspoon 플러그인은 ~/.hammerspoon/Spoons 에 넣으면 된다. 예제 저장소에서 받은 KCDVpn.spoon이 Hammerspoon 플러그인인데, Hammerspoon 앱으로 구동할 수 있는 모듈이자 디렉토리다. Spoons 디렉토리 안에 KCDVPN.spoon을 넣자. 디렉토리 구성물은 이렇다:
    • init.lua: hammerspoon 파일
    • ./scripts/*: applescript 스크립트 파일들 및 bash 스크립트
  3. ~/.hammerspoon/init.lua 를 아래처럼 편집해서 Hammerspoon 실행시 구동시킨다. 이리저리 바꿔가면서 자신에게 맞는 설정을 찾아보자.
-- Spoons/KCDVpn.spoon 디렉토리를 불러온다.
hs.loadSpoon('KCDVpn')
-- Spoon을 시작한다. 5초 단위로 ifconfig 를 질의하도록 한다. 
spoon.KCDVpn:start({ interval = 5 })
-- 아래 코멘트를 해제하면 단축키를 지정할 수 있다.
--hs.hotkey.bind({'ctrl', 'option', 'v'}, 'i', function() spoon.KCDVpn:connectToInt() end)
--hs.hotkey.bind({"ctrl", "option", "v"}, "e", function() spoon.KCDVpn:connectToExt() end)
--hs.hotkey.bind({"ctrl", "option", "v"}, "t", function() spoon.KCDVpn:toggleVpnConnection() end)
  1. scripts 디렉토리 안에는 애플스크립트 구동시 참조하는 kcd_vpn.plist 파일이 있다. 이 파일을 자신의 환경에 맞게 설정을 해준다.
  2. Profit!

KCD의 보안을 위하여 의도적으로 누락한 설명도 있으니 양해 부탁드리며, 서두에 적은 대로 이 글이 반복업무에 지친 여러분의 아픈 관절을 조금이라도 편하게 하는데 도움이 되길 바란다.