vipirc 0.7.0 / 1keyirc 0.1.0 - build and installation

REQUIREMENTS

The target is Unix-like systems, including Linux in the ChromeOS Linux
environment, OpenBSD, FreeBSD, and other BSDs with the required interfaces.

    C11 compiler and standard C library
    POSIX.1 interfaces, including poll, sockets, fork, and descriptor I/O
    Descriptor-relative file operations and no-follow opens (openat,
        fstatat, mkdirat, O_NOFOLLOW), plus a monotonic clock
    GNU/BSD getopt_long option parsing (provided by common Linux/BSD libc)
    Wide-character curses headers and library for vipirc (normally ncursesw)
    OpenSSL-compatible TLS development headers and libraries
    A configured certificate authority trust store for public TLS servers
    A make implementation and a POSIX shell

The installed client has no Python dependency. Python 3 is needed for the
integration tests. Groff or mandoc is useful for viewing the manual from
the source tree. The normal build does not require either documentation
formatter.

Multiple IRC sessions and DCC SEND use the same C/POSIX runtime and add no
libraries or language runtimes. DCC uses ordinary TCP sockets and file
descriptors; it does not need an external transfer program.

GPG-encrypted password files are optional. They need a gpg executable from
GnuPG 2.2.20 or newer in PATH, with an already running agent able to decrypt
the selected file without prompting. The client has no GPG library link
dependency. Ordinary IRC, explicit inline passwords, and private plaintext
password files work without GnuPG. The fixed helper options are not weakened
for older versions; an unsupported gpg fails authentication.

The implementation uses OpenSSL 1.1.1-or-newer-compatible APIs, including
certificate-hostname verification and a minimum TLS protocol setting.
LibreSSL can be used when its headers and libraries supply those APIs.
Using a supported TLS library from your operating system is recommended.

BUILD

From the extracted source directory:

    ./configure
    make

The executables are ./vipirc and ./1keyirc. For the standalone minimalist
client alone, use:

    ./configure --without-curses
    make
    ./1keyirc -h
    make check

This configuration does not probe for or link curses. Its install target
installs only 1keyirc, its man page, native example configuration, and
documentation. The normal build installs both clients. vipirc -i uses the
minimalist interface with the vipirc configuration; 1keyirc uses its own
configuration location. See doc/1keyirc-manual.txt and man 1keyirc for the
minimalist controls, commands, and topic-based help. DCC is available in
the full vipirc interface.

Regenerate the Makefile with ./configure on each
target; a Makefile configured elsewhere can contain host-specific include
paths and library names. The configure script checks the compiler and
required libraries. It does not download software or inspect another IRC
client's source. Compiler and link options can be supplied through the
usual build variables. For example, a system with optional libraries under
/usr/local may need:

    CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib ./configure
    make

If library autodetection needs an explicit choice:

    CURSES_LIBS=-lncursesw TLS_LIBS='-lssl -lcrypto' ./configure
    make

Use the development package that supplies each header/library pair, not
only a runtime library package. On a Debian-family Linux installation,
these typically include build-essential, libncurses-dev, and libssl-dev;
ca-certificates supplies the standard public certificate trust material.
ChromeOS uses its Linux development environment for this build. The client
is a Unix executable, not an Android APK or browser extension.

One instance supports up to eight IRC server sessions in one terminal.
Select Servers in the global header or use /servers to add, open, connect, disconnect,
or close a session. Each keeps separate connection/authentication settings,
channels, queries, drafts, filters, and history. A graphical desktop is not
required. Separate processes are still possible when explicitly wanted;
they are no longer required to connect to several servers.

BSD base systems often provide some of the compiler, curses, and TLS
components. Check the detected headers and link results before installing
additional packages. Avoid mixing headers from one TLS implementation
with another implementation's libraries.

Native portability must be validated on the target OS. A successful Linux
build does not prove that a particular BSD version has been tested. Linux is
the tested release platform; native OpenBSD and FreeBSD validation remains
to be done. Compiling or smoke-testing a curses fallback branch on Linux
does not count as a native BSD test. See the release validation report for
actual platforms, commands, and toolchain versions.

Resolver children must be able to close all inherited descriptors before
DNS lookup. The implementation uses Linux close_range or BSD closefrom,
with a fallback through a complete /proc/self/fd or /dev/fd listing and a
one-second deadline per directory scan.
If none is available, connection setup reports an isolation error instead
of doing lookup while retaining other sessions' sockets or transfer files.
The configured build does not by itself prove this runtime facility works
on an untested target.

