SSH client: a terminal on another machine (#2)
CI / build (pull_request) Successful in 2m22s
Site / build (pull_request) Successful in 10s

The SSH App opens one session to a shell, over libssh (LibSSH-ESP32 5.10.0)
on mbedTLS. A server is trusted the first time on its fingerprint, and a
changed key is a warning with Cancel selected. The password is typed each
time and kept nowhere; or the device makes itself an Ed25519 key, whose
public half is shown, written to /ssh/id_ed25519.pub and printed by
`ssh status`.

lib/term is the terminal: what a shell, less, top, nano and vim send, with
sixteen colours, scroll regions, the alternate screen and 100 lines of
scrollback. Five text sizes with Ctrl and + or -, from 60x20 to 26x8, told
to the far end. The session goes on when the App is left; SSH shows in the
Status Bar. `ssh user@host` in the Shell opens the App.

Also:
- Keys that aren't characters carry Shift, Ctrl and Alt. The terminal needs
  it, and it makes Ctrl+Fn+up/down in a note and Shift+Tab in Gemini work
  from the real keyboard.
- IRC doesn't try to connect under 60 KB free: started with a session open,
  its TLS handshake took the heap down to 236 bytes.
- libssh's own curve25519 is left out of the build (scripts/libssh_filter.py):
  libsodium's has the same names.

Costs 292 KB of flash and about 50 KB of heap while a session is open; not
started under 75 KB free.

Docs: guide page, how-to, FAQ, home page, Status Bar, SD card folders, the
memory how-to, README, glossary, N1 notes with Q254 to Q266 and the checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-08 04:38:36 +02:00
co-authored by Claude Opus 5.5
parent 7223147f26
commit 6b71aace4f
47 changed files with 2801 additions and 20 deletions
+2
View File
@@ -42,6 +42,7 @@ Irc | Wifi | Gnss | Gemini | Lora | Storage | Notes | Shell | System | Settings
ping <host> [count] [size] | nslookup <name> [server] | port <host> <port> | traceroute <host> | cancel is it there, does its name resolve, is its port open, which way; one at a time
tls <host> [port] | ntp [server] a TLS handshake: who the certificate is for, by whom, until when, and whether this device trusts it; a time server's clock against this one
ifconfig | arp | netstat the interfaces (Wi-Fi and the VPN), their addresses, the default route and the DNS servers; the neighbours heard; what listens and what is connected
ssh user@host[:port] | ssh status | ssh stop a terminal on another machine, in the SSH App; the password is asked there, never here
vpn status | vpn up [seconds] | vpn down | vpn import [path] | vpn forget | vpn auto on|off the WireGuard tunnel (Settings > VPN); import reads /vpn/wg0.conf; with seconds, it goes down by itself
debug status | debug off [seconds] the Debug Console over Wi-Fi (Settings > Debug Console); with seconds, it comes back
debug on | debug token <16 to 64 characters> | debug token new (USB serial only) switch it on, set its token
@@ -116,6 +117,7 @@ In **Safe Mode** (see [Crashes and Safe Mode](/dev/debug/crashes/)) only a few r
| `ping <host> [count] [size]` / `nslookup <name> [server]` / `port <host> <port>` / `traceroute <host>` / `cancel` | Network troubleshooting (issue #90): does a host answer and how fast; a name's addresses, from which DNS server and in how long; is a TCP port open, refused or silent; the routers on the way. Each runs on a task of its own and prints as it goes, one at a time; `cancel` stops it |
| `tls <host> [port]` / `ntp [server]` | A TLS handshake that checks nothing, then the certificate said in words: who it is for, who signed it, until when, its SHA-256, and whether this device's roots and the name asked for accept it (about 52 KB of heap while it runs; refused under 70 KB free). A time server's clock against the device's, with the round trip |
| `ifconfig` / `arp` / `netstat` | The interfaces (Wi-Fi and the VPN) with their addresses, MTU, which is the default route, and the DNS servers; the neighbours heard on the Wi-Fi; what listens and what is connected |
| `ssh user@host[:port]` / `ssh status` / `ssh stop` | Opens the SSH App and connects (the password is asked there, never on a console); the session's state and this device's public key; end the session |
| `vpn status` / `vpn up [seconds]` / `vpn down` / `vpn import [path]` / `vpn forget` / `vpn auto on\|off` | The WireGuard tunnel: its state, on (for that many seconds, then off by itself: for trying a configuration from afar), off, read a `.conf` from the card (`/vpn/wg0.conf`), erase it, start with Wi-Fi. No key is ever printed |
| `debug status` / `debug off [seconds]` | The Debug Console: whether it's on, has a token and a client; switch it off. With a number of seconds, it comes back by itself after that long |
| `debug on` / `debug token <value>` / `debug token new` | USB serial only: switch it on (making a token if there's none), give it a token of 16 to 64 characters, or make a new one. The token is never printed |
+80 -1
View File
@@ -8,7 +8,7 @@ docs = true
source = "docs/milestones/N1.md"
tag = "N1"
+++
**Status:** in progress. The WireGuard tunnel (issue #8) shipped as **v0.19.0**. The network troubleshooting commands (issue #90) shipped in two parts: `ping`, `nslookup`, `port`, `traceroute`, `ifconfig` and `arp` as **v0.20.0**; `tls`, `ntp` and `netstat` as **v0.21.0**. SSH (#2) is not started.
**Status:** in progress. The WireGuard tunnel (issue #8) shipped as **v0.19.0**. The network troubleshooting commands (issue #90) shipped in two parts: `ping`, `nslookup`, `port`, `traceroute`, `ifconfig` and `arp` as **v0.20.0**; `tls`, `ntp` and `netstat` as **v0.21.0**. The SSH client (issue #2) is built and waits for its release.
**Goal:** reach things from the device that aren't on the Wi-Fi it happens to be on, and keep its traffic private on a network that isn't yours.
@@ -149,3 +149,82 @@ With a tunnel, fixed addresses and a file server on the device, "is it the netwo
| Memory during a `tls` | 44.5 KB free at the lowest, from 104 KB |
**Not checked:** `tls` with IRC connected (it should refuse for lack of memory); `ntp` against a clock that is wrong; `netstat` while sharing.
## The SSH client (issue #2)
A terminal on another machine: one session to a shell, from the SSH App.
### Measured before deciding (2026-10-08)
`ewpa/LibSSH-ESP32` 5.10.0 (libssh on mbedTLS) was built into a trial firmware and a session opened against OpenSSH in a container, with a password.
| | Measured in the trial | As built |
|---|---|---|
| Flash | 120 KB | **292 KB** |
| Static RAM | 1.2 KB | |
| The session's task stack | 13 KB used | 13.7 KB used of 20 KB |
| Free heap with a session open | | 49 KB of 99 KB: it costs about 50 KB, the stack included |
| Lowest free heap during a login | | 30 KB |
| Key exchange (curve25519, ed25519 host key) | 227 ms | |
- **The trial undercounted the flash.** It logged in with a password. Signing with a key of the device's own (Q255) links libssh's table of multiples of the Ed25519 base point: `ge25519.c.o` alone is 109 KB. The rest of the difference is the public-key code, the terminal and three fonts. The firmware is at 71% of its slot.
- **libssh carries its own curve25519** (`src/external/curve25519_ref.c`) with the names libsodium uses, and libsodium is already here for WireGuard: two definitions don't link. A build script (`scripts/libssh_filter.py`) leaves libssh's copy out, and it uses libsodium's. It has to be a `pre:` script: libraries are built before any `post:` one runs.
- A session and a TLS connection don't fit together: IRC holds 40 KB.
### Decisions (design round 2026-10-08)
| # | Decision |
|---|---|
| Q254 | **LibSSH-ESP32 5.10.0**, with its duplicate curve file left out of the build. |
| Q255 | **A password, typed each time and never stored**, or **a key the device makes for itself** (Ed25519, no passphrase, kept in the settings store). Its public half is shown, written to `/ssh/id_ed25519.pub` and printed by `ssh status`. Keys made elsewhere aren't imported. |
| Q256 | **A terminal good enough for a shell, `less`, `top`, `nano` and `vim`:** cursor movement, erasing, sixteen colours, scroll regions, the alternate screen, the cursor keys' two modes. `TERM=xterm`. No mouse. |
| Q257 | **Five text sizes, changed with Ctrl and + or -** (the user's change to the round: the proposal was a setting). 4x6, 5x8, 6x10, 6x13 and 9x15 pixels: from 60 x 20 to 26 x 8 characters. The far end is told the new size; the choice is kept. |
| Q258 | **100 lines of scrollback**, as text, with Alt and up or down, as in the Shell. Not on the alternate screen. |
| Q259 | **Keys:** Ctrl with a letter; Tab; the backtick key sends Esc, as it is printed; Alt with it types a backtick; Fn with the arrow keys; Shift with those for Page Up and Down; Ctrl+Alt+q disconnects. Fn with backtick is Home, as everywhere. |
| Q260 | **The session outlives the App's time in front.** `SSH` in the Status Bar while one is open. |
| Q261 | **Not started under 75 KB free**, with the reason in words. |
| Q262 | **Up to eight hosts remembered**, the last used first, once a login has succeeded. Forgetting one forgets its server's fingerprint too, unless another remembered host is the same server. |
| Q263 | **Trust on first use,** on the SHA-256 fingerprint; sixteen servers remembered. **A changed key is a warning**, with Cancel selected. |
| Q264 | **UTF-8 in, the fonts' Latin-1 out:** what they lack is `?`, box-drawing lines are `+ - \|`. |
| Q265 | **`ssh user@host` in the Shell opens the App** and connects there. The password is never asked on a console. |
| Q266 | **Not built:** port forwarding, SFTP and scp, jump hosts, agent forwarding, keys with a passphrase, more than one session. |
### As built
- **`lib/term`** (host-tested, 12 tests in `test/test_terminal`): `Terminal`, the screen a program's output makes, with its history and the replies a program asks for; `encodeKey`, what a key sends; `SshHosts` and `SshKnownHosts`, the two lists kept in the settings store as lines of text.
- **`SshService`** (`src/services/ssh_service`): one session on a task of its own (20 KB of stack). The task and the main loop share the terminal, the bytes to send and the state under one lock. Its questions (is this the right server? the password?) are states it waits in until the App answers; the settings store is only written from the main loop. The password and the private key are overwritten after use.
- **`SshApp`** (`src/apps/ssh_app`): the hosts, the entry, the session, the key page. It draws the grid a run of same-coloured cells at a time, at most every 60 ms.
- **Keys that aren't characters now say what was held with them** (`lib/input/src/key_mapper.cpp`): the arrows, Enter, Del, Tab and Back carry Shift, Ctrl and Alt. The terminal needs it for Page Up and Alt+backtick. It also makes two documented keys work from the real keyboard, which until now only worked from the Debug Console's `key` command: Ctrl with Fn and up or down in a note, and Shift+Tab in Gemini.
- **IRC doesn't try to connect with less than 60 KB free** (`IrcService::kNeedFree`): see below.
- Settings: `SshHosts`, `SshKnown`, `SshKey`, `SshPublic`, `SshFont`.
### Checks on the device (2026-10-08, against OpenSSH 9.7 in a container on the same network)
| Check | Result |
|---|---|
| `ssh tester@host:2222` from the Debug Console | The App opens; the fingerprint shown is the one `ssh-keygen -lf` prints on the server |
| Trust it, a password | A shell; `stty size` says 15 48, `$TERM` is xterm |
| `ls -la`, `top`, `vim` (insert, Esc, `:wq`) | Drawn right: `top`'s reverse-video header, `vim`'s alternate screen and what was there before coming back; the file is on the server |
| Ctrl with + and - | `stty size` says 12 40, then 20 60; `top` redraws for it |
| `seq 1 60`, Alt with up | The history, in grey, with how far back in the corner |
| Fn+backtick, then the App again | The Launcher with `SSH` in the Status Bar; the session as it was |
| `sleep 100`, Ctrl+C; Alt+backtick | Interrupted; `` echo `id -u` `` prints 1000 |
| Ctrl+Alt+q; `exit` | "Disconnected"; "The session ended" |
| This device's key, its public half in `authorized_keys` | "Accepted publickey" in the server's log; no password asked |
| The server's host keys replaced | "THE SERVER'S KEY CHANGED" with the new fingerprint, Cancel selected. Cancel: "Not trusted: not connected". Replace: it connects, and doesn't ask again |
| A wrong password | "Wrong password", and asked again; Back gives up |
| A port nothing listens on | "Nothing listens there: the connection was refused" |
| `ssh nobody`, `ssh a@`, a port of 99999 | Refused, each with its reason |
| Forgetting a host | Asked, then gone from the list |
| `irc start` with a session open | "not enough memory: close the SSH session, retrying in 5 s", and no attempt: the lowest free heap doesn't move |
| Memory | 99 KB free before, 49 KB with a session open, 30 KB at the lowest during a login, 99 KB again after |
| Stacks | `ssh` 6.8 KB free of 20 KB; `loopTask` 1.9 KB free, as before |
**Not checked:** the refusal under 75 KB free (it is one comparison, and wasn't provoked). A server on the internet, or through the VPN. Wi-Fi lost in the middle of a session. Servers other than OpenSSH. `nano`, `less`, `htop`, `tmux`. Keyboard-interactive logins (two-factor prompts). A session left open for hours.
### What went wrong while building it
- **IRC, started with a session open, took the free heap down to 236 bytes.** A test script's keys went to the Launcher instead of the terminal and opened the IRC App, which connects when opened. Its TLS handshake found no memory, failed, and tried again with its usual back-off, six times; nothing crashed and it never connected, but 236 bytes is no margin at all. IRC now looks at the free heap before each attempt and says "not enough memory: close the SSH session" instead of trying.
- **The build script did nothing as a `post:` script:** the libraries were already built when it ran.
- **A failed connection was first shown as an empty terminal** with its reason squeezed on the last line, and a host was remembered before anyone had logged in to it. Both changed: the reason has a page, and a host is remembered once a login succeeds.
- **The trust question didn't fit its dialog:** the fingerprint is 50 characters. It is now split over two lines, under one line of words.
+5 -1
View File
@@ -53,7 +53,7 @@ Yes: it is [open source](https://git.twis.la/twisla/roro9stack) (GPL-3.0). The d
**The firmware contacts the project's server once a day,** to see whether a new release exists: **Settings → Check for updates**, on by default, only when Wi-Fi is up and the clock is set. It installs nothing by itself, and you can switch it off. Besides that, the device talks to what you ask it to: the IRC server and Gemini capsules you open, DNS servers (9.9.9.9 and 1.1.1.1 by default) and time servers (pool.ntp.org and time.cloudflare.com by default), all of which you can change. There is no account, no analytics and no telemetry.
**It listens on the network only for what you switched on:** the port that receives signed firmware updates, always; the Debug Console, the card shared with a browser and the VPN, only while you have them on.
**It listens on the network only for what you switched on:** the port that receives signed firmware updates, always; the Debug Console, the card shared with a browser and the VPN, only while you have them on. The SSH App connects out to the server you name and listens for nothing.
**This website** sets no cookies and has no analytics, loads nothing from other sites, and its server logs keep only a masked part of visitors' addresses. The Install page asks the project's own server for the latest release.
@@ -65,6 +65,10 @@ A secure connection takes about 52 KB of the 107 KB the device has, and IRC's ta
Yes, WireGuard: one tunnel to one server, set up by copying the client's `.conf` to the SD card and importing it in Settings. It can carry everything, or just the VPN's own subnet. See [VPN](/guide/vpn/).
## Can it log in to my server?
Yes, over SSH: the [SSH App](/guide/ssh/) is a terminal on another machine, with a password or with a key the device makes for itself. `vim`, `top` and `less` work. One session at a time, and not at the same time as IRC: there isn't the memory for both.
## How do I copy files to and from my phone?
In the Storage App, press <kbd>w</kbd>: the device serves a small web page to any browser on the same Wi-Fi. Scan the QR code it shows, type the code, and upload or download. Nothing to install. See [From a phone](/guide/storage/#from-a-phone).
+2
View File
@@ -12,6 +12,8 @@ This guide says what the firmware does **today** and nothing else. Start with th
**Three things work on every screen:** <kbd>Fn</kbd> + <kbd>h</kbd> for the keys, <kbd>Fn</kbd> + <kbd>p</kbd> for a screenshot, and <kbd>Fn</kbd> + <kbd>`</kbd> to go back to the Launcher.
**Two things keep running when you leave their App:** IRC stays connected, and an [SSH](/guide/ssh/) session stays open.
Looking for a recipe or a quick answer? There are [how-tos](/howto/) and a [FAQ](/faq/).
Something missing or wrong? Write to the [issue tracker](https://git.twis.la/twisla/roro9stack/issues) or to contact@roro9stack.net.
+1
View File
@@ -49,6 +49,7 @@ A strip at the top of every screen: the name of the App on the left, and on the
| `97%` | The battery (in the warning colour at 15% and under) |
| `SD` | A card is in; the warning colour at 80% full |
| bars and `W` | Wi-Fi connected, with its signal; `W?` is searching; `MON` is the Wi-Fi radio in its monitoring mode, which pauses IRC |
| `SSH` | An [SSH session](/guide/ssh/) is open, whatever App is in front |
| `VPN` | The [WireGuard tunnel](/guide/vpn/) is wanted; brighter once the server has answered |
| `DBG` | The Debug Console is switched on (Settings → Debug Console); brighter while a PC is connected to it |
| `REC` | A GNSS Track is being recorded |
+1 -1
View File
@@ -1,7 +1,7 @@
+++
title = "Every key"
description = "The keys of every screen of the firmware, as the help panel lists them on the device: one table for each screen and state."
weight = 14
weight = 15
[extra]
tag = "Reference"
+++
+2 -1
View File
@@ -39,7 +39,7 @@ Type a command and press <kbd>Enter</kbd>. `help` lists them all; the [command r
Type an App's name **with a capital letter** to open it, without going back to the Launcher:
`Irc` `Wifi` `Gnss` `Gemini` `Lora` `Storage` `Notes` `System` `Settings`
`Irc` `Wifi` `Gnss` `Gemini` `Lora` `Storage` `Notes` `Ssh` `System` `Settings`
The capital is the difference: every command is in small letters, every App starts with a capital. <kbd>Tab</kbd> completes them too.
@@ -55,6 +55,7 @@ For the day the network doesn't do what it should. Each of the first six takes a
| `traceroute 9.9.9.9` | The routers on the way, one a line |
| `tls git.twis.la` | A secure connection's certificate: who it is for, who signed it, until when, and **whether this device trusts it**. A port may follow (443 if not) |
| `ntp` | A time server's clock against this device's, and how far the server is. Another server may follow |
| `ssh user@host` | Opens the [SSH App](/guide/ssh/) and connects; `ssh status` and `ssh stop` for the session |
| `ifconfig` | The interfaces (Wi-Fi, and the [VPN](/guide/vpn/) when it is up), their addresses, **which one is the default route**, and the DNS servers |
| `arp` | The neighbours heard on the Wi-Fi: is the gateway there at all |
| `netstat` | What the device **listens** on (updates, the Debug Console, sharing) and what is connected to it now |
+105
View File
@@ -0,0 +1,105 @@
+++
title = "SSH"
description = "A terminal on another machine: log in to a server with a password or with the device's own key, and run a shell, vim or top on the Cardputer's screen."
weight = 13
[extra]
tag = "SSH"
screens = ["ssh.png", "ssh-trust.png", "ssh-terminal.png", "ssh-key.png", "ssh-changed.png"]
+++
The SSH App opens a **terminal on another machine**: a server, a Raspberry Pi, a router. What you type goes there, and what its programs print is drawn here: a shell, `less`, `top`, `nano`, `vim`.
It is a client and nothing more: one session at a time, to a shell. No file transfer, no port forwarding, no jump hosts.
## Connecting
The App opens on the hosts it has connected to before, the last one first, then **New connection** and **This device's key**.
1. Choose **New connection** (or press <kbd>n</kbd>) and type **`user@host`**, or `user@host:port` when the port isn't 22. The host is a name or an address.
2. **The first time, it shows the server's fingerprint** and asks whether that is the right server. Compare it with what the server's owner gives you (`ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub` on the server prints it), and choose **Trust it**. It is remembered, and not asked again.
3. **Type the password** when it asks. It is used for this login and kept nowhere: not in the device, not on the card.
A host you logged in to is kept in the list: <kbd>Enter</kbd> connects to it again, <kbd>d</kbd> forgets it, and its fingerprint with it. Up to eight are kept.
From the [Shell](/guide/shell/), `ssh user@host` does the same and opens this App.
## If the server has changed
A server that shows **a different key than the one remembered** gets a warning instead of a question: **THE SERVER'S KEY CHANGED**. Either the server was reinstalled, or something between you and it is answering in its place. The safe answer, **Cancel**, is the one selected. Choose **Replace** only when you know why the key changed.
## Without a password: this device's key
**This device's key** makes a key pair for the device (Ed25519). The private half stays inside the device and is never shown. The public half is one line of text:
- it is shown on that page,
- written to the card as **`/ssh/id_ed25519.pub`**,
- and printed by `ssh status` in the Shell.
Add that line to `~/.ssh/authorized_keys` on a server, and the device logs in there with no password. See [Log in to a server without a password](/howto/ssh-key/).
Making a **new key** replaces the old one: servers that knew the old one ask for a password again.
The key has no passphrase, so **whoever holds the device can log in wherever its key is accepted**. Keys made elsewhere can't be imported.
## In the terminal
Every key goes to the other machine, with these differences:
| Key | Does |
|---|---|
| <kbd>`</kbd> | **Esc** (the key is printed "esc") |
| <kbd>Alt</kbd> + <kbd>`</kbd> | types a backtick |
| <kbd>Fn</kbd> + <kbd>;</kbd> <kbd>.</kbd> <kbd>,</kbd> <kbd>/</kbd> | the arrows |
| <kbd>Fn</kbd> + <kbd>Shift</kbd> + <kbd>;</kbd> <kbd>.</kbd> | Page Up, Page Down |
| <kbd>Ctrl</kbd> + a letter | Ctrl+C, Ctrl+D, Ctrl+Z and the rest |
| <kbd>Alt</kbd> + a key | that key with Alt (Esc first) |
| <kbd>Alt</kbd> + <kbd>;</kbd> <kbd>.</kbd> | scroll back and forward through the last 100 lines |
| <kbd>Ctrl</kbd> + <kbd>+</kbd> <kbd>-</kbd> | larger and smaller text |
| <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>q</kbd> | disconnect |
| <kbd>Fn</kbd> + <kbd>`</kbd> | back to the Launcher, **leaving the session running** |
**Leaving doesn't disconnect.** Go to the Launcher or to another App and the session goes on; `SSH` shows in the [Status Bar](/guide/basics/#the-status-bar) for as long as it does. Open the SSH App again and you are back in it. `exit` on the other machine, or <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>q</kbd> here, ends it.
### The size of the text
Five sizes, changed with <kbd>Ctrl</kbd> + <kbd>+</kbd> and <kbd>Ctrl</kbd> + <kbd>-</kbd>; the other machine is told the new size at once, and the choice is kept.
| Text | Columns × rows |
|---|---|
| tiny | 60 × 20 |
| small (to start with) | 48 × 15 |
| medium | 40 × 12 |
| normal | 40 × 9 |
| large | 26 × 8 |
A terminal is usually 80 columns wide, and this screen isn't: long lines wrap, and programs that want 80 columns look cramped. `top`, `vim` and `less` adapt.
### What it shows, and what it doesn't
- Sixteen colours, bold as a brighter colour, reverse video.
- Western European letters (é, ñ, ü). Other characters show as `?`, and box-drawing lines as `+`, `-` and `|`.
- No mouse.
## Memory
A session takes about **50 KB** of the device's 100 KB while it is open, and gives it back when it ends. That is too much to share with a secure connection:
- **It doesn't start with less than 75 KB free.** With IRC connected, or a Gemini page open, it says "Not enough memory: close IRC or a Gemini page".
- **While a session is open, IRC doesn't connect:** it says so in its buffer and tries again later.
See [When a connection says "not enough memory"](/howto/not-enough-memory/).
## From the Shell
```
ssh user@host connect, in the SSH App
ssh user@host:2222 on another port
ssh status the session, and this device's public key
ssh stop end the session
```
## Keys
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
{{ keys(scopes=["ssh", "ssh-terminal", "ssh-key"]) }}
+1 -1
View File
@@ -1,7 +1,7 @@
+++
title = "Updates"
description = "How the device updates itself from the project's releases, from the SD card or from a PC, and how it protects itself when an update goes wrong."
weight = 13
weight = 14
[extra]
tag = "Firmware"
screens = ["update.png"]
+1 -1
View File
@@ -1,6 +1,6 @@
+++
title = "How-tos"
description = "Short recipes for things you will want to do: move files with your phone, set up the VPN, take a screenshot, find out why the network doesn't work, record a track, capture radio packets, and what to try when something does not work."
description = "Short recipes for things you will want to do: move files with your phone, set up the VPN, log in to a server with the device's SSH key, take a screenshot, find out why the network doesn't work, record a track, capture radio packets, and what to try when something does not work."
template = "guide-index.html"
page_template = "guide-page.html"
sort_by = "weight"
+2 -1
View File
@@ -8,7 +8,7 @@ tag = "Memory"
## What you see
Opening a Gemini page fails with *not enough memory: stop IRC or retry*. Or an update check or install makes IRC disconnect for a moment.
Opening a Gemini page fails with *not enough memory: stop IRC or retry*. Or SSH says *Not enough memory: close IRC or a Gemini page*, or IRC says *not enough memory: close the SSH session*. Or an update check or install makes IRC disconnect for a moment.
## What to do
@@ -26,6 +26,7 @@ Small next to a secure connection, but they add up when memory is already short:
| | While it is in use |
|---|---|
| An [SSH](/guide/ssh/) session, from login until it ends | 50 KB: IRC waits while it is open, and it doesn't start under 75 KB free |
| A note open in the editor, whatever its size | 17 KB |
| Sharing the card with a browser | 13 KB |
| The Shell | 7 KB |
+1
View File
@@ -20,6 +20,7 @@ Everything the firmware writes goes in a folder at the top of the card. To get a
| Files saved from Gemini that are not text | `/gemini/downloads` | whatever they were |
| Update files | `/updates` | `.ota` |
| [Screenshots](/howto/screenshot/) (<kbd>Fn</kbd> + <kbd>p</kbd>) | `/screenshots` | `.png`, named by date and time |
| The public half of the device's [SSH key](/guide/ssh/#without-a-password-this-device-s-key) | `/ssh/id_ed25519.pub` | one line, for a server's `authorized_keys`; safe to copy anywhere |
| A VPN configuration waiting to be imported | `/vpn/wg0.conf` | you put it there; delete it once imported |
## Rules worth knowing
+34
View File
@@ -0,0 +1,34 @@
+++
title = "Log in to a server without a password"
description = "Make the Cardputer's own SSH key, put its public half on a server, and connect with no password to type."
weight = 13
[extra]
tag = "SSH"
+++
Typing a password on a small keyboard, every time, gets old. With a key, the server recognises the device.
1. On the Cardputer, open **SSH → This device's key** and press <kbd>Enter</kbd>. It makes the key and shows its **public half**: one line starting with `ssh-ed25519`.
2. Get that line to the server. It was also written to the card as **`/ssh/id_ed25519.pub`**, so the easy way is the phone: in **Storage** press <kbd>w</kbd>, open the page ([Move files with your phone](/howto/phone-files/)) and download the file. Or log in to the server with your password, from the Cardputer or anything else, and paste it.
3. On the server, add the line to the end of **`~/.ssh/authorized_keys`** of the user you log in as:
```
cat id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
```
4. On the Cardputer, connect: **SSH → New connection**, `user@host`. It logs in without asking for anything.
## If it still asks for a password
| Check | How |
|---|---|
| The line is whole, on one line | `ssh-ed25519`, a long word, then `roro9stack` |
| The file's permissions | `chmod 700 ~/.ssh` and `chmod 600 ~/.ssh/authorized_keys`: OpenSSH ignores a file others can write |
| It is the right user's file | the `user` of `user@host` |
| The server takes keys | `PubkeyAuthentication yes` in its `sshd_config` (the default) |
| You made a new key since | a new key replaces the old one: put the new line on the server |
## Worth knowing
The private half never leaves the device and has no passphrase: **whoever holds the Cardputer can log in wherever its key is accepted**. If the device is lost, remove its line from `authorized_keys` on your servers. More in the [SSH](/guide/ssh/) page.
+34
View File
@@ -365,6 +365,40 @@ rows = [
["; .", "up, down"],
]
[[scope]]
id = "ssh"
title = "SSH, the hosts"
rows = [
["Enter", "connect, open"],
["; .", "up, down"],
["n", "a new connection"],
["d", "forget this host"],
]
[[scope]]
id = "ssh-terminal"
title = "SSH, the terminal"
rows = [
["`", "Esc"],
["Alt `", "a backtick"],
["Fn ; . , /", "the arrows"],
["Fn Shift ; .", "Page Up, Page Down"],
["Ctrl a..z", "Ctrl+C and the rest"],
["Alt ; .", "scroll back, forward"],
["Ctrl + -", "larger, smaller text"],
["Ctrl Alt q", "disconnect"],
["Fn `", "leave it running"],
]
[[scope]]
id = "ssh-key"
title = "SSH, this device's key"
rows = [
["Enter", "make a key, or a new one"],
["w", "write the public half to the card"],
["`", "back"],
]
[[scope]]
id = "notes"
title = "Notes, the list"
+25
View File
@@ -59,6 +59,31 @@ file = "vpn.png"
alt = "Settings, VPN: VPN On, Start with Wi-Fi On, Import /vpn/wg0.conf, Forget it; then It is up, heard 66 s ago, the server's address, and This device 10.9.0.2, through it everything. VPN shows in the Status Bar"
caption = "Settings, VPN"
[[screen]]
file = "ssh.png"
alt = "The SSH App's list: a saved host, tester at 172.16.42.249 port 2222, then New connection and This device's key"
caption = "SSH, the hosts"
[[screen]]
file = "ssh-trust.png"
alt = "A dialog over the SSH App: A server not met before, the address 172.16.42.249, and a SHA256 fingerprint on two lines; buttons Cancel, selected, and Trust it"
caption = "SSH, a server met for the first time"
[[screen]]
file = "ssh-terminal.png"
alt = "The SSH App's terminal showing top on a remote machine: uptime, tasks, memory, then a reverse-video header and five processes. SSH shows in the Status Bar"
caption = "SSH, top on another machine"
[[screen]]
file = "ssh-key.png"
alt = "This device's key: its public half, for a server's authorized_keys file, a line starting ssh-ed25519; it is also in /ssh/id_ed25519.pub, and the private half never leaves"
caption = "SSH, this device's key"
[[screen]]
file = "ssh-changed.png"
alt = "A dialog over the SSH App: THE SERVER'S KEY CHANGED. Someone in between? Now it is, and a SHA256 fingerprint; buttons Cancel, selected, and Replace"
caption = "SSH, a server whose key changed"
[[screen]]
file = "help.png"
alt = "The help panel over the Launcher: Keys: Launcher, up and down, Enter opens the App, then Everywhere: back, home, the arrows, Fn h for these keys and Fn p for a screenshot"
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+1 -1
View File
@@ -68,7 +68,7 @@
</article>
{% endfor %}
</div>
<p class="cards-note">Plus a <a class="accent-link" href="/guide/shell/">Shell</a> that runs the firmware's commands on the device, a <a class="accent-link" href="/guide/vpn/">WireGuard VPN</a>, a screenshot key that works on every screen, and Settings, with its Wi-Fi and Firmware pages. Every App has a page in the <a class="accent-link" href="/guide/">user guide</a>.</p>
<p class="cards-note">Plus a <a class="accent-link" href="/guide/shell/">Shell</a> that runs the firmware's commands on the device, an <a class="accent-link" href="/guide/ssh/">SSH client</a> with a real terminal, a <a class="accent-link" href="/guide/vpn/">WireGuard VPN</a>, a screenshot key that works on every screen, and Settings, with its Wi-Fi and Firmware pages. Every App has a page in the <a class="accent-link" href="/guide/">user guide</a>.</p>
</section>
<section class="wrap" id="screens" aria-labelledby="screens-title">