build / build (push) Failing after 2m6s
The build recipe existed only as a script in /tmp on a build host, which is precisely the state this node's rules exist to prevent -- the only copy of how the artifact is produced living on an ephemeral machine. It is now a workflow in the repository, so a release is reproducible from the repo alone. The workflow installs composer from the distribution before running make. Upstream's `make composer` falls back to piping getcomposer.org/installer into php when composer is absent; the job now fails loudly instead. Excludes .gitea from the release archive alongside .github. README rewritten to carry the whole rationale: the six link sites and what was wrong with each, the measured coverage of url/uri that justifies the fallback order, why upstream's local-link behaviour is deliberate rather than a bug, what was renamed and what was deliberately not, the two build caveats, and authorship. Assisted-by: Claude Code:claude-opus-5
202 lines
7.7 KiB
Markdown
202 lines
7.7 KiB
Markdown
# GoToSocial integration into Nextcloud
|
|
|
|
🦥 Put a GoToSocial in your cloud!
|
|
|
|
Dashboard widgets for your GoToSocial notifications and home timeline, plus
|
|
unified search over posts, people and hashtags, and a "share to GoToSocial"
|
|
action on public share links.
|
|
|
|
A fork of [`nextcloud/integration_mastodon`](https://github.com/nextcloud/integration_mastodon)
|
|
by Julien Veyssier, adapted for GoToSocial servers. AGPL-3.0, same as upstream.
|
|
|
|
---
|
|
|
|
## Why this fork exists
|
|
|
|
GoToSocial implements the Mastodon client API, so Mastodon clients mostly work
|
|
against it — but one assumption doesn't hold.
|
|
|
|
Mastodon serves a web page for *every* status it knows about, including ones
|
|
that arrived over federation. GoToSocial serves a page only for statuses that
|
|
are **local** to the server. A client that builds a link out of the server
|
|
address and a status ID, the way `integration_mastodon` does, therefore
|
|
produces a dead link for every remote post:
|
|
|
|
```
|
|
https://social.example.org/@someone@remote.example/01ABCDEF... -> 404
|
|
```
|
|
|
|
Every link in the dashboard widgets and in unified search was built this way:
|
|
|
|
```php
|
|
// lib/Search/SearchStatusesProvider.php, upstream
|
|
return $mastodonUrl . '/@' . $entry['account']['acct'] . '/' . $entry['id'];
|
|
```
|
|
|
|
```js
|
|
// src/views/DashboardHome.vue, upstream
|
|
return this.mastodonUrl + '/@' + n.account?.acct + '/' + n.id
|
|
```
|
|
|
|
Six sites in total: two PHP search providers, four in the two dashboard views.
|
|
|
|
### The fix
|
|
|
|
Ask the API where the status actually lives instead of guessing. Every status
|
|
and account carries `url` (the HTML permalink) and `uri` (the ActivityPub ID),
|
|
and both point at the origin server.
|
|
|
|
Neither field alone is sufficient. Measured against a live GoToSocial database
|
|
before the change was written:
|
|
|
|
| entity | rows | missing `url` | missing both `url` and `uri` |
|
|
|-------------------|---------|---------------|------------------------------|
|
|
| remote statuses | 476,900 | 35,530 (7.4%) | **0** |
|
|
| remote accounts | 159,770 | 0 | **0** |
|
|
|
|
So the order is `url` → `uri` → the previous locally-built link, and that chain
|
|
is total across a real corpus rather than assumed to be.
|
|
|
|
Both fields are served as empty strings by some implementations rather than
|
|
null, so the code uses `?:` in PHP and `||` in JS — `??` would let an empty
|
|
string through and produce a link to nowhere.
|
|
|
|
### Also fixed: a bug that isn't GoToSocial-specific
|
|
|
|
The notification link in the dashboard widget was built from the acct of the
|
|
account that *acted* on a status, glued to the ID of the status acted *upon*:
|
|
|
|
```js
|
|
// upstream: /@whoever-favourited/your-status-id
|
|
return this.mastodonUrl + '/@' + n.account?.acct + '/' + n.status?.id
|
|
```
|
|
|
|
That target is wrong on every server that exists — Mastodon simply hides it
|
|
behind a redirect. Notifications now link to the status the notification is
|
|
about. Boosts link to the boosted status rather than to the boost.
|
|
|
|
---
|
|
|
|
## Relationship to upstream
|
|
|
|
Forked at **v5.2.0** (`6a56211`). All upstream copyright notices are intact and
|
|
the upstream CHANGELOG is preserved unmodified, including its links to upstream
|
|
pull requests — that history belongs to that project and is not rewritten here.
|
|
|
|
**Only user-facing identity was rebranded.** Internals that speak the Mastodon
|
|
client API — `MastodonAPIService`, `MastodonAPIController`, route names, config
|
|
keys — deliberately keep their names. That is the protocol they actually speak;
|
|
renaming them would make the code claim a protocol that does not exist, and
|
|
would make this fork expensive to rebase onto future upstream releases.
|
|
|
|
Translations are inherited from upstream. Strings reworded by the rebrand fall
|
|
back to English until retranslated; everything else still works across all 124
|
|
locales.
|
|
|
|
### This is not a competing app
|
|
|
|
Upstream's behaviour is a deliberate choice, not an oversight. Their code
|
|
contains the origin-link version, written and then commented out:
|
|
|
|
```php
|
|
// this is the account URL on its Mastodon instance
|
|
// return $entry['url'];
|
|
// this is on the instance where the search was done
|
|
return $mastodonUrl . '/@' . $entry['acct'];
|
|
```
|
|
|
|
On Mastodon, a local link is the *better* link: you stay logged in and can
|
|
reply, boost and favourite as yourself. That reasoning is sound for their
|
|
target and wrong for ours, which is the entire reason this fork exists rather
|
|
than a pull request demanding they change it.
|
|
|
|
A conservative variant of the fix — keep local links for local content, fall
|
|
back to `url`/`uri` only for remote content — is portable to upstream and
|
|
preserves their intent exactly. That belongs in their tracker, not here.
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
Not on the Nextcloud app store. Install from a release archive:
|
|
|
|
```bash
|
|
# verify before unpacking — never install an unverified archive
|
|
sha256sum -c integration_gotosocial-<version>.tar.gz.sha256
|
|
tar -xzf integration_gotosocial-<version>.tar.gz -C /path/to/nextcloud/custom_apps/
|
|
occ app:enable integration_gotosocial
|
|
```
|
|
|
|
If your `custom_apps` lives on storage where root cannot chown (NFS with
|
|
root_squash, for instance), extract as the web server user instead of as root.
|
|
|
|
Because the app is not app-store-signed, Nextcloud shows a code-integrity
|
|
notice on the admin page. Per Nextcloud's own documentation, signing is
|
|
optional outside the app store and *"code signing errors on upgrades will not
|
|
prevent Nextcloud from running"*. It is cosmetic and expected.
|
|
|
|
### Coexistence
|
|
|
|
This app uses its own app ID, so it installs alongside `integration_mastodon`
|
|
without conflict — useful if you connect to both a Mastodon and a GoToSocial
|
|
account. Each keeps its own credentials and must be connected separately.
|
|
|
|
## Configuration
|
|
|
|
Set a default GoToSocial server address in the admin settings so users get a
|
|
one-click OAuth connect. Users then connect their own account under
|
|
*Settings → Connected accounts*.
|
|
|
|
## Building from source
|
|
|
|
Requires PHP, Node 20 and npm 9/10 — all present in Debian trixie, no
|
|
third-party toolchain needed.
|
|
|
|
```bash
|
|
make composer # PHP dependencies
|
|
make npm # JS dependencies + production bundle
|
|
make appstore version=1.0.0 # release archive + sha256
|
|
```
|
|
|
|
Releases are built by CI (`.gitea/workflows/build.yml`) so the archive is
|
|
reproducible from the repository alone.
|
|
|
|
Two things worth knowing about this build:
|
|
|
|
- `make composer` (inherited from upstream) falls back to piping
|
|
`getcomposer.org/installer` into `php` when composer is not on `PATH`. The CI
|
|
job installs composer from the distribution first and **fails loudly** rather
|
|
than allow that fallback to run.
|
|
- `npm ci` fetches from the public npm registry — the one dependency hop not
|
|
served from a local mirror. `package-lock.json` pins every package with an
|
|
integrity hash, which is the mitigation.
|
|
|
|
---
|
|
|
|
## Project status
|
|
|
|
Version 1.0.0 — the fix is written, built and installed; see the tracker for
|
|
what is verified and what is still open.
|
|
|
|
Maintained on a best-effort basis. Bug reports and patches welcome at
|
|
<https://code.bawnet.io/bawnet/integration_gotosocial/issues>.
|
|
|
|
The intended relationship with upstream is cooperative, not competitive: the
|
|
GoToSocial-specific behaviour lives here, and the portable part of the fix —
|
|
the notification-target bug, plus the conservative origin-link fallback — is
|
|
offered to upstream on their terms.
|
|
|
|
## Authorship
|
|
|
|
The fork, the fix and this document were written by Neon Overlord, the AI
|
|
operator of the bawnet node, working under direction from the node's operator.
|
|
Commits carry an `Assisted-by:` trailer naming the model, following the
|
|
convention Nextcloud uses for AI-assisted contributions.
|
|
|
|
Upstream authorship is unchanged and credited in `AUTHORS.md`, `COPYING` and
|
|
every file header.
|
|
|
|
## Licence
|
|
|
|
AGPL-3.0, inherited from upstream. See `COPYING`.
|