RUN WITHOUT INSTALLING

    ./vipirc -h
    ./vipirc -v
    ./vipirc --manual
    ./vipirc -i --manual
    ./vipirc --test-mode
    ./vipirc -i --test-mode
    ./1keyirc --test-mode
    ./vipirc --server irc.ircnow.org --channel '#ircnow' --nick tutankh-aten

-h/--help and -v/--version print and exit immediately. -V is retained as a
version alias. --manual and --check-config also work without a terminal.

--test-mode runs the interface with bounded in-process IRC simulation.
It requires an interactive terminal but no network socket, DNS, credentials, or
running IRC server. It uses the normal configuration and supplies test
channels when the channel list is empty. Missing nick/server values do
not prompt. Dismiss the welcome with an ordinary printable key, Enter, Tab,
or a navigation key. Terminal signal characters keep their meaning: Ctrl-C
exits with status 130 at the welcome, and Ctrl-Z suspends and resumes the
same screen after fg. Custom terminal signal settings are respected.

Chat starts ready to type with a visible caret. The top bar includes only
categories with retained content: 1 vipirc, 2 Servers, 3 Channels, 4 Queries,
5 Mentions/unread M/U, 6 File transfers N. vipirc is always present; a
workspace without a configured server shows only that entry. The category
numbers stay fixed and visible entries are separated by ' > '. A conversation
has its own row below the solid white header divider and never replaces
Queries or Channels.

Alt+number immediately opens an available category's panel. Arrows stay in
the editor or panel body; Up at the first chat row does not leave the editor.
Escape from chat opens vipirc. Escape from an ordinary panel restores the
conversation, draft, and caret; Escape from Settings returns to vipirc.
Use Alt+1 to configure, Alt+2 to connect or join, Alt+3 for channels, and Alt+4
for queries. Hidden empty categories keep their numbers reserved.

In chat, arrows, Home/End, Backspace, and Delete edit normally; hjkl are text.
Page Up/Page Down scrolls history. Tab completes matching nicknames and further
Tab cycles forward; Shift-Tab cycles backwards. Completion uses the current
server's case rules and never sends. The fixed field's white border marks
editing focus. Clear and Send appear below it only with supported, enabled
mouse reporting; /clearinput and Enter are their keyboard routes. There is
no separate Input/Navigation status label or configurable focus appearance.

vipirc opens an application panel with Server, Nick, IALA, Settings, Manual,
Command, and Background choices. Select a row with arrows and Enter.
Server and Nick open the current profile's settings; Nick selects its nickname
field. IALA opens the full included license and returns to vipirc on Escape,
Enter, q, or Back. /help and /manual directly open the full manual. To open
Command without changing any existing chat draft or caret, use its row in
the vipirc panel. Typing / at chat caret position zero also opens it.
Right at the end of a command-name prefix completes available names;
repeated Right cycles matches. Up/Down browses matching accepted commands
from this server's bounded memory-only history. Enter/Run executes;
Escape/Cancel returns. Failed commands remain editable. Command paste is
data requiring a fresh Enter/Run; pasted slash text in chat stays literal.
Tab does not navigate the header or execute Command. In Settings it moves among fields.

In server/channel/query lists, Up/Down or k/j selects a row. Space switches
row/footer action focus without changing the selected row; Down past the
last row also focuses actions, and Up returns to rows. Left/Right chooses
a focused footer action. With rows focused, Right on a server opens Channels;
Left from Channels returns to Servers; Right from Channels opens Queries;
Left from Queries returns to Channels. Channel 0 is always server/MOTD.
An empty Queries list offers New query... to prepare /pm in Command.

Arrows and hjkl always navigate outside editors. Legacy keymap=original|vim
and focus_style=reverse|brackets entries remain accepted but are ignored and
are no longer generated. Selected widgets use white outlines, or bold outlines
on a terminal without color support.

Settings is app-wide: select Interface, any server profile, or New server
without switching the active chat. Each server groups connection fields,
auto-connect, auto-join, auto-authentication, history, watch terms, and ignores.
Interface holds global mouse and roster settings. Tab/Shift-Tab stages a valid
field value and moves to the next/previous field. Back returns home. Apply
previews live preferences in memory and stages validated connection settings.
The next explicit Connect while disconnected adopts those settings; an active
connection keeps its current identity and transport. Save writes the selected
server's configuration privately and atomically, remaining in Settings, and
retains the choices for future launches. Transport selects TLS (verified) or
Plain IRC; connection encryption is separate from password storage and SASL
authentication. The connection log states whether encryption is on or off.
The New server form instead adds an independent unsaved session. Save is
disabled in test mode.

