Files
mudfish-decky/docs/superpowers/specs/2026-10-24-mudfish-decky-design.md
T
ysj7705andClaude Opus 5.5 9cf6efb8ad
ysj7705/mudfish-decky/pipeline/head This commit looks good
feat: put the account's Full VPN setting back when the VPN goes off; release 0.1.3
The node the plugin sets is account-wide, so the PC's Mudfish would also
start in Full VPN. Remember the account's own value before changing it and
restore it on every teardown (off, connect failure, resume, unload,
uninstall); retry in the background if the restore fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:21:01 +09:00

23 KiB
Raw Blame History

미꾸라지 Full VPN 데키 플러그인 설계

작성일: 2026-10-24

1. 왜 만드는가

사용자의 지포스나우(GeForce NOW) 계정은 일본 계정이다. 스팀덱에서 지포스나우를 쓰려면 앱이 켜질 때와 로그인할 때, 게임을 실행할 때 일본 IP로 보여야 한다. 게임이 실행된 뒤에는 VPN을 꺼도 스트리밍이 문제없이 이어진다는 것을 사용자가 직접 확인했다.

미꾸라지(Mudfish) VPN은 스팀덱(SteamOS)을 공식 지원한다. 하지만 Konsole에서 띄운 미꾸라지는 데스크탑 모드에서 게임모드로 넘어가면 종료된다. 그래서 게임모드에서 켜고 끄려면 매번 데스크탑 모드를 오가야 한다.

이 플러그인은 게임모드의 데키 패널에서 미꾸라지 Full VPN을 켜고 끄는 스위치다.

사용 흐름

  1. 게임모드에서 ... 버튼을 누르고 데키 패널의 미꾸라지 플러그인에서 켜기를 누른다.
  2. 패널에 일본 IP가 표시되는 것을 확인한다.
  3. 지포스나우 앱을 실행해 로그인하고 게임을 시작한다.
  4. 패널에서 끄기를 누른다. 이후 스트리밍은 직접 연결로 이어진다.

2. 목표 / 비목표 / 반목표

목표

  • 게임모드 데키 패널에서 미꾸라지 Full VPN을 켜고 끈다.
  • 연결 상태와 현재 공인 IP·국가를 보여 준다.
  • 일본 노드는 기본이 **자동(그 시점에 핑이 가장 낮은 일본 노드)**이다. 수동 선택은 "고급" 접기 메뉴 안에 둔다.
  • 연결된 뒤 지난 시간을 보여 줘서, 켜 둔 채로 잊는 일을 줄인다.
  • 미리 데스크탑 모드에서 한 번 로그인해 두면(로그인 기억하기) 게임모드에서는 다시 로그인할 필요가 없다.

비목표

  • 게임모드에서 아이디와 비밀번호를 입력해 로그인하는 기능. 토큰이 만료되면 안내 문구만 보여 준다.
  • 미꾸라지 설치와 업데이트. 사용자가 공식 가이드대로 직접 한다.
  • 자동 끄기, 시간 경과 알림 같은 자동화. 사용자가 수동으로 켜고 끄기로 했다.
  • 아이템 모드 지원. 일본 이외 국가의 노드 선택.
  • 데키 스토어 배포. 사용자 본인의 스팀덱 전용이다.

반목표 (기술적으로는 돌아가도 실패로 보는 것)

  • 사용자가 모르는 사이에 VPN이 켜진 상태로 남아 스트리밍 트래픽이 미꾸라지로 흘러가는 것. 종량제는 1GB당 135원이고 노드 경유 트래픽은 두 번 집계된다. 스트리밍 중에 켜져 있으면 시간당 수천 원이 나간다.
    • 플러그인이 자동으로 연결을 시작하는 동작은 없어야 한다. 부팅할 때, 플러그인을 다시 불러올 때, 절전에서 깨어날 때 모두 마찬가지다.
  • 끄기를 눌렀는데 실제로는 터널이나 경로 설정이 남아 있는 것.
  • 사용자의 미꾸라지 계정 설정을 묻지 않고 바꾸는 것. 예외는 Full VPN 노드 번호 하나로, 사용자가 노드를 선택하거나 자동 선택이 될 때만 바꾼다.

