FuguWeb

FUGUWEB(1) General Commands Manual FUGUWEB(1)

fuguweb — build a documentation website for a Perl project

fuguweb [-hq] [--project dir] command [argument ...]

fuguweb init [dir]

fuguweb build [--out dir]

fuguweb check [-v] [--out dir]

fuguweb clean [--out dir]

fuguweb mint-key --purpose word [--type type] --secret path [--signer path] [--bind stem=path] [--email address] [--expires date] [--dir name] [--bootstrap --org word [--url prefix]]

fuguweb import-key --purpose word [--type type] --file path --secret path [--signer path] [--bind stem=path] [--dir name] [--bootstrap --org word [--url prefix]]

fuguweb promote-key --purpose word --retiring path [--secret path] [--signer path] [--bind stem=path] [--dir name]

fuguweb help [command]

fuguweb renders one static website from the documentation that a Perl project already keeps: mdoc(7) manuals, POD sidecars, and Markdown. It runs mandoc(1), lowdown(1) and pod2man(1), and wraps each result in one shared page layout. The output is plain HTML and one stylesheet. There is no templating language and no JavaScript.

A project is a directory that holds a .fuguwebrc file, the site description. Without --project, fuguweb searches up from the current directory for that file. The directory that holds it is the project root. Every relative path in the description, and a relative --out, resolves against the project root.

The build reads nothing from the network, writes nothing outside the output directory, and gives the same bytes for the same checkout.

The options are as follows:

, --help
Print the usage message and exit.
dir
Use dir as the project root, and do not search for one.
, --quiet
Do not print informational messages.

The commands are as follows:

[dir]
Write a starter .fuguwebrc into dir, which defaults to the current directory. The starter names one page and one navigation entry, and it builds once web/index.body.html exists. The command refuses a directory that already holds a description.
[--out dir]
Render the whole site into the output directory. --out overrides the out_dir setting.

The command first checks that every renderer is installed, and names the first one that is not. It runs mandoc(1) in lint mode over every mdoc source, and a warning fails the build. It then writes the stylesheet, the assets and each key directory. It writes each page into each language tree, and then the pages of the top level: the copy of 404.html when the description holds that page, and the forward index.html. Next it writes one page for each manual into each tree. Last, it removes every file of the site that the description no longer names, so a second build over one directory gives the same tree as the first. See THE PAGE LAYOUT and THE OUTPUT DIRECTORY.

[-v] [--out dir]
Check a built site. The command prints one line for each problem and exits with status 5. It prints nothing and exits with status 0 when the site is good.

The checks are these:

  • Every page and asset of the site exists and is not empty.
  • On every page but the forward index.html, the check finds a title, the banner link and the whole navigation.
  • Each of those links takes the form of the kind of its page: <page> on a tree page, ../en/<page> on the key page, and en/<page> on the copy of 404.html at the top level. An absolute URL and a fragment stay as they stand.
  • Every tree page carries the code of its tree as ‘lang’.
  • The forward index.html holds a meta refresh to the entry page of en/. Its one reference is a link to that page, and it holds no navigation.
  • Every local link, manual cross-reference, and fragment resolves.
  • No reference uses a ‘file:’ URL, or reads as a URL scheme.
  • No reference starts with a solidus. The one exception is the ‘<base href="/">’ of the copy of 404.html at the top level, and that page must carry it.
  • Every page of each language tree is reachable from the entry page of that tree, except a page marked unlinked. The pages of the top level and the key page take no such walk.
  • The output holds the site and nothing else, at any depth.

A description with a keys block adds the checks of THE KEY DIRECTORY.

-v, --verbose also prints every external link. The command never fetches one.

[--out dir]
Remove the output directory. The command deletes a tree without asking, so it removes a built site and refuses anything else. See THE OUTPUT DIRECTORY.

With --out the command runs without a loadable description. It then knows the files of the top level and the language trees, by their shape. It refuses a target that holds no stylesheet, because every build writes one.

