Skip to content

feat(api): add draft Utoipa scheme#1726

Draft
f18m wants to merge 4 commits intorustic-rs:mainfrom
f18m:rustic-serve-cmd
Draft

feat(api): add draft Utoipa scheme#1726
f18m wants to merge 4 commits intorustic-rs:mainfrom
f18m:rustic-serve-cmd

Conversation

@f18m
Copy link
Copy Markdown

@f18m f18m commented Apr 8, 2026

Submit first draft of API for a "serve" command.
See #1714 for full context

@f18m f18m marked this pull request as draft April 8, 2026 17:02
@aawsome
Copy link
Copy Markdown
Member

aawsome commented Apr 9, 2026

Hi @f18m, thanks a lot for the proposal!

Now, I must say there is something I actually don't like about this proposal: The serve command here does basically save some payload into a temporary file and then calls some command. This is actually nothing rustic-specific and I would claim if someone needs such a solution it is either fitted better in a separate tool and most likely such tool already exists. (at least there is ssh which can be used as a non-webservice solution for this).

I was thinking about a more integrated serve command. Something like:

  • read the config from a local toml
  • maybe allow to overwrite/extend the config by a specific API call (payload could be in toml here).
  • for the backup API-call, directly call the backup command on the currently used repo (note that RusticRepository is clone()able) instead of spawing a new binary command.
  • (future extension) maybe even think about keeping the in-memory index and list of snapshots instead of re-reading it for every call. This would result in a much faster backup startup.
  • (going more into the future) this could be even used to efficiently run commands in a batch like backup followed by merge and forget - which is a feature request of some users.

What do you think @f18m ?

@f18m
Copy link
Copy Markdown
Author

f18m commented Apr 9, 2026

Now, I must say there is something I actually don't like about this proposal: The serve command here does basically save some payload into a temporary file and then calls some command.

Yeah sorry about that; consider the code is 99% AI generated.
I didn't get to the spawn_backup_job() part yet. I totally agree it's non-sense to spawn the rustic binary from... the rustic binary itself.

The reason for me to open this PR is to get feedback about the form/format of the HTTP POST.
Questions like:

  1. do we want to have versioned APIs, so e.g. have /v1/backup instead of just /backup ?
  2. do we want the HTTP endpoints to match the same command name of the CLI; e.g. I choose backup because today we have rustic backup as command. I think it could be a good choice but up for comments
  3. do we want the HTTP POST to accept TOML payloads? This is where I mostly got "stuck": I had an hard time finding other tools that take an HTTP POST payloads in TOML format. I didn't find any actually. JSON/protobufs are the real standards over HTTP.
    OTOH I believe that accepting a TOML payload body would help users to switch back and forth between the CLI syntax/usage and the HTTP network API. Moreover all the documentation is written assuming TOML configurations.
    An alternative option would be to have a JSON schema automatically generated by the TOML schema.

I basically stopped at point 3 and wanted to get your feedback.

I was thinking about a more integrated serve command. Something like:

  • read the config from a local toml
    yep
  • maybe allow to overwrite/extend the config by a specific API call (payload could be in toml here).

see my question #3 above.
But generally speaking I like the idea of merging the options coming via HTTP with those loaded normally by rustic.
The precedence (taking inspiration from https://github.com/rustic-rs/rustic/tree/main/config#merge-precedence ) would be :

HTTP Payload >> Commandline Arguments >> Environment Variables >> Configuration Profile

WDYT ?

  • for the backup API-call, directly call the backup command on the currently used repo (note that RusticRepository is clone()able) instead of spawing a new binary command.

will do

  • (future extension) maybe even think about keeping the in-memory index and list of snapshots instead of re-reading it for every call. This would result in a much faster backup startup.
  • (going more into the future) this could be even used to efficiently run commands in a batch like backup followed by merge and forget - which is a feature request of some users.

yep all good ideas. I have no clue how to implement them in Rust though, as it's not part of my expertise. Will see if Copilot/Claude can create at least a good starting proposal

What do you think @f18m ?

thanks for fast feedback!

@aawsome
Copy link
Copy Markdown
Member

aawsome commented Apr 10, 2026

  • do we want to have versioned APIs, so e.g. have /v1/backup instead of just /backup ?

From my side, we currently don't need a versioned API. Also the "CLI API" is not versioned - and yes, we may break compatibility with newer rustic versions, but IMO this is ok, currently.
We may think about a versioned API once we release a 1.0 version of rustic ;-)

  • do we want the HTTP endpoints to match the same command name of the CLI; e.g. I choose backup because today we have rustic backup as command. I think it could be a good choice but up for comments

Yes, of course!

  • do we want the HTTP POST to accept TOML payloads? This is where I mostly got "stuck": I had an hard time finding other tools that take an HTTP POST payloads in TOML format. I didn't find any actually. JSON/protobufs are the real standards over HTTP.
    OTOH I believe that accepting a TOML payload body would help users to switch back and forth between the CLI syntax/usage and the HTTP network API. Moreover all the documentation is written assuming TOML configurations.
    An alternative option would be to have a JSON schema automatically generated by the TOML schema.

I think we should go with normal JSON payload for the commands. However, there might be a command like update-profile which allows to update the config profile which is normally read from CLI-arguments and the config profile. Here I'd prefer a TOML payload. We could however just start with a simple backup command in the API and leave this to a future PR.

The precedence (taking inspiration from https://github.com/rustic-rs/rustic/tree/main/config#merge-precedence ) would be :
HTTP Payload >> Commandline Arguments >> Environment Variables >> Configuration Profile

Yes, like this, but as written above, I would leave the config-update to a single command and have a quite slim API for normal command calling where this precedence is no topic as only the already loaded config is taken.

@f18m
Copy link
Copy Markdown
Author

f18m commented Apr 13, 2026

Hi @aawsome ,
Update on this PR attempt: I've tried a few different approaches, so far with no real success.
In summary the problem I'm hitting is the fact that current rustic implementation is (obviously!) very much focused on a CLI execution style:

  • The code often uses global variables like e.g. RUSTIC_APP; using global variables from a network server is not generally speaking a good idea (the server should be able to handle multiple requests in parallel -- even if that means accepting only the first one and rejecting all the other ones that come while some operation is still in progress);
  • The code very often invokes RUSTIC_APP.shutdown in cases where some error happened. This is not so good for a server; the expectation is that the server returns an error to the client, instead of shutting down

So it turns out that to implement any server in Rustic (the same would apply if e.g. instead of an HTTP server we attempted to build a gRPC server or whatever other server), there is some refactor involved.
So... the way to go is difficult but not impossible.
Here would be a plan, originally proposed by AI, which I reviewed and partially changed:

Refactor Plan (shared backup core for CLI + HTTP)

Targets

  • Create a backup core service that returns Result and never calls shutdown.
  • Keep Abscissa Runnable only as an adapter layer for CLI.
  • Keep HTTP handler as another adapter layer.
  • Scope PR only to backup first, not all commands.

Steps

  1. Introduce an explicit execution context object.
  • Add a context struct in a new module, for example rustic/src/services/backup_core.rs.
  • The context carries everything currently pulled from globals: effective config, progress options, dry-run/check-index behavior, snapshot filters, hooks policy.
  • Goal: core code takes this context as input and does not touch RUSTIC_APP.
  1. Extract backup business logic from command methods.
  • Move logic currently split across BackupCmd methods in rustic/src/commands/backup.rs into pure functions on the new backup_core service.
  • Keep parsing/CLI-specific fields in BackupCmd, but convert BackupCmd into a request DTO for the service.
  • Make current BackupCmd run function become a thin wrapper: build context from RUSTIC_APP, call service, map error to status/shutdown.
  1. Remove backup-path dependencies on globals in repository helpers.
  • Replace RUSTIC_APP.config reads used during backup execution in rustic/src/repository.rs with explicit parameters.
  • Specifically pass values currently fetched implicitly: progress options, check-index flag, snapshot filter/grouping inputs.
  • Keep old helper signatures temporarily as compatibility wrappers if needed, but migrate backup to explicit APIs.
  1. Build HTTP-specific config assembly path.
  • In rustic/src/commands/serve/api.rs, construct an effective config per request from base server config plus request overrides/profile payload.
  • Do not mutate global app config in request path.
  • Call shared backup service (backup_core) and map errors to HTTP responses (400 for validation, 500 for runtime failures).
  1. Make async boundary explicit.
  • Because backup is blocking and heavy, run the shared sync service in a blocking task from HTTP handler.
  • Keep service itself sync and deterministic
  1. Standardize error model between CLI and HTTP.
  • Introduce a typed error enum for backup service (validation, repository-open, snapshot-failed, hook-failed).
  • CLI adapter prints and exits.
  • HTTP adapter serializes into ApiErrorResponse without terminating server.
  1. Add parity tests before and after extraction.
  • Add service-level tests for validation and snapshot selection parity with current behavior.
  • Add CLI regression tests to ensure existing backup command behavior remains unchanged.
  • Add HTTP tests for success and failure mapping in rustic/src/commands/serve/api.rs.
  1. Deliver in staged PRs to reduce risk.
  • PR1: Introduce backup_core service + CLI adapter migration, no HTTP usage yet.
  • PR2: Add an HTTP handler + blocking execution + error mapping.
  • PR3: Cleanup compatibility wrappers.

Notes

Address full app-wide decoupling later:

  • Do not attempt to remove RUSTIC_APP from all commands now.
  • Limit refactor radius to backup vertical slice plus repository surfaces needed by backup.
  • This keeps the change large but tractable.

So now the big question: WDYT about that?
Would you accept a series of staged PRs implementing above plan?
Thanks for the time to read this long comment :)

@f18m
Copy link
Copy Markdown
Author

f18m commented Apr 16, 2026

@aawsome ping on above msg :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants