diff --git a/Containerfile b/Containerfile index 55057d8..9ae9198 100644 --- a/Containerfile +++ b/Containerfile @@ -70,6 +70,7 @@ COPY container/root/ / ENV \ # Docker-specific configuration MALOJA_SKIP_SETUP=yes \ + MALOJA_CONTAINER=yes \ PYTHONUNBUFFERED=1 \ # Prevents breaking change for previous container that ran maloja as root # On linux hosts (non-podman rootless) these variables should be set to the diff --git a/dev/releases/3.2.yml b/dev/releases/3.2.yml index 75971c7..3c890e6 100644 --- a/dev/releases/3.2.yml +++ b/dev/releases/3.2.yml @@ -33,5 +33,7 @@ minor_release_name: "Nicole" - "[Technical] Upgraded all third party modules to use requests module and send User Agent" 3.2.2: notes: + - "[Security] Fixed XSS vulnerability in error page (Disclosed by https://github.com/NULLYUKI)" + - "[Architecture] Reworked the default directory selection" - "[Feature] Added option to show scrobbles on tile charts" - "[Bugfix] Fixed Last.fm authentication" \ No newline at end of file diff --git a/maloja/__pkginfo__.py b/maloja/__pkginfo__.py index 47c8e25..6bd621b 100644 --- a/maloja/__pkginfo__.py +++ b/maloja/__pkginfo__.py @@ -4,7 +4,7 @@ # you know what f*ck it # this is hardcoded for now because of that damn project / package name discrepancy # i'll fix it one day -VERSION = "3.2.1" +VERSION = "3.2.2" HOMEPAGE = "https://github.com/krateng/maloja" diff --git a/maloja/data_files/config/rules/predefined/krateng_kpopgirlgroups.tsv b/maloja/data_files/config/rules/predefined/krateng_kpopgirlgroups.tsv index 840bd84..ad205fb 100644 --- a/maloja/data_files/config/rules/predefined/krateng_kpopgirlgroups.tsv +++ b/maloja/data_files/config/rules/predefined/krateng_kpopgirlgroups.tsv @@ -160,8 +160,8 @@ replaceartist 여자친구 GFriend GFriend # Girl's Generation replaceartist 소녀시대 Girls' Generation replaceartist SNSD Girls' Generation -replaceartist Girls' Generation-TTS TaeTiSeo -countas TaeTiSeo Girls' Generation +replaceartist Girls' Generation-TTS TaeTiSeo +countas TaeTiSeo Girls' Generation # Apink replaceartist A Pink Apink @@ -217,6 +217,8 @@ countas Pristin V Pristin # CLC countas Sorn CLC +countas Yeeun CLC +countas Seungyeon CLC # Popular Remixes artistintitle Areia Remix Areia diff --git a/maloja/pkg_global/conf.py b/maloja/pkg_global/conf.py index be280fc..0d404fd 100644 --- a/maloja/pkg_global/conf.py +++ b/maloja/pkg_global/conf.py @@ -17,9 +17,11 @@ AUX_MODE = True # DIRECRORY_CONFIG, DIRECRORY_STATE, DIRECTORY_LOGS and DIRECTORY_CACHE # config can only be determined by environment variable, the others can be loaded # from the config files -# explicit settings will always be respected, fallback to default -# if default isn't usable, and config writable, find alternative and fix it in settings +# we don't specify 'default' values in the normal sense of the config object +# the default is none, meaning the app should figure it out (depending on environment) +# the actual 'default' values of our folders are simply in code since they are dependent on environment (container?) +# and we need to actually distinguish them from the user having specified something # USEFUL FUNCS pthj = os.path.join @@ -27,9 +29,7 @@ pthj = os.path.join def is_dir_usable(pth): try: os.makedirs(pth,exist_ok=True) - os.mknod(pthj(pth,".test")) - os.remove(pthj(pth,".test")) - return True + return os.access(pth,os.W_OK) except Exception: return False @@ -41,6 +41,9 @@ def get_env_vars(key,pathsuffix=[]): directory_info = { "config":{ "sentinel":".maloja_config_sentinel", + "possible_folders_container":[ + "/config/config" + ], "possible_folders":[ "/etc/maloja", os.path.expanduser("~/.local/share/maloja") @@ -49,14 +52,21 @@ directory_info = { }, "cache":{ "sentinel":".maloja_cache_sentinel", + "possible_folders_container":[ + "/config/cache" + ], "possible_folders":[ "/var/cache/maloja", - os.path.expanduser("~/.local/share/maloja/cache") + os.path.expanduser("~/.local/share/maloja/cache"), + "/tmp/maloja" ], "setting":"directory_cache" }, "state":{ "sentinel":".maloja_state_sentinel", + "possible_folders_container":[ + "/config/state" + ], "possible_folders":[ "/var/lib/maloja", os.path.expanduser("~/.local/share/maloja") @@ -65,6 +75,9 @@ directory_info = { }, "logs":{ "sentinel":".maloja_logs_sentinel", + "possible_folders_container":[ + "/config/logs" + ], "possible_folders":[ "/var/log/maloja", os.path.expanduser("~/.local/share/maloja/logs") @@ -77,25 +90,27 @@ directory_info = { # checks if one has been in use before and writes it to dict/config # if not, determines which to use and writes it to dict/config # returns determined folder -def find_good_folder(datatype,configobject): +def find_good_folder(datatype): info = directory_info[datatype] + possible_folders = info['possible_folders'] + if os.environ.get("MALOJA_CONTAINER"): + possible_folders = info['possible_folders_container'] + possible_folders + # check each possible folder if its used - for p in info['possible_folders']: + for p in possible_folders: if os.path.exists(pthj(p,info['sentinel'])): if is_dir_usable(p): - print(p,"was apparently used as maloja's folder for",datatype,"- fixing in settings") - configobject[info['setting']] = p + #print(p,"was apparently used as maloja's folder for",datatype,"- fixing in settings") return p else: - raise PermissionError(f"Can no longer use previously used path {p}") + raise PermissionError(f"Can no longer use previously used {datatype} folder {p}") #print("Could not find previous",datatype,"folder") # check which one we can use - for p in info['possible_folders']: + for p in possible_folders: if is_dir_usable(p): - print(p,"has been selected as maloja's folder for",datatype) - configobject[info['setting']] = p + #print(p,"has been selected as maloja's folder for",datatype) return p #print("No folder can be used for",datatype) #print("This should not happen!") @@ -106,26 +121,20 @@ def find_good_folder(datatype,configobject): ### STEP 1 - find out where the settings file is -# environment variables + maloja_dir_config = os.environ.get("MALOJA_DATA_DIRECTORY") or os.environ.get("MALOJA_DIRECTORY_CONFIG") - if maloja_dir_config is None: - maloja_dir_config = find_good_folder('config',{}) - found_new_config_dir = True + # if nothing is set, we set our own + maloja_dir_config = find_good_folder('config') else: - found_new_config_dir = False - # remember whether we had to find our config dir or it was user-specified + pass + # if there is an environment variable, this is 100% explicitly defined by the user, so we respect it + # the user might run more than one instances on the same machine, so we don't do any heuristics here + # if you define this, we believe it! os.makedirs(maloja_dir_config,exist_ok=True) - -oldsettingsfile = pthj(maloja_dir_config,"settings","settings.ini") -newsettingsfile = pthj(maloja_dir_config,"settings.ini") - - - -if os.path.exists(oldsettingsfile): - os.rename(oldsettingsfile,newsettingsfile) +settingsfile = pthj(maloja_dir_config,"settings.ini") ### STEP 2 - create settings object @@ -135,10 +144,10 @@ malojaconfig = Configuration( settings={ "Setup":{ "data_directory":(tp.String(), "Data Directory", None, "Folder for all user data. Overwrites all choices for specific directories."), - "directory_config":(tp.String(), "Config Directory", "/etc/maloja", "Folder for config data. Only applied when global data directory is not set."), - "directory_state":(tp.String(), "State Directory", "/var/lib/maloja", "Folder for state data. Only applied when global data directory is not set."), - "directory_logs":(tp.String(), "Log Directory", "/var/log/maloja", "Folder for log data. Only applied when global data directory is not set."), - "directory_cache":(tp.String(), "Cache Directory", "/var/cache/maloja", "Folder for cache data. Only applied when global data directory is not set."), + "directory_config":(tp.String(), "Config Directory", None, "Folder for config data. Only applied when global data directory is not set."), + "directory_state":(tp.String(), "State Directory", None, "Folder for state data. Only applied when global data directory is not set."), + "directory_logs":(tp.String(), "Log Directory", None, "Folder for log data. Only applied when global data directory is not set."), + "directory_cache":(tp.String(), "Cache Directory", None, "Folder for cache data. Only applied when global data directory is not set."), "skip_setup":(tp.Boolean(), "Skip Setup", False, "Make server setup process non-interactive. Vital for Docker."), "force_password":(tp.String(), "Force Password", None, "On startup, overwrite admin password with this one. This should usually only be done via environment variable in Docker."), "clean_output":(tp.Boolean(), "Avoid Mutable Console Output", False, "Use if console output will be redirected e.g. to a web interface.") @@ -214,18 +223,15 @@ malojaconfig = Configuration( "theme":(tp.String(), "Theme", "maloja") } }, - configfile=newsettingsfile, + configfile=settingsfile, save_endpoint="/apis/mlj_1/settings", env_prefix="MALOJA_", extra_files=["/run/secrets/maloja.yml","/run/secrets/maloja.ini"] ) -if found_new_config_dir: - try: - malojaconfig["DIRECTORY_CONFIG"] = maloja_dir_config - except PermissionError as e: - pass +if not malojaconfig.readonly: + malojaconfig["DIRECTORY_CONFIG"] = maloja_dir_config # this really doesn't matter because when are we gonna load info about where # the settings file is stored from the settings file # but oh well @@ -247,17 +253,17 @@ except PermissionError as e: pass -### STEP 3 - check all possible folders for files (old installation) - +### STEP 3 - now check the other directories if not malojaconfig.readonly: for datatype in ("state","cache","logs"): - # obviously default values shouldn't trigger this - # if user has nothing specified, we need to use this - if malojaconfig.get_specified(directory_info[datatype]['setting']) is None and malojaconfig.get_specified('DATA_DIRECTORY') is None: - find_good_folder(datatype,malojaconfig) - + # if the setting is specified in the file or via a user environment variable, we accept it (we'll check later if it's usable) + if malojaconfig[directory_info[datatype]['setting']] or malojaconfig['DATA_DIRECTORY']: + pass + # otherwise, find a good one + else: + malojaconfig[directory_info[datatype]['setting']] = find_good_folder(datatype) @@ -310,11 +316,11 @@ for identifier,path in data_directories.items(): # just move to the next one if identifier in ['cache']: print("Cannot use",path,"for cache, finding new folder...") - find_good_folder('cache',malojaconfig) - data_directories['cache'] = dir_settings['cache'] = malojaconfig['DIRECTORY_CACHE'] + data_directories['cache'] = dir_settings['cache'] = malojaconfig['DIRECTORY_CACHE'] = find_good_folder('cache') else: print("Directory",path,"is not usable.") print("Please change permissions or settings!") + print("Make sure Maloja has write and execute access to this directory.") raise diff --git a/maloja/web/jinja/abstracts/base.jinja b/maloja/web/jinja/abstracts/base.jinja index be9c746..8934040 100644 --- a/maloja/web/jinja/abstracts/base.jinja +++ b/maloja/web/jinja/abstracts/base.jinja @@ -75,7 +75,7 @@
- +
diff --git a/maloja/web/jinja/admin_albumless.jinja b/maloja/web/jinja/admin_albumless.jinja index a04ee6a..9c062d6 100644 --- a/maloja/web/jinja/admin_albumless.jinja +++ b/maloja/web/jinja/admin_albumless.jinja @@ -6,6 +6,8 @@ Here you can find tracks that currently have no album.

{% with list = dbc.get_tracks_without_album() %} +You have {{list|length}} tracks with no album.

+ {% include 'partials/list_tracks.jinja' %} {% endwith %} diff --git a/maloja/web/jinja/admin_overview.jinja b/maloja/web/jinja/admin_overview.jinja index b741070..d67d79b 100644 --- a/maloja/web/jinja/admin_overview.jinja +++ b/maloja/web/jinja/admin_overview.jinja @@ -67,9 +67,9 @@
  • manually scrobble from track pages
  • delete scrobbles
  • reparse scrobbles
  • -
  • edit tracks and artists
  • -
  • merge tracks and artists
  • -
  • upload artist and track art by dropping a file on the existing image on an artist or track page
  • +
  • edit tracks, albums and artists
  • +
  • merge tracks, albums and artists
  • +
  • upload artist, album and track art by dropping a file on the existing image on an artist or track page
  • see more detailed error pages
  • diff --git a/maloja/web/jinja/error.jinja b/maloja/web/jinja/error.jinja index 3654bd3..9904316 100644 --- a/maloja/web/jinja/error.jinja +++ b/maloja/web/jinja/error.jinja @@ -8,8 +8,8 @@
    -

    {{ error_desc }}


    - {{ error_full_desc }} +

    {{ error_desc | e }}


    + {{ error_full_desc | e }} diff --git a/maloja/web/jinja/partials/awards_album.jinja b/maloja/web/jinja/partials/awards_album.jinja index 7b63cb2..ce7f834 100644 --- a/maloja/web/jinja/partials/awards_album.jinja +++ b/maloja/web/jinja/partials/awards_album.jinja @@ -63,7 +63,7 @@ {%- if e.scrobbles >= settings.scrobbles_diamond -%}{% set cert = 'diamond' %}{%- endif -%} {%- if cert -%} - + {% include 'icons/cert_track.jinja' %} {%- endif %} diff --git a/maloja/web/jinja/partials/awards_artist.jinja b/maloja/web/jinja/partials/awards_artist.jinja index 9944a9f..b87c22c 100644 --- a/maloja/web/jinja/partials/awards_artist.jinja +++ b/maloja/web/jinja/partials/awards_artist.jinja @@ -72,7 +72,7 @@ {%- if e.scrobbles >= settings.scrobbles_diamond_album -%}{% set cert = 'diamond' %}{%- endif -%} {%- if cert -%} - + {% include 'icons/cert_album.jinja' %} {%- endif %} @@ -87,7 +87,7 @@ {%- if e.scrobbles >= settings.scrobbles_diamond -%}{% set cert = 'diamond' %}{%- endif -%} {%- if cert -%} - + {% include 'icons/cert_track.jinja' %} {%- endif %} diff --git a/pyproject.toml b/pyproject.toml index e543872..3a56757 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "malojaserver" -version = "3.2.1" +version = "3.2.2" description = "Self-hosted music scrobble database" readme = "./README.md" requires-python = ">=3.10"