3. 환경 (2026-10-24 SSH로 확인)

  • 스팀덱: SteamOS (holo), 커널 7.2.7, Python 3.14.6. SSH(deck@<스팀덱 IP>)는 키 인증으로 접속한다.
  • 데키로더 v3.3.0과 플러그인 13개가 설치되어 있다. 참고할 만한 기존 플러그인으로 tailscale-control(VPN 스위치)이 있다.
  • 지포스나우: Flatpak com.nvidia.geforcenow (user).
  • 미꾸라지 6.6.0이 /opt/mudfish/6.6.0/에 설치되어 있다. /opt는 /home 쪽 파티션(/.steamos/offload/opt)이라 SteamOS 업데이트 후에도 남는다.
  • 미꾸라지 계정은 종량제(PPT)이고 isOkayToUseNodes: true이다. "로그인 기억하기"로 로그인해 둔 상태다(autologin on, token_v3 저장).
  • 개발 중에는 deck 계정에 sudo 비밀번호 없음 설정(/etc/sudoers.d/zz-dev-nopasswd)이 켜져 있다. 개발이 끝나면 지워야 한다. 플러그인 자체는 이 설정에 의존하면 안 된다. 데키 백엔드는 root로 실행된다.

4. 확인된 미꾸라지 동작 (실측)

실행 바이너리

  • /opt/mudfish/<버전>/bin/mudrun-headless -B: 로컬 웹 UI와 API를 127.0.0.1:8282에서 연다. -B는 브라우저를 띄우지 않는 옵션이다. 8282가 사용 중이면 임의의 포트로 열리고, 실제 포트는 /opt/mudfish/<버전>/var/.ipport에 기록된다.
  • root가 필요하다. 라우팅 테이블을 바꾸고 tap0 인터페이스를 만들기 때문이다.
  • Konsole이나 SSH 세션에서 띄우면 세션이 끝날 때 같이 죽는다. sudo systemd-run --unit=<이름> --collect <바이너리> -B로 띄우면 세션과 상관없이 유지되는 것을 확인했다.
  • 설정과 상태 파일은 /opt/mudfish/<버전>/var/.conf(텍스트), .conf.db, mudrun.pid 등이다.

