fix(monkeyd): make /monkeyd_crawl public and fix PDF font resolution

Advance the crawler submodule to the fix for the production failure "stat
usr/share/fonts/...: no such file or directory": the PDF writer was handing
fpdf a font path, and fpdf rewrote the absolute path into a
working-directory-relative one. Font data is now passed as bytes, with a
fallback font compiled into the binary when the host has none.

The runtime image therefore no longer installs DejaVuSans, which also removes
the font layer from the builder stage.

/monkeyd_crawl becomes public. The host allowlist and the single in-flight
export were already the controls that bound its cost; they now carry that job
alone, so both are load-bearing.
This commit is contained in:
tiennm99 committed 2026-07-29 23:46:33 +07:00
1 parent af1e8776af
commit 67934adfa0
6 files changed
+45 -34

No files matched your search

+19 -10
View File
@@ -246,15 +246,24 @@ func TestCrawl_RepliesBusyWhileAnotherExportRuns(t *testing.T) {
}
}
// A Protected command must not respond at all to an unauthorized sender, or its
// existence leaks.
func TestCrawl_SilentForUnauthorizedSender(t *testing.T) {
rb, _ := install(t, 999)
rb.Bot.ProcessUpdate(context.Background(),
testutil.NewPrivateMessage(12345, "/monkeyd_crawl "+testNovelURL))
// The command is public: a sender who is neither owner nor admin must still get
// a reply, including in a group.
func TestCrawl_AvailableToAnySender(t *testing.T) {
rb, r := install(t, 999)
dir := t.TempDir()
r.exporter = func(context.Context, export.Request) (*export.Result, error) {
return stubPDF(t, dir, "Example-Novel.pdf", 2048), nil
}
if calls := rb.Sent(); len(calls) != 0 {
t.Errorf("expected no reply to an unauthorized sender, got %+v", calls)
rb.Bot.ProcessUpdate(context.Background(),
testutil.NewGroupMessage(-100, 12345, "/monkeyd_crawl "+testNovelURL))
calls := rb.Sent()
if len(calls) == 0 {
t.Fatal("a non-admin sender got no reply from a public command")
}
if last := calls[len(calls)-1]; last.Method != "sendDocument" {
t.Errorf("last call = %q, want sendDocument", last.Method)
}
}
@@ -267,8 +276,8 @@ func TestRegistration(t *testing.T) {
if cmd.Name != commandName {
t.Errorf("Name = %q, want %q", cmd.Name, commandName)
}
if cmd.Visibility != modules.VisibilityProtected {
t.Errorf("Visibility = %v, want Protected", cmd.Visibility)
if cmd.Visibility != modules.VisibilityPublic {
t.Errorf("Visibility = %v, want Public", cmd.Visibility)
}
if cmd.Parameters != "<url>" {
t.Errorf("Parameters = %q, want %q", cmd.Parameters, "<url>")
+5 -3
View File
@@ -39,9 +39,11 @@ func newModule(r *runner) modules.Module {
Commands: []modules.Command{
{
Name: commandName,
Visibility: modules.VisibilityProtected,
// One invocation makes hundreds of outbound requests over
// several minutes, so it stays off the public surface.
Visibility: modules.VisibilityPublic,
// Public despite being expensive: one invocation makes
// hundreds of outbound requests over several minutes. The
// host allowlist and the single in-flight export are what
// bound the cost, so both are load-bearing here.
Description: "Export a " + AllowedHostsHint + " novel as a PDF",
Parameters: "<url>",
Handler: r.handle,