CH-J ToolsSSH를 통한 서버 관리
메뉴

원격 편집기 저장 및 지연 시간 모니터링

파일 다운로드

이 번역은 현재 언어 검수 중입니다. 내용이 불분명한 경우 영어 원문과 비교해 주세요.

근본 원인

이전 편집기는 작업 파일을 대상 파일의 상위 디렉터리에 업로드했는데, /etc나 /var 아래에서는 SSH 계정이 그 디렉터리에 쓸 수 없는 경우가 많았습니다. SFTP 이름 바꾸기에는 승인된 sudo 경로, 기준 상태 충돌 확인, 보안 메타데이터 검증, 신뢰할 수 있는 덮어쓰기 방식이 없었습니다. 또한 오류 처리기가 임시 사본을 삭제했기 때문에, 완료 여부가 불확실한 경우에도 복구에 쓸 자료가 사라졌습니다.

이전에는 세션 상태가 연결 상태만 보여 주었습니다. 터미널 표시기에 반영되는 ICMP나 인증된 SSH 응답 측정이 없었습니다.

저장 프로토콜

  1. 고정된 SSH 도우미가 디스크립터 기준 상대 작업과 모든 경로 구성 요소에 대한 O_NOFOLLOW를 사용해 링크가 하나뿐인 일반 파일을 엽니다. 내용, SHA-256 해시, 장치/inode, UID, GID, 전체 권한 비트, 나노초 단위의 수정/변경 시간, 읽을 수 있는 모든 확장 속성을 기록합니다. Core는 해시를 검증하고 BOM을 유지한 채 엄격한 UTF-8로 디코딩합니다. 각 문서에는 해당 플러그인, 터미널 세션, 서버 식별 정보로 범위가 한정된 불투명한 editId가 부여됩니다.
  2. 권한이 없는 준비 작업이 pwd.getpwuid(os.getuid())로 인증된 계정의 홈 디렉터리를 확인하고, 0700 모드로 $HOME/ch-j-sm/을 만든 뒤, 비공개 전용 복구 저널을 만듭니다. 안전하지 않은 홈, 심볼릭 링크 작업 공간, 다른 계정이 소유한 작업 공간은 거부됩니다.
  3. Core는 배타적 생성(wx), 0600 모드, 60초의 채널/업로드 시간 제한을 사용해 숨겨진 무작위 128비트 이름의 .tmp 작업 파일을 SFTP로 업로드합니다. 저널에는 대상, 원래 기준 상태, 예상 작업 파일 해시가 들어 있으며, 자격 증명은 들어 있지 않습니다.
  4. 한 번의 SSH exec가 최종 처리 전체를 수행합니다. 필요에 따라 기존 sudo 실행기를 통해 실행됩니다. 도우미는 비공개 작업 사본을 안전하게 열어 해시를 계산하고 디스크에 기록합니다. 원래 inode를 잠그고 기준 상태를 확인한 뒤, 대상 옆에 배타적 대상 파일을 만들어 모든 내용을 쓰고, 메타데이터를 적용·검증한 다음, 원본을 다시 확인하고 교체 파일을 디스크에 기록합니다.
  5. 같은 디렉터리 안에서 os.replace로 대상을 원자적으로 교체합니다. 상위 디렉터리를 디스크에 기록하고 최종 경로, 해시, inode, 메타데이터를 검증합니다. 교체가 끝날 때까지 원래 경로는 계속 사용할 수 있습니다. 작업 파일은 파일 시스템을 넘어 이동되지 않으며, 최종 처리 과정에서 삭제되지도 않습니다.
  6. Core가 검증된 성공 결과를 받은 후에는 별도의 정리 단계가 최종 내용과 보안 메타데이터를 다시 확인한 다음 작업 파일과 저널을 삭제합니다. 정리에 실패해도 recoveryAvailable: true와 함께 저장 확정이 보고됩니다.

업로드 실패, sudo 실패, 충돌, 메타데이터 오류, 교체 실패가 발생하면 원본과 준비된 모든 자료가 유지됩니다. 완료되지 않은 업로드는 그렇게 표시되며, 완전한 편집 문서로 복구할 수 없습니다. SSH 통신 끊김, 종료 상태 누락, 시간 초과, 예상치 못한 최종 처리 결과는 저장됨이 아니라 알 수 없음으로 보고됩니다. 결과가 불확실한 문서에 대해서는 Core가 다음 저장을 막으며, 교체를 자동으로 재시도하지 않습니다.

메타데이터, 권한 및 안전 경계

