# Forgejo integration This directory contains assets for the ifcurl preview extension. The features split into two tiers by what infrastructure they require. ## Feature overview | Feature | What it needs | Forgejo rebuild? | |---|---|---| | "View in 3D" button on file/history pages | `ifcurl.js` + ifcurl service | No | | Browser IFC viewer | `viewer.html` static asset | No | | PR diff 3D render | `ifcurl.js` + ifcurl service + **Nginx proxy** | No | | `[title](ifc://...)` links in markdown → inline preview | `ifcurl.js` + ifcurl service + **Nginx proxy** | No | | Bare `ifc://...` text in markdown → inline preview | Go patch + `PREVIEW_SERVICE_URL` in app.ini | **Yes** | All features except bare-URL markdown rendering work with static asset deployment only — no Forgejo rebuild required. `ifcurl.js` detects `` links produced by Goldmark's standard link parser and replaces them with preview figures at page load. The Go patch is needed **only** if you want bare `ifc://...` text in markdown (without `[title](...)` syntax) to render as a preview. The Issue button in the viewer emits `[title](ifc://...)` link syntax, so normal issue/PR workflows work without the patch. Minimising the patch surface is deliberate — every line of Go code must be re-verified against each Forgejo release. The PR diff feature requires a **reverse proxy** (e.g. Nginx) to expose the ifcurl service at the same origin as Forgejo. This is because the diff image is fetched by the browser (not by Forgejo's server), and browsers block cross-origin image requests to plain `http://localhost` URLs. Most production Forgejo deployments already sit behind Nginx for TLS termination; adding two `proxy_pass` lines is the only extra step. --- ## Directory layout ``` forgejo/ go.patch ← diff against Forgejo source (apply once) modules/markup/markdown/ ifc_url.go ← new Go source file (copy into source tree) ifc_url_test.go ← Go tests (copy into source tree) custom/public/assets/ viewer.html ← browser IFC viewer (no rebuild needed) viewer-url.js ← viewer URL logic module ifcurl.js ← "View in 3D" + PR diff injection (no rebuild needed) templates/custom/ footer.tmpl ← loads ifcurl.js (no rebuild needed) server-config/ ifcurl-api.service ← systemd unit for the API service (git + caching) ifcurl-render.service ← systemd unit for the render isolation service gitconfig-ifcmerge ← git merge driver registration for /etc/gitconfig gitattributes ← system-wide gitattributes for bare repos ``` --- ## Local setup (Windows + Docker) For a self-contained local deployment of Forgejo + ifcurl on Windows using Docker, see [SETUP_LOCAL.md](SETUP_LOCAL.md). --- ## Prerequisites - ifcurl API service + render service running (see [Running the preview service](#running-the-preview-service)) - Nginx (or equivalent) in front of Forgejo if PR diff images are wanted - Forgejo source tree cloned and building cleanly (`go` 1.21+) — **only if** markdown inline preview is wanted The patch was written against the `v13.0` branch of Forgejo. Check which Forgejo commit the patch was authored against with: ```bash cd /path/to/forgejo git log --oneline modules/markup/markdown/markdown.go | head -3 ``` --- ## Applying the patch ### 1. Copy new Go source files ```bash cp forgejo/modules/markup/markdown/ifc_url.go /path/to/forgejo/modules/markup/markdown/ cp forgejo/modules/markup/markdown/ifc_url_test.go /path/to/forgejo/modules/markup/markdown/ ``` ### 2. Apply the diff to existing files ```bash cd /path/to/forgejo git apply /path/to/ifcurl/forgejo/go.patch ``` This adds one line to `modules/markup/markdown/markdown.go` and six lines to `modules/setting/markup.go`. If the patch does not apply cleanly (e.g. after a Forgejo upstream upgrade), the changes are small enough to apply by hand — see `go.patch` for the exact hunks. ### 3. Run the Go tests ```bash cd /path/to/forgejo go test ./modules/markup/markdown/ -run TestIfcURL -v ``` All seven tests should pass before proceeding. ### 4. Build and deploy Forgejo ```bash cd /path/to/forgejo go build \ -tags 'sqlite sqlite_unlock_notify' \ -ldflags "-X 'forgejo.org/modules/setting.StaticRootPath=/usr/share/forgejo'" \ -o forgejo . sudo cp forgejo /usr/bin/forgejo sudo systemctl restart forgejo ``` Adjust build tags and `-ldflags` to match your existing Forgejo build configuration. --- ## Building the viewer JavaScript bundle The viewer loads Three.js, ThatOpen components, and JSZip from locally-bundled files rather than CDNs. The bundle files are committed to the repository, so you only need to re-run the build when upgrading a dependency version. ### Prerequisites Node.js 18+ and npm (used only at build time; not required on the server). ### Run the build ```bash cd forgejo/ npm install # installs pinned versions from package-lock.json npm run build # produces the four files below ``` Output files (committed to the repository): | File | Source | Purpose | |---|---|---| | `custom/public/assets/viewer-deps.js` | three + @thatopen/components + jszip | Main viewer dependencies | | `custom/public/assets/fragments-worker.js` | @thatopen/fragments worker | IFC parsing web worker | | `custom/public/assets/web-ifc.wasm` | web-ifc | IFC geometry kernel (single-threaded) | | `custom/public/assets/web-ifc-mt.wasm` | web-ifc | IFC geometry kernel (multi-threaded) | The `node_modules/` directory is gitignored and is never deployed. ### Upgrading a dependency Edit the version number in `package.json`, then: ```bash cd forgejo/ npm install # updates package-lock.json npm run build # regenerates the bundle from the new version git add package.json package-lock.json custom/public/assets/ git commit -m "viewer: upgrade to vX.Y.Z" ``` --- ## Deploying custom assets (no rebuild required) These files can be updated at any time without recompiling Forgejo. ### Viewer and URL logic ```bash sudo mkdir -p /var/lib/forgejo/custom/public/assets/ sudo cp forgejo/custom/public/assets/viewer.html /var/lib/forgejo/custom/public/assets/ sudo cp forgejo/custom/public/assets/viewer-url.js /var/lib/forgejo/custom/public/assets/ sudo cp forgejo/custom/public/assets/viewer-deps.js /var/lib/forgejo/custom/public/assets/ sudo cp forgejo/custom/public/assets/fragments-worker.js /var/lib/forgejo/custom/public/assets/ sudo cp forgejo/custom/public/assets/web-ifc.wasm /var/lib/forgejo/custom/public/assets/ sudo cp forgejo/custom/public/assets/web-ifc-mt.wasm /var/lib/forgejo/custom/public/assets/ sudo cp forgejo/custom/public/assets/ifcurl.js /var/lib/forgejo/custom/public/assets/ ``` Served at `/assets/viewer.html`, `/assets/viewer-deps.js`, etc. Note: the correct CustomPath is `/var/lib/forgejo/custom` (Forgejo's default when `FORGEJO_CUSTOM` is not set). Do not use `/etc/forgejo/public/assets/` — Forgejo does not serve from there. ### "View in 3D" footer template ```bash sudo mkdir -p /var/lib/forgejo/custom/templates/custom/ sudo cp forgejo/templates/custom/footer.tmpl /var/lib/forgejo/custom/templates/custom/ sudo systemctl restart forgejo ``` --- ## Running the preview service The service is split into two systemd units for security isolation: - **ifcurl-api** — handles HTTP requests, git fetching, and caching. Has network access and can call git CLI tools. Holds credentials (`/etc/ifcurl/env`). Delegates all IFC rendering to ifcurl-render over a Unix socket. - **ifcurl-render** — handles IFC parsing and rendering via ifcopenshell and pyvista. Has no network access, no credentials, and `execve`/`execveat` are blocked by systemd. Communicates only over the Unix socket. This split limits the blast radius if a crafted IFC file achieves code execution in the render process: it cannot reach git hosts or read credentials. ### As systemd services (recommended) ```bash # Create the two service users sudo useradd --system --no-create-home --shell /usr/sbin/nologin ifcurl sudo useradd --system --no-create-home --shell /usr/sbin/nologin ifcurl-render # Add ifcurl user to ifcurl-render group so it can read the socket sudo usermod -aG ifcurl-render ifcurl # Install the units (edit AllowedHosts and port in ifcurl-api.service first) sudo cp forgejo/server-config/ifcurl-api.service /etc/systemd/system/ sudo cp forgejo/server-config/ifcurl-render.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now ifcurl-render sudo systemctl enable --now ifcurl-api ``` Edit the `ExecStart` line in `ifcurl-api.service` to set your Forgejo hostname: ```ini ExecStart=/usr/local/bin/ifcurl serve \ --host 127.0.0.1 \ --port 8000 \ --allowed-hosts git.example.com ``` Cache limits and other environment variables for the API service go in `/etc/ifcurl/env` (one `KEY=value` per line): ```bash IFCURL_CACHE_MAX_GB=10 IFCURL_T2_MAX=16 ``` Render-side environment variables (timeouts, memory limits) go in `/etc/ifcurl/render-env`: ```bash IFCURL_SANDBOX_TIMEOUT=180 IFCURL_SANDBOX_MEMORY_MB=2048 ``` ### Single-service mode (testing / simple deployments) Without `IFCURL_RENDER_SOCKET` set, the API service handles rendering directly in its own subprocess sandbox. This is simpler to set up but offers less isolation. ```bash ifcurl serve --allowed-hosts git.example.com ``` For a Forgejo instance on a non-standard port (e.g. 3000 for local dev): ```bash ifcurl serve --allowed-hosts localhost:3000 ``` `--allowed-hosts` accepts a comma-separated list. Omitting it allows all non-private remote hosts, which is unsafe if the service is reachable from untrusted clients. ## Configuration Add to `/etc/forgejo/conf/app.ini`: ```ini [ifcurl] PREVIEW_SERVICE_URL = http://localhost:8000 ``` `PREVIEW_SERVICE_URL` is used **server-side** by Forgejo's markdown renderer to fetch preview images when rendering ifc:// links in issue descriptions and comments. It runs on the Forgejo server itself, so `localhost:8000` is the right value. If left empty, ifc:// links in markdown render as plain links with no preview image. --- ## Nginx reverse-proxy for PR diff images The PR diff viewer (Case 3 in `footer.tmpl`) injects `` tags that the **browser** must fetch. The browser resolves `/render_diff` against the Forgejo origin, so the ifcurl service must be reachable at the public URL. Add these proxy locations to the Nginx virtual host that fronts Forgejo: ```nginx # ifcurl preview service — served under the same origin as Forgejo location /preview { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_read_timeout 120s; } location /render_diff { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_read_timeout 120s; } location /bcf { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_read_timeout 120s; } ``` Without this proxy the "View in 3D" button (Case 1/2) still works — it opens the viewer HTML which fetches the IFC file directly. The PR diff images (Case 3) will silently fail to load until the proxy is in place. --- ## IFC merge driver (ifcmerge) Configuring `ifcmerge` as the git merge driver for `.ifc` files lets Forgejo automatically merge IFC pull requests instead of marking them as conflicted. ### How Forgejo checks mergeability Forgejo (with git ≥ 2.38) uses `git merge-tree --write-tree` to perform a trial merge in the bare repository. This command **does** invoke configured merge drivers — confirmed by testing. With ifcmerge set up correctly, the "can be automatically merged" indicator on a PR reflects ifcmerge's actual result, not a naive text-conflict check. **Important:** bare repositories do not read committed `.gitattributes` files from the tree. The merge driver must be declared via a system-wide `core.attributesFile` setting in `/etc/gitconfig`. ### Prerequisites `ifcmerge` is a single Perl script from [github.com/brunopostle/ifcmerge](https://github.com/brunopostle/ifcmerge). Download and install it on the Forgejo server: ```bash curl -o /usr/local/bin/ifcmerge \ https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge chmod +x /usr/local/bin/ifcmerge ``` Verify it is on `PATH` for the `forgejo` system user: ```bash sudo -u forgejo which ifcmerge ``` ### Deploy the config files ```bash # System-wide gitattributes (the critical piece for bare repos) sudo cp forgejo/server-config/gitattributes /etc/gitattributes # Append merge driver registration + core.attributesFile to /etc/gitconfig sudo git config --system include.path /path/to/ifcurl/forgejo/server-config/gitconfig-ifcmerge ``` Or apply the blocks from `server-config/gitconfig-ifcmerge` directly into `/etc/gitconfig` by hand. ### Merge direction The invariant is that step-IDs in `main`/`master` must never be renumbered, because existing cross-references depend on them. The correct driver depends on which branch is the "local" (`%A`) side: | Situation | `%A` (local) | `%B` (remote) | Driver to use | |---|---|---|---| | Forgejo "Merge commit" button | `main` | PR branch | `ifcmerge_ours` (`--prioritise-local`) | | Developer rebasing PR from `main` | PR branch | `main` | `ifcmerge` (default) | For Forgejo server-side merges, use the **"Merge commit"** strategy (not rebase or squash) and configure `.gitattributes` to use `ifcmerge_ours`: ``` *.ifc merge=ifcmerge_ours ``` `ifcmerge` (no flag) is for client-side use by developers updating their working branch from `main`, where `main` arrives as the remote (`%B`) side and its IDs are preserved by default. ### Client-side `.gitattributes` For client-side merges (`git merge` on a developer's machine) committed `.gitattributes` files in the repository work fine. It is still worth adding one: ``` *.ifc merge=ifcmerge ``` This ensures developers with ifcmerge installed get automatic IFC merges locally, and tools that read gitattributes (e.g. diff viewers) can identify `.ifc` files correctly. --- ## Upgrading Forgejo After upgrading Forgejo upstream: 1. Re-apply `go.patch` (or apply the two hunks by hand if context has shifted). 2. Copy `ifc_url.go` and `ifc_url_test.go` back in — they are not modified by upstream. 3. Run the Go tests to verify compatibility. 4. Rebuild and redeploy. Custom assets and `footer.tmpl` do not require a rebuild and are unaffected by Forgejo upgrades.