로컬 API (http://127.0.0.1:8282)

요청 용도 응답/비고
GET /do/mudrun/status 전체 상태 JSON. isSignedIn, autologin, username, token_v3, jwt, api_server(api.mudfish.net), item_conf_mode, version, core_running, core_ready, client_ready, fullvpn_enabled, mudfish_fullvpn_sid
POST /do/auth (JSON) 로컬 데몬에 로그인 결과 전달 {username, password, remember_me, token_v3, jwt, expert_mode, lang, alias_enable} → {"status":200}
POST /do/mudfish/start 코어(터널) 시작 {"status":200}. 이미 실행 중이면 201
POST /do/mudfish/stop 코어 종료 {"status":200}
POST /do/fullvpn/refresh (form) 실행 중에 Full VPN 노드 반영 sid, exceptions
GET /do/rtt/staticnodes 노드별 핑 측정값 {"rtts":[{sid, rttavg, success, failure, ...}]}

자동 로그인 (데몬을 다시 띄우면 isSignedIn: false가 됨)

미꾸라지 웹 UI(signin.html)가 하는 절차를 그대로 따르며, 명령어로 성공을 확인했다.

  1. GET /do/mudrun/status로 username, token_v3, item_conf_mode, version, api_server를 읽는다.
  2. POST https://api.mudfish.net/signin_ng_v3를 multipart form으로 보낸다. 필드는 autologin=ON, username, password=<token_v3>, item_conf_mode, version이다. 응답은 {status, token_v3, jwt, expert_mode, lang, alias_enable}이다. status가 401이면 캡차가 필요하다는 뜻이다(웹 UI 기준).
  3. POST /do/auth에 JSON을 보낸다. 필드는 username, password=<저장된 token_v3>, remember_me=true, token_v3=<응답값>, jwt=<응답값>, expert_mode, lang, alias_enable이다.
  4. 이후 status.isSignedIn == true가 된다.

Full VPN 노드 설정 (계정에 저장되는 값)

  • 읽기: POST https://api.mudfish.net/graphql, 헤더 Authorization: <jwt>
    • { user { dataPlan isOkayToUseNodes progConf { mudfishFullvpnSid mudfishFullvpnVid mudfishFullvpnExceptions ... } staticNodes { sid location } } }
    • 웹 UI(prog.html의 prog_graphql)는 progConf의 다음 필드를 모두 읽는다: mudfishCreditSavingMode, mudfishFullvpnSid, mudfishFullvpnVid, mudfishFullvpnXp, mudfishFullvpnExceptions, mudfishMtu, mudfishPreferWfp, mudfishProtomethod, mudfishReBrokerMaxconns, mudflowCompatv1, mudflowRttmethod.
  • 쓰기: POST https://api.mudfish.net/prog에 progConf 객체 전체(위 필드)를 JSON으로 보내고, 헤더는 Authorization: <jwt>이다. 응답은 {status, msg, need_restart}이다. 웹 UI는 이어서 로컬 기록용으로 POST /do/db/prog_conf에 같은 객체를 보낸다.
    • 다른 필드는 읽은 값을 그대로 보내고, mudfishFullvpnSid만 바꾼다. mudfishFullvpnVid는 0이어야 한다. 웹 UI에는 sid와 vid가 둘 다 0보다 크면 막는 로직이 있다.
  • 코어가 실행 중일 때 sid를 바꾸면 웹 UI는 need_restart이면 재시작하고, 아니면 /do/fullvpn/refresh를 호출한다.
  • 현재 계정 값은 mudfishFullvpnSid: 0(Full VPN 꺼짐)이다. sid > 0이면 Full VPN이 켜진 것으로 동작한다(status.fullvpn_enabled). 아직 sid를 넣고 실제로 연결해 보지는 않았다. 아래 "검증이 필요한 부분"을 참고한다.

일본 노드

  • staticNodes 228개 중 location이 JP Asia (...)로 시작하는 노드가 135개다. 예: JP Asia (Tokyo - Linode3 IDC)(sid 9), JP Asia (Osaka - Vultr 1). 지역은 도쿄, 오사카, 홋카이도다.
  • 핑은 GET /do/rtt/staticnodes의 rttavg(ms)를 sid로 맞춰 쓴다. 데몬이 켜져 있으면 미꾸라지가 주기적으로 측정한다. 측정 실패 노드는 failure > 0이거나 목록에 없다.

연결/해제 실측 (아이템 모드로 시험)

  • start 후 약 6초 만에 core_running → core_ready → client_ready가 모두 true가 됐다. tap0가 생기고 아이템 IP 대역이 tap0로 잡혔다.
  • stop 후 몇 초 안에 코어 프로세스가 종료되고 tap0도 사라졌다. 다만 노드 IP로 가는 /32 경로 349개가 기존 게이트웨이(via 192.168.1.1 dev wlan0)로 남았다. 같은 네트워크에서는 무해하지만, 다른 와이파이로 옮기면 그 노드들로 가는 경로가 틀어질 수 있다.
  • 리눅스판에는 Windows의 프로세스 단위 캡처(WFP)가 없다. 아이템 모드에서는 지포스나우 로그인과 API 호스트(login.nvidia.com, play/pcs/uas/als/mes.geforcenow.com)가 미꾸라지를 거치지 않는다. 그래서 Full VPN이 필요하다(이 문서의 전제).

지포스나우 쪽 관찰

  • 스팀덱 지포스나우 로그(2026-07-27)에 지역이 KR GFN 1(prod.gkr.geforcenow.nvidiagrid.net)로 잡혀 있었다. 앱이 켜질 때 IP로 지역을 정하므로, 앱을 켜기 전에 VPN이 연결되어 있어야 한다. 그래서 패널에 "연결됨 + 일본 IP"를 분명하게 보여 주는 것이 중요하다.

5. 정해진 설계 (사용자 결정)

  • Full VPN 방식만 지원한다.
  • 켜기와 끄기는 사용자가 수동으로 한다. 자동 끄기나 알림은 넣지 않는다.
  • 로그인은 데스크탑 모드에서 미리 한다(완료). 게임모드 로그인은 범위 밖이다.
  • 끄기를 눌러도 계정의 Full VPN 노드 설정(sid)은 지우지 않는다. → 2026-10-26 변경(사용자 결정): 켤 때 바꾸기 직전 계정 값(mudfishFullvpnSid/Vid)을 저장해 두고, 터널을 내리는 모든 경로(끄기, 연결 실패, 절전 복귀, 종료, 제거)에서 그 값으로 되돌린다. 실패하면 대기로 남겨 플러그인 시작 시와 1분마다 다시 시도한다.
  • 노드 선택은 **기본이 "자동"**이고, 켤 때마다 그 시점에 핑이 가장 낮은 일본 노드를 고른다. 수동 선택은 "고급" 접기 메뉴 안에 숨긴다.
  • 개발 중에는 Claude가 SSH로 스팀덱에 직접 배포하고 테스트한다.

6. 패널 구성

위에서 아래 순서다.

  1. 상태 줄: 꺼짐 / 연결 중… / 연결됨 / 오류.
    • 연결됨이면 현재 공인 IP와 국가(예: 🇯🇵 JP · 1.2.3.4)와 연결 후 지난 시간(예: 12분째)을 보여 준다.
    • 국가가 JP가 아니면 경고색으로 표시한다.
  2. 켜기/끄기 토글.
  3. 노드 줄: 자동 · Tokyo - Linode3 IDC (38ms)처럼 실제로 쓰는(또는 쓸) 노드를 보여 준다.
  4. 고급 (접힘): 펼치면 "자동" 항목과 일본 노드 목록이 나온다. 목록은 핑 오름차순이고 측정 실패 노드는 맨 아래에 둔다. 하나를 고르면 그 노드로 고정되고, "자동"을 고르면 자동으로 돌아간다. 이 선택은 플러그인 설정에 저장한다.
  5. 오류/안내 문구(필요할 때만):
    • 로그인 토큰 만료: "데스크탑 모드에서 미꾸라지(127.0.0.1:8282)에 '로그인 기억하기'로 다시 로그인하세요".
    • 미꾸라지가 설치되지 않음: 설치 가이드 안내.
    • 연결 실패: 원인 한 줄.

7. 동작 규칙

  • 데몬 상주: 플러그인이 로드되면(부팅 시 포함) 미꾸라지 데몬이 떠 있지 않을 때만 transient systemd unit으로 띄운다. 이 상태는 코어 미실행 대기 상태라 핑 측정만 한다. 코어는 절대 자동으로 시작하지 않는다.
  • 켜기:
    1. 데몬을 확인한다.
    2. 필요하면 자동 로그인한다.
    3. 노드를 정한다. 자동이면 핑 최저 일본 노드, 수동이면 저장된 sid다.
    4. 계정 sid가 다르면 /prog로 갱신한다.
    5. /do/mudfish/start를 호출한다.
    6. client_ready가 될 때까지 기다린다.
    7. 공인 IP와 국가를 확인한다.
  • 연결 중 노드 변경(고급 메뉴): sid를 갱신한 뒤 /do/fullvpn/refresh를 호출하거나, need_restart이면 stop 후 start한다.
  • 끄기: /do/mudfish/stop을 호출하고, 코어 종료와 tap0 소멸을 확인한다. 미꾸라지가 남긴 노드 /32 경로를 정리한다. 데몬은 계속 떠 있다.
  • 상태 판단의 기준은 미꾸라지 실제 상태(/do/mudrun/status와 tap0 존재)다. 플러그인 내부 플래그에 의존하지 않는다. 패널을 열 때마다 새로 조회하고, 열려 있는 동안 주기적으로 갱신한다.
  • 미꾸라지 경로는 /opt/mudfish/*/bin/mudrun-headless 중 가장 높은 버전을 쓴다.
  • 미꾸라지 실행 파일은 root로 실행된다. 플러그인은 경로를 고정된 위치에서만 찾고, 사용자 입력을 명령어에 넣지 않는다.

8. 구현 기본값 (빌더가 바꿔도 되는 것)

  • 데키 플러그인 표준 구성(공식 decky-plugin-template 계열)을 쓴다. 프론트엔드는 React/TypeScript(@decky/ui, @decky/api), 백엔드는 main.py(Python, plugin.json의 flags: ["root"])다.
  • HTTP 호출은 백엔드에서 Python 표준 라이브러리로 한다. 스팀덱에 별도 패키지를 설치하지 않는다.
  • 공인 IP와 국가 확인 서비스는 빌더가 정한다. 예: https://ipinfo.io/json, https://myip.mudfish.net.
  • systemd unit 이름, 폴링 주기, 타임아웃, 남은 경로 정리 방법(어떤 경로를 미꾸라지가 추가한 것으로 볼지)은 빌더가 정한다.
  • 빌드는 Windows PC에서 하고, SSH로 ~/homebrew/plugins/<플러그인>/에 배포한 뒤 plugin_loader를 재시작한다. 구체적인 도구는 빌더가 정한다.

9. 검증이 필요한 부분 (구현 초기에 실측)

  1. /prog로 mudfishFullvpnSid를 바꾼 뒤 start했을 때 실제로 모든 트래픽이 tap0로 가고 공인 IP가 일본으로 바뀌는지. 기본 경로가 어떻게 바뀌는지, DNS는 어떻게 되는지도 확인한다.
  2. 연결 중 sid를 바꿀 때 need_restart 값과 /do/fullvpn/refresh의 실제 효과.
  3. Full VPN을 끈 뒤 기본 경로와 DNS가 원래대로 돌아오는지, 남는 것이 무엇인지.
  4. 절전 후 깨어났을 때 데몬과 터널 상태.
  5. 데스크탑 모드와 게임모드를 오갈 때 systemd로 띄운 데몬이 유지되는지.
  6. 스팀덱 기본 Tailscale(tailscale0)과 Full VPN이 충돌하지 않는지.

실측 결과 (2026-10-24, 구현 Task 1)

  1. Full VPN이 동작한다. /prog로 mudfishFullvpnSid=165(Osaka - Azure 01)를 저장하고 start하자 약 8초 뒤 client_ready가 됐다.
    • 경로: 0.0.0.0/1과 128.0.0.0/1을 tap0로 보내는 분할 방식이다. 기존 default via 192.168.1.1은 그대로 남는다.
    • 공인 IP: 13.73.234.162, JP(Microsoft).
    • login.nvidia.com과 play.geforcenow.com이 모두 dev tap0으로 나간다.
    • DNS는 그대로 1.1.1.1이지만, 1.1.1.1로 가는 질의도 터널을 타서 일본에서 질의한 것으로 보인다.
  2. 꺼진 상태에서 sid를 저장하면 /prog 응답이 need_restart: True다. 로컬 status.mudfish_fullvpn_sid는 다음 start 때 반영된다.
  3. 연결된 상태에서 sid를 165 → 504로 바꾸면 need_restart: False다. 이어서 /do/fullvpn/refresh를 부르면 재연결 없이 새 노드(Amazon 오사카, 13.208.184.0)로 바뀐다. 이때 응답 본문은 비어 있다.
    • 단, 로컬 status.mudfish_fullvpn_sid는 165로 남는다. 그래서 현재 노드는 status가 아니라 플러그인 저장값이나 클라우드 progConf로 판단해야 한다.
  4. 해제하면 tap0가 없어지고, 분할 경로가 사라지고, 국가가 KR로 돌아온다. DNS 설정은 변화 없다. 노드 /32 경로는 이번 연결에서 5개가 새로 남았다(이전 시험분 포함 총 353줄).
  5. /do/rtt/staticnodes는 측정 라운드가 도는 동안 {"status": 300}만 준다. rtts는 라운드가 끝난 순간에만 status: 200으로 잠깐 나오고, 라운드 간격은 1분 이상이다. 그래서 플러그인은 마지막으로 성공한 측정값을 저장해 둬야 한다.
  6. Tailscale은 로그아웃 상태(/opt/tailscale/tailscale status → Logged out)라 충돌 우려가 없다.
  7. 게임모드 전환 시 데몬 유지: 확인했다. 데스크탑 모드에서 게임모드로 넘어간 뒤에도 mudfish-decky unit이 active였다.

실사용 검증 (2026-10-24, 구현 Task 7)

  • 게임모드 패널(사용자 확인): 꺼짐 상태와 자동 노드 표시, 켜기 → 🇯🇵 JP, 고급에서 노드 변경, 끄기가 모두 정상이었다. 끌 때마다 미꾸라지가 남긴 노드 경로 400여 줄이 정리되어 라우팅 테이블에는 기본 2줄만 남았다.
  • 지포스나우(사용자 확인): 플러그인으로 켠 상태에서 로그인하고 게임을 시작했다. GFN 로그에 Lowest latency zone is ap-japan.cloudmatchbeta.nvidiagrid.net with 40ms가 남았다(이전에는 KR GFN 1).
  • 절전 버그와 결정: 켠 채로 절전했다 깨우면 NetworkManager가 wlan0 경로를 다시 만들면서 미꾸라지의 중계 서버 /32 경로를 지운다. 반면 tap0의 0/1, 128/1 경로는 남는다. 그래서 중계 서버로 가는 패킷까지 터널로 들어가 인터넷 전체가 끊긴다. 이때도 미꾸라지는 client_ready: true를 보고한다. 중계 서버 /32 경로를 다시 넣으면 복구되는 것으로 원인을 확인했다.
    • 사용자 결정: 절전에서 깨어날 때와 덱 종료(플러그인 unload) 때 VPN을 자동으로 끈다.
    • 구현: CLOCK_BOOTTIME − CLOCK_MONOTONIC 증가로 깨어남을 감지하고 2초마다 확인한다. 깨어난 것이 감지되면 teardown하고 안내 문구를 표시한다.
    • 재검증(사용자 확인 + 로그 resume detected; turning VPN off): 깨어난 직후 인터넷이 정상이고 패널은 꺼짐이다.
  • 재부팅(사용자 확인): 재부팅 후 패널은 꺼짐이고 코어는 미실행이다. 부팅 시 prepare()가 와이파이 연결 전에 실행되어 자동 로그인이 한 번 실패하지만, 노드 목록은 저장된 값을 쓰고 켜기를 누르면 그때 로그인한다.

추가 기능: 휴대폰 QR 로그인 (2026-10-24, 사용자 요청)

  • 목적: 저장된 로그인이 거부되면 데스크탑 모드로 가지 않고 게임모드에서 휴대폰으로 다시 로그인한다.
  • 사용자가 승인한 설계:
    • 패널에 "휴대폰으로 로그인 (QR)" 버튼을 띄운다. 플러그인 시작 시나 켜기를 누를 때 SigninError가 나면 need_login이 된다.
    • 버튼을 누르면 QR 팝업이 뜨고, 그동안 덱의 LAN IP의 무작위 포트로 런처 UI를 중계한다.
    • 중계는 일회용 토큰 /_mfd/<token>으로 보호한다. 이 경로를 처음 연 IP만 통과하고 나머지는 403이다.
    • 최대 5분 동안 열고, 새 세션(jwt 변경)이 감지되거나 팝업을 닫거나 만료되거나 unload되면 닫는다.
  • 확인된 사실:
    • api.mudfish.net은 CORS로 요청 Origin을 그대로 허용한다.
    • 런처 UI는 LAN 주소로 열어도 로그인 흐름이 동작한다.
    • /logout.html을 열면 런처가 autologin=False, token_v3="0"이 된다. 이 상태를 "저장된 로그인 없음"으로 처리한다.
  • 실사용 검증(사용자 확인 + 로그):
    • 로그아웃 → 패널에 QR 버튼이 표시됐다.
    • 휴대폰(192.168.1.60)이 인증됐고 10초 뒤 로그인이 감지되어 중계가 닫혔다.
    • 이후 자동 로그인이 다시 켜졌고 켜기가 정상 동작했다. 닫힌 뒤 LAN에 남은 포트는 없다.

추가 기능: 미꾸라지 설치·업데이트·제거 (2026-10-24, 사용자 요청)

이 기능으로 2절의 비목표 "미꾸라지 설치와 업데이트"를 철회한다.

  • 설치:
    • mudfish.net/download에서 최신 mudfish-<ver>-linux-x86_64.sh를 찾는다.
    • 이 파일은 Makeself 형식(셸 헤더 skip줄 + bzip2 tar, 헤더에 MD5)이다. MD5를 검증한 뒤 tar를 직접 푼다. 이때 경로 조작과 링크를 거부하고 filter="data"를 쓴다.
    • 결과는 /opt/mudfish/<ver>/{bin,sbin,etc,share,var}이다. 다운로드한 스크립트는 실행하지 않는다.
    • 실제 6.6.0을 이 방식으로 설치하면 데몬 바이너리가 공식 설치본과 동일하다. 공식 스크립트가 복사하지 않는 mudstat, .desktop, polkit 파일도 함께 들어간다.
  • 업데이트:
    • 최신 버전 확인은 하루 1회 백그라운드에서 한다. 실제 업데이트는 버튼으로만 하고, 터널이 있으면 거부한다.
    • 새 버전을 설치한 뒤 var/.conf와 var/.conf.db를 복사하고, 데몬을 새 버전으로 재시작한다. 직전 버전 1개만 남긴다.
    • 실측: 6.5.5에서 QR 로그인 후 6.6.0으로 업데이트했다. 재로그인 없이 isSignedIn=True, autologin=True였다.
  • 제거(사용자 결정: 버튼과 플러그인 삭제 시 모두):
    • 순서: 터널 teardown → 데몬 중지(--no-block) → mudfish-tap 삭제 → /opt/mudfish 삭제 → 플러그인 설정 비우기.
    • 데키 3.3.0 제약: SIGTERM과 동시에 소켓이 닫히면 sandboxed plugin의 listener가 EOF에서 돌며 이벤트 루프를 굶긴다. 그러면 _unload/_uninstall이 실행되지 못하고 5초 뒤 SIGKILL된다. 이 덱에서 여러 플러그인이 이 상태로 수십 % 비율로 종료됐다.
    • 대책 1: _unload/_uninstall은 동기로 실행한다.
    • 대책 2: 일시적인 systemd path 유닛 mudfish-decky-cleanup이 plugins 폴더를 감시한다. 이 플러그인 폴더가 20초 동안 없으면 정리하고 스스로 멈춘다. 재설치나 업데이트처럼 폴더를 바로 다시 만드는 경우는 건드리지 않는다.
    • 실측: 데키에서 플러그인을 삭제하자 _uninstall과 감시가 모두 동작했다. /opt/mudfish, 설정, 데몬, 프로세스가 모두 없어졌다.

10. 끝나면 할 일

  • 스팀덱의 /etc/sudoers.d/zz-dev-nopasswd를 지운다(사용자가 직접 실행: sudo rm /etc/sudoers.d/zz-dev-nopasswd).
  • 개발 중에 띄운 mudfish-dev transient unit을 정리한다.