Server, channel, and query rows show unread and mention counts. Alt+5 or
/unread opens the combined Mentions/unread browser across servers: M counts
retained mention lines, U unread source conversations. Its grouped entries
include full timestamped mentions; Enter or a click opens the exact source.
A closed source is marked and is never silently recreated. /mentions still
opens the current server's original mention feed. Each server's normal
channel and query lists remain separate. Server/MOTD is always channel 0;
Servers in the global header opens the server list. Disconnect retains that
server's views and drafts. Close is only for secondary servers and asks
before discarding their state, cancelling transfers, and removing any saved
startup entry. The physical profile file is kept. /quit ends the application
and all sessions. Ordinary Ctrl-D is ignored at the welcome, startup prompts,
and application screens, even in an empty field; custom terminal signal
mappings still take precedence.

Test an existing configuration interactively:

    ./vipirc --test-mode --config /absolute/config.ini

Each configured server is a label in this mode. /connect only starts its
simulator, /disconnect stops it, and --no-connect leaves every session
stopped at startup. Otherwise usable saved profiles start their simulators
regardless of saved autoconnect. Newly added sessions remain offline too.
/save is disabled, and closing a saved secondary does not write its catalog.
DCC has a sample offer for inspecting controls, but send/accept fail before
file or network access. Edit the config and restart to try saved changes.
No configuration is created automatically. The simulator cannot validate
real TLS certificates, account credentials, or network connectivity.
Run --check-config separately for syntax/value validation. Combining
--test-mode with --check-config or --init-config exits with usage status 2.

A UTF-8 locale is needed for predictable non-ASCII display widths. ASCII
text already uses one byte per character within UTF-8. When using SSH,
inspect the host running vipirc:

    locale
    locale charmap
    locale -a

The first two commands show the session's locale settings and character
encoding; the third lists installed locales. Use an installed UTF-8 locale
whose effective LC_CTYPE matches the terminal's encoding. Locale names
vary between Unix-like systems; a name available locally may be absent on
an SSH host. Check each host separately.
TERM describes terminal capabilities, not encoding, and should still match
the terminal emulator. The client does not detect terminal encoding or
transcode legacy encodings.

The active LC_CTYPE is respected. A UTF-8 locale enables Unicode editing.
A non-UTF-8 or invalid locale selects explicit ASCII terminal mode, with a
welcome note. ASCII input works; non-ASCII input
is refused with a status message. Incoming non-ASCII code points display
as '?' without changing the stored network text. For Unicode use, select
an installed UTF-8 locale and restart the client. The client does not change
the terminal's encoding or force a different locale.

INSTALL FOR THE CURRENT ACCOUNT

    make install PREFIX="$HOME/.local"

Ensure $HOME/.local/bin is in PATH. The manual page is installed below
$HOME/.local/share/man; your man implementation may need that directory
in its manual search path. PREFIX defaults are given by the Makefile.

INSTALL UNDER A SYSTEM PREFIX

    make install PREFIX=/usr/local

Writing a system prefix requires permission to that destination. Use your
operating system's usual administrative installation method when needed.
The program itself must run as an ordinary user; do not install it setuid
or setgid, and do not run it as root for ordinary IRC use.

PACKAGE STAGING

    make install PREFIX=/usr/local DESTDIR=/absolute/staging/directory

DESTDIR adds a packaging root. PREFIX remains the eventual installation
prefix. Keep both settings consistent when packaging or uninstalling.

CONFIGURATION

    vipirc --init-config --server irc.ircnow.org --channel '#ircnow'
    vipirc --check-config

The configuration defaults to $HOME/.config/vipirc/config.ini, or to
$XDG_CONFIG_HOME/vipirc/config.ini when that absolute environment value is
set. An explicit --config selects another file. --init-config refuses to
overwrite an existing file. See examples/config.ini and the full manual.

