From 5628e4f0624a10c37d1d4f8ce7faef8dcc2e8689 Mon Sep 17 00:00:00 2001
From: Firq-ow <firelp42@gmail.com>
Date: Tue, 9 Jul 2024 14:34:07 +0200
Subject: [PATCH 01/13] Added better uri parsing, updated README

---
 README.md                               | 30 +++++++++++++++++++++++++
 dockge_cli/client/commands/functions.py |  9 +++++---
 2 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 4c51468..3ce3770 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,33 @@
 # dockge-cli
 
 A simple CLI application written in Python for communicating with Dockge using websockets
+
+## Background
+
+Dockge (spoken dock-ge or dockage) is a tool to manage docker-compose stacks from a web ui. It is developed by louislam, who also develops UptimeKuma.
+
+Dockge itself doesn't offer any kind of API or programmatic access, as it is just intended for managing stacks via UI.
+
+My current deployment solution for firq.dev and fgo-ta.com is based on Dockge, and I was over it always having to reload the stack whenever I pushed an update. Instead, I wanted to have this as a separate CI step, automatically redeploying a givens stack.
+
+As Dockge is using a websocket-based system under the hood, it was easy to take a look at how communication occurs. In general, communication is achieved by leveraging socket.io for the data. Since Python already offers a solution for socket.io, it is just a matter of emulating the calls the webui sends and receives.
+
+In the end, this is the current result that works pretty well for my understanding. I am still trying to improve upon some issues (login times out, stability, features), but in general this works as a fine solution for automatic stack updating.
+
+## Installation
+
+Install it from the custom package index using
+
+```
+pip install --extra-index-url https://forgejo.neshweb.net/api/packages/Firq/pypi/simple/ dockge-cli
+```
+
+Alternativly, install it using this repository. When installing for development, make sure to install with the additional dependencies
+
+```
+pip install -e .[lint,typing]
+```
+
+## Usage
+
+Call the CLI using `dockge-cli` or `dockge`.
diff --git a/dockge_cli/client/commands/functions.py b/dockge_cli/client/commands/functions.py
index f62ed9c..1cca01d 100644
--- a/dockge_cli/client/commands/functions.py
+++ b/dockge_cli/client/commands/functions.py
@@ -1,5 +1,6 @@
 from urllib.parse import urlparse
 from getpass import getpass
+import re
 
 from ...models import Credentials
 from ...service import storage
