import os import functools import hashlib import json import time import docker import docker.errors import pymysql import pymysql.cursors from flask import Flask, Response, request, session, redirect, url_for, render_template, flash from werkzeug.middleware.proxy_fix import ProxyFix app = Flask(__name__) app.secret_key = os.environ.get("ADMIN_SECRET_KEY", "dev-only-change-me") # Sits behind the webtools nginx container at /admin/ (see webtools/nginx.conf); # this reads X-Forwarded-Prefix/-Proto/-For so url_for()/redirect() produce # correct /admin/-prefixed URLs instead of root-relative ones. app.wsgi_app = ProxyFix(app.wsgi_app, x_prefix=1, x_proto=1, x_for=1, x_host=1) def hash_password(plaintext): # The game client MD5-hashes the password client-side before it ever # hits the wire, so accounts.password is always an MD5 hex digest -- # match that here so admin-created/changed passwords work in-game. return hashlib.md5(plaintext.encode("utf-8")).hexdigest() DB_HOST = os.environ.get("DB_HOST", "db") DB_PORT = int(os.environ.get("DB_PORT", "3306")) DB_USER = os.environ.get("DB_USER", "rose") DB_PASSWORD = os.environ.get("DB_PASSWORD", "change_me") DB_NAME = os.environ.get("DB_NAME", "ktrose") # Only accounts with accesslevel >= this may log in to the admin panel. ADMIN_MIN_ACCESSLEVEL = int(os.environ.get("ADMIN_MIN_ACCESSLEVEL", "900")) COMPOSE_PROJECT_NAME = os.environ.get("COMPOSE_PROJECT_NAME", "ross") GAME_SERVICES = ["loginserver", "charserver", "worldserver"] _docker_client = None def get_docker(): global _docker_client if _docker_client is None: _docker_client = docker.from_env() return _docker_client def get_service_container(service): # Looked up by compose labels rather than a hardcoded "--1" # name, since that naming scheme isn't guaranteed across compose versions. containers = get_docker().containers.list( all=True, filters={ "label": [ f"com.docker.compose.project={COMPOSE_PROJECT_NAME}", f"com.docker.compose.service={service}", ] }, ) return containers[0] if containers else None CHAR_STAT_FIELDS = [ "level", "zuly", "str", "con", "dex", "_int", "cha", "sen", "curHp", "curMp", "statp", "skillp", "stamina", "exp", "classid", ] # Column edited via its own