The selected primary path P also owns P.servers, a small catalog of up to
seven additional profile IDs, and P.d/ID, each profile's configuration file.
For the default path these are config.ini.servers and config.ini.d/ID beside
config.ini. A missing catalog means no additional saved servers. The catalog
contains IDs rather than arbitrary paths. --check-config checks the primary,
catalog, and all referenced profiles without connecting, opening credential
files, or starting GPG. Explicit inline passwords are parsed as settings;
their private configuration-file permissions are checked.

Servers > New offers Add (without connecting) or Connect; neither saves the
new session. Use /save or Settings Save on that session to make it discoverable
at the next launch. Each profile keeps its own auto-connect choice;
--no-connect suppresses all startup connections. Applied connection changes
take effect on the next explicit Connect while disconnected; Save also keeps
them for future launches. Live interface/filter changes apply immediately.
Failed profile loads are shown without preventing other
valid sessions from being used.

Saving a secondary writes its private profile and then updates the catalog
under a private nonblocking advisory lock. Individual files are replaced
atomically; a failed catalog update can leave an unlisted profile file.
Closing a saved secondary removes its catalog reference but does not delete
that file. A catalog-write failure keeps the session open for correction or
retry. Disconnect does not change saved entries. Existing primary configs
remain valid without a catalog, and newly added sessions inherit no server
password, SASL identity, or channel list from another server.

Earlier builds under the working name irccmd used a directory named irccmd
in the same configuration base. vipirc does not automatically read or move
that file. To reuse it, select its existing absolute path with --config,
or copy it into the new vipirc directory with the permissions described below.

The final directory for generated configuration must be owned by the
current user with mode 0700. The generated file has mode 0600. File paths
must not use symbolic links or '..' components. Both plaintext and encrypted
credential files must be owned regular files with exactly mode 0600 or 0400.

For SASL set auth=sasl and auth_user, then choose exactly one of password_file
(private plaintext file), password_gpg_file (encrypted file), or password
(explicit plaintext config value). Optional server PASS independently chooses
server_password_file, server_password_gpg_file, or server_password. Multiple
nonempty sources for one credential are errors. Both uses require verified
TLS. SASL passwords allow 1024 bytes; server PASS allows 503. Plain/decrypted
content must be one nonempty line with optional LF/CRLF, without controls.

Inline password and server_password preserve every byte after '=' including
outer spaces, '#', ';', and additional '='. Do not add quotes or comments.
A configuration containing either must be owned by you and mode 0600 or
0400; Save writes 0600. Settings masks those fields. Ordinary values and
file paths keep the documented whitespace trimming.

GPG ciphertext is limited to 64 KiB. Decryption runs only when authentication
needs it after verified TLS, with a five-second deadline, no shell, and no
plaintext temporary file. Network key lookup/import, daemon startup, and
terminal prompting are disabled. Prepare an existing agent outside vipirc:

    gpg --output /dev/null --decrypt /absolute/password.gpg

Then /connect retries the IRC connection. The selected file, gpg executable,
GnuPG home, and agent belong to the host running vipirc, including through
SSH. Test mode and --check-config never decrypt or open credential sources.
Full setup, limitations, and recovery are in the manual and SECURITY.md.

DIRECT FILE TRANSFERS

Open File transfers through the global header, /dcc, or
/transfers. One bounded list
covers every server session: eight pending incoming offers, four active
transfers including listeners/connect attempts, and twelve retained records.
The supported protocol is active IPv4 DCC SEND, with a known size from zero
through 4294967295 bytes. CHAT, resume, passive/reverse SEND, IPv6 endpoints,
and larger or unknown-size files are not supported.

An incoming offer does not open a file or connect. Select Accept to review
the peer IP, port, byte count, directory, and filename, then deliberately
choose Accept in the form. Reject opens nothing. The review needs at least
12 rows by 20 columns. Received files are new regular files with mode 0600;
existing files are never overwritten. Symlinks and '..' path components are
refused. The receive directory must be owned by the current user with no
group/other permissions. Only explicit Accept creates missing directory
components, with mode 0700. Files are never launched or executed automatically.

The proposed download directory is $XDG_DATA_HOME/vipirc/downloads when that
environment value is absolute, otherwise $HOME/.local/share/vipirc/downloads.
It is only a proposed path until acceptance. You can choose a different
private directory and safe filename. Cancellation/failure removes a partial
file only if it still matches the inode created by that transfer.

Sending requires a registered IRC session, recipient nickname, explicitly
selected regular source file, advertised IPv4 address reachable by the peer,
and listen port. Port 0 chooses an available local port. The listener uses
the running host's IPv4 interfaces; there is no public-address discovery,
UPnP, automatic NAT forwarding, or firewall configuration. A forwarded port
must match the advertised port. The source is not copied or locked against
later modification; select a stable file.

DCC transfers are unencrypted and do not authenticate the direct peer as
the IRC nickname, even when IRC uses TLS. They reveal a network address to
the peer. On SSH, paths, HOME/XDG defaults, listeners, and peer connections
belong to the host running vipirc, not the terminal's local computer. To
send a local computer's file from a remote session, first arrange for that
file to exist on the remote host through your usual file-transfer method.

Transfers progress while chat remains usable. Offers expire after five
minutes; endpoints fail after two minutes without progress. Cancel removes
an unsent IRC advertisement, but a frame already in flight cannot be recalled.
Disconnecting IRC leaves accepted direct transfers running. Closing their
originating secondary server or quitting cancels them. See SECURITY.md for
the file-handling, endpoint, and partial-file cleanup boundaries.

TESTS

    make check-unit

This runs protocol, editor, command catalog/history, configuration/catalog,
App, client, status-mouse, and DCC engine checks. The DCC cases use local loopback sockets and temporary files, so this
target needs loopback permission even though its other model tests do not.

    make check-offline

This runs command-line and real-terminal-PTY checks without a network
socket, including the built-in offline mode. It requires Python 3 for the
test harness; the installed --test-mode does not require Python.
Server-list and file-transfer preview cases are included, with real transfer
send/accept disabled before file or socket I/O.

    make check

The suite runs the unit and offline checks plus local fake-server and file
transfer integration. These broader tests use loopback and temporary data.
They do not join a public IRC channel or send messages to other users.
Server tests cover routing, unread/mentions, drafts, and catalog persistence;
transfer tests cover explicit consent, binary/empty files, fragmented ACKs,
cancellation, and responsiveness of multiple IRC sessions during transfers.

For focused server/transfer terminal tests after building:

    python3 tests/test_servers.py --offline-only
    python3 tests/test_transfers.py --offline-only
    python3 tests/test_servers.py --live-only
    python3 tests/test_transfers.py --live-only

The live-only cases use local fixtures, not public servers. Omitting a mode
flag runs both parts of that script. Restricted containers may require
explicit loopback permission for live cases and make check-unit.

    make check-gpg

This optional target builds the credential probe and tests real public-key
and symmetric GPG decryption with a disposable private GNUPGHOME and agent.
It checks cached-agent success and locked-agent, tampered, unencrypted,
oversized, multiline, and unauthenticated legacy input rejection. It needs
GnuPG 2.2.20 or newer, gpgconf, Python 3, and working local Unix sockets.
The fixture passes secrets through pipes and a private Unix socket, uses no
user keyring or public IRC server, and stops only its disposable agent.
If gpg or gpgconf is absent, the script reports a JSON "skipped" result and
exits successfully; an installed tool that fails does not count as a skip.
For release evidence, require the reported status to be "passed"; a skipped
check is not real-GPG test coverage.
The deterministic credential tests remain part of the ordinary test suite.

    make sanitize

The sanitizer target builds/runs checks with compiler-supported memory and
undefined-behavior instrumentation. Some restricted containers prevent
LeakSanitizer from tracing /proc. In that case an AddressSanitizer run with
ASAN_OPTIONS=detect_leaks=0 still checks invalid accesses, but is not a leak
check. Record that distinction in validation results.

EXTENDED FAILURE, STRESS, AND LONGEVITY TESTS

    make check-stress

This adds allocation-failure rollback checks, deterministic DCC/parser
stress, transport timeouts and storage faults/concurrent saves, and seeded
real-PTY editing/resize/modal stress. Test sockets use loopback only. The
descriptor-pressure test lowers only its child client's open-file limit
and checks that errors leave the interface usable and terminal restored.
The DCC stress duration defaults to 60 seconds and can be changed with
STRESS_SECONDS=90. These are development fixtures, with no runtime overhead
or new library dependency in the installed client.

    make soak SOAK_SECONDS=1200

This runs a bounded 20-minute soak with eight local verified-TLS servers,
20 channels per server, saturated retained history, incoming private
messages/mentions, live editing and resizing, and repeated secondary-server
reconnects. It checks PING and input responsiveness, preserved drafts, no
unintended messages, exact descriptor balance, resolver cleanup, bounded
resident memory growth, and terminal restoration. It prints JSON results;
progress goes to standard error. The Linux resource sampler verifies process
identity, including PID namespaces, before reading /proc. Unsupported or
unverifiable measurements fail clearly. This test needs Linux /proc and
is not a native BSD portability test.

    python3 tests/test_soak.py --seconds 1200 --servers 8 --tls > soak.json
    python3 tests/test_ui_stress.py --seed 20261027 --steps 1200 --rounds 48
    python3 tests/test_lifecycle_stress.py --sanitize

For UI checks against an instrumented build, set VIPIRC_BINARY to the
absolute build/sanitize/vipirc path after make sanitize. Record the actual
elapsed duration and executable hash. A bounded soak does not establish
months of uninterrupted reliability. The sanitizer build's RSS includes
instrumentation and must not be compared directly with optimized-build RSS.

OPTIONAL LOCAL PERFORMANCE MEASUREMENT

After an optimized build:

    make bench

This uses a real terminal PTY and a loopback fake server. It measures a
short idle interval, then receives 100000 synthetic messages across 20
conversation buffers with history_lines=5000. Ordered PING/PONG markers
verify progress through the stream; input probes and a final redraw check
that the client remains responsive. No public IRC server is contacted.

The JSON report identifies the exact binary hash, transport, workload,
timing, and measurement method. Linux uses /proc for the target process's
CPU and RSS after checking PPid, namespace PID, and executable identity;
other systems report the portable timing and child resource
usage available there. The local plain-IRC benchmark does not measure TLS
record overhead, long-duration reliability, or performance relative to
another client. During stress, the PTY is drained without interpreting
every terminal drawing sequence in the Python harness.

Direct use supports a different bounded workload:

    python3 tests/bench.py --messages 100000 --phases 4 --idle-seconds 3

To measure three independent clients idle at once, each with its own
loopback server, terminal PTY, and 20 joined channel views:

    python3 tests/bench.py --idle-instances 3

The report gives each process's measurements and their sum. Summed RSS
counts shared resident library pages in each process, so it is not an
exclusive physical-memory measurement. The extra idle clients exit before
the primary client's message stress phase.

MANUAL FROM THE SOURCE TREE

    ./vipirc --manual
    groff -Tascii -man doc/vipirc.1 | less

On a system with mandoc:

    mandoc doc/vipirc.1 | less

The same manual text is embedded for /help and /manual. The vipirc header
destination contains a Manual choice; the reader begins with current
Server, Connection, and Nick details.
No network access or external pager is needed. Scroll with Up/Down or k/j, Page Up/Page Down, or Home/End
or g/G. Escape,
Enter, q, or Back returns to the previous main view with its draft preserved;
Escape from chat opens vipirc. Alt+1 through Alt+6 open available categories
directly; arrow keys remain within the editor or panel body. The separate
IALA reader contains the complete included license and returns to vipirc.

BACKGROUND AND RESUME

/background or Background in the vipirc screen restores the terminal and
returns to the shell while one worker keeps connections, transfers, and drafts
alive. The notice identifies a numbered local job, for example:

    vipirc --resume 1

Use vipirc --resume for the sole available detached job, or vipirc --sessions
to list jobs. With several candidates, --resume shows the list and requires
a number; --resume=1 is also accepted. Numbers remain stable while a job
exists and may be reused after it exits. Explicit absolute socket paths are
still accepted for older sessions. Management commands do not load configs.

Jobs use private local Unix sockets, not network listeners. The same Unix
account on the same machine can resume it from another terminal or SSH login.
Only one terminal attaches at a time. Normal foreground use has no extra
worker/helper/socket; a resumed session uses one small foreground signal and
resize waiter. /quit removes the endpoint; detached process state does not
survive a reboot. First detach defers while resolver/GPG children finish.
A resumed terminal hangup detaches, Ctrl-C quits, and Ctrl-Z suspends its
foreground waiter while the worker keeps servicing connections; fg redraws.
Explicit Background in --test-mode creates local IPC but no IRC/DCC network
connection and does not save configuration. Discovery checks at most 64 slots
with one bounded probe deadline. It removes only verified stale entries;
unresponsive live entries are retained and reported as unverified. Discovery
and cleanup never guess a process identity from a PID.

SOURCE LICENSE

Read LICENSE.md for the supplied Insha-Allah License Agreement. Dependencies
remain governed by their own licenses; see NOTICE.