@@ -26,10 +27,12 @@ class FunctionBindings():
         host command binding
         """
         if len(extra_args) > 0:
-            res = urlparse(extra_args[0])
+            mat = re.search(r"((\w+\.)?\w+\.\w+(\/.+)?)", extra_args[0], re.IGNORECASE)
+            if mat is None:
+                raise ValueError("Given host did not match regex")
+            res = urlparse(f"https://{mat[0]}")
             if all([res.scheme, res.netloc]):
-                host = extra_args[0].rstrip("/").replace("https://", "").replace("wss://", "")
-                storage.put("host", host)
+                storage.put("host", mat[0])
             else:
                 raise ValueError(f"Malformed URL {extra_args[0]}")
         print(storage.get("host"))

From f5ffe07e77bceffe883e3489a0e7c5365147696a Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Fri, 12 Jul 2024 23:25:52 +0200
Subject: [PATCH 02/13] Renamed communication to connection

---
 dockge_cli/client/commands/functions.py              | 2 +-
 dockge_cli/service/{communicate.py => connection.py} | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename dockge_cli/service/{communicate.py => connection.py} (100%)

diff --git a/dockge_cli/client/commands/functions.py b/dockge_cli/client/commands/functions.py
index 1cca01d..3ee695d 100644
--- a/dockge_cli/client/commands/functions.py
+++ b/dockge_cli/client/commands/functions.py
@@ -4,7 +4,7 @@ import re
 
 from ...models import Credentials
 from ...service import storage
-from ...service.communicate import DockgeConnection
+from ...service.connection import DockgeConnection
 from ..utils import stack_formatter, status_formatter, generic_formatter, credential_parser_factory
 
 class FunctionBindings():
diff --git a/dockge_cli/service/communicate.py b/dockge_cli/service/connection.py
similarity index 100%
rename from dockge_cli/service/communicate.py
rename to dockge_cli/service/connection.py

From 360aca36c5aae1c9042b838bc620d94fa764a27a Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 20 Jul 2024 16:37:41 +0200
Subject: [PATCH 03/13] First official release

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index d1abd6c..1412bbb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.0-c.3"
+version = "0.1.0"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",

From 34cb6e5dc7e1e12fdfef245184052c58b6bf07d3 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 20 Jul 2024 17:25:34 +0200
Subject: [PATCH 04/13] Increased timeouts

---
 dockge_cli/service/connection.py | 15 ++++++++-------
 pyproject.toml                   |  4 ++--
 2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/dockge_cli/service/connection.py b/dockge_cli/service/connection.py
index 9fb063b..bcb3802 100644
--- a/dockge_cli/service/connection.py
+++ b/dockge_cli/service/connection.py
@@ -99,10 +99,11 @@ class DockgeConnection:
                         "password": storage.get("password", encoded=True),
                         "token": ""
                     },
-                    timeout=5
+                    timeout=10
                 )
                 retry = False
             except socketio.exceptions.TimeoutError:
+                print("Reached timeout for login, retrying ...")
                 retry = True
                 count += 1
 
@@ -125,42 +126,42 @@ class DockgeConnection:
         """
         Lists status for a stack
         """
-        ret = self._sio.call("agent", ("", "serviceStatusList", name), timeout=5)
+        ret = self._sio.call("agent", ("", "serviceStatusList", name), timeout=10)
         return ret
 
     def restart(self, name):
         """
         Restarts a given stack
         """
-        ret = self._sio.call("agent", ("", "restartStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "restartStack", name), timeout=30)
         return ret
 
     def update(self, name):
         """
         Updates a given stack
         """
-        ret = self._sio.call("agent", ("", "updateStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "updateStack", name), timeout=30)
         return ret
 
     def stop(self, name):
         """
         Stops a given stack
         """
-        ret = self._sio.call("agent", ("", "stopStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "stopStack", name), timeout=30)
         return ret
 
     def start(self, name):
         """
         Starts a given stack
         """
-        ret = self._sio.call("agent", ("", "startStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "startStack", name), timeout=30)
         return ret
 
     def down(self, name):
         """
         Stops and downs a given stack
         """
-        ret = self._sio.call("agent", ("", "downStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "downStack", name), timeout=30)
         return ret
 
     def disconnect(self):
diff --git a/pyproject.toml b/pyproject.toml
index 1412bbb..6371330 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.0"
+version = "0.1.1-c.1"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",
@@ -11,7 +11,7 @@ dependencies = [
 requires-python = ">= 3.10"
 authors = [{name = "Firq", email = "firelp42@gmail.com"}]
 maintainers = [{name = "Firq", email = "firelp42@gmail.com"}]
-description = "CLi for interacting with dockge"
+description = "CLI for interacting with dockge"
 classifiers = [
     "Development Status :: 3 - Alpha",
     "Programming Language :: Python :: 3",

From 44a04097985ebb68c4ec64a6f5e4b56adb931b9d Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 20 Jul 2024 17:49:25 +0200
Subject: [PATCH 05/13] More logging

---
 dockge_cli/service/connection.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/dockge_cli/service/connection.py b/dockge_cli/service/connection.py
index bcb3802..35ba9a6 100644
--- a/dockge_cli/service/connection.py
+++ b/dockge_cli/service/connection.py
@@ -63,6 +63,7 @@ class DockgeConnection:
             success = True
         else:
             print("Issue with login procedure")
+            print(data)
         return success
 
     # Functions

From 003204afb137fe7c0678b20a47b68ab4b96bca14 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Mon, 22 Jul 2024 17:40:57 +0200
Subject: [PATCH 06/13] Add docker build step to provide ready-to-use dockge
 cli container

---
 .forgejo/workflows/build-release.yaml | 35 +++++++++++++++++++++++++++
 Dockerfile                            |  4 +++
 2 files changed, 39 insertions(+)
 create mode 100644 Dockerfile

diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml
index 600b9b0..9bbcfd2 100644
--- a/.forgejo/workflows/build-release.yaml
+++ b/.forgejo/workflows/build-release.yaml
@@ -56,3 +56,38 @@ jobs:
         run: pip install twine
       - name: Upload package to registry
         run: python -m twine upload --repository-url ${{ secrets.REPOSITORY_URL }} -u ${{ secrets.TWINE_DEPLOY_USER }} -p ${{ secrets.TWINE_DEPLOY_PASSWORD }} dist/*
+
+  build-and-push-container:
+    needs: [ "publish-artifacts" ]
+    runs-on: dind
+    steps:
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+      - name: Log into Docker Package Registry
+        uses: docker/login-action@v3
+        with:
+          registry: forgejo.neshweb.net
+          username: ${{ secrets.FORGEJO_USERNAME }}
+          password: ${{ secrets.FORGEJO_TOKEN }}
+      - name: Build and push to Docker Package Registry
+        uses: docker/build-push-action@v5
+        with:
+          context: .
+          build-args: |
+            PACKAGE_VERSION=${{ github.ref_name }}
+            push: true
+          tags: forgejo.neshweb.net/firq/dockge-cli:${{ github.ref_name }}
+
+  release:
+    needs: [ build-and-push-container, publish-artifacts ]
+    if: success()
+    runs-on: docker
+    steps:
+      - name: Release New Version
+        uses: actions/forgejo-release@v1
+        with:
+          direction: upload
+          url: https://forgejo.neshweb.net
+          release-dir: release
+          token: ${{ secrets.FORGEJO_TOKEN }}
+          tag: ${{  github.ref_name }}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..88e754d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,4 @@
+FROM forgejo.neshweb.net/ci-docker-images/python-neshweb:3.11
+
+ARG PACKAGE_VERSION=0.1.0
+RUN pip install dockge-cli==${PACKAGE_VERSION}

From ca5803bcccfa5a9575572c28c229f0ea60bda930 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Mon, 22 Jul 2024 17:42:48 +0200
Subject: [PATCH 07/13] Fixed version

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 6371330..7e3f81e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.1-c.1"
+version = "0.1.1-c.2"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",

From de234133f9fac7e516691288030af9844cec565b Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Mon, 22 Jul 2024 17:44:54 +0200
Subject: [PATCH 08/13] Fixed workflow

---
 .forgejo/workflows/build-release.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml
index 9bbcfd2..6932d93 100644
--- a/.forgejo/workflows/build-release.yaml
+++ b/.forgejo/workflows/build-release.yaml
@@ -75,7 +75,7 @@ jobs:
           context: .
           build-args: |
             PACKAGE_VERSION=${{ github.ref_name }}
-            push: true
+          push: true
           tags: forgejo.neshweb.net/firq/dockge-cli:${{ github.ref_name }}
 
   release:

From 09da354b0ec9f56eb9eb5d95d9f16c8c414fe1e1 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Mon, 22 Jul 2024 17:47:06 +0200
Subject: [PATCH 09/13] Fixed workflow 2

---
 .forgejo/workflows/build-release.yaml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml
index 6932d93..48e5e92 100644
--- a/.forgejo/workflows/build-release.yaml
+++ b/.forgejo/workflows/build-release.yaml
@@ -72,7 +72,6 @@ jobs:
       - name: Build and push to Docker Package Registry
         uses: docker/build-push-action@v5
         with:
-          context: .
           build-args: |
             PACKAGE_VERSION=${{ github.ref_name }}
           push: true

From fc7ecdcd774e9fddd4c0b1204521409c5d4645bc Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Thu, 15 Aug 2024 23:09:02 +0200
Subject: [PATCH 10/13] Readme

---
 README.md | 38 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 36 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 3ce3770..2f946e3 100644
--- a/README.md
+++ b/README.md
@@ -18,16 +18,50 @@ In the end, this is the current result that works pretty well for my understandi
 
 Install it from the custom package index using
 
-```
+```shell
 pip install --extra-index-url https://forgejo.neshweb.net/api/packages/Firq/pypi/simple/ dockge-cli
 ```
 
 Alternativly, install it using this repository. When installing for development, make sure to install with the additional dependencies
 
-```
+```shell
 pip install -e .[lint,typing]
 ```
 
 ## Usage
 
 Call the CLI using `dockge-cli` or `dockge`.
+
+```shell
+usage: dockge_cli [-h] [--version] {host,login,logout,list,status,restart,start,stop,down,update,exit,help}
+
+CLI interface for interacting with Dockge
+
+positional arguments:
+  {host,login,logout,list,status,restart,start,stop,down,update,exit,help}
+
+options:
+  -h, --help            show this help message and exit
+  --version             show program's version number and exit
+```
+
+Help for each individual command can be invoked by calling `dockge-cli help <command>`
+
+## The magic behind this
+
+Generally, this makes use of the underlying Websockets API that the Dockge frontend uses to communicate with the server. By analyzing the traffic and looking into the codebase, I was able to reverse most of the packets that are being sent. This allows me to then contruct, send and receive my own packets, making the whole thing work.
+
+There are some things that need to be taken into account for this: For one, dockge uses socket.io for the websocket communication. This meant I had to find the corresponding socket.io version to get the correct version of python-socketio. In addition, I had to find out how the authorization mechanism behind this works.
+
+After finishing up the first prototype, the workings are as follows:
+
+1. A websocket session is established using socket.io - this happens automatically
+2. After the session is ready, the `login` command is sent together with a provided username and password
+3. Once the CLI is authorized, the selected command is sent
+4. The CLI waits for any response values and exits once the command has executed successfully
+
+To provide a smooth experience, both the credentials and the remote host URI are stored on disk. just like the `docker` cli, the credentials are not encrypted, meaning it is advised to either clear the credentials after use OR to use the `--username` and `--password` parameters. This is especially recommended for CI applications.
+
+## Known issues
+
+This CLI does not work when Mullvad is used, as Mullvad actively blocks port forwarding (which python-socketio uses)

From 401e446059320a325ed5fc0e6de4df0fc8d498ef Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 28 Sep 2024 15:39:22 +0200
Subject: [PATCH 11/13] Release 0.1.1

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 7e3f81e..43d9c5d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.1-c.2"
+version = "0.1.1"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",

From ed24d59a9203631d1066036f04f7fcde8054a510 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Tue, 1 Oct 2024 23:24:31 +0200
Subject: [PATCH 12/13] HTTP long polling before upgrade

---
 dockge_cli/service/connection.py | 2 +-
 pyproject.toml                   | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/dockge_cli/service/connection.py b/dockge_cli/service/connection.py
index 35ba9a6..78e7582 100644
--- a/dockge_cli/service/connection.py
+++ b/dockge_cli/service/connection.py
@@ -72,7 +72,7 @@ class DockgeConnection:
         Connect to the websocket
         """
         # Dockge uses Socket.io for the websockets, so this URI and params are always the same
-        self._sio.connect(f"https://{self._host}/socket.io/", transports=['websocket'])
+        self._sio.connect(f"https://{self._host}/socket.io/")
         self.login()
 
     def login(self):
diff --git a/pyproject.toml b/pyproject.toml
index 43d9c5d..39793ce 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,6 +4,7 @@ version = "0.1.1"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",
+    "requests~=2.32.3",
     "python-socketio~=5.11.3",
     "websocket-client~=1.8.0",
     "tabulate ~=0.9.0",

From ac982668c73b7e6a296e755c3ffff3174b4caa17 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Tue, 1 Oct 2024 23:25:30 +0200
Subject: [PATCH 13/13] HTTP long polling before upgrade

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 39793ce..b706b37 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.1"
+version = "0.1.2"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",