--purpose word [--type type] --secret path [--signer path] [--bind stem=path] [--email address] [--expires date] [--dir name] [--bootstrap --org word [--url prefix]]
Mint the next key of a purpose. The type defaults to signify, and openpgp is the other type that this release mints. An OpenPGP mint needs --email, and it takes an optional --expires. A mint of another type takes neither option. See KEY ROTATION.
--purpose word [--type type] --file path --secret path [--signer path] [--bind stem=path] [--dir name] [--bootstrap --org word [--url prefix]]
Publish a key that another tool made, under the next name of the purpose. The command takes --purpose, --signer and --bind as a mint of that purpose does, with --file beside them. The type defaults to signify, and openpgp and x509 are the other two. An issuer makes an x509 certificate, so this command is the one that publishes one. It takes no --email and no --expires, because it generates no key. The block of an imported openpgp key therefore carries no email, and the Web Key Directory serves no file for the address of its user id. The KEYS file of its own directory still holds that key. The Encryption field of security.txt can still name it, when that directory is the one that names the contact. A directory with no keys block takes one from --bootstrap, --dir, --org and --url, as a mint does. --secret names the private half of the key in --file, and that file must stand. The command writes no key file of its own, and that private half signs the binding of the new key. A mint refuses a --secret that stands already, because a mint writes one. See KEY ROTATION.
--purpose word --retiring path [--secret path] [--signer path] [--bind stem=path] [--dir name]
Make the next key of a purpose current, and retire the key that was current. See KEY ROTATION.
[command]
Print the usage of the tool, or of one command.

The description is .fuguwebrc at the project root. A setting is one name = value line. A block opens with its type, a name, and a brace, and closes with a brace on a line of its own. A name that holds whitespace takes double quotes. A block with a language code in front of its name is a translation, and LANGUAGES states its rules. A ‘#’ starts a comment, and no value can hold one.

The settings are as follows:

The name of the site. It ends the title of every page, and it is the text of the banner link. This is the one required setting.
The output directory. The default is web/build.
The source directory. The default is web.
The front page of each language tree, and the target of the banner link. The default is index.html.
The prefix that a module name drops. The default is lib.
The operating system name in the footer of every rendered manual. The default is ‘OpenBSD’.
The manual host for a cross-reference to a manual outside the site. The default is https://man.openbsd.org/.
The path of the base stylesheet. By default the build searches for the sheet that ships with the tool.

A path setting must not step out of the project.

The block types are as follows:

One navigation entry, in file order. Every page carries it, except the forward index.html at the top level. The block must set a label.
"file"
One page of each language tree, named by its file in the tree. The name must be one path segment. The block sets a title, which defaults to the file name, and exactly one source:
An HTML body fragment in the source directory.
A Markdown file, relative to the project root. lowdown(1) renders it, and the HTML that it holds stays as written.
= yes
The generated manual index. The page can also set a body, which opens the index below its heading.

unlinked = yes marks a page that no other page links to, such as 404.html, and the reachability check skips it.

name
One group of mdoc(7) manuals. The name is one word of lowercase letters, digits and hyphens, and it is the fragment identifier of the group on the index page. Two groups must not share a name. The block must set heading, the text of the heading of the group, and dir, the directory of the sources. An optional namespace prefixes each manual name, so man/fugu/Daemon.3p with the namespace ‘Fugu::’ becomes ‘Fugu::Daemon(3p)’.
name
One group of POD sidecars. The name follows the rules of a manuals name. The block must set heading and dir, and dir must lie below module_root.
"name"
One published key directory. A description can hold several, and two blocks must not name one directory. See THE KEY DIRECTORY.

The load fails when a block names no source or two sources, when two blocks name one page, or when a group directory does not exist. A symlink at the top level of the source directory fails the load. A symlink below the source directory on the path of a fragment that the build reads fails it too. A group block that sets anchor, or whose name is a heading text, fails the load. The message names the block to write in its place, such as ‘write manuals commands with heading = Commands, and no anchor’. Every message names the file and the block at fault.

English is the default language, and its code is ‘en’. A translation is a block with a language code in front of its name. The code is two lowercase letters, per ISO 639-1. The block translates the block of one argument of the same type and name:

page "index.html" {
	title = Home
	body  = index.body.html
}

page de "index.html" {
	title = Start
	body  = de/index.body.html
}

manuals commands {
	heading = Commands
	dir     = man/fuguweb
}

manuals de commands {
	heading = Befehle
	dir     = man/de/fuguweb
}

The languages of a site are ‘en’ and each code that a translation names. The build writes one tree for each language, and each tree holds the same pages. A page, a nav, a manuals and a modules block can take a translation:

, body and markdown.
.
, modules
and dir.

A page of a language takes each value of its translation, and the value of the default block where the translation holds none. A page whose body comes from the default source carries ‘lang="en"’ on the element that holds the body. The index page marks each part on its own element instead: the title, the body, each group heading and each manual description. On another tree, each part of the default language carries ‘lang="en"’.

