mirror of https://github.com/SystemRage/py-kms.git
Merge 03992a851d
into a3b0c85b5b
This commit is contained in:
commit
364831a0e7
|
@ -0,0 +1,8 @@
|
||||||
|
log/
|
||||||
|
.idea
|
||||||
|
.github
|
||||||
|
*.db
|
||||||
|
*.yml
|
||||||
|
*.md
|
||||||
|
*.sh
|
||||||
|
Makefile
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: Build release-tags
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bake-latest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2.3.4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1.6.0
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v1.10.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
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: ghcr.io/py-kms-organization/py-kms:python3
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
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: ghcr.io/py-kms-organization/py-kms:latest,ghcr.io/py-kms-organization/py-kms:minimal
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: Build next-tags
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- next
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bake-next:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2.3.4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1.6.0
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v1.10.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
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: ghcr.io/py-kms-organization/py-kms:python3-next
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
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: ghcr.io/py-kms-organization/py-kms:latest-next,ghcr.io/py-kms-organization/py-kms:minimal-next
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
|
@ -0,0 +1,38 @@
|
||||||
|
name: Test-Build Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bake-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2.3.4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1.6.0
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/docker-py3-kms/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||||
|
push: false
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/docker-py3-kms-minimal/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||||
|
push: false
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
|
@ -2,7 +2,6 @@
|
||||||
pykms_logserver.log*
|
pykms_logserver.log*
|
||||||
pykms_logclient.log*
|
pykms_logclient.log*
|
||||||
pykms_database.db*
|
pykms_database.db*
|
||||||
etrigan.log*
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
@ -128,3 +127,8 @@ dmypy.json
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
# Helm
|
||||||
|
charts/*/*.tgz
|
||||||
|
/.idea/
|
||||||
|
docker-compose-*.yml
|
||||||
|
*.sh
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
build:
|
||||||
|
os: "ubuntu-22.04"
|
||||||
|
tools:
|
||||||
|
python: "3.10"
|
||||||
|
|
||||||
|
python:
|
||||||
|
install:
|
||||||
|
- requirements: docs/requirements.txt
|
||||||
|
|
||||||
|
formats: all
|
116
CHANGELOG.md
116
CHANGELOG.md
|
@ -1,26 +1,70 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
### py-kms_2020-10-01
|
## py-kms_2022-12-16
|
||||||
|
- Added support for new web-gui into Docker
|
||||||
|
- Implemented whole-new web-based GUI with Flask
|
||||||
|
- Removed old GUI (Etrigan) from code and resources
|
||||||
|
- Removed sqliteweb
|
||||||
|
- Removed Etrigan (GUI)
|
||||||
|
|
||||||
|
## py-kms_2022-12-07
|
||||||
|
- Added warning about Etrigan (GUI) being deprecated
|
||||||
|
- More docs (do not run on same machine as client)
|
||||||
|
- Added Docker support for multiple listen IPs
|
||||||
|
- Added graceful Docker shutdowns
|
||||||
|
|
||||||
|
## py-kms_2021-12-23
|
||||||
|
- More Windows 10/11 keys
|
||||||
|
- Fixed some deprecation warnings
|
||||||
|
- Fixed SO_REUSEPORT platform checks
|
||||||
|
- Fixed loglevel "MININFO" with Docker
|
||||||
|
- Added Docker healthcheck
|
||||||
|
- Added UID/GID change support for Docker
|
||||||
|
- Dependabot alerts
|
||||||
|
|
||||||
|
## py-kms_2021-10-22
|
||||||
|
- Integrated Office 2021 GLVK keys & database
|
||||||
|
- Docker entrypoint fixes
|
||||||
|
- Updated docs to include SQLite stuff
|
||||||
|
- Fix for undefined timezones
|
||||||
|
- Removed LOGFILE extension checks
|
||||||
|
- Added support for Windows 11
|
||||||
|
|
||||||
|
## py-kms_2021-10-07
|
||||||
|
- Helm charts for Kubernetes deployment
|
||||||
|
- Windows 2022 updates
|
||||||
|
- Faster Github Action builds
|
||||||
|
|
||||||
|
## py-kms_2021-11-12
|
||||||
|
- Addded GHCR support
|
||||||
|
- Docs table reformatted
|
||||||
|
- Updated GUI
|
||||||
|
- Windows Sandbox fix
|
||||||
|
- Added contribution guidelines
|
||||||
|
- Docker multiarch
|
||||||
|
- Reshot screenshots in docs
|
||||||
|
|
||||||
|
## py-kms_2020-10-01
|
||||||
- Sql database path customizable.
|
- Sql database path customizable.
|
||||||
- Sql database file keeps different AppId.
|
- Sql database file keeps different AppId.
|
||||||
- Support for multi-address connection.
|
- Support for multi-address connection.
|
||||||
- Added timeout send / receive.
|
- Added timeout send / receive.
|
||||||
|
|
||||||
### py-kms_2020-07-01
|
## py-kms_2020-07-01
|
||||||
- py-kms Gui: now matches all cli options, added modes onlyserver / onlyclient,
|
- py-kms Gui: now matches all cli options, added modes onlyserver / onlyclient,
|
||||||
added some animations.
|
added some animations.
|
||||||
- Added suboptions FILEOFF and STDOUTOFF of -F.
|
- Added suboptions FILEOFF and STDOUTOFF of -F.
|
||||||
- Created option for asynchronous messages.
|
- Created option for asynchronous messages.
|
||||||
- Cleaned options parsing process.
|
- Cleaned options parsing process.
|
||||||
|
|
||||||
### py-kms_2020-02-02
|
## py-kms_2020-02-02
|
||||||
- Optimized pretty-print messages process.
|
- Optimized pretty-print messages process.
|
||||||
- Added -F FILESTDOUT option.
|
- Added -F FILESTDOUT option.
|
||||||
- Added deamonization options (via [Etrigan](https://github.com/SystemRage/Etrigan) project).
|
- Added deamonization options (via [Etrigan](https://github.com/SystemRage/Etrigan) project).
|
||||||
- py-kms GUI resurrected (and improved).
|
- py-kms GUI resurrected (and improved).
|
||||||
- Cleaned, cleaned, cleaned.
|
- Cleaned, cleaned, cleaned.
|
||||||
|
|
||||||
### py-kms_2019-05-15
|
## py-kms_2019-05-15
|
||||||
- Merging for Python2 / Python3 compatibility all-in-one.
|
- Merging for Python2 / Python3 compatibility all-in-one.
|
||||||
- Added new options:
|
- Added new options:
|
||||||
- timeout, [logsize](https://github.com/SystemRage/py-kms/pull/21).
|
- timeout, [logsize](https://github.com/SystemRage/py-kms/pull/21).
|
||||||
|
@ -32,7 +76,7 @@
|
||||||
- Fixed activation threshold.
|
- Fixed activation threshold.
|
||||||
- Renamed files, cosmetics and many other little big adjustments.
|
- Renamed files, cosmetics and many other little big adjustments.
|
||||||
|
|
||||||
### py-kms_2018-11-15
|
## py-kms_2018-11-15
|
||||||
- Implemented some good modifications inspired by [this](https://github.com/ThunderEX/py-kms) other fork.
|
- Implemented some good modifications inspired by [this](https://github.com/ThunderEX/py-kms) other fork.
|
||||||
- Clean up code ( deleted no longer useful files randomHWID.py, randomEPID.py, timezones.py;
|
- Clean up code ( deleted no longer useful files randomHWID.py, randomEPID.py, timezones.py;
|
||||||
erased useless functions and import modules )
|
erased useless functions and import modules )
|
||||||
|
@ -41,128 +85,128 @@
|
||||||
- Improved random EPID generation.
|
- Improved random EPID generation.
|
||||||
- Corrected [this](https://github.com/SystemRage/py-kms/issues/8) in kmsBase.py
|
- Corrected [this](https://github.com/SystemRage/py-kms/issues/8) in kmsBase.py
|
||||||
|
|
||||||
### py-kms_2018-03-01
|
## py-kms_2018-03-01
|
||||||
- *py-kms NOW is for Python3 too ( py3-kms ), the previous one ( written with Python2 ) is renamed py2-kms*
|
- *py-kms NOW is for Python3 too ( py3-kms ), the previous one ( written with Python2 ) is renamed py2-kms*
|
||||||
- *Repaired logging messages*
|
- *Repaired logging messages*
|
||||||
- *Added pretty processing messages*
|
- *Added pretty processing messages*
|
||||||
|
|
||||||
### py-kms_2017-06-01
|
## py-kms_2017-06-01
|
||||||
- *Added option verbose logging in a file*
|
- *Added option verbose logging in a file*
|
||||||
- *Updated "kmsBase.py" with new SKUIDs*
|
- *Updated "kmsBase.py" with new SKUIDs*
|
||||||
- *Added a brief guide "py-kms-Guide.pdf" ( replaced "client-activation.txt" )*
|
- *Added a brief guide "py-kms-Guide.pdf" ( replaced "client-activation.txt" )*
|
||||||
- *Added a well formatted and more complete list of volume keys "py-kms-ClientKeys.pdf" ( replaced "client-keys.txt" )*
|
- *Added a well formatted and more complete list of volume keys "py-kms-ClientKeys.pdf" ( replaced "client-keys.txt" )*
|
||||||
|
|
||||||
### py-kms_2016-12-30
|
## py-kms_2016-12-30
|
||||||
- *Updated kmsBase.py (Matches LicenseManager 4.6.0 by Hotbird64 HGM)*
|
- *Updated kmsBase.py (Matches LicenseManager 4.6.0 by Hotbird64 HGM)*
|
||||||
|
|
||||||
### py-kms_2016-08-13
|
## py-kms_2016-08-13
|
||||||
- *Fixed major bug on Response function*
|
- *Fixed major bug on Response function*
|
||||||
- *Fixed Random PID Generator (thanks: mkuba50)*
|
- *Fixed Random PID Generator (thanks: mkuba50)*
|
||||||
|
|
||||||
### py-kms_2016-08-12
|
## py-kms_2016-08-12
|
||||||
- *Added missing UUID, credits: Hotbird64*
|
- *Added missing UUID, credits: Hotbird64*
|
||||||
- *Added Windows Server 2016 in random PID generator*
|
- *Added Windows Server 2016 in random PID generator*
|
||||||
|
|
||||||
### py-kms_2016-08-11
|
## py-kms_2016-08-11
|
||||||
- *Added Windows Server 2016 UUID*
|
- *Added Windows Server 2016 UUID*
|
||||||
- *Fixed GroupID and PIDRange*
|
- *Fixed GroupID and PIDRange*
|
||||||
- *Added Office 2016 CountKMSID*
|
- *Added Office 2016 CountKMSID*
|
||||||
|
|
||||||
### py-kms_2015-07-29
|
## py-kms_2015-07-29
|
||||||
- *Added Windows 10 UUID*
|
- *Added Windows 10 UUID*
|
||||||
|
|
||||||
### py-kms_2014-10-13 build 3:
|
## py-kms_2014-10-13 build 3:
|
||||||
- *Added Client Activation Examples: "client-activation.txt"*
|
- *Added Client Activation Examples: "client-activation.txt"*
|
||||||
- *Added Volume Keys: "client-keys.txt"*
|
- *Added Volume Keys: "client-keys.txt"*
|
||||||
|
|
||||||
### py-kms_2014-10-13 build 2:
|
## py-kms_2014-10-13 build 2:
|
||||||
- *Added missing skuIds in file "kmsbase.py". Thanks (user_hidden)*
|
- *Added missing skuIds in file "kmsbase.py". Thanks (user_hidden)*
|
||||||
|
|
||||||
### py-kms_2014-10-13 build 1:
|
## py-kms_2014-10-13 build 1:
|
||||||
- *The server now outputs the hwid in use.*
|
- *The server now outputs the hwid in use.*
|
||||||
- *The server hwid can be random by using parameter: "-w random". Example: "python server.py -w random"*
|
- *The server hwid can be random by using parameter: "-w random". Example: "python server.py -w random"*
|
||||||
- *Included file "randomHWID.py" to generate random hwid on demand.*
|
- *Included file "randomHWID.py" to generate random hwid on demand.*
|
||||||
- *Included file "randomPID.py" to generate random epid and hwid on demand.*
|
- *Included file "randomPID.py" to generate random epid and hwid on demand.*
|
||||||
|
|
||||||
### py-kms_2014-03-21T232943Z:
|
## py-kms_2014-03-21T232943Z:
|
||||||
- *The server HWID can now be specified on the command line.*
|
- *The server HWID can now be specified on the command line.*
|
||||||
- *The client will print the HWID when using the v6 protocol.*
|
- *The client will print the HWID when using the v6 protocol.*
|
||||||
|
|
||||||
### py-kms_2014-01-03T032458Z:
|
## py-kms_2014-01-03T032458Z:
|
||||||
- *Made the sqlite3 module optional.*
|
- *Made the sqlite3 module optional.*
|
||||||
- *Changed the "log" flag to an "sqlite" flag and made a real log flag in preparation for when real request logging is implemented.*
|
- *Changed the "log" flag to an "sqlite" flag and made a real log flag in preparation for when real request logging is implemented.*
|
||||||
|
|
||||||
### py-kms_2014-01-03T025524Z:
|
## py-kms_2014-01-03T025524Z:
|
||||||
- *Added RPC response decoding to the KMS client emulator.*
|
- *Added RPC response decoding to the KMS client emulator.*
|
||||||
|
|
||||||
### py-kms_2013-12-30T064443Z:
|
## py-kms_2013-12-30T064443Z:
|
||||||
- *The v4 hash now uses the proper pre-expanded key.*
|
- *The v4 hash now uses the proper pre-expanded key.*
|
||||||
|
|
||||||
### py-kms_2013-12-28T073506Z:
|
## py-kms_2013-12-28T073506Z:
|
||||||
- *Modified the v4 code to use the custom aes module in order to make it more streamlined and efficient.*
|
- *Modified the v4 code to use the custom aes module in order to make it more streamlined and efficient.*
|
||||||
|
|
||||||
### py-kms_2013-12-20T051257Z:
|
## py-kms_2013-12-20T051257Z:
|
||||||
- *Removed the need for the pre-computed table (tablecomplex.py) for v4 CMAC calculation, cutting the zip file size in half.*
|
- *Removed the need for the pre-computed table (tablecomplex.py) for v4 CMAC calculation, cutting the zip file size in half.*
|
||||||
|
|
||||||
### py-kms_2013-12-16T214638Z:
|
## py-kms_2013-12-16T214638Z:
|
||||||
- *Switched to getting the to-be-logged request time from the KMS server instead of the client.*
|
- *Switched to getting the to-be-logged request time from the KMS server instead of the client.*
|
||||||
|
|
||||||
### py-kms_2013-12-16T030001Z:
|
## py-kms_2013-12-16T030001Z:
|
||||||
- *You can now specify the CMID and the Machine Name to use with the client emulator.*
|
- *You can now specify the CMID and the Machine Name to use with the client emulator.*
|
||||||
|
|
||||||
### py-kms_2013-12-16T021215Z:
|
## py-kms_2013-12-16T021215Z:
|
||||||
- *Added a request-logging feature to the server. It stores requests in an SQLite database and uses the ePIDs stored there on a per-CMID basis.*
|
- *Added a request-logging feature to the server. It stores requests in an SQLite database and uses the ePIDs stored there on a per-CMID basis.*
|
||||||
- *The client emulator now works for v4, v5, and v6 requests.*
|
- *The client emulator now works for v4, v5, and v6 requests.*
|
||||||
- *The client emulator now also verifies the KMS v4 responses it receives.*
|
- *The client emulator now also verifies the KMS v4 responses it receives.*
|
||||||
|
|
||||||
### py-kms_2013-12-14T230215Z
|
## py-kms_2013-12-14T230215Z
|
||||||
- *Added a client (work in progress) that right now can only generate and send RPC bind requests.*
|
- *Added a client (work in progress) that right now can only generate and send RPC bind requests.*
|
||||||
- *Added a bunch of new classes to handle RPC client stuff, but I might just end up moving their functions back into the old classes at some point.*
|
- *Added a bunch of new classes to handle RPC client stuff, but I might just end up moving their functions back into the old classes at some point.*
|
||||||
- *Lots of other code shuffling.*
|
- *Lots of other code shuffling.*
|
||||||
- *Made the verbose and debug option help easier to read.*
|
- *Made the verbose and debug option help easier to read.*
|
||||||
- *Added some server error messages.*
|
- *Added some server error messages.*
|
||||||
|
|
||||||
### py-kms_2013-12-08T051332Z:
|
## py-kms_2013-12-08T051332Z:
|
||||||
- *Made some really huge internal changes to streamline packet parsing.*
|
- *Made some really huge internal changes to streamline packet parsing.*
|
||||||
|
|
||||||
### py-kms_2013-12-06T034100Z:
|
## py-kms_2013-12-06T034100Z:
|
||||||
- *Added tons of new SKU IDs*
|
- *Added tons of new SKU IDs*
|
||||||
|
|
||||||
### py-kms_2013-12-05T044849Z:
|
## py-kms_2013-12-05T044849Z:
|
||||||
- *Added Office SKU IDs*
|
- *Added Office SKU IDs*
|
||||||
- *Small internal changes*
|
- *Small internal changes*
|
||||||
|
|
||||||
### py-kms_2013-12-04T010942Z:
|
## py-kms_2013-12-04T010942Z:
|
||||||
- *Made the rpcResponseArray in rpcRequest output closer to spec*
|
- *Made the rpcResponseArray in rpcRequest output closer to spec*
|
||||||
|
|
||||||
### py-kms_2013-12-01T063938Z:
|
## py-kms_2013-12-01T063938Z:
|
||||||
- *SKUID conversion: Converts the SKUID UUID into a human-readable product version for SKUIDs in its SKUID dictionary.*
|
- *SKUID conversion: Converts the SKUID UUID into a human-readable product version for SKUIDs in its SKUID dictionary.*
|
||||||
- *Fancy new timezone conversion stuff.*
|
- *Fancy new timezone conversion stuff.*
|
||||||
- *Enabled setting custom LCIDs.*
|
- *Enabled setting custom LCIDs.*
|
||||||
- *Data parsing is now handled by structure.py.*
|
- *Data parsing is now handled by structure.py.*
|
||||||
- *Some other minor stuff you probably won't notice.*
|
- *Some other minor stuff you probably won't notice.*
|
||||||
|
|
||||||
### py-kms_2013-11-27T061658Z:
|
## py-kms_2013-11-27T061658Z:
|
||||||
- *Got rid of custom functions module (finally)*
|
- *Got rid of custom functions module (finally)*
|
||||||
|
|
||||||
### py-kms_2013-11-27T054744Z:
|
## py-kms_2013-11-27T054744Z:
|
||||||
- *Simplified custom functions module*
|
- *Simplified custom functions module*
|
||||||
- *Got rid of "v4" subfolder*
|
- *Got rid of "v4" subfolder*
|
||||||
- *Cleaned up a bunch of code*
|
- *Cleaned up a bunch of code*
|
||||||
|
|
||||||
### py-kms_2013-11-23T044244Z:
|
## py-kms_2013-11-23T044244Z:
|
||||||
- *Added timestamps to verbose output*
|
- *Added timestamps to verbose output*
|
||||||
- *Made the verbose output look better*
|
- *Made the verbose output look better*
|
||||||
|
|
||||||
### py-kms_2013-11-21T014002Z:
|
## py-kms_2013-11-21T014002Z:
|
||||||
- *Moved some stuff into verbose output*
|
- *Moved some stuff into verbose output*
|
||||||
- *Enabled server ePIDs of arbitrary length*
|
- *Enabled server ePIDs of arbitrary length*
|
||||||
|
|
||||||
### py-kms_2013-11-20T180347Z:
|
## py-kms_2013-11-20T180347Z:
|
||||||
- *Permanently fixed machineName decoding*
|
- *Permanently fixed machineName decoding*
|
||||||
- *Adheres closer to the DCE/RPC protocol spec*
|
- *Adheres closer to the DCE/RPC protocol spec*
|
||||||
- *Added client info to program output*
|
- *Added client info to program output*
|
||||||
- *Small formatting changes*
|
- *Small formatting changes*
|
||||||
|
|
||||||
### py-kms_2013-11-13:
|
## py-kms_2013-11-13:
|
||||||
- *First working release added to the Mega folder.*
|
- *First working release added to the Mega folder.*
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Matteo ℱan
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
35
README.md
35
README.md
|
@ -1,14 +1,15 @@
|
||||||
# Readme
|
# Readme
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|
|
||||||

|
|
||||||

|

|
||||||
***
|
***
|
||||||
|
|
||||||
|
_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
|
## 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.
|
_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.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Responds to `v4`, `v5`, and `v6` KMS requests.
|
- Responds to `v4`, `v5`, and `v6` KMS requests.
|
||||||
|
@ -18,31 +19,33 @@ _py-kms_ is a port of node-kms created by [cyrozap](http://forums.mydigitallife.
|
||||||
- Windows 8
|
- Windows 8
|
||||||
- Windows 8.1
|
- Windows 8.1
|
||||||
- Windows 10 ( 1511 / 1607 / 1703 / 1709 / 1803 / 1809 )
|
- Windows 10 ( 1511 / 1607 / 1703 / 1709 / 1803 / 1809 )
|
||||||
- Windows 10 ( 1903 / 1909 / 20H1 )
|
- Windows 10 ( 1903 / 1909 / 20H1, 20H2, 21H1, 21H2 )
|
||||||
|
- Windows 11 ( 21H2 )
|
||||||
- Windows Server 2008
|
- Windows Server 2008
|
||||||
- Windows Server 2008 R2
|
- Windows Server 2008 R2
|
||||||
- Windows Server 2012
|
- Windows Server 2012
|
||||||
- Windows Server 2012 R2
|
- Windows Server 2012 R2
|
||||||
- Windows Server 2016
|
- Windows Server 2016
|
||||||
- Windows Server 2019
|
- Windows Server 2019
|
||||||
|
- Windows Server 2022
|
||||||
|
- Windows Server 2025
|
||||||
- Microsoft Office 2010 ( Volume License )
|
- Microsoft Office 2010 ( Volume License )
|
||||||
- Microsoft Office 2013 ( Volume License )
|
- Microsoft Office 2013 ( Volume License )
|
||||||
- Microsoft Office 2016 ( Volume License )
|
- Microsoft Office 2016 ( Volume License )
|
||||||
- Microsoft Office 2019 ( Volume License )
|
- Microsoft Office 2019 ( Volume License )
|
||||||
- It's written in Python (tested with Python 3.6.9).
|
- Microsoft Office 2021 ( Volume License )
|
||||||
- Supports execution by `Docker`, `systemd`, `Upstart` and many more...
|
- Microsoft Office 2024 ( Volume License )
|
||||||
- Includes a GUI for simple managing.
|
- It's written in Python (tested with Python 3.10.1).
|
||||||
- Uses `sqlite` for persistent data storage.
|
- Supports execution by `Docker`, `systemd` and many more...
|
||||||
|
- Uses `sqlite` for persistent data storage (with a simple web-based explorer).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
The wiki has been completly reworked and is now available on [readthedocs.com](https://py-kms.readthedocs.io/en/latest/). It should you provide all necessary information 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 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.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
- To start the server, execute `python3 pykms_Server.py [IPADDRESS] [PORT]`, the default _IPADDRESS_ is `0.0.0.0` ( 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.
|
- 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 automatically using Docker, execute `docker run -d --name py-kms --restart always -p 1688:1688 pykmsorg/py-kms`.
|
- 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`.
|
- To show the help pages type: `python3 pykms_Server.py -h` and `python3 pykms_Client.py -h`.
|
||||||
- For launching _py-kms_ GUI make the file `pykms_Server.py` executable with `chmod +x /path/to/folder/py-kms/pykms_Server.py`, then simply run `pykms_Server.py` by double-clicking.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
- _py-kms_ is [](https://github.com/SystemRage/py-kms/blob/master/LICENSE)
|
- _py-kms_ is [](./LICENSE)
|
||||||
- _py-kms GUI_ is [](https://github.com/SystemRage/py-kms/blob/master/LICENSE.gui.md) © Matteo ℱan
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
|
@ -0,0 +1,24 @@
|
||||||
|
apiVersion: v2
|
||||||
|
name: py-kms
|
||||||
|
description: A Helm chart for Kubernetes
|
||||||
|
|
||||||
|
# A chart can be either an 'application' or a 'library' chart.
|
||||||
|
#
|
||||||
|
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||||
|
# to be deployed.
|
||||||
|
#
|
||||||
|
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||||
|
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||||
|
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||||
|
type: application
|
||||||
|
|
||||||
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
|
# to the chart and its templates, including the app version.
|
||||||
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
# This is the version number of the application being deployed. This version number should be
|
||||||
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
|
# It is recommended to use it with quotes.
|
||||||
|
appVersion: "python3"
|
|
@ -0,0 +1,60 @@
|
||||||
|
# py-kms
|
||||||
|
|
||||||
|
  
|
||||||
|
|
||||||
|
A Helm chart for Kubernetes
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Below is a basic overview of the steps required to deploy the Helm chart to an existing Kubernetes cluster which is accessible via Kubectl
|
||||||
|
|
||||||
|
### Create py-kms Namespace (recommended)
|
||||||
|
|
||||||
|
`kubectl create ns py-kms`
|
||||||
|
|
||||||
|
### Deploy chart with default values.yaml
|
||||||
|
|
||||||
|
`helm install -n py-kms -f myvalues.yaml charts/py-kms`
|
||||||
|
|
||||||
|
For more information please refer to the Helm Install command documentation located at: https://helm.sh/docs/helm/helm_install/
|
||||||
|
|
||||||
|
## Values
|
||||||
|
|
||||||
|
| Key | Type | Default | Description |
|
||||||
|
|-----|------|---------|-------------|
|
||||||
|
| affinity | object | `{}` | |
|
||||||
|
| autoscaling.enabled | bool | `false` | |
|
||||||
|
| autoscaling.maxReplicas | int | `100` | |
|
||||||
|
| autoscaling.minReplicas | int | `1` | |
|
||||||
|
| autoscaling.targetCPUUtilizationPercentage | int | `80` | |
|
||||||
|
| fullnameOverride | string | `""` | |
|
||||||
|
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||||
|
| image.repository | string | `"ghcr.io/py-kms-organization/py-kms"` | |
|
||||||
|
| image.tag | string | `"python3"` | |
|
||||||
|
| imagePullSecrets | list | `[]` | |
|
||||||
|
| ingress.annotations | object | `{}` | |
|
||||||
|
| ingress.className | string | `""` | |
|
||||||
|
| ingress.enabled | bool | `false` | |
|
||||||
|
| ingress.hosts[0].host | string | `"chart-example.local"` | |
|
||||||
|
| ingress.hosts[0].paths[0].path | string | `"/"` | |
|
||||||
|
| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | |
|
||||||
|
| ingress.tls | list | `[]` | |
|
||||||
|
| nameOverride | string | `""` | |
|
||||||
|
| nodeSelector | object | `{}` | |
|
||||||
|
| podAnnotations | object | `{}` | |
|
||||||
|
| podSecurityContext | object | `{}` | |
|
||||||
|
| py-kms.environment.HWID | string | `"RANDOM"` | |
|
||||||
|
| py-kms.environment.IP | string | `"::"` | |
|
||||||
|
| py-kms.environment.LOGLEVEL | string | `"INFO"` | |
|
||||||
|
| py-kms.environment.LOGSIZE | int | `2` | |
|
||||||
|
| replicaCount | int | `1` | |
|
||||||
|
| resources | object | `{}` | |
|
||||||
|
| securityContext | object | `{}` | |
|
||||||
|
| service.httpPort | int | `80` | |
|
||||||
|
| service.kmsPort | int | `1688` | |
|
||||||
|
| service.type | string | `"ClusterIP"` | |
|
||||||
|
| serviceAccount | object | `{}` | |
|
||||||
|
| tolerations | list | `[]` | |
|
||||||
|
|
||||||
|
----------------------------------------------
|
||||||
|
Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0)
|
|
@ -0,0 +1,22 @@
|
||||||
|
1. Get the application URL by running these commands:
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- range $host := .Values.ingress.hosts }}
|
||||||
|
{{- range .paths }}
|
||||||
|
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if contains "NodePort" .Values.service.type }}
|
||||||
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "py-kms.fullname" . }})
|
||||||
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
|
echo http://$NODE_IP:$NODE_PORT
|
||||||
|
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||||
|
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||||
|
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "py-kms.fullname" . }}'
|
||||||
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "py-kms.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||||
|
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||||
|
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||||
|
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "py-kms.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||||
|
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||||
|
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||||
|
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||||
|
{{- end }}
|
|
@ -0,0 +1,62 @@
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "py-kms.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "py-kms.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "py-kms.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "py-kms.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "py-kms.chart" . }}
|
||||||
|
{{ include "py-kms.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "py-kms.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "py-kms.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "py-kms.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "py-kms.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
|
@ -0,0 +1,72 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "py-kms.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "py-kms.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
{{- if not .Values.autoscaling.enabled }}
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "py-kms.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "py-kms.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ include "py-kms.serviceAccountName" . }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
env:
|
||||||
|
{{- range $key, $val := (index .Values "py-kms" "environment") }}
|
||||||
|
- name: {{ $key }}
|
||||||
|
value: {{ $val | quote }}
|
||||||
|
{{- end }}
|
||||||
|
ports:
|
||||||
|
- name: kms
|
||||||
|
containerPort: 1688
|
||||||
|
protocol: TCP
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
port: http
|
||||||
|
path: /readyz
|
||||||
|
failureThreshold: 30 # 30 seconds seem to be enough under heavy workloads
|
||||||
|
periodSeconds: 1
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /livez
|
||||||
|
port: http
|
||||||
|
periodSeconds: 20
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
|
@ -0,0 +1,28 @@
|
||||||
|
{{- if .Values.autoscaling.enabled }}
|
||||||
|
apiVersion: autoscaling/v2beta1
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: {{ include "py-kms.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "py-kms.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: {{ include "py-kms.fullname" . }}
|
||||||
|
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||||
|
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||||
|
metrics:
|
||||||
|
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
|
@ -0,0 +1,61 @@
|
||||||
|
{{- if .Values.ingress.enabled -}}
|
||||||
|
{{- $fullName := include "py-kms.fullname" . -}}
|
||||||
|
{{- $svcPort := .Values.service.httpPort -}}
|
||||||
|
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||||
|
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||||
|
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
{{- else -}}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
{{- end }}
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
labels:
|
||||||
|
{{- include "py-kms.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||||
|
ingressClassName: {{ .Values.ingress.className }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.ingress.tls }}
|
||||||
|
tls:
|
||||||
|
{{- range .Values.ingress.tls }}
|
||||||
|
- hosts:
|
||||||
|
{{- range .hosts }}
|
||||||
|
- {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: {{ .secretName }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ .host | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
{{- range .paths }}
|
||||||
|
- path: {{ .path }}
|
||||||
|
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||||
|
pathType: {{ .pathType }}
|
||||||
|
{{- end }}
|
||||||
|
backend:
|
||||||
|
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||||
|
service:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
port:
|
||||||
|
number: {{ $svcPort }}
|
||||||
|
{{- else }}
|
||||||
|
serviceName: {{ $fullName }}
|
||||||
|
servicePort: {{ $svcPort }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
|
@ -0,0 +1,19 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "py-kms.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "py-kms.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.kmsPort }}
|
||||||
|
targetPort: 1688
|
||||||
|
protocol: TCP
|
||||||
|
name: kms
|
||||||
|
- port: {{ .Values.service.httpPort }}
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
{{- include "py-kms.selectorLabels" . | nindent 4 }}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{{- if .Values.serviceAccount.create -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "py-kms.serviceAccountName" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "py-kms.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.serviceAccount.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
|
@ -0,0 +1,15 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: "{{ include "py-kms.fullname" . }}-test-connection"
|
||||||
|
labels:
|
||||||
|
{{- include "py-kms.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": test
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: wget
|
||||||
|
image: busybox
|
||||||
|
command: ['wget']
|
||||||
|
args: ['{{ include "py-kms.fullname" . }}:{{ .Values.service.port }}']
|
||||||
|
restartPolicy: Never
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Default values for py-kms.
|
||||||
|
# This is a YAML-formatted file.
|
||||||
|
# Declare variables to be passed into your templates.
|
||||||
|
|
||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: ghcr.io/py-kms-organization/py-kms
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
|
tag: python3
|
||||||
|
|
||||||
|
imagePullSecrets: []
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
py-kms:
|
||||||
|
environment:
|
||||||
|
LOGLEVEL: INFO
|
||||||
|
LOGSIZE: 2
|
||||||
|
LOGFILE: /var/log/py-kms.log
|
||||||
|
HWID: RANDOM
|
||||||
|
IP: '::'
|
||||||
|
|
||||||
|
serviceAccount: {}
|
||||||
|
# # Specifies whether a service account should be created
|
||||||
|
# create: true
|
||||||
|
# # Annotations to add to the service account
|
||||||
|
# annotations: {}
|
||||||
|
# # The name of the service account to use.
|
||||||
|
# # If not set and create is true, a name is generated using the fullname template
|
||||||
|
# name: ""
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
podSecurityContext: {}
|
||||||
|
# fsGroup: 2000
|
||||||
|
|
||||||
|
securityContext: {}
|
||||||
|
# capabilities:
|
||||||
|
# drop:
|
||||||
|
# - ALL
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# runAsUser: 1000
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
httpPort: 80
|
||||||
|
kmsPort: 1688
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
className: ""
|
||||||
|
annotations: {}
|
||||||
|
# kubernetes.io/ingress.class: nginx
|
||||||
|
# kubernetes.io/tls-acme: "true"
|
||||||
|
hosts:
|
||||||
|
- host: chart-example.local
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
tls: []
|
||||||
|
# - secretName: chart-example-tls
|
||||||
|
# hosts:
|
||||||
|
# - chart-example.local
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||||
|
# choice for the user. This also increases chances charts run on environments with little
|
||||||
|
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||||
|
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
# requests:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
|
||||||
|
autoscaling:
|
||||||
|
enabled: false
|
||||||
|
minReplicas: 1
|
||||||
|
maxReplicas: 100
|
||||||
|
targetCPUUtilizationPercentage: 80
|
||||||
|
# targetMemoryUtilizationPercentage: 80
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
|
@ -0,0 +1,2 @@
|
||||||
|
Both docker files must access the source code of this repository. Therefore the build context must be the root of the project directory.
|
||||||
|
Take a look into the build script for the normal py-kms version, as it demonstrates exactly that case and how to use these docker files.
|
|
@ -0,0 +1,44 @@
|
||||||
|
# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size
|
||||||
|
FROM alpine:3.15
|
||||||
|
|
||||||
|
ENV IP ::
|
||||||
|
ENV DUALSTACK 1
|
||||||
|
ENV PORT 1688
|
||||||
|
ENV EPID ""
|
||||||
|
ENV LCID 1033
|
||||||
|
ENV CLIENT_COUNT 26
|
||||||
|
ENV ACTIVATION_INTERVAL 120
|
||||||
|
ENV RENEWAL_INTERVAL 10080
|
||||||
|
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 \
|
||||||
|
bash \
|
||||||
|
python3 \
|
||||||
|
py3-pip \
|
||||||
|
ca-certificates \
|
||||||
|
shadow \
|
||||||
|
tzdata \
|
||||||
|
&& 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 \
|
||||||
|
# Fix undefined timezone, in case the user did not mount the /etc/localtime
|
||||||
|
&& ln -sf /usr/share/zoneinfo/UTC /etc/localtime
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
WORKDIR /home/py-kms
|
||||||
|
|
||||||
|
EXPOSE ${PORT}/tcp
|
||||||
|
|
||||||
|
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"]
|
|
@ -1,36 +0,0 @@
|
||||||
# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size
|
|
||||||
|
|
||||||
FROM alpine:3.12
|
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
|
||||||
ENV PORT 1688
|
|
||||||
ENV EPID ""
|
|
||||||
ENV LCID 1033
|
|
||||||
ENV CLIENT_COUNT 26
|
|
||||||
ENV ACTIVATION_INTERVAL 120
|
|
||||||
ENV RENEWAL_INTERVAL 10080
|
|
||||||
ENV HWID "RANDOM"
|
|
||||||
ENV LOGLEVEL INFO
|
|
||||||
ENV LOGFILE /var/log/pykms_logserver.log
|
|
||||||
ENV LOGSIZE ""
|
|
||||||
|
|
||||||
RUN apk add --no-cache --update \
|
|
||||||
bash \
|
|
||||||
git \
|
|
||||||
py3-argparse \
|
|
||||||
py3-flask \
|
|
||||||
py3-pygments \
|
|
||||||
python3-tkinter \
|
|
||||||
sqlite-libs \
|
|
||||||
py3-pip && \
|
|
||||||
pip3 install peewee tzlocal && \
|
|
||||||
git clone https://github.com/SystemRage/py-kms/ /tmp/py-kms && \
|
|
||||||
mv /tmp/py-kms/py-kms /home/ && \
|
|
||||||
rm -rf /tmp/py-kms && \
|
|
||||||
apk del git
|
|
||||||
|
|
||||||
WORKDIR /home/py-kms
|
|
||||||
|
|
||||||
EXPOSE ${PORT}/tcp
|
|
||||||
|
|
||||||
ENTRYPOINT /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE}
|
|
|
@ -1,44 +0,0 @@
|
||||||
# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size
|
|
||||||
|
|
||||||
# Prepare the multiarch env
|
|
||||||
FROM alpine AS builder
|
|
||||||
RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-arm.tar.gz" | tar zxvf - -C . --strip-components 1
|
|
||||||
|
|
||||||
# Switch to the target image
|
|
||||||
FROM arm32v6/alpine:3.12
|
|
||||||
|
|
||||||
# Import qemu from the preparation
|
|
||||||
COPY --from=builder qemu-arm-static /usr/bin
|
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
|
||||||
ENV PORT 1688
|
|
||||||
ENV EPID ""
|
|
||||||
ENV LCID 1033
|
|
||||||
ENV CLIENT_COUNT 26
|
|
||||||
ENV ACTIVATION_INTERVAL 120
|
|
||||||
ENV RENEWAL_INTERVAL 10080
|
|
||||||
ENV HWID "RANDOM"
|
|
||||||
ENV LOGLEVEL INFO
|
|
||||||
ENV LOGFILE /var/log/pykms_logserver.log
|
|
||||||
ENV LOGSIZE ""
|
|
||||||
|
|
||||||
RUN apk add --no-cache --update \
|
|
||||||
bash \
|
|
||||||
git \
|
|
||||||
py3-argparse \
|
|
||||||
py3-flask \
|
|
||||||
py3-pygments \
|
|
||||||
python3-tkinter \
|
|
||||||
sqlite-libs \
|
|
||||||
py3-pip && \
|
|
||||||
pip3 install peewee tzlocal && \
|
|
||||||
git clone https://github.com/SystemRage/py-kms/ /tmp/py-kms && \
|
|
||||||
mv /tmp/py-kms/py-kms /home/ && \
|
|
||||||
rm -rf /tmp/py-kms && \
|
|
||||||
apk del git
|
|
||||||
|
|
||||||
WORKDIR /home/py-kms
|
|
||||||
|
|
||||||
EXPOSE ${PORT}/tcp
|
|
||||||
|
|
||||||
ENTRYPOINT /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE}
|
|
|
@ -1,44 +0,0 @@
|
||||||
# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size
|
|
||||||
|
|
||||||
# Prepare the multiarch env
|
|
||||||
FROM alpine AS builder
|
|
||||||
RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-arm.tar.gz" | tar zxvf - -C . --strip-components 1
|
|
||||||
|
|
||||||
# Switch to the target image
|
|
||||||
FROM arm32v7/alpine:3.12
|
|
||||||
|
|
||||||
# Import qemu from the preparation
|
|
||||||
COPY --from=builder qemu-arm-static /usr/bin
|
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
|
||||||
ENV PORT 1688
|
|
||||||
ENV EPID ""
|
|
||||||
ENV LCID 1033
|
|
||||||
ENV CLIENT_COUNT 26
|
|
||||||
ENV ACTIVATION_INTERVAL 120
|
|
||||||
ENV RENEWAL_INTERVAL 10080
|
|
||||||
ENV HWID "RANDOM"
|
|
||||||
ENV LOGLEVEL INFO
|
|
||||||
ENV LOGFILE /var/log/pykms_logserver.log
|
|
||||||
ENV LOGSIZE ""
|
|
||||||
|
|
||||||
RUN apk add --no-cache --update \
|
|
||||||
bash \
|
|
||||||
git \
|
|
||||||
py3-argparse \
|
|
||||||
py3-flask \
|
|
||||||
py3-pygments \
|
|
||||||
python3-tkinter \
|
|
||||||
sqlite-libs \
|
|
||||||
py3-pip && \
|
|
||||||
pip3 install peewee tzlocal && \
|
|
||||||
git clone https://github.com/SystemRage/py-kms/ /tmp/py-kms && \
|
|
||||||
mv /tmp/py-kms/py-kms /home/ && \
|
|
||||||
rm -rf /tmp/py-kms && \
|
|
||||||
apk del git
|
|
||||||
|
|
||||||
WORKDIR /home/py-kms
|
|
||||||
|
|
||||||
EXPOSE ${PORT}/tcp
|
|
||||||
|
|
||||||
ENTRYPOINT /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE}
|
|
|
@ -1,44 +0,0 @@
|
||||||
# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size
|
|
||||||
|
|
||||||
# Prepare the multiarch env
|
|
||||||
FROM alpine AS builder
|
|
||||||
RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-aarch64.tar.gz" | tar zxvf - -C . --strip-components 1
|
|
||||||
|
|
||||||
# Switch to the target image
|
|
||||||
FROM arm64v8/alpine:3.12
|
|
||||||
|
|
||||||
# Import qemu from the preparation
|
|
||||||
COPY --from=builder qemu-aarch64-static /usr/bin
|
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
|
||||||
ENV PORT 1688
|
|
||||||
ENV EPID ""
|
|
||||||
ENV LCID 1033
|
|
||||||
ENV CLIENT_COUNT 26
|
|
||||||
ENV ACTIVATION_INTERVAL 120
|
|
||||||
ENV RENEWAL_INTERVAL 10080
|
|
||||||
ENV HWID "RANDOM"
|
|
||||||
ENV LOGLEVEL INFO
|
|
||||||
ENV LOGFILE /var/log/pykms_logserver.log
|
|
||||||
ENV LOGSIZE ""
|
|
||||||
|
|
||||||
RUN apk add --no-cache --update \
|
|
||||||
bash \
|
|
||||||
git \
|
|
||||||
py3-argparse \
|
|
||||||
py3-flask \
|
|
||||||
py3-pygments \
|
|
||||||
python3-tkinter \
|
|
||||||
sqlite-libs \
|
|
||||||
py3-pip && \
|
|
||||||
pip3 install peewee tzlocal && \
|
|
||||||
git clone https://github.com/SystemRage/py-kms/ /tmp/py-kms && \
|
|
||||||
mv /tmp/py-kms/py-kms /home/ && \
|
|
||||||
rm -rf /tmp/py-kms && \
|
|
||||||
apk del git
|
|
||||||
|
|
||||||
WORKDIR /home/py-kms
|
|
||||||
|
|
||||||
EXPOSE ${PORT}/tcp
|
|
||||||
|
|
||||||
ENTRYPOINT /usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE}
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Use manifest-tool to create the manifest, given the experimental
|
|
||||||
# "docker manifest" command isn't available yet on Docker Hub.
|
|
||||||
|
|
||||||
curl -Lo manifest-tool "https://github.com/estesp/manifest-tool/releases/download/v1.0.2/manifest-tool-linux-amd64"
|
|
||||||
chmod +x manifest-tool
|
|
||||||
|
|
||||||
./manifest-tool push from-spec multi-arch-manifest-latest.yaml
|
|
||||||
./manifest-tool push from-spec multi-arch-manifest-minimal.yaml
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Register qemu-*-static for all supported processors except the
|
|
||||||
# current one, but also remove all registered binfmt_misc before
|
|
||||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
|
|
@ -1,21 +0,0 @@
|
||||||
image: pykmsorg/py-kms:latest
|
|
||||||
manifests:
|
|
||||||
- image: pykmsorg/py-kms:minimal-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: pykmsorg/py-kms:minimal-arm32v6
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v6
|
|
||||||
- image: pykmsorg/py-kms:minimal-arm32v7
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
- image: pykmsorg/py-kms:minimal-arm64v8
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
|
@ -1,21 +0,0 @@
|
||||||
image: pykmsorg/py-kms:minimal
|
|
||||||
manifests:
|
|
||||||
- image: pykmsorg/py-kms:minimal-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: pykmsorg/py-kms:minimal-arm32v6
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v6
|
|
||||||
- image: pykmsorg/py-kms:minimal-arm32v7
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
- image: pykmsorg/py-kms:minimal-arm64v8
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
dnspython==2.6.1
|
||||||
|
tzlocal==4.2
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Switch to the target image
|
||||||
|
FROM alpine:3.15
|
||||||
|
|
||||||
|
ARG BUILD_COMMIT=unknown
|
||||||
|
ARG BUILD_BRANCH=unknown
|
||||||
|
|
||||||
|
ENV IP ::
|
||||||
|
ENV DUALSTACK 1
|
||||||
|
ENV PORT 1688
|
||||||
|
ENV EPID ""
|
||||||
|
ENV LCID 1033
|
||||||
|
ENV CLIENT_COUNT 26
|
||||||
|
ENV ACTIVATION_INTERVAL 120
|
||||||
|
ENV RENEWAL_INTERVAL 10080
|
||||||
|
ENV HWID RANDOM
|
||||||
|
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 \
|
||||||
|
bash \
|
||||||
|
python3 \
|
||||||
|
py3-pip \
|
||||||
|
sqlite-libs \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
shadow \
|
||||||
|
&& pip3 install --no-cache-dir -r /home/py-kms/requirements.txt \
|
||||||
|
&& mkdir /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
|
||||||
|
&& ln -sf /usr/share/zoneinfo/UTC /etc/localtime
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
WORKDIR /home/py-kms
|
||||||
|
|
||||||
|
EXPOSE ${PORT}/tcp
|
||||||
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
|
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" ]
|
|
@ -1,41 +0,0 @@
|
||||||
FROM alpine:3.12
|
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
|
||||||
ENV PORT 1688
|
|
||||||
ENV EPID ""
|
|
||||||
ENV LCID 1033
|
|
||||||
ENV CLIENT_COUNT 26
|
|
||||||
ENV ACTIVATION_INTERVAL 120
|
|
||||||
ENV RENEWAL_INTERVAL 10080
|
|
||||||
ENV SQLITE false
|
|
||||||
ENV HWID "364F463A8863D35F"
|
|
||||||
ENV LOGLEVEL ERROR
|
|
||||||
ENV LOGFILE /var/log/pykms_logserver.log
|
|
||||||
ENV LOGSIZE ""
|
|
||||||
|
|
||||||
COPY start.sh /usr/bin/start.sh
|
|
||||||
|
|
||||||
RUN apk add --no-cache --update \
|
|
||||||
bash \
|
|
||||||
git \
|
|
||||||
py3-argparse \
|
|
||||||
py3-flask \
|
|
||||||
py3-pygments \
|
|
||||||
python3-tkinter \
|
|
||||||
sqlite-libs \
|
|
||||||
py3-pip && \
|
|
||||||
git clone https://github.com/SystemRage/py-kms.git /tmp/py-kms && \
|
|
||||||
git clone https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web && \
|
|
||||||
mv /tmp/py-kms/py-kms /home/ && \
|
|
||||||
mv /tmp/sqlite_web/sqlite_web /home/ && \
|
|
||||||
rm -rf /tmp/py-kms && \
|
|
||||||
rm -rf /tmp/sqlite_web && \
|
|
||||||
pip3 install peewee tzlocal pysqlite3 && \
|
|
||||||
chmod a+x /usr/bin/start.sh && \
|
|
||||||
apk del git
|
|
||||||
|
|
||||||
WORKDIR /home/py-kms
|
|
||||||
|
|
||||||
EXPOSE ${PORT}/tcp
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/start.sh"]
|
|
|
@ -1,49 +0,0 @@
|
||||||
# Prepare the multiarch env
|
|
||||||
FROM alpine AS builder
|
|
||||||
RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-arm.tar.gz" | tar zxvf - -C . --strip-components 1
|
|
||||||
|
|
||||||
# Switch to the target image
|
|
||||||
FROM arm32v6/alpine:3.12
|
|
||||||
|
|
||||||
# Import qemu from the preparation
|
|
||||||
COPY --from=builder qemu-arm-static /usr/bin
|
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
|
||||||
ENV PORT 1688
|
|
||||||
ENV EPID ""
|
|
||||||
ENV LCID 1033
|
|
||||||
ENV CLIENT_COUNT 26
|
|
||||||
ENV ACTIVATION_INTERVAL 120
|
|
||||||
ENV RENEWAL_INTERVAL 10080
|
|
||||||
ENV SQLITE false
|
|
||||||
ENV HWID "364F463A8863D35F"
|
|
||||||
ENV LOGLEVEL ERROR
|
|
||||||
ENV LOGFILE /var/log/pykms_logserver.log
|
|
||||||
ENV LOGSIZE ""
|
|
||||||
|
|
||||||
COPY start.sh /usr/bin/start.sh
|
|
||||||
|
|
||||||
RUN apk add --no-cache --update \
|
|
||||||
bash \
|
|
||||||
git \
|
|
||||||
py3-argparse \
|
|
||||||
py3-flask \
|
|
||||||
py3-pygments \
|
|
||||||
python3-tkinter \
|
|
||||||
sqlite-libs \
|
|
||||||
py3-pip && \
|
|
||||||
git clone https://github.com/SystemRage/py-kms.git /tmp/py-kms && \
|
|
||||||
git clone https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web && \
|
|
||||||
mv /tmp/py-kms/py-kms /home/ && \
|
|
||||||
mv /tmp/sqlite_web/sqlite_web /home/ && \
|
|
||||||
rm -rf /tmp/py-kms && \
|
|
||||||
rm -rf /tmp/sqlite_web && \
|
|
||||||
pip3 install peewee tzlocal pysqlite3 && \
|
|
||||||
chmod a+x /usr/bin/start.sh && \
|
|
||||||
apk del git
|
|
||||||
|
|
||||||
WORKDIR /home/py-kms
|
|
||||||
|
|
||||||
EXPOSE ${PORT}/tcp
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/start.sh"]
|
|
|
@ -1,49 +0,0 @@
|
||||||
# Prepare the multiarch env
|
|
||||||
FROM alpine AS builder
|
|
||||||
RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-arm.tar.gz" | tar zxvf - -C . --strip-components 1
|
|
||||||
|
|
||||||
# Switch to the target image
|
|
||||||
FROM arm32v7/alpine:3.12
|
|
||||||
|
|
||||||
# Import qemu from the preparation
|
|
||||||
COPY --from=builder qemu-arm-static /usr/bin
|
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
|
||||||
ENV PORT 1688
|
|
||||||
ENV EPID ""
|
|
||||||
ENV LCID 1033
|
|
||||||
ENV CLIENT_COUNT 26
|
|
||||||
ENV ACTIVATION_INTERVAL 120
|
|
||||||
ENV RENEWAL_INTERVAL 10080
|
|
||||||
ENV SQLITE false
|
|
||||||
ENV HWID "364F463A8863D35F"
|
|
||||||
ENV LOGLEVEL ERROR
|
|
||||||
ENV LOGFILE /var/log/pykms_logserver.log
|
|
||||||
ENV LOGSIZE ""
|
|
||||||
|
|
||||||
COPY start.sh /usr/bin/start.sh
|
|
||||||
|
|
||||||
RUN apk add --no-cache --update \
|
|
||||||
bash \
|
|
||||||
git \
|
|
||||||
py3-argparse \
|
|
||||||
py3-flask \
|
|
||||||
py3-pygments \
|
|
||||||
python3-tkinter \
|
|
||||||
sqlite-libs \
|
|
||||||
py3-pip && \
|
|
||||||
git clone https://github.com/SystemRage/py-kms.git /tmp/py-kms && \
|
|
||||||
git clone https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web && \
|
|
||||||
mv /tmp/py-kms/py-kms /home/ && \
|
|
||||||
mv /tmp/sqlite_web/sqlite_web /home/ && \
|
|
||||||
rm -rf /tmp/py-kms && \
|
|
||||||
rm -rf /tmp/sqlite_web && \
|
|
||||||
pip3 install peewee tzlocal pysqlite3 && \
|
|
||||||
chmod a+x /usr/bin/start.sh && \
|
|
||||||
apk del git
|
|
||||||
|
|
||||||
WORKDIR /home/py-kms
|
|
||||||
|
|
||||||
EXPOSE ${PORT}/tcp
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/start.sh"]
|
|
|
@ -1,49 +0,0 @@
|
||||||
# Prepare the multiarch env
|
|
||||||
FROM alpine AS builder
|
|
||||||
RUN apk add curl && curl -L "https://github.com/balena-io/qemu/releases/download/v4.0.0%2Bbalena2/qemu-4.0.0.balena2-aarch64.tar.gz" | tar zxvf - -C . --strip-components 1
|
|
||||||
|
|
||||||
# Switch to the target image
|
|
||||||
FROM arm64v8/alpine:3.12
|
|
||||||
|
|
||||||
# Import qemu from the preparation
|
|
||||||
COPY --from=builder qemu-aarch64-static /usr/bin
|
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
|
||||||
ENV PORT 1688
|
|
||||||
ENV EPID ""
|
|
||||||
ENV LCID 1033
|
|
||||||
ENV CLIENT_COUNT 26
|
|
||||||
ENV ACTIVATION_INTERVAL 120
|
|
||||||
ENV RENEWAL_INTERVAL 10080
|
|
||||||
ENV SQLITE false
|
|
||||||
ENV HWID "364F463A8863D35F"
|
|
||||||
ENV LOGLEVEL ERROR
|
|
||||||
ENV LOGFILE /var/log/pykms_logserver.log
|
|
||||||
ENV LOGSIZE ""
|
|
||||||
|
|
||||||
COPY start.sh /usr/bin/start.sh
|
|
||||||
|
|
||||||
RUN apk add --no-cache --update \
|
|
||||||
bash \
|
|
||||||
git \
|
|
||||||
py3-argparse \
|
|
||||||
py3-flask \
|
|
||||||
py3-pygments \
|
|
||||||
python3-tkinter \
|
|
||||||
sqlite-libs \
|
|
||||||
py3-pip && \
|
|
||||||
git clone https://github.com/SystemRage/py-kms.git /tmp/py-kms && \
|
|
||||||
git clone https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web && \
|
|
||||||
mv /tmp/py-kms/py-kms /home/ && \
|
|
||||||
mv /tmp/sqlite_web/sqlite_web /home/ && \
|
|
||||||
rm -rf /tmp/py-kms && \
|
|
||||||
rm -rf /tmp/sqlite_web && \
|
|
||||||
pip3 install peewee tzlocal pysqlite3 && \
|
|
||||||
chmod a+x /usr/bin/start.sh && \
|
|
||||||
apk del git
|
|
||||||
|
|
||||||
WORKDIR /home/py-kms
|
|
||||||
|
|
||||||
EXPOSE ${PORT}/tcp
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/start.sh"]
|
|
|
@ -1 +0,0 @@
|
||||||
docker build -t pykms/pykms:py3-kms . --file Dockerfile.amd64
|
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Use manifest-tool to create the manifest, given the experimental
|
|
||||||
# "docker manifest" command isn't available yet on Docker Hub.
|
|
||||||
|
|
||||||
curl -Lo manifest-tool "https://github.com/estesp/manifest-tool/releases/download/v1.0.2/manifest-tool-linux-amd64"
|
|
||||||
chmod +x manifest-tool
|
|
||||||
|
|
||||||
./manifest-tool push from-spec multi-arch-manifest-python3.yaml
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Register qemu-*-static for all supported processors except the
|
|
||||||
# current one, but also remove all registered binfmt_misc before
|
|
||||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
|
|
@ -1,21 +0,0 @@
|
||||||
image: pykmsorg/py-kms:python3
|
|
||||||
manifests:
|
|
||||||
- image: pykmsorg/py-kms:python3-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: pykmsorg/py-kms:python3-arm32v6
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v6
|
|
||||||
- image: pykmsorg/py-kms:python3-arm32v7
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
- image: pykmsorg/py-kms:python3-arm64v8
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
dnspython==2.6.1
|
||||||
|
tzlocal==4.2
|
||||||
|
|
||||||
|
Flask==2.3.2
|
||||||
|
gunicorn==22.0.0
|
|
@ -1,16 +0,0 @@
|
||||||
docker stop py3-kms
|
|
||||||
docker rm py3-kms
|
|
||||||
docker run -d --name py3-kms \
|
|
||||||
-t \
|
|
||||||
-p 8080:8080 \
|
|
||||||
-p 1688:1688 \
|
|
||||||
-e IP=0.0.0.0 \
|
|
||||||
-e PORT=1688 \
|
|
||||||
-e SQLITE=true \
|
|
||||||
-e HWID=RANDOM \
|
|
||||||
-e LOGLEVEL=INFO \
|
|
||||||
-e LOGFILE=/var/log/pykms_logserver.log \
|
|
||||||
-e LOGSIZE=2 \
|
|
||||||
-v /etc/localtime:/etc/localtime:ro \
|
|
||||||
-v /var/log:/var/log:rw \
|
|
||||||
--restart unless-stopped pykmsorg/py-kms:python3
|
|
|
@ -1,50 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
cd /home/py-kms
|
|
||||||
if [ "$SQLITE" == false ];
|
|
||||||
then
|
|
||||||
if [ "$EPID" == "" ];
|
|
||||||
then
|
|
||||||
if [ "$LOGSIZE" == "" ];
|
|
||||||
then
|
|
||||||
/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE}
|
|
||||||
else
|
|
||||||
/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE}
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$LOGSIZE" == "" ];
|
|
||||||
then
|
|
||||||
/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE}
|
|
||||||
else
|
|
||||||
/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE}
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$EPID" == "" ];
|
|
||||||
then
|
|
||||||
if [ "$LOGSIZE" == "" ];
|
|
||||||
then
|
|
||||||
/bin/bash -c "/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s ${PWD}/pykms_database.db -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} &"
|
|
||||||
sleep 5
|
|
||||||
/usr/bin/python3 pykms_Client.py ${IP} ${PORT} -m Windows10 &
|
|
||||||
/usr/bin/python3 /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/pykms_database.db --read-only
|
|
||||||
else
|
|
||||||
/bin/bash -c "/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s ${PWD}/pykms_database.db -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE} &"
|
|
||||||
sleep 5
|
|
||||||
/usr/bin/python3 pykms_Client.py ${IP} ${PORT} -m Windows10 &
|
|
||||||
/usr/bin/python3 /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/pykms_database.db --read-only
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$LOGSIZE" == "" ];
|
|
||||||
then
|
|
||||||
/bin/bash -c "/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s ${PWD}/pykms_database.db -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} &"
|
|
||||||
sleep 5
|
|
||||||
/usr/bin/python3 pykms_Client.py ${IP} ${PORT} -m Windows10 &
|
|
||||||
/usr/bin/python3 /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/pykms_database.db --read-only
|
|
||||||
else
|
|
||||||
/bin/sh -c "/usr/bin/python3 pykms_Server.py ${IP} ${PORT} -e ${EPID} -l ${LCID} -c ${CLIENT_COUNT} -a ${ACTIVATION_INTERVAL} -r ${RENEWAL_INTERVAL} -s ${PWD}/pykms_database.db -w ${HWID} -V ${LOGLEVEL} -F ${LOGFILE} -S ${LOGSIZE} &"
|
|
||||||
sleep 5
|
|
||||||
/usr/bin/python3 pykms_Client.py ${IP} ${PORT} -m Windows10 &
|
|
||||||
/usr/bin/python3 /home/sqlite_web/sqlite_web.py -H ${IP} -x ${PWD}/pykms_database.db --read-only
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
#!/usr/bin/python3 -u
|
||||||
|
|
||||||
|
# Need root privileges to change timezone, and user uid/gid, file/folder ownernship
|
||||||
|
|
||||||
|
import grp
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pwd
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)))
|
||||||
|
os.chown("/home/py-kms", new_uid, new_gid)
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
logger.debug(f'Correcting owner permissions on {fPath}.')
|
||||||
|
os.chown(fPath, new_uid, new_gid)
|
||||||
|
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)
|
||||||
|
logger.error(str(subprocess.check_output(['ls', '-la', os.environ['LOGFILE']])))
|
||||||
|
logger.info("Setting gid to '%s'." % str(new_gid))
|
||||||
|
os.setgid(new_gid)
|
||||||
|
|
||||||
|
logger.info("Setting uid to '%s'." % str(new_uid))
|
||||||
|
os.setuid(new_uid)
|
||||||
|
|
||||||
|
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'):
|
||||||
|
logger.info("Setting timzeone to %s" % tz )
|
||||||
|
# time.tzet() should be called on Unix, but doesn't exist on Windows.
|
||||||
|
time.tzset()
|
||||||
|
|
||||||
|
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(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
|
||||||
|
signal.signal(signal.SIGTERM, shutdown) # This signal will be sent by Docker to request shutdown
|
||||||
|
childProcess.communicate()
|
|
@ -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)
|
|
@ -0,0 +1,95 @@
|
||||||
|
#!/usr/bin/python3 -u
|
||||||
|
|
||||||
|
# This replaces the old start.sh and ensures all arguments are bound correctly from the environment variables...
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
PYTHON3 = '/usr/bin/python3'
|
||||||
|
argumentVariableMapping = {
|
||||||
|
'-l': 'LCID',
|
||||||
|
'-c': 'CLIENT_COUNT',
|
||||||
|
'-a': 'ACTIVATION_INTERVAL',
|
||||||
|
'-r': 'RENEWAL_INTERVAL',
|
||||||
|
'-w': 'HWID',
|
||||||
|
'-V': 'LOGLEVEL',
|
||||||
|
'-F': 'LOGFILE',
|
||||||
|
'-S': 'LOGSIZE',
|
||||||
|
'-e': 'EPID'
|
||||||
|
}
|
||||||
|
|
||||||
|
db_path = os.path.join(os.sep, 'home', 'py-kms', 'db', 'pykms_database.db')
|
||||||
|
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') == '1' # if the variable is not provided, we assume the user does not want the webui
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Build the command to execute
|
||||||
|
command = [PYTHON3, '-u', 'pykms_Server.py', listen_ip[0], listen_port]
|
||||||
|
for (arg, env) in argumentVariableMapping.items():
|
||||||
|
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 dual := os.environ.get('DUALSTACK'):
|
||||||
|
command.append("-d")
|
||||||
|
command.append(dual)
|
||||||
|
|
||||||
|
logger.debug("server_cmd: %s" % (" ".join(str(x) for x in command).strip()))
|
||||||
|
pykms_process = subprocess.Popen(command)
|
||||||
|
pykms_webui_process = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
if want_webui:
|
||||||
|
time.sleep(2) # Wait for the server to start up
|
||||||
|
pykms_webui_env = os.environ.copy()
|
||||||
|
pykms_webui_env['PYKMS_SQLITE_DB_PATH'] = db_path
|
||||||
|
pykms_webui_env['PORT'] = '8080'
|
||||||
|
pykms_webui_env['PYKMS_LICENSE_PATH'] = '/LICENSE'
|
||||||
|
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:
|
||||||
|
logger.error("Failed to start webui (ignoring and continuing anyways): %s" % e)
|
||||||
|
|
||||||
|
try:
|
||||||
|
pykms_process.wait()
|
||||||
|
except Exception:
|
||||||
|
# In case of any error - just shut down
|
||||||
|
pass
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if pykms_webui_process:
|
||||||
|
pykms_webui_process.terminate()
|
||||||
|
pykms_process.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
# Main
|
||||||
|
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')
|
||||||
|
streamhandler.setFormatter(formatter)
|
||||||
|
loggersrv.addHandler(streamhandler)
|
||||||
|
loggersrv.debug("user id: %s" % os.getuid())
|
||||||
|
|
||||||
|
start_kms(loggersrv)
|
|
@ -2,29 +2,26 @@
|
||||||
What follows are some guides how to start the `pykms_Server.py` script, which provides the emulated server.
|
What follows are some guides how to start the `pykms_Server.py` script, which provides the emulated server.
|
||||||
|
|
||||||
## Running as a service
|
## Running as a service
|
||||||
***
|
|
||||||
You can simply manage a daemon that runs as a background process. This can be achieved by using any of the notes below or by writing your own solution.
|
You can simply manage a daemon that runs as a background process. This can be achieved by using any of the notes below or by writing your own solution.
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
If you wish to get _py-kms_ just up and running without installing any dependencies or writing own scripts: Just use Docker !
|
If you wish to get _py-kms_ just up and running without installing any dependencies or writing own scripts: Just use Docker !
|
||||||
Docker also solves problems regarding the explicit IPv4 and IPv6 usage (it just supports both). The following
|
Docker also solves problems regarding the explicit IPv4 and IPv6 usage (it just supports both). The following
|
||||||
command will download, "install" and start _py-kms_ and also keep it alive after any service disruption.
|
command will download, "install" and start _py-kms_ and also keep it alive after any service disruption.
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name py-kms --restart always -p 1688:1688 pykmsorg/py-kms
|
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 [Docker Hub](https://hub.docker.com/r/pykmsorg/py-kms) (`pykmsorg/py-kms`). To ensure that you are using always the
|
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!
|
||||||
latest version you should check something like [watchtower](https://github.com/containrrr/watchtower) out !
|
|
||||||
|
|
||||||
#### Tags
|
#### Tags
|
||||||
There are currently three tags of the image available (select one just by appending `:<tag>` to the image from above):
|
There are currently three tags of the image available (select one just by appending `:<tag>` to the image from above):
|
||||||
* `latest`, currently the same like `minimal`.
|
* `latest`, currently the same like `minimal`.
|
||||||
* `minimal`, which is based on the python3 minimal configuration of py-kms. _This tag does NOT include `sqlite` support !_
|
* `minimal`, which is based on the python3 minimal configuration of py-kms. _This tag does NOT include `sqlite` support !_
|
||||||
* `python3`, which is fully configurable and equipped with `sqlite` support and a web interface (make sure to expose port 8080) for management.
|
* `python3`, which is fully configurable and equipped with `sqlite` support and a web-interface (make sure to expose port `8080`) for management.
|
||||||
|
|
||||||
|
Wait... Web-interface? Yes! `py-kms` now comes with a simple web-ui to let you browse the known clients or its supported products. In case you wonder, here is a screenshot of the web-ui (*note that this screenshot may not reflect the current state of the ui*):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
#### Architectures
|
#### Architectures
|
||||||
There are currently the following architectures available (if you need an other, feel free to open an issue):
|
There are currently the following architectures available (if you need an other, feel free to open an issue):
|
||||||
|
@ -37,45 +34,36 @@ _Please note that any architecture other than the classic `amd64` is slightly bi
|
||||||
|
|
||||||
#### Docker Compose
|
#### Docker Compose
|
||||||
You can use `docker-compose` instead of building and running the Dockerfile, so you do not need to respecify your settings again and again. The following Docker Compose file will deploy the `latest` image with the log into your local directory.
|
You can use `docker-compose` instead of building and running the Dockerfile, so you do not need to respecify your settings again and again. The following Docker Compose file will deploy the `latest` image with the log into your local directory.
|
||||||
|
Make sure to take a look into the `entrypoint.py` script to see all supported variable mappings!
|
||||||
```yaml
|
```yaml
|
||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
kms:
|
kms:
|
||||||
image: pykmsorg/py-kms:latest
|
image: ghcr.io/py-kms-organization/py-kms:python3
|
||||||
ports:
|
ports:
|
||||||
- 1688:1688
|
- 1688:1688 # kms
|
||||||
|
- 8080:8080 # web-interface
|
||||||
environment:
|
environment:
|
||||||
- IP=0.0.0.0
|
IP: "::"
|
||||||
- SQLITE=true
|
HWID: RANDOM
|
||||||
- HWID=RANDOM
|
LOGLEVEL: INFO
|
||||||
- LOGLEVEL=INFO
|
|
||||||
- LOGSIZE=2
|
|
||||||
- LOGFILE=/var/log/pykms_logserver.log
|
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./db:/home/py-kms/db
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- ./:/var/log:rw
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
Below is a fully expanded run command, detailing all the different supported environment variables to set. For further reference see the [start parameters](Usage.html#docker-environment) for the docker environment.
|
Below is a little bit more extended run command, detailing all the different supported environment variables to set. For further reference see the [start parameters](#docker-environment) for the docker environment.
|
||||||
```bash
|
```bash
|
||||||
docker run -it -d --name py3-kms \
|
docker run -it -d --name py3-kms \
|
||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-p 1688:1688 \
|
-p 1688:1688 \
|
||||||
-e IP=0.0.0.0 \
|
|
||||||
-e PORT=1688 \
|
|
||||||
-e SQLITE=true \
|
|
||||||
-e HWID=RANDOM \
|
|
||||||
-e LOGLEVEL=INFO \
|
|
||||||
-e LOGSIZE=2 \
|
|
||||||
-e LOGFILE=/var/log/pykms_logserver.log \
|
|
||||||
-v /etc/localtime:/etc/localtime:ro \
|
-v /etc/localtime:/etc/localtime:ro \
|
||||||
-v /var/log:/var/log:rw \
|
--restart unless-stopped ghcr.io/py-kms-organization/py-kms:[TAG]
|
||||||
--restart unless-stopped pykmsorg/py-kms:[TAG]
|
|
||||||
```
|
```
|
||||||
You can omit the `-e SQLITE=...` and `-p 8080:8080` option if you plan to use the `minimal` or `latest` image, which does not include the respective module support.
|
You can omit the `-p 8080:8080` option if you plan to use the `minimal` or `latest` image, which does not include the `sqlite` module support.
|
||||||
|
|
||||||
### Systemd
|
### Systemd
|
||||||
If you are running a Linux distro using `systemd`, create the file: `sudo nano /etc/systemd/system/py3-kms.service`, then add the following (change it where needed) and save:
|
If you are running a Linux distro using `systemd`, create the file: `sudo nano /etc/systemd/system/py3-kms.service`, then add the following (change it where needed) and save:
|
||||||
|
@ -91,7 +79,7 @@ Restart=always
|
||||||
RestartSec=1
|
RestartSec=1
|
||||||
KillMode=process
|
KillMode=process
|
||||||
User=root
|
User=root
|
||||||
ExecStart=/usr/bin/python3 </path/to/your/pykms/files/folder>/py-kms/pykms_Server.py 0.0.0.0 1688 -V DEBUG -F </path/to/your/log/files/folder>/pykms_logserver.log
|
ExecStart=/usr/bin/python3 </path/to/your/pykms/files/folder>/py-kms/pykms_Server.py :: 1688 -V DEBUG -F </path/to/your/log/files/folder>/pykms_logserver.log
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -100,10 +88,6 @@ Check syntax with `sudo systemd-analyze verify py3-kms.service`, correct file pe
|
||||||
start the daemon `sudo systemctl start py3-kms.service` and view its status `sudo systemctl status py3-kms.service`. Check if daemon is correctly running with `cat </path/to/your/log/files/folder>/pykms_logserver.log`. Finally a
|
start the daemon `sudo systemctl start py3-kms.service` and view its status `sudo systemctl status py3-kms.service`. Check if daemon is correctly running with `cat </path/to/your/log/files/folder>/pykms_logserver.log`. Finally a
|
||||||
few generic commands useful for interact with your daemon [here](https://linoxide.com/linux-how-to/enable-disable-services-ubuntu-systemd-upstart/).
|
few generic commands useful for interact with your daemon [here](https://linoxide.com/linux-how-to/enable-disable-services-ubuntu-systemd-upstart/).
|
||||||
|
|
||||||
### Etrigan
|
|
||||||
You can run py-kms daemonized (via [Etrigan](https://github.com/SystemRage/Etrigan)) using a command like `python3 pykms_Server.py etrigan start` and stop it with `python3 pykms_Server.py etrigan stop`. With Etrigan you have another
|
|
||||||
way to launch py-kms GUI (specially suitable if you're using a virtualenv), so `python3 pykms_Server.py etrigan start -g` and stop the GUI with `python3 pykms_Server.py etrigan stop` (or interact with the `EXIT` button).
|
|
||||||
|
|
||||||
### Upstart (deprecated)
|
### Upstart (deprecated)
|
||||||
If you are running a Linux distro using `upstart` (deprecated), create the file: `sudo nano /etc/init/py3-kms.conf`, then add the following (change it where needed) and save:
|
If you are running a Linux distro using `upstart` (deprecated), create the file: `sudo nano /etc/init/py3-kms.conf`, then add the following (change it where needed) and save:
|
||||||
```
|
```
|
||||||
|
@ -114,7 +98,7 @@ env PYKMSPATH=</path/to/your/pykms/files/folder>/py-kms
|
||||||
env LOGPATH=</path/to/your/log/files/folder>/pykms_logserver.log
|
env LOGPATH=</path/to/your/log/files/folder>/pykms_logserver.log
|
||||||
start on runlevel [2345]
|
start on runlevel [2345]
|
||||||
stop on runlevel [016]
|
stop on runlevel [016]
|
||||||
exec $PYTHONPATH/python3 $PYKMSPATH/pykms_Server.py 0.0.0.0 1688 -V DEBUG -F $LOGPATH
|
exec $PYTHONPATH/python3 $PYKMSPATH/pykms_Server.py :: 1688 -V DEBUG -F $LOGPATH
|
||||||
respawn
|
respawn
|
||||||
```
|
```
|
||||||
Check syntax with `sudo init-checkconf -d /etc/init/py3-kms.conf`, then reload upstart to recognise this process `sudo initctl reload-configuration`. Now start the service `sudo start py3-kms`, and you can see the logfile
|
Check syntax with `sudo init-checkconf -d /etc/init/py3-kms.conf`, then reload upstart to recognise this process `sudo initctl reload-configuration`. Now start the service `sudo start py3-kms`, and you can see the logfile
|
||||||
|
@ -134,7 +118,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
|
||||||
_svc_name_ = "py-kms"
|
_svc_name_ = "py-kms"
|
||||||
_svc_display_name_ = "py-kms"
|
_svc_display_name_ = "py-kms"
|
||||||
_proc = None
|
_proc = None
|
||||||
_cmd = ["C:\Windows\Python27\python.exe", "C:\Windows\Python27\py-kms\pykms_Server.py"]
|
_cmd = ["C:\Windows\Python27\python.exe", "C:\Windows\Python27\py-kms\pykms_Server.py"] # UPDATE THIS - because Python 2.7 is end of life and you will use other parameters anyway
|
||||||
|
|
||||||
def __init__(self,args):
|
def __init__(self,args):
|
||||||
win32serviceutil.ServiceFramework.__init__(self,args)
|
win32serviceutil.ServiceFramework.__init__(self,args)
|
||||||
|
@ -171,17 +155,15 @@ They might be useful to you:
|
||||||
- [FreeBSD](https://github.com/SystemRage/py-kms/issues/89)
|
- [FreeBSD](https://github.com/SystemRage/py-kms/issues/89)
|
||||||
|
|
||||||
## Manual Execution
|
## Manual Execution
|
||||||
***
|
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
- Python 3.x.
|
- Python 3.x.
|
||||||
- Tkinter module (for the GUI).
|
|
||||||
- If the `tzlocal` module is installed, the "Request Time" in the verbose output will be converted into local time. Otherwise, it will be in UTC.
|
- If the `tzlocal` module is installed, the "Request Time" in the verbose output will be converted into local time. Otherwise, it will be in UTC.
|
||||||
- It can use the `sqlite3` module, storing activation data in a database so it can be recalled again.
|
- It can use the `sqlite3` module, storing activation data in a database so it can be recalled again.
|
||||||
- Installation example on Ubuntu / Mint:
|
- Installation example on Ubuntu / Mint (`requirements.txt` is from the sources):
|
||||||
- `sudo apt-get update`
|
- `sudo apt-get update`
|
||||||
- `sudo apt-get install python3-tk python3-pip`
|
- `sudo apt-get install python3-pip`
|
||||||
- `sudo pip3 install tzlocal pysqlite3`
|
- `pip3 install -r requirements.txt` (on Ubuntu Server 22, you'll need `pysqlite3-binary` - see [this issue](https://github.com/Py-KMS-Organization/py-kms/issues/76))
|
||||||
|
|
||||||
### Startup
|
### Startup
|
||||||
A Linux user with `ip addr` command can get his KMS IP (Windows users can try `ipconfig /all`).
|
A Linux user with `ip addr` command can get his KMS IP (Windows users can try `ipconfig /all`).
|
||||||
|
@ -211,6 +193,9 @@ user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 1688
|
||||||
To stop `pykms_Server.py`, in the same bash window where code running, simply press `CTRL+C`.
|
To stop `pykms_Server.py`, in the same bash window where code running, simply press `CTRL+C`.
|
||||||
Alternatively, in a new bash window, use `kill <pid>` command (you can type `ps aux` first and have the process <pid>) or `killall <name_of_server>`.
|
Alternatively, in a new bash window, use `kill <pid>` command (you can type `ps aux` first and have the process <pid>) or `killall <name_of_server>`.
|
||||||
|
|
||||||
|
### Web-Interface
|
||||||
|
As you may have noticed, the Docker container contains a web-interface, replacing the old GUI. If you want to launch it manually, checkout this [issue discussion](https://github.com/Py-KMS-Organization/py-kms/issues/100#issuecomment-1710827824) to learn more.
|
||||||
|
|
||||||
### Quick Guide
|
### Quick Guide
|
||||||
The following are just some brief notes about parameters handling. For a more detailed description see [here](Usage.md).
|
The following are just some brief notes about parameters handling. For a more detailed description see [here](Usage.md).
|
||||||
|
|
||||||
|
|
676
docs/Keys.md
676
docs/Keys.md
|
@ -3,387 +3,411 @@ These are keys, which can be used to activate a product with _py-kms_ (note this
|
||||||
sometimes even reject it by itself (often due too many uses - in that case try to use an other one).
|
sometimes even reject it by itself (often due too many uses - in that case try to use an other one).
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
***
|
|
||||||
|
### Windows Server 2022
|
||||||
|
|
||||||
|
| Product | GVLK |
|
||||||
|
| -------------------------------------------- | ------------------------------- |
|
||||||
|
| Windows Server 2022 Datacenter | `WX4NM-KYWYW-QJJR4-XV3QB-6VM33` |
|
||||||
|
| Windows Server 2022 Standard | `VDYBN-27WPP-V4HQT-9VMD4-VMK7H` |
|
||||||
|
| Windows Server 2022 Datacenter Azure Edition | `NTBV8-9K7Q8-V27C6-M2BTV-KHMXV` |
|
||||||
|
|
||||||
### Windows Server 2019
|
### Windows Server 2019
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ----------------------------------------------------------- | ------------------------------- |
|
||||||
| Windows Server 2019 Datacenter | `WMDGN-G9PQG-XVVXX-R3X43-63DFG` |
|
| Windows Server 2019 Datacenter | `WMDGN-G9PQG-XVVXX-R3X43-63DFG` |
|
||||||
| Windows Server 2019 Standard | `N69G4-B89J2-4G8F4-WWYCC-J464C` |
|
| Windows Server 2019 Standard | `N69G4-B89J2-4G8F4-WWYCC-J464C` |
|
||||||
| Windows Server 2019 Essentials | `WVDHN-86M7X-466P6-VHXV7-YY726` |
|
| Windows Server 2019 Essentials | `WVDHN-86M7X-466P6-VHXV7-YY726` |
|
||||||
| Windows Server 2019 Azure Core | `FDNH6-VW9RW-BXPJ7-4XTYG-239TB` |
|
| Windows Server 2019 Azure Core | `FDNH6-VW9RW-BXPJ7-4XTYG-239TB` |
|
||||||
| Windows Server 2019 Datacenter Semi-Annual Channel (v.1809) | `6NMRW-2C8FM-D24W7-TQWMY-CWH2D` |
|
| Windows Server 2019 Datacenter Semi-Annual Channel (v.1809) | `6NMRW-2C8FM-D24W7-TQWMY-CWH2D` |
|
||||||
| Windows Server 2019 Standard Semi-Annual Channel (v.1809) | `N2KJX-J94YW-TQVFB-DG9YT-724CC` |
|
| Windows Server 2019 Standard Semi-Annual Channel (v.1809) | `N2KJX-J94YW-TQVFB-DG9YT-724CC` |
|
||||||
| Windows Server 2019 ARM64 | `GRFBW-QNDC4-6QBHG-CCK3B-2PR88` |
|
| Windows Server 2019 ARM64 | `GRFBW-QNDC4-6QBHG-CCK3B-2PR88` |
|
||||||
|
|
||||||
### Windows Server 2016
|
### Windows Server 2016
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ----------------------------------------------------------- | ------------------------------------------------------------------ |
|
||||||
| Windows Server 2016 Standard Semi-Annual Channel (v.1803) | `PTXN8-JFHJM-4WC78-MPCBR-9W4KR` |
|
| Windows Server 2016 Standard Semi-Annual Channel (v.1803) | `PTXN8-JFHJM-4WC78-MPCBR-9W4KR` |
|
||||||
| Windows Server 2016 Datacenter Semi-Annual Channel (v.1803) | `2HXDN-KRXHB-GPYC7-YCKFJ-7FVDG` |
|
| Windows Server 2016 Datacenter Semi-Annual Channel (v.1803) | `2HXDN-KRXHB-GPYC7-YCKFJ-7FVDG` |
|
||||||
| Windows Server 2016 Datacenter Semi-Annual Channel (v.1709) | `6Y6KB-N82V8-D8CQV-23MJW-BWTG6` |
|
| Windows Server 2016 Datacenter Semi-Annual Channel (v.1709) | `6Y6KB-N82V8-D8CQV-23MJW-BWTG6` |
|
||||||
| Windows Server 2016 Standard Semi-Annual Channel (v.1709) | `DPCNP-XQFKJ-BJF7R-FRC8D-GF6G4` |
|
| Windows Server 2016 Standard Semi-Annual Channel (v.1709) | `DPCNP-XQFKJ-BJF7R-FRC8D-GF6G4` |
|
||||||
| Windows Server 2016 ARM64 | `K9FYF-G6NCK-73M32-XMVPY-F9DRR` |
|
| Windows Server 2016 ARM64 | `K9FYF-G6NCK-73M32-XMVPY-F9DRR` |
|
||||||
| Windows Server 2016 Datacenter | `CB7KF-BWN84-R7R2Y-793K2-8XDDG` |
|
| Windows Server 2016 Datacenter | `CB7KF-BWN84-R7R2Y-793K2-8XDDG` |
|
||||||
| Windows Server 2016 Standard | `WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY` |
|
| Windows Server 2016 Standard | `WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY` |
|
||||||
| Windows Server 2016 Essentials | `JCKRF-N37P4-C2D82-9YXRT-4M63B` |
|
| Windows Server 2016 Essentials | `JCKRF-N37P4-C2D82-9YXRT-4M63B` |
|
||||||
| Windows Server 2016 Cloud Storage | `QN4C6-GBJD2-FB422-GHWJK-GJG2R` |
|
| Windows Server 2016 Cloud Storage | `QN4C6-GBJD2-FB422-GHWJK-GJG2R` |
|
||||||
| Windows Server 2016 Azure Core | `VP34G-4NPPG-79JTQ-864T4-R3MQX`<br>`WNCYY-GFBH2-M4WTT-XQ2FP-PG2K9` |
|
| Windows Server 2016 Azure Core | `VP34G-4NPPG-79JTQ-864T4-R3MQX`<br>`WNCYY-GFBH2-M4WTT-XQ2FP-PG2K9` |
|
||||||
|
|
||||||
### Windows 10
|
### Windows 10 & Windows 11
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Windows 10 Professional Workstation | `NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J` |
|
| Windows 10/11 Professional Workstation | `NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J` |
|
||||||
| Windows 10 Professional Workstation N | `9FNHH-K3HBT-3W4TD-6383H-6XYWF` |
|
| Windows 10/11 Professional Workstation N | `9FNHH-K3HBT-3W4TD-6383H-6XYWF` |
|
||||||
| Windows 10 Enterprise G | `YYVX9-NTFWV-6MDM3-9PT4T-4M68B` |
|
| Windows 10/11 Enterprise G | `YYVX9-NTFWV-6MDM3-9PT4T-4M68B` |
|
||||||
| Windows 10 Enterprise G N | `44RPN-FTY23-9VTTB-MP9BX-T84FV` |
|
| Windows 10/11 Enterprise G N | `44RPN-FTY23-9VTTB-MP9BX-T84FV` |
|
||||||
| Windows 10 Enterprise LTSC 2019 | `M7XTQ-FN8P6-TTKYV-9D4CC-J462D` |
|
| Windows 10 Enterprise LTSC 2019/2021 | `M7XTQ-FN8P6-TTKYV-9D4CC-J462D` |
|
||||||
| Windows 10 Enterprise LTSC 2019 N | `92NFX-8DJQP-P6BBQ-THF9C-7CG2H` |
|
| Windows 10 Enterprise LTSC 2019/2021 N | `92NFX-8DJQP-P6BBQ-THF9C-7CG2H` |
|
||||||
| Windows 10 Remote Server | `7NBT4-WGBQX-MP4H7-QXFF8-YP3KX` |
|
| Windows 10/11 Remote Server | `7NBT4-WGBQX-MP4H7-QXFF8-YP3KX` |
|
||||||
| Windows 10 Enterprise for Remote Sessions | `CPWHC-NT2C7-VYW78-DHDB2-PG3GK` |
|
| Windows 10 Enterprise for Remote Sessions <br> Windows 10 Enterprise for virtual desktops <br> Windows 11 Enterprise multi-session | `CPWHC-NT2C7-VYW78-DHDB2-PG3GK` |
|
||||||
| Windows 10 S (Lean) | `NBTWJ-3DR69-3C4V8-C26MC-GQ9M6` |
|
| Windows 10 S (Lean) | `NBTWJ-3DR69-3C4V8-C26MC-GQ9M6` |
|
||||||
| Windows 10 Professional | `W269N-WFGWX-YVC9B-4J6C9-T83GX` |
|
| Windows 10/11 Professional | `W269N-WFGWX-YVC9B-4J6C9-T83GX` |
|
||||||
| Windows 10 Professional N | `MH37W-N47XK-V7XM9-C7227-GCQG9`<br>`HMNWJ-V69R6-B2CDC-8P7VT-2373K` |
|
| Windows 10/11 Professional N | `MH37W-N47XK-V7XM9-C7227-GCQG9`<br>`HMNWJ-V69R6-B2CDC-8P7VT-2373K` |
|
||||||
| Windows 10 Professional Education | `6TP4R-GNPTD-KYYHQ-7B7DP-J447Y` |
|
| Windows 10/11 Professional Education | `6TP4R-GNPTD-KYYHQ-7B7DP-J447Y` |
|
||||||
| Windows 10 Professional Education N | `YVWGF-BXNMC-HTQYQ-CPQ99-66QFC` |
|
| Windows 10/11 Professional Education N | `YVWGF-BXNMC-HTQYQ-CPQ99-66QFC` |
|
||||||
| Windows 10 Education | `NW6C2-QMPVW-D7KKK-3GKT6-VCFB2`<br>`F48BJ-8NX82-MRVY9-PF8BW-HMHY2` |
|
| Windows 10/11 Education | `NW6C2-QMPVW-D7KKK-3GKT6-VCFB2`<br>`F48BJ-8NX82-MRVY9-PF8BW-HMHY2` |
|
||||||
| Windows 10 Education N | `2WH4N-8QGBV-H22JP-CT43Q-MDWWJ`<br>`PPWGW-8NW9C-J77Q9-8WHB9-QV64W` |
|
| Windows 10/11 Education N | `2WH4N-8QGBV-H22JP-CT43Q-MDWWJ`<br>`PPWGW-8NW9C-J77Q9-8WHB9-QV64W` |
|
||||||
| Windows 10 Enterprise | `NPPR9-FWDCX-D2C8J-H872K-2YT43`<br>`96YNV-9X4RP-2YYKB-RMQH4-6Q72D`<br>`TN6CM-KCVXP-VVP8X-YVCF7-R9BDH`<br>`3PMKQ-YNVGT-HFJGG-2F4FQ-9D6T7` |
|
| Windows 10/11 Enterprise | `NPPR9-FWDCX-D2C8J-H872K-2YT43`<br>`96YNV-9X4RP-2YYKB-RMQH4-6Q72D`<br>`TN6CM-KCVXP-VVP8X-YVCF7-R9BDH`<br>`3PMKQ-YNVGT-HFJGG-2F4FQ-9D6T7` |
|
||||||
| Windows 10 Enterprise N | `DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4`<br>`WGGHN-J84D6-QYCPR-T7PJ7-X766F` |
|
| Windows 10/11 Enterprise N | `DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4`<br>`WGGHN-J84D6-QYCPR-T7PJ7-X766F` |
|
||||||
| Windows 10 Enterprise S | `H76BG-QBNM7-73XY9-V6W2T-684BJ` |
|
| Windows 10/11 Enterprise S | `H76BG-QBNM7-73XY9-V6W2T-684BJ` |
|
||||||
| Windows 10 Enterprise S N | `X4R4B-NV6WD-PKTVK-F98BH-4C2J8` |
|
| Windows 10/11 Enterprise S N | `X4R4B-NV6WD-PKTVK-F98BH-4C2J8` |
|
||||||
| Windows 10 Enterprise 2015 LTSB | `WNMTR-4C88C-JK8YV-HQ7T2-76DF9` |
|
| Windows 10 Enterprise 2015 LTSB | `WNMTR-4C88C-JK8YV-HQ7T2-76DF9` |
|
||||||
| Windows 10 Enterprise 2015 LTSB N | `2F77B-TNFGY-69QQF-B8YKP-D69TJ`<br>`RW7WN-FMT44-KRGBK-G44WK-QV7YK` |
|
| Windows 10 Enterprise 2015 LTSB N | `2F77B-TNFGY-69QQF-B8YKP-D69TJ`<br>`RW7WN-FMT44-KRGBK-G44WK-QV7YK` |
|
||||||
| Windows 10 Enterprise 2016 LTSB | `DCPHK-NFMTC-H88MJ-PFHPY-QJ4BJ` |
|
| Windows 10 Enterprise 2016 LTSB | `DCPHK-NFMTC-H88MJ-PFHPY-QJ4BJ` |
|
||||||
| Windows 10 Enterprise 2016 LTSB N | `QFFDN-GRT3P-VKWWX-X7T3R-8B639` |
|
| Windows 10 Enterprise 2016 LTSB N | `QFFDN-GRT3P-VKWWX-X7T3R-8B639` |
|
||||||
| Windows 10 Home<br>Windows 10 Core | `TX9XD-98N7V-6WMQ6-BX7FG-H8Q99`<br>`33QT6-RCNYF-DXB4F-DGP7B-7MHX9` |
|
| Windows 10/11 Home<br>Windows 10/11 Core | `TX9XD-98N7V-6WMQ6-BX7FG-H8Q99`<br>`33QT6-RCNYF-DXB4F-DGP7B-7MHX9` |
|
||||||
| Windows 10 Home N<br>Windows 10 Core N | `3KHY7-WNT83-DGQKR-F7HPR-844BM`<br>`CP4KF-NG6TC-9K6QF-P6GTT-H8RBM` |
|
| Windows 10/11 Home N<br>Windows 10/11 Core N | `3KHY7-WNT83-DGQKR-F7HPR-844BM`<br>`CP4KF-NG6TC-9K6QF-P6GTT-H8RBM` |
|
||||||
| Windows 10 Home Single Language<br>Windows 10 Core Single Language | `7HNRX-D7KGG-3K4RQ-4WPJ4-YTDFH`<br>`9HGRW-NH2CQ-XQHJD-YCRWB-6VJV7`<br>`4NX46-6DHCG-MR3PH-9FMCX-3RQ3G` |
|
| Windows 10/11 Home Single Language<br>Windows 10 Core Single Language | `7HNRX-D7KGG-3K4RQ-4WPJ4-YTDFH`<br>`9HGRW-NH2CQ-XQHJD-YCRWB-6VJV7`<br>`4NX46-6DHCG-MR3PH-9FMCX-3RQ3G` |
|
||||||
| Windows 10 Home Country Specific<br>Windows 10 Core Country Specific | `PVMJN-6DFY6-9CCP6-7BKTT-D3WVR`<br>`JN9HR-MH7K4-DBPDD-TFTXF-Q9MMF` |
|
| Windows 10/11 Home Country Specific<br>Windows 10 Core Country Specific | `PVMJN-6DFY6-9CCP6-7BKTT-D3WVR`<br>`JN9HR-MH7K4-DBPDD-TFTXF-Q9MMF` |
|
||||||
|
|
||||||
### Windows Server 2012 R2
|
### Windows Server 2012 R2
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ------------------------------------ | ------------------------------- |
|
||||||
| Windows Server 2012 R2 Standard | `D2N9P-3P6X9-2R39C-7RTCD-MDVJX` |
|
| Windows Server 2012 R2 Standard | `D2N9P-3P6X9-2R39C-7RTCD-MDVJX` |
|
||||||
| Windows Server 2012 R2 Datacenter | `W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9` |
|
| Windows Server 2012 R2 Datacenter | `W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9` |
|
||||||
| Windows Server 2012 R2 Essentials | `KNC87-3J2TX-XB4WP-VCPJV-M4FWM` |
|
| Windows Server 2012 R2 Essentials | `KNC87-3J2TX-XB4WP-VCPJV-M4FWM` |
|
||||||
| Windows Server 2012 R2 Cloud Storage | `3NPTF-33KPT-GGBPR-YX76B-39KDD` |
|
| Windows Server 2012 R2 Cloud Storage | `3NPTF-33KPT-GGBPR-YX76B-39KDD` |
|
||||||
|
|
||||||
### Windows 8.1
|
### Windows 8.1
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ------------------------------------------- | ------------------------------------------------------------------ |
|
||||||
| Windows 8.1 Professional | `GCRJD-8NW9H-F2CDX-CCM8D-9D6T9` |
|
| Windows 8.1 Professional | `GCRJD-8NW9H-F2CDX-CCM8D-9D6T9` |
|
||||||
| Windows 8.1 Professional N | `HMCNV-VVBFX-7HMBH-CTY9B-B4FXY` |
|
| Windows 8.1 Professional N | `HMCNV-VVBFX-7HMBH-CTY9B-B4FXY` |
|
||||||
| Windows 8.1 Professional WMC | `789NJ-TQK6T-6XTH8-J39CJ-J8D3P` |
|
| Windows 8.1 Professional WMC | `789NJ-TQK6T-6XTH8-J39CJ-J8D3P` |
|
||||||
| Windows 8.1 Enterprise | `MHF9N-XY6XB-WVXMC-BTDCT-MKKG7`<br>`FHQNR-XYXYC-8PMHT-TV4PH-DRQ3H` |
|
| Windows 8.1 Enterprise | `MHF9N-XY6XB-WVXMC-BTDCT-MKKG7`<br>`FHQNR-XYXYC-8PMHT-TV4PH-DRQ3H` |
|
||||||
| Windows 8.1 Enterprise N | `TT4HM-HN7YT-62K67-RGRQJ-JFFXW`<br>`NDRDJ-3YBP2-8WTKD-CK7VB-HT8KW` |
|
| Windows 8.1 Enterprise N | `TT4HM-HN7YT-62K67-RGRQJ-JFFXW`<br>`NDRDJ-3YBP2-8WTKD-CK7VB-HT8KW` |
|
||||||
| Windows 8.1 Embedded Industry Automotive | `VHXM3-NR6FT-RY6RT-CK882-KW2CJ` |
|
| Windows 8.1 Embedded Industry Automotive | `VHXM3-NR6FT-RY6RT-CK882-KW2CJ` |
|
||||||
| Windows 8.1 Embedded Industry Enterprise | `FNFKF-PWTVT-9RC8H-32HB2-JB34X` |
|
| Windows 8.1 Embedded Industry Enterprise | `FNFKF-PWTVT-9RC8H-32HB2-JB34X` |
|
||||||
| Windows 8.1 Embedded Industry Professional | `NMMPB-38DD4-R2823-62W8D-VXKJB` |
|
| Windows 8.1 Embedded Industry Professional | `NMMPB-38DD4-R2823-62W8D-VXKJB` |
|
||||||
| Windows 8.1 Core | `M9Q9P-WNJJT-6PXPY-DWX8H-6XWKK` |
|
| Windows 8.1 Core | `M9Q9P-WNJJT-6PXPY-DWX8H-6XWKK` |
|
||||||
| Windows 8.1 Core N | `7B9N3-D94CG-YTVHR-QBPX3-RJP64` |
|
| Windows 8.1 Core N | `7B9N3-D94CG-YTVHR-QBPX3-RJP64` |
|
||||||
| Windows 8.1 Core Single Language | `BB6NG-PQ82V-VRDPW-8XVD2-V8P66` |
|
| Windows 8.1 Core Single Language | `BB6NG-PQ82V-VRDPW-8XVD2-V8P66` |
|
||||||
| Windows 8.1 Core Country Specific | `NCTT7-2RGK8-WMHRF-RY7YQ-JTXG3` |
|
| Windows 8.1 Core Country Specific | `NCTT7-2RGK8-WMHRF-RY7YQ-JTXG3` |
|
||||||
| Windows 8.1 Core ARM | `XYTND-K6QKT-K2MRH-66RTM-43JKP` |
|
| Windows 8.1 Core ARM | `XYTND-K6QKT-K2MRH-66RTM-43JKP` |
|
||||||
| Windows 8.1 Core Connected | `3PY8R-QHNP9-W7XQD-G6DPH-3J2C9` |
|
| Windows 8.1 Core Connected | `3PY8R-QHNP9-W7XQD-G6DPH-3J2C9` |
|
||||||
| Windows 8.1 Core Connected N | `Q6HTR-N24GM-PMJFP-69CD8-2GXKR` |
|
| Windows 8.1 Core Connected N | `Q6HTR-N24GM-PMJFP-69CD8-2GXKR` |
|
||||||
| Windows 8.1 Core Connected Country Specific | `R962J-37N87-9VVK2-WJ74P-XTMHR` |
|
| Windows 8.1 Core Connected Country Specific | `R962J-37N87-9VVK2-WJ74P-XTMHR` |
|
||||||
| Windows 8.1 Core Connected Single Language | `KF37N-VDV38-GRRTV-XH8X6-6F3BB` |
|
| Windows 8.1 Core Connected Single Language | `KF37N-VDV38-GRRTV-XH8X6-6F3BB` |
|
||||||
| Windows 8.1 Professional Student | `MX3RK-9HNGX-K3QKC-6PJ3F-W8D7B` |
|
| Windows 8.1 Professional Student | `MX3RK-9HNGX-K3QKC-6PJ3F-W8D7B` |
|
||||||
| Windows 8.1 Professional Student N | `TNFGH-2R6PB-8XM3K-QYHX2-J4296` |
|
| Windows 8.1 Professional Student N | `TNFGH-2R6PB-8XM3K-QYHX2-J4296` |
|
||||||
|
|
||||||
### Windows Server 2012
|
### Windows Server 2012
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ----------------------------------------------------------------------- | ------------------------------- |
|
||||||
| Windows Server 2012<br>Windows 8 Core | `BN3D2-R7TKB-3YPBD-8DRP2-27GG4` |
|
| Windows Server 2012<br>Windows 8 Core | `BN3D2-R7TKB-3YPBD-8DRP2-27GG4` |
|
||||||
| Windows Server 2012 N<br>Windows 8 Core N | `8N2M2-HWPGY-7PGT9-HGDD8-GVGGY` |
|
| Windows Server 2012 N<br>Windows 8 Core N | `8N2M2-HWPGY-7PGT9-HGDD8-GVGGY` |
|
||||||
| Windows Server 2012 Single Language<br>Windows 8 Core Single Language | `2WN2H-YGCQR-KFX6K-CD6TF-84YXQ` |
|
| Windows Server 2012 Single Language<br>Windows 8 Core Single Language | `2WN2H-YGCQR-KFX6K-CD6TF-84YXQ` |
|
||||||
| Windows Server 2012 Country Specific<br>Windows 8 Core Country Specific | `4K36P-JN4VD-GDC6V-KDT89-DYFKP` |
|
| Windows Server 2012 Country Specific<br>Windows 8 Core Country Specific | `4K36P-JN4VD-GDC6V-KDT89-DYFKP` |
|
||||||
| Windows Server 2012 Standard | `XC9B7-NBPP2-83J2H-RHMBY-92BT4` |
|
| Windows Server 2012 Standard | `XC9B7-NBPP2-83J2H-RHMBY-92BT4` |
|
||||||
| Windows Server 2012 MultiPoint Standard | `HM7DN-YVMH3-46JC3-XYTG7-CYQJJ` |
|
| Windows Server 2012 MultiPoint Standard | `HM7DN-YVMH3-46JC3-XYTG7-CYQJJ` |
|
||||||
| Windows Server 2012 MultiPoint Premium | `XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G` |
|
| Windows Server 2012 MultiPoint Premium | `XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G` |
|
||||||
| Windows Server 2012 Datacenter | `48HP8-DN98B-MYWDG-T2DCC-8W83P` |
|
| Windows Server 2012 Datacenter | `48HP8-DN98B-MYWDG-T2DCC-8W83P` |
|
||||||
|
|
||||||
### Windows 8
|
### Windows 8
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ----------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
||||||
| Windows 8 Professional | `NG4HW-VH26C-733KW-K6F98-J8CK4` |
|
| Windows 8 Professional | `NG4HW-VH26C-733KW-K6F98-J8CK4` |
|
||||||
| Windows 8 Professional N | `XCVCF-2NXM9-723PB-MHCB7-2RYQQ` |
|
| Windows 8 Professional N | `XCVCF-2NXM9-723PB-MHCB7-2RYQQ` |
|
||||||
| Windows 8 Professional WMC | `GNBB8-YVD74-QJHX6-27H4K-8QHDG`<br>`NQ3PX-BBY8Y-RRHMM-TBHFW-PJ866` |
|
| Windows 8 Professional WMC | `GNBB8-YVD74-QJHX6-27H4K-8QHDG`<br>`NQ3PX-BBY8Y-RRHMM-TBHFW-PJ866` |
|
||||||
| Windows 8 Enterprise | `32JNW-9KQ84-P47T8-D8GGY-CWCK7`<br>`8M9BN-YB7W9-YV3VJ-7WMGG-MKH3V` |
|
| Windows 8 Enterprise | `32JNW-9KQ84-P47T8-D8GGY-CWCK7`<br>`8M9BN-YB7W9-YV3VJ-7WMGG-MKH3V` |
|
||||||
| Windows 8 Enterprise N | `JMNMF-RHW7P-DMY6X-RF3DR-X2BQT`<br>`NCVKH-RB9D4-R86X8-GB8WG-4M2K6` |
|
| Windows 8 Enterprise N | `JMNMF-RHW7P-DMY6X-RF3DR-X2BQT`<br>`NCVKH-RB9D4-R86X8-GB8WG-4M2K6` |
|
||||||
| Windows 8 Embedded Industry Professional | `JVPDN-TBWJW-PD94V-QYKJ2-KWYQM`<br>`RYXVT-BNQG7-VD29F-DBMRY-HT73M` |
|
| Windows 8 Embedded Industry Professional | `JVPDN-TBWJW-PD94V-QYKJ2-KWYQM`<br>`RYXVT-BNQG7-VD29F-DBMRY-HT73M` |
|
||||||
| Windows 8 Embedded Industry Enterprise | `NKB3R-R2F8T-3XCDP-7Q2KW-XWYQ2` |
|
| Windows 8 Embedded Industry Enterprise | `NKB3R-R2F8T-3XCDP-7Q2KW-XWYQ2` |
|
||||||
| Windows 8 Core<br>Windows Server 2012 | `BN3D2-R7TKB-3YPBD-8DRP2-27GG4` |
|
| Windows 8 Core<br>Windows Server 2012 | `BN3D2-R7TKB-3YPBD-8DRP2-27GG4` |
|
||||||
| Windows 8 Core N<br>Windows Server 2012 N | `8N2M2-HWPGY-7PGT9-HGDD8-GVGGY` |
|
| Windows 8 Core N<br>Windows Server 2012 N | `8N2M2-HWPGY-7PGT9-HGDD8-GVGGY` |
|
||||||
| Windows 8 Core Single Language<br>Windows Server 2012 Single Language | `2WN2H-YGCQR-KFX6K-CD6TF-84YXQ` |
|
| Windows 8 Core Single Language<br>Windows Server 2012 Single Language | `2WN2H-YGCQR-KFX6K-CD6TF-84YXQ` |
|
||||||
| Windows 8 Core Country Specific<br>Windows Server 2012 Country Specific | `4K36P-JN4VD-GDC6V-KDT89-DYFKP` |
|
| Windows 8 Core Country Specific<br>Windows Server 2012 Country Specific | `4K36P-JN4VD-GDC6V-KDT89-DYFKP` |
|
||||||
| Windows 8 Core ARM | `DXHJF-N9KQX-MFPVR-GHGQK-Y7RKV` |
|
| Windows 8 Core ARM | `DXHJF-N9KQX-MFPVR-GHGQK-Y7RKV` |
|
||||||
|
|
||||||
### Windows Server 2008 R2
|
### Windows Server 2008 R2
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ------------------------------------------------ | ------------------------------- |
|
||||||
| Windows MultiPoint Server 2010 | `736RG-XDKJK-V34PF-BHK87-J6X3K` |
|
| Windows MultiPoint Server 2010 | `736RG-XDKJK-V34PF-BHK87-J6X3K` |
|
||||||
| Windows Server 2008 R2 Web | `6TPJF-RBVHG-WBW2R-86QPH-6RTM4` |
|
| Windows Server 2008 R2 Web | `6TPJF-RBVHG-WBW2R-86QPH-6RTM4` |
|
||||||
| Windows Server 2008 R2 HPC edition | `TT8MH-CG224-D3D7Q-498W2-9QCTX` |
|
| Windows Server 2008 R2 HPC edition | `TT8MH-CG224-D3D7Q-498W2-9QCTX` |
|
||||||
| Windows Server 2008 R2 Standard | `YC6KT-GKW9T-YTKYR-T4X34-R7VHC` |
|
| Windows Server 2008 R2 Standard | `YC6KT-GKW9T-YTKYR-T4X34-R7VHC` |
|
||||||
| Windows Server 2008 R2 Enterprise | `489J6-VHDMP-X63PK-3K798-CPX3Y` |
|
| Windows Server 2008 R2 Enterprise | `489J6-VHDMP-X63PK-3K798-CPX3Y` |
|
||||||
| Windows Server 2008 R2 Datacenter | `74YFP-3QFB3-KQT8W-PMXWJ-7M648` |
|
| Windows Server 2008 R2 Datacenter | `74YFP-3QFB3-KQT8W-PMXWJ-7M648` |
|
||||||
| Windows Server 2008 R2 for Itanium-based Systems | `GT63C-RJFQ3-4GMB6-BRFB9-CB83V` |
|
| Windows Server 2008 R2 for Itanium-based Systems | `GT63C-RJFQ3-4GMB6-BRFB9-CB83V` |
|
||||||
|
|
||||||
### Windows 7
|
### Windows 7
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| --------------------------- | ------------------------------------------------------------------ |
|
||||||
| Windows 7 Professional | `FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4`<br>`MYKDJ-XV4CV-M2D3P-KDVY4-MPTW8` |
|
| Windows 7 Professional | `FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4`<br>`MYKDJ-XV4CV-M2D3P-KDVY4-MPTW8` |
|
||||||
| Windows 7 Professional N | `MRPKT-YTG23-K7D7T-X2JMM-QY7MG` |
|
| Windows 7 Professional N | `MRPKT-YTG23-K7D7T-X2JMM-QY7MG` |
|
||||||
| Windows 7 Professional E | `W82YF-2Q76Y-63HXB-FGJG9-GF7QX` |
|
| Windows 7 Professional E | `W82YF-2Q76Y-63HXB-FGJG9-GF7QX` |
|
||||||
| Windows 7 Enterprise | `33PXH-7Y6KF-2VJC9-XBBR8-HVTHH` |
|
| Windows 7 Enterprise | `33PXH-7Y6KF-2VJC9-XBBR8-HVTHH` |
|
||||||
| Windows 7 Enterprise N | `YDRBP-3D83W-TY26F-D46B2-XCKRJ` |
|
| Windows 7 Enterprise N | `YDRBP-3D83W-TY26F-D46B2-XCKRJ` |
|
||||||
| Windows 7 Enterprise E | `C29WB-22CC8-VJ326-GHFJW-H9DH4` |
|
| Windows 7 Enterprise E | `C29WB-22CC8-VJ326-GHFJW-H9DH4` |
|
||||||
| Windows 7 Embedded POSReady | `YBYF6-BHCR3-JPKRB-CDW7B-F9BK4` |
|
| Windows 7 Embedded POSReady | `YBYF6-BHCR3-JPKRB-CDW7B-F9BK4` |
|
||||||
| Windows 7 Embedded ThinPC | `73KQT-CD9G6-K7TQG-66MRP-CQ22C` |
|
| Windows 7 Embedded ThinPC | `73KQT-CD9G6-K7TQG-66MRP-CQ22C` |
|
||||||
| Windows 7 Embedded Standard | `XGY72-BRBBT-FF8MH-2GG8H-W7KCW` |
|
| Windows 7 Embedded Standard | `XGY72-BRBBT-FF8MH-2GG8H-W7KCW` |
|
||||||
|
|
||||||
### Windows Server 2008
|
### Windows Server 2008
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| -------------------------------------------------- | ------------------------------- |
|
||||||
| Windows Server 2008 Web | `WYR28-R7TFJ-3X2YQ-YCY4H-M249D` |
|
| Windows Server 2008 Web | `WYR28-R7TFJ-3X2YQ-YCY4H-M249D` |
|
||||||
| Windows Server 2008 Standard | `TM24T-X9RMF-VWXK6-X8JC9-BFGM2` |
|
| Windows Server 2008 Standard | `TM24T-X9RMF-VWXK6-X8JC9-BFGM2` |
|
||||||
| Windows Server 2008 Standard without Hyper-V | `W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ` |
|
| Windows Server 2008 Standard without Hyper-V | `W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ` |
|
||||||
| Windows Server 2008 Enterprise | `YQGMW-MPWTJ-34KDK-48M3W-X4Q6V` |
|
| Windows Server 2008 Enterprise | `YQGMW-MPWTJ-34KDK-48M3W-X4Q6V` |
|
||||||
| Windows Server 2008 Enterprise without Hyper-V | `39BXF-X8Q23-P2WWT-38T2F-G3FPG` |
|
| Windows Server 2008 Enterprise without Hyper-V | `39BXF-X8Q23-P2WWT-38T2F-G3FPG` |
|
||||||
| Windows Server 2008 HPC edition (Computer Cluster) | `RCTX3-KWVHP-BR6TB-RB6DM-6X7HP` |
|
| Windows Server 2008 HPC edition (Computer Cluster) | `RCTX3-KWVHP-BR6TB-RB6DM-6X7HP` |
|
||||||
| Windows Server 2008 Datacenter | `7M67G-PC374-GR742-YH8V4-TCBY3` |
|
| Windows Server 2008 Datacenter | `7M67G-PC374-GR742-YH8V4-TCBY3` |
|
||||||
| Windows Server 2008 Datacenter without Hyper-V | `22XQ2-VRXRG-P8D42-K34TD-G3QQC` |
|
| Windows Server 2008 Datacenter without Hyper-V | `22XQ2-VRXRG-P8D42-K34TD-G3QQC` |
|
||||||
| Windows Server 2008 for Itanium-Based Systems | `4DWFP-JF3DJ-B7DTH-78FJB-PDRHK` |
|
| Windows Server 2008 for Itanium-Based Systems | `4DWFP-JF3DJ-B7DTH-78FJB-PDRHK` |
|
||||||
|
|
||||||
### Windows Vista
|
### Windows Vista
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| -------------------------- | ------------------------------- |
|
||||||
| Windows Vista Business | `YFKBB-PQJJV-G996G-VWGXY-2V3X8` |
|
| Windows Vista Business | `YFKBB-PQJJV-G996G-VWGXY-2V3X8` |
|
||||||
| Windows Vista Business N | `HMBQG-8H2RH-C77VX-27R82-VMQBT` |
|
| Windows Vista Business N | `HMBQG-8H2RH-C77VX-27R82-VMQBT` |
|
||||||
| Windows Vista Enterprise | `VKK3X-68KWM-X2YGT-QR4M6-4BWMV` |
|
| Windows Vista Enterprise | `VKK3X-68KWM-X2YGT-QR4M6-4BWMV` |
|
||||||
| Windows Vista Enterprise N | `VTC42-BM838-43QHV-84HX6-XJXKV` |
|
| Windows Vista Enterprise N | `VTC42-BM838-43QHV-84HX6-XJXKV` |
|
||||||
|
|
||||||
### Windows Previews
|
### Windows Previews
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ---------------------------------------------------------- | ------------------------------------------------------------------ |
|
||||||
| Windows Server 2019 Datacenter [Preview] | `6XBNX-4JQGW-QX6QG-74P76-72V67` |
|
| Windows Server 2019 Datacenter [Preview] | `6XBNX-4JQGW-QX6QG-74P76-72V67` |
|
||||||
| Windows Server 2019 Standard [Preview] | `MFY9F-XBN2F-TYFMP-CCV49-RMYVH` |
|
| Windows Server 2019 Standard [Preview] | `MFY9F-XBN2F-TYFMP-CCV49-RMYVH` |
|
||||||
| Windows 10 Home / Core [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Home / Core [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Home / Core Country Specific [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Home / Core Country Specific [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Home / Core N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Home / Core N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Home / Core Single Language [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Home / Core Single Language [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Home / Core [Technical Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Home / Core [Technical Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Education [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Education [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Education N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Education N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Enterprise [Preview] | `QNMXX-GCD3W-TCCT4-872RV-G6P4J` |
|
| Windows 10 Enterprise [Preview] | `QNMXX-GCD3W-TCCT4-872RV-G6P4J` |
|
||||||
| Windows 10 Enterprise 2015 LTSB [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Enterprise 2015 LTSB [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Enterprise 2015 LTSB N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Enterprise 2015 LTSB N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Enterprise N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Enterprise N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Professional N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows 10 Professional N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 10 Professional [Preview] | `XQHPH-N4D9W-M8P96-DPDFP-TMVPY` |
|
| Windows 10 Professional [Preview] | `XQHPH-N4D9W-M8P96-DPDFP-TMVPY` |
|
||||||
| Windows 10 Professional WMC [Pre-Release] | `NKPM6-TCVPT-3HRFX-Q4H9B-QJ34Y`<br>`328NF-RTPQT-84J4Q-V44B8-78R2B` |
|
| Windows 10 Professional WMC [Pre-Release] | `NKPM6-TCVPT-3HRFX-Q4H9B-QJ34Y`<br>`328NF-RTPQT-84J4Q-V44B8-78R2B` |
|
||||||
| Windows 10 IoT Core [Pre-Release] | `7NX88-X6YM3-9Q3YT-CCGBF-KBVQF`<br>`NHY4C-KCMKV-V9K9M-7R43T-KTP64` |
|
| Windows 10 IoT Core [Pre-Release] | `7NX88-X6YM3-9Q3YT-CCGBF-KBVQF`<br>`NHY4C-KCMKV-V9K9M-7R43T-KTP64` |
|
||||||
| Windows 10 Core Connected [Pre-Release] | `DJMYQ-WN6HG-YJ2YX-82JDB-CWFCW`<br>`QBWKP-NFVG3-CYGTT-724CF-FCYPW` |
|
| Windows 10 Core Connected [Pre-Release] | `DJMYQ-WN6HG-YJ2YX-82JDB-CWFCW`<br>`QBWKP-NFVG3-CYGTT-724CF-FCYPW` |
|
||||||
| Windows 10 Core Connected N [Pre-Release] | `JQNT7-W63G4-WX4QX-RD9M9-6CPKM`<br>`TKDDW-N77V2-WXKMG-QY6WQ-WQJXM` |
|
| Windows 10 Core Connected N [Pre-Release] | `JQNT7-W63G4-WX4QX-RD9M9-6CPKM`<br>`TKDDW-N77V2-WXKMG-QY6WQ-WQJXM` |
|
||||||
| Windows 10 Core Connected Single Language [Pre-Release] | `QQMNF-GPVQ6-BFXGG-GWRCX-7XKT7`<br>`RQ2MN-RKR94-P86YQ-TM76X-P3667` |
|
| Windows 10 Core Connected Single Language [Pre-Release] | `QQMNF-GPVQ6-BFXGG-GWRCX-7XKT7`<br>`RQ2MN-RKR94-P86YQ-TM76X-P3667` |
|
||||||
| Windows 10 Core Connected Country Specific [Pre-Release] | `FTNXM-J4RGP-MYQCV-RVM8R-TVH24`<br>`TNPJK-GCKPR-4WX4C-HCJHT-HFRC4` |
|
| Windows 10 Core Connected Country Specific [Pre-Release] | `FTNXM-J4RGP-MYQCV-RVM8R-TVH24`<br>`TNPJK-GCKPR-4WX4C-HCJHT-HFRC4` |
|
||||||
| Windows 10 Professional S [Pre-Release] | `3NF4D-GF9GY-63VKH-QRC3V-7QW8P`<br>`NFDD9-FX3VM-DYCKP-B8HT8-D9M2C` |
|
| Windows 10 Professional S [Pre-Release] | `3NF4D-GF9GY-63VKH-QRC3V-7QW8P`<br>`NFDD9-FX3VM-DYCKP-B8HT8-D9M2C` |
|
||||||
| Windows 10 Professional S N [Pre-Release] | `KNDJ3-GVHWT-3TV4V-36K8Y-PR4PF`<br>`8Q36Y-N2F39-HRMHT-4XW33-TCQR4` |
|
| Windows 10 Professional S N [Pre-Release] | `KNDJ3-GVHWT-3TV4V-36K8Y-PR4PF`<br>`8Q36Y-N2F39-HRMHT-4XW33-TCQR4` |
|
||||||
| Windows 10 Professional Student [Pre-Release] | `YNXW3-HV3VB-Y83VG-KPBXM-6VH3Q`<br>`N6X24-448X6-HYV8Y-8XQ3V-DRRDQ` |
|
| Windows 10 Professional Student [Pre-Release] | `YNXW3-HV3VB-Y83VG-KPBXM-6VH3Q`<br>`N6X24-448X6-HYV8Y-8XQ3V-DRRDQ` |
|
||||||
| Windows 10 Professional Student N [Pre-Release] | `8G9XJ-GN6PJ-GW787-MVV7G-GMR99`<br>`XHGFB-WNK7Q-BG8VG-BG2KQ-KKWX9` |
|
| Windows 10 Professional Student N [Pre-Release] | `8G9XJ-GN6PJ-GW787-MVV7G-GMR99`<br>`XHGFB-WNK7Q-BG8VG-BG2KQ-KKWX9` |
|
||||||
| Windows 10 PPIPro [Pre-Release (build 15063)] | `?????-?????-?????-?????-?????` |
|
| Windows 10 PPIPro [Pre-Release (build 15063)] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 8 Core / Server 2012 [RC] | `?????-?????-?????-?????-?????` |
|
| Windows 8 Core / Server 2012 [RC] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 8 Core / Server 2012 Country Specific [RC] | `?????-?????-?????-?????-?????` |
|
| Windows 8 Core / Server 2012 Country Specific [RC] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 8 Core / Server 2012 N [RC] | `?????-?????-?????-?????-?????` |
|
| Windows 8 Core / Server 2012 N [RC] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 8 Core / Server 2012 Single Language [RC] | `?????-?????-?????-?????-?????` |
|
| Windows 8 Core / Server 2012 Single Language [RC] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 8 Core ARM64 [RC] | `?????-?????-?????-?????-?????` |
|
| Windows 8 Core ARM64 [RC] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 8 Embedded Industry Professional [Beta] | `?????-?????-?????-?????-?????` |
|
| Windows 8 Embedded Industry Professional [Beta] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 8 Embedded Industry Enterprise [Beta] | `?????-?????-?????-?????-?????` |
|
| Windows 8 Embedded Industry Enterprise [Beta] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 8.1 Enterprise [Preview] | `2MP7K-98NK8-WPVF3-Q2WDG-VMD98` |
|
| Windows 8.1 Enterprise [Preview] | `2MP7K-98NK8-WPVF3-Q2WDG-VMD98` |
|
||||||
| Windows 8.1 Professional (Blue) [Preview] | `MTWNQ-CKDHJ-3HXW9-Q2PFX-WB2HQ` |
|
| Windows 8.1 Professional (Blue) [Preview] | `MTWNQ-CKDHJ-3HXW9-Q2PFX-WB2HQ` |
|
||||||
| Windows 8 Professional WMC [RC] | `MY4N9-TGH34-4X4VY-8FG2T-RRDPV` |
|
| Windows 8 Professional WMC [RC] | `MY4N9-TGH34-4X4VY-8FG2T-RRDPV` |
|
||||||
| Windows 8.x [Preview] | `MPWP3-DXNP9-BRD79-W8WFP-3YFJ6` |
|
| Windows 8.x [Preview] | `MPWP3-DXNP9-BRD79-W8WFP-3YFJ6` |
|
||||||
| Windows 8.x ARM64 [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 8.x ARM64 [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Core Connected [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Core Connected [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Core Connected N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Core Connected N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Core Connected Country Specific [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Core Connected Country Specific [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Core Connected Single Language [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Core Connected Single Language [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Professional Student [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Professional Student [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Professional Student N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Professional Student N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Embedded Industry Professional [Beta] | `XY4TQ-CXNVJ-YCT73-HH6R7-R897X` |
|
| Windows Next Embedded Industry Professional [Beta] | `XY4TQ-CXNVJ-YCT73-HH6R7-R897X` |
|
||||||
| Windows Next Embedded Industry Enterprise [Beta] | `XCNC9-BPK3C-KCCMD-FTDTC-KWY4G`<br>`WN3XP-M9YFD-JRJ84-4J9FB-QJY4G` |
|
| Windows Next Embedded Industry Enterprise [Beta] | `XCNC9-BPK3C-KCCMD-FTDTC-KWY4G`<br>`WN3XP-M9YFD-JRJ84-4J9FB-QJY4G` |
|
||||||
| Windows Next Embedded Industry Automotive [Beta] | `GN2X2-KXTK6-P92FR-VBB9G-PDJFP`<br>`434XB-NH62H-JG7RG-P3KMD-XHHJC` |
|
| Windows Next Embedded Industry Automotive [Beta] | `GN2X2-KXTK6-P92FR-VBB9G-PDJFP`<br>`434XB-NH62H-JG7RG-P3KMD-XHHJC` |
|
||||||
| Windows Server Next MultiPoint Standard [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows Server Next MultiPoint Standard [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Server Next MultiPoint Premium [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows Server Next MultiPoint Premium [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Server Next Enterprise [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows Server Next Enterprise [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Server Next Standard [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows Server Next Standard [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Server Next Web [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows Server Next Web [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Server Next HPC Edition [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows Server Next HPC Edition [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Server Next HI [Preview] | `7VX4N-3VDHQ-VYGHB-JXJVP-9QB26` |
|
| Windows Server Next HI [Preview] | `7VX4N-3VDHQ-VYGHB-JXJVP-9QB26` |
|
||||||
| Enterprise ProdKey3 Win 9984 DLA/Bypass NQR Test | `?????-?????-?????-?????-?????` |
|
| Enterprise ProdKey3 Win 9984 DLA/Bypass NQR Test | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Server 2012 R2 Essentials [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows Server 2012 R2 Essentials [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Server 2016 Datacenter [Preview] | `VRDD2-NVGDP-K7QG8-69BR4-TVFHB` |
|
| Windows Server 2016 Datacenter [Preview] | `VRDD2-NVGDP-K7QG8-69BR4-TVFHB` |
|
||||||
| Windows Vista Business [Preview 1] | `XQYF4-QVCMY-YXQRD-9QPV8-3YP9V` |
|
| Windows Vista Business [Preview 1] | `XQYF4-QVCMY-YXQRD-9QPV8-3YP9V` |
|
||||||
| Windows Vista Business [Preview 2] | `YVT36-YVCP2-J97GQ-7T22R-RWV8P` |
|
| Windows Vista Business [Preview 2] | `YVT36-YVCP2-J97GQ-7T22R-RWV8P` |
|
||||||
| Windows Vista Business N [Preview] | `HGBJ9-RWD6M-6HDGW-6T2XD-JQ66F` |
|
| Windows Vista Business N [Preview] | `HGBJ9-RWD6M-6HDGW-6T2XD-JQ66F` |
|
||||||
| Windows Vista Enterprise [Preview 1] | `3JHG3-Y66GP-B7F3K-JFVX2-VBH7K` |
|
| Windows Vista Enterprise [Preview 1] | `3JHG3-Y66GP-B7F3K-JFVX2-VBH7K` |
|
||||||
| Windows Vista Enterprise [Beta-2 build 5384] | `MF9PG-RQK7R-26BPJ-TWFYK-RHXCM` |
|
| Windows Vista Enterprise [Beta-2 build 5384] | `MF9PG-RQK7R-26BPJ-TWFYK-RHXCM` |
|
||||||
| Windows Vista Enterprise N [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows Vista Enterprise N [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Longhorn Web [Preview] | `MDRCM-4WKCW-J93FF-J9Q48-M6KBB` |
|
| Windows Longhorn Web [Preview] | `MDRCM-4WKCW-J93FF-J9Q48-M6KBB` |
|
||||||
| Windows Longhorn HPC Edition [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows Longhorn HPC Edition [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Longhorn Standard [Preview] | `Q37JX-P3HHB-GKRH2-PDBKG-GGXPW` |
|
| Windows Longhorn Standard [Preview] | `Q37JX-P3HHB-GKRH2-PDBKG-GGXPW` |
|
||||||
| Windows Longhorn Enterprise [Preview] | `7KYMQ-R788Q-4RF69-KTWKM-92PFJ` |
|
| Windows Longhorn Enterprise [Preview] | `7KYMQ-R788Q-4RF69-KTWKM-92PFJ` |
|
||||||
| Windows Longhorn Datacenter [Preview] | `HR8VD-7DHG2-48378-M9D73-28F4T` |
|
| Windows Longhorn Datacenter [Preview] | `HR8VD-7DHG2-48378-M9D73-28F4T` |
|
||||||
| Windows Longhorn for Itanium Systems [Preview] | `CWV9H-PHGPW-V93WV-QBQV9-8V336` |
|
| Windows Longhorn for Itanium Systems [Preview] | `CWV9H-PHGPW-V93WV-QBQV9-8V336` |
|
||||||
| Windows 7 Business [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Business [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Business N [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Business N [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Enterprise [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Enterprise [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Enterprise N [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Enterprise N [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Server Web [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Server Web [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Server Standard [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Server Standard [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Server Standard without Hyper-V [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Server Standard without Hyper-V [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Server Enterprise [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Server Enterprise [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Server Enterprise without Hyper-V [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Server Enterprise without Hyper-V [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Server Datacenter [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Server Datacenter [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Server Datacenter without Hyper-V [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Server Datacenter without Hyper-V [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows 7 Server for Itanium Systems [Preview] | `?????-?????-?????-?????-?????` |
|
| Windows 7 Server for Itanium Systems [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Education [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Education [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Education N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Education N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Professional [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Professional [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Professional N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Professional N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Enterprise N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Enterprise N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Enterprise [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Enterprise [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Enterprise S [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Enterprise S [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Enterprise S N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Enterprise S N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Professional S [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Professional S [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
| Windows Next Professional S N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
| Windows Next Professional S N [Pre-Release] | `?????-?????-?????-?????-?????` |
|
||||||
|
|
||||||
## Office
|
## Office
|
||||||
***
|
|
||||||
|
### Office 2021
|
||||||
|
|
||||||
|
| Product | GVLK |
|
||||||
|
| ----------------------------------- | ------------------------------- |
|
||||||
|
| Office Access LTSC 2021 | `WM8YG-YNGDD-4JHDC-PG3F4-FC4T4` |
|
||||||
|
| Office Excel LTSC 2021 | `NWG3X-87C9K-TC7YY-BC2G7-G6RVC` |
|
||||||
|
| Office Outlook LTSC 2021 | `C9FM6-3N72F-HFJXB-TM3V9-T86R9` |
|
||||||
|
| Office Powerpoint LTSC 2021 | `TY7XF-NFRBR-KJ44C-G83KF-GX27K` |
|
||||||
|
| Office LTSC Professional Plus 2021 | `FXYTK-NJJ8C-GB6DW-3DYQT-6F7TH` |
|
||||||
|
| Office Project Pro 2021 | `FTNWT-C6WBT-8HMGF-K9PRX-QV9H8` |
|
||||||
|
| Office Project Standard 2021 | `J2JDC-NJCYY-9RGQ4-YXWMH-T3D4T` |
|
||||||
|
| Office Publisher LTSC 2021 | `2MW9D-N4BXM-9VBPG-Q7W6M-KFBGQ` |
|
||||||
|
| Office Skype for Business LTSC 2021 | `HWCXN-K3WBT-WJBKY-R8BD9-XK29P` |
|
||||||
|
| Office LTSC Standard 2021 | `KDX7X-BNVR8-TXXGX-4Q7Y8-78VT3` |
|
||||||
|
| Office Visio LTSC Pro 2021 | `KNH8D-FGHT4-T8RK3-CTDYJ-K2HT4` |
|
||||||
|
| Office Visio LTSC Standard 2021 | `MJVNY-BYWPY-CWV6J-2RKRT-4M8QG` |
|
||||||
|
| Office Word LTSC 2021 | `TN8H9-M34D3-Y64V9-TR72V-X79KV` |
|
||||||
|
|
||||||
### Office 2019
|
### Office 2019
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ------------------------------- | ------------------------------- |
|
||||||
| Professional Plus 2019 [C2R] | `VQ9DP-NVHPH-T9HJC-J9PDT-KTQRG` |
|
| Professional Plus 2019 [C2R] | `VQ9DP-NVHPH-T9HJC-J9PDT-KTQRG` |
|
||||||
| Professional Plus 2019 | `NMMKJ-6RK4F-KMJVX-8D9MJ-6MWKP` |
|
| Professional Plus 2019 | `NMMKJ-6RK4F-KMJVX-8D9MJ-6MWKP` |
|
||||||
| Standard 2019 | `6NWWJ-YQWMR-QKGCB-6TMB3-9D9HK` |
|
| Standard 2019 | `6NWWJ-YQWMR-QKGCB-6TMB3-9D9HK` |
|
||||||
| Project Professional 2019 [C2R] | `XM2V9-DN9HH-QB449-XDGKC-W2RMW` |
|
| Project Professional 2019 [C2R] | `XM2V9-DN9HH-QB449-XDGKC-W2RMW` |
|
||||||
| Project Professional 2019 | `B4NPR-3FKK7-T2MBV-FRQ4W-PKD2B` |
|
| Project Professional 2019 | `B4NPR-3FKK7-T2MBV-FRQ4W-PKD2B` |
|
||||||
| Project Standard 2019 | `C4F7P-NCP8C-6CQPT-MQHV9-JXD2M` |
|
| Project Standard 2019 | `C4F7P-NCP8C-6CQPT-MQHV9-JXD2M` |
|
||||||
| Visio Professional 2019 [C2R] | `N2CG9-YD3YK-936X4-3WR82-Q3X4H` |
|
| Visio Professional 2019 [C2R] | `N2CG9-YD3YK-936X4-3WR82-Q3X4H` |
|
||||||
| Visio Professional 2019 | `9BGNQ-K37YR-RQHF2-38RQ3-7VCBB` |
|
| Visio Professional 2019 | `9BGNQ-K37YR-RQHF2-38RQ3-7VCBB` |
|
||||||
| Visio Standard 2019 | `7TQNQ-K3YQQ-3PFH7-CCPPM-X4VQ2` |
|
| Visio Standard 2019 | `7TQNQ-K3YQQ-3PFH7-CCPPM-X4VQ2` |
|
||||||
| Access 2019 | `9N9PT-27V4Y-VJ2PD-YXFMF-YTFQT` |
|
| Access 2019 | `9N9PT-27V4Y-VJ2PD-YXFMF-YTFQT` |
|
||||||
| Excel 2019 | `TMJWT-YYNMB-3BKTF-644FC-RVXBD` |
|
| Excel 2019 | `TMJWT-YYNMB-3BKTF-644FC-RVXBD` |
|
||||||
| Outlook 2019 | `7HD7K-N4PVK-BHBCQ-YWQRW-XW4VK` |
|
| Outlook 2019 | `7HD7K-N4PVK-BHBCQ-YWQRW-XW4VK` |
|
||||||
| PowerPoint 2019 | `RRNCX-C64HY-W2MM7-MCH9G-TJHMQ` |
|
| PowerPoint 2019 | `RRNCX-C64HY-W2MM7-MCH9G-TJHMQ` |
|
||||||
| Publisher 2019 | `G2KWX-3NW6P-PY93R-JXK2T-C9Y9V` |
|
| Publisher 2019 | `G2KWX-3NW6P-PY93R-JXK2T-C9Y9V` |
|
||||||
| Skype for Business 2019 | `NCJ33-JHBBY-HTK98-MYCV8-HMKHJ` |
|
| Skype for Business 2019 | `NCJ33-JHBBY-HTK98-MYCV8-HMKHJ` |
|
||||||
| Word 2019 | `PBX3G-NWMT6-Q7XBW-PYJGG-WXD33` |
|
| Word 2019 | `PBX3G-NWMT6-Q7XBW-PYJGG-WXD33` |
|
||||||
|
|
||||||
### Office 2016
|
### Office 2016
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ------------------------------- | ------------------------------- |
|
||||||
| Professional Plus 2016 | `XQNVK-8JYDB-WJ9W3-YJ8YR-WFG99` |
|
| Professional Plus 2016 | `XQNVK-8JYDB-WJ9W3-YJ8YR-WFG99` |
|
||||||
| Standard 2016 | `JNRGM-WHDWX-FJJG3-K47QV-DRTFM` |
|
| Standard 2016 | `JNRGM-WHDWX-FJJG3-K47QV-DRTFM` |
|
||||||
| Project Professional 2016 | `YG9NW-3K39V-2T3HJ-93F3Q-G83KT` |
|
| Project Professional 2016 | `YG9NW-3K39V-2T3HJ-93F3Q-G83KT` |
|
||||||
| Project Professional 2016 [C2R] | `WGT24-HCNMF-FQ7XH-6M8K7-DRTW9` |
|
| Project Professional 2016 [C2R] | `WGT24-HCNMF-FQ7XH-6M8K7-DRTW9` |
|
||||||
| Project Standard 2016 | `GNFHQ-F6YQM-KQDGJ-327XX-KQBVC` |
|
| Project Standard 2016 | `GNFHQ-F6YQM-KQDGJ-327XX-KQBVC` |
|
||||||
| Project Standard 2016 [C2R] | `D8NRQ-JTYM3-7J2DX-646CT-6836M` |
|
| Project Standard 2016 [C2R] | `D8NRQ-JTYM3-7J2DX-646CT-6836M` |
|
||||||
| Visio Professional 2016 | `PD3PC-RHNGV-FXJ29-8JK7D-RJRJK` |
|
| Visio Professional 2016 | `PD3PC-RHNGV-FXJ29-8JK7D-RJRJK` |
|
||||||
| Visio Professional 2016 [C2R] | `69WXN-MBYV6-22PQG-3WGHK-RM6XC` |
|
| Visio Professional 2016 [C2R] | `69WXN-MBYV6-22PQG-3WGHK-RM6XC` |
|
||||||
| Visio Standard 2016 | `7WHWN-4T7MP-G96JF-G33KR-W8GF4` |
|
| Visio Standard 2016 | `7WHWN-4T7MP-G96JF-G33KR-W8GF4` |
|
||||||
| Visio Standard 2016 [C2R] | `NY48V-PPYYH-3F4PX-XJRKJ-W4423` |
|
| Visio Standard 2016 [C2R] | `NY48V-PPYYH-3F4PX-XJRKJ-W4423` |
|
||||||
| Access 2016 | `GNH9Y-D2J4T-FJHGG-QRVH7-QPFDW` |
|
| Access 2016 | `GNH9Y-D2J4T-FJHGG-QRVH7-QPFDW` |
|
||||||
| Excel 2016 | `9C2PK-NWTVB-JMPW8-BFT28-7FTBF` |
|
| Excel 2016 | `9C2PK-NWTVB-JMPW8-BFT28-7FTBF` |
|
||||||
| Mondo 2016 | `HFTND-W9MK4-8B7MJ-B6C4G-XQBR2` |
|
| Mondo 2016 | `HFTND-W9MK4-8B7MJ-B6C4G-XQBR2` |
|
||||||
| Mondo Retail 2016 | `DMTCJ-KNRKX-26982-JYCKT-P7KB6` |
|
| Mondo Retail 2016 | `DMTCJ-KNRKX-26982-JYCKT-P7KB6` |
|
||||||
| OneNote 2016 | `DR92N-9HTF2-97XKM-XW2WJ-XW3J6` |
|
| OneNote 2016 | `DR92N-9HTF2-97XKM-XW2WJ-XW3J6` |
|
||||||
| Outlook 2016 | `R69KK-NTPKF-7M3Q4-QYBHW-6MT9B` |
|
| Outlook 2016 | `R69KK-NTPKF-7M3Q4-QYBHW-6MT9B` |
|
||||||
| PowerPoint 2016 | `J7MQP-HNJ4Y-WJ7YM-PFYGF-BY6C6` |
|
| PowerPoint 2016 | `J7MQP-HNJ4Y-WJ7YM-PFYGF-BY6C6` |
|
||||||
| Publisher 2016 | `F47MM-N3XJP-TQXJ9-BP99D-8K837` |
|
| Publisher 2016 | `F47MM-N3XJP-TQXJ9-BP99D-8K837` |
|
||||||
| Skype for Business 2016 | `869NQ-FJ69K-466HW-QYCP2-DDBV6` |
|
| Skype for Business 2016 | `869NQ-FJ69K-466HW-QYCP2-DDBV6` |
|
||||||
| Word 2016 | `WXY84-JN2Q9-RBCCQ-3Q3J3-3PFJ6` |
|
| Word 2016 | `WXY84-JN2Q9-RBCCQ-3Q3J3-3PFJ6` |
|
||||||
|
|
||||||
### Office 2013
|
### Office 2013
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ----------------------------------------------------- | ------------------------------- |
|
||||||
| Professional Plus 2013 [Preview] | `PGD67-JN23K-JGVWV-KTHP4-GXR9G` |
|
| Professional Plus 2013 [Preview] | `PGD67-JN23K-JGVWV-KTHP4-GXR9G` |
|
||||||
| Professional Plus 2013 | `YC7DK-G2NP3-2QQC3-J6H88-GVGXT` |
|
| Professional Plus 2013 | `YC7DK-G2NP3-2QQC3-J6H88-GVGXT` |
|
||||||
| Standard 2013 | `KBKQT-2NMXY-JJWGP-M62JB-92CD4` |
|
| Standard 2013 | `KBKQT-2NMXY-JJWGP-M62JB-92CD4` |
|
||||||
| Project Professional 2013 [Preview] | `NFKVM-DVG7F-TYWYR-3RPHY-F872K` |
|
| Project Professional 2013 [Preview] | `NFKVM-DVG7F-TYWYR-3RPHY-F872K` |
|
||||||
| Project Professional 2013 | `FN8TT-7WMH6-2D4X9-M337T-2342K` |
|
| Project Professional 2013 | `FN8TT-7WMH6-2D4X9-M337T-2342K` |
|
||||||
| Project Standard 2013 [Preview] | `N89QF-GGB8J-BKD28-C4V28-W4XTK` |
|
| Project Standard 2013 [Preview] | `N89QF-GGB8J-BKD28-C4V28-W4XTK` |
|
||||||
| Project Standard 2013 | `6NTH3-CW976-3G3Y2-JK3TX-8QHTT` |
|
| Project Standard 2013 | `6NTH3-CW976-3G3Y2-JK3TX-8QHTT` |
|
||||||
| Visio Professional 2013 [Preview] | `B3C7Q-D6NH2-2VRFW-HHWDG-FVQB6` |
|
| Visio Professional 2013 [Preview] | `B3C7Q-D6NH2-2VRFW-HHWDG-FVQB6` |
|
||||||
| Visio Professional 2013 | `C2FG9-N6J68-H8BTJ-BW3QX-RM3B3` |
|
| Visio Professional 2013 | `C2FG9-N6J68-H8BTJ-BW3QX-RM3B3` |
|
||||||
| Visio Standard 2013 [Preview] | `9MKNF-J9XQ6-JV4XB-FJQPY-43F43` |
|
| Visio Standard 2013 [Preview] | `9MKNF-J9XQ6-JV4XB-FJQPY-43F43` |
|
||||||
| Visio Standard 2013 | `J484Y-4NKBF-W2HMG-DBMJC-PGWR7` |
|
| Visio Standard 2013 | `J484Y-4NKBF-W2HMG-DBMJC-PGWR7` |
|
||||||
| Access 2013 [Preview] | `DJBH8-RGN7Q-836KD-DMP3M-DM9MF` |
|
| Access 2013 [Preview] | `DJBH8-RGN7Q-836KD-DMP3M-DM9MF` |
|
||||||
| Access 2013 | `NG2JY-H4JBT-HQXYP-78QH9-4JM2D` |
|
| Access 2013 | `NG2JY-H4JBT-HQXYP-78QH9-4JM2D` |
|
||||||
| Excel 2013 [Preview] | `Q3BNP-3WXDT-GG8HF-24KMW-HMDBK` |
|
| Excel 2013 [Preview] | `Q3BNP-3WXDT-GG8HF-24KMW-HMDBK` |
|
||||||
| Excel 2013 | `VGPNG-Y7HQW-9RHP7-TKPV3-BG7GB` |
|
| Excel 2013 | `VGPNG-Y7HQW-9RHP7-TKPV3-BG7GB` |
|
||||||
| OneNote 2013 [Preview] | `VYNYX-8GPBC-7FQMD-D6B7B-7MDFD` |
|
| OneNote 2013 [Preview] | `VYNYX-8GPBC-7FQMD-D6B7B-7MDFD` |
|
||||||
| OneNote 2013 | `TGN6P-8MMBC-37P2F-XHXXK-P34VW` |
|
| OneNote 2013 | `TGN6P-8MMBC-37P2F-XHXXK-P34VW` |
|
||||||
| Outlook 2013 [Preview] | `X2KNB-FRRG2-WXDPH-739DM-DM9RH` |
|
| Outlook 2013 [Preview] | `X2KNB-FRRG2-WXDPH-739DM-DM9RH` |
|
||||||
| Outlook 2013 | `QPN8Q-BJBTJ-334K3-93TGY-2PMBT` |
|
| Outlook 2013 | `QPN8Q-BJBTJ-334K3-93TGY-2PMBT` |
|
||||||
| PowerPoint 2013 [Preview] | `B8CT8-BTNFQ-XQXBK-BFWV8-HMDFQ` |
|
| PowerPoint 2013 [Preview] | `B8CT8-BTNFQ-XQXBK-BFWV8-HMDFQ` |
|
||||||
| PowerPoint 2013 | `4NT99-8RJFH-Q2VDH-KYG2C-4RD4F` |
|
| PowerPoint 2013 | `4NT99-8RJFH-Q2VDH-KYG2C-4RD4F` |
|
||||||
| Publisher 2013 [Preview] | `NB67P-J8XP4-XDK9B-V73VH-M4CKR` |
|
| Publisher 2013 [Preview] | `NB67P-J8XP4-XDK9B-V73VH-M4CKR` |
|
||||||
| Publisher 2013 | `PN2WF-29XG2-T9HJ7-JQPJR-FCXK4` |
|
| Publisher 2013 | `PN2WF-29XG2-T9HJ7-JQPJR-FCXK4` |
|
||||||
| InfoPath 2013 (Preview) | `7KPJJ-N8TT7-CK3KR-QTV98-YPVXQ` |
|
| InfoPath 2013 (Preview) | `7KPJJ-N8TT7-CK3KR-QTV98-YPVXQ` |
|
||||||
| InfoPath 2013 | `DKT8B-N7VXH-D963P-Q4PHY-F8894` |
|
| InfoPath 2013 | `DKT8B-N7VXH-D963P-Q4PHY-F8894` |
|
||||||
| Lync 2013 [Preview] | `XNVD3-RYC7T-7R6BT-WX6CF-8BYH7` |
|
| Lync 2013 [Preview] | `XNVD3-RYC7T-7R6BT-WX6CF-8BYH7` |
|
||||||
| Lync 2013 | `2MG3G-3BNTT-3MFW9-KDQW3-TCK7R` |
|
| Lync 2013 | `2MG3G-3BNTT-3MFW9-KDQW3-TCK7R` |
|
||||||
| Word 2013 [Preview] | `JBGD4-3JNG7-JWWGV-CR6TP-DC62Q` |
|
| Word 2013 [Preview] | `JBGD4-3JNG7-JWWGV-CR6TP-DC62Q` |
|
||||||
| Word 2013 | `6Q7VD-NX8JD-WJ2VH-88V73-4GBJ7` |
|
| Word 2013 | `6Q7VD-NX8JD-WJ2VH-88V73-4GBJ7` |
|
||||||
| Mondo 2013 [Preview] | `GCGCN-6FJRM-TR9Q3-BGMWJ-78KQV` |
|
| Mondo 2013 [Preview] | `GCGCN-6FJRM-TR9Q3-BGMWJ-78KQV` |
|
||||||
| Mondo 2013 | `42QTK-RN8M7-J3C4G-BBGYM-88CYV` |
|
| Mondo 2013 | `42QTK-RN8M7-J3C4G-BBGYM-88CYV` |
|
||||||
| Mondo 2013 Retail | `?????-?????-?????-?????-?????` |
|
| Mondo 2013 Retail | `?????-?????-?????-?????-?????` |
|
||||||
| SharePoint Workspace (Groove) 2013 [Preview] | `WVCGG-NK4FG-7XKXM-BD4WF-3C624` |
|
| SharePoint Workspace (Groove) 2013 [Preview] | `WVCGG-NK4FG-7XKXM-BD4WF-3C624` |
|
||||||
| SharePoint Workspace (Groove) 2013 | `H7R7V-WPNXQ-WCYYC-76BGV-VT7GH` |
|
| SharePoint Workspace (Groove) 2013 | `H7R7V-WPNXQ-WCYYC-76BGV-VT7GH` |
|
||||||
| SharePoint Designer (Frontpage) 2013 Retail [Preview] | `?????-?????-?????-?????-?????` |
|
| SharePoint Designer (Frontpage) 2013 Retail [Preview] | `?????-?????-?????-?????-?????` |
|
||||||
| SharePoint Designer (Frontpage) 2013 Retail | `GYJRG-NMYMF-VGBM4-T3QD4-842DW` |
|
| SharePoint Designer (Frontpage) 2013 Retail | `GYJRG-NMYMF-VGBM4-T3QD4-842DW` |
|
||||||
|
|
||||||
### Office 2010
|
### Office 2010
|
||||||
|
|
||||||
| Product | GVLK |
|
| Product | GVLK |
|
||||||
| --- | --- |
|
| ------------------------------------------- | ------------------------------- |
|
||||||
| Professional Plus 2010 | `VYBBJ-TRJPB-QFQRF-QFT4D-H3GVB` |
|
| Professional Plus 2010 | `VYBBJ-TRJPB-QFQRF-QFT4D-H3GVB` |
|
||||||
| Standard 2010 | `V7QKV-4XVVR-XYV4D-F7DFM-8R6BM` |
|
| Standard 2010 | `V7QKV-4XVVR-XYV4D-F7DFM-8R6BM` |
|
||||||
| Project Professional 2010 | `YGX6F-PGV49-PGW3J-9BTGG-VHKC6` |
|
| Project Professional 2010 | `YGX6F-PGV49-PGW3J-9BTGG-VHKC6` |
|
||||||
| Project Standard 2010 | `4HP3K-88W3F-W2K3D-6677X-F9PGB` |
|
| Project Standard 2010 | `4HP3K-88W3F-W2K3D-6677X-F9PGB` |
|
||||||
| Visio Professional 2010 | `7MCW8-VRQVK-G677T-PDJCM-Q8TCP` |
|
| Visio Professional 2010 | `7MCW8-VRQVK-G677T-PDJCM-Q8TCP` |
|
||||||
| Visio Standard 2010 | `767HD-QGMWX-8QTDB-9G3R2-KHFGJ` |
|
| Visio Standard 2010 | `767HD-QGMWX-8QTDB-9G3R2-KHFGJ` |
|
||||||
| Visio Premium 2010 | `D9DWC-HPYVV-JGF4P-BTWQB-WX8BJ` |
|
| Visio Premium 2010 | `D9DWC-HPYVV-JGF4P-BTWQB-WX8BJ` |
|
||||||
| Access 2010 | `V7Y44-9T38C-R2VJK-666HK-T7DDX` |
|
| Access 2010 | `V7Y44-9T38C-R2VJK-666HK-T7DDX` |
|
||||||
| Excel 2010 | `H62QG-HXVKF-PP4HP-66KMR-CW9BM` |
|
| Excel 2010 | `H62QG-HXVKF-PP4HP-66KMR-CW9BM` |
|
||||||
| OneNote 2010 | `Q4Y4M-RHWJM-PY37F-MTKWH-D3XHX` |
|
| OneNote 2010 | `Q4Y4M-RHWJM-PY37F-MTKWH-D3XHX` |
|
||||||
| Outlook 2010 | `7YDC2-CWM8M-RRTJC-8MDVC-X3DWQ` |
|
| Outlook 2010 | `7YDC2-CWM8M-RRTJC-8MDVC-X3DWQ` |
|
||||||
| PowerPoint 2010 | `RC8FX-88JRY-3PF7C-X8P67-P4VTT` |
|
| PowerPoint 2010 | `RC8FX-88JRY-3PF7C-X8P67-P4VTT` |
|
||||||
| Publisher 2010 | `BFK7F-9MYHM-V68C7-DRQ66-83YTP` |
|
| Publisher 2010 | `BFK7F-9MYHM-V68C7-DRQ66-83YTP` |
|
||||||
| InfoPath 2010 | `K96W8-67RPQ-62T9Y-J8FQJ-BT37T` |
|
| InfoPath 2010 | `K96W8-67RPQ-62T9Y-J8FQJ-BT37T` |
|
||||||
| SharePoint Workspace (Groove) 2010 | `QYYW6-QP4CB-MBV6G-HYMCJ-4T3J4` |
|
| SharePoint Workspace (Groove) 2010 | `QYYW6-QP4CB-MBV6G-HYMCJ-4T3J4` |
|
||||||
| Word 2010 | `HVHB3-C6FV7-KQX9W-YQG79-CRY7T` |
|
| Word 2010 | `HVHB3-C6FV7-KQX9W-YQG79-CRY7T` |
|
||||||
| Small Business Basics 2010 | `D6QFG-VBYP2-XQHM7-J97RH-VVRCK` |
|
| Small Business Basics 2010 | `D6QFG-VBYP2-XQHM7-J97RH-VVRCK` |
|
||||||
| Starter 2010 Retail | `VXHHB-W7HBD-7M342-RJ7P8-CHBD6` |
|
| Starter 2010 Retail | `VXHHB-W7HBD-7M342-RJ7P8-CHBD6` |
|
||||||
| SharePoint Designer (Frontpage) 2010 Retail | `H48K6-FB4Y6-P83GH-9J7XG-HDKKX` |
|
| SharePoint Designer (Frontpage) 2010 Retail | `H48K6-FB4Y6-P83GH-9J7XG-HDKKX` |
|
||||||
| Office Mondo 1 2010 | `YBJTT-JG6MD-V9Q7P-DBKXJ-38W9R` |
|
| Office Mondo 1 2010 | `YBJTT-JG6MD-V9Q7P-DBKXJ-38W9R` |
|
||||||
| Office Mondo 2 2010 | `7TC2V-WXF6P-TD7RT-BQRXR-B8K32` |
|
| Office Mondo 2 2010 | `7TC2V-WXF6P-TD7RT-BQRXR-B8K32` |
|
||||||
|
|
|
@ -6,6 +6,7 @@ If you not follow this, do not expect that we can or want to help you!
|
||||||
|
|
||||||
* Are you activating a legit Windows copy checked with `sha256`, `md5` or is it maybe a warez torrent version ?
|
* Are you activating a legit Windows copy checked with `sha256`, `md5` or is it maybe a warez torrent version ?
|
||||||
* Did you tried a clean installation (format all) ? You skipped entering any key during installation, turning off internet connection, first activating and then updating Windows (and eventually later upgrading) ?
|
* Did you tried a clean installation (format all) ? You skipped entering any key during installation, turning off internet connection, first activating and then updating Windows (and eventually later upgrading) ?
|
||||||
|
* Are you activating Windows or Office on a different machine (physical or virtual) where py-kms runs?
|
||||||
* Have you installed all latest packages ? Especially before upgrading ? Are you upgrading using the "Update Assistant"/"Media Creation" tool to switch from Windows 7 / 8 / 8.1 to 10 (for me has always worked) ?
|
* Have you installed all latest packages ? Especially before upgrading ? Are you upgrading using the "Update Assistant"/"Media Creation" tool to switch from Windows 7 / 8 / 8.1 to 10 (for me has always worked) ?
|
||||||
* If isn't a clean install, so far as you have kept activated your Windows copy ? Have you used some other activator (maybe not trusted) that injects or changes .dll files and therefore may have corrupted something ?
|
* If isn't a clean install, so far as you have kept activated your Windows copy ? Have you used some other activator (maybe not trusted) that injects or changes .dll files and therefore may have corrupted something ?
|
||||||
* Have you forgot to reactivate at least once before 180 (45 or 30, depending on your version) days ?
|
* Have you forgot to reactivate at least once before 180 (45 or 30, depending on your version) days ?
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
## Start Parameters
|
## Start Parameters
|
||||||
***
|
|
||||||
|
|
||||||
|
(pykms-server-py)=
|
||||||
### pykms_Server.py
|
### pykms_Server.py
|
||||||
Follows a list of usable parameters:
|
Follows a list of usable parameters:
|
||||||
|
|
||||||
ip <IPADDRESS>
|
ip <IPADDRESS>
|
||||||
> Instructs py-kms to listen on _IPADDRESS_ (can be an hostname too). If this option is not specified, _IPADDRESS_ 0.0.0.0 is used.
|
> Instructs py-kms to listen on _IPADDRESS_ (can be an hostname too). If this option is not specified, _IPADDRESS_ `::` is used.
|
||||||
|
|
||||||
port <PORT>
|
port <PORT>
|
||||||
> Define TCP _PORT_ the KMS service is listening on. Default is 1688.
|
> Define TCP _PORT_ the KMS service is listening on. Default is 1688.
|
||||||
|
@ -17,7 +17,7 @@ Follows a list of usable parameters:
|
||||||
Use _EPID_ as Windows _EPID_. If no _EPID_ is specified, a random one will be generated.
|
Use _EPID_ as Windows _EPID_. If no _EPID_ is specified, a random one will be generated.
|
||||||
|
|
||||||
-l or --lcid <LCID>
|
-l or --lcid <LCID>
|
||||||
> Do not randomize the locale ID part of the _EPID_ and use _LCID_ instead.
|
> Specify the _LCID_ part of the _EPID_. If an _EPID_ is manually specified, this setting is ignored. Default is 1033 (English - US).
|
||||||
The Language Code Identifier (_LCID_) describes localizable information in Windows.
|
The Language Code Identifier (_LCID_) describes localizable information in Windows.
|
||||||
This structure is used to identify specific languages for the purpose of customizing
|
This structure is used to identify specific languages for the purpose of customizing
|
||||||
software for particular languages and cultures. For example, it can specify the way dates,
|
software for particular languages and cultures. For example, it can specify the way dates,
|
||||||
|
@ -26,10 +26,9 @@ The _LCID_ must be specified as a decimal number (example: 1049 for "Russian - R
|
||||||
By default py-kms generates a valid locale ID but this may lead to a value which is unlikely to occur in your country.
|
By default py-kms generates a valid locale ID but this may lead to a value which is unlikely to occur in your country.
|
||||||
You may want to select the locale ID of your country instead.
|
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_.
|
See [here](https://msdn.microsoft.com/en-us/library/cc233982.aspx) for a list of valid _LCIDs_.
|
||||||
If an _EPID_ is manually specified, this setting is ignored. Default is a fixed _LCID_ of 1033 (English - US).
|
|
||||||
|
|
||||||
-w or --hwid <HWID>
|
-w or --hwid <HWID>
|
||||||
> Use specified _HWID_ for all products.
|
> 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
|
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
|
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
|
HWID number is generated when the operating system is first installed. The _HWID_ identifies the hardware components that the system
|
||||||
|
@ -39,8 +38,7 @@ to make sure that the operating system is still running on the same device.
|
||||||
If the two _HWID_ numbers differ too much then the operating system will shut down until Microsoft reactivates the product.
|
If the two _HWID_ numbers differ too much then the operating system will shut down until Microsoft reactivates the product.
|
||||||
The theory behind _HWID_ is to ensure that the operating system is not being used on any device other than the one
|
The theory behind _HWID_ is to ensure that the operating system is not being used on any device other than the one
|
||||||
for which it was purchased and registered.
|
for which it was purchased and registered.
|
||||||
HWID must be an 16-character string of hex characters that are interpreted as a series of 8 bytes (big endian).
|
HWID must be an 16-character string of hex characters that are interpreted as a series of 8 bytes (big endian).
|
||||||
Default is _364F463A8863D35F_. To auto generate the _HWID_, type `-w RANDOM`.
|
|
||||||
|
|
||||||
-c or --client-count <CLIENTCOUNT>
|
-c or --client-count <CLIENTCOUNT>
|
||||||
> Use this flag to specify the current _CLIENTCOUNT_. Default is None. Remember that a number >=25 is
|
> Use this flag to specify the current _CLIENTCOUNT_. Default is None. Remember that a number >=25 is
|
||||||
|
@ -55,7 +53,6 @@ e.g. because it could not reach the server. The default is 120 minutes (2 hours)
|
||||||
|
|
||||||
-s or --sqlite [<SQLFILE>]
|
-s or --sqlite [<SQLFILE>]
|
||||||
> Use this option to store request information from unique clients in an SQLite database. Deactivated by default.
|
> Use this option to store request information from unique clients in an SQLite database. Deactivated by default.
|
||||||
If enabled the default database file is _pykms_database.db_. You can also provide a specific location.
|
|
||||||
|
|
||||||
-t0 or --timeout-idle <TIMEOUTIDLE>
|
-t0 or --timeout-idle <TIMEOUTIDLE>
|
||||||
> Maximum inactivity time (in seconds) after which the connection with the client is closed.
|
> Maximum inactivity time (in seconds) after which the connection with the client is closed.
|
||||||
|
@ -77,7 +74,7 @@ user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py -V INFO
|
||||||
```
|
```
|
||||||
creates _pykms_logserver.log_ with these initial messages:
|
creates _pykms_logserver.log_ with these initial messages:
|
||||||
```
|
```
|
||||||
Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 0.0.0.0 on port 1688.
|
Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at :: on port 1688.
|
||||||
Mon, 12 Jun 2017 22:09:00 INFO HWID: 364F463A8863D35F
|
Mon, 12 Jun 2017 22:09:00 INFO HWID: 364F463A8863D35F
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -85,28 +82,28 @@ Mon, 12 Jun 2017 22:09:00 INFO HWID: 364F463A8863D35F
|
||||||
> Creates a _LOGFILE.log_ logging file. The default is named _pykms_logserver.log_.
|
> Creates a _LOGFILE.log_ logging file. The default is named _pykms_logserver.log_.
|
||||||
example:
|
example:
|
||||||
```
|
```
|
||||||
user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 8080 -F ~/path/to/folder/py-kms/newlogfile.log -V INFO -w RANDOM
|
user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 1688 -F ~/path/to/folder/py-kms/newlogfile.log -V INFO -w RANDOM
|
||||||
```
|
```
|
||||||
creates _newlogfile.log_ with these initial messages:
|
creates _newlogfile.log_ with these initial messages:
|
||||||
```
|
```
|
||||||
Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 192.168.1.102 on port 8080.
|
Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 192.168.1.102 on port 1688.
|
||||||
Mon, 12 Jun 2017 22:09:00 INFO HWID: 58C4F4E53AE14224
|
Mon, 12 Jun 2017 22:09:00 INFO HWID: 58C4F4E53AE14224
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also enable other suboptions of `-F` doing what is reported in the following table:
|
You can also enable other suboptions of `-F` doing what is reported in the following table:
|
||||||
|
|
||||||
| command | pretty msg | logging msg | logfile |
|
| command | pretty msg | logging msg | logfile |
|
||||||
| --- | --- | --- | --- |
|
| ------------------------- | ---------- | ----------- | ------- |
|
||||||
| `-F <logfile>` | ON | OFF | ON |
|
| `-F <logfile>` | ON | OFF | ON |
|
||||||
| `-F STDOUT` | OFF | ON | OFF |
|
| `-F STDOUT` | OFF | ON | OFF |
|
||||||
| `-F FILESTDOUT <logfile>` | OFF | ON | ON |
|
| `-F FILESTDOUT <logfile>` | OFF | ON | ON |
|
||||||
| `-F STDOUTOFF <logfile>` | OFF | OFF | ON |
|
| `-F STDOUTOFF <logfile>` | OFF | OFF | ON |
|
||||||
| `-F FILEOFF` | ON | OFF | OFF |
|
| `-F FILEOFF` | ON | OFF | OFF |
|
||||||
|
|
||||||
-S or --logsize <MAXSIZE>
|
-S or --logsize <MAXSIZE>
|
||||||
> Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.
|
> Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.
|
||||||
|
|
||||||
##### subparser `connect`
|
#### subparser `connect`
|
||||||
|
|
||||||
-n or --listen <'IP,PORT'>
|
-n or --listen <'IP,PORT'>
|
||||||
> Use this option to add multiple listening ip address - port couples. Note the format with the comma between the ip address and the port number. You can use this option more than once.
|
> Use this option to add multiple listening ip address - port couples. Note the format with the comma between the ip address and the port number. You can use this option more than once.
|
||||||
|
@ -119,22 +116,22 @@ If placed just after `connect` refers to the main address and all additive coupl
|
||||||
> Use this option not to allow binding / listening to the same ip address - port couple specified with `-n`.
|
> Use this option not to allow binding / listening to the same ip address - port couple specified with `-n`.
|
||||||
If placed just after `connect` refers to the main address and all additive couples without `-u` option. Reusing port is activated by default (except when running inside the Windows Sandbox and the current user is `WDAGUtilityAccount`).
|
If placed just after `connect` refers to the main address and all additive couples without `-u` option. Reusing port is activated by default (except when running inside the Windows Sandbox and the current user is `WDAGUtilityAccount`).
|
||||||
|
|
||||||
-d or --dual
|
-d or --dual <bool>
|
||||||
> Use this option to allow listening to an IPv6 address also accepting connections via IPv4.
|
> Allows listening to an IPv6 address while also accepting connections via IPv4. If used, it refers to all addresses (main and additional). Activated by default. Pass in "false" or "true" to disable or enable.
|
||||||
If used it refers to all addresses (main and additional). Deactivated by default.
|
|
||||||
|
|
||||||
examples (with fictitious addresses and ports):
|
Examples (with fictitious addresses and ports):
|
||||||
|
|
||||||
| command | address (main) | backlog (main) | reuse port (main) | address (listen) | backlog (listen) | reuse port (listen) | dualstack (main / listen) |
|
| command | address (main) | backlog (main) | reuse port (main) | address (listen) | backlog (listen) | reuse port (listen) | dualstack (main / listen) |
|
||||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| ---------------------------------------------------------------------------------------------------- | -------------- | -------------- | ----------------- | ------------------------------------------------ | ---------------- | ------------------- | ------------------------- |
|
||||||
| `python3 pykms_Server.py connect -b 12` | ('0.0.0.0', 1688) | 12 | True | [] | [] | [] | False |
|
| `python3 pykms_Server.py connect -b 12` | ('::', 1688) | 12 | True | [] | [] | [] | True |
|
||||||
| `python3 pykms_Server.py :: connect -b 12 -u -d` | ('::', 1688) | 12 | False | [] | [] | [] | True |
|
| `python3 pykms_Server.py :: connect -b 12 -u -d yes` | ('::', 1688) | 12 | False | [] | [] | [] | True |
|
||||||
| `python3 pykms_Server.py connect -n 1.1.1.1,1699 -b 10` | ('0.0.0.0', 1688) | 5 | True | [('1.1.1.1', 1699)] | [10] | [True] | False |
|
| `python3 pykms_Server.py :: connect -b 12 -u -d false` | ('::', 1688) | 12 | False | [] | [] | [] | False |
|
||||||
| `python3 pykms_Server.py :: 1655 connect -n 2001:db8:0:200::7,1699 -d -b 10 -n 2.2.2.2,1677 -u` | ('::', 1655) | 5 | True | [('2001:db8:0:200::7', 1699), ('2.2.2.2', 1677)] | [10, 5] | [True, False] | True |
|
| `python3 pykms_Server.py connect -n 1.1.1.1,1699 -b 10` | ('::', 1688) | 5 | True | [('1.1.1.1', 1699)] | [10] | [True] | True |
|
||||||
| `python3 pykms_Server.py connect -b 12 -u -n 1.1.1.1,1699 -b 10 -n 2.2.2.2,1677 -b 15` | ('0.0.0.0', 1688) | 12 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [10, 15] | [False, False] | False |
|
| `python3 pykms_Server.py :: 1655 connect -n 2001:db8:0:200::7,1699 -d true -b 10 -n 2.2.2.2,1677 -u` | ('::', 1655) | 5 | True | [('2001:db8:0:200::7', 1699), ('2.2.2.2', 1677)] | [10, 5] | [True, False] | True |
|
||||||
| `python3 pykms_Server.py connect -b 12 -n 1.1.1.1,1699 -u -n 2.2.2.2,1677` | ('0.0.0.0', 1688) | 12 | True | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [12, 12] | [False, True] | False |
|
| `python3 pykms_Server.py connect -b 12 -u -n 1.1.1.1,1699 -b 10 -n 2.2.2.2,1677 -b 15` | ('::', 1688) | 12 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [10, 15] | [False, False] | True |
|
||||||
| `python3 pykms_Server.py connect -d -u -b 8 -n 1.1.1.1,1699 -n 2.2.2.2,1677 -b 12` | ('0.0.0.0', 1688) | 8 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [8, 12] | [False, False] | True |
|
| `python3 pykms_Server.py connect -b 12 -n 1.1.1.1,1699 -u -n 2.2.2.2,1677` | ('::', 1688) | 12 | True | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [12, 12] | [False, True] | True |
|
||||||
| `python3 pykms_Server.py connect -b 11 -u -n ::,1699 -n 2.2.2.2,1677` | ('0.0.0.0', 1688) | 11 | False | [('::', 1699), ('2.2.2.2', 1677)] | [11, 11] | [False, False] | False |
|
| `python3 pykms_Server.py connect -d 0 -u -b 8 -n 1.1.1.1,1699 -n 2.2.2.2,1677 -b 12` | ('::', 1688) | 8 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [8, 12] | [False, False] | False |
|
||||||
|
| `python3 pykms_Server.py connect -b 11 -u -n ::,1699 -n 2.2.2.2,1677` | ('::', 1688) | 11 | False | [('::', 1699), ('2.2.2.2', 1677)] | [11, 11] | [False, False] | True |
|
||||||
|
|
||||||
### pykms_Client.py
|
### pykms_Client.py
|
||||||
If _py-kms_ server doesn't works correctly, you can test it with the KMS client `pykms_Client.py`, running on the same machine where you started `pykms_Server.py`.
|
If _py-kms_ server doesn't works correctly, you can test it with the KMS client `pykms_Client.py`, running on the same machine where you started `pykms_Server.py`.
|
||||||
|
@ -145,6 +142,12 @@ user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py -V DEBUG
|
||||||
user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG
|
user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you wish to get KMS server from DNS server: (ie perform a DNS resolution on _vlmcs._tcp.domain.tld, if ever there are several answers, only the first one is selected.). Althought that mode is supposed to be specific to devices connect to an Active Directory domain, setting a fully qualified name and a workgroup may help to use that automatic KMS discovery feature.
|
||||||
|
```
|
||||||
|
user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG -F STDOUT -D contoso.com
|
||||||
|
user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG -F STDOUT -D contoso.com
|
||||||
|
```
|
||||||
|
|
||||||
Or if you want better specify:
|
Or if you want better specify:
|
||||||
```
|
```
|
||||||
user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py <YOUR_IPADDRESS> 1688 -V DEBUG
|
user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py <YOUR_IPADDRESS> 1688 -V DEBUG
|
||||||
|
@ -194,12 +197,13 @@ You can enable same _pykms_Server.py_ suboptions of `-F`.
|
||||||
-S or --logsize <MAXSIZE>
|
-S or --logsize <MAXSIZE>
|
||||||
> Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.
|
> Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.
|
||||||
|
|
||||||
|
(docker-environment)=
|
||||||
## Docker Environment
|
## Docker Environment
|
||||||
This are the currently used `ENV` statements from the Dockerfile(s). For further references what exactly the parameters mean, please see the start parameters for the [server](Usage.html#pykms-server-py).
|
This are the currently used `ENV` statements from the Dockerfile(s). For further references what exactly the parameters mean, please see the start parameters for the [server](#pykms-server-py).
|
||||||
```
|
```
|
||||||
# IP-address
|
# IP-address
|
||||||
# The IP address to listen on. The default is "0.0.0.0" (all interfaces).
|
# The IP address to listen on. The default is "::" (all interfaces).
|
||||||
ENV IP 0.0.0.0
|
ENV IP ::
|
||||||
|
|
||||||
# TCP-port
|
# TCP-port
|
||||||
# The network port to listen on. The default is "1688".
|
# The network port to listen on. The default is "1688".
|
||||||
|
@ -226,15 +230,11 @@ ENV ACTIVATION_INTERVAL 120
|
||||||
# Use this flag to specify the renewal interval (in minutes). Default is 10080 minutes (7 days).
|
# Use this flag to specify the renewal interval (in minutes). Default is 10080 minutes (7 days).
|
||||||
ENV RENEWAL_INTERVAL 10080
|
ENV RENEWAL_INTERVAL 10080
|
||||||
|
|
||||||
# Use SQLITE
|
|
||||||
# Use this flag to store request information from unique clients in an SQLite database.
|
|
||||||
ENV SQLITE false
|
|
||||||
|
|
||||||
# hwid
|
# hwid
|
||||||
# Use this flag to specify a HWID.
|
# Use this flag to specify a HWID.
|
||||||
# The HWID must be an 16-character string of hex characters.
|
# The HWID must be an 16-character string of hex characters.
|
||||||
# The default is "364F463A8863D35F" or type "RANDOM" to auto generate the HWID.
|
# The default is "RANDOM" to auto-generate the HWID or type a specific value.
|
||||||
ENV HWID 364F463A8863D35F
|
ENV HWID RANDOM
|
||||||
|
|
||||||
# log level ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG")
|
# log level ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG")
|
||||||
# Use this flag to set a Loglevel. The default is "ERROR".
|
# Use this flag to set a Loglevel. The default is "ERROR".
|
||||||
|
@ -253,7 +253,6 @@ ENV LOGSIZE ""
|
||||||
The product asks for a key during installation, so it needs you to enter the GVLK. Then the user can set connection parameters, while KMS server must already be running on server machine. Finally with specific commands, activation occurs automatically and can be extended later every time for another 180 (or 30 or 45) days.
|
The product asks for a key during installation, so it needs you to enter the GVLK. Then the user can set connection parameters, while KMS server must already be running on server machine. Finally with specific commands, activation occurs automatically and can be extended later every time for another 180 (or 30 or 45) days.
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
***
|
|
||||||
The `//nologo` option of `cscript` was used only to hide the startup logo.
|
The `//nologo` option of `cscript` was used only to hide the startup logo.
|
||||||
|
|
||||||

|

|
||||||
|
@ -271,7 +270,6 @@ The `//nologo` option of `cscript` was used only to hide the startup logo.
|
||||||
6. View license informations (optional).
|
6. View license informations (optional).
|
||||||
|
|
||||||
### Office
|
### Office
|
||||||
***
|
|
||||||
Note that you’ll have to install a volume license (VL) version of Office. Office versions downloaded from MSDN and / or Technet are non-VL.
|
Note that you’ll have to install a volume license (VL) version of Office. Office versions downloaded from MSDN and / or Technet are non-VL.
|
||||||
|
|
||||||

|

|
||||||
|
|
124
docs/conf.py
124
docs/conf.py
|
@ -18,29 +18,23 @@ import os
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
#sys.path.insert(0, os.path.abspath('.'))
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
#needs_sphinx = '1.0'
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
# ones.
|
# ones.
|
||||||
extensions = ['recommonmark', 'sphinx_markdown_tables']
|
extensions = ['myst_parser', 'sphinx_markdown_tables', 'sphinx_rtd_theme']
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
# templates_path = ['_templates']
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = {
|
|
||||||
'.rst': 'restructuredtext',
|
|
||||||
'.md': 'markdown',
|
|
||||||
}
|
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
#source_encoding = 'utf-8-sig'
|
# source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
@ -54,19 +48,19 @@ copyright = u'2020, SystemRage'
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '1.0'
|
# version = '1.0'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '1.0'
|
# release = '1.0'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
#language = None
|
# language = None
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
# non-false value, then it is used:
|
# non-false value, then it is used:
|
||||||
#today = ''
|
# today = ''
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
#today_fmt = '%B %d, %Y'
|
# today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
|
@ -74,109 +68,109 @@ exclude_patterns = ['_build']
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
# documents.
|
# documents.
|
||||||
#default_role = None
|
# default_role = None
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
#add_function_parentheses = True
|
# add_function_parentheses = True
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
# If true, the current module name will be prepended to all description
|
||||||
# unit titles (such as .. function::).
|
# unit titles (such as .. function::).
|
||||||
#add_module_names = True
|
# add_module_names = True
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
# output. They are ignored by default.
|
# output. They are ignored by default.
|
||||||
#show_authors = False
|
# show_authors = False
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
#modindex_common_prefix = []
|
# modindex_common_prefix = []
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||||
#keep_warnings = False
|
# keep_warnings = False
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'default'
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#html_theme_options = {}
|
# html_theme_options = {}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
#html_theme_path = []
|
# html_theme_path = []
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
# "<project> v<release> documentation".
|
# "<project> v<release> documentation".
|
||||||
#html_title = None
|
# html_title = None
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
#html_short_title = None
|
# html_short_title = None
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# of the sidebar.
|
||||||
#html_logo = None
|
# html_logo = None
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
# pixels large.
|
# pixels large.
|
||||||
#html_favicon = None
|
# html_favicon = None
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
# html_static_path = ['_static']
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
# Add any extra paths that contain custom files (such as robots.txt or
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
# .htaccess) here, relative to this directory. These files are copied
|
||||||
# directly to the root of the documentation.
|
# directly to the root of the documentation.
|
||||||
#html_extra_path = []
|
# html_extra_path = []
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
# html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
# typographically correct entities.
|
# typographically correct entities.
|
||||||
#html_use_smartypants = True
|
# html_use_smartypants = True
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
# Custom sidebar templates, maps document names to template names.
|
||||||
#html_sidebars = {}
|
# html_sidebars = {}
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
# template names.
|
# template names.
|
||||||
#html_additional_pages = {}
|
# html_additional_pages = {}
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#html_domain_indices = True
|
# html_domain_indices = True
|
||||||
|
|
||||||
# If false, no index is generated.
|
# If false, no index is generated.
|
||||||
#html_use_index = True
|
# html_use_index = True
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
# If true, the index is split into individual pages for each letter.
|
||||||
#html_split_index = False
|
# html_split_index = False
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
# If true, links to the reST sources are added to the pages.
|
||||||
#html_show_sourcelink = True
|
# html_show_sourcelink = True
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
#html_show_sphinx = True
|
# html_show_sphinx = True
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
#html_show_copyright = True
|
# html_show_copyright = True
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
# base URL from which the finished HTML is served.
|
# base URL from which the finished HTML is served.
|
||||||
#html_use_opensearch = ''
|
# html_use_opensearch = ''
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
#html_file_suffix = None
|
# html_file_suffix = None
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'py-kms'
|
htmlhelp_basename = 'py-kms'
|
||||||
|
@ -185,43 +179,43 @@ htmlhelp_basename = 'py-kms'
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
|
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
#'papersize': 'letterpaper',
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
#'pointsize': '10pt',
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
#'preamble': '',
|
# 'preamble': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title,
|
# (source start file, target name, title,
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'py-kms.tex', u'py-kms Documentation',
|
('index', 'py-kms.tex', u'py-kms Documentation',
|
||||||
u'SystemRage', 'manual'),
|
u'SystemRage', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
# the title page.
|
# the title page.
|
||||||
#latex_logo = None
|
# latex_logo = None
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
# not chapters.
|
# not chapters.
|
||||||
#latex_use_parts = False
|
# latex_use_parts = False
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
# If true, show page references after internal links.
|
||||||
#latex_show_pagerefs = False
|
# latex_show_pagerefs = False
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
#latex_show_urls = False
|
# latex_show_urls = False
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
#latex_appendices = []
|
# latex_appendices = []
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#latex_domain_indices = True
|
# latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
# -- Options for manual page output ---------------------------------------
|
||||||
|
@ -234,7 +228,7 @@ man_pages = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
#man_show_urls = False
|
# man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
# -- Options for Texinfo output -------------------------------------------
|
||||||
|
@ -243,19 +237,19 @@ man_pages = [
|
||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
('index', 'py-kms', u'py-kms Documentation',
|
('index', 'py-kms', u'py-kms Documentation',
|
||||||
u'SystemRage', 'py-kms', 'KMS Server Emulator written in Python',
|
u'SystemRage', 'py-kms', 'KMS Server Emulator written in Python',
|
||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
#texinfo_appendices = []
|
# texinfo_appendices = []
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#texinfo_domain_indices = True
|
# texinfo_domain_indices = True
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
#texinfo_show_urls = 'footnote'
|
# texinfo_show_urls = 'footnote'
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||||
#texinfo_no_detailmenu = False
|
# texinfo_no_detailmenu = False
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
|
@ -1,44 +1,5 @@
|
||||||
alabaster==0.7.12
|
Sphinx~=7.2.6
|
||||||
appdirs==1.4.4
|
sphinx-rtd-theme~=2.0.0
|
||||||
Babel==2.8.0
|
readthedocs-sphinx-search~=0.3.2
|
||||||
CacheControl==0.12.6
|
sphinx-markdown-tables~=0.0.17
|
||||||
certifi==2020.4.5.1
|
myst-parser~=2.0.0
|
||||||
chardet==3.0.4
|
|
||||||
colorama==0.4.3
|
|
||||||
commonmark==0.9.1
|
|
||||||
contextlib2==0.6.0
|
|
||||||
distlib==0.3.0
|
|
||||||
distro==1.5.0
|
|
||||||
docutils==0.16
|
|
||||||
html5lib==1.0.1
|
|
||||||
idna==2.9
|
|
||||||
imagesize==1.2.0
|
|
||||||
Jinja2==2.11.2
|
|
||||||
lockfile==0.12.2
|
|
||||||
Markdown==3.2.2
|
|
||||||
MarkupSafe==1.1.1
|
|
||||||
msgpack==1.0.0
|
|
||||||
ordered-set==4.0.1
|
|
||||||
packaging==20.4
|
|
||||||
pep517==0.8.2
|
|
||||||
progress==1.5
|
|
||||||
Pygments==2.6.1
|
|
||||||
pyparsing==2.4.7
|
|
||||||
pytoml==0.1.21
|
|
||||||
pytz==2020.1
|
|
||||||
recommonmark==0.6.0
|
|
||||||
requests==2.23.0
|
|
||||||
retrying==1.3.3
|
|
||||||
six==1.15.0
|
|
||||||
snowballstemmer==2.0.0
|
|
||||||
Sphinx==3.1.2
|
|
||||||
sphinx-markdown-tables==0.0.15
|
|
||||||
sphinxcontrib-applehelp==1.0.2
|
|
||||||
sphinxcontrib-devhelp==1.0.2
|
|
||||||
sphinxcontrib-htmlhelp==1.0.3
|
|
||||||
sphinxcontrib-jsmath==1.0.1
|
|
||||||
sphinxcontrib-qthelp==1.0.3
|
|
||||||
sphinxcontrib-serializinghtml==1.1.4
|
|
||||||
toml==0.10.1
|
|
||||||
urllib3==1.25.9
|
|
||||||
webencodings==0.5.1
|
|
||||||
|
|
|
@ -1,609 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import atexit
|
|
||||||
import errno
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import signal
|
|
||||||
import logging
|
|
||||||
import argparse
|
|
||||||
from collections import Sequence
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
__license__ = "MIT License"
|
|
||||||
__author__ = u"Matteo ℱan <SystemRage@protonmail.com>"
|
|
||||||
__copyright__ = "© Copyright 2020"
|
|
||||||
__url__ = "https://github.com/SystemRage/Etrigan"
|
|
||||||
__description__ = "Etrigan: a python daemonizer that rocks."
|
|
||||||
|
|
||||||
|
|
||||||
class Etrigan(object):
|
|
||||||
"""
|
|
||||||
Daemonizer based on double-fork method
|
|
||||||
--------------------------------------
|
|
||||||
Each option can be passed as a keyword argument or modified by assigning
|
|
||||||
to an attribute on the instance:
|
|
||||||
|
|
||||||
jasonblood = Etrigan(pidfile,
|
|
||||||
argument_example_1 = foo,
|
|
||||||
argument_example_2 = bar)
|
|
||||||
|
|
||||||
that is equivalent to:
|
|
||||||
|
|
||||||
jasonblood = Etrigan(pidfile)
|
|
||||||
jasonblood.argument_example_1 = foo
|
|
||||||
jasonblood.argument_example_2 = bar
|
|
||||||
|
|
||||||
Object constructor expects always `pidfile` argument.
|
|
||||||
`pidfile`
|
|
||||||
Path to the pidfile.
|
|
||||||
|
|
||||||
The following other options are defined:
|
|
||||||
`stdin`
|
|
||||||
`stdout`
|
|
||||||
`stderr`
|
|
||||||
:Default: `os.devnull`
|
|
||||||
File objects used as the new file for the standard I/O streams
|
|
||||||
`sys.stdin`, `sys.stdout`, and `sys.stderr` respectively.
|
|
||||||
|
|
||||||
`funcs_to_daemonize`
|
|
||||||
:Default: `[]`
|
|
||||||
Define a list of your custom functions
|
|
||||||
which will be executed after daemonization.
|
|
||||||
If None, you have to subclass Etrigan `run` method.
|
|
||||||
Note that these functions can return elements that will be
|
|
||||||
added to Etrigan object (`etrigan_add` list) so the other subsequent
|
|
||||||
ones can reuse them for further processing.
|
|
||||||
You only have to provide indexes of `etrigan_add` list,
|
|
||||||
(an int (example: 2) for single index or a string (example: '1:4') for slices)
|
|
||||||
as first returning element.
|
|
||||||
|
|
||||||
`want_quit`
|
|
||||||
:Default: `False`
|
|
||||||
If `True`, runs Etrigan `quit_on_start` or `quit_on_stop`
|
|
||||||
lists of your custom functions at the end of `start` or `stop` operations.
|
|
||||||
These can return elements as `funcs_to_daemonize`.
|
|
||||||
|
|
||||||
`logfile`
|
|
||||||
:Default: `None`
|
|
||||||
Path to the output log file.
|
|
||||||
|
|
||||||
`loglevel`
|
|
||||||
:Default: `None`
|
|
||||||
Set the log level of logging messages.
|
|
||||||
|
|
||||||
`mute`
|
|
||||||
:Default: `False`
|
|
||||||
Disable all stdout and stderr messages (before double forking).
|
|
||||||
|
|
||||||
`pause_loop`
|
|
||||||
:Default: `None`
|
|
||||||
Seconds of pause between the calling, in an infinite loop,
|
|
||||||
of every function in `funcs_to_daemonize` list.
|
|
||||||
If `-1`, no pause between the calling, in an infinite loop,
|
|
||||||
of every function in `funcs_to_daemonize` list.
|
|
||||||
If `None`, only one run (no infinite loop) of functions in
|
|
||||||
`funcs_to_daemonize` list, without pause.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, pidfile,
|
|
||||||
stdin = os.devnull, stdout = os.devnull, stderr = os.devnull,
|
|
||||||
funcs_to_daemonize = [], want_quit = False,
|
|
||||||
logfile = None, loglevel = None,
|
|
||||||
mute = False, pause_loop = None):
|
|
||||||
|
|
||||||
self.pidfile = pidfile
|
|
||||||
self.funcs_to_daemonize = funcs_to_daemonize
|
|
||||||
self.stdin = stdin
|
|
||||||
self.stdout = stdout
|
|
||||||
self.stderr = stderr
|
|
||||||
self.logfile = logfile
|
|
||||||
self.loglevel = loglevel
|
|
||||||
self.mute = mute
|
|
||||||
self.want_quit = want_quit
|
|
||||||
self.pause_loop = pause_loop
|
|
||||||
# internal only.
|
|
||||||
self.homedir = '/'
|
|
||||||
self.umask = 0o22
|
|
||||||
self.etrigan_restart, self.etrigan_reload = (False for _ in range(2))
|
|
||||||
self.etrigan_alive = True
|
|
||||||
self.etrigan_add = []
|
|
||||||
self.etrigan_index = None
|
|
||||||
# seconds of pause between stop and start during the restart of the daemon.
|
|
||||||
self.pause_restart = 5
|
|
||||||
# when terminate a process, seconds to wait until kill the process with signal.
|
|
||||||
# self.pause_kill = 3
|
|
||||||
|
|
||||||
# create logfile.
|
|
||||||
self.setup_files()
|
|
||||||
|
|
||||||
def handle_terminate(self, signum, frame):
|
|
||||||
if os.path.exists(self.pidfile):
|
|
||||||
self.etrigan_alive = False
|
|
||||||
# eventually run quit (on stop) function/s.
|
|
||||||
if self.want_quit:
|
|
||||||
if not isinstance(self.quit_on_stop, (list, tuple)):
|
|
||||||
self.quit_on_stop = [self.quit_on_stop]
|
|
||||||
self.execute(self.quit_on_stop)
|
|
||||||
# then always run quit standard.
|
|
||||||
self.quit_standard()
|
|
||||||
else:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: can't find PIDFILE '%s'" %self.pidfile)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def handle_reload(self, signum, frame):
|
|
||||||
self.etrigan_reload = True
|
|
||||||
|
|
||||||
def setup_files(self):
|
|
||||||
self.pidfile = os.path.abspath(self.pidfile)
|
|
||||||
|
|
||||||
if self.logfile is not None:
|
|
||||||
self.logdaemon = logging.getLogger('logdaemon')
|
|
||||||
self.logdaemon.setLevel(self.loglevel)
|
|
||||||
|
|
||||||
filehandler = logging.FileHandler(self.logfile)
|
|
||||||
filehandler.setLevel(self.loglevel)
|
|
||||||
formatter = logging.Formatter(fmt = '[%(asctime)s] [%(levelname)8s] --- %(message)s',
|
|
||||||
datefmt = '%Y-%m-%d %H:%M:%S')
|
|
||||||
filehandler.setFormatter(formatter)
|
|
||||||
self.logdaemon.addHandler(filehandler)
|
|
||||||
else:
|
|
||||||
nullhandler = logging.NullHandler()
|
|
||||||
self.logdaemon.addHandler(nullhandler)
|
|
||||||
|
|
||||||
def emit_error(self, message, to_exit = True):
|
|
||||||
""" Print an error message to STDERR. """
|
|
||||||
if not self.mute:
|
|
||||||
sys.stderr.write(message + '\n')
|
|
||||||
sys.stderr.flush()
|
|
||||||
if to_exit:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def emit_message(self, message, to_exit = False):
|
|
||||||
""" Print a message to STDOUT. """
|
|
||||||
if not self.mute:
|
|
||||||
sys.stdout.write(message + '\n')
|
|
||||||
sys.stdout.flush()
|
|
||||||
if to_exit:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def view(self, logobj, emitobj, msg, **kwargs):
|
|
||||||
options = {'to_exit' : False,
|
|
||||||
'silent' : False
|
|
||||||
}
|
|
||||||
options.update(kwargs)
|
|
||||||
|
|
||||||
if logobj:
|
|
||||||
logobj(msg)
|
|
||||||
if emitobj:
|
|
||||||
if not options['silent']:
|
|
||||||
emitobj(msg, to_exit = options['to_exit'])
|
|
||||||
|
|
||||||
def daemonize(self):
|
|
||||||
"""
|
|
||||||
Double-forks the process to daemonize the script.
|
|
||||||
see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177)
|
|
||||||
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
|
||||||
"""
|
|
||||||
self.view(self.logdaemon.debug, None, "Attempting to daemonize the process...")
|
|
||||||
|
|
||||||
# First fork.
|
|
||||||
self.fork(msg = "First fork")
|
|
||||||
# Decouple from parent environment.
|
|
||||||
self.detach()
|
|
||||||
# Second fork.
|
|
||||||
self.fork(msg = "Second fork")
|
|
||||||
# Write the PID file.
|
|
||||||
self.create_pidfile()
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "The daemon process has started.")
|
|
||||||
# Redirect standard file descriptors.
|
|
||||||
sys.stdout.flush()
|
|
||||||
sys.stderr.flush()
|
|
||||||
self.attach('stdin', mode = 'r')
|
|
||||||
self.attach('stdout', mode = 'a+')
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.attach('stderr', mode = 'a+', buffering = 0)
|
|
||||||
except ValueError:
|
|
||||||
# Python 3 can't have unbuffered text I/O.
|
|
||||||
self.attach('stderr', mode = 'a+', buffering = 1)
|
|
||||||
|
|
||||||
# Handle signals.
|
|
||||||
signal.signal(signal.SIGINT, self.handle_terminate)
|
|
||||||
signal.signal(signal.SIGTERM, self.handle_terminate)
|
|
||||||
signal.signal(signal.SIGHUP, self.handle_reload)
|
|
||||||
#signal.signal(signal.SIGKILL....)
|
|
||||||
|
|
||||||
def fork(self, msg):
|
|
||||||
try:
|
|
||||||
pid = os.fork()
|
|
||||||
if pid > 0:
|
|
||||||
self.view(self.logdaemon.debug, None, msg + " success with PID %d." %pid)
|
|
||||||
# Exit from parent.
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception as e:
|
|
||||||
msg += " failed: %s." %str(e)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def detach(self):
|
|
||||||
# cd to root for a guarenteed working dir.
|
|
||||||
try:
|
|
||||||
os.chdir(self.homedir)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "Unable to change working directory: %s." %str(e)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
# clear the session id to clear the controlling tty.
|
|
||||||
pid = os.setsid()
|
|
||||||
if pid == -1:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# set the umask so we have access to all files created by the daemon.
|
|
||||||
try:
|
|
||||||
os.umask(self.umask)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "Unable to change file creation mask: %s." %str(e)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def attach(self, name, mode, buffering = -1):
|
|
||||||
with open(getattr(self, name), mode, buffering) as stream:
|
|
||||||
os.dup2(stream.fileno(), getattr(sys, name).fileno())
|
|
||||||
|
|
||||||
def checkfile(self, path, typearg, typefile):
|
|
||||||
filename = os.path.basename(path)
|
|
||||||
pathname = os.path.dirname(path)
|
|
||||||
if not os.path.isdir(pathname):
|
|
||||||
msg = "argument %s: invalid directory: '%s'. Exiting..." %(typearg, pathname)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
elif not filename.lower().endswith(typefile):
|
|
||||||
msg = "argument %s: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, filename)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def create_pidfile(self):
|
|
||||||
atexit.register(self.delete_pidfile)
|
|
||||||
pid = os.getpid()
|
|
||||||
try:
|
|
||||||
with open(self.pidfile, 'w+') as pf:
|
|
||||||
pf.write("%s\n" %pid)
|
|
||||||
self.view(self.logdaemon.debug, None, "PID %d written to '%s'." %(pid, self.pidfile))
|
|
||||||
except Exception as e:
|
|
||||||
msg = "Unable to write PID to PIDFILE '%s': %s" %(self.pidfile, str(e))
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def delete_pidfile(self, pid):
|
|
||||||
# Remove the PID file.
|
|
||||||
try:
|
|
||||||
os.remove(self.pidfile)
|
|
||||||
self.view(self.logdaemon.debug, None, "Removing PIDFILE '%s' with PID %d." %(self.pidfile, pid))
|
|
||||||
except Exception as e:
|
|
||||||
if e.errno != errno.ENOENT:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, str(e))
|
|
||||||
|
|
||||||
def get_pidfile(self):
|
|
||||||
# Get the PID from the PID file.
|
|
||||||
if self.pidfile is None:
|
|
||||||
return None
|
|
||||||
if not os.path.isfile(self.pidfile):
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(self.pidfile, 'r') as pf:
|
|
||||||
pid = int(pf.read().strip())
|
|
||||||
self.view(self.logdaemon.debug, None, "Found PID %d in PIDFILE '%s'" %(pid, self.pidfile))
|
|
||||||
except Exception as e:
|
|
||||||
self.view(self.logdaemon.warning, None, "Empty or broken PIDFILE")
|
|
||||||
pid = None
|
|
||||||
|
|
||||||
def pid_exists(pid):
|
|
||||||
# psutil _psposix.py.
|
|
||||||
if pid == 0:
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
os.kill(pid, 0)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == errno.ESRCH:
|
|
||||||
return False
|
|
||||||
elif e.errno == errno.EPERM:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, str(e))
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if pid is not None and pid_exists(pid):
|
|
||||||
return pid
|
|
||||||
else:
|
|
||||||
# Remove the stale PID file.
|
|
||||||
self.delete_pidfile(pid)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
""" Start the daemon. """
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "Starting the daemon process...", silent = self.etrigan_restart)
|
|
||||||
|
|
||||||
# Check for a PID file to see if the Daemon is already running.
|
|
||||||
pid = self.get_pidfile()
|
|
||||||
if pid is not None:
|
|
||||||
msg = "A previous daemon process with PIDFILE '%s' already exists. Daemon already running ?" %self.pidfile
|
|
||||||
self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Daemonize the main process.
|
|
||||||
self.daemonize()
|
|
||||||
# Start a infinitive loop that periodically runs `funcs_to_daemonize`.
|
|
||||||
self.loop()
|
|
||||||
# eventualy run quit (on start) function/s.
|
|
||||||
if self.want_quit:
|
|
||||||
if not isinstance(self.quit_on_start, (list, tuple)):
|
|
||||||
self.quit_on_start = [self.quit_on_start]
|
|
||||||
self.execute(self.quit_on_start)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
""" Stop the daemon. """
|
|
||||||
self.view(None, self.emit_message, "Stopping the daemon process...", silent = self.etrigan_restart)
|
|
||||||
|
|
||||||
self.logdaemon.disabled = True
|
|
||||||
pid = self.get_pidfile()
|
|
||||||
self.logdaemon.disabled = False
|
|
||||||
if not pid:
|
|
||||||
# Just to be sure. A ValueError might occur
|
|
||||||
# if the PIDFILE is empty but does actually exist.
|
|
||||||
if os.path.exists(self.pidfile):
|
|
||||||
self.delete_pidfile(pid)
|
|
||||||
|
|
||||||
msg = "Can't find the daemon process with PIDFILE '%s'. Daemon not running ?" %self.pidfile
|
|
||||||
self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Try to kill the daemon process.
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
os.kill(pid, signal.SIGTERM)
|
|
||||||
time.sleep(0.1)
|
|
||||||
except Exception as e:
|
|
||||||
if (e.errno != errno.ESRCH):
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: %s" %str(e))
|
|
||||||
else:
|
|
||||||
self.view(None, self.emit_message, "The daemon process has ended correctly.", silent = self.etrigan_restart)
|
|
||||||
|
|
||||||
def restart(self):
|
|
||||||
""" Restart the daemon. """
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "Restarting the daemon process...")
|
|
||||||
self.etrigan_restart = True
|
|
||||||
self.stop()
|
|
||||||
if self.pause_restart:
|
|
||||||
time.sleep(self.pause_restart)
|
|
||||||
self.etrigan_alive = True
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
def reload(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def status(self):
|
|
||||||
""" Get status of the daemon. """
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "Viewing the daemon process status...")
|
|
||||||
|
|
||||||
if self.pidfile is None:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Cannot get the status of daemon without PIDFILE.")
|
|
||||||
|
|
||||||
pid = self.get_pidfile()
|
|
||||||
if pid is None:
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "The daemon process is not running.", to_exit = True)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
with open("/proc/%d/status" %pid, 'r') as pf:
|
|
||||||
pass
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "The daemon process is running.", to_exit = True)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "There is not a process with the PIDFILE '%s': %s" %(self.pidfile, str(e))
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def flatten(self, alistoflists, ltypes = Sequence):
|
|
||||||
# https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists/2158532#2158532
|
|
||||||
alistoflists = list(alistoflists)
|
|
||||||
while alistoflists:
|
|
||||||
while alistoflists and isinstance(alistoflists[0], ltypes):
|
|
||||||
alistoflists[0:1] = alistoflists[0]
|
|
||||||
if alistoflists: yield alistoflists.pop(0)
|
|
||||||
|
|
||||||
def exclude(self, func):
|
|
||||||
from inspect import getargspec
|
|
||||||
args = getargspec(func)
|
|
||||||
if callable(func):
|
|
||||||
try:
|
|
||||||
args[0].pop(0)
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
return args
|
|
||||||
else:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Not a function.")
|
|
||||||
return
|
|
||||||
|
|
||||||
def execute(self, some_functions):
|
|
||||||
returned = None
|
|
||||||
if isinstance(some_functions, (list, tuple)):
|
|
||||||
for func in some_functions:
|
|
||||||
l_req = len(self.exclude(func)[0])
|
|
||||||
|
|
||||||
if l_req == 0:
|
|
||||||
returned = func()
|
|
||||||
else:
|
|
||||||
l_add = len(self.etrigan_add)
|
|
||||||
if l_req > l_add:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error,
|
|
||||||
"Can't evaluate function: given %s, required %s." %(l_add, l_req))
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
arguments = self.etrigan_add[self.etrigan_index]
|
|
||||||
l_args = (len(arguments) if isinstance(arguments, list) else 1)
|
|
||||||
if (l_args > l_req) or (l_args < l_req):
|
|
||||||
self.view(self.logdaemon.error, self.emit_error,
|
|
||||||
"Can't evaluate function: given %s, required %s." %(l_args, l_req))
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
if isinstance(arguments, list):
|
|
||||||
returned = func(*arguments)
|
|
||||||
else:
|
|
||||||
returned = func(arguments)
|
|
||||||
|
|
||||||
if returned:
|
|
||||||
if isinstance(returned, (list, tuple)):
|
|
||||||
if isinstance(returned[0], int):
|
|
||||||
self.etrigan_index = returned[0]
|
|
||||||
else:
|
|
||||||
self.etrigan_index = slice(*map(int, returned[0].split(':')))
|
|
||||||
if returned[1:] != []:
|
|
||||||
self.etrigan_add.append(returned[1:])
|
|
||||||
self.etrigan_add = list(self.flatten(self.etrigan_add))
|
|
||||||
else:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Function should return list or tuple.")
|
|
||||||
returned = None
|
|
||||||
else:
|
|
||||||
if some_functions is None:
|
|
||||||
self.run()
|
|
||||||
|
|
||||||
def loop(self):
|
|
||||||
try:
|
|
||||||
if self.pause_loop is None:
|
|
||||||
# one-shot.
|
|
||||||
self.execute(self.funcs_to_daemonize)
|
|
||||||
else:
|
|
||||||
if self.pause_loop >= 0:
|
|
||||||
# infinite with pause.
|
|
||||||
time.sleep(self.pause_loop)
|
|
||||||
while self.etrigan_alive:
|
|
||||||
self.execute(self.funcs_to_daemonize)
|
|
||||||
time.sleep(self.pause_loop)
|
|
||||||
elif self.pause_loop == -1:
|
|
||||||
# infinite without pause.
|
|
||||||
while self.etrigan_alive:
|
|
||||||
self.execute(self.funcs_to_daemonize)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "The daemon process start method failed: %s" %str(e)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def quit_standard(self):
|
|
||||||
self.view(self.logdaemon.info, None, "Stopping the daemon process...")
|
|
||||||
self.delete_pidfile(self.get_pidfile())
|
|
||||||
self.view(self.logdaemon.info, None, "The daemon process has ended correctly.")
|
|
||||||
|
|
||||||
def quit_on_start(self):
|
|
||||||
"""
|
|
||||||
Override this method when you subclass Daemon.
|
|
||||||
"""
|
|
||||||
self.quit_standard()
|
|
||||||
|
|
||||||
def quit_on_stop(self):
|
|
||||||
"""
|
|
||||||
Override this method when you subclass Daemon.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""
|
|
||||||
Override this method when you subclass Daemon.
|
|
||||||
It will be called after the process has been
|
|
||||||
daemonized by start() or restart().
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class JasonBlood(Etrigan):
|
|
||||||
def run(self):
|
|
||||||
jasonblood_func()
|
|
||||||
|
|
||||||
def jasonblood_func():
|
|
||||||
with open(os.path.join('.', 'etrigan_test.txt'), 'a') as file:
|
|
||||||
file.write("Yarva Demonicus Etrigan " + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) + '\n')
|
|
||||||
|
|
||||||
def Etrigan_parser(parser = None):
|
|
||||||
if parser is None:
|
|
||||||
# create a new parser.
|
|
||||||
parser = argparse.ArgumentParser(description = __description__, epilog = __version__)
|
|
||||||
if not parser.add_help:
|
|
||||||
# create help argument.
|
|
||||||
parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
|
||||||
|
|
||||||
# attach to an existent parser.
|
|
||||||
parser.add_argument("operation", action = "store", choices = ["start", "stop", "restart", "status", "reload"],
|
|
||||||
help = "Select an operation for daemon.", type = str)
|
|
||||||
parser.add_argument("--etrigan-pid",
|
|
||||||
action = "store", dest = "etriganpid", default = "/tmp/etrigan.pid",
|
|
||||||
help = "Choose a pidfile path. Default is \"/tmp/etrigan.pid\".", type = str) #'/var/run/etrigan.pid'
|
|
||||||
parser.add_argument("--etrigan-log",
|
|
||||||
action = "store", dest = "etriganlog", default = os.path.join('.', "etrigan.log"),
|
|
||||||
help = "Use this option to choose an output log file; for not logging don't select it. Default is \"etrigan.log\".", type = str)
|
|
||||||
parser.add_argument("--etrigan-lev",
|
|
||||||
action = "store", dest = "etriganlev", default = "DEBUG",
|
|
||||||
choices = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
|
|
||||||
help = "Use this option to set a log level. Default is \"DEBUG\".", type = str)
|
|
||||||
parser.add_argument("--etrigan-mute",
|
|
||||||
action = "store_const", dest = 'etriganmute', const = True, default = False,
|
|
||||||
help = "Disable all stdout and stderr messages.")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
class Etrigan_check(object):
|
|
||||||
def emit_opt_err(self, msg):
|
|
||||||
print(msg)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def checkfile(self, path, typearg, typefile):
|
|
||||||
filename, extension = os.path.splitext(path)
|
|
||||||
pathname = os.path.dirname(path)
|
|
||||||
if not os.path.isdir(pathname):
|
|
||||||
msg = "argument `%s`: invalid directory: '%s'. Exiting..." %(typearg, pathname)
|
|
||||||
self.emit_opt_err(msg)
|
|
||||||
elif not extension == typefile:
|
|
||||||
msg = "argument `%s`: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, extension)
|
|
||||||
self.emit_opt_err(msg)
|
|
||||||
|
|
||||||
def checkfunction(self, funcs, booleans):
|
|
||||||
if not isinstance(funcs, (list, tuple)):
|
|
||||||
if funcs is not None:
|
|
||||||
msg = "argument `funcs_to_daemonize`: provide list, tuple or None"
|
|
||||||
self.emit_opt_err(msg)
|
|
||||||
|
|
||||||
for elem in booleans:
|
|
||||||
if not type(elem) == bool:
|
|
||||||
msg = "argument `want_quit`: not a boolean."
|
|
||||||
self.emit_opt_err(msg)
|
|
||||||
|
|
||||||
def Etrigan_job(type_oper, daemon_obj):
|
|
||||||
Etrigan_check().checkfunction(daemon_obj.funcs_to_daemonize,
|
|
||||||
[daemon_obj.want_quit])
|
|
||||||
if type_oper == "start":
|
|
||||||
daemon_obj.start()
|
|
||||||
elif type_oper == "stop":
|
|
||||||
daemon_obj.stop()
|
|
||||||
elif type_oper == "restart":
|
|
||||||
daemon_obj.restart()
|
|
||||||
elif type_oper == "status":
|
|
||||||
daemon_obj.status()
|
|
||||||
elif type_oper == "reload":
|
|
||||||
daemon_obj.reload()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Parse arguments.
|
|
||||||
parser = Etrigan_parser()
|
|
||||||
args = vars(parser.parse_args())
|
|
||||||
# Check arguments.
|
|
||||||
Etrigan_check().checkfile(args['etriganpid'], '--etrigan-pid', '.pid')
|
|
||||||
Etrigan_check().checkfile(args['etriganlog'], '--etrigan-log', '.log')
|
|
||||||
|
|
||||||
# Setup daemon.
|
|
||||||
jasonblood_1 = Etrigan(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'],
|
|
||||||
mute = args['etriganmute'],
|
|
||||||
funcs_to_daemonize = [jasonblood_func], pause_loop = 5)
|
|
||||||
|
|
||||||
## jasonblood_2 = JasonBlood(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'],
|
|
||||||
## mute = args['etriganmute'],
|
|
||||||
## funcs_to_daemonize = None, pause_loop = 5)
|
|
||||||
# Do job.
|
|
||||||
Etrigan_job(args['operation'], jasonblood_1)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -103,6 +103,14 @@
|
||||||
<Activate KmsItem="7ba0bf23-d0f5-4072-91d9-d55af5a481b6" />
|
<Activate KmsItem="7ba0bf23-d0f5-4072-91d9-d55af5a481b6" />
|
||||||
</CsvlkItem>
|
</CsvlkItem>
|
||||||
|
|
||||||
|
<CsvlkItem DisplayName="Windows Server 2022" VlmcsdIndex="0" GroupId="206" MinKeyId="551000000" MaxKeyId="570999999" IniFileName="Windows" EPid="06401-00206-566-174993-03-1033-9600.0000-2802018" Id="ef6cfc9f-8c5d-44ac-9aad-de6a2ea0ae03" InvalidWinBuild="[0,1,2]">
|
||||||
|
<Activate KmsItem="ef6cfc9f-8c5d-44ac-9aad-de6a2ea0ae03" />
|
||||||
|
</CsvlkItem>
|
||||||
|
|
||||||
|
<CsvlkItem DisplayName="Windows Server 2025" VlmcsdIndex="0" GroupId="206" MinKeyId="551000000" MaxKeyId="570999999" IniFileName="Windows" EPid="06401-00206-566-174993-03-1033-9600.0000-2802018" Id="c052f164-cdf6-409a-a0cb-853ba0f0f55a" InvalidWinBuild="[0,1,2]">
|
||||||
|
<Activate KmsItem="c052f164-cdf6-409a-a0cb-853ba0f0f55a" />
|
||||||
|
</CsvlkItem>
|
||||||
|
|
||||||
<CsvlkItem DisplayName="Windows Server 2019" VlmcsdIndex="0" GroupId="206" MinKeyId="551000000" MaxKeyId="570999999" IniFileName="Windows" EPid="06401-00206-566-174993-03-1033-9600.0000-2802018" Id="2e7a9ad1-a849-4b56-babe-17d5a29fe4b4" InvalidWinBuild="[0,1,2]">
|
<CsvlkItem DisplayName="Windows Server 2019" VlmcsdIndex="0" GroupId="206" MinKeyId="551000000" MaxKeyId="570999999" IniFileName="Windows" EPid="06401-00206-566-174993-03-1033-9600.0000-2802018" Id="2e7a9ad1-a849-4b56-babe-17d5a29fe4b4" InvalidWinBuild="[0,1,2]">
|
||||||
<Activate KmsItem="58e2134f-8e11-4d17-9cb2-91069c151148" />
|
<Activate KmsItem="58e2134f-8e11-4d17-9cb2-91069c151148" />
|
||||||
<Activate KmsItem="7fde5219-fbfa-484a-82c9-34d1ad53e856" />
|
<Activate KmsItem="7fde5219-fbfa-484a-82c9-34d1ad53e856" />
|
||||||
|
@ -530,6 +538,10 @@
|
||||||
<Activate KmsItem="02000000-0000-0000-0000-000000000000" />
|
<Activate KmsItem="02000000-0000-0000-0000-000000000000" />
|
||||||
</CsvlkItem>
|
</CsvlkItem>
|
||||||
|
|
||||||
|
<CsvlkItem DisplayName="Office 2021" VlmcsdIndex="6" GroupId="206" MinKeyId="571000000" MaxKeyId="590999999" IniFileName="Office2021" EPid="05426-00206-586-025264-03-1033-9200.0000-2602021" Id="47f3b983-7c53-4d45-abc6-bcd91e2dd90a" InvalidWinBuild="[0,1]">
|
||||||
|
<Activate KmsItem="86d50b16-4808-41af-b83b-b338274318b2" />
|
||||||
|
</CsvlkItem>
|
||||||
|
|
||||||
<CsvlkItem DisplayName="Office 2019" VlmcsdIndex="5" GroupId="206" MinKeyId="666000000" MaxKeyId="685999999" IniFileName="Office2019" EPid="06401-00206-678-008369-03-1033-9600.0000-2802018" Id="70512334-47b4-44db-a233-be5ea33b914c" InvalidWinBuild="[0,1]">
|
<CsvlkItem DisplayName="Office 2019" VlmcsdIndex="5" GroupId="206" MinKeyId="666000000" MaxKeyId="685999999" IniFileName="Office2019" EPid="06401-00206-678-008369-03-1033-9600.0000-2802018" Id="70512334-47b4-44db-a233-be5ea33b914c" InvalidWinBuild="[0,1]">
|
||||||
<Activate KmsItem="617d9eb1-ef36-4f82-86e0-a65ae07b96c6" />
|
<Activate KmsItem="617d9eb1-ef36-4f82-86e0-a65ae07b96c6" />
|
||||||
</CsvlkItem>
|
</CsvlkItem>
|
||||||
|
@ -560,6 +572,16 @@
|
||||||
|
|
||||||
<AppItem DisplayName="Windows" VlmcsdIndex="0" Id="55c92734-d682-4d71-983e-d6ec3f16059f" MinActiveClients="50">
|
<AppItem DisplayName="Windows" VlmcsdIndex="0" Id="55c92734-d682-4d71-983e-d6ec3f16059f" MinActiveClients="50">
|
||||||
|
|
||||||
|
<KmsItem DisplayName="Windows Server 2025" Id="c052f164-cdf6-409a-a0cb-853ba0f0f55a" DefaultKmsProtocol="6.0" NCountPolicy="5">
|
||||||
|
<SkuItem DisplayName="Windows Server 2025 Datacenter" Id="c052f164-cdf6-409a-a0cb-853ba0f0f55a" Gvlk="D764K-2NDRG-47T6Q-P8T8W-YP6DF" />
|
||||||
|
<SkuItem DisplayName="Windows Server 2025 Standard" Id="7dc26449-db21-4e09-ba37-28f2958506a6" Gvlk="TVRH6-WHNXV-R9WG3-9XRFY-MY832" />
|
||||||
|
</KmsItem>
|
||||||
|
|
||||||
|
<KmsItem DisplayName="Windows Server 2022" Id="ef6cfc9f-8c5d-44ac-9aad-de6a2ea0ae03" DefaultKmsProtocol="6.0" NCountPolicy="5">
|
||||||
|
<SkuItem DisplayName="Windows Server 2022 Datacenter" Id="ef6cfc9f-8c5d-44ac-9aad-de6a2ea0ae03" Gvlk="WX4NM-KYWYW-QJJR4-XV3QB-6VM33" />
|
||||||
|
<SkuItem DisplayName="Windows Server 2022 Standard" Id="de32eafd-aaee-4662-9444-c1befb41bde2" Gvlk="VDYBN-27WPP-V4HQT-9VMD4-VMK7H" />
|
||||||
|
</KmsItem>
|
||||||
|
|
||||||
<KmsItem DisplayName="Windows Server 2019" Id="8449b1fb-f0ea-497a-99ab-66ca96e9a0f5" DefaultKmsProtocol="6.0" NCountPolicy="5">
|
<KmsItem DisplayName="Windows Server 2019" Id="8449b1fb-f0ea-497a-99ab-66ca96e9a0f5" DefaultKmsProtocol="6.0" NCountPolicy="5">
|
||||||
<SkuItem DisplayName="Windows Server 2019 ARM64" Id="8de8eb62-bbe0-40ac-ac17-f75595071ea3" Gvlk="GRFBW-QNDC4-6QBHG-CCK3B-2PR88" />
|
<SkuItem DisplayName="Windows Server 2019 ARM64" Id="8de8eb62-bbe0-40ac-ac17-f75595071ea3" Gvlk="GRFBW-QNDC4-6QBHG-CCK3B-2PR88" />
|
||||||
<SkuItem DisplayName="Windows Server 2019 Azure Core" Id="a99cc1f0-7719-4306-9645-294102fbff95" Gvlk="FDNH6-VW9RW-BXPJ7-4XTYG-239TB" />
|
<SkuItem DisplayName="Windows Server 2019 Azure Core" Id="a99cc1f0-7719-4306-9645-294102fbff95" Gvlk="FDNH6-VW9RW-BXPJ7-4XTYG-239TB" />
|
||||||
|
@ -573,16 +595,16 @@
|
||||||
</KmsItem>
|
</KmsItem>
|
||||||
|
|
||||||
<KmsItem DisplayName="Windows 10 2019 (Volume)" Id="11b15659-e603-4cf1-9c1f-f0ec01b81888" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
<KmsItem DisplayName="Windows 10 2019 (Volume)" Id="11b15659-e603-4cf1-9c1f-f0ec01b81888" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise LTSC 2019" Id="32d2fab3-e4a8-42c2-923b-4bf4fd13e6ee" Gvlk="M7XTQ-FN8P6-TTKYV-9D4CC-J462D" />
|
<SkuItem DisplayName="Windows 10 Enterprise LTSC 2019/2021" Id="32d2fab3-e4a8-42c2-923b-4bf4fd13e6ee" Gvlk="M7XTQ-FN8P6-TTKYV-9D4CC-J462D" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise LTSC 2019 N" Id="7103a333-b8c8-49cc-93ce-d37c09687f92" Gvlk="92NFX-8DJQP-P6BBQ-THF9C-7CG2H" />
|
<SkuItem DisplayName="Windows 10 Enterprise LTSC 2019/2021 N" Id="7103a333-b8c8-49cc-93ce-d37c09687f92" Gvlk="92NFX-8DJQP-P6BBQ-THF9C-7CG2H" />
|
||||||
</KmsItem>
|
</KmsItem>
|
||||||
|
|
||||||
<KmsItem DisplayName="Windows 10 Unknown (Volume)" Id="d27cd636-1962-44e9-8b4f-27b6c23efb85" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
<KmsItem DisplayName="Windows 10 Unknown (Volume)" Id="d27cd636-1962-44e9-8b4f-27b6c23efb85" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
||||||
</KmsItem>
|
</KmsItem>
|
||||||
|
|
||||||
<KmsItem DisplayName="Windows 10 China Government" Id="7ba0bf23-d0f5-4072-91d9-d55af5a481b6" CanMapToDefaultCsvlk="false" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
<KmsItem DisplayName="Windows 10/11 China Government" Id="7ba0bf23-d0f5-4072-91d9-d55af5a481b6" CanMapToDefaultCsvlk="false" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise G" Id="e0b2d383-d112-413f-8a80-97f373a5820c" Gvlk="YYVX9-NTFWV-6MDM3-9PT4T-4M68B" />
|
<SkuItem DisplayName="Windows 10/11 Enterprise G" Id="e0b2d383-d112-413f-8a80-97f373a5820c" Gvlk="YYVX9-NTFWV-6MDM3-9PT4T-4M68B" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise G N" Id="e38454fb-41a4-4f59-a5dc-25080e354730" Gvlk="44RPN-FTY23-9VTTB-MP9BX-T84FV" />
|
<SkuItem DisplayName="Windows 10/11 Enterprise G N" Id="e38454fb-41a4-4f59-a5dc-25080e354730" Gvlk="44RPN-FTY23-9VTTB-MP9BX-T84FV" />
|
||||||
</KmsItem>
|
</KmsItem>
|
||||||
|
|
||||||
<KmsItem DisplayName="Windows 10 2016 (Volume)" Id="969fe3c0-a3ec-491a-9f25-423605deb365" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
<KmsItem DisplayName="Windows 10 2016 (Volume)" Id="969fe3c0-a3ec-491a-9f25-423605deb365" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
||||||
|
@ -590,44 +612,44 @@
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise 2016 LTSB N" Id="9f776d83-7156-45b2-8a5c-359b9c9f22a3" Gvlk="QFFDN-GRT3P-VKWWX-X7T3R-8B639" />
|
<SkuItem DisplayName="Windows 10 Enterprise 2016 LTSB N" Id="9f776d83-7156-45b2-8a5c-359b9c9f22a3" Gvlk="QFFDN-GRT3P-VKWWX-X7T3R-8B639" />
|
||||||
</KmsItem>
|
</KmsItem>
|
||||||
|
|
||||||
<KmsItem DisplayName="Windows 10 (Retail)" Id="e1c51358-fe3e-4203-a4a2-3b6b20c9734e" IsRetail="true" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
<KmsItem DisplayName="Windows 10/11 (Retail)" Id="e1c51358-fe3e-4203-a4a2-3b6b20c9734e" IsRetail="true" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
||||||
<SkuItem DisplayName="Windows 10 Home / Core" Id="58e97c99-f377-4ef1-81d5-4ad5522b5fd8" Gvlk="TX9XD-98N7V-6WMQ6-BX7FG-H8Q99" />
|
<SkuItem DisplayName="Windows 10/11 Home / Core" Id="58e97c99-f377-4ef1-81d5-4ad5522b5fd8" Gvlk="TX9XD-98N7V-6WMQ6-BX7FG-H8Q99" />
|
||||||
<SkuItem DisplayName="Windows 10 Home / Core [Pre-Release]" Id="903663f7-d2ab-49c9-8942-14aa9e0a9c72" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Home / Core [Pre-Release]" Id="903663f7-d2ab-49c9-8942-14aa9e0a9c72" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Home / Core Country Specific" Id="a9107544-f4a0-4053-a96a-1479abdef912" Gvlk="PVMJN-6DFY6-9CCP6-7BKTT-D3WVR" />
|
<SkuItem DisplayName="Windows 10/11 Home / Core Country Specific" Id="a9107544-f4a0-4053-a96a-1479abdef912" Gvlk="PVMJN-6DFY6-9CCP6-7BKTT-D3WVR" />
|
||||||
<SkuItem DisplayName="Windows 10 Home / Core Country Specific [Pre-Release]" Id="5fe40dd6-cf1f-4cf2-8729-92121ac2e997" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Home / Core Country Specific [Pre-Release]" Id="5fe40dd6-cf1f-4cf2-8729-92121ac2e997" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Home / Core N" Id="7b9e1751-a8da-4f75-9560-5fadfe3d8e38" Gvlk="3KHY7-WNT83-DGQKR-F7HPR-844BM" />
|
<SkuItem DisplayName="Windows 10/11 Home / Core N" Id="7b9e1751-a8da-4f75-9560-5fadfe3d8e38" Gvlk="3KHY7-WNT83-DGQKR-F7HPR-844BM" />
|
||||||
<SkuItem DisplayName="Windows 10 Home / Core N [Pre-Release]" Id="4dfd543d-caa6-4f69-a95f-5ddfe2b89567" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Home / Core N [Pre-Release]" Id="4dfd543d-caa6-4f69-a95f-5ddfe2b89567" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Home / Core Single Language" Id="cd918a57-a41b-4c82-8dce-1a538e221a83" Gvlk="7HNRX-D7KGG-3K4RQ-4WPJ4-YTDFH" />
|
<SkuItem DisplayName="Windows 10/11 Home / Core Single Language" Id="cd918a57-a41b-4c82-8dce-1a538e221a83" Gvlk="7HNRX-D7KGG-3K4RQ-4WPJ4-YTDFH" />
|
||||||
<SkuItem DisplayName="Windows 10 Home / Core Single Language [Pre-Release]" Id="2cc171ef-db48-4adc-af09-7c574b37f139" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Home / Core Single Language [Pre-Release]" Id="2cc171ef-db48-4adc-af09-7c574b37f139" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Home / Core [Technical Preview]" Id="6496e59d-89dc-49eb-a353-09ceb9404845" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Home / Core [Technical Preview]" Id="6496e59d-89dc-49eb-a353-09ceb9404845" Gvlk="" />
|
||||||
</KmsItem>
|
</KmsItem>
|
||||||
|
|
||||||
<KmsItem DisplayName="Windows 10 2015 (Volume)" Id="58e2134f-8e11-4d17-9cb2-91069c151148" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
<KmsItem DisplayName="Windows 10/11 2015 (Volume)" Id="58e2134f-8e11-4d17-9cb2-91069c151148" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
||||||
<SkuItem DisplayName="Windows 10 Education" Id="e0c42288-980c-4788-a014-c080d2e1926e" Gvlk="NW6C2-QMPVW-D7KKK-3GKT6-VCFB2" />
|
<SkuItem DisplayName="Windows 10/11 Education" Id="e0c42288-980c-4788-a014-c080d2e1926e" Gvlk="NW6C2-QMPVW-D7KKK-3GKT6-VCFB2" />
|
||||||
<SkuItem DisplayName="Windows 10 Education [Pre-Release]" Id="af43f7f0-3b1e-4266-a123-1fdb53f4323b" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Education [Pre-Release]" Id="af43f7f0-3b1e-4266-a123-1fdb53f4323b" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Education N" Id="3c102355-d027-42c6-ad23-2e7ef8a02585" Gvlk="2WH4N-8QGBV-H22JP-CT43Q-MDWWJ" />
|
<SkuItem DisplayName="Windows 10/11 Education N" Id="3c102355-d027-42c6-ad23-2e7ef8a02585" Gvlk="2WH4N-8QGBV-H22JP-CT43Q-MDWWJ" />
|
||||||
<SkuItem DisplayName="Windows 10 Education N [Pre-Release]" Id="075aca1f-05d7-42e5-a3ce-e349e7be7078" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Education N [Pre-Release]" Id="075aca1f-05d7-42e5-a3ce-e349e7be7078" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise" Id="73111121-5638-40f6-bc11-f1d7b0d64300" Gvlk="NPPR9-FWDCX-D2C8J-H872K-2YT43" />
|
<SkuItem DisplayName="Windows 10/11 Enterprise" Id="73111121-5638-40f6-bc11-f1d7b0d64300" Gvlk="NPPR9-FWDCX-D2C8J-H872K-2YT43" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise [Preview]" Id="43f2ab05-7c87-4d56-b27c-44d0f9a3dabd" Gvlk="QNMXX-GCD3W-TCCT4-872RV-G6P4J" IsGeneratedGvlk="true" />
|
<SkuItem DisplayName="Windows 10 Enterprise [Preview]" Id="43f2ab05-7c87-4d56-b27c-44d0f9a3dabd" Gvlk="QNMXX-GCD3W-TCCT4-872RV-G6P4J" IsGeneratedGvlk="true" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise S" Id="00000000-0000-0000-0000-000000000000" Gvlk="H76BG-QBNM7-73XY9-V6W2T-684BJ" />
|
<SkuItem DisplayName="Windows 10/11 Enterprise S" Id="00000000-0000-0000-0000-000000000000" Gvlk="H76BG-QBNM7-73XY9-V6W2T-684BJ" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise 2015 LTSB" Id="7b51a46c-0c04-4e8f-9af4-8496cca90d5e" Gvlk="WNMTR-4C88C-JK8YV-HQ7T2-76DF9" />
|
<SkuItem DisplayName="Windows 10 Enterprise 2015 LTSB" Id="7b51a46c-0c04-4e8f-9af4-8496cca90d5e" Gvlk="WNMTR-4C88C-JK8YV-HQ7T2-76DF9" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise 2015 LTSB [Pre-Release]" Id="2cf5af84-abab-4ff0-83f8-f040fb2576eb" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Enterprise 2015 LTSB [Pre-Release]" Id="2cf5af84-abab-4ff0-83f8-f040fb2576eb" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise 2015 LTSB N" Id="87b838b7-41b6-4590-8318-5797951d8529" Gvlk="2F77B-TNFGY-69QQF-B8YKP-D69TJ" />
|
<SkuItem DisplayName="Windows 10 Enterprise 2015 LTSB N" Id="87b838b7-41b6-4590-8318-5797951d8529" Gvlk="2F77B-TNFGY-69QQF-B8YKP-D69TJ" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise 2015 LTSB N [Pre-Release]" Id="11a37f09-fb7f-4002-bd84-f3ae71d11e90" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Enterprise 2015 LTSB N [Pre-Release]" Id="11a37f09-fb7f-4002-bd84-f3ae71d11e90" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise N" Id="e272e3e2-732f-4c65-a8f0-484747d0d947" Gvlk="DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4" />
|
<SkuItem DisplayName="Windows 10/11 Enterprise N" Id="e272e3e2-732f-4c65-a8f0-484747d0d947" Gvlk="DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise S N" Id="00000000-0000-0000-0000-000000000000" Gvlk="X4R4B-NV6WD-PKTVK-F98BH-4C2J8" />
|
<SkuItem DisplayName="Windows 10/11 Enterprise S N" Id="00000000-0000-0000-0000-000000000000" Gvlk="X4R4B-NV6WD-PKTVK-F98BH-4C2J8" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise N [Pre-Release]" Id="6ae51eeb-c268-4a21-9aae-df74c38b586d" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Enterprise N [Pre-Release]" Id="6ae51eeb-c268-4a21-9aae-df74c38b586d" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Professional Workstation" Id="82bbc092-bc50-4e16-8e18-b74fc486aec3" Gvlk="NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J" />
|
<SkuItem DisplayName="Windows 10/11 Professional Workstation" Id="82bbc092-bc50-4e16-8e18-b74fc486aec3" Gvlk="NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J" />
|
||||||
<SkuItem DisplayName="Windows 10 Professional Workstation N" Id="4b1571d3-bafb-4b40-8087-a961be2caf65" Gvlk="9FNHH-K3HBT-3W4TD-6383H-6XYWF" />
|
<SkuItem DisplayName="Windows 10/11 Professional Workstation N" Id="4b1571d3-bafb-4b40-8087-a961be2caf65" Gvlk="9FNHH-K3HBT-3W4TD-6383H-6XYWF" />
|
||||||
<SkuItem DisplayName="Windows 10 Professional" Id="2de67392-b7a7-462a-b1ca-108dd189f588" Gvlk="W269N-WFGWX-YVC9B-4J6C9-T83GX" />
|
<SkuItem DisplayName="Windows 10/11 Professional" Id="2de67392-b7a7-462a-b1ca-108dd189f588" Gvlk="W269N-WFGWX-YVC9B-4J6C9-T83GX" />
|
||||||
<SkuItem DisplayName="Windows 10 Professional Education" Id="3f1afc82-f8ac-4f6c-8005-1d233e606eee" Gvlk="6TP4R-GNPTD-KYYHQ-7B7DP-J447Y" />
|
<SkuItem DisplayName="Windows 10/11 Professional Education" Id="3f1afc82-f8ac-4f6c-8005-1d233e606eee" Gvlk="6TP4R-GNPTD-KYYHQ-7B7DP-J447Y" />
|
||||||
<SkuItem DisplayName="Windows 10 Professional Education N" Id="5300b18c-2e33-4dc2-8291-47ffcec746dd" Gvlk="YVWGF-BXNMC-HTQYQ-CPQ99-66QFC" />
|
<SkuItem DisplayName="Windows 10/11 Professional Education N" Id="5300b18c-2e33-4dc2-8291-47ffcec746dd" Gvlk="YVWGF-BXNMC-HTQYQ-CPQ99-66QFC" />
|
||||||
<SkuItem DisplayName="Windows 10 Professional N" Id="a80b5abf-76ad-428b-b05d-a47d2dffeebf" Gvlk="MH37W-N47XK-V7XM9-C7227-GCQG9" />
|
<SkuItem DisplayName="Windows 10/11 Professional N" Id="a80b5abf-76ad-428b-b05d-a47d2dffeebf" Gvlk="MH37W-N47XK-V7XM9-C7227-GCQG9" />
|
||||||
<SkuItem DisplayName="Windows 10 Professional N [Pre-Release]" Id="34260150-69ac-49a3-8a0d-4a403ab55763" Gvlk="" />
|
<SkuItem DisplayName="Windows 10 Professional N [Pre-Release]" Id="34260150-69ac-49a3-8a0d-4a403ab55763" Gvlk="" />
|
||||||
<SkuItem DisplayName="Windows 10 Professional [Preview]" Id="ff808201-fec6-4fd4-ae16-abbddade5706" Gvlk="XQHPH-N4D9W-M8P96-DPDFP-TMVPY" IsGeneratedGvlk="true" />
|
<SkuItem DisplayName="Windows 10 Professional [Preview]" Id="ff808201-fec6-4fd4-ae16-abbddade5706" Gvlk="XQHPH-N4D9W-M8P96-DPDFP-TMVPY" IsGeneratedGvlk="true" />
|
||||||
<SkuItem DisplayName="Windows 10 Professional WMC [Pre-Release]" Id="cc17e18a-fa93-43d6-9179-72950a1e931a" Gvlk="NKPM6-TCVPT-3HRFX-Q4H9B-QJ34Y" />
|
<SkuItem DisplayName="Windows 10 Professional WMC [Pre-Release]" Id="cc17e18a-fa93-43d6-9179-72950a1e931a" Gvlk="NKPM6-TCVPT-3HRFX-Q4H9B-QJ34Y" />
|
||||||
<SkuItem DisplayName="Windows 10 Enterprise for Virtual Desktops" Id="ec868e65-fadf-4759-b23e-93fe37f2cc29" Gvlk="CPWHC-NT2C7-VYW78-DHDB2-PG3GK" />
|
<SkuItem DisplayName="Windows 10/11 Enterprise for Virtual Desktops" Id="ec868e65-fadf-4759-b23e-93fe37f2cc29" Gvlk="CPWHC-NT2C7-VYW78-DHDB2-PG3GK" />
|
||||||
<SkuItem DisplayName="Windows 10 Remote Server" Id="e4db50ea-bda1-4566-b047-0ca50abc6f07" Gvlk="7NBT4-WGBQX-MP4H7-QXFF8-YP3KX" />
|
<SkuItem DisplayName="Windows 10/11 Remote Server" Id="e4db50ea-bda1-4566-b047-0ca50abc6f07" Gvlk="7NBT4-WGBQX-MP4H7-QXFF8-YP3KX" />
|
||||||
<SkuItem DisplayName="Windows 10 S (Lean)" Id="0df4f814-3f57-4b8b-9a9d-fddadcd69fac" Gvlk="NBTWJ-3DR69-3C4V8-C26MC-GQ9M6" />
|
<SkuItem DisplayName="Windows 10 S (Lean)" Id="0df4f814-3f57-4b8b-9a9d-fddadcd69fac" Gvlk="NBTWJ-3DR69-3C4V8-C26MC-GQ9M6" />
|
||||||
<SkuItem DisplayName="Windows 10 IoT Core [Pre-Release]" Id="b554b49f-4d57-4f08-955e-87886f514d49" Gvlk="7NX88-X6YM3-9Q3YT-CCGBF-KBVQF" />
|
<SkuItem DisplayName="Windows 10 IoT Core [Pre-Release]" Id="b554b49f-4d57-4f08-955e-87886f514d49" Gvlk="7NX88-X6YM3-9Q3YT-CCGBF-KBVQF" />
|
||||||
<SkuItem DisplayName="Windows 10 Core Connected [Pre-Release]" Id="827a0032-dced-4609-ab6e-16b9d8a40280" Gvlk="DJMYQ-WN6HG-YJ2YX-82JDB-CWFCW" />
|
<SkuItem DisplayName="Windows 10 Core Connected [Pre-Release]" Id="827a0032-dced-4609-ab6e-16b9d8a40280" Gvlk="DJMYQ-WN6HG-YJ2YX-82JDB-CWFCW" />
|
||||||
|
@ -984,6 +1006,37 @@
|
||||||
<SkuItem DisplayName="Office Word 2019" Id="059834fe-a8ea-4bff-b67b-4d006b5447d3" Gvlk="PBX3G-NWMT6-Q7XBW-PYJGG-WXD33" />
|
<SkuItem DisplayName="Office Word 2019" Id="059834fe-a8ea-4bff-b67b-4d006b5447d3" Gvlk="PBX3G-NWMT6-Q7XBW-PYJGG-WXD33" />
|
||||||
</KmsItem>
|
</KmsItem>
|
||||||
|
|
||||||
|
<KmsItem DisplayName="Office 2021" Id="86d50b16-4808-41af-b83b-b338274318b2" IsPreview="false" CanMapToDefaultCsvlk="false" DefaultKmsProtocol="6.0" NCountPolicy="5">
|
||||||
|
<SkuItem DisplayName="Office Access LTSC 2021" Id="1fe429d8-3fa7-4a39-b6f0-03dded42fe14" Gvlk="WM8YG-YNGDD-4JHDC-PG3F4-FC4T4" />
|
||||||
|
<SkuItem DisplayName="Office Excel LTSC 2021" Id="ea71effc-69f1-4925-9991-2f5e319bbc24" Gvlk="NWG3X-87C9K-TC7YY-BC2G7-G6RVC" />
|
||||||
|
<SkuItem DisplayName="Office Outlook LTSC 2021" Id="a5799e4c-f83c-4c6e-9516-dfe9b696150b" Gvlk="C9FM6-3N72F-HFJXB-TM3V9-T86R9" />
|
||||||
|
<SkuItem DisplayName="Office Powerpoint LTSC 2021" Id="6e166cc3-495d-438a-89e7-d7c9e6fd4dea" Gvlk="TY7XF-NFRBR-KJ44C-G83KF-GX27K" />
|
||||||
|
<SkuItem DisplayName="Office LTSC Professional Plus 2021" Id="fbdb3e18-a8ef-4fb3-9183-dffd60bd0984" Gvlk="FXYTK-NJJ8C-GB6DW-3DYQT-6F7TH" />
|
||||||
|
<SkuItem DisplayName="Office Project Pro 2021" Id="76881159-155c-43e0-9db7-2d70a9a3a4ca" Gvlk="FTNWT-C6WBT-8HMGF-K9PRX-QV9H8" />
|
||||||
|
<SkuItem DisplayName="Office Project Standard 2021" Id="6dd72704-f752-4b71-94c7-11cec6bfc355" Gvlk="J2JDC-NJCYY-9RGQ4-YXWMH-T3D4T" />
|
||||||
|
<SkuItem DisplayName="Office Publisher LTSC 2021" Id="aa66521f-2370-4ad8-a2bb-c095e3e4338f" Gvlk="2MW9D-N4BXM-9VBPG-Q7W6M-KFBGQ" />
|
||||||
|
<SkuItem DisplayName="Office Skype for Business LTSC 2021" Id="1f32a9af-1274-48bd-ba1e-1ab7508a23e8" Gvlk="HWCXN-K3WBT-WJBKY-R8BD9-XK29P" />
|
||||||
|
<SkuItem DisplayName="Office LTSC Standard 2021" Id="080a45c5-9f9f-49eb-b4b0-c3c610a5ebd3" Gvlk="KDX7X-BNVR8-TXXGX-4Q7Y8-78VT3" />
|
||||||
|
<SkuItem DisplayName="Office Visio LTSC Pro 2021" Id="fb61ac9a-1688-45d2-8f6b-0674dbffa33c" Gvlk="KNH8D-FGHT4-T8RK3-CTDYJ-K2HT4" />
|
||||||
|
<SkuItem DisplayName="Office Visio LTSC Standard 2021" Id="72fce797-1884-48dd-a860-b2f6a5efd3ca" Gvlk="MJVNY-BYWPY-CWV6J-2RKRT-4M8QG" />
|
||||||
|
<SkuItem DisplayName="Office Word LTSC 2021" Id="abe28aea-625a-43b1-8e30-225eb8fbd9e5" Gvlk="TN8H9-M34D3-Y64V9-TR72V-X79KV" />
|
||||||
|
</KmsItem>
|
||||||
|
|
||||||
|
<KmsItem DisplayName="Office 2024" Id="8d368fc1-9470-4be2-8d66-90e836cbb051" CanMapToDefaultCsvlk="false" DefaultKmsProtocol="6.0" NCountPolicy="5">
|
||||||
|
<SkuItem DisplayName="Office Access 2024" Id="9e9bceeb-e736-4f26-88de-763f87dcc485" Gvlk="82FTR-NCHR7-W3944-MGRHM-JMCWD" />
|
||||||
|
<SkuItem DisplayName="Office Excel 2024" Id="237854e9-79fc-4497-a0c1-a70969691c6b" Gvlk="F4DYN-89BP2-WQTWJ-GR8YC-CKGJG" />
|
||||||
|
<SkuItem DisplayName="Office Outlook 2024" Id="c8f8a301-19f5-4132-96ce-2de9d4adbd33" Gvlk="D2F8D-N3Q3B-J28PV-X27HD-RJWB9" />
|
||||||
|
<SkuItem DisplayName="Office Powerpoint 2024" Id="3131fd61-5e4f-4308-8d6d-62be1987c92c" Gvlk="TY7XF-NFRBR-KJ44C-G83KF-GX27K" />
|
||||||
|
<SkuItem DisplayName="Office Professional Plus 2024" Id="8d368fc1-9470-4be2-8d66-90e836cbb051" Gvlk="CW94N-K6GJH-9CTXY-MG2VC-FYCWP" />
|
||||||
|
<SkuItem DisplayName="Office Project Professional 2024" Id="f510af75-8ab7-4426-a236-1bfb95c34ff8" Gvlk="FQQ23-N4YCY-73HQ3-FM9WC-76HF4" />
|
||||||
|
<SkuItem DisplayName="Office Project Standard 2024" Id="1777f0e3-7392-4198-97ea-8ae4de6f6381" Gvlk="PD3TT-NTHQQ-VC7CY-MFXK3-G87F8" />
|
||||||
|
<SkuItem DisplayName="Office Publisher 2024" Id="9d3e4cca-e172-46f1-a2f4-1d2107051444" Gvlk="2MW9D-N4BXM-9VBPG-Q7W6M-KFBGQ" />
|
||||||
|
<SkuItem DisplayName="Office Skype for Business 2024" Id="734c6c6e-b0ba-4298-a891-671772b2bd1b" Gvlk="4NKHF-9HBQF-Q3B6C-7YV34-F64P3" />
|
||||||
|
<SkuItem DisplayName="Office Standard 2024" Id="6912a74b-a5fb-401a-bfdb-2e3ab46f4b02" Gvlk="V28N4-JG22K-W66P8-VTMGK-H6HGR" />
|
||||||
|
<SkuItem DisplayName="Office Visio Professional 2024" Id="fa187091-8246-47b1-964f-80a0b1e5d69a" Gvlk="B7TN8-FJ8V3-7QYCP-HQPMV-YY89G" />
|
||||||
|
<SkuItem DisplayName="Office Visio Standard 2024" Id="e06d7df3-aad0-419d-8dfb-0ac37e2bdf39" Gvlk="JMMVY-XFNQC-KK4HK-9H7R3-WQQTV" />
|
||||||
|
<SkuItem DisplayName="Office Word 2024" Id="059834fe-a8ea-4bff-b67b-4d006b5447d3" Gvlk="MQ84N-7VYDM-FXV7C-6K7CC-VFW9J" />
|
||||||
|
</KmsItem>
|
||||||
</AppItem>
|
</AppItem>
|
||||||
|
|
||||||
</AppItems>
|
</AppItems>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
Before Width: | Height: | Size: 524 KiB |
|
@ -4,13 +4,12 @@ import binascii
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
import socket
|
|
||||||
|
|
||||||
from pykms_Structure import Structure
|
from pykms_Structure import Structure
|
||||||
from pykms_DB2Dict import kmsDB2Dict
|
from pykms_DB2Dict import kmsDB2Dict
|
||||||
from pykms_PidGenerator import epidGenerator
|
from pykms_PidGenerator import epidGenerator
|
||||||
from pykms_Filetimes import filetime_to_dt
|
from pykms_Filetimes import filetime_to_dt
|
||||||
from pykms_Sql import sql_initialize, sql_update, sql_update_epid
|
from pykms_Sql import sql_update, sql_update_epid
|
||||||
from pykms_Format import justify, byterize, enco, deco, pretty_printer
|
from pykms_Format import justify, byterize, enco, deco, pretty_printer
|
||||||
|
|
||||||
#--------------------------------------------------------------------------------------------------------------------------------------------------------
|
#--------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -119,19 +118,24 @@ class kmsBase:
|
||||||
|
|
||||||
# Localize the request time, if module "tzlocal" is available.
|
# Localize the request time, if module "tzlocal" is available.
|
||||||
try:
|
try:
|
||||||
|
from datetime import datetime
|
||||||
from tzlocal import get_localzone
|
from tzlocal import get_localzone
|
||||||
from pytz.exceptions import UnknownTimeZoneError
|
from pytz.exceptions import UnknownTimeZoneError
|
||||||
try:
|
try:
|
||||||
tz = get_localzone()
|
local_dt = datetime.fromisoformat(str(requestDatetime)).astimezone(get_localzone())
|
||||||
local_dt = tz.localize(requestDatetime)
|
|
||||||
except UnknownTimeZoneError:
|
except UnknownTimeZoneError:
|
||||||
pretty_printer(log_obj = loggersrv.warning,
|
pretty_printer(log_obj = loggersrv.warning,
|
||||||
put_text = "{reverse}{yellow}{bold}Unknown time zone ! Request time not localized.{end}")
|
put_text = "{reverse}{yellow}{bold}Unknown time zone ! Request time not localized.{end}")
|
||||||
local_dt = requestDatetime
|
local_dt = requestDatetime
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pretty_printer(log_obj = loggersrv.warning,
|
pretty_printer(log_obj = loggersrv.warning,
|
||||||
put_text = "{reverse}{yellow}{bold}Module 'tzlocal' not available ! Request time not localized.{end}")
|
put_text = "{reverse}{yellow}{bold}Module 'tzlocal' or 'pytz' not available ! Request time not localized.{end}")
|
||||||
local_dt = requestDatetime
|
local_dt = requestDatetime
|
||||||
|
except Exception as e:
|
||||||
|
# Just in case something else goes wrong
|
||||||
|
loggersrv.warning('Okay, something went horribly wrong while localizing the request time (proceeding anyways): ' + str(e))
|
||||||
|
local_dt = requestDatetime
|
||||||
|
pass
|
||||||
|
|
||||||
# Activation threshold.
|
# Activation threshold.
|
||||||
# https://docs.microsoft.com/en-us/windows/deployment/volume-activation/activate-windows-10-clients-vamt
|
# https://docs.microsoft.com/en-us/windows/deployment/volume-activation/activate-windows-10-clients-vamt
|
||||||
|
@ -161,6 +165,7 @@ could be detected as not genuine !{end}" %currentClientCount)
|
||||||
|
|
||||||
# Get a name for SkuId, AppId.
|
# Get a name for SkuId, AppId.
|
||||||
kmsdb = kmsDB2Dict()
|
kmsdb = kmsDB2Dict()
|
||||||
|
appName, skuName = str(applicationId), str(skuId)
|
||||||
|
|
||||||
appitems = kmsdb[2]
|
appitems = kmsdb[2]
|
||||||
for appitem in appitems:
|
for appitem in appitems:
|
||||||
|
@ -208,7 +213,6 @@ could be detected as not genuine !{end}" %currentClientCount)
|
||||||
'product' : infoDict["skuId"]})
|
'product' : infoDict["skuId"]})
|
||||||
# Create database.
|
# Create database.
|
||||||
if self.srv_config['sqlite']:
|
if self.srv_config['sqlite']:
|
||||||
sql_initialize(self.srv_config['sqlite'])
|
|
||||||
sql_update(self.srv_config['sqlite'], infoDict)
|
sql_update(self.srv_config['sqlite'], infoDict)
|
||||||
|
|
||||||
return self.createKmsResponse(kmsRequest, currentClientCount, appName)
|
return self.createKmsResponse(kmsRequest, currentClientCount, appName)
|
||||||
|
|
|
@ -13,7 +13,13 @@ import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import pykms_RpcBind, pykms_RpcRequest
|
import dns.message
|
||||||
|
import dns.rdataclass
|
||||||
|
import dns.rdatatype
|
||||||
|
import dns.query
|
||||||
|
import dns.resolver
|
||||||
|
|
||||||
|
import pykms_RpcBind, pykms_RpcRequest
|
||||||
from pykms_Filetimes import dt_to_filetime
|
from pykms_Filetimes import dt_to_filetime
|
||||||
from pykms_Dcerpc import MSRPCHeader, MSRPCBindNak, MSRPCRequestHeader, MSRPCRespHeader
|
from pykms_Dcerpc import MSRPCHeader, MSRPCBindNak, MSRPCRequestHeader, MSRPCRespHeader
|
||||||
from pykms_Base import kmsBase, UUID
|
from pykms_Base import kmsBase, UUID
|
||||||
|
@ -39,10 +45,9 @@ class client_thread(threading.Thread):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.with_gui = False
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
clt_main(with_gui = self.with_gui)
|
clt_main()
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -50,7 +55,7 @@ loggerclt = logging.getLogger('logclt')
|
||||||
|
|
||||||
# 'help' string - 'default' value - 'dest' string.
|
# 'help' string - 'default' value - 'dest' string.
|
||||||
clt_options = {
|
clt_options = {
|
||||||
'ip' : {'help' : 'The IP address or hostname of the KMS server.', 'def' : "0.0.0.0", 'des' : "ip"},
|
'ip' : {'help' : 'The IP address or hostname of the KMS server.', 'def' : "::", 'des' : "ip"},
|
||||||
'port' : {'help' : 'The port the KMS service is listening on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
|
'port' : {'help' : 'The port the KMS service is listening on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
|
||||||
'mode' : {'help' : 'Use this flag to manually specify a Microsoft product for testing the server. The default is \"Windows81\"',
|
'mode' : {'help' : 'Use this flag to manually specify a Microsoft product for testing the server. The default is \"Windows81\"',
|
||||||
'def' : "Windows8.1", 'des' : "mode",
|
'def' : "Windows8.1", 'des' : "mode",
|
||||||
|
@ -72,6 +77,7 @@ Type \"STDOUT\" to view log info on stdout. Type \"FILESTDOUT\" to combine previ
|
||||||
Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to create logfile.',
|
Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to create logfile.',
|
||||||
'def' : os.path.join('.', 'pykms_logclient.log'), 'des' : "logfile"},
|
'def' : os.path.join('.', 'pykms_logclient.log'), 'des' : "logfile"},
|
||||||
'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.', 'def' : 0, 'des': "logsize"},
|
'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.', 'def' : 0, 'des': "logsize"},
|
||||||
|
'discovery' : {'help': 'ask the client to perform a _vlmcs._tcp.domain.tld DNS request to set KMS server.', 'def': None , 'des': 'discovery' },
|
||||||
}
|
}
|
||||||
|
|
||||||
def client_options():
|
def client_options():
|
||||||
|
@ -99,6 +105,8 @@ def client_options():
|
||||||
default = clt_options['lfile']['def'], help = clt_options['lfile']['help'], type = str)
|
default = clt_options['lfile']['def'], help = clt_options['lfile']['help'], type = str)
|
||||||
client_parser.add_argument("-S", "--logsize", dest = clt_options['lsize']['des'], action = "store",
|
client_parser.add_argument("-S", "--logsize", dest = clt_options['lsize']['des'], action = "store",
|
||||||
default = clt_options['lsize']['def'], help = clt_options['lsize']['help'], type = float)
|
default = clt_options['lsize']['def'], help = clt_options['lsize']['help'], type = float)
|
||||||
|
client_parser.add_argument("-D", "--discovery", dest = clt_options['discovery']['des'], action = "store",
|
||||||
|
default = clt_options['discovery']['def'], help = clt_options['discovery']['help'], type = str)
|
||||||
|
|
||||||
client_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
client_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
||||||
|
|
||||||
|
@ -156,18 +164,25 @@ def client_check():
|
||||||
def client_update():
|
def client_update():
|
||||||
kmsdb = kmsDB2Dict()
|
kmsdb = kmsDB2Dict()
|
||||||
|
|
||||||
|
loggerclt.debug(f'Searching in kms database for machine "{clt_config["mode"]}"...')
|
||||||
|
|
||||||
appitems = kmsdb[2]
|
appitems = kmsdb[2]
|
||||||
for appitem in appitems:
|
for appitem in appitems:
|
||||||
kmsitems = appitem['KmsItems']
|
kmsitems = appitem['KmsItems']
|
||||||
for kmsitem in kmsitems:
|
for kmsitem in kmsitems:
|
||||||
name = re.sub('\(.*\)', '', kmsitem['DisplayName']).replace('2015', '').replace(' ', '')
|
name = re.sub('\(.*\)', '', kmsitem['DisplayName']) # Remove bracets
|
||||||
|
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
|
||||||
if name == clt_config['mode']:
|
if name == clt_config['mode']:
|
||||||
skuitems = kmsitem['SkuItems']
|
skuitems = kmsitem['SkuItems']
|
||||||
# Select 'Enterprise' for Windows or 'Professional Plus' for Office.
|
# Select 'Enterprise' for Windows or 'Professional Plus' for Office.
|
||||||
for skuitem in skuitems:
|
for skuitem in skuitems:
|
||||||
if skuitem['DisplayName'].replace(' ','') == name + 'Enterprise' or \
|
sName = skuitem['DisplayName']
|
||||||
skuitem['DisplayName'].replace(' ','') == name[:6] + 'ProfessionalPlus' + name[6:]:
|
sName = sName.replace(' ', '') # Ignore whitespaces
|
||||||
|
sName = sName.replace('/11', '', 1) # Cut out Windows 11, as it is basically Windows 10
|
||||||
|
if sName == name + 'Enterprise' or \
|
||||||
|
sName == name[:6] + 'ProfessionalPlus' + name[6:]:
|
||||||
clt_config['KMSClientSkuID'] = skuitem['Id']
|
clt_config['KMSClientSkuID'] = skuitem['Id']
|
||||||
clt_config['RequiredClientCount'] = int(kmsitem['NCountPolicy'])
|
clt_config['RequiredClientCount'] = int(kmsitem['NCountPolicy'])
|
||||||
clt_config['KMSProtocolMajorVersion'] = int(float(kmsitem['DefaultKmsProtocol']))
|
clt_config['KMSProtocolMajorVersion'] = int(float(kmsitem['DefaultKmsProtocol']))
|
||||||
|
@ -175,9 +190,25 @@ def client_update():
|
||||||
clt_config['KMSClientLicenseStatus'] = 2
|
clt_config['KMSClientLicenseStatus'] = 2
|
||||||
clt_config['KMSClientAppID'] = appitem['Id']
|
clt_config['KMSClientAppID'] = appitem['Id']
|
||||||
clt_config['KMSClientKMSCountedID'] = kmsitem['Id']
|
clt_config['KMSClientKMSCountedID'] = kmsitem['Id']
|
||||||
break
|
return
|
||||||
|
raise RuntimeError(f'Client failed to find machine configuration in kms database - make sure it contains an entry for "{clt_config["mode"]}"')
|
||||||
|
|
||||||
def client_connect():
|
def client_connect():
|
||||||
|
|
||||||
|
if clt_config['discovery'] is not None:
|
||||||
|
loggerclt.info(f'Using Domain: {clt_config["discovery"]}')
|
||||||
|
r= None
|
||||||
|
try:
|
||||||
|
r = dns.resolver.resolve('_vlmcs._tcp.' + clt_config['discovery'], dns.rdatatype.SRV)
|
||||||
|
for a in r:
|
||||||
|
loggerclt.debug(f'answer KMS server: {a.target} , port: {a.port}')
|
||||||
|
clt_config['ip'] = socket.gethostbyname(r[0].target.to_text())
|
||||||
|
clt_config['port'] = r[0].port
|
||||||
|
except (dns.exception.Timeout, dns.resolver.NXDOMAIN) as e:
|
||||||
|
pretty_printer(log_obj = loggerclt.warning,
|
||||||
|
put_text = "{reverse}{red}{bold}Cannot resolve '%s'. Error: '%s'...{end}" %(clt_config['discovery'],
|
||||||
|
str(e)))
|
||||||
|
|
||||||
loggerclt.info("Connecting to %s on port %d" % (clt_config['ip'], clt_config['port']))
|
loggerclt.info("Connecting to %s on port %d" % (clt_config['ip'], clt_config['port']))
|
||||||
try:
|
try:
|
||||||
clt_sock = socket.create_connection((clt_config['ip'], clt_config['port']), timeout = clt_config['timeoutidle'])
|
clt_sock = socket.create_connection((clt_config['ip'], clt_config['port']), timeout = clt_config['timeoutidle'])
|
||||||
|
@ -265,11 +296,10 @@ def client_create(clt_sock):
|
||||||
pretty_printer(log_obj = loggerclt.warning, to_exit = True, where = "clt",
|
pretty_printer(log_obj = loggerclt.warning, to_exit = True, where = "clt",
|
||||||
put_text = "{reverse}{magenta}{bold}Something went wrong. Exiting...{end}")
|
put_text = "{reverse}{magenta}{bold}Something went wrong. Exiting...{end}")
|
||||||
|
|
||||||
def clt_main(with_gui = False):
|
def clt_main():
|
||||||
try:
|
try:
|
||||||
if not with_gui:
|
# Parse options.
|
||||||
# Parse options.
|
client_options()
|
||||||
client_options()
|
|
||||||
|
|
||||||
# Check options.
|
# Check options.
|
||||||
client_check()
|
client_check()
|
||||||
|
@ -361,4 +391,4 @@ def readKmsResponseV6(data):
|
||||||
return message
|
return message
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
clt_main(with_gui = False)
|
clt_main()
|
||||||
|
|
|
@ -4,6 +4,9 @@ import os
|
||||||
import socket
|
import socket
|
||||||
import selectors
|
import selectors
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import logging
|
||||||
|
from pykms_Format import pretty_printer
|
||||||
|
loggersrv = logging.getLogger('logsrv')
|
||||||
|
|
||||||
# https://github.com/python/cpython/blob/master/Lib/socket.py
|
# https://github.com/python/cpython/blob/master/Lib/socket.py
|
||||||
def has_dualstack_ipv6():
|
def has_dualstack_ipv6():
|
||||||
|
@ -27,12 +30,13 @@ def create_server_sock(address, *, family = socket.AF_INET, backlog = None, reus
|
||||||
|
|
||||||
*family* should be either AF_INET or AF_INET6.
|
*family* should be either AF_INET or AF_INET6.
|
||||||
*backlog* is the queue size passed to socket.listen().
|
*backlog* is the queue size passed to socket.listen().
|
||||||
*reuse_port* dictates whether to use the SO_REUSEPORT socket option.
|
*reuse_port* if True and the platform supports it, we will use the SO_REUSEPORT socket option.
|
||||||
*dualstack_ipv6* if True and the platform supports it, it will create an AF_INET6 socket able to accept both IPv4 or IPv6 connections;
|
*dualstack_ipv6* if True and the platform supports it, it will create an AF_INET6 socket able to accept both IPv4 or IPv6 connections;
|
||||||
when False it will explicitly disable this option on platforms that enable it by default (e.g. Linux).
|
when False it will explicitly disable this option on platforms that enable it by default (e.g. Linux).
|
||||||
"""
|
"""
|
||||||
if reuse_port and not hasattr(socket._socket, "SO_REUSEPORT"):
|
if reuse_port and not hasattr(socket._socket, "SO_REUSEPORT"):
|
||||||
raise ValueError("SO_REUSEPORT not supported on this platform")
|
pretty_printer(log_obj = loggersrv.warning, put_text = "{reverse}{yellow}{bold}SO_REUSEPORT not supported on this platform - ignoring socket option.{end}")
|
||||||
|
reuse_port = False
|
||||||
|
|
||||||
if dualstack_ipv6:
|
if dualstack_ipv6:
|
||||||
if not has_dualstack_ipv6():
|
if not has_dualstack_ipv6():
|
||||||
|
|
|
@ -274,9 +274,7 @@ class ShellMessage(object):
|
||||||
ShellMessage.indx += 1
|
ShellMessage.indx += 1
|
||||||
|
|
||||||
def print_logging_setup(self, logger, async_flag, formatter = logging.Formatter('%(name)s %(message)s')):
|
def print_logging_setup(self, logger, async_flag, formatter = logging.Formatter('%(name)s %(message)s')):
|
||||||
from pykms_GuiBase import gui_redirector
|
handler = logging.StreamHandler(StringIO())
|
||||||
stream = gui_redirector(StringIO())
|
|
||||||
handler = logging.StreamHandler(stream)
|
|
||||||
handler.name = 'LogStream'
|
handler.name = 'LogStream'
|
||||||
handler.setLevel(logging.INFO)
|
handler.setLevel(logging.INFO)
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
|
@ -293,9 +291,6 @@ class ShellMessage(object):
|
||||||
|
|
||||||
def print_logging(self, toprint):
|
def print_logging(self, toprint):
|
||||||
if (self.nshell and ((0 in self.nshell) or (2 in self.nshell and not ShellMessage.viewclt))) or ShellMessage.indx == 0:
|
if (self.nshell and ((0 in self.nshell) or (2 in self.nshell and not ShellMessage.viewclt))) or ShellMessage.indx == 0:
|
||||||
from pykms_GuiBase import gui_redirector_setup, gui_redirector_clear
|
|
||||||
gui_redirector_setup()
|
|
||||||
gui_redirector_clear()
|
|
||||||
self.print_logging_setup(ShellMessage.loggersrv_pty, ShellMessage.asyncmsgsrv)
|
self.print_logging_setup(ShellMessage.loggersrv_pty, ShellMessage.asyncmsgsrv)
|
||||||
self.print_logging_setup(ShellMessage.loggerclt_pty, ShellMessage.asyncmsgclt)
|
self.print_logging_setup(ShellMessage.loggerclt_pty, ShellMessage.asyncmsgclt)
|
||||||
|
|
||||||
|
@ -405,7 +400,6 @@ def pretty_printer(**kwargs):
|
||||||
if None `put_text` must be defined for printing process.
|
if None `put_text` must be defined for printing process.
|
||||||
`to_exit ` --> if True system exit is called.
|
`to_exit ` --> if True system exit is called.
|
||||||
`where` --> specifies if message is server-side or client-side
|
`where` --> specifies if message is server-side or client-side
|
||||||
(useful for GUI redirect).
|
|
||||||
"""
|
"""
|
||||||
# Set defaults for not defined options.
|
# Set defaults for not defined options.
|
||||||
options = {'log_obj' : None,
|
options = {'log_obj' : None,
|
||||||
|
|
|
@ -1,948 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
from time import sleep
|
|
||||||
import tkinter as tk
|
|
||||||
from tkinter import ttk
|
|
||||||
from tkinter import messagebox
|
|
||||||
from tkinter import filedialog
|
|
||||||
import tkinter.font as tkFont
|
|
||||||
|
|
||||||
from pykms_Server import srv_options, srv_version, srv_config, server_terminate, serverqueue, serverthread
|
|
||||||
from pykms_GuiMisc import ToolTip, TextDoubleScroll, TextRedirect, ListboxOfRadiobuttons
|
|
||||||
from pykms_GuiMisc import custom_background, custom_pages
|
|
||||||
from pykms_Client import clt_options, clt_version, clt_config, client_thread
|
|
||||||
|
|
||||||
gui_version = "py-kms_gui_v3.0"
|
|
||||||
__license__ = "MIT License"
|
|
||||||
__author__ = u"Matteo ℱan <SystemRage@protonmail.com>"
|
|
||||||
__copyright__ = "© Copyright 2020"
|
|
||||||
__url__ = "https://github.com/SystemRage/py-kms"
|
|
||||||
gui_description = "A GUI for py-kms."
|
|
||||||
|
|
||||||
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
def get_ip_address():
|
|
||||||
if os.name == 'posix':
|
|
||||||
import subprocess
|
|
||||||
ip = subprocess.getoutput("hostname -I")
|
|
||||||
elif os.name == 'nt':
|
|
||||||
import socket
|
|
||||||
ip = socket.gethostbyname(socket.gethostname())
|
|
||||||
else:
|
|
||||||
ip = 'Unknown'
|
|
||||||
return ip
|
|
||||||
|
|
||||||
def gui_redirector(stream, redirect_to = TextRedirect.Pretty, redirect_conditio = True, stderr_side = "srv"):
|
|
||||||
global txsrv, txclt, txcol
|
|
||||||
if redirect_conditio:
|
|
||||||
if stream == 'stdout':
|
|
||||||
sys.stdout = redirect_to(txsrv, txclt, txcol)
|
|
||||||
elif stream == 'stderr':
|
|
||||||
sys.stderr = redirect_to(txsrv, txclt, txcol, stderr_side)
|
|
||||||
else:
|
|
||||||
stream = redirect_to(txsrv, txclt, txcol)
|
|
||||||
return stream
|
|
||||||
|
|
||||||
def gui_redirector_setup():
|
|
||||||
TextRedirect.Pretty.tag_num = 0
|
|
||||||
TextRedirect.Pretty.newlinecut = [-1, -2, -4, -5]
|
|
||||||
|
|
||||||
def gui_redirector_clear():
|
|
||||||
global txsrv, oysrv
|
|
||||||
try:
|
|
||||||
if oysrv:
|
|
||||||
txsrv.configure(state = 'normal')
|
|
||||||
txsrv.delete('1.0', 'end')
|
|
||||||
txsrv.configure(state = 'disabled')
|
|
||||||
except:
|
|
||||||
# self.onlysrv not defined (menu not used)
|
|
||||||
pass
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class KmsGui(tk.Tk):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
tk.Tk.__init__(self, *args, **kwargs)
|
|
||||||
self.wraplength = 200
|
|
||||||
serverthread.with_gui = True
|
|
||||||
self.validation_int = (self.register(self.validate_int), "%S")
|
|
||||||
self.validation_float = (self.register(self.validate_float), "%P")
|
|
||||||
|
|
||||||
## Define fonts and colors.
|
|
||||||
self.customfonts = {'btn' : tkFont.Font(family = 'Fixedsys', size = 11, weight = 'bold'),
|
|
||||||
'oth' : tkFont.Font(family = 'Times', size = 9, weight = 'bold'),
|
|
||||||
'opt' : tkFont.Font(family = 'Fixedsys', size = 9, weight = 'bold'),
|
|
||||||
'lst' : tkFont.Font(family = 'Fixedsys', size = 8, weight = 'bold', slant = 'italic'),
|
|
||||||
'msg' : tkFont.Font(family = 'Monospace', size = 6), # need a monospaced type (like courier, etc..).
|
|
||||||
}
|
|
||||||
|
|
||||||
self.customcolors = { 'black' : '#000000',
|
|
||||||
'white' : '#FFFFFF',
|
|
||||||
'green' : '#00EE76',
|
|
||||||
'yellow' : '#FFFF00',
|
|
||||||
'magenta' : '#CD00CD',
|
|
||||||
'orange' : '#FFA500',
|
|
||||||
'red' : '#FF4500',
|
|
||||||
'blue' : '#1E90FF',
|
|
||||||
'cyan' : '#AFEEEE',
|
|
||||||
'lavender': '#E6E6FA',
|
|
||||||
'brown' : '#A52A2A',
|
|
||||||
}
|
|
||||||
|
|
||||||
self.option_add('*TCombobox*Listbox.font', self.customfonts['lst'])
|
|
||||||
|
|
||||||
self.gui_create()
|
|
||||||
|
|
||||||
def invert(self, widgets = []):
|
|
||||||
for widget in widgets:
|
|
||||||
if widget['state'] == 'normal':
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
elif widget['state'] == 'disabled':
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
|
|
||||||
def gui_menu(self):
|
|
||||||
self.onlysrv, self.onlyclt = (False for _ in range(2))
|
|
||||||
menubar = tk.Menu(self)
|
|
||||||
prefmenu = tk.Menu(menubar, tearoff = 0, font = ("Noto Sans Regular", 10), borderwidth = 3, relief = 'ridge')
|
|
||||||
menubar.add_cascade(label = 'Preferences', menu = prefmenu)
|
|
||||||
prefmenu.add_command(label = 'Enable server-side mode', command = lambda: self.pref_onlysrv(prefmenu))
|
|
||||||
prefmenu.add_command(label = 'Enable client-side mode', command = lambda: self.pref_onlyclt(prefmenu))
|
|
||||||
self.config(menu = menubar)
|
|
||||||
|
|
||||||
def pref_onlysrv(self, menu):
|
|
||||||
global oysrv
|
|
||||||
|
|
||||||
if self.onlyclt or serverthread.is_running_server:
|
|
||||||
return
|
|
||||||
self.onlysrv = not self.onlysrv
|
|
||||||
if self.onlysrv:
|
|
||||||
menu.entryconfigure(0, label = 'Disable server-side mode')
|
|
||||||
self.clt_on_show(force_remove = True)
|
|
||||||
else:
|
|
||||||
menu.entryconfigure(0, label = 'Enable server-side mode')
|
|
||||||
self.invert(widgets = [self.shbtnclt])
|
|
||||||
oysrv = self.onlysrv
|
|
||||||
|
|
||||||
def pref_onlyclt(self, menu):
|
|
||||||
if self.onlysrv or serverthread.is_running_server:
|
|
||||||
return
|
|
||||||
self.onlyclt = not self.onlyclt
|
|
||||||
if self.onlyclt:
|
|
||||||
menu.entryconfigure(1, label = 'Disable client-side mode')
|
|
||||||
if self.shbtnclt['text'] == 'SHOW\nCLIENT':
|
|
||||||
self.clt_on_show(force_view = True)
|
|
||||||
self.optsrvwin.grid_remove()
|
|
||||||
self.msgsrvwin.grid_remove()
|
|
||||||
gui_redirector('stderr', redirect_to = TextRedirect.Stderr, stderr_side = "clt")
|
|
||||||
else:
|
|
||||||
menu.entryconfigure(1, label = 'Enable client-side mode')
|
|
||||||
self.optsrvwin.grid()
|
|
||||||
self.msgsrvwin.grid()
|
|
||||||
gui_redirector('stderr', redirect_to = TextRedirect.Stderr)
|
|
||||||
|
|
||||||
self.invert(widgets = [self.runbtnsrv, self.shbtnclt, self.runbtnclt])
|
|
||||||
|
|
||||||
def gui_create(self):
|
|
||||||
## Create server gui
|
|
||||||
self.gui_srv()
|
|
||||||
## Create client gui + other operations.
|
|
||||||
self.gui_complete()
|
|
||||||
## Create menu.
|
|
||||||
self.gui_menu()
|
|
||||||
## Create globals for printing process (redirect stdout).
|
|
||||||
global txsrv, txclt, txcol
|
|
||||||
txsrv = self.textboxsrv.get()
|
|
||||||
txclt = self.textboxclt.get()
|
|
||||||
txcol = self.customcolors
|
|
||||||
## Redirect stderr.
|
|
||||||
gui_redirector('stderr', redirect_to = TextRedirect.Stderr)
|
|
||||||
|
|
||||||
def gui_pages_show(self, pagename, side):
|
|
||||||
# https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter
|
|
||||||
# https://www.reddit.com/r/learnpython/comments/7xxtsy/trying_to_understand_tkinter_and_how_to_switch/
|
|
||||||
pageside = self.pagewidgets[side]
|
|
||||||
tk.Misc.lift(pageside["PageWin"][pagename], aboveThis = None)
|
|
||||||
keylist = list(pageside["PageWin"].keys())
|
|
||||||
|
|
||||||
for elem in [pageside["BtnAni"], pageside["LblAni"]]:
|
|
||||||
if pagename == "PageStart":
|
|
||||||
elem["Left"].config(state = "disabled")
|
|
||||||
if len(keylist) == 2:
|
|
||||||
elem["Right"].config(state = "normal")
|
|
||||||
elif pagename == "PageEnd":
|
|
||||||
elem["Right"].config(state = "disabled")
|
|
||||||
if len(keylist) == 2:
|
|
||||||
elem["Left"].config(state = "normal")
|
|
||||||
else:
|
|
||||||
for where in ["Left", "Right"]:
|
|
||||||
elem[where].config(state = "normal")
|
|
||||||
|
|
||||||
if pagename != "PageStart":
|
|
||||||
page_l = keylist[keylist.index(pagename) - 1]
|
|
||||||
pageside["BtnAni"]["Left"]['command'] = lambda pag=page_l, pos=side: self.gui_pages_show(pag, pos)
|
|
||||||
if pagename != "PageEnd":
|
|
||||||
page_r = keylist[keylist.index(pagename) + 1]
|
|
||||||
pageside["BtnAni"]["Right"]['command'] = lambda pag=page_r, pos=side: self.gui_pages_show(pag, pos)
|
|
||||||
|
|
||||||
def gui_pages_buttons(self, parent, side):
|
|
||||||
btnwin = tk.Canvas(parent, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
btnwin.grid(row = 14, column = 2, padx = 2, pady = 2, sticky = 'nsew')
|
|
||||||
btnwin.grid_columnconfigure(1, weight = 1)
|
|
||||||
self.pagewidgets[side]["BtnWin"] = btnwin
|
|
||||||
|
|
||||||
for position in ["Left", "Right"]:
|
|
||||||
if position == "Left":
|
|
||||||
col = [0, 0, 1]
|
|
||||||
stick = 'e'
|
|
||||||
elif position == "Right":
|
|
||||||
col = [2, 1, 0]
|
|
||||||
stick = 'w'
|
|
||||||
|
|
||||||
aniwin = tk.Canvas(btnwin, background = self.customcolors['white'], borderwidth = 0, relief = 'ridge')
|
|
||||||
aniwin.grid(row = 0, column = col[0], padx = 5, pady = 5, sticky = 'nsew')
|
|
||||||
self.pagewidgets[side]["AniWin"][position] = aniwin
|
|
||||||
|
|
||||||
lblani = tk.Label(aniwin, width = 1, height = 1)
|
|
||||||
lblani.grid(row = 0, column = col[1], padx = 2, pady = 2, sticky = stick)
|
|
||||||
self.pagewidgets[side]["LblAni"][position] = lblani
|
|
||||||
|
|
||||||
btnani = tk.Button(aniwin)
|
|
||||||
btnani.grid(row = 0, column = col[2], padx = 2, pady = 2, sticky = stick)
|
|
||||||
self.pagewidgets[side]["BtnAni"][position] = btnani
|
|
||||||
## Customize buttons.
|
|
||||||
custom_pages(self, side)
|
|
||||||
|
|
||||||
def gui_pages_create(self, parent, side, create = {}):
|
|
||||||
self.pagewidgets.update({side : {"PageWin" : create,
|
|
||||||
"BtnWin" : None,
|
|
||||||
"BtnAni" : {"Left" : None,
|
|
||||||
"Right" : None},
|
|
||||||
"AniWin" : {"Left" : None,
|
|
||||||
"Right" : None},
|
|
||||||
"LblAni" : {"Left" : None,
|
|
||||||
"Right" : None},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for pagename in self.pagewidgets[side]["PageWin"].keys():
|
|
||||||
page = tk.Canvas(parent, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
self.pagewidgets[side]["PageWin"][pagename] = page
|
|
||||||
page.grid(row = 0, column = 2, padx = 2, pady = 2, sticky = "nsew")
|
|
||||||
page.grid_columnconfigure(1, weight = 1)
|
|
||||||
self.gui_pages_buttons(parent = parent, side = side)
|
|
||||||
self.gui_pages_show("PageStart", side = side)
|
|
||||||
|
|
||||||
def gui_store(self, side, typewidgets):
|
|
||||||
stored = []
|
|
||||||
for pagename in self.pagewidgets[side]["PageWin"].keys():
|
|
||||||
for widget in self.pagewidgets[side]["PageWin"][pagename].winfo_children():
|
|
||||||
if widget.winfo_class() in typewidgets:
|
|
||||||
stored.append(widget)
|
|
||||||
return stored
|
|
||||||
|
|
||||||
def gui_srv(self):
|
|
||||||
## Create main containers. ------------------------------------------------------------------------------------------------------------------
|
|
||||||
self.masterwin = tk.Canvas(self, borderwidth = 3, relief = tk.RIDGE)
|
|
||||||
self.btnsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
self.optsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
self.msgsrvwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200)
|
|
||||||
|
|
||||||
## Layout main containers.
|
|
||||||
self.masterwin.grid(row = 0, column = 0, sticky = 'nsew')
|
|
||||||
self.btnsrvwin.grid(row = 0, column = 1, padx = 2, pady = 2, sticky = 'nw')
|
|
||||||
self.optsrvwin.grid(row = 0, column = 2, padx = 2, pady = 2, sticky = 'nsew')
|
|
||||||
self.optsrvwin.grid_rowconfigure(0, weight = 1)
|
|
||||||
self.optsrvwin.grid_columnconfigure(1, weight = 1)
|
|
||||||
|
|
||||||
self.pagewidgets = {}
|
|
||||||
|
|
||||||
## Subpages of "optsrvwin".
|
|
||||||
self.gui_pages_create(parent = self.optsrvwin, side = "Srv", create = {"PageStart": None,
|
|
||||||
"PageEnd": None})
|
|
||||||
|
|
||||||
## Continue to grid.
|
|
||||||
self.msgsrvwin.grid(row = 1, column = 2, padx = 1, pady = 1, sticky = 'nsew')
|
|
||||||
self.msgsrvwin.grid_propagate(False)
|
|
||||||
self.msgsrvwin.grid_columnconfigure(0, weight = 1)
|
|
||||||
self.msgsrvwin.grid_rowconfigure(0, weight = 1)
|
|
||||||
|
|
||||||
## Create widgets (btnsrvwin) ---------------------------------------------------------------------------------------------------------------
|
|
||||||
self.statesrv = tk.Label(self.btnsrvwin, text = 'Server\nState:\nStopped', font = self.customfonts['oth'],
|
|
||||||
foreground = self.customcolors['red'])
|
|
||||||
self.runbtnsrv = tk.Button(self.btnsrvwin, text = 'START\nSERVER', background = self.customcolors['green'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = self.srv_on_start)
|
|
||||||
self.shbtnclt = tk.Button(self.btnsrvwin, text = 'SHOW\nCLIENT', background = self.customcolors['magenta'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = self.clt_on_show)
|
|
||||||
self.defaubtnsrv = tk.Button(self.btnsrvwin, text = 'DEFAULTS', background = self.customcolors['brown'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = self.on_defaults)
|
|
||||||
self.clearbtnsrv = tk.Button(self.btnsrvwin, text = 'CLEAR', background = self.customcolors['orange'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = lambda: self.on_clear([txsrv, txclt]))
|
|
||||||
self.exitbtnsrv = tk.Button(self.btnsrvwin, text = 'EXIT', background = self.customcolors['black'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = self.on_exit)
|
|
||||||
|
|
||||||
## Layout widgets (btnsrvwin)
|
|
||||||
self.statesrv.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.runbtnsrv.grid(row = 1, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.shbtnclt.grid(row = 2, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.defaubtnsrv.grid(row = 3, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.clearbtnsrv.grid(row = 4, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.exitbtnsrv.grid(row = 5, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
|
|
||||||
## Create widgets (optsrvwin:Srv:PageWin:PageStart) -----------------------------------------------------------------------------------------
|
|
||||||
# Version.
|
|
||||||
ver = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"],
|
|
||||||
text = 'You are running server version: ' + srv_version, font = self.customfonts['oth'],
|
|
||||||
foreground = self.customcolors['red'])
|
|
||||||
# Ip Address.
|
|
||||||
srvipaddlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'IP Address: ', font = self.customfonts['opt'])
|
|
||||||
self.srvipadd = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'ip')
|
|
||||||
self.srvipadd.insert('end', srv_options['ip']['def'])
|
|
||||||
ToolTip(self.srvipadd, text = srv_options['ip']['help'], wraplength = self.wraplength)
|
|
||||||
myipadd = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Your IP address is: {}'.format(get_ip_address()),
|
|
||||||
font = self.customfonts['oth'], foreground = self.customcolors['red'])
|
|
||||||
# Port.
|
|
||||||
srvportlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Port: ', font = self.customfonts['opt'])
|
|
||||||
self.srvport = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'port',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.srvport.insert('end', str(srv_options['port']['def']))
|
|
||||||
ToolTip(self.srvport, text = srv_options['port']['help'], wraplength = self.wraplength)
|
|
||||||
# EPID.
|
|
||||||
epidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'EPID: ', font = self.customfonts['opt'])
|
|
||||||
self.epid = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'epid')
|
|
||||||
self.epid.insert('end', str(srv_options['epid']['def']))
|
|
||||||
ToolTip(self.epid, text = srv_options['epid']['help'], wraplength = self.wraplength)
|
|
||||||
# LCID.
|
|
||||||
lcidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'LCID: ', font = self.customfonts['opt'])
|
|
||||||
self.lcid = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lcid',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.lcid.insert('end', str(srv_options['lcid']['def']))
|
|
||||||
ToolTip(self.lcid, text = srv_options['lcid']['help'], wraplength = self.wraplength)
|
|
||||||
# HWID.
|
|
||||||
hwidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'HWID: ', font = self.customfonts['opt'])
|
|
||||||
self.hwid = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = (str(srv_options['hwid']['def']), 'RANDOM'),
|
|
||||||
width = 17, height = 10, font = self.customfonts['lst'], name = 'hwid')
|
|
||||||
self.hwid.set(str(srv_options['hwid']['def']))
|
|
||||||
ToolTip(self.hwid, text = srv_options['hwid']['help'], wraplength = self.wraplength)
|
|
||||||
# Client Count
|
|
||||||
countlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Client Count: ', font = self.customfonts['opt'])
|
|
||||||
self.count = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'count')
|
|
||||||
self.count.insert('end', str(srv_options['count']['def']))
|
|
||||||
ToolTip(self.count, text = srv_options['count']['help'], wraplength = self.wraplength)
|
|
||||||
# Activation Interval.
|
|
||||||
activlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Activation Interval: ', font = self.customfonts['opt'])
|
|
||||||
self.activ = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'activation',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.activ.insert('end', str(srv_options['activation']['def']))
|
|
||||||
ToolTip(self.activ, text = srv_options['activation']['help'], wraplength = self.wraplength)
|
|
||||||
# Renewal Interval.
|
|
||||||
renewlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Renewal Interval: ', font = self.customfonts['opt'])
|
|
||||||
self.renew = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'renewal',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.renew.insert('end', str(srv_options['renewal']['def']))
|
|
||||||
ToolTip(self.renew, text = srv_options['renewal']['help'], wraplength = self.wraplength)
|
|
||||||
# Logfile.
|
|
||||||
srvfilelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logfile Path / Name: ', font = self.customfonts['opt'])
|
|
||||||
self.srvfile = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lfile')
|
|
||||||
self.srvfile.insert('end', srv_options['lfile']['def'])
|
|
||||||
self.srvfile.xview_moveto(1)
|
|
||||||
ToolTip(self.srvfile, text = srv_options['lfile']['help'], wraplength = self.wraplength)
|
|
||||||
srvfilebtnwin = tk.Button(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Browse', font = self.customfonts['opt'],
|
|
||||||
command = lambda: self.on_browse(self.srvfile, srv_options))
|
|
||||||
# Loglevel.
|
|
||||||
srvlevellbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.customfonts['opt'])
|
|
||||||
self.srvlevel = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = tuple(srv_options['llevel']['choi']),
|
|
||||||
width = 10, height = 10, font = self.customfonts['lst'], state = "readonly", name = 'llevel')
|
|
||||||
self.srvlevel.set(srv_options['llevel']['def'])
|
|
||||||
ToolTip(self.srvlevel, text = srv_options['llevel']['help'], wraplength = self.wraplength)
|
|
||||||
# Logsize.
|
|
||||||
srvsizelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.customfonts['opt'])
|
|
||||||
self.srvsize = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lsize',
|
|
||||||
validate = "key", validatecommand = self.validation_float)
|
|
||||||
self.srvsize.insert('end', srv_options['lsize']['def'])
|
|
||||||
ToolTip(self.srvsize, text = srv_options['lsize']['help'], wraplength = self.wraplength)
|
|
||||||
# Asynchronous messages.
|
|
||||||
self.chkvalsrvasy = tk.BooleanVar()
|
|
||||||
self.chkvalsrvasy.set(srv_options['asyncmsg']['def'])
|
|
||||||
chksrvasy = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Async\nMsg',
|
|
||||||
font = self.customfonts['opt'], var = self.chkvalsrvasy, relief = 'groove', name = 'asyncmsg')
|
|
||||||
ToolTip(chksrvasy, text = srv_options['asyncmsg']['help'], wraplength = self.wraplength)
|
|
||||||
|
|
||||||
# Listbox radiobuttons server.
|
|
||||||
self.chksrvfile = ListboxOfRadiobuttons(self.pagewidgets["Srv"]["PageWin"]["PageStart"],
|
|
||||||
['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'],
|
|
||||||
self.customfonts['lst'],
|
|
||||||
changed = [(self.srvfile, srv_options['lfile']['def']),
|
|
||||||
(srvfilebtnwin, ''),
|
|
||||||
(self.srvsize, srv_options['lsize']['def']),
|
|
||||||
(self.srvlevel, srv_options['llevel']['def'])],
|
|
||||||
width = 10, height = 1, borderwidth = 2, relief = 'ridge')
|
|
||||||
|
|
||||||
## Layout widgets (optsrvwin:Srv:PageWin:PageStart)
|
|
||||||
ver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
myipadd.grid(row = 2, column = 1, columnspan = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvportlbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvport.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
epidlbl.grid(row = 4, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.epid.grid(row = 4, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
lcidlbl.grid(row = 5, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.lcid.grid(row = 5, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
hwidlbl.grid(row = 6, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.hwid.grid(row = 6, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
countlbl.grid(row = 7, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.count.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
activlbl.grid(row = 8, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.activ.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
renewlbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.renew.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvfilelbl.grid(row = 10, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvfile.grid(row = 10, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvfilebtnwin.grid(row = 10, column = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
self.chksrvfile.grid(row = 11, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
chksrvasy.grid(row = 11, column = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvlevellbl.grid(row = 12, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvlevel.grid(row = 12, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvsizelbl.grid(row = 13, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvsize.grid(row = 13, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
|
|
||||||
## Create widgets (optsrvwin:Srv:PageWin:PageEnd)-------------------------------------------------------------------------------------------
|
|
||||||
# Timeout connection.
|
|
||||||
srvtimeout0lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.customfonts['opt'])
|
|
||||||
self.srvtimeout0 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time0')
|
|
||||||
self.srvtimeout0.insert('end', str(srv_options['time0']['def']))
|
|
||||||
ToolTip(self.srvtimeout0, text = srv_options['time0']['help'], wraplength = self.wraplength)
|
|
||||||
# Timeout send/recv.
|
|
||||||
srvtimeout1lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout send-recv: ', font = self.customfonts['opt'])
|
|
||||||
self.srvtimeout1 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time1')
|
|
||||||
self.srvtimeout1.insert('end', str(srv_options['time1']['def']))
|
|
||||||
ToolTip(self.srvtimeout1, text = srv_options['time1']['help'], wraplength = self.wraplength)
|
|
||||||
# Sqlite database.
|
|
||||||
self.chkvalsql = tk.BooleanVar()
|
|
||||||
self.chkvalsql.set(srv_options['sql']['def'])
|
|
||||||
self.chkfilesql = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'sql')
|
|
||||||
self.chkfilesql.insert('end', srv_options['sql']['file'])
|
|
||||||
self.chkfilesql.xview_moveto(1)
|
|
||||||
self.chkfilesql.configure(state = 'disabled')
|
|
||||||
|
|
||||||
chksql = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Create Sqlite\nDatabase',
|
|
||||||
font = self.customfonts['opt'], var = self.chkvalsql, relief = 'groove',
|
|
||||||
command = lambda: self.sql_status())
|
|
||||||
ToolTip(chksql, text = srv_options['sql']['help'], wraplength = self.wraplength)
|
|
||||||
|
|
||||||
## Layout widgets (optsrvwin:Srv:PageWin:PageEnd)
|
|
||||||
# a label for vertical aligning with PageStart
|
|
||||||
tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 0,
|
|
||||||
height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw')
|
|
||||||
srvtimeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvtimeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
srvtimeout1lbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvtimeout1.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
chksql.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.chkfilesql.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
|
|
||||||
# Store server-side widgets.
|
|
||||||
self.storewidgets_srv = self.gui_store(side = "Srv", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton'])
|
|
||||||
self.storewidgets_srv.append(self.chksrvfile)
|
|
||||||
|
|
||||||
## Create widgets and layout (msgsrvwin) ---------------------------------------------------------------------------------------------------
|
|
||||||
self.textboxsrv = TextDoubleScroll(self.msgsrvwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled',
|
|
||||||
relief = 'ridge', font = self.customfonts['msg'])
|
|
||||||
self.textboxsrv.put()
|
|
||||||
|
|
||||||
def sql_status(self):
|
|
||||||
if self.chkvalsql.get():
|
|
||||||
self.chkfilesql.configure(state = 'normal')
|
|
||||||
else:
|
|
||||||
self.chkfilesql.insert('end', srv_options['sql']['file'])
|
|
||||||
self.chkfilesql.xview_moveto(1)
|
|
||||||
self.chkfilesql.configure(state = 'disabled')
|
|
||||||
|
|
||||||
def always_centered(self, geo, centered, refs):
|
|
||||||
x = (self.winfo_screenwidth() // 2) - (self.winfo_width() // 2)
|
|
||||||
y = (self.winfo_screenheight() // 2) - (self.winfo_height() // 2)
|
|
||||||
w, h, dx, dy = geo.split('+')[0].split('x') + geo.split('+')[1:]
|
|
||||||
|
|
||||||
if w == refs[1]:
|
|
||||||
if centered:
|
|
||||||
self.geometry('+%d+%d' %(x, y))
|
|
||||||
centered = False
|
|
||||||
elif w == refs[0]:
|
|
||||||
if not centered:
|
|
||||||
self.geometry('+%d+%d' %(x, y))
|
|
||||||
centered = True
|
|
||||||
|
|
||||||
if dx != str(x) or dy != str(y):
|
|
||||||
self.geometry('+%d+%d' %(x, 0))
|
|
||||||
|
|
||||||
self.after(200, self.always_centered, self.geometry(), centered, refs)
|
|
||||||
|
|
||||||
def gui_complete(self):
|
|
||||||
## Create client widgets (optcltwin, msgcltwin, btncltwin)
|
|
||||||
self.update_idletasks() # update Gui to get btnsrvwin values --> btncltwin.
|
|
||||||
minw, minh = self.winfo_width(), self.winfo_height()
|
|
||||||
self.iconify()
|
|
||||||
self.gui_clt()
|
|
||||||
maxw, minh = self.winfo_width(), self.winfo_height()
|
|
||||||
## Main window custom background.
|
|
||||||
self.update_idletasks() # update Gui for custom background
|
|
||||||
self.iconify()
|
|
||||||
custom_background(self)
|
|
||||||
## Main window other modifications.
|
|
||||||
self.eval('tk::PlaceWindow %s center' %self.winfo_pathname(self.winfo_id()))
|
|
||||||
self.wm_attributes("-topmost", True)
|
|
||||||
self.protocol("WM_DELETE_WINDOW", lambda: 0)
|
|
||||||
## Disable maximize button.
|
|
||||||
self.resizable(False, False)
|
|
||||||
## Centered window.
|
|
||||||
self.always_centered(self.geometry(), False, [minw, maxw])
|
|
||||||
|
|
||||||
def get_position(self, widget):
|
|
||||||
x, y = (widget.winfo_x(), widget.winfo_y())
|
|
||||||
w, h = (widget.winfo_width(), widget.winfo_height())
|
|
||||||
return x, y, w, h
|
|
||||||
|
|
||||||
def gui_clt(self):
|
|
||||||
self.count_clear, self.keep_clear = (0, '0.0')
|
|
||||||
self.optcltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
self.msgcltwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200)
|
|
||||||
self.btncltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
|
|
||||||
xb, yb, wb, hb = self.get_position(self.btnsrvwin)
|
|
||||||
self.btncltwin_X = xb
|
|
||||||
self.btncltwin_Y = yb + hb + 6
|
|
||||||
self.btncltwin.place(x = self.btncltwin_X, y = self.btncltwin_Y, bordermode = 'outside', anchor = 'center')
|
|
||||||
|
|
||||||
self.optcltwin.grid(row = 0, column = 4, padx = 2, pady = 2, sticky = 'nsew')
|
|
||||||
self.optcltwin.grid_rowconfigure(0, weight = 1)
|
|
||||||
self.optcltwin.grid_columnconfigure(1, weight = 1)
|
|
||||||
|
|
||||||
## Subpages of "optcltwin".
|
|
||||||
self.gui_pages_create(parent = self.optcltwin, side = "Clt", create = {"PageStart": None,
|
|
||||||
"PageEnd": None})
|
|
||||||
|
|
||||||
## Continue to grid.
|
|
||||||
self.msgcltwin.grid(row = 1, column = 4, padx = 1, pady = 1, sticky = 'nsew')
|
|
||||||
self.msgcltwin.grid_propagate(False)
|
|
||||||
self.msgcltwin.grid_columnconfigure(0, weight = 1)
|
|
||||||
self.msgcltwin.grid_rowconfigure(0, weight = 1)
|
|
||||||
|
|
||||||
## Create widgets (btncltwin) ----------------------------------------------------------------------------------------------------------------
|
|
||||||
self.runbtnclt = tk.Button(self.btncltwin, text = 'START\nCLIENT', background = self.customcolors['blue'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
state = 'disabled', command = self.clt_on_start, width = 8, height = 2)
|
|
||||||
|
|
||||||
## Layout widgets (btncltwin)
|
|
||||||
self.runbtnclt.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
|
|
||||||
## Create widgets (optcltwin:Clt:PageWin:PageStart) ------------------------------------------------------------------------------------------
|
|
||||||
# Version.
|
|
||||||
cltver = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'You are running client version: ' + clt_version,
|
|
||||||
font = self.customfonts['oth'], foreground = self.customcolors['red'])
|
|
||||||
# Ip Address.
|
|
||||||
cltipaddlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'IP Address: ', font = self.customfonts['opt'])
|
|
||||||
self.cltipadd = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'ip')
|
|
||||||
self.cltipadd.insert('end', clt_options['ip']['def'])
|
|
||||||
ToolTip(self.cltipadd, text = clt_options['ip']['help'], wraplength = self.wraplength)
|
|
||||||
# Port.
|
|
||||||
cltportlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Port: ', font = self.customfonts['opt'])
|
|
||||||
self.cltport = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'port',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.cltport.insert('end', str(clt_options['port']['def']))
|
|
||||||
ToolTip(self.cltport, text = clt_options['port']['help'], wraplength = self.wraplength)
|
|
||||||
# Mode.
|
|
||||||
cltmodelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Mode: ', font = self.customfonts['opt'])
|
|
||||||
self.cltmode = ttk.Combobox(self.pagewidgets["Clt"]["PageWin"]["PageStart"], values = tuple(clt_options['mode']['choi']),
|
|
||||||
width = 17, height = 10, font = self.customfonts['lst'], state = "readonly", name = 'mode')
|
|
||||||
self.cltmode.set(clt_options['mode']['def'])
|
|
||||||
ToolTip(self.cltmode, text = clt_options['mode']['help'], wraplength = self.wraplength)
|
|
||||||
# CMID.
|
|
||||||
cltcmidlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'CMID: ', font = self.customfonts['opt'])
|
|
||||||
self.cltcmid = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'cmid')
|
|
||||||
self.cltcmid.insert('end', str(clt_options['cmid']['def']))
|
|
||||||
ToolTip(self.cltcmid, text = clt_options['cmid']['help'], wraplength = self.wraplength)
|
|
||||||
# Machine Name.
|
|
||||||
cltnamelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Machine Name: ', font = self.customfonts['opt'])
|
|
||||||
self.cltname = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'name')
|
|
||||||
self.cltname.insert('end', str(clt_options['name']['def']))
|
|
||||||
ToolTip(self.cltname, text = clt_options['name']['help'], wraplength = self.wraplength)
|
|
||||||
# Logfile.
|
|
||||||
cltfilelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logfile Path / Name: ', font = self.customfonts['opt'])
|
|
||||||
self.cltfile = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lfile')
|
|
||||||
self.cltfile.insert('end', clt_options['lfile']['def'])
|
|
||||||
self.cltfile.xview_moveto(1)
|
|
||||||
ToolTip(self.cltfile, text = clt_options['lfile']['help'], wraplength = self.wraplength)
|
|
||||||
cltfilebtnwin = tk.Button(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Browse', font = self.customfonts['opt'],
|
|
||||||
command = lambda: self.on_browse(self.cltfile, clt_options))
|
|
||||||
# Loglevel.
|
|
||||||
cltlevellbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.customfonts['opt'])
|
|
||||||
self.cltlevel = ttk.Combobox(self.pagewidgets["Clt"]["PageWin"]["PageStart"], values = tuple(clt_options['llevel']['choi']),
|
|
||||||
width = 10, height = 10, font = self.customfonts['lst'], state = "readonly", name = 'llevel')
|
|
||||||
self.cltlevel.set(clt_options['llevel']['def'])
|
|
||||||
ToolTip(self.cltlevel, text = clt_options['llevel']['help'], wraplength = self.wraplength)
|
|
||||||
# Logsize.
|
|
||||||
cltsizelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.customfonts['opt'])
|
|
||||||
self.cltsize = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lsize',
|
|
||||||
validate = "key", validatecommand = self.validation_float)
|
|
||||||
self.cltsize.insert('end', clt_options['lsize']['def'])
|
|
||||||
ToolTip(self.cltsize, text = clt_options['lsize']['help'], wraplength = self.wraplength)
|
|
||||||
# Asynchronous messages.
|
|
||||||
self.chkvalcltasy = tk.BooleanVar()
|
|
||||||
self.chkvalcltasy.set(clt_options['asyncmsg']['def'])
|
|
||||||
chkcltasy = tk.Checkbutton(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Async\nMsg',
|
|
||||||
font = self.customfonts['opt'], var = self.chkvalcltasy, relief = 'groove', name = 'asyncmsg')
|
|
||||||
ToolTip(chkcltasy, text = clt_options['asyncmsg']['help'], wraplength = self.wraplength)
|
|
||||||
|
|
||||||
# Listbox radiobuttons client.
|
|
||||||
self.chkcltfile = ListboxOfRadiobuttons(self.pagewidgets["Clt"]["PageWin"]["PageStart"],
|
|
||||||
['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'],
|
|
||||||
self.customfonts['lst'],
|
|
||||||
changed = [(self.cltfile, clt_options['lfile']['def']),
|
|
||||||
(cltfilebtnwin, ''),
|
|
||||||
(self.cltsize, clt_options['lsize']['def']),
|
|
||||||
(self.cltlevel, clt_options['llevel']['def'])],
|
|
||||||
width = 10, height = 1, borderwidth = 2, relief = 'ridge')
|
|
||||||
|
|
||||||
## Layout widgets (optcltwin:Clt:PageWin:PageStart)
|
|
||||||
cltver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltportlbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltport.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltmodelbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltmode.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltcmidlbl.grid(row = 4, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltcmid.grid(row = 4, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltnamelbl.grid(row = 5, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltname.grid(row = 5, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltfilelbl.grid(row = 6, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltfile.grid(row = 6, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltfilebtnwin.grid(row = 6, column = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
self.chkcltfile.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
chkcltasy.grid(row = 7, column = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltlevellbl.grid(row = 8, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltlevel.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltsizelbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltsize.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
|
|
||||||
# ugly fix when client-side mode is activated.
|
|
||||||
templbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"],
|
|
||||||
bg = self.customcolors['lavender']).grid(row = 10, column = 0,
|
|
||||||
padx = 35, pady = 54, sticky = 'e')
|
|
||||||
|
|
||||||
## Create widgets (optcltwin:Clt:PageWin:PageEnd) -------------------------------------------------------------------------------------------
|
|
||||||
# Timeout connection.
|
|
||||||
clttimeout0lbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.customfonts['opt'])
|
|
||||||
self.clttimeout0 = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time0')
|
|
||||||
self.clttimeout0.insert('end', str(clt_options['time0']['def']))
|
|
||||||
ToolTip(self.clttimeout0, text = clt_options['time0']['help'], wraplength = self.wraplength)
|
|
||||||
# Timeout send/recv.
|
|
||||||
clttimeout1lbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], text = 'Timeout send-recv: ', font = self.customfonts['opt'])
|
|
||||||
self.clttimeout1 = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time1')
|
|
||||||
self.clttimeout1.insert('end', str(clt_options['time1']['def']))
|
|
||||||
ToolTip(self.clttimeout1, text = clt_options['time1']['help'], wraplength = self.wraplength)
|
|
||||||
|
|
||||||
## Layout widgets (optcltwin:Clt:PageWin:PageEnd)
|
|
||||||
# a label for vertical aligning with PageStart
|
|
||||||
tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 0,
|
|
||||||
height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw')
|
|
||||||
clttimeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.clttimeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
clttimeout1lbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.clttimeout1.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
|
|
||||||
## Store client-side widgets.
|
|
||||||
self.storewidgets_clt = self.gui_store(side = "Clt", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton'])
|
|
||||||
self.storewidgets_clt.append(self.chkcltfile)
|
|
||||||
|
|
||||||
## Create widgets and layout (msgcltwin) -----------------------------------------------------------------------------------------------------
|
|
||||||
self.textboxclt = TextDoubleScroll(self.msgcltwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled',
|
|
||||||
relief = 'ridge', font = self.customfonts['msg'])
|
|
||||||
self.textboxclt.put()
|
|
||||||
|
|
||||||
def prep_option(self, value):
|
|
||||||
try:
|
|
||||||
# is an INT
|
|
||||||
return int(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
try:
|
|
||||||
# is a FLOAT
|
|
||||||
return float(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
# is a STRING.
|
|
||||||
return value
|
|
||||||
|
|
||||||
def prep_logfile(self, filepath, status):
|
|
||||||
# FILE (pretty on, log view off, logfile yes)
|
|
||||||
# FILEOFF (pretty on, log view off, no logfile)
|
|
||||||
# STDOUT (pretty off, log view on, no logfile)
|
|
||||||
# STDOUTOFF (pretty off, log view off, logfile yes)
|
|
||||||
# FILESTDOUT (pretty off, log view on, logfile yes)
|
|
||||||
|
|
||||||
if status == 'FILE':
|
|
||||||
return filepath
|
|
||||||
elif status in ['FILESTDOUT', 'STDOUTOFF']:
|
|
||||||
return [status, filepath]
|
|
||||||
elif status in ['STDOUT', 'FILEOFF']:
|
|
||||||
return status
|
|
||||||
|
|
||||||
def validate_int(self, value):
|
|
||||||
return value == "" or value.isdigit()
|
|
||||||
|
|
||||||
def validate_float(self, value):
|
|
||||||
if value == "":
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
float(value)
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def clt_on_show(self, force_remove = False, force_view = False):
|
|
||||||
if self.optcltwin.winfo_ismapped() or force_remove:
|
|
||||||
self.shbtnclt.configure(text = 'SHOW\nCLIENT', relief = 'raised')
|
|
||||||
self.optcltwin.grid_remove()
|
|
||||||
self.msgcltwin.grid_remove()
|
|
||||||
self.btncltwin.place_forget()
|
|
||||||
elif not self.optcltwin.winfo_ismapped() or force_view:
|
|
||||||
self.shbtnclt.configure(text = 'HIDE\nCLIENT', relief = 'sunken')
|
|
||||||
self.optcltwin.grid()
|
|
||||||
self.msgcltwin.grid()
|
|
||||||
self.btncltwin.place(x = self.btncltwin_X, y = self.btncltwin_Y, bordermode = 'inside', anchor = 'nw')
|
|
||||||
|
|
||||||
def srv_on_start(self):
|
|
||||||
if self.runbtnsrv['text'] == 'START\nSERVER':
|
|
||||||
self.on_clear([txsrv, txclt])
|
|
||||||
self.srv_actions_start()
|
|
||||||
# wait for switch.
|
|
||||||
while not serverthread.is_running_server:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.srv_toggle_all(on_start = True)
|
|
||||||
# run thread for interrupting server when an error happens.
|
|
||||||
self.srv_eject_thread = threading.Thread(target = self.srv_eject, name = "Thread-SrvEjt")
|
|
||||||
self.srv_eject_thread.setDaemon(True)
|
|
||||||
self.srv_eject_thread.start()
|
|
||||||
|
|
||||||
elif self.runbtnsrv['text'] == 'STOP\nSERVER':
|
|
||||||
serverthread.terminate_eject()
|
|
||||||
|
|
||||||
def srv_eject(self):
|
|
||||||
while not serverthread.eject:
|
|
||||||
sleep(0.1)
|
|
||||||
self.srv_actions_stop()
|
|
||||||
|
|
||||||
def srv_actions_start(self):
|
|
||||||
srv_config[srv_options['ip']['des']] = self.srvipadd.get()
|
|
||||||
srv_config[srv_options['port']['des']] = self.prep_option(self.srvport.get())
|
|
||||||
srv_config[srv_options['epid']['des']] = self.epid.get()
|
|
||||||
srv_config[srv_options['lcid']['des']] = self.prep_option(self.lcid.get())
|
|
||||||
srv_config[srv_options['hwid']['des']] = self.hwid.get()
|
|
||||||
srv_config[srv_options['count']['des']] = self.prep_option(self.count.get())
|
|
||||||
srv_config[srv_options['activation']['des']] = self.prep_option(self.activ.get())
|
|
||||||
srv_config[srv_options['renewal']['des']] = self.prep_option(self.renew.get())
|
|
||||||
srv_config[srv_options['lfile']['des']] = self.prep_logfile(self.srvfile.get(), self.chksrvfile.state())
|
|
||||||
srv_config[srv_options['asyncmsg']['des']] = self.chkvalsrvasy.get()
|
|
||||||
srv_config[srv_options['llevel']['des']] = self.srvlevel.get()
|
|
||||||
srv_config[srv_options['lsize']['des']] = self.prep_option(self.srvsize.get())
|
|
||||||
|
|
||||||
srv_config[srv_options['time0']['des']] = self.prep_option(self.srvtimeout0.get())
|
|
||||||
srv_config[srv_options['time1']['des']] = self.prep_option(self.srvtimeout1.get())
|
|
||||||
srv_config[srv_options['sql']['des']] = (self.chkfilesql.get() if self.chkvalsql.get() else self.chkvalsql.get())
|
|
||||||
|
|
||||||
## Redirect stdout.
|
|
||||||
gui_redirector('stdout', redirect_to = TextRedirect.Log,
|
|
||||||
redirect_conditio = (srv_config[srv_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT']))
|
|
||||||
serverqueue.put('start')
|
|
||||||
|
|
||||||
def srv_actions_stop(self):
|
|
||||||
if serverthread.is_running_server:
|
|
||||||
if serverthread.server is not None:
|
|
||||||
server_terminate(serverthread, exit_server = True)
|
|
||||||
# wait for switch.
|
|
||||||
while serverthread.is_running_server:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
serverthread.is_running_server = False
|
|
||||||
self.srv_toggle_all(on_start = False)
|
|
||||||
self.count_clear, self.keep_clear = (0, '0.0')
|
|
||||||
|
|
||||||
def srv_toggle_all(self, on_start = True):
|
|
||||||
self.srv_toggle_state()
|
|
||||||
if on_start:
|
|
||||||
self.runbtnsrv.configure(text = 'STOP\nSERVER', background = self.customcolors['red'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'sunken')
|
|
||||||
for widget in self.storewidgets_srv:
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
self.runbtnclt.configure(state = 'normal')
|
|
||||||
else:
|
|
||||||
self.runbtnsrv.configure(text = 'START\nSERVER', background = self.customcolors['green'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised')
|
|
||||||
for widget in self.storewidgets_srv:
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
if isinstance(widget, ListboxOfRadiobuttons):
|
|
||||||
widget.change()
|
|
||||||
self.runbtnclt.configure(state = 'disabled')
|
|
||||||
|
|
||||||
def srv_toggle_state(self):
|
|
||||||
if serverthread.is_running_server:
|
|
||||||
txt, color = ('Server\nState:\nServing', self.customcolors['green'])
|
|
||||||
else:
|
|
||||||
txt, color = ('Server\nState:\nStopped', self.customcolors['red'])
|
|
||||||
|
|
||||||
self.statesrv.configure(text = txt, foreground = color)
|
|
||||||
|
|
||||||
def clt_on_start(self):
|
|
||||||
if self.onlyclt:
|
|
||||||
self.on_clear([txclt])
|
|
||||||
else:
|
|
||||||
rng, add_newline = self.on_clear_setup()
|
|
||||||
self.on_clear([txsrv, txclt], clear_range = [rng, None], newline_list = [add_newline, False])
|
|
||||||
|
|
||||||
self.runbtnclt.configure(relief = 'sunken')
|
|
||||||
self.clt_actions_start()
|
|
||||||
# run thread for disabling interrupt server and client, when client running.
|
|
||||||
self.clt_eject_thread = threading.Thread(target = self.clt_eject, name = "Thread-CltEjt")
|
|
||||||
self.clt_eject_thread.setDaemon(True)
|
|
||||||
self.clt_eject_thread.start()
|
|
||||||
|
|
||||||
for widget in self.storewidgets_clt + [self.runbtnsrv, self.runbtnclt, self.defaubtnsrv]:
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
self.runbtnclt.configure(relief = 'raised')
|
|
||||||
|
|
||||||
def clt_actions_start(self):
|
|
||||||
clt_config[clt_options['ip']['des']] = self.cltipadd.get()
|
|
||||||
clt_config[clt_options['port']['des']] = self.prep_option(self.cltport.get())
|
|
||||||
clt_config[clt_options['mode']['des']] = self.cltmode.get()
|
|
||||||
clt_config[clt_options['cmid']['des']] = self.cltcmid.get()
|
|
||||||
clt_config[clt_options['name']['des']] = self.cltname.get()
|
|
||||||
clt_config[clt_options['lfile']['des']] = self.prep_logfile(self.cltfile.get(), self.chkcltfile.state())
|
|
||||||
clt_config[clt_options['asyncmsg']['des']] = self.chkvalcltasy.get()
|
|
||||||
clt_config[clt_options['llevel']['des']] = self.cltlevel.get()
|
|
||||||
clt_config[clt_options['lsize']['des']] = self.prep_option(self.cltsize.get())
|
|
||||||
|
|
||||||
clt_config[clt_options['time0']['des']] = self.prep_option(self.clttimeout0.get())
|
|
||||||
clt_config[clt_options['time1']['des']] = self.prep_option(self.clttimeout1.get())
|
|
||||||
|
|
||||||
## Redirect stdout.
|
|
||||||
gui_redirector('stdout', redirect_to = TextRedirect.Log,
|
|
||||||
redirect_conditio = (clt_config[clt_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT']))
|
|
||||||
|
|
||||||
# run client (in a thread).
|
|
||||||
self.clientthread = client_thread(name = "Thread-Clt")
|
|
||||||
self.clientthread.setDaemon(True)
|
|
||||||
self.clientthread.with_gui = True
|
|
||||||
self.clientthread.start()
|
|
||||||
|
|
||||||
def clt_eject(self):
|
|
||||||
while self.clientthread.is_alive():
|
|
||||||
sleep(0.1)
|
|
||||||
|
|
||||||
widgets = self.storewidgets_clt + [self.runbtnclt] + [self.defaubtnsrv]
|
|
||||||
if not self.onlyclt:
|
|
||||||
widgets += [self.runbtnsrv]
|
|
||||||
|
|
||||||
for widget in widgets:
|
|
||||||
if isinstance(widget, ttk.Combobox):
|
|
||||||
widget.configure(state = 'readonly')
|
|
||||||
else:
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
if isinstance(widget, ListboxOfRadiobuttons):
|
|
||||||
widget.change()
|
|
||||||
|
|
||||||
def on_browse(self, entrywidget, options):
|
|
||||||
path = filedialog.askdirectory()
|
|
||||||
if os.path.isdir(path):
|
|
||||||
entrywidget.delete('0', 'end')
|
|
||||||
entrywidget.insert('end', path + os.sep + os.path.basename(options['lfile']['def']))
|
|
||||||
|
|
||||||
def on_exit(self):
|
|
||||||
if serverthread.is_running_server:
|
|
||||||
if serverthread.server is not None:
|
|
||||||
server_terminate(serverthread, exit_server = True)
|
|
||||||
else:
|
|
||||||
serverthread.is_running_server = False
|
|
||||||
server_terminate(serverthread, exit_thread = True)
|
|
||||||
self.destroy()
|
|
||||||
|
|
||||||
def on_clear_setup(self):
|
|
||||||
if any(opt in ['STDOUT', 'FILESTDOUT'] for opt in srv_config[srv_options['lfile']['des']]):
|
|
||||||
add_newline = True
|
|
||||||
if self.count_clear == 0:
|
|
||||||
self.keep_clear = txsrv.index('end-1c')
|
|
||||||
else:
|
|
||||||
add_newline = False
|
|
||||||
if self.count_clear == 0:
|
|
||||||
self.keep_clear = txsrv.index('end')
|
|
||||||
|
|
||||||
rng = [self.keep_clear, 'end']
|
|
||||||
self.count_clear += 1
|
|
||||||
|
|
||||||
return rng, add_newline
|
|
||||||
|
|
||||||
def on_clear(self, widget_list, clear_range = None, newline_list = []):
|
|
||||||
if newline_list == []:
|
|
||||||
newline_list = len(widget_list) * [False]
|
|
||||||
|
|
||||||
for num, couple in enumerate(zip(widget_list, newline_list)):
|
|
||||||
widget, add_n = couple
|
|
||||||
try:
|
|
||||||
ini, fin = clear_range[num]
|
|
||||||
except TypeError:
|
|
||||||
ini, fin = '1.0', 'end'
|
|
||||||
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
widget.delete(ini, fin)
|
|
||||||
if add_n:
|
|
||||||
widget.insert('end', '\n')
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
|
|
||||||
def on_defaults(self):
|
|
||||||
|
|
||||||
def put_defaults(widgets, chkasy, listofradio, options):
|
|
||||||
for widget in widgets:
|
|
||||||
wclass, wname = widget.winfo_class(), widget.winfo_name()
|
|
||||||
if wname == '!checkbutton':
|
|
||||||
continue
|
|
||||||
|
|
||||||
opt = options[wname]['def']
|
|
||||||
if wclass == 'Entry':
|
|
||||||
widget.delete(0, 'end')
|
|
||||||
if wname == 'sql':
|
|
||||||
self.chkvalsql.set(opt)
|
|
||||||
self.sql_status()
|
|
||||||
else:
|
|
||||||
widget.insert('end', (opt if isinstance(opt, str) else str(opt)))
|
|
||||||
elif wclass == 'Checkbutton':
|
|
||||||
if wname == 'asyncmsg':
|
|
||||||
chkasy.set(opt)
|
|
||||||
elif wclass == 'TCombobox':
|
|
||||||
widget.set(str(opt))
|
|
||||||
|
|
||||||
# ListboxOfRadiobuttons default.
|
|
||||||
listofradio.radiovar.set('FILE')
|
|
||||||
listofradio.textbox.yview_moveto(0)
|
|
||||||
listofradio.change()
|
|
||||||
|
|
||||||
if self.runbtnsrv['text'] == 'START\nSERVER':
|
|
||||||
apply_default = zip(["Srv", "Clt"],
|
|
||||||
[self.chkvalsrvasy, self.chkvalcltasy],
|
|
||||||
[self.chksrvfile, self.chkcltfile],
|
|
||||||
[srv_options, clt_options])
|
|
||||||
elif self.runbtnsrv['text'] == 'STOP\nSERVER':
|
|
||||||
apply_default = zip(*[("Clt",),
|
|
||||||
(self.chkvalcltasy,),
|
|
||||||
(self.chkcltfile,),
|
|
||||||
(clt_options,)])
|
|
||||||
|
|
||||||
for side, chkasy, listofradio, options in apply_default:
|
|
||||||
widgets = self.gui_store(side = side, typewidgets = ['Entry', 'TCombobox', 'Checkbutton'])
|
|
||||||
put_defaults(widgets, chkasy, listofradio, options)
|
|
|
@ -1,517 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from collections import Counter
|
|
||||||
from time import sleep
|
|
||||||
import threading
|
|
||||||
import tkinter as tk
|
|
||||||
from tkinter import ttk
|
|
||||||
import tkinter.font as tkFont
|
|
||||||
|
|
||||||
from pykms_Format import MsgMap, unshell_message, unformat_message
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/3221956/how-do-i-display-tooltips-in-tkinter
|
|
||||||
class ToolTip(object):
|
|
||||||
""" Create a tooltip for a given widget """
|
|
||||||
def __init__(self, widget, bg = '#FFFFEA', pad = (5, 3, 5, 3), text = 'widget info', waittime = 400, wraplength = 250):
|
|
||||||
self.waittime = waittime # ms
|
|
||||||
self.wraplength = wraplength # pixels
|
|
||||||
self.widget = widget
|
|
||||||
self.text = text
|
|
||||||
self.widget.bind("<Enter>", self.onEnter)
|
|
||||||
self.widget.bind("<Leave>", self.onLeave)
|
|
||||||
self.widget.bind("<ButtonPress>", self.onLeave)
|
|
||||||
self.bg = bg
|
|
||||||
self.pad = pad
|
|
||||||
self.id = None
|
|
||||||
self.tw = None
|
|
||||||
|
|
||||||
def onEnter(self, event = None):
|
|
||||||
self.schedule()
|
|
||||||
|
|
||||||
def onLeave(self, event = None):
|
|
||||||
self.unschedule()
|
|
||||||
self.hide()
|
|
||||||
|
|
||||||
def schedule(self):
|
|
||||||
self.unschedule()
|
|
||||||
self.id = self.widget.after(self.waittime, self.show)
|
|
||||||
|
|
||||||
def unschedule(self):
|
|
||||||
id_ = self.id
|
|
||||||
self.id = None
|
|
||||||
if id_:
|
|
||||||
self.widget.after_cancel(id_)
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
def tip_pos_calculator(widget, label, tip_delta = (10, 5), pad = (5, 3, 5, 3)):
|
|
||||||
w = widget
|
|
||||||
s_width, s_height = w.winfo_screenwidth(), w.winfo_screenheight()
|
|
||||||
width, height = (pad[0] + label.winfo_reqwidth() + pad[2],
|
|
||||||
pad[1] + label.winfo_reqheight() + pad[3])
|
|
||||||
mouse_x, mouse_y = w.winfo_pointerxy()
|
|
||||||
x1, y1 = mouse_x + tip_delta[0], mouse_y + tip_delta[1]
|
|
||||||
x2, y2 = x1 + width, y1 + height
|
|
||||||
|
|
||||||
x_delta = x2 - s_width
|
|
||||||
if x_delta < 0:
|
|
||||||
x_delta = 0
|
|
||||||
y_delta = y2 - s_height
|
|
||||||
if y_delta < 0:
|
|
||||||
y_delta = 0
|
|
||||||
|
|
||||||
offscreen = (x_delta, y_delta) != (0, 0)
|
|
||||||
|
|
||||||
if offscreen:
|
|
||||||
if x_delta:
|
|
||||||
x1 = mouse_x - tip_delta[0] - width
|
|
||||||
if y_delta:
|
|
||||||
y1 = mouse_y - tip_delta[1] - height
|
|
||||||
|
|
||||||
offscreen_again = y1 < 0 # out on the top
|
|
||||||
|
|
||||||
if offscreen_again:
|
|
||||||
# No further checks will be done.
|
|
||||||
|
|
||||||
# TIP:
|
|
||||||
# A further mod might automagically augment the
|
|
||||||
# wraplength when the tooltip is too high to be
|
|
||||||
# kept inside the screen.
|
|
||||||
y1 = 0
|
|
||||||
|
|
||||||
return x1, y1
|
|
||||||
|
|
||||||
bg = self.bg
|
|
||||||
pad = self.pad
|
|
||||||
widget = self.widget
|
|
||||||
|
|
||||||
# creates a toplevel window
|
|
||||||
self.tw = tk.Toplevel(widget)
|
|
||||||
|
|
||||||
# leaves only the label and removes the app window
|
|
||||||
self.tw.wm_overrideredirect(True)
|
|
||||||
|
|
||||||
win = tk.Frame(self.tw, background = bg, borderwidth = 0)
|
|
||||||
label = ttk.Label(win, text = self.text, justify = tk.LEFT, background = bg, relief = tk.SOLID, borderwidth = 0,
|
|
||||||
wraplength = self.wraplength)
|
|
||||||
label.grid(padx = (pad[0], pad[2]), pady = (pad[1], pad[3]), sticky=tk.NSEW)
|
|
||||||
win.grid()
|
|
||||||
|
|
||||||
x, y = tip_pos_calculator(widget, label)
|
|
||||||
|
|
||||||
self.tw.wm_geometry("+%d+%d" % (x, y))
|
|
||||||
|
|
||||||
def hide(self):
|
|
||||||
tw = self.tw
|
|
||||||
if tw:
|
|
||||||
tw.destroy()
|
|
||||||
self.tw = None
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class TextRedirect(object):
|
|
||||||
class Pretty(object):
|
|
||||||
grpmsg = unformat_message([MsgMap[1], MsgMap[7], MsgMap[12], MsgMap[20]])
|
|
||||||
arrows = [ item[0] for item in grpmsg ]
|
|
||||||
clt_msg_nonewline = [ item[1] for item in grpmsg ]
|
|
||||||
arrows = list(set(arrows))
|
|
||||||
lenarrow = len(arrows[0])
|
|
||||||
srv_msg_nonewline = [ item[0] for item in unformat_message([MsgMap[2], MsgMap[5], MsgMap[13], MsgMap[18]]) ]
|
|
||||||
msg_align = [ msg[0].replace('\t', '').replace('\n', '') for msg in unformat_message([MsgMap[-2], MsgMap[-4]]) ]
|
|
||||||
|
|
||||||
def __init__(self, srv_text_space, clt_text_space, customcolors):
|
|
||||||
self.srv_text_space = srv_text_space
|
|
||||||
self.clt_text_space = clt_text_space
|
|
||||||
self.customcolors = customcolors
|
|
||||||
|
|
||||||
def textbox_write(self, tag, message, color, extras):
|
|
||||||
widget = self.textbox_choose(message)
|
|
||||||
self.w_maxpix, self.h_maxpix = widget.winfo_width(), widget.winfo_height()
|
|
||||||
self.xfont = tkFont.Font(font = widget['font'])
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
widget.insert('end', self.textbox_format(message), tag)
|
|
||||||
self.textbox_color(tag, widget, color, self.customcolors['black'], extras)
|
|
||||||
widget.after(100, widget.see('end'))
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
|
|
||||||
def textbox_choose(self, message):
|
|
||||||
if any(item.startswith('logsrv') for item in [message, self.str_to_print]):
|
|
||||||
self.srv_text_space.focus_set()
|
|
||||||
self.where = "srv"
|
|
||||||
return self.srv_text_space
|
|
||||||
elif any(item.startswith('logclt') for item in [message, self.str_to_print]):
|
|
||||||
self.clt_text_space.focus_set()
|
|
||||||
self.where = "clt"
|
|
||||||
return self.clt_text_space
|
|
||||||
|
|
||||||
def textbox_color(self, tag, widget, forecolor = 'white', backcolor = 'black', extras = []):
|
|
||||||
for extra in extras:
|
|
||||||
if extra == 'bold':
|
|
||||||
self.xfont.configure(weight = "bold")
|
|
||||||
elif extra == 'italic':
|
|
||||||
self.xfont.configure(slant = "italic")
|
|
||||||
elif extra == 'underlined':
|
|
||||||
self.xfont.text_font.configure(underline = True)
|
|
||||||
elif extra == 'strike':
|
|
||||||
self.xfont.configure(overstrike = True)
|
|
||||||
elif extra == 'reverse':
|
|
||||||
forecolor, backcolor = backcolor, forecolor
|
|
||||||
|
|
||||||
widget.tag_configure(tag, foreground = forecolor, background = backcolor, font = self.xfont)
|
|
||||||
widget.tag_add(tag, "insert linestart", "insert lineend")
|
|
||||||
|
|
||||||
def textbox_newline(self, message):
|
|
||||||
if not message.endswith('\n'):
|
|
||||||
return message + '\n'
|
|
||||||
else:
|
|
||||||
return message
|
|
||||||
|
|
||||||
def textbox_format(self, message):
|
|
||||||
# vertical align.
|
|
||||||
self.w_maxpix = self.w_maxpix - 5 # pixel reduction for distance from border.
|
|
||||||
w_fontpix, h_fontpix = (self.xfont.measure('0'), self.xfont.metrics('linespace'))
|
|
||||||
msg_unformat = message.replace('\t', '').replace('\n', '')
|
|
||||||
lenfixed_chars = int((self.w_maxpix / w_fontpix) - len(msg_unformat))
|
|
||||||
|
|
||||||
if message in self.srv_msg_nonewline + self.clt_msg_nonewline:
|
|
||||||
lung = lenfixed_chars - self.lenarrow
|
|
||||||
if message in self.clt_msg_nonewline:
|
|
||||||
message = self.textbox_newline(message)
|
|
||||||
else:
|
|
||||||
lung = lenfixed_chars
|
|
||||||
if (self.where == "srv") or (self.where == "clt" and message not in self.arrows):
|
|
||||||
message = self.textbox_newline(message)
|
|
||||||
# horizontal align.
|
|
||||||
if msg_unformat in self.msg_align:
|
|
||||||
msg_strip = message.lstrip('\n')
|
|
||||||
message = '\n' * (len(message) - len(msg_strip) + TextRedirect.Pretty.newlinecut[0]) + msg_strip
|
|
||||||
TextRedirect.Pretty.newlinecut.pop(0)
|
|
||||||
|
|
||||||
count = Counter(message)
|
|
||||||
countab = (count['\t'] if count['\t'] != 0 else 1)
|
|
||||||
message = message.replace('\t' * countab, ' ' * lung)
|
|
||||||
return message
|
|
||||||
|
|
||||||
def textbox_do(self):
|
|
||||||
msgs, TextRedirect.Pretty.tag_num = unshell_message(self.str_to_print, TextRedirect.Pretty.tag_num)
|
|
||||||
for tag in msgs:
|
|
||||||
self.textbox_write(tag, msgs[tag]['text'], self.customcolors[msgs[tag]['color']], msgs[tag]['extra'])
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def write(self, string):
|
|
||||||
if string != '\n':
|
|
||||||
self.str_to_print = string
|
|
||||||
self.textbox_do()
|
|
||||||
|
|
||||||
class Stderr(Pretty):
|
|
||||||
def __init__(self, srv_text_space, clt_text_space, customcolors, side):
|
|
||||||
self.srv_text_space = srv_text_space
|
|
||||||
self.clt_text_space = clt_text_space
|
|
||||||
self.customcolors = customcolors
|
|
||||||
self.side = side
|
|
||||||
self.tag_err = 'STDERR'
|
|
||||||
self.xfont = tkFont.Font(font = self.srv_text_space['font'])
|
|
||||||
|
|
||||||
def textbox_choose(self, message):
|
|
||||||
if self.side == "srv":
|
|
||||||
return self.srv_text_space
|
|
||||||
elif self.side == "clt":
|
|
||||||
return self.clt_text_space
|
|
||||||
|
|
||||||
def write(self, string):
|
|
||||||
widget = self.textbox_choose(string)
|
|
||||||
self.textbox_color(self.tag_err, widget, self.customcolors['red'], self.customcolors['black'])
|
|
||||||
self.srv_text_space.configure(state = 'normal')
|
|
||||||
self.srv_text_space.insert('end', string, self.tag_err)
|
|
||||||
self.srv_text_space.see('end')
|
|
||||||
self.srv_text_space.configure(state = 'disabled')
|
|
||||||
|
|
||||||
class Log(Pretty):
|
|
||||||
def textbox_format(self, message):
|
|
||||||
if message.startswith('logsrv'):
|
|
||||||
message = message.replace('logsrv ', '')
|
|
||||||
if message.startswith('logclt'):
|
|
||||||
message = message.replace('logclt ', '')
|
|
||||||
return message + '\n'
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
class TextDoubleScroll(tk.Frame):
|
|
||||||
def __init__(self, master, **kwargs):
|
|
||||||
""" Initialize.
|
|
||||||
- horizontal scrollbar
|
|
||||||
- vertical scrollbar
|
|
||||||
- text widget
|
|
||||||
"""
|
|
||||||
tk.Frame.__init__(self, master)
|
|
||||||
self.master = master
|
|
||||||
|
|
||||||
self.textbox = tk.Text(self.master, **kwargs)
|
|
||||||
self.sizegrip = ttk.Sizegrip(self.master)
|
|
||||||
self.hs = ttk.Scrollbar(self.master, orient = "horizontal", command = self.on_scrollbar_x)
|
|
||||||
self.vs = ttk.Scrollbar(self.master, orient = "vertical", command = self.on_scrollbar_y)
|
|
||||||
self.textbox.configure(yscrollcommand = self.on_textscroll, xscrollcommand = self.hs.set)
|
|
||||||
|
|
||||||
def on_scrollbar_x(self, *args):
|
|
||||||
""" Horizontally scrolls text widget. """
|
|
||||||
self.textbox.xview(*args)
|
|
||||||
|
|
||||||
def on_scrollbar_y(self, *args):
|
|
||||||
""" Vertically scrolls text widget. """
|
|
||||||
self.textbox.yview(*args)
|
|
||||||
|
|
||||||
def on_textscroll(self, *args):
|
|
||||||
""" Moves the scrollbar and scrolls text widget when the mousewheel is moved on a text widget. """
|
|
||||||
self.vs.set(*args)
|
|
||||||
self.on_scrollbar_y('moveto', args[0])
|
|
||||||
|
|
||||||
def put(self, **kwargs):
|
|
||||||
""" Grid the scrollbars and textbox correctly. """
|
|
||||||
self.textbox.grid(row = 0, column = 0, padx = 3, pady = 3, sticky = "nsew")
|
|
||||||
self.vs.grid(row = 0, column = 1, sticky = "ns")
|
|
||||||
self.hs.grid(row = 1, column = 0, sticky = "we")
|
|
||||||
self.sizegrip.grid(row = 1, column = 1, sticky = "news")
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
""" Return the "frame" useful to place inner controls. """
|
|
||||||
return self.textbox
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
def custom_background(window):
|
|
||||||
# first level canvas.
|
|
||||||
allwidgets = window.grid_slaves(0,0)[0].grid_slaves() + window.grid_slaves(0,0)[0].place_slaves()
|
|
||||||
widgets_alphalow = [ widget for widget in allwidgets if widget.winfo_class() == 'Canvas']
|
|
||||||
widgets_alphahigh = []
|
|
||||||
# sub-level canvas.
|
|
||||||
for side in ["Srv", "Clt"]:
|
|
||||||
widgets_alphahigh.append(window.pagewidgets[side]["BtnWin"])
|
|
||||||
for position in ["Left", "Right"]:
|
|
||||||
widgets_alphahigh.append(window.pagewidgets[side]["AniWin"][position])
|
|
||||||
for pagename in window.pagewidgets[side]["PageWin"].keys():
|
|
||||||
widgets_alphalow.append(window.pagewidgets[side]["PageWin"][pagename])
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PIL import Image, ImageTk
|
|
||||||
|
|
||||||
# Open Image.
|
|
||||||
img = Image.open(os.path.dirname(os.path.abspath( __file__ )) + "/graphics/pykms_Keys.gif")
|
|
||||||
img = img.convert('RGBA')
|
|
||||||
# Resize image.
|
|
||||||
img.resize((window.winfo_width(), window.winfo_height()), Image.ANTIALIAS)
|
|
||||||
# Put semi-transparent background chunks.
|
|
||||||
window.backcrops_alphalow, window.backcrops_alphahigh = ([] for _ in range(2))
|
|
||||||
|
|
||||||
def cutter(master, image, widgets, crops, alpha):
|
|
||||||
for widget in widgets:
|
|
||||||
x, y, w, h = master.get_position(widget)
|
|
||||||
cropped = image.crop((x, y, x + w, y + h))
|
|
||||||
cropped.putalpha(alpha)
|
|
||||||
crops.append(ImageTk.PhotoImage(cropped))
|
|
||||||
# Not in same loop to prevent reference garbage.
|
|
||||||
for crop, widget in zip(crops, widgets):
|
|
||||||
widget.create_image(1, 1, image = crop, anchor = 'nw')
|
|
||||||
|
|
||||||
cutter(window, img, widgets_alphalow, window.backcrops_alphalow, 36)
|
|
||||||
cutter(window, img, widgets_alphahigh, window.backcrops_alphahigh, 96)
|
|
||||||
|
|
||||||
# Put semi-transparent background overall.
|
|
||||||
img.putalpha(128)
|
|
||||||
window.backimg = ImageTk.PhotoImage(img)
|
|
||||||
window.masterwin.create_image(1, 1, image = window.backimg, anchor = 'nw')
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
for widget in widgets_alphalow + widgets_alphahigh:
|
|
||||||
widget.configure(background = window.customcolors['lavender'])
|
|
||||||
|
|
||||||
# Hide client.
|
|
||||||
window.clt_on_show(force_remove = True)
|
|
||||||
# Show Gui.
|
|
||||||
window.deiconify()
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
class Animation(object):
|
|
||||||
def __init__(self, gifpath, master, widget, loop = False):
|
|
||||||
from PIL import Image, ImageTk, ImageSequence
|
|
||||||
|
|
||||||
self.master = master
|
|
||||||
self.widget = widget
|
|
||||||
self.loop = loop
|
|
||||||
self.cancelid = None
|
|
||||||
self.flagstop = False
|
|
||||||
self.index = 0
|
|
||||||
self.frames = []
|
|
||||||
|
|
||||||
img = Image.open(gifpath)
|
|
||||||
size = img.size
|
|
||||||
for frame in ImageSequence.Iterator(img):
|
|
||||||
static_img = ImageTk.PhotoImage(frame.convert('RGBA'))
|
|
||||||
try:
|
|
||||||
static_img.delay = int(frame.info['duration'])
|
|
||||||
except KeyError:
|
|
||||||
static_img.delay = 100
|
|
||||||
self.frames.append(static_img)
|
|
||||||
|
|
||||||
self.widget.configure(width = size[0], height = size[1])
|
|
||||||
self.initialize()
|
|
||||||
|
|
||||||
def initialize(self):
|
|
||||||
self.widget.configure(image = self.frames[0])
|
|
||||||
self.widget.image = self.frames[0]
|
|
||||||
|
|
||||||
def deanimate(self):
|
|
||||||
while not self.flagstop:
|
|
||||||
pass
|
|
||||||
self.flagstop = False
|
|
||||||
self.index = 0
|
|
||||||
self.widget.configure(relief = "raised")
|
|
||||||
|
|
||||||
def animate(self):
|
|
||||||
frame = self.frames[self.index]
|
|
||||||
self.widget.configure(image = frame, relief = "sunken")
|
|
||||||
self.index += 1
|
|
||||||
self.cancelid = self.master.after(frame.delay, self.animate)
|
|
||||||
if self.index == len(self.frames):
|
|
||||||
if self.loop:
|
|
||||||
self.index = 0
|
|
||||||
else:
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
def start(self, event = None):
|
|
||||||
if str(self.widget['state']) != 'disabled':
|
|
||||||
if self.cancelid is None:
|
|
||||||
if not self.loop:
|
|
||||||
self.btnani_thread = threading.Thread(target = self.deanimate, name = "Thread-BtnAni")
|
|
||||||
self.btnani_thread.setDaemon(True)
|
|
||||||
self.btnani_thread.start()
|
|
||||||
self.cancelid = self.master.after(self.frames[0].delay, self.animate)
|
|
||||||
|
|
||||||
def stop(self, event = None):
|
|
||||||
if self.cancelid:
|
|
||||||
self.master.after_cancel(self.cancelid)
|
|
||||||
self.cancelid = None
|
|
||||||
self.flagstop = True
|
|
||||||
self.initialize()
|
|
||||||
|
|
||||||
|
|
||||||
def custom_pages(window, side):
|
|
||||||
buttons = window.pagewidgets[side]["BtnAni"]
|
|
||||||
labels = window.pagewidgets[side]["LblAni"]
|
|
||||||
|
|
||||||
for position in buttons.keys():
|
|
||||||
buttons[position].config(anchor = "center",
|
|
||||||
font = window.customfonts['btn'],
|
|
||||||
background = window.customcolors['white'],
|
|
||||||
activebackground = window.customcolors['white'],
|
|
||||||
borderwidth = 2)
|
|
||||||
|
|
||||||
try:
|
|
||||||
anibtn = Animation(os.path.dirname(os.path.abspath( __file__ )) + "/graphics/pykms_Keyhole_%s.gif" %position,
|
|
||||||
window, buttons[position], loop = False)
|
|
||||||
anilbl = Animation(os.path.dirname(os.path.abspath( __file__ )) + "/graphics/pykms_Arrow_%s.gif" %position,
|
|
||||||
window, labels[position], loop = True)
|
|
||||||
|
|
||||||
def animationwait(master, button, btn_animation, lbl_animation):
|
|
||||||
while btn_animation.cancelid:
|
|
||||||
pass
|
|
||||||
sleep(1)
|
|
||||||
x, y = master.winfo_pointerxy()
|
|
||||||
if master.winfo_containing(x, y) == button:
|
|
||||||
lbl_animation.start()
|
|
||||||
|
|
||||||
def animationcombo(master, button, btn_animation, lbl_animation):
|
|
||||||
wait_thread = threading.Thread(target = animationwait,
|
|
||||||
args = (master, button, btn_animation, lbl_animation),
|
|
||||||
name = "Thread-WaitAni")
|
|
||||||
wait_thread.setDaemon(True)
|
|
||||||
wait_thread.start()
|
|
||||||
lbl_animation.stop()
|
|
||||||
btn_animation.start()
|
|
||||||
|
|
||||||
buttons[position].bind("<ButtonPress>", lambda event, anim1 = anibtn, anim2 = anilbl,
|
|
||||||
bt = buttons[position], win = window:
|
|
||||||
animationcombo(win, bt, anim1, anim2))
|
|
||||||
buttons[position].bind("<Enter>", anilbl.start)
|
|
||||||
buttons[position].bind("<Leave>", anilbl.stop)
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
buttons[position].config(activebackground = window.customcolors['blue'],
|
|
||||||
foreground = window.customcolors['blue'])
|
|
||||||
labels[position].config(background = window.customcolors['lavender'])
|
|
||||||
|
|
||||||
if position == "Left":
|
|
||||||
buttons[position].config(text = '<<')
|
|
||||||
elif position == "Right":
|
|
||||||
buttons[position].config(text = '>>')
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
class ListboxOfRadiobuttons(tk.Frame):
|
|
||||||
def __init__(self, master, radios, font, changed, **kwargs):
|
|
||||||
tk.Frame.__init__(self, master)
|
|
||||||
|
|
||||||
self.master = master
|
|
||||||
self.radios = radios
|
|
||||||
self.font = font
|
|
||||||
self.changed = changed
|
|
||||||
|
|
||||||
self.scrollv = tk.Scrollbar(self, orient = "vertical")
|
|
||||||
self.textbox = tk.Text(self, yscrollcommand = self.scrollv.set, **kwargs)
|
|
||||||
self.scrollv.config(command = self.textbox.yview)
|
|
||||||
# layout.
|
|
||||||
self.scrollv.pack(side = "right", fill = "y")
|
|
||||||
self.textbox.pack(side = "left", fill = "both", expand = True)
|
|
||||||
# create radiobuttons.
|
|
||||||
self.radiovar = tk.StringVar()
|
|
||||||
self.radiovar.set('FILE')
|
|
||||||
self.create()
|
|
||||||
|
|
||||||
def create(self):
|
|
||||||
self.rdbtns = []
|
|
||||||
for n, nameradio in enumerate(self.radios):
|
|
||||||
rdbtn = tk.Radiobutton(self, text = nameradio, value = nameradio, variable = self.radiovar,
|
|
||||||
font = self.font, indicatoron = 0, width = 15,
|
|
||||||
borderwidth = 3, selectcolor = 'yellow', command = self.change)
|
|
||||||
self.textbox.window_create("end", window = rdbtn)
|
|
||||||
# to force one checkbox per line
|
|
||||||
if n != len(self.radios) - 1:
|
|
||||||
self.textbox.insert("end", "\n")
|
|
||||||
self.rdbtns.append(rdbtn)
|
|
||||||
self.textbox.configure(state = "disabled")
|
|
||||||
|
|
||||||
def change(self):
|
|
||||||
st = self.state()
|
|
||||||
for widget, default in self.changed:
|
|
||||||
wclass = widget.winfo_class()
|
|
||||||
if st in ['STDOUT', 'FILEOFF']:
|
|
||||||
if wclass == 'Entry':
|
|
||||||
widget.delete(0, 'end')
|
|
||||||
widget.configure(state = "disabled")
|
|
||||||
elif wclass == 'TCombobox':
|
|
||||||
if st == 'STDOUT':
|
|
||||||
widget.set(default)
|
|
||||||
widget.configure(state = "readonly")
|
|
||||||
elif st == 'FILEOFF':
|
|
||||||
widget.set('')
|
|
||||||
widget.configure(state = "disabled")
|
|
||||||
elif st in ['FILE', 'FILESTDOUT', 'STDOUTOFF']:
|
|
||||||
if wclass == 'Entry':
|
|
||||||
widget.configure(state = "normal")
|
|
||||||
widget.delete(0, 'end')
|
|
||||||
widget.insert('end', default)
|
|
||||||
widget.xview_moveto(1)
|
|
||||||
elif wclass == 'TCombobox':
|
|
||||||
widget.configure(state = "readonly")
|
|
||||||
widget.set(default)
|
|
||||||
elif wclass == 'Button':
|
|
||||||
widget.configure(state = "normal")
|
|
||||||
|
|
||||||
def configure(self, state):
|
|
||||||
for rb in self.rdbtns:
|
|
||||||
rb.configure(state = state)
|
|
||||||
|
|
||||||
def state(self):
|
|
||||||
return self.radiovar.get()
|
|
|
@ -194,9 +194,6 @@ def logger_create(log_obj, config, mode = 'a'):
|
||||||
frmt_name = '%(name)s '
|
frmt_name = '%(name)s '
|
||||||
|
|
||||||
from pykms_Server import serverthread
|
from pykms_Server import serverthread
|
||||||
if serverthread.with_gui:
|
|
||||||
frmt_std = frmt_name + frmt_std
|
|
||||||
frmt_min = frmt_name + frmt_min
|
|
||||||
|
|
||||||
def apply_formatter(levelnum, formats, handler, color = False):
|
def apply_formatter(levelnum, formats, handler, color = False):
|
||||||
levelformdict = {}
|
levelformdict = {}
|
||||||
|
@ -227,7 +224,7 @@ def logger_create(log_obj, config, mode = 'a'):
|
||||||
log_obj.setLevel(config['loglevel'])
|
log_obj.setLevel(config['loglevel'])
|
||||||
|
|
||||||
#------------------------------------------------------------------------------------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
def check_dir(path, where, log_obj = None, argument = '-F/--logfile', typefile = '.log'):
|
def check_dir(path, where, log_obj = None, argument = '-F/--logfile'):
|
||||||
filename = os.path.basename(path)
|
filename = os.path.basename(path)
|
||||||
pathname = os.path.dirname(path)
|
pathname = os.path.dirname(path)
|
||||||
extension = os.path.splitext(filename)[1]
|
extension = os.path.splitext(filename)[1]
|
||||||
|
@ -243,9 +240,6 @@ def check_dir(path, where, log_obj = None, argument = '-F/--logfile', typefile =
|
||||||
pathname = filename
|
pathname = filename
|
||||||
pretty_printer(log_obj = log_obj, where = where, to_exit = True,
|
pretty_printer(log_obj = log_obj, where = where, to_exit = True,
|
||||||
put_text = msg_dir %(argument, pathname))
|
put_text = msg_dir %(argument, pathname))
|
||||||
elif not extension.lower() == typefile:
|
|
||||||
pretty_printer(log_obj = log_obj, where = where, to_exit = True,
|
|
||||||
put_text = msg_fil %(argument, typefile, extension))
|
|
||||||
|
|
||||||
def check_logfile(optionlog, defaultlog, where):
|
def check_logfile(optionlog, defaultlog, where):
|
||||||
if not isinstance(optionlog, list):
|
if not isinstance(optionlog, list):
|
||||||
|
@ -524,7 +518,7 @@ def check_setup(config, options, logger, where):
|
||||||
# Check logfile.
|
# Check logfile.
|
||||||
config['logfile'] = check_logfile(config['logfile'], options['lfile']['def'], where = where)
|
config['logfile'] = check_logfile(config['logfile'], options['lfile']['def'], where = where)
|
||||||
|
|
||||||
# Check logsize (py-kms Gui).
|
# Check logsize
|
||||||
if config['logsize'] == "":
|
if config['logsize'] == "":
|
||||||
if any(opt in ['STDOUT', 'FILEOFF'] for opt in config['logfile']):
|
if any(opt in ['STDOUT', 'FILEOFF'] for opt in config['logfile']):
|
||||||
# set a recognized size never used.
|
# set a recognized size never used.
|
||||||
|
@ -533,7 +527,7 @@ def check_setup(config, options, logger, where):
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}argument `-S/--logsize`: invalid with: '%s'. Exiting...{end}" %config['logsize'],
|
pretty_printer(put_text = "{reverse}{red}{bold}argument `-S/--logsize`: invalid with: '%s'. Exiting...{end}" %config['logsize'],
|
||||||
where = where, to_exit = True)
|
where = where, to_exit = True)
|
||||||
|
|
||||||
# Check loglevel (py-kms Gui).
|
# Check loglevel
|
||||||
if config['loglevel'] == "":
|
if config['loglevel'] == "":
|
||||||
# set a recognized level never used.
|
# set a recognized level never used.
|
||||||
config['loglevel'] = 'ERROR'
|
config['loglevel'] = 'ERROR'
|
||||||
|
|
|
@ -9,23 +9,20 @@ import uuid
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import pickle
|
|
||||||
import socketserver
|
import socketserver
|
||||||
import queue as Queue
|
import queue as Queue
|
||||||
import selectors
|
import selectors
|
||||||
from getpass import getuser
|
|
||||||
from tempfile import gettempdir
|
|
||||||
from time import monotonic as time
|
from time import monotonic as time
|
||||||
|
|
||||||
import pykms_RpcBind, pykms_RpcRequest
|
import pykms_RpcBind, pykms_RpcRequest
|
||||||
from pykms_RpcBase import rpcBase
|
from pykms_RpcBase import rpcBase
|
||||||
from pykms_Dcerpc import MSRPCHeader
|
from pykms_Dcerpc import MSRPCHeader
|
||||||
from pykms_Misc import check_setup, check_lcid, check_dir, check_other
|
from pykms_Misc import check_setup, check_lcid, check_other
|
||||||
from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp
|
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_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_Format import enco, deco, pretty_printer, justify
|
||||||
from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job
|
|
||||||
from pykms_Connect import MultipleListener
|
from pykms_Connect import MultipleListener
|
||||||
|
from pykms_Sql import sql_initialize
|
||||||
|
|
||||||
srv_version = "py-kms_2020-10-01"
|
srv_version = "py-kms_2020-10-01"
|
||||||
__license__ = "The Unlicense"
|
__license__ = "The Unlicense"
|
||||||
|
@ -136,7 +133,8 @@ class server_thread(threading.Thread):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.server = None
|
self.server = None
|
||||||
self.is_running_server, self.with_gui, self.checked = [False for _ in range(3)]
|
self.is_running_server = False
|
||||||
|
self.checked = False
|
||||||
self.is_running_thread = threading.Event()
|
self.is_running_thread = threading.Event()
|
||||||
|
|
||||||
def terminate_serve(self):
|
def terminate_serve(self):
|
||||||
|
@ -171,21 +169,25 @@ class server_thread(threading.Thread):
|
||||||
self.server.pykms_serve()
|
self.server.pykms_serve()
|
||||||
except (SystemExit, Exception) as e:
|
except (SystemExit, Exception) as e:
|
||||||
self.eject = True
|
self.eject = True
|
||||||
if not self.with_gui:
|
raise
|
||||||
raise
|
|
||||||
else:
|
|
||||||
if isinstance(e, SystemExit):
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
loggersrv = logging.getLogger('logsrv')
|
loggersrv = logging.getLogger('logsrv')
|
||||||
|
|
||||||
|
def _str2bool(v):
|
||||||
|
if isinstance(v, bool):
|
||||||
|
return v
|
||||||
|
if v.lower() in ('yes', 'true', 't', 'y', '1'):
|
||||||
|
return True
|
||||||
|
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
raise ValueError('Boolean value expected.')
|
||||||
|
|
||||||
# 'help' string - 'default' value - 'dest' string.
|
# 'help' string - 'default' value - 'dest' string.
|
||||||
srv_options = {
|
srv_options = {
|
||||||
'ip' : {'help' : 'The IP address (IPv4 or IPv6) to listen on. The default is \"0.0.0.0\" (all interfaces).', 'def' : "0.0.0.0", 'des' : "ip"},
|
'ip' : {'help' : 'The IP address (IPv4 or IPv6) to listen on. The default is \"::\" (all interfaces).', 'def' : "::", 'des' : "ip"},
|
||||||
'port' : {'help' : 'The network port to listen on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
|
'port' : {'help' : 'The network port to listen on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
|
||||||
'epid' : {'help' : 'Use this option to manually specify an ePID to use. If no ePID is specified, a random ePID will be auto generated.',
|
'epid' : {'help' : 'Use this option to manually specify an ePID to use. If no ePID is specified, a random ePID will be auto generated.',
|
||||||
'def' : None, 'des' : "epid"},
|
'def' : None, 'des' : "epid"},
|
||||||
|
@ -197,19 +199,18 @@ for server OSes and Office >=5', 'def' : None, 'des' : "clientcount"},
|
||||||
'def' : 120, 'des': "activation"},
|
'def' : 120, 'des': "activation"},
|
||||||
'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).',
|
'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).',
|
||||||
'def' : 1440 * 7, 'des' : "renewal"},
|
'def' : 1440 * 7, 'des' : "renewal"},
|
||||||
'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default. \
|
'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default.', 'def' : False,
|
||||||
If enabled the default .db file is \"pykms_database.db\". You can also provide a specific location.', 'def' : False,
|
|
||||||
'file': os.path.join('.', 'pykms_database.db'), 'des' : "sqlite"},
|
'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. \
|
'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.',
|
Type \"RANDOM\" to auto-generate the HWID.',
|
||||||
'def' : "364F463A8863D35F", 'des' : "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.',
|
'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"},
|
'def' : None, 'des' : "timeoutidle"},
|
||||||
'time1' : {'help' : 'Set the maximum time to wait for sending / receiving a request / response. Default is no timeout.',
|
'time1' : {'help' : 'Set the maximum time to wait for sending / receiving a request / response. Default is no timeout.',
|
||||||
'def' : None, 'des' : "timeoutsndrcv"},
|
'def' : None, 'des' : "timeoutsndrcv"},
|
||||||
'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Deactivated by default.',
|
'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Deactivated by default.',
|
||||||
'def' : False, 'des' : "asyncmsg"},
|
'def' : False, 'des' : "asyncmsg"},
|
||||||
'llevel' : {'help' : 'Use this option to set a log level. The default is \"ERROR\".', 'def' : "ERROR", 'des' : "loglevel",
|
'llevel' : {'help' : 'Use this option to set a log level. The default is \"WARNING\".', 'def' : "WARNING", 'des' : "loglevel",
|
||||||
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MININFO"]},
|
'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MININFO"]},
|
||||||
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logserver.log\". \
|
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logserver.log\". \
|
||||||
Type \"STDOUT\" to view log info on stdout. Type \"FILESTDOUT\" to combine previous actions. \
|
Type \"STDOUT\" to view log info on stdout. Type \"FILESTDOUT\" to combine previous actions. \
|
||||||
|
@ -220,8 +221,8 @@ Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to
|
||||||
'backlog' : {'help' : 'Specifies the maximum length of the queue of pending connections. Default is \"5\".', 'def' : 5, 'des': "backlog"},
|
'backlog' : {'help' : 'Specifies the maximum length of the queue of pending connections. Default is \"5\".', 'def' : 5, 'des': "backlog"},
|
||||||
'reuse' : {'help' : 'Do not allows binding / listening to the same address and port. Reusing port is activated by default.', 'def' : True,
|
'reuse' : {'help' : 'Do not allows binding / listening to the same address and port. Reusing port is activated by default.', 'def' : True,
|
||||||
'des': "reuse"},
|
'des': "reuse"},
|
||||||
'dual' : {'help' : 'Allows listening to an IPv6 address also accepting connections via IPv4. Deactivated by default.',
|
'dual' : {'help' : 'Allows listening to an IPv6 address while also accepting connections via IPv4. If used, it refers to all addresses (main and additional). Activated by default. Pass in "false" or "true" to disable or enable.',
|
||||||
'def' : False, 'des': "dual"}
|
'def' : True, 'des': "dual"}
|
||||||
}
|
}
|
||||||
|
|
||||||
def server_options():
|
def server_options():
|
||||||
|
@ -257,15 +258,6 @@ def server_options():
|
||||||
|
|
||||||
server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
||||||
|
|
||||||
## Daemon (Etrigan) parsing.
|
|
||||||
daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False)
|
|
||||||
daemon_subparser = daemon_parser.add_subparsers(dest = "mode")
|
|
||||||
|
|
||||||
etrigan_parser = daemon_subparser.add_parser("etrigan", add_help = False)
|
|
||||||
etrigan_parser.add_argument("-g", "--gui", action = "store_const", dest = 'gui', const = True, default = False,
|
|
||||||
help = "Enable py-kms GUI usage.")
|
|
||||||
etrigan_parser = Etrigan_parser(parser = etrigan_parser)
|
|
||||||
|
|
||||||
## Connection parsing.
|
## Connection parsing.
|
||||||
connection_parser = KmsParser(description = "connect options", add_help = False)
|
connection_parser = KmsParser(description = "connect options", add_help = False)
|
||||||
connection_subparser = connection_parser.add_subparsers(dest = "mode")
|
connection_subparser = connection_parser.add_subparsers(dest = "mode")
|
||||||
|
@ -277,7 +269,7 @@ def server_options():
|
||||||
help = srv_options['backlog']['help'], type = int)
|
help = srv_options['backlog']['help'], type = int)
|
||||||
connect_parser.add_argument("-u", "--no-reuse", action = "append_const", dest = srv_options['reuse']['des'], const = False, default = [],
|
connect_parser.add_argument("-u", "--no-reuse", action = "append_const", dest = srv_options['reuse']['des'], const = False, default = [],
|
||||||
help = srv_options['reuse']['help'])
|
help = srv_options['reuse']['help'])
|
||||||
connect_parser.add_argument("-d", "--dual", action = "store_true", dest = srv_options['dual']['des'], default = srv_options['dual']['def'],
|
connect_parser.add_argument("-d", "--dual", type = _str2bool, dest = srv_options['dual']['des'], default = srv_options['dual']['def'],
|
||||||
help = srv_options['dual']['help'])
|
help = srv_options['dual']['help'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -285,16 +277,14 @@ def server_options():
|
||||||
|
|
||||||
# Run help.
|
# Run help.
|
||||||
if any(arg in ["-h", "--help"] for arg in userarg):
|
if any(arg in ["-h", "--help"] for arg in userarg):
|
||||||
KmsParserHelp().printer(parsers = [server_parser, (daemon_parser, etrigan_parser),
|
KmsParserHelp().printer(parsers = [server_parser, (connection_parser, connect_parser)])
|
||||||
(connection_parser, connect_parser)])
|
|
||||||
|
|
||||||
# Get stored arguments.
|
# Get stored arguments.
|
||||||
pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser)
|
pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser)
|
||||||
etrigan_zeroarg, etrigan_onearg = kms_parser_get(etrigan_parser)
|
|
||||||
connect_zeroarg, connect_onearg = kms_parser_get(connect_parser)
|
connect_zeroarg, connect_onearg = kms_parser_get(connect_parser)
|
||||||
subdict = {'etrigan' : (etrigan_zeroarg, etrigan_onearg, daemon_parser.parse_args),
|
subdict = {
|
||||||
'connect' : (connect_zeroarg, connect_onearg, connection_parser.parse_args)
|
'connect' : (connect_zeroarg, connect_onearg, connection_parser.parse_args)
|
||||||
}
|
}
|
||||||
subpars = list(subdict.keys())
|
subpars = list(subdict.keys())
|
||||||
pykmssrv_zeroarg += subpars # add subparsers
|
pykmssrv_zeroarg += subpars # add subparsers
|
||||||
|
|
||||||
|
@ -310,14 +300,7 @@ def server_options():
|
||||||
if subindx:
|
if subindx:
|
||||||
# Set `daemon options` and/or `connect options` for server dict config.
|
# Set `daemon options` and/or `connect options` for server dict config.
|
||||||
# example cases:
|
# example cases:
|
||||||
# 1 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] etrigan daemon_positional [--daemon_optionals] \
|
# 1 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals]
|
||||||
# connect [--connect_optionals]
|
|
||||||
#
|
|
||||||
# 2 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals] etrigan \
|
|
||||||
# daemon_positional [--daemon_optionals]
|
|
||||||
#
|
|
||||||
# 3 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] etrigan daemon_positional [--daemon_optionals]
|
|
||||||
# 4 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals]
|
|
||||||
first = subindx[0][0]
|
first = subindx[0][0]
|
||||||
# initial.
|
# initial.
|
||||||
kms_parser_check_optionals(userarg[0 : first], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
|
kms_parser_check_optionals(userarg[0 : first], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
|
||||||
|
@ -339,7 +322,7 @@ def server_options():
|
||||||
else:
|
else:
|
||||||
# Update `pykms options` for server dict config.
|
# Update `pykms options` for server dict config.
|
||||||
# example case:
|
# example case:
|
||||||
# 5 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals]
|
# 2 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals]
|
||||||
kms_parser_check_optionals(userarg, pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
|
kms_parser_check_optionals(userarg, pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
|
||||||
kms_parser_check_positionals(srv_config, server_parser.parse_args)
|
kms_parser_check_positionals(srv_config, server_parser.parse_args)
|
||||||
|
|
||||||
|
@ -348,63 +331,6 @@ def server_options():
|
||||||
except KmsParserException as e:
|
except KmsParserException as e:
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
|
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
|
||||||
|
|
||||||
class Etrigan_Check(Etrigan_check):
|
|
||||||
def emit_opt_err(self, msg):
|
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}%s{end}" %msg, to_exit = True)
|
|
||||||
|
|
||||||
class Etrigan(Etrigan):
|
|
||||||
def emit_message(self, message, to_exit = False):
|
|
||||||
if not self.mute:
|
|
||||||
pretty_printer(put_text = "{reverse}{green}{bold}%s{end}" %message)
|
|
||||||
if to_exit:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def emit_error(self, message, to_exit = True):
|
|
||||||
if not self.mute:
|
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}%s{end}" %message, to_exit = True)
|
|
||||||
|
|
||||||
def server_daemon():
|
|
||||||
if 'etrigan' in srv_config.values():
|
|
||||||
path = os.path.join(gettempdir(), 'pykms_config.pickle')
|
|
||||||
|
|
||||||
if srv_config['operation'] in ['stop', 'restart', 'status'] and len(sys.argv[1:]) > 2:
|
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}too much arguments with etrigan '%s'. Exiting...{end}" %srv_config['operation'],
|
|
||||||
to_exit = True)
|
|
||||||
|
|
||||||
# Check file arguments.
|
|
||||||
Etrigan_Check().checkfile(srv_config['etriganpid'], '--etrigan-pid', '.pid')
|
|
||||||
Etrigan_Check().checkfile(srv_config['etriganlog'], '--etrigan-log', '.log')
|
|
||||||
|
|
||||||
if srv_config['gui']:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if srv_config['operation'] == 'start':
|
|
||||||
with open(path, 'wb') as file:
|
|
||||||
pickle.dump(srv_config, file, protocol = pickle.HIGHEST_PROTOCOL)
|
|
||||||
elif srv_config['operation'] in ['stop', 'status', 'restart']:
|
|
||||||
with open(path, 'rb') as file:
|
|
||||||
old_srv_config = pickle.load(file)
|
|
||||||
old_srv_config = {x: old_srv_config[x] for x in old_srv_config if x not in ['operation']}
|
|
||||||
srv_config.update(old_srv_config)
|
|
||||||
|
|
||||||
serverdaemon = Etrigan(srv_config['etriganpid'],
|
|
||||||
logfile = srv_config['etriganlog'], loglevel = srv_config['etriganlev'],
|
|
||||||
mute = srv_config['etriganmute'], pause_loop = None)
|
|
||||||
|
|
||||||
if srv_config['operation'] in ['start', 'restart']:
|
|
||||||
serverdaemon.want_quit = True
|
|
||||||
if srv_config['gui']:
|
|
||||||
serverdaemon.funcs_to_daemonize = [server_with_gui]
|
|
||||||
else:
|
|
||||||
server_without_gui = ServerWithoutGui()
|
|
||||||
serverdaemon.funcs_to_daemonize = [server_without_gui.start, server_without_gui.join]
|
|
||||||
indx_for_clean = lambda: (0, )
|
|
||||||
serverdaemon.quit_on_stop = [indx_for_clean, server_without_gui.clean]
|
|
||||||
elif srv_config['operation'] == 'stop':
|
|
||||||
os.remove(path)
|
|
||||||
|
|
||||||
Etrigan_job(srv_config['operation'], serverdaemon)
|
|
||||||
|
|
||||||
def server_check():
|
def server_check():
|
||||||
# Setup and some checks.
|
# Setup and some checks.
|
||||||
check_setup(srv_config, srv_options, loggersrv, where = "srv")
|
check_setup(srv_config, srv_options, loggersrv, where = "srv")
|
||||||
|
@ -446,25 +372,25 @@ def server_check():
|
||||||
|
|
||||||
# Check sqlite.
|
# Check sqlite.
|
||||||
if srv_config['sqlite']:
|
if srv_config['sqlite']:
|
||||||
if isinstance(srv_config['sqlite'], str):
|
if srv_config['sqlite'] is True: # Resolve bool to the default path
|
||||||
check_dir(srv_config['sqlite'], 'srv', log_obj = loggersrv.error, argument = '-s/--sqlite', typefile = '.db')
|
|
||||||
elif srv_config['sqlite'] is True:
|
|
||||||
srv_config['sqlite'] = srv_options['sql']['file']
|
srv_config['sqlite'] = srv_options['sql']['file']
|
||||||
|
if os.path.isdir(srv_config['sqlite']):
|
||||||
|
pretty_printer(log_obj = loggersrv.warning,
|
||||||
|
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:
|
try:
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
sql_initialize(srv_config['sqlite'])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pretty_printer(log_obj = loggersrv.warning,
|
pretty_printer(log_obj = loggersrv.warning,
|
||||||
put_text = "{reverse}{yellow}{bold}Module 'sqlite3' not installed, database support disabled.{end}")
|
put_text = "{reverse}{yellow}{bold}Module 'sqlite3' not installed, database support disabled.{end}")
|
||||||
srv_config['sqlite'] = False
|
srv_config['sqlite'] = False
|
||||||
|
|
||||||
# Check other specific server options.
|
# Check other specific server options.
|
||||||
opts = [('clientcount', '-c/--client-count'),
|
opts = [('clientcount', '-c/--client-count'),
|
||||||
('timeoutidle', '-t0/--timeout-idle'),
|
('timeoutidle', '-t0/--timeout-idle'),
|
||||||
('timeoutsndrcv', '-t1/--timeout-sndrcv')]
|
('timeoutsndrcv', '-t1/--timeout-sndrcv')]
|
||||||
if serverthread.with_gui:
|
|
||||||
opts += [('activation', '-a/--activation-interval'),
|
|
||||||
('renewal', '-r/--renewal-interval')]
|
|
||||||
check_other(srv_config, opts, loggersrv, where = 'srv')
|
check_other(srv_config, opts, loggersrv, where = 'srv')
|
||||||
|
|
||||||
# Check further addresses / ports.
|
# Check further addresses / ports.
|
||||||
|
@ -495,14 +421,12 @@ def server_create():
|
||||||
all_address = [(
|
all_address = [(
|
||||||
srv_config['ip'], srv_config['port'],
|
srv_config['ip'], srv_config['port'],
|
||||||
(srv_config['backlog_main'] if 'backlog_main' in srv_config else srv_options['backlog']['def']),
|
(srv_config['backlog_main'] if 'backlog_main' in srv_config else srv_options['backlog']['def']),
|
||||||
(srv_config['reuse_main'] if 'reuse_main' in srv_config else False if getuser() == 'WDAGUtilityAccount' \
|
(srv_config['reuse_main'] if 'reuse_main' in srv_config else srv_options['reuse']['def'])
|
||||||
else srv_options['reuse']['def'])
|
|
||||||
)]
|
)]
|
||||||
log_address = "TCP server listening at %s on port %d" %(srv_config['ip'], srv_config['port'])
|
log_address = "TCP server listening at %s on port %d" %(srv_config['ip'], srv_config['port'])
|
||||||
|
|
||||||
if 'listen' in srv_config:
|
if 'listen' in srv_config:
|
||||||
for l, b, r in zip(srv_config['listen'], srv_config['backlog'], srv_config['reuse']):
|
for l, b, r in zip(srv_config['listen'], srv_config['backlog'], srv_config['reuse']):
|
||||||
r = (False if getuser() == 'WDAGUtilityAccount' else r)
|
|
||||||
all_address.append(l + (b,) + (r,))
|
all_address.append(l + (b,) + (r,))
|
||||||
log_address += justify("at %s on port %d" %(l[0], l[1]), indent = 56)
|
log_address += justify("at %s on port %d" %(l[0], l[1]), indent = 56)
|
||||||
|
|
||||||
|
@ -546,32 +470,14 @@ def server_main_terminal():
|
||||||
server_check()
|
server_check()
|
||||||
serverthread.checked = True
|
serverthread.checked = True
|
||||||
|
|
||||||
if 'etrigan' not in srv_config.values():
|
# Run threaded server.
|
||||||
# (without GUI) and (without daemon).
|
serverqueue.put('start')
|
||||||
# Run threaded server.
|
# Wait to finish.
|
||||||
serverqueue.put('start')
|
try:
|
||||||
# Wait to finish.
|
while serverthread.is_alive():
|
||||||
try:
|
serverthread.join(timeout = 0.5)
|
||||||
while serverthread.is_alive():
|
except (KeyboardInterrupt, SystemExit):
|
||||||
serverthread.join(timeout = 0.5)
|
server_terminate(serverthread, exit_server = True, exit_thread = True)
|
||||||
except (KeyboardInterrupt, SystemExit):
|
|
||||||
server_terminate(serverthread, exit_server = True, exit_thread = True)
|
|
||||||
else:
|
|
||||||
# (with or without GUI) and (with daemon)
|
|
||||||
# Setup daemon (eventually).
|
|
||||||
server_daemon()
|
|
||||||
|
|
||||||
def server_with_gui():
|
|
||||||
import pykms_GuiBase
|
|
||||||
|
|
||||||
root = pykms_GuiBase.KmsGui()
|
|
||||||
root.title(pykms_GuiBase.gui_description + ' (' + pykms_GuiBase.gui_version + ')')
|
|
||||||
root.mainloop()
|
|
||||||
|
|
||||||
def server_main_no_terminal():
|
|
||||||
# Run tkinter GUI.
|
|
||||||
# (with GUI) and (without daemon).
|
|
||||||
server_with_gui()
|
|
||||||
|
|
||||||
class kmsServerHandler(socketserver.BaseRequestHandler):
|
class kmsServerHandler(socketserver.BaseRequestHandler):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
|
@ -585,7 +491,7 @@ class kmsServerHandler(socketserver.BaseRequestHandler):
|
||||||
try:
|
try:
|
||||||
self.data = self.request.recv(1024)
|
self.data = self.request.recv(1024)
|
||||||
if self.data == '' or not self.data:
|
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}")
|
put_text = "{reverse}{yellow}{bold}No data received.{end}")
|
||||||
break
|
break
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
|
@ -632,14 +538,8 @@ class kmsServerHandler(socketserver.BaseRequestHandler):
|
||||||
|
|
||||||
serverqueue = Queue.Queue(maxsize = 0)
|
serverqueue = Queue.Queue(maxsize = 0)
|
||||||
serverthread = server_thread(serverqueue, name = "Thread-Srv")
|
serverthread = server_thread(serverqueue, name = "Thread-Srv")
|
||||||
serverthread.setDaemon(True)
|
serverthread.daemon = True
|
||||||
serverthread.start()
|
serverthread.start()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if sys.stdout.isatty():
|
server_main_terminal()
|
||||||
server_main_terminal()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
server_main_no_terminal()
|
|
||||||
except:
|
|
||||||
server_main_terminal()
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import datetime
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -18,21 +19,40 @@ loggersrv = logging.getLogger('logsrv')
|
||||||
def sql_initialize(dbName):
|
def sql_initialize(dbName):
|
||||||
if not os.path.isfile(dbName):
|
if not os.path.isfile(dbName):
|
||||||
# Initialize the database.
|
# Initialize the database.
|
||||||
|
loggersrv.debug(f'Initializing database file "{dbName}"...')
|
||||||
con = None
|
con = None
|
||||||
try:
|
try:
|
||||||
con = sqlite3.connect(dbName)
|
con = sqlite3.connect(dbName)
|
||||||
cur = con.cursor()
|
cur = con.cursor()
|
||||||
cur.execute("CREATE TABLE clients(clientMachineId TEXT, machineName TEXT, applicationId TEXT, skuId TEXT, \
|
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))")
|
||||||
licenseStatus TEXT, lastRequestTime INTEGER, kmsEpid TEXT, requestCount INTEGER)")
|
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
pretty_printer(log_obj = loggersrv.error, to_exit = True,
|
pretty_printer(log_obj = loggersrv.error, to_exit = True, put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
|
||||||
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
|
|
||||||
finally:
|
finally:
|
||||||
if con:
|
if con:
|
||||||
con.commit()
|
con.commit()
|
||||||
con.close()
|
con.close()
|
||||||
|
|
||||||
|
def sql_get_all(dbName):
|
||||||
|
if not os.path.isfile(dbName):
|
||||||
|
return None
|
||||||
|
with sqlite3.connect(dbName) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
cur.execute("SELECT * 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]
|
||||||
|
})
|
||||||
|
return clients
|
||||||
|
|
||||||
def sql_update(dbName, infoDict):
|
def sql_update(dbName, infoDict):
|
||||||
con = None
|
con = None
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
import os, uuid, datetime
|
||||||
|
from flask import Flask, render_template
|
||||||
|
from pykms_Sql import sql_get_all
|
||||||
|
from pykms_DB2Dict import kmsDB2Dict
|
||||||
|
|
||||||
|
def _random_uuid():
|
||||||
|
return str(uuid.uuid4()).replace('-', '_')
|
||||||
|
|
||||||
|
_serve_count = 0
|
||||||
|
def _increase_serve_count():
|
||||||
|
global _serve_count
|
||||||
|
_serve_count += 1
|
||||||
|
|
||||||
|
def _get_serve_count():
|
||||||
|
return _serve_count
|
||||||
|
|
||||||
|
_kms_items = None
|
||||||
|
_kms_items_ignored = None
|
||||||
|
def _get_kms_items_cache():
|
||||||
|
global _kms_items, _kms_items_ignored
|
||||||
|
if _kms_items is None:
|
||||||
|
_kms_items = {}
|
||||||
|
_kms_items_ignored = 0
|
||||||
|
queue = [kmsDB2Dict()]
|
||||||
|
while len(queue):
|
||||||
|
item = queue.pop(0)
|
||||||
|
if isinstance(item, list):
|
||||||
|
for i in item:
|
||||||
|
queue.append(i)
|
||||||
|
elif isinstance(item, dict):
|
||||||
|
if 'KmsItems' in item:
|
||||||
|
queue.append(item['KmsItems'])
|
||||||
|
elif 'SkuItems' in item:
|
||||||
|
queue.append(item['SkuItems'])
|
||||||
|
elif 'Gvlk' in item:
|
||||||
|
if len(item['Gvlk']):
|
||||||
|
_kms_items[item['DisplayName']] = item['Gvlk']
|
||||||
|
else:
|
||||||
|
_kms_items_ignored += 1
|
||||||
|
#else:
|
||||||
|
# print(item)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f'Unknown type: {type(item)}')
|
||||||
|
return _kms_items, _kms_items_ignored
|
||||||
|
|
||||||
|
app = Flask('pykms_webui')
|
||||||
|
app.jinja_env.globals['start_time'] = datetime.datetime.now()
|
||||||
|
app.jinja_env.globals['get_serve_count'] = _get_serve_count
|
||||||
|
app.jinja_env.globals['random_uuid'] = _random_uuid
|
||||||
|
app.jinja_env.globals['version_info'] = None
|
||||||
|
|
||||||
|
_version_info_path = os.environ.get('PYKMS_VERSION_PATH', '../VERSION')
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
_dbEnvVarName = 'PYKMS_SQLITE_DB_PATH'
|
||||||
|
def _env_check():
|
||||||
|
if _dbEnvVarName not in os.environ:
|
||||||
|
raise Exception(f'Environment variable is not set: {_dbEnvVarName}')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def root():
|
||||||
|
_increase_serve_count()
|
||||||
|
error = None
|
||||||
|
# Get the db name / path
|
||||||
|
dbPath = None
|
||||||
|
if _dbEnvVarName in os.environ:
|
||||||
|
dbPath = os.environ.get(_dbEnvVarName)
|
||||||
|
else:
|
||||||
|
error = f'Environment variable is not set: {_dbEnvVarName}'
|
||||||
|
# Fetch all clients from the database.
|
||||||
|
clients = None
|
||||||
|
try:
|
||||||
|
if dbPath:
|
||||||
|
clients = sql_get_all(dbPath)
|
||||||
|
except Exception as e:
|
||||||
|
error = f'Error while loading database: {e}'
|
||||||
|
countClients = len(clients) if clients else 0
|
||||||
|
countClientsWindows = len([c for c in clients if c['applicationId'] == 'Windows']) if clients else 0
|
||||||
|
countClientsOffice = countClients - countClientsWindows
|
||||||
|
return render_template(
|
||||||
|
'clients.html',
|
||||||
|
path='/',
|
||||||
|
error=error,
|
||||||
|
clients=clients,
|
||||||
|
count_clients=countClients,
|
||||||
|
count_clients_windows=countClientsWindows,
|
||||||
|
count_clients_office=countClientsOffice,
|
||||||
|
count_projects=len(_get_kms_items_cache()[0])
|
||||||
|
), 200 if error is None else 500
|
||||||
|
|
||||||
|
@app.route('/readyz')
|
||||||
|
def readyz():
|
||||||
|
try:
|
||||||
|
_env_check()
|
||||||
|
except Exception as e:
|
||||||
|
return f'Whooops! {e}', 503
|
||||||
|
if (datetime.datetime.now() - app.jinja_env.globals['start_time']).seconds > 10: # Wait 10 seconds before accepting requests
|
||||||
|
return 'OK', 200
|
||||||
|
else:
|
||||||
|
return 'Not ready', 503
|
||||||
|
|
||||||
|
@app.route('/livez')
|
||||||
|
def livez():
|
||||||
|
try:
|
||||||
|
_env_check()
|
||||||
|
return 'OK', 200 # There are no checks for liveness, so we just return OK
|
||||||
|
except Exception as e:
|
||||||
|
return f'Whooops! {e}', 503
|
||||||
|
|
||||||
|
@app.route('/license')
|
||||||
|
def license():
|
||||||
|
_increase_serve_count()
|
||||||
|
with open(os.environ.get('PYKMS_LICENSE_PATH', '../LICENSE'), 'r') as f:
|
||||||
|
return render_template(
|
||||||
|
'license.html',
|
||||||
|
path='/license/',
|
||||||
|
license=f.read()
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route('/products')
|
||||||
|
def products():
|
||||||
|
_increase_serve_count()
|
||||||
|
items, ignored = _get_kms_items_cache()
|
||||||
|
countProducts = len(items)
|
||||||
|
countProductsWindows = len([i for i in items if 'windows' in i.lower()])
|
||||||
|
countProductsOffice = len([i for i in items if 'office' in i.lower()])
|
||||||
|
return render_template(
|
||||||
|
'products.html',
|
||||||
|
path='/products/',
|
||||||
|
products=items,
|
||||||
|
filtered=ignored,
|
||||||
|
count_products=countProducts,
|
||||||
|
count_products_windows=countProductsWindows,
|
||||||
|
count_products_office=countProductsOffice
|
||||||
|
)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,56 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>py-kms {% block title %}{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename= 'css/bulma.min.css') }}">
|
||||||
|
<style>
|
||||||
|
#content {
|
||||||
|
margin: 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
{% if path != '/' %}
|
||||||
|
div.backtohome {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
{% block style %}{% endblock %}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
{% if path != '/' %}
|
||||||
|
<div class="block backtohome">
|
||||||
|
<a class="button is-normal is-responsive" href="/">
|
||||||
|
Back to home
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="content has-text-centered">
|
||||||
|
<p>
|
||||||
|
<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.
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
for(let element of document.getElementsByClassName('convert_timestamp')) {
|
||||||
|
element.innerText = new Date(element.innerText).toLocaleString();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,103 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}clients{% endblock %}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
th {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if error %}
|
||||||
|
<article class="message is-danger">
|
||||||
|
<div class="message-header">
|
||||||
|
Whoops! Something went wrong...
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% else %}
|
||||||
|
<nav class="level">
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Clients</p>
|
||||||
|
<p class="title">{{ count_clients }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Windows</p>
|
||||||
|
<p class="title">{{ count_clients_windows }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Office</p>
|
||||||
|
<p class="title">{{ count_clients_office }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Products</p>
|
||||||
|
<p class="title"><a href="/products">{{ count_projects }}</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{% if clients %}
|
||||||
|
<table class="table is-striped is-hoverable is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Client ID</th>
|
||||||
|
<th>Machine Name</th>
|
||||||
|
<th>Application ID</th>
|
||||||
|
<th><abbr title="Stock Keeping Unit">SKU</abbr> ID</th>
|
||||||
|
<th>License Status</th>
|
||||||
|
<th>Last Seen</th>
|
||||||
|
<th>KMS <abbr title="Enhanced Privacy ID">EPID</abbr></th>
|
||||||
|
<th>Seen Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for client in clients %}
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
{% else %}
|
||||||
|
{{ client.machineName }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ client.applicationId }}</td>
|
||||||
|
<td>{{ client.skuId }}</td>
|
||||||
|
<td>{{ client.licenseStatus }}</td>
|
||||||
|
<td class="convert_timestamp">{{ client.lastRequestTime }}</td>
|
||||||
|
<td>
|
||||||
|
{% if client.kmsEpid | length > 16 %}
|
||||||
|
<abbr title="{{ client.kmsEpid }}">{{ client.kmsEpid | truncate(16, True, '...') }}</abbr>
|
||||||
|
{% else %}
|
||||||
|
{{ client.kmsEpid }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ client.requestCount }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<article class="message is-warning">
|
||||||
|
<div class="message-header">
|
||||||
|
<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.
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}license{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="block">
|
||||||
|
<pre>{{ license }}</pre>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,53 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}clients{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<nav class="level">
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Products</p>
|
||||||
|
<p class="title">{{ count_products + filtered }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Windows</p>
|
||||||
|
<p class="title">{{ count_products_windows }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Office</p>
|
||||||
|
<p class="title">{{ count_products_office }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading"><abbr title="Empty GLVK or not recognized">Other</abbr></p>
|
||||||
|
<p class="title">{{ filtered }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<table class="table is-bordered is-striped is-hoverable is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th><abbr title="Group Volume License Key">GVLK</abbr></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for name, gvlk in products | dictsort %}
|
||||||
|
{% if gvlk %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ name }}</td>
|
||||||
|
<td><pre>{{ gvlk }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1 @@
|
||||||
|
docker/docker-py3-kms/requirements.txt
|
Loading…
Reference in New Issue