Self-hosted service
asciimoo/omnom avatar
asciimoo/omnom

asciimoo/omnom: a self-hosted bookmark and feed tool that saves pages as your browser renders them

A web content preservation service

666 stars42 forksGoAGPL-3.0

At a glance

What is it?
Omnom is a Go web application that bookmarks pages, captures the rendered DOM as a snapshot, aggregates RSS and Atom feeds, and follows ActivityPub streams. It solves one problem well and leaves a specific set of others to the operator.
Who is it for?
Adopt asciimoo/omnom if you need a private archive of pages as they rendered, plus feed reading, on hardware you control. Do not adopt it if you want a hosted service, if you cannot run a headless browser next to it, or if you need password logins, because the README states Omnom does not store passwords.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 99 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: bookmarks that survive the page

A bookmark stores a URL. That is a promise the web does not keep. Pages move, paywalls close, single-page applications render nothing useful in a saved HTML file, and a link you saved three years ago may now resolve to a redirect or a 404. Omnom's answer is to store the page itself, captured the way a browser displayed it, so the saved copy includes content that only exists after JavaScript runs. The README frames the project as "A web content preservation service" and lists bookmarking with website snapshots as the first feature.

The audience is narrow and identifiable. It is someone willing to run a Go binary or a container, who wants their reading list and their archive in the same place, and who cares that the archive is theirs. The multiuser web interface and the ActivityPub support suggest small groups or individuals who already live in the fediverse rather than teams buying an archiving product. If you only want read-later links and do not care about the rendered state of a page, the snapshot machinery is cost without benefit.

What happens between a bookmark and a stored snapshot

The dependency list in go.mod is the clearest description of the architecture available. chromedp and its cdproto package drive a Chrome instance over the DevTools protocol, which is how the project captures what the browser rendered rather than what the server returned. The README states this directly: "Websites are captured as your browser renders it - saves the displayed content of dynamic pages as well". That is the mechanism, and it explains why the Dockerfile disables bookmark creation from the web application by default with the comment that no browser is installed in the image.

Around that core sit recognisable Go components. gin and gin-contrib/multitemplate serve the web interface, gorm with the sqlite and postgres drivers handle persistence, gofeed parses RSS and Atom, and go-diff plus the contentdiff directory back the compare and diff views the README mentions for multiple snapshots of the same URL. bluemonday and goldmark suggest HTML sanitisation and Markdown rendering. The activitypub, feed, oauth, storage and validator directories in the repository layout map onto the features listed in the README, so the project is organised by capability rather than by layer.

The consequence of the chromedp choice is operational. Snapshot capture needs a real browser process somewhere reachable, and the Dockerfile's default configuration assumes there is not one, so a container deployment that wants snapshots has to be changed. The single-file binary release has the same requirement outside the binary itself.

Installing omnom from the release binary

The README points at a single-file binary release and notes that you should `chmod +x` it. After that, the documented setup is two commands: generate a configuration file, then start the server. The configuration is read from config.yml and the README warns that the web application must be restarted after the file changes.

bash
chmod +x omnom
./omnom create-config config.yml
./omnom listen

The generated file contains the default values, so the first thing worth reading is the listen address. The sample configuration used by the Docker build listens on 127.0.0.1:7331, and the Dockerfile rewrites that to 0.0.0.0:7331 for container use, with port 7331 exposed. If you are running the binary directly, expect to find the same port in your config.yml and to adjust it if something else already holds it.

Before you can log in you need a user and a token. The README says Omnom does not store passwords and that login requires a one time token, OAuth, or a remote user header. The command line tool covers the token path, and `./omnom help` lists the available commands including create-user and create-token.

bash
./omnom create-user
./omnom create-token [username] login

The token printed by create-token is what you paste into the web interface. If you would rather not run that command each time, the README describes email delivery of login tokens, which requires a valid SMTP configuration in config.yml, and a reverse proxy path where the proxy passes the logged-in username in a header such as Remote-User, trusted through the remote_user_header option. The README adds a constraint on that last route: remote user header authentication cannot be combined with OAuth or open signups.

Running omnom in Docker and what the image changes

The Dockerfile builds the binary with Go, copies it into a runtime image, installs gosu for user and group handling, and starts the server through entrypoint.sh with the command `/omnom/omnom listen`. It declares two volumes, /omnom/config and /omnom/static/data, and the entrypoint runs the binary as a non-root user. If you bind mount only one directory, mount the config volume, because the image moves the SQLite database to ./config/db.sqlite3 and the ActivityPub keys into ./config/ as well.

The image also flips two defaults. It listens on 0.0.0.0:7331 instead of localhost, and it sets create_bookmark_from_webapp to false with the comment "No browser is installed, disable bookmark creation by default". That second change is the one to understand before you deploy. A stock container will run, but the snapshot feature that distinguishes Omnom from a plain bookmark manager will not work until you either supply a browser to the container or point the configuration at one. The README's Docker details live in the wiki rather than the README itself, so the exact configuration keys for that are not documented in the repository's front page.

