FixTweet icon indicating copy to clipboard operation
FixTweet copied to clipboard

Better documentation for self hosting

Open MobinAskari opened this issue 7 months ago • 3 comments

Hello First of all, I wanted to thank you for your amazing project.

Is it possible for you to write a better documentation on how to self host this project?

I followed the steps that you've have provided and was able to deploy my project to Cloudflare, but nothing is usable. I'm getting this error while visiting "/":

{
  "source": {
    "level": "error",
    "message": "Error: next() called multiple times\n    at a (worker.js:1:690)\n    at worker.js:1:839\n    at worker.js:467:16470\n    at async a (worker.js:1:825)\n    at async worker.js:467:22107\n    at async a (worker.js:1:825)\n    at async worker.js:1:21923\n    at async a (worker.js:1:825)\n    at async worker.js:467:21007\n    at async a (worker.js:1:825)",
    "$cloudflare": {
      "$metadata": {
        "id": "01JWGTB8T3EWM---",
        "type": "cf-worker",
        "error": "Error: next() called multiple times\n    at a (worker.js:1:690)\n    at worker.js:1:839\n    at worker.js:467:16470\n    at async a (worker.js:1:825)\n    at async worker.js:467:22107\n    at async a (worker.js:1:825)\n    at async worker.js:1:21923\n    at async a (worker.js:1:825)\n    at async worker.js:467:21007\n    at async a (worker.js:1:825)",
        "errorPattern": "Error: next() called multiple times\n    at a (<DOMAIN>:<NUM>:<NUM>)\n    at <DOMAIN>:<NUM>:<NUM>\n    at <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)",
        "messagePattern": "Error: next() called multiple times\n    at a (<DOMAIN>:<NUM>:<NUM>)\n    at <DOMAIN>:<NUM>:<NUM>\n    at <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)"
      }
    }
  },
  "dataset": "cloudflare-workers",
  "timestamp": "2025-05-30T14:43:12.765Z",
  "$workers": {
    "truncated": false,
    "event": {
      "request": {
        "url": "https://---.workers.dev/favicon.ico",
        "method": "GET",
        "path": "/favicon.ico"
      }
    },
    "outcome": "ok",
    "scriptName": "fixtweet-self-hosted",
    "eventType": "fetch",
    "executionModel": "stateless",
    "scriptVersion": {
      "id": "0ab5dea2-2442-4---"
    },
    "requestId": "947f00a---"
  },
  "$metadata": {
    "id": "01JWGTB8T3EWM---",
    "requestId": "947f00a49----",
    "trigger": "GET /favicon.ico",
    "service": "fixtweet-self-hosted",
    "level": "error",
    "error": "Error: next() called multiple times\n    at a (worker.js:1:690)\n    at worker.js:1:839\n    at worker.js:467:16470\n    at async a (worker.js:1:825)\n    at async worker.js:467:22107\n    at async a (worker.js:1:825)\n    at async worker.js:1:21923\n    at async a (worker.js:1:825)\n    at async worker.js:467:21007\n    at async a (worker.js:1:825)",
    "message": "Error: next() called multiple times\n    at a (worker.js:1:690)\n    at worker.js:1:839\n    at worker.js:467:16470\n    at async a (worker.js:1:825)\n    at async worker.js:467:22107\n    at async a (worker.js:1:825)\n    at async worker.js:1:21923\n    at async a (worker.js:1:825)\n    at async worker.js:467:21007\n    at async a (worker.js:1:825)",
    "account": "336ae820---",
    "type": "cf-worker",
    "fingerprint": "59e7a60---",
    "origin": "fetch",
    "messageTemplate": "Error: next() called multiple times\n    at a (<DOMAIN>:<NUM>:<NUM>)\n    at <DOMAIN>:<NUM>:<NUM>\n    at <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)",
    "errorTemplate": "Error: next() called multiple times\n    at a (<DOMAIN>:<NUM>:<NUM>)\n    at <DOMAIN>:<NUM>:<NUM>\n    at <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)\n    at async <DOMAIN>:<NUM>:<NUM>\n    at async a (<DOMAIN>:<NUM>:<NUM>)"
  },
  "links": []
}

If I try to generate a link preview in Telegram, I get: "Owie, you crashed FxTwitter :( This may be caused by API downtime or a new " as the preview with the same error message above in my CF logs.

My Wrangler.toml file:

name = "fixtweet-self-hosted"
account_id = "336ae8201c8a66----"
main = "./dist/worker.js"
compatibility_date = "2024-11-14"
send_metrics = false

[build]
command = "npm run build"

My .env (I have not configured other envs since I don't have the domains & I don't need them either):

STANDARD_DOMAIN_LIST = "mydomain....workers.dev"
API_HOST_LIST = "mydomain....workers.dev"
SENTRY_DSN = "---"
SENTRY_AUTH_TOKEN = "---"
SENTRY_ORG = "---"
SENTRY_PROJECT = "---"

My branding.json:

