apt purge는 패키지가 사라졌다고 말하지만, 부팅할 때마다 터널이 다시 올라온다. journal은 계속 자라고, 경로는 되돌아오며, 기계는 아무것도 지워지지 않은 것처럼 행동한다. 공급업체 설치 프로그램은 패키지 관리자가 기록하지 않은 파일을 쓸 수 있으므로 패키지를 지우는 일과 제품을 지우는 일은 서로 다른 작업이다. 아래 runbook은 두 기록을 분리하고 제거를 의도적으로 끝맺는다.

설치 프로그램 둘, 기록 둘

Debian 패키지는 파일 목록을 유지하고, purge는 그 목록이 지목한 것과 패키지 설정만 지운다. 셸 설치 프로그램이나 .run 묶음은 그런 목록을 두지 않는다. 원하는 곳, 대개 /opt, /usr/local, /etc/systemd/system에 쓴다. 이런 경로는 어느 패키지의 파일 목록에도 없으므로 dpkg는 알지도, 보고하지도 않는다. 결국 한 파일 시스템에 설치 주인이 둘 남고, 장부를 쓰는 쪽은 하나뿐이다.

지우기 전에 그 경로를 dpkg에 물어보라. dpkg가 부인하는 경로는 직접 지워도 되고, 같은 답이 나중 업그레이드가 그것을 되돌릴지도 알려 준다.

dpkg -S /etc/systemd/system/acmevpn.service
systemctl list-unit-files | grep -i acmevpn

unit과 wants 링크는 별개의 파일이다

활성화는 설치와 다른 행위다. unit은 [Install] 절에서 대상 관계를 선언하고, systemctl enable가 그 선언을 각 대상의 .wants/ 디렉터리 안 심볼릭 링크로 바꾼다. 이제 경로가 둘 생긴다. unit 파일과, 그것을 부팅 대상으로 끌어들이는 링크다. 파일만 지우면 링크가 아무것도 가리키지 않아 부팅 때 불평이 나온다. 링크만 지우면 unit은 이름으로 시작할 수 있는 채로 남는다. 둘 다 없애야 한다.

순서대로 제거한 뒤 다시 불러와라. systemd가 옛 정의를 메모리에 계속 붙들지 않게 하기 위해서다.

systemctl disable --now acmevpn.service
systemctl show -p FragmentPath -p ExecStart acmevpn.service
sudo rm /etc/systemd/system/acmevpn.service
sudo rm /etc/systemd/system/multi-user.target.wants/acmevpn.service
systemctl daemon-reload

위치를 짐작하지 말고 FragmentPath를 읽어라. 공급업체가 /etc가 아니라 /usr/lib/systemd/system이나 /lib/systemd/system에 썼을 수 있다. 예상한 사본을 지우면 실제로 유효한 쪽이 계속 돌고, 다음 재적재까지 수정은 성공한 것처럼 보인다. 재적재가 끝나면 unit 파일을 다시 나열하고 그 이름이 더 이상 나오지 않는지 확인하라.

이 배치를 살려 두는 바이너리를 찾아라

활성화된 unit은 패키지 데이터베이스가 무엇을 믿든 데몬을 계속 시작한다. 그 프로세스는 터널을 다시 올리고, 경로를 다시 넣고, kill-switch 규칙을 복원할 수 있다. 배치를 살펴보기 전까지 그 행동은 초자연적으로 보인다. ExecStart 경로를 따라가 바이너리가 아직 있는지 확인하라. /opt 아래 남은 실행 파일 하나면 이 배치에는 충분하다. unit을 지우면 예정된 시작이 멈출 뿐, 프로그램이 지워지지는 않는다.

상태 디렉터리도 같은 주의로 다뤄라. 계정 토큰, 생성된 프로필, 캐시된 서버 목록은 /var/lib 아래나 홈 디렉터리에 있고, 이것은 버릴 캐시가 아니라 자격 증명이다. 서비스를 멈춘 뒤 의도적으로 제거하고, 무엇을 가져갔는지 적어 두라.

sudo ls -l /opt/acmevpn /usr/local/bin/acmevpn
sudo find /var/lib -maxdepth 2 -iname "*acme*"
systemctl is-enabled acmevpn.service

부팅 주인을 하나 골라 시험하라

부팅을 주장할 후보는 셋이고, 셋을 다 설치하면 이중 안전이 아니라 경쟁이 된다. 데스크톱 자동 시작 파일은 누군가 로그인한 뒤에야 돌고, systemd unit은 더 일찍 돌지만 화면 없는 클라이언트가 필요하며, cron @reboot 항목은 네트워크 클라이언트에 맞지 않을 수 있는 시각에 발동한다. 공급업체 CLI는 서버 인수를 더 요구할 수 있다. 부팅 때는 이어 갈 이전 세션이 없기 때문이다. 클라이언트가 인증하는 방식에 맞는 후보를 고르고 나머지는 버려라.

기계를 다시 부팅하고 세 가지 독립적 사실을 보며 시험하라. unit 상태, 프로세스 목록, 그리고 검증할 수 있는 무언가로의 새 연결이다. 키링이나 대화형 로그인이 필요한 클라이언트는 올바른 unit 파일이 있어도 부팅 때 실패한다. journal을 읽으면 그렇게 적혀 있다. 서비스 상태만 봐서는 알 수 없다.

묻지 않고 동작하는 자격 증명을 클라이언트에 주거나, 로그인할 때 시작하라. 둘 다 그럴듯하다. 둘을 섞으면 터널 하나에 주인 둘이 생겨 서로 싸운다.

다음 purge가 무엇을 예상해야 하는지 적어 두라

손으로 제거한 뒤 두 가지 사실을 적어라. 어느 설치 프로그램이 이 배치를 만들었는지, 지금 누가 그것을 시작하는지. 그 메모는 다음 purge를 뜻밖의 일에서 점검 항목으로 바꾸고, 일을 한 사람보다 오래 남는다.

개인 디렉터리가 아니라 서비스 설정 옆에 두고 날짜를 적어라. 공급업체 업데이트가 지운 unit을 되살릴 수 있기 때문이다. 몇 달마다 systemctl list-unit-files를 그 메모와 견주면 수정이 아직 쌀 때 복원을 잡아낸다.

관련 글