Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

3 Commits

Repository files navigation

ArchiveNet

NodePlatform

A self-hosted, open-source alternative to Archive.org and ArchiveBox. Paste a URL into the address bar and ArchiveNet renders it in headless Chrome, then saves a real, self-contained HTML snapshot, a PDF, and a thumbnail — all on your own disk, under your own control.

Contents

Features

FeatureWhat it does
ArchivePaste a URL, get back HTML + PDF + a thumbnail, saved to disk.
Recent galleryThumbnails of the most recently archived sites, right below the address bar.
Recursive archivingOptionally crawl and archive an entire site instead of just the page you typed (warns you first — it's much heavier).
SearchLive search across every archived URL, including sub-links, from the top bar.
SnapshotsA thumbnail grid of every archived site; click through to a Wayback-Machine-style calendar of every date it was archived. Delete a single day's snapshots from the calendar, or an entire site's history from the grid.
Export / ImportBack up everything to a single .zip and restore it later — including onto a different OS.
Dark modeFollows your system theme by default; toggle it manually from the top bar. The animated background changes mood with it — soft pastels in light mode, a glowing aurora in dark mode.
LoggingPrints progress to the terminal as pages are archived, and appends every archive request (URL + requester IP) to traffic.log.

Installing

Prerequisites:Node.js 18 or newer. On Linux you'll also need a handful of Chromium system libraries — see Requirements.

git clone <this-repository-url>
cd ArchiveNet
npm install

The first install downloads a bundled Chromium via Puppeteer's postinstall script. If your environment blocks install scripts (e.g. npm's allow-scripts guard, or a locked-down CI runner), approve it first, then install again:

npm approve-scripts puppeteer
npm install

Running on a Workstation

For personal, local-only use — nothing else needs to reach it:

npm start

Open http://localhost:3000, archive some pages, and press Ctrl+C in the terminal when you're done. That's the whole workflow: no background service, no database, just this one process while you're using it. Archived files land in archived/ right next to the project.

Use a different port if 3000 is taken:

PORT=8080 npm start

Managing the Service

This section is for running ArchiveNet continuously — e.g. always-on on a home server or NAS — as opposed to starting it only when you want to use it.

Starting / stopping / restarting, using pm2 as a process manager:

npm install -g pm2
pm2 start server.js --name archivenet # start
pm2 stop archivenet # stop
pm2 restart archivenet # restart
pm2 logs archivenet # tail logs

Running it as a systemd service instead is covered under Hosting as a Server — that also makes it survive a reboot.

Updating to a new version:

git pull
npm install
pm2 restart archivenet # or just re-run `npm start` if you run it in the foreground

npm install re-checks Puppeteer's bundled Chromium and only re-downloads it if the required version changed.

Backing up: everything ArchiveNet knows lives under archived/. Click Export in the top bar (or hit GET /api/export) to download it as a single .zip — see Export and import for the full picture, including moving to a different OS entirely.

Changing the port: set the PORT environment variable before starting. With pm2, set it on the first launch (PORT=8080 pm2 start server.js --name archivenet) — pm2 remembers the environment from that run.

Hosting as a Server

ArchiveNet is a plain Node/Express process — any host that can run a long-lived Node service works. A typical self-hosted setup:

  1. Get the code onto the server and run npm install there (Chromium is downloaded per-platform, so don't just copy node_modules/ from a different OS).
  2. Keep it running with pm2 (see Managing the Service) and make it survive reboots:
    pm2 save
    pm2 startup # wires pm2 into your OS's boot process
    
    Or use a systemd unit instead:
    # /etc/systemd/system/archivenet.service[Unit]Description=ArchiveNet
    After=network.target
    [Service]WorkingDirectory=/opt/archivenet
    ExecStart=/usr/bin/node server.js
    Environment=PORT=3000
    Restart=on-failure
    User=archivenet
    [Install]WantedBy=multi-user.target
    Then sudo systemctl enable --now archivenet.
  3. Put a reverse proxy in front of it (nginx, Caddy, etc.) to handle TLS and your domain name, proxying to http://127.0.0.1:3000. Caddy example:
    archive.yourdomain.com {
    reverse_proxy 127.0.0.1:3000
    }
    
    If you plan to use recursive archiving on larger sites, raise your proxy's read/response timeout (e.g. nginx's proxy_read_timeout) — the request to /api/archive doesn't return until the whole crawl finishes, and a big one can take a while.
  4. Persist the archived/ directory. It holds every saved archive, so back it up like you would a database and, if you containerize the app, mount it as a volume rather than baking it into the image.
  5. Access control. ArchiveNet has no authentication of its own — anything reachable can trigger an archive and browse archived/. If exposing it beyond your local network, put it behind your reverse proxy's auth (e.g. Caddy's basic_auth, an nginx auth_basic block, or a VPN/tailnet) rather than the open internet.

Docker (optional)

If you prefer a container, Puppeteer's official base image already includes Chromium and its dependencies:

FROM ghcr.io/puppeteer/puppeteer:22.15.0
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]

Mount archived/ as a volume so archives survive container restarts:

docker build -t archivenet .
docker run -d -p 3000:3000 -v $(pwd)/archived:/app/archived --name archivenet archivenet

How It Works

How pages are captured

Getting a faithful, self-contained snapshot from a live page takes more than one page.content() call, so lib/archiver.js does the following for every URL it archives:

  1. Loads the page and waits for the network to go idle.
  2. Scrolls through the full page and waits again. Lots of sites only fetch images (or trigger other lazy-loaded content) once an element scrolls into view; without this step those images are missing from both the PDF and the saved HTML.
  3. Captures the PDF and thumbnail from that fully-loaded state.
  4. Inlines every stylesheet, image, and font it saw load as a data: URI directly into the HTML, and rewrites any it couldn't capture (too large, blocked, etc.) to an absolute URL instead of a relative one. This is what makes page.html open correctly on its own, later, on another machine, with no dependency on the original site still being up or reachable at the same relative paths.
  5. Strips <script> tags from the saved HTML. The DOM has already been fully rendered by that point, so scripts add no visual value in a static snapshot — keeping them would only risk them re-executing against a site that's since changed or gone offline (broken widgets, tracking pings, JS errors).
  6. Rewrites <a href> links to absolute URLs so they still work (by going back to the live site) when you open an archived page later.

Known limitation: assets loaded from Chromium's disk cache (rather than over the network) can occasionally fail to inline; when that happens the archiver falls back to an absolute URL for that one asset rather than failing the whole capture.

Recursive archiving

By default, ArchiveNet only archives links found directly on the page you typed in (up to MAX_SUBLINKS, 15). Checking Recursive under the address bar archives the whole site instead: it follows same-domain links breadth-first — pages found on those sub-pages get crawled too, and so on — until either there's nothing left to follow or it hits the MAX_RECURSIVE_PAGES safety cap (100 pages, including the main one). All of it still lands in the same place: archived/<domain>/<timestamp>/sub-links/<path>/<timestamp>/, exactly like non-recursive sub-links do.

The UI warns before you enable it because a full-site crawl is dramatically heavier than a normal archive: many more pages means many more HTML/PDF/thumbnail files (a lot more disk space) and a much longer archive run, since every page still gets the full capture treatment (scroll, wait, inline assets, PDF, screenshot) one at a time. If the site has more pages than the cap allows, the response — and the completion toast — say so rather than silently archiving only part of the site without telling you.

Directory layout

archived/
example.com/
2026-07-25_21-35-56/ <- the URL you typed (https://example.com/blog/post)
page.html
page.pdf
thumbnail.jpg
metadata.json
sub-links/
blog/
post-1/
2026-07-25_21-36-10/ <- https://example.com/blog/post-1, found on that page
page.html
page.pdf
thumbnail.jpg
metadata.json
  • The URL you type always becomes archived/<domain>/<timestamp>/, whether or not it has a path.
  • Same-domain links discovered on that page are archived inside that same folder, under sub-links/<path>/<timestamp>/ (see Recursive archiving for how many).
  • The Search, Snapshots, and calendar views are all derived by walking this directory tree (lib/history.js) — there's no separate database.

Export and import

Everything ArchiveNet knows is just files under archived/ — folder and file names use only plain ASCII, so the archive tree itself is already portable across Windows/macOS/Linux. Export/Import just wraps that in a single file for convenience:

  • Export (top bar, or GET /api/export) downloads a .zip of the entire archived/ directory.
  • Import (top bar, or POST /api/import with the zip as a multipart/form-data field named archive) extracts that zip into archived/ on whatever machine you run it on. Existing archives already on that machine are kept — importing merges rather than replaces, so it's also safe to combine two machines' archives into one.

To move to a different OS: click Export on the old machine, copy the .zip over by whatever means (USB drive, cloud storage, scp, etc.), install ArchiveNet on the new machine, start it, and click Import.

Dark mode

ArchiveNet follows your system's light/dark preference (prefers-color-scheme) the first time you open it. Use the 🌙/☀️ toggle in the top-right of the app bar to override that — your choice is remembered in the browser (localStorage) and takes precedence over the system setting from then on, independently per browser/device.

Logging

While it's running, ArchiveNet prints one line per event to the terminal, each timestamped:

[2026-07-26T05:09:50.014Z] Archive requested: https://example.com/ from ::1
[2026-07-26T05:09:51.903Z] [::1] archived https://example.com/
[2026-07-26T05:09:51.920Z] Archive completed: https://example.com/ (0 sub-link(s)) for ::1

For a recursive or many-sub-link archive, every page gets its own archived <url> line (or failed <url>: <reason> if that one page couldn't be captured) as it happens, so you can watch the crawl's progress in real time rather than waiting on one big response at the end.

Every top-level archive request also appends one line to traffic.log in the project root — the website that was requested, followed by the requester's IP:

2026-07-26T05:09:50.014Z https://example.com/ 127.0.0.1
2026-07-26T05:10:00.147Z https://www.iana.org/help/example-domains 192.168.1.42

(Sub-links discovered and archived along the way show up in the terminal output, not as separate traffic.log lines — the log tracks requests, one per site a client actually asked for.) The IP is always normalized to IPv4 where possible — Node's dual-stack server reports IPv4 clients as an IPv6-mapped address (::ffff:1.2.3.4) or the IPv6 loopback (::1), and both get converted back to plain IPv4 before logging. If ArchiveNet is behind a reverse proxy, the IP recorded is taken from the X-Forwarded-For header when present (which nginx and Caddy set by default), falling back to the direct connection's address otherwise.

All of this — what gets printed, what gets written to traffic.log, and in what format — is controlled from one place: lib/logger.js. Edit log() or logTraffic() there to change it (e.g. write JSON lines instead, log to a different file, or add fields).

Configuration reference

SettingDefaultWherePurpose
PORT (env var)3000shell environmentPort the server listens on
MAX_SUBLINKS15lib/archiver.jsNon-recursive archiving: max links archived from the one page you typed
MAX_RECURSIVE_PAGES100lib/archiver.jsRecursive archiving: max total pages crawled per site, including the main page

Requirements

  • Node.js 18 or newer.
  • On Linux, Chromium (used via Puppeteer) needs a few system libraries. If npm start fails to launch the browser, install them, e.g. on Debian/Ubuntu:
    sudo apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 \
    libatk1.0-0 libcups2 libdbus-1-3 libdrm2 libgbm1 libgtk-3-0 libnspr4 libnss3 \
    libxcomposite1 libxdamage1 libxfixes3 libxkbcommon0 libxrandr2 xdg-utils
    

About

Self-Hosted & Easy to set up Archive.org and ArchiveBox alternative.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages