Trying to make a new rock-on for Seafile

It seems like the documentation for rock-ons is more user-facing than developer-oriented, and the closest forum topic I found on this is two years old.

I’m trying to package Seafile, which already has an official docker container available as detailed here. I saw a link to the rockstor rock-on registry, which was just a git link with a bunch of short JSON files in it. I looked at one of the owncloud JSON files, and it looks like there’s not much to it. Is that really all that’s required? If so it seems like low-hanging fruit to integrate Seafile into Rockstor. Or is there some step I’ve missed?

(ack, I had more links in this to the documentation, to the registry, and to the json file but it won’t let me because I’m a new user. Just imagine that the links are there; I definitely did try to RTFM first)

@TexasDex Hello again.

Have you seen the README.md at the Rockon-registry:

and our

Yes, essentially. Rock-ons are just UI informing wrappers for Rockstor so that it knows what questions to pose during an install and to display as intended within the Rockstor Web-UI.

I’m assuming here that this is the README.md, in which case my above is redundant. Once a few posts have been made to the forum you can add links.

See how you get on with that readme and also note that we do have within the Community Contributions doc a Developers subsection but that is far more than is needed for submitting Rock-ons. All that you should need is in that first referenced readme I think. And as you have already found, there are a bunch of examples in that repository.

Essentially, when a user clicks the “Update” button, after having configured their Rock-on root the system will grab those json files from that repository and present them as install options.

Hope that helps and although it may not be relevant, we do have a pending pull request open on that readme by recent rockstor-core contributor @Flox who has just added the ability to define --device targets during a Rock-on install:

As mentioned by @TexasDex, it is true that there is no mention of “Rock-ons” in the Community Contributions section of the doc. Maybe we could add a brief subsection there about “Contributing to Rock-ons” pointing to the Rockon-registry Readme? I can start a draft if relevant.

1 Like

@Flox

That would be dandy. Thanks.

Remember that when editing the docs we have to ensure they compile OK with Sphinx after the changes. I have some old pr’s there that indicate how I’ve done it in the past. That there rst stuff can be quite fussy.

Thanks for stepping up for this one. And as you say, if most of the available info is already in the rockon-registry README.md we need only link to that. I’m conscious of trying not to over complicate the rock-on submission process, as it’s usually on a single file, but then we have already had a few submissions for different rock-ons all on the same branch so probably best if we have a little more git context in the docs for the need to keep each pull request on it’s own specially named branch.

Cheers.

Somehow I missed the README.md file in that registry.

I got a preliminary file–basically just a search-and-replaced version of the one for OwnCloud–and put it in the right place, it showed up and after a few minor fixes it appeared in the list and successfully installed.

{
    "Seafile-Official": {
        "containers": {
            "seafile": {
                "image": "seafileltd/seafile",
                "launch_order": 1,
                "ports": {
                    "80": {
                        "description": "Seafile WebUI port. Suggested default: 8080",
                        "host_default": 8080,
                        "label": "WebUI port",
                        "ui": true
                    }
                },
                "volumes": {
                    "/shared": {
                        "description": "Choose a Share for Seafile. Eg: create a Share called seafile-all for this purpose alone.",
                        "label": "Storage",
                        "min_size": 1073741824
                    }
                }
            }
        },
            "version": "latest",
        "description": "Secure file sharing and hosting",
        "icon": "https://avatars2.githubusercontent.com/u/1948782?s=400&v=4",
        "more_info": "<p>Default username for your Seafile UI is<code>me@example.com</code>and password is<code>asecret</code></p>",
        "ui": {
            "slug": ""
        },
        "website": "https://seafile.org/"
    }
}

BUT it doesn’t actually work.

I ran the docker container using a normal Linux command, for testing. This works:

docker run -d --name seafile -e SEAFILE_SERVER_HOSTNAME=seafile.example.com -v /opt/seafile-data:/shared -p 8080:80 seafileltd/seafile:latest

I can access the web UI for Seafile at http://rockstor-test:8080

But when I try the rock-on, I get a 502 gateway error. It seems like one of the components isn’t starting correctly. Additionally, when I try to docker exec -it seafile /bin/bash it’s killed randomly, usually after a few seconds (the non-rock-on container doesn’t have that issue either).

What is the difference between a normal docker run and a rock-on start?

Hi @TexasDex,