The dir of a group translation mirrors the default dir. A manual of a language comes from the file of the same relative path in that directory, and from the default source when the directory holds no such file. A modules translation mirrors the umbrella sidecar as well. A manual keeps the page name of its default source in every language.

The load fails when a code has another shape, or is ‘en’. It fails when a translation names no default block of its type, when it comes twice for one name and one language, or when it sets a setting outside its list. It fails when a manual source of a translation dir has no source of the same path in the default dir. A keys block and a key block take no translation.

Every source becomes one HTML body fragment, and the build wraps each fragment in one fixed layout: the head, a banner, the navigation, the fragment, and a footer. A project decides the site name, the navigation, and the footer prose.

Every page of the description sits in the tree of its language, below <code>/ of the output. English is the default language, and its tree en/ holds every page of the description. The ‘html’ element of a page carries the code of its tree as ‘lang’. The banner and the navigation of a tree page name each page of the tree by its file name. An absolute URL stays as it stands. After the navigation entries, a tree page links the same page in each other language of the site, as ../<code>/<page>. The code is the text and the ‘hreflang’ of each link. A site of one language carries no such link.

The top level holds two pages for the host. The forward index.html leads to the entry page of en/ with a meta refresh, and holds one link to it. Its title is the site name alone. It carries no banner, no navigation and no footer, and it loads no stylesheet. When the description holds the page 404.html, the top level holds one more copy of it, with ‘<base href="/">’. A host serves that copy at each path that no file answers, and each relative link of the page resolves from the root of the origin. Its banner and its navigation therefore link en/<page>.

On every page but the forward, the title is the title of the page, an em dash, and the site name. The banner links the site name to the entry page. The navigation lists each nav block, in file order.

A footer.body.html in the source directory becomes the footer prose of each tree page. A language takes its own footer from <code>/footer.body.html in the source directory, and the default one when it has none. The key page and the pages of the top level carry no footer. A project without one gets no footer element.

The base stylesheet ships with the tool as share/fuguweb/style.css. The build copies it to style.css at the top level of the output, outside every tree. A tree page and the key page link it as ../style.css, and the copy of the 404 page links it as style.css. The stylesheet setting names another sheet. A stylesheet that is not found fails the build.

A manuals group reads its directory for the plain files *.1, *.3p, *.5, *.7 and *.8. It sorts them by section, in the order 1, 3p, 5, 7, 8, and then by file name. The build renders each source with mandoc(1) and names the page ‘<name>.<section>.html’. A cross-reference to a manual of the site becomes a local link. Any other cross-reference links to the man_url host.

A modules group finds every .pod file below its directory, and the sidecar that names the directory itself: lib/App/FuguWeb.pod is the umbrella of lib/App/FuguWeb/. The manual name is the path below module_root with ‘::’ between the segments, and the section is ‘3p’. The build renders each sidecar with pod2man(1) and then mandoc(1), so a module page carries the same layout as a hand-written manual. The page date is the date of the last commit, from git(1). A ‘L<>’ reference renders as text, not as a link.

The index page is the page block with index = yes. Its heading is the title of the page, and its body, when it sets one, follows the heading. The index writes no prose of its own. Each group then adds one heading, with the group name as its fragment identifier and the heading of the language as its text, and one list of its manuals. Each entry links the page and shows the one-line description, from the first Nd macro of an mdoc source or the NAME section of a sidecar. A group with no manual adds nothing.

The source directory holds the body fragments, the footer, and the assets. Every plain file directly in it that the build does not render is an asset, and the build copies it as it stands. A *.body.html fragment, a *.md file, and a dot file are not assets. robots.txt and CNAME therefore need no entry in the description, and notes for the maintainers are not published.

The build copies each asset to the top level of the output, outside every language tree. A tree page sits one directory below the top level. A body fragment, a Markdown page and the footer of a tree page therefore link an asset as ../<asset>.

The body of 404.html is the one exception. The build writes it into each tree, and into the copy at the top level. The check resolves each link of that copy against the top level, so there it refuses ../<asset>. Each relative link of an asset fails in one of the two copies, and a link that starts with a solidus fails in both. The body fragment or Markdown page of 404.html must therefore link an asset with an absolute URL, such as ‘https://example.org/logo.png’.

