Compare commits

...

223 Commits

Author SHA1 Message Date
Ginger 96c069cd67 chore: Update Cargo.lock 2025-11-11 11:44:07 -05:00
Ginger 9a38395f0a chore: Fix template EOFs 2025-11-11 11:36:28 -05:00
Ginger fdbb1c86b1 chore: Clippy and formatting 2025-11-11 11:36:28 -05:00
lafleur 17f8ec21a3 fix accidental project name regression 2025-11-11 11:36:28 -05:00
lafleur c46104ac0c OIDC: fix build after rebase 2025-11-11 11:36:28 -05:00
lafleur 900ba33a4e OIDC: impl client_registrar over db (sync impl) 2025-11-11 11:36:28 -05:00
lafleur f2e696ae3e OIDC auth flow: correct device registration 2025-11-11 11:36:04 -05:00
lafleur 1088aa5020 OIDC private clients: correct client secret 2025-11-11 11:36:04 -05:00
lafleur 5c2a5de7d3 unlimit log levels and update Cargo.lock 2025-11-11 11:36:04 -05:00
lafleur 600b8cb366 oidc: implement registering devices 2025-11-11 11:34:26 -05:00
lafleur 3bbbfcdd46 WIP: show discrepancy between device_id and client_id 2025-11-11 11:34:26 -05:00
lafleur e9b387414f add services::oidc::user_and_device_from_token(), use in auth 2025-11-11 11:34:26 -05:00
lafleur 426b113c30 OIDC: embed user_id in consent 2025-11-11 11:34:26 -05:00
lafleur ddae8af99f web::login: add form-data CSP rules for localhost 2025-11-11 11:34:26 -05:00
lafleur 769db9b818 add some OIDC docstrings 2025-11-11 11:34:26 -05:00
lafleur 27d9d1d78d fix oxide-auth's redirect_uri comparison
oxide-auth's `RegisteredUrl::IgnorePortOnLocalhost` doesn't work when the host is 127.0.0.1 or [::1].
This commit lets the authentication process translate the host. The new registrar already supports this.
2025-11-11 11:34:26 -05:00
lafleur 2e34ac9f59 basic OIDC client registrar with auth tracing 2025-11-11 11:34:26 -05:00
lafleur 95632103bc OIDC: make response_mode optional
Fractal omits the `response_mode` field when in an auth flow (its value must be
the literal "S256", so it's mainly here for OIDC compliance I guess). Accepting
this lets it proceed to the next authentication step.
2025-11-11 11:34:26 -05:00
lafleur d916bb9f21 support OIDC private clients 2025-11-11 11:34:26 -05:00
lafleur 4e50064740 oidc: add debug/trace logs 2025-11-11 11:34:26 -05:00
lafleur 56bd11013f oidc authorize: make response_mode optional 2025-11-11 11:34:26 -05:00
lafleur 8893bd1613 fix build warning : explicit cast 2025-11-11 11:34:26 -05:00
nexy7574 e7b9446b5a fix build errors 2025-11-11 11:34:26 -05:00
Jade Ellis bc23071dd4 fixup! fix OidcResponse: reimplement IntoResponse 2025-11-11 11:34:26 -05:00
lafleur b602be0921 fix OidcResponse: reimplement IntoResponse 2025-11-11 11:34:26 -05:00
Jade Ellis 0c333c9a05 chore: fix up 2025-11-11 11:34:26 -05:00
lafleur b9b3a466f4 oidc: small cosmetics + typos 2025-11-11 11:34:26 -05:00
lafleur 3803f06392 remove stale debugging logs
I don't have the hd space to do debug builds, so I use tracing::info to debug
on release builds. Silly, right ?
2025-11-11 11:34:26 -05:00
lafleur cadcbd7d49 use config.server_name as title in OIDC pages 2025-11-11 11:34:26 -05:00
lafleur 144036f58b fix oidc_provider discovery message and docstrings 2025-11-11 11:34:26 -05:00
lafleur 2afe656e12 typos oidc_provider discovery 2025-11-11 11:34:26 -05:00
lafleur c89dfe38da fix oidc_provider config section's doc generation 2025-11-11 11:34:26 -05:00
Jade Ellis 1ce1254514 fix: Don't crash when the client URL doesn't have a domain
Having a URL with an IP literal, for example, is allowed
2025-11-11 11:34:26 -05:00
Jade Ellis 5bd1bedad0 fix: Use correct CSP for login page 2025-11-11 11:34:26 -05:00
Jade Ellis 68ca6eabe3 chore: Ignore formatting PR in blame 2025-11-11 11:34:26 -05:00
Jade Ellis e75f5cbbed chore: Fix most clippy issue, format & typos 2025-11-11 11:34:26 -05:00
lafleur 97c692b052 remove stale dependency oxide-auth-axum 2025-11-11 11:34:26 -05:00
lafleur a586ea390c feat(oidc_provider) use askama templates
Implements a custom OidcResponse with CSP headers and oxide-auth processing
compatibility.
2025-11-11 11:34:26 -05:00
lafleur dba528a5e0 rebase on current main 2025-11-11 11:34:26 -05:00
lafleur aafc93f6fb impl MSC2966: register clients dynamically 2025-11-11 11:34:26 -05:00
lafleur cd0c3886fb impl MSC2964: OIDC token flow 2025-11-11 11:34:26 -05:00
lafleur be4ccbc11b impl MSC2965: self-advertise as OIDC authentication provider
MSC2965 proposes to let the homeserver advertise its current OIDC authentication
issuer. These changes let conduwuit advertise itself as the issuer when
[global.auth.enable_oidc_login] is set. It also advertises its account management
endpoint if [global.auth.enable_oidc_account_management] is set.

None of these endpoints are implemented. This commit only implements the bare
advertisement, as requested by the MSC.
2025-11-11 11:34:26 -05:00
Ginger 9a5ba6171f ci: Remove hardcoded default in setup-rust action 2025-11-11 10:37:03 -05:00
renovate da3efa05b5 chore(Nix): Updated flake hashes 2025-11-11 15:07:05 +00:00
Ginger b53ba2eef4 ci: Give flake hashes workflow permissions to push 2025-11-11 15:07:05 +00:00
Jade Ellis 33019c4529 chore: Update rust 2025-11-11 15:07:05 +00:00
Jade Ellis f7bd9eaba8 chore(clippy): Remove old redundant lint 2025-11-11 13:59:12 +00:00
Jade Ellis f9c42bbadc refactor(clippy): Unused self 2025-11-11 13:59:12 +00:00
Jade Ellis fe62c39501 style(clippy): Remove unneeded allocation 2025-11-11 13:59:12 +00:00
Jade Ellis 35320cf0d4 style(clippy): Elide lifetimes 2025-11-11 13:59:12 +00:00
Jade Ellis eaf6a889c2 style(clippy): Unnecessary move
Function is used in a single place and the move doesn't seem to provide
any safety benefits, so 💨
2025-11-11 13:59:12 +00:00
Jade Ellis b04f1332db style(clippy): Remove dead code
Looks like this has been dead since we forked at least, seems pretty
safe to remove
2025-11-11 13:59:12 +00:00
Jade Ellis 9e4bcda17b style(clippy): Make the event graph generic over the hasher 2025-11-11 13:59:12 +00:00
Jade 45e4053883 fix: Don't break when encountering the server user, as there may be real users after 2025-11-10 23:56:02 +00:00
Jade Ellis c0b617f4f1 feat(sentry): Include the commit hash in the release name 2025-11-10 16:57:24 +00:00
Jade Ellis a28cfd284b chore(deps): Upgrade tracing / telemetry ecosystem
We no longer need the tracing patches, so I've removed those and
unpinned them in renovate.

otel's jaeger propagator is deprecated too, so it's replaced with the
builtin W3C TraceContext propagator
2025-11-10 16:42:28 +00:00
Jade Ellis a5b9cb69bd fix(deps): Pin hyper-util back to the patched version 2025-11-10 15:56:09 +00:00
Renovate Bot 3c8f252a14 chore(deps): update opentelemetry-rust monorepo to 0.31.0 2025-11-10 05:03:16 +00:00
Jade 8a63818f31 feat: Enable sentry compilation feature 2025-11-10 01:33:50 +00:00
Renovate Bot 5b5e26e529 chore(deps): update dependency cargo-bins/cargo-binstall to v1.15.10 2025-11-09 19:05:26 +00:00
aviac 866769c054 chore: replace serde-yml with serde-saphyr
- serde-yml has an un-addressed [security issue][sec-issue]
- [saphyr][saphyr] is a pretty recent and active crate that deals with YAML parsing
- based on that, someone recently created [serde-saphyr][serde-saphyr]

---

The change was pretty straightforward and mostly "just a search and replace". The new crate has it's `Error` type split
into serialization and derserialization errors. Hence I created one Continuwuity-Error variant for each instead of just
having a single `Yaml` variant. This was already done previously with the `Toml` errors so I thought this would be
rather acceptable.

[sec-issue]: https://github.com/advisories/GHSA-gfxp-f68g-8x78
[saphyr]: https://github.com/saphyr-rs/saphyr
[serde-saphyr]: https://github.com/saphyr-rs/saphyr/issues/66#issuecomment-3353212289
2025-11-09 11:23:32 +01:00
Renovate Bot 2e3b71f5f1 chore(deps): update rust-patch-updates 2025-11-08 23:57:36 +00:00
Jade 1312d61141 revert f7867cf6ca
revert ci: Clean up old images
2025-11-08 23:56:02 +00:00
Jade Ellis f7867cf6ca ci: Clean up old images 2025-11-08 23:29:25 +00:00
Jade Ellis 2ca6887a5d chore(ci): Fix merge error 2025-11-08 23:08:10 +00:00
Jade Ellis 368685f8cd ci: Re-run mirror script when files change 2025-11-08 23:00:37 +00:00
Jade Ellis ad2d192b94 ci: Use PATs for github registry
https://stackoverflow.com/questions/76821352/how-can-you-authenticate-to-the-github-container-registry-using-a-github-app

thx github
2025-11-08 23:00:31 +00:00
Jade Ellis 3214e94cdb ci: Mirror to ghcr 2025-11-08 22:59:27 +00:00
timedout 37c537379d chore(ci): Add git.nexy7574.co.uk image mirror (#1149)
secrets were added to the org

Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1149
Co-authored-by: timedout <git@nexy7574.co.uk>
Co-committed-by: timedout <git@nexy7574.co.uk>
2025-11-08 22:56:16 +00:00
Jade Ellis 3c01c5f085 chore: Don't try to update patched deps automatically 2025-11-08 21:17:04 +00:00
Renovate Bot 4c552bb8ca chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v6 2025-11-08 20:56:00 +00:00
Jade Ellis ce73d29855 chore: Fix typos 2025-11-08 20:54:49 +00:00
Renovate Bot d6e314744b chore(deps): update pre-commit hook crate-ci/typos to v1.39.0 2025-11-08 14:34:13 +00:00
Jade ec603188de ci: Enable pre-commit in renovate 2025-11-08 14:31:35 +00:00
timedout fbf48addc7 fix(user_can): Fix room creators being unable to redact events in v12 rooms 2025-10-27 14:34:50 +00:00
nexy7574 cbf726580f fix: Kicks in !v12 are impossible 2025-10-27 14:34:50 +00:00
nexy7574 28f258fc8c fix: Incorrect interpretation of 5.5.4 2025-10-27 14:34:50 +00:00
nexy7574 8b3acfd770 fix: Inverted creatorship check 2025-10-27 14:34:50 +00:00
nexy7574 a581e8de01 fix: Don't check restricted join rules for invite joins 2025-10-27 14:34:50 +00:00
nexy7574 7c74db5e74 fix: Weird re-application of partially resolved state 2025-10-27 14:34:50 +00:00
nexy7574 b17b4235f3 fix: Unbans and kicks incorrectly checked creatorship in !v12 2025-10-27 14:34:50 +00:00
aviac ec3564e8aa chore: use upstream rust-jemalloc-sys-unprefixed after flake.lock update 2025-10-27 12:55:21 +00:00
aviac 9a887ac04b chore: fix CI to make all checks green
- define a nix default package
- try to fix CI
- fix/improve (?) CI even more (??)
2025-10-27 12:55:21 +00:00
aviac fed808a3c6 feat: add taplo.toml to check now that we have it 2025-10-27 12:55:21 +00:00
aviac 37983b33a2 feat: add treefmt 2025-10-27 12:55:21 +00:00
aviac 1b2224fac6 feat: add hydra jobs to build all packages 2025-10-27 12:55:21 +00:00
aviac c1c165ab48 fix: apply rocksdb changes in checks and shll 2025-10-27 12:55:20 +00:00
aviac 68bea1816f feat(nix): flake-parts, first draft 2025-10-27 12:55:20 +00:00
Odd E. Ebbesen cb7875e479 fix(#1134): Update docs and implementation of admin media delete-past-remote-media (#1136)
Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1136
Co-authored-by: Odd E. Ebbesen <git@oddware.net>
Co-committed-by: Odd E. Ebbesen <git@oddware.net>
2025-10-27 12:31:25 +00:00
Jade Ellis 910a3182f7 fix: Prevent crash on process exit on MacOS 2025-10-26 17:42:08 +00:00
Jade Ellis 05886f8dcb feat: Add option to control WAL compression
Also enables zstd compression by default
2025-10-26 17:30:42 +00:00
timedout cff3c27729 fix: Bump ruwuma, export new route, config loading 2025-10-24 16:37:22 -04:00
Kierre 80be2ca22c Repair 2025-10-24 16:07:06 -04:00
Kierre d133b6c0c3 feat: set MSC4373 values 2025-10-24 15:33:16 -04:00
Ginger a3592bd3b7 feat: Make a few improvements to the systemd unit
- Use systemd's credential system to supply our config file
- Remove `ConfigurationDirectory` to prevent conflicts with package managers
- Set `config_reload_signal` to true using an envvar
2025-10-17 13:37:42 +00:00
Ginger 70e8e96302 fix: Use mode 600 for config files on Fedora because they contain secret info 2025-10-17 13:37:42 +00:00
timedout 6002edccd3 perf: Remove extraneous policy server check 2025-10-16 23:57:07 +01:00
timedout d189004d65 feat: Add more granular controls for policy server calling (#1127)
Adds two new toggles to the configuration, the first of which allows disabling the policy server checks entirely, and the second of which allows disabling checking events created locally. They're both enabled by default for maximum PS efficacy but allowing them to be disabled allows people who frequently cannot contact policy servers, for example those in censored countries, to be able to still use rooms with pace, allows single-user/trusted-only homeservers to disable the preliminary check on their own events, and also gives an escape hatch in case an issue like #1060 happens again, especially with MSCs not in FCP being moving targets.

In future, I think we should gate all MSC implementations behind config flags, even if they default to on.

Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1127
Reviewed-by: Jade Ellis <jade@ellis.link>
Co-authored-by: timedout <git@nexy7574.co.uk>
Co-committed-by: timedout <git@nexy7574.co.uk>
2025-10-16 22:45:23 +00:00
timedout 26b700bf51 fix: Policy server calls use the correct JSON object (#1126)
Fixes #1060

Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1126
Reviewed-by: Jacob Taylor <aranjedeath@noreply.forgejo.ellis.link>
Co-authored-by: timedout <git@nexy7574.co.uk>
Co-committed-by: timedout <git@nexy7574.co.uk>
2025-10-16 21:06:54 +00:00
Renovate Bot 09f24745c3 chore(deps): lock file maintenance 2025-10-15 19:05:50 +00:00
Renovate Bot 7ffbbe6890 chore(deps): update https://github.com/actions/setup-node action to v6 2025-10-15 18:55:02 +00:00
Renovate Bot ad94c112fe chore(deps): update rust-patch-updates 2025-10-15 17:55:58 +00:00
Jade 8c7cc68cbf fix(ci): Don't use shallow clone when we're comparing git history 2025-10-15 12:53:15 +00:00
Ginger dc047b635f feat: Send notifications to systemd when a reload is triggered 2025-10-15 03:12:25 +00:00
Renovate Bot cc4c2fed25 chore(deps): lock file maintenance 2025-10-13 12:05:52 +00:00
Renovate Bot 17e47ecd6d chore(deps): update github-actions-non-major 2025-10-13 11:27:22 +00:00
Jade b1d5ff477b chore: Update renovate config
- Limit renovate updates to mondays
- Don't group lock updates
- Update checksums if possible
2025-10-13 11:26:26 +00:00
Renovate Bot d6dc01ac2c chore(deps): update https://code.forgejo.org/actions/checkout action to v5 2025-10-13 10:41:20 +00:00
Jimmy Brush 77ebe0d02f fix(!714): Off-by-one in v5 sync
Simplified sliding sync specifies ranges to be inclusive while rust ranges are
exclusive.
2025-10-13 10:28:19 +00:00
Renovate Bot 81e3d4c905 chore(deps): update dependency cargo-bins/cargo-binstall to v1.15.7 2025-10-13 10:27:18 +00:00
nexy7574 cb8f36444c feat: Proactively read Content-Length to reject oversized uploads 2025-10-12 19:42:57 +00:00
nexy7574 799def70dc feat: Produce even more informative errors when saving media fails 2025-10-12 19:42:57 +00:00
nexy7574 20f741d0e5 feat: Produce a more informative error when uploading media fails 2025-10-12 19:42:57 +00:00
Renovate Bot d38f4a24f2 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.146.0 2025-10-11 05:03:03 +00:00
Renovate Bot 6604cc4df9 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.144.1 2025-10-10 05:01:39 +00:00
Renovate Bot 89aa4d1eae chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.143.1 2025-10-09 05:03:56 +00:00
Renovate Bot 9231ea5114 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.141.0 2025-10-08 05:01:41 +00:00
Renovate Bot 4a3c72338d chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.138.1 2025-10-07 05:02:54 +00:00
Renovate Bot ab862f4383 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.135.5 2025-10-06 05:01:26 +00:00
Renovate Bot bd43be931a chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.135.4 2025-10-05 05:03:52 +00:00
Ginger 148240cbbb fix: Add missing ldap3 feature 2025-10-01 18:55:30 +00:00
Renovate Bot 2e9e42d9ae chore(deps): update rust crate ldap3 to 0.12.0 2025-10-01 18:55:30 +00:00
Renovate Bot 89fbda0d6e chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.132.5 2025-10-01 05:03:28 +00:00
Renovate Bot c97eb5c889 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.132.2 2025-09-30 05:01:26 +00:00
Ginger 366ec46b26 fix: Upload debs built on a schedule 2025-09-29 14:17:44 +00:00
ginger 62a98ebc71 fix: Upload RPMs built on a schedule 2025-09-29 14:17:44 +00:00
Renovate Bot 439c605efe chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.131.9 2025-09-29 05:03:13 +00:00
Renovate Bot 32df2f3487 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.131.8 2025-09-28 05:03:46 +00:00
Renovate Bot 692da7ffc2 chore(deps): update dependency cargo-bins/cargo-binstall to v1.15.6 2025-09-27 16:17:44 +00:00
Renovate Bot 1082b24b1d chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.131.6 2025-09-27 05:03:28 +00:00
nexy7574 f45ceedb8a fix(upgrade): Potentially resolve CI clippy errors
I'm not convinced this isn't a rust bug itself,
but CI was complaining about lifetimes
and those complaints couldn't be reproduced locally,
so this should probably fix it maybe?
2025-09-26 18:47:49 +01:00
nexy7574 d614e43981 fix(stateres): Creators can always unban
Also basically rewrote all of the event auth logs to be more digestable
2025-09-26 18:47:49 +01:00
Renovate Bot 1e0e7a31aa chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.131.2 2025-09-26 05:02:43 +00:00
Renovate Bot 92fffe9c82 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.130.1 2025-09-25 08:28:06 +00:00
Renovate Bot 11e51300a5 chore(deps): update github-actions-non-major 2025-09-25 08:16:34 +00:00
Jade Ellis ef84e1bb02 fix(v12): Create tombstone event on room upgrade 2025-09-25 08:15:23 +00:00
nexy7574 1887d58df8 fix: V12 room upgrades 2025-09-25 08:15:23 +00:00
nexy7574 c66f6f8900 fix(stateres): Correctly fetch missing auth events for incoming PDUs 2025-09-25 02:54:00 +01:00
Ginger 902fe7b7ab fix: Fix panic in debug builds caused by MSC4133 migration 2025-09-24 16:45:11 -04:00
Renovate Bot 472e1fee17 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.127.2 2025-09-24 05:03:46 +00:00
Jade Ellis 3c6f2d07e0 ci: Only run RPM builds on tags or schedule 2025-09-23 22:16:46 +01:00
ginger 43254aa396 fix: Attempt to enable RPM signing 2025-09-23 22:15:01 +01:00
Tom Foster 48ebf86335 feat(ci): Add Fedora RPM package build workflow
Build and publish RPM packages for Fedora using rpkg and official
rust-packaging macros. Packages are automatically signed by Forgejo's
built-in package registry (introduced in v9.0).

Publishes packages to organised groups:
- continuwuity (binary): base group (stable/dev/branch-name)
- continuwuity-debuginfo: GROUP-debug
- continuwuity (source RPM): GROUP-src

Workflow triggers on pushes to relevant paths and version tags (v*).
Tagged releases use clean version numbers (v1.2.3 becomes 1.2.3-1)
while branch builds use sanitised branch name versioning.

Uses dnf builddep to install build dependencies directly from the
generated SRPM, ensuring consistency between CI and spec file without
duplication. This also prevents hiding packaging issues that could
occur with --nodeps fallbacks.
2025-09-23 22:15:00 +01:00
Ginger f1e3b4907e Build Debian packages and upload them to Forgejo's repository (#996)
This uses the existing `cargo-deb` metadata.

Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/996
Reviewed-by: Tom Foster <tom@tcpip.uk>
Reviewed-by: nex <nex@noreply.forgejo.ellis.link>
Co-authored-by: Ginger <ginger@gingershaped.computer>
Co-committed-by: Ginger <ginger@gingershaped.computer>
2025-09-23 19:53:37 +00:00
Jade Ellis 9346a0d05e fix(ci): Typo 2025-09-23 12:55:04 +01:00
nyanbinary c99faae115 chore(nix): bump rocksdb version in flake.nix to 10.5.fb 2025-09-23 06:31:10 +00:00
Renovate Bot a5aa68ee8d chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.125.2 2025-09-23 03:53:03 +00:00
Tom Foster 8959ac06ac ci: Split Rust build cache into dependencies and incremental caches
Replace single large build cache with separate dependencies and incremental
caches. Dependencies cache survives source code changes and uses tiered
restore keys. Removes build directory from caching to improve CI performance
while maintaining effective compilation caching with sccache.
2025-09-23 04:30:35 +01:00
Tom Foster 47f7ebfd68 fix: Use node_version in npm cache key for wrangler installation
Replace hashFiles('**/package-lock.json') with node_version since wrangler
is installed via npm without a lockfile to hash. Removes trailing dash from
cache keys and ensures npm dependencies are regenerated when Node.js version
changes.
2025-09-23 04:30:35 +01:00
Tom Foster 7d91f218b1 ci: Migrate to detect-versions with namespaced cache keys
Replace local detect-runner-os action with external detect-versions@v1 to
reduce custom action maintenance. Add architecture detection for future
cross-platform support and namespace all cache keys with "continuwuity-"
prefix to prevent collisions with other projects on shared runners.

Updates cache mount IDs in Dockerfiles to match the new namespacing
convention, ensuring consistent cache isolation across CI and Docker builds.
2025-09-23 04:30:22 +01:00
Jade Ellis e5e2db37d9 ci: Run image release workflow on tag 2025-09-22 17:03:26 +01:00
Jade Ellis e08ea3b9e5 ci: Trace commands to push docker manifests 2025-09-22 17:03:26 +01:00
Jade Ellis 4f1907abfa ci: Change tag generation to use suffix flavour 2025-09-22 17:03:26 +01:00
Ginger 92d74c293e feat: Advertise support for MSC4155 2025-09-22 11:33:45 -04:00
Renovate Bot 3fbdced0e1 chore(deps): update github-actions-non-major 2025-09-22 05:04:03 +00:00
nexy7574 b70470fa71 fix: Event filters all non-state events 2025-09-21 20:10:36 +01:00
nexy7574 703d6a2075 chore: Bump version to rc.8 2025-09-21 18:17:24 +01:00
Savyasachee Jha 5b75e21810 Update resolv-conf to upstream 0.7.5 2025-09-21 17:13:38 +00:00
Ginger 13b7538785 Add support for MSC4155 (#1013)
[rendered msc here](https://github.com/Johennes/matrix-spec-proposals/blob/johannes/invite-filtering/proposals/4155-invite-filtering.md). Closes #836.

Co-authored-by: nexy7574 <git@nexy7574.co.uk>
Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1013
Reviewed-by: nex <nex@noreply.forgejo.ellis.link>
Co-authored-by: Ginger <ginger@gingershaped.computer>
Co-committed-by: Ginger <ginger@gingershaped.computer>
2025-09-21 17:03:40 +00:00
Renovate Bot 9745bcba1c chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.121.4 2025-09-21 05:02:02 +00:00
nexy7574 c9c79fbea6 fix: Fix restricted join rules inconsistencies 2025-09-20 21:07:13 +00:00
nexy7574 92e9802340 style: Tidy up 1054 2025-09-20 21:07:00 +00:00
nexy7574 1d80b7ce0c fix: Don't perform local join when there's no remote servers 2025-09-20 21:07:00 +00:00
Jade 563b6d4b30 fix: Update debug assertion with new serde type location
Fixes !1052
2025-09-20 18:04:16 +00:00
Renovate Bot e86fc6d9f8 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.119.5 2025-09-20 05:03:27 +00:00
Renovate Bot 13adea6498 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.118.1 2025-09-19 10:31:58 +00:00
Renovate Bot 17d0bb6cf6 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.117.0 2025-09-18 21:06:35 +00:00
Renovate Bot 6dc5051fa6 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.116.10 2025-09-18 19:26:39 +00:00
Renovate Bot 3034c03ad1 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.116.8 2025-09-18 13:36:19 +00:00
Renovate Bot fa6f549d39 chore(deps): lock file maintenance 2025-09-18 13:32:26 +00:00
Renovate Bot 999217b0f6 chore(deps): update dependency cargo-bins/cargo-binstall to v1.15.5 2025-09-18 13:31:48 +00:00
Renovate Bot 74fccff2cc chore(deps): update github-actions-non-major 2025-09-18 13:31:19 +00:00
Shuroii 7a56a2462c fix(ci): Use github env namespace as forgejo is still unsupported 2025-09-18 13:30:50 +00:00
Ginger 458811f241 fix: Fix nexy's very accurate and not-at-all busted fix to my fix 2025-09-17 20:04:50 -04:00
nexy7574 0672ce5b88 style: Fix clippy lint errors 2025-09-17 23:54:09 +01:00
Ginger 7f287c7880 fix: Use a database migration to fix corrupted us.cloke.msc4175.tz fields
(cherry picked from commit 4a893ce4cc81487bcf324dccefd8184ddef5b215)
2025-09-17 23:14:07 +01:00
Shuroii 9142978a15 fix: Fully qualify action
This fixes an issue where Forgejo tries to look for code.forgejo.org for the action despite it not being available.
2025-09-17 21:37:50 +00:00
Shuroii a8eb9c47f8 feat(ci): Add a workflow to update flake hashes
This workflow is intended to be ran as dispatch whenever the rocksdb fork changes!
Other than that, it'll run on any toolchain changes (rust-toolchain.toml, Cargo.lock, Cargo.toml) and update the relevant hash accordingly.
2025-09-17 21:37:50 +00:00
nexy7574 9f18cf667a chore: Temporarily disable bad tests 2025-09-17 22:25:04 +01:00
nexy7574 7e4071c117 Implement room v12 (#943)
**Does not yet work!** Currently, state resolution does not correctly resolve conflicting states. Everything else appears to work as expected, so stateres will be fixed soon, then we should be clear for takeoff.

Also: a lot of things currently accept a nullable room ID that really just don't need to. This will need tidying up before merge. Some authentication checks have also been disabled temporarily but nothing important.

A lot of things are tagged with `TODO(hydra)`, those need resolving before merge. External contributors should PR to the `hydra/public` branch, *not* ` main`.

---

This PR should be squash merged.

Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/943
Co-authored-by: nexy7574 <git@nexy7574.co.uk>
Co-committed-by: nexy7574 <git@nexy7574.co.uk>
2025-09-17 20:46:03 +00:00
Renovate Bot 51423c9d7d chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.115.6 2025-09-17 05:03:46 +00:00
Ginger a0b0ff9d5c fix: Remove legacy check for u. prefix 2025-09-16 11:30:39 +00:00
Ginger 8e27d74c4a fix: Slightly more parallelism 2025-09-16 11:30:39 +00:00
Ginger d6b1055683 fix: Remove needless async marker 2025-09-16 11:30:39 +00:00
Ginger c9117e6ee4 fix: Fix incorrect deserialization of MSC4133 profile fields 2025-09-16 11:30:39 +00:00
Ginger e3415a500d chore: Code cleanup 2025-09-16 11:30:39 +00:00
Ginger e6fd3c970b fix: Nuke explicit references to the MSC4175 tz profile field 2025-09-16 11:30:39 +00:00
Renovate Bot 6b7f35a8b8 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.115.0 2025-09-16 05:01:56 +00:00
Tom Foster a120a4fa95 fix: Handle runner cargo bin path migration in timelord action
Runner images have migrated from /usr/share/rust/.cargo/bin to standard
~/.cargo/bin location. Action now checks old location first and migrates
binaries if found, maintaining compatibility with both paths.

Bump cache key to v3 to ensure fresh binary cache after path changes.
2025-09-15 16:17:32 +01:00
Renovate Bot f872210b20 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.113.4 2025-09-15 05:01:40 +00:00
Renovate Bot 3dd04bd9df chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.113.2 2025-09-14 05:03:21 +00:00
Ginger af45c348a4 fix: Properly deserialize changes to legacy fields made with MSC4133 endpoints 2025-09-14 01:28:08 +00:00
nexy7574 36dabecb82 chore(1014): Include MSC4155 in build features to resolve build errors 2025-09-14 00:53:43 +00:00
nexy7574 50cd1081ba chore(1014): Bump ruwuma 2025-09-14 00:53:43 +00:00
nexy7574 14df55e5c5 style(1014): Remove unnecessary commented code 2025-09-14 00:53:43 +00:00
nexy7574 d9d0d1a465 fix(!1014): Don't prematurely return during registration 2025-09-14 00:53:43 +00:00
Tom Foster 81b6b3547c fix: Resolve Forgejo runner v11 matrix job execution failure
Matrix jobs stopped starting after upgrading from runner v9 to v11 due to
changes in job dependency resolution. Remove redundant define-variables job
that computed static image paths and replace with IMAGE_PATH environment
variable.

Also fix timelord action binary caching for compatibility between different
runner images that install cargo binaries in different locations.
2025-09-13 17:12:09 +01:00
Renovate Bot 0bbc3c4e05 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.112.0 2025-09-12 21:11:13 +00:00
Jade 0f09fa3d31 chore(renovate): Specify automerge strategy 2025-09-12 21:02:25 +00:00
Tom Foster 3d5355dfc3 chore(renovate): Add auto-merge for renovatebot and reorganise package rules
Enable automatic merging of ghcr.io/renovatebot/renovate docker image updates
to reduce manual maintenance overhead.

Reorganise package rules by manager type (cargo, github-actions, docker) and
add missing description for cargo concurrency limit rule to improve config
maintainability.
2025-09-12 17:50:08 +01:00
Renovate Bot 2547eb3a90 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.109.0 2025-09-12 13:29:47 +00:00
Renovate Bot 51ba41823f chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.106.0 2025-09-12 13:23:28 +00:00
Tom Foster 542dff50bd ci: Split Docker builds into sequential release and max-perf stages
Separate fast release builds from slow max-perf builds to optimise runner
utilisation and provide quicker feedback. Release builds complete first with
standard optimisations, followed by Haswell-optimised dragrace builds once
the safe builds pass successfully.

Extract build logic into focused composite actions for better log visibility
in Forgejo UI. Split monolithic build action into prepare-docker-build,
inline docker build step, and upload-docker-artifacts to ensure each phase
completes independently and shows logs immediately.

Creates separate manifests at each stage to avoid waiting for all builds
before publishing.
2025-09-12 12:43:19 +01:00
Tom Foster 9c147b182f ci: Fix BuildKit cache invalidation and add Haswell-optimised builds
The workflow was rebuilding dependencies unnecessarily despite timelord
restoring timestamps because TARGET_CPU and RUST_PROFILE weren't passed
to Docker, creating inconsistent cache keys. Now passes both arguments
for proper cache reuse.

Adds Haswell-optimised builds alongside baseline builds using -march=haswell
for PCLMUL instruction support. Recent build improvements reducing compile
times from 15-20 minutes to ~5 minutes make this additional CPU variant
feasible. Users can pull optimised images with -haswell suffix.
2025-09-11 13:59:43 +01:00
Renovate Bot 7e76ca45c1 chore(deps): lock file maintenance 2025-09-11 12:28:11 +00:00
Tom Foster 5126cb4554 fix: Use forgejo/upload-artifact@v4 for artifact consistency
Follow-on to correct #1009. The previous fix downgraded upload-artifact
to v3 but kept download-artifact@v4, creating incompatible storage
formats that prevented artifact pattern filtering from working.

Update all upload-artifact actions to v4 and adjust renovate
configuration to disable automatic updates for forgejo artifact
actions to maintain version consistency.
2025-09-11 11:57:04 +01:00
Renovate Bot 4d05d0f677 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.99.9 2025-09-11 09:56:48 +00:00
Tom Foster 0673ac1a6c fix: Fix artifact action compatibility and add digest debugging
Resolve upload-artifact v4 GHES compatibility errors by downgrading to v3.
Switch to standard forgejo/download-artifact@v4 for pattern filtering support.
Update renovate configuration to prevent future incompatible upgrades.

Add diagnostic output to digest export step to troubleshoot zero-byte
artifact uploads preventing manifest creation. Include CI triggers for
Element workflow to test changes in pull requests.
2025-09-11 10:44:11 +01:00
Jade Ellis ad11417145 chore(deps): Replace serde_yaml with serde_yml 2025-09-10 20:20:45 +01:00
Renovate Bot 0de904ffe4 chore(deps): update rust crate const-str to 0.7.0 2025-09-10 18:05:00 +00:00
Renovate Bot d74b9de221 chore(deps): update dependency cargo-bins/cargo-binstall to v1.15.4 2025-09-10 17:44:44 +00:00
Renovate Bot e7ac5988cb chore(deps): update https://github.com/actions/setup-node action to v5 2025-09-10 17:06:45 +00:00
Jade Ellis 571f05017c chore: Update resolv git hash 2025-09-10 17:50:37 +01:00
Jade Ellis a339e73eb5 chore: Unify actions versions 2025-09-10 17:39:25 +01:00
Jade Ellis 72b78ed6d4 chore: Fix nightly-only clippy lints 2025-09-10 17:35:17 +01:00
nexy7574 baa89586e2 fix(MSC4277): Undo refuted response changes 2025-09-10 16:25:06 +00:00
nexy7574 7ad8ff2e45 style(MSC4277): Run lints to satisfy checks 2025-09-10 16:25:06 +00:00
nexy7574 2046b1e2f6 feat(MSC4277): Unify reporting endpoint behaviours
* reporting rooms now always returns 200 OK
* reporting an event returns OK if we don't know about the reported event
* removed the score parameter (needs a followup ruwuma update)
2025-09-10 16:25:06 +00:00
Renovate Bot 2cb980cd4c chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.99.7 2025-09-10 16:16:34 +00:00
Jade Ellis 27e0ef7b2e chore: Update renovate CI
- Fixes some issues with the action - Enables OSV vuln scanning -
Enables updating the dockerfile tool versions
2025-09-10 16:53:59 +01:00
Jade Ellis 7091882887 chore: Update cargo lockfile 2025-09-10 16:47:20 +01:00
Jade Ellis a81546374d ci: Make timelord docker work locally 2025-09-10 16:40:55 +01:00
Tom Foster 7950e2cc7f ci: Refactor timelord action to use git-warp-time fallback
Updates the timelord action to fall back to git-warp-time when the cache
is completely empty, enabling timestamp restoration even on fresh builds.
When git-warp-time is used, performs an unshallow fetch to get full history,
while subsequent runs use normal fetches. Simplifies the interface by making
inputs optional with sensible defaults.

Adds binary caching for timelord-cli and git-warp-time tools to avoid
repeated installations, and updates paths to use /usr/share/rust/.cargo/bin/
for the catthehacker runner image used by the dind profile (may need updating
if/when switching to standard image).

The main timelord restore now happens inside the Dockerfile itself, as Docker
intentionally wipes all file mtimes on COPY/ADD operations.
2025-09-08 08:34:29 +00:00
190 changed files with 7557 additions and 3825 deletions
@@ -0,0 +1,108 @@
name: create-manifest
description: |
Create and push a multi-platform Docker manifest from individual platform digests.
Handles downloading digests, creating manifest lists, and pushing to registry.
inputs:
digest_pattern:
description: Glob pattern to match digest artifacts (e.g. "digests-linux-{amd64,arm64}")
required: true
tag_suffix:
description: Suffix to add to all Docker tags (e.g. "-maxperf")
required: false
default: ""
images:
description: Container registry images (newline-separated)
required: true
registry_user:
description: Registry username for authentication
required: false
registry_password:
description: Registry password for authentication
required: false
outputs:
version:
description: The version tag created for the manifest
value: ${{ steps.meta.outputs.version }}
tags:
description: All tags created for the manifest
value: ${{ steps.meta.outputs.tags }}
runs:
using: composite
steps:
- name: Download digests
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/download-artifact@v4
with:
path: /tmp/digests
pattern: ${{ inputs.digest_pattern }}
merge-multiple: true
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
password: ${{ inputs.registry_password }}
- name: Set up Docker Buildx
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/setup-buildx-action@v3
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
- name: Extract metadata (tags) for Docker
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
id: meta
uses: docker/metadata-action@v5
with:
flavor: |
suffix=${{ inputs.tag_suffix }},onlatest=true
tags: |
type=semver,pattern={{version}},prefix=v
type=semver,pattern={{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }},prefix=v
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }},prefix=v
type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }},
type=ref,event=pr
type=sha,format=short
type=raw,value=latest${{ inputs.tag_suffix }},enable=${{ startsWith(github.ref, 'refs/tags/v') }},priority=1100
images: ${{ inputs.images }}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
- name: Create manifest list and push
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
working-directory: /tmp/digests
shell: bash
env:
IMAGES: ${{ inputs.images }}
run: |
set -o xtrace
IFS=$'\n'
IMAGES_LIST=($IMAGES)
ANNOTATIONS_LIST=($DOCKER_METADATA_OUTPUT_ANNOTATIONS)
TAGS_LIST=($DOCKER_METADATA_OUTPUT_TAGS)
for REPO in "${IMAGES_LIST[@]}"; do
docker buildx imagetools create \
$(for tag in "${TAGS_LIST[@]}"; do echo "--tag"; echo "$tag"; done) \
$(for annotation in "${ANNOTATIONS_LIST[@]}"; do echo "--annotation"; echo "$annotation"; done) \
$(for reference in *; do printf "$REPO@sha256:%s\n" $reference; done)
done
- name: Inspect image
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
shell: bash
env:
IMAGES: ${{ inputs.images }}
run: |
set -o xtrace
IMAGES_LIST=($IMAGES)
for REPO in "${IMAGES_LIST[@]}"; do
docker buildx imagetools inspect $REPO:${{ steps.meta.outputs.version }}
done
@@ -1,58 +0,0 @@
name: detect-runner-os
description: |
Detect the actual OS name and version of the runner.
Provides separate outputs for name, version, and a combined slug.
outputs:
name:
description: 'OS name (e.g. Ubuntu, Debian)'
value: ${{ steps.detect.outputs.name }}
version:
description: 'OS version (e.g. 22.04, 11)'
value: ${{ steps.detect.outputs.version }}
slug:
description: 'Combined OS slug (e.g. Ubuntu-22.04)'
value: ${{ steps.detect.outputs.slug }}
node_major:
description: 'Major version of Node.js if available (e.g. 22)'
value: ${{ steps.detect.outputs.node_major }}
node_version:
description: 'Full Node.js version if available (e.g. 22.19.0)'
value: ${{ steps.detect.outputs.node_version }}
runs:
using: composite
steps:
- name: Detect runner OS
id: detect
shell: bash
run: |
# Detect OS version (try lsb_release first, fall back to /etc/os-release)
OS_VERSION=$(lsb_release -rs 2>/dev/null || grep VERSION_ID /etc/os-release | cut -d'"' -f2)
# Detect OS name and capitalise (try lsb_release first, fall back to /etc/os-release)
OS_NAME=$(lsb_release -is 2>/dev/null || grep "^ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"' | sed 's/\b\(.\)/\u\1/g')
# Create combined slug
OS_SLUG="${OS_NAME}-${OS_VERSION}"
# Detect Node.js version if available
if command -v node >/dev/null 2>&1; then
NODE_VERSION=$(node --version | sed 's/v//')
NODE_MAJOR=$(echo $NODE_VERSION | cut -d. -f1)
echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT
echo "node_major=${NODE_MAJOR}" >> $GITHUB_OUTPUT
echo "🔍 Detected Node.js: v${NODE_VERSION}"
else
echo "node_version=" >> $GITHUB_OUTPUT
echo "node_major=" >> $GITHUB_OUTPUT
echo "🔍 Node.js not found"
fi
# Set OS outputs
echo "name=${OS_NAME}" >> $GITHUB_OUTPUT
echo "version=${OS_VERSION}" >> $GITHUB_OUTPUT
echo "slug=${OS_SLUG}" >> $GITHUB_OUTPUT
# Log detection results
echo "🔍 Detected Runner OS: ${OS_NAME} ${OS_VERSION}"
@@ -0,0 +1,169 @@
name: prepare-docker-build
description: |
Prepare the Docker build environment for Continuwuity builds.
Sets up Rust toolchain, Docker Buildx, caching, and extracts metadata for Docker builds.
inputs:
platform:
description: Target platform (e.g. linux/amd64, linux/arm64)
required: true
slug:
description: Platform slug for artifact naming (e.g. linux-amd64, linux-arm64)
required: true
target_cpu:
description: Target CPU architecture (e.g. haswell, empty for base)
required: false
default: ""
profile:
description: Cargo build profile (release or release-max-perf)
required: true
images:
description: Container registry images (newline-separated)
required: true
registry_user:
description: Registry username for authentication
required: false
registry_password:
description: Registry password for authentication
required: false
outputs:
cpu_suffix:
description: CPU suffix for artifact naming
value: ${{ steps.cpu-suffix.outputs.suffix }}
metadata_labels:
description: Docker labels for the image
value: ${{ steps.meta.outputs.labels }}
metadata_annotations:
description: Docker annotations for the image
value: ${{ steps.meta.outputs.annotations }}
runs:
using: composite
steps:
- name: Set CPU suffix variable
id: cpu-suffix
shell: bash
run: |
if [[ -n "${{ inputs.target_cpu }}" ]]; then
echo "suffix=-${{ inputs.target_cpu }}" >> $GITHUB_OUTPUT
echo "CPU_SUFFIX=-${{ inputs.target_cpu }}" >> $GITHUB_ENV
else
echo "suffix=" >> $GITHUB_OUTPUT
echo "CPU_SUFFIX=" >> $GITHUB_ENV
fi
- name: Echo matrix configuration
shell: bash
run: |
echo "Platform: ${{ inputs.platform }}"
echo "Slug: ${{ inputs.slug }}"
echo "Target CPU: ${{ inputs.target_cpu }}"
echo "Profile: ${{ inputs.profile }}"
- name: Install rust
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: rust-toolchain
uses: ./.forgejo/actions/rust-toolchain
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
- name: Set up QEMU
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: docker/setup-qemu-action@v3
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
password: ${{ inputs.registry_password }}
- name: Extract metadata (labels, annotations) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ inputs.images }}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
- name: Get short git commit SHA
id: sha
shell: bash
run: |
calculatedSha=$(git rev-parse --short ${{ github.sha }})
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
echo "Short SHA: $calculatedSha"
- name: Get Git commit timestamps
shell: bash
run: |
timestamp=$(git log -1 --pretty=%ct)
echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV
echo "Commit timestamp: $timestamp"
- uses: ./.forgejo/actions/timelord
id: timelord
- name: Cache Rust registry
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: actions/cache@v3
with:
path: |
.cargo/git
.cargo/git/checkouts
.cargo/registry
.cargo/registry/src
key: continuwuity-rust-registry-image-${{hashFiles('**/Cargo.lock') }}
- name: Cache cargo target
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-cargo-target
uses: actions/cache@v3
with:
path: |
cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}
key: continuwuity-cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}}
- name: Cache apt cache
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt
uses: actions/cache@v3
with:
path: |
var-cache-apt-${{ inputs.slug }}
key: continuwuity-var-cache-apt-${{ inputs.slug }}
- name: Cache apt lib
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt-lib
uses: actions/cache@v3
with:
path: |
var-lib-apt-${{ inputs.slug }}
key: continuwuity-var-lib-apt-${{ inputs.slug }}
- name: inject cache into docker
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.3.0
with:
cache-map: |
{
".cargo/registry": "/usr/local/cargo/registry",
".cargo/git/db": "/usr/local/cargo/git/db",
"cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}": {
"target": "/app/target",
"id": "cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}"
},
"var-cache-apt-${{ inputs.slug }}": "/var/cache/apt",
"var-lib-apt-${{ inputs.slug }}": "/var/lib/apt",
"${{ steps.timelord.outputs.database-path }}":"/timelord"
}
skip-extraction: ${{ steps.cache.outputs.cache-hit }}
+1 -1
View File
@@ -40,7 +40,7 @@ runs:
!~/.rustup/tmp
!~/.rustup/downloads
# Requires repo to be cloned if toolchain is not specified
key: ${{ runner.os }}-rustup-${{ inputs.toolchain || hashFiles('**/rust-toolchain.toml') }}
key: continuwuity-${{ runner.os }}-rustup-${{ inputs.toolchain || hashFiles('**/rust-toolchain.toml') }}
- name: Install Rust toolchain
if: steps.rustup-version.outputs.version == ''
shell: bash
+1 -1
View File
@@ -9,7 +9,7 @@ runs:
- name: Install sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Configure sccache
uses: https://github.com/actions/github-script@v7
uses: https://github.com/actions/github-script@v8
with:
script: |
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
@@ -29,7 +29,7 @@ runs:
steps:
- name: Detect runner OS
id: runner-os
uses: ./.forgejo/actions/detect-runner-os
uses: https://git.tomfos.tr/actions/detect-versions@v1
- name: Configure cross-compilation architecture
if: inputs.dpkg-arch != ''
@@ -57,7 +57,7 @@ runs:
- name: Check for LLVM cache
id: cache
uses: https://github.com/actions/cache@v4
uses: actions/cache@v4
with:
path: |
/usr/bin/clang-*
@@ -69,7 +69,7 @@ runs:
/usr/lib/x86_64-linux-gnu/libclang*.so*
/etc/apt/sources.list.d/archive_uri-*
/etc/apt/trusted.gpg.d/apt.llvm.org.asc
key: llvm-${{ steps.runner-os.outputs.slug }}-v${{ inputs.llvm-version }}-v3-${{ hashFiles('**/Cargo.lock', 'rust-toolchain.toml') }}
key: continuwuity-llvm-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-v${{ inputs.llvm-version }}-${{ hashFiles('**/Cargo.lock', 'rust-toolchain.toml') }}
- name: End LLVM cache group
shell: bash
+30 -17
View File
@@ -19,7 +19,7 @@ inputs:
rust-version:
description: 'Rust version to install (e.g. nightly). Defaults to 1.87.0'
required: false
default: '1.87.0'
default: ''
sccache-cache-limit:
description: 'Maximum size limit for sccache local cache (e.g. 2G, 500M)'
required: false
@@ -39,7 +39,7 @@ runs:
steps:
- name: Detect runner OS
id: runner-os
uses: ./.forgejo/actions/detect-runner-os
uses: https://git.tomfos.tr/actions/detect-versions@v1
- name: Configure Cargo environment
shell: bash
@@ -65,7 +65,7 @@ runs:
- name: Cache Cargo registry and git
id: registry-cache
uses: https://github.com/actions/cache@v4
uses: actions/cache@v4
with:
path: |
.cargo/registry/index
@@ -73,42 +73,55 @@ runs:
.cargo/git/db
# Registry cache saved per workflow, restored from any workflow's cache
# Each workflow maintains its own registry that accumulates its needed crates
key: cargo-registry-${{ steps.runner-os.outputs.slug }}-${{ github.workflow }}
key: continuwuity-cargo-registry-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ github.workflow }}
restore-keys: |
cargo-registry-${{ steps.runner-os.outputs.slug }}-
continuwuity-cargo-registry-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-
- name: Cache toolchain binaries
id: toolchain-cache
uses: https://github.com/actions/cache@v4
uses: actions/cache@v4
with:
path: |
.cargo/bin
.rustup/toolchains
.rustup/update-hashes
# Shared toolchain cache across all Rust versions
key: toolchain-${{ steps.runner-os.outputs.slug }}
key: continuwuity-toolchain-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}
- name: Setup sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Cache build artifacts
id: build-cache
uses: https://github.com/actions/cache@v4
- name: Cache dependencies
id: deps-cache
uses: actions/cache@v4
with:
path: |
target/**/deps
!target/**/deps/*.rlib
target/**/build
target/**/.fingerprint
target/**/incremental
target/**/deps
target/**/*.d
target/**/.cargo-lock
target/**/CACHEDIR.TAG
target/**/.rustc_info.json
/timelord/
# Build artifacts - cache per code change, restore from deps when code changes
# Dependencies cache - based on Cargo.lock, survives source code changes
key: >-
build-${{ steps.runner-os.outputs.slug }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-${{ hashFiles('**/*.rs', '**/Cargo.toml') }}
continuwuity-deps-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}
restore-keys: |
build-${{ steps.runner-os.outputs.slug }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-
continuwuity-deps-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-
- name: Cache incremental compilation
id: incremental-cache
uses: actions/cache@v4
with:
path: |
target/**/incremental
# Incremental cache - based on source code changes
key: >-
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-${{ hashFiles('**/*.rs', '**/Cargo.toml') }}
restore-keys: |
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-
- name: End cache restore group
shell: bash
+102 -28
View File
@@ -1,46 +1,120 @@
name: timelord
description: |
Use timelord to set file timestamps
Use timelord to set file timestamps with git-warp-time fallback for cache misses
inputs:
key:
description: |
The key to use for caching the timelord data.
This should be unique to the repository and the runner.
required: true
default: timelord-v0
required: false
default: ''
path:
description: |
The path to the directory to be timestamped.
This should be the root of the repository.
required: true
default: .
required: false
default: ''
outputs:
database-path:
description: Path to timelord database
value: '${{ env.TIMELORD_CACHE_PATH }}'
runs:
using: composite
steps:
- name: Cache timelord-cli installation
id: cache-timelord-bin
uses: actions/cache@v3
with:
path: ~/.cargo/bin/timelord
key: timelord-cli-v3.0.1
- name: Install timelord-cli
uses: https://github.com/cargo-bins/cargo-binstall@main
if: steps.cache-timelord-bin.outputs.cache-hit != 'true'
- run: cargo binstall timelord-cli@3.0.1
- name: Set defaults
shell: bash
if: steps.cache-timelord-bin.outputs.cache-hit != 'true'
run: |
echo "TIMELORD_KEY=${{ inputs.key || format('timelord-v1-{0}-{1}', github.repository, hashFiles('**/*.rs', '**/Cargo.toml', '**/Cargo.lock')) }}" >> $GITHUB_ENV
echo "TIMELORD_PATH=${{ inputs.path || '.' }}" >> $GITHUB_ENV
echo "TIMELORD_CACHE_PATH=$HOME/.cache/timelord" >> $GITHUB_ENV
echo "PATH=$HOME/.cargo/bin:/usr/share/rust/.cargo/bin:$PATH" >> $GITHUB_ENV
- name: Load timelord files
uses: actions/cache/restore@v3
- name: Restore binary cache
id: binary-cache
uses: actions/cache/restore@v4
with:
path: /timelord/
key: ${{ inputs.key }}
- name: Run timelord to set timestamps
path: |
/usr/share/rust/.cargo/bin
~/.cargo/bin
key: continuwuity-timelord-binaries
- name: Check if binaries need installation
shell: bash
run: timelord sync --source-dir ${{ inputs.path }} --cache-dir /timelord/
- name: Save timelord
uses: actions/cache/save@v3
id: check-binaries
run: |
NEED_INSTALL=false
# Ensure ~/.cargo/bin exists
mkdir -p ~/.cargo/bin
# Check and move timelord if needed
if [ -f /usr/share/rust/.cargo/bin/timelord ] && [ ! -f ~/.cargo/bin/timelord ]; then
echo "Moving timelord from /usr/share/rust/.cargo/bin to ~/.cargo/bin"
mv /usr/share/rust/.cargo/bin/timelord ~/.cargo/bin/
fi
if [ ! -f ~/.cargo/bin/timelord ]; then
echo "timelord-cli not found, needs installation"
NEED_INSTALL=true
fi
# Check and move git-warp-time if needed
if [ -f /usr/share/rust/.cargo/bin/git-warp-time ] && [ ! -f ~/.cargo/bin/git-warp-time ]; then
echo "Moving git-warp-time from /usr/share/rust/.cargo/bin to ~/.cargo/bin"
mv /usr/share/rust/.cargo/bin/git-warp-time ~/.cargo/bin/
fi
if [ ! -f ~/.cargo/bin/git-warp-time ]; then
echo "git-warp-time not found, needs installation"
NEED_INSTALL=true
fi
echo "need-install=$NEED_INSTALL" >> $GITHUB_OUTPUT
- name: Install timelord-cli and git-warp-time
if: steps.check-binaries.outputs.need-install == 'true'
uses: https://github.com/taiki-e/install-action@v2
with:
path: /timelord/
key: ${{ inputs.key }}
tool: git-warp-time,timelord-cli@3.0.1
- name: Save binary cache
if: steps.check-binaries.outputs.need-install == 'true'
uses: actions/cache/save@v4
with:
path: |
/usr/share/rust/.cargo/bin
~/.cargo/bin
key: continuwuity-timelord-binaries
- name: Restore timelord cache with fallbacks
id: timelord-restore
uses: actions/cache/restore@v4
with:
path: ${{ env.TIMELORD_CACHE_PATH }}
key: ${{ env.TIMELORD_KEY }}
restore-keys: |
continuwuity-timelord-${{ github.repository }}-
- name: Initialize timestamps on complete cache miss
if: steps.timelord-restore.outputs.cache-hit != 'true'
shell: bash
run: |
echo "Complete timelord cache miss - running git-warp-time"
git fetch --unshallow
if [ "${{ env.TIMELORD_PATH }}" = "." ]; then
git-warp-time --quiet
else
git-warp-time --quiet ${{ env.TIMELORD_PATH }}
fi
echo "Git timestamps restored"
- name: Run timelord sync
shell: bash
run: |
mkdir -p ${{ env.TIMELORD_CACHE_PATH }}
timelord sync --source-dir ${{ env.TIMELORD_PATH }} --cache-dir ${{ env.TIMELORD_CACHE_PATH }}
- name: Save updated timelord cache immediately
uses: actions/cache/save@v4
with:
path: ${{ env.TIMELORD_CACHE_PATH }}
key: ${{ env.TIMELORD_KEY }}
@@ -0,0 +1,70 @@
name: upload-docker-artifacts
description: |
Upload Docker build artifacts including binary and digest files.
Handles artifact naming and conditional digest uploads for registry publishing.
inputs:
slug:
description: Platform slug for artifact naming (e.g. linux-amd64, linux-arm64)
required: true
cpu_suffix:
description: CPU suffix for artifact naming (e.g. -haswell)
required: false
default: ""
artifact_suffix:
description: Suffix for binary artifacts (e.g. -maxperf)
required: false
default: ""
digest_suffix:
description: Suffix for digest artifacts (e.g. -maxperf)
required: false
default: ""
digest:
description: The digest of the built Docker image
required: true
outputs:
binary_artifact_name:
description: The name of the uploaded binary artifact
value: conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
runs:
using: composite
steps:
- name: Export digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
shell: bash
run: |
mkdir -p /tmp/digests
digest="${{ inputs.digest }}"
echo "🔍 Build step digest output: '$digest'"
if [[ -z "$digest" ]]; then
echo "❌ ERROR: No digest found from build step"
exit 1
fi
digest_file="/tmp/digests/${digest#sha256:}"
echo "📁 Creating digest file: $digest_file"
touch "$digest_file"
echo "✅ Digest file created successfully"
echo "📋 Contents of /tmp/digests:"
ls -la /tmp/digests/
- name: Rename extracted binary
shell: bash
run: mv /tmp/binaries/sbin/conduwuit /tmp/binaries/conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
- name: Upload binary artifact
uses: forgejo/upload-artifact@v4
with:
name: conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
path: /tmp/binaries/conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
if-no-files-found: error
- name: Upload digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/upload-artifact@v4
with:
name: digests${{ inputs.digest_suffix }}-${{ inputs.slug }}${{ inputs.cpu_suffix }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 5
+14
View File
@@ -40,6 +40,12 @@ creds:
- registry: registry.gitlab.com
user: "{{env \"GITLAB_USERNAME\"}}"
pass: "{{env \"GITLAB_TOKEN\"}}"
- registry: git.nexy7574.co.uk
user: "{{env \"N7574_GIT_USERNAME\"}}"
pass: "{{env \"N7574_GIT_TOKEN\"}}"
- registry: ghcr.io
user: "{{env \"GH_PACKAGES_USER\"}}"
pass: "{{env \"GH_PACKAGES_TOKEN\"}}"
# Global defaults
defaults:
@@ -53,3 +59,11 @@ sync:
target: registry.gitlab.com/continuwuity/continuwuity
type: repository
<<: *tags-main
- source: *source
target: git.nexy7574.co.uk/mirrored/continuwuity
type: repository
<<: *tags-releases
- source: *source
target: ghcr.io/continuwuity/continuwuity
type: repository
<<: *tags-main
+148
View File
@@ -0,0 +1,148 @@
name: Build / Debian DEB
concurrency:
group: "build-debian-${{ forge.ref }}"
cancel-in-progress: true
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
schedule:
- cron: '30 0 * * *'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
container: ["ubuntu-latest", "ubuntu-previous", "debian-latest", "debian-oldstable"]
container:
image: "ghcr.io/tcpipuk/act-runner:${{ matrix.container }}"
steps:
- name: Get Debian version
id: debian-version
run: |
VERSION=$(cat /etc/debian_version)
DISTRIBUTION=$(lsb_release -sc 2>/dev/null)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT
echo "Debian distribution: $DISTRIBUTION ($VERSION)"
- name: Checkout repository with full history
uses: https://code.forgejo.org/actions/checkout@v5
with:
fetch-depth: 0
- name: Cache Cargo registry
uses: https://code.forgejo.org/actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-debian-${{ steps.debian-version.outputs.distribution }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-debian-${{ steps.debian-version.outputs.distribution }}-
- name: Setup sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Configure sccache environment
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
echo "SCCACHE_CACHE_SIZE=10G" >> $GITHUB_ENV
# Aggressive GC since cache restores don't increment counter
echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV
- name: Setup Rust nightly
uses: ./.forgejo/actions/setup-rust
with:
rust-version: nightly
github-token: ${{ secrets.GH_PUBLIC_RO }}
- name: Get package version and component
id: package-meta
run: |
BASE_VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r ".packages[] | select(.name == \"conduwuit\").version" | sed 's/[^a-zA-Z0-9.+]/~/g')
# VERSION is the package version, COMPONENT is used in
# apt's repository config like a git repo branch
if [[ "${{ forge.ref }}" == "refs/tags/"* ]]; then
# Use the "stable" component for tagged releases
COMPONENT="stable"
VERSION=$BASE_VERSION
else
# Use the "dev" component for development builds
SHA=$(echo "${{ forge.sha }}" | cut -c1-7)
DATE=$(date +%Y%m%d)
if [ "${{ forge.ref_name }}" = "main" ]; then
COMPONENT="dev"
else
# Use the sanitized ref name as the component for feature branches
COMPONENT="dev-$(echo '${{ forge.ref_name }}' | sed 's/[^a-zA-Z0-9.+]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30)"
fi
CLEAN_COMPONENT=$(echo $COMPONENT | sed 's/[^a-zA-Z0-9.+]/~/g')
VERSION="$BASE_VERSION~git$DATE.$SHA-$CLEAN_COMPONENT"
fi
echo "component=$COMPONENT" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Component: $COMPONENT"
echo "Version: $VERSION"
- name: Install cargo-deb
run: |
if command -v cargo-deb &> /dev/null; then
echo "cargo-deb already available"
else
echo "Installing cargo-deb"
cargo-binstall -y --no-symlinks cargo-deb
fi
- name: Install build dependencies
run: |
apt-get update -y
# Build dependencies for rocksdb
apt-get install -y clang liburing-dev
- name: Run cargo-deb
id: cargo-deb
run: |
DEB_PATH=$(cargo deb --deb-version ${{ steps.package-meta.outputs.version }})
echo "path=$DEB_PATH" >> $GITHUB_OUTPUT
- name: Test deb installation
run: |
echo "Installing: ${{ steps.cargo-deb.outputs.path }}"
apt-get install -y ${{ steps.cargo-deb.outputs.path }}
dpkg -s continuwuity
[ -f /usr/bin/conduwuit ] && echo "✅ Binary installed successfully"
[ -f /usr/lib/systemd/system/conduwuit.service ] && echo "✅ Systemd service installed"
[ -f /etc/conduwuit/conduwuit.toml ] && echo "✅ Config file installed"
- name: Upload deb artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: continuwuity-${{ steps.debian-version.outputs.distribution }}
path: ${{ steps.cargo-deb.outputs.path }}
- name: Publish to Forgejo package registry
if: ${{ forge.event_name == 'push' || forge.event_name == 'workflow_dispatch' || forge.event_name == 'schedule' }}
run: |
OWNER="continuwuation"
DISTRIBUTION=${{ steps.debian-version.outputs.distribution }}
COMPONENT=${{ steps.package-meta.outputs.component }}
DEB=${{ steps.cargo-deb.outputs.path }}
echo "Publishing: $DEB in component $COMPONENT for distribution $DISTRIBUTION"
curl --fail-with-body \
-X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
--upload-file "$DEB" \
"${{ forge.server_url }}/api/packages/$OWNER/debian/pool/$DISTRIBUTION/$COMPONENT/upload"
+389
View File
@@ -0,0 +1,389 @@
name: Build / Fedora RPM
concurrency:
group: "build-fedora-${{ github.ref }}"
cancel-in-progress: true
on:
push:
tags:
- "v*.*.*"
# paths:
# - 'pkg/fedora/**'
# - 'src/**'
# - 'Cargo.toml'
# - 'Cargo.lock'
# - '.forgejo/workflows/build-fedora.yml'
workflow_dispatch:
schedule:
- cron: '30 0 * * *'
jobs:
build:
runs-on: fedora-latest
steps:
- name: Detect Fedora version
id: fedora
run: |
VERSION=$(rpm -E %fedora)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Fedora version: $VERSION"
- name: Checkout repository with full history
uses: https://code.forgejo.org/actions/checkout@v5
with:
fetch-depth: 0
- name: Cache DNF packages
uses: https://code.forgejo.org/actions/cache@v4
with:
path: |
/var/cache/dnf
/var/cache/yum
key: dnf-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('pkg/fedora/continuwuity.spec.rpkg') }}-v1
restore-keys: |
dnf-fedora${{ steps.fedora.outputs.version }}-
- name: Cache Cargo registry
uses: https://code.forgejo.org/actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-fedora${{ steps.fedora.outputs.version }}-
- name: Cache Rust build dependencies
uses: https://code.forgejo.org/actions/cache@v4
with:
path: |
~/rpmbuild/BUILD/*/target/release/deps
~/rpmbuild/BUILD/*/target/release/build
~/rpmbuild/BUILD/*/target/release/.fingerprint
~/rpmbuild/BUILD/*/target/release/incremental
key: rust-deps-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
rust-deps-fedora${{ steps.fedora.outputs.version }}-
- name: Setup sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Configure sccache environment
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
echo "SCCACHE_CACHE_SIZE=10G" >> $GITHUB_ENV
# Aggressive GC since cache restores don't increment counter
echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV
- name: Install base RPM tools
run: |
dnf install -y --setopt=keepcache=1 \
fedora-packager \
python3-pip \
rpm-sign \
rpkg \
wget
- name: Setup build environment and build SRPM
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global user.email "ci@continuwuity.org"
git config --global user.name "Continuwuity"
rpmdev-setuptree
cd "$GITHUB_WORKSPACE"
# Determine release suffix and version based on ref type and branch
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
# Tags get clean version numbers for stable releases
RELEASE_SUFFIX=""
TAG_NAME="${{ github.ref_name }}"
# Extract version from tag (remove v prefix if present)
TAG_VERSION=$(echo "$TAG_NAME" | sed 's/^v//')
# Create spec file with tag version
sed -e "s/^Version:.*$/Version: $TAG_VERSION/" \
-e "s/^Release:.*$/Release: 1%{?dist}/" \
pkg/fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg
elif [ "${{ github.ref_name }}" = "main" ]; then
# Main branch gets .dev suffix
RELEASE_SUFFIX=".dev"
# Replace the Release line to include our suffix
sed "s/^Release:.*$/Release: 1${RELEASE_SUFFIX}%{?dist}/" \
pkg/fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg
else
# Other branches get sanitized branch name as suffix
SAFE_BRANCH=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/_/g' | cut -c1-20)
RELEASE_SUFFIX=".${SAFE_BRANCH}"
# Replace the Release line to include our suffix
sed "s/^Release:.*$/Release: 1${RELEASE_SUFFIX}%{?dist}/" \
pkg/fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg
fi
rpkg srpm --outdir "$HOME/rpmbuild/SRPMS"
ls -la $HOME/rpmbuild/SRPMS/
- name: Install build dependencies from SRPM
run: |
SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1)
if [ -z "$SRPM" ]; then
echo "Error: No SRPM file found"
exit 1
fi
echo "Installing build dependencies from: $(basename $SRPM)"
dnf builddep -y "$SRPM"
- name: Build RPM from SRPM
run: |
SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1)
if [ -z "$SRPM" ]; then
echo "Error: No SRPM file found"
exit 1
fi
echo "Building from SRPM: $SRPM"
rpmbuild --rebuild "$SRPM" \
--define "_topdir $HOME/rpmbuild" \
--define "_sourcedir $GITHUB_WORKSPACE" \
--nocheck # Skip %check section to avoid test dependencies
- name: Test RPM installation
run: |
# Find the main binary RPM (exclude debug and source RPMs)
RPM=$(find "$HOME/rpmbuild/RPMS" -name "continuwuity-*.rpm" \
! -name "*debuginfo*" \
! -name "*debugsource*" \
! -name "*.src.rpm" | head -1)
if [ -z "$RPM" ]; then
echo "Error: No binary RPM file found"
exit 1
fi
echo "Testing installation of: $RPM"
# Dry run first
rpm -qpi "$RPM"
echo ""
rpm -qpl "$RPM"
# Actually install it
dnf install -y "$RPM"
# Verify installation
rpm -qa | grep continuwuity
# Check that the binary exists
[ -f /usr/bin/conduwuit ] && echo "✅ Binary installed successfully"
[ -f /usr/lib/systemd/system/conduwuit.service ] && echo "✅ Systemd service installed"
[ -f /etc/conduwuit/conduwuit.toml ] && echo "✅ Config file installed"
- name: List built packages
run: |
echo "Binary RPMs:"
find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec ls -la {} \;
echo ""
echo "Source RPMs:"
find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec ls -la {} \;
- name: Collect artifacts
run: |
mkdir -p artifacts
find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \;
find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \;
cd artifacts
echo "Build Information:" > BUILD_INFO.txt
echo "==================" >> BUILD_INFO.txt
echo "Git commit: ${{ github.sha }}" >> BUILD_INFO.txt
echo "Git branch: ${{ github.ref_name }}" >> BUILD_INFO.txt
echo "Build date: $(date -u +%Y-%m-%d_%H:%M:%S_UTC)" >> BUILD_INFO.txt
echo "" >> BUILD_INFO.txt
echo "Package contents:" >> BUILD_INFO.txt
echo "-----------------" >> BUILD_INFO.txt
for rpm in *.rpm; do
echo "" >> BUILD_INFO.txt
echo "File: $rpm" >> BUILD_INFO.txt
rpm -qpi "$rpm" 2>/dev/null | grep -E "^(Name|Version|Release|Architecture|Size)" >> BUILD_INFO.txt
done
ls -la
- name: Upload binary RPM artifact
run: |
# Find the main binary RPM (exclude debug and source RPMs)
BIN_RPM=$(find artifacts -name "continuwuity-*.rpm" \
! -name "*debuginfo*" \
! -name "*debugsource*" \
! -name "*.src.rpm" \
-type f)
mkdir -p upload-bin
cp $BIN_RPM upload-bin/
- name: Upload binary RPM
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: continuwuity
path: upload-bin/
- name: Upload debug RPM artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: continuwuity-debug
path: artifacts/*debuginfo*.rpm
- name: Publish to RPM Package Registry
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}
run: |
# Find the main binary RPM (exclude debug and source RPMs)
RPM=$(find artifacts -name "continuwuity-*.rpm" \
! -name "*debuginfo*" \
! -name "*debugsource*" \
! -name "*.src.rpm" \
-type f | head -1)
if [ -z "$RPM" ]; then
echo "No binary RPM found to publish"
exit 0
fi
RPM_BASENAME=$(basename "$RPM")
echo "Publishing: $RPM_BASENAME"
# Determine the group based on ref type and branch
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
GROUP="stable"
# For tags, extract the tag name for version info
TAG_NAME="${{ github.ref_name }}"
elif [ "${{ github.ref_name }}" = "main" ]; then
GROUP="dev"
else
# Use sanitized branch name as group for feature branches
GROUP=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30)
fi
PACKAGE_INFO=$(rpm -qpi "$RPM" 2>/dev/null)
PACKAGE_NAME=$(echo "$PACKAGE_INFO" | grep "^Name" | awk '{print $3}')
PACKAGE_VERSION=$(echo "$PACKAGE_INFO" | grep "^Version" | awk '{print $3}')
PACKAGE_RELEASE=$(echo "$PACKAGE_INFO" | grep "^Release" | awk '{print $3}')
PACKAGE_ARCH=$(echo "$PACKAGE_INFO" | grep "^Architecture" | awk '{print $2}')
# Full version includes release
FULL_VERSION="${PACKAGE_VERSION}-${PACKAGE_RELEASE}"
# Forgejo's RPM registry cannot overwrite existing packages, so we must delete first
# 404 is OK if package doesn't exist yet
echo "Removing any existing package: $PACKAGE_NAME-$FULL_VERSION.$PACKAGE_ARCH"
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/package/$PACKAGE_NAME/$FULL_VERSION/$PACKAGE_ARCH")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then
echo "ERROR: Failed to delete package (HTTP $HTTP_CODE)"
echo "$RESPONSE" | head -n -1
exit 1
fi
curl --fail-with-body \
-X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/x-rpm" \
-T "$RPM" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/upload?sign=true"
echo ""
echo "✅ Published binary RPM to: https://forgejo.ellis.link/continuwuation/-/packages/rpm/continuwuity/"
echo "Group: $GROUP"
# Upload debug RPMs to separate group
DEBUG_RPMS=$(find artifacts -name "*debuginfo*.rpm")
if [ -n "$DEBUG_RPMS" ]; then
echo ""
echo "Publishing debug RPMs to group: ${GROUP}-debug"
for DEBUG_RPM in $DEBUG_RPMS; do
echo "Publishing: $(basename "$DEBUG_RPM")"
DEBUG_INFO=$(rpm -qpi "$DEBUG_RPM" 2>/dev/null)
DEBUG_NAME=$(echo "$DEBUG_INFO" | grep "^Name" | awk '{print $3}')
DEBUG_VERSION=$(echo "$DEBUG_INFO" | grep "^Version" | awk '{print $3}')
DEBUG_RELEASE=$(echo "$DEBUG_INFO" | grep "^Release" | awk '{print $3}')
DEBUG_ARCH=$(echo "$DEBUG_INFO" | grep "^Architecture" | awk '{print $2}')
DEBUG_FULL_VERSION="${DEBUG_VERSION}-${DEBUG_RELEASE}"
# Must delete existing package first (Forgejo limitation)
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/package/$DEBUG_NAME/$DEBUG_FULL_VERSION/$DEBUG_ARCH")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then
echo "ERROR: Failed to delete debug package (HTTP $HTTP_CODE)"
echo "$RESPONSE" | head -n -1
exit 1
fi
curl --fail-with-body \
-X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/x-rpm" \
-T "$DEBUG_RPM" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/upload?sign=true"
done
echo "✅ Published debug RPMs to group: ${GROUP}-debug"
fi
# Also upload the SRPM to separate group
SRPM=$(find artifacts -name "*.src.rpm" | head -1)
if [ -n "$SRPM" ]; then
echo ""
echo "Publishing source RPM: $(basename "$SRPM")"
echo "Publishing to group: ${GROUP}-src"
SRPM_INFO=$(rpm -qpi "$SRPM" 2>/dev/null)
SRPM_NAME=$(echo "$SRPM_INFO" | grep "^Name" | awk '{print $3}')
SRPM_VERSION=$(echo "$SRPM_INFO" | grep "^Version" | awk '{print $3}')
SRPM_RELEASE=$(echo "$SRPM_INFO" | grep "^Release" | awk '{print $3}')
SRPM_FULL_VERSION="${SRPM_VERSION}-${SRPM_RELEASE}"
# Must delete existing SRPM first (Forgejo limitation)
echo "Removing any existing SRPM: $SRPM_NAME-$SRPM_FULL_VERSION.src"
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/package/$SRPM_NAME/$SRPM_FULL_VERSION/src")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then
echo "ERROR: Failed to delete SRPM (HTTP $HTTP_CODE)"
echo "$RESPONSE" | head -n -1
exit 1
fi
curl --fail-with-body \
-X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/x-rpm" \
-T "$SRPM" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/upload?sign=true"
echo "✅ Published source RPM to group: ${GROUP}-src"
fi
+4 -6
View File
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Sync repository
uses: https://github.com/actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
fetch-depth: 0
@@ -51,11 +51,11 @@ jobs:
- name: Detect runner environment
id: runner-env
uses: ./.forgejo/actions/detect-runner-os
uses: https://git.tomfos.tr/actions/detect-versions@v1
- name: Setup Node.js
if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20'
uses: https://github.com/actions/setup-node@v4
uses: https://github.com/actions/setup-node@v6
with:
node-version: 22
@@ -63,9 +63,7 @@ jobs:
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ steps.runner-env.outputs.slug }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ steps.runner-env.outputs.slug }}-node-
key: continuwuity-${{ steps.runner-env.outputs.slug }}-${{ steps.runner-env.outputs.arch }}-node-${{ steps.runner-env.outputs.node_version }}
- name: Install dependencies
run: npm install --save-dev wrangler@latest
+10 -2
View File
@@ -4,6 +4,14 @@ on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
pull_request:
paths:
- ".forgejo/workflows/element.yml"
push:
branches:
- main
paths:
- ".forgejo/workflows/element.yml"
concurrency:
group: "element-${{ github.ref }}"
@@ -16,7 +24,7 @@ jobs:
steps:
- name: 📦 Setup Node.js
uses: https://github.com/actions/setup-node@v4
uses: https://github.com/actions/setup-node@v6
with:
node-version: "22"
@@ -101,7 +109,7 @@ jobs:
cat ./element-web/webapp/config.json
- name: 📤 Upload Artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
uses: forgejo/upload-artifact@v4
with:
name: element-web
path: ./element-web/webapp/
+21 -2
View File
@@ -11,7 +11,13 @@ on:
required: false
default: false
type: boolean
push:
branches:
- main
paths:
# Re-run when config changes
- '.forgejo/regsync/regsync.yml'
- '.forgejo/workflows/mirror-images.yml'
concurrency:
group: "mirror-images"
cancel-in-progress: true
@@ -24,12 +30,25 @@ jobs:
BUILTIN_REGISTRY_PASSWORD: ${{ secrets.BUILTIN_REGISTRY_PASSWORD }}
GITLAB_USERNAME: ${{ vars.GITLAB_USERNAME }}
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
N7574_GIT_USERNAME: ${{ vars.N7574_GIT_USERNAME }}
N7574_GIT_TOKEN: ${{ secrets.N7574_GIT_TOKEN }}
GH_PACKAGES_USER: ${{ vars.GH_PACKAGES_USER }}
GH_PACKAGES_TOKEN: ${{ secrets.GH_PACKAGES_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
# - uses: https://github.com/actions/create-github-app-token@v2
# id: app-token
# with:
# app-id: ${{ vars.GH_APP_ID }}
# private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
# github-api-url: https://api.github.com
# owner: continuwuity
# repositories: continuwuity
- name: Install regctl
uses: https://forgejo.ellis.link/continuwuation/regclient-actions/regctl-installer@main
with:
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
@@ -47,7 +47,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
+129 -256
View File
@@ -23,55 +23,20 @@ on:
- "renovate.json"
- "pkg/**"
- "docs/**"
tags:
- "v*.*.*"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
env:
BUILTIN_REGISTRY: forgejo.ellis.link
BUILTIN_REGISTRY_ENABLED: "${{ ((vars.BUILTIN_REGISTRY_USER && secrets.BUILTIN_REGISTRY_PASSWORD) || (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)) && 'true' || 'false' }}"
IMAGE_PATH: forgejo.ellis.link/continuwuation/continuwuity
jobs:
define-variables:
runs-on: ubuntu-latest
outputs:
images: ${{ steps.var.outputs.images }}
images_list: ${{ steps.var.outputs.images_list }}
build_matrix: ${{ steps.var.outputs.build_matrix }}
steps:
- name: Setting variables
uses: https://github.com/actions/github-script@v7
id: var
with:
script: |
const githubRepo = '${{ github.repository }}'.toLowerCase()
const repoId = githubRepo.split('/')[1]
core.setOutput('github_repository', githubRepo)
const builtinImage = '${{ env.BUILTIN_REGISTRY }}/' + githubRepo
let images = []
if (process.env.BUILTIN_REGISTRY_ENABLED === "true") {
images.push(builtinImage)
} else {
// Fallback to official registry for forks/PRs without credentials
images.push('forgejo.ellis.link/continuwuation/continuwuity')
}
core.setOutput('images', images.join("\n"))
core.setOutput('images_list', images.join(","))
const platforms = ['linux/amd64', 'linux/arm64']
core.setOutput('build_matrix', JSON.stringify({
platform: platforms,
target_cpu: ['base'],
include: platforms.map(platform => { return {
platform,
slug: platform.replace('/', '-')
}})
}))
build-image:
build-release:
name: "Build ${{ matrix.slug }} (release)"
runs-on: dind
needs: define-variables
permissions:
contents: read
packages: write
@@ -79,133 +44,28 @@ jobs:
id-token: write
strategy:
matrix:
{
"target_cpu": ["base"],
"profile": ["release"],
"include":
[
{ "platform": "linux/amd64", "slug": "linux-amd64" },
{ "platform": "linux/arm64", "slug": "linux-arm64" },
],
"platform": ["linux/amd64", "linux/arm64"],
}
include:
- platform: "linux/amd64"
slug: "linux-amd64"
- platform: "linux/arm64"
slug: "linux-arm64"
steps:
- name: Echo strategy
run: echo '${{ toJSON(fromJSON(needs.define-variables.outputs.build_matrix)) }}'
- name: Echo matrix
run: echo '${{ toJSON(matrix) }}'
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Install rust
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: rust-toolchain
uses: ./.forgejo/actions/rust-toolchain
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Prepare Docker build environment
id: prepare
uses: ./.forgejo/actions/prepare-docker-build
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
- name: Set up QEMU
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: docker/setup-qemu-action@v3
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (labels, annotations) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{needs.define-variables.outputs.images}}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
# It will not push images generated from a pull request
- name: Get short git commit SHA
id: sha
run: |
calculatedSha=$(git rev-parse --short ${{ github.sha }})
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
echo "Short SHA: $calculatedSha"
- name: Get Git commit timestamps
run: |
timestamp=$(git log -1 --pretty=%ct)
echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV
echo "Commit timestamp: $timestamp"
- uses: ./.forgejo/actions/timelord
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
with:
key: timelord-v0
path: .
- name: Cache Rust registry
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: actions/cache@v3
with:
path: |
.cargo/git
.cargo/git/checkouts
.cargo/registry
.cargo/registry/src
key: rust-registry-image-${{hashFiles('**/Cargo.lock') }}
- name: Cache cargo target
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-cargo-target
uses: actions/cache@v3
with:
path: |
cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
key: cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}}
- name: Cache apt cache
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt
uses: actions/cache@v3
with:
path: |
var-cache-apt-${{ matrix.slug }}
key: var-cache-apt-${{ matrix.slug }}
- name: Cache apt lib
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt-lib
uses: actions/cache@v3
with:
path: |
var-lib-apt-${{ matrix.slug }}
key: var-lib-apt-${{ matrix.slug }}
- name: inject cache into docker
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.3.0
with:
cache-map: |
{
".cargo/registry": "/usr/local/cargo/registry",
".cargo/git/db": "/usr/local/cargo/git/db",
"cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}": {
"target": "/app/target",
"id": "cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}"
},
"var-cache-apt-${{ matrix.slug }}": "/var/cache/apt",
"var-lib-apt-${{ matrix.slug }}": "/var/lib/apt"
}
skip-extraction: ${{ steps.cache.outputs.cache-hit }}
platform: ${{ matrix.platform }}
slug: ${{ matrix.slug }}
target_cpu: ""
profile: "release"
images: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Build and push Docker image by digest
id: build
uses: docker/build-push-action@v6
@@ -217,117 +77,130 @@ jobs:
GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }}
GIT_REMOTE_URL=${{github.event.repository.html_url }}
GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }}
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
TARGET_CPU=
RUST_PROFILE=release
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
labels: ${{ steps.prepare.outputs.metadata_labels }}
annotations: ${{ steps.prepare.outputs.metadata_annotations }}
cache-from: type=gha
# cache-to: type=gha,mode=max
sbom: true
outputs: |
${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', needs.define-variables.outputs.images_list) || format('type=image,"name={0}",push=false', needs.define-variables.outputs.images_list) }}
${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', env.IMAGE_PATH) || format('type=image,"name={0}",push=false', env.IMAGE_PATH) }}
type=local,dest=/tmp/binaries
env:
SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }}
# For publishing multi-platform manifests
- name: Export digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
# Binary extracted via local output for all builds
- name: Rename extracted binary
run: mv /tmp/binaries/sbin/conduwuit /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
- name: Upload binary artifact
uses: forgejo/upload-artifact@v4
- name: Upload Docker artifacts
uses: ./.forgejo/actions/upload-docker-artifacts
with:
name: conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
path: /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
if-no-files-found: error
slug: ${{ matrix.slug }}
cpu_suffix: ${{ steps.prepare.outputs.cpu_suffix }}
artifact_suffix: ""
digest_suffix: ""
digest: ${{ steps.build.outputs.digest }}
- name: Upload digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/upload-artifact@v4
with:
name: digests-${{ matrix.slug }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 5
merge:
merge-release:
name: "Create Multi-arch Release Manifest"
runs-on: dind
needs: [define-variables, build-image]
needs: build-release
steps:
- name: Download digests
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/download-artifact@v4
- name: Checkout repository
uses: actions/checkout@v5
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
persist-credentials: false
- name: Create multi-platform manifest
uses: ./.forgejo/actions/create-docker-manifest
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
digest_pattern: "digests-linux-{amd64,arm64}"
tag_suffix: ""
images: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/setup-buildx-action@v3
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
build-maxperf:
name: "Build ${{ matrix.slug }} (max-perf)"
runs-on: dind
needs: build-release
permissions:
contents: read
packages: write
attestations: write
id-token: write
strategy:
matrix:
include:
- platform: "linux/amd64"
slug: "linux-amd64"
target_cpu: "haswell"
- platform: "linux/arm64"
slug: "linux-arm64"
target_cpu: ""
- name: Extract metadata (tags) for Docker
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
id: meta
uses: docker/metadata-action@v5
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
tags: |
type=semver,pattern={{version}},prefix=v
type=semver,pattern={{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }},prefix=v
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }},prefix=v
type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }}
type=ref,event=pr
type=sha,format=long
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
images: ${{needs.define-variables.outputs.images}}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
persist-credentials: false
- name: Prepare max-perf Docker build environment
id: prepare
uses: ./.forgejo/actions/prepare-docker-build
with:
platform: ${{ matrix.platform }}
slug: ${{ matrix.slug }}
target_cpu: ${{ matrix.target_cpu }}
profile: "release-max-perf"
images: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Build and push max-perf Docker image by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
file: "docker/Dockerfile"
build-args: |
GIT_COMMIT_HASH=${{ github.sha }}
GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }}
GIT_REMOTE_URL=${{github.event.repository.html_url }}
GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }}
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
TARGET_CPU=${{ matrix.target_cpu }}
RUST_PROFILE=release-max-perf
platforms: ${{ matrix.platform }}
labels: ${{ steps.prepare.outputs.metadata_labels }}
annotations: ${{ steps.prepare.outputs.metadata_annotations }}
cache-from: type=gha
# cache-to: type=gha,mode=max
sbom: true
outputs: |
${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', env.IMAGE_PATH) || format('type=image,"name={0}",push=false', env.IMAGE_PATH) }}
type=local,dest=/tmp/binaries
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }}
- name: Upload max-perf Docker artifacts
uses: ./.forgejo/actions/upload-docker-artifacts
with:
slug: ${{ matrix.slug }}
cpu_suffix: ${{ steps.prepare.outputs.cpu_suffix }}
artifact_suffix: "-maxperf"
digest_suffix: "-maxperf"
digest: ${{ steps.build.outputs.digest }}
- name: Create manifest list and push
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
working-directory: /tmp/digests
env:
IMAGES: ${{needs.define-variables.outputs.images}}
shell: bash
run: |
IFS=$'\n'
IMAGES_LIST=($IMAGES)
ANNOTATIONS_LIST=($DOCKER_METADATA_OUTPUT_ANNOTATIONS)
TAGS_LIST=($DOCKER_METADATA_OUTPUT_TAGS)
for REPO in "${IMAGES_LIST[@]}"; do
docker buildx imagetools create \
$(for tag in "${TAGS_LIST[@]}"; do echo "--tag"; echo "$tag"; done) \
$(for annotation in "${ANNOTATIONS_LIST[@]}"; do echo "--annotation"; echo "$annotation"; done) \
$(for reference in *; do printf "$REPO@sha256:%s\n" $reference; done)
done
- name: Inspect image
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
env:
IMAGES: ${{needs.define-variables.outputs.images}}
shell: bash
run: |
IMAGES_LIST=($IMAGES)
for REPO in "${IMAGES_LIST[@]}"; do
docker buildx imagetools inspect $REPO:${{ steps.meta.outputs.version }}
done
merge-maxperf:
name: "Create Max-Perf Manifest"
runs-on: dind
needs: build-maxperf
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Create max-perf manifest
uses: ./.forgejo/actions/create-docker-manifest
with:
digest_pattern: "digests-maxperf-linux-{amd64-haswell,arm64}"
tag_suffix: "-maxperf"
images: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
+42 -21
View File
@@ -1,5 +1,7 @@
name: Maintenance / Renovate
enable-email-notifications: true
on:
schedule:
# Run at 5am UTC daily to avoid late-night dev
@@ -10,10 +12,10 @@ on:
dryRun:
description: 'Dry run mode'
required: false
default: null
default: ''
type: choice
options:
- null
- ''
- 'extract'
- 'lookup'
- 'full'
@@ -23,6 +25,7 @@ on:
default: 'info'
type: choice
options:
- 'debug'
- 'info'
- 'warning'
- 'critical'
@@ -40,11 +43,11 @@ jobs:
name: Renovate
runs-on: ubuntu-latest
container:
image: ghcr.io/renovatebot/renovate:41
image: ghcr.io/renovatebot/renovate:41.146.4@sha256:bb70194b7405faf10a6f279b60caa10403a440ba37d158c5a4ef0ae7b67a0f92
options: --tmpfs /tmp:exec
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
show-progress: false
@@ -52,25 +55,34 @@ jobs:
run: /usr/local/renovate/node -e 'console.log(`node heap limit = ${require("v8").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'
- name: Restore renovate repo cache
uses: https://github.com/actions/cache@v4
uses: actions/cache/restore@v4
with:
path: |
/tmp/renovate/cache/renovate/repository
key: repo-cache-${{ github.run_id }}
key: renovate-repo-cache-${{ github.run_id }}
restore-keys: |
repo-cache-
renovate-repo-cache-
- name: Restore renovate package cache
uses: https://github.com/actions/cache@v4
uses: actions/cache/restore@v4
with:
path: |
/tmp/renovate/cache/renovate/renovate-cache-sqlite
key: package-cache-${{ github.run_id }}
key: renovate-package-cache-${{ github.run_id }}
restore-keys: |
package-cache-
renovate-package-cache-
- name: Restore renovate OSV cache
uses: actions/cache/restore@v4
with:
path: |
/tmp/osv
key: renovate-osv-cache-${{ github.run_id }}
restore-keys: |
renovate-osv-cache-
- name: Self-hosted Renovate
uses: https://github.com/renovatebot/github-action@v43.0.11
run: renovate
env:
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}
RENOVATE_DRY_RUN: ${{ inputs.dryRun || 'false' }}
@@ -84,28 +96,37 @@ jobs:
RENOVATE_REQUIRE_CONFIG: 'required'
RENOVATE_ONBOARDING: 'false'
RENOVATE_PR_COMMITS_PER_RUN_LIMIT: 3
RENOVATE_INHERIT_CONFIG: 'true'
RENOVATE_GITHUB_TOKEN_WARN: 'false'
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
GITHUB_COM_TOKEN: ${{ secrets.GH_PUBLIC_RO }}
GITHUB_COM_TOKEN: ${{ secrets.GH_PUBLIC_RO || secrets.GH_TOKEN }}
RENOVATE_REPOSITORY_CACHE: 'enabled'
RENOVATE_X_SQLITE_PACKAGE_CACHE: true
RENOVATE_X_SQLITE_PACKAGE_CACHE: 'true'
OSV_OFFLINE_ROOT_DIR: /tmp/osv
- name: Save renovate repo cache
if: always() && env.RENOVATE_DRY_RUN != 'full'
uses: https://github.com/actions/cache@v4
if: always()
uses:
actions/cache/save@v4
with:
path: |
/tmp/renovate/cache/renovate/repository
key: repo-cache-${{ github.run_id }}
key: renovate-repo-cache-${{ github.run_id }}
- name: Save renovate package cache
if: always() && env.RENOVATE_DRY_RUN != 'full'
uses: https://github.com/actions/cache@v4
if: always()
uses: actions/cache/save@v4
with:
path: |
/tmp/renovate/cache/renovate/renovate-cache-sqlite
key: package-cache-${{ github.run_id }}
key: renovate-package-cache-${{ github.run_id }}
- name: Save renovate OSV cache
if: always()
uses: actions/cache/save@v4
with:
path: |
/tmp/osv
key: renovate-osv-cache-${{ github.run_id }}
+121
View File
@@ -0,0 +1,121 @@
name: Update flake hashes
on:
workflow_dispatch:
pull_request:
paths:
- "Cargo.lock"
- "Cargo.toml"
- "rust-toolchain.toml"
- "nix/**/*"
- ".forgejo/workflows/update-flake-hashes.yml"
jobs:
update-flake-hashes:
runs-on: ubuntu-latest
steps:
- uses: https://code.forgejo.org/actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
fetch-tags: false
fetch-single-branch: true
submodules: false
persist-credentials: true
token: ${{ secrets.FORGEJO_TOKEN }}
- uses: https://github.com/cachix/install-nix-action@7ab6e7fd29da88e74b1e314a4ae9ac6b5cda3801 # v31.8.0
with:
nix_path: nixpkgs=channel:nixos-unstable
# We can skip getting a toolchain hash if this was ran as a dispatch with the intent
# to update just the rocksdb hash. If this was ran as a dispatch and the toolchain
# files are changed, we still update them, as well as the rocksdb import.
- name: Detect changed files
id: changes
run: |
git fetch origin ${{ github.base_ref }} --depth=1 || true
if [ -n "${{ github.event.pull_request.base.sha }}" ]; then
base=${{ github.event.pull_request.base.sha }}
else
base=$(git rev-parse HEAD~1)
fi
echo "Base: $base"
echo "HEAD: $(git rev-parse HEAD)"
git diff --name-only $base HEAD > changed_files.txt
echo "detected changes in $(cat changed_files.txt)"
# Join files with commas
files=$(paste -sd, changed_files.txt)
echo "files=$files" >> $FORGEJO_OUTPUT
- name: Debug output
run: |
echo "State of output"
echo "Changed files: ${{ steps.changes.outputs.files }}"
- name: Get new toolchain hash
if: contains(steps.changes.outputs.files, 'Cargo.toml') || contains(steps.changes.outputs.files, 'Cargo.lock') || contains(steps.changes.outputs.files, 'rust-toolchain.toml')
run: |
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
awk '/fromToolchainFile *\{/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/packages/rust.nix > temp.nix
mv temp.nix nix/packages/rust.nix
# Build continuwuity and filter for the new hash
# We do `|| true` because we want this to fail without stopping the workflow
nix build .#default 2>&1 | tee >(grep 'got:' | awk '{print $2}' > new_toolchain_hash.txt) || true
# Place the new hash in place of the empty hash
new_hash=$(cat new_toolchain_hash.txt)
sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/packages/rust.nix
echo "New hash:"
awk -F'"' '/fromToolchainFile/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/packages/rust.nix
echo "Expected new hash:"
cat new_toolchain_hash.txt
rm new_toolchain_hash.txt
- name: Get new rocksdb hash
if: contains(steps.changes.outputs.files, '.nix') || contains(steps.changes.outputs.files, 'flake.lock')
run: |
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
awk '/repo = "rocksdb";/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/packages/rocksdb/package.nix > temp.nix
mv temp.nix nix/packages/rocksdb/package.nix
# Build continuwuity and filter for the new hash
# We do `|| true` because we want this to fail without stopping the workflow
nix build .#default 2>&1 | tee >(grep 'got:' | awk '{print $2}' > new_rocksdb_hash.txt) || true
# Place the new hash in place of the empty hash
new_hash=$(cat new_rocksdb_hash.txt)
sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/packages/rocksdb/package.nix
echo "New hash:"
awk -F'"' '/repo = "rocksdb";/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/packages/rocksdb/package.nix
echo "Expected new hash:"
cat new_rocksdb_hash.txt
rm new_rocksdb_hash.txt
- name: Show diff
run: git diff flake.nix nix
- name: Push changes
run: |
set -euo pipefail
if git diff --quiet --exit-code; then
echo "No changes to commit."
exit 0
fi
git config user.email "renovate@mail.ellis.link"
git config user.name "renovate"
REF="${{ github.head_ref }}"
git fetch origin "$REF"
git checkout "$REF"
git commit -a -m "chore(Nix): Updated flake hashes"
git push origin HEAD:refs/heads/"$REF"
+3
View File
@@ -7,3 +7,6 @@ f419c64aca300a338096b4e0db4c73ace54f23d0
5998a0d883d31b866f7c8c46433a8857eae51a89
# trailing whitespace and newlines
46c193e74b2ce86c48ce802333a0aabce37fd6e9
# Formatting PRs
fd972f114293ea1be9633b750a703edd661e970d
+2 -2
View File
@@ -7,7 +7,7 @@ default_stages:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: fix-byte-order-marker
- id: check-case-conflict
@@ -23,7 +23,7 @@ repos:
- id: check-added-large-files
- repo: https://github.com/crate-ci/typos
rev: v1.26.0
rev: v1.39.0
hooks:
- id: typos
- id: typos
+3
View File
@@ -13,6 +13,9 @@ extend-ignore-re = [
"[0-9+][A-Za-z0-9+]{30,}[a-z0-9+]",
"\\$[A-Z0-9+][A-Za-z0-9+]{6,}[a-z0-9+]",
"\\b[a-z0-9+/=][A-Za-z0-9+/=]{7,}[a-z0-9+/=][A-Z]\\b",
# In the renovate config
".ontainer"
]
[default.extend-words]
Generated
+942 -1146
View File
File diff suppressed because it is too large Load Diff
+40 -43
View File
@@ -21,7 +21,7 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
rust-version = "1.86.0"
version = "0.5.0-rc.7"
version = "0.5.0-rc.8"
[workspace.metadata.crane]
name = "conduwuit"
@@ -45,7 +45,7 @@ version = "0.3"
features = ["ffi", "std", "union"]
[workspace.dependencies.const-str]
version = "0.6.2"
version = "0.7.0"
[workspace.dependencies.ctor]
version = "0.5.0"
@@ -103,6 +103,9 @@ features = [
"matched-path",
"tokio",
"tracing",
"query",
# Needed for debug_handler.
#"macros",
]
[workspace.dependencies.axum-extra]
@@ -166,8 +169,8 @@ default-features = false
features = ["raw_value"]
# Used for appservice registration files
[workspace.dependencies.serde_yaml]
version = "0.9.34"
[workspace.dependencies.serde-saphyr]
version = "0.0.7"
# Used to load forbidden room/user regex from config
[workspace.dependencies.serde_regex]
@@ -210,13 +213,13 @@ default-features = false
version = "0.1.41"
default-features = false
[workspace.dependencies.tracing-subscriber]
version = "0.3.19"
version = "0.3.20"
default-features = false
features = ["env-filter", "std", "tracing", "tracing-log", "ansi", "fmt"]
[workspace.dependencies.tracing-journald]
version = "0.3.1"
[workspace.dependencies.tracing-core]
version = "0.1.33"
version = "0.1.34"
default-features = false
# for URL previews
@@ -286,7 +289,7 @@ features = [
]
[workspace.dependencies.hyper-util]
version = "0.1.11"
version = "=0.1.17"
default-features = false
features = [
"server-auto",
@@ -351,8 +354,7 @@ version = "0.1.2"
# Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
#branch = "conduwuit-changes"
rev = "8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
rev = "50b2a91b2ab8f9830eea80b9911e11234e0eac66"
features = [
"compat",
"rand",
@@ -370,6 +372,7 @@ features = [
"unstable-msc2666",
"unstable-msc2867",
"unstable-msc2870",
"unstable-msc2965",
"unstable-msc3026",
"unstable-msc3061",
"unstable-msc3245",
@@ -382,16 +385,18 @@ features = [
"unstable-msc4095",
"unstable-msc4121",
"unstable-msc4125",
"unstable-msc4155",
"unstable-msc4186",
"unstable-msc4203", # sending to-device events to appservices
"unstable-msc4210", # remove legacy mentions
"unstable-extensible-events",
"unstable-pdu",
"unstable-msc4155"
]
[workspace.dependencies.rust-rocksdb]
git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
rev = "99b0319416b64830dd6f8943e1f65e15aeef18bc"
rev = "61d9d23872197e9ace4a477f2617d5c9f50ecb23"
default-features = false
features = [
"multi-threaded-cf",
@@ -411,28 +416,27 @@ default-features = false
# optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring
[workspace.dependencies.opentelemetry]
version = "0.30.0"
version = "0.31.0"
[workspace.dependencies.tracing-flame]
version = "0.2.0"
[workspace.dependencies.tracing-opentelemetry]
version = "0.31.0"
version = "0.32.0"
[workspace.dependencies.opentelemetry_sdk]
version = "0.30.0"
version = "0.31.0"
features = ["rt-tokio"]
[workspace.dependencies.opentelemetry-otlp]
version = "0.30.0"
version = "0.31.0"
features = ["http", "trace", "logs", "metrics"]
[workspace.dependencies.opentelemetry-jaeger-propagator]
version = "0.30.0"
# optional sentry metrics for crash/panic reporting
[workspace.dependencies.sentry]
version = "0.42.0"
version = "0.45.0"
default-features = false
features = [
"backtrace",
@@ -448,9 +452,9 @@ features = [
]
[workspace.dependencies.sentry-tracing]
version = "0.42.0"
version = "0.45.0"
[workspace.dependencies.sentry-tower]
version = "0.42.0"
version = "0.45.0"
# jemalloc usage
[workspace.dependencies.tikv-jemalloc-sys]
@@ -476,7 +480,7 @@ default-features = false
features = ["use_std"]
[workspace.dependencies.console-subscriber]
version = "0.4"
version = "0.5"
[workspace.dependencies.nix]
version = "0.30.1"
@@ -550,29 +554,28 @@ features = ["std"]
version = "1.0.2"
[workspace.dependencies.ldap3]
version = "0.11.5"
version = "0.12.0"
default-features = false
features = ["sync", "tls-rustls"]
features = ["sync", "tls-rustls", "rustls-provider"]
[workspace.dependencies.resolv-conf]
version = "0.7.5"
[workspace.dependencies.oxide-auth]
version = "0.6.1"
[workspace.dependencies.once_cell]
version = "1.21.3"
[workspace.dependencies.percent-encoding]
version = "2.3.1"
#
# Patches
#
# backport of [https://github.com/tokio-rs/tracing/pull/2956] to the 0.1.x branch of tracing.
# we can switch back to upstream if #2956 is merged and backported in the upstream repo.
# https://forgejo.ellis.link/continuwuation/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
[patch.crates-io.tracing-subscriber]
git = "https://forgejo.ellis.link/continuwuation/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
[patch.crates-io.tracing]
git = "https://forgejo.ellis.link/continuwuation/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
[patch.crates-io.tracing-core]
git = "https://forgejo.ellis.link/continuwuation/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
[patch.crates-io.tracing-log]
git = "https://forgejo.ellis.link/continuwuation/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
# adds a tab completion callback: https://forgejo.ellis.link/continuwuation/rustyline-async/src/branch/main/.patchy/0002-add-tab-completion-callback.patch
# adds event for CTRL+\: https://forgejo.ellis.link/continuwuation/rustyline-async/src/branch/main/.patchy/0001-add-event-for-ctrl.patch
@@ -596,13 +599,7 @@ rev = "9c8e51510c35077df888ee72a36b4b05637147da"
# reverts hyperium#148 conflicting with our delicate federation resolver hooks
[patch.crates-io.hyper-util]
git = "https://forgejo.ellis.link/continuwuation/hyper-util"
rev = "e4ae7628fe4fcdacef9788c4c8415317a4489941"
# Allows no-aaaa option in resolv.conf
# Use 1-indexed line numbers when displaying parse error messages
[patch.crates-io.resolv-conf]
git = "https://forgejo.ellis.link/continuwuation/resolv-conf"
rev = "56251316cc4127bcbf36e68ce5e2093f4d33e227"
rev = "5886d5292bf704c246206ad72d010d674a7b77d0"
#
# Our crates
@@ -962,7 +959,7 @@ semicolon_outside_block = "warn"
str_to_string = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
string_to_string = "warn"
suspicious_xor_used_as_pow = "warn"
tests_outside_test_module = "warn"
try_err = "warn"
+48 -1
View File
@@ -23,7 +23,8 @@
# See the docs for reverse proxying and delegation:
# https://continuwuity.org/deploying/generic.html#setting-up-the-reverse-proxy
#
# Also see the `[global.well_known]` config section at the very bottom.
# Also see the `[global.auth]` and `[global.well_known]` config sections
# at the very bottom.
#
# Examples of delegation:
# - https://puppygock.gay/.well-known/matrix/server
@@ -957,6 +958,21 @@
#
#rocksdb_bottommost_compression = true
# Compression algorithm for RocksDB's Write-Ahead-Log (WAL).
#
# At present, only ZSTD compression is supported by RocksDB for WAL
# compression. Enabling this can reduce WAL size at the expense of some
# CPU usage during writes.
#
# The options are:
# - "none" = No compression
# - "zstd" = ZSTD compression
#
# For more information on WAL compression, see:
# https://github.com/facebook/rocksdb/wiki/WAL-Compression
#
#rocksdb_wal_compression = "zstd"
# Database recovery mode (for RocksDB WAL corruption).
#
# Use this option when the server reports corruption and refuses to start.
@@ -1497,6 +1513,19 @@
#
#block_non_admin_invites = false
# Enable or disable making requests to MSC4284 Policy Servers.
# It is recommended you keep this enabled unless you experience frequent
# connectivity issues, such as in a restricted networking environment.
#
#enable_msc4284_policy_servers = true
# Enable running locally generated events through configured MSC4284
# policy servers. You may wish to disable this if your server is
# single-user for a slight speed benefit in some rooms, but otherwise
# should leave it enabled.
#
#policy_server_check_own_events = true
# Allow admins to enter commands in rooms other than "#admins" (admin
# room) by prefixing your message with "\!admin" or "\\!admin" followed up
# a normal continuwuity admin command. The reply will be publicly visible
@@ -1726,6 +1755,24 @@
#
#dual_protocol = false
[global.auth]
# Use this homeserver as the OIDC authentication reference. It will
# advertise itself as the OIDC authentication issuer to new clients,
# and use the internal user database to answer on the advertised
# endpoints. Note that the legacy Matrix authentication still will be
# reachable.
# Unset by default.
#
#enable_oidc_login =
# Whether this homeserver should provide users with an account management
# interface. Only used if `enable_oidc_login` is set. Note that the
# endpoint is unimplemented at the moment.
# Unset by default.
#
#enable_oidc_account_management =
[global.well_known]
# The server URL that the client well-known file will serve. This should
+15 -6
View File
@@ -48,11 +48,13 @@ EOF
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.13.0
ENV BINSTALL_VERSION=1.15.10
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
ENV LDDTREE_VERSION=0.3.7
# renovate: datasource=crate depName=timelord-cli
ENV TIMELORD_VERSION=3.0.1
# Install unpackaged tools
RUN <<EOF
@@ -60,6 +62,7 @@ RUN <<EOF
curl --retry 5 -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm cargo-sbom --version $CARGO_SBOM_VERSION
cargo binstall --no-confirm lddtree --version $LDDTREE_VERSION
cargo binstall --no-confirm timelord-cli --version $TIMELORD_VERSION
EOF
# Set up xx (cross-compilation scripts)
@@ -81,8 +84,9 @@ RUN rustc --version \
&& xx-cargo --setup-target-triple
# Build binary
# We disable incremental compilation to save disk space, as it only produces a minimal speedup for this case.
RUN echo "CARGO_INCREMENTAL=0" >> /etc/environment
# Configure incremental compilation based on build context
ARG CARGO_INCREMENTAL=0
RUN echo "CARGO_INCREMENTAL=${CARGO_INCREMENTAL}" >> /etc/environment
# Configure pkg-config
RUN <<EOF
@@ -133,6 +137,11 @@ FROM toolchain AS builder
# Get source
COPY . .
# Restore timestamps from timelord cache if available
RUN --mount=type=cache,target=/timelord/ \
echo "Restoring timestamps from timelord cache"; \
timelord sync --source-dir /app --cache-dir /timelord;
ARG TARGETPLATFORM
# Verify environment configuration
@@ -157,7 +166,7 @@ ARG RUST_PROFILE=release
# Build the binary
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/app/target,id=cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \
--mount=type=cache,target=/app/target,id=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \
bash <<'EOF'
set -o allexport
set -o xtrace
@@ -172,8 +181,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
for BINARY in "${BINARIES[@]}"; do
echo $BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY /out/sbin/$BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY /out/sbin/$BINARY
done
EOF
+2 -2
View File
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.13.0
ENV BINSTALL_VERSION=1.15.10
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
@@ -122,7 +122,7 @@ ARG RUST_PROFILE=release
# Build the binary
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/app/target,id=cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-musl-${RUST_PROFILE} \
--mount=type=cache,target=/app/target,id=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-musl-${RUST_PROFILE} \
bash <<'EOF'
set -o allexport
set -o xtrace
+1
View File
@@ -10,6 +10,7 @@ # Summary
- [Kubernetes](deploying/kubernetes.md)
- [Arch Linux](deploying/arch-linux.md)
- [Debian](deploying/debian.md)
- [Fedora](deploying/fedora.md)
- [FreeBSD](deploying/freebsd.md)
- [TURN](turn.md)
- [Appservices](appservices.md)
+18 -3
View File
@@ -1078,7 +1078,10 @@ ###### **Subcommands:**
* `delete` — - Deletes a single media file from our database and on the filesystem via a single MXC URL or event ID (not redacted)
* `delete-list` — - Deletes a codeblock list of MXC URLs from our database and on the filesystem. This will always ignore errors
* `delete-past-remote-media` - Deletes all remote (and optionally local) media created before or after [duration] time using filesystem metadata first created at date, or fallback to last modified date. This will always ignore errors by default
* `delete-past-remote-media` — Deletes all remote (and optionally local) media created before/after
[duration] ago, using filesystem metadata first created at date, or
fallback to last modified date. This will always ignore errors by
default.
* `delete-all-from-user` — - Deletes all the local media from a local user on our server. This will always ignore errors by default
* `delete-all-from-server` — - Deletes all remote media from the specified remote server. This will always ignore errors by default
* `get-file-info`
@@ -1110,13 +1113,25 @@ ## `admin media delete-list`
## `admin media delete-past-remote-media`
- Deletes all remote (and optionally local) media created before or after [duration] time using filesystem metadata first created at date, or fallback to last modified date. This will always ignore errors by default
Deletes all remote (and optionally local) media created before/after
[duration] ago, using filesystem metadata first created at date, or
fallback to last modified date. This will always ignore errors by
default.
* Examples:
* Delete all remote media older than a year:
`!admin media delete-past-remote-media -b 1y`
* Delete all remote and local media from 3 days ago, up until now:
`!admin media delete-past-remote-media -a 3d --yes-i-want-to-delete-local-media`
**Usage:** `admin media delete-past-remote-media [OPTIONS] <DURATION>`
###### **Arguments:**
* `<DURATION>` — - The relative time (e.g. 30s, 5m, 7d) within which to search
* `<DURATION>` — - The relative time (e.g. 30s, 5m, 7d) from now within which to search
###### **Options:**
+201
View File
@@ -0,0 +1,201 @@
# RPM Installation Guide
Continuwuity is available as RPM packages for Fedora, RHEL, and compatible distributions.
The RPM packaging files are maintained in the `fedora/` directory:
- `continuwuity.spec.rpkg` - RPM spec file using rpkg macros for building from git
- `continuwuity.service` - Systemd service file for the server
- `RPM-GPG-KEY-continuwuity.asc` - GPG public key for verifying signed packages
RPM packages built by CI are signed with our GPG key (Ed25519, ID: `5E0FF73F411AAFCA`).
```bash
# Import the signing key
sudo rpm --import https://forgejo.ellis.link/continuwuation/continuwuity/raw/branch/main/fedora/RPM-GPG-KEY-continuwuity.asc
# Verify a downloaded package
rpm --checksig continuwuity-*.rpm
```
## Installation methods
**Stable releases** (recommended)
```bash
# Add the repository and install
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable/continuwuation.repo
sudo dnf install continuwuity
```
**Development builds** from main branch
```bash
# Add the dev repository and install
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/dev/continuwuation.repo
sudo dnf install continuwuity
```
**Feature branch builds** (example: `tom/new-feature`)
```bash
# Branch names are sanitized (slashes become hyphens, lowercase only)
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/tom-new-feature/continuwuation.repo
sudo dnf install continuwuity
```
**Direct installation** without adding repository
```bash
# Latest stable release
sudo dnf install https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable/continuwuity
# Latest development build
sudo dnf install https://forgejo.ellis.link/api/packages/continuwuation/rpm/dev/continuwuity
# Specific feature branch
sudo dnf install https://forgejo.ellis.link/api/packages/continuwuation/rpm/branch-name/continuwuity
```
**Manual repository configuration** (alternative method)
```bash
cat << 'EOF' | sudo tee /etc/yum.repos.d/continuwuity.repo
[continuwuity]
name=Continuwuity - Matrix homeserver
baseurl=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable
enabled=1
gpgcheck=1
gpgkey=https://forgejo.ellis.link/continuwuation/continuwuity/raw/branch/main/fedora/RPM-GPG-KEY-continuwuity.asc
EOF
sudo dnf install continuwuity
```
## Package management
**Automatic updates** with DNF Automatic
```bash
# Install and configure
sudo dnf install dnf-automatic
sudo nano /etc/dnf/automatic.conf # Set: apply_updates = yes
sudo systemctl enable --now dnf-automatic.timer
```
**Manual updates**
```bash
# Check for updates
sudo dnf check-update continuwuity
# Update to latest version
sudo dnf update continuwuity
```
**Switching channels** (stable/dev/feature branches)
```bash
# List enabled repositories
dnf repolist | grep continuwuation
# Disable current repository
sudo dnf config-manager --set-disabled continuwuation-stable # or -dev, or branch name
# Enable desired repository
sudo dnf config-manager --set-enabled continuwuation-dev # or -stable, or branch name
# Update to the new channel's version
sudo dnf update continuwuity
```
**Verifying installation**
```bash
# Check installed version
rpm -q continuwuity
# View package information
rpm -qi continuwuity
# List installed files
rpm -ql continuwuity
# Verify package integrity
rpm -V continuwuity
```
## Service management and removal
**Systemd service commands**
```bash
# Start the service
sudo systemctl start conduwuit
# Enable on boot
sudo systemctl enable conduwuit
# Check status
sudo systemctl status conduwuit
# View logs
sudo journalctl -u conduwuit -f
```
**Uninstallation**
```bash
# Stop and disable the service
sudo systemctl stop conduwuit
sudo systemctl disable conduwuit
# Remove the package
sudo dnf remove continuwuity
# Remove the repository (optional)
sudo rm /etc/yum.repos.d/continuwuation-*.repo
```
## Troubleshooting
**GPG key errors**: Temporarily disable GPG checking
```bash
sudo dnf --nogpgcheck install continuwuity
```
**Repository metadata issues**: Clear and rebuild cache
```bash
sudo dnf clean all
sudo dnf makecache
```
**Finding specific versions**
```bash
# List all available versions
dnf --showduplicates list continuwuity
# Install a specific version
sudo dnf install continuwuity-<version>
```
## Building locally
Build the RPM locally using rpkg:
```bash
# Install dependencies
sudo dnf install rpkg rpm-build cargo-rpm-macros systemd-rpm-macros
# Clone the repository
git clone https://forgejo.ellis.link/continuwuation/continuwuity.git
cd continuwuity
# Build SRPM
rpkg srpm
# Build RPM
rpmbuild --rebuild *.src.rpm
```
+1 -1
View File
@@ -241,7 +241,7 @@ ## Documentation
### Code Comments
- Reference related documentation or parts of the specification
- When a task has multiple ways of being acheved, explain your reasoning for your decision
- When a task has multiple ways of being achieved, explain your reasoning for your decision
- Update comments when code changes
```rs
Generated
+52 -443
View File
@@ -1,95 +1,28 @@
{
"nodes": {
"attic": {
"inputs": {
"crane": "crane",
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"nix-github-actions": "nix-github-actions",
"nixpkgs": "nixpkgs",
"nixpkgs-stable": "nixpkgs-stable"
},
"advisory-db": {
"flake": false,
"locked": {
"lastModified": 1751403276,
"narHash": "sha256-V0EPQNsQko1a8OqIWc2lLviLnMpR1m08Ej00z5RVTfs=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "896ad88fa57ad5dbcd267c0ac51f1b71ccfcb4dd",
"lastModified": 1761112158,
"narHash": "sha256-RIXu/7eyKpQHjsPuAUODO81I4ni8f+WYSb7K4mTG6+0=",
"owner": "rustsec",
"repo": "advisory-db",
"rev": "58f3aaec0e1776f4a900737be8cd7cb00972210d",
"type": "github"
},
"original": {
"owner": "zhaofengli",
"ref": "main",
"repo": "attic",
"type": "github"
}
},
"cachix": {
"inputs": {
"devenv": "devenv",
"flake-compat": "flake-compat_2",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1748883665,
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
"owner": "cachix",
"repo": "cachix",
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "master",
"repo": "cachix",
"type": "github"
}
},
"cachix_2": {
"inputs": {
"devenv": [
"cachix",
"devenv"
],
"flake-compat": [
"cachix",
"devenv"
],
"git-hooks": [
"cachix",
"devenv"
],
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1744206633,
"narHash": "sha256-pb5aYkE8FOoa4n123slgHiOf1UbNSnKe5pEZC+xXD5g=",
"owner": "cachix",
"repo": "cachix",
"rev": "8a60090640b96f9df95d1ab99e5763a586be1404",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"owner": "rustsec",
"repo": "advisory-db",
"type": "github"
}
},
"crane": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1722960479,
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
"lastModified": 1760924934,
"narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=",
"owner": "ipetkov",
"repo": "crane",
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
"rev": "c6b4d5308293d0d04fcfeee92705017537cad02f",
"type": "github"
},
"original": {
@@ -98,53 +31,6 @@
"type": "github"
}
},
"crane_2": {
"locked": {
"lastModified": 1750266157,
"narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=",
"owner": "ipetkov",
"repo": "crane",
"rev": "e37c943371b73ed87faf33f7583860f81f1d5a48",
"type": "github"
},
"original": {
"owner": "ipetkov",
"ref": "master",
"repo": "crane",
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix_2",
"flake-compat": [
"cachix",
"flake-compat"
],
"git-hooks": [
"cachix",
"git-hooks"
],
"nix": "nix",
"nixpkgs": [
"cachix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1748273445,
"narHash": "sha256-5V0dzpNgQM0CHDsMzh+ludYeu1S+Y+IMjbaskSSdFh0=",
"owner": "cachix",
"repo": "devenv",
"rev": "668a50d8b7bdb19a0131f53c9f6c25c9071e1ffb",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
@@ -153,53 +39,20 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1755585599,
"narHash": "sha256-tl/0cnsqB/Yt7DbaGMel2RLa7QG5elA8lkaOXli6VdY=",
"lastModified": 1761115517,
"narHash": "sha256-Fev/ag/c3Fp3JBwHfup3lpA5FlNXfkoshnQ7dssBgJ0=",
"owner": "nix-community",
"repo": "fenix",
"rev": "6ed03ef4c8ec36d193c18e06b9ecddde78fb7e42",
"rev": "320433651636186ea32b387cff05d6bbfa30cea7",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "main",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_3": {
"flake": false,
"locked": {
"lastModified": 1747046372,
@@ -218,17 +71,14 @@
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"attic",
"nixpkgs"
]
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"lastModified": 1760948891,
"narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04",
"type": "github"
},
"original": {
@@ -237,225 +87,13 @@
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"cachix",
"devenv",
"nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"ref": "main",
"repo": "flake-utils",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"cachix",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"cachix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1747372754,
"narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"cachix",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": [
"cachix",
"devenv"
],
"flake-parts": "flake-parts_2",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_3",
"nixpkgs-23-11": [
"cachix",
"devenv"
],
"nixpkgs-regression": [
"cachix",
"devenv"
],
"pre-commit-hooks": [
"cachix",
"devenv"
]
},
"locked": {
"lastModified": 1745930071,
"narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=",
"owner": "domenkozar",
"repo": "nix",
"rev": "b455edf3505f1bf0172b39a735caef94687d0d9c",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.24",
"repo": "nix",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"type": "github"
},
"original": {
"owner": "numtide",
"ref": "main",
"repo": "nix-filter",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1726042813,
"narHash": "sha256-LnNKCCxnwgF+575y0pxUdlGZBO/ru1CtGHIqQVfvjlA=",
"lastModified": 1760878510,
"narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "159be5db480d1df880a0135ca0bfed84c2f88353",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1724316499,
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1733212471,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67",
"type": "github"
},
"original": {
@@ -465,74 +103,40 @@
"type": "github"
}
},
"nixpkgs_3": {
"nixpkgs-lib": {
"locked": {
"lastModified": 1717432640,
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"lastModified": 1754788789,
"narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "a73b9c743612e4244d865a2fdee11865283c04e6",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1748190013,
"narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1751498133,
"narHash": "sha256-QWJ+NQbMU+NcU2xiyo7SNox1fAuwksGlQhpzBl76g1I=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d55716bb59b91ae9d1ced4b1ccdea7a442ecbfdb",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"attic": "attic",
"cachix": "cachix",
"crane": "crane_2",
"advisory-db": "advisory-db",
"crane": "crane",
"fenix": "fenix",
"flake-compat": "flake-compat_3",
"flake-utils": "flake-utils",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_5"
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs",
"treefmt-nix": "treefmt-nix"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1755504847,
"narHash": "sha256-VX0B9hwhJypCGqncVVLC+SmeMVd/GAYbJZ0MiiUn2Pk=",
"lastModified": 1761077270,
"narHash": "sha256-O1uTuvI/rUlubJ8AXKyzh1WSWV3qCZX0huTFUvWLN4E=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "a905e3b21b144d77e1b304e49f3264f6f8d4db75",
"rev": "39990a923c8bca38f5bd29dc4c96e20ee7808d5d",
"type": "github"
},
"original": {
@@ -542,18 +146,23 @@
"type": "github"
}
},
"systems": {
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"lastModified": 1760945191,
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
+33 -338
View File
@@ -1,351 +1,46 @@
{
description = "A nix flake for the continuwuity project";
inputs = {
attic.url = "github:zhaofengli/attic?ref=main";
cachix.url = "github:cachix/cachix?ref=master";
crane = {
url = "github:ipetkov/crane?ref=master";
};
# basics
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# for rust via nix
crane.url = "github:ipetkov/crane";
fenix = {
url = "github:nix-community/fenix?ref=main";
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
# for vuln checks
advisory-db = {
url = "github:rustsec/advisory-db";
flake = false;
};
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
# for default.nix
flake-compat = {
url = "github:edolstra/flake-compat?ref=master";
flake = false;
};
flake-utils.url = "github:numtide/flake-utils?ref=main";
nix-filter.url = "github:numtide/nix-filter?ref=main";
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixpkgs-unstable";
};
outputs =
inputs:
inputs.flake-utils.lib.eachDefaultSystem (
system:
let
pkgsHost = import inputs.nixpkgs {
inherit system;
};
fnx = inputs.fenix.packages.${system};
# The Rust toolchain to use
toolchain = fnx.combine [
(fnx.fromToolchainFile {
file = ./rust-toolchain.toml;
# See also `rust-toolchain.toml`
sha256 = "sha256-+9FmLhAOezBZCOziO0Qct1NOrfpjNsXxc/8I0c7BdKE=";
})
fnx.complete.rustfmt
];
mkScope =
pkgs:
pkgs.lib.makeScope pkgs.newScope (self: {
inherit pkgs inputs;
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (_: toolchain);
main = self.callPackage ./pkg/nix/pkgs/main { };
liburing = pkgs.liburing.overrideAttrs {
# Tests weren't building
outputs = [
"out"
"dev"
"man"
];
buildFlags = [ "library" ];
};
rocksdb =
(pkgs.rocksdb_9_10.override {
# Override the liburing input for the build with our own so
# we have it built with the library flag
inherit (self) liburing;
}).overrideAttrs
(old: {
src = pkgsHost.fetchFromGitea {
domain = "forgejo.ellis.link";
owner = "continuwuation";
repo = "rocksdb";
rev = "10.4.fb";
sha256 = "sha256-/Hvy1yTH/0D5aa7bc+/uqFugCQq4InTdwlRw88vA5IY=";
};
version = "v10.4.fb";
cmakeFlags =
pkgs.lib.subtractLists [
# No real reason to have snappy or zlib, no one uses this
"-DWITH_SNAPPY=1"
"-DZLIB=1"
"-DWITH_ZLIB=1"
# We don't need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=1"
# We don't need to build rocksdb tests
"-DWITH_TESTS=1"
# We use rust-rocksdb via C interface and don't need C++ RTTI
"-DUSE_RTTI=1"
# This doesn't exist in RocksDB, and USE_SSE is deprecated for
# PORTABLE=$(march)
"-DFORCE_SSE42=1"
# PORTABLE will get set in main/default.nix
"-DPORTABLE=1"
] old.cmakeFlags
++ [
# No real reason to have snappy, no one uses this
"-DWITH_SNAPPY=0"
"-DZLIB=0"
"-DWITH_ZLIB=0"
# We don't need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=0"
# We don't need trace tools
"-DWITH_TRACE_TOOLS=0"
# We don't need to build rocksdb tests
"-DWITH_TESTS=0"
# We use rust-rocksdb via C interface and don't need C++ RTTI
"-DUSE_RTTI=0"
];
# outputs has "tools" which we don't need or use
outputs = [ "out" ];
# preInstall hooks has stuff for messing with ldb/sst_dump which we don't need or use
preInstall = "";
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
# Unsetting this so we don't have to revert it and make this nix exclusive
patches = [ ];
postPatch = ''
# Fix gcc-13 build failures due to missing <cstdint> and
# <system_error> includes, fixed upstream since 8.x
sed -e '1i #include <cstdint>' -i db/compaction/compaction_iteration_stats.h
sed -e '1i #include <cstdint>' -i table/block_based/data_block_hash_index.h
sed -e '1i #include <cstdint>' -i util/string_util.h
sed -e '1i #include <cstdint>' -i include/rocksdb/utilities/checkpoint.h
'';
});
});
scopeHost = mkScope pkgsHost;
mkCrossScope =
crossSystem:
let
pkgsCrossStatic =
(import inputs.nixpkgs {
inherit system;
crossSystem = {
config = crossSystem;
};
}).pkgsStatic;
in
mkScope pkgsCrossStatic;
in
{
packages =
{
default = scopeHost.main.override {
disable_features = [
# Don't include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# This is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
default-debug = scopeHost.main.override {
profile = "dev";
# Debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# Don't include experimental features
"experimental"
# This is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
# Just a test profile used for things like CI and complement
default-test = scopeHost.main.override {
profile = "test";
disable_release_max_log_level = true;
disable_features = [
# Don't include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
all-features = scopeHost.main.override {
all_features = true;
disable_features = [
# Don't include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# This is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
all-features-debug = scopeHost.main.override {
profile = "dev";
all_features = true;
# Debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# Don't include experimental features
"experimental"
# This is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
hmalloc = scopeHost.main.override { features = [ "hardened_malloc" ]; };
}
// builtins.listToAttrs (
builtins.concatLists (
builtins.map
(
crossSystem:
let
binaryName = "static-${crossSystem}";
scopeCrossStatic = mkCrossScope crossSystem;
in
[
# An output for a statically-linked binary
{
name = binaryName;
value = scopeCrossStatic.main;
}
# An output for a statically-linked binary with x86_64 haswell
# target optimisations
{
name = "${binaryName}-x86_64-haswell-optimised";
value = scopeCrossStatic.main.override {
x86_64_haswell_target_optimised =
if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false;
};
}
# An output for a statically-linked unstripped debug ("dev") binary
{
name = "${binaryName}-debug";
value = scopeCrossStatic.main.override {
profile = "dev";
# debug build users expect full logs
disable_release_max_log_level = true;
};
}
# An output for a statically-linked unstripped debug binary with the
# "test" profile (for CI usage only)
{
name = "${binaryName}-test";
value = scopeCrossStatic.main.override {
profile = "test";
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
# An output for a statically-linked binary with `--all-features`
{
name = "${binaryName}-all-features";
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
# An output for a statically-linked binary with `--all-features` and with x86_64 haswell
# target optimisations
{
name = "${binaryName}-all-features-x86_64-haswell-optimised";
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
x86_64_haswell_target_optimised =
if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false;
};
}
# An output for a statically-linked unstripped debug ("dev") binary with `--all-features`
{
name = "${binaryName}-all-features-debug";
value = scopeCrossStatic.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
# An output for a statically-linked binary with hardened_malloc
{
name = "${binaryName}-hmalloc";
value = scopeCrossStatic.main.override {
features = [ "hardened_malloc" ];
};
}
]
)
[
#"x86_64-apple-darwin"
#"aarch64-apple-darwin"
"x86_64-linux-gnu"
"x86_64-linux-musl"
"aarch64-linux-musl"
]
)
);
}
);
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ ./nix ];
systems = [
# good support
"x86_64-linux"
# support untested but theoretically there
"aarch64-linux"
];
};
}
+108
View File
@@ -0,0 +1,108 @@
{ inputs, ... }:
{
perSystem =
{
self',
lib,
pkgs,
...
}:
let
uwulib = inputs.self.uwulib.init pkgs;
rocksdbAllFeatures = self'.packages.rocksdb.override {
enableJemalloc = true;
enableLiburing = true;
};
commonAttrs = (uwulib.build.commonAttrs { }) // {
buildInputs = [
pkgs.liburing
pkgs.rust-jemalloc-sys-unprefixed
rocksdbAllFeatures
];
nativeBuildInputs = [
pkgs.pkg-config
# bindgen needs the build platform's libclang. Apparently due to "splicing
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
# right thing here.
pkgs.rustPlatform.bindgenHook
];
env = {
LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.llvmPackages.libclang.lib ];
LD_LIBRARY_PATH = lib.makeLibraryPath [
pkgs.liburing
pkgs.rust-jemalloc-sys-unprefixed
rocksdbAllFeatures
];
}
// uwulib.environment.buildPackageEnv
// {
ROCKSDB_INCLUDE_DIR = "${rocksdbAllFeatures}/include";
ROCKSDB_LIB_DIR = "${rocksdbAllFeatures}/lib";
};
};
cargoArtifacts = self'.packages.continuwuity-all-features-deps;
in
{
# taken from
#
# https://crane.dev/examples/quick-start.html
checks = {
continuwuity-all-features-build = self'.packages.continuwuity-all-features-bin;
continuwuity-all-features-clippy = uwulib.build.craneLibForChecks.cargoClippy (
commonAttrs
// {
inherit cargoArtifacts;
cargoClippyExtraArgs = "-- --deny warnings";
}
);
continuwuity-all-features-docs = uwulib.build.craneLibForChecks.cargoDoc (
commonAttrs
// {
inherit cargoArtifacts;
# This can be commented out or tweaked as necessary, e.g. set to
# `--deny rustdoc::broken-intra-doc-links` to only enforce that lint
env.RUSTDOCFLAGS = "--deny warnings";
}
);
# Check formatting
continuwuity-all-features-fmt = uwulib.build.craneLibForChecks.cargoFmt {
src = uwulib.build.src;
};
continuwuity-all-features-toml-fmt = uwulib.build.craneLibForChecks.taploFmt {
src = pkgs.lib.sources.sourceFilesBySuffices uwulib.build.src [ ".toml" ];
# taplo arguments can be further customized below as needed
taploExtraArgs = "--config ${inputs.self}/taplo.toml";
};
# Audit dependencies
continuwuity-all-features-audit = uwulib.build.craneLibForChecks.cargoAudit {
inherit (inputs) advisory-db;
src = uwulib.build.src;
};
# Audit licenses
continuwuity-all-features-deny = uwulib.build.craneLibForChecks.cargoDeny {
src = uwulib.build.src;
};
# Run tests with cargo-nextest
# Consider setting `doCheck = false` on `continuwuity-all-features` if you do not want
# the tests to run twice
continuwuity-all-features-nextest = uwulib.build.craneLibForChecks.cargoNextest (
commonAttrs
// {
inherit cargoArtifacts;
partitions = 1;
partitionType = "count";
cargoNextestPartitionsExtraArgs = "--no-tests=pass";
}
);
};
};
}
+11
View File
@@ -0,0 +1,11 @@
{
imports = [
./checks
./packages
./shells
./tests
./hydra.nix
./fmt.nix
];
}
+37
View File
@@ -0,0 +1,37 @@
{ inputs, ... }:
{
# load the flake module from upstream
imports = [ inputs.treefmt-nix.flakeModule ];
perSystem =
{ self', lib, ... }:
{
treefmt = {
# repo root as project root
projectRoot = inputs.self;
# the formatters
programs = {
nixfmt.enable = true;
typos = {
enable = true;
configFile = "${inputs.self}/.typos.toml";
};
taplo = {
enable = true;
settings = lib.importTOML "${inputs.self}/taplo.toml";
};
};
settings.formatter.rustfmt = {
command = "${lib.getExe' self'.packages.dev-toolchain "rustfmt"}";
includes = [ "**/*.rs" ];
options = [
"--unstable-features"
"--edition=2024"
"--config-path=${inputs.self}/rustfmt.toml"
];
};
};
};
}
+9
View File
@@ -0,0 +1,9 @@
{ inputs, ... }:
let
lib = inputs.nixpkgs.lib;
in
{
flake.hydraJobs.packages = builtins.mapAttrs (
_name: lib.hydraJob
) inputs.self.packages.x86_64-linux;
}
+60
View File
@@ -0,0 +1,60 @@
{ inputs, ... }:
{
perSystem =
{
self',
lib,
pkgs,
...
}:
let
uwulib = inputs.self.uwulib.init pkgs;
in
{
packages =
lib.pipe
[
# this is the default variant
{
variantName = "default";
commonAttrsArgs.profile = "release";
rocksdb = self'.packages.rocksdb;
features = { };
}
# this is the variant with all features enabled (liburing + jemalloc)
{
variantName = "all-features";
commonAttrsArgs.profile = "release";
rocksdb = self'.packages.rocksdb.override {
enableJemalloc = true;
enableLiburing = true;
};
features = {
enabledFeatures = "all";
disabledFeatures = uwulib.features.defaultDisabledFeatures ++ [ "bindgen-static" ];
};
}
]
[
(builtins.map (cfg: rec {
deps = {
name = "continuwuity-${cfg.variantName}-deps";
value = uwulib.build.buildDeps {
features = uwulib.features.calcFeatures cfg.features;
inherit (cfg) commonAttrsArgs rocksdb;
};
};
bin = {
name = "continuwuity-${cfg.variantName}-bin";
value = uwulib.build.buildPackage {
deps = self'.packages.${deps.name};
features = uwulib.features.calcFeatures cfg.features;
inherit (cfg) commonAttrsArgs rocksdb;
};
};
}))
(builtins.concatMap builtins.attrValues)
builtins.listToAttrs
];
};
}
+14
View File
@@ -0,0 +1,14 @@
{
imports = [
./continuwuity
./rocksdb
./rust.nix
./uwulib
];
perSystem =
{ self', ... }:
{
packages.default = self'.packages.continuwuity-default-bin;
};
}
+12
View File
@@ -0,0 +1,12 @@
{
perSystem =
{
pkgs,
...
}:
{
packages = {
rocksdb = pkgs.callPackage ./package.nix { };
};
};
}
+88
View File
@@ -0,0 +1,88 @@
{
lib,
stdenv,
rocksdb,
liburing,
rust-jemalloc-sys-unprefixed,
enableJemalloc ? false,
enableLiburing ? false,
fetchFromGitea,
...
}:
let
notDarwin = !stdenv.hostPlatform.isDarwin;
in
(rocksdb.override {
# Override the liburing input for the build with our own so
# we have it built with the library flag
inherit liburing;
jemalloc = rust-jemalloc-sys-unprefixed;
# rocksdb fails to build with prefixed jemalloc, which is required on
# darwin due to [1]. In this case, fall back to building rocksdb with
# libc malloc. This should not cause conflicts, because all of the
# jemalloc symbols are prefixed.
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
enableJemalloc = enableJemalloc && notDarwin;
# for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely
enableLiburing = enableLiburing && notDarwin;
}).overrideAttrs
(old: {
src = fetchFromGitea {
domain = "forgejo.ellis.link";
owner = "continuwuation";
repo = "rocksdb";
rev = "10.5.fb";
sha256 = "sha256-X4ApGLkHF9ceBtBg77dimEpu720I79ffLoyPa8JMHaU=";
};
version = "10.5.fb";
cmakeFlags =
lib.subtractLists (builtins.map (flag: lib.cmakeBool flag true) [
# No real reason to have snappy or zlib, no one uses this
"WITH_SNAPPY"
"ZLIB"
"WITH_ZLIB"
# We don't need to use ldb or sst_dump (core_tools)
"WITH_CORE_TOOLS"
# We don't need to build rocksdb tests
"WITH_TESTS"
# We use rust-rocksdb via C interface and don't need C++ RTTI
"USE_RTTI"
# This doesn't exist in RocksDB, and USE_SSE is deprecated for
# PORTABLE=$(march)
"FORCE_SSE42"
]) old.cmakeFlags
++ (builtins.map (flag: lib.cmakeBool flag false) [
# No real reason to have snappy, no one uses this
"WITH_SNAPPY"
"ZLIB"
"WITH_ZLIB"
# We don't need to use ldb or sst_dump (core_tools)
"WITH_CORE_TOOLS"
# We don't need trace tools
"WITH_TRACE_TOOLS"
# We don't need to build rocksdb tests
"WITH_TESTS"
# We use rust-rocksdb via C interface and don't need C++ RTTI
"USE_RTTI"
]);
enableLiburing = enableLiburing && notDarwin;
# outputs has "tools" which we don't need or use
outputs = [ "out" ];
# preInstall hooks has stuff for messing with ldb/sst_dump which we don't need or use
preInstall = "";
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
# Unsetting `patches` so we don't have to revert it and make this nix exclusive
patches = [ ];
})
+32
View File
@@ -0,0 +1,32 @@
{ inputs, ... }:
{
perSystem =
{
system,
lib,
...
}:
{
packages =
let
fnx = inputs.fenix.packages.${system};
stable = fnx.fromToolchainFile {
file = inputs.self + "/rust-toolchain.toml";
# See also `rust-toolchain.toml`
sha256 = "sha256-SJwZ8g0zF2WrKDVmHrVG3pD2RGoQeo24MEXnNx5FyuI=";
};
in
{
# used for building nix stuff (doesn't include rustfmt overhead)
build-toolchain = stable;
# used for dev shells
dev-toolchain = fnx.combine [
stable
# use the nightly rustfmt because we use nightly features
fnx.complete.rustfmt
];
};
};
}
+108
View File
@@ -0,0 +1,108 @@
args@{ pkgs, inputs, ... }:
let
inherit (pkgs) lib;
uwuenv = import ./environment.nix args;
selfpkgs = inputs.self.packages.${pkgs.stdenv.system};
in
rec {
# basic, very minimal instance of the crane library with a minimal rust toolchain
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (_: selfpkgs.build-toolchain);
# the checks require more rust toolchain components, hence we have this separate instance of the crane library
craneLibForChecks = (inputs.crane.mkLib pkgs).overrideToolchain (_: selfpkgs.dev-toolchain);
# meta information (name, version, etc) of the rust crate based on the Cargo.toml
crateInfo = craneLib.crateNameFromCargoToml { cargoToml = "${inputs.self}/Cargo.toml"; };
src =
let
# see https://crane.dev/API.html#cranelibfiltercargosources
#
# we need to keep the `web` directory which would be filtered out by the regular source filtering function
#
# https://crane.dev/API.html#cranelibcleancargosource
isWebTemplate = path: _type: builtins.match ".*src/web.*" path != null;
isRust = craneLib.filterCargoSources;
isNix = path: _type: builtins.match ".+/nix.*" path != null;
webOrRustNotNix = p: t: !(isNix p t) && (isWebTemplate p t || isRust p t);
in
lib.cleanSourceWith {
src = inputs.self;
filter = webOrRustNotNix;
name = "source";
};
# common attrs that are shared between building continuwuity's deps and the package itself
commonAttrs =
{
profile ? "dev",
...
}:
{
inherit (crateInfo)
pname
version
;
inherit src;
# this prevents unnecessary rebuilds
strictDeps = true;
dontStrip = profile == "dev" || profile == "test";
dontPatchELF = profile == "dev" || profile == "test";
doCheck = true;
nativeBuildInputs = [
# bindgen needs the build platform's libclang. Apparently due to "splicing
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
# right thing here.
pkgs.rustPlatform.bindgenHook
];
};
makeRocksDBEnv =
{ rocksdb }:
{
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
};
# function that builds the continuwuity dependencies derivation
buildDeps =
{
rocksdb,
features,
commonAttrsArgs,
}:
craneLib.buildDepsOnly (
(commonAttrs commonAttrsArgs)
// {
env = uwuenv.buildDepsOnlyEnv // (makeRocksDBEnv { inherit rocksdb; });
inherit (features) cargoExtraArgs;
}
);
# function that builds the continuwuity package
buildPackage =
{
deps,
rocksdb,
features,
commonAttrsArgs,
}:
let
rocksdbEnv = makeRocksDBEnv { inherit rocksdb; };
in
craneLib.buildPackage (
(commonAttrs commonAttrsArgs)
// {
cargoArtifacts = deps;
doCheck = true;
env = uwuenv.buildPackageEnv // rocksdbEnv;
passthru.env = uwuenv.buildPackageEnv // rocksdbEnv;
meta.mainProgram = crateInfo.pname;
inherit (features) cargoExtraArgs;
}
);
}
+10
View File
@@ -0,0 +1,10 @@
{ inputs, ... }:
{
flake.uwulib = {
init = pkgs: {
features = import ./features.nix { inherit pkgs inputs; };
environment = import ./environment.nix { inherit pkgs inputs; };
build = import ./build.nix { inherit pkgs inputs; };
};
};
}
+18
View File
@@ -0,0 +1,18 @@
args@{ pkgs, inputs, ... }:
let
uwubuild = import ./build.nix args;
in
rec {
buildDepsOnlyEnv = {
# https://crane.dev/faq/rebuilds-bindgen.html
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
CARGO_PROFILE = "release";
}
// uwubuild.craneLib.mkCrossToolchainEnv (p: pkgs.clangStdenv);
buildPackageEnv = {
GIT_COMMIT_HASH = inputs.self.rev or inputs.self.dirtyRev or "";
GIT_COMMIT_HASH_SHORT = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
}
// buildDepsOnlyEnv;
}
+77
View File
@@ -0,0 +1,77 @@
{ pkgs, inputs, ... }:
let
inherit (pkgs) lib;
in
rec {
defaultDisabledFeatures = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
# we don't want to enable this feature set by default but be more specific about it
"full"
];
# We perform default-feature unification in nix, because some of the dependencies
# on the nix side depend on feature values.
calcFeatures =
{
tomlPath ? "${inputs.self}/src/main",
# either a list of feature names or a string "all" which enables all non-default features
enabledFeatures ? [ ],
disabledFeatures ? defaultDisabledFeatures,
default_features ? true,
disable_release_max_log_level ? false,
}:
let
# simple helper to get the contents of a Cargo.toml file in a nix format
getToml = path: lib.importTOML "${path}/Cargo.toml";
# get all the features except for the default features
allFeatures = lib.pipe tomlPath [
getToml
(manifest: manifest.features)
lib.attrNames
(lib.remove "default")
];
# get just the default enabled features
allDefaultFeatures = lib.pipe tomlPath [
getToml
(manifest: manifest.features.default)
];
# depending on the value of enabledFeatures choose just a set or all non-default features
#
# - [ list of features ] -> choose exactly the features listed
# - "all" -> choose all non-default features
additionalFeatures = if enabledFeatures == "all" then allFeatures else enabledFeatures;
# unification with default features (if enabled)
features = lib.unique (additionalFeatures ++ lib.optionals default_features allDefaultFeatures);
# prepare the features that are subtracted from the set
disabledFeatures' =
disabledFeatures ++ lib.optionals disable_release_max_log_level [ "release_max_log_level" ];
# construct the final feature set
finalFeatures = lib.subtractLists disabledFeatures' features;
in
{
# final feature set, useful for querying it
features = finalFeatures;
# crane flag with the relevant features
cargoExtraArgs = builtins.concatStringsSep " " [
"--no-default-features"
"--locked"
(lib.optionalString (finalFeatures != [ ]) "--features")
(builtins.concatStringsSep "," finalFeatures)
];
};
}
+29
View File
@@ -0,0 +1,29 @@
{ inputs, ... }:
{
perSystem =
{
self',
lib,
pkgs,
...
}:
let
uwulib = inputs.self.uwulib.init pkgs;
rocksdbAllFeatures = self'.packages.rocksdb.override {
enableJemalloc = true;
enableLiburing = true;
};
in
{
# basic nix shell containing all things necessary to build continuwuity in all flavors manually (on x86_64-linux)
devShells.default = uwulib.build.craneLib.devShell {
packages = [
pkgs.pkg-config
pkgs.liburing
pkgs.rust-jemalloc-sys-unprefixed
rocksdbAllFeatures
];
env.LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.llvmPackages.libclang.lib ];
};
};
}
+124
View File
@@ -0,0 +1,124 @@
{
perSystem =
{
self',
lib,
pkgs,
...
}:
{
# run some nixos tests as checks
checks = lib.pipe self'.packages [
# we take all packages (names)
builtins.attrNames
# we filter out all packages that end with `-bin` (which we are interested in for testing)
(builtins.filter (lib.hasSuffix "-bin"))
# for each of these binaries we built the basic nixos test
#
# this test was initially yoinked from
#
# https://github.com/NixOS/nixpkgs/blob/960ce26339661b1b69c6f12b9063ca51b688615f/nixos/tests/matrix/continuwuity.nix
(builtins.map (name: {
name = "test-${name}";
value = pkgs.testers.runNixOSTest {
inherit name;
nodes = {
continuwuity = {
services.matrix-continuwuity = {
enable = true;
package = self'.packages.${name};
settings.global = {
server_name = name;
address = [ "0.0.0.0" ];
allow_registration = true;
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true;
};
extraEnvironment.RUST_BACKTRACE = "yes";
};
networking.firewall.allowedTCPPorts = [ 6167 ];
};
client =
{ pkgs, ... }:
{
environment.systemPackages = [
(pkgs.writers.writePython3Bin "do_test" { libraries = [ pkgs.python3Packages.matrix-nio ]; } ''
import asyncio
import nio
async def main() -> None:
# Connect to continuwuity
client = nio.AsyncClient("http://continuwuity:6167", "alice")
# Register as user alice
response = await client.register("alice", "my-secret-password")
# Log in as user alice
response = await client.login("my-secret-password")
# Create a new room
response = await client.room_create(federate=False)
print("Matrix room create response:", response)
assert isinstance(response, nio.RoomCreateResponse)
room_id = response.room_id
# Join the room
response = await client.join(room_id)
print("Matrix join response:", response)
assert isinstance(response, nio.JoinResponse)
# Send a message to the room
response = await client.room_send(
room_id=room_id,
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": "Hello continuwuity!"
}
)
print("Matrix room send response:", response)
assert isinstance(response, nio.RoomSendResponse)
# Sync responses
response = await client.sync(timeout=30000)
print("Matrix sync response:", response)
assert isinstance(response, nio.SyncResponse)
# Check the message was received by continuwuity
last_message = response.rooms.join[room_id].timeline.events[-1].body
assert last_message == "Hello continuwuity!"
# Leave the room
response = await client.room_leave(room_id)
print("Matrix room leave response:", response)
assert isinstance(response, nio.RoomLeaveResponse)
# Close the client
await client.close()
if __name__ == "__main__":
asyncio.run(main())
'')
];
};
};
testScript = ''
start_all()
with subtest("start continuwuity"):
continuwuity.wait_for_unit("continuwuity.service")
continuwuity.wait_for_open_port(6167)
with subtest("ensure messages can be exchanged"):
client.succeed("do_test >&2")
'';
};
}))
builtins.listToAttrs
];
};
}
+7 -5
View File
@@ -12,13 +12,14 @@ Group=conduwuit
Type=notify-reload
ReloadSignal=SIGUSR1
Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
Environment="CONTINUWUITY_LOG_TO_JOURNALD=true"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit"
Environment="CONTINUWUITY_DATABASE_PATH=%S/conduwuit"
Environment="CONTINUWUITY_CONFIG_RELOAD_SIGNAL=true"
ExecStart=/usr/bin/conduwuit
LoadCredential=conduwuit.toml:/etc/conduwuit/conduwuit.toml
ExecStart=/usr/bin/conduwuit --config ${CREDENTIALS_DIRECTORY}/conduwuit.toml
AmbientCapabilities=
CapabilityBoundingSet=
@@ -52,8 +53,9 @@ SystemCallFilter=@system-service @resources
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
SystemCallErrorNumber=EPERM
# ConfigurationDirectory isn't specified here because it's created by
# the distro's package manager.
StateDirectory=conduwuit
ConfigurationDirectory=conduwuit
RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750
+20 -4
View File
@@ -1,12 +1,28 @@
# Continuwuity for Debian
This document provides information about downloading and deploying the Debian package. You can also use this guide for other `apt`-based distributions such as Ubuntu.
This document provides information about downloading and deploying the Debian package. You can also use this guide for other deb-based distributions such as Ubuntu.
### Installation
See the [generic deployment guide](../deploying/generic.md) for additional information about using the Debian package.
To add the Continuwuation apt repository:
```bash
# Replace with `"dev"` for bleeding-edge builds at your own risk
export COMPONENT="stable"
# Import the Continuwuation signing key
sudo curl https://forgejo.ellis.link/api/packages/continuwuation/debian/repository.key -o /etc/apt/keyrings/forgejo-continuwuation.asc
# Add a new apt source list pointing to the repository
echo "deb [signed-by=/etc/apt/keyrings/forgejo-continuwuation.asc] https://forgejo.ellis.link/api/packages/continuwuation/debian $(lsb_release -sc) $COMPONENT" | sudo tee /etc/apt/sources.list.d/continuwuation.list
# Update remote package lists
sudo apt update
```
No `apt` repository is currently available. This feature is in development.
To install continuwuity:
```bash
sudo apt install continuwuity
```
The `continuwuity` package conflicts with the old `conduwuit` package and will remove it automatically when installed.
See the [generic deployment guide](../deploying/generic.md) for additional information about using the Debian package.
### Configuration
@@ -16,7 +32,7 @@ ### Configuration
### Running
The package uses the [`conduwuit.service`](../configuration/examples.md#example-systemd-unit-file) systemd unit file to start and stop Continuwuity. The binary installs at `/usr/sbin/conduwuit`.
The package uses the [`conduwuit.service`](../configuration/examples.md#example-systemd-unit-file) systemd unit file to start and stop Continuwuity. The binary installs at `/usr/bin/conduwuit`.
By default, this package assumes that Continuwuity runs behind a reverse proxy. The default configuration options apply (listening on `localhost` and TCP port `6167`). Matrix federation requires a valid domain name and TLS. To federate properly, you must set up TLS certificates and certificate renewal.
+3 -4
View File
@@ -1,6 +1,5 @@
# This should be run using rpkg-util: https://docs.pagure.org/rpkg-util
# This should be run using rpkg: https://docs.pagure.org/rpkg
# it requires Internet access and is not suitable for Fedora main repos
# TODO: rpkg-util is no longer maintained, find a replacement
Name: continuwuity
Version: {{{ git_repo_version }}}
@@ -52,7 +51,7 @@ find .cargo/registry/ -executable -name "*.rs" -exec chmod -x {} +
%install
install -Dpm0755 target/rpm/conduwuit -t %{buildroot}%{_bindir}
install -Dpm0644 pkg/conduwuit.service -t %{buildroot}%{_unitdir}
install -Dpm0644 conduwuit-example.toml %{buildroot}%{_sysconfdir}/conduwuit/conduwuit.toml
install -Dpm0600 conduwuit-example.toml %{buildroot}%{_sysconfdir}/conduwuit/conduwuit.toml
%files
%license LICENSE
@@ -61,7 +60,7 @@ install -Dpm0644 conduwuit-example.toml %{buildroot}%{_sysconfdir}/conduwuit/con
%doc CONTRIBUTING.md
%doc README.md
%doc SECURITY.md
%config %{_sysconfdir}/conduwuit/conduwuit.toml
%config(noreplace) %{_sysconfdir}/conduwuit/conduwuit.toml
%{_bindir}/conduwuit
%{_unitdir}/conduwuit.service
@@ -1,83 +0,0 @@
{ lib
, pkgsBuildHost
, rust
, stdenv
}:
lib.optionalAttrs stdenv.hostPlatform.isStatic
{
ROCKSDB_STATIC = "";
}
//
{
CARGO_BUILD_RUSTFLAGS =
lib.concatStringsSep
" "
(lib.optionals
stdenv.hostPlatform.isStatic
[ "-C" "relocation-model=static" ]
++ lib.optionals
(stdenv.buildPlatform.config != stdenv.hostPlatform.config)
[
"-l"
"c"
"-l"
"stdc++"
"-L"
"${stdenv.cc.cc.lib}/${stdenv.hostPlatform.config}/lib"
]
);
}
# What follows is stolen from [here][0]. Its purpose is to properly
# configure compilers and linkers for various stages of the build, and
# even covers the case of build scripts that need native code compiled and
# run on the build platform (I think).
#
# [0]: https://github.com/NixOS/nixpkgs/blob/nixpkgs-unstable/pkgs/build-support/rust/lib/default.nix#L48-L68
//
(
let
inherit (rust.lib) envVars;
in
lib.optionalAttrs
(stdenv.targetPlatform.rust.rustcTarget
!= stdenv.hostPlatform.rust.rustcTarget)
(
let
inherit (stdenv.targetPlatform.rust) cargoEnvVarTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForTarget;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForTarget;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForTarget;
}
)
//
(
let
inherit (stdenv.hostPlatform.rust) cargoEnvVarTarget rustcTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForHost;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForHost;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForHost;
CARGO_BUILD_TARGET = rustcTarget;
}
)
//
(
let
inherit (stdenv.buildPlatform.rust) cargoEnvVarTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForBuild;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForBuild;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForBuild;
HOST_CC = "${pkgsBuildHost.stdenv.cc}/bin/cc";
HOST_CXX = "${pkgsBuildHost.stdenv.cc}/bin/c++";
}
)
)
-224
View File
@@ -1,224 +0,0 @@
# Dependencies (keep sorted)
{ craneLib
, inputs
, jq
, lib
, libiconv
, liburing
, pkgsBuildHost
, rocksdb
, removeReferencesTo
, rust
, rust-jemalloc-sys
, stdenv
# Options (keep sorted)
, all_features ? false
, default_features ? true
# default list of disabled features
, disable_features ? [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
]
, disable_release_max_log_level ? false
, features ? [ ]
, profile ? "release"
# rocksdb compiled with -march=haswell and target-cpu=haswell rustflag
# haswell is pretty much any x86 cpu made in the last 12 years, and
# supports modern CPU extensions that rocksdb can make use of.
# disable if trying to make a portable x86_64 build for very old hardware
, x86_64_haswell_target_optimised ? false
}:
let
# We perform default-feature unification in nix, because some of the dependencies
# on the nix side depend on feature values.
crateFeatures = path:
let manifest = lib.importTOML "${path}/Cargo.toml"; in
lib.remove "default" (lib.attrNames manifest.features);
crateDefaultFeatures = path:
(lib.importTOML "${path}/Cargo.toml").features.default;
allDefaultFeatures = crateDefaultFeatures "${inputs.self}/src/main";
allFeatures = crateFeatures "${inputs.self}/src/main";
features' = lib.unique
(features ++
lib.optionals default_features allDefaultFeatures ++
lib.optionals all_features allFeatures);
disable_features' = disable_features ++ lib.optionals disable_release_max_log_level [ "release_max_log_level" ];
features'' = lib.subtractLists disable_features' features';
featureEnabled = feature: builtins.elem feature features'';
enableLiburing = featureEnabled "io_uring" && !stdenv.hostPlatform.isDarwin;
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the
# tikv-jemalloc-sys crate to use the nixpkgs jemalloc instead of building it's
# own. In order for this to work, we need to set flags on the build that match
# whatever flags tikv-jemalloc-sys was going to use. These are dependent on
# which features we enable in tikv-jemalloc-sys.
rust-jemalloc-sys' = (rust-jemalloc-sys.override {
# tikv-jemalloc-sys/unprefixed_malloc_on_supported_platforms feature
unprefixed = true;
}).overrideAttrs (old: {
configureFlags = old.configureFlags ++
# we dont need docs
[ "--disable-doc" ] ++
# we dont need cxx/C++ integration
[ "--disable-cxx" ] ++
# tikv-jemalloc-sys/profiling feature
lib.optional (featureEnabled "jemalloc_prof") "--enable-prof" ++
# tikv-jemalloc-sys/stats feature
(if (featureEnabled "jemalloc_stats") then [ "--enable-stats" ] else [ "--disable-stats" ]);
});
buildDepsOnlyEnv =
let
rocksdb' = (rocksdb.override {
jemalloc = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
# rocksdb fails to build with prefixed jemalloc, which is required on
# darwin due to [1]. In this case, fall back to building rocksdb with
# libc malloc. This should not cause conflicts, because all of the
# jemalloc symbols are prefixed.
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
enableJemalloc = featureEnabled "jemalloc" && !stdenv.hostPlatform.isDarwin;
# for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely
inherit enableLiburing;
}).overrideAttrs (old: {
inherit enableLiburing;
cmakeFlags = (if x86_64_haswell_target_optimised then
(lib.subtractLists [
# dont make a portable build if x86_64_haswell_target_optimised is enabled
"-DPORTABLE=1"
]
old.cmakeFlags
++ [ "-DPORTABLE=haswell" ]) else [ "-DPORTABLE=1" ]
)
++ old.cmakeFlags;
# outputs has "tools" which we dont need or use
outputs = [ "out" ];
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
preInstall = "";
});
in
{
# https://crane.dev/faq/rebuilds-bindgen.html
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
CARGO_PROFILE = profile;
ROCKSDB_INCLUDE_DIR = "${rocksdb'}/include";
ROCKSDB_LIB_DIR = "${rocksdb'}/lib";
}
//
(import ./cross-compilation-env.nix {
# Keep sorted
inherit
lib
pkgsBuildHost
rust
stdenv;
});
buildPackageEnv = {
GIT_COMMIT_HASH = inputs.self.rev or inputs.self.dirtyRev or "";
GIT_COMMIT_HASH_SHORT = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
} // buildDepsOnlyEnv // {
# Only needed in static stdenv because these are transitive dependencies of rocksdb
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
" -L${lib.getLib liburing}/lib -luring"
+ lib.optionalString x86_64_haswell_target_optimised
" -Ctarget-cpu=haswell";
};
commonAttrs = {
inherit
(craneLib.crateNameFromCargoToml {
cargoToml = "${inputs.self}/Cargo.toml";
})
pname
version;
src = let filter = inputs.nix-filter.lib; in filter {
root = inputs.self;
# Keep sorted
include = [
".cargo"
"Cargo.lock"
"Cargo.toml"
"src"
"xtask"
];
};
doCheck = true;
cargoExtraArgs = "--no-default-features --locked "
+ lib.optionalString
(features'' != [ ])
"--features " + (builtins.concatStringsSep "," features'');
dontStrip = profile == "dev" || profile == "test";
dontPatchELF = profile == "dev" || profile == "test";
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys'
# needed to build Rust applications on macOS
++ lib.optionals stdenv.hostPlatform.isDarwin [
# https://github.com/NixOS/nixpkgs/issues/206242
# ld: library not found for -liconv
libiconv
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
pkgsBuildHost.darwin.apple_sdk.frameworks.Security
];
nativeBuildInputs = [
# bindgen needs the build platform's libclang. Apparently due to "splicing
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
# right thing here.
pkgsBuildHost.rustPlatform.bindgenHook
# We don't actually depend on `jq`, but crane's `buildPackage` does, but
# its `buildDepsOnly` doesn't. This causes those two derivations to have
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
# rebuilds of bindgen and its depedents.
jq
];
};
in
craneLib.buildPackage (commonAttrs // {
cargoArtifacts = craneLib.buildDepsOnly (commonAttrs // {
env = buildDepsOnlyEnv;
});
doCheck = true;
cargoExtraArgs = "--no-default-features --locked "
+ lib.optionalString
(features'' != [ ])
"--features " + (builtins.concatStringsSep "," features'');
env = buildPackageEnv;
passthru = {
env = buildPackageEnv;
};
meta.mainProgram = commonAttrs.pname;
})
+47 -18
View File
@@ -1,22 +1,28 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"extends": ["config:recommended", "replacements:all"],
"osvVulnerabilityAlerts": true,
"lockFileMaintenance": {
"enabled": true,
"schedule": ["at any time"]
},
"platformAutomerge": true,
"nix": {
"enabled": true
},
"pre-commit": {
"enabled": true
},
"labels": ["Dependencies", "Dependencies/Renovate"],
"ignoreDeps": [
"tikv-jemallocator",
"tikv-jemalloc-sys",
"tikv-jemalloc-ctl",
"opentelemetry",
"opentelemetry_sdk",
"opentelemetry-jaeger",
"tracing-opentelemetry"
"rustyline-async",
"event-listener",
"async-channel",
"core_affinity",
"hyper-util"
],
"github-actions": {
"enabled": true,
@@ -29,10 +35,15 @@
},
"packageRules": [
{
"description": "Batch minor and patch GitHub Actions updates",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions-non-major"
"description": "Batch patch-level Rust dependency updates",
"matchManagers": ["cargo"],
"matchUpdateTypes": ["patch"],
"groupName": "rust-patch-updates"
},
{
"description": "Limit concurrent Cargo PRs",
"matchManagers": ["cargo"],
"prConcurrentLimit": 5
},
{
"description": "Group Rust toolchain updates into a single PR",
@@ -41,19 +52,37 @@
"groupName": "rust-toolchain"
},
{
"description": "Group lockfile updates into a single PR",
"matchUpdateTypes": ["lockFileMaintenance"],
"groupName": "lockfile-maintenance"
"description": "Batch minor and patch GitHub Actions updates",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions-non-major"
},
{
"description": "Batch patch-level Rust dependency updates",
"matchManagers": ["cargo"],
"matchUpdateTypes": ["patch"],
"groupName": "rust-patch-updates"
"description": "Pin forgejo artifact actions to prevent breaking changes",
"matchManagers": ["github-actions"],
"matchPackageNames": ["forgejo/upload-artifact", "forgejo/download-artifact"],
"enabled": false
},
{
"matchManagers": ["cargo"],
"prConcurrentLimit": 5
"description": "Auto-merge renovatebot docker image updates",
"matchDatasources": ["docker"],
"matchPackageNames": ["ghcr.io/renovatebot/renovate"],
"automerge": true,
"automergeStrategy": "fast-forward",
"extends": ["schedule:earlyMondays"]
}
],
"customManagers": [
{
"customType": "regex",
"description": "Update _VERSION variables in Dockerfiles",
"managerFilePatterns": [
"/(^|/)([Dd]ocker|[Cc]ontainer)file[^/]*$/",
"/(^|/|\\.)([Dd]ocker|[Cc]ontainer)file$/"
],
"matchStrings": [
"# renovate: datasource=(?<datasource>[a-zA-Z0-9-._]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?(?: registryUrl=(?<registryUrl>[^\\s]+?))?\\s+(?:ENV\\s+|ARG\\s+)?[A-Za-z0-9_]+?_VERSION[ =][\"']?(?<currentValue>.+?)[\"']?\\s+(?:(?:ENV\\s+|ARG\\s+)?[A-Za-z0-9_]+?_CHECKSUM[ =][\"']?(?<currentDigest>.+?)[\"']?\\s)?"
]
}
]
}
+1 -1
View File
@@ -10,7 +10,7 @@
[toolchain]
profile = "minimal"
channel = "1.89.0"
channel = "1.90.0"
components = [
# For rust-analyzer
"rust-src",
+1 -1
View File
@@ -85,7 +85,7 @@ futures.workspace = true
log.workspace = true
ruma.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
serde-saphyr.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
+2 -2
View File
@@ -16,7 +16,7 @@ pub(super) async fn register(&self) -> Result {
let range = 1..checked!(body_len - 1)?;
let appservice_config_body = body[range].join("\n");
let parsed_config = serde_yaml::from_str(&appservice_config_body);
let parsed_config = serde_saphyr::from_str(&appservice_config_body);
match parsed_config {
| Err(e) => return Err!("Could not parse appservice config as YAML: {e}"),
| Ok(registration) => match self
@@ -57,7 +57,7 @@ pub(super) async fn show_appservice_config(&self, appservice_identifier: String)
{
| None => return Err!("Appservice does not exist."),
| Some(config) => {
let config_str = serde_yaml::to_string(&config)?;
let config_str = serde_saphyr::to_string(&config)?;
write!(self, "Config for {appservice_identifier}:\n\n```yaml\n{config_str}\n```")
},
}
+3 -1
View File
@@ -632,6 +632,7 @@ pub(super) async fn force_set_room_state_from_server(
.add_pdu_outlier(&event_id, &value);
}
info!("Resolving new room state");
let new_room_state = self
.services
.rooms
@@ -639,7 +640,7 @@ pub(super) async fn force_set_room_state_from_server(
.resolve_state(&room_id, &room_version, state)
.await?;
info!("Forcing new room state");
info!("Compressing new room state");
let HashSetCompressStateEvent {
shortstatehash: short_state_hash,
added,
@@ -653,6 +654,7 @@ pub(super) async fn force_set_room_state_from_server(
let state_lock = self.services.rooms.state.mutex.lock(&*room_id).await;
info!("Forcing new room state");
self.services
.rooms
.state
+12 -6
View File
@@ -2,7 +2,8 @@
use conduwuit::{
Err, Result, debug, debug_info, debug_warn, error, info, trace,
utils::time::parse_timepoint_ago, warn,
utils::time::{TimeDirection, parse_timepoint_ago},
warn,
};
use conduwuit_service::media::Dim;
use ruma::{Mxc, OwnedEventId, OwnedMxcUri, OwnedServerName};
@@ -235,14 +236,19 @@ pub(super) async fn delete_past_remote_media(
}
assert!(!(before && after), "--before and --after should not be specified together");
let duration = parse_timepoint_ago(&duration)?;
let direction = if after {
TimeDirection::After
} else {
TimeDirection::Before
};
let time_boundary = parse_timepoint_ago(&duration)?;
let deleted_count = self
.services
.media
.delete_all_remote_media_at_after_time(
duration,
before,
after,
.delete_all_media_within_timeframe(
time_boundary,
direction,
yes_i_want_to_delete_local_media,
)
.await?;
+17 -5
View File
@@ -27,12 +27,24 @@ pub enum MediaCommand {
/// filesystem. This will always ignore errors.
DeleteList,
/// - Deletes all remote (and optionally local) media created before or
/// after [duration] time using filesystem metadata first created at date,
/// or fallback to last modified date. This will always ignore errors by
/// default.
/// Deletes all remote (and optionally local) media created before/after
/// [duration] ago, using filesystem metadata first created at date, or
/// fallback to last modified date. This will always ignore errors by
/// default.
///
/// * Examples:
/// * Delete all remote media older than a year:
///
/// `!admin media delete-past-remote-media -b 1y`
///
/// * Delete all remote and local media from 3 days ago, up until now:
///
/// `!admin media delete-past-remote-media -a 3d
/// --yes-i-want-to-delete-local-media`
#[command(verbatim_doc_comment)]
DeletePastRemoteMedia {
/// - The relative time (e.g. 30s, 5m, 7d) within which to search
/// - The relative time (e.g. 30s, 5m, 7d) from now within which to
/// search
duration: String,
/// - Only delete media created before [duration] ago
+25 -7
View File
@@ -179,7 +179,11 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
.await
.is_ok_and(is_equal_to!(1))
{
self.services.admin.make_user_admin(&user_id).await?;
self.services
.admin
.make_user_admin(&user_id)
.boxed()
.await?;
warn!("Granting {user_id} admin privileges as the first user");
}
} else {
@@ -217,7 +221,9 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
.collect()
.await;
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
.boxed()
.await?;
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await;
leave_all_rooms(self.services, &user_id).await;
@@ -376,7 +382,9 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
.collect()
.await;
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
.boxed()
.await?;
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms)
.await;
@@ -756,7 +764,7 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
&user_id,
&room_id,
Some(&room_id),
&state_lock,
)
.await?;
@@ -776,7 +784,11 @@ pub(super) async fn make_user_admin(&self, user_id: String) -> Result {
"Parsed user_id must be a local user"
);
self.services.admin.make_user_admin(&user_id).await?;
self.services
.admin
.make_user_admin(&user_id)
.boxed()
.await?;
self.write_str(&format!("{user_id} has been granted admin privileges.",))
.await
@@ -901,7 +913,13 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
);
let redaction_event_id = {
let state_lock = self.services.rooms.state.mutex.lock(event.room_id()).await;
let state_lock = self
.services
.rooms
.state
.mutex
.lock(&event.room_id_or_hash())
.await;
self.services
.rooms
@@ -915,7 +933,7 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
})
},
event.sender(),
event.room_id(),
Some(&event.room_id_or_hash()),
&state_lock,
)
.await?
+3
View File
@@ -94,6 +94,9 @@ sha1.workspace = true
tokio.workspace = true
tracing.workspace = true
ctor.workspace = true
oxide-auth.workspace = true
conduwuit-web.workspace = true
percent-encoding.workspace = true
[lints]
workspace = true
+29 -34
View File
@@ -405,41 +405,36 @@ pub(crate) async fn register_route(
)
.await?;
if (!is_guest && body.inhibit_login)
// Generate new device id if the user didn't specify one
let no_device = body.inhibit_login
|| body
.appservice_info
.as_ref()
.is_some_and(|appservice| appservice.registration.device_management)
{
return Ok(register::v3::Response {
access_token: None,
user_id,
device_id: None,
refresh_token: None,
expires_in: None,
});
}
.is_some_and(|aps| aps.registration.device_management);
let (token, device) = if !no_device {
// Don't create a device for inhibited logins
let device_id = if is_guest { None } else { body.device_id.clone() }
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
// Generate new device id if the user didn't specify one
let device_id = if is_guest { None } else { body.device_id.clone() }
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
// Generate new token for the device
let new_token = utils::random_string(TOKEN_LENGTH);
// Generate new token for the device
let token = utils::random_string(TOKEN_LENGTH);
// Create device for this account
services
.users
.create_device(
&user_id,
&device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
.await?;
debug_info!(%user_id, %device_id, "User account was created");
// Create device for this account
services
.users
.create_device(
&user_id,
&device_id,
&new_token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
.await?;
debug_info!(%user_id, %device_id, "User account was created");
(Some(new_token), Some(device_id))
} else {
(None, None)
};
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
@@ -505,7 +500,7 @@ pub(crate) async fn register_route(
.await
.is_ok_and(is_equal_to!(1))
{
services.admin.make_user_admin(&user_id).await?;
services.admin.make_user_admin(&user_id).boxed().await?;
warn!("Granting {user_id} admin privileges as the first user");
} else if services.config.suspend_on_register {
// This is not an admin, suspend them.
@@ -583,9 +578,9 @@ pub(crate) async fn register_route(
}
Ok(register::v3::Response {
access_token: Some(token),
access_token: token,
user_id,
device_id: Some(device_id),
device_id: device,
refresh_token: None,
expires_in: None,
})
@@ -929,7 +924,7 @@ pub async fn full_user_deactivate(
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
user_id,
room_id,
Some(room_id),
&state_lock,
)
.await
+2 -2
View File
@@ -69,7 +69,7 @@ pub(crate) async fn get_context_route(
let (base_id, base_pdu, visible) = try_join3(base_id, base_pdu, visible).await?;
if base_pdu.room_id != *room_id || base_pdu.event_id != *event_id {
if base_pdu.room_id_or_hash() != *room_id || base_pdu.event_id != *event_id {
return Err!(Request(NotFound("Base event not found.")));
}
@@ -130,7 +130,7 @@ pub(crate) async fn get_context_route(
let state_at = events_after
.last()
.map(ref_at!(1))
.map_or(body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
.map_or_else(|| body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
let state_ids = services
.rooms
+3 -1
View File
@@ -75,7 +75,9 @@ pub(crate) async fn upload_keys_route(
}
if deser_device_keys.device_id != sender_device {
return Err!(Request(Unknown(
"Device ID in keys uploaded does not match your own device ID"
"Device ID in keys uploaded ({}) does not match your own device ID ({})",
deser_device_keys.device_id,
sender_device,
)));
}
+6 -2
View File
@@ -64,10 +64,14 @@ pub(crate) async fn create_content_route(
media_id: &utils::random_string(MXC_LENGTH),
};
services
if let Err(e) = services
.media
.create(mxc, Some(user), Some(&content_disposition), content_type, &body.file)
.await?;
.await
{
err!("Failed to save uploaded media: {e}");
return Err!(Request(Unknown("Failed to save uploaded media")));
}
let blurhash = body.generate_blurhash.then(|| {
services
+1 -1
View File
@@ -49,7 +49,7 @@ pub(crate) async fn ban_user_route(
..current_member_content
}),
sender_user,
&body.room_id,
Some(&body.room_id),
&state_lock,
)
.await?;
+42 -29
View File
@@ -4,11 +4,14 @@
Err, Result, debug_error, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
};
use futures::{FutureExt, join};
use futures::FutureExt;
use ruma::{
OwnedServerName, RoomId, UserId,
api::{client::membership::invite_user, federation::membership::create_invite},
events::room::member::{MembershipState, RoomMemberEventContent},
events::{
invite_permission_config::FilterLevel,
room::member::{MembershipState, RoomMemberEventContent},
},
};
use service::Services;
@@ -47,22 +50,21 @@ pub(crate) async fn invite_user_route(
.await?;
match &body.recipient {
| invite_user::v3::InvitationRecipient::UserId { user_id } => {
let sender_ignored_recipient = services.users.user_is_ignored(sender_user, user_id);
let recipient_ignored_by_sender =
services.users.user_is_ignored(user_id, sender_user);
| invite_user::v3::InvitationRecipient::UserId { user_id: recipient_user } => {
let sender_filter_level = services
.users
.invite_filter_level(recipient_user, sender_user)
.await;
let (sender_ignored_recipient, recipient_ignored_by_sender) =
join!(sender_ignored_recipient, recipient_ignored_by_sender);
if sender_ignored_recipient {
if !matches!(sender_filter_level, FilterLevel::Allow) {
// drop invites if the sender has the recipient filtered
return Ok(invite_user::v3::Response {});
}
if let Ok(target_user_membership) = services
.rooms
.state_accessor
.get_member(&body.room_id, user_id)
.get_member(&body.room_id, recipient_user)
.await
{
if target_user_membership.membership == MembershipState::Ban {
@@ -70,16 +72,27 @@ pub(crate) async fn invite_user_route(
}
}
if recipient_ignored_by_sender {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
return Ok(invite_user::v3::Response {});
// check for blocked invites if the recipient is a local user.
if services.globals.user_is_local(recipient_user) {
let recipient_filter_level = services
.users
.invite_filter_level(sender_user, recipient_user)
.await;
// ignored invites aren't handled here
// since the recipient's membership should still be changed to `invite`.
// they're filtered out in the individual /sync handlers.
if matches!(recipient_filter_level, FilterLevel::Block) {
return Err!(Request(InviteBlocked(
"{recipient_user} has blocked invites from you."
)));
}
}
invite_helper(
&services,
sender_user,
user_id,
recipient_user,
&body.room_id,
body.reason.clone(),
false,
@@ -98,7 +111,7 @@ pub(crate) async fn invite_user_route(
pub(crate) async fn invite_helper(
services: &Services,
sender_user: &UserId,
user_id: &UserId,
recipient_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
is_direct: bool,
@@ -111,12 +124,12 @@ pub(crate) async fn invite_helper(
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
if !services.globals.user_is_local(user_id) {
if !services.globals.user_is_local(recipient_user) {
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
avatar_url: services.users.avatar_url(user_id).await.ok(),
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
@@ -126,14 +139,14 @@ pub(crate) async fn invite_helper(
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(user_id.to_string(), &content),
PduBuilder::state(recipient_user.to_string(), &content),
sender_user,
room_id,
Some(room_id),
&state_lock,
)
.await?;
let invite_room_state = services.rooms.state.summary_stripped(&pdu).await;
let invite_room_state = services.rooms.state.summary_stripped(&pdu, room_id).await;
drop(state_lock);
@@ -144,7 +157,7 @@ pub(crate) async fn invite_helper(
let response = services
.sending
.send_federation_request(user_id.server_name(), create_invite::v2::Request {
.send_federation_request(recipient_user.server_name(), create_invite::v2::Request {
room_id: room_id.to_owned(),
event_id: (*pdu.event_id).to_owned(),
room_version: room_version_id.clone(),
@@ -173,7 +186,7 @@ pub(crate) async fn invite_helper(
return Err!(Request(BadJson(warn!(
%pdu.event_id, %event_id,
"Server {} sent event with wrong event ID",
user_id.server_name()
recipient_user.server_name()
))));
}
@@ -213,9 +226,9 @@ pub(crate) async fn invite_helper(
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
displayname: services.users.displayname(user_id).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
blurhash: services.users.blurhash(user_id).await.ok(),
displayname: services.users.displayname(recipient_user).await.ok(),
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
blurhash: services.users.blurhash(recipient_user).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
@@ -225,9 +238,9 @@ pub(crate) async fn invite_helper(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &content),
PduBuilder::state(recipient_user.to_string(), &content),
sender_user,
room_id,
Some(room_id),
&state_lock,
)
.await?;
+23 -10
View File
@@ -18,7 +18,7 @@
},
warn,
};
use futures::{FutureExt, StreamExt};
use futures::{FutureExt, StreamExt, TryFutureExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
RoomVersionId, UserId,
@@ -313,11 +313,14 @@ pub async fn join_room_by_id_helper(
}
}
let local_join = server_in_room
|| servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
if !server_in_room && servers.is_empty() {
return Err!(Request(NotFound(
"No servers were provided to assist in joining the room remotely, and we are not \
already participating in the room."
)));
}
if local_join {
if server_in_room {
join_room_by_id_helper_local(
services,
sender_user,
@@ -556,6 +559,10 @@ async fn join_room_by_id_helper_remote(
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
.inspect_err(|e| {
debug_warn!("Could not validate send_join response room_state event: {e:?}");
})
.inspect(|_| debug!("Completed validating send_join response room_state event"))
})
.ready_filter_map(Result::ok)
.fold(HashMap::new(), |mut state, (event_id, value)| async move {
@@ -566,7 +573,6 @@ async fn join_room_by_id_helper_remote(
return state;
},
};
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
if let Some(state_key) = &pdu.state_key {
let shortstatekey = services
@@ -577,7 +583,6 @@ async fn join_room_by_id_helper_remote(
state.insert(shortstatekey, pdu.event_id.clone());
}
state
})
.await;
@@ -598,6 +603,7 @@ async fn join_room_by_id_helper_remote(
})
.ready_filter_map(Result::ok)
.ready_for_each(|(event_id, value)| {
trace!(%event_id, "Adding PDU as an outlier from send_join auth_chain");
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
})
.await;
@@ -618,6 +624,9 @@ async fn join_room_by_id_helper_remote(
&parsed_join_pdu,
None, // TODO: third party invite
|k, s| state_fetch(k.clone(), s.into()),
&state_fetch(StateEventType::RoomCreate, "".into())
.await
.expect("create event is missing from send_join auth"),
)
.await
.map_err(|e| err!(Request(Forbidden(warn!("Auth check failed: {e:?}")))))?;
@@ -652,7 +661,7 @@ async fn join_room_by_id_helper_remote(
.force_state(room_id, statehash_before_join, added, removed, &state_lock)
.await?;
info!("Updating joined counts for new room");
debug!("Updating joined counts for new room");
services
.rooms
.state_cache
@@ -665,7 +674,7 @@ async fn join_room_by_id_helper_remote(
let statehash_after_join = services
.rooms
.state
.append_to_state(&parsed_join_pdu)
.append_to_state(&parsed_join_pdu, room_id)
.await?;
info!("Appending new room join event");
@@ -677,6 +686,7 @@ async fn join_room_by_id_helper_remote(
join_event,
once(parsed_join_pdu.event_id.borrow()),
&state_lock,
room_id,
)
.await?;
@@ -732,6 +742,7 @@ async fn join_room_by_id_helper_local(
.iter()
.stream()
.any(|restriction_room_id| {
trace!("Checking if {sender_user} is joined to {restriction_room_id}");
services
.rooms
.state_cache
@@ -744,6 +755,7 @@ async fn join_room_by_id_helper_local(
.state_cache
.local_users_in_room(room_id)
.filter(|user| {
trace!("Checking if {user} can invite {sender_user} to {room_id}");
services.rooms.state_accessor.user_can_invite(
room_id,
user,
@@ -756,6 +768,7 @@ async fn join_room_by_id_helper_local(
.await
.map(ToOwned::to_owned)
} else {
trace!("No restriction rooms are joined by {sender_user}");
None
}
};
@@ -776,7 +789,7 @@ async fn join_room_by_id_helper_local(
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
sender_user,
room_id,
Some(room_id),
&state_lock,
)
.await
+1 -1
View File
@@ -54,7 +54,7 @@ pub(crate) async fn kick_user_route(
..event
}),
sender_user,
&body.room_id,
Some(&body.room_id),
&state_lock,
)
.await?;
+4 -2
View File
@@ -373,7 +373,7 @@ async fn knock_room_helper_local(
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
sender_user,
room_id,
Some(room_id),
&state_lock,
)
.await
@@ -502,6 +502,7 @@ async fn knock_room_helper_local(
knock_event,
once(parsed_knock_pdu.event_id.borrow()),
&state_lock,
room_id,
)
.await?;
@@ -672,7 +673,7 @@ async fn knock_room_helper_remote(
let statehash_after_knock = services
.rooms
.state
.append_to_state(&parsed_knock_pdu)
.append_to_state(&parsed_knock_pdu, room_id)
.await?;
info!("Updating membership locally to knock state with provided stripped state events");
@@ -701,6 +702,7 @@ async fn knock_room_helper_remote(
knock_event,
once(parsed_knock_pdu.event_id.borrow()),
&state_lock,
room_id,
)
.await?;
+1 -1
View File
@@ -206,7 +206,7 @@ pub async fn leave_room(
..event
}),
user_id,
room_id,
Some(room_id),
&state_lock,
)
.await?;
+5 -6
View File
@@ -69,11 +69,11 @@ pub(crate) async fn banned_room_check(
}
if let Some(room_id) = room_id {
if services.rooms.metadata.is_banned(room_id).await
|| services
.moderation
.is_remote_server_forbidden(room_id.server_name().expect("legacy room mxid"))
{
let room_banned = services.rooms.metadata.is_banned(room_id).await;
let server_banned = room_id.server_name().is_some_and(|server_name| {
services.moderation.is_remote_server_forbidden(server_name)
});
if room_banned || server_banned {
warn!(
"User {user_id} who is not an admin attempted to send an invite for or \
attempted to join a banned room or banned room server name: {room_id}"
@@ -106,7 +106,6 @@ pub(crate) async fn banned_room_check(
.boxed()
.await?;
}
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
} else if let Some(server_name) = server_name {
+1 -1
View File
@@ -47,7 +47,7 @@ pub(crate) async fn unban_user_route(
..current_member_content
}),
sender_user,
&body.room_id,
Some(&body.room_id),
&state_lock,
)
.await?;
+47 -14
View File
@@ -30,6 +30,7 @@
events::{
AnyStateEvent, StateEventType,
TimelineEventType::{self, *},
invite_permission_config::FilterLevel,
},
serde::Raw,
};
@@ -267,7 +268,7 @@ pub(crate) async fn ignored_filter(
pub(crate) async fn is_ignored_pdu<Pdu>(
services: &Services,
event: &Pdu,
user_id: &UserId,
recipient_user: &UserId,
) -> bool
where
Pdu: Event + Send + Sync,
@@ -278,20 +279,29 @@ pub(crate) async fn is_ignored_pdu<Pdu>(
return true;
}
let ignored_type = IGNORED_MESSAGE_TYPES.binary_search(event.kind()).is_ok();
let ignored_server = services
let sender_user = event.sender();
let type_ignored = IGNORED_MESSAGE_TYPES.binary_search(event.kind()).is_ok();
let server_ignored = services
.moderation
.is_remote_server_ignored(event.sender().server_name());
.is_remote_server_ignored(sender_user.server_name());
let user_ignored = services
.users
.user_is_ignored(sender_user, recipient_user)
.await;
if ignored_type
&& (ignored_server
|| (!services.config.send_messages_from_ignored_users_to_client
&& services
.users
.user_is_ignored(event.sender(), user_id)
.await))
{
if !type_ignored {
// We cannot safely ignore this type
return false;
}
if server_ignored {
// the sender's server is ignored, so ignore this event
return true;
}
if user_ignored && !services.config.send_messages_from_ignored_users_to_client {
// the recipient of this PDU has the sender ignored, and we're not
// configured to send ignored messages to clients
return true;
}
@@ -309,7 +319,7 @@ pub(crate) async fn visibility_filter(
services
.rooms
.state_accessor
.user_can_see_event(user_id, pdu.room_id(), pdu.event_id())
.user_can_see_event(user_id, &pdu.room_id_or_hash(), pdu.event_id())
.await
.then_some(item)
}
@@ -320,6 +330,29 @@ pub(crate) fn event_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Opti
filter.matches(pdu).then_some(item)
}
#[inline]
pub(crate) async fn is_ignored_invite(
services: &Services,
recipient_user: &UserId,
room_id: &RoomId,
) -> bool {
let Ok(sender_user) = services
.rooms
.state_cache
.invite_sender(recipient_user, room_id)
.await
else {
// the invite may have been sent before the invite_sender table existed.
// assume it's not ignored
return false;
};
services
.users
.invite_filter_level(&sender_user, recipient_user)
.await == FilterLevel::Ignore
}
#[cfg_attr(debug_assertions, ctor::ctor)]
fn _is_sorted() {
debug_assert!(
+2
View File
@@ -14,6 +14,7 @@
pub(super) mod media_legacy;
pub(super) mod membership;
pub(super) mod message;
pub(super) mod oidc;
pub(super) mod openid;
pub(super) mod presence;
pub(super) mod profile;
@@ -58,6 +59,7 @@
pub(super) use membership::*;
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room};
pub(super) use message::*;
pub(super) use oidc::*;
pub(super) use openid::*;
pub(super) use presence::*;
pub(super) use profile::*;
+137
View File
@@ -0,0 +1,137 @@
use axum::extract::{Query, State};
use conduwuit::{Result, err, utils::ReadyExt};
use conduwuit_web::oidc::{
AuthorizationQuery, OidcRequest, OidcResponse, oidc_consent_form, oidc_login_form,
};
use oxide_auth::{
endpoint::{OwnerConsent, Solicitation},
frontends::simple::endpoint::FnSolicitor,
};
use percent_encoding::percent_decode_str;
use ruma::UserId;
use service::oidc::{SCOPE_PREFIX_API, SCOPE_PREFIX_DEVICE};
/// # `GET /_matrix/client/unstable/org.matrix.msc2964/authorize`
///
/// Authenticate a user and device, and solicit the user's consent.
///
/// Redirects to the login page if no token or token not belonging to any user.
/// [super::login::oidc_login] takes it up at the same point, so it's either
/// the client has a token, or the user does user password. Then the user gets
/// access to stage two, [authorize_consent].
pub(crate) async fn authorize(
State(services): State<crate::State>,
Query(query): Query<AuthorizationQuery>,
oauth: OidcRequest,
) -> Result<OidcResponse> {
tracing::trace!("processing OAuth request: {query:#?}");
// Enforce MSC2964's restrictions on OAuth2 flow.
let Ok(scope) = percent_decode_str(&query.scope).decode_utf8() else {
return Err(err!(Request(Unknown("the scope could not be percent-decoded"))));
};
if !scope.contains(&format!("{SCOPE_PREFIX_API}*")) {
return Err(err!(Request(Unknown("the scope does not include the client API"))));
}
if !scope.contains(SCOPE_PREFIX_DEVICE) {
return Err(err!(Request(Unknown("the scope does not include a device ID"))));
}
if query.code_challenge_method != "S256" {
return Err(err!(Request(Unknown("unsupported code challenge method"))));
}
// Redirect to the login page if no token or token not known.
let hostname = services.config.server_name.host();
let Some(token) = oauth.authorization_header() else {
return Ok(oidc_login_form(hostname, &query));
};
tracing::debug!("submitting OIDC authorisation for token : {token:#?}");
// Get the user id from the token and add it to the query.
let (owner_id, _) = services.oidc.user_and_device_from_token(token).await?;
let mut query_with_user_id = query.clone();
query_with_user_id.username = Some(owner_id.localpart().to_owned());
services
.oidc
.endpoint()
.with_solicitor(oidc_consent_form(hostname, &query_with_user_id))
.authorization_flow()
.execute(oauth)
.map_err(|err| err!("authorization failed: {err:?}"))
}
/// Whether a user allows their device to access this homeserver's resources.
#[derive(serde::Deserialize)]
pub(crate) struct Allowance {
allow: Option<String>,
}
/// # `POST /_matrix/client/unstable/org.matrix.msc2964/authorize?allow=[Option<String>]`
///
/// Authorize the device based on the owner's consent. If the owner allows
/// it to access their data, the client may request a token at the
/// [super::token::token] endpoint.
///
/// On the owner's consent, if their specific device is unregistered it will be
/// registered in their device list (not to be confused with the OIDC client
/// registration).
pub(crate) async fn authorize_consent(
Query(Allowance { allow }): Query<Allowance>,
State(services): State<crate::State>,
Query(query): Query<AuthorizationQuery>,
oauth: OidcRequest,
) -> Result<OidcResponse> {
tracing::debug!("processing owner's consent: {:?}", allow);
tracing::trace!("owner's consent request: {:#?}", query);
let Some(owner_id) = allow.clone() else {
return Err(err!(Request(Unknown("the owner did not consent to the client's access"))));
};
let server_name = services.globals.server_name();
let owner_id = UserId::parse_with_server_name(owner_id.clone(), server_name)
.map_err(|err| err!(Request(InvalidUsername("invalid username {owner_id:?}: {err}"))))?;
let Some(matrix_client) = services
.oidc
.client_from_client_id(&query.client_id)
.await?
else {
return Err(err!(Request(Unknown(
"no client has registered client_id {:?}",
query.client_id
))));
};
let scope = query.scope.parse().map_err(|err| {
err!(Request(Unknown("could not parse scope {:?}: {}", query.scope, err)))
})?;
let device_id = services.oidc.device_id_from_scope(&scope)?;
// Check that the device is registered in the owner devices list.
// Note that this is _not_ the OIDC client registration.
let device_is_registered_with_owner = services
.users
.all_device_ids(&owner_id)
.ready_any(|v| v == device_id)
.await;
if !device_is_registered_with_owner {
// TODO get the client's IP from the request.
let client_ip = None;
services
.oidc
.register_device(
&query.client_id,
(&owner_id, &device_id),
matrix_client.name.as_deref(),
client_ip,
)
.await?;
}
services
.oidc
.endpoint()
.with_solicitor(FnSolicitor(move |_: &mut _, _: Solicitation<'_>| match allow.clone() {
| None => OwnerConsent::Denied,
| Some(user_id) => OwnerConsent::Authorized(user_id),
}))
.authorization_flow()
.execute(oauth)
.map_err(|err| err!(Request(Unknown("consent request failed: {err:?}"))))
}
+93
View File
@@ -0,0 +1,93 @@
/// Manual implementation of [MSC2965]'s OIDC server discovery.
///
/// [MSC2965]: https://github.com/matrix-org/matrix-spec-proposals/pull/2965
use axum::extract::State;
use conduwuit::Result;
use ruma::{
api::client::{
discovery::get_authorization_server_metadata::msc2965::{
self, AccountManagementAction, AuthorizationServerMetadata, CodeChallengeMethod,
GrantType, Prompt, ResponseMode, ResponseType,
},
error::{
Error as ClientError, ErrorBody as ClientErrorBody, ErrorKind as ClientErrorKind,
},
},
serde::Raw,
};
use crate::{Ruma, RumaResponse, conduwuit::Error};
/// # `GET /_matrix/client/unstable/org.matrix.msc2965/auth_metadata`
///
/// If `globals.auth.enable_oidc_login` is set, advertise this homeserver's
/// OAuth2 endpoints. Otherwise, MSC2965 requires that the homeserver responds
/// with 404/M_UNRECOGNIZED.
pub(crate) async fn get_auth_metadata(
State(services): State<crate::State>,
_body: Ruma<msc2965::Request>,
) -> Result<RumaResponse<msc2965::Response>> {
let unrecognized_error = Err(Error::Ruma(ClientError::new(
http::StatusCode::NOT_FOUND,
ClientErrorBody::Standard {
kind: ClientErrorKind::Unrecognized,
message: "This homeserver has disabled OIDC authentication.".to_owned(),
},
)));
let Some(ref auth) = services.server.config.auth else {
return unrecognized_error;
};
if !auth.enable_oidc_login {
return unrecognized_error;
}
// Advertise this homeserver's access URL as the issuer URL.
// Unwrap all Url::parse() calls because the issuer URL is validated at startup.
let issuer = services.server.config.well_known.client.as_ref().unwrap();
let account_management_uri = auth.enable_oidc_account_management.then_some(
issuer
.join("/_matrix/client/unstable/org.matrix.msc2964/account")
.unwrap(),
);
// Build up metadata with primitives from ruma::api::client::msc2965.
let metadata = AuthorizationServerMetadata {
issuer: issuer.clone(),
authorization_endpoint: issuer
.join("/_matrix/client/unstable/org.matrix.msc2964/authorize")
.unwrap(),
device_authorization_endpoint: Some(
issuer
.join("/_matrix/client/unstable/org.matrix.msc2964/device")
.unwrap(),
),
token_endpoint: issuer
.join("/_matrix/client/unstable/org.matrix.msc2964/token")
.unwrap(),
registration_endpoint: Some(
issuer
.join("/_matrix/client/unstable/org.matrix.msc2964/device/register")
.unwrap(),
),
revocation_endpoint: issuer
.join("/_matrix/client/unstable/org.matrix.msc2964/revoke")
.unwrap(),
response_types_supported: [ResponseType::Code].into(),
grant_types_supported: [GrantType::AuthorizationCode, GrantType::RefreshToken].into(),
response_modes_supported: [ResponseMode::Fragment, ResponseMode::Query].into(),
code_challenge_methods_supported: [CodeChallengeMethod::S256].into(),
account_management_uri,
account_management_actions_supported: [
AccountManagementAction::Profile,
AccountManagementAction::SessionView,
AccountManagementAction::SessionEnd,
]
.into(),
prompt_values_supported: match services.server.config.allow_registration {
| true => vec![Prompt::Create],
| false => vec![],
},
};
let metadata = Raw::new(&metadata).expect("authorization server metadata should serialize");
Ok(RumaResponse(msc2965::Response::new(metadata)))
}
+48
View File
@@ -0,0 +1,48 @@
use axum::extract::State;
use conduwuit::{Result, err, utils::hash::verify_password};
use conduwuit_web::oidc::{LoginError, LoginQuery, OidcRequest, OidcResponse, oidc_consent_form};
use ruma::user_id::UserId;
//#[axum::debug_handler]
/// # `POST /_matrix/client/unstable/org.matrix.msc2964/login`
///
/// Display a login UI to the user and return an authorization code on success.
/// We presume that the OAuth2 query parameters are provided in the form.
/// With the code, the client may then access stage two,
/// [super::authorize::authorize_consent].
pub(crate) async fn oidc_login(
State(services): State<crate::State>,
request: OidcRequest,
) -> Result<OidcResponse> {
let query: LoginQuery = request.clone().try_into().map_err(|LoginError(err)| {
err!(Request(InvalidParam("Cannot process login form. {err}")))
})?;
tracing::trace!("processing login query {:#?}", query.clone());
// Only accept local usernames. Mostly to simplify things at first.
let user_id =
UserId::parse_with_server_name(query.username.clone(), &services.config.server_name)
.map_err(|e| err!(Request(InvalidUsername("Username is invalid: {e}"))))?;
if !services.users.exists(&user_id).await {
return Err(err!(Request(Unknown("unknown username"))));
}
let valid_hash = services.users.password_hash(&user_id).await?;
if valid_hash.is_empty() {
return Err(err!(Request(UserDeactivated("the user's hash was not found"))));
}
if verify_password(&query.password, &valid_hash).is_err() {
return Err(err!(Request(InvalidParam("password does not match"))));
}
// TODO check if user disabled, etc. See /src/api/client/session.rs
let hostname = services.config.server_name.host();
tracing::info!("logging in {user_id:?}");
services
.oidc
.endpoint()
.with_solicitor(oidc_consent_form(hostname, &query.into()))
.authorization_flow()
.execute(request)
.map_err(|err| err!(Request(Unknown("authorisation failed: {err:?}"))))
}
+27
View File
@@ -0,0 +1,27 @@
//! OIDC
//!
//! Stands for OpenID Connect, and is an authentication scheme relying on
//! OAuth2. The [MSC2964] Matrix Spec Proposal describes an authentication
//! process based on the OIDC flow, with restrictions. See the [sample flow] for
//! details on what's expected.
//!
//! This module implements the needed endpoints. It relies on the [oxide-auth]
//! crate, and the [`service::oidc`] and [`web::oidc`] modules.
//!
//! [MSC2964]: https://github.com/matrix-org/matrix-spec-proposals/pull/2964
//! [oxide-auth]: https://docs.rs/oxide-auth
//! [sample flow]: https://github.com/sandhose/matrix-spec-proposals/blob/msc/sandhose/oauth2-profile/proposals/2964-oauth2-profile.md#sample-flow
mod authorize;
mod discovery;
mod login;
mod register;
mod token;
pub(crate) use self::{
authorize::{authorize, authorize_consent},
discovery::get_auth_metadata,
login::oidc_login,
register::register_client,
token::token,
};
+139
View File
@@ -0,0 +1,139 @@
use axum::{Json, extract::State};
use conduwuit::{Result, err};
use conduwuit_service::oidc::normalize_redirect;
use oxide_auth::primitives::prelude::Client;
use reqwest::Url;
use ruma::{ClientSecret, DeviceId, identifiers_validation};
/// The required parameters to register a new client for OAuth2 application.
/// See the required metadata in OAuth2 authorization grant flow in [MSC2966].
///
/// [MSC2966]: https://github.com/matrix-org/matrix-spec-proposals/pull/2966
#[derive(serde::Deserialize, Clone, Debug)]
pub(crate) struct ClientQuery {
/// Human-readable name.
client_name: String,
/// A public page that tells more about the client. All other links must be
/// within.
client_uri: Url,
/// Redirect URIs declared by the client. At least one.
redirect_uris: Vec<Url>,
/// Must include the literal "code".
response_types: Vec<String>,
/// Must include the literals "authorization_code" and "refresh_token".
grant_types: Vec<String>,
/// How the client intends to authenticate its requests. Can be "none",
/// meaning that the client will negotiate its token with the
/// "authorization code" flow.
token_endpoint_auth_method: String,
/// Link to the logo.
logo_uri: Option<Url>,
/// Link to the client's policy.
policy_uri: Option<Url>,
/// Link to the terms of service.
tos_uri: Option<Url>,
/// Can be "native", implying localhost or reserved redirect pages.
/// Defaults to "web" if not present.
application_type: Option<String>,
}
/// A successful response that the client was registered.
#[derive(serde::Serialize, Debug)]
pub(crate) struct ClientResponse {
client_id: String,
/// If the client is private, the secret it authenticates itself with.
client_secret: Option<String>,
/// If there's a `client_secret`, its expiration date in seconds since
/// 1970-01-01T00:00. Some(0) means no expiration date.
client_secret_expires_at: Option<u32>,
client_name: String,
/// Points to the "about" page of the client.
client_uri: Url,
logo_uri: Option<Url>,
tos_uri: Option<Url>,
policy_uri: Option<Url>,
/// Registered redirect uris, which will be matched against when
/// authenticating. If a localhost address, must contain instances of
/// oxide-auth's `RegisteredUrl::IgnorePortOnLocalhost` to let
/// authorization flow through any port over localhost.
redirect_uris: Vec<Url>,
token_endpoint_auth_method: String,
response_types: Vec<String>,
grant_types: Vec<String>,
application_type: Option<String>,
}
/// # `GET /_matrix/client/unstable/org.matrix.msc2964/device/register`
///
/// Register a client, as specified in [MSC2966]. This client, "device" in OIDC
/// parlance, will have the right to submit [super::authorize::authorize]
/// requests.
///
/// [MSC2966]: https://github.com/matrix-org/matrix-spec-proposals/pull/2966
pub(crate) async fn register_client(
State(services): State<crate::State>,
Json(client): Json<ClientQuery>,
) -> Result<Json<ClientResponse>> {
tracing::trace!("processing OIDC device register request for client: {client:#?}");
if client.redirect_uris.is_empty() {
return Err(err!(Request(Unknown(
"the client's registration request should contain at least a redirect_uri"
))));
}
let mut redirect_uris = client.redirect_uris.clone();
let redirect_uri = redirect_uris.pop().expect("at least one redirect_uri");
let redirect_uri = normalize_redirect(redirect_uri);
let remaining_uris = redirect_uris.into_iter().map(normalize_redirect).collect();
let device_id = DeviceId::new();
// Only provide a default scope, we'll test the client's proposed scope for
// consent anyway.
let scope = "default".parse().unwrap();
// TODO check if the users service needs an update.
//services.users.update_device_metadata();
// If the client cannot authenticate itself at the token endpoint, then
// it's a public client. This is usually the case in Matrix.
let is_private = client.token_endpoint_auth_method != "none";
let client_secret = match is_private {
| true => {
let secret = ClientSecret::new();
identifiers_validation::client_secret::validate(secret.as_str())?;
Some(secret.to_string())
},
| false => None,
};
let registration = match is_private {
| true => &Client::confidential(
device_id.as_ref(),
redirect_uri,
scope,
client_secret.as_ref().unwrap().as_bytes(),
)
.with_additional_redirect_uris(remaining_uris),
| _ => &Client::public(device_id.as_ref(), redirect_uri, scope)
.with_additional_redirect_uris(remaining_uris),
};
tracing::trace!("registering OIDC device : {registration:#?}");
services
.oidc
.register_client(Some(client.client_name.clone()), registration);
let client_response = ClientResponse {
client_id: device_id.to_string(),
client_secret,
client_secret_expires_at: if is_private { Some(0) } else { None },
client_name: client.client_name.clone(),
client_uri: client.client_uri.clone(),
redirect_uris: client.redirect_uris.clone(),
logo_uri: client.logo_uri.clone(),
policy_uri: client.policy_uri.clone(),
tos_uri: client.tos_uri.clone(),
token_endpoint_auth_method: client.token_endpoint_auth_method.clone(),
response_types: client.response_types.clone(),
grant_types: client.grant_types.clone(),
application_type: client.application_type,
};
tracing::debug!("OIDC device registered : {client_response:#?}");
Ok(Json(client_response))
}
+35
View File
@@ -0,0 +1,35 @@
use axum::extract::State;
use conduwuit::{Result, err};
use conduwuit_web::oidc::{OidcRequest, OidcResponse};
use oxide_auth::endpoint::QueryParameter;
/// # `POST /_matrix/client/unstable/org.matrix.msc2964/token`
///
/// Depending on `grant_type`, either deliver a new token to a device, and store
/// it in the server's ring, or refresh the token.
pub(crate) async fn token(
State(services): State<crate::State>,
oauth: OidcRequest,
) -> Result<OidcResponse> {
tracing::trace!("processing OpenID token request {:#?}", oauth);
let Some(body) = oauth.body() else {
return Err(err!(Request(Unknown("OAuth request had an empty body"))));
};
let grant_type = body
.unique_value("grant_type")
.map(|value| value.to_string());
let endpoint = services.oidc.endpoint();
tracing::debug!("submitting OpenID token request for grant type {grant_type:?}");
match grant_type.as_deref() {
| Some("authorization_code") => endpoint
.access_token_flow()
.execute(oauth)
.map_err(|err| err!(Request(Unknown("token grant failed: {err:?}")))),
| Some("refresh_token") => endpoint
.refresh_flow()
.execute(oauth)
.map_err(|err| err!(Request(Unknown("token refresh failed: {err:?}")))),
| other => Err(err!(Request(Unknown("unsupported grant type: {other:?}")))),
}
}
+5 -21
View File
@@ -1,5 +1,3 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduwuit::{
Err, Result,
@@ -226,7 +224,8 @@ pub(crate) async fn get_avatar_url_route(
/// # `GET /_matrix/client/v3/profile/{userId}`
///
/// Returns the displayname, avatar_url, blurhash, and tz of the user.
/// Returns the displayname, avatar_url, blurhash, and custom profile fields of
/// the user.
///
/// - If user is on another server and we do not have a local copy already,
/// fetch profile over federation.
@@ -260,9 +259,6 @@ pub(crate) async fn get_profile_route(
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone());
services
.users
.set_timezone(&body.user_id, response.tz.clone());
for (profile_key, profile_key_value) in &response.custom_profile_fields {
services.users.set_profile_key(
@@ -276,7 +272,6 @@ pub(crate) async fn get_profile_route(
displayname: response.displayname,
avatar_url: response.avatar_url,
blurhash: response.blurhash,
tz: response.tz,
custom_profile_fields: response.custom_profile_fields,
});
}
@@ -288,21 +283,11 @@ pub(crate) async fn get_profile_route(
return Err!(Request(NotFound("Profile was not found.")));
}
let mut custom_profile_fields: BTreeMap<String, serde_json::Value> = services
.users
.all_profile_keys(&body.user_id)
.collect()
.await;
// services.users.timezone will collect the MSC4175 timezone key if it exists
custom_profile_fields.remove("us.cloke.msc4175.tz");
custom_profile_fields.remove("m.tz");
let (avatar_url, blurhash, displayname, tz) = join4(
let (avatar_url, blurhash, displayname, custom_profile_fields) = join4(
services.users.avatar_url(&body.user_id).ok(),
services.users.blurhash(&body.user_id).ok(),
services.users.displayname(&body.user_id).ok(),
services.users.timezone(&body.user_id).ok(),
services.users.all_profile_keys(&body.user_id).collect(),
)
.await;
@@ -310,7 +295,6 @@ pub(crate) async fn get_profile_route(
avatar_url,
blurhash,
displayname,
tz,
custom_profile_fields,
})
}
@@ -423,7 +407,7 @@ pub async fn update_all_rooms(
if let Err(e) = services
.rooms
.timeline
.build_and_append_pdu(pdu_builder, user_id, room_id, &state_lock)
.build_and_append_pdu(pdu_builder, user_id, Some(room_id), &state_lock)
.await
{
warn!(%user_id, %room_id, "Failed to update/send new profile join membership update in room: {e}");
+1 -1
View File
@@ -36,7 +36,7 @@ pub(crate) async fn redact_event_route(
})
},
sender_user,
&body.room_id,
Some(&body.room_id),
&state_lock,
)
.await?;
+1 -1
View File
@@ -222,7 +222,7 @@ async fn visibility_filter<Pdu: Event + Send + Sync>(
services
.rooms
.state_accessor
.user_can_see_event(sender_user, pdu.room_id(), pdu.event_id())
.user_can_see_event(sender_user, &pdu.room_id_or_hash(), pdu.event_id())
.await
.then_some(item)
}
+12 -22
View File
@@ -1,8 +1,8 @@
use std::{fmt::Write as _, ops::Mul, time::Duration};
use std::{fmt::Write as _, time::Duration};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
use conduwuit::{Err, Event, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
use conduwuit_service::Services;
use rand::Rng;
use ruma::{
@@ -12,7 +12,6 @@
room::{report_content, report_room},
},
events::{Mentions, room::message::RoomMessageEventContent},
int,
};
use tokio::time::sleep;
@@ -25,7 +24,6 @@ struct Report {
user_id: Option<OwnedUserId>,
report_type: String,
reason: Option<String>,
score: Option<ruma::Int>,
}
/// # `POST /_matrix/client/v3/rooms/{roomId}/report`
@@ -50,6 +48,15 @@ pub(crate) async fn report_room_route(
delay_response().await;
// We log this early in case the room ID does actually exist, in which case
// admins who scan their logs can see the report and choose to investigate at
// their discretion.
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
);
if !services
.rooms
.state_cache
@@ -60,11 +67,6 @@ pub(crate) async fn report_room_route(
"Room does not exist to us, no local users have joined at all"
)));
}
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
);
let report = Report {
sender: sender_user.to_owned(),
@@ -73,7 +75,6 @@ pub(crate) async fn report_room_route(
user_id: None,
report_type: "room".to_owned(),
reason: body.reason.clone(),
score: None,
};
services.admin.send_message(build_report(report)).await.ok();
@@ -109,7 +110,6 @@ pub(crate) async fn report_event_route(
&body.room_id,
sender_user,
body.reason.as_ref(),
body.score,
&pdu,
)
.await?;
@@ -127,7 +127,6 @@ pub(crate) async fn report_event_route(
user_id: None,
report_type: "event".to_owned(),
reason: body.reason.clone(),
score: body.score,
};
services.admin.send_message(build_report(report)).await.ok();
@@ -166,7 +165,6 @@ pub(crate) async fn report_user_route(
user_id: Some(body.user_id.clone()),
report_type: "user".to_owned(),
reason: body.reason.clone(),
score: None,
};
info!(
@@ -192,7 +190,6 @@ async fn is_event_report_valid(
room_id: &RoomId,
sender_user: &UserId,
reason: Option<&String>,
score: Option<ruma::Int>,
pdu: &PduEvent,
) -> Result<()> {
debug_info!(
@@ -200,14 +197,10 @@ async fn is_event_report_valid(
valid"
);
if room_id != pdu.room_id {
if room_id != pdu.room_id_or_hash() {
return Err!(Request(NotFound("Event ID does not belong to the reported room",)));
}
if score.is_some_and(|s| s > int!(0) || s < int!(-100)) {
return Err!(Request(InvalidParam("Invalid score, must be within 0 to -100",)));
}
if reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err!(Request(
InvalidParam("Reason too long, should be 750 characters or fewer",)
@@ -240,9 +233,6 @@ fn build_report(report: Report) -> RoomMessageEventContent {
if report.event_id.is_some() {
let _ = writeln!(text, "- Reported Event ID: `{}`", report.event_id.unwrap());
}
if let Some(score) = report.score {
let _ = writeln!(text, "- User-supplied offensiveness score: {}%", score.mul(int!(-1)));
}
if let Some(reason) = report.reason {
let _ = writeln!(text, "- Report Reason: {reason}");
}
+177 -84
View File
@@ -1,10 +1,10 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use axum::extract::State;
use conduwuit::{
Err, Result, debug_info, debug_warn, err, info,
Err, Result, RoomVersion, debug, debug_info, debug_warn, err, info,
matrix::{StateKey, pdu::PduBuilder},
warn,
trace, warn,
};
use conduwuit_service::{Services, appservice::RegistrationInfo};
use futures::FutureExt;
@@ -13,6 +13,7 @@
api::client::room::{self, create_room},
events::{
TimelineEventType,
invite_permission_config::FilterLevel,
room::{
canonical_alias::RoomCanonicalAliasEventContent,
create::RoomCreateEventContent,
@@ -49,6 +50,7 @@
/// - Send events implied by `name` and `topic`
/// - Send invite events
#[allow(clippy::large_stack_frames)]
#[allow(clippy::cognitive_complexity)]
pub(crate) async fn create_room_route(
State(services): State<crate::State>,
body: Ruma<create_room::v3::Request>,
@@ -68,51 +70,6 @@ pub(crate) async fn create_room_route(
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let room_id: OwnedRoomId = match &body.room_id {
| Some(custom_room_id) => custom_room_id_check(&services, custom_room_id)?,
| _ => RoomId::new(&services.server.name),
};
// check if room ID doesn't already exist instead of erroring on auth check
if services.rooms.short.get_shortroomid(&room_id).await.is_ok() {
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
}
if body.visibility == room::Visibility::Public
&& services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
warn!(
"Non-admin user {sender_user} tried to publish {room_id} to the room directory \
while \"lockdown_public_room_directory\" is enabled"
);
if services.server.config.admin_room_notices {
services
.admin
.notice(&format!(
"Non-admin user {sender_user} tried to publish {room_id} to the room \
directory while \"lockdown_public_room_directory\" is enabled"
))
.await;
}
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
}
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(&room_id)
.await;
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
| Some(alias) =>
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
| _ => None,
};
let room_version = match body.room_version.clone() {
| Some(room_version) =>
if services.server.supported_room_version(&room_version) {
@@ -124,6 +81,86 @@ pub(crate) async fn create_room_route(
},
| None => services.server.config.default_room_version.clone(),
};
let room_features = RoomVersion::new(&room_version)?;
let room_id: Option<OwnedRoomId> = if !room_features.room_ids_as_hashes {
match &body.room_id {
| Some(custom_room_id) => Some(custom_room_id_check(&services, custom_room_id)?),
| None => Some(RoomId::new(services.globals.server_name())),
}
} else {
None
};
// check if room ID doesn't already exist instead of erroring on auth check
if let Some(ref room_id) = room_id {
if services.rooms.short.get_shortroomid(room_id).await.is_ok() {
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
}
}
if body.visibility == room::Visibility::Public
&& services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
warn!(
"Non-admin user {sender_user} tried to publish {room_id:?} to the room directory \
while \"lockdown_public_room_directory\" is enabled"
);
if services.server.config.admin_room_notices {
services
.admin
.notice(&format!(
"Non-admin user {sender_user} tried to publish {room_id:?} to the room \
directory while \"lockdown_public_room_directory\" is enabled"
))
.await;
}
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
}
let mut invitees = BTreeSet::new();
for recipient_user in &body.invite {
if !matches!(
services
.users
.invite_filter_level(recipient_user, sender_user)
.await,
FilterLevel::Allow
) {
// drop invites if the creator has them blocked
continue;
}
// if the recipient of the invite is local and has the sender blocked, error
// out. if the recipient is remote we can't tell yet, and if they're local and
// have the sender _ignored_ their invite will be filtered out in
// the handlers for the individual /sync endpoints
if services.globals.user_is_local(recipient_user)
&& matches!(
services
.users
.invite_filter_level(sender_user, recipient_user)
.await,
FilterLevel::Block
) {
return Err!(Request(InviteBlocked(
"{recipient_user} has blocked invites from you."
)));
}
invitees.insert(recipient_user.clone());
}
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
| Some(alias) =>
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
| _ => None,
};
let create_content = match &body.creation_content {
| Some(content) => {
@@ -164,18 +201,36 @@ pub(crate) async fn create_room_route(
let content = match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
RoomCreateEventContent::new_v1(sender_user.to_owned()),
| _ => RoomCreateEventContent::new_v11(),
| V11 => RoomCreateEventContent::new_v11(),
| _ => RoomCreateEventContent::new_v12(),
};
let mut content =
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())
.unwrap();
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
content
},
};
let state_lock = match room_id.clone() {
| Some(room_id) => {
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(&room_id)
.await;
services.rooms.state.mutex.lock(&room_id).await
},
| None => {
let temp_room_id = RoomId::new(services.globals.server_name());
trace!("Locking temporary room state mutex for {temp_room_id}");
services.rooms.state.mutex.lock(&temp_room_id).await
},
};
// 1. The room create event
services
debug!("Creating room create event for {sender_user} in room {room_id:?}");
let tmp_id = room_id.as_deref();
let create_event_id = services
.rooms
.timeline
.build_and_append_pdu(
@@ -186,13 +241,26 @@ pub(crate) async fn create_room_route(
..Default::default()
},
sender_user,
&room_id,
tmp_id,
&state_lock,
)
.boxed()
.await?;
trace!("Created room create event with ID {}", &create_event_id);
let room_id = match room_id.clone() {
| Some(room_id) => room_id,
| None => {
let as_room_id = create_event_id.as_str().replace('$', "!");
trace!("Creating room with v12 room ID {as_room_id}");
RoomId::parse(&as_room_id)?.to_owned()
},
};
drop(state_lock);
debug!("Room created with ID {room_id}");
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
// 2. Let the room creator join
debug_info!("Joining {sender_user} to room {room_id}");
services
.rooms
.timeline
@@ -205,7 +273,7 @@ pub(crate) async fn create_room_route(
..RoomMemberEventContent::new(MembershipState::Join)
}),
sender_user,
&room_id,
Some(&room_id),
&state_lock,
)
.boxed()
@@ -219,26 +287,45 @@ pub(crate) async fn create_room_route(
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
});
let mut users = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
let mut power_levels_to_grant = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
if preset == RoomPreset::TrustedPrivateChat {
for invite in &body.invite {
if services.users.user_is_ignored(sender_user, invite).await {
continue;
} else if services.users.user_is_ignored(invite, sender_user).await {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
continue;
}
users.insert(invite.clone(), int!(100));
for recipient_user in &invitees {
power_levels_to_grant.insert(recipient_user.clone(), int!(100));
}
}
let mut creators: Vec<OwnedUserId> = vec![sender_user.to_owned()];
// Do we care about additional_creators?
if room_features.explicitly_privilege_room_creators {
// Have they been specified?
if let Some(additional_creators) = create_content.get("additional_creators") {
// Are they a real array?
if let Some(additional_creators) = additional_creators.as_array() {
// Iterate through them
for creator in additional_creators {
// Are they a string?
if let Some(creator) = creator.as_str() {
// Do they parse into a real user ID?
if let Ok(creator) = OwnedUserId::parse(creator) {
// Add them to the power levels and creators
creators.push(creator.clone());
}
}
}
}
}
} else {
power_levels_to_grant.insert(sender_user.to_owned(), int!(100));
creators.clear(); // If this vec is not empty, default_power_levels_content will
// treat this as a v12 room
}
let power_levels_content = default_power_levels_content(
body.power_level_content_override.as_ref(),
&body.visibility,
users,
power_levels_to_grant,
creators,
)?;
services
@@ -252,7 +339,7 @@ pub(crate) async fn create_room_route(
..Default::default()
},
sender_user,
&room_id,
Some(&room_id),
&state_lock,
)
.boxed()
@@ -269,7 +356,7 @@ pub(crate) async fn create_room_route(
alt_aliases: vec![],
}),
sender_user,
&room_id,
Some(&room_id),
&state_lock,
)
.boxed()
@@ -292,7 +379,7 @@ pub(crate) async fn create_room_route(
}),
),
sender_user,
&room_id,
Some(&room_id),
&state_lock,
)
.boxed()
@@ -308,7 +395,7 @@ pub(crate) async fn create_room_route(
&RoomHistoryVisibilityEventContent::new(HistoryVisibility::Shared),
),
sender_user,
&room_id,
Some(&room_id),
&state_lock,
)
.boxed()
@@ -327,7 +414,7 @@ pub(crate) async fn create_room_route(
}),
),
sender_user,
&room_id,
Some(&room_id),
&state_lock,
)
.boxed()
@@ -363,7 +450,7 @@ pub(crate) async fn create_room_route(
services
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
.build_and_append_pdu(pdu_builder, sender_user, Some(&room_id), &state_lock)
.boxed()
.await?;
}
@@ -376,7 +463,7 @@ pub(crate) async fn create_room_route(
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomNameEventContent::new(name.clone())),
sender_user,
&room_id,
Some(&room_id),
&state_lock,
)
.boxed()
@@ -390,7 +477,7 @@ pub(crate) async fn create_room_route(
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomTopicEventContent { topic: topic.clone() }),
sender_user,
&room_id,
Some(&room_id),
&state_lock,
)
.boxed()
@@ -399,17 +486,9 @@ pub(crate) async fn create_room_route(
// 8. Events implied by invite (and TODO: invite_3pid)
drop(state_lock);
for user_id in &body.invite {
if services.users.user_is_ignored(sender_user, user_id).await {
continue;
} else if services.users.user_is_ignored(user_id, sender_user).await {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
continue;
}
for recipient_user in &invitees {
if let Err(e) =
invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct)
invite_helper(&services, sender_user, recipient_user, &room_id, None, body.is_direct)
.boxed()
.await
{
@@ -450,6 +529,7 @@ fn default_power_levels_content(
power_level_content_override: Option<&Raw<RoomPowerLevelsEventContent>>,
visibility: &room::Visibility,
users: BTreeMap<OwnedUserId, Int>,
creators: Vec<OwnedUserId>,
) -> Result<serde_json::Value> {
let mut power_levels_content =
serde_json::to_value(RoomPowerLevelsEventContent { users, ..Default::default() })
@@ -499,6 +579,19 @@ fn default_power_levels_content(
}
}
if !creators.is_empty() {
// Raise the default power level of tombstone to 150
power_levels_content["events"]["m.room.tombstone"] =
serde_json::to_value(150).expect("150 is valid Value");
for creator in creators {
// Omit creators from the power level list altogether
power_levels_content["users"]
.as_object_mut()
.expect("users is an object")
.remove(creator.as_str());
}
}
Ok(power_levels_content)
}
+117 -41
View File
@@ -2,7 +2,7 @@
use axum::extract::State;
use conduwuit::{
Err, Error, Event, Result, debug, err, info,
Err, Error, Event, Result, RoomVersion, debug, err, info,
matrix::{StateKey, pdu::PduBuilder},
};
use futures::{FutureExt, StreamExt};
@@ -68,37 +68,77 @@ pub(crate) async fn upgrade_room_route(
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
// First, check if the user has permission to upgrade the room (send tombstone
// event)
let old_room_state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
// Check tombstone permission by attempting to create (but not send) the event
// Note that this does internally call the policy server with a fake room ID,
// which may not be good?
let tombstone_test_result = services
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: RoomId::new(services.globals.server_name()),
}),
sender_user,
Some(&body.room_id),
&old_room_state_lock,
)
.await;
if let Err(_e) = tombstone_test_result {
return Err!(Request(Forbidden("User does not have permission to upgrade this room.")));
}
drop(old_room_state_lock);
// Create a replacement room
let replacement_room = RoomId::new(services.globals.server_name());
let room_features = RoomVersion::new(&body.new_version)?;
let replacement_room_owned = if !room_features.room_ids_as_hashes {
Some(RoomId::new(services.globals.server_name()))
} else {
None
};
let replacement_room: Option<&RoomId> = replacement_room_owned.as_ref().map(AsRef::as_ref);
let replacement_room_tmp = match replacement_room {
| Some(v) => v,
| None => &RoomId::new(services.globals.server_name()),
};
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(&replacement_room)
.get_or_create_shortroomid(replacement_room_tmp)
.await;
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further Fail if the sender does not have the required
// permissions
let tombstone_event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.clone(),
}),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
// Change lock to replacement room
drop(state_lock);
let state_lock = services.rooms.state.mutex.lock(&replacement_room).await;
// For pre-v12 rooms, send tombstone before creating replacement room
let tombstone_event_id = if !room_features.room_ids_as_hashes {
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further
let tombstone_event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.unwrap().to_owned(),
}),
sender_user,
Some(&body.room_id),
&state_lock,
)
.await?;
// Change lock to replacement room
drop(state_lock);
Some(tombstone_event_id)
} else {
None
};
let state_lock = services.rooms.state.mutex.lock(replacement_room_tmp).await;
// Get the old room creation event
let mut create_event_content: CanonicalJsonObject = services
@@ -111,7 +151,7 @@ pub(crate) async fn upgrade_room_route(
// Use the m.room.tombstone event as the predecessor
let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
body.room_id.clone(),
Some(tombstone_event_id),
tombstone_event_id,
));
// Send a m.room.create event containing a predecessor field and the applicable
@@ -132,6 +172,7 @@ pub(crate) async fn upgrade_room_route(
// "creator" key no longer exists in V11 rooms
create_event_content.remove("creator");
},
// TODO(hydra): additional_creators
}
}
@@ -159,7 +200,7 @@ pub(crate) async fn upgrade_room_route(
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
}
services
let create_event_id = services
.rooms
.timeline
.build_and_append_pdu(
@@ -173,11 +214,18 @@ pub(crate) async fn upgrade_room_route(
timestamp: None,
},
sender_user,
&replacement_room,
replacement_room,
&state_lock,
)
.boxed()
.await?;
let create_id = create_event_id.as_str().replace('$', "!");
let (replacement_room, state_lock) = if room_features.room_ids_as_hashes {
let parsed_room_id = RoomId::parse(&create_id)?;
(Some(parsed_room_id), services.rooms.state.mutex.lock(parsed_room_id).await)
} else {
(replacement_room, state_lock)
};
// Join the new room
services
@@ -204,7 +252,7 @@ pub(crate) async fn upgrade_room_route(
timestamp: None,
},
sender_user,
&replacement_room,
replacement_room,
&state_lock,
)
.boxed()
@@ -243,7 +291,7 @@ pub(crate) async fn upgrade_room_route(
..Default::default()
},
sender_user,
&replacement_room,
replacement_room,
&state_lock,
)
.boxed()
@@ -268,7 +316,7 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.alias
.set_alias(alias, &replacement_room, sender_user)?;
.set_alias(alias, replacement_room.unwrap(), sender_user)?;
}
// Get the old room power levels
@@ -302,7 +350,7 @@ pub(crate) async fn upgrade_room_route(
..power_levels_event_content
}),
sender_user,
&body.room_id,
Some(&body.room_id),
&state_lock,
)
.boxed()
@@ -310,6 +358,27 @@ pub(crate) async fn upgrade_room_route(
drop(state_lock);
// For v12 rooms, send tombstone AFTER creating replacement room
if room_features.room_ids_as_hashes {
let old_room_state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
// For v12 rooms, no event reference in predecessor due to cyclic dependency -
// could best effort one maybe?
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.unwrap().to_owned(),
}),
sender_user,
Some(&body.room_id),
&old_room_state_lock,
)
.await?;
drop(old_room_state_lock);
}
// Check if the old room has a space parent, and if so, whether we should update
// it (m.space.parent, room_id)
let parents = services
@@ -334,8 +403,9 @@ pub(crate) async fn upgrade_room_route(
continue;
};
debug!(
"Updating space {space_id} child event for room {} to {replacement_room}",
&body.room_id
"Updating space {space_id} child event for room {} to {}",
&body.room_id,
replacement_room.unwrap()
);
// First, drop the space's child event
let state_lock = services.rooms.state.mutex.lock(space_id).await;
@@ -352,14 +422,17 @@ pub(crate) async fn upgrade_room_route(
..Default::default()
},
sender_user,
space_id,
Some(space_id),
&state_lock,
)
.boxed()
.await
.ok();
// Now, add a new child event for the replacement room
debug!("Adding space child event for room {replacement_room} in space {space_id}");
debug!(
"Adding space child event for room {} in space {space_id}",
replacement_room.unwrap()
);
services
.rooms
.timeline
@@ -372,23 +445,26 @@ pub(crate) async fn upgrade_room_route(
suggested: child.suggested,
})
.expect("event is valid, we just created it"),
state_key: Some(replacement_room.as_str().into()),
state_key: Some(replacement_room.unwrap().as_str().into()),
..Default::default()
},
sender_user,
space_id,
Some(space_id),
&state_lock,
)
.boxed()
.await
.ok();
debug!(
"Finished updating space {space_id} child event for room {} to {replacement_room}",
&body.room_id
"Finished updating space {space_id} child event for room {} to {}",
&body.room_id,
replacement_room.unwrap()
);
drop(state_lock);
}
// Return the replacement room id
Ok(upgrade_room::v3::Response { replacement_room })
Ok(upgrade_room::v3::Response {
replacement_room: replacement_room.unwrap().to_owned(),
})
}
+1 -1
View File
@@ -80,7 +80,7 @@ pub(crate) async fn send_message_event_route(
..Default::default()
},
sender_user,
&body.room_id,
Some(&body.room_id),
&state_lock,
)
.await?;
+2 -2
View File
@@ -145,9 +145,9 @@ pub(super) async fn ldap_login(
let is_conduwuit_admin = services.admin.user_is_admin(lowercased_user_id).await;
if is_ldap_admin && !is_conduwuit_admin {
services.admin.make_user_admin(lowercased_user_id).await?;
Box::pin(services.admin.make_user_admin(lowercased_user_id)).await?;
} else if !is_ldap_admin && is_conduwuit_admin {
services.admin.revoke_admin(lowercased_user_id).await?;
Box::pin(services.admin.revoke_admin(lowercased_user_id)).await?;
}
Ok(user_id)
+1 -1
View File
@@ -44,7 +44,7 @@ pub(crate) async fn get_hierarchy_route(
.as_ref()
.and_then(|s| PaginationToken::from_str(s).ok());
// Should prevent unexpeded behaviour in (bad) clients
// Should prevent unexpected behaviour in (bad) clients
if let Some(ref token) = key {
if token.suggested_only != body.suggested_only || token.max_depth != max_depth {
return Err!(Request(InvalidParam(
+1 -1
View File
@@ -201,7 +201,7 @@ async fn send_state_event_for_key_helper(
..Default::default()
},
sender,
room_id,
Some(room_id),
&state_lock,
)
.await?;
+12 -2
View File
@@ -60,7 +60,10 @@
use service::rooms::short::{ShortEventId, ShortStateKey};
use super::{load_timeline, share_encrypted_room};
use crate::{Ruma, RumaResponse, client::ignored_filter};
use crate::{
Ruma, RumaResponse,
client::{ignored_filter, is_ignored_invite},
};
#[derive(Default)]
struct StateChanges {
@@ -238,6 +241,13 @@ pub(crate) async fn build_sync_events(
.rooms
.state_cache
.rooms_invited(sender_user)
.wide_filter_map(async |(room_id, invite_state)| {
if is_ignored_invite(services, sender_user, &room_id).await {
None
} else {
Some((room_id, invite_state))
}
})
.fold_default(|mut invited_rooms: BTreeMap<_, _>, (room_id, invite_state)| async move {
let invite_count = services
.rooms
@@ -457,7 +467,7 @@ async fn handle_left_room(
state_key: Some(sender_user.as_str().into()),
unsigned: None,
// The following keys are dropped on conversion
room_id: room_id.clone(),
room_id: Some(room_id.clone()),
prev_events: vec![],
depth: uint!(1),
auth_events: vec![],
+9 -1
View File
@@ -11,6 +11,7 @@
utils::{
BoolExt, IterStream, ReadyExt, TryFutureExtExt,
math::{ruma_from_usize, usize_from_ruma, usize_from_u64_truncated},
stream::WidebandExt,
},
warn,
};
@@ -39,7 +40,7 @@
use super::{load_timeline, share_encrypted_room};
use crate::{
Ruma,
client::{DEFAULT_BUMP_TYPES, ignored_filter},
client::{DEFAULT_BUMP_TYPES, ignored_filter, is_ignored_invite},
};
type TodoRooms = BTreeMap<OwnedRoomId, (BTreeSet<TypeStateKey>, usize, u64)>;
@@ -102,6 +103,13 @@ pub(crate) async fn sync_events_v4_route(
.rooms
.state_cache
.rooms_invited(sender_user)
.wide_filter_map(async |(room_id, invite_state)| {
if is_ignored_invite(&services, sender_user, &room_id).await {
None
} else {
Some((room_id, invite_state))
}
})
.map(|r| r.0)
.collect()
.await;
+10 -1
View File
@@ -14,6 +14,7 @@
BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
future::ReadyEqExt,
math::{ruma_from_usize, usize_from_ruma},
stream::WidebandExt,
},
warn,
};
@@ -38,7 +39,7 @@
use super::share_encrypted_room;
use crate::{
Ruma,
client::{DEFAULT_BUMP_TYPES, ignored_filter, sync::load_timeline},
client::{DEFAULT_BUMP_TYPES, ignored_filter, is_ignored_invite, sync::load_timeline},
};
type SyncInfo<'a> = (&'a UserId, &'a DeviceId, u64, &'a sync_events::v5::Request);
@@ -106,6 +107,13 @@ pub(crate) async fn sync_events_v5_route(
.rooms
.state_cache
.rooms_invited(sender_user)
.wide_filter_map(async |(room_id, invite_state)| {
if is_ignored_invite(services, sender_user, &room_id).await {
None
} else {
Some((room_id, invite_state))
}
})
.map(|r| r.0)
.collect::<Vec<OwnedRoomId>>();
@@ -312,6 +320,7 @@ async fn handle_lists<'a, Rooms, AllRooms>(
for mut range in ranges {
range.0 = uint!(0);
range.1 = range.1.checked_add(uint!(1)).unwrap_or(range.1);
range.1 = range
.1
.clamp(range.0, UInt::try_from(active_rooms.len()).unwrap_or(UInt::MAX));
+11 -135
View File
@@ -2,18 +2,14 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Error, Result};
use conduwuit::{Err, Result};
use futures::StreamExt;
use ruma::{
OwnedRoomId,
api::{
client::{
error::ErrorKind,
membership::mutual_rooms,
profile::{
delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key,
set_profile_key, set_timezone_key,
},
profile::{delete_profile_key, get_profile_key, set_profile_key},
},
federation,
},
@@ -60,62 +56,6 @@ pub(crate) async fn get_mutual_rooms_route(
})
}
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
///
/// Deletes the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
///
/// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn delete_timezone_key_route(
State(services): State<crate::State>,
body: Ruma<delete_timezone_key::unstable::Request>,
) -> Result<delete_timezone_key::unstable::Response> {
let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
services.users.set_timezone(&body.user_id, None);
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
Ok(delete_timezone_key::unstable::Response {})
}
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
///
/// Updates the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
///
/// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn set_timezone_key_route(
State(services): State<crate::State>,
body: Ruma<set_timezone_key::unstable::Request>,
) -> Result<set_timezone_key::unstable::Response> {
let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
services.users.set_timezone(&body.user_id, body.tz.clone());
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
Ok(set_timezone_key::unstable::Response {})
}
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
///
/// Updates the profile key-value field of a user, as per MSC4133.
@@ -150,19 +90,14 @@ pub(crate) async fn set_profile_key_route(
)));
};
if body
.kv_pair
.keys()
.any(|key| key.starts_with("u.") && !profile_key_value.is_string())
{
return Err!(Request(BadJson("u.* profile key fields must be strings")));
}
if body.kv_pair.keys().any(|key| key.len() > 128) {
return Err!(Request(BadJson("Key names cannot be longer than 128 bytes")));
}
if body.key_name == "displayname" {
let Some(display_name) = profile_key_value.as_str() else {
return Err!(Request(BadJson("displayname must be a string")));
};
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
@@ -174,12 +109,15 @@ pub(crate) async fn set_profile_key_route(
update_displayname(
&services,
&body.user_id,
Some(profile_key_value.to_string()),
Some(display_name.to_owned()),
&all_joined_rooms,
)
.await;
} else if body.key_name == "avatar_url" {
let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string());
let Some(avatar_url) = profile_key_value.as_str() else {
return Err!(Request(BadJson("avatar_url must be a string")));
};
let mxc = ruma::OwnedMxcUri::from(avatar_url);
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
@@ -268,70 +206,12 @@ pub(crate) async fn delete_profile_key_route(
Ok(delete_profile_key::unstable::Response {})
}
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
///
/// Returns the `timezone` of the user as per MSC4133 and MSC4175.
///
/// - If user is on another server and we do not have a local copy already fetch
/// `timezone` over federation
pub(crate) async fn get_timezone_key_route(
State(services): State<crate::State>,
body: Ruma<get_timezone_key::unstable::Request>,
) -> Result<get_timezone_key::unstable::Response> {
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
.sending
.send_federation_request(
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: body.user_id.clone(),
field: None, // we want the full user's profile to update locally as well
},
)
.await
{
if !services.users.exists(&body.user_id).await {
services.users.create(&body.user_id, None, None).await?;
}
services
.users
.set_displayname(&body.user_id, response.displayname.clone());
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone());
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone());
services
.users
.set_timezone(&body.user_id, response.tz.clone());
return Ok(get_timezone_key::unstable::Response { tz: response.tz });
}
}
if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
Ok(get_timezone_key::unstable::Response {
tz: services.users.timezone(&body.user_id).await.ok(),
})
}
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{userId}/{field}}`
///
/// Gets the profile key-value field of a user, as per MSC4133.
///
/// - If user is on another server and we do not have a local copy already fetch
/// `timezone` over federation
/// the value over federation
pub(crate) async fn get_profile_key_route(
State(services): State<crate::State>,
body: Ruma<get_profile_key::unstable::Request>,
@@ -367,10 +247,6 @@ pub(crate) async fn get_profile_key_route(
.users
.set_blurhash(&body.user_id, response.blurhash.clone());
services
.users
.set_timezone(&body.user_id, response.tz.clone());
match response.custom_profile_fields.get(&body.key_name) {
| Some(value) => {
profile_key_value.insert(body.key_name.clone(), value.clone());
+1
View File
@@ -59,6 +59,7 @@ pub(crate) async fn get_supported_versions_route(
("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */
("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */
("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */
("org.matrix.msc4155".to_owned(), true), /* invite filtering (https://github.com/matrix-org/matrix-spec-proposals/pull/4155) */
]),
};
+13 -3
View File
@@ -3,7 +3,9 @@
use futures::StreamExt;
use ruma::api::client::{
discovery::{
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
discover_homeserver::{
self, AuthenticationServerInfo, HomeserverInfo, SlidingSyncProxyInfo,
},
discover_support::{self, Contact},
},
error::ErrorKind,
@@ -26,8 +28,16 @@ pub(crate) async fn well_known_client(
Ok(discover_homeserver::Response {
homeserver: HomeserverInfo { base_url: client_url.clone() },
identity_server: None,
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url.clone() }),
tile_server: None,
authentication: services.config.auth.as_ref().and_then(|auth| {
auth.enable_oidc_login
.then_some(AuthenticationServerInfo::new(
client_url.clone(),
auth.enable_oidc_account_management
.then_some(format!("{client_url}/account")),
))
}),
})
}
@@ -78,7 +88,7 @@ pub(crate) async fn well_known_support(
while let Some(user_id) = stream.next().await {
// Skip server user
if *user_id == services.globals.server_user {
break;
continue;
}
contacts.push(Contact {
role: role_value.clone(),
+16 -3
View File
@@ -22,12 +22,9 @@
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
let config = &server.config;
let mut router = router
.ruma_route(&client::get_timezone_key_route)
.ruma_route(&client::get_profile_key_route)
.ruma_route(&client::set_profile_key_route)
.ruma_route(&client::delete_profile_key_route)
.ruma_route(&client::set_timezone_key_route)
.ruma_route(&client::delete_timezone_key_route)
.ruma_route(&client::appservice_ping)
.ruma_route(&client::get_supported_versions_route)
.ruma_route(&client::get_register_available_route)
@@ -118,6 +115,21 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::get_protocols_route)
.route("/_matrix/client/unstable/thirdparty/protocols",
get(client::get_protocols_route_unstable))
// MSC2965: OAuth 2.0 Authorization Server Metadata discovery.
.route("/_matrix/client/unstable/org.matrix.msc2965/auth_metadata",
get(client::get_auth_metadata))
// MSC2964: Usage of OAuth 2.0 authorization code grant and refresh token grant.
.route("/_matrix/client/unstable/org.matrix.msc2964/authorize",
get(client::authorize))
.route("/_matrix/client/unstable/org.matrix.msc2964/authorize",
post(client::authorize_consent))
.route("/_matrix/client/unstable/org.matrix.msc2964/login",
post(client::oidc_login))
.route("/_matrix/client/unstable/org.matrix.msc2964/token",
post(client::token))
// MSC2966: Usage of OAuth 2.0 Dynamic Client Registration in Matrix.
.route("/_matrix/client/unstable/org.matrix.msc2964/device/register",
post(client::register_client))
.ruma_route(&client::send_message_event_route)
.ruma_route(&client::send_state_event_for_key_route)
.ruma_route(&client::get_state_events_route)
@@ -229,6 +241,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&server::well_known_server)
.ruma_route(&server::get_content_route)
.ruma_route(&server::get_content_thumbnail_route)
.ruma_route(&server::get_edutypes_route)
.route("/_conduwuit/local_user_count", get(client::conduwuit_local_user_count))
.route("/_continuwuity/local_user_count", get(client::conduwuit_local_user_count));
} else {
+2 -5
View File
@@ -20,9 +20,7 @@
client::{
directory::get_public_rooms,
error::ErrorKind,
profile::{
get_avatar_url, get_display_name, get_profile, get_profile_key, get_timezone_key,
},
profile::{get_avatar_url, get_display_name, get_profile, get_profile_key},
voip::get_turn_server_info,
},
federation::{authentication::XMatrix, openid::get_openid_userinfo},
@@ -89,8 +87,7 @@ pub(super) async fn auth(
| &get_profile::v3::Request::METADATA
| &get_profile_key::unstable::Request::METADATA
| &get_display_name::v3::Request::METADATA
| &get_avatar_url::v3::Request::METADATA
| &get_timezone_key::unstable::Request::METADATA => {
| &get_avatar_url::v3::Request::METADATA => {
if services.server.config.require_auth_for_profile_requests {
match token {
| Token::Appservice(_) | Token::User(_) => {

Some files were not shown because too many files have changed in this diff Show More