Glad you found what you were looking for!
I can’t test your Json right now but upon a quick look at it I can see a few differences with the cli command you said work; I will use the latter as a reference as you said it works.

The most important one, I believe, may probably relate to the environment variable. You need to add the environment variable SEAFILE_SERVER_HOSTNAME to you JSON. To do that, you simply need to add an “environment” object to the JSon. You can find how to do that in the Readme and use another rockon Json as illustration.

Regarding what is currently faulting, it appears the container keeps restarting as this would explain why your bash shell within the container gets killed after a few seemingly random seconds. It may be helpful to stop the container (either the Rock-on from the webui, of docker stop seafile and look at the logs docker logs seafile. Alternatively, without stopping the container, you can monitor the system journal journalctl -f. If I’m correct this should show you what’s wrong with the container.

Sorry for not being more helpful right now… I’ll try to give it a test when I can.

One more thing, would you mind creating an issue in the rockons registry repo (https://github.com/rockstor/rockon-registry/issues)? This way we’ll be able to easily center and track the creation and later submission of this RockOn.

1 Like

Issue created.

You’re right, the docker container was restarting over and over. The docker logs are just an endless stream of this:

Starting seafile server, please wait ...
Seafile server started
Done.
Starting seahub at port 8000 ...
Error:Seahub failed to start.
Please try to run "./seahub.sh start" again
Sep 25 11:42:14 d016456cf398 syslog-ng[23]: syslog-ng shutting down; version='3.5.6'
Sep 25 11:42:14 d016456cf398 syslog-ng[23]: EOF on control channel, closing connection;
Sep 25 11:42:16 d016456cf398 syslog-ng[23]: syslog-ng starting up; version='3.5.6'
Sep 25 11:42:17 d016456cf398 syslog-ng[23]: EOF on control channel, closing connection;
Sep 25 11:42:17 d016456cf398 cron[37]: (CRON) INFO (pidfile fd = 3)
Sep 25 11:42:17 d016456cf398 cron[37]: (CRON) INFO (Skipping @reboot jobs -- not system startup)
[09/25/18 11:42:19] ../common/session.c(132): using config file /opt/seafile/conf/ccnet.conf
Starting seafile server, please wait ...
Seafile server started
Done.
Starting seahub at port 8000 ...
Error:Seahub failed to start.
Please try to run "./seahub.sh start" again
Sep 25 11:42:31 d016456cf398 syslog-ng[23]: syslog-ng shutting down; version='3.5.6'
Sep 25 11:42:31 d016456cf398 syslog-ng[23]: EOF on control channel, closing connection;
Sep 25 11:42:32 d016456cf398 syslog-ng[23]: syslog-ng starting up; version='3.5.6'
Sep 25 11:42:33 d016456cf398 syslog-ng[23]: EOF on control channel, closing connection;
Sep 25 11:42:33 d016456cf398 cron[38]: (CRON) INFO (pidfile fd = 3)
Sep 25 11:42:33 d016456cf398 cron[38]: (CRON) INFO (Skipping @reboot jobs -- not system startup)
[09/25/18 11:42:33] ../common/session.c(132): using config file /opt/seafile/conf/ccnet.conf
Starting seafile server, please wait ...

journalctl -f gives:

Sep 25 12:05:06 rockstor-test dockerd[11041]: Seafile server started
Sep 25 12:05:06 rockstor-test dockerd[11041]: 
Sep 25 12:05:06 rockstor-test dockerd[11041]: Done.
Sep 25 12:05:06 rockstor-test dockerd[11041]: 
Sep 25 12:05:06 rockstor-test dockerd[11041]: Starting seahub at port 8000 ...
Sep 25 12:05:12 rockstor-test dockerd[11041]: Error:Seahub failed to start.
Sep 25 12:05:12 rockstor-test dockerd[11041]: Please try to run "./seahub.sh start" again
Sep 25 12:05:12 rockstor-test dockerd[11041]: Traceback (most recent call last):
Sep 25 12:05:12 rockstor-test dockerd[11041]:   File "/scripts/start.py", line 85, in <module>
Sep 25 12:05:12 rockstor-test dockerd[11041]:     main()
Sep 25 12:05:12 rockstor-test dockerd[11041]:   File "/scripts/start.py", line 72, in main
Sep 25 12:05:12 rockstor-test dockerd[11041]:     call('{} start'.format(get_script('seahub.sh')))
Sep 25 12:05:12 rockstor-test dockerd[11041]:   File "/scripts/utils/__init__.py", line 68, in call
Sep 25 12:05:12 rockstor-test dockerd[11041]:     return subprocess.check_call(*a, **kw)
Sep 25 12:05:12 rockstor-test dockerd[11041]:   File "/usr/lib/python2.7/subprocess.py", line 541, in check_call
Sep 25 12:05:12 rockstor-test dockerd[11041]:     raise CalledProcessError(retcode, cmd)
Sep 25 12:05:12 rockstor-test dockerd[11041]: subprocess.CalledProcessError: Command '/opt/seafile/seafile-server-6.3.3/seahub.sh start' returned non-zero exit status 1
Sep 25 12:05:12 rockstor-test dockerd[11041]: *** /scripts/start.py exited with status 1.
Sep 25 12:05:15 rockstor-test dockerd[11041]: *** Shutting down runit daemon (PID 30)...
Sep 25 12:05:15 rockstor-test dockerd[11041]: *** Running /etc/my_init.post_shutdown.d/10_syslog-ng.shutdown...
Sep 25 12:05:15 rockstor-test dockerd[11041]: Sep 25 12:05:15 d016456cf398 syslog-ng[23]: syslog-ng shutting down; version='3.5.6'
Sep 25 12:05:15 rockstor-test dockerd[11041]: Sep 25 12:05:15 d016456cf398 syslog-ng[23]: EOF on control channel, closing connection;

I can’t seem to see why the seahub/gunicorn component is failing though. It’s the exact same container.

I don’t think the environment variable is the cause; I started the non-rock-on container without it and it seems to work fine, even after I removed and restarted it using an empty folder for the persistent storage.

I ran these commands:

docker rm seafile2
mv /opt/seafile-data /opt/seafile-data2
docker run -d --name seafile2 -v /opt/seafile-data:/shared   -p 8080:80   seafileltd/seafile:latest

And I still get a successful Seafile instance running on port 8080.

So I think there’s something else going on?

If your docker run command without the SEAFILE_SERVER_HOSTNAME is not the culprit, then it leaves “volume binding” as a candidate.
Could you share with us a screenshot of the summary table at the end of the Rockon install (the one summarizing the ports and volumes chosen during the webUI install). Alternatively, if you already have the rockon installed you can see a similar summary table by clicking on the little wren icon next to the ON/OFF toggle switch.

Yet another option to see that is to run docker inspect seafile after installing the Rock-on and paste the output here.

To answer your earlier question:

not much is different… Based on your json file, the command should be:

docker run -d --unless-stopped --name seafile -p 8080:80 -v <rockstor share selected>:/shared seafileltd/seafile:latest

The sumary table says “share: seafile, mount point: /shared” and port 8090.

Here’s a diff between seafile (the rock-on) and seafile2 (the CLI instance):

[root@rockstor-test logs]# diff -u <(docker inspect seafile) <(docker inspect seafile2)
--- /dev/fd/63  2018-09-25 13:47:31.059246418 -0400
+++ /dev/fd/62  2018-09-25 13:47:31.059246418 -0400
@@ -1,32 +1,32 @@
 [
     {
-        "Id": "d016456cf398967403906c4928655c6d49beea53c12cb2703e2d309bbe90fb46",
-        "Created": "2018-09-25T02:15:38.437161948Z",
+        "Id": "243da137ef35356c9c6afed8aec97779eed2203fe2028c58bfddeb1322b5394a",
+        "Created": "2018-09-25T16:02:02.806175914Z",
         "Path": "/sbin/my_init",
         "Args": [
             "--",
             "/scripts/start.py"
         ],
         "State": {
-            "Status": "exited",
-            "Running": false,
+            "Status": "running",
+            "Running": true,
             "Paused": false,
             "Restarting": false,
             "OOMKilled": false,
             "Dead": false,
-            "Pid": 0,
-            "ExitCode": 2,
+            "Pid": 10337,
+            "ExitCode": 0,
             "Error": "",
-            "StartedAt": "2018-09-25T16:15:10.010471848Z",
-            "FinishedAt": "2018-09-25T16:15:25.81722002Z"
+            "StartedAt": "2018-09-25T16:02:04.396458913Z",
+            "FinishedAt": "0001-01-01T00:00:00Z"
         },
         "Image": "sha256:9dc395e1dd439e73ecbec1d0c25a57e7f4e5cafc34017600951996bb575c1637",
-        "ResolvConfPath": "/mnt2/home/containers/d016456cf398967403906c4928655c6d49beea53c12cb2703e2d309bbe90fb46/resolv.conf",
-        "HostnamePath": "/mnt2/home/containers/d016456cf398967403906c4928655c6d49beea53c12cb2703e2d309bbe90fb46/hostname",
-        "HostsPath": "/mnt2/home/containers/d016456cf398967403906c4928655c6d49beea53c12cb2703e2d309bbe90fb46/hosts",
+        "ResolvConfPath": "/mnt2/home/containers/243da137ef35356c9c6afed8aec97779eed2203fe2028c58bfddeb1322b5394a/resolv.conf",
+        "HostnamePath": "/mnt2/home/containers/243da137ef35356c9c6afed8aec97779eed2203fe2028c58bfddeb1322b5394a/hostname",
+        "HostsPath": "/mnt2/home/containers/243da137ef35356c9c6afed8aec97779eed2203fe2028c58bfddeb1322b5394a/hosts",
         "LogPath": "",
-        "Name": "/seafile",
-        "RestartCount": 3113,
+        "Name": "/seafile2",
+        "RestartCount": 0,
         "Driver": "btrfs",
         "MountLabel": "",
         "ProcessLabel": "",
@@ -34,8 +34,7 @@
         "ExecIDs": null,
         "HostConfig": {
             "Binds": [
-                "/mnt2/seafile:/shared",
-                "/etc/localtime:/etc/localtime:ro"
+                "/opt/seafile-data:/shared"
             ],
             "ContainerIDFile": "",
             "LogConfig": {
@@ -47,18 +46,12 @@
                 "80/tcp": [
                     {
                         "HostIp": "",
-                        "HostPort": "8090"
-                    }
-                ],
-                "80/udp": [
-                    {
-                        "HostIp": "",
-                        "HostPort": "8090"
+                        "HostPort": "8080"
                     }
                 ]
             },
             "RestartPolicy": {
-                "Name": "unless-stopped",
+                "Name": "no",
                 "MaximumRetryCount": 0
             },
             "AutoRemove": false,
@@ -126,31 +119,22 @@
         "Mounts": [
             {
                 "Type": "bind",
-                "Source": "/mnt2/seafile",
+                "Source": "/opt/seafile-data",
                 "Destination": "/shared",
                 "Mode": "",
                 "RW": true,
                 "Propagation": ""
-            },
-            {
-                "Type": "bind",
-                "Source": "/etc/localtime",
-                "Destination": "/etc/localtime",
-                "Mode": "ro",
-                "RW": false,
-                "Propagation": ""
             }
         ],
         "Config": {
-            "Hostname": "d016456cf398",
+            "Hostname": "243da137ef35",
             "Domainname": "",
             "User": "",
             "AttachStdin": false,
             "AttachStdout": false,
             "AttachStderr": false,
             "ExposedPorts": {
-                "80/tcp": {},
-                "80/udp": {}
+                "80/tcp": {}
             },
             "Tty": false,
             "OpenStdin": false,
@@ -171,7 +155,7 @@
                 "/scripts/start.py"
             ],
             "ArgsEscaped": true,
-            "Image": "seafileltd/seafile",
+            "Image": "seafileltd/seafile:latest",
             "Volumes": null,
             "WorkingDir": "/opt/seafile",
             "Entrypoint": null,
@@ -180,36 +164,43 @@
         },
         "NetworkSettings": {
             "Bridge": "",
-            "SandboxID": "029e1cf295cc40155d981a70a0b97926b20da3f0caac651107c7bfaeadaaf9ee",
+            "SandboxID": "81dfa8e8cca83024f11fa880641a346b3815b92d25afdbdb2ebd991a61deb6fa",
             "HairpinMode": false,
             "LinkLocalIPv6Address": "",
             "LinkLocalIPv6PrefixLen": 0,
-            "Ports": null,
-            "SandboxKey": "/var/run/docker/netns/029e1cf295cc",
+            "Ports": {
+                "80/tcp": [
+                    {
+                        "HostIp": "0.0.0.0",
+                        "HostPort": "8080"
+                    }
+                ]
+            },
+            "SandboxKey": "/var/run/docker/netns/81dfa8e8cca8",
             "SecondaryIPAddresses": null,
             "SecondaryIPv6Addresses": null,
-            "EndpointID": "",
-            "Gateway": "",
+            "EndpointID": "f3962efa7e8c1a2f17a0a28edd69ff976cfe3f4874d9e000fa3113cc4115c98d",
+            "Gateway": "172.17.0.1",
             "GlobalIPv6Address": "",
             "GlobalIPv6PrefixLen": 0,
-            "IPAddress": "",
-            "IPPrefixLen": 0,
+            "IPAddress": "172.17.0.4",
+            "IPPrefixLen": 16,
             "IPv6Gateway": "",
-            "MacAddress": "",
+            "MacAddress": "02:42:ac:11:00:04",
             "Networks": {
                 "bridge": {
                     "IPAMConfig": null,
                     "Links": null,
                     "Aliases": null,
                     "NetworkID": "09334b8c6c4783483a24bda3c4f18f267cdfc737cde84ee44bf454c803d1de54",
-                    "EndpointID": "",
-                    "Gateway": "",
-                    "IPAddress": "",
-                    "IPPrefixLen": 0,
+                    "EndpointID": "f3962efa7e8c1a2f17a0a28edd69ff976cfe3f4874d9e000fa3113cc4115c98d",
+                    "Gateway": "172.17.0.1",
+                    "IPAddress": "172.17.0.4",
+                    "IPPrefixLen": 16,
                     "IPv6Gateway": "",
                     "GlobalIPv6Address": "",
                     "GlobalIPv6PrefixLen": 0,
-                    "MacAddress": ""
+                    "MacAddress": "02:42:ac:11:00:04"
                 }
             }
         }

I can share a full one if necessary.

The only major differences I see, aside from the fact that one is running and one isn’t: RestartPolicy, and the fact that seafile2 is using :latest explicitly.

Just to make sure, when you installed the Rock-on, you did try to access the Seafile UI by clicking the “Seafile UI” button on the Rock-ons page, isn’t it? (or by going to yourip:8090)
I’m asking because the Rockon is using port 8090 as 8080 was already attributed to something else (maybe a previous seafile attempt).

I’m running the CLI version on port 8080 and the rock-on version on port 8090, just to avoid any collisions. I’ve accessed it both through the button and through typing in https://hostname:8090. Either way I get an Nginx ‘502 Bad Gateway’ page, meaning that the nginx component of the rock-on is starting properly, but it’s forwarding to another process (seahub) that isn’t responding.

I think the next thing to do is for me to dig down and figure out the root cause of seahub not starting properly.

EDIT: Just for shiggles, I stopped the rock-on using the web UI, deleted the rock-on container, recreated it using the commandline, stopped it via the CLI, then started it via the web UI and it seems to work just fine now. So there’s something about the creation process that is different, but once the container exists it works.

(web ui: turn Seafile Rock-on off)
CLI:

docker rm seafile
docker run -d --name seafile -v /mnt2/seafile:/shared   -p 8090:80   seafileltd/seafile:latest
docker stop seafile

(web UI: turn Seafile Rock-on back on)
(click on the Seafile-Official UI button and it takes me to a working Seafile instance at rockstor-test:8090)

Well that’s interesting… Nginx configuration is definitely not my forte but I’ll try to see why cli and rockon differ. On top of my head, I can think of folder ownership and/or user permission, but that’s quite a long shot / guess…
I should be able to try on my end later tonight if you haven’t solved the mystery before that.

Hi @TexasDex,

I’m still quite puzzled as to why this fails from Rockons, but I’ve made some progress. I will simply list my observations here as I cannot really explain how this container behaves.

First of all, I can reproduced exactly what you observed, with two different versions of Rockstor (with different docker versions as well).

  1. Running /usr/bin/docker run -d --name seafile-cli -v /mnt2/seafile-cli:/shared -p 8900:80 seafileltd/seafile does create the container without error, and I can access the webUI without Nginx erroring with 502.

  2. Running the above command with /opt or a Rockstor’s webUI-created share both work, so no apparent user permissions issues here.

  3. Then, I tried to re-create the command that Rockstor generates. As a reminder, this is the same, but with the added binding of /etc/localtime and the --unless-stopped restart policy. Surprisingly, as soon as I bind /etc/locatime, I get the nginx 502 error. Indeed, the following command:

/usr/bin/docker run -d --name seafile-cli -v /mnt2/seafile-cli:/shared -v /etc/localtime:/etc/localtime:ro -p 8900:80 seafileltd/seafile

creates the container without problem, but it fails upon restart. At the first creation, however, both seafile-server and seahub server start successfully, but I get the Nginx 502 error.
Although I can’t explain why /etc/localtime would do that, it seems enough to create the problem. Interestingly, it has been reported already on the project’s Github–but without explanation, unfortunately.

1 Like

@Flox

Nice find. I’ve no idea why that would break it though.

1 Like

That is pretty weird! Thanks for the help with this, it was the strangest thing, and I never would have guessed that /etc/localtime was the cause. Could it be some kind of permissions issue maybe?

Is there any way to get Rockstor to skip that bind?

Not sure why I didn’t think of this before, but I did a bit of research on the bug above, and the part where it overwrites /etc/localtime can be skipped entirely if it has TIME_ZONE set.

So, I added an env var to the rockon and it seems to be working better:

{
    "Seafile-Official": {
        "containers": {
            "seafile": {
                "image": "seafileltd/seafile",
                "launch_order": 1,
                "ports": {
                    "80": {
                        "description": "Seafile WebUI port. Suggested default: 8080",
                        "host_default": 8080,
                        "label": "WebUI port",
                        "ui": true
                    }
                },
                "volumes": {
                    "/shared": {
                        "description": "Choose a Share for Seafile. Eg: create a Share called seafile-all for this purpose alone.",
                        "label": "Storage",
                        "min_size": 1073741824
                    }
                },
                "environment": {
                    "TIME_ZONE": {
                         "description": "The TZ Database Name of your time zone, e.g. America/New_York",
                         "label": "Time Zone"
                    }
                }
            }
        },
        "version": "latest",
        "description": "Secure file sharing and hosting",
        "icon": "https://avatars2.githubusercontent.com/u/1948782?s=400&v=4",
        "more_info": "<p>Default username for your Seafile UI is<code>me@example.com</code>and password is<code>asecret</code></p>",
        "ui": {
            "slug": ""
        },
        "website": "https://seafile.org/"
    }
}

I’ve had some success with it, although it’s bit a while so I’m not totally sure if I’m duplicating the issue correctly (and therefore fixing it). Could someone else test this out?

Also, is there a better way to get the time zone than asking the user?

2 Likes

Thanks for your perseverance and for the find!
I’m really short on time lately so I can’t promise when I’ll have time to test it out myself, but make sure to ping me again in a few days if I haven’t done so.
That would be a very nice addition so I’m looking forward to reviewing your work. Would you be so kind as to updating your issue on GitHub as well?

Thanks a lot @TexasDex for working on that… Your resolution makes sense to me and would in theory work, but actually fails in my testing from what I believe to be an unmet requirement in their script (it may need updating).
I’ve detailed a little bit more the corresponding Github issue:

There must have been some kind of change in the docker image we’re pulling, because the original JSON (without TIME_ZONE set) works now. Hell if I know why. The problem section doesn’t even seem to have changed in the git history of seafile-docker in the intervening time, but now it works fine.

I was prepared to bind /usr/share/zoneinfo into the container, since it didn’t have the time zone files installed in the parent image, but now it looks like I don’t have to. I can keep that idea up my sleeve if things break in the future but it’s probably simplest to not need it at all.

I did a full test, from brand new Rockstor install to working Seafile instance, just using the older JSON in my previous post. There are a few things that could be refined, like the initial username/password (right now I just let the container set the defaults, and document them in the more_info field), and it’d be nice to have somebody double check my work, but I’m ready to submit a pull request.

1 Like

There may be something related to the removal of this timezone packages from their base image (phusion). Either way, it is great news that you seem to have a working JSON :-). Their docker is rather big (over 1GB), but that is a nice rock-on to have.

If I’m correct, that’s pretty much what Rockstor’s automatic binding of the host’s /etc/localtime does anyway, as it is a symbolic link to the set binary file in /use/share/zoneinfo.

@phillxnet can correct me if I’m wrong, but if you have a working JSON that meets your criteria and Rockstor’s, feel free to submit a PR and any improvement (if any) that users will see can easily be suggested and included there. You can have a look at the TeamSpeak3 JSON file for an example:

Thanks again for all your time and sharing your work! I’m looking forward to seeing this.

1 Like