The build rejects a symlink at the top level of the source directory. It also rejects a symlink on the path of each fragment that it reads. The rule holds for the file, and for each directory between the source directory and the file. These fragments are the body fragment of each page in each language, the footer, and the footer of each language. An asset or a fragment would publish the bytes of the target, from anywhere on the machine.

The top level of the output holds the two pages for the host, the stylesheet and the assets. Each language tree is one flat directory of pages below it. The key directory and the well-known tree are the other parts below the top level.

The build writes plain files only. It refuses a symlink on any path that it writes, before it writes one file. It owns a plain file at the top level, and a plain file directly in a directory of the top level whose name has the shape of a language code. It also owns a file that the site names, and a file that has a shape of the key directory. A page of an older flat layout, and the tree of a language that the description dropped, are therefore removed. The build keeps every other entry, reports it, and never removes it. clean removes what the build owns, and a directory of the site, of a language tree, or of the key tree. It refuses every other entry, and names it, so it never removes a file that the build keeps.

Both build and clean refuse a target that no build may own: the root of the filesystem, the home directory, the project root, and any directory that holds the project. They refuse the source directory and every directory below it. The output directory of the description is the one exception, because the default output directory sits inside the default source directory.

The build stages every manual source in .man/<code> inside the output directory, one directory for each language, so that mandoc(1) resolves a cross-reference against the manuals of the site. The build removes the staging directory when it ends. It refuses a staging directory of another shape, and so does clean.

An organization publishes its public keys under one prefix, so a consumer can fetch a key and verify a release with it. A keys block describes that directory, and the build publishes it. A description can hold several such blocks, and each one names one directory. Two blocks must not name one directory.

Each directory holds its own root of trust, and a binding never crosses a directory. A publisher that keeps two trust domains therefore keeps two directories in one site. The site-level files stay single: one security.txt, and one Web Key Directory that gathers every address.

keys "keys" {
	org     = fugubsd
	contact = mailto:security@fugubsd.org
	expires = 2027-09-07T00:00:00Z
	url     = https://www.fugubsd.org/keys
}

key "fugubsd-1-release" {
	status = current
	since  = 2026-09-07
}

The block name is the directory, one path segment below the source directory. A name of two lowercase letters is refused, because a language tree takes a name of that shape in the output. The settings are as follows:

The organization word, the first field of every key name. This setting is required. Two blocks must not name one word. A key name holds no directory name, and the serial of a purpose counts inside one directory, so a mint of the second block would write a name that the first block publishes.
The ‘Contact’ field of security.txt.
The ‘Expires’ field of security.txt, an RFC 3339 timestamp. contact and expires stand or fall together. A site publishes one security.txt, so one block alone names the two.
The published prefix of the directory, an absolute URL.

Each key block names the stem of one key file in the directory. The file extension gives the type: .pub is a signify(1) key, .asc is an armored OpenPGP key, and .pem is one X.509 certificate. The block sets a status of current, next or retired, and optional since and until dates. An OpenPGP key can set an email and a fingerprint of 40 hexadecimal characters. A certificate can set a fingerprint of 64 hexadecimal characters, which is the SHA-256 of its DER form.

One signify key of the directory is the root of trust, and its purpose word is root. The current root key signs the manifest, and every other key of the directory is a subordinate key. A binding is a detached signature by one key over the public key file of another key, and its name is <target file>.<signer stem>.<ext>. The extension is sig for a signify(1) signer, asc for an OpenPGP signer, and p7s for an X.509 signer. A binding needs no key block: its name holds the target, the signer and the type.

The directory must hold a SHA256 manifest and its SHA256.sig signature. The build copies each key file, each binding file and the manifest pair byte for byte. It signs nothing, and it verifies no SHA256.sig. For the example above it generates these files:

keys/KEYS
Every OpenPGP key, when the set holds one.
keys/index.html
The human page, with the serial, purpose, type, status, fingerprint, dates and bindings of each key. The row of a certificate names its subject and its validity dates beside the fingerprint. The page is a page of the default language: its banner and its navigation link ../en/<page>, and it carries no footer.
.well-known/openpgpkey/hu/<hash>
The Web Key Directory file of each email address. The file holds the keys of that address of every directory, in publication order.
.well-known/openpgpkey/policy
The Web Key Directory policy file.
.well-known/security.txt
The RFC 9116 file, when a block sets a contact. The Encryption field names the current OpenPGP key of that block.

check reads the source key directory, so the answer does not depend on a build. It checks that every key file has a block and every block has a file, that every name matches the key name pattern or the binding name pattern, that every purpose holds exactly one current key, that the directory holds one current signify key of the purpose root, that each binding verifies against the public key of its signer and holds the retention rule, that the manifest names every key file and every binding file with the digest that it has and nothing else, that every declared fingerprint equals the computed one, and that the signature has the shape of a signify(1) signature.

One report reads the whole site. The block which names the contact and a url must hold a current OpenPGP key of its own, because the Encryption field names no key of another directory. The check reports that block when another key directory holds such a key. A site that publishes no OpenPGP key at all writes no such field, and that one is no problem.

The retention rule holds each binding of the directory. A binding whose signer is current or next must target the current root key. A binding whose signer is retired must target a key of its own purpose with a higher serial. A signify binding verifies with no command. A binding of another type needs gpg(1) or openssl(1), and an absent command is a problem.

The check reads the validity of each current and next key that carries one. It reads the expiry of an OpenPGP key with gpg(1), and the ‘notBefore’ and ‘notAfter’ of a certificate from the certificate itself. An expiry that passed is a problem, and so is a certificate whose ‘notBefore’ has not come. An expiry within 30 days of a current key is a problem when its purpose holds no next key, because one rotation runs in two steps. A signify key carries no date, and an OpenPGP key with no expiry reaches no rule.

A key verb fails for none of these reports, so mint-key and import-key each run on a directory that check rejects for a date. An expired current key is still current when the verb reads its work back, so a step of that purpose could never be made. The 30-day report reads the whole directory, so it would fail a step of another purpose, which writes no next key of the purpose that the report names.

promote-key fails when the key that retires is an OpenPGP key whose expiry passed. That key signs the chain binding, and gpg(1) signs nothing with an expired key. Promote the successor before the current key expires.

Every key verb writes one key directory. --dir names it. A description with one keys block needs no name, and one with several refuses a step without it.

A consumer verifies a release against the copy of the public key that it holds. A release that a new key signs therefore fails in each consumer that holds the old copy. One rotation runs in two steps.

One signify key of the directory is the root of trust, and its purpose word is root. The current root key signs the manifest, and every other key binds to it. mint-key and import-key refuse a key of another type for the root purpose, and the mint refuses before it generates one. Every step takes the private half of the current root as --signer, with two exceptions below.

mint-key generates the next key of the purpose, writes its public half into the key directory, and writes its private half to --secret with no group and no other permissions. The new key takes the status next, or current when the purpose holds no current key. --secret must not name a file that exists, and must not name the signer. A mint refuses a purpose that already holds a next key. A mint of a subordinate purpose also writes the binding of the new key over the public key file of the current root.

promote-key makes the next key of the purpose current, and retires the key that was current with an until date. --retiring names the private half of the key that retires, and that key signs the chain binding over the key which takes its place. A consumer that trusts the old key therefore reaches the new one. The promote removes the root binding of the retired key, and the chain binding stays published. A retired key keeps its file and its URL, so a release that it signed still verifies.

signify(1) generates a signify key. The public half is one comment line and one key body.

gpg(1) generates an openpgp key: one Ed25519 primary key with one Curve25519 encryption subkey. --email names the address, which is the one user id of the key, and a correspondent encrypts to the subkey. Both halves are armored text. The key block of the new key takes that email and the fingerprint, which the command reads from the key that it generated.

--expires is a date of the form YYYY-MM-DD, and the key expires at the start of that date, in UTC. The date must be after the day of the run. The day of the run and each earlier date fail the mint, and the reason names the date that you gave. A mint with no --expires makes a key with no expiry. A key directory retires a key with an until date, so a rotation needs no expiry on the key itself.

An issuer makes an x509 certificate, such as a code signing certificate of Apple Developer ID, so no verb mints one. import-key publishes it: --file names one PEM certificate, and --secret names its unencrypted PEM private key. The verbs take no passphrase, so convert a PKCS#12 file with openssl pkcs12 first. The private key signs the binding of the new key, and openssl(1) makes that detached CMS signature. The key block of the new key takes the fingerprint, which the command reads from the certificate that it published. The renderer knows no issuer by name, and the root manifest vouches for the certificate. A renewal is a rotation: the new certificate enters as next, and a promote retires the old one.

The first mint of the root purpose finds no root. That key is current at once, it signs its own manifest, and it takes no --signer. A directory that already holds keys takes one --bind stem=path for each current key and each next key of a subordinate purpose, and the step writes the binding of every one of them again. It refuses before it writes when one private half is absent.

A directory with no keys block takes one from the command line: --bootstrap states the intent to make that directory, --dir names it, which defaults to keys, --org names the organization word, and --url names the published prefix. A second key directory of a site arrives the same way.

A step without --bootstrap refuses a --dir that no keys block names. Each key directory holds a root of trust of its own, so a mistyped name would otherwise publish a second root in silence. mint-key and import-key take the option, and promote-key makes no directory.

A root promote signs the manifest with the key that it makes current, so it takes --secret and no --signer. It takes one --bind for each key in force, as the first root mint does, and it removes each binding of the retired root but the chain binding that names it.

Each step verifies the new signature and each new binding against the published public key of the signer before one byte reaches the key directory, and a step that fails puts every file back. On success the command prints one name=value line for each fact: the file name, the stem, the serial, the status, the retired name of a promote, and the digest and the URL of a new key file.

The command reaches no network and holds no token. The caller stores the private key and commits the public one.

.fuguwebrc
The site description, at the project root.
share/fuguweb/style.css
The base stylesheet, which ships with the tool.
web/footer.body.html
The footer prose, in the default source directory.
web/<code>/footer.body.html
The footer prose of one language.
web/build/.man
The staging directory of the build, in the default output directory.

fuguweb exits with one of the following values:

0
Success.
1
General error.
2
Invalid arguments.
3
Configuration error, or no .fuguwebrc found.
4
The build failed: a renderer failed, or a manual source is malformed.
5
The check found a problem.
6
A renderer, or a command of a key step such as signify(1), gpg(1) or openssl(1), is not installed.

Start a site, write its front page, build it, and check it:

$ fuguweb init
$ mkdir web
$ echo "<h1>Home</h1>" >web/index.body.html
$ fuguweb build
$ fuguweb check

Build into another directory, from anywhere in the project:

$ fuguweb build --out /tmp/site
$ fuguweb check --out /tmp/site

Publish the root key and the first release key of an organization:

$ fuguweb mint-key --purpose root --bootstrap \
	--secret ~/keys/root-1.sec --org fugubsd \
	--url https://www.fugubsd.org/keys
$ fuguweb mint-key --purpose release \
	--secret ~/keys/release-1.sec --signer ~/keys/root-1.sec

Publish an OpenPGP contact key that expires at the start of 2028:

$ fuguweb mint-key --purpose contact --type openpgp \
	--email security@fugubsd.org --expires 2028-01-01 \
	--secret ~/keys/contact-1.sec --signer ~/keys/root-1.sec

Publish a code signing certificate, whose private key stays with the publisher:

$ fuguweb import-key --purpose sign --type x509 \
	--file ~/keys/sign-1.pem --secret ~/keys/sign-1.key \
	--signer ~/keys/root-1.sec

Rotate the release key:

$ fuguweb mint-key --purpose release \
	--secret ~/keys/release-2.sec --signer ~/keys/root-1.sec
$ fuguweb promote-key --purpose release \
	--signer ~/keys/root-1.sec --retiring ~/keys/release-1.sec

Rotate the root key. The promote writes the binding of each key in force again, so it takes one --bind for each of them:

$ fuguweb mint-key --purpose root \
	--secret ~/keys/root-2.sec --signer ~/keys/root-1.sec
$ fuguweb promote-key --purpose root \
	--secret ~/keys/root-2.sec --retiring ~/keys/root-1.sec \
	--bind fugubsd-2-release=~/keys/release-2.sec \
	--bind fugubsd-1-contact=~/keys/contact-1.sec \
	--bind fugubsd-1-sign=~/keys/sign-1.key

Publish a second key directory, for the keys of one project:

$ fuguweb mint-key --purpose root --dir project --bootstrap \
	--secret ~/keys/project-root-1.sec --org fuguproj \
	--url https://www.fugubsd.org/project
$ fuguweb mint-key --purpose release --dir project \
	--secret ~/keys/project-release-1.sec \
	--signer ~/keys/project-root-1.sec

gpg(1), lowdown(1), mandoc(1), openssl(1), pod2man(1), signify(1), mdoc(7)

The module documentation is in the .pod sidecars beside lib/App/FuguWeb/.

E. Foudil and Y. Shafranovich, A File Format to Aid in Security Vulnerability Disclosure, RFC 9116, April 2022.

Dick Olsson <hi@senzilla.io>

October 21, 2026 OpenBSD