도우미는 UID, GID, 그리고 실행, setuid, setgid, sticky 비트를 포함한 모든 POSIX 권한 비트를 보존하고 검증합니다. POSIX ACL, capabilities, SELinux 레이블을 포함한 Linux 확장 속성을 바이트 단위로 그대로 재현합니다. 쓰기/chown이 capabilities나 특수 권한 비트를 지울 수 있으므로, 소유권과 파일 내용을 보안 속성보다 먼저 설정합니다. 원본에는 없지만 대상 파일이 상속한 속성은 제거됩니다. 수정 시간은 새로 쓴 시점을 반영하며 원본에서 복사하지 않습니다.

읽을 수 없거나 표현할 수 없는 속성이 있으면 작업이 중단되며, 메타데이터가 몰래 버려지지 않습니다. IMA/EVM 서명은 명시적으로 거부됩니다. 내용/inode에 묶인 무결성 서명을 복사하면 서명이 무효가 되기 때문입니다. 대상 경로나 홈 경로의 심볼릭 링크, 하드 링크된 파일, 일반 파일이 아닌 파일은 거부됩니다. 제자리 덮어쓰기나 더 약한 SFTP 덮어쓰기 방식으로 대체하지 않습니다.

쓰기가 가능한 일반 저장은 SSH 계정으로 실행됩니다. 보호된 저장에는 명시적인 { sudo: true }가 필요하며, 비밀번호 없는 sudo라면 sudo 비밀번호는 선택 사항입니다. 비밀번호는 검증된 뒤 채널 stdin으로만 전달되며, 명령, 저널, 작업 파일, 로거, 원격 오류에는 절대 포함되지 않습니다. root SSH 세션은 기존 직접 root 실행기를 사용합니다. 권한 부여에 실패하면 원본과 작업 사본은 그대로 남습니다. root로 실행되는 모든 저장(직접 root SSH 또는 sudo)은 다른 계정이 소유하거나 쓸 수 있는, 또는 접근 ACL이 설정된 대상 디렉터리/상위 디렉터리도 거부하며, 다른 계정이 제어하는 특권 경로 이름을 신뢰하지 않습니다.

고정된 도우미는 python3 -I -B로 실행됩니다. 사용자 Python 경로, 작업 디렉터리, 사용자 site-packages를 무시하며 바이트코드를 쓰지 않습니다. 경로와 JSON 인수는 Core에서 셸용으로 인용 처리됩니다. 플러그인은 임의의 SSH exec나 SFTP 핸들을 받지 않습니다. 기존 샌드박스, CSP, 매니페스트 권한, 호스트 키 검증, 업데이트 검증은 계속 적용됩니다.

Plugin API 및 남은 Monaco 통합

별도로 배포되는 Monaco/File Manager UI는 이 체크아웃에 없습니다. 그 UI 통합, sudo 확인 창, 저장되지 않은 버퍼 처리, 번역은 여기에서 구현하거나 테스트할 수 없었습니다. 번들된 플러그인 소스는 Hash & Checksum 플러그인입니다. 대체 File Manager 소스를 임의로 만들지 않았습니다.

기존 메서드는 계속 사용할 수 있습니다. 추가된 메서드/옵션은 샌드박스 preload와 권한을 확인하는 런타임을 통해 제공됩니다:

const opened = await chjPlugin.files.readText(sessionId, path);
// For a protected read, explicitly pass { sudo: true, sudoPassword }.

const result = await chjPlugin.files.saveText(sessionId, path, editor.getValue(), {
  editId: opened.editId,
  sudo: true,           // Include only after the user authorizes elevation.
  sudoPassword         // Omit for passwordless sudo; never persist this value.
});

if (result.ok && result.value.status === "saved") {
  // Mark this exact submitted editor version as saved. Later edits stay dirty.
} else {
  // Keep the Monaco model dirty. Translate result.error.code/status.
  // result.error.recoveryId/recoveryPath identify retained working material.
}

await chjPlugin.files.closeText(opened.editId);
const recovery = await chjPlugin.files.listRecovery(sessionId);
const copy = await chjPlugin.files.readRecovery(sessionId, recoveryId);
// Review copy.status and copy.text; open a fresh document before another save.
await chjPlugin.files.cleanupRecovery(sessionId, recoveryId);
// Cleanup rejects anything whose target is not currently confirmed.

대상을 확인하는 데 권한 상승이 필요한 경우, 복구 메서드는 마지막 인수로 선택적인 { sudo: true, sudoPassword }를 받습니다. sudo로 실행하더라도 인증된 사용자의 작업 공간이 유지됩니다. 저널은 앱을 다시 시작하거나 SSH를 다시 연결한 후에도 남아 있으며, 복구가 최종 처리를 자동으로 반복하지는 않습니다. 복구한 버퍼를 명시적으로 저장하기 전에 새로 연 대상과 비교해 확인하세요.

files.read는 읽기, 검사, 핸들 해제를 보호합니다. files.write는 저장과 복구 정리를 보호합니다. 기존 플러그인을 위해 writeText는 실패 시 계속 reject를 반환합니다. 반면 saveText는 Electron이 reject된 Error 객체의 사용자 지정 속성을 버리기 때문에 구조화된 { ok, value/error } 형태를 반환합니다. 기존 3인수 writeText는 열린 문서가 하나일 때 그 문서의 기준 상태를 사용할 수 있습니다. 같은 경로에 열린 문서가 여러 개라면 editId가 필요하며, 모호한 기존 호출은 다른 문서의 기준 상태를 고르지 않고 안전하게 실패합니다. 플러그인은 사용하지 않는 핸들을 해제해야 합니다.

외부 UI는 저장 중임을 즉시 표시하고, 실패하면 버퍼를 저장되지 않은 상태로 유지하며, 권한/인증 실패 후에만 sudo를 제안하고, 덮어쓰지 않고 충돌을 표시하며, 연결이 끊기거나 결과를 알 수 없을 때 복구를 제안해야 합니다. 이 상태들은 체코어, 독일어, 영어로 번역해야 합니다. 권장 상태 문구:

상태 체코어 독일어 영어
저장 중 Ukládání… Wird gespeichert… Saving…
확정 Uloženo Gespeichert Saved
실패 Uložení selhalo Speichern fehlgeschlagen Save failed
sudo 필요 Vyžadováno oprávnění sudo sudo-Autorisierung erforderlich Sudo authorization required
충돌 Původní soubor byl změněn Originaldatei wurde geändert Original file changed
연결 끊김 Spojení bylo přerušeno Verbindung unterbrochen Connection lost
알 수 없음 Výsledek uložení není znám Speicherergebnis unbekannt Save result unknown
복구 Obnova je dostupná Wiederherstellung verfügbar Recovery available

지연 시간 정의 및 수명 주기

Ping은 SSH가 사용하는 확인된 IP로 보낸 ICMP echo에 대해 클라이언트가 실제로 받은 응답으로 보고되는 지연 시간입니다. Core는 셸 없이, 관리자 권한으로 시작할 필요도 없이 고정된 인수 배열로 플랫폼의 ping 실행 파일을 비동기로 호출합니다. Linux, macOS(ping6 포함), Windows의 인수는 각각 따로 선택됩니다. ICMP 차단, 실행 파일 누락, 시간 초과, 잘못된 출력, <1ms처럼 상한값만 주는 Windows 응답은 숫자를 지어내지 않고 '사용할 수 없음' 값이 됩니다.

SSH RTT는 기존의 인증된 SSH 클라이언트로 고정된 true exec 요청을 실행하는 데 걸린 단조 performance.now() 시간입니다. 채널 설정, 서버 처리, 응답 전달이 포함되며, 순수한 네트워크 RTT나 SSH 로그인 시간이 아닙니다. 번역된 표시기 도움말이 이 차이를 설명합니다.

모니터링은 터미널이 연결되자마자 시작됩니다. 프로브는 동시에 비동기로 실행되며, 요청/프로세스 시간 제한은 5초입니다. 겹침을 막기 위해 다음 샘플은 이전 샘플이 끝난 뒤 12초 후로 예약됩니다. 연결을 끊으면 ICMP를 중단하고, 타이머를 지우고, 대기 중인 SSH 작업을 취소하고, 값을 비웁니다. 레코드 식별 확인은 다시 연결한 후 이전 연결이 값을 게시하지 못하게 합니다. ICMP 실패는 SSH 연결 상태를 절대 바꾸지 않습니다.

지연 시간은 기존 state 이벤트 / ssh:state IPC 페이로드에 담겨 전달됩니다:

latency: { pingMs: 24.1, sshRttMs: 31.25, measuredAt: "…" }

사용할 수 없는 숫자는 null입니다. 간결한 터미널 표시기는 밀리초, 현지화된 '사용할 수 없음' 문구, 체코어/독일어/영어 레이블을 사용하며, 연결이 끊기면 숨겨집니다. 렌더러에 추가 실행 기능이나 IPC 요청은 필요하지 않았습니다.

검증 및 운영상 제한

사용자 요청에 따라 변경 사항은 기존 작업 트리(현재 feature/hash-checksum-plugin)에 유지했으며, 브랜치 전환이나 관련 없는 정리는 하지 않았습니다.

이 작업에서 변경된 파일