From 0dea7a55f4721ab1bc7877f37973cb375caa1200 Mon Sep 17 00:00:00 2001 From: Yannik Schmidt Date: Fri, 18 Apr 2025 10:26:41 +0200 Subject: [PATCH 1/4] add: docker server build --- .github/workflows/server.yaml | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/server.yaml diff --git a/.github/workflows/server.yaml b/.github/workflows/server.yaml new file mode 100644 index 0000000..66751cf --- /dev/null +++ b/.github/workflows/server.yaml @@ -0,0 +1,37 @@ +name: ci + +on: + push: + branches: + - "master" + +jobs: + docker: + runs-on: ubuntu-latest + environment: + name: prod + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Login to Docker Registry + uses: docker/login-action@v2 + with: + registry: ${{ secrets.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASS }} + - + name: Build and push async-icinga image + uses: docker/build-push-action@v3 + with: + context: server + platforms: linux/amd64 + push: true + tags: "${{ secrets.REGISTRY }}/atlantishq/gamevault-server:latest" From 2e8a5facfdc664ede6164b8a433331ac0ee5c696 Mon Sep 17 00:00:00 2001 From: Yannik Schmidt Date: Thu, 17 Apr 2025 12:22:22 +0200 Subject: [PATCH 2/4] wip: self updater --- check_release.py | 31 ++++++++++++++++++++++++ client.py | 2 ++ updater/requirements.txt | 2 ++ updater/updater.py | 51 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 check_release.py create mode 100644 updater/requirements.txt create mode 100644 updater/updater.py diff --git a/check_release.py b/check_release.py new file mode 100644 index 0000000..a5c57c9 --- /dev/null +++ b/check_release.py @@ -0,0 +1,31 @@ +import requests +import os + +REPO = "FAUSheppy/homelab_gamevault" +API_URL = f"https://api.github.com/repos/{REPO}/releases/latest" +VERSION_FILE = ".gamevault_version" + +def get_latest_release(): + response = requests.get(API_URL) + response.raise_for_status() + data = response.json() + version = data['tag_name'] + zip_url = data['zipball_url'] + return version, zip_url + + +def read_local_version(): + if not os.path.exists(VERSION_FILE): + return None + with open(VERSION_FILE, 'r') as f: + return f.read().strip() + +def update_updater(): + pass # TODO + # download updater + # replace updater + +def execute_updater(new_version): + # TODO + # os.system(["updater.exe", new_version]) + pass \ No newline at end of file diff --git a/client.py b/client.py index c5076eb..8651828 100644 --- a/client.py +++ b/client.py @@ -320,6 +320,8 @@ def update_button_positions(event=None): if __name__ == "__main__": + # run updater # + pgw = pgwrapper.ProgressBarWrapper() pgw.new(app) diff --git a/updater/requirements.txt b/updater/requirements.txt new file mode 100644 index 0000000..9252f95 --- /dev/null +++ b/updater/requirements.txt @@ -0,0 +1,2 @@ +tkinter +requests \ No newline at end of file diff --git a/updater/updater.py b/updater/updater.py new file mode 100644 index 0000000..0cdfec0 --- /dev/null +++ b/updater/updater.py @@ -0,0 +1,51 @@ +import os +import requests +import zipfile +import io +import shutil +import tkinter as tk + + +CLIENT_DIR = os.path.join("client") +INTERNAL_DIR = os.path.join(CLIENT_DIR, "_internal") + +def prompt_user(version): + root = tk.Tk() + root.withdraw() # Hide main window + result = tk.messagebox.askyesno("Update Available", f"New version {version} available. Download and install?") + root.destroy() + return result + +def download_and_extract(zip_url): + print("Downloading...") + response = requests.get(zip_url) + response.raise_for_status() + with zipfile.ZipFile(io.BytesIO(response.content)) as z: + temp_dir = "_temp_extracted" + z.extractall(temp_dir) + top_folder = next(os.scandir(temp_dir)).path + + # Replace _internal + source_internal = os.path.join(top_folder, "client", "_internal") + if os.path.exists(INTERNAL_DIR): + shutil.rmtree(INTERNAL_DIR) + shutil.copytree(source_internal, INTERNAL_DIR) + + # Replace client.exe + source_exe = os.path.join(top_folder, "client", "client.exe") + target_exe = os.path.join(CLIENT_DIR, "client.exe") + shutil.copy2(source_exe, target_exe) + + shutil.rmtree(temp_dir) + print("Update complete.") + + +def main(): + + + if prompt_user(): + download_and_extract() + # TODO: run main file again + +if __name__ == '__main__': + main() \ No newline at end of file From cf55f6f3879befb8c1a9a6a1e79b9ef4bcf78761 Mon Sep 17 00:00:00 2001 From: Yannik Schmidt Date: Fri, 18 Apr 2025 11:59:32 +0200 Subject: [PATCH 3/4] fix: remove intransparent server url changes --- client.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client.py b/client.py index 8651828..f569bd8 100644 --- a/client.py +++ b/client.py @@ -357,10 +357,11 @@ if __name__ == "__main__": elif backend_type == "HTTP/HTTPS": server = config_loaded["Server/Path:"] remote_root_dir = None - if not server.startswith("http://") or "https://": + if not any(server.startswith(s) for s in ["http://", "https://"]): server = "http://" + server - if not ":" in server.split("://")[1]: - server = server + ":5000" + #if not ":" in server.split("://")[1]: + # server = server + ":5000" + print(server) elif backend_type == "Local Filesystem": remote_root_dir = config_loaded["Server/Path:"] server = None @@ -372,7 +373,8 @@ if __name__ == "__main__": # add db backend # if backend_type == "HTTP/HTTPS": - db = data_backend.HTTP(None, None, install_dir, remote_root_dir="./", server=server, progress_bar_wrapper=pgw, + db = data_backend.HTTP(user, password, install_dir, + remote_root_dir="./", server=server, progress_bar_wrapper=pgw, tkinter_root=app, hide_above_age=hide_above_age) elif backend_type == "FTP/FTPS": db = data_backend.FTP(user, password, install_dir, server=server, From 472d9cfca28f5a839b508d4812c987bac06fe661 Mon Sep 17 00:00:00 2001 From: Yannik Schmidt Date: Fri, 18 Apr 2025 11:59:59 +0200 Subject: [PATCH 4/4] feat: implement HTTP auth --- data_backend.py | 10 ++++++---- infowidget.py | 4 ++-- statekeeper.py | 18 +++++++++--------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/data_backend.py b/data_backend.py index 19e4aed..d96a616 100644 --- a/data_backend.py +++ b/data_backend.py @@ -20,6 +20,7 @@ class DataBackend: self.user = user self.password = password + self.auth = (self.user, self.password) self.remote_root_dir = remote_root_dir self.server = server self.install_dir = install_dir @@ -92,6 +93,7 @@ class LocalFS(DataBackend): meta_info_list.append(software.Software(meta_file, self, self.progress_bar_wrapper)) return list(filter(lambda x: not x.invalid, meta_info_list)) + class HTTP(DataBackend): paths_listed = {} @@ -165,7 +167,7 @@ class HTTP(DataBackend): # this is with streaming chunk_size = 1024 * 1024 * 5 # 5MB - r = requests.get(self._get_url(), params={"path": path, "as_string": True}, stream=True) + r = requests.get(self._get_url(), params={"path": path, "as_string": True}, stream=True, auth=(self.user, self.password)) r.raise_for_status() if path.endswith(".txt"): @@ -197,7 +199,7 @@ class HTTP(DataBackend): else: print("Async Requested for:", local_file) - statekeeper.add_to_download_queue(self._get_url(), path) + statekeeper.add_to_download_queue(self._get_url(), path, auth=(self.user, self.password)) return local_file elif return_content: @@ -220,7 +222,7 @@ class HTTP(DataBackend): paths = self.paths_listed[fullpath] else: - r = requests.get(self._get_url(), params={ "path" : path }) + r = requests.get(self._get_url(), params={ "path" : path }, auth=(self.user, self.password)) r.raise_for_status() #print(r, r.status_code, r.content) paths = r.json()["contents"] @@ -273,4 +275,4 @@ class HTTP(DataBackend): print("Age limit set to", self.hide_above_age, "games have", [x.age_limit for x in software_list]) results_with_age = list(filter(lambda x: x.age_limit <= self.hide_above_age, results_valid)) - return results_with_age \ No newline at end of file + return results_with_age diff --git a/infowidget.py b/infowidget.py index 846950d..f13e9f1 100644 --- a/infowidget.py +++ b/infowidget.py @@ -86,7 +86,7 @@ class ProgressBarApp: return try: - percent_filled = statekeeper.get_percent_filled(path) + percent_filled = statekeeper.get_percent_filled(path, self.data_backend.auth) except OSError as e: fail_count += 1 if fail_count > 6: @@ -143,4 +143,4 @@ class ProgressBarApp: def on_close(self): self.running = False - self.root.destroy() \ No newline at end of file + self.root.destroy() diff --git a/statekeeper.py b/statekeeper.py index 143e5b9..cdc4a9b 100644 --- a/statekeeper.py +++ b/statekeeper.py @@ -8,10 +8,10 @@ from sqlalchemy import or_, and_ def _bytes_to_mb(size): return size / (1024*1024) -def add_to_download_queue(url, path): +def add_to_download_queue(url, path, auth): '''The download is added to the global queue and downloaded eventually''' #_download(url, path) - thread = threading.Thread(target=_download, args=(url, path)) + thread = threading.Thread(target=_download, args=(url, path, auth)) thread.start() def add_to_task_queue(task): @@ -21,9 +21,9 @@ def add_to_task_queue(task): thread.start() #task() -def _download(url, path): +def _download(url, path, auth): - response = requests.get(url + "?path=" + path, stream=True) + response = requests.get(url + "?path=" + path, stream=True, auth=auth) # Check if the request was successful if response.status_code == 200: @@ -72,7 +72,7 @@ def log_end_download(path): db.close_session() -def get_download_size(path): +def get_download_size(path, auth): session = db.session() obj = session.query(Download).filter(Download.path==path).first() @@ -85,7 +85,7 @@ def get_download_size(path): return obj.size # query size # - r = requests.get(obj.url, params={"path": path, "info": 1}) + r = requests.get(obj.url, params={"path": path, "info": 1}, auth=auth) r.raise_for_status() size = r.json()["size"] @@ -96,14 +96,14 @@ def get_download_size(path): return size -def get_percent_filled(path): +def get_percent_filled(path, auth): session = db.session() obj = session.query(Download).filter(Download.path==path, Download.finished==False).first() if not obj: return 100 # means its finished size = _bytes_to_mb(os.stat(obj.local_path).st_size) - total_size = get_download_size(obj.path) + total_size = get_download_size(obj.path, auth) session.close() if total_size == 0: @@ -122,4 +122,4 @@ def get_download(path=None): downloads = session.query(Download).filter(Download.finished==False).all() session.close() - return downloads \ No newline at end of file + return downloads