mirror of
https://github.com/Py-KMS-Organization/py-kms.git
synced 2026-04-21 06:03:43 +00:00
Merge 8dea676a3d into db7409b51a
This commit is contained in:
commit
f694c71030
22 changed files with 336 additions and 194 deletions
36
.github/workflows/bake_to_latest.yml
vendored
36
.github/workflows/bake_to_latest.yml
vendored
|
|
@ -1,10 +1,10 @@
|
|||
name: Build release-tags
|
||||
name: Build latest/main tags
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
bake-latest:
|
||||
|
|
@ -14,20 +14,20 @@ jobs:
|
|||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: Set up QEMU
|
||||
uses: actions/checkout@v6
|
||||
- name: Docker Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: all
|
||||
- name: Set up Docker Buildx
|
||||
- name: Docker Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
|
@ -39,10 +39,17 @@ jobs:
|
|||
file: ./docker/docker-py3-kms/Dockerfile
|
||||
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||
push: true
|
||||
tags: pykmsorg/py-kms:python3,ghcr.io/py-kms-organization/py-kms:python3
|
||||
# the tag "python3" is for backward compatibility only
|
||||
tags: |
|
||||
pykmsorg/py-kms:main
|
||||
ghcr.io/py-kms-organization/py-kms:main
|
||||
pykmsorg/py-kms:main-full
|
||||
ghcr.io/py-kms-organization/py-kms:main-full
|
||||
pykmsorg/py-kms:python3
|
||||
ghcr.io/py-kms-organization/py-kms:python3
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ github.sha }}
|
||||
BUILD_BRANCH=${{ github.ref_name }}
|
||||
BUILD_REFERENCE=${{ github.ref_name }}
|
||||
- name: Build (minimal)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
|
|
@ -50,7 +57,14 @@ jobs:
|
|||
file: ./docker/docker-py3-kms-minimal/Dockerfile
|
||||
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||
push: true
|
||||
tags: pykmsorg/py-kms:latest,ghcr.io/py-kms-organization/py-kms:latest,pykmsorg/py-kms:minimal,ghcr.io/py-kms-organization/py-kms:minimal
|
||||
# the tag "minimal" is for backward compatibility only
|
||||
tags: |
|
||||
pykmsorg/py-kms:latest
|
||||
ghcr.io/py-kms-organization/py-kms:latest
|
||||
pykmsorg/py-kms:main-minimal
|
||||
ghcr.io/py-kms-organization/py-kms:main-minimal
|
||||
pykmsorg/py-kms:minimal
|
||||
ghcr.io/py-kms-organization/py-kms:minimal
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ github.sha }}
|
||||
BUILD_BRANCH=${{ github.ref_name }}
|
||||
BUILD_REFERENCE=${{ github.ref_name }}
|
||||
|
|
|
|||
34
.github/workflows/bake_to_next.yml
vendored
34
.github/workflows/bake_to_next.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
name: Build next-tags
|
||||
name: Build next tags
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
|
@ -14,20 +14,20 @@ jobs:
|
|||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: Set up QEMU
|
||||
uses: actions/checkout@v6
|
||||
- name: Docker Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: all
|
||||
- name: Set up Docker Buildx
|
||||
- name: Docker Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
|
@ -39,10 +39,17 @@ jobs:
|
|||
file: ./docker/docker-py3-kms/Dockerfile
|
||||
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||
push: true
|
||||
tags: pykmsorg/py-kms:python3-next,ghcr.io/py-kms-organization/py-kms:python3-next
|
||||
# the tag "python3-next" is for backward compatibility only
|
||||
tags: |
|
||||
pykmsorg/py-kms:next
|
||||
ghcr.io/py-kms-organization/py-kms:next
|
||||
pykmsorg/py-kms:next-full
|
||||
ghcr.io/py-kms-organization/py-kms:next-full
|
||||
pykmsorg/py-kms:python3-next
|
||||
ghcr.io/py-kms-organization/py-kms:python3-next
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ github.sha }}
|
||||
BUILD_BRANCH=${{ github.ref_name }}
|
||||
BUILD_REFERENCE=${{ github.ref_name }}
|
||||
- name: Build (minimal)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
|
|
@ -50,7 +57,14 @@ jobs:
|
|||
file: ./docker/docker-py3-kms-minimal/Dockerfile
|
||||
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||
push: true
|
||||
tags: pykmsorg/py-kms:latest-next,ghcr.io/py-kms-organization/py-kms:latest-next,pykmsorg/py-kms:minimal-next,ghcr.io/py-kms-organization/py-kms:minimal-next
|
||||
# the tag "latest-next" and "minimal-next" are for backward compatibility only
|
||||
tags: |
|
||||
pykmsorg/py-kms:next-minimal
|
||||
ghcr.io/py-kms-organization/py-kms:next-minimal
|
||||
pykmsorg/py-kms:latest-next
|
||||
ghcr.io/py-kms-organization/py-kms:latest-next
|
||||
pykmsorg/py-kms:minimal-next
|
||||
ghcr.io/py-kms-organization/py-kms:minimal-next
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ github.sha }}
|
||||
BUILD_BRANCH=${{ github.ref_name }}
|
||||
BUILD_REFERENCE=${{ github.ref_name }}
|
||||
|
|
|
|||
62
.github/workflows/bake_to_version.yml
vendored
Normal file
62
.github/workflows/bake_to_version.yml
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
name: Build version tags
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
bake-latest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Docker Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: all
|
||||
- name: Docker Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build (full)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/docker-py3-kms/Dockerfile
|
||||
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||
push: true
|
||||
tags: |
|
||||
pykmsorg/py-kms:${{ github.ref_name }}-full
|
||||
ghcr.io/py-kms-organization/py-kms:${{ github.ref_name }}-full
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ github.sha }}
|
||||
BUILD_REFERENCE=${{ github.ref_name }}
|
||||
- name: Build (minimal)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/docker-py3-kms-minimal/Dockerfile
|
||||
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||
push: true
|
||||
tags: |
|
||||
pykmsorg/py-kms:${{ github.ref_name }}
|
||||
ghcr.io/py-kms-organization/py-kms:${{ github.ref_name }}
|
||||
pykmsorg/py-kms:${{ github.ref_name }}-minimal
|
||||
ghcr.io/py-kms-organization/py-kms:${{ github.ref_name }}-minimal
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ github.sha }}
|
||||
BUILD_REFERENCE=${{ github.ref_name }}
|
||||
27
.github/workflows/test_basic_client.yml
vendored
Normal file
27
.github/workflows/test_basic_client.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
name: "Test: Basic Client"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
run-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd py-kms; timeout 30 python3 pykms_Server.py -F STDOUT -s ./pykms_database.db &
|
||||
sleep 5
|
||||
python3 pykms_Client.py -F STDOUT # fresh client
|
||||
python3 pykms_Client.py -F STDOUT -c 174f5409-0624-4ce3-b209-adde1091956b # (maybe) existing client
|
||||
python3 pykms_Client.py -F STDOUT -c 174f5409-0624-4ce3-b209-adde1091956b # now-for-sure existing client
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
name: Test-Build Docker Image
|
||||
name: "Test: Build Docker Image"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
|
@ -9,12 +9,12 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: Set up QEMU
|
||||
uses: actions/checkout@v6
|
||||
- name: Docker Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: all
|
||||
- name: Set up Docker Buildx
|
||||
- name: Docker Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build (full)
|
||||
uses: docker/build-push-action@v6
|
||||
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
push: false
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ github.sha }}
|
||||
BUILD_BRANCH=${{ github.ref_name }}
|
||||
BUILD_REFERENCE=${{ github.ref_name }}
|
||||
- name: Build (minimal)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
|
|
@ -35,4 +35,4 @@ jobs:
|
|||
push: false
|
||||
build-args: |
|
||||
BUILD_COMMIT=${{ github.sha }}
|
||||
BUILD_BRANCH=${{ github.ref_name }}
|
||||
BUILD_REFERENCE=${{ github.ref_name }}
|
||||
42
README.md
42
README.md
|
|
@ -1,48 +1,36 @@
|
|||
# Readme
|
||||
# py-kms
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
***
|
||||
|
||||
_Keep in mind that this project is not intended for production use. Feel free to use it to test your own systems or maybe even learn something from the protocol structure. :)_
|
||||
_Keep in mind that this project is not intended for production use. Feel free to use it to test your own systems or maybe even learn something from the protocol structure._ 😉
|
||||
|
||||
## History
|
||||
_py-kms_ is a port of node-kms created by [cyrozap](http://forums.mydigitallife.info/members/183074-markedsword), which is a port of either the C#, C++, or .NET implementations of KMS Emulator. The original version was written by [CODYQX4](http://forums.mydigitallife.info/members/89933-CODYQX4) and is derived from the reverse-engineered code of Microsoft's official KMS.
|
||||
This version of _py-kms_ is for itself a fork of the original implementation by [SystemRage](https://github.com/SystemRage/py-kms), which was abandoned early 2021.
|
||||
|
||||
### What is with version `1.0.0`?
|
||||
Semantic versioning is now being used in this project, so checkout the [GitHub Releases](https://github.com/Py-KMS-Organization/py-kms/releases). Before, a `CHANGELOG.md` file was used to track changes, but got abandoned over time. Its content got moved into the [Historic Releases](docs/Historic%20Releases.md) document for reference.
|
||||
|
||||
## Features
|
||||
- Responds to `v4`, `v5`, and `v6` KMS requests.
|
||||
- Supports activating:
|
||||
- Windows Vista
|
||||
- Windows 7
|
||||
- Windows 8
|
||||
- Windows 8.1
|
||||
- Windows 10 ( 1511 / 1607 / 1703 / 1709 / 1803 / 1809 )
|
||||
- Windows 10 ( 1903 / 1909 / 20H1, 20H2, 21H1, 21H2 )
|
||||
- Windows 11 ( 21H2 )
|
||||
- Windows Server 2008
|
||||
- Windows Server 2008 R2
|
||||
- Windows Server 2012
|
||||
- Windows Server 2012 R2
|
||||
- Windows Server 2016
|
||||
- Windows Server 2019
|
||||
- Windows Server 2022
|
||||
- Microsoft Office 2010 ( Volume License )
|
||||
- Microsoft Office 2013 ( Volume License )
|
||||
- Microsoft Office 2016 ( Volume License )
|
||||
- Microsoft Office 2019 ( Volume License )
|
||||
- Microsoft Office 2021 ( Volume License )
|
||||
- It's written in Python (tested with Python 3.10.1).
|
||||
- Supports activating [a lot of products](docs/Keys.md), so checkout the docs for more information.
|
||||
- It's written in Python.
|
||||
- Supports execution by `Docker`, `systemd` and many more...
|
||||
- Uses `sqlite` for persistent data storage (with a simple web-based explorer).
|
||||
|
||||
## Documentation
|
||||
The wiki has been completly reworked and is now available on [readthedocs.io](https://py-kms.readthedocs.io/en/latest/). It should provide you all the necessary information about how to setup and to use _py-kms_ , all without clumping this readme. The documentation also houses more details about activation with _py-kms_ and how to get GVLK keys.
|
||||
The wiki has been completely reworked and is now available on [readthedocs.io](https://py-kms.readthedocs.io/en/latest/). It should provide you all the necessary information about how to setup and to use _py-kms_, all without clumping this readme. The documentation also houses more details about the activation procedure with _py-kms_ and how to get GVLK keys.
|
||||
|
||||
## Quick start
|
||||
- To start the server, execute `python3 pykms_Server.py [IPADDRESS] [PORT]`, the default _IPADDRESS_ is `::` ( all interfaces ) and the default _PORT_ is `1688`. Note that both the address and port are optional. It's allowed to use IPv4 and IPv6 addresses. If you have a IPv6-capable dual-stack OS, a dual-stack socket is created when using a IPv6 address. **In case your OS does not support IPv6, make sure to explicitly specify the legacy IPv4 of `0.0.0.0`!**
|
||||
- To start the server, execute `python3 pykms_Server.py [IPADDRESS] [PORT]`, the default _IPADDRESS_ is `::` ( all interfaces ) and the default _PORT_ is `1688`.
|
||||
- Note that both the address and port are optional.
|
||||
- It's allowed to use IPv4 and IPv6 addresses.
|
||||
- If you have an IPv6-capable dual-stack OS, a dual-stack socket is created when using a IPv6 address.
|
||||
- **[In case your OS does not support IPv6](https://github.com/Py-KMS-Organization/py-kms/issues/108), make sure to explicitly specify the legacy IPv4 of `0.0.0.0`!**
|
||||
- To start the server automatically using Docker, execute `docker run -d --name py-kms --restart always -p 1688:1688 ghcr.io/py-kms-organization/py-kms`.
|
||||
- To show the help pages type: `python3 pykms_Server.py -h` and `python3 pykms_Client.py -h`.
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ ENV LCID=1033
|
|||
ENV CLIENT_COUNT=26
|
||||
ENV ACTIVATION_INTERVAL=120
|
||||
ENV RENEWAL_INTERVAL=10080
|
||||
ENV HWID RANDOM
|
||||
ENV HWID=RANDOM
|
||||
ENV LOGLEVEL=INFO
|
||||
ENV LOGFILE=STDOUT
|
||||
ENV LOGSIZE=""
|
||||
|
|
@ -36,11 +36,10 @@ COPY docker/start.py /usr/bin/start.py
|
|||
RUN chmod 555 /usr/bin/entrypoint.py /usr/bin/healthcheck.py /usr/bin/start.py
|
||||
|
||||
# Additional permission hardening: All files read-only for the executing user
|
||||
RUN chown root: -R /home/py-kms && \
|
||||
chmod 444 -R /home/py-kms && \
|
||||
chown py-kms: /home/py-kms && \
|
||||
chmod 700 /home/py-kms && \
|
||||
find /home/py-kms -type d -print -exec chmod +x {} ';'
|
||||
RUN find /home/py-kms -type f -print -exec chmod 444 {} ';' && \
|
||||
find /home/py-kms -type d -print -exec chmod 555 {} ';' && \
|
||||
chown root: -R /home/py-kms && \
|
||||
chown py-kms: /home/py-kms
|
||||
|
||||
WORKDIR /home/py-kms
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
FROM alpine:3.22
|
||||
|
||||
ARG BUILD_COMMIT=unknown
|
||||
ARG BUILD_BRANCH=unknown
|
||||
ARG BUILD_REFERENCE=unknown
|
||||
|
||||
ENV IP=::
|
||||
ENV DUALSTACK=1
|
||||
|
|
@ -29,7 +29,7 @@ RUN apk add --no-cache --update \
|
|||
tzdata \
|
||||
shadow \
|
||||
&& pip3 install --break-system-packages --no-cache-dir -r /home/py-kms/requirements.txt \
|
||||
&& mkdir /db/ \
|
||||
&& mkdir /db/ /home/py-kms/db \
|
||||
&& adduser -S py-kms -G users -s /bin/bash \
|
||||
&& chown py-kms:users /home/py-kms \
|
||||
# Fix undefined timezone, in case the user did not mount the /etc/localtime
|
||||
|
|
@ -41,16 +41,17 @@ 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
|
||||
|
||||
# Additional permission hardening: All files read-only for the executing user
|
||||
RUN chown root: -R /home/py-kms && \
|
||||
chmod 444 -R /home/py-kms && \
|
||||
# Additional permission hardening: keep application files read-only, but preserve
|
||||
# a dedicated writable database directory for WebUI/SQLite at runtime.
|
||||
RUN find /home/py-kms -type f -print -exec chmod 444 {} ';' && \
|
||||
find /home/py-kms -type d -print -exec chmod 555 {} ';' && \
|
||||
chown root: -R /home/py-kms && \
|
||||
chown py-kms: /home/py-kms && \
|
||||
chmod 700 /home/py-kms && \
|
||||
find /home/py-kms -type d -print -exec chmod +x {} ';'
|
||||
chmod 1777 /home/py-kms/db
|
||||
|
||||
# Web-interface specifics
|
||||
COPY LICENSE /LICENSE
|
||||
RUN echo "$BUILD_COMMIT" > /VERSION && echo "$BUILD_BRANCH" >> /VERSION
|
||||
RUN echo "$BUILD_COMMIT" > /VERSION && echo "$BUILD_REFERENCE" >> /VERSION
|
||||
|
||||
WORKDIR /home/py-kms
|
||||
|
||||
|
|
|
|||
|
|
@ -15,15 +15,20 @@ 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)
|
||||
|
||||
def change_uid_grp(logger):
|
||||
if os.geteuid() != 0:
|
||||
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")
|
||||
uid = int(user_db_entries.pw_uid)
|
||||
gid = int(user_grp_db_entries.gr_gid)
|
||||
new_gid = int(os.getenv('GID', str(gid)))
|
||||
new_uid = int(os.getenv('UID', str(uid)))
|
||||
now_uid = os.geteuid() # as what are we running effectively right now?
|
||||
now_gid = os.getegid()
|
||||
ebd_uid = int(user_db_entries.pw_uid) # what was compiled (embedded) into the image?
|
||||
ebd_gid = int(user_grp_db_entries.gr_gid)
|
||||
new_gid = int(os.getenv('GID', str(ebd_gid))) # what is desired by the user at runtime?
|
||||
new_uid = int(os.getenv('UID', str(ebd_uid)))
|
||||
if now_uid == new_uid and now_gid == new_gid:
|
||||
logger.info(f'UID/GID already set to {new_uid}:{new_gid}')
|
||||
return None
|
||||
if now_uid != 0:
|
||||
logger.warning(f'Not root user (UID is {now_uid}), cannot change UID/GID to {new_uid}:{new_gid}!')
|
||||
return None
|
||||
os.chown("/home/py-kms", new_uid, new_gid)
|
||||
os.chmod("/home/py-kms", 0o700)
|
||||
if os.path.isdir(dbPath):
|
||||
|
|
@ -50,9 +55,8 @@ def change_uid_grp(logger):
|
|||
os.chmod(os.environ['LOGFILE'], 0o777)
|
||||
logger.error(str(subprocess.check_output(['ls', '-la', os.environ['LOGFILE']])))
|
||||
# Drop actual permissions
|
||||
logger.info(f"Setting gid to {new_gid}")
|
||||
logger.info(f"Setting UID/GID to {new_uid}:{new_gid}")
|
||||
os.setgid(new_gid)
|
||||
logger.info(f"Setting uid to {new_uid}")
|
||||
os.setuid(new_uid)
|
||||
|
||||
def change_tz(logger):
|
||||
|
|
@ -75,7 +79,7 @@ if __name__ == "__main__":
|
|||
streamhandler.setFormatter(formatter)
|
||||
loggersrv.addHandler(streamhandler)
|
||||
loggersrv.info("Log level: %s" % log_level)
|
||||
loggersrv.debug("user id: %s" % os.getuid())
|
||||
loggersrv.debug("Running as UID/GID %s:%s" % (os.geteuid(), os.getegid()))
|
||||
|
||||
change_tz(loggersrv)
|
||||
childProcess = subprocess.Popen(PYTHON3 + " -u /usr/bin/start.py", preexec_fn=change_uid_grp(loggersrv), shell=True)
|
||||
|
|
|
|||
|
|
@ -72,9 +72,12 @@ def start_kms(logger):
|
|||
pass
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
logger.info("Shutting down...")
|
||||
|
||||
if pykms_webui_process:
|
||||
logger.debug("Terminating webui process...")
|
||||
pykms_webui_process.terminate()
|
||||
logger.debug("Terminating KMS process...")
|
||||
pykms_process.terminate()
|
||||
|
||||
|
||||
|
|
@ -90,6 +93,7 @@ if __name__ == "__main__":
|
|||
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())
|
||||
loggersrv.info("Log level: %s" % log_level)
|
||||
loggersrv.debug("Running as UID/GID %s:%s" % (os.geteuid(), os.getegid()))
|
||||
|
||||
start_kms(loggersrv)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
You want to improve this project?
|
||||
Awesome! But before you write or modify the existing source code, please note the following guideline:
|
||||
|
||||
- Always make sure to add your changes to the wiki.
|
||||
- Always base your branch on the latest `next` branch to avoid merge conflicts.
|
||||
- 8-space indentation without tabs.
|
||||
- Docstrings as this:
|
||||
```python
|
||||
|
|
@ -13,3 +13,14 @@ Awesome! But before you write or modify the existing source code, please note th
|
|||
```
|
||||
- Wrap lines only if really long (it does not matter 79 chars return)
|
||||
- For the rest a bit as it comes with a look at [PEP8](https://www.python.org/dev/peps/pep-0008/) :)
|
||||
|
||||
Test your changes, please. For example, run the server via:
|
||||
```bash
|
||||
python3 pykms_Server.py -F STDOUT -s ./pykms_database.db
|
||||
```
|
||||
Then trigger (multiple) client requests and check the output for errors via:
|
||||
```bash
|
||||
python3 pykms_Client.py -F STDOUT # fresh client
|
||||
python3 pykms_Client.py -F STDOUT -c 174f5409-0624-4ce3-b209-adde1091956b # (maybe) existing client
|
||||
python3 pykms_Client.py -F STDOUT -c 174f5409-0624-4ce3-b209-adde1091956b # now-for-sure existing client
|
||||
```
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ command will download, "install" and start _py-kms_ and also keep it alive after
|
|||
```bash
|
||||
docker run -d --name py-kms --restart always -p 1688:1688 -v /etc/localtime:/etc/localtime:ro ghcr.io/py-kms-organization/py-kms
|
||||
```
|
||||
If you just want to use the image and don't want to build them yourself, you can always use the official image at the [GitHub Container Registry](https://github.com/Py-KMS-Organization/py-kms/pkgs/container/py-kms) (`ghcr.io/py-kms-organization/py-kms`). To ensure that you are using always the latest version you should check something like [watchtower](https://github.com/containrrr/watchtower) out!
|
||||
If you just want to use the image and don't want to build them yourself, you can always use the official image at the [GitHub Container Registry](https://github.com/Py-KMS-Organization/py-kms/pkgs/container/py-kms) (`ghcr.io/py-kms-organization/py-kms`). To ensure that you are using always the latest version you should check something like [watchtower](https://github.com/nicholas-fedor/watchtower/) out!
|
||||
|
||||
#### Tags
|
||||
There are currently three tags of the image available (select one just by appending `:<tag>` to the image from above):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# Changelog
|
||||
|
||||
**THIS IS A HISTORIC RELEASES FILE.** Nowadays we moved to proper semantic versioning and use Git-based tagging to track releases instead.
|
||||
|
||||
## py-kms_2022-12-16
|
||||
- Added support for new web-gui into Docker
|
||||
- Implemented whole-new web-based GUI with Flask
|
||||
|
|
@ -1 +0,0 @@
|
|||
../CHANGELOG.md
|
||||
|
|
@ -193,16 +193,17 @@ could be detected as not genuine !{end}" %currentClientCount)
|
|||
infoDict = {
|
||||
"machineName" : kmsRequest.getMachineName(),
|
||||
"clientMachineId" : str(clientMachineId),
|
||||
"appId" : appName,
|
||||
"applicationId" : appName,
|
||||
"skuId" : skuName,
|
||||
"licenseStatus" : kmsRequest.getLicenseStatus(),
|
||||
"requestTime" : int(time.time()),
|
||||
"lastRequestIP" : self.srv_config['raddr'][0], # (ip, port)
|
||||
"lastRequestTime" : int(time.time()),
|
||||
"kmsEpid" : None
|
||||
}
|
||||
|
||||
loggersrv.info("Machine Name: %s" % infoDict["machineName"])
|
||||
loggersrv.info("Client Machine ID: %s" % infoDict["clientMachineId"])
|
||||
loggersrv.info("Application ID: %s" % infoDict["appId"])
|
||||
loggersrv.info("Application ID: %s" % infoDict["applicationId"])
|
||||
loggersrv.info("SKU ID: %s" % infoDict["skuId"])
|
||||
loggersrv.info("License Status: %s" % infoDict["licenseStatus"])
|
||||
loggersrv.info("Request Time: %s" % local_dt.strftime('%Y-%m-%d %H:%M:%S %Z (UTC%z)'))
|
||||
|
|
@ -211,7 +212,7 @@ could be detected as not genuine !{end}" %currentClientCount)
|
|||
loggersrv.mininfo("", extra = {'host': str(self.srv_config['raddr']),
|
||||
'status' : infoDict["licenseStatus"],
|
||||
'product' : infoDict["skuId"]})
|
||||
# Create database.
|
||||
# Send change to database.
|
||||
if self.srv_config['sqlite']:
|
||||
sql_update(self.srv_config['sqlite'], infoDict)
|
||||
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ def client_update():
|
|||
for appitem in appitems:
|
||||
kmsitems = appitem['KmsItems']
|
||||
for kmsitem in kmsitems:
|
||||
name = re.sub('\(.*\)', '', kmsitem['DisplayName']) # Remove bracets
|
||||
name = re.sub(r'\(.*\)', '', kmsitem['DisplayName']) # Remove brackets
|
||||
name = name.replace('2015', '') # Remove specific years
|
||||
name = name.replace(' ', '') # Ignore whitespaces
|
||||
name = name.replace('/11', '', 1) # Cut out Windows 11, as it is basically Windows 10
|
||||
|
|
@ -328,7 +328,7 @@ def createKmsRequestBase():
|
|||
requestDict['clientMachineId'] = UUID(uuid.UUID(clt_config['cmid']).bytes_le if (clt_config['cmid'] is not None) else uuid.uuid4().bytes_le)
|
||||
requestDict['previousClientMachineId'] = '\0' * 16 # I'm pretty sure this is supposed to be a null UUID.
|
||||
requestDict['requiredClientCount'] = clt_config['RequiredClientCount']
|
||||
requestDict['requestTime'] = dt_to_filetime(datetime.datetime.utcnow())
|
||||
requestDict['requestTime'] = dt_to_filetime(datetime.datetime.now(datetime.timezone.utc))
|
||||
requestDict['machineName'] = (clt_config['machine'] if (clt_config['machine'] is not None) else
|
||||
''.join(random.choice(string.ascii_letters + string.digits) for i in range(random.randint(2,63)))).encode('utf-16le')
|
||||
requestDict['mnPad'] = '\0'.encode('utf-16le') * (63 - len(requestDict['machineName'].decode('utf-16le')))
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ def epidGenerator(kmsId, version, lcid):
|
|||
except IndexError:
|
||||
# fallback to Windows Server 2019 parameters.
|
||||
pkeys.append( ('206', '551000000', '570999999', '[0,1,2]') )
|
||||
except KeyError:
|
||||
# ignore malformed/incomplete entries
|
||||
pass
|
||||
|
||||
pkey = random.choice(pkeys)
|
||||
GroupId, MinKeyId, MaxKeyId, Invalid = int(pkey[0]), int(pkey[1]), int(pkey[2]), literal_eval(pkey[3])
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ import threading
|
|||
import socketserver
|
||||
import queue as Queue
|
||||
import selectors
|
||||
import traceback
|
||||
from time import monotonic as time
|
||||
|
||||
import pykms_RpcBind, pykms_RpcRequest
|
||||
import pykms_RpcBind, pykms_RpcRequest, pykms_Sql
|
||||
from pykms_RpcBase import rpcBase
|
||||
from pykms_Dcerpc import MSRPCHeader
|
||||
from pykms_Misc import check_setup, check_lcid, check_other
|
||||
|
|
@ -22,7 +23,6 @@ from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp
|
|||
from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals, kms_parser_check_connect
|
||||
from pykms_Format import enco, deco, pretty_printer, justify
|
||||
from pykms_Connect import MultipleListener
|
||||
from pykms_Sql import sql_initialize
|
||||
|
||||
srv_version = "py-kms_2020-10-01"
|
||||
__license__ = "The Unlicense"
|
||||
|
|
@ -124,7 +124,8 @@ class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|||
put_text = "{reverse}{red}{bold}Server connection timed out. Exiting...{end}")
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
pass
|
||||
pretty_printer(log_obj = loggersrv.error,
|
||||
put_text = "{reverse}{red}{bold}Exception happened during processing of request from %s:\n%s{end}" % (str(client_address), traceback.format_exc()))
|
||||
|
||||
|
||||
class server_thread(threading.Thread):
|
||||
|
|
@ -379,12 +380,9 @@ def server_check():
|
|||
put_text = "{reverse}{yellow}{bold}You specified a folder instead of a database file! This behavior is not officially supported anymore, please change your start parameters soon.{end}")
|
||||
srv_config['sqlite'] = os.path.join(srv_config['sqlite'], 'pykms_database.db')
|
||||
|
||||
try:
|
||||
import sqlite3
|
||||
sql_initialize(srv_config['sqlite'])
|
||||
except ImportError:
|
||||
pretty_printer(log_obj = loggersrv.warning,
|
||||
put_text = "{reverse}{yellow}{bold}Module 'sqlite3' not installed, database support disabled.{end}")
|
||||
if pykms_Sql.available:
|
||||
pykms_Sql.sql_initialize(srv_config['sqlite'])
|
||||
else:
|
||||
srv_config['sqlite'] = False
|
||||
|
||||
# Check other specific server options.
|
||||
|
|
|
|||
|
|
@ -1,121 +1,131 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import datetime
|
||||
from datetime import datetime
|
||||
import os
|
||||
import logging
|
||||
|
||||
# sqlite3 is optional.
|
||||
try:
|
||||
import sqlite3
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from pykms_Format import pretty_printer
|
||||
|
||||
#--------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
loggersrv = logging.getLogger('logsrv')
|
||||
_column_names = ('clientMachineId', 'machineName', 'applicationId', 'skuId', 'licenseStatus', 'lastRequestTime', 'kmsEpid', 'requestCount', 'lastRequestIP')
|
||||
|
||||
# sqlite3 is optional.
|
||||
available = False
|
||||
try:
|
||||
import sqlite3
|
||||
available = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def sql_initialize(dbName):
|
||||
if available is False:
|
||||
loggersrv.info("'sqlite3' module not found! SQLite database support cannot be enabled.")
|
||||
return
|
||||
loggersrv.debug(f'SQLite database support enabled. Database file: "{dbName}"')
|
||||
if not os.path.isfile(dbName):
|
||||
# Initialize the database.
|
||||
# Initialize the database
|
||||
loggersrv.debug(f'Initializing database file "{dbName}"...')
|
||||
con = None
|
||||
try:
|
||||
con = sqlite3.connect(dbName)
|
||||
with sqlite3.connect(dbName) as con:
|
||||
cur = con.cursor()
|
||||
cur.execute("CREATE TABLE clients(clientMachineId TEXT , machineName TEXT, applicationId TEXT, skuId TEXT, licenseStatus TEXT, lastRequestTime INTEGER, kmsEpid TEXT, requestCount INTEGER, PRIMARY KEY(clientMachineId, applicationId))")
|
||||
cur.execute("CREATE TABLE clients(clientMachineId TEXT, machineName TEXT, applicationId TEXT, skuId TEXT, licenseStatus TEXT, lastRequestTime INTEGER, kmsEpid TEXT, requestCount INTEGER, PRIMARY KEY(clientMachineId, applicationId))")
|
||||
|
||||
if os.path.isfile(dbName):
|
||||
# Update database
|
||||
with sqlite3.connect(dbName) as con:
|
||||
cur = con.cursor()
|
||||
# Create simple "metadata" table if not exists.
|
||||
cur.execute("CREATE TABLE IF NOT EXISTS metadata (key TEXT PRIMARY KEY, value TEXT);")
|
||||
# Get the current schema version
|
||||
cur.execute("SELECT value FROM metadata WHERE key='schema_version';")
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
current_version = 0
|
||||
else:
|
||||
current_version = int(row[0])
|
||||
loggersrv.debug(f'Current database schema version: {current_version}')
|
||||
# Apply necessary migrations
|
||||
if current_version < 1:
|
||||
# v1: Add "lastRequestIP" column to "clients" table.
|
||||
loggersrv.info("Upgrading database schema to version 1...")
|
||||
cur.execute("ALTER TABLE clients ADD COLUMN lastRequestIP TEXT;")
|
||||
cur.execute("INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '1');")
|
||||
loggersrv.info("Database schema updated to version 1.")
|
||||
|
||||
except sqlite3.Error as e:
|
||||
pretty_printer(log_obj = loggersrv.error, to_exit = True, put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
|
||||
finally:
|
||||
if con:
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
def sql_get_all(dbName):
|
||||
if available is False:
|
||||
return
|
||||
if not os.path.isfile(dbName):
|
||||
return None
|
||||
with sqlite3.connect(dbName) as con:
|
||||
con.row_factory = sqlite3.Row
|
||||
cur = con.cursor()
|
||||
cur.execute("SELECT * FROM clients")
|
||||
cur.execute(f"SELECT {', '.join(_column_names)} FROM clients")
|
||||
clients = []
|
||||
for row in cur.fetchall():
|
||||
clients.append({
|
||||
'clientMachineId': row[0],
|
||||
'machineName': row[1],
|
||||
'applicationId': row[2],
|
||||
'skuId': row[3],
|
||||
'licenseStatus': row[4],
|
||||
'lastRequestTime': datetime.datetime.fromtimestamp(row[5]).isoformat(),
|
||||
'kmsEpid': row[6],
|
||||
'requestCount': row[7]
|
||||
})
|
||||
loggersrv.debug(f"Row: {row}")
|
||||
obj = {}
|
||||
for col_name in _column_names:
|
||||
if col_name == "lastRequestTime":
|
||||
obj[col_name] = datetime.fromtimestamp(row['lastRequestTime']).isoformat()
|
||||
else:
|
||||
obj[col_name] = row[col_name]
|
||||
loggersrv.debug(f"Obj: {obj}")
|
||||
clients.append(obj)
|
||||
return clients
|
||||
|
||||
def sql_update(dbName, infoDict):
|
||||
con = None
|
||||
try:
|
||||
con = sqlite3.connect(dbName)
|
||||
cur = con.cursor()
|
||||
cur.execute("SELECT * FROM clients WHERE clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
|
||||
try:
|
||||
data = cur.fetchone()
|
||||
if not data:
|
||||
# Insert row.
|
||||
cur.execute("INSERT INTO clients (clientMachineId, machineName, applicationId, \
|
||||
skuId, licenseStatus, lastRequestTime, requestCount) VALUES (:clientMachineId, :machineName, :appId, :skuId, :licenseStatus, :requestTime, 1);", infoDict)
|
||||
else:
|
||||
# Update data.
|
||||
if data[1] != infoDict["machineName"]:
|
||||
cur.execute("UPDATE clients SET machineName=:machineName WHERE \
|
||||
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
|
||||
if data[2] != infoDict["appId"]:
|
||||
cur.execute("UPDATE clients SET applicationId=:appId WHERE \
|
||||
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
|
||||
if data[3] != infoDict["skuId"]:
|
||||
cur.execute("UPDATE clients SET skuId=:skuId WHERE \
|
||||
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
|
||||
if data[4] != infoDict["licenseStatus"]:
|
||||
cur.execute("UPDATE clients SET licenseStatus=:licenseStatus WHERE \
|
||||
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
|
||||
if data[5] != infoDict["requestTime"]:
|
||||
cur.execute("UPDATE clients SET lastRequestTime=:requestTime WHERE \
|
||||
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
|
||||
# Increment requestCount
|
||||
cur.execute("UPDATE clients SET requestCount=requestCount+1 WHERE \
|
||||
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
|
||||
if available is False:
|
||||
return
|
||||
|
||||
except sqlite3.Error as e:
|
||||
pretty_printer(log_obj = loggersrv.error, to_exit = True,
|
||||
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
|
||||
except sqlite3.Error as e:
|
||||
pretty_printer(log_obj = loggersrv.error, to_exit = True,
|
||||
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
|
||||
finally:
|
||||
if con:
|
||||
con.commit()
|
||||
con.close()
|
||||
# make sure all column names are present
|
||||
for col_name in _column_names:
|
||||
if col_name in ["requestCount", "kmsEpid"]:
|
||||
continue
|
||||
if col_name not in infoDict:
|
||||
raise ValueError(f"infoDict is missing required column: {col_name}")
|
||||
|
||||
with sqlite3.connect(dbName) as con:
|
||||
con.row_factory = sqlite3.Row
|
||||
cur = con.cursor()
|
||||
cur.execute(f"SELECT {', '.join(_column_names)} FROM clients WHERE clientMachineId=:clientMachineId AND applicationId=:applicationId;", infoDict)
|
||||
data = cur.fetchone()
|
||||
if not data:
|
||||
# Insert new row with all given info
|
||||
infoDict["kmsEpid"] = "" # Default empty value
|
||||
infoDict["requestCount"] = 1
|
||||
cur.execute(f"""INSERT INTO clients ({', '.join(_column_names)})
|
||||
VALUES ({', '.join(':' + col for col in _column_names)});""", infoDict)
|
||||
|
||||
else:
|
||||
# Update only changed columns
|
||||
common_postfix = "WHERE clientMachineId=:clientMachineId AND applicationId=:applicationId"
|
||||
def update_column_if_changed(column_name, new_value):
|
||||
assert "clientMachineId" in infoDict and "applicationId" in infoDict, "infoDict must contain 'clientMachineId' and 'applicationId'"
|
||||
if column_name not in _column_names:
|
||||
raise ValueError(f"Unknown column name: {column_name}")
|
||||
if data[column_name] != new_value:
|
||||
query = f"UPDATE clients SET {column_name}=:value {common_postfix}"
|
||||
cur.execute(query, {"value": new_value, "clientMachineId": infoDict['clientMachineId'], "applicationId": infoDict['applicationId']})
|
||||
|
||||
# Dynamically check and maybe update all columns
|
||||
for column_name in _column_names:
|
||||
if column_name in ["clientMachineId", "applicationId", "requestCount"]:
|
||||
continue # Skip these columns
|
||||
if column_name == "kmsEpid":
|
||||
# this one can only be updated by the special function
|
||||
continue
|
||||
update_column_if_changed(column_name, infoDict[column_name])
|
||||
|
||||
# Finally increment requestCount
|
||||
cur.execute(f"UPDATE clients SET requestCount=requestCount+1 {common_postfix}", infoDict)
|
||||
|
||||
def sql_update_epid(dbName, kmsRequest, response, appName):
|
||||
cmid = str(kmsRequest['clientMachineId'].get())
|
||||
con = None
|
||||
try:
|
||||
con = sqlite3.connect(dbName)
|
||||
cur = con.cursor()
|
||||
cur.execute("SELECT * FROM clients WHERE clientMachineId=? AND applicationId=?;", (cmid, appName))
|
||||
try:
|
||||
data = cur.fetchone()
|
||||
cur.execute("UPDATE clients SET kmsEpid=? WHERE \
|
||||
clientMachineId=? AND applicationId=?;", (str(response["kmsEpid"].decode('utf-16le')), cmid, appName))
|
||||
if available is False:
|
||||
return
|
||||
|
||||
except sqlite3.Error as e:
|
||||
pretty_printer(log_obj = loggersrv.error, to_exit = True,
|
||||
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
|
||||
except sqlite3.Error as e:
|
||||
pretty_printer(log_obj = loggersrv.error, to_exit = True,
|
||||
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
|
||||
finally:
|
||||
if con:
|
||||
con.commit()
|
||||
con.close()
|
||||
cmid = str(kmsRequest['clientMachineId'].get())
|
||||
with sqlite3.connect(dbName) as con:
|
||||
cur = con.cursor()
|
||||
cur.execute("UPDATE clients SET kmsEpid=? WHERE clientMachineId=? AND applicationId=?;",
|
||||
(str(response["kmsEpid"].decode('utf-16le')), cmid, appName))
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ if os.path.exists(_version_info_path):
|
|||
with open(_version_info_path, 'r') as f:
|
||||
app.jinja_env.globals['version_info'] = {
|
||||
'hash': f.readline().strip(),
|
||||
'branch': f.readline().strip()
|
||||
'reference': f.readline().strip()
|
||||
}
|
||||
|
||||
_dbEnvVarName = 'PYKMS_SQLITE_DB_PATH'
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
<strong>py-kms</strong> is online since <span class="convert_timestamp">{{ start_time }}</span>.
|
||||
This instance was accessed {{ get_serve_count() }} times. View this softwares license <a href="/license">here</a>.
|
||||
{% if version_info %}
|
||||
<br>This instance is running version "{{ version_info['hash'] }}" from branch "{{ version_info['branch'] }}" of py-kms.
|
||||
<br>This instance is running version "{{ version_info['hash'] }}" from Git "{{ version_info['reference'] }}" of py-kms.
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ th {
|
|||
<th>Application ID</th>
|
||||
<th><abbr title="Stock Keeping Unit">SKU</abbr> ID</th>
|
||||
<th>License Status</th>
|
||||
<th>Last Address</th>
|
||||
<th>Last Seen</th>
|
||||
<th>KMS <abbr title="Enhanced Privacy ID">EPID</abbr></th>
|
||||
<th>Seen Count</th>
|
||||
|
|
@ -65,7 +66,9 @@ th {
|
|||
<tbody>
|
||||
{% for client in clients %}
|
||||
<tr>
|
||||
<th><pre class="clientMachineId">{{ client.clientMachineId }}</pre></th>
|
||||
<th>
|
||||
<pre class="clientMachineId">{{ client.clientMachineId }}</pre>
|
||||
</th>
|
||||
<td class="machineName">
|
||||
{% if client.machineName | length > 16 %}
|
||||
<abbr title="{{ client.machineName }}">{{ client.machineName | truncate(16, True, '...') }}</abbr>
|
||||
|
|
@ -76,6 +79,7 @@ th {
|
|||
<td>{{ client.applicationId }}</td>
|
||||
<td>{{ client.skuId }}</td>
|
||||
<td>{{ client.licenseStatus }}</td>
|
||||
<td>{{ client.lastRequestIP or "N/A" }}</td>
|
||||
<td class="convert_timestamp">{{ client.lastRequestTime }}</td>
|
||||
<td>
|
||||
{% if client.kmsEpid | length > 16 %}
|
||||
|
|
@ -95,9 +99,10 @@ th {
|
|||
<p>Whoops?</p>
|
||||
</div>
|
||||
<div class="message-body">
|
||||
This page seems to be empty, because no clients are available. Try to use the server with a compartible client to add it to the database.
|
||||
This page seems to be empty, because no clients are available. Try to use the server with a compatible client
|
||||
to add it to the database.
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue