diff --git a/docker/docker-py3-kms-minimal/Dockerfile b/docker/docker-py3-kms-minimal/Dockerfile index 883d12e..adabdab 100644 --- a/docker/docker-py3-kms-minimal/Dockerfile +++ b/docker/docker-py3-kms-minimal/Dockerfile @@ -12,6 +12,7 @@ ENV HWID RANDOM ENV LOGLEVEL INFO ENV LOGFILE STDOUT ENV LOGSIZE "" +ENV WEBUI 0 COPY docker/docker-py3-kms-minimal/requirements.txt /home/py-kms/requirements.txt RUN apk add --no-cache --update \ @@ -21,7 +22,6 @@ bash \ ca-certificates \ shadow \ tzdata \ - netcat-openbsd \ && pip3 install --no-cache-dir -r /home/py-kms/requirements.txt \ && adduser -S py-kms -G users -s /bin/bash \ && chown py-kms:users /home/py-kms \ @@ -30,14 +30,14 @@ bash \ COPY ./py-kms /home/py-kms COPY docker/entrypoint.py /usr/bin/entrypoint.py +COPY docker/healthcheck.py /usr/bin/healthcheck.py COPY docker/start.py /usr/bin/start.py - -RUN chmod 755 /usr/bin/entrypoint.py +RUN chmod 555 /usr/bin/entrypoint.py /usr/bin/healthcheck.py /usr/bin/start.py WORKDIR /home/py-kms EXPOSE ${PORT}/tcp -HEALTHCHECK --interval=5m --timeout=3s --start-period=10s --retries=4 CMD echo | nc -z ${IP%% *} ${PORT} || exit 1 +HEALTHCHECK --interval=5m --timeout=10s --start-period=10s --retries=3 CMD /usr/bin/python3 /usr/bin/healthcheck.py ENTRYPOINT ["/usr/bin/python3", "-u", "/usr/bin/entrypoint.py"] diff --git a/docker/docker-py3-kms/Dockerfile b/docker/docker-py3-kms/Dockerfile index 996da01..7bfb256 100644 --- a/docker/docker-py3-kms/Dockerfile +++ b/docker/docker-py3-kms/Dockerfile @@ -16,6 +16,7 @@ ENV LOGLEVEL INFO ENV LOGFILE STDOUT ENV LOGSIZE "" ENV TZ America/Chicago +ENV WEBUI 1 COPY docker/docker-py3-kms/requirements.txt /home/py-kms/ RUN apk add --no-cache --update \ @@ -26,7 +27,6 @@ RUN apk add --no-cache --update \ ca-certificates \ tzdata \ shadow \ - netcat-openbsd \ && pip3 install --no-cache-dir -r /home/py-kms/requirements.txt \ #&& apk del git build-base python3-dev \ && mkdir /db/ \ @@ -37,19 +37,19 @@ RUN apk add --no-cache --update \ COPY py-kms /home/py-kms/ COPY docker/entrypoint.py /usr/bin/entrypoint.py +COPY docker/healthcheck.py /usr/bin/healthcheck.py COPY docker/start.py /usr/bin/start.py +RUN chmod 555 /usr/bin/entrypoint.py /usr/bin/healthcheck.py /usr/bin/start.py # Web-interface specifics COPY LICENSE /LICENSE RUN echo "$BUILD_COMMIT" > /VERSION && echo "$BUILD_BRANCH" >> /VERSION -RUN chmod 755 /usr/bin/entrypoint.py - WORKDIR /home/py-kms EXPOSE ${PORT}/tcp EXPOSE 8080/tcp -HEALTHCHECK --interval=5m --timeout=3s --start-period=10s --retries=4 CMD echo | nc -z ${IP%% *} ${PORT} || exit 1 +HEALTHCHECK --interval=5m --timeout=10s --start-period=10s --retries=3 CMD /usr/bin/python3 /usr/bin/healthcheck.py ENTRYPOINT [ "/usr/bin/python3", "-u", "/usr/bin/entrypoint.py" ] diff --git a/docker/docker-py3-kms/requirements.txt b/docker/docker-py3-kms/requirements.txt index 7bdfb22..0c301de 100644 --- a/docker/docker-py3-kms/requirements.txt +++ b/docker/docker-py3-kms/requirements.txt @@ -1,5 +1,5 @@ dnspython==2.2.1 tzlocal==4.2 -Flask==2.1.2 +Flask==2.3.2 gunicorn==20.1.0 \ No newline at end of file diff --git a/docker/entrypoint.py b/docker/entrypoint.py index 34a7e9a..8035cd0 100755 --- a/docker/entrypoint.py +++ b/docker/entrypoint.py @@ -13,22 +13,10 @@ import time PYTHON3 = '/usr/bin/python3' dbPath = os.path.join(os.sep, 'home', 'py-kms', 'db') # Do not include the database file name, as we must correct the folder permissions (the db file is recursively reachable) -log_level_bootstrap = log_level = os.getenv('LOGLEVEL', 'INFO') -if log_level_bootstrap == "MININFO": - log_level_bootstrap = "INFO" -loggersrv = logging.getLogger('logsrv') -loggersrv.setLevel(log_level_bootstrap) -streamhandler = logging.StreamHandler(sys.stdout) -streamhandler.setLevel(log_level_bootstrap) -formatter = logging.Formatter(fmt = '\x1b[94m%(asctime)s %(levelname)-8s %(message)s', - datefmt = '%a, %d %b %Y %H:%M:%S',) -streamhandler.setFormatter(formatter) -loggersrv.addHandler(streamhandler) - -def change_uid_grp(): +def change_uid_grp(logger): if os.geteuid() != 0: - loggersrv.info(f'not root user, cannot change uid/gid.') + logger.info(f'not root user, cannot change uid/gid.') return None user_db_entries = pwd.getpwnam("py-kms") user_grp_db_entries = grp.getgrnam("users") @@ -40,43 +28,52 @@ def change_uid_grp(): os.chown("/usr/bin/start.py", new_uid, new_gid) if os.path.isdir(dbPath): # Corret permissions recursively, as to access the database file, also its parent folder must be accessible - loggersrv.debug(f'Correcting owner permissions on {dbPath}.') + logger.debug(f'Correcting owner permissions on {dbPath}.') os.chown(dbPath, new_uid, new_gid) for root, dirs, files in os.walk(dbPath): for dName in dirs: dPath = os.path.join(root, dName) - loggersrv.debug(f'Correcting owner permissions on {dPath}.') + logger.debug(f'Correcting owner permissions on {dPath}.') os.chown(dPath, new_uid, new_gid) for fName in files: fPath = os.path.join(root, fName) - loggersrv.debug(f'Correcting owner permissions on {fPath}.') + logger.debug(f'Correcting owner permissions on {fPath}.') os.chown(fPath, new_uid, new_gid) - loggersrv.debug(subprocess.check_output(['ls', '-la', dbPath]).decode()) + logger.debug(subprocess.check_output(['ls', '-la', dbPath]).decode()) if 'LOGFILE' in os.environ and os.path.exists(os.environ['LOGFILE']): # Oh, the user also wants a custom log file -> make sure start.py can access it by setting the correct permissions (777) os.chmod(os.environ['LOGFILE'], 0o777) - loggersrv.error(str(subprocess.check_output(['ls', '-la', os.environ['LOGFILE']]))) - loggersrv.info("Setting gid to '%s'." % str(new_gid)) + logger.error(str(subprocess.check_output(['ls', '-la', os.environ['LOGFILE']]))) + logger.info("Setting gid to '%s'." % str(new_gid)) os.setgid(new_gid) - loggersrv.info("Setting uid to '%s'." % str(new_uid)) + logger.info("Setting uid to '%s'." % str(new_uid)) os.setuid(new_uid) - -def change_tz(): +def change_tz(logger): tz = os.getenv('TZ', 'etc/UTC') # TZ is not symlinked and defined TZ exists if tz not in os.readlink('/etc/localtime') and os.path.isfile('/usr/share/zoneinfo/' + tz) and hasattr(time, 'tzset'): - loggersrv.info("Setting timzeone to %s" % tz ) + logger.info("Setting timzeone to %s" % tz ) # time.tzet() should be called on Unix, but doesn't exist on Windows. time.tzset() -# Main -if (__name__ == "__main__"): +if __name__ == "__main__": + log_level_bootstrap = log_level = os.getenv('LOGLEVEL', 'INFO') + if log_level_bootstrap == "MININFO": + log_level_bootstrap = "INFO" + loggersrv = logging.getLogger('entrypoint.py') + loggersrv.setLevel(log_level_bootstrap) + streamhandler = logging.StreamHandler(sys.stdout) + streamhandler.setLevel(log_level_bootstrap) + formatter = logging.Formatter(fmt = '\x1b[94m%(asctime)s %(levelname)-8s %(message)s', datefmt = '%a, %d %b %Y %H:%M:%S',) + streamhandler.setFormatter(formatter) + loggersrv.addHandler(streamhandler) loggersrv.info("Log level: %s" % log_level) loggersrv.debug("user id: %s" % os.getuid()) - change_tz() - childProcess = subprocess.Popen(PYTHON3 + " -u /usr/bin/start.py", preexec_fn=change_uid_grp(), shell=True) + + change_tz(loggersrv) + childProcess = subprocess.Popen(PYTHON3 + " -u /usr/bin/start.py", preexec_fn=change_uid_grp(loggersrv), shell=True) def shutdown(signum, frame): loggersrv.info("Received signal %s, shutting down..." % signum) childProcess.terminate() # This will also cause communicate() from below to continue diff --git a/docker/healthcheck.py b/docker/healthcheck.py new file mode 100755 index 0000000..0ad45ca --- /dev/null +++ b/docker/healthcheck.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 -u +import os +import sys +import logging + +def do_check(logger): + import socket + listen_ip = os.environ.get('IP', '::').split() + listen_ip.insert(0, '127.0.0.1') # always try to connect to localhost first + listen_port = os.environ.get('PORT', '1688') + for ip in listen_ip: + try: + s = socket.socket(socket.AF_INET6 if ':' in ip else socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) # 1 second timeout + address = ip if ':' in ip else (ip, int(listen_port)) + logger.debug(f"Trying to connect to {address}...") + s.connect(address) + s.close() + return True + except: + pass + return False # no connection could be established + + +if __name__ == '__main__': + log_level_bootstrap = log_level = os.getenv('LOGLEVEL', 'INFO') + if log_level_bootstrap == "MININFO": + log_level_bootstrap = "INFO" + loggersrv = logging.getLogger('healthcheck.py') + loggersrv.setLevel(log_level_bootstrap) + streamhandler = logging.StreamHandler(sys.stdout) + streamhandler.setLevel(log_level_bootstrap) + formatter = logging.Formatter(fmt='\x1b[94m%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S') + streamhandler.setFormatter(formatter) + loggersrv.addHandler(streamhandler) + + sys.exit(0 if do_check(loggersrv) else 1) diff --git a/docker/start.py b/docker/start.py old mode 100644 new mode 100755 index fd8a546..f5e2df3 --- a/docker/start.py +++ b/docker/start.py @@ -21,15 +21,12 @@ argumentVariableMapping = { } db_path = os.path.join(os.sep, 'home', 'py-kms', 'db', 'pykms_database.db') -log_level_bootstrap = log_level = os.environ.get('LOGLEVEL', 'INFO') -if log_level_bootstrap == "MININFO": - log_level_bootstrap = "INFO" log_file = os.environ.get('LOGFILE', 'STDOUT') listen_ip = os.environ.get('IP', '::').split() listen_port = os.environ.get('PORT', '1688') -want_webui = os.environ.get('WEBUI', '0') +want_webui = os.environ.get('WEBUI', '0') == '1' # if the variable is not provided, we assume the user does not want the webui -def start_kms(): +def start_kms(logger): # Make sure the full path to the db exists if want_webui and not os.path.exists(os.path.dirname(db_path)): os.makedirs(os.path.dirname(db_path), exist_ok=True) @@ -40,16 +37,16 @@ def start_kms(): if env in os.environ and os.environ.get(env) != '': command.append(arg) command.append(os.environ.get(env)) + if want_webui: # add this command directly before the "connect" subparser - otherwise you'll get silent crashes! + command.append('-s') + command.append(db_path) if len(listen_ip) > 1: command.append("connect") for i in range(1, len(listen_ip)): command.append("-n") command.append(listen_ip[i] + "," + listen_port) - if want_webui: - command.append('-s') - command.append(db_path) - loggersrv.debug("server_cmd: %s" % (" ".join(str(x) for x in command).strip())) + logger.debug("server_cmd: %s" % (" ".join(str(x) for x in command).strip())) pykms_process = subprocess.Popen(command) pykms_webui_process = None @@ -63,7 +60,7 @@ def start_kms(): pykms_webui_env['PYKMS_VERSION_PATH'] = '/VERSION' pykms_webui_process = subprocess.Popen(['gunicorn', '--log-level', os.environ.get('LOGLEVEL'), 'pykms_WebUI:app'], env=pykms_webui_env) except Exception as e: - loggersrv.error("Failed to start webui: %s" % e) + logger.error("Failed to start webui (ignoring and continuing anyways): %s" % e) try: pykms_process.wait() @@ -79,14 +76,17 @@ def start_kms(): # Main -if (__name__ == "__main__"): - loggersrv = logging.getLogger('logsrv') +if __name__ == "__main__": + log_level_bootstrap = log_level = os.environ.get('LOGLEVEL', 'INFO') + if log_level_bootstrap == "MININFO": + log_level_bootstrap = "INFO" + loggersrv = logging.getLogger('start.py') loggersrv.setLevel(log_level_bootstrap) streamhandler = logging.StreamHandler(sys.stdout) streamhandler.setLevel(log_level_bootstrap) - formatter = logging.Formatter(fmt='\x1b[94m%(asctime)s %(levelname)-8s %(message)s', - datefmt='%a, %d %b %Y %H:%M:%S') + formatter = logging.Formatter(fmt='\x1b[94m%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S') streamhandler.setFormatter(formatter) loggersrv.addHandler(streamhandler) loggersrv.debug("user id: %s" % os.getuid()) - start_kms() + + start_kms(loggersrv) diff --git a/docs/Getting Started.md b/docs/Getting Started.md index d66a051..f066153 100644 --- a/docs/Getting Started.md +++ b/docs/Getting Started.md @@ -46,9 +46,9 @@ services: - 1688:1688 - 8080:8080 environment: - - IP='::' - - HWID=RANDOM - - LOGLEVEL=INFO + IP: "::" + HWID: RANDOM + LOGLEVEL: INFO restart: always volumes: - ./db:/home/py-kms/db diff --git a/docs/Usage.md b/docs/Usage.md index 6586386..fbe590d 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -28,7 +28,7 @@ You may want to select the locale ID of your country instead. See [here](https://msdn.microsoft.com/en-us/library/cc233982.aspx) for a list of valid _LCIDs_. -w or --hwid -> Use specified _HWID_ for all products. Use `-w RANDOM` to generate a random HWID. Default is _364F463A8863D35F_. +> Use specified _HWID_ for all products. Use `-w RANDOM` to generate a random HWID. Default is random. Hardware Identification is a security measure used by Microsoft upon the activation of the Windows operating system. As part of the Product Activation system, a unique HWID number is generated when the operating system is first installed. The _HWID_ identifies the hardware components that the system @@ -232,8 +232,8 @@ ENV RENEWAL_INTERVAL 10080 # hwid # Use this flag to specify a HWID. # The HWID must be an 16-character string of hex characters. -# The default is "364F463A8863D35F" or type "RANDOM" to auto generate the HWID. -ENV HWID 364F463A8863D35F +# The default is "RANDOM" to auto-generate the HWID or type a specific value. +ENV HWID RANDOM # log level ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG") # Use this flag to set a Loglevel. The default is "ERROR". diff --git a/py-kms/pykms_Server.py b/py-kms/pykms_Server.py index f506455..cbe96a8 100755 --- a/py-kms/pykms_Server.py +++ b/py-kms/pykms_Server.py @@ -192,8 +192,8 @@ for server OSes and Office >=5', 'def' : None, 'des' : "clientcount"}, 'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default.', 'def' : False, 'file': os.path.join('.', 'pykms_database.db'), 'des' : "sqlite"}, 'hwid' : {'help' : 'Use this option to specify a HWID. The HWID must be an 16-character string of hex characters. \ -The default is \"364F463A8863D35F\" or type \"RANDOM\" to auto generate the HWID.', - 'def' : "364F463A8863D35F", 'des' : "hwid"}, +Type \"RANDOM\" to auto-generate the HWID.', + 'def' : "RANDOM", 'des' : "hwid"}, 'time0' : {'help' : 'Maximum inactivity time (in seconds) after which the connection with the client is closed. If \"None\" (default) serve forever.', 'def' : None, 'des' : "timeoutidle"}, 'time1' : {'help' : 'Set the maximum time to wait for sending / receiving a request / response. Default is no timeout.', @@ -481,7 +481,7 @@ class kmsServerHandler(socketserver.BaseRequestHandler): try: self.data = self.request.recv(1024) if self.data == '' or not self.data: - pretty_printer(log_obj = loggersrv.warning, + pretty_printer(log_obj = loggersrv.debug, # use debug, as the healthcheck will spam this put_text = "{reverse}{yellow}{bold}No data received.{end}") break except socket.error as e: