Build a zero-JS static article site from hand-written HTML fragments.
Created · Updated
DESCRIPTION
arachnopress reads hand-written HTML articles below
articles/ and builds a single-page or multi-page static site.
The generated interface uses only HTML and CSS; it requires no JavaScript,
cookies, or browser storage.
Generated output may be opened directly through file:// or
served as static files. It requires no CGI, PHP, application server, or
database.
The build uses sh, make, and standard Unix
utilities on BSD, Linux, and macOS. Pygments and GNU Source-highlight are
optional. A missing or failed highlighter falls back to escaped source.
FEATURES
Editable HTML fragments with arbitrary HTML where required; no
Markdown or template pipeline.
HTML and CSS interface without JavaScript, cookies, or browser storage.
POSIX-style sh and make build for BSD, Linux,
and macOS.
Single-page or per-article output, unlisted articles, and extensionless
internal URLs.
Run make build from the project root. It builds every source
article and replaces site/. Supply settings through the
environment or as make command-line variables.
Maintainers run make release followed by
make full to publish and integrate a generator release. Run
every target from the project root. Do not store source files in or run
make from site/; each successful target replaces
it.
cd /path/to/arachnopress
make build
# Override only the settings required for this build.
make build SITE_TITLE="Example Site"DEFAULT_THEME=solarized-light
# Use a fixed theme and omit the automatic mode control.
make build DEFAULT_THEME=solarized-light DEFAULT_THEME_AUTO=false# Omit the Theme selector.
make build DEFAULT_THEME_SELECTOR=false# Generate one HTML file per article.
make build SITE_MODE=multi-page
# Exclude unlisted articles.
make build SITE_MODE_UNLISTED=false
REQUIREMENTS
Required utilities
The build requires a POSIX-like operating environment with
sh, make, awk, sed,
grep, sort, tr, date,
dirname, pwd,
printf, mktemp, mkdir, rm,
wc, cat, cksum,
chmod, mv, cp, and find.
The release and full profiles also require cmp,
tar, and gzip support in tar.
mktemp is widely available but is not specified by POSIX.
Optional utilities
pygmentize
Provides Pygments syntax highlighting. This is the default highlighter
when the command is available.
source-highlight
Provides GNU Source-highlight syntax highlighting when selected.
sha256, sha256sum, shasum, or openssl
Provides SHA256 values for download blocks. The first available
implementation is used. Downloads remain usable without a checksum.
Browser features
The generated interface requires browser support only for HTML and CSS.
It uses CSS :has(), :target,
prefers-color-scheme, native
details/summary, and the HTML popover API.
SOURCE LAYOUT
Maintained files
Makefile
Defines build, release, and full,
their defaults, and their explicit inclusion lists.
styles.css
Defines layout, themes, syntax colours, responsive behaviour, popovers,
and generated block presentation. Targets copy it into their fresh site
output but do not modify it.
THIRD_PARTY_NOTICES.txt
Identifies the upstream colour schemes, sources, authors, and licences.
The license popover and optional Theme popover link to this file.
licenses/
Contains the retained third-party copyright and licence notices.
tools/build.sh
Discovers, validates, orders, and renders articles. It generates
the selected page layout, favicon.svg, automatic-theme
rules, and embedded navigation rules in the selected build root.
tools/build-profile.sh
Stages, builds, publishes, cleans, and packages target output.
tools/theme-menu.html
Supplies theme controls and optional data-auto-dark and
data-auto-light pair mappings. Its IDs are reserved as page
anchors.
tools/license.txt
Supplies the authoritative static project-license text. Its first non-blank
line is the displayed title.
tools/html-fragment.outlang
Configures Source-highlight to emit an HTML fragment.
articles/<slug>/article.html
Supplies one article. The directory name is the article slug and must
match the article element ID.
articles/<slug>/*
Supplies article-local code, download, and image files. These paths are
copied below site/articles/ and linked from the page.
The example tree shows maintained source and make build
output. site/ contains deployed files and copied articles. The
generator article is articles/arachnopress;
GENERATOR_VERSION names release archives.
.PHONY:buildreleasefull.NOTPARALLEL:# Site-profile defaults. Edit RELEASE_SITE_ENV for release, or PUBLIC_SITE_ENV# for build and full. Environment and command-line make values take precedence# without being expanded into shell source by make.RELEASE_SITE_ENV=\SITE_TITLE="$${SITE_TITLE:-arachnopress}"\SITE_ICON="$${SITE_ICON:-䷖}"\SITE_ICON_COLOUR="$${SITE_ICON_COLOUR:-}"\SITE_ICON_PATH="$${SITE_ICON_PATH:-}"\SITE_ICON_PATH_THEME="$${SITE_ICON_PATH_THEME:-false}"\SITE_URL_STYLE="$${SITE_URL_STYLE:-html}"\SITE_MODE="$${SITE_MODE:-single-page}"\SITE_MODE_UNLISTED="$${SITE_MODE_UNLISTED:-false}"\DEFAULT_THEME="$${DEFAULT_THEME:-solarized-dark}"\DEFAULT_THEME_AUTO="$${DEFAULT_THEME_AUTO:-false}"\DEFAULT_THEME_SELECTOR="$${DEFAULT_THEME_SELECTOR:-true}"PUBLIC_SITE_ENV=\SITE_TITLE="$${SITE_TITLE:-arachnogoat}"\SITE_ICON="$${SITE_ICON:-䷪}"\SITE_ICON_COLOUR="$${SITE_ICON_COLOUR:-}"\SITE_ICON_PATH="$${SITE_ICON_PATH-articles/arachnopress/arachnogoatsundual.svg}"\SITE_ICON_PATH_THEME="$${SITE_ICON_PATH_THEME:-true}"\SITE_URL_STYLE="$${SITE_URL_STYLE:-extensionless}"\SITE_MODE="$${SITE_MODE:-single-page}"\SITE_MODE_UNLISTED="$${SITE_MODE_UNLISTED:-true}"\DEFAULT_THEME="$${DEFAULT_THEME:-everforest-hard-dark}"\DEFAULT_THEME_AUTO="$${DEFAULT_THEME_AUTO:-true}"\DEFAULT_THEME_SELECTOR="$${DEFAULT_THEME_SELECTOR:-true}"BUILD_ENV=\GENERATOR_LABEL="$${GENERATOR_LABEL:-arachnopress}"\GENERATOR_VERSION="$${GENERATOR_VERSION:-1.0.53}"\ARTICLE_ORDER="$${ARTICLE_ORDER:-title}"\HIGHLIGHTER="$${HIGHLIGHTER:-pygments}"\CODE_FOOTER_LINES="$${CODE_FOOTER_LINES:-23}"RUNTIME_FILES=\
THIRD_PARTY_NOTICES.txt \
styles.css \
licenses
RELEASE_FILES=\
Makefile \
README.arachnopress \$(RUNTIME_FILES)\
tools/build.sh \
tools/build-profile.sh \
tools/html-fragment.outlang \
tools/license.txt \
tools/theme-menu.html
build:$(PUBLIC_SITE_ENV)$(BUILD_ENV)\
sh tools/build-profile.sh build $(RUNTIME_FILES) articles
release:$(RELEASE_SITE_ENV)$(BUILD_ENV)\
sh tools/build-profile.sh release \$(RELEASE_FILES) articles/arachnopress
full:$(PUBLIC_SITE_ENV)$(BUILD_ENV)\
sh tools/build-profile.sh full $(RELEASE_FILES) articles
BUILD MODEL
Discovery and validation
Each target copies its Makefile inclusion list to a private
.site-build.* tree in the project root. Build and full select
every staged article; release selects articles/arachnopress.
Inclusion paths must exist and contain no symbolic links.
tools/build.sh validates and renders the staged tree. Its
transient files remain below TMPDIR, or /tmp
when TMPDIR is unset.
Publication and ownership
The target completes the staged output before replacing
site/. A failure before publication preserves the previous
site/.
Exit and handled signal traps remove unpublished staging and rendering
files. An untrappable termination may leave them behind.
Release and full prepare the generator-release marker and archive cleanup
in staging and update the source only after publication succeeds.
All targets share site/ and must not run concurrently.
ARTICLE FORMAT
Root metadata
Each article begins with a one-line article element. Its
class list must include article. Its id must
equal the containing directory name. data-title is required.
Optional data-created and data-modified values
begin with YYYY-MM-DD. data-created defaults to
the current UTC build date; data-modified defaults to
data-created. Set both on published articles to keep visible
metadata and date ordering stable.
data-listed defaults to true and accepts only
true or false.
SITE_MODE_UNLISTED=false excludes articles marked
data-listed=false from generated HTML and navigation in
either output mode.
SITE_MODE_UNLISTED=true enables unlisted output.
Enabled single-page output embeds unlisted articles after listed articles,
omits them from article indexes, and exposes them through their article,
section, and subsection fragments. Enabled multi-page output writes an
unlisted article as slug.html, omits it from listed-page
indexes, and includes it only in its own index.
A multi-page unlisted document includes a noindex, nofollow
directive. A single-page article shares index.html and cannot
have a separate robots directive. Unlisted output remains publicly
accessible. At least one listed article is required. Multi-page mode
reserves the index slug. Targets still copy included article
directories to site/articles/.
articles/getting-started/article.htmlComplete example articleraw
<articleclass="article"id="getting-started"data-title="Getting Started"data-created="2026-07-09"data-modified="2026-07-09"><headerclass="article-header"><pclass="kicker">Guide</p><h1>Getting Started</h1><pclass="summary">
A short article built from one editable HTML fragment.
</p></header><section><h2>Example</h2><p>Article text is normal HTML.</p><h3>Subsection</h3><p>Plain one-line h3 headings appear under their preceding section.</p></section></article>
Header metadata
The source header contains an h1 and summary. The build inserts
Created and a time element before its closing tag. It adds
Updated when the dates differ and prefixes unlisted articles with
Unlisted. The order is Unlisted, Created, Updated, using one separator.
Indexed headings
Use h2 for major sections and h3 for genuine
subdivisions. Indexable headings contain plain text and close on the same
source line. An h3 must follow an h2. The build
preserves a valid explicit ID or derives a unique ID from the heading and
article.
Article, heading, theme, generated control, and generated block IDs share
one collision registry. Explicit IDs and slugs may contain ASCII letters,
digits, dots, underscores, and hyphens.
Generated markers are empty, file-backed elements contained on one source
line. data-src is relative to the article directory. Missing
or invalid sources render visible missing blocks.
Code blocks
data-title changes the display title;
data-note adds a qualifier; and data-open
values 0, false, no,
closed, and collapsed close the block initially.
Other values leave it open. data-header="false" emits
always-visible content without a summary. A headerless block has no
footer unless data-footer="true" is explicit.
For normal blocks, an omitted data-footer hides the footer
when the source line count is at or below
CODE_FOOTER_LINES. Explicit true or false values override
that threshold. Raw links and sizes refer to the source file, whose
contents are HTML-escaped.
<preclass="code-block"data-src="src/example.c"data-title="example.c"data-note="A short C example"data-lang="c"></pre><preclass="code-block"data-src="build.sh"data-lang="sh"data-open="false"data-footer="false"></pre><preclass="code-block"data-src="output.txt"data-lang="text"data-header="false"></pre><preclass="code-block"data-src="output.txt"data-title="output.txt"data-lang="text"data-header="false"data-footer="true"></pre>
Download blocks
A download marker names exactly one file. data-title changes
only its display title and data-note adds a qualifier. The
link continues to target data-src. The block shows the raw
size and, when a supported checksum program is available, its SHA256.
The obsolete data-downloads attribute is rejected; use one
data-src marker per file.
The generator article has exactly one marker carrying
data-release="generator". The release and full targets replace
that complete line with the validated current archive name; a normal
build leaves it unchanged.
Download block markersOne source file per markerraw
data-alt supplies alternative text.
data-caption adds a caption; data-title and
data-note label the header. data-open,
data-header, and data-footer behave as for code
blocks.
data-scale accepts small, medium,
large, or full and caps inline size at 20rem,
32rem, 48rem, or the article width. full is the default.
Images retain their aspect ratio, do not upscale, and remain within the
viewport.
data-border accepts full, fit, or
none. full is the default. fit
shrink-wraps the frame. none removes it and suppresses the
header and footer.
data-align accepts start, center, or
end; center is the default.
Above 760px, following prose and text lists may flow beside a start- or
end-aligned fit or borderless block. Such a block uses at most 55% of the
article measure.
Headings, generated blocks, preformatted blocks, tables, horizontal rules,
consecutive images, and section ends clear the flow. Centered, full-frame,
and narrower layouts keep images on their own row.
Other scale, border, and alignment values are fatal. Generated images use
lazy loading and asynchronous decoding.
A missing or invalid source renders a missing block. A non-empty or
multiline marker is fatal.
SYNTAX HIGHLIGHTING
Pygments
Pygments is the default. An omitted data-lang requests
filename inference. data-lang="auto" first uses filename
inference and may guess from content. An explicit value requests that
lexer.
Source-highlight
Source-highlight uses tools/html-fragment.outlang. An omitted
or automatic language lets the program infer its input language. The
make alias is translated to makefile.
Fallback and optimisation
A missing program, unknown lexer, failed invocation, missing output
definition, or empty highlighted result for non-empty input falls back to
escaped raw source for that block. HIGHLIGHTER=none always
uses that path.
Successful highlighted output is filtered to unwrap whitespace-only
Pygments span.w and Source-highlight
span.sh-normal elements. Their whitespace remains in the
preformatted code content.
NAVIGATION AND THEMES
Article and section selection
The build writes title, creation-date, and modification-date order lists.
ARTICLE_ORDER selects the initial list; CSS radio controls
select another.
In single-page mode, URL fragments and CSS
:target/:has() select articles, sections, and
subsections. The first listed article is visible without a target.
index.html embeds the generated navigation rules.
In multi-page mode, index.html contains the first listed
article and each article has slug.html. Each file contains one
article, its section index, and its generated navigation rules.
Title order is case-insensitive by title, then slug. Creation and
modification orders are newest first by the corresponding source value,
then slug.
Responsive index
Wide landscape layouts place article and section navigation in a vertical
left pane. Narrow portrait layouts use independent horizontal article,
section, and conditional subsection rows. Short and narrow layouts hide
descriptive build metadata before hiding the generator identity, Contact,
or license controls.
Theme state
DEFAULT_THEME selects the initial exact variant.
DEFAULT_THEME_AUTO adds an Exact, Auto, Light, and Dark
header control, beside the Theme button when present, and initially
selects Auto. Exact applies the selected theme unchanged. The other modes
use its light/dark mapping; Auto follows the browser preference. An
unmapped theme remains fixed.
Both controls have equal height. The mode selector has no frame or
selected background; its checked symbol uses the active heading colour.
DEFAULT_THEME_SELECTOR enables the selector from
tools/theme-menu.html. Single-page selections span all
articles until reload. Multi-page selections reset when another page
loads. The mode control is independent and appears only when
DEFAULT_THEME_AUTO is true.
Contact opens a native popover with an optional 254-character reply
address and a required 500-character message. The form submits GET to the
current page and targets contact-confirmation.
The first query item is a lowercase SITE_TITLE identifier
followed by _contact=1. Runs outside letters, digits, dots,
underscores, and hyphens become one underscore; edge underscores are
removed; an empty identifier becomes arachnopress. Defaults are
arachnopress_contact=1 for release and
arachnogoat_contact=1 for build and full.
Contact request shapeValues are URL-encoded by the browserraw
Browser URL with SITE_TITLE="Example Site":
https://host.example/?example_site_contact=1&reply=operator%40example.com&message=Short+message#contact-confirmation
HTTP request target logged by a server that retains query strings:
GET /?example_site_contact=1&reply=operator%40example.com&message=Short+message
Contact privacy
HTTPS encrypts the request in transit. The address and message remain in
the URL, browser history, and logs that record the query string. Do not
submit confidential information. The static site provides no separate
message delivery or storage.
The operator extracts and processes marked requests from the server log.
arachnopress provides no log-processing component.
Project license
The license popover HTML-escapes and embeds
tools/license.txt. Its first non-blank line is the title.
THIRD_PARTY_NOTICES.txt maps bundled colour palettes to
notices below licenses/.
TARGETS
build
Routine site-generation target. Replaces site/ with the
deployment files, every source article, and newly generated output.
Leaves release markers and archives unchanged.
release
Maintainer target for a new generator version. Builds only the generator
article with its release download absent, replaces site/,
creates arachnopress_GENERATOR_VERSION.tar.gz in the project
root, and updates the source marker. The archive excludes itself.
full
Maintainer target used after release. Requires the root release archive,
installs it in the staged generator article, generates the configured
article set, replaces site/, and updates the source marker.
No clean, install, serve, or watch target is defined.
VARIABLES
RELEASE_SITE_ENV defines release site defaults.
PUBLIC_SITE_ENV defines build and full site defaults.
BUILD_ENV defines shared defaults. Environment and
command-line make values override them.
Site identity
SITE_TITLE
Page title and displayed brand. It also forms the contact request marker
prefix. Defaults: arachnopress for release;
arachnogoat for build and full.
SITE_ICON
Character shown beside SITE_TITLE and rendered into the
generated SVG favicon. Defaults: U+4DD6 for release; U+4DEA
for build and full.
SITE_ICON_COLOUR
Optional fixed colour for the Unicode header icon and generated
favicon. The value must be #rgb or #rrggbb.
When empty, the header icon follows the current article-title colour
and the generated favicon uses the SVG default text fill. It is unused
when SITE_ICON_PATH is set. Default: empty.
SITE_ICON_PATH
Optional relative path to an SVG in the selected build tree. The build
copies it unchanged to site/favicon.svg and uses it beside
SITE_TITLE. SITE_ICON and
SITE_ICON_COLOUR are then unused. Defaults: empty for
release;
articles/arachnopress/arachnogoatsundual.svg for
build and full. Direct
tools/build.sh execution defaults to empty. Set it to an
empty value to restore the generated text icon.
SITE_ICON_PATH_THEME
true embeds the SITE_ICON_PATH SVG in the
header and maps its colour-out and colour-in
classes to the current article background and title colours. The
favicon remains an unchanged copy. true requires
SITE_ICON_PATH. false uses the SVG as an external
header image. Defaults: false for release;
true for build and full.
GENERATOR_LABEL
Generator name displayed in site metadata. Changing it does not affect
project identity, the contact marker, source paths, or archive names.
Default: arachnopress.
GENERATOR_VERSION
Generator software version, displayed after
GENERATOR_LABEL and used in release archive names.
Maintainers change it for a new generator release. It must contain
dot-separated digits only. Default: 1.0.53.
Navigation and rendering
DEFAULT_THEME
Exact input ID from tools/theme-menu.html, with or without
the theme- prefix. Defaults:
solarized-dark for release;
everforest-hard-dark for build and
full.
DEFAULT_THEME_AUTO
Theme mode control. true renders Exact, Auto, Light, and
Dark header choices, beside the Theme button when present, and
initially selects Auto. Exact applies the selected theme unchanged. The
other choices use its data-auto-dark and
data-auto-light mapping; Auto follows the browser
preference. A theme without a mapping remains fixed.
false uses the selected exact variant and omits the
control. Defaults: false for release;
true for build and full.
DEFAULT_THEME_SELECTOR
Theme selector state. true renders the selector in either
output mode; false fixes the selection to
DEFAULT_THEME. Default: true.
SITE_MODE
Output layout. single-page writes all generated articles
to index.html. multi-page writes
index.html plus one slug.html per article.
Default: single-page.
SITE_MODE_UNLISTED
Unlisted article generation. true enables unlisted output
using the selected SITE_MODE; false excludes
unlisted articles from generated HTML and navigation. Defaults:
false for release; true for
build and full.
SITE_URL_STYLE
Page-link format. html retains .html links.
extensionless keeps generated .html files but
uses slug and ./ for multi-page navigation, home
links, and contact actions. Defaults: html for
release; extensionless for build
and full. Extensionless output requires a matching
web-server rewrite. Single-page output is unchanged.
ARTICLE_ORDER
Initial order. Accepted values are title;
created, creation, or ctime; and
modified, modification, or
mtime. Default: title.
HIGHLIGHTER
pygments, source-highlight, or
none. Default: pygments.
CODE_FOOTER_LINES
Non-negative line threshold for automatically hiding code block
footers. Default: 23.
ENVIRONMENT
PATH
Locates required utilities and optional highlighters and checksum
programs.
TMPDIR
Parent for private static-site-build.* rendering
directories. Default: /tmp. Target staging uses
.site-build.* below the project root.
LC_ALL is set to C.
SOURCE_HIGHLIGHT_DATADIR is unset so Source-highlight uses
its installed data files and the project output definition.
BUILD
Routine site build
Run make build from the project root. It defaults to
single-page output, includes unlisted articles, and enables the Theme and
Exact/Auto/Light/Dark controls. The extensionless URL setting has no
effect in single-page mode.
Set SITE_MODE=multi-page for per-article HTML files.
Set DEFAULT_THEME_AUTO=false or
DEFAULT_THEME_SELECTOR=false to omit either theme control.
Set SITE_MODE_UNLISTED=false to exclude unlisted articles.
Representative build outputCounts depend on article content and installed toolsraw
Built 2 listed and 1 unlisted articles into index.html.
Default theme: theme-everforest-hard-dark. Article order: title. URL style: extensionless.
Theme selector: true.
Theme mode control: exact / auto / light / dark (theme-everforest-hard-dark / theme-everforest-hard-light).
Pygments available: 51 highlighted, 15 escaped raw.
Built site output in /path/to/arachnopress/site.
Custom site icon
Store the SVG below an included article and define
colour-out and colour-in classes. Clear
SITE_ICON_PATH to restore the generated text icon.
Custom site icon buildReplace article-slug after adding an SVGraw
# Run from the project root after adding the required SVG classes.SITE_ICON_PATH=articles/article-slug/site-icon.svg \SITE_ICON_PATH_THEME=true make build
Generator release
After updating GENERATOR_VERSION, run
make release from the project root. It renders the generator
download as missing and creates the root archive.
Release integration
Run make full from the same project root after
make release. It requires the matching root archive and
installs it in the generated generator article. Both targets update the
source marker only after publication.
Highlighter variants
HIGHLIGHTER=none selects escaped source. Select either
installed highlighter explicitly to test its output.
Highlighter buildsRun separately from the project rootraw
# Run each command separately from the project root.
make build HIGHLIGHTER=none
make build HIGHLIGHTER=pygments
make build HIGHLIGHTER=source-highlight
REBUILD AND RECOVERY
Rebuild conditions
Re-run the build after changing an article, an article-local file,
styles.css, a tool input, or a build setting. Generated
stylesheet and favicon URLs contain cache tokens, so rebuilding publishes
new URLs when those inputs change.
Failed or interrupted builds
Correct the diagnostic and rerun the target. Before publication, a
validation, staging, or rendering failure leaves the previous
site/, source marker, source archives, and root release archive
unchanged.
A publication failure may leave site/ incomplete; rerun before
serving.
After all builds stop, remove files left by an untrappable termination.
Inspect the wildcard expansions before removal.
Temporary-file recoveryRun from the project root after all builds stopraw
# Run from the project root after confirming that no build is running.
rm -rf "${TMPDIR:-/tmp}"/static-site-build.*
rm -rf .site-build.*
# Rebuild and publish a complete site tree.
make build
Remove generated output
No clean target is defined. Remove site/ without changing
maintained source. Release replaces the current-version root archive but
does not remove older root archives. Remove obsolete root archives by
exact name.
Remove generated outputRun from the project rootraw
# Run from the project root when no build is running.
rm -rf site
FILES
site/
Fresh published output from the most recent target. Do not store
maintained articles or run make in this directory.
site/index.html
Generated entry point. It contains every generated article in
single-page mode or the first listed article in multi-page mode.
site/<slug>.html
Multi-page output for each listed article and each enabled unlisted
article.
site/favicon.svg
Generated fallback or unchanged copy of SITE_ICON_PATH.
Its link contains a cache version token.
site/theme-auto.css
Generated when DEFAULT_THEME_AUTO=true and a reachable
theme has an automatic mapping. It contains light/dark rules derived
from styles.css and the mappings in
tools/theme-menu.html. Its link contains the shared
stylesheet cache token.
site/styles.css
Copied site stylesheet.
THIRD_PARTY_NOTICES.txt
Maintained notice for bundled colour palettes. Targets copy it into
site/ and release archives.
licenses/
Retained third-party licence notices. Targets copy the directory into
site/ and release archives.
articles/<slug>/*
Maintained article source and published raw, download, and image assets.
Targets copy them to site/articles/<slug>/.
arachnopress_GENERATOR_VERSION.tar.gz
Maintainer release archive produced by make release and
consumed by make full from the same project root. Its
top-level directory has the same name without the .tar.gz
suffix.
README.arachnopress
The compact operator and interface reference.
EXIT STATUS
A target exits zero after its complete staged tree is published as
site/, after release packaging when applicable, and after
managed source cleanup. It exits non-zero on invalid settings or article
structure, missing required tool inputs, duplicate or invalid IDs, no
articles, or an unhandled command or filesystem failure.
Optional highlighting and checksum failures are not fatal. The build
either emits escaped raw source or omits the checksum.
DIAGNOSTICS
Missing release archive
Run make release with the same generator version before
make full. Run both commands from the same project root, not
from site/.
DEFAULT_THEME does not exist in theme menu / Automatic theme mapping
does not exist
Use IDs from tools/theme-menu.html. Define
data-auto-dark and data-auto-light together,
and include the source theme in its pair.
No listed articles found
Add or select at least one article not marked
data-listed="false".
Extensionless article directory names must not contain dots /
Extensionless article URL conflicts with a site root path
Rename the article directory and matching article ID. The slug must not
contain a dot or match another generated root path.
Unsafe SITE_ICON_PATH / SITE_ICON_PATH_THEME requires SVG class
Use a readable relative .svg file in the staged tree. A
themed icon must contain colour-out and
colour-in class tokens.
Article, heading, or generated-marker format error
Apply the ARTICLE FORMAT and GENERATED BLOCKS rules. Keep indexed
headings and empty generated markers on one source line.
Missing or invalid generated block files appear as visible missing blocks.
They do not produce these fatal diagnostics.
SERVING
Local file access
Generated documentation may be bundled with a project and opened directly
as site/index.html through file://. Single-page
output works directly; multi-page output requires
SITE_URL_STYLE=html. Contact submission is for HTTP or HTTPS
deployment.
Local HTTP inspection
Serve site/ without changing its relative paths. The Python 3
command provides HTTP only, creates no project files, and does not rewrite
extensionless URLs. Stop it with an interrupt.
Local HTTP serverRun after a build from the project rootraw
# Run from the project root after make build.
python3 -m http.server 8000 --directory site
Static deployment
Deploy site/ without changing its relative paths. Serve contact
forms over HTTPS. The access log must retain query strings for contact
messages to be available there.
OpenBSD httpd example
The OpenBSD httpd(8) example serves the site and rewrites
extensionless article paths. Replace its host and document-root paths,
validate the configuration, and reload httpd.
Its final rule appends .html internally to a missing final
path component without a dot. Keep specific rules before it.
/etc/httpd.confReplace host, certificate, and document-root pathsraw↓
server "arachnogoat.com" {
listen on * port 80
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block return 301 "https://$HTTP_HOST$REQUEST_URI"
}
}
server "arachnogoat.com" {
listen on * tls port 443
tls {
certificate "/etc/ssl/arachnogoat.com.fullchain.pem"
key "/etc/ssl/private/arachnogoat.com.key"
}
root "/htdocs/arachnogoat.com"
# Required when arachnopress uses SITE_URL_STYLE=extensionless.
location not found match "/[^./]+$" {
request rewrite "$DOCUMENT_URI.html"
}
}
CAVEATS
Trusted input
Article HTML and a themed SITE_ICON_PATH SVG are trusted
author content. The build escapes generated text and code, but does not
sanitize embedded article or SVG markup. Review both before building.
Single-page scale
Single-page output contains every generated article and highlighted code
span. Large code-heavy collections increase HTML size and DOM cost. Use
multi-page mode, focused excerpts, raw downloads, or
HIGHLIGHTER=none when that cost becomes material.
Browser compatibility
CSS and popover support varies on older clients. No JavaScript
compatibility layer is provided.
SEE ALSO
sh(1), make(1), awk(1),
sed(1), tar(1), and cksum(1). On OpenBSD,
see also httpd(8) for the optional deployment example.
LICENSE
arachnopress is distributed under the BSD 3-Clause license. See
tools/license.txt or the License control in the site metadata.
Bundled colour palettes retain their upstream values and third-party
licences; see
THIRD_PARTY_NOTICES.txt and licenses/.
Patch Notes
bsdsshd.rd
OpenBSD/amd64 installer ramdisk and miniroot targets with sshd access.
The patch adds separate bsdsshd.rd and
minirootXX_sshd.img targets. The ramdisk configures
networking from /auto_install.conf, starts sshd, and leaves
the installer environment available to the remote root user.
The default retains the local installer menu and shell.
RDSSHD_CONSOLE_LOCK=yes removes the local installer userland
session. Boot-loader and kernel console output remain visible.
The target supports remote storage preparation and installation,
including softraid(4) work with bioctl(8). Use
autoinstall(8), install.site(5), and
siteXX.tgz for fully unattended installation.
The patch adds files only. Stock source files, Makefiles,
bsd.rd, minirootXX.img, and
cdXX.iso targets are unchanged. Enhanced targets are
available only through Makefile.rdsshd.
PATCH
The aggregate patch contains the build wrapper, kernel configurations,
overlay patches, ramdisk files, and plain-text reference.
Private rdsshd paths:
obj/build/kernel enhanced kernel objects
obj/build/instbin copy of stock instbin
obj/sshobj static OpenSSH objects
obj/initsrc optional patched init source
obj/initobj optional private init objects
obj/stage generated and overlay files
obj/rdobj ramdisk assembly
obj/bsd* copied enhanced kernels
obj/boot miniroot boot file
obj/mnt miniroot mount point
obj/vnd vnd ownership record
Normal OpenBSD paths used by stock instbin:
${BSDOBJDIR}/distrib/amd64/ramdisk_cd
${BSDOBJDIR}/distrib/special
${BSDOBJDIR}/lib
The enhanced kernel, OpenSSH programs, optional init, staging tree,
ramdisk, media, mount point, and vnd record remain private. The kernel
uses config(8) -b. OpenSSH uses explicit private
MAKEOBJDIR paths. No directory below
sys/arch/amd64/compile is added or used.
Stock instbin
Installer instbin is unmodified. The wrapper runs the normal
obj targets for lib,
distrib/special, and ramdisk_cd. It builds stock
distrib/special/libstubs, invokes the stock
instbin target, then copies the result to
obj/build/instbin below the private object directory.
Component objects, reduced libraries, and crunchgen output retain their
normal paths below ${BSDOBJDIR}. The wrapper does not rewrite
generated crunchgen files. Normal OpenBSD clean targets own these stock
objects. clean-rdsshd owns the private copy and enhanced
outputs only. Enhanced and normal builds or cleans must not run
concurrently when they share instbin objects.
No parent Makefile or SUBDIR list is changed. Normal
top-level builds and cleans do not enter the rdsshd source
directory.
Object checks and media state
BSDOBJDIR must exist with normal OpenBSD ownership and
permissions. The default is /usr/obj, owned by
build:wobj with mode 770. The build rejects source-directory
fallback for both the wrapper and ramdisk_cd.
Normal and enhanced media use distinct boot files, image names, mount
points, and vnd state. Cleanup detaches only the recorded vnd covering
the absolute enhanced image path. A missing, malformed, or reused vnd is
reported and retained.
KERNEL
RAMDISK_CD_SSHD includes stock RAMDISK_CD. It
changes only the inherited stack-protection option, rdroot reservation,
and pty count.
# $OpenBSD$
include "arch/amd64/conf/RAMDISK_CD"
# Keep config(8)'s option append pointer valid while removing the tail.
option RDSSHD_RDROOT
rmoption MINIROOTSIZE
option MINIROOTSIZE=40960
rmoption RDSSHD_RDROOT
pseudo-device pty 16
MINIROOTSIZE=40960 reserves a 20 MiB rdroot. Sixteen ptys
support ssh sessions. SMALL_KERNEL remains enabled.
The temporary RDSSHD_RDROOT option protects
config(8)'s option-list append pointer while inherited tail
entries are removed. It is absent from the final configuration.
Stock installer programs remain in instbin. OpenSSH is built through its
normal Makefiles as separate static PIE executables with zlib disabled.
The ssh(1) client is not included.
The separate executables are /usr/sbin/sshd,
/usr/bin/ssh-keygen,
/usr/libexec/sshd-session, and
/usr/libexec/sshd-auth.
# $OpenBSD$
# bsdsshd.rd overlay.
MKDIR usr/libexec
MKDIR etc/ssh
MKDIR root
MKDIR root/.ssh
COPY ${OBJDIR}/../stage/sshd_config etc/ssh/sshd_config
SPECIAL test ! -s ${OBJDIR}/../stage/ssh_host_ed25519_key || install -c -m 600 -o root -g wheel ${OBJDIR}/../stage/ssh_host_ed25519_key etc/ssh/ssh_host_ed25519_key
COPY ${OBJDIR}/../stage/auto_install.conf auto_install.conf
SCRIPT ${OBJDIR}/../stage/dot.profile .profile
SCRIPT ${OBJDIR}/../stage/install.sub install.sub
SPECIAL chmod 755 install.sub
COPY ${OBJDIR}/../stage/master.passwd etc/master.passwd
COPY ${OBJDIR}/../stage/group etc/group
SPECIAL pwd_mkdb -p -d etc master.passwd; rm etc/master.passwd
COPY ${CURDIR}/rdsshd/root.profile root/.profile
COPY ${OBJDIR}/../stage/authorized_keys root/.ssh/authorized_keys
SPECIAL chmod 700 root root/.ssh; chmod 600 root/.ssh/authorized_keys
SPECIAL cd dev; sh MAKEDEV pty0 ptm
COPY ${OBJDIR}/../stage/bin/sshd usr/sbin/sshd
SPECIAL chmod 511 usr/sbin/sshd
COPY ${OBJDIR}/../stage/bin/ssh-keygen usr/bin/ssh-keygen
SPECIAL chmod 555 usr/bin/ssh-keygen
COPY ${OBJDIR}/../stage/bin/sshd-session usr/libexec/sshd-session
SPECIAL chmod 511 usr/libexec/sshd-session
COPY ${OBJDIR}/../stage/bin/sshd-auth usr/libexec/sshd-auth
SPECIAL chmod 511 usr/libexec/sshd-auth
COPY ${CURDIR}/../../../etc/etc.amd64/login.conf etc/login.conf
The overlay adds sshd configuration, authorized keys, the root profile,
login classes, early network answers, the sshd account, and pty devices.
Root retains stock instbin's -sh argv link. The root profile
leaves sh mode, sets the installer environment, and selects
TERM=vt220. The sshd Port directive is substituted only in
private staging.
The outer image follows the stock amd64 BIOS and EFI miniroot layout. It
uses private boot, mount, image, and vnd paths. The default size is 32768
512-byte blocks, or 16 MiB.
sshd permits root public-key authentication only. Forwarding, user rc,
interactive authentication, passwords, and compression are disabled.
Static PIE executables retain normal OpenBSD OpenSSH compiler and linker
protections.
Port ${RDSSHD_PORT}
HostKey /etc/ssh/ssh_host_ed25519_key
AllowUsers root
PermitRootLogin prohibit-password
AuthorizedKeysFile .ssh/authorized_keys
PubkeyAuthentication yes
AuthenticationMethods publickey
PasswordAuthentication no
KbdInteractiveAuthentication no
Compression no
PermitUserRC no
PrintMotd no
PrintLastLog no
DisableForwarding yes
Authorized keys
RDSSHD_AUTHORIZED_KEYS is mandatory. It may contain one or
more public keys. Blank and comment lines are removed. Every retained
line must pass ssh-keygen(1) public-key validation.
The installed file has mode 0600. /root and
/root/.ssh have mode 0700.
sed -e '/^[ ]*$$/d' -e '/^[ ]*#/d'\
< "${RDSSHD_AUTHORIZED_KEYS}" > ${RDSSHD_STAGE}/authorized_keys
test -s ${RDSSHD_STAGE}/authorized_keys
@_n=0;whileIFS=read -r _key;do\_n=$$((_n + 1));\ if ! printf '%s\n' "$$_key" | \ ${RDSSHD_STAGE}/bin/ssh-keygen -l -f - >/dev/null 2>&1; then \ echo "invalid public key on line $$_n of RDSSHD_AUTHORIZED_KEYS" >&2; \ exit 1; \ fi; \ done < ${RDSSHD_STAGE}/authorized_keys
Build-time configuration test
If RDSSHD_HOST_KEY is empty, the build generates
obj/stage/ssh_host_ed25519_key.test and its public key. These
files exist only to give the newly built sshd -t a host key
while it validates the generated configuration.
The build removes the test configuration and key pair after validation.
They never enter the ramdisk. An interrupted build may leave them below
obj/stage; the next staging pass or cleandir
removes them.
No host key is embedded by default. First boot generates
/etc/ssh/ssh_host_ed25519_key. Normal installer CGI fetches
call feed_random before generation. An unreachable fetch
delays sshd until the normal CGI timeout.
Embedded host key
RDSSHD_HOST_KEY may name an Ed25519 host private key without
a passphrase. The build validates it and copies it to private staging
with mode 0600. The newly built sshd validates the generated configuration
against that staged key. Build-host ssh configuration and keys are not
read or modified.
An embedded key is recoverable from private objects, kernels, images,
and release copies. Key-bearing kernel and media artifacts use mode 0600.
Startup
sshd starts after donetconfig. A live numeric PID greater
than one in /var/run/sshd.pid suppresses restart. The ramdisk
has no process-inspection utility, so this checks liveness only.
install.sub creates /var/run/rdsshd.ready only
after sshd starts. Profiles use this marker because installer exit status
does not establish readiness.
The initial profile invokes install -af with early network
answers. Static IPv4 configuration includes a netmask and default route.
autoconf, dhcp, and none omit them.
Static IPv4 response fileDocumentation addresses use RFC 5737 spaceraw
System hostname = rdinstall
Network interface to configure = em0
IPv4 address for em0 = 192.0.2.10
Netmask for em0 = 255.255.255.0
Default IPv4 route = 192.0.2.1
IPv6 address for em0 = none
Network interface to configure = done
DNS domain name = example.com
DNS nameservers = 192.0.2.53
Default console
Automatic setup starts after the normal installer-menu timeout unless a
local operator selects another action. The profile returns to the local
ramdisk shell after sshd starts.
With RDSSHD_CONSOLE_LOCK=yes, a private static init replaces
/sbin/init only in the enhanced ramdisk. It is built through
the stock distrib/special/init Makefile using the dedicated
wrapper and stock pathnames.h. Patched source and objects stay
private.
The bootstrap child retains inherited /dev/null descriptors.
It never acquires /dev/console as a controlling terminal. The
profile branches before terminal setup, retries setup until the ready
marker exists, then sleeps. It presents no menu or shell. Init restarts
it after exit. The default uses stock init from instbin.
After ssh login, run install. Complete storage preparation
first. At network prompts, select done to retain the active
configuration. Reconfiguration can drop the ssh session.
CAVEATS
Console locking removes the interactive local installer userland. The
boot-loader and kernel consoles remain active. A local operator can still
change early boot state, reset, halt, or deny remote access.
RDSSHD_KERNEL_STACK_PROTECTOR=no deliberately retains
NO_PROPOLICE. This may reduce image size and weakens
mitigation of kernel stack corruption.
Protect an embedded host key, the object tree, every key-bearing image,
and installed copies. Use a distinct host key for each machine identity.
RDSSHD_FSSIZE is not auto-sized. A value too small for the
compressed kernel and boot files causes miniroot assembly to fail.
Network variables become installer response-file answers. The build does
not probe the interface or validate reachability. Incorrect values can
prevent ssh access. A locked image then has no local installer userland
with which to repair the configuration.
BUILD
Prerequisites and base build
Build on OpenBSD/amd64 with src.tar.gz and
sys.tar.gz matching the installed OpenBSD release. Normal
source-build prerequisites apply. The media target requires root for vnd,
mount, device, and ownership operations. Kernel compilation runs as
BUILDUSER.
Apply the patch below /usr/src. The configured
BSDOBJDIR root, normally /usr/obj, must already
exist.
cd /usr/src
patch -p1 < /root/bsdsshd.rd.patch
cd /usr/src/distrib/amd64/ramdisk_cd/rdsshd
make -f Makefile.rdsshd obj
make -f Makefile.rdsshd rdsshd \RDSSHD_AUTHORIZED_KEYS=/root/id_ed25519.pub
Run obj separately. OpenBSD make selects
.OBJDIR at startup and rejects a combined
obj rdsshd invocation. No top-level
/usr/src make obj is required. The wrapper creates only the
normal object links required by stock instbin. Custom components retain
private paths.
Static network
Supply netmask and route with a static IPv4 address. The documentation
addresses below use the reserved 192.0.2.0/24 range.
cd /usr/src/distrib/amd64/ramdisk_cd/rdsshd
make -f Makefile.rdsshd rdsshd \RDSSHD_AUTHORIZED_KEYS=/root/id_ed25519.pub \RDSSHD_KERNEL_STACK_PROTECTOR=no
ssh-keygen -q -t ed25519 -N '' -f /root/rdsshd_host_ed25519_key
cd /usr/src/distrib/amd64/ramdisk_cd/rdsshd
make -f Makefile.rdsshd rdsshd \RDSSHD_AUTHORIZED_KEYS=/root/id_ed25519.pub \RDSSHD_HOST_KEY=/root/rdsshd_host_ed25519_key
Use a distinct host key per machine identity. Protect its source, object
tree, and all resulting images. Derive its public-key fingerprint before
deployment and compare it when establishing host trust.
Repeat rdsshd. Normal dependency rules reuse current stock
instbin components, OpenSSH, private init, and the selected kernel.
Staging, ramdisk, and media are regenerated.
cd /usr/src/distrib/amd64/ramdisk_cd/rdsshd
make -f Makefile.rdsshd rdsshd \RDSSHD_AUTHORIZED_KEYS=/root/id_ed25519.pub
Source or toolchain changes
Clean both ownership domains after source, compiler, flag, or Makefile
changes. A top-level cleandir may replace the three stock
cleans. The next enhanced build recreates required normal object
directories. The wrapper clean never removes stock objects.
cd /usr/src/distrib/amd64/ramdisk_cd
make cleandir
cd /usr/src/distrib/special
make cleandir
cd /usr/src/lib
make cleandir
cd /usr/src/distrib/amd64/ramdisk_cd/rdsshd
make -f Makefile.rdsshd cleandir
make -f Makefile.rdsshd obj
make -f Makefile.rdsshd rdsshd \RDSSHD_AUTHORIZED_KEYS=/root/id_ed25519.pub
Failed instbin trace link
Clean ramdisk_cd before retrying. Its
instbin.map and reduced archives may be incomplete.
RDSSHD_AUTHORIZED_KEYS?=RDSSHD_HOST_KEY?=RDSSHD_CONSOLE_LOCK?= no
RDSSHD_KERNEL_STACK_PROTECTOR?= yes
RDSSHD_PORT?=22RDSSHD_AI_IF?= em0
RDSSHD_AI_HOSTNAME?= rdinstall
RDSSHD_AI_IPV4?= autoconf
RDSSHD_AI_NETMASK?=255.255.255.0
RDSSHD_AI_ROUTE?= none
RDSSHD_AI_IPV6?= none
RDSSHD_AI_DOMAIN?= my.domain
RDSSHD_AI_DNS?= none
RDSSHD_FSSIZE?=32768RDSSHD_MRMAKEFSARGS?= -s 20m \
-o rdroot,minfree=0,bsize=4096,fsize=512,density=4096rdsshd-check:
@if [ -z "${RDSSHD_AUTHORIZED_KEYS}"];then\echo"set RDSSHD_AUTHORIZED_KEYS to a public key file" >&2;\exit1;\fi
@case "${RDSSHD_CONSOLE_LOCK:L}"in\
yes|no);;\
*)echo"RDSSHD_CONSOLE_LOCK must be yes or no" >&2;exit1;;\esac
@case "${RDSSHD_KERNEL_STACK_PROTECTOR:L}"in\
yes|no);;\
*)echo"RDSSHD_KERNEL_STACK_PROTECTOR must be yes or no" >&2;exit1;;\esac
USE
Prepare a non-interactive next boot from the running system.
No boot prompt or console access is assumed.
Whole-device image
minirootXX_sshd.img is a complete disk image. Writing it to
a block device replaces the device's partition table and filesystems.
Verify the output device before writing it.
# Linux example. This destroys the existing contents of /dev/sda.
dd if=./miniroot79_sshd.img of=/dev/sda bs=512
sync
reboot
The running OS or storage stack may deny raw writes to the initial
sectors of the device backing its active root filesystem. Use the
boot-loader method if this cannot be changed remotely.
The image boots in UEFI or BIOS/CSM mode with Secure Boot disabled.
Firmware must already select the target device. The rdroot runs from
memory, so the installer can reuse that device as its target.
GRUB one-shot boot
Copy the uncompressed ramdisk kernel. Do not rely on gzio.
This example was tested in BIOS/CSM mode. UEFI is untested and may lack
display console output. Secure Boot is unsupported. part_gpt
and ext2 match the tested /boot filesystem. The
target layout may require different modules. The GRUB build must provide
the bsd module and
kopenbsd command. Other boot loaders are untested.
Network booting
The matching OpenBSD/amd64 pxeboot(8) can load the
compressed bsdsshd.rd over the network in place of
bsd.rd.
Remote session
After the enhanced kernel boots, connect as root when the configured
address accepts ssh. Perform required storage preparation, then start the
interactive installer.
ssh -i /path/to/private_key root@host.example
# Perform the required storage setup.# Enter "done" at the network prompt to preserve network configuration.
install
RDSSHD_CONSOLE_LOCK=yes presents no local installer menu or
shell.
REMOVE PATCH
Prepare private state before reversal. The target prints the recorded
object path and removes the source obj symlink.
/usr/obj is the default; use the printed path when different.
patch(1) removes added files but leaves their empty parent
directory. rmdir refuses non-empty directories.
Irssi normally places server status and the joined channel in separate
windows. Use /WIN 1 for the first window and
/WIN 2 for the second. The status bar shows the actual window
numbers when they differ.
AUTOMATIC CONNECTION
Add a named network, its server, and the channel to Irssi, then save the
configuration. Irssi will reconnect and join #chat on later
starts.
Persistent Irssi configurationReplace SERVER_PASSWORD before useraw