From ce520b914a44e611550faacb6c36bb859c42fa43 Mon Sep 17 00:00:00 2001 From: Kumi Date: Wed, 5 Jun 2024 18:58:17 +0200 Subject: [PATCH] feat: enhance GameServer installation flow Improved the game server installation process by implementing several key changes. Added support for optional installation paths in the GameServer model, form, and installation view. This allows users to leave the installation path empty and use a default path, improving usability and flexibility. Additionally, updated the form validation to accept empty paths and provided a user-friendly widget placeholder in the form to guide users. Included a new migration to reflect the optional installation path in the database schema. Updated the installation view to automatically determine the installation path based on settings if not specified by the user. Enhanced the script download process in the installation view by setting a custom User-Agent header. This addresses potential issues with servers that block the default Python User-Agent. Added CSRF_TRUSTED_ORIGINS settings dynamically based on ALLOWED_HOSTS to enhance security. Also updated the .gitignore to exclude the newly introduced default game servers directory from version control. These changes collectively streamline the setup process for new game servers, provide clearer guidance to users, and ensure compatibility with a broader range of hosting configurations. --- .gitignore | 1 + coffeemachine/servers/forms.py | 9 +- ...0002_alter_gameserver_installation_path.py | 18 ++++ coffeemachine/servers/models.py | 2 +- coffeemachine/servers/views.py | 17 +++- coffeemachine/settings.py | 86 +++++++++++-------- 6 files changed, 90 insertions(+), 43 deletions(-) create mode 100644 coffeemachine/servers/migrations/0002_alter_gameserver_installation_path.py diff --git a/.gitignore b/.gitignore index dff67e9..fea679b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ venv/ __pycache__/ settings.ini db.sqlite3 +game_servers/ diff --git a/coffeemachine/servers/forms.py b/coffeemachine/servers/forms.py index 780a363..9acedc0 100644 --- a/coffeemachine/servers/forms.py +++ b/coffeemachine/servers/forms.py @@ -20,10 +20,15 @@ class GameServerForm(forms.ModelForm): "script_file", "script_content", ] + widgets = { + "installation_path": forms.TextInput( + attrs={"placeholder": "Leave empty to use default path"} + ), + } def clean_installation_path(self): - path = self.cleaned_data["installation_path"] - if not os.path.isabs(path): + path = self.cleaned_data.get("installation_path") + if path and not os.path.isabs(path): raise forms.ValidationError( "The installation path must be an absolute path." ) diff --git a/coffeemachine/servers/migrations/0002_alter_gameserver_installation_path.py b/coffeemachine/servers/migrations/0002_alter_gameserver_installation_path.py new file mode 100644 index 0000000..f9569fe --- /dev/null +++ b/coffeemachine/servers/migrations/0002_alter_gameserver_installation_path.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-05 15:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("servers", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="gameserver", + name="installation_path", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/coffeemachine/servers/models.py b/coffeemachine/servers/models.py index 7b01f63..7aef5ff 100644 --- a/coffeemachine/servers/models.py +++ b/coffeemachine/servers/models.py @@ -12,7 +12,7 @@ class GameServer(models.Model): name = models.CharField(max_length=100) game = models.CharField(max_length=100) status = models.CharField(max_length=100, choices=STATUS_CHOICES, default='stopped') - installation_path = models.CharField(max_length=255) + installation_path = models.CharField(max_length=255, blank=True, null=True) custom_script_content = models.TextField(blank=True, null=True) def __str__(self): diff --git a/coffeemachine/servers/views.py b/coffeemachine/servers/views.py index 90db98b..081a6e1 100644 --- a/coffeemachine/servers/views.py +++ b/coffeemachine/servers/views.py @@ -1,8 +1,9 @@ -# servers/views.py from django.shortcuts import render, redirect from django.views import View +from django.conf import settings from .models import GameServer from .forms import GameServerForm +from pathlib import Path import subprocess import os import urllib.request @@ -48,6 +49,10 @@ class InstallServerView(View): form = self.form_class(request.POST, request.FILES) if form.is_valid(): server = form.save(commit=False) + if not form.cleaned_data["installation_path"]: + server.installation_path = os.path.join( + settings.GAME_SERVERS_DIR, server.game + ) os.makedirs(server.installation_path, exist_ok=True) if form.cleaned_data["existing_server"]: @@ -59,6 +64,7 @@ class InstallServerView(View): script_path = self.handle_custom_script(form, server) if not script_path: script_path = self.download_lgsm_script(server.installation_path) + print(server.installation_path) server.status = "installing" server.save() subprocess.run( @@ -101,7 +107,14 @@ class InstallServerView(View): def download_lgsm_script(self, installation_path): url = "https://linuxgsm.sh" script_path = os.path.join(installation_path, "linuxgsm.sh") - urllib.request.urlretrieve(url, script_path) + request = urllib.request.Request(url) + request.add_header( + "User-Agent", + "Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0", + ) + response = urllib.request.urlopen(request) + with open(script_path, "wb") as script_file: + script_file.write(response.read()) os.chmod(script_path, 0o755) # Make the script executable return script_path diff --git a/coffeemachine/settings.py b/coffeemachine/settings.py index 165bc53..1c485c0 100644 --- a/coffeemachine/settings.py +++ b/coffeemachine/settings.py @@ -17,7 +17,7 @@ from autosecretkey import AutoSecretKey # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent -ASK = AutoSecretKey(BASE_DIR / 'settings.ini') +ASK = AutoSecretKey(BASE_DIR / "settings.ini") SECRET_KEY = ASK.secret_key CONFIG = ASK.config @@ -26,59 +26,60 @@ CONFIG = ASK.config DEBUG = CONFIG.getboolean("CoffeeMachine", "Debug", fallback=False) ALLOWED_HOSTS = CONFIG.get("CoffeeMachine", "Hostname", fallback="*").split(",") +CSRF_TRUSTED_ORIGINS = [f"https://{host}" for host in ALLOWED_HOSTS] + # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'coffeemachine.servers', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "coffeemachine.servers", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'coffeemachine.urls' +ROOT_URLCONF = "coffeemachine.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'coffeemachine.wsgi.application' +WSGI_APPLICATION = "coffeemachine.wsgi.application" # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } @@ -88,16 +89,16 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -105,9 +106,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/5.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -117,7 +118,7 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = "static/" STATICFILES_DIRS = [ BASE_DIR / "coffeemachine" / "static", @@ -126,4 +127,13 @@ STATICFILES_DIRS = [ # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# Coffee Machine settings + +GAME_SERVERS_DIR = Path( + CONFIG.get("CoffeeMachine", "GameServersDir", fallback=BASE_DIR / "game_servers") +) + +if not GAME_SERVERS_DIR.exists(): + GAME_SERVERS_DIR.mkdir()