Skip to content

feat: ssh device suite#21

Merged
keyldev merged 10 commits into
mainfrom
feat/phase-13-ssh-device-suite
Jun 19, 2026
Merged

feat: ssh device suite#21
keyldev merged 10 commits into
mainfrom
feat/phase-13-ssh-device-suite

Conversation

@keyldev

@keyldev keyldev commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds direct device management over SSH for OpenIPC cameras:
an interactive terminal, an SCP file manager, "open web interface", and an
SSH transport for the Majestic config. All cross-platform (Win / Lin / Mac /
Android / iOS) — desktop opens windows, mobile uses full-screen overlays, and
local file I/O goes through StorageProvider pickers (no sandbox issues).

  • 13.1 — SSH abstraction + implementation. ISshSession / ISshShell /
    ISshSessionFactory + SshEndpoint in Core (no package deps). Implemented in
    Infrastructure on SSH.NET 2025.1.0: connect (password / private key), exec,
    directory listing via ls -la, SCP upload/download with progress, delete,
    mkdir, interactive shell. Host keys are pinned on first use (TOFU) via the
    secrets store; a changed key is refused.
  • 13.2 — Open in browser. "Open web" opens http://{host}[:port] through
    the platform launcher.
  • Per-camera SSH credentials + port. Migration 008 adds SshPort (NULL =
    22); SSH login/password live in the secrets store under cam:{id}:ssh:*,
    separate from the RTSP/ONVIF login, with fallback to the main login.
  • 13.5 — Majestic config over SSH. MajesticSshConfigClient reads
    /etc/majestic.yaml (cat), writes it atomically (SCP + mv) and reloads
    (killall -HUP majestic). Surfaced via "Edit via SSH" on the camera page —
    a fallback for when the HTTP API is disabled.
  • 13.3 — SSH terminal. A small VT/ANSI emulator in Core (CSI cursor/erase
    moves, SGR colors, scroll, UTF-8 reassembly), a TerminalView control
    (grid render + keyboard → shell bytes), and SshTerminalViewModel.
  • 13.4 — File manager (SCP). Remote FS browser: navigate, download, upload
    (progress), mkdir, delete with confirm. Root-level deletes are refused
    (RemotePathGuard).

Architecture

  • App → Core only; SSH.NET impl is wired into the heads via DI
    (SshNetSessionFactory). ViewModels stay unit-testable.
  • New entry points in the camera library row: Open web, SSH, Files.

Testing

  • Unit (~20): ls -la parser, root-delete guard, RemotePath helpers,
    VT emulator (control codes, SGR, scroll, partial-UTF-8).
  • Integration (13.6): new OpenIPC.Viewer.Infrastructure.Tests round-trips
    exec/list/upload/download/mkdir/delete against a real sshd
    (tools/sshd/docker-compose.yml). SkippableFact — skips when the container
    is down (e.g. Windows CI without Docker).

Related

Type

  • Bug fix
  • Feature
  • Refactor / cleanup
  • Docs / CI
  • Other:

Checklist

  • Builds with 0 warnings (TreatWarningsAsErrors=true).
  • Tests pass (dotnet test); new Core logic has unit tests.
  • No layering violation — App references Core only (Infrastructure / Video / Devices wired via DI in a head).
  • Scope stays within one phase (didn't pull work from a later phase's "Не входит").
  • README / docs updated if public commands, options, or setup changed.

Platforms tested

  • Windows
  • Linux
  • macOS
  • Android
  • iOS
  • CI build only

Screenshots / notes

keyldev added 7 commits June 19, 2026 16:36
- ISshSession/ISshShell/ISshSessionFactory + SshEndpoint in Core (no deps)
- SSH.NET 2025.1.0 impl: exec/list/scp/shell, host-key TOFU via secrets store
- busybox ls -la parser + root-delete guard (+unit tests)
- 13.2 "Open web": library button -> LaunchUriAsync, Camera.WebInterfaceUrl
- migration 008: SshPort column (NULL = 22)
- SSH login/password in secrets store under cam:{id}:ssh:* (separate from RTSP/ONVIF)
- CameraDirectoryService.GetSshEndpointAsync: SSH creds or fall back to main
- SSH section in camera editor (user/password/port, optional) + validation
- IMajesticSshConfigClient + impl: cat read, scp+mv write, killall -HUP reload
- SingleCameraPage "Edit via SSH" button (advanced opt-in) -> raw editor -> write+restart
- fallback transport for when the HTTP Majestic API is disabled
- basic VT/ANSI emulator in Core (CSI moves, SGR colors, scroll, UTF-8) + tests
- TerminalView control: grid render + keyboard -> shell bytes
- SshTerminalViewModel over ISshShell; non-modal window on desktop, overlay on mobile
- "SSH" button in the camera library row
- FileManagerViewModel: browse remote FS, download/upload with progress, mkdir, delete (root-guard + confirm)
- cross-platform: remote panel + OS file pickers for local I/O (no local panel, avoids mobile sandbox)
- ISshSession.CreateDirectoryAsync; RemotePath helpers (+tests); reusable PromptDialog for folder name
- "Files" button in the camera library row; window on desktop, overlay on mobile
- new OpenIPC.Viewer.Infrastructure.Tests project
- round-trip exec/list/upload/download/mkdir/delete against a real sshd (SkippableFact)
- tools/sshd/docker-compose.yml + SshdFixture; skips when the container is down
@keyldev keyldev marked this pull request as draft June 19, 2026 14:51
keyldev added 3 commits June 19, 2026 18:02
- AndroidIPInterfaceProperties.GatewayAddresses throws PlatformNotSupported,
  killing the app at startup via SettingsPageViewModel ctor -> NIC selector
- catch it and treat the NIC as gateway-less when unsupported
- Settings -> SSH: strict host-key toggle, default port, terminal font size, majestic.yaml path, "forget all host keys"
- move host-key TOFU from the secrets store to JsonFileHostKeyStore (ISshHostKeyStore) so reset is one op
- strict-off accepts and re-pins a changed key (reflashed camera); wired via IUserSettingsAccessor
- TerminalView honors the configured font size
- OverlayDialogPresenter gains a fullScreen mode (fills TopLevel, no sheet card / scroll wrapper)
- terminal & file manager use it on mobile; safe-area top inset so the title clears the status bar
- lower content MinWidth/MinHeight so they fit narrow phones
@keyldev keyldev marked this pull request as ready for review June 19, 2026 16:11
@keyldev keyldev merged commit 13880c4 into main Jun 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant