diff --git a/.gitignore b/.gitignore index 65b0396..f6c231a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ vault.json # Agent worktrees .claude/worktrees/ + +# User guide LaTeX output +docs/guide/build/ diff --git a/README.md b/README.md index e4ad419..fd27424 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ On first run, Nexus Terminal copies your Alchementary Link profiles, encrypted v - Product overview: [docs/overview.md](docs/overview.md) - Quick start (shareable): [docs/quick-start.md](docs/quick-start.md) - User manual: [docs/user-manual.md](docs/user-manual.md) +- Illustrated user guide (PDF): [docs/guide/nexus-guide.pdf](docs/guide/nexus-guide.pdf), built from LaTeX in [docs/guide](docs/guide) (`make` there; `tools/screenshots.sh` regenerates the screenshots) - Build and release: [docs/build-and-release.md](docs/build-and-release.md) ## Keyboard shortcuts diff --git a/docs/guide/Makefile b/docs/guide/Makefile new file mode 100644 index 0000000..ae5a486 --- /dev/null +++ b/docs/guide/Makefile @@ -0,0 +1,11 @@ +# Builds nexus-guide.pdf with LuaLaTeX (three passes: contents, page references, then page overlays settle). +# Needs LuaLaTeX with luaotfload (Debian/Ubuntu: texlive-luatex texlive-latex-extra). +# Screenshots: tools/screenshots.sh (needs xvfb-run, sshd, python3). +OUT ?= build +nexus-guide.pdf: nexus-guide.tex nexus-guide.sty chapters/*.tex img/* + mkdir -p $(OUT) + lualatex -interaction=nonstopmode -halt-on-error -output-directory=$(OUT) nexus-guide.tex > /dev/null || (grep -A8 '^!' $(OUT)/nexus-guide.log; false) + for i in 1 2; do lualatex -interaction=nonstopmode -halt-on-error -output-directory=$(OUT) nexus-guide.tex > /dev/null; done + cp $(OUT)/nexus-guide.pdf . +clean: + rm -rf $(OUT) diff --git a/docs/guide/chapters/appendix.tex b/docs/guide/chapters/appendix.tex new file mode 100644 index 0000000..7180959 --- /dev/null +++ b/docs/guide/chapters/appendix.tex @@ -0,0 +1,111 @@ +\guidepart{slate}{Extras}{The extras} + {The cheat sheet, the command line, and words you might trip over.} + {\partitem{\iKeyboard}{Keyboard cheat sheet} + \partitem{\iTerminal}{Command-line options} + \partitem{\iBrain}{Glossary}} + +% ========================================================================================= +\chapter{Keyboard cheat sheet}\label{app:keys} +\chapmeta{print it}{Stick this page next to your monitor} + +\newcommand\shortcut[2]{#1 & #2\\} +\newcommand\sheet[3]{% + \begin{tcolorbox}[enhanced, arc=8pt, boxrule=0pt, colback=#1!7, left=10pt, right=10pt, top=8pt, bottom=8pt, + before skip=0pt, after skip=10pt, fontupper=\small] + {\heavy\large\color{#1!85!black}#2}\par\vspace{4pt} + \renewcommand\arraystretch{1.5}\begin{tabularx}{\linewidth}{@{}X>{\raggedleft\arraybackslash}p{36mm}@{}}#3\end{tabularx} + \end{tcolorbox}} + +\begin{multicols}{2} +\sheet{accent}{\iRocket\enspace Everywhere}{% + \shortcut{Command palette}{\keys{\mod+K}} + \shortcut{New connection}{\keys{\mod+N}} + \shortcut{Quick Connect}{\keys{\mod+Shift+Q}} + \shortcut{Settings}{\keys{\mod+,}} + \shortcut{Full screen}{\kbd{F11}}} + +\sheet{violet}{\iSplit\enspace Tabs \& panes}{% + \shortcut{Close tab}{\keys{\mod+W}} + \shortcut{Next tab}{\keys{\mod+Tab}} + \shortcut{Previous tab}{\keys{\mod+Shift+Tab}} + \shortcut{Go to tab 1–9}{\keys{\mod+1}…\kbd{9}} + \shortcut{Split right}{\keys{\mod+Shift+D}} + \shortcut{Split down}{\keys{\mod+Shift+E}}} + +\sheet{teal}{\iWand\enspace Power tools}{% + \shortcut{Broadcast on/off}{\keys{\mod+Shift+B}} + \shortcut{Snippets}{\keys{\mod+Shift+S}} + \shortcut{Record on/off}{\keys{\mod+Shift+R}}} + +\sheet{amber}{\iTerminal\enspace In the terminal}{% + \shortcut{Copy selection}{\keys{\mod+C}} + \shortcut{Paste}{\keys{\mod+V}} + \shortcut{Find}{\keys{\mod+F}} + \shortcut{Next / previous match}{\kbd{Enter} / \keys{Shift+Enter}} + \shortcut{Scroll a page}{\keys{Shift+PageUp}} + \shortcut{Top / bottom}{\keys{Shift+Home} / \kbd{End}} + \shortcut{Open link}{\kbd{Ctrl}/\kbd{\ph{E1C4}} + click}} + +\sheet{pink}{\iMap\enspace Mouse moves}{% + \shortcut{Select word / line}{double / triple-click} + \shortcut{Extend selection}{\kbd{Shift} + click} + \shortcut{Select several profiles}{\kbd{Ctrl}/\kbd{\ph{E1C4}} + click} + \shortcut{Everything else}{right-click it}} +\end{multicols} + +% ========================================================================================= +\chapter{Command-line options}\label{app:cli} +\chapmeta{1 minute}{You'll script, demo and test with a few handy flags} + +\renewcommand\arraystretch{1.5} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\ttfamily\color{ink}}p{52mm}X@{}} + \toprule + --data-dir DIR & Use another data folder (portable installs, demos, testing).\\ + --theme pro|midnight|light & Start with this theme.\\ + --connect NAME & Connect to a saved profile by name at startup. Repeat it for several.\\ + --open WINDOW & Open a window at startup: \code{settings}, \code{settings:security}, \code{snippets}, + \code{keys}, \code{recordings}, \code{palette}, \code{quick}…\\ + --version & Print the version and exit.\\ + \bottomrule +\end{tabularx} + +\begin{sidequest}[How the screenshots in this guide were made] +Every screenshot here is the real app, driven by \code{docs/guide/tools/screenshots.sh}: it starts throwaway +SSH, IRC, VNC and GitHub-ish servers, fills a demo data folder, and runs Nexus Terminal headless with +\code{--screenshot}, \code{--do} (run a palette command) and \code{--answer} (fill a password prompt). Change the app, +rerun the script, rebuild the guide. +\end{sidequest} + +% ========================================================================================= +\chapter{Glossary}\label{app:glossary} +\chapmeta{dip in}{Words you might trip over, in one line each} + +\begin{multicols}{2}\small +\begin{description}[leftmargin=0pt, itemsep=5pt] + \item[Agent] A background program holding your unlocked SSH keys, so you type the passphrase once. + \item[Asciicast] The file format for terminal recordings. Plays in Nexus Terminal and \code{asciinema}. + \item[Bastion / jump host] A server you go \emph{through} to reach the ones behind it. + \item[Baud rate] How fast a serial line talks. Both ends must agree. + \item[Broadcast] Typing into several sessions at once. + \item[DTR / RTS] Serial control lines. Some boards use them for reset or flashing. + \item[Fingerprint] A short summary of a server's host key, used to spot imposters. + \item[Host key] A server's identity. Changes when it's rebuilt, or when someone's pretending. + \item[Keychain] Your operating system's password store. + \item[Port forward] A tunnel carrying one TCP port through SSH. + \item[Profile] A saved connection. + \item[RAW TCP] A plain socket: bytes in, bytes out, no terminal rules. + \item[SFTP] File transfer over SSH. + \item[Snippet] A saved command, optionally with blanks to fill in. + \item[TOTP] Time-based one-time passwords: the six digits from your authenticator app. + \item[Trigger] A rule that watches output and reacts to a pattern. + \item[Vault] Nexus Terminal's encrypted password store, for when there's no keychain. + \item[VNC] A way of seeing and controlling another computer's desktop. +\end{description} +\end{multicols} + +\vfill +\begin{center} +\includegraphics[width=16mm]{img/logo.pdf}\par\vspace{6pt} +{\heavy\color{ink}That's the lot.}\par +{\small\color{muted}Go and connect to something. \iRocket} +\end{center} diff --git a/docs/guide/chapters/part1-start.tex b/docs/guide/chapters/part1-start.tex new file mode 100644 index 0000000..8304747 --- /dev/null +++ b/docs/guide/chapters/part1-start.tex @@ -0,0 +1,248 @@ +\guidepart{accent}{Get going}{Get going} + {From zero to connected before your tea goes cold.} + {\partitem{\iRocket}{Five-minute start} + \partitem{\iMap}{The grand tour} + \partitem{\iList}{Profiles: your address book}} + +% ========================================================================================= +\chapter{Five-minute start}\label{ch:start} +\chapmeta{5 minutes}{You'll be connected to something and know the one shortcut that matters} + +\begin{tldr} +Install it, open it, press \keys{\mod+N}, fill in a host, hit \ui{Create \& Connect}. Done. +Lost? Press \keys{\mod+K} and type what you want. +\end{tldr} + +\section{Install it} + +Grab the zip for your machine from \code{dixon.cx/downloads/nexus}. They're named +\code{NexusTerminal--.zip}, so a modern Mac wants \code{darwin-arm64} and most PCs want \code{amd64}. + +\begin{description} + \item[\iDesktop\enspace Windows] Unzip it and run \code{NexusTerminal-windows-amd64.exe}. No installer, no admin rights, + nothing in the registry. Pop it somewhere sensible like \code{C:\textbackslash Tools} and pin it to the taskbar. + \item[\iDesktop\enspace macOS] Unzip it and drag \code{NexusTerminal-darwin-arm64.app} into \ui{Applications}. If macOS + grumbles that it can't check the developer, right-click the app, choose \ui{Open}, then \ui{Open} again. You only + have to do that once. + \item[\iDesktop\enspace Linux] Unzip it and run \code{./install.sh}. It copies \code{nexus-terminal} into + \code{~/.local/bin} and adds a launcher entry with the icon, so it turns up in your app menu. + (Make sure \code{~/.local/bin} is on your \code{PATH}.) +\end{description} + +\begin{sidequest}[Build it yourself] +It's a single Rust program. With Rust installed: \code{cd rust \&\& cargo run --release}. The finished binary lands in +\code{rust/target/release/nexus-terminal}. The build guide in \code{docs/build-and-release.md} has the full story. +\end{sidequest} + +\section{Open it} + +The first time you open Nexus Terminal you get a calm, empty welcome screen. Three buttons, four shortcuts. That's it. + +\shot{welcome.png}{The welcome screen. Your saved connections will live down the left; here there are a few already.} + +\begin{headsup}[Coming from Alchementary Link?] +Nexus Terminal spots your old data on first run and copies your profiles, vault and recordings across +automatically. You'll see a little toast saying so. Your old data is left exactly where it was. +The details are in \hyperref[ch:move]{Moving house} on p.\,\pageref{ch:move}. +\end{headsup} + +\section{Make your first connection} + +\begin{enumerate} + \item Click \ui{New Connection}, or press \keys{\mod+N}. + \item Pick the protocol along the top: \proto[teal]{SSH}, \proto[amber]{Telnet}, \proto[violet]{RAW TCP}, + \proto[slate]{GitHub}, \proto[rose]{VNC}, \proto[amber]{IRC} or \proto[accent]{Serial}. + \item Fill in the \ui{General} bit: a \ui{Display Name} you'll recognise, the \ui{Host}, the \ui{Port} + and your \ui{Username}. + \item Click \ui{Create \& Connect}. +\end{enumerate} + +That's genuinely it. A tab opens, you log in, and next time it's one click in the sidebar. + +\crop{editor-new.png}{1515}{40}{0}{70}{0.52\linewidth}{A brand-new SSH profile. Only the top few fields are needed to get going.} + +\begin{quickwin} +Click \ui{Test} before you save. Nexus Terminal checks the host and port are reachable and tells you the +round-trip time. If that fails, no amount of password-fiddling will help, so it's a great first check. +\end{quickwin} + +\section{Two shortcuts for impatient people} + +\subsection{Quick Connect} +Press \keys{\mod+Shift+Q}. +For a one-off session you don't want to save. Pick SSH, Telnet, RAW, IRC or Serial, type the host (or serial +device), hit \kbd{Enter}. Nothing gets saved. + +\crop{quick.png}{951}{620}{946}{481}{0.5\linewidth}{Quick Connect. In, out, nobody gets hurt.} + +\subsection{Local terminal} +Click \ui{Local Machine} at the top of the sidebar (or \ui{Local} on the welcome screen) for a shell on +\emph{this} computer. When you type \code{exit}, the tab says \code{[Exited - press enter to restart]}. +Press \kbd{Enter} and you've got a fresh shell in the same tab. + +\section{The one shortcut to rule them all} + +\begin{center} +\begin{tcolorbox}[enhanced, width=0.8\linewidth, arc=10pt, boxrule=0pt, colback=ink, fontupper=\color{white}, + halign=center, top=10pt, bottom=10pt, drop fuzzy shadow] +{\scalebox{2.4}{\keys{\mod+K}}}\par\vspace{6pt}\par\vspace{4pt} +{\color{white!70!ink} The command palette. If you remember nothing else, remember this.} +\end{tcolorbox} +\end{center} + +Type a few letters of \emph{anything}: a connection name, a tab, ``theme'', ``split'', ``lock'', ``snippets''. Use +\kbd{↑}\kbd{↓} to pick, \kbd{Enter} to go, \kbd{Esc} to bail. It's fuzzy, so ``atl'' finds \emph{atlas} and +``sft'' finds the \emph{SFTP Browser}. + +\crop{palette.png}{755}{732}{733}{154}{0.72\linewidth}{The command palette: every connection, tab and action in one searchable list.} + +\begin{quickwin}[Your five-minute checklist] +\begin{checklist} + \item Installed and opened Nexus Terminal + \item Opened a \ui{Local Machine} tab and typed \code{exit}, then \kbd{Enter} to get it back + \item Made a profile with \keys{\mod+N} and clicked \ui{Test} + \item Connected to it + \item Opened the palette with \keys{\mod+K} and found something by typing three letters +\end{checklist} +All ticked? You're officially more productive than you were five minutes ago. \iConfetti +\end{quickwin} + +% ========================================================================================= +\chapter{The grand tour}\label{ch:tour} +\chapmeta{4 minutes}{You'll know what every bit of the window is for} + +\begin{tldr} +Connections on the left. Sessions in tabs across the top. The terminal in the middle. Status along the bottom. +Everything else is one \keys{\mod+K} away. +\end{tldr} + +\begin{annotated}{ssh.png} + \callout{1}{0.124}{0.022} + \callout{2}{0.66}{0.022} + \callout{3}{0.888}{0.022} + \callout{4}{0.22}{0.067} + \callout{5}{0.03}{0.152} + \callout{6}{0.53}{0.063} + \callout{7}{0.966}{0.063} + \callout{8}{0.62}{0.42} + \callout{9}{0.16}{0.945} + \callout{10}{0.26}{0.985} + \callout{11}{0.86}{0.985} +\end{annotated} + +\begin{multicols}{2}\small +\begin{description}[leftmargin=0pt, labelsep=0.4em] + \item[\num{1} Menus] \menu{File}, \menu{Session}, \menu{View} and \menu{Help}. Drag the title bar to move the + window; double-click it to maximise. + \item[\num{2} Palette] The search box is the command palette. Click it or press \keys{\mod+K}. + \item[\num{3} Light/dark] Flip between the dark and light themes in one click. + \item[\num{4} Search] Fuzzy-search your connections by name, host, tag or protocol. + \item[\num{5} Sections] \ui{Local Machine}, \ui{Favourites} \iStar, \ui{Recent} (your last five), then one section + per group. Click a header to fold it away. + \item[\num{6} Tabs] One per session, with a coloured state dot, protocol badge and close button. SSH tabs get a + little \iFolder{} for files. A blue dot means ``new output while you weren't looking''. + \item[\num{7} New \& broadcast] \ui{+} opens a new connection; \iBroadcast{} toggles broadcast mode. + \item[\num{8} Workspace] Your terminal (or IRC chat, VNC desktop, GitHub list…). Split it into panes, too. + \item[\num{9} Footer] New connection, palette, import, export, recordings and settings. + \item[\num{10} Status] State, protocol and live latency for SSH. Shows \ui{REC} while recording and + \ui{BROADCAST} when broadcasting. + \item[\num{11} Sessions \& vault] How many sessions are connected and whether the vault is locked. Click the vault to + open security settings. +\end{description} +\end{multicols} + +\begin{protip} +Drag the right-hand edge of the sidebar to make it wider or narrower. Toasts (the little pop-up messages in the +top-right) tidy themselves away after a few seconds; click \ui{×} if you're impatient. +\end{protip} + +\section{Three looks} + +Nexus Terminal comes in \ui{Pro} (the default, a soft charcoal), \ui{Midnight} (darker, bluer, for 2\,a.m. +deploys) and \ui{Light} (for sunny days and projectors). Switch with the \iBulb{} button in the title bar, the +\menu{View} menu, or type ``theme'' into the palette. + +\shotpair{welcome-midnight.png}{Midnight}{ssh-light.png}{Light, with a session open} + +The \emph{terminal} has its own colour schemes too: eleven of them, including \ui{Dracula}, \ui{Nord}, +\ui{Tokyo Night}, \ui{Catppuccin Mocha} and \ui{Rosé Pine}. Those live in \menu{Settings\then Terminal} and the \menu{View} menu. + +% ========================================================================================= +\chapter{Profiles: your address book}\label{ch:profiles} +\chapmeta{6 minutes}{You'll keep dozens of connections tidy without thinking about it} + +\begin{tldr} +A \emph{profile} is a saved connection. Give it a name and a group, star the ones you use daily, and +right-click for everything else. Ctrl-click to select lots at once. +\end{tldr} + +\section{Create, edit, copy} + +\begin{itemize} + \item \textbf{New:} \ui{+} in the sidebar footer or tab strip, \keys{\mod+N}, or right-click a group header and choose + \ui{New connection in …} (it lands in that group automatically). + \item \textbf{Edit:} double-click a profile, or right-click\then\ui{Edit}. The editor slides in on the right. + \item \textbf{Copy:} right-click\then\ui{Duplicate}. Great for ``same as web-01 but web-02''. + \item \textbf{Delete:} right-click\then\ui{Delete}. This also deletes the saved passwords for that profile. +\end{itemize} + +\crop{editor-ssh.png}{1515}{40}{0}{70}{0.52\linewidth}{Editing an existing SSH profile. The editor scrolls; sections are grouped so you can ignore most of them.} + +\subsection{What goes in a profile} + +Every profile has the same \ui{General} section, then sections specific to its protocol (Part 2 covers each +one). The common bits are: + +\renewcommand\arraystretch{1.4} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\heavy\color{ink}}p{32mm}X@{}} + \toprule + Display Name & What you'll see in the sidebar. Make it obvious: ``db-primary'', not ``server 3''.\\ + Host / Port & Where to connect. Serial profiles have a \ui{Device} instead.\\ + Group & Pick an existing group or type a new name to create one.\\ + Description & One line shown on hover.\\ + Terminal & Font, size (8–36), scrollback (0–100\,000 lines) and cursor style, just for this profile.\\ + Tags \& Notes & Comma-separated tags (searchable!) and free-form notes for future-you.\\ + \bottomrule +\end{tabularx} + +Each protocol adds its own sections underneath. Here are four of them side by side; Part~2 goes through each one. + +\begin{center} +\newcommand\mini[2]{\begin{minipage}[t]{0.24\linewidth}\centering\includegraphics[width=\linewidth, trim=1515 600 0 70, clip]{img/#1}\par\vspace{2pt}{\scriptsize\color{muted}#2}\end{minipage}} +\mini{editor-telnet.png}{Telnet}\hfill\mini{editor-raw.png}{RAW TCP}\hfill\mini{editor-vnc.png}{VNC}\hfill\mini{editor-irc.png}{IRC} +\end{center} + +\begin{protip}[Tags are secretly brilliant] +Tag things by where they live and what they do (\code{eu-west}, \code{db}, \code{customer-x}). The sidebar search +matches tags, so typing ``db'' instantly narrows hundreds of profiles to the database servers. +\end{protip} + +\section{Favourites, groups and Recent} + +\begin{itemize} + \item Right-click\then\ui{Add to Favourites} pins a profile into the \ui{\iStar\ Favourites} section at the top. + \item \ui{Recent} always shows the last five things you connected to. You don't have to do anything. + \item Groups are just the \ui{Group} field. No group? It goes in \ui{Ungrouped}. +\end{itemize} + +\section{Do lots at once} + +Hold \kbd{Ctrl} (\kbd{\ph{E1C4}} on a Mac) and click several profiles. A bar appears at the bottom of the sidebar: + +\begin{center}\small +\begin{tabular}{@{}ll@{\qquad}ll@{}} + \ui{Connect all} & opens them all in tabs & \ui{Move group} & moves them (blank = Ungrouped)\\ + \ui{Disconnect} & closes their sessions & \ui{Delete} & deletes them (asks first)\\ + \ui{Tags} & add or replace tags & \ui{Clear} & forget the selection\\ +\end{tabular} +\end{center} + +\begin{quickwin} +Select every web server, choose \ui{Connect all}, then turn on broadcast (\keys{\mod+Shift+B}). You're now typing into +all of them at once. Chapter~\ref{ch:repeat} has the details, and the safety advice. +\end{quickwin} + +\section{``It's already open…''} + +Click a profile that already has a live session and Nexus Terminal asks whether to \ui{Use Existing} (jump to that +tab), open a \ui{New Connection} alongside it, or \ui{Cancel}. No more accidental duplicate sessions. diff --git a/docs/guide/chapters/part2-connect.tex b/docs/guide/chapters/part2-connect.tex new file mode 100644 index 0000000..c6529b0 --- /dev/null +++ b/docs/guide/chapters/part2-connect.tex @@ -0,0 +1,273 @@ +\guidepart{teal}{Connect}{Connect to all the things} + {Eight ways in. Pick the one you need; skip the rest.} + {\partitem{\iTerminal}{SSH: the big one} + \partitem{\iUsb}{Serial: cables and consoles} + \partitem{\iPlug}{Telnet and RAW TCP} + \partitem{\iDesktop}{VNC remote desktops} + \partitem{\iChat}{IRC} + \partitem{\iGithub}{GitHub}} + +% ========================================================================================= +\chapter{SSH: the big one}\label{ch:ssh} +\chapmeta{10 minutes (or 2 for the basics)}{You'll log in with keys, hop through bastions and never type a 2FA code again} + +\begin{tldr} +Host, username, key, done. Everything else (jump hosts, proxies, forwards, 2FA, login scripts) is optional and +lives in its own fold-away section of the editor. Open only the ones you need. +\end{tldr} + +\begin{glance}{teal}{SSH} + Default port & 22\\ + Logs in with & Password, public key, keyboard-interactive, SSH agent\\ + Extras & SFTP file browser, port forwards, live latency, agent forwarding, TOTP 2FA\\ + Good for & Servers, VMs, Raspberry Pis, anything with \code{sshd}\\ +\end{glance} + +\section{The two-minute version} + +\begin{enumerate} + \item \keys{\mod+N}, leave the protocol on \proto[teal]{SSH}. + \item \ui{Host}, \ui{Username}. Leave the port on 22 unless you know otherwise. + \item Under \ui{Key \& Passphrase}, pick your key from the suggestions (Nexus Terminal lists the keys in your + \code{\textasciitilde/.ssh} folder) and check for a little green \ui{Key verified}. + \item \ui{Create \& Connect}. The first time, you'll be asked to trust the server's host key. Say yes if you + were expecting it. +\end{enumerate} + +\crop{hostkey.png}{842}{720}{845}{589}{0.66\linewidth}{First time connecting? You'll be shown the server's fingerprint. \ui{Accept \& Save} if it's the server you meant.} + +\begin{headsup} +Type the full path to a key if you're not using the suggestions. Nexus Terminal doesn't expand \code{\textasciitilde} +in that box, so write \code{/home/you/.ssh/id\_ed25519} (or \code{C:\textbackslash Users\textbackslash you\textbackslash .ssh\textbackslash id\_ed25519}). +\end{headsup} + +\section{How it logs in} + +Under \ui{Authentication}, tick which \ui{Auth Methods} to try: \ui{Public key}, \ui{Password} and +\ui{Keyboard-interactive}. Under \ui{SSH Agent}, tick \ui{Use SSH Agent for Authentication} to use keys already +loaded into \code{ssh-agent} (or Pageant, or the Windows OpenSSH agent). + +Every password and passphrase has a \emph{mode}: save it securely, remember it until you quit, or ask every time. +That's all explained in \hyperref[ch:secrets]{Passwords \& the vault} (p.\,\pageref{ch:secrets}). + +\begin{protip}[Agent forwarding] +Tick \ui{Forward Agent to Remote} (it's \code{ssh -A}) and you can \code{git pull} or hop onwards from the server using +the keys on \emph{your} machine. Only do it on servers you trust: anyone with root there can use your agent while +you're connected. +\end{protip} + +\section{Jump hosts: getting through the bastion} + +Lots of servers hide behind a bastion. Add one or more hops under \ui{Jump Hosts}, each with its own user, host, +port and optional key. Nexus Terminal tunnels through them in order, so the chain +\textit{you\then bastion\then internal-box\then db-primary} is just three rows. + +\section{Proxies} + +Stuck behind a corporate proxy? Under \ui{Proxy}, choose \ui{SOCKS5} or \ui{HTTP CONNECT} and fill in the host, +port and (optional) username and password. The proxy is used to reach the SSH server, or the first jump host if +you have some. + +\section{2FA without the phone faff}\label{sec:totp} + +If your server asks for a one-time code, paste the Base32 TOTP secret (the text version of the QR code) into +\ui{TOTP Secret} and hit \ui{Save}. From then on, Nexus Terminal answers the ``Verification code:'' prompt itself. +\ui{Test} shows today's code so you can check it matches your phone. + +\begin{headsup} +Save the profile first, then save the TOTP secret. And make sure your computer's clock is right: TOTP codes are +time-based, so a clock that's a minute out will produce wrong codes. +\end{headsup} + +\section{Do things automatically on login} + +\begin{description} + \item[Startup] A \ui{Startup Command} to run once you're in (say, \code{tmux attach}), plus + \ui{Environment Variables} as \code{KEY=VALUE}, one per line. + \item[Login Script] For old-school boxes that ask questions. Add steps: \ui{Wait for} some text, then + \ui{Send} a reply. Repeat as needed. +\end{description} + +\section{Keeping it alive} + +Under \ui{Advanced}: the \ui{Terminal Type}, the \ui{Keepalive Interval} and \ui{Max Missed Keepalives}, compression, +and \ui{Auto-Reconnect} with a maximum number of attempts (1–20). Reconnection backs off politely: 1\,s, 2\,s, +4\,s… up to 30\,s. + +\begin{sidequest}[What's that number in the status bar?] +For SSH sessions the status bar shows live latency, the round trip to the server. Green is snappy. If it's +in the hundreds and your typing feels sticky, it's the network, not you. +\end{sidequest} + +% ========================================================================================= +\chapter{Serial: cables and consoles}\label{ch:serial} +\chapmeta{4 minutes}{You'll be talking to a switch, router or microcontroller over USB} + +\begin{tldr} +Plug in the adapter, pick the device, match the baud rate. 115200 8N1 is the default and usually right. +Right-click the tab to send a break or wiggle DTR/RTS. +\end{tldr} + +\begin{glance}{accent}{Serial} + Device & \code{/dev/ttyUSB0}, \code{/dev/cu.usbserial-…} or \code{COM3}\\ + Defaults & 115200 baud, 8 data bits, no parity, 1 stop bit, no flow control, CR line endings, DTR and RTS on\\ + Extras & Send Break, toggle DTR/RTS, auto-reconnect when the cable comes back\\ + Good for & Network gear consoles, Arduinos, ESP32s, Raspberry Pi UARTs, 3D printers\\ +\end{glance} + +\crop{editor-serial.png}{1515}{40}{0}{70}{0.52\linewidth}{A serial profile for a switch console at 9600 baud. Click refresh after plugging in an adapter.} + +\section{Setting it up} + +\begin{description} + \item[Device] Pick it from the list (hit refresh after plugging in) or type the path. + \item[Line Settings] \ui{Baud Rate} (a preset from 300 to 1\,000\,000, or \ui{Custom}), \ui{Data Bits} (5–8), + \ui{Parity} (none, odd, even), \ui{Stop Bits} (1 or 2) and \ui{Flow Control} (none, XON/XOFF, RTS/CTS). + \item[Behaviour] \ui{Line Ending}, \ui{Encoding}, \ui{Local Echo} (for devices that don't echo what you type), + \ui{Assert DTR on connect}, \ui{Assert RTS on connect} and \ui{Auto-reconnect}. +\end{description} + +\section{While you're connected} + +Right-click the tab for \ui{Send Break} (handy for getting into a router's ROMMON) and to toggle \ui{DTR} and +\ui{RTS} (handy for resetting boards that wire those to reset). Pull the cable out and the session disconnects; with +auto-reconnect on, it picks up again when you plug it back in. + +Serial tabs are proper terminals, so recording, triggers, broadcast, snippets, find and split panes all work. + +\begin{headsup}[Gibberish on screen?] +Garbled characters nearly always mean the baud rate, parity or stop bits don't match the device. Check the +device's manual. 9600 and 115200 are the usual suspects. +\end{headsup} + +\begin{protip}[Linux and macOS quirks] +On Linux, add yourself to the \code{dialout} group (\code{uucp} on some distributions) and log out and in again. +On a Mac, use the \code{/dev/cu.*} device, not \code{/dev/tty.*}. +\end{protip} + +% ========================================================================================= +\chapter{Telnet and RAW TCP}\label{ch:raw} +\chapmeta{4 minutes}{You'll poke at old kit and raw sockets without reaching for netcat} + +\begin{tldr} +Telnet is a terminal for old devices. RAW is a packet-level conversation with any TCP port, with a hex view. +\end{tldr} + +\section{Telnet} + +\begin{glance}{amber}{Telnet} + Default port & 23\\ + Options & Local echo, line mode, terminal type, encoding (UTF-8, ASCII, ISO 8859-1), line endings, login script\\ + Extras & Send Break from the tab menu, \ui{Debug Negotiation} for when options go wrong\\ +\end{glance} + +\begin{danger}[Telnet is plain text] +Everything, including your password, crosses the network unencrypted. Fine on a lab bench, not fine over the +internet or café Wi-Fi. +\end{danger} + +\section{RAW TCP} + +RAW connects to any TCP port and shows you exactly what goes back and forth. Instead of a terminal you get a +traffic log with \ui{Text}, \ui{Hex} and \ui{Split} views. + +\shot{raw.png}{A RAW session to a chatty sensor, with timestamps and direction markers turned on.} + +\begin{itemize} + \item \textbf{Send bar:} pick \ui{Text}, \ui{Hex} or \ui{Escaped} (\code{\textbackslash r\textbackslash n} and friends), + type, press \kbd{Enter}. One-click buttons send \ui{NUL}, \ui{CR}, \ui{LF}, \ui{ETX} and \ui{EOT}. + \item \textbf{Presets:} save payloads you send often in the profile under \ui{Payload Presets}, then pick them from + \ui{Presets…}. + \item \textbf{Capture:} \ui{Pause}, \ui{Clear}, and \ui{Save} (writes the whole conversation as JSON). + \item \textbf{Profile options:} encoding (UTF-8, ASCII, Hex), line ending, timestamps, direction, timeouts and + auto-reconnect. +\end{itemize} + +\begin{quickwin} +Point a RAW profile at a web server on port 80 and send \code{HEAD / HTTP/1.0\textbackslash r\textbackslash n\textbackslash r\textbackslash n} +in \ui{Escaped} mode. Instant HTTP headers, no browser required. +\end{quickwin} + +% ========================================================================================= +\chapter{VNC remote desktops}\label{ch:vnc} +\chapmeta{4 minutes}{You'll see and drive a remote desktop, safely} + +\begin{tldr} +VNC shows you another computer's screen. Tunnel it through SSH (tick a box), because plain VNC isn't encrypted. +\end{tldr} + +\begin{glance}{rose}{VNC} + Default port & 5900 (plus the display number: display \code{:1} is 5901)\\ + Display & \ui{View Only}, \ui{Scale to Fit}, \ui{Shared Session}, \ui{Quality (0-9)}\\ + Security & Optional SSH tunnel with password, key or agent login\\ +\end{glance} + +\shot{vnc.png}{A remote desktop, scaled to fit the tab.} + +\section{The toolbar} + +Connection state and desktop size on the left. On the right: switch between scaled and 1:1, refresh, +\ui{Paste Clipboard} (sends your clipboard to the remote machine) and \ui{Ctrl+Alt+Del}. Click the desktop to give +it your keyboard. Anything the remote machine copies lands on your clipboard automatically. + +\section{Tunnel it} + +Tick \ui{Enable SSH Tunnel} and fill in the SSH host, port, username and login method. The VNC traffic then +travels inside an encrypted SSH connection. If you connect to a VNC profile \emph{without} a tunnel, Nexus Terminal +warns you and offers \ui{Connect Anyway}, \ui{Edit Profile} or \ui{Cancel}. + +\begin{headsup}[Connecting to a Mac?] +Turn on \ui{VNC viewers may control screen with password} in macOS Screen Sharing settings. Nexus Terminal uses +standard VNC password authentication, not Apple's own login method. +\end{headsup} + +% ========================================================================================= +\chapter{IRC}\label{ch:irc} +\chapmeta{3 minutes}{You'll be chatting in your project's channels from the same window as your servers} + +\begin{tldr} +Nick, server, rooms to join. Tabs for each room, \code{/commands} as you'd expect, \kbd{Tab} completes nicks. +\end{tldr} + +\begin{glance}{amber}{IRC} + Default port & 6667, or 6697 with \ui{Use TLS} (use TLS!)\\ + Extras & NickServ identify on connect, auto-join rooms, room browser, SSH tunnel\\ +\end{glance} + +\shot{irc.png}{Chatting in \#nexus. Rooms down the right, users in the room below them.} + +\begin{itemize} + \item A \ui{Server} tab plus one per room or private conversation. A dot means unread messages. + \item Commands: \code{/join}, \code{/part}, \code{/msg}, \code{/me}, \code{/notice}, \code{/nick}, \code{/topic}, + \code{/list}, \code{/names}, \code{/whois}, \code{/who}, \code{/raw}, \code{/quit} and \code{/help}. + \item \kbd{Tab} completes a nick; click a nick to mention them. + \item The side panel lists your rooms, a room browser with a filter (click to join) and who's in the current room. +\end{itemize} + +% ========================================================================================= +\chapter{GitHub}\label{ch:github} +\chapmeta{2 minutes}{You'll see all your repositories, freshest first} + +\crop{editor-github.png}{1515}{780}{0}{70}{0.5\linewidth}{A GitHub profile: just the API address and a token.} + +\begin{tldr} +Paste a personal access token with the \code{repo} scope. Get a tidy, filterable list of every repository you can see. +\end{tldr} + +\shot{github.png}{Repositories grouped by owner, with the latest commit and who made it.} + +\begin{itemize} + \item Grouped by owner or organisation, sorted by most recent activity. + \item Badges for \proto[rose]{Private}, \proto[violet]{Fork} and \proto[amber]{Archived}, plus language and stars. + \item Filter box, \ui{Refresh now}, \ui{Visit} (opens in your browser) and \ui{ZIP} (downloads an archive). + \item Works with GitHub Enterprise: set \ui{API Base URL} to your server's API address. Refreshes every 60–3600 seconds. +\end{itemize} + +% ========================================================================================= +\chapter{Your own computer}\label{ch:local} +\chapmeta{1 minute}{You'll stop alt-tabbing to a separate terminal app} + +Click \ui{Local Machine} for a shell on this computer. It uses your system's default shell unless you choose +another in \menu{Settings\then General\then Local shell} (\code{pwsh}, \code{zsh}, \code{fish}, whatever you fancy). + +\shot[0.86\linewidth]{local.png}{A local shell, with all the same terminal powers as SSH.} diff --git a/docs/guide/chapters/part3-faster.tex b/docs/guide/chapters/part3-faster.tex new file mode 100644 index 0000000..bb816c1 --- /dev/null +++ b/docs/guide/chapters/part3-faster.tex @@ -0,0 +1,234 @@ +\guidepart{violet}{Work faster}{Work faster} + {Fewer clicks, less typing, more getting on with it.} + {\partitem{\iSplit}{Tabs and split panes} + \partitem{\iKeyboard}{Terminal superpowers} + \partitem{\iWand}{Never type it twice} + \partitem{\iFolder}{Files: SFTP and drag-and-drop} + \partitem{\iArrows}{Port forwards} + \partitem{\iFilm}{Recordings}} + +% ========================================================================================= +\chapter{Tabs and split panes}\label{ch:tabs} +\chapmeta{4 minutes}{You'll juggle a dozen sessions without losing any of them} + +\begin{tldr} +Right-click a tab for everything. \keys{\mod+Shift+D} splits right, \keys{\mod+Shift+E} splits down. +\keys{\mod+1} to \keys{\mod+9} jump straight to a tab. +\end{tldr} + +\section{Tabs} + +\begin{description} + \item[Move] Drag tabs about, or right-click\then\ui{Move Left} / \ui{Move Right}. + \item[Pin] Right-click\then\ui{Pin}. Pinned tabs stay at the front. + \item[Rename] Right-click\then\ui{Rename}. ``prod DB, DON'T TOUCH'' is a perfectly valid tab name. + \item[Duplicate] Opens another session to the same profile. + \item[Reconnect] Does what it says. + \item[Peek] Hover a tab for its name, host, state and when it connected. + \item[Close] The \ui{×}, \keys{\mod+W}, or right-click\then\ui{Close}. Connected tabs ask first (you can turn that + off in Settings). + \item[Switch] \keys{\mod+Tab} and \keys{\mod+Shift+Tab}, or \keys{\mod+1} to \keys{\mod+9}. +\end{description} + +Lots of tabs? Scroll the tab strip with your mouse wheel or trackpad. + +\section{Split panes} + +Want two sessions side by side? Split the focused pane right with \keys{\mod+Shift+D} or down with +\keys{\mod+Shift+E} (also in the \menu{Session} menu, the right-click menu and the palette). The new pane asks +what to open: \ui{Local Machine} or any of your terminal profiles. + +\shot{split.png}{A fresh split. Pick what goes in the new pane.} + +Drag a divider to resize. Click \ui{×} on a pane's header to close it and its neighbour takes the space. Closing +the tab closes every pane in it. + +\begin{quickwin} +Split right, open \code{htop} on one side and tail a log on the other. Now you look like a hacker in a film. +\end{quickwin} + +% ========================================================================================= +\chapter{Terminal superpowers}\label{ch:terminal} +\chapmeta{4 minutes}{You'll copy, search and scroll like you've used it for years} + +\begin{tldr} +Select to copy, \keys{\mod+F} to search, Ctrl/\ph{E1C4}-click links. Right-click for the rest. +\end{tldr} + +\section{Keyboard and mouse} + +\renewcommand\arraystretch{1.45} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\heavy\color{ink}}p{24mm}X@{}} + \toprule + Copy & Select text, then \keys{\mod+C}. Nothing selected? \keys{Ctrl+C} interrupts the program as usual.\\ + Paste & \keys{\mod+V}. Pastes are bracketed when the program asks for it, so editors won't auto-indent your paste + into a staircase.\\ + Select & Drag for a range, double-click a word, triple-click a line, \kbd{Shift}-click to extend.\\ + Links & Hold \kbd{Ctrl} (\kbd{\ph{E1C4}}) and click a URL to open it in your browser.\\ + Scroll & Mouse wheel, or \keys{Shift+PageUp}, \keys{Shift+PageDown}, \keys{Shift+Home}, \keys{Shift+End}.\\ + \bottomrule +\end{tabularx} + +\begin{sidequest}[Why does the scroll wheel move the cursor in less?] +Full-screen programs (\code{less}, \code{vim}, \code{tmux}, \code{byobu}) use the terminal's ``alternate screen'', where the +wheel sends arrow keys instead of scrolling. Programs with mouse support (\code{htop}, \code{vim} with +\code{mouse=a}, \code{tmux} mouse mode) get your clicks directly. That's normal terminal behaviour, not a bug. +\end{sidequest} + +\section{The right-click menu} + +\ui{Copy}, \ui{Paste}, \ui{Select All}, \ui{Find}, \ui{Send Snippet}, \ui{Port Forwards} (SSH), \ui{Split Right}, +\ui{Split Down}, \ui{Export Output} (saves the whole scrollback as a text file), \ui{Clear} and \ui{Reset}. + +\section{Find} + +\keys{\mod+F} opens the find bar. Toggle \ui{Aa} for case-sensitive and \ui{.*} for regular expressions. +\kbd{Enter} for the next match, \keys{Shift+Enter} for the previous one, \kbd{Esc} to close. It shows +``3 of 17'' so you know where you are. + +\section{When the connection drops} + +The pane dims and shows a \ui{Disconnected} card with \ui{Reconnect} and \ui{Close}. If auto-reconnect is on, +it says \ui{Reconnecting…} with a \ui{Cancel} button, and a toast tells you which attempt it's on. + +If the problem was a password or host key, you get a friendly dialog explaining what happened, with +\ui{Retry}, \ui{Edit Profile}, \ui{Known Hosts} (for host-key trouble) and \ui{Close}. + +% ========================================================================================= +\chapter{Never type it twice}\label{ch:repeat} +\chapmeta{6 minutes}{You'll stop retyping the same commands, and let Nexus Terminal watch the logs for you} + +\begin{tldr} +\textbf{Snippets} store commands. \textbf{Broadcast} types into many sessions at once. \textbf{Triggers} watch output and +tap you on the shoulder. +\end{tldr} + +\section{Snippets: \keys{\mod+Shift+S}} + +Save commands you use a lot, with a name, description and folder. Search them, click +\ui{Send to Session}, and the command is typed into the active session followed by \kbd{Enter}. + +\crop{snippets.png}{633}{384}{611}{402}{0.8\linewidth}{The snippet library, organised into folders.} + +Snippets can have blanks to fill in: + +\begin{itemize} + \item \code{\{\{hostname\}\}}, \code{\{\{date\}\}}, \code{\{\{time\}\}} and \code{\{\{timestamp\}\}} are filled in for you. + \item Anything else, like \code{\{\{service\}\}}, is asked for when you send it. +\end{itemize} + +So \code{journalctl -fu \{\{service\}\}} asks ``service?'' and tails whichever one you type. + +\section{Broadcast: \keys{\mod+Shift+B}} + +Turn broadcast on and everything you type goes to \emph{every} connected terminal. The status bar shows +\ui{BROADCAST} so you can't forget it's on. + +\shot{broadcast.png}{Broadcast is on: the status bar says so in no uncertain terms.} + +To broadcast to only some sessions, right-click the \iBroadcast{} button (or \menu{Session\then Broadcast +targets…}), tick the ones you want and click \ui{Apply \& Enable}. + +\begin{danger}[With great power…] +Broadcast doesn't care which tab you \emph{meant}. Double-check the target list before you type anything +destructive. \code{sudo reboot} into twenty servers is very efficient and very memorable. +\end{danger} + +\section{Triggers: let it watch for you} + +\menu{Session\then Trigger Rules} (or the palette). Each rule has a pattern (plain text or a regular +expression), a case-sensitivity switch, an on/off switch, and an action: + +\begin{center}\small +\begin{tabular}{@{}ll@{}} + \ui{Notify} & shows a toast\\ + \ui{Highlight tab} & briefly outlines the pane\\ + \ui{Notify + Highlight} & both\\ + \ui{Sound} & asks your OS for attention (a bouncing dock icon or flashing taskbar button) and shows a toast\\ +\end{tabular} +\end{center} + +\crop{triggers.png}{725}{610}{725}{625}{0.75\linewidth}{Three rules: shout about errors, ping when a build finishes, and one switched off for now.} + +Rules check each complete line of output, with colours stripped out first. + +\begin{quickwin} +Add a rule for \code{BUILD SUCCESSFUL} with the \ui{Sound} action, start a long build, and go and make a cup of tea. +Nexus Terminal will tell you when it's done. (Or \code{(?i)error|fatal} for the opposite.) +\end{quickwin} + +% ========================================================================================= +\chapter{Files: SFTP and drag-and-drop}\label{ch:files} +\chapmeta{4 minutes}{You'll move files to and from servers without leaving the terminal} + +\begin{tldr} +Click the \iFolder{} on an SSH tab. Or drop a file onto the terminal and choose \ui{Upload}. +\end{tldr} + +\section{The SFTP browser} + +Click the \iFolder{} folder icon on an SSH tab, \menu{Session\then SFTP browser}, or type ``sftp'' in the palette. +It opens in the side dock next to your terminal. + +\shot{sftp.png}{Browsing the server's files next to the terminal.} + +\begin{itemize} + \item Move around with the breadcrumb trail, the \code{..} entry or \ui{Go to path}. + \item Upload files; download one or many to a folder you choose. + \item Rename, delete (it asks first), make folders, refresh. + \item \iStar{} bookmarks the current folder, per profile. + \item Ctrl-click (\ph{E1C4}-click) or Shift-click to select several things. Transfers show progress as they go. +\end{itemize} + +\section{Drag and drop} + +Drop files onto a terminal and choose: + +\begin{description} + \item[Upload to current directory] Pick the remote folder (SSH only). + \item[Paste contents] Types the file's contents into the terminal, as if you'd typed it. +\end{description} + +The upload picker looks like the SFTP browser but shows folders only, labelled \proto[amber]{READ ONLY} if you can't +write there and \proto[rose]{NO READ} if you can't look inside. Keyboard fans: \kbd{↑}\kbd{↓} to move, \kbd{Enter} to +choose, \kbd{Backspace} to go up, \kbd{Esc} to cancel. If the SFTP browser is open, the picker starts in the same folder. + +% ========================================================================================= +\chapter{Port forwards}\label{ch:forwards} +\chapmeta{3 minutes}{You'll reach a database or web app that only listens on the server} + +\begin{tldr} +\emph{Local} forwards bring a remote port to your machine. \emph{Remote} forwards do the opposite. +\end{tldr} + +Forwards saved in an SSH profile (under \ui{Port Forwarding}, with \ui{Active} ticked) start when you connect. +To manage them on a live session, right-click the terminal\then\ui{Port Forwards}, or \menu{Session\then Port forwards}. + +\begin{description} + \item[Local \iArrow] Listen on your computer, forward to somewhere the server can reach. + Example: \code{127.0.0.1:15432}\then\code{127.0.0.1:5432} lets your desktop database app talk to Postgres on + the server as if it were local. + \item[Remote \ph{E058}] Ask the server to listen and send connections back to somewhere \emph{your} computer can reach. + Great for showing a colleague the dev server running on your laptop. +\end{description} + +Active forwards are listed with an \proto[accent]{L} or \proto[violet]{R} badge and a stop button. Toasts tell you when one +starts, stops or fails. + +% ========================================================================================= +\chapter{Recordings}\label{ch:recordings} +\chapmeta{2 minutes}{You'll have a replayable record of exactly what happened} + +\keys{\mod+Shift+R} starts and stops recording the active session. A red dot appears on the tab and \ui{REC} in +the status bar. Recordings are standard asciicast v2 files, so other tools (like \code{asciinema}) can play them too. + +\crop{recordings.png}{712}{392}{690}{415}{0.75\linewidth}{The recordings browser. Play, delete, or tidy up in bulk.} + +The browser (sidebar footer, \menu{Session} menu or palette) shows how many recordings you have and how much space +they take. Play or delete each one, or use \ui{Clean up} to remove recordings older than 7, 30 or 90 days, or to keep +the total under 1\,GB or 5\,GB. Playback has play/pause, restart, a seek bar and speed control. + +\begin{protip} +Record the next time you do something fiddly and rare (a database failover, a certificate rotation). Six months +later, ``how did we do that again?'' has an answer you can literally watch. +\end{protip} diff --git a/docs/guide/chapters/part4-safe.tex b/docs/guide/chapters/part4-safe.tex new file mode 100644 index 0000000..89597d2 --- /dev/null +++ b/docs/guide/chapters/part4-safe.tex @@ -0,0 +1,123 @@ +\guidepart{amber}{Stay safe}{Stay safe} + {Passwords, keys and host checks, minus the jargon headache.} + {\partitem{\iLock}{Passwords and the vault} + \partitem{\iShield}{Host keys: is it really that server?} + \partitem{\iKey}{SSH keys}} + +% ========================================================================================= +\chapter{Passwords and the vault}\label{ch:secrets} +\chapmeta{5 minutes}{You'll know exactly where your passwords live and who can read them} + +\begin{tldr} +Passwords go in your OS keychain. If there isn't one, they go in an encrypted vault protected by a master password. +For each password you choose: save it, remember it until you quit, or ask every time. +\end{tldr} + +\section{Where secrets live} + +\begin{center} +\begin{tikzpicture}[ + box/.style={rounded corners=6pt, fill=#1!12, draw=#1!60, line width=0.6pt, minimum height=13mm, text width=40mm, + align=center, font=\small}, + arr/.style={-latex, line width=0.8pt, color=muted}] + \node[box=accent] (need) {\heavy A session needs\\a password}; + \node[box=green, right=12mm of need] (kc) {\heavy OS keychain\\{\footnotesize Keychain · Credential Manager · Secret Service}}; + \node[box=amber, right=12mm of kc] (vault) {\heavy Encrypted vault\\{\footnotesize AES-256-GCM, Argon2id}}; + \node[box=rose, below=9mm of kc] (ask) {\heavy Ask you\\{\footnotesize then carry on}}; + \draw[arr] (need) -- (kc); + \draw[arr] (kc) -- node[above, font=\scriptsize]{not there?} (vault); + \draw[arr] (vault) |- node[pos=0.25, right, font=\scriptsize]{still not there?} (ask); +\end{tikzpicture} +\end{center} + +\begin{description} + \item[OS keychain first] macOS Keychain, Windows Credential Manager, or the Linux Secret Service (GNOME Keyring, + KWallet and friends). + \item[Encrypted vault as backup] If there's no keychain, or it refuses, secrets go into Nexus Terminal's own + vault: AES-256-GCM with a key derived from your master password by Argon2id using 128\,MiB of memory. In + plain English: very, very expensive to brute-force. +\end{description} + +\section{Setting up the vault} + +\menu{Settings\then Security\then Initialise Secret Store}. Choose a master password; the strength meter goes from +\proto[rose]{Weak} through \proto[amber]{Fair} and \proto[accent]{Good} to \proto[green]{Strong}. Unlock and lock it +from the same page, from the palette, or by clicking the vault badge in the status bar. It locks itself when you quit. + +\crop{settings-security.png}{581}{253}{576}{285}{0.8\linewidth}{Security settings on a machine without an OS keychain: the vault does the job instead.} + +\begin{danger}[There's no ``forgot password'' link] +Lose the master password and the secrets in the vault are gone for good. That's the point of encryption. Put it in +your password manager. +\end{danger} + +\section{Three ways to handle each password} + +Every password, passphrase, token or TOTP secret has its own setting: + +\begin{center} +\begin{tabular}{@{}p{0.3\linewidth}p{0.3\linewidth}p{0.3\linewidth}@{}} + \begin{tcolorbox}[colback=green!8, boxrule=0pt, arc=6pt, fontupper=\small, height=33mm] + {\heavy\color{green!60!black}\iLock\ Save securely}\par\smallskip Stored in the keychain or vault and used automatically. Most convenient. + \end{tcolorbox} & + \begin{tcolorbox}[colback=accent!8, boxrule=0pt, arc=6pt, fontupper=\small, height=33mm] + {\heavy\color{accent!80!black}\iClock\ Remember for this session}\par\smallskip Asked once, kept in memory, never written to disk. + \end{tcolorbox} & + \begin{tcolorbox}[colback=amber!10, boxrule=0pt, arc=6pt, fontupper=\small, height=33mm] + {\heavy\color{amber!75!black}\iDetective\ Ask every time}\par\smallskip Asked on every single connection. Most paranoid. + \end{tcolorbox} +\end{tabular} +\end{center} + +When Nexus Terminal needs a secret it doesn't have, it asks. Press \kbd{Esc} or \ui{Cancel} to give up on that +connection. + +\begin{sidequest}[What gets written down?] +Connections, disconnections, profile changes, credential access, imports and exports go into an audit log in the +database. Secrets themselves never do. TOTP secrets live in the keychain or vault too, never in the profile. +\end{sidequest} + +% ========================================================================================= +\chapter{Host keys: is it really that server?}\label{ch:hostkeys} +\chapmeta{3 minutes}{You'll understand the scary fingerprint dialog and what to do about it} + +\begin{tldr} +Every SSH server has a fingerprint. Nexus Terminal remembers it the first time and shouts if it ever changes. +A changed fingerprint means a reinstalled server, or someone in the middle. Find out which before carrying on. +\end{tldr} + +Pick a policy per SSH profile under \ui{Host Verification}: + +\renewcommand\arraystretch{1.5} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\heavy\color{ink}}p{48mm}X@{}} + \toprule + Strict (known hosts only) & Only connects to servers you've already trusted.\\ + Ask on new or changed keys & Shows the fingerprint and asks. \ui{Accept \& Save} or \ui{Reject}. The default.\\ + Accept new, reject changed & Trusts first-time servers silently, still refuses changed keys.\\ + Insecure (accept anything) & Exactly what it says. Lab use only.\\ + \bottomrule +\end{tabularx} + +Under every policy except \emph{Insecure}, a key that differs from the saved one is rejected. + +\crop{settings-knownhosts.png}{581}{253}{576}{285}{0.8\linewidth}{Known hosts. Click a fingerprint to copy it; \ui{Remove} to forget a server.} + +\begin{headsup} +Nexus Terminal keeps its own list of known hosts. It doesn't read \code{\textasciitilde/.ssh/known\_hosts}, so the +first connection to each server asks once, even if your command-line \code{ssh} already knows it. +\end{headsup} + +% ========================================================================================= +\chapter{SSH keys}\label{ch:keys} +\chapmeta{2 minutes}{You'll have a key pair without memorising ssh-keygen flags} + +\menu{Session\then SSH key manager} (or the palette) lists the key pairs in \code{\textasciitilde/.ssh} with their type. Copy a +public key to the clipboard (ready to paste into GitHub or \code{authorized\_keys}), delete a pair, or generate a new +\proto[teal]{Ed25519}, \proto[accent]{RSA} (2048 or 4096 bits) or \proto[violet]{ECDSA} key with an optional passphrase. + +\crop{keys.png}{725}{523}{725}{546}{0.75\linewidth}{The SSH key manager.} + +\begin{protip} +Not sure which type? Pick Ed25519. It's small, fast and secure. Key generation uses your system's +\code{ssh-keygen}, which comes with macOS, Linux and Windows 10 and later. +\end{protip} diff --git a/docs/guide/chapters/part5-yours.tex b/docs/guide/chapters/part5-yours.tex new file mode 100644 index 0000000..07511c6 --- /dev/null +++ b/docs/guide/chapters/part5-yours.tex @@ -0,0 +1,107 @@ +\guidepart{pink}{Make it yours}{Make it yours} + {Settings, backups, and moving in from Alchementary Link.} + {\partitem{\iGear}{Settings} + \partitem{\iUpload}{Import and export} + \partitem{\iPlane}{Moving house}} + +% ========================================================================================= +\chapter{Settings}\label{ch:settings} +\chapmeta{3 minutes}{You'll know where every setting lives} + +\keys{\mod+,} opens settings (also the \iGear{} in the sidebar footer and \menu{File\then Settings}). Change things, +then click \ui{Save}. + +\begin{center}\begin{minipage}[t]{0.49\linewidth}\crop{settings-general.png}{581}{253}{576}{285}{\linewidth}{General}\end{minipage}\hfill\begin{minipage}[t]{0.49\linewidth}\crop{settings-terminal.png}{581}{253}{576}{285}{\linewidth}{Terminal}\end{minipage}\end{center} + +\begin{description} + \item[General] App theme (Pro, Midnight, Light), default protocol for new profiles, whether to ask before closing or + disconnecting, status bar on or off, sidebar width, and the local shell (leave it empty for the system default, + or \ui{Browse…} for one). + \item[Terminal] Colour scheme, font, size, scrollback and cursor style. These are the defaults for new profiles; + each profile can override them. + \item[Security] Keychain and vault status; set up, unlock or lock the vault. + \item[Known Hosts] Saved server fingerprints, with first-seen and last-seen times. + \item[Help] Built-in help: connection types, shortcuts and troubleshooting, when this guide isn't to hand. + \item[About] Version, \ui{Check for Updates} and \ui{Import \textasciitilde/.ssh/config}. +\end{description} + +\crop{settings-help.png}{581}{253}{576}{285}{0.8\linewidth}{The built-in help, for when this guide isn't to hand.} + +\section{Updates} + +Nexus Terminal checks once at launch. If there's a newer version the status bar says \ui{Update available}; click it to +see the release notes and a \ui{View release} button. Nothing downloads or installs behind your back. + +% ========================================================================================= +\chapter{Import and export}\label{ch:transfer} +\chapmeta{3 minutes}{You'll back up your profiles, or copy them to another computer} + +\begin{tldr} +\menu{File\then Export profiles…} makes a password-protected \code{.nexus} file. \menu{File\then Import profiles…} +reads it back. Already have an \code{\textasciitilde/.ssh/config}? Import that too. +\end{tldr} + +\begin{description} + \item[Encrypted export] Choose the profiles, set a password, save a \code{.nexus} file. Saved passwords aren't + included, so the file is safe to email to yourself. + \item[Encrypted import] Pick a \code{.nexus} (or an old \code{.link}) file, enter its password and decide what happens to + profiles that already exist: \ui{Skip existing}, \ui{Overwrite existing} or \ui{Create duplicates}. + \item[JSON] \ui{Export Profiles (JSON)} and \ui{Import Profiles (JSON)} are in the palette. Tick + \ui{Include secrets} to keep proxy passwords and TOTP secrets, which are otherwise left out. + \item[OpenSSH config] \menu{File\then Import \textasciitilde/.ssh/config} turns your \code{Host} blocks into profiles, + with host names, users, ports, identity files and jump hosts. +\end{description} + +\crop{export.png}{894}{357}{872}{380}{0.55\linewidth}{Exporting profiles. Everything's ticked to start with; untick what you don't want.} + +\begin{danger}[JSON with secrets is not encrypted] +A JSON export with \ui{Include secrets} ticked has those secrets in plain text. Use the encrypted \code{.nexus} export to +move things between machines. +\end{danger} + +% ========================================================================================= +\chapter{Moving house}\label{ch:move} +\chapmeta{2 minutes}{You'll know what came over from Alchementary Link and what didn't} + +\begin{tldr} +It's automatic. First run copies your Alchementary Link profiles, vault and recordings. Your old folder is left +alone. Recreate trigger rules and SFTP bookmarks by hand. +\end{tldr} + +When Nexus Terminal starts for the first time and finds an \code{AlchementaryLink} folder next to where its own data +goes, it \emph{copies} \code{link.db} to \code{nexus.db}, along with the \code{vault/} and \code{recordings/} folders, +and shows a toast saying where they came from. + +\begin{checklist} + \item[\color{green}\iCheck] Profiles, groups, favourites, known hosts, snippets and preferences + \item[\color{green}\iCheck] Vault (same master password as before) + \item[\color{green}\iCheck] Recordings + \item[\color{green}\iCheck] Keychain passwords: picked up the first time each one is needed, then copied across + \item[\color{green}\iCheck] Old \code{.link} export files still import + \item[\color{rose}\ph{E4F6}] Trigger rules and SFTP bookmarks (they lived in the old app's web storage) +\end{checklist} + +\begin{headsup} +If a \code{NexusTerminal/nexus.db} already exists (say, from before the Alchementary Link rebrand), it's used as it is +and nothing is copied. +\end{headsup} + +\section{Where everything lives} + +\renewcommand\arraystretch{1.45} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\heavy\color{ink}}p{20mm}X@{}} + \toprule + macOS & \code{\textasciitilde/Library/Application Support/NexusTerminal}\\ + Windows & \code{\%APPDATA\%\textbackslash NexusTerminal}\\ + Linux & \code{\$XDG\_CONFIG\_HOME/NexusTerminal}, usually \code{\textasciitilde/.config/NexusTerminal}\\ + \bottomrule +\end{tabularx} + +Inside: \code{nexus.db} (profiles, known hosts, preferences, snippets, IRC state and the audit log), +\code{vault/vault.json}, \code{recordings/}, \code{triggers.json} and \code{sftp-bookmarks.json}. Keychain entries are +stored under the service name \code{NexusTerminal}. + +\begin{protip}[Try things without touching your real data] +Start Nexus Terminal with \code{--data-dir /some/other/folder} and it uses that folder instead. Brilliant for +experiments, demos and a portable install on a USB stick. +\end{protip} diff --git a/docs/guide/chapters/part6-fix.tex b/docs/guide/chapters/part6-fix.tex new file mode 100644 index 0000000..cc0bda0 --- /dev/null +++ b/docs/guide/chapters/part6-fix.tex @@ -0,0 +1,88 @@ +\guidepart{rose}{Fix it}{When things go wrong} + {Symptom first, fix second. No essays.} + {\partitem{\iLifebuoy}{Troubleshooting, by symptom}} +\label{part:fix} + +% ========================================================================================= +\chapter{Troubleshooting, by symptom}\label{ch:trouble} +\chapmeta{as long as it takes}{You'll find your symptom and the fix in one place} + +\begin{tldr} +Find the heading that matches what you're seeing. Try the fixes in order. Most problems are the network, the +password, or the baud rate. +\end{tldr} + +\section{Connecting} + +\fix{``Authentication failed''}{% +\begin{itemize} + \item Check the username, password, key path and the ticked \ui{Auth Methods}. + \item If the key has a passphrase, check how the passphrase is stored (\ui{Passphrase Mode}). + \item Two-factor server? Click \ui{Test} next to the TOTP secret and compare with your phone. Check your clock. + \item Saved password not being used? The vault might be locked: click \ui{Vault locked} in the status bar. +\end{itemize}} + +\fix{``Host key verification failed''}{% +The server's fingerprint changed since you last connected. That happens when a server is rebuilt, but it's also exactly +what an attack looks like. \textbf{Check with whoever runs the server.} If it's legitimate: +\menu{Settings\then Known Hosts}, \ui{Remove} the entry, reconnect and accept the new key.} + +\fix{It just says ``Connecting…'' forever}{% +\begin{itemize} + \item Click \ui{Test} in the profile editor. If that fails too, it's the network, a firewall or the wrong port. + \item Behind a corporate proxy? Set one up under \ui{Proxy} (SSH profiles). + \item Going through a bastion? Check each jump host on its own first. +\end{itemize}} + +\fix{The session keeps reconnecting}{% +Check the network and the credentials, then the profile's auto-reconnect settings. Reconnection backs off (1\,s, 2\,s, +4\,s… up to 30\,s) and stops after the maximum number of attempts.} + +\section{Serial} + +\fix{The port won't open}{% +\begin{itemize} + \item Is the adapter plugged in? Hit refresh in the device list. + \item \textbf{Permission denied} on Linux: add yourself to \code{dialout} (or \code{uucp}), then log out and back in. + \item On a Mac, use \code{/dev/cu.*}, not \code{/dev/tty.*}. + \item \textbf{Port is in use by another program}: close \code{screen}, \code{minicom}, the Arduino IDE serial monitor or + whatever else has it open. +\end{itemize}} + +\fix{Gibberish on screen}{The baud rate, parity or stop bits don't match the device. Check its manual.} + +\section{Remote desktops} + +\fix{VNC won't connect}{% +\begin{itemize} + \item VNC ports are 5900 plus the display number: \code{:0} is 5900, \code{:1} is 5901. + \item Check the password and how it's stored. + \item Using an SSH tunnel? Check the tunnel's host, user, login method and key, and the host key policy. + \item Connecting to a Mac? Turn on password access in macOS Screen Sharing settings (see p.\,\pageref{ch:vnc}). +\end{itemize}} + +\section{Files} + +\fix{Upload is greyed out or fails}{% +The folder is probably \proto[amber]{READ ONLY} for your user. Pick another, or check permissions on the server. Uploads +need an SSH session; other protocols can't do it.} + +\fix{``NO READ'' on a folder}{You don't have permission to look inside. Try the parent folder, or ask the server's admin.} + +\section{The terminal itself} + +\fix{The terminal is the wrong size}{% +Resize the window or nudge a split divider; the terminal re-fits itself. Still wrong? Reconnect. Very old servers may +prefer a different \ui{Terminal Type} in the profile (try \code{vt100}).} + +\fix{The scroll wheel moves the cursor instead of scrolling}{% +You're in a full-screen program (\code{less}, \code{vim}, \code{tmux}). That's how terminals work: the wheel sends arrow keys +there. Quit the program, or use its own scrolling (in \code{tmux}: \keys{Ctrl+B} then \kbd{[}).} + +\fix{Emoji are in black and white}{Known limitation: colour emoji show as monochrome glyphs. Everything else renders +normally.} + +\begin{quickwin}[Still stuck?] +Start Nexus Terminal with \code{--data-dir} pointing at an empty folder and try the same connection there. If it +works, something in your profile is off; duplicate the working one and compare. +\end{quickwin} diff --git a/docs/guide/fonts/Phosphor-Fill.ttf b/docs/guide/fonts/Phosphor-Fill.ttf new file mode 100644 index 0000000..b02c4cb Binary files /dev/null and b/docs/guide/fonts/Phosphor-Fill.ttf differ diff --git a/docs/guide/fonts/Phosphor-LICENSE.txt b/docs/guide/fonts/Phosphor-LICENSE.txt new file mode 100644 index 0000000..2629407 --- /dev/null +++ b/docs/guide/fonts/Phosphor-LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Romet Tagobert + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/guide/fonts/Phosphor.ttf b/docs/guide/fonts/Phosphor.ttf new file mode 100644 index 0000000..7c1b8a7 Binary files /dev/null and b/docs/guide/fonts/Phosphor.ttf differ diff --git a/docs/guide/img/broadcast.png b/docs/guide/img/broadcast.png new file mode 100644 index 0000000..d6b517f Binary files /dev/null and b/docs/guide/img/broadcast.png differ diff --git a/docs/guide/img/editor-github.png b/docs/guide/img/editor-github.png new file mode 100644 index 0000000..6ab162c Binary files /dev/null and b/docs/guide/img/editor-github.png differ diff --git a/docs/guide/img/editor-irc.png b/docs/guide/img/editor-irc.png new file mode 100644 index 0000000..728dedf Binary files /dev/null and b/docs/guide/img/editor-irc.png differ diff --git a/docs/guide/img/editor-new.png b/docs/guide/img/editor-new.png new file mode 100644 index 0000000..ae418d4 Binary files /dev/null and b/docs/guide/img/editor-new.png differ diff --git a/docs/guide/img/editor-raw.png b/docs/guide/img/editor-raw.png new file mode 100644 index 0000000..69ea669 Binary files /dev/null and b/docs/guide/img/editor-raw.png differ diff --git a/docs/guide/img/editor-serial.png b/docs/guide/img/editor-serial.png new file mode 100644 index 0000000..0768672 Binary files /dev/null and b/docs/guide/img/editor-serial.png differ diff --git a/docs/guide/img/editor-ssh.png b/docs/guide/img/editor-ssh.png new file mode 100644 index 0000000..ae4db85 Binary files /dev/null and b/docs/guide/img/editor-ssh.png differ diff --git a/docs/guide/img/editor-telnet.png b/docs/guide/img/editor-telnet.png new file mode 100644 index 0000000..7afebd4 Binary files /dev/null and b/docs/guide/img/editor-telnet.png differ diff --git a/docs/guide/img/editor-vnc.png b/docs/guide/img/editor-vnc.png new file mode 100644 index 0000000..9b1ceca Binary files /dev/null and b/docs/guide/img/editor-vnc.png differ diff --git a/docs/guide/img/export.png b/docs/guide/img/export.png new file mode 100644 index 0000000..7e693d5 Binary files /dev/null and b/docs/guide/img/export.png differ diff --git a/docs/guide/img/github.png b/docs/guide/img/github.png new file mode 100644 index 0000000..ed88319 Binary files /dev/null and b/docs/guide/img/github.png differ diff --git a/docs/guide/img/hostkey.png b/docs/guide/img/hostkey.png new file mode 100644 index 0000000..af4d956 Binary files /dev/null and b/docs/guide/img/hostkey.png differ diff --git a/docs/guide/img/irc.png b/docs/guide/img/irc.png new file mode 100644 index 0000000..6e4f609 Binary files /dev/null and b/docs/guide/img/irc.png differ diff --git a/docs/guide/img/keys.png b/docs/guide/img/keys.png new file mode 100644 index 0000000..04d3820 Binary files /dev/null and b/docs/guide/img/keys.png differ diff --git a/docs/guide/img/local.png b/docs/guide/img/local.png new file mode 100644 index 0000000..4c4766c Binary files /dev/null and b/docs/guide/img/local.png differ diff --git a/docs/guide/img/logo.pdf b/docs/guide/img/logo.pdf new file mode 100644 index 0000000..f1a8075 Binary files /dev/null and b/docs/guide/img/logo.pdf differ diff --git a/docs/guide/img/palette.png b/docs/guide/img/palette.png new file mode 100644 index 0000000..99d1c2c Binary files /dev/null and b/docs/guide/img/palette.png differ diff --git a/docs/guide/img/quick.png b/docs/guide/img/quick.png new file mode 100644 index 0000000..650e338 Binary files /dev/null and b/docs/guide/img/quick.png differ diff --git a/docs/guide/img/raw.png b/docs/guide/img/raw.png new file mode 100644 index 0000000..72c925b Binary files /dev/null and b/docs/guide/img/raw.png differ diff --git a/docs/guide/img/recordings.png b/docs/guide/img/recordings.png new file mode 100644 index 0000000..b6439bb Binary files /dev/null and b/docs/guide/img/recordings.png differ diff --git a/docs/guide/img/settings-about.png b/docs/guide/img/settings-about.png new file mode 100644 index 0000000..21062cc Binary files /dev/null and b/docs/guide/img/settings-about.png differ diff --git a/docs/guide/img/settings-general.png b/docs/guide/img/settings-general.png new file mode 100644 index 0000000..e677664 Binary files /dev/null and b/docs/guide/img/settings-general.png differ diff --git a/docs/guide/img/settings-help.png b/docs/guide/img/settings-help.png new file mode 100644 index 0000000..828bb45 Binary files /dev/null and b/docs/guide/img/settings-help.png differ diff --git a/docs/guide/img/settings-knownhosts.png b/docs/guide/img/settings-knownhosts.png new file mode 100644 index 0000000..ced393d Binary files /dev/null and b/docs/guide/img/settings-knownhosts.png differ diff --git a/docs/guide/img/settings-security.png b/docs/guide/img/settings-security.png new file mode 100644 index 0000000..fac5f64 Binary files /dev/null and b/docs/guide/img/settings-security.png differ diff --git a/docs/guide/img/settings-terminal.png b/docs/guide/img/settings-terminal.png new file mode 100644 index 0000000..f6fe1fb Binary files /dev/null and b/docs/guide/img/settings-terminal.png differ diff --git a/docs/guide/img/sftp.png b/docs/guide/img/sftp.png new file mode 100644 index 0000000..7955aff Binary files /dev/null and b/docs/guide/img/sftp.png differ diff --git a/docs/guide/img/snippets.png b/docs/guide/img/snippets.png new file mode 100644 index 0000000..89fcddc Binary files /dev/null and b/docs/guide/img/snippets.png differ diff --git a/docs/guide/img/split.png b/docs/guide/img/split.png new file mode 100644 index 0000000..e45d582 Binary files /dev/null and b/docs/guide/img/split.png differ diff --git a/docs/guide/img/ssh-light.png b/docs/guide/img/ssh-light.png new file mode 100644 index 0000000..f3edc6d Binary files /dev/null and b/docs/guide/img/ssh-light.png differ diff --git a/docs/guide/img/ssh.png b/docs/guide/img/ssh.png new file mode 100644 index 0000000..4ca7990 Binary files /dev/null and b/docs/guide/img/ssh.png differ diff --git a/docs/guide/img/triggers.png b/docs/guide/img/triggers.png new file mode 100644 index 0000000..c9231bc Binary files /dev/null and b/docs/guide/img/triggers.png differ diff --git a/docs/guide/img/vnc.png b/docs/guide/img/vnc.png new file mode 100644 index 0000000..a924b70 Binary files /dev/null and b/docs/guide/img/vnc.png differ diff --git a/docs/guide/img/welcome-light.png b/docs/guide/img/welcome-light.png new file mode 100644 index 0000000..09c1b08 Binary files /dev/null and b/docs/guide/img/welcome-light.png differ diff --git a/docs/guide/img/welcome-midnight.png b/docs/guide/img/welcome-midnight.png new file mode 100644 index 0000000..739fba1 Binary files /dev/null and b/docs/guide/img/welcome-midnight.png differ diff --git a/docs/guide/img/welcome.png b/docs/guide/img/welcome.png new file mode 100644 index 0000000..3c32cbb Binary files /dev/null and b/docs/guide/img/welcome.png differ diff --git a/docs/guide/nexus-guide.pdf b/docs/guide/nexus-guide.pdf new file mode 100644 index 0000000..9d23ced Binary files /dev/null and b/docs/guide/nexus-guide.pdf differ diff --git a/docs/guide/nexus-guide.sty b/docs/guide/nexus-guide.sty new file mode 100644 index 0000000..a70f5af --- /dev/null +++ b/docs/guide/nexus-guide.sty @@ -0,0 +1,286 @@ +% Nexus Terminal user guide — look and feel. Build with lualatex (see Makefile). +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{nexus-guide} + +\RequirePackage[a4paper,top=24mm,bottom=26mm,inner=22mm,outer=26mm,headsep=8mm,footskip=12mm]{geometry} +% Latin Modern OpenType isn't always installed, and the kernel selects it once before the guide's own +% fonts take over. Point that one use at Inter so the build only needs fonts from the repo. +\DeclareFontShape{TU}{lmr}{m}{n}{<->"[../../rust/app/assets/fonts/Inter-Regular.ttf]:mode=node;"}{} +\RequirePackage{fontspec} +\RequirePackage[dvipsnames]{xcolor} +\RequirePackage{graphicx} +\RequirePackage[most]{tcolorbox} +\RequirePackage{tikz} +\usetikzlibrary{calc,positioning,shadows.blur,fadings} +\RequirePackage{titlesec} +\RequirePackage{fancyhdr} +\RequirePackage{enumitem} +\RequirePackage{tabularx} +\RequirePackage{booktabs} +\RequirePackage{multicol} +\RequirePackage{eso-pic} +\RequirePackage{etoolbox} +\RequirePackage{needspace} +\RequirePackage{microtype} +\RequirePackage[hidelinks]{hyperref} +\RequirePackage{bookmark} + +% --- Fonts: the app's own Inter + JetBrains Mono, and its Phosphor icons ---------------- +\defaultfontfeatures{Ligatures=TeX} +\setmainfont{Inter-Regular.ttf}[ + Path=../../rust/app/assets/fonts/, + BoldFont=Inter-SemiBold.ttf, + ItalicFont=Inter-Regular.ttf, ItalicFeatures={FakeSlant=0.18}, + BoldItalicFont=Inter-SemiBold.ttf, BoldItalicFeatures={FakeSlant=0.18}] +\setsansfont{Inter-Regular.ttf}[Path=../../rust/app/assets/fonts/, BoldFont=Inter-SemiBold.ttf] +\setmonofont{JetBrainsMono-Regular.ttf}[Path=../../rust/app/assets/fonts/, Scale=0.92] +\newfontfamily\heavy{Inter-SemiBold.ttf}[Path=../../rust/app/assets/fonts/] +\newfontfamily\phfont{Phosphor.ttf}[Path=fonts/] +\newfontfamily\phfill{Phosphor-Fill.ttf}[Path=fonts/] +\linespread{1.18} +\setlength{\parindent}{0pt} +\setlength{\parskip}{0.62em} +\raggedbottom +\sloppy + +% --- Palette (mirrors the app's Pro theme) --------------------------------------------- +\definecolor{ink}{HTML}{1B1B1F} +\definecolor{ink2}{HTML}{26262C} +\definecolor{body}{HTML}{2A2A33} +\definecolor{muted}{HTML}{6B6B78} +\definecolor{rule}{HTML}{E4E4EA} +\definecolor{paper}{HTML}{F7F7FA} +\definecolor{accent}{HTML}{3B82F6} +\definecolor{teal}{HTML}{14B8A6} +\definecolor{violet}{HTML}{8B5CF6} +\definecolor{amber}{HTML}{F59E0B} +\definecolor{rose}{HTML}{F43F5E} +\definecolor{pink}{HTML}{EC4899} +\definecolor{green}{HTML}{22C55E} +\definecolor{slate}{HTML}{64748B} +\color{body} + +% Page-level drawing. Everything that paints relative to the page goes through here, drawn at shipout +% from the page's lower-left corner, so it never depends on remember-picture positions settling. +% Inside, the node (pg) is the whole page: use pg.north west, pg.south east and so on. +\newcommand\pgnode{\node[inner sep=0pt, minimum width=\paperwidth, minimum height=\paperheight, anchor=south west] (pg) at (0,0) {};} +\newcommand\pagecanvas[1]{\AddToShipoutPictureBG*{\AtPageLowerLeft{\begin{tikzpicture}[overlay]\pgnode#1\end{tikzpicture}}}} + +% Current part colour (set by \guidepart). +\colorlet{partcol}{accent} +\newcounter{guidepart} +\newcommand\partshort{} + +% --- Icons ------------------------------------------------------------------------------ +\newcommand\ph[1]{{\phfont\symbol{"#1}}} +\newcommand\phf[1]{{\phfill\symbol{"#1}}} +\newcommand\iTerminal{\ph{EAE8}} \newcommand\iBolt{\ph{E2DE}} \newcommand\iBrain{\ph{E74E}} +\newcommand\iWarn{\ph{E4E0}} \newcommand\iBulb{\ph{E2DC}} \newcommand\iCoffee{\ph{E1C2}} +\newcommand\iRocket{\ph{E3FE}} \newcommand\iCheck{\ph{E184}} \newcommand\iTimer{\ph{E492}} +\newcommand\iKeyboard{\ph{E2D8}} \newcommand\iSparkle{\ph{E6A2}} \newcommand\iShield{\ph{E40C}} +\newcommand\iWand{\ph{E6B6}} \newcommand\iBug{\ph{E5F4}} \newcommand\iStar{\phf{E46A}} +\newcommand\iSiren{\ph{E9B8}} \newcommand\iWave{\ph{E580}} \newcommand\iTarget{\ph{E47C}} +\newcommand\iMap{\ph{E31A}} \newcommand\iList{\ph{EADC}} \newcommand\iBox{\ph{E45E}} +\newcommand\iKey{\ph{E2D6}} \newcommand\iPlugs{\ph{EB5A}} \newcommand\iFolder{\ph{E256}} +\newcommand\iBroadcast{\ph{E0F2}} \newcommand\iFilm{\ph{E792}} \newcommand\iCode{\ph{E1BC}} +\newcommand\iGear{\ph{E270}} \newcommand\iLock{\ph{E2FA}} \newcommand\iGithub{\ph{E576}} +\newcommand\iChat{\ph{E16C}} \newcommand\iDesktop{\ph{E560}} \newcommand\iUsb{\ph{E956}} +\newcommand\iTree{\ph{E67C}} \newcommand\iArrows{\ph{E0A0}} \newcommand\iCmd{\ph{E1C4}} +\newcommand\iSearch{\ph{E30C}} \newcommand\iSplit{\ph{E870}} \newcommand\iLifebuoy{\ph{E63A}} +\newcommand\iInfo{\ph{E2CE}} \newcommand\iPin{\ph{E3E2}} \newcommand\iConfetti{\ph{E81A}} +\newcommand\iDetective{\ph{E83E}} \newcommand\iCompass{\ph{E1C8}} \newcommand\iBinary{\ph{EE60}} +\newcommand\iPalette{\ph{E6C8}} \newcommand\iRecord{\ph{E3EE}} \newcommand\iArrow{\ph{E06C}} +\newcommand\iClock{\ph{E19A}} \newcommand\iFlag{\ph{EA38}} \newcommand\iPlug{\ph{E946}} +\newcommand\iGhost{\ph{E62A}} \newcommand\iBell{\ph{E5E8}} \newcommand\iHourglass{\ph{E2B2}} +\newcommand\iDownload{\ph{E20C}} \newcommand\iUpload{\ph{E4C0}} \newcommand\iPlane{\ph{E398}} + +% --- Inline bits -------------------------------------------------------------------------- +% \kbd{Ctrl} — a keycap. \keys{Ctrl+Shift+K} — keycaps joined with thin pluses. +\newtcbox{\kbd}{on line, boxrule=0.4pt, arc=2.2pt, colback=paper, colframe=rule!70!slate, + boxsep=0pt, left=3.2pt, right=3.2pt, top=1.6pt, bottom=1.2pt, fontupper=\ttfamily\footnotesize\color{ink}} +% \mod is "Ctrl, or ⌘ on a Mac": every shortcut in the guide is written \keys{\mod+K}. +\renewcommand\mod{\kbd{Ctrl\,/\,\ph{E1C4}}} +\def\keystop{\relax}\def\modtok{\mod} +\DeclareRobustCommand\keys[1]{\keysplit#1+\relax+} +\def\keysplit#1+{\def\kk{#1}\ifx\kk\keystop\else\keycap{#1}\expandafter\keysplitnext\fi} +\def\keysplitnext#1+{\def\kk{#1}\ifx\kk\keystop\else{\kern0.15em\scriptsize\color{muted}+\kern0.15em}\keycap{#1}\expandafter\keysplitnext\fi} +\newcommand\keycap[1]{\def\kc{#1}\ifx\kc\modtok\mod\else\kbd{#1}\fi} + +\newcommand\ui[1]{{\heavy\color{ink}#1}} % on-screen labels +\newcommand\code[1]{{\ttfamily\color{ink}#1}} % file paths and commands +\newcommand\menu[1]{{\heavy\color{ink}#1}} % File → Thing +\newcommand\then{\,{\color{muted}\iArrow}\,} +\newcommand\proto[2][accent]{\tcbox[on line, arc=6pt, boxrule=0pt, colback=#1!14, boxsep=0pt, + left=4pt, right=4pt, top=1.2pt, bottom=1.2pt]{\scriptsize\heavy\color{#1!80!black}#2}} + +% --- Callout boxes ---------------------------------------------------------------------- +\tcbset{guidebox/.style={enhanced, breakable, arc=7pt, boxrule=0pt, left=12pt, right=12pt, + top=8pt, bottom=8pt, before skip=10pt, after skip=12pt, fontupper=\small, + parbox=false, before upper={\setlength{\parskip}{0.5em}}}} +\newcommand\boxhead[3]{{\heavy\color{#1}\large#2\enspace\normalsize#3}\par\vspace{2pt}} + +\newtcolorbox{tldr}{guidebox, colback=partcol!8, borderline west={3pt}{0pt}{partcol}, + before upper={\boxhead{partcol}{\iBolt}{TL;DR}\setlength{\parskip}{0.4em}}} +\newtcolorbox{quickwin}[1][Quick win]{guidebox, colback=green!8, borderline west={3pt}{0pt}{green}, + before upper={\boxhead{green!65!black}{\iTarget}{#1}}} +\newtcolorbox{headsup}[1][Heads up]{guidebox, colback=amber!10, borderline west={3pt}{0pt}{amber}, + before upper={\boxhead{amber!75!black}{\iWarn}{#1}}} +\newtcolorbox{protip}[1][Pro tip]{guidebox, colback=accent!7, borderline west={3pt}{0pt}{accent}, + before upper={\boxhead{accent!80!black}{\iBulb}{#1}}} +\newtcolorbox{sidequest}[1][Side quest]{guidebox, colback=violet!7, + borderline west={3pt}{0pt}{violet}, borderline={0.5pt}{0pt}{violet!35,dashed}, + before upper={\boxhead{violet}{\iCompass}{#1 {\normalfont\footnotesize\color{muted}\enspace·\enspace optional, skip it if you're in a hurry}}}} +\newtcolorbox{danger}[1][Careful]{guidebox, colback=rose!8, borderline west={3pt}{0pt}{rose}, + before upper={\boxhead{rose!85!black}{\iSiren}{#1}}} + +% "You are here": a one-line chapter header strip with reading time. +\newcommand\chapmeta[2]{% + \begin{tcolorbox}[enhanced, arc=6pt, boxrule=0pt, colback=paper, left=10pt, right=10pt, + top=5pt, bottom=5pt, before skip=0pt, after skip=10pt, fontupper=\small\color{muted}] + {\color{partcol}\iTimer}\enspace about \textbf{#1}\hfill{\color{partcol}\iTarget}\enspace #2 + \end{tcolorbox}} + +% Checklists. +\newlist{checklist}{itemize}{1} +\setlist[checklist]{label={\color{partcol}\large\iBox}, leftmargin=1.9em, itemsep=2pt, topsep=4pt} +\setlist[itemize]{label={\color{partcol}\small\textbullet}, leftmargin=1.4em, itemsep=1pt, topsep=3pt} +\setlist[enumerate]{label={\heavy\color{partcol}\arabic*}, leftmargin=1.6em, itemsep=2pt, topsep=4pt} +\setlist[description]{font=\heavy\color{ink}, leftmargin=1.2em, itemsep=2pt} + +% --- Screenshots -------------------------------------------------------------------------- +% \shot[width]{file}{caption} +\newcommand\shot[3][\linewidth]{% + \par\needspace{0.3\textheight}\begin{center} + \begin{tikzpicture} + \node[inner sep=0pt, rounded corners=6pt, blur shadow={shadow blur steps=8, shadow xshift=0pt, + shadow yshift=-2pt, shadow opacity=25}] (img) {\includegraphics[width=#1]{img/#2}}; + \draw[rule!60!slate, line width=0.4pt, rounded corners=3pt] (img.south west) rectangle (img.north east); + \end{tikzpicture}\par\vspace{4pt} + {\footnotesize\color{muted}#3}\end{center}\vspace{-4pt}} + +% Two screenshots side by side. +\newcommand\shotpair[4]{% + \par\needspace{0.22\textheight}\begin{center} + \begin{minipage}[t]{0.485\linewidth}\centering\includegraphics[width=\linewidth]{img/#1}\par\vspace{3pt}{\footnotesize\color{muted}#2}\end{minipage}\hfill + \begin{minipage}[t]{0.485\linewidth}\centering\includegraphics[width=\linewidth]{img/#3}\par\vspace{3pt}{\footnotesize\color{muted}#4}\end{minipage} + \end{center}} + +% A crop of a screenshot: \crop{file}{left}{bottom}{right}{top}{width}{caption}, trims in px of the 2720x1720 image. +\newcommand\crop[7]{% + \par\needspace{0.2\textheight}\begin{center} + \begin{tikzpicture} + \node[inner sep=0pt, blur shadow={shadow blur steps=8, shadow yshift=-2pt, shadow opacity=25}] (img) + {\includegraphics[width=#6, trim=#2 #3 #4 #5, clip]{img/#1}}; + \draw[rule!60!slate, line width=0.4pt] (img.south west) rectangle (img.north east); + \end{tikzpicture}\par\vspace{4pt}{\footnotesize\color{muted}#7}\end{center}\vspace{-4pt}} + +% Annotated screenshot: \begin{annotated}{file} \callout{1}{x}{y} … \end{annotated}, x/y in 0–1 from top left. +\newenvironment{annotated}[1]{% + \par\begin{center}\begin{tikzpicture} + \node[anchor=north west, inner sep=0pt, blur shadow={shadow blur steps=8, shadow yshift=-2pt, shadow opacity=25}] (img) + {\includegraphics[width=\linewidth]{img/#1}}; + \draw[rule!60!slate, line width=0.4pt] (img.south west) rectangle (img.north east); + \begin{scope}[x={(img.north east)}, y={($(img.north west)-(img.south west)$)}, shift={(img.north west)}]} + {\end{scope}\end{tikzpicture}\end{center}} +\newcommand\callout[3]{% + \node[circle, fill=partcol, text=white, font=\heavy\footnotesize, inner sep=0pt, minimum size=15pt, + draw=white, line width=1.4pt, blur shadow={shadow blur steps=5, shadow opacity=40}] at (#2,-#3) {#1};} +\newcommand\num[1]{\tikz[baseline=(n.base)]\node[circle, fill=partcol, text=white, font=\heavy\scriptsize, + inner sep=0pt, minimum size=13pt] (n) {#1};} + +% "At a glance" fact card for each connection type. +\newenvironment{glance}[2]{% + \begin{tcolorbox}[enhanced, arc=8pt, boxrule=0pt, colback=#1!7, left=12pt, right=12pt, top=9pt, bottom=9pt, + before skip=6pt, after skip=12pt, fontupper=\small, + overlay={\node[anchor=north east, font=\heavy\tiny, text=#1!80!black, inner sep=8pt] at (frame.north east) {AT A GLANCE};}] + {\heavy\Large\color{#1!85!black}#2}\par\vspace{4pt} + \renewcommand\arraystretch{1.35}\begin{tabular}{@{}>{\leavevmode\color{muted}}p{0.24\linewidth}p{0.72\linewidth}@{}}} + {\end{tabular}\end{tcolorbox}} + +% --- Headings -------------------------------------------------------------------------- +\titleformat{\chapter}[display]{\normalfont}{% + \pagecanvas{\fill[partcol] (pg.north west) rectangle ([yshift=-9mm]pg.north east); + \fill[partcol!12] ([yshift=-9mm]pg.north west) rectangle ([yshift=-11mm]pg.north east);}% + {\heavy\small\color{partcol}\MakeUppercase{\partshort}\enspace·\enspace CHAPTER \thechapter}}{2pt} + {\heavy\fontsize{30}{34}\selectfont\color{ink}} +\titlespacing*{\chapter}{0pt}{0pt}{14pt} +\titleformat{\section}{\heavy\Large\color{ink}}{% + \tikz[baseline=(n.base)]\node[fill=partcol, rounded corners=3pt, text=white, font=\heavy\small, inner sep=3pt, minimum width=18pt] (n) {\thesection};}{8pt}{} +\titlespacing*{\section}{0pt}{18pt}{4pt} +\titleformat{\subsection}{\heavy\large\color{ink}}{}{0pt}{{\color{partcol}\iArrow}\enspace} +\titlespacing*{\subsection}{0pt}{12pt}{2pt} +\renewcommand\thesection{\arabic{chapter}.\arabic{section}} +\setcounter{secnumdepth}{1} +\setcounter{tocdepth}{1} + +% --- Running heads + thumb tabs --------------------------------------------------------- +\pagestyle{fancy} +\fancyhf{} +\renewcommand{\headrulewidth}{0pt} +\renewcommand\chaptermark[1]{\markboth{#1}{}} +\fancyhead[LE]{\footnotesize\color{muted}\heavy Nexus Terminal\enspace\normalfont·\enspace User Guide} +\fancyhead[RO]{\footnotesize\color{muted}\leftmark} +\fancyfoot[LE,RO]{\tikz[baseline=(n.base)]\node[fill=partcol, rounded corners=3pt, text=white, + font=\heavy\footnotesize, inner sep=3pt, minimum width=20pt] (n) {\thepage};} +\fancyfoot[RE,LO]{\footnotesize\color{muted}\partshort} +\fancypagestyle{plain}{\fancyhf{}\fancyfoot[LE,RO]{\tikz[baseline=(n.base)]\node[fill=partcol, rounded corners=3pt, + text=white, font=\heavy\footnotesize, inner sep=3pt, minimum width=20pt] (n) {\thepage};}} + +% Thumb tabs on the page edge, one slot per part, so you can flick to a part in print. +\newif\ifthumbs +\AddToShipoutPictureBG{% + \ifthumbs\ifnum\value{guidepart}>0 + \AtPageLowerLeft{\begin{tikzpicture}[overlay]\pgnode + \pgfmathsetmacro\slot{28 + (\value{guidepart}-1)*30} + \ifodd\value{page} + \fill[partcol, rounded corners=4pt] ([xshift=-7mm, yshift=-\slot mm]pg.north east) rectangle ++(9mm,-26mm); + \node[rotate=-90, text=white, font=\heavy\tiny] at ([xshift=-3.6mm, yshift=-\slot mm - 13mm]pg.north east) {\MakeUppercase{\partshort}}; + \else + \fill[partcol, rounded corners=4pt] ([xshift=7mm, yshift=-\slot mm]pg.north west) rectangle ++(-9mm,-26mm); + \node[rotate=90, text=white, font=\heavy\tiny] at ([xshift=3.6mm, yshift=-\slot mm - 13mm]pg.north west) {\MakeUppercase{\partshort}}; + \fi + \end{tikzpicture}}\fi\fi} + +% \guidepart{colour}{short name}{Title}{tagline}{list of chapters} +\newcommand\guidepart[5]{% + \clearpage + \stepcounter{guidepart}\colorlet{partcol}{#1}\renewcommand\partshort{#2} + \phantomsection\addcontentsline{toc}{part}{\protect\textcolor{#1}{\arabic{guidepart}}\quad #3} + \thispagestyle{empty}\thumbsfalse + \pagecanvas{ + \fill[ink] (pg.south west) rectangle (pg.north east); + \fill[#1, opacity=0.9] (pg.north west) rectangle ([yshift=-95mm]pg.north east); + \fill[#1!70!black, opacity=0.35] ([yshift=-95mm]pg.north west) -- ++(0,-18mm) -- ([yshift=-95mm]pg.north east) -- cycle; + \node[anchor=north west, text=white, font=\heavy\fontsize{150}{150}\selectfont, opacity=0.25] + at ([xshift=14mm, yshift=-4mm]pg.north west) {\arabic{guidepart}}; + \node[anchor=south west, text=white, font=\heavy\small] at ([xshift=24mm, yshift=-72mm]pg.north west) {PART \arabic{guidepart}}; + \node[anchor=north west, text=white, font=\heavy\fontsize{38}{42}\selectfont, text width=160mm] + at ([xshift=24mm, yshift=-73mm]pg.north west) {#3}; + \node[anchor=north west, text=white!80!#1, font=\large, text width=150mm] + at ([xshift=24mm, yshift=-125mm]pg.north west) {#4}; + \node[anchor=north west, text=white, font=\normalsize, text width=150mm] + at ([xshift=24mm, yshift=-145mm]pg.north west) {#5}; + }\null\clearpage\thumbstrue} +\newcommand\partitem[2]{{\color{partcol}\large#1}\enspace #2\par\vspace{5pt}} + +% Table of contents. +\RequirePackage{tocloft} +\renewcommand\cftpartfont{\heavy\large\color{ink}} +\renewcommand\cftpartpagefont{\heavy\color{ink}} +\renewcommand\cftchapfont{\color{body}} +\renewcommand\cftchappagefont{\color{muted}} +\renewcommand\cftchapleader{\cftdotfill{\cftdotsep}} +\setlength\cftbeforepartskip{14pt} +\setlength\cftbeforechapskip{3pt} +\renewcommand\cfttoctitlefont{\heavy\fontsize{30}{34}\selectfont\color{ink}} + +% Symptom → fix cards for troubleshooting. +\newcommand\fix[2]{% + \begin{tcolorbox}[enhanced, breakable, arc=7pt, boxrule=0.5pt, colframe=rule, colback=white, left=11pt, right=11pt, + top=8pt, bottom=8pt, before skip=6pt, after skip=8pt, fontupper=\small, parbox=false, + title={\heavy\color{ink}\normalsize{\color{partcol}\iBug}\enspace #1}, coltitle=ink, colbacktitle=paper, + titlerule=0pt, toptitle=5pt, bottomtitle=5pt, attach title to upper={}, detach title, before upper={\tcbtitle\par\vspace{4pt}}] + #2\end{tcolorbox}} + +% Big pull-quote line used to break up long stretches. +\newcommand\breather[1]{\par\vspace{6pt}{\centering\heavy\large\color{partcol}#1\par}\vspace{6pt}} diff --git a/docs/guide/nexus-guide.tex b/docs/guide/nexus-guide.tex new file mode 100644 index 0000000..2d37763 --- /dev/null +++ b/docs/guide/nexus-guide.tex @@ -0,0 +1,100 @@ +% Nexus Terminal — User Guide. Build: make (in docs/guide), or: lualatex nexus-guide && lualatex nexus-guide +\documentclass[10pt,twoside,openany]{book} +\usepackage{nexus-guide} + +\hypersetup{pdftitle={Nexus Terminal: The User Guide}, pdfauthor={Andy Dixon}, + pdfsubject={SSH, Telnet, RAW, Serial, VNC, IRC and GitHub in one native app}} + +\begin{document} +\frontmatter +\pagestyle{empty} + +% --- Cover ------------------------------------------------------------------------------ +\pagecanvas{ + \fill[ink] (pg.south west) rectangle (pg.north east); + \shade[left color=accent, right color=violet, opacity=0.55] + ([yshift=-60mm]pg.north west) -- ([yshift=-20mm]pg.north east) -- + ([yshift=-130mm]pg.north east) -- ([yshift=-170mm]pg.north west) -- cycle; + \shade[left color=teal, right color=accent, opacity=0.35] + ([yshift=-150mm]pg.north west) -- ([yshift=-120mm]pg.north east) -- + ([yshift=-150mm]pg.north east) -- ([yshift=-185mm]pg.north west) -- cycle; + \node[anchor=north east, inner sep=0pt] at ([xshift=-22mm, yshift=-24mm]pg.north east) + {\includegraphics[width=34mm]{img/logo.pdf}}; + \node[anchor=north west, text=white!70!ink, font=\heavy\small] at ([xshift=22mm, yshift=-30mm]pg.north west) + {THE USER GUIDE}; + \node[anchor=north west, text=white, font=\heavy\fontsize{58}{60}\selectfont, text width=170mm] + at ([xshift=22mm, yshift=-36mm]pg.north west) {Nexus\\[-2pt]Terminal}; + \node[anchor=north west, text=white, font=\Large, text width=150mm] + at ([xshift=22mm, yshift=-88mm]pg.north west) + {Every terminal you need. One window.\\Zero faff.}; + \node[anchor=north west, text=white!65!ink, font=\normalsize, text width=150mm] + at ([xshift=22mm, yshift=-110mm]pg.north west) + {SSH\enspace·\enspace Telnet\enspace·\enspace RAW TCP\enspace·\enspace Serial\enspace·\enspace VNC\enspace·\enspace IRC\enspace·\enspace GitHub\enspace·\enspace Local shells}; + \node[anchor=south west, inner sep=0pt] at ([xshift=22mm, yshift=22mm]pg.south west) + {\begin{tikzpicture} + \node[inner sep=0pt, draw=white!25!ink, line width=0.6pt, rounded corners=4pt] {\includegraphics[width=166mm]{img/ssh.png}}; + \end{tikzpicture}}; + \node[anchor=south east, text=white!55!ink, font=\footnotesize, align=right] + at ([xshift=-22mm, yshift=24mm]pg.south east) {Version 0.1\\October 2026\\dixon.cx}; +}\null +\cleardoublepage + +% --- How to use this guide ---------------------------------------------------------------- +\colorlet{partcol}{accent} +{\heavy\fontsize{30}{34}\selectfont\color{ink} Start here.\par} +\vspace{2pt}{\large\color{muted} (Yes, even if you never read manuals. \emph{Especially} then.)\par} +\vspace{10pt} + +This guide is built for skimming. You do \textbf{not} have to read it in order, cover to cover, like it's +homework. Find the bit you need, grab it, get back to work. Every chapter opens with the time it takes and +what you'll be able to do afterwards, and a \textbf{TL;DR} if you only want the gist. + +\vspace{4pt} +{\heavy\large\color{ink}\iMap\enspace I just want to…}\par\vspace{2pt} +\renewcommand\arraystretch{1.5} +\begin{tabularx}{\linewidth}{@{}X>{\raggedleft\arraybackslash}p{52mm}@{}} + \toprule + …get connected to something \emph{right now} & \hyperref[ch:start]{Five-minute start}, p.\,\pageref{ch:start}\\ + …work out what all the buttons do & \hyperref[ch:tour]{The grand tour}, p.\,\pageref{ch:tour}\\ + …SSH into a server (keys, jump hosts, 2FA) & \hyperref[ch:ssh]{SSH}, p.\,\pageref{ch:ssh}\\ + …talk to a router or Arduino over a USB cable & \hyperref[ch:serial]{Serial}, p.\,\pageref{ch:serial}\\ + …copy files to and from a server & \hyperref[ch:files]{Files}, p.\,\pageref{ch:files}\\ + …type the same thing into ten servers at once & \hyperref[ch:repeat]{Never type it twice}, p.\,\pageref{ch:repeat}\\ + …keep my passwords safe & \hyperref[ch:secrets]{Passwords \& the vault}, p.\,\pageref{ch:secrets}\\ + …move over from Alchementary Link & \hyperref[ch:move]{Moving house}, p.\,\pageref{ch:move}\\ + …fix something that's broken & \hyperref[part:fix]{When things go wrong}, p.\,\pageref{ch:trouble}\\ + …just see the keyboard shortcuts & \hyperref[app:keys]{The cheat sheet}, p.\,\pageref{app:keys}\\ + \bottomrule +\end{tabularx} + +\vspace{8pt} +{\heavy\large\color{ink}\iSparkle\enspace The little boxes}\par\vspace{-2pt} +\begin{multicols}{2}\small +\begin{tldr}The whole chapter in three lines.\end{tldr} +\begin{quickwin}Something you can try in under a minute.\end{quickwin} +\begin{protip}A trick that saves you time later.\end{protip} +\columnbreak +\begin{headsup}A thing that might trip you up.\end{headsup} +\begin{danger}Could lose data or weaken security. Read it.\end{danger} +\begin{sidequest}Interesting, but not needed to get the job done.\end{sidequest} +\end{multicols} + +\vspace{-6pt} +{\small\color{muted}\textbf{Shortcuts:} \mod{} means \kbd{Ctrl} on Windows and Linux, \kbd{\ph{E1C4}} (Command) on a Mac. So +\keys{\mod+K} is \kbd{Ctrl}+\kbd{K} or \kbd{\ph{E1C4}}+\kbd{K} depending on your machine.} + +\clearpage +\pagestyle{plain} +{\hypersetup{linkcolor=body}\tableofcontents} +\clearpage + +\mainmatter +\pagestyle{fancy} +\input{chapters/part1-start} +\input{chapters/part2-connect} +\input{chapters/part3-faster} +\input{chapters/part4-safe} +\input{chapters/part5-yours} +\input{chapters/part6-fix} +\input{chapters/appendix} +\end{document} diff --git a/docs/guide/tools/demo-shell.sh b/docs/guide/tools/demo-shell.sh new file mode 100755 index 0000000..7767b23 --- /dev/null +++ b/docs/guide/tools/demo-shell.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# ForceCommand for the throwaway screenshot sshd: never a login shell (no ~/.profile, no byobu). +DEMO_HOME=$1 SFTP_SERVER=$2 +export LC_ALL=C.UTF-8 +case "$SSH_ORIGINAL_COMMAND" in + *sftp*) exec "$SFTP_SERVER" -d "$DEMO_HOME" ;; + ?*) cd "$DEMO_HOME" && HOME="$DEMO_HOME" exec bash --norc --noprofile -c "$SSH_ORIGINAL_COMMAND" ;; +esac +cd "$DEMO_HOME" || exit 1 +export HOME="$DEMO_HOME" PS1='\[\e[1;32m\]andy@atlas\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]\$ ' +row() { # boxed line, padded on visible width + local plain; plain=$(printf '%b' "$1" | sed 's/\x1b\[[0-9;]*m//g') + printf '\e[1;36m│\e[0m %b%*s\e[1;36m│\e[0m\n' "$1" $((44 - ${#plain})) '' +} +printf '\e[1;36m╭──────────────────────────────────────────────╮\e[0m\n' +row 'Welcome to \e[1matlas\e[0m · Debian 13 · kernel 6.12' +row 'Load \e[32m0.42\e[0m Memory \e[33m61%\e[0m Disk \e[32m38%\e[0m' +row 'Last deploy: \e[1m2 hours ago\e[0m by andy' +printf '\e[1;36m╰──────────────────────────────────────────────╯\e[0m\n\n' +printf '\e[1;32m$\e[0m ls -l --color\n'; ls -l --color=always +printf '\n\e[1;32m$\e[0m ./deploy.sh --dry-run\n' +for s in "Resolving build" "Uploading artefacts" "Rolling out to 4 nodes" "Health checks"; do + printf ' \e[32m✔\e[0m %-26s \e[2m%s\e[0m\n' "$s" "$((RANDOM % 900 + 100))ms" +done +printf ' \e[33m⚠\e[0m 1 warning: \e[1mnginx.conf\e[0m has a deprecated directive\n' +printf ' \e[1;32mDone.\e[0m Nothing was actually deployed (dry run).\n\n' +exec bash --norc --noprofile -i diff --git a/docs/guide/tools/fakes.py b/docs/guide/tools/fakes.py new file mode 100644 index 0000000..5c0ecd2 --- /dev/null +++ b/docs/guide/tools/fakes.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +"""Tiny fake servers for the user-guide screenshots: RAW sensor feed (42023), IRC (42667), +GitHub API (42080) and a VNC desktop (42900). Stdlib only. Runs until killed.""" + +import json, math, random, socket, socketserver, struct, threading, time +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + + +class Threaded(socketserver.ThreadingMixIn, socketserver.TCPServer): + allow_reuse_address = True + daemon_threads = True + + +# --- RAW: a chatty sensor -------------------------------------------------------------- + +class Raw(socketserver.BaseRequestHandler): + def handle(self): + s = self.request + s.sendall(b"SENSOR-7 ready. Commands: STATUS, RESET\n") + t = 20.5 + s.settimeout(0.25) + for i in range(400): + t += random.uniform(-0.3, 0.3) + s.sendall(f"temp={t:.2f}C hum={48 + 4 * math.sin(i / 3):.1f}% seq={i}\n".encode()) + try: + if (cmd := s.recv(256)): + s.sendall(b"OK " + cmd.strip() + b"\n") + except socket.timeout: + pass + + +# --- IRC: a small, friendly network ---------------------------------------------------- + +CHAT = [ + ("ferris", "anyone tried the new Nexus build yet?"), + ("crabby", "yep, the split panes are lovely"), + ("ferris", "the serial console support saved my afternoon tbh"), + ("lin", "does it do jump hosts?"), + ("crabby", "lin: multiple hops, plus SOCKS5/HTTP proxies"), + ("ferris", "and it's Rust all the way down now :)"), +] + + +class Irc(socketserver.StreamRequestHandler): + def send(self, line): + self.wfile.write((line + "\r\n").encode()) + + def handle(self): + nick = "andy" + for raw in self.rfile: + line = raw.decode(errors="replace").strip() + cmd, _, rest = line.partition(" ") + if cmd == "NICK": + nick = rest.strip() + elif cmd == "USER": + self.send(f":irc.demo 001 {nick} :Welcome to the Demo IRC Network, {nick}") + self.send(f":irc.demo 375 {nick} :- irc.demo Message of the Day -") + self.send(f":irc.demo 372 {nick} :- Be excellent to each other.") + self.send(f":irc.demo 376 {nick} :End of /MOTD command.") + elif cmd == "JOIN": + for room in rest.split()[0].split(","): + self.send(f":{nick}!u@demo JOIN {room}") + self.send(f":irc.demo 332 {nick} {room} :Nexus Terminal chat · be kind · no pasting walls of logs") + self.send(f":irc.demo 353 {nick} = {room} :@ferris +crabby lin {nick} octo rustacean") + self.send(f":irc.demo 366 {nick} {room} :End of /NAMES list.") + if room == "#nexus": + for who, msg in CHAT: + self.send(f":{who}!u@demo PRIVMSG #nexus :{msg}") + else: + self.send(f":octo!u@demo PRIVMSG {room} :borrow checker 1, me 0") + elif cmd == "LIST": + for room, n, topic in [("#nexus", 42, "Nexus Terminal"), ("#rust", 3110, "Rust"), ("#linux", 905, "Linux"), ("#homelab", 77, "Racks & routers")]: + self.send(f":irc.demo 322 {nick} {room} {n} :{topic}") + self.send(f":irc.demo 323 {nick} :End of /LIST") + elif cmd == "PING": + self.send(f":irc.demo PONG irc.demo {rest}") + elif cmd == "QUIT": + return + + +# --- GitHub API -------------------------------------------------------------------------- + +REPOS = [ + ("andydixon", "nexus-terminal", "SSH, Telnet, Serial, IRC and VNC in one tidy native app", "Rust", 128, False, False, False, 1), + ("andydixon", "leviathan", "The egui design system Nexus borrows its looks from", "Rust", 64, False, False, False, 3), + ("andydixon", "dotfiles", "Shell, editor and terminal config", "Shell", 12, False, False, False, 6), + ("andydixon", "homelab-ansible", "Playbooks for the rack under the stairs", "Jinja", 7, True, False, False, 9), + ("andydixon", "alchementary-link", "The Wails/Go predecessor to Nexus Terminal", "Go", 31, False, False, True, 40), + ("alchementary", "website", "Marketing site", "TypeScript", 3, True, False, False, 2), + ("alchementary", "infra", "Terraform for everything", "HCL", 2, True, False, False, 5), + ("alchementary", "russh", "Fork with a jump-host fix", "Rust", 0, False, True, False, 15), +] +WHO = ["Andy Dixon", "ferris", "crabby", "dependabot[bot]"] + + +def iso(days): + return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(time.time() - days * 86400 - 3600 * 5)) + + +class Github(BaseHTTPRequestHandler): + def log_message(self, *a): + pass + + def reply(self, obj): + body = json.dumps(obj).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def do_GET(self): + if self.path == "/user": + return self.reply({"login": "andydixon"}) + if self.path.startswith("/user/repos"): + page = 1 if "page=1" in self.path else 2 + if page != 1: + return self.reply([]) + return self.reply([ + {"full_name": f"{o}/{n}", "name": n, "description": d, "html_url": f"https://github.com/{o}/{n}", + "default_branch": "main", "private": p, "fork": f, "archived": a, "language": l, + "stargazers_count": s, "pushed_at": iso(age), "updated_at": iso(age), "owner": {"login": o}} + for o, n, d, l, s, p, f, a, age in REPOS]) + if "/commits/" in self.path: + i = sum(map(ord, self.path)) % len(WHO) + age = next((r[8] for r in REPOS if f"/{r[1]}/" in self.path), 1) + return self.reply({"commit": {"committer": {"name": WHO[i], "date": iso(age)}, "author": {"name": WHO[i], "date": iso(age)}}}) + self.send_error(404) + + +# --- VNC: a static "desktop" --------------------------------------------------------------- + +W, H = 1280, 800 + + +def desktop(): + px = bytearray(W * H * 4) + wins = [(90, 90, 620, 420, (40, 44, 52)), (560, 260, 1180, 700, (250, 250, 252)), (140, 560, 460, 740, (32, 140, 120))] + for y in range(H): + for x in range(W): + # Diagonal violet → teal wallpaper. + k = (x + y) / (W + H) + r, g, b = int(88 * (1 - k) + 20 * k), int(40 * (1 - k) + 150 * k), int(160 * (1 - k) + 170 * k) + for x0, y0, x1, y1, c in wins: + if x0 <= x < x1 and y0 <= y < y1: + r, g, b = (200, 70, 90) if y < y0 + 28 else c + if y >= y0 + 28 and c == (40, 44, 52) and (y - y0) % 22 < 9 and x0 + 20 < x < x0 + 20 + ((y * 37) % 400): + r, g, b = (120, 220, 140) if (y // 22) % 3 else (230, 200, 120) + if y >= H - 40: + r, g, b = 24, 24, 30 + i = (y * W + x) * 4 + px[i:i + 4] = bytes((r, g, b, 0)) + return bytes(px) + + +PIXELS = None + + +class Vnc(socketserver.BaseRequestHandler): + def recv(self, n): + b = b"" + while len(b) < n: + c = self.request.recv(n - len(b)) + if not c: + raise EOFError + b += c + return b + + def handle(self): + s = self.request + s.sendall(b"RFB 003.008\n") + self.recv(12) + s.sendall(bytes([1, 1])) # one security type: None + self.recv(1) + s.sendall(struct.pack(">I", 0)) + self.recv(1) # ClientInit + name = b"studio:0 (demo)" + s.sendall(struct.pack(">HH", W, H) + bytes([32, 24, 0, 1, 0, 255, 0, 255, 0, 255, 0, 8, 16, 0, 0, 0]) + struct.pack(">I", len(name)) + name) + sent = False + try: + while True: + t = self.recv(1)[0] + if t == 0: + self.recv(19) + elif t == 2: + n = struct.unpack(">xH", self.recv(3))[0] + self.recv(4 * n) + elif t == 3: + self.recv(9) + if not sent: + s.sendall(struct.pack(">BxH", 0, 1) + struct.pack(">HHHHi", 0, 0, W, H, 0) + PIXELS) + sent = True + else: + time.sleep(0.5) + s.sendall(struct.pack(">BxH", 0, 0)) + elif t == 4: + self.recv(7) + elif t == 5: + self.recv(5) + elif t == 6: + n = struct.unpack(">3xI", self.recv(7))[0] + self.recv(n) + else: + return + except (EOFError, ConnectionError): + pass + + +if __name__ == "__main__": + PIXELS = desktop() + for port, handler in [(42023, Raw), (42667, Irc), (42900, Vnc)]: + threading.Thread(target=Threaded(("127.0.0.1", port), handler).serve_forever, daemon=True).start() + print("fakes ready", flush=True) + ThreadingHTTPServer(("127.0.0.1", 42080), Github).serve_forever() diff --git a/docs/guide/tools/screenshots.sh b/docs/guide/tools/screenshots.sh new file mode 100755 index 0000000..75f0a1c --- /dev/null +++ b/docs/guide/tools/screenshots.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# Regenerates docs/guide/img/*.png: a throwaway sshd (ForceCommand, never a login shell), +# fake RAW/IRC/GitHub/VNC servers, a demo data directory, then one headless app run per shot. +# Needs: xvfb-run, sshd, ssh-keygen, python3, ImageMagick. Usage: docs/guide/tools/screenshots.sh [shot...] +set -euo pipefail +cd "$(dirname "$0")/.." +GUIDE=$PWD +B=${NEXUS_GUIDE_TMP:-/tmp/nexus-guide} +IMG=$GUIDE/img +APP=$GUIDE/../../rust/target/debug/nexus-terminal +SCALE=${SCALE:-2} +mkdir -p "$IMG" +rm -rf "$B" && mkdir -p "$B/home/.ssh" "$B/sshd" + +(cd ../../rust && cargo build -q -p nexus && cargo build -q -p nexus-core --example demo_data) + +# Demo home: what the SFTP browser, the shell and the SSH key manager see. +H=$B/home +mkdir -p "$H"/{projects/nexus,backups,logs,www/static} +printf 'server {\n listen 443 ssl;\n}\n' > "$H/nginx.conf" +echo '#!/bin/sh' > "$H/deploy.sh"; chmod +x "$H/deploy.sh" +head -c 180000 /dev/urandom > "$H/backups/db-2026-10-10.sql.gz" +head -c 52000 /dev/urandom > "$H/logs/access.log" +echo "# Notes" > "$H/README.md" +ssh-keygen -q -t ed25519 -N '' -C 'andy@laptop' -f "$H/.ssh/id_ed25519" +ssh-keygen -q -t rsa -b 2048 -N '' -C 'andy@work' -f "$H/.ssh/work_rsa" +ssh-keygen -q -t ecdsa -N '' -C 'deploy-bot' -f "$H/.ssh/deploy_ecdsa" + +# sshd on 42022 (ForceCommand keeps it away from ~/.profile). +S=$B/sshd +ssh-keygen -q -t ed25519 -N '' -f "$S/host" +cp "$H/.ssh/id_ed25519.pub" "$S/authorized_keys" +SFTP=$(ls /usr/lib/openssh/sftp-server /usr/libexec/openssh/sftp-server /usr/libexec/sftp-server 2>/dev/null | head -1 || true) +cat > "$S/config" < "$B/fakes.log" 2>&1 & +FAKES=$! +trap 'kill $FAKES 2>/dev/null; kill "$(cat "$S/pid" 2>/dev/null)" 2>/dev/null || true' EXIT +until grep -q ready "$B/fakes.log"; do sleep 0.2; done + +"$(dirname "$APP")/examples/demo_data" "$B/data" "$H/.ssh/id_ed25519" > /dev/null +cp -r "$B/data" "$B/data.clean" + +shot() { # name, then app args + local name=$1; shift + if [ ${#ONLY[@]} -gt 0 ] && [[ ! " ${ONLY[*]} " =~ " $name " ]]; then return 0; fi + rm -rf "$B/data" && cp -r "$B/data.clean" "$B/data" + HOME=$H WINIT_X11_SCALE_FACTOR=$SCALE timeout 60 xvfb-run -a -s "-screen 0 3200x2000x24" \ + "$APP" --data-dir "$B/data" --size 1360x860 --screenshot "$IMG/$name.png" "$@" 2>> "$B/app.log" || echo " ! $name failed" + # 256-colour PNGs are a quarter of the size and look identical for UI shots. + [ -f "$IMG/$name.png" ] && magick "$IMG/$name.png" -colors 256 "PNG8:$IMG/$name.png" + echo " $name" +} +ONLY=("$@") + +shot welcome +shot welcome-midnight --theme midnight +shot welcome-light --theme light +shot ssh --connect "Sensor feed" --connect "Libera Chat" --connect atlas --shot-delay 5500 +shot ssh-light --theme light --connect atlas --shot-delay 5500 +shot hostkey --connect hello-ssh --shot-delay 5000 +shot split --connect atlas --do "Split Pane Right" --shot-delay 5500 +shot sftp --connect atlas --do "SFTP Browser" --shot-delay 6000 +shot broadcast --connect build-runner --connect atlas --do "Toggle Broadcast Mode" --shot-delay 5500 +shot raw --connect "Sensor feed" --shot-delay 5500 +shot irc --connect "Libera Chat" --shot-delay 5500 +shot github --connect GitHub --answer ghp_demo --shot-delay 6000 +shot vnc --connect "Studio desktop" --answer demo --shot-delay 6000 +shot local --run-local 'printf "\e[1;35mhello from the local shell\e[0m\n"; ls -la --color=always' --shot-delay 5000 +shot palette --open palette +shot quick --open quick +shot editor-ssh --open editor:ssh +shot editor-new --open editor:new +shot editor-serial --open editor:serial +shot editor-telnet --open editor:telnet +shot editor-raw --open editor:raw +shot editor-vnc --open editor:vnc +shot editor-irc --open editor:irc +shot editor-github --open editor:github +for t in general terminal security knownhosts help about; do shot "settings-$t" --open "settings:$t"; done +shot keys --open keys +shot snippets --open snippets +shot triggers --open triggers +shot recordings --open recordings +shot export --open export