docs: illustrated LaTeX user guide (docs/guide) with generated screenshots
@@ -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)
|
||||
@@ -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}
|
||||
@@ -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-<os>-<arch>.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.
|
||||
@@ -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.}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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.
|
||||
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 76 KiB |
@@ -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}}
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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" <<CFG
|
||||
Port 42022
|
||||
ListenAddress 127.0.0.1
|
||||
HostKey $S/host
|
||||
AuthorizedKeysFile $S/authorized_keys
|
||||
PidFile $S/pid
|
||||
StrictModes no
|
||||
UsePAM no
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
PrintMotd no
|
||||
PrintLastLog no
|
||||
Subsystem sftp $SFTP
|
||||
ForceCommand $GUIDE/tools/demo-shell.sh $H $SFTP
|
||||
CFG
|
||||
"$(command -v sshd)" -f "$S/config" -E "$S/log"
|
||||
python3 -I "$GUIDE/tools/fakes.py" > "$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
|
||||