Notes: replacing a manual deploy with an agent skill

English · 한국어


Notes: replacing a manual deploy with an agent skill

한국어

Every few weeks another team hands me a config bundle to deploy. Eight steps over a bastion: verify the archive, two-hop copy, sudo, back up, overwrite, run the deploy script, then compare directory timestamps to check the right files changed. About 20 minutes, usually when I’m in a hurry.

Step eight is the problem. It’s a judgement call and I’m making it tired. The realistic failure here isn’t a crash, it’s a deploy that goes slightly wrong and gets called fine.

Parts of this were already scripted. It stayed manual because two steps need a human: is this the right build, and is this change set right. A script that pretends to answer those is worse than one that doesn’t.

what I built

Instructions an agent loads, plus shell scripts it drives. The one rule I’d keep:

The agent never mutates anything. It runs scripts that do, and reads exit codes.

  • checksums at 4 points; mismatch aborts before anything is touched
  • backup is mandatory, and its checksum is verified before the overwrite
  • 2 human gates, at the two judgement calls; everything else unattended
  • the post-deploy check is a diff — pre/post snapshots, changed files matched against the archive manifest, non-zero exit on anything unexpected
  • read-only doctor that walks the whole chain and copies nothing

Step eight is now a diff with an exit code.

what took longer than it should have

Most of the time went here, not into the automation.

The sandbox lied about the archive. I assumed every file in the bundle lands in the served directory. Only one extension does; the rest go elsewhere. My first verification pass would have warned about those on every single run — and a check that always warns is a check people stop reading. Narrowed what counts as expected, kept the hard rule: anything changing in there that isn’t from the archive still fails.

A whole auth path was dead code. The bastion had moved to SSO-issued short-lived SSH certificates and reaches the app server with its own key. The password file my scripts were carefully passing around did not exist anymore. It had never failed, because key auth won the race every time. Found it because a read-only probe printed “password file missing” and “login succeeded” in the same output and I went looking instead of taking the green result. About 170 lines deleted.

grep -c got me twice in one day. It prints 0 and exits 1 when there are no matches, so n=$(grep -c x f || echo 0) gives you "0\n0" and your arithmetic quietly breaks. Fixed it in the deploy script. Then wrote the identical bug in the throwaway script I was using to check the blog post for leaked hostnames.

The state file wiped its own session identity. The preflight step resets its state file, which also held the saved jump-host identity, so the next step couldn’t find the live connection and asked for credentials again. Only surfaced on a real run with a session already open — the sandbox never had one.

note to self

Worth reusing next time an agent touches something real:

  • checksums, because the agent can’t eyeball a bad transfer
  • verified backup + rollback for every mutation
  • gates at the judgement calls, not everywhere
  • verification that produces an exit code, not an opinion
  • a read-only dry run before anything writes

That list is just artifact integrity, provenance, rollback, gates, reproducibility. Swap “config bundle” for “model artifact” and it’s a deploy pipeline, which is most of why I’m poking at this right now.

The other thing: it’s only useful because it refuses. Checksums disagree, or files changed that had no business changing, and it stops. Something that always returns success is something I can’t use for anything that matters.

Unexpected side effect — writing instructions precise enough for an agent made me write down why the backup gets verified and what the timestamp check is actually for. That stuff only lived in my head.


메모: 수동 배포를 에이전트 스킬로 교체

English

몇 주에 한 번씩 다른 팀에서 설정 묶음을 받아 배포한다. 배스천 경유 8단계. 아카이브 검증, 2홉 복사, sudo, 백업, 덮어쓰기, 배포 스크립트 실행, 그다음 디렉터리 타임스탬프를 비교해서 맞는 파일이 바뀌었는지 확인. 20분쯤 걸리고, 대체로 급할 때 한다.

8번이 문제다. 판단이 필요한 단계인데 피곤한 상태에서 판단한다. 여기서 현실적인 실패는 뻑 나는 게 아니라, 조금 잘못된 배포가 괜찮다고 보고되는 것이다.

일부는 이미 스크립트로 돼 있었다. 수작업으로 남은 이유는 사람이 필요한 단계가 둘 있어서다. 맞는 빌드인가, 그리고 이 변경 집합이 맞는가. 이 둘에 답하는 척하는 스크립트는 아예 안 하는 것보다 나쁘다.

만든 것

에이전트가 읽는 지침 + 에이전트가 구동하는 셸 스크립트. 남겨둘 규칙 하나:

에이전트는 아무것도 직접 변경하지 않는다. 변경하는 스크립트를 실행하고 종료 코드를 읽는다.

  • 4개 지점 체크섬. 하나라도 다르면 아무것도 건드리기 전에 중단
  • 백업 필수, 덮어쓰기 전에 백업본 체크섬 확인
  • 사람 게이트 2회, 판단이 필요한 두 지점에만. 나머지는 무인
  • 배포 후 검사는 diff — 사전/사후 스냅샷, 변경 파일을 아카이브 목록과 대조, 예상 밖이면 0 아닌 코드로 종료
  • 전 구간을 돌면서 아무것도 복사하지 않는 읽기 전용 doctor

8번은 이제 종료 코드가 붙은 diff 다.

예상보다 오래 걸린 것들

시간은 자동화가 아니라 여기서 다 갔다.

샌드박스가 아카이브에 대해 거짓말을 했다. 묶음의 모든 파일이 서비스 디렉터리로 간다고 가정했는데, 실제로는 한 확장자만 가고 나머지는 다른 경로로 간다. 첫 검증 로직은 그 파일들에 대해 매 실행마다 경고를 냈을 것이다. 항상 경고하는 검사는 사람들이 안 읽는 검사다. “예상됨” 범위를 좁히고 강한 규칙은 유지했다. 거기서 아카이브에 없는 파일이 바뀌면 여전히 실패.

인증 경로 하나가 통째로 죽은 코드였다. 배스천은 SSO 기반 단기 SSH 인증서로 옮겨 갔고, 앱 서버에는 자체 키로 접속한다. 내 스크립트가 애지중지 넘기던 비밀번호 파일은 이미 없었다. 실패한 적이 없었던 건 매번 키 인증이 먼저 성공했기 때문이다. 읽기 전용 점검이 “비밀번호 파일 없음"과 “로그인 성공"을 한 출력에 같이 뱉었고, 초록불을 그냥 받지 않고 파고들어서 찾았다. 약 170줄 삭제.

grep -c 에 하루에 두 번 당했다. 매치가 없으면 0 을 출력하면서 종료 코드는 1 이다. 그래서 n=$(grep -c x f || echo 0) 하면 "0\n0" 이 되고 산술이 조용히 깨진다. 배포 스크립트에서 고쳤다. 그리고 블로그 글에서 호스트명 유출을 검사하려고 급히 쓴 스크립트에 똑같은 버그를 다시 썼다.

상태 파일이 자기 세션 정보를 지웠다. preflight 단계가 상태 파일을 초기화하는데, 거기에 저장된 점프 호스트 접속 정보까지 같이 날아가서 다음 단계가 이미 열린 연결을 못 찾고 인증을 다시 요구했다. 세션이 열려 있는 실제 실행에서만 드러났다. 샌드박스에는 세션이 없었다.

나중에 볼 메모

에이전트가 실제 시스템을 건드릴 때 다시 쓸 것들.

  • 체크섬. 에이전트는 깨진 전송을 눈으로 못 본다
  • 모든 변경에 검증된 백업 + 롤백
  • 게이트는 판단이 필요한 곳에만, 전부에 걸지 않는다
  • 의견이 아니라 종료 코드를 내는 검증
  • 쓰기 전에 읽기 전용 드라이런

이 목록은 결국 아티팩트 무결성, 출처 추적, 롤백, 게이트, 재현성이다. “설정 묶음"을 “모델 아티팩트"로 바꾸면 그대로 배포 파이프라인이고, 지금 이걸 들여다보는 이유도 대체로 그거다.

또 하나. 이 도구가 쓸 만한 이유는 거부하기 때문이다. 체크섬이 다르거나, 바뀔 이유가 없는 파일이 바뀌면 멈춘다. 항상 성공을 반환하는 건 중요한 일에 못 쓴다.

예상 못 한 부수 효과 — 에이전트가 따라올 만큼 정확하게 지침을 쓰다 보니 백업을 왜 검증하는지, 타임스탬프 검사가 실제로 뭘 위한 건지 적게 됐다. 머릿속에만 있던 것들이다.