업데이트가 잘 될 것이라는 기대만으로 현재 설치판을 바꾸고 싶지는 않았다. 실패했을 때 돌아갈 실행물이 무엇인지 먼저 알아야 했다. 북극성의 복원 준비 코드와 실제 설치 기록을 읽으며 이전 설치 파일을 보관하는 일, 그 바이트를 확인하는 일과 자료를 복구하는 일을 나눠 정리했다.

소스 백업만으로 실행 복원이 준비되지는 않는다

소스를 남겨도 같은 실행물을 다시 만들려면 의존성과 빌드 조건이 필요하다. 이전 설치 EXE를 함께 보관하면 돌아갈 배포물을 더 구체적으로 정할 수 있다. 실제 설치 기록도 직전 설치물과 소스, 배포 파일 해시를 따로 남겼다.

이 준비는 원고와 DB의 복원과 다르다. 실행 파일을 이전 버전으로 되돌렸다고 그 사이 바뀐 자료까지 자동으로 되돌아가는 것은 아니다. 저장 형식과 호환성도 별도 질문이다. 나는 프로그램 복원과 사용자 작업 보존을 같은 백업 완료 문구로 묶지 않았다. 어떤 파일이 어떤 실패를 대비하는지 알아야 복원 준비를 검토할 수 있다.

이미 있는 설치물도 이름만 보고 믿지 않는다

prepareRollbackInstaller는 이전 설치 파일이 있으면 일반 파일인지 보고 복사한 바이트를 해시로 대조한다. 대응 영수증이 있으면 버전과installerSha256도 확인한다. 현재 코드에서 그 영수증은 선택적이므로 모든 기존 파일이 반드시 신뢰된 영수증으로 인증된다고 설명하지 않는다.

복사 전후 해시 일치는 같은 파일을 옮겼다는 근거이며 그 파일이 올바른 공식 배포물이라는 출처 증명까지는 아니다. 기존 경로에 놓인 파일의 신뢰와 실제 실행 가능성은 더 확인해야 한다. 함수 이름에verified가 들어간다고 모든 종류의 검증이 끝났다는 뜻으로 읽지 않은 이유다. 실제로 비교한 값과 빠진 조건을 함께 적었다.

없으면 현재 설치 payload를 별도로 준비한다

설치물이 없을 때의 분기는 소스 package 버전과 설치 버전을 비교하고 현재 설치 폴더에서 허용된 payload를 읽는다. 사용자 저장소나 로그인 폴더를 통째로 복사하지 않는다. stage에 복사한 파일을 원래 해시와 맞추고 build 전후에 stage와 현재 설치 파일을 다시 대조한다.

여기서 build는 주입받는 동작이다. 단위 시험은 합성 설치 파일을 쓰는 대역을 사용하므로 그 시험만으로 실제 NSIS 복원 설치가 성공했다고 할 수 없다. 생성된 파일의 영수증과 복사 해시도 같은 제한을 가진다. 실제 복원 실행과 재시작 결과는 별도 통합 근거가 있어야 한다. 준비 경로가 존재하는 것과 복구를 끝낸 것은 다른 상태다.

불완전한 현재 설치물로 복원본을 만들지 않는다

관련 시험은 app.asar 누락이면 build가 호출되지 않는지, 소스 버전이 더 새로우면 옛 설치 버전의 복원본으로 포장하지 않는지 본다. 재사용할 설치물 바이트를 바꾸면 해시 불일치로 거절하는 경우도 있다. 복원 준비가 실패했는데 최신 설치로 먼저 넘어가는 흐름을 정상이라고 소개하지 않는다.

현재 설치판은 내장 수리 runtime을 제거했으므로 이 보관 코드와 시험을 현재 앱의 자동 자가수리 기능으로 쓰지도 않는다. 역사적 설계의 선택을 재사용할 수는 있지만 실제로 연결된 진입점은 따로 확인해야 한다. 과거의 준비 함수가 남아 있다는 사실과 지금 업데이트가 자동으로 실행된다는 주장은 분리했다.

준비·반영·복구를 다른 영수증으로 남긴다

실제 후속 설치 기록에는 정상 종료를 기다리기 전 첫 호출이 안전 검사에서 멈추고 종료 뒤 반영한 경과도 있다. 설치물이 준비됐다는 사실이 실행 중인 자료를 건드려도 된다는 뜻은 아니었다. 현재 상태와 보존 확인을 순서대로 잇는 것이 필요했다.

이번 집필에서는 설치와 복원을 새로 실행하지 않았다. 공유 모듈은 선언된 합성 스냅샷과 해시 문자열을 비교할 뿐 복원 도구가 아니다. 내가 남기려는 기준은 변경 전 돌아갈 바이트를 알고, 변경 뒤 실제 보존을 확인하며, 복구를 했다면 그 결과를 따로 검증하는 것이다. 준비된 길과 실제로 돌아온 결과를 구별해야 업데이트 실패에서도 작업의 맥락을 잃지 않는다.