Send-http-json-urls produces mysterious warnings

I add the option send-http-json-urls under the global settings with value http://192.168…:8075/Backups

Any backup I now run produces two warnings. But asking Duplicati to show them produces no results so I have no idea what these warnings are..

Checking the backup log for the job shows no warnings.

Checking About/Show Log shows nothing.

The Docker log for the container shows nothing

The log for the destination application shows nothing; it looks like the selected endpoint isn’t called.

If I remove the option the warnings persist. I have to restart the Duplicati container for the warnings to go away!

If that means clicking Show on the Warning box gives you a successful log, that’s because there was a successful backup. Any issue after the backup is not present in backup results.

What about About → Show log → Live → Warning, then backup, then click any warnings?

Don’t hurt it, but can it show other things? Do you know curl enough to test it, maybe try

curl --request POST http://192.168…:8075/Backups --data testing

You could do something like arp -n to see if IP has been needed recently. Other options include netstat -an within a few minutes, or lsof -i with remote endpoint information.

Testing with https://webhook-test.com/ (although on a newer Duplicati), this works fine.

Attempting to test using rclone serve http, I think I found bugs in that and in Duplicati…

Something related send-http ?

I thought rclone bug or my possible misconfiguration caused a bug in Duplicati’s send.

rclone serve http C:\rclone --addr 127.0.0.1:8075

--send-http-json-urls=http://127.0.0.1:8075

Results are erratic, but testing varied. Here’s rclone rejecting send, upsetting Duplicati:

2025-04-20 17:10:35 -04 - [Verbose-Duplicati.Library.Modules.Builtin.SendHttpMessage-HttpResponseMessage]: HTTP Response to http://127.0.0.1:8075: 405 - Method Not Allowed: Method Not Allowed

2025-04-20 17:10:35 -04 - [Warning-Duplicati.Library.Modules.Builtin.SendHttpMessage-HttpResponseError]: HTTP Response request attempt 1 of 3 failed for: http://127.0.0.1:8075
System.Net.Http.HttpRequestException: Response status code does not indicate success: 405 (Method Not Allowed).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.<>c__DisplayClass62_0.<<SendMessage>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.<>c__DisplayClass2_0.<<Retry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.Retry[T](Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
2025-04-20 17:10:36 -04 - [Warning-Duplicati.Library.Modules.Builtin.SendHttpMessage-HttpResponseError]: HTTP Response request attempt 2 of 3 failed for: http://127.0.0.1:8075
System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.
   at System.Net.Http.HttpClient.CheckRequestMessage(HttpRequestMessage request)
   at System.Net.Http.HttpClient.CheckRequestBeforeSend(HttpRequestMessage request)
   at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.<>c__DisplayClass62_0.<<SendMessage>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.<>c__DisplayClass2_0.<<Retry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.Retry[T](Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
2025-04-20 17:10:37 -04 - [Warning-Duplicati.Library.Modules.Builtin.SendHttpMessage-HttpResponseError]: HTTP Response request attempt 3 of 3 failed for: http://127.0.0.1:8075
System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.
   at System.Net.Http.HttpClient.CheckRequestMessage(HttpRequestMessage request)
   at System.Net.Http.HttpClient.CheckRequestBeforeSend(HttpRequestMessage request)
   at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.<>c__DisplayClass62_0.<<SendMessage>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.<>c__DisplayClass2_0.<<Retry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.Retry[T](Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
2025-04-20 17:10:37 -04 - [Warning-Duplicati.Library.Modules.Builtin.ReportHelper-ReportSubmitError]: Failed to send message: System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.

System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.
   at System.Net.Http.HttpClient.CheckRequestMessage(HttpRequestMessage request)
   at System.Net.Http.HttpClient.CheckRequestBeforeSend(HttpRequestMessage request)
   at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.<>c__DisplayClass62_0.<<SendMessage>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.<>c__DisplayClass2_0.<<Retry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.Retry[T](Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
   at Duplicati.Library.Utility.RetryHelper.Retry(Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.SendMessage(HttpClient client, SendRequestType target, String subject, String body)
   at Duplicati.Library.Utility.Utility.Await[T](Task`1 task)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.SendMessage(String subject, String body)
   at Duplicati.Library.Modules.Builtin.ReportHelper.OnFinish(IBasicResults result, Exception exception)
2025-04-20 17:10:37 -04 - [Warning-Duplicati.Library.Modules.Builtin.ReportHelper-ReportSubmitError]: Failed to send message: System.NullReferenceException: Object reference not set to an instance of an object.

System.NullReferenceException: Object reference not set to an instance of an object.
   at Duplicati.Library.Modules.Builtin.SendMail.SendMessage(String subject, String body)
   at Duplicati.Library.Modules.Builtin.ReportHelper.OnFinish(IBasicResults result, Exception exception)
2025-04-20 17:10:37 -04 - [Information-Duplicati.Library.Main.Controller-CompletedOperation]: The operation Backup has completed

Here’s one with rclone down, so Duplicati is instantly refused on its connection request:

System.Net.Http.HttpRequestException: No connection could be made because the target machine actively refused it. (127.0.0.1:8075)
 ---> System.Net.Sockets.SocketException (10061): No connection could be made because the target machine actively refused it.
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem)
   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.<>c__DisplayClass62_0.<<SendMessage>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.<>c__DisplayClass2_0.<<Retry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.Retry[T](Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
2025-04-20 17:02:48 -04 - [Warning-Duplicati.Library.Modules.Builtin.SendHttpMessage-HttpResponseError]: HTTP Response request attempt 2 of 3 failed for: http://127.0.0.1:8075
System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.
   at System.Net.Http.HttpClient.CheckRequestMessage(HttpRequestMessage request)
   at System.Net.Http.HttpClient.CheckRequestBeforeSend(HttpRequestMessage request)
   at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.<>c__DisplayClass62_0.<<SendMessage>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.<>c__DisplayClass2_0.<<Retry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.Retry[T](Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
2025-04-20 17:02:49 -04 - [Warning-Duplicati.Library.Modules.Builtin.SendHttpMessage-HttpResponseError]: HTTP Response request attempt 3 of 3 failed for: http://127.0.0.1:8075
System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.
   at System.Net.Http.HttpClient.CheckRequestMessage(HttpRequestMessage request)
   at System.Net.Http.HttpClient.CheckRequestBeforeSend(HttpRequestMessage request)
   at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.<>c__DisplayClass62_0.<<SendMessage>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.<>c__DisplayClass2_0.<<Retry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.Retry[T](Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
2025-04-20 17:02:49 -04 - [Warning-Duplicati.Library.Modules.Builtin.ReportHelper-ReportSubmitError]: Failed to send message: System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.

System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.
   at System.Net.Http.HttpClient.CheckRequestMessage(HttpRequestMessage request)
   at System.Net.Http.HttpClient.CheckRequestBeforeSend(HttpRequestMessage request)
   at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.<>c__DisplayClass62_0.<<SendMessage>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.<>c__DisplayClass2_0.<<Retry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Duplicati.Library.Utility.RetryHelper.Retry[T](Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
   at Duplicati.Library.Utility.RetryHelper.Retry(Func`1 action, Action`2 errorCallback, Int32 maxRetries, TimeSpan delay, CancellationToken token)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.SendMessage(HttpClient client, SendRequestType target, String subject, String body)
   at Duplicati.Library.Utility.Utility.Await[T](Task`1 task)
   at Duplicati.Library.Modules.Builtin.SendHttpMessage.SendMessage(String subject, String body)
   at Duplicati.Library.Modules.Builtin.ReportHelper.OnFinish(IBasicResults result, Exception exception)
2025-04-20 17:02:50 -04 - [Warning-Duplicati.Library.Modules.Builtin.ReportHelper-ReportSubmitError]: Failed to send message: System.NullReferenceException: Object reference not set to an instance of an object.

System.NullReferenceException: Object reference not set to an instance of an object.
   at Duplicati.Library.Modules.Builtin.SendMail.SendMessage(String subject, String body)
   at Duplicati.Library.Modules.Builtin.ReportHelper.OnFinish(IBasicResults result, Exception exception)
2025-04-20 17:02:50 -04 - [Information-Duplicati.Library.Main.Controller-CompletedOperation]: The operation Backup has completed

EDIT 1:

Of course this is a log-file (which I always have), but the live log was also showing the maybe-expected retries, but then also the probably-not-correct NullReferenceException.

This is Windows and 2.1.0.112, just something convenient at the time, so I did some tests.

EDIT 2:

I never did figure out how to get rclone to stop giving 405 Method Not Allowed. I tried an rclone web server because I didn’t have anything else handy except maybe just nc -l.

Then I went looking for a suitably free-and-easy webhook test site. This was the first I tried.

Perfect thanks I will try to replicate that tomorrow & get it sorted.

I’m now noticing that the log actually shows SendMail for the final failure.
There’s nothing for sendmail in either the job or Settings Default options.

For the original issue, maybe we’ll get some live log or other test results.

EDIT 1:

Finding the 2.1.0.112 release note saying it added retries, I tried 2.1.0.5:

Live log with rclone down:

Apr 20, 2025 10:32 PM: Failed to send message: System.Net.Http.HttpRequestException: No connection could be made because the target machine actively refused it. (127.0.0.1:8075) --> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it.
Apr 20, 2025 10:32 PM: HTTP Response request failed for: http://127.0.0.1:8075

and with rclone up (but rejecting):

Apr 20, 2025 10:40 PM: Failed to send message: System.Net.Http.HttpRequestException: Response status code does not indicate success: 405 (Method Not Allowed).
Apr 20, 2025 10:40 PM: HTTP Response request failed for: http://127.0.0.1:8075

It’s not trying SendMail, so this might be as good as it gets with remote errors.

I ran a https://webhook-test.com test again, and again everything was fine.

Thanks for the suggestions. Looking at the Live logs provided the information. It shows the destination rejecting the request with 400 - Bad Request: {“code”:40014,“http”:400,“error”:“invalid request: attachments not allowed”. So now I need to work out what Duplicati is sending as an attachment…

But what’s curious is it still says this AFTER I have removed the option. So it looks like once you add this option Duplicati sets some configuration somewhere that is not removed if you subsequently remove the option which is presumably a bug.

So it looks like Duplicati is sending the JSON as an attachment rather than as the body of the message which is not what I expected. Is there a way to tell the app to just use the body. I’m running ntfy at the other end, and being informed I have an attachment each time isn’t much use.

Probably simplest to just ignore the send-http options and just write a script. That way it’s under my control and not dependent on options that aren’t quite what I want. Thanks for the suggestion to check the live log which helped a lot.

1 Like

Doubtful, in my view. Are you an HTTP expert? I’m not, but here’s a sample packet trace:

POST / HTTP/1.1 Host: 127.0.0.1:8075 Content-Type: application/json Content-Length: 3031

{"Data":{"DeletedFiles":0,"DeletedFolders":0,"ModifiedFiles":0,"ExaminedFiles":1,"OpenedFiles":0,"AddedFiles":0,"SizeOfModifiedFiles":0,"SizeOfAddedFiles":0,"SizeOfExaminedFiles":1,"SizeOfOpenedFiles":0,"NotProcessedFiles":0,"AddedFolders":0,"TooLargeFiles":0,"FilesWithError":0,"ModifiedFolders":0,"ModifiedSymlinks":0,"AddedSymlinks":0,"DeletedSymlinks":0,"PartialBackup":false,"Dryrun":false,"MainOperation":"Backup","CompactResults":null,"VacuumResults":null,"DeleteResults":null,"RepairResults":null,"TestResults":{"MainOperation":"Test","VerificationsActualLength":3,"Verifications":[{"Key":"duplicati-20250414T145746Z.dlist.zip","Value":[]},{"Key":"duplicati-i7c3ec29dd67740a1a46520d1805bc23f.dindex.zip","Value":[]},{"Key":"duplicati-b9fabbf0228e54f8c8cd90642b7970606.dblock.zip","Value":[]}],"ParsedResult":"Success","Interrupted":false,"Version":"2.1.0.5 (2.1.0.5_stable_2025-03-04)","EndTime":"2025-04-21T12:27:47.7638168Z","BeginTime":"2025-04-21T12:27:47.5243961Z","Duration":"00:00:00.2394207","MessagesActualLength":0,"WarningsActualLength":0,"ErrorsActualLength":0,"BackendStatistics":{"RemoteCalls":5,"BytesUploaded":0,"BytesDownloaded":1968,"FilesUploaded":0,"FilesDownloaded":3,"FilesDeleted":0,"FoldersCreated":0,"RetryAttempts":0,"UnknownFileSize":0,"UnknownFileCount":0,"KnownFileCount":3,"KnownFileSize":1968,"LastBackupDate":"2025-04-14T10:57:46-04:00","BackupListCount":1,"TotalQuotaSpace":999618043904,"FreeQuotaSpace":14353993728,"AssignedQuotaSpace":-1,"ReportedQuotaError":false,"ReportedQuotaWarning":false,"MainOperation":"Backup","ParsedResult":"Success","Interrupted":false,"Version":"2.1.0.5 (2.1.0.5_stable_2025-03-04)","EndTime":"0001-01-01T00:00:00","BeginTime":"2025-04-21T12:27:46.9696534Z","Duration":"00:00:00","MessagesActualLength":0,"WarningsActualLength":0,"ErrorsActualLength":0}},"ParsedResult":"Success","Interrupted":false,"Version":"2.1.0.5 (2.1.0.5_stable_2025-03-04)","EndTime":"2025-04-21T12:27:48.0722677Z","BeginTime":"2025-04-21T12:27:46.9696487Z","Duration":"00:00:01.1026190","MessagesActualLength":12,"WarningsActualLength":0,"ErrorsActualLength":0,"BackendStatistics":{"RemoteCalls":5,"BytesUploaded":0,"BytesDownloaded":1968,"FilesUploaded":0,"FilesDownloaded":3,"FilesDeleted":0,"FoldersCreated":0,"RetryAttempts":0,"UnknownFileSize":0,"UnknownFileCount":0,"KnownFileCount":3,"KnownFileSize":1968,"LastBackupDate":"2025-04-14T10:57:46-04:00","BackupListCount":1,"TotalQuotaSpace":999618043904,"FreeQuotaSpace":14353993728,"AssignedQuotaSpace":-1,"ReportedQuotaError":false,"ReportedQuotaWarning":false,"MainOperation":"Backup","ParsedResult":"Success","Interrupted":false,"Version":"2.1.0.5 (2.1.0.5_stable_2025-03-04)","EndTime":"0001-01-01T00:00:00","BeginTime":"2025-04-21T12:27:46.9696534Z","Duration":"00:00:00","MessagesActualLength":0,"WarningsActualLength":0,"ErrorsActualLength":0}},"Extra":{"OperationName":"Backup","machine-id":"5719973b9fa74b9d93d70300e8a5fd58","machine-name":"HP4","backup-name":"test 4","backup-id":"DB-7"},"LogLines":[],"Exception":null}

To my not-highly-HTTP-trained eye, that looks like the JSON got sent as the body.

Attach local file in ntfy manual has curl and HTTP examples that look different.

curl -T hello.txt -H "Filename: hello.txt" 127.0.0.1:8075 --trace-ascii log.txt

sends

0000: PUT /hello.txt HTTP/1.1
0019: Host: 127.0.0.1:8075
002f: User-Agent: curl/8.9.1
0047: Accept: */*
0054: Filename: hello.txt
0069: Content-Length: 5
007c:
=> Send data, 5 bytes (0x5)
0000: Hello

with the most obvious clue (to me) of attachment status being Filename header sent.
That looks like case 4 in the ntfy comment. You might be hitting listed message limit:

Message limits says how to raise the message limit, but also advises against doing so.

4096 bytes isn’t a lot, especially if JSON size is enlarged by whatever else might get in.
Surprisingly to me, it doesn’t list its Messages. I didn’t test Warnings. I see Verifications.

Scripts would be a way out, if this is a length limit, as you can make output to fit in that.

1 Like

When I enabled attachments in ntfy, which is what is running on the other end, the message got through OK (so it’s not a length problem) and if I opened the attachment I could see the JSON. But as receiving the JSON as an attachment wasn’t much good to me it was easier to just use scripts where I have full control of anything I send.

That’s not disproving a length problem, but appears to be entirely consistent with one:

Limitations

Message length Each message can be up to 4,096 bytes long. Longer messages are treated as attachments.

How big is it? If oversized, then that’s why it’s now an attachment. If less, I’m stumped, however I’d ask what else in the network rewrites HTTP (e.g. proxy), or if nothing does.

If it’s not length, then maybe it’s one of the other factors in the ntfy source that I cited.

If your JSON is within size limit, then you can also try sending it in with curl to see if it results in a perceived attachment. If it does not, then a --trace-ascii may reveal how.

I still think the “attachment” is just how ntfy perceives what Duplicati didn’t send as one, however I’m not finding firm rules on how one is sent, so apps might interpret as they like, which is how decides whether the body is an attachment or the message. sounds.

I didn’t send any custom JSON. It was just what Duplicati sends by default. I just had one option, the send-http-json-urls option. Maybe the default is greater than 4096 bytes. I wouldn’t worry about it. The script mechanism is a lot more flexible, and works well. it’s perhaps worth checking why errors persist even after the option is removed, but that’s not worth worrying about.

The length varies depending on what has to be said. I’ve seen some larger than 4096.

Reporting options shows more. Custom message content can let you shorten the send.

I can see that one without Docker, on Windows. Remove, Save, backup, Warning.
Can’t see it in the GUI or the server database, yet it seems there until restart. Odd.

Maybe development can look at it along with some of the oddities I saw in Canary.
Repro now was on 2.1.0.5. Thanks for noticing that config seemingly isn’t effective.

There’s quite a lot of information available to pick from (per the cited documentation).
Custom message content as cited is more limited, but use whichever way you prefer.

Hopefully you can put something together that’s to your liking and suits your systems.