{
  "zones": [
    {
      "name": "FxTwitter",
      "default": true,
      "domains": ["mydomain....workers.dev"],
      "provider": "twitter",
      "favicon": "https://abs.twimg.com/favicons/twitter.2.ico",
      "redirect": "https://github.com/FxEmbed",
      "color": "#6363ff",
      "activityIcons": {
        "default": "https://assets.fxembed.com/logos/fxtwitter-pride32.png",
        "svg": "https://assets.fxembed.com/logos/fxtwitter.svg",
        "16": "https://assets.fxembed.com/logos/fxtwitter-pride16.png",
        "24": "https://assets.fxembed.com/logos/fxtwitter-pride24.png",
        "32": "https://assets.fxembed.com/logos/fxtwitter-pride32.png",
        "48": "https://assets.fxembed.com/logos/fxtwitter-pride48.png",
        "64": "https://assets.fxembed.com/logos/fxtwitter-pride64.png"
      }
    }
  ]
}

MobinAskari avatar May 30 '25 15:05 MobinAskari

"Owie, you crashed FxTwitter :( This may be caused by API downtime or a new bug" is recent. I was able to successfully deploy a few months ago and the worker just died recently.

shodanon avatar May 30 '25 16:05 shodanon

I ended up solving the issues with the above error. Add return c.res.clone(); under the await next() on line 24 of caches.ts. This bug only occurs for workers.dev urls.

The new issue I run into is I've got no idea what routes I need to point at the worker. I setup a custom domain but I sense I need to point more at it.

mja00 avatar Jul 13 '25 18:07 mja00

I've learned how to fully self-host this, so I'll document my findings.

  1. Do the above fix if you plan on just using your workers.dev domain. It'll most likely handle all the next steps.
  2. In your .env file, set all of your domains up here. I essentially just removed any domain that wasn't a subdomain of fxtwitter.com and then converted those over to my domains. In the end it looks like this:
    STANDARD_DOMAIN_LIST = "femboysx.com,fxembed.mja00.workers.dev"
    STANDARD_BSKY_DOMAIN_LIST = ""
    DIRECT_MEDIA_DOMAINS = "d.femboysx.com,dl.femboysx.com"
    TEXT_ONLY_DOMAINS = "t.femboysx.com"
    INSTANT_VIEW_DOMAINS = "i.femboysx.com"
    GALLERY_DOMAINS = "g.femboysx.com"
    NATIVE_MULTI_IMAGE_DOMAINS = "m.femboysx.com"
    OLD_EMBED_DOMAINS = "o.femboysx.com"
    MOSAIC_DOMAIN_LIST = "mosaic.femboysx.com,mosaic.fxtwitter.com"
    MOSAIC_BSKY_DOMAIN_LIST = ""
    GIF_TRANSCODE_DOMAIN_LIST = "gif.femboysx.com,gif.fxtwitter.com"
    API_HOST_LIST = "api.femboysx.com,api-canary.femboysx.com"
    
    (yes that's the domain I wanted shh) Make note of keeping mosaic.fxtwitter.com, we'll come back to that.
  3. Setup your wrangler.toml file, keep the elongator binding, we'll come back to that. For routes add this:
    routes = [
      { pattern = "femboysx.com", zone_id = "ZONE_ID", custom_domain = true },
      { pattern = "d.femboysx.com", zone_id = "ZONE_ID", custom_domain = true  },
      { pattern = "dl.femboysx.com", zone_id = "ZONE_ID", custom_domain = true  },
      { pattern = "t.femboysx.com", zone_id = "ZONE_ID", custom_domain = true  },
      { pattern = "i.femboysx.com", zone_id = "ZONE_ID", custom_domain = true  },
      { pattern = "g.femboysx.com", zone_id = "ZONE_ID", custom_domain = true  },
      { pattern = "m.femboysx.com", zone_id = "ZONE_ID", custom_domain = true  },
      { pattern = "o.femboysx.com", zone_id = "ZONE_ID", custom_domain = true  },
      { pattern = "api.femboysx.com", zone_id = "ZONE_ID", custom_domain = true  },
    ]
    
    Changing your base domain and zone ID. Keep the custom_domain option set.
  4. Deploy with npm run deploy. It will not work at first most likely, as we need to setup a few more things.
  5. Setup elongator. Just copy the wrangler example and update your account ID and deploy it. Needs no domain and we do a service binding for FxEmbed to talk to it. You'll want to add some accounts following the readme, for my personal use I just did 1 account (YMMV).
  6. If you wanna be nice setup your own mosaic instance. I recommend not doing it in docker, mostly because it requires edits to the Dockerfile and the host the web server binds to. If you don't care/don't wanna be nice, just remove mosaic.your-domain.com from the MOSAIC_DOMAIN_LIST
    • If you do wanna run it in docker, use my PR to get the correct Dockerfile. Then just edit src/main.rs and change line 150 to let addr = SocketAddr::from(([0, 0, 0, 0], port)); to have it bind inside of the container correctly.
  7. Same for fastgif although it's much easier to setup in Docker.

After all of that, you should have a working copy of FxEmbed setup on your own worker. It's kind of a pain and required a lot of tinkering. I'll probably PR some proper docs to self-hosting it (and fix Docker builds for mosaic) at some point.

mja00 avatar Jul 15 '25 23:07 mja00