Where omnom is the wrong tool

The passwordless login model is the sharpest limitation. The README states plainly that Omnom does not store passwords. That is a defensible design, and it removes a class of credential risk, but it means every login depends on either working SMTP, an OAuth provider, or a reverse proxy you already trust with authentication. If you cannot configure SMTP and do not run an authenticating proxy, your users are running create-token from the command line. For a single operator that is fine. For a group of non-technical users it is not.

The second limitation is the browser dependency. Snapshot capture is the feature that justifies the project's existence, and it is also the part most likely to break in a minimal container. The Dockerfile's own comment is evidence that the maintainers consider a browserless deployment a normal, supported state.

The third is scope. Omnom is not a general web archiving system. The README describes snapshots of a URL with resource summaries and diff views, not WARC output, not crawl scheduling, and not the fidelity guarantees that dedicated archiving tools make. If your requirement is a legally defensible archive of a site, this is the wrong layer.

How omnom differs from Wallabag and ArchiveBox

The closest comparison in the self-hosted space is Wallabag, which also stores articles you save for later, also runs on your own server, and also offers a browser extension and a mobile reading experience. The difference is what gets stored. Wallabag extracts the article body and presents it in a reading view, which is a deliberate reduction of the page. Omnom keeps the rendered page and adds a diff view across multiple snapshots of the same URL, so the question it answers is not "what did this article say" but "what did this page look like, and what changed since".

ArchiveBox overlaps more directly, since both capture rendered content. ArchiveBox is built around producing archive formats and running many extractors per URL, and it is typically driven as a capture pipeline. Omnom is built around the reading experience: the same web application holds your bookmarks, your RSS and Atom feeds via gofeed, and your ActivityPub stream, with a documented API for automation. If you want an archive you can query and browse alongside your feeds, Omnom's shape fits. If you want output you can hand to another system, ArchiveBox's shape fits.

Maintenance, licence and the cost of upgrading

The repository is not archived and the last push was on 2026-06-22, so the codebase has moved within the last few months. Releases are spaced a few months apart: v0.7.0 on 2025-09-29, v0.8.0 on 2025-11-26, and v0.9.0 on 2026-01-18. The project is funded through NGI Zero Core, which the README credits along with NLnet, and it carries a Weblate translation project. Those are signs of institutional backing rather than guarantees of a release cadence.

Upgrade cost is dominated by two things. The Go version floor is stated in two places: the README requires go >= 1.24 for a local build, while go.mod declares go 1.26, and the Dockerfile still builds on golang:1.24. That mismatch is worth checking before you pin a build image. Second, the chromedp and cdproto dependencies are pinned to dated pseudo-versions, and browser protocol bindings tend to need updates when Chrome moves, so a source build is more fragile than the release binary.

The licence is AGPLv3. For self-hosting, that is the common case and the obligations are light. The AGPL's network clause matters if you modify Omnom and let other people use it over a network, because that can trigger a source disclosure obligation for your modified version. That is a description of the licence's structure, not legal advice; if you plan to run a modified Omnom as a service, read the licence text or ask a lawyer.

Editorial conclusion

Adopt asciimoo/omnom if you need a private archive of pages as they rendered, plus feed reading, on hardware you control. Do not adopt it if you want a hosted service, if you cannot run a headless browser next to it, or if you need password logins, because the README states Omnom does not store passwords. Verify first that your deployment can reach a Chromium binary for snapshot capture, whether you want the SQLite default or PostgreSQL, and how you will issue login tokens, either through SMTP or the create-token command.

Frequently asked questions

What is asciimoo/omnom?

It is a self-hosted web content preservation service written in Go. The README lists bookmarking with website snapshots, feed aggregation and ActivityPub support as its main capabilities.

How do I install omnom?

The README points to a single-file binary release, which needs chmod +x, and to a Docker image documented in the project wiki. A local build requires go >= 1.24, then go get -u and go build in the repository root.

Does omnom store passwords?

No. The README states that Omnom does not store passwords and that login requires a one time login token, OAuth, or a remote user header.

Can omnom save pages that need JavaScript?

Yes. The README says websites are captured as your browser renders it, so the displayed content of dynamic pages is saved as well. This depends on a browser being available to the capture process, which the Docker image does not include by default.

How do I create a login token for a user in omnom?

The README gives the command ./omnom create-token [username] login, which generates a login or addon token from the command line. Tokens can also be requested through the web interface by email if a valid SMTP configuration exists in config.yml.

Official sources

  1. asciimoo/omnom on GitHub
  2. License: AGPL-3.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/asciimoo-omnom.svg)](https://hysenlabs.com/projects/asciimoo-omnom)