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.
This commit is contained in:
Kumi 2024-06-05 18:58:17 +02:00
parent 6778c2a2fb
commit ce520b914a
Signed by: kumi
GPG key ID: ECBCC9082395383F
6 changed files with 90 additions and 43 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ venv/
__pycache__/
settings.ini
db.sqlite3
game_servers/

View file

@ -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."
)

View file

@ -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),
),
]

View file

@ -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):

View file

@ -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

View file

@ -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()