93 Commits

Author SHA1 Message Date
Tam Nguyen Duc 1ba8c8943e Bump go toolchain to 1.26.5 (#71) v0.3.10 2026-07-11 10:53:28 +07:00
Tam Nguyen Duc 35860d5aaa Drop the embedded leakless binary that trips antivirus (#69)
* Drop the embedded leakless binary that trips antivirus (#68)

go-rod's launcher imports github.com/ysmood/leakless, which base64/gzip
embeds a prebuilt leakless.exe for every target. On Windows that helper is
linked straight into kage.exe, and Windows Defender flags its signature and
quarantines a fresh scoop install before kage ever runs.

kage already launches Chrome with leakless disabled (browser/leakless.go),
so the guard was never doing anything, only adding the flagged bytes. This
adds an API-compatible stub for the package under third_party/leakless with
no embedded binary and points a replace directive at it. The Windows build
loses about 1.28 MB of packed executable and no longer carries the payload
that antivirus reacts to. Support() returns false, so go-rod skips the
leakless path even if a caller re-enabled it.

* Document the leakless antivirus fix for v0.3.9

Add the release-notes and changelog entries for the leakless helper removal
(#68), so the docs site and CHANGELOG explain why the Windows build shrank and
what the earlier virus warning was.

* Drop the now-unused leakless module hash from go.sum

The replace points leakless at a local directory, so go mod tidy no longer
needs the upstream module's checksum. Keeps the tidy CI check green.
v0.3.9
2026-07-08 19:45:16 +07:00
Tam Nguyen Duc 7483efd2a9 Dequarantine the brew cask binary on install (#67)
Homebrew quarantines cask artifacts and Gatekeeper SIGKILLs quarantined
binaries that are only ad-hoc signed, which cross-compiled Go binaries
are. shirabe hit this first: brew install worked but the binary died
with exit 137 until the attribute was stripped by hand. Add the same
postflight xattr hook here so casks from the next release run first
try.
2026-07-07 08:39:54 +07:00
Tam Nguyen Duc 320b21a2ba Pin release actions and cosign to fixed versions (#63)
Bump the release workflow to the action versions the channel repos already
run and pin cosign to the 2.x line so signing keeps its detached .sig and
.pem outputs instead of floating to a breaking cosign 3.x.
2026-06-29 19:11:59 +07:00
Tam Nguyen Duc 2dabb93a78 Run the container as root so a bind-mounted /out works (issue #7) (#53)
The image dropped to a fixed non-root user (uid 10001) and pointed HOME at
/out. On native Linux Docker a bind-mounted /out is owned by whoever created
it on the host, so uid 10001 cannot write into it. Two things then failed:
kage's output and resume state under $HOME/data/kage hit "mkdir /out:
permission denied", and Chrome launched chrome_crashpad_handler with an empty
crash database path, which aborts the whole browser with
"chrome_crashpad_handler: --database is required" and fails every render.

The earlier attempt set HOME=/out, but that only helps when /out is writable,
which it is not for a non-root uid against a host-owned mount. The crash-reporter
flags in the launcher did not help either: they do not stop Chrome from spawning
the handler, so the abort stayed.

Run as root instead. Container root writes a host-owned bind mount whatever its
ownership, so both /out and HOME stay writable and the documented one-liner just
works. This does not loosen the sandbox: Chrome's sandbox is already off inside
any container (kage drops it on container detection), so root here changes
nothing that was holding.

Verified end to end in an Alpine + chromium container: the non-root image
reproduces both the crashpad abort and the permission-denied exactly as
reported, and the root image clones example.com cleanly, writing index.html and
resume state into a host-owned mounted volume.
2026-06-23 15:20:31 +07:00
Xirui 8bc9c8bea1 feat: 1. add a crawl delay function to honor the Crawl-delay directive parsed from robots.txt during clone. (#57)
2. add --craw-delay flag to specify/override robots directive.
v0.3.8
2026-06-22 16:16:54 +07:00
Tam Nguyen Duc 28d08ade88 fix brew install command to include --cask flag (#56)
The tap distributes kage as a Cask (prebuilt binary), not a Formula,
so brew needs --cask to find it. Without the flag brew errors out with
"No available formula or cask with the name kage".
2026-06-21 10:31:34 +07:00
Valid-Systems 1e59c10e6f Disable Rod leakless launcher on Windows (#54)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: DeepWiki Dev <dev@deepwiki.local>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-21 10:18:05 +07:00
Tam Nguyen Duc 105e68f036 Push the scoop manifest into the bucket directory (#52)
Without a directory the scoop manifest landed at the repository root,
while the seeded manifests and the path scoop reads live under bucket/.
A scoop install would keep resolving the stale root-shadowed manifest.

Set directory: bucket so the release overwrites bucket/<tool>.json, the
same place the manifest is served from.
v0.3.7
2026-06-19 23:16:37 +07:00
Tam Nguyen Duc 792878d6f3 Use the bare .Env form for the tap and bucket tokens (#51)
GoReleaser validates that a repository token template is exactly
{{ .Env.VAR_NAME }} and rejects any other interpolation, so the
index .Env form aborted the publish with 'expected {{ .Env.VAR_NAME }}
only'. The token is only rendered when an upload actually happens, and
in CI the secret is always set, so the bare form is safe here.

skip_upload keeps the index .Env form: it is not subject to the token
rule and index degrades to an empty string when the secret is unset, so
an unconfigured fork still skips the upload instead of erroring.
2026-06-19 23:03:59 +07:00
Tam Nguyen Duc 910619bc7a Use index .Env for the optional tap and bucket tokens (#50)
The Homebrew cask and Scoop manifest stanzas gated their token and
skip_upload on envOrDefault, which is a sprig function GoReleaser does
not define, so a real release aborted with 'function envOrDefault not
defined' the moment it reached the publish phase. goreleaser check does
not evaluate templates, so the gate slipped through until a tagged run.

Switch to index .Env "NAME", the idiom GoReleaser documents for an
optional variable: it yields the token when the secret is set (push the
update) and an empty string when it is not (skip_upload stays true).
2026-06-19 22:44:38 +07:00
Tam Nguyen Duc e0e4096f93 Pin cosign to 2.x in the release workflow (#49)
cosign 3.x flips the new bundle format on by default, which ignores the
--output-signature and --output-certificate flags our signs block passes
to sign-blob and then aborts trying to write a bundle to an empty path.
The last few releases failed at the signing step for exactly this reason.

Pin cosign-installer to v2.6.3 so the release keeps producing the
checksums.txt.sig and checksums.txt.pem pair, and so the signing tool
stops floating to a latest that can break the pipeline without warning.
2026-06-19 22:31:26 +07:00
Tam Nguyen Duc c176719031 Merge pull request #48 from tamnd/docs/install-audit
Document Homebrew and Scoop install, fix deb/rpm filenames
2026-06-19 22:17:58 +07:00
Duc-Tam Nguyen a1b82ebd1b Make the Linux repo dispatch best-effort
The release must never fail because the dispatch token is missing scope or the
call hiccups, matching how the Homebrew and Scoop steps are non-fatal.
2026-06-19 19:20:03 +07:00
Duc-Tam Nguyen 5ad8fff9df Add the Linux apt/dnf repository to install docs and releases
Documents the signed apt and dnf repository alongside Homebrew and Scoop, and
fires a repository_dispatch on release so the Linux repo rebuilds with the new
packages. The step is skipped when the dispatch token is unset.
2026-06-19 19:16:57 +07:00
Duc-Tam Nguyen 7157144c75 Document Homebrew and Scoop, and fix the deb/rpm filenames
The README never mentioned the package managers and the docs covered Homebrew
but not Scoop, even though both ship from every release. Add the Homebrew
(macOS) and Scoop (Windows) channels to both. Fix the Linux package globs:
GoReleaser names them kage_<ver>_amd64.deb and kage-<ver>-1.x86_64.rpm, not
kage_*_linux_amd64.{deb,rpm}, so the documented commands matched nothing.
2026-06-19 17:16:11 +07:00
Tam Nguyen Duc de1c0adde3 Merge pull request #47 from tamnd/chore/upgrade-actions
chore: upgrade all GitHub Actions to latest versions
2026-06-19 16:45:11 +07:00
Duc-Tam Nguyen 16bbd1b9d0 chore: upgrade all GitHub Actions to latest versions
Node.js 20 is being deprecated in the Actions runtime. Upgrade every
action to its latest release so the workflows run on Node.js 22+:

  actions/checkout        v5/v6.0.2  → v7.0.0
  browser-actions/setup-chrome  v1   → v2.1.2
  golangci/golangci-lint-action v8   → v9.2.1
  goreleaser/goreleaser-action  v6   → v7.2.2
  docker/setup-qemu-action      v3   → v4.1.0
  docker/setup-buildx-action    v3   → v4.1.0
  docker/login-action           v3   → v4.2.0
  sigstore/cosign-installer     v3   → v4.1.2
  anchore/sbom-action           v0   → v0.24.0
2026-06-19 16:44:57 +07:00
Tam Nguyen Duc 3220f6d230 Merge pull request #46 from tamnd/fix/gofmt
fix: gofmt cli/clone.go
2026-06-19 16:43:25 +07:00
Duc-Tam Nguyen 9d452ddf11 fix: gofmt cli/clone.go 2026-06-19 16:43:15 +07:00
Tam Nguyen Duc c8fc6faaa3 Merge pull request #45 from tamnd/release-0.3.6
Cut the v0.3.6 release notes
v0.3.6
2026-06-19 16:14:53 +07:00
Duc-Tam Nguyen c5b39d6816 Cut the v0.3.6 release notes 2026-06-19 16:14:39 +07:00
Tam Nguyen Duc 057937938d Merge pull request #44 from tamnd/fix/mobile-css-layout
fix: correct mobile layout for paulgraham.com-style table pages
2026-06-19 15:55:51 +07:00
Duc-Tam Nguyen 4e920c57ec fix: correct mobile layout for paulgraham.com-style table pages
Three issues surfaced when viewing the ZIM in Kiwix iOS:

1. Blank column at top: hiding img[usemap] left its containing <td> as an
   empty box. Now td:has(>img[usemap]) hides the entire nav column.

2. Content clipped on the right: the inner content table had width="435"
   as an HTML attribute. Added [width]{width:auto!important} to cancel all
   fixed HTML attribute widths on any element (tables, tds, imgs).

3. Spacer column: the 26px <td> holding a 1x1 transparent GIF kept its
   allocated space. td:has(>img[src*="trans_1x1"]:only-child) hides it.

Also: overflow-x:hidden on body, box-sizing:border-box globally, and
img{max-width:100%;height:auto} so any inline images stay within column.
2026-06-19 15:55:28 +07:00
Tam Nguyen Duc 2c15f79ff5 Merge pull request #43 from tamnd/feat/mobile-readable
feat: add --mobile flag for readable archives of legacy sites
v0.3.5
2026-06-19 15:14:44 +07:00
Duc-Tam Nguyen 060b4b6449 feat: add --mobile flag for readable archives of legacy sites
Cloning a 1990s/2000s site like paulgraham.com and opening it in Kiwix
on a phone produces microscopic text: the pages use <font size="2">,
table layouts, and no viewport declaration, so the mobile browser shrinks
everything to desktop scale and then the font-size attribute makes it
smaller still.

kage clone --mobile injects two things into every saved page:

- <meta name="viewport" content="width=device-width, initial-scale=1">
  so the browser stops shrinking the page
- a <style> block that lifts the base font to 18px, inherits it through
  <font> elements (overriding the in-HTML size/face attributes), caps the
  content width at 720px, loosens line height to 1.7, and hides
  image-map nav elements whose source GIFs 404 offline

The style block goes last in <head> to win specificity ties, and
ensureViewport skips pages that already carry a viewport meta.

Two tests cover the happy path and the no-duplicate case.
2026-06-19 15:14:19 +07:00
Tam Nguyen Duc 3117c4c55c Merge pull request #41 from tamnd/feat/zim-search-index
Store each page under its real title in a packed ZIM
2026-06-17 18:23:21 +07:00
Duc-Tam Nguyen 6655a434b9 Store each page under its real title in a packed ZIM
A packed page entry took its URL path as its title, so a ZIM reader's
search box suggested filenames like 5founders.html instead of readable
titles. The page's <title> was only read for the archive-level M/Title.

Read each HTML page's <title> at pack time and store it on the entry,
collapsing wrapped whitespace to one line and falling back to the path
when a page has none. A reader's suggestion search walks the title
pointer list, which is sorted by title, so typing now offers entries
like Five Founders and Female Founders. The per-page title also lands in
the title column of a parquet export.

Document the search story in the packing guide: title suggestions in any
reader, and full-text search of page bodies through parquet and DuckDB.
A Xapian full-text index stays out on purpose, since Xapian is GPL and
kage is MIT.
2026-06-17 18:12:46 +07:00
Tam Nguyen Duc 8bb1e344d2 Merge pull request #40 from tamnd/release-0.3.4
Cut the v0.3.4 release notes
v0.3.4
2026-06-17 16:11:35 +07:00
Duc-Tam Nguyen 122a80210c Cut the v0.3.4 release notes 2026-06-17 16:09:05 +07:00
Tam Nguyen Duc 0a8ab2e238 Merge pull request #38 from zkd-11/fix-serve-signal
fix: serve Ctrl-C stop
2026-06-17 16:03:05 +07:00
Tam Nguyen Duc 994e94f3fb Merge pull request #39 from GautamKumarOffical/fix/handle-object-reference-chain-error
fix: continue rendering when Chrome hits 'Object reference chain is too long'
2026-06-17 15:51:19 +07:00
Gautam Kumar eb683ddd2c fix: continue rendering when Chrome hits 'Object reference chain is too long'
When a page's JavaScript builds deeply nested object graphs, Chrome's
DevTools Protocol returns error -32000 'Object reference chain is too
long' during WaitLoad. The page has still loaded its HTML — the error is
about Chrome's internal object tracking, not the document itself.

This change detects this specific error and proceeds with rendering
instead of failing the entire page, so sites with complex JS still get
cloned successfully (issue #36).

Signed-off-by: Gautam Kumar <gautamkumarofficial@users.noreply.github.com>
2026-06-17 08:27:21 +05:30
Kaden c85745b230 fix: serve Ctrl-C stop 2026-06-16 23:26:52 +08:00
Tam Nguyen Duc 602f4dfa40 Merge pull request #35 from Xirui/main 2026-06-16 11:54:36 +07:00
Xirui e80d38a8bc fix: kage serve Ctrl-C handling 2026-06-16 15:38:47 +12:00
Tam Nguyen Duc 8178b767fd Merge pull request #34 from tamnd/release-0.3.3
Cut the v0.3.3 release notes
v0.3.3
2026-06-16 10:16:52 +07:00
Duc-Tam Nguyen a68d2b12e0 Cut the v0.3.3 release notes 2026-06-16 10:14:19 +07:00
Tam Nguyen Duc 5cbb7f83ea Merge pull request #33 from tamnd/fix-download-deny
Stop Chrome downloading files a crawl links to
2026-06-16 10:13:34 +07:00
Duc-Tam Nguyen bf850c6b93 Stop Chrome downloading files a crawl links to
An extensionless link is queued as a page, so the page worker navigated to
it in headless Chrome. When such a link served a binary, a zip or a CSV,
Chrome saved the file to the user's Downloads folder, a surprise side effect
of a clone (issue #32).

Deny Chrome-initiated downloads browser-wide, since kage fetches every asset
through its own downloader and never needs the browser to write a file. Then
watch the main document's response, and when it is not HTML, return a typed
ErrNotHTML so the page worker reroutes the URL to the asset downloader, where
the existing size and media policy decides whether to localise it or leave it
on the live web.

Verified against the two URLs from the issue, a zip and a CSV: both land
under the mirror's reserved tree and nothing is written to Downloads.
2026-06-16 09:41:43 +07:00
Tam Nguyen Duc 83822c1629 Merge pull request #31 from tamnd/release-0.3.2
Cut the v0.3.2 release notes
v0.3.2
2026-06-16 00:21:03 +07:00
Duc-Tam Nguyen 3ec5c8fdaf Cut the v0.3.2 release notes
Date the Unreleased charset fix as 0.3.2, update the compare links, and
add a v0.3.2 summary to the docs release-notes page.
2026-06-16 00:18:35 +07:00
Tam Nguyen Duc 7709bccfcd Merge pull request #30 from tamnd/fix-charset-utf8
Add a meta charset to saved pages so text does not mojibake
2026-06-16 00:17:52 +07:00
Duc-Tam Nguyen 16c9fcbcfe Add a meta charset to saved pages so text does not mojibake
A page that declared its charset only in the HTTP Content-Type header,
with no meta charset in the markup, lost that signal once kage saved it
as a file. A reader serving the bytes without a charset then fell back to
its locale encoding and garbled every curly quote, dash, and nbsp.

kage writes UTF-8, so it now inserts a meta charset utf-8 at the top of
head when the page does not already declare one. Verified on the blog
post from the report: the saved HTML now carries the meta and renders the
quotes correctly.
2026-06-16 00:15:27 +07:00
Tam Nguyen Duc 2d8a64ca6f Merge pull request #28 from tamnd/release-0.3.1
Cut the v0.3.1 release notes
v0.3.1
2026-06-15 23:14:41 +07:00
Duc-Tam Nguyen a96233cf73 Cut the v0.3.1 release notes
Move the Unreleased fix into a dated 0.3.1 section, update the compare
links, and add a v0.3.1 summary to the docs release-notes page. This
patch redirects / to the main page so a mirror whose entry point is a
nested page keeps its CSS and images when served.
2026-06-15 23:12:08 +07:00
Tam Nguyen Duc 2064ef918a Merge pull request #27 from tamnd/fix-mainpage-redirect
Redirect / to the main page instead of serving it in place
2026-06-15 22:49:08 +07:00
Duc-Tam Nguyen 9eabd93098 Redirect / to the main page instead of serving it in place
The served main page broke its own CSS and images when the entry point
was a nested page. kage saves each page's asset links as mirror-relative
paths (../_kage/...) computed for that page's own location, but the
handler served the main page's bytes directly at /, so the browser
resolved those relative URLs against / and 404ed every one of them. A
developer.apple.com/documentation mirror landed at / with no styles.

Redirect / to the main page's canonical content path, the way the
archive's W/mainPage redirect already does, so the browser navigates to
the page's real URL and resolves its relative assets correctly. Kiwix
was unaffected because it follows that redirect itself.
2026-06-15 22:46:42 +07:00
Tam Nguyen Duc 2ccf7bc5db Merge pull request #26 from tamnd/release-0.3.0
Cut the v0.3.0 release notes
v0.3.0
2026-06-15 21:32:23 +07:00
Duc-Tam Nguyen 15fb1d8be4 Cut the v0.3.0 release notes
Move the Unreleased entries into a dated 0.3.0 section, update the
compare links, and add a v0.3.0 summary to the docs release-notes page.
This release leaves bulk and off-domain assets remote, skips over-cap
downloads instead of truncating them, adds parquet export and import,
incremental packing, and identical-page dedup.
2026-06-15 21:29:49 +07:00