feat: Improve admin command reference generation

- Change xtasks to use `clap` for argument parsing
- Generate admin command reference manually instead of with `clap_markdown`
- Split admin command reference into multiple files
This commit is contained in:
Ginger 2026-01-09 17:05:34 -05:00
parent 60dd6baffd
commit 89be9d1efc
No known key found for this signature in database
31 changed files with 1297 additions and 5822 deletions

View file

@ -7,6 +7,5 @@
"continuwuity",
"homeserver",
"homeservers"
],
"rust-analyzer.cargo.features": ["full"]
]
}

209
Cargo.lock generated
View file

@ -72,56 +72,12 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.60.2",
]
[[package]]
name = "anyhow"
version = "1.0.100"
@ -199,7 +155,20 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4"
dependencies = [
"askama_derive",
"askama_derive 0.14.0",
"itoa",
"percent-encoding",
"serde",
"serde_json",
]
[[package]]
name = "askama"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7125972258312e79827b60c9eb93938334100245081cf701a2dee981b17427"
dependencies = [
"askama_macros",
"itoa",
"percent-encoding",
"serde",
@ -212,7 +181,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f"
dependencies = [
"askama_parser",
"askama_parser 0.14.0",
"basic-toml",
"memchr",
"proc-macro2",
@ -223,6 +192,32 @@ dependencies = [
"syn",
]
[[package]]
name = "askama_derive"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ba5e7259a1580c61571e3116ebaaa01e3c001b2132b17c4cc5c70780ca3e994"
dependencies = [
"askama_parser 0.15.1",
"basic-toml",
"memchr",
"proc-macro2",
"quote",
"rustc-hash",
"serde",
"serde_derive",
"syn",
]
[[package]]
name = "askama_macros"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "236ce20b77cb13506eaf5024899f4af6e12e8825f390bd943c4c37fd8f322e46"
dependencies = [
"askama_derive 0.15.1",
]
[[package]]
name = "askama_parser"
version = "0.14.0"
@ -235,6 +230,19 @@ dependencies = [
"winnow",
]
[[package]]
name = "askama_parser"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c63392767bb2df6aa65a6e1e3b80fd89bb7af6d58359b924c0695620f1512e"
dependencies = [
"rustc-hash",
"serde",
"serde_derive",
"unicode-ident",
"winnow",
]
[[package]]
name = "asn1-rs"
version = "0.7.1"
@ -757,6 +765,39 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "camino"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
dependencies = [
"serde_core",
]
[[package]]
name = "cargo-platform"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "cargo_metadata"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
"thiserror 2.0.17",
]
[[package]]
name = "cargo_toml"
version = "0.22.3"
@ -839,25 +880,14 @@ dependencies = [
"clap_derive",
]
[[package]]
name = "clap-markdown"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2a2617956a06d4885b490697b5307ebb09fec10b088afc18c81762d848c2339"
dependencies = [
"clap",
]
[[package]]
name = "clap_builder"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
@ -878,16 +908,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "clap_mangen"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ea63a92086df93893164221ad4f24142086d535b3a0957b9b9bea2dc86301"
dependencies = [
"clap",
"roff",
]
[[package]]
name = "cmake"
version = "0.1.54"
@ -903,12 +923,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "compression-codecs"
version = "0.4.31"
@ -1202,7 +1216,7 @@ dependencies = [
name = "conduwuit_web"
version = "0.5.2"
dependencies = [
"askama",
"askama 0.14.0",
"axum 0.7.9",
"conduwuit_build_metadata",
"conduwuit_service",
@ -2639,12 +2653,6 @@ dependencies = [
"serde",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.12.1"
@ -3272,12 +3280,6 @@ dependencies = [
"portable-atomic",
]
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "openssl-probe"
version = "0.1.6"
@ -4076,12 +4078,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "roff"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]]
name = "ruma"
version = "0.10.1"
@ -4506,6 +4502,10 @@ name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "sentry"
@ -4961,12 +4961,6 @@ dependencies = [
"quote",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subslice"
version = "0.2.3"
@ -5700,12 +5694,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.18.1"
@ -6230,18 +6218,9 @@ dependencies = [
name = "xtask"
version = "0.5.2"
dependencies = [
"askama 0.15.1",
"cargo_metadata",
"clap",
"serde",
"serde_json",
]
[[package]]
name = "xtask-generate-commands"
version = "0.5.2"
dependencies = [
"clap-markdown",
"clap_builder",
"clap_mangen",
"conduwuit",
"conduwuit_admin",
]

View file

@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["src/*", "xtask/*"]
members = ["src/*", "xtask/"]
default-members = ["src/*"]
[workspace.package]

View file

@ -57,9 +57,9 @@
"name": "/reference/config"
},
{
"type": "file",
"type": "dir",
"label": "Admin Command Reference",
"name": "/reference/admin"
"name": "/reference/admin/"
},
{
"type": "file",

View file

@ -18,7 +18,7 @@
},
{
"text": "Admin Command Reference",
"link": "/reference/admin"
"link": "/reference/admin/"
},
{
"text": "Server Reference",

File diff suppressed because it is too large Load diff

View file

@ -8,10 +8,5 @@
"type": "file",
"name": "admin",
"label": "Admin Commands"
},
{
"type": "file",
"name": "server",
"label": "Server command"
}
]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
# `!admin appservices`
- Commands for managing appservices
## `!admin appservices register`
- Register an appservice using its registration YAML
This command needs a YAML generated by an appservice (such as a bridge), which must be provided in a Markdown code block below the command.
Registering a new bridge using the ID of an existing bridge will replace the old one.
## `!admin appservices unregister`
- Unregister an appservice using its ID
You can find the ID using the `list-appservices` command.
## `!admin appservices show-appservice-config`
- Show an appservice's config using its ID
You can find the ID using the `list-appservices` command.
## `!admin appservices list-registered`
- List all the currently registered appservices

View file

@ -0,0 +1,8 @@
# `!admin check`
- Commands for checking integrity
## `!admin check check-all-users`
_(no description)_

View file

@ -0,0 +1,154 @@
# `!admin debug`
- Commands for debugging things
## `!admin debug echo`
- Echo input of admin command
## `!admin debug get-auth-chain`
- Get the auth_chain of a PDU
## `!admin debug parse-pdu`
- Parse and print a PDU from a JSON
The PDU event is only checked for validity and is not added to the database.
This command needs a JSON blob provided in a Markdown code block below the command.
## `!admin debug get-pdu`
- Retrieve and print a PDU by EventID from the Continuwuity database
## `!admin debug get-short-pdu`
- Retrieve and print a PDU by PduId from the Continuwuity database
## `!admin debug get-remote-pdu`
- Attempts to retrieve a PDU from a remote server. **Does not** insert it into the database or persist it anywhere
## `!admin debug get-remote-pdu-list`
- Same as `get-remote-pdu` but accepts a codeblock newline delimited list of PDUs and a single server to fetch from
## `!admin debug get-room-state`
- Gets all the room state events for the specified room.
This is functionally equivalent to `GET /_matrix/client/v3/rooms/{roomid}/state`, except the admin command does *not* check if the sender user is allowed to see state events. This is done because it's implied that server admins here have database access and can see/get room info themselves anyways if they were malicious admins.
Of course the check is still done on the actual client API.
## `!admin debug get-signing-keys`
- Get and display signing keys from local cache or remote server
## `!admin debug get-verify-keys`
- Get and display signing keys from local cache or remote server
## `!admin debug ping`
- Sends a federation request to the remote server's `/_matrix/federation/v1/version` endpoint and measures the latency it took for the server to respond
## `!admin debug force-device-list-updates`
- Forces device lists for all local and remote users to be updated (as having new keys available)
## `!admin debug change-log-level`
- Change tracing log level/filter on the fly
This accepts the same format as the `log` config option.
## `!admin debug verify-json`
- Verify JSON signatures
This command needs a JSON blob provided in a Markdown code block below the command.
## `!admin debug verify-pdu`
- Verify PDU
This re-verifies a PDU existing in the database found by ID.
## `!admin debug first-pdu-in-room`
- Prints the very first PDU in the specified room (typically m.room.create)
## `!admin debug latest-pdu-in-room`
- Prints the latest ("last") PDU in the specified room (typically a message)
## `!admin debug force-set-room-state-from-server`
- Forcefully replaces the room state of our local copy of the specified room, with the copy (auth chain and room state events) the specified remote server says.
A common desire for room deletion is to simply "reset" our copy of the room. While this admin command is not a replacement for that, if you know you have split/broken room state and you know another server in the room that has the best/working room state, this command can let you use their room state. Such example is your server saying users are in a room, but other servers are saying they're not in the room in question.
This command will get the latest PDU in the room we know about, and request the room state at that point in time via `/_matrix/federation/v1/state/{roomId}`.
## `!admin debug resolve-true-destination`
- Runs a server name through Continuwuity's true destination resolution process
Useful for debugging well-known issues
## `!admin debug memory-stats`
- Print extended memory usage
Optional argument is a character mask (a sequence of characters in any order) which enable additional extended statistics. Known characters are "abdeglmx". For convenience, a '*' will enable everything.
## `!admin debug runtime-metrics`
- Print general tokio runtime metric totals
## `!admin debug runtime-interval`
- Print detailed tokio runtime metrics accumulated since last command invocation
## `!admin debug time`
- Print the current time
## `!admin debug list-dependencies`
- List dependencies
## `!admin debug database-stats`
- Get database statistics
## `!admin debug trim-memory`
- Trim memory usage
## `!admin debug database-files`
- List database files
## `!admin debug tester`
- Developer test stubs
### `!admin debug tester panic`
_(no description)_
### `!admin debug tester failure`
_(no description)_
### `!admin debug tester tester`
_(no description)_
### `!admin debug tester timer`
_(no description)_

View file

@ -0,0 +1,28 @@
# `!admin federation`
- Commands for managing federation
## `!admin federation incoming-federation`
- List all rooms we are currently handling an incoming pdu from
## `!admin federation disable-room`
- Disables incoming federation handling for a room
## `!admin federation enable-room`
- Enables incoming federation handling for a room again
## `!admin federation fetch-support-well-known`
- Fetch `/.well-known/matrix/support` from the specified server
Despite the name, this is not a federation endpoint and does not go through the federation / server resolution process as per-spec this is supposed to be served at the server_name.
Respecting homeservers put this file here for listing administration, moderation, and security inquiries. This command provides a way to easily fetch that information.
## `!admin federation remote-user-in-rooms`
- Lists all the rooms we share/track with the specified *remote* user

View file

@ -0,0 +1,14 @@
# Admin Commands
These are all the admin commands. TODO fill me out
- [`!admin appservices`](appservices/) - Commands for managing appservices
- [`!admin users`](users/) - Commands for managing local users
- [`!admin token`](token/) - Commands for managing registration tokens
- [`!admin rooms`](rooms/) - Commands for managing rooms
- [`!admin federation`](federation/) - Commands for managing federation
- [`!admin server`](server/) - Commands for managing the server
- [`!admin media`](media/) - Commands for managing media
- [`!admin check`](check/) - Commands for checking integrity
- [`!admin debug`](debug/) - Commands for debugging things
- [`!admin query`](query/) - Low-level queries for database getters and iterators

View file

@ -0,0 +1,49 @@
# `!admin media`
- Commands for managing media
## `!admin media delete`
- Deletes a single media file from our database and on the filesystem via a single MXC URL or event ID (not redacted)
## `!admin media delete-list`
- Deletes a codeblock list of MXC URLs from our database and on the filesystem. This will always ignore errors
## `!admin media 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.
* 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`
## `!admin media delete-all-from-user`
- Deletes all the local media from a local user on our server. This will always ignore errors by default
## `!admin media delete-all-from-server`
- Deletes all remote media from the specified remote server. This will always ignore errors by default
## `!admin media get-file-info`
_(no description)_
## `!admin media get-remote-file`
_(no description)_
## `!admin media get-remote-thumbnail`
_(no description)_

View file

@ -0,0 +1,360 @@
# `!admin query`
- Low-level queries for database getters and iterators
## `!admin query account-data`
- account_data.rs iterators and getters
### `!admin query account-data changes-since`
- Returns all changes to the account data that happened after `since`
### `!admin query account-data account-data-get`
- Searches the account data for a specific kind
## `!admin query appservice`
- appservice.rs iterators and getters
### `!admin query appservice get-registration`
- Gets the appservice registration info/details from the ID as a string
### `!admin query appservice all`
- Gets all appservice registrations with their ID and registration info
## `!admin query presence`
- presence.rs iterators and getters
### `!admin query presence get-presence`
- Returns the latest presence event for the given user
### `!admin query presence presence-since`
- Iterator of the most recent presence updates that happened after the event with id `since`
## `!admin query room-alias`
- rooms/alias.rs iterators and getters
### `!admin query room-alias resolve-local-alias`
_(no description)_
### `!admin query room-alias local-aliases-for-room`
- Iterator of all our local room aliases for the room ID
### `!admin query room-alias all-local-aliases`
- Iterator of all our local aliases in our database with their room IDs
## `!admin query room-state-cache`
- rooms/state_cache iterators and getters
### `!admin query room-state-cache server-in-room`
_(no description)_
### `!admin query room-state-cache room-servers`
_(no description)_
### `!admin query room-state-cache server-rooms`
_(no description)_
### `!admin query room-state-cache room-members`
_(no description)_
### `!admin query room-state-cache local-users-in-room`
_(no description)_
### `!admin query room-state-cache active-local-users-in-room`
_(no description)_
### `!admin query room-state-cache room-joined-count`
_(no description)_
### `!admin query room-state-cache room-invited-count`
_(no description)_
### `!admin query room-state-cache room-user-once-joined`
_(no description)_
### `!admin query room-state-cache room-members-invited`
_(no description)_
### `!admin query room-state-cache get-invite-count`
_(no description)_
### `!admin query room-state-cache get-left-count`
_(no description)_
### `!admin query room-state-cache rooms-joined`
_(no description)_
### `!admin query room-state-cache rooms-left`
_(no description)_
### `!admin query room-state-cache rooms-invited`
_(no description)_
### `!admin query room-state-cache invite-state`
_(no description)_
## `!admin query room-timeline`
- rooms/timeline iterators and getters
### `!admin query room-timeline pdus`
_(no description)_
### `!admin query room-timeline last`
_(no description)_
## `!admin query globals`
- globals.rs iterators and getters
### `!admin query globals database-version`
_(no description)_
### `!admin query globals current-count`
_(no description)_
### `!admin query globals last-check-for-announcements-id`
_(no description)_
### `!admin query globals signing-keys-for`
- This returns an empty `Ok(BTreeMap<..>)` when there are no keys found for the server
## `!admin query sending`
- sending.rs iterators and getters
### `!admin query sending active-requests`
- Queries database for all `servercurrentevent_data`
### `!admin query sending active-requests-for`
- Queries database for `servercurrentevent_data` but for a specific destination
This command takes only *one* format of these arguments:
appservice_id server_name user_id AND push_key
See src/service/sending/mod.rs for the definition of the `Destination` enum
### `!admin query sending queued-requests`
- Queries database for `servernameevent_data` which are the queued up requests that will eventually be sent
This command takes only *one* format of these arguments:
appservice_id server_name user_id AND push_key
See src/service/sending/mod.rs for the definition of the `Destination` enum
### `!admin query sending get-latest-edu-count`
_(no description)_
## `!admin query users`
- users.rs iterators and getters
### `!admin query users count-users`
_(no description)_
### `!admin query users iter-users`
_(no description)_
### `!admin query users iter-users2`
_(no description)_
### `!admin query users password-hash`
_(no description)_
### `!admin query users list-devices`
_(no description)_
### `!admin query users list-devices-metadata`
_(no description)_
### `!admin query users get-device-metadata`
_(no description)_
### `!admin query users get-devices-version`
_(no description)_
### `!admin query users count-one-time-keys`
_(no description)_
### `!admin query users get-device-keys`
_(no description)_
### `!admin query users get-user-signing-key`
_(no description)_
### `!admin query users get-master-key`
_(no description)_
### `!admin query users get-to-device-events`
_(no description)_
### `!admin query users get-latest-backup`
_(no description)_
### `!admin query users get-latest-backup-version`
_(no description)_
### `!admin query users get-backup-algorithm`
_(no description)_
### `!admin query users get-all-backups`
_(no description)_
### `!admin query users get-room-backups`
_(no description)_
### `!admin query users get-backup-session`
_(no description)_
### `!admin query users get-shared-rooms`
_(no description)_
## `!admin query resolver`
- resolver service
### `!admin query resolver destinations-cache`
Query the destinations cache
### `!admin query resolver overrides-cache`
Query the overrides cache
## `!admin query pusher`
- pusher service
### `!admin query pusher get-pushers`
- Returns all the pushers for the user
## `!admin query short`
- short service
### `!admin query short short-event-id`
_(no description)_
### `!admin query short short-room-id`
_(no description)_
## `!admin query raw`
- raw service
### `!admin query raw raw-maps`
- List database maps
### `!admin query raw raw-get`
- Raw database query
### `!admin query raw raw-del`
- Raw database delete (for string keys)
### `!admin query raw raw-keys`
- Raw database keys iteration
### `!admin query raw raw-keys-sizes`
- Raw database key size breakdown
### `!admin query raw raw-keys-total`
- Raw database keys total bytes
### `!admin query raw raw-vals-sizes`
- Raw database values size breakdown
### `!admin query raw raw-vals-total`
- Raw database values total bytes
### `!admin query raw raw-iter`
- Raw database items iteration
### `!admin query raw raw-keys-from`
- Raw database keys iteration
### `!admin query raw raw-iter-from`
- Raw database items iteration
### `!admin query raw raw-count`
- Raw database record count
### `!admin query raw compact`
- Compact database

View file

@ -0,0 +1,82 @@
# `!admin rooms`
- Commands for managing rooms
## `!admin rooms list-rooms`
- List all rooms the server knows about
## `!admin rooms info`
- View information about a room we know about
### `!admin rooms info list-joined-members`
- List joined members in a room
### `!admin rooms info view-room-topic`
- Displays room topic
Room topics can be huge, so this is in its own separate command
## `!admin rooms moderation`
- Manage moderation of remote or local rooms
### `!admin rooms moderation ban-room`
- Bans a room from local users joining and evicts all our local users (including server admins) from the room. Also blocks any invites (local and remote) for the banned room, and disables federation entirely with it
### `!admin rooms moderation ban-list-of-rooms`
- Bans a list of rooms (room IDs and room aliases) from a newline delimited codeblock similar to `user deactivate-all`. Applies the same steps as ban-room
### `!admin rooms moderation unban-room`
- Unbans a room to allow local users to join again
### `!admin rooms moderation list-banned-rooms`
- List of all rooms we have banned
## `!admin rooms alias`
- Manage rooms' aliases
### `!admin rooms alias set`
- Make an alias point to a room
### `!admin rooms alias remove`
- Remove a local alias
### `!admin rooms alias which`
- Show which room is using an alias
### `!admin rooms alias list`
- List aliases currently being used
## `!admin rooms directory`
- Manage the room directory
### `!admin rooms directory publish`
- Publish a room to the room directory
### `!admin rooms directory unpublish`
- Unpublish a room to the room directory
### `!admin rooms directory list`
- List rooms that are published
## `!admin rooms exists`
- Check if we know about a room

View file

@ -0,0 +1,52 @@
# `!admin server`
- Commands for managing the server
## `!admin server uptime`
- Time elapsed since startup
## `!admin server show-config`
- Show configuration values
## `!admin server reload-config`
- Reload configuration values
## `!admin server list-features`
- List the features built into the server
## `!admin server memory-usage`
- Print database memory usage statistics
## `!admin server clear-caches`
- Clears all of Continuwuity's caches
## `!admin server backup-database`
- Performs an online backup of the database (only available for RocksDB at the moment)
## `!admin server list-backups`
- List database backups
## `!admin server admin-notice`
- Send a message to the admin room
## `!admin server reload-mods`
- Hot-reload the server
## `!admin server restart`
- Restart the server
## `!admin server shutdown`
- Shutdown the server

View file

@ -0,0 +1,16 @@
# `!admin token`
- Commands for managing registration tokens
## `!admin token issue`
- Issue a new registration token
## `!admin token revoke`
- Revoke a registration token
## `!admin token list`
- List all registration tokens

View file

@ -0,0 +1,140 @@
# `!admin users`
- Commands for managing local users
## `!admin users create-user`
- Create a new user
## `!admin users reset-password`
- Reset user password
## `!admin users deactivate`
- Deactivate a user
User will be removed from all rooms by default. Use --no-leave-rooms to not leave all rooms by default.
## `!admin users deactivate-all`
- Deactivate a list of users
Recommended to use in conjunction with list-local-users.
Users will be removed from joined rooms by default.
Can be overridden with --no-leave-rooms.
Removing a mass amount of users from a room may cause a significant amount of leave events. The time to leave rooms may depend significantly on joined rooms and servers.
This command needs a newline separated list of users provided in a Markdown code block below the command.
## `!admin users logout`
- Forcefully log a user out of all of their devices.
This will invalidate all access tokens for the specified user, effectively logging them out from all sessions. Note that this is destructive and may result in data loss for the user, such as encryption keys. Use with caution. Can only be used in the admin room.
## `!admin users suspend`
- Suspend a user
Suspended users are able to log in, sync, and read messages, but are not able to send events nor redact them, cannot change their profile, and are unable to join, invite to, or knock on rooms.
Suspended users can still leave rooms and deactivate their account. Suspending them effectively makes them read-only.
## `!admin users unsuspend`
- Unsuspend a user
Reverses the effects of the `suspend` command, allowing the user to send messages, change their profile, create room invites, etc.
## `!admin users lock`
- Lock a user
Locked users are unable to use their accounts beyond logging out. This is akin to a temporary deactivation that does not change the user's password. This can be used to quickly prevent a user from accessing their account.
## `!admin users unlock`
- Unlock a user
Reverses the effects of the `lock` command, allowing the user to use their account again.
## `!admin users enable-login`
- Enable login for a user
## `!admin users disable-login`
- Disable login for a user
Disables login for the specified user without deactivating or locking their account. This prevents the user from obtaining new access tokens, but does not invalidate existing sessions.
## `!admin users list-users`
- List local users in the database
## `!admin users list-joined-rooms`
- Lists all the rooms (local and remote) that the specified user is joined in
## `!admin users force-join-room`
- Manually join a local user to a room
## `!admin users force-leave-room`
- Manually leave a local user from a room
## `!admin users force-leave-remote-room`
- Manually leave a remote room for a local user
## `!admin users force-demote`
- Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits
## `!admin users make-user-admin`
- Grant server-admin privileges to a user
## `!admin users put-room-tag`
- Puts a room tag for the specified user and room ID.
This is primarily useful if you'd like to set your admin room to the special "System Alerts" section in Element as a way to permanently see your admin room without it being buried away in your favourites or rooms. To do this, you would pass your user, your admin room's internal ID, and the tag name `m.server_notice`.
## `!admin users delete-room-tag`
- Deletes the room tag for the specified user and room ID
## `!admin users get-room-tags`
- Gets all the room tags for the specified user and room ID
## `!admin users redact-event`
- Attempts to forcefully redact the specified event ID from the sender user
This is only valid for local users
## `!admin users force-join-list-of-local-users`
- Force joins a specified list of local users to join the specified room.
Specify a codeblock of usernames.
At least 1 server admin must be in the room to reduce abuse.
Requires the `--yes-i-want-to-do-this` flag.
## `!admin users force-join-all-local-users`
- Force joins all local users to the specified room.
At least 1 server admin must be in the room to reduce abuse.
Requires the `--yes-i-want-to-do-this` flag.

View file

@ -1,21 +0,0 @@
# Command-Line Help for `continuwuity`
This document contains the help content for the `continuwuity` command-line program.
**Command Overview:**
* [`continuwuity`↴](#continuwuity)
## `continuwuity`
A Matrix homeserver written in Rust, the official continuation of the conduwuit homeserver.
**Usage:** `continuwuity [OPTIONS]`
###### **Options:**
* `-c`, `--config <CONFIG>` — Path to the config TOML file (optional)
* `-O`, `--option <OPTION>` — Override a configuration variable using TOML 'key=value' syntax
* `--read-only` — Run in a stricter read-only --maintenance mode
* `--maintenance` — Run in maintenance mode while refusing connections
* `--execute <EXECUTE>` — Execute console command automatically after startup

26
package-lock.json generated
View file

@ -47,7 +47,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@ -729,7 +728,6 @@
"integrity": "sha512-wf41bbFIzqQsGkrDal2eVC4cxN6II1k4bUo1g7OFuvWeEOJzjoeK4R5xxKM9g5hRjbGAJs6OiQaGpASvUnDrsw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rspack/core": "1.6.5",
"@rspack/lite-tapable": "~1.1.0",
@ -989,16 +987,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@rspack/lite-tapable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz",
"integrity": "sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/@rspack/plugin-react-refresh": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.5.3.tgz",
@ -1025,7 +1013,6 @@
"integrity": "sha512-vYbHDoAy7fjQC8hYM4rRgkrsN48CZNSFpD1WEr6lZGXDQcWWrrpFjWw3LK2OQ04bZ0OXZeqapP2rgg/jTyPaZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.1",
@ -1288,7 +1275,6 @@
"integrity": "sha512-m93nmR0iY3N9Y+9Xi2xCA0NfDnTZVYauJl2SJ9bqRhJmxFHAbWe5f4Ik3VI0gK1g3lvfQ3eBpZihkpUtxCJFBw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rspress/shared": "2.0.0-rc.1",
"@unhead/react": "^2.0.19",
@ -1423,7 +1409,6 @@
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
@ -1592,7 +1577,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -1774,7 +1758,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@ -1973,7 +1956,8 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/debug": {
"version": "4.4.3",
@ -4250,7 +4234,6 @@
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -4261,7 +4244,6 @@
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@ -4298,7 +4280,6 @@
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -4325,7 +4306,6 @@
"integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@remix-run/router": "1.23.1",
"react-router": "6.30.2"
@ -4663,6 +4643,7 @@
"integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@ -4836,7 +4817,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},

View file

@ -10,10 +10,12 @@ repository.workspace = true
version.workspace = true
[dependencies]
conduwuit-admin.workspace = true
conduwuit.workspace = true
clap.workspace = true
# Required for working with JSON output from cargo metadata
serde.workspace = true
serde_json = "1.0"
askama = "0.15.1"
cargo_metadata = "0.23.1"
[lints]
workspace = true

View file

@ -1,23 +0,0 @@
[package]
name = "xtask-generate-commands"
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true
[dependencies]
clap-markdown = "0.1.5"
clap_builder = { version = "4.5.38", default-features = false }
clap_mangen = "0.2"
conduwuit-admin.workspace = true
# Hack to prevent rebuilds
conduwuit.workspace = true
[lints]
workspace = true

View file

@ -1,113 +0,0 @@
use std::{
fs::{self, File},
io::{self, Write},
path::Path,
};
use clap_builder::{Command, CommandFactory};
use conduwuit_admin::AdminCommand;
enum CommandType {
Admin,
Server,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args().skip(1);
let command_type = args.next();
let task = args.next();
match (command_type, task) {
| (None, _) => {
return Err("Missing command type (admin or server)".into());
},
| (Some(cmd_type), None) => {
return Err(format!("Missing task for {cmd_type} command").into());
},
| (Some(cmd_type), Some(task)) => {
let command_type = match cmd_type.as_str() {
| "admin" => CommandType::Admin,
| "server" => CommandType::Server,
| _ => return Err(format!("Invalid command type: {cmd_type}").into()),
};
match task.as_str() {
| "man" => match command_type {
| CommandType::Admin => {
let dir = Path::new("./admin-man");
gen_admin_manpages(dir)?;
},
| CommandType::Server => {
let dir = Path::new("./server-man");
gen_server_manpages(dir)?;
},
},
| "md" => {
match command_type {
| CommandType::Admin => {
let command = AdminCommand::command().name("admin");
let res = clap_markdown::help_markdown_command_custom(
&command,
&clap_markdown::MarkdownOptions::default().show_footer(false),
)
.replace("\n\r", "\n")
.replace("\r\n", "\n")
.replace(" \n", "\n");
let mut file = File::create(Path::new("./docs/admin_reference.md"))?;
file.write_all(res.trim_end().as_bytes())?;
file.write_all(b"\n")?;
},
| CommandType::Server => {
// Get the server command from the conduwuit crate
let command = conduwuit::Args::command();
let res = clap_markdown::help_markdown_command_custom(
&command,
&clap_markdown::MarkdownOptions::default().show_footer(false),
)
.replace("\n\r", "\n")
.replace("\r\n", "\n")
.replace(" \n", "\n");
let mut file = File::create(Path::new("./docs/server_reference.md"))?;
file.write_all(res.trim_end().as_bytes())?;
file.write_all(b"\n")?;
},
}
},
| invalid => return Err(format!("Invalid task name: {invalid}").into()),
}
},
}
Ok(())
}
fn gen_manpage_common(dir: &Path, c: &Command, prefix: Option<&str>) -> Result<(), io::Error> {
fs::create_dir_all(dir)?;
let sub_name = c.get_display_name().unwrap_or_else(|| c.get_name());
let name = if let Some(prefix) = prefix {
format!("{prefix}-{sub_name}")
} else {
sub_name.to_owned()
};
let mut out = File::create(dir.join(format!("{name}.1")))?;
let clap_mangen = clap_mangen::Man::new(c.to_owned().disable_help_flag(true));
clap_mangen.render(&mut out)?;
for sub in c.get_subcommands() {
gen_manpage_common(&dir.join(sub_name), sub, Some(&name))?;
}
Ok(())
}
fn gen_admin_manpages(dir: &Path) -> Result<(), io::Error> {
gen_manpage_common(dir, &AdminCommand::command().name("admin"), None)
}
fn gen_server_manpages(dir: &Path) -> Result<(), io::Error> {
gen_manpage_common(dir, &conduwuit::Args::command(), None)
}

View file

@ -1,11 +0,0 @@
use std::{env, process::Command};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut child = Command::new("cargo").args(["run", "--package", "xtask-generate-commands", "--"].into_iter().map(ToOwned::to_owned).chain(env::args().skip(2)))
// .stdout(Stdio::piped())
// .stderr(Stdio::piped())
.spawn()
.expect("failed to execute child");
child.wait()?;
Ok(())
}

26
xtask/src/main.rs Normal file
View file

@ -0,0 +1,26 @@
mod tasks;
use clap::Parser;
use crate::tasks::Task;
#[derive(clap::Parser)]
struct BaseArgs {
#[command(subcommand)]
task: Task,
#[command(flatten)]
args: Args,
}
#[derive(clap::Args)]
struct Args {
/// Simulate without actually touching the filesystem
#[arg(long)]
dry_run: bool,
}
fn main() -> impl std::process::Termination {
let BaseArgs { task, args } = BaseArgs::parse();
task.invoke(args)
}

View file

@ -0,0 +1,112 @@
//! Generates documentation for the various commands that may be used in the admin room and server console.
//!
//! This generates one index page and several category pages, one for each of the direct subcommands of the top-level
//! `!admin` command. Those category pages then list all of the sub-subcommands.
use std::path::Path;
use askama::Template;
use clap::{Command, CommandFactory};
use conduwuit_admin::AdminCommand;
use crate::tasks::{TaskResult, generate_docs::FileOutput};
#[derive(askama::Template)]
#[template(path = "admin/index.md")]
/// The template for the index page, which links to all of the category pages.
struct Index {
categories: Vec<Category>
}
/// A direct subcommand of the top-level `!admin` command.
#[derive(askama::Template)]
#[template(path = "admin/category.md")]
struct Category {
name: String,
description: String,
commands: Vec<Subcommand>,
}
/// A second-or-deeper level subcommand of the `!admin` command.
struct Subcommand {
name: String,
description: String,
/// How deeply nested this command was in the original command tree.
/// This determines the header size used for it in the documentation.
depth: usize,
}
fn flatten_subcommands(command: &Command) -> Vec<Subcommand> {
let mut subcommands = Vec::new();
let mut name_stack = Vec::new();
fn flatten(
subcommands: &mut Vec<Subcommand>,
stack: &mut Vec<String>,
command: &Command
) {
let depth = stack.len();
stack.push(command.get_name().to_owned());
// do not include the root command
if depth > 0 {
let name = stack.join(" ");
let description = command
.get_long_about()
.or_else(|| command.get_about())
.map(|about| about.to_string())
.unwrap_or("_(no description)_".to_owned());
subcommands.push(
Subcommand {
name,
description,
depth,
}
);
}
for command in command.get_subcommands() {
flatten(subcommands, stack, command);
}
stack.pop();
}
flatten(&mut subcommands, &mut name_stack, command);
subcommands
}
pub(super) fn generate(out: &mut impl FileOutput) -> TaskResult<()> {
let admin_commands = AdminCommand::command();
let categories: Vec<_> = admin_commands
.get_subcommands()
.map(|command| {
Category {
name: command.get_name().to_owned(),
description: command.get_about().expect("categories should have a docstring").to_string(),
commands: flatten_subcommands(command),
}
})
.collect();
let root = Path::new("reference/admin/");
for category in &categories {
out.create_file(
root.join(&category.name).with_extension("md"),
category.render()?
);
}
out.create_file(
root.join("index.md"),
Index { categories }.render()?,
);
Ok(())
}

View file

@ -0,0 +1,67 @@
mod admin_commands;
use std::{collections::HashMap, path::{Path, PathBuf}};
use cargo_metadata::MetadataCommand;
use crate::tasks::TaskResult;
trait FileOutput {
fn create_file(&mut self, path: PathBuf, contents: String);
}
#[derive(Default)]
struct FileQueue {
queue: HashMap<PathBuf, String>,
}
impl FileQueue {
fn write(self, root: &Path, dry_run: bool) -> std::io::Result<()> {
for (path, contents) in self.queue.into_iter() {
let path = root.join(&path);
eprintln!("Writing {}", path.display());
if !dry_run {
std::fs::write(path, contents)?;
}
}
Ok(())
}
}
impl FileOutput for FileQueue {
fn create_file(&mut self, path: PathBuf, contents: String) {
assert!(path.is_relative(), "path must be relative");
assert!(path.extension().is_some(), "path must not point to a directory");
if self.queue.contains_key(&path) {
panic!("attempted to create an already created file {}", path.display());
}
self.queue.insert(path, contents);
}
}
#[derive(clap::Args)]
pub(crate) struct Args {
/// The base path of the documentation. Defaults to `docs/` in the crate root.
root: Option<PathBuf>,
}
pub(super) fn run(common_args: crate::Args, task_args: Args) -> TaskResult<()> {
let mut queue = FileQueue::default();
let metadata = MetadataCommand::new()
.no_deps()
.exec()
.expect("should have been able to run cargo");
let root = task_args.root.unwrap_or_else(|| metadata.workspace_root.join_os("docs/"));
admin_commands::generate(&mut queue)?;
queue.write(&root, common_args.dry_run)?;
Ok(())
}

37
xtask/src/tasks/mod.rs Normal file
View file

@ -0,0 +1,37 @@
type TaskResult<T> = Result<T, Box<dyn std::error::Error>>;
#[macro_export]
macro_rules! tasks {
(
$(
$module:ident: $desc:literal
),*
) => {
$(pub(super) mod $module;)*
#[derive(clap::Subcommand)]
#[allow(non_camel_case_types)]
pub(super) enum Task {
$(
#[clap(about = $desc, long_about = None)]
$module($module::Args),
)*
}
impl Task {
pub(super) fn invoke(self, common_args: $crate::Args) -> TaskResult<impl std::process::Termination> {
match self {
$(
Self::$module(task_args) => {
$module::run(common_args, task_args)
},
)*
}
}
}
};
}
tasks! {
generate_docs: "Generate various documentation files. This is run automatically when compiling the website."
}

View file

@ -0,0 +1,10 @@
# `!admin {{ name }}`
{{ description }}
{% for command in commands %}
{% let header = "#".repeat((command.depth + 1).min(3)) -%}
{{ header }} `!admin {{ command.name }}`
{{ command.description }}
{% endfor %}

View file

@ -0,0 +1,7 @@
# Admin Commands
These are all the admin commands. TODO fill me out
{%~ for category in categories %}
- [`!admin {{ category.name }}`]({{ category.name }}/) {{ category.description }}
{%- endfor %}