44 Commits

Author SHA1 Message Date
github-actions[bot]
b02cacc8cb [upgrade] 1.0.1 2025-07-10 05:22:12 +00:00
ElevenNotes
efbc374fdf Merge branch 'master' of https://github.com/11notes/docker-kms 2025-07-09 21:39:48 +02:00
ElevenNotes
2fe67967b0 [upgrade] to latest workflow 2025-07-09 21:39:39 +02:00
github-actions[bot]
7fe09c3a65 github-actions[bot]: update README.md 2025-07-09 19:38:36 +00:00
ElevenNotes
abf93ebf36 [upgrade] latest workflows 2025-07-09 21:32:31 +02:00
ElevenNotes
c7ceef1895 [upgrade] 1.0.3 2025-07-09 21:32:18 +02:00
ElevenNotes
79e9f980dd [fix] refactor and better health check 2025-07-09 21:32:00 +02:00
ElevenNotes
75e540239a [upgrade] 1.0.3 2025-07-09 21:31:45 +02:00
ElevenNotes
3b9fdb0518 Merge branch 'master' of https://github.com/11notes/docker-kms 2025-07-09 20:54:14 +02:00
ElevenNotes
8744c5a656 [feature] latest version 2025-07-09 20:53:37 +02:00
github-actions[bot]
d5643d374d github-actions[bot]: update README.md 2025-06-12 05:23:08 +00:00
github-actions[bot]
febdc20df2 [upgrade] 1.0.1 2025-06-12 05:18:49 +00:00
github-actions[bot]
a3c4b0ccbf github-actions[bot]: update README.md 2025-06-11 07:30:03 +00:00
ElevenNotes
f8ec600025 [feature] new style 2025-06-11 08:57:07 +02:00
ElevenNotes
24a9b2f00e [upgrade] latest workflow 2025-06-11 08:56:06 +02:00
ElevenNotes
2e5987e07e [upgrade] 1.0.2 2025-06-11 08:55:54 +02:00
ElevenNotes
6174e7f2e3 [fix] allow IPv6 2025-06-11 08:55:34 +02:00
ElevenNotes
bde8202670 Merge branch 'master' of https://github.com/11notes/docker-kms 2025-05-21 08:53:08 +02:00
ElevenNotes
0e8ba02ebc [cut] KMS_LOGLEVEL and KMS_CLIENTCOUNT 2025-05-21 08:52:58 +02:00
github-actions[bot]
0a8b7acd55 github-actions[bot]: update README.md 2025-05-21 06:48:52 +00:00
ElevenNotes
f4f1ab656f Merge branch 'master' of https://github.com/11notes/docker-kms 2025-05-21 08:44:57 +02:00
ElevenNotes
687d4eebdc [fix] missing input version on downstream workflow for GUI 2025-05-21 08:44:45 +02:00
github-actions[bot]
a90ee477d1 github-actions[bot]: update README.md 2025-05-21 06:27:54 +00:00
ElevenNotes
274c6587ea [upgrade] to latest workflow 2025-05-21 08:20:17 +02:00
ElevenNotes
be06157c03 Merge branch 'master' of https://github.com/11notes/docker-kms 2025-05-21 07:25:49 +02:00
ElevenNotes
468118bf97 [upgrade] to latest workflow 2025-05-21 07:25:40 +02:00
github-actions[bot]
485a5524eb github-actions[bot]: update README.md 2025-05-20 13:48:05 +00:00
ElevenNotes
24b5369071 [cut] KMS_CLIENTCOUNT 2025-05-20 15:42:14 +02:00
ElevenNotes
9da9b799b3 Merge branch 'master' of https://github.com/11notes/docker-kms 2025-05-20 15:32:25 +02:00
ElevenNotes
b676412fc9 [cut] KMS_CLIENTCOUNT 2025-05-20 15:32:18 +02:00
ElevenNotes
89605118da [upgrade] 1.0.1 2025-05-20 15:32:05 +02:00
ElevenNotes
ed61e0a389 [fix] race condition 2025-05-20 15:30:35 +02:00
github-actions[bot]
7dfaf728ea github-actions[bot]: update README.md 2025-05-20 13:15:50 +00:00
ElevenNotes
e41bf5a487 Merge branch 'master' of https://github.com/11notes/docker-kms 2025-05-20 15:07:25 +02:00
ElevenNotes
638cbd9150 [upgrade] 1.0.1 2025-05-20 15:07:15 +02:00
github-actions[bot]
55853de064 github-actions[bot]: update README.md 2025-05-19 13:39:10 +00:00
ElevenNotes
fce33aa489 [upgrade] to latest workflow 2025-05-19 09:02:11 +02:00
ElevenNotes
b9dd62fa54 [feature] add ARM v7 2025-05-19 09:01:59 +02:00
ElevenNotes
7acd95278f Merge branch 'master' of https://github.com/11notes/docker-kms 2025-05-05 19:48:53 +02:00
ElevenNotes
f254a289c2 [upgrade] to latest workflows 2025-05-05 19:48:44 +02:00
github-actions[bot]
727bf1f243 github-actions[bot]: update README.md 2025-05-05 09:03:42 +00:00
ElevenNotes
2dcd91990a [upgrade] switch to fork with semver 2025-05-05 10:58:28 +02:00
ElevenNotes
7519a01ba5 Merge branch 'master' of https://github.com/11notes/docker-kms 2025-05-05 10:41:09 +02:00
ElevenNotes
b4f0d240df [upgrade] switch to https://github.com/11notes/fork-py-kms with semver 2025-05-05 10:41:01 +02:00
14 changed files with 345 additions and 2114 deletions

115
.github/workflows/cron.update.yml vendored Normal file
View File

@@ -0,0 +1,115 @@
name: cron-update
on:
workflow_dispatch:
schedule:
- cron: "0 5 * * *"
jobs:
cron-update:
runs-on: ubuntu-latest
permissions:
actions: read
contents: write
steps:
- name: init / checkout
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
with:
ref: 'master'
fetch-depth: 0
- name: cron-update / get latest version
run: |
echo "LATEST_VERSION=$(curl -s https://api.github.com/repos/11notes/fork-py-kms/releases/latest | jq -r '.tag_name' | sed 's/v//')" >> "${GITHUB_ENV}"
echo "LATEST_TAG=$(git describe --abbrev=0 --tags `git rev-list --tags --max-count=1` | sed 's/v//')" >> "${GITHUB_ENV}"
- name: cron-update / setup node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- run: npm i semver
- name: cron-update / compare latest with current version
uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298
with:
script: |
const { existsSync, readFileSync, writeFileSync } = require('node:fs');
const { resolve } = require('node:path');
const { inspect } = require('node:util');
const semver = require('semver')
const repository = {dot:{}};
try{
const path = resolve('.json');
if(existsSync(path)){
try{
repository.dot = JSON.parse(readFileSync(path).toString());
}catch(e){
throw new Error('could not parse .json');
}
}else{
throw new Error('.json does not exist');
}
}catch(e){
core.setFailed(e);
}
const latest = semver.valid(semver.coerce('${{ env.LATEST_VERSION }}'));
const current = semver.valid(semver.coerce(repository.dot.semver.version));
const tag = semver.valid(semver.coerce('${{ env.LATEST_TAG }}'));
if(latest && latest !== current){
core.info(`new ${semver.diff(current, latest)} release found (${latest})!`)
repository.dot.semver.version = latest;
if(tag){
core.exportVariable('WORKFLOW_NEW_TAG', semver.inc(tag, semver.diff(current, latest)));
}
if(repository.dot.semver?.latest){
repository.dot.semver.latest = repository.dot.semver.version;
}
if(repository.dot?.readme?.comparison?.image){
repository.dot.readme.comparison.image = repository.dot.readme.comparison.image.replace(current, repository.dot.semver.version);
}
try{
writeFileSync(resolve('.json'), JSON.stringify(repository.dot, null, 2));
core.exportVariable('WORKFLOW_AUTO_UPDATE', true);
}catch(e){
core.setFailed(e);
}
}else{
core.info('no new release found');
}
core.info(inspect(repository.dot, {showHidden:false, depth:null, colors:true}));
- name: cron-update / checkout
id: checkout
if: env.WORKFLOW_AUTO_UPDATE == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .json
git commit -m "[upgrade] ${{ env.LATEST_VERSION }}"
git push origin HEAD:master
- name: cron-update / tag
if: env.WORKFLOW_AUTO_UPDATE == 'true' && steps.checkout.outcome == 'success'
run: |
SHA256=$(git rev-list --branches --max-count=1)
git tag -a v${{ env.WORKFLOW_NEW_TAG }} -m "v${{ env.WORKFLOW_NEW_TAG }}" ${SHA256}
git push --follow-tags
- name: cron-update / build docker image
if: env.WORKFLOW_AUTO_UPDATE == 'true' && steps.checkout.outcome == 'success'
uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7
with:
workflow: docker.yml
wait-for-completion: false
token: "${{ secrets.REPOSITORY_TOKEN }}"
inputs: '{ "release":"true", "readme":"true" }'
ref: "v${{ env.WORKFLOW_NEW_TAG }}"

70
.github/workflows/cve.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
name: cve
on:
workflow_dispatch:
schedule:
- cron: "30 15 */2 * *"
jobs:
cve:
runs-on: ubuntu-latest
steps:
- name: init / checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
- name: init / setup environment
uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298
with:
script: |
const { existsSync, readFileSync } = require('node:fs');
const { resolve } = require('node:path');
const { inspect } = require('node:util');
const { Buffer } = require('node:buffer');
const inputs = `${{ toJSON(github.event.inputs) }}`;
const opt = {input:{}, dot:{}};
try{
if(inputs.length > 0){
opt.input = JSON.parse(inputs);
if(opt.input?.etc){
opt.input.etc = JSON.parse(Buffer.from(opt.input.etc, 'base64').toString('ascii'));
}
}
}catch(e){
core.warning('could not parse github.event.inputs');
}
try{
const path = resolve('.json');
if(existsSync(path)){
try{
opt.dot = JSON.parse(readFileSync(path).toString());
}catch(e){
throw new Error('could not parse .json');
}
}else{
throw new Error('.json does not exist');
}
}catch(e){
core.setFailed(e);
}
core.info(inspect(opt, {showHidden:false, depth:null, colors:true}));
core.exportVariable('WORKFLOW_IMAGE', `${opt.dot.image}:${(opt.dot?.semver?.version === undefined) ? 'rolling' : opt.dot.semver.version}`);
core.exportVariable('WORKFLOW_GRYPE_SEVERITY_CUTOFF', (opt.dot?.grype?.severity || 'high'));
- name: grype / scan
id: grype
uses: anchore/scan-action@dc6246fcaf83ae86fcc6010b9824c30d7320729e
with:
image: ${{ env.WORKFLOW_IMAGE }}
fail-build: true
severity-cutoff: ${{ env.WORKFLOW_GRYPE_SEVERITY_CUTOFF }}
output-format: 'sarif'
by-cve: true
cache-db: true

View File

@@ -16,6 +16,7 @@ on:
required: false
default: 'ubuntu-22.04'
build:
description: 'set WORKFLOW_BUILD'
required: false
@@ -109,7 +110,7 @@ jobs:
app:{
image:opt.dot.image,
name:opt.dot.name,
version:(opt.input?.etc?.version || opt.dot.semver.version),
version:(opt.input?.etc?.version || opt.dot?.semver?.version),
root:opt.dot.root,
UID:(opt.input?.etc?.uid || 1000),
GID:(opt.input?.etc?.gid || 1000),
@@ -127,22 +128,25 @@ jobs:
docker.app.suffix = docker.image.suffix;
// setup tags
if(!opt.dot?.semver?.disable?.rolling){
docker.image.tags.push('rolling');
}
if(opt.input?.etc?.dockerfile !== 'arch.dockerfile' && opt.input?.etc?.tag){
docker.image.tags.push(`${context.sha.substring(0,7)}`);
docker.image.tags.push(opt.input.etc.tag);
docker.image.tags.push(`${opt.input.etc.tag}-${docker.app.version}`);
docker.cache.name = `${docker.image.name}:buildcache-${opt.input.etc.tag}`;
}else if(opt.dot?.semver?.version){
const semver = opt.dot.semver.version.split('.');
}else if(docker.app.version !== 'latest'){
const semver = docker.app.version.split('.');
docker.image.tags.push(`${context.sha.substring(0,7)}`);
if(Array.isArray(semver)){
if(semver.length >= 1) docker.image.tags.push(`${semver[0]}`);
if(semver.length >= 2) docker.image.tags.push(`${semver[0]}.${semver[1]}`);
if(semver.length >= 3) docker.image.tags.push(`${semver[0]}.${semver[1]}.${semver[2]}`);
}
if(opt.dot.semver?.stable && new RegExp(opt.dot.semver.stable, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('stable');
if(opt.dot.semver?.latest && new RegExp(opt.dot.semver.latest, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('latest');
}else if(opt.input?.etc?.version && opt.input.etc.version === 'latest'){
if(opt.dot?.semver?.stable && new RegExp(opt.dot?.semver.stable, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('stable');
if(opt.dot?.semver?.latest && new RegExp(opt.dot?.semver.latest, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('latest');
}else{
docker.image.tags.push('latest');
}
@@ -224,7 +228,7 @@ jobs:
with:
driver-opts: network=host
- name: docker / build & push & tag grype
- name: docker / build image locally
if: env.WORKFLOW_BUILD == 'true'
id: docker-build
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
@@ -253,7 +257,7 @@ jobs:
cache-db: true
- name: grype / fail
if: env.WORKFLOW_BUILD == 'true' && (failure() || steps.grype.outcome == 'failure')
if: env.WORKFLOW_BUILD == 'true' && (failure() || steps.grype.outcome == 'failure') && steps.docker-build.outcome == 'success'
uses: anchore/scan-action@dc6246fcaf83ae86fcc6010b9824c30d7320729e
with:
image: ${{ env.DOCKER_CACHE_GRYPE }}
@@ -263,7 +267,7 @@ jobs:
by-cve: true
cache-db: true
- name: docker / build & push
- name: docker / build image from cache and push to registries
if: env.WORKFLOW_BUILD == 'true'
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
with:
@@ -409,6 +413,9 @@ jobs:
if [ -f compose.yaml ]; then
git add compose.yaml
fi
if [ -f compose.yml ]; then
git add compose.yml
fi
if [ -f LICENSE ]; then
git add LICENSE
fi

View File

@@ -32,7 +32,6 @@ jobs:
- name: build docker image for unraid community
uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7
with:
wait-for-completion: false
workflow: docker.yml
token: "${{ secrets.REPOSITORY_TOKEN }}"
inputs: '{ "release":"false", "readme":"false", "run-name":"unraid", "etc":"${{ env.WORKFLOW_BASE64JSON }}" }'
@@ -41,6 +40,24 @@ jobs:
runs-on: ubuntu-latest
needs: docker
steps:
- name: init / base64 nested json
uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298
with:
script: |
const { Buffer } = require('node:buffer');
(async()=>{
try{
const master = await fetch('https://raw.githubusercontent.com/11notes/docker-kms/refs/heads/master/.json');
const dot = await master.json();
const etc = {
version:dot.semver.version,
};
core.exportVariable('WORKFLOW_BASE64JSON', Buffer.from(JSON.stringify(etc)).toString('base64'));
}catch(e){
core.setFailed(`workflow failed: ${e}`);
}
})();
- name: build downstream kms gui
uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7
with:
@@ -48,7 +65,7 @@ jobs:
token: "${{ secrets.REPOSITORY_TOKEN }}"
repo: 11notes/docker-kms-gui
ref: master
inputs: '{ "release":"false", "readme":"true" }'
inputs: '{ "release":"false", "readme":"true", "etc":"${{ env.WORKFLOW_BASE64JSON }}" }'
kms-gui-unraid:
runs-on: ubuntu-latest
@@ -59,12 +76,21 @@ jobs:
with:
script: |
const { Buffer } = require('node:buffer');
const etc = {
semversuffix:"unraid",
uid:99,
gid:100,
};
core.exportVariable('WORKFLOW_BASE64JSON', Buffer.from(JSON.stringify(etc)).toString('base64'));
(async()=>{
try{
const master = await fetch('https://raw.githubusercontent.com/11notes/docker-kms/refs/heads/master/.json');
const dot = await master.json();
const etc = {
version:dot.semver.version,
semversuffix:"unraid",
uid:99,
gid:100,
};
core.exportVariable('WORKFLOW_BASE64JSON', Buffer.from(JSON.stringify(etc)).toString('base64'));
}catch(e){
core.setFailed(`workflow failed: ${e}`);
}
})();
- name: build downstream kms gui for unraid community
uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7

25
.json
View File

@@ -1,19 +1,18 @@
{
"image":"11notes/kms",
"name":"kms",
"root":"/kms",
"semver":{
"version":"465f4d1"
"image": "11notes/kms",
"name": "kms",
"root": "/kms",
"arch": "linux/amd64,linux/arm64,linux/arm/v7",
"semver": {
"version": "1.0.1"
},
"readme":{
"description":"Activate any version of Windows and Office, forever",
"parent":{
"image":"11notes/alpine:stable"
"readme": {
"description": "Activate any version of Windows and Office, forever",
"parent": {
"image": "11notes/python:3.13"
},
"built":{
"py-kms":"https://github.com/Py-KMS-Organization/py-kms"
"built": {
"11notes/py-kms": "https://github.com/11notes/fork-py-kms"
}
}
}

View File

@@ -1,7 +1,7 @@
![banner](https://github.com/11notes/defaults/blob/main/static/img/banner.png?raw=true)
# KMS
[<img src="https://img.shields.io/badge/github-source-blue?logo=github&color=040308">](https://github.com/11notes/docker-KMS)![5px](https://github.com/11notes/defaults/blob/main/static/img/transparent5x2px.png?raw=true)![size](https://img.shields.io/docker/image-size/11notes/kms/465f4d1?color=0eb305)![5px](https://github.com/11notes/defaults/blob/main/static/img/transparent5x2px.png?raw=true)![version](https://img.shields.io/docker/v/11notes/kms/465f4d1?color=eb7a09)![5px](https://github.com/11notes/defaults/blob/main/static/img/transparent5x2px.png?raw=true)![pulls](https://img.shields.io/docker/pulls/11notes/kms?color=2b75d6)![5px](https://github.com/11notes/defaults/blob/main/static/img/transparent5x2px.png?raw=true)[<img src="https://img.shields.io/github/issues/11notes/docker-KMS?color=7842f5">](https://github.com/11notes/docker-KMS/issues)![5px](https://github.com/11notes/defaults/blob/main/static/img/transparent5x2px.png?raw=true)![swiss_made](https://img.shields.io/badge/Swiss_Made-FFFFFF?labelColor=FF0000&logo=data:image/svg%2bxml;base64,PHN2ZyB2ZXJzaW9uPSIxIiB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDMyIDMyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0wIDBoMzJ2MzJoLTMyeiIgZmlsbD0iI2YwMCIvPjxwYXRoIGQ9Im0xMyA2aDZ2N2g3djZoLTd2N2gtNnYtN2gtN3YtNmg3eiIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg==)
![size](https://img.shields.io/docker/image-size/11notes/kms/1.0.3?color=0eb305)![5px](https://github.com/11notes/defaults/blob/main/static/img/transparent5x2px.png?raw=true)![version](https://img.shields.io/docker/v/11notes/kms/1.0.3?color=eb7a09)![5px](https://github.com/11notes/defaults/blob/main/static/img/transparent5x2px.png?raw=true)![pulls](https://img.shields.io/docker/pulls/11notes/kms?color=2b75d6)![5px](https://github.com/11notes/defaults/blob/main/static/img/transparent5x2px.png?raw=true)[<img src="https://img.shields.io/github/issues/11notes/docker-KMS?color=7842f5">](https://github.com/11notes/docker-KMS/issues)![5px](https://github.com/11notes/defaults/blob/main/static/img/transparent5x2px.png?raw=true)![swiss_made](https://img.shields.io/badge/Swiss_Made-FFFFFF?labelColor=FF0000&logo=data:image/svg%2bxml;base64,PHN2ZyB2ZXJzaW9uPSIxIiB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDMyIDMyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0idHJhbnNwYXJlbnQiLz4KICA8cGF0aCBkPSJtMTMgNmg2djdoN3Y2aC03djdoLTZ2LTdoLTd2LTZoN3oiIGZpbGw9IiNmZmYiLz4KPC9zdmc+)
Activate any version of Windows and Office, forever
@@ -42,7 +42,7 @@ Works with:
name: "kms"
services:
app:
image: "11notes/kms:465f4d1"
image: "11notes/kms:1.0.3"
environment:
TZ: "Europe/Zurich"
volumes:
@@ -52,7 +52,7 @@ services:
restart: "always"
gui:
image: "11notes/kms-gui:465f4d1"
image: "11notes/kms:1.0.3"
depends_on:
app:
condition: "service_healthy"
@@ -102,35 +102,38 @@ slmgr /ato
| `TZ` | [Time Zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | |
| `DEBUG` | Will activate debug option for container image and app (if available) | |
| `KMS_LOCALE` | see Microsoft LICD specification | 1033 (en-US) |
| `KMS_CLIENTCOUNT` | client count > 25 | 26 |
| `KMS_ACTIVATIONINTERVAL` | Retry unsuccessful after N minutes | 120 (2 hours) |
| `KMS_RENEWALINTERVAL` | re-activation after N minutes | 259200 (180 days) |
| `KMS_LOGLEVEL` | CRITICAL, ERROR, WARNING, INFO, DEBUG, MININFO | INFO |
# MAIN TAGS 🏷️
These are the main tags for the image. There is also a tag for each commit and its shorthand sha256 value.
* [465f4d1](https://hub.docker.com/r/11notes/kms/tags?name=465f4d1)
* [465f4d1-unraid](https://hub.docker.com/r/11notes/kms/tags?name=465f4d1-unraid)
* [1.0.3](https://hub.docker.com/r/11notes/kms/tags?name=1.0.3)
* [1.0.3-unraid](https://hub.docker.com/r/11notes/kms/tags?name=1.0.3-unraid)
### There is no latest tag, what am I supposed to do about updates?
It is of my opinion that the ```:latest``` tag is dangerous. Many times, Ive introduced **breaking** changes to my images. This would have messed up everything for some people. If you dont want to change the tag to the latest [semver](https://semver.org/), simply use the short versions of [semver](https://semver.org/). Instead of using ```:1.0.3``` you can use ```:1``` or ```:1.0```. Since on each new version these tags are updated to the latest version of the software, using them is identical to using ```:latest``` but at least fixed to a major or minor version.
If you still insist on having the bleeding edge release of this app, simply use the ```:rolling``` tag, but be warned! You will get the latest version of the app instantly, regardless of breaking changes or security issues or what so ever. You do this at your own risk!
# REGISTRIES ☁️
```
docker pull 11notes/kms:465f4d1
docker pull ghcr.io/11notes/kms:465f4d1
docker pull quay.io/11notes/kms:465f4d1
docker pull 11notes/kms:1.0.3
docker pull ghcr.io/11notes/kms:1.0.3
docker pull quay.io/11notes/kms:1.0.3
```
${{ title_unraid }}
# UNRAID VERSION 🟠
This image supports unraid by default. Simply add **-unraid** to any tag and the image will run as 99:100 instead of 1000:1000 causing no issues on unraid. Enjoy.
# SOURCE 💾
* [11notes/kms](https://github.com/11notes/docker-KMS)
# PARENT IMAGE 🏛️
* [11notes/alpine:stable](https://hub.docker.com/r/11notes/alpine)
* [11notes/python:3.13](${{ json_readme_parent_url }})
# BUILT WITH 🧰
* [py-kms](https://github.com/Py-KMS-Organization/py-kms)
* [11notes/py-kms](https://github.com/11notes/fork-py-kms)
* [11notes/util](https://github.com/11notes/docker-util)
# GENERAL TIPS 📌
@@ -144,4 +147,4 @@ This image supports unraid by default. Simply add **-unraid** to any tag and the
# ElevenNotes™
This image is provided to you at your own risk. Always make backups before updating an image to a different version. Check the [releases](https://github.com/11notes/docker-kms/releases) for breaking changes. If you have any problems with using this image simply raise an [issue](https://github.com/11notes/docker-kms/issues), thanks. If you have a question or inputs please create a new [discussion](https://github.com/11notes/docker-kms/discussions) instead of an issue. You can find all my other repositories on [github](https://github.com/11notes?tab=repositories).
*created 02.05.2025, 10:31:30 (CET)*
*created 09.07.2025, 21:38:36 (CET)*

View File

@@ -1,70 +1,95 @@
ARG APP_UID=1000
ARG APP_GID=1000
# ╔═════════════════════════════════════════════════════╗
# ║ SETUP ║
# ╚═════════════════════════════════════════════════════╝
# GLOBAL
ARG APP_UID=1000 \
APP_GID=1000 \
BUILD_SRC=https://github.com/11notes/fork-py-kms.git \
BUILD_ROOT=/git/fork-py-kms
# :: Util
# :: FOREIGN IMAGES
FROM 11notes/util AS util
# :: Build / py-kms
# ╔═════════════════════════════════════════════════════╗
# ║ BUILD ║
# ╚═════════════════════════════════════════════════════╝
# :: PY-KMS
FROM alpine/git AS build
ARG APP_VERSION
ARG APP_VERSION \
BUILD_SRC \
BUILD_ROOT
RUN set -ex; \
git clone https://github.com/Py-KMS-Organization/py-kms.git -b next; \
cd /git/py-kms; \
git checkout ${APP_VERSION}; \
cp -R /git/py-kms/docker/docker-py3-kms-minimal/requirements.txt /git/py-kms/py-kms/requirements.txt; \
cp -R /git/py-kms/docker/docker-py3-kms/requirements.txt /git/py-kms/py-kms/requirements.gui.txt;
git clone ${BUILD_SRC} -b next; \
cd ${BUILD_ROOT}; \
git checkout v${APP_VERSION};
# :: Header
FROM 11notes/alpine:stable
RUN set -ex; \
cd ${BUILD_ROOT}; \
cp -R ${BUILD_ROOT}/docker/docker-py3-kms-minimal/requirements.txt ${BUILD_ROOT}/py-kms/requirements.txt; \
cp -R ${BUILD_ROOT}/docker/docker-py3-kms/requirements.txt ${BUILD_ROOT}/py-kms/requirements.gui.txt;
# :: arguments
ARG TARGETARCH
ARG APP_IMAGE
ARG APP_NAME
ARG APP_VERSION
ARG APP_ROOT
ARG APP_UID
ARG APP_GID
ARG APP_NO_CACHE
# ╔═════════════════════════════════════════════════════╗
# ║ IMAGE ║
# ╚═════════════════════════════════════════════════════╝
# :: HEADER
FROM 11notes/python:3.13
# :: environment
ENV APP_IMAGE=${APP_IMAGE}
ENV APP_NAME=${APP_NAME}
ENV APP_VERSION=${APP_VERSION}
ENV APP_ROOT=${APP_ROOT}
# :: default arguments
ARG TARGETPLATFORM \
TARGETOS \
TARGETARCH \
TARGETVARIANT \
APP_IMAGE \
APP_NAME \
APP_VERSION \
APP_ROOT \
APP_UID \
APP_GID \
APP_NO_CACHE
ENV KMS_LOCALE=1033
ENV KMS_CLIENTCOUNT=26
ENV KMS_ACTIVATIONINTERVAL=120
ENV KMS_RENEWALINTERVAL=259200
ENV KMS_LOGLEVEL="INFO"
# :: default python image
ARG PIP_ROOT_USER_ACTION=ignore \
PIP_BREAK_SYSTEM_PACKAGES=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1
ENV PIP_ROOT_USER_ACTION=ignore
# :: image specific arguments
ARG BUILD_ROOT
# :: default environment
ENV APP_IMAGE=${APP_IMAGE} \
APP_NAME=${APP_NAME} \
APP_VERSION=${APP_VERSION} \
APP_ROOT=${APP_ROOT}
# :: app specific variables
ENV KMS_LOCALE=1033 \
KMS_ACTIVATIONINTERVAL=120 \
KMS_RENEWALINTERVAL=259200
# :: multi-stage
COPY --from=util /usr/local/bin /usr/local/bin
COPY --from=build /git/py-kms/py-kms /opt/py-kms
COPY --from=build ${BUILD_ROOT}/py-kms /opt/py-kms
# :: Run
# :: RUN
USER root
RUN eleven printenv;
# :: install application
# :: install dependencies
RUN set -ex; \
apk --no-cache --update add \
python3; \
apk --no-cache --update --virtual .build add \
py3-pip;
# :: install and update application
RUN set -ex; \
mkdir -p ${APP_ROOT}/var; \
pip3 install --no-cache-dir --break-system-packages -r /opt/py-kms/requirements.txt; \
pip3 install --no-cache-dir --break-system-packages pytz; \
pip3 list -o | sed 's/pip.*//' | grep . | cut -f1 -d' ' | tr " " "\n" | awk '{if(NR>=3)print}' | cut -d' ' -f1 | xargs -n1 pip3 install --no-cache-dir --break-system-packages -U; \
pip3 install -r /opt/py-kms/requirements.txt; \
pip3 install pytz; \
pip3 list -o | sed 's/pip.*//' | grep . | cut -f1 -d' ' | tr " " "\n" | awk '{if(NR>=3)print}' | cut -d' ' -f1 | xargs -n1 pip3 install -U; \
apk del --no-network .build; \
rm -rf /usr/lib/python3.12/site-packages/pip;
rm -rf /usr/lib/python3.13/site-packages/pip;
# :: copy filesystem changes and set correct permissions
# :: copy root filesystem and set correct permissions
COPY ./rootfs /
RUN set -ex; \
chmod +x -R /usr/local/bin; \
@@ -72,15 +97,17 @@ ARG APP_GID=1000
${APP_ROOT} \
/opt/py-kms;
# :: support unraid
# :: enable unraid support
RUN set -ex; \
eleven unraid
# :: Volumes
# :: PERSISTENT DATA
VOLUME ["${APP_ROOT}/var"]
# :: Monitor
HEALTHCHECK --interval=5s --timeout=2s CMD netstat -an | grep -q 1688 || exit 1
# :: HEALTH
HEALTHCHECK --interval=5s --timeout=2s --start-interval=5s \
CMD ["/usr/bin/nc", "-z", "localhost", "1688"]
# :: Start
USER ${APP_UID}:${APP_GID}
# :: EXECUTE
USER ${APP_UID}:${APP_GID}
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]

View File

@@ -1,7 +1,7 @@
name: "kms"
services:
app:
image: "11notes/kms:465f4d1"
image: "11notes/kms:1.0.3"
environment:
TZ: "Europe/Zurich"
volumes:
@@ -11,7 +11,7 @@ services:
restart: "always"
gui:
image: "11notes/kms-gui:465f4d1"
image: "11notes/kms:1.0.3"
depends_on:
app:
condition: "service_healthy"

View File

@@ -54,10 +54,8 @@ ${{ content_defaults }}
${{ content_environment }}
| `KMS_LOCALE` | see Microsoft LICD specification | 1033 (en-US) |
| `KMS_CLIENTCOUNT` | client count > 25 | 26 |
| `KMS_ACTIVATIONINTERVAL` | Retry unsuccessful after N minutes | 120 (2 hours) |
| `KMS_RENEWALINTERVAL` | re-activation after N minutes | 259200 (180 days) |
| `KMS_LOGLEVEL` | CRITICAL, ERROR, WARNING, INFO, DEBUG, MININFO | INFO |
${{ content_source }}

File diff suppressed because it is too large Load Diff

View File

@@ -1,268 +0,0 @@
#!/usr/bin/env python3
import binascii
import logging
import time
import uuid
from pykms_Structure import Structure
from pykms_DB2Dict import kmsDB2Dict
from pykms_PidGenerator import epidGenerator
from pykms_Filetimes import filetime_to_dt
from pykms_Sql import sql_update, sql_update_epid
from pykms_Format import justify, byterize, enco, deco, pretty_printer
#--------------------------------------------------------------------------------------------------------------------------------------------------------
loggersrv = logging.getLogger('logsrv')
class UUID(Structure):
commonHdr = ()
structure = (
('raw', '16s'),
)
def get(self):
return uuid.UUID(bytes_le = enco(str(self), 'latin-1'))
class kmsBase:
def __init__(self, data, srv_config):
self.data = data
self.srv_config = srv_config
class kmsRequestStruct(Structure):
commonHdr = ()
structure = (
('versionMinor', '<H'),
('versionMajor', '<H'),
('isClientVm', '<I'),
('licenseStatus', '<I'),
('graceTime', '<I'),
('applicationId', ':', UUID),
('skuId', ':', UUID),
('kmsCountedId' , ':', UUID),
('clientMachineId', ':', UUID),
('requiredClientCount', '<I'),
('requestTime', '<Q'),
('previousClientMachineId', ':', UUID),
('machineName', 'u'),
('_mnPad', '_-mnPad', '126-len(machineName)'),
('mnPad', ':'),
)
def getMachineName(self):
return self['machineName'].decode('utf-16le')
def getLicenseStatus(self):
return kmsBase.licenseStates[self['licenseStatus']] or "Unknown"
class kmsResponseStruct(Structure):
commonHdr = ()
structure = (
('versionMinor', '<H'),
('versionMajor', '<H'),
('epidLen', '<I=len(kmsEpid)+2'),
('kmsEpid', 'u'),
('clientMachineId', ':', UUID),
('responseTime', '<Q'),
('currentClientCount', '<I'),
('vLActivationInterval', '<I'),
('vLRenewalInterval', '<I'),
)
class GenericRequestHeader(Structure):
commonHdr = ()
structure = (
('bodyLength1', '<I'),
('bodyLength2', '<I'),
('versionMinor', '<H'),
('versionMajor', '<H'),
('remainder', '_'),
)
licenseStates = {
0 : "Unlicensed",
1 : "Activated",
2 : "Grace Period",
3 : "Out-of-Tolerance Grace Period",
4 : "Non-Genuine Grace Period",
5 : "Notifications Mode",
6 : "Extended Grace Period",
}
licenseStatesEnum = {
'unlicensed' : 0,
'licensed' : 1,
'oobGrace' : 2,
'ootGrace' : 3,
'nonGenuineGrace' : 4,
'notification' : 5,
'extendedGrace' : 6
}
def getPadding(self, bodyLength):
## https://forums.mydigitallife.info/threads/71213-Source-C-KMS-Server-from-Microsoft-Toolkit?p=1277542&viewfull=1#post1277542
return 4 + (((~bodyLength & 3) + 1) & 3)
def serverLogic(self, kmsRequest):
pretty_printer(num_text = 15, where = "srv")
kmsRequest = byterize(kmsRequest)
loggersrv.debug("KMS Request Bytes: \n%s\n" % justify(deco(binascii.b2a_hex(enco(str(kmsRequest), 'latin-1')), 'latin-1')))
loggersrv.debug("KMS Request: \n%s\n" % justify(kmsRequest.dump(print_to_stdout = False)))
clientMachineId = kmsRequest['clientMachineId'].get()
applicationId = kmsRequest['applicationId'].get()
skuId = kmsRequest['skuId'].get()
requestDatetime = filetime_to_dt(kmsRequest['requestTime'])
# Localize the request time, if module "tzlocal" is available.
try:
from datetime import datetime
from tzlocal import get_localzone
from pytz.exceptions import UnknownTimeZoneError
try:
local_dt = datetime.fromisoformat(str(requestDatetime)).astimezone(get_localzone())
except UnknownTimeZoneError:
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Unknown time zone ! Request time not localized.{end}")
local_dt = requestDatetime
except ImportError:
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Module 'tzlocal' or 'pytz' not available ! Request time not localized.{end}")
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.
# https://docs.microsoft.com/en-us/windows/deployment/volume-activation/activate-windows-10-clients-vamt
MinClients = kmsRequest['requiredClientCount']
RequiredClients = MinClients * 2
if self.srv_config["clientcount"] != None:
if 0 < self.srv_config["clientcount"] < MinClients:
# fixed to 6 (product server) or 26 (product desktop)
currentClientCount = MinClients + 1
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Not enough clients ! Fixed with %s, but activated client \
could be detected as not genuine !{end}" %currentClientCount)
elif MinClients <= self.srv_config["clientcount"] < RequiredClients:
currentClientCount = self.srv_config["clientcount"]
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}With count = %s, activated client could be detected as not genuine !{end}" %currentClientCount)
elif self.srv_config["clientcount"] >= RequiredClients:
# fixed to 10 (product server) or 50 (product desktop)
currentClientCount = RequiredClients
if self.srv_config["clientcount"] > RequiredClients:
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Too many clients ! Fixed with %s{end}" %currentClientCount)
else:
# fixed to 10 (product server) or 50 (product desktop)
currentClientCount = RequiredClients
# Get a name for SkuId, AppId.
kmsdb = kmsDB2Dict()
appName, skuName = str(applicationId), str(skuId)
appitems = kmsdb[2]
for appitem in appitems:
kmsitems = appitem['KmsItems']
for kmsitem in kmsitems:
skuitems = kmsitem['SkuItems']
for skuitem in skuitems:
try:
if uuid.UUID(skuitem['Id']) == skuId:
skuName = skuitem['DisplayName']
break
except:
skuName = skuId
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Can't find a name for this product !{end}")
try:
if uuid.UUID(appitem['Id']) == applicationId:
appName = appitem['DisplayName']
except:
appName = applicationId
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Can't find a name for this application group !{end}")
infoDict = {
"machineName" : kmsRequest.getMachineName(),
"clientMachineId" : str(clientMachineId),
"appId" : appName,
"skuId" : skuName,
"licenseStatus" : kmsRequest.getLicenseStatus(),
"requestTime" : int(time.time()),
"kmsEpid" : None,
"machineIp" : self.srv_config['raddr']
}
loggersrv.info("Machine Name: %s" % infoDict["machineName"])
loggersrv.info("Machine IP: %s" % infoDict["machineIp"])
loggersrv.info("Client Machine ID: %s" % infoDict["clientMachineId"])
loggersrv.info("Application ID: %s" % infoDict["appId"])
loggersrv.info("SKU ID: %s" % infoDict["skuId"])
loggersrv.info("License Status: %s" % infoDict["licenseStatus"])
loggersrv.info("Request Time: %s" % local_dt.strftime('%Y-%m-%d %H:%M:%S %Z (UTC%z)'))
if self.srv_config['loglevel'] == 'MININFO':
loggersrv.mininfo("", extra = {'host': self.srv_config['raddr'],
'status' : infoDict["licenseStatus"],
'product' : infoDict["skuId"]})
# Create database.
if self.srv_config['sqlite']:
sql_update(self.srv_config['sqlite'], infoDict)
return self.createKmsResponse(kmsRequest, currentClientCount, appName)
def createKmsResponse(self, kmsRequest, currentClientCount, appName):
response = self.kmsResponseStruct()
response['versionMinor'] = kmsRequest['versionMinor']
response['versionMajor'] = kmsRequest['versionMajor']
if not self.srv_config["epid"]:
response["kmsEpid"] = epidGenerator(kmsRequest['kmsCountedId'].get(), kmsRequest['versionMajor'],
self.srv_config["lcid"]).encode('utf-16le')
else:
response["kmsEpid"] = self.srv_config["epid"].encode('utf-16le')
response['clientMachineId'] = kmsRequest['clientMachineId']
# rule: timeserver - 4h <= timeclient <= timeserver + 4h, check if is satisfied (TODO).
response['responseTime'] = kmsRequest['requestTime']
response['currentClientCount'] = currentClientCount
response['vLActivationInterval'] = self.srv_config["activation"]
response['vLRenewalInterval'] = self.srv_config["renewal"]
# Update database epid.
if self.srv_config['sqlite']:
sql_update_epid(self.srv_config['sqlite'], kmsRequest, response, appName)
loggersrv.info("Server ePID: %s" % response["kmsEpid"].decode('utf-16le'))
return response
import pykms_RequestV4, pykms_RequestV5, pykms_RequestV6, pykms_RequestUnknown
def generateKmsResponseData(data, srv_config):
version = kmsBase.GenericRequestHeader(data)['versionMajor']
currentDate = time.strftime("%a %b %d %H:%M:%S %Y")
if version == 4:
loggersrv.info("Received V%d request on %s." % (version, currentDate))
messagehandler = pykms_RequestV4.kmsRequestV4(data, srv_config)
elif version == 5:
loggersrv.info("Received V%d request on %s." % (version, currentDate))
messagehandler = pykms_RequestV5.kmsRequestV5(data, srv_config)
elif version == 6:
loggersrv.info("Received V%d request on %s." % (version, currentDate))
messagehandler = pykms_RequestV6.kmsRequestV6(data, srv_config)
else:
loggersrv.info("Unhandled KMS version V%d." % version)
messagehandler = pykms_RequestUnknown.kmsRequestUnknown(data, srv_config)
return messagehandler.executeRequestLogic()

View File

@@ -1,545 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import binascii
import re
import sys
import socket
import uuid
import logging
import os
import threading
import socketserver
import queue as Queue
import selectors
from time import monotonic as time
import pykms_RpcBind, pykms_RpcRequest
from pykms_RpcBase import rpcBase
from pykms_Dcerpc import MSRPCHeader
from pykms_Misc import check_setup, check_lcid, check_other
from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp
from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals, kms_parser_check_connect
from pykms_Format import enco, deco, pretty_printer, justify
from pykms_Connect import MultipleListener
from pykms_Sql import sql_initialize
srv_version = "py-kms_2020-10-01"
__license__ = "The Unlicense"
__author__ = u"Matteo an <SystemRage@protonmail.com>"
__url__ = "https://github.com/SystemRage/py-kms"
srv_description = "py-kms: KMS Server Emulator written in Python"
srv_config = {}
##---------------------------------------------------------------------------------------------------------------------------------------------------------
class KeyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
daemon_threads = True
def __init__(self, server_address, RequestHandlerClass, bind_and_activate = True, want_dual = False):
socketserver.BaseServer.__init__(self, server_address, RequestHandlerClass)
self.__shutdown_request = False
self.r_service, self.w_service = socket.socketpair()
if hasattr(selectors, 'PollSelector'):
self._ServerSelector = selectors.PollSelector
else:
self._ServerSelector = selectors.SelectSelector
if bind_and_activate:
try:
self.multisock = MultipleListener(server_address, want_dual = want_dual)
except Exception as e:
if want_dual and str(e) == "dualstack_ipv6 not supported on this platform":
try:
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}%s. Creating not dualstack sockets...{end}" %str(e))
self.multisock = MultipleListener(server_address, want_dual = False)
except Exception as e:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
else:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e))
if self.multisock.cant_dual:
delim = ('' if len(self.multisock.cant_dual) == 1 else ', ')
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}IPv4 [%s] can't be dualstack{end}" %delim.join(self.multisock.cant_dual))
def pykms_serve(self):
""" Mixing of socketserver serve_forever() and handle_request() functions,
without elements blocking tkinter.
Handle one request at a time, possibly blocking.
Respects self.timeout.
"""
# Support people who used socket.settimeout() to escape
# pykms_serve() before self.timeout was available.
timeout = self.multisock.gettimeout()
if timeout is None:
timeout = self.timeout
elif self.timeout is not None:
timeout = min(timeout, self.timeout)
if timeout is not None:
deadline = time() + timeout
try:
# Wait until a request arrives or the timeout expires.
with self._ServerSelector() as selector:
self.multisock.register(selector)
# self-pipe trick.
selector.register(fileobj = self.r_service.fileno(), events = selectors.EVENT_READ)
while not self.__shutdown_request:
ready = selector.select(timeout)
if self.__shutdown_request:
break
if ready == []:
if timeout is not None:
timeout = deadline - time()
if timeout < 0:
return self.handle_timeout()
else:
for key, mask in ready:
if key.fileobj in self.multisock.filenos():
self.socket = self.multisock.sockmap[key.fileobj]
self.server_address = self.socket.getsockname()
self._handle_request_noblock()
elif key.fileobj is self.r_service.fileno():
# only to clean buffer.
msgkill = os.read(self.r_service.fileno(), 8).decode('utf-8')
sys.exit(0)
finally:
self.__shutdown_request = False
def shutdown(self):
self.__shutdown_request = True
def server_close(self):
self.multisock.close()
def handle_timeout(self):
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Server connection timed out. Exiting...{end}")
def handle_error(self, request, client_address):
pass
class server_thread(threading.Thread):
def __init__(self, queue, name):
threading.Thread.__init__(self)
self.name = name
self.queue = queue
self.server = None
self.is_running_server = False
self.checked = False
self.is_running_thread = threading.Event()
def terminate_serve(self):
self.server.shutdown()
self.server.server_close()
self.server = None
self.is_running_server = False
def terminate_thread(self):
self.is_running_thread.set()
def terminate_eject(self):
os.write(self.server.w_service.fileno(), u''.encode('utf-8'))
def run(self):
while not self.is_running_thread.is_set():
try:
item = self.queue.get(block = True, timeout = 0.1)
self.queue.task_done()
except Queue.Empty:
continue
else:
try:
if item == 'start':
self.eject = False
self.is_running_server = True
# Check options.
if not self.checked:
server_check()
# Create and run server.
self.server = server_create()
self.server.pykms_serve()
except (SystemExit, Exception) as e:
self.eject = True
raise
##---------------------------------------------------------------------------------------------------------------------------------------------------------
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.
srv_options = {
'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"},
'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"},
'lcid' : {'help' : 'Use this option to manually specify an LCID for use with randomly generated ePIDs. Default is \"1033\" (en-us)',
'def' : 1033, 'des' : "lcid"},
'count' : {'help' : 'Use this option to specify the current client count. A number >=25 is required to enable activation of client OSes; \
for server OSes and Office >=5', 'def' : None, 'des' : "clientcount"},
'activation' : {'help' : 'Use this option to specify the activation interval (in minutes). Default is \"120\" minutes (2 hours).',
'def' : 120, 'des': "activation"},
'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).',
'def' : 1440 * 7, 'des' : "renewal"},
'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default.', 'def' : False,
'file': os.path.join('.', 'pykms_database.db'), 'des' : "sqlite"},
'hwid' : {'help' : 'Use this option to specify a HWID. The HWID must be an 16-character string of hex characters. \
Type \"RANDOM\" to auto-generate the HWID.',
'def' : "RANDOM", 'des' : "hwid"},
'time0' : {'help' : 'Maximum inactivity time (in seconds) after which the connection with the client is closed. If \"None\" (default) serve forever.',
'def' : None, 'des' : "timeoutidle"},
'time1' : {'help' : 'Set the maximum time to wait for sending / receiving a request / response. Default is no timeout.',
'def' : None, 'des' : "timeoutsndrcv"},
'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Deactivated by default.',
'def' : False, 'des' : "asyncmsg"},
'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"]},
'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. \
Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to create logfile.',
'def' : os.path.join('.', 'pykms_logserver.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"},
'listen' : {'help' : 'Adds multiple listening ip address - port couples.', 'des': "listen"},
'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,
'des': "reuse"},
'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' : True, 'des': "dual"}
}
def server_options():
server_parser = KmsParser(description = srv_description, epilog = 'version: ' + srv_version, add_help = False)
server_parser.add_argument("ip", nargs = "?", action = "store", default = srv_options['ip']['def'], help = srv_options['ip']['help'], type = str)
server_parser.add_argument("port", nargs = "?", action = "store", default = srv_options['port']['def'], help = srv_options['port']['help'], type = int)
server_parser.add_argument("-e", "--epid", action = "store", dest = srv_options['epid']['des'], default = srv_options['epid']['def'],
help = srv_options['epid']['help'], type = str)
server_parser.add_argument("-l", "--lcid", action = "store", dest = srv_options['lcid']['des'], default = srv_options['lcid']['def'],
help = srv_options['lcid']['help'], type = int)
server_parser.add_argument("-c", "--client-count", action = "store", dest = srv_options['count']['des'] , default = srv_options['count']['def'],
help = srv_options['count']['help'], type = str)
server_parser.add_argument("-a", "--activation-interval", action = "store", dest = srv_options['activation']['des'],
default = srv_options['activation']['def'], help = srv_options['activation']['help'], type = int)
server_parser.add_argument("-r", "--renewal-interval", action = "store", dest = srv_options['renewal']['des'],
default = srv_options['renewal']['def'], help = srv_options['renewal']['help'], type = int)
server_parser.add_argument("-s", "--sqlite", nargs = "?", dest = srv_options['sql']['des'], const = True,
default = srv_options['sql']['def'], help = srv_options['sql']['help'], type = str)
server_parser.add_argument("-w", "--hwid", action = "store", dest = srv_options['hwid']['des'], default = srv_options['hwid']['def'],
help = srv_options['hwid']['help'], type = str)
server_parser.add_argument("-t0", "--timeout-idle", action = "store", dest = srv_options['time0']['des'], default = srv_options['time0']['def'],
help = srv_options['time0']['help'], type = str)
server_parser.add_argument("-t1", "--timeout-sndrcv", action = "store", dest = srv_options['time1']['des'], default = srv_options['time1']['def'],
help = srv_options['time1']['help'], type = str)
server_parser.add_argument("-y", "--async-msg", action = "store_true", dest = srv_options['asyncmsg']['des'],
default = srv_options['asyncmsg']['def'], help = srv_options['asyncmsg']['help'])
server_parser.add_argument("-V", "--loglevel", action = "store", dest = srv_options['llevel']['des'], choices = srv_options['llevel']['choi'],
default = srv_options['llevel']['def'], help = srv_options['llevel']['help'], type = str)
server_parser.add_argument("-F", "--logfile", nargs = "+", action = "store", dest = srv_options['lfile']['des'],
default = srv_options['lfile']['def'], help = srv_options['lfile']['help'], type = str)
server_parser.add_argument("-S", "--logsize", action = "store", dest = srv_options['lsize']['des'], default = srv_options['lsize']['def'],
help = srv_options['lsize']['help'], type = float)
server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
## Connection parsing.
connection_parser = KmsParser(description = "connect options", add_help = False)
connection_subparser = connection_parser.add_subparsers(dest = "mode")
connect_parser = connection_subparser.add_parser("connect", add_help = False)
connect_parser.add_argument("-n", "--listen", action = "append", dest = srv_options['listen']['des'], default = [],
help = srv_options['listen']['help'], type = str)
connect_parser.add_argument("-b", "--backlog", action = "append", dest = srv_options['backlog']['des'], default = [],
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 = [],
help = srv_options['reuse']['help'])
connect_parser.add_argument("-d", "--dual", type = _str2bool, dest = srv_options['dual']['des'], default = srv_options['dual']['def'],
help = srv_options['dual']['help'])
try:
userarg = sys.argv[1:]
# Run help.
if any(arg in ["-h", "--help"] for arg in userarg):
KmsParserHelp().printer(parsers = [server_parser, (connection_parser, connect_parser)])
# Get stored arguments.
pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser)
connect_zeroarg, connect_onearg = kms_parser_get(connect_parser)
subdict = {
'connect' : (connect_zeroarg, connect_onearg, connection_parser.parse_args)
}
subpars = list(subdict.keys())
pykmssrv_zeroarg += subpars # add subparsers
exclude_kms = ['-F', '--logfile']
exclude_dup = ['-n', '--listen', '-b', '--backlog', '-u', '--no-reuse']
# Set defaults for server dict config.
# example case:
# python3 pykms_Server.py
srv_config.update(vars(server_parser.parse_args([])))
subindx = sorted([(userarg.index(pars), pars) for pars in subpars if pars in userarg], key = lambda x: x[0])
if subindx:
# Set `daemon options` and/or `connect options` for server dict config.
# example cases:
# 1 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals]
first = subindx[0][0]
# initial.
kms_parser_check_optionals(userarg[0 : first], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
kms_parser_check_positionals(srv_config, server_parser.parse_args, arguments = userarg[0 : first], force_parse = True)
# middle.
for i in range(len(subindx) - 1):
posi, posf, typ = subindx[i][0], subindx[i + 1][0], subindx[i][1]
kms_parser_check_optionals(userarg[posi : posf], subdict[typ][0], subdict[typ][1], msg = 'optional %s' %typ,
exclude_opt_dup = (exclude_dup if typ == 'connect' else []))
kms_parser_check_positionals(srv_config, subdict[typ][2], arguments = userarg[posi : posf], msg = 'positional %s' %typ)
# final.
pos, typ = subindx[-1]
kms_parser_check_optionals(userarg[pos:], subdict[typ][0], subdict[typ][1], msg = 'optional %s' %typ,
exclude_opt_dup = (exclude_dup if typ == 'connect' else []))
kms_parser_check_positionals(srv_config, subdict[typ][2], arguments = userarg[pos:], msg = 'positional %s' %typ)
if len(subindx) > 1:
srv_config['mode'] = '+'.join(elem[1] for elem in subindx)
else:
# Update `pykms options` for server dict config.
# example case:
# 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_positionals(srv_config, server_parser.parse_args)
kms_parser_check_connect(srv_config, srv_options, userarg, connect_zeroarg, connect_onearg)
except KmsParserException as e:
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
def server_check():
# Setup and some checks.
check_setup(srv_config, srv_options, loggersrv, where = "srv")
# Random HWID.
if srv_config['hwid'] == "RANDOM":
randomhwid = uuid.uuid4().hex
srv_config['hwid'] = randomhwid[:16]
# Sanitize HWID.
hexstr = srv_config['hwid']
# Strip 0x from the start of hexstr
if hexstr.startswith("0x"):
hexstr = hexstr[2:]
hexsub = re.sub(r'[^0-9a-fA-F]', '', hexstr)
diff = set(hexstr).symmetric_difference(set(hexsub))
if len(diff) != 0:
diff = str(diff).replace('{', '').replace('}', '')
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}HWID '%s' is invalid. Digit %s non hexadecimal. Exiting...{end}" %(hexstr.upper(), diff))
else:
lh = len(hexsub)
if lh % 2 != 0:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}HWID '%s' is invalid. Hex string is odd length. Exiting...{end}" %hexsub.upper())
elif lh < 16:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}HWID '%s' is invalid. Hex string is too short. Exiting...{end}" %hexsub.upper())
elif lh > 16:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}HWID '%s' is invalid. Hex string is too long. Exiting...{end}" %hexsub.upper())
else:
srv_config['hwid'] = binascii.a2b_hex(hexsub)
# Check LCID.
srv_config['lcid'] = check_lcid(srv_config['lcid'], loggersrv.warning)
# Check sqlite.
if srv_config['sqlite']:
if srv_config['sqlite'] is True: # Resolve bool to the default path
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:
import sqlite3
sql_initialize(srv_config['sqlite'])
except ImportError:
pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Module 'sqlite3' not installed, database support disabled.{end}")
srv_config['sqlite'] = False
# Check other specific server options.
opts = [('clientcount', '-c/--client-count'),
('timeoutidle', '-t0/--timeout-idle'),
('timeoutsndrcv', '-t1/--timeout-sndrcv')]
check_other(srv_config, opts, loggersrv, where = 'srv')
# Check further addresses / ports.
if 'listen' in srv_config:
addresses = []
for elem in srv_config['listen']:
try:
addr, port = elem.split(',')
except ValueError:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}argument `-n/--listen`: %s not well defined. Exiting...{end}" %elem)
try:
port = int(port)
except ValueError:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}argument `-n/--listen`: port number '%s' is invalid. Exiting...{end}" %port)
if not (1 <= port <= 65535):
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}argument `-n/--listen`: port number '%s' is invalid. Enter between 1 - 65535. Exiting...{end}" %port)
addresses.append((addr, port))
srv_config['listen'] = addresses
def server_create():
# Create address list (when the current user indicates execution inside the Windows Sandbox,
# then we wont allow port reuse - it is not supported).
all_address = [(
srv_config['ip'], srv_config['port'],
(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 srv_options['reuse']['def'])
)]
log_address = "TCP server listening at %s on port %d" %(srv_config['ip'], srv_config['port'])
if 'listen' in srv_config:
for l, b, r in zip(srv_config['listen'], srv_config['backlog'], srv_config['reuse']):
all_address.append(l + (b,) + (r,))
log_address += justify("at %s on port %d" %(l[0], l[1]), indent = 56)
server = KeyServer(all_address, kmsServerHandler, want_dual = (srv_config['dual'] if 'dual' in srv_config else srv_options['dual']['def']))
server.timeout = srv_config['timeoutidle']
loggersrv.info(log_address)
loggersrv.info("HWID: %s" % deco(binascii.b2a_hex(srv_config['hwid']), 'utf-8').upper())
return server
def server_terminate(generic_srv, exit_server = False, exit_thread = False):
if exit_server:
generic_srv.terminate_serve()
if exit_thread:
generic_srv.terminate_thread()
class ServerWithoutGui(object):
def start(self):
import queue as Queue
daemon_queue = Queue.Queue(maxsize = 0)
daemon_serverthread = server_thread(daemon_queue, name = "Thread-Srv-Daemon")
daemon_serverthread.setDaemon(True)
# options already checked in `server_main_terminal`.
daemon_serverthread.checked = True
daemon_serverthread.start()
daemon_queue.put('start')
return 0, daemon_serverthread
def join(self, daemon_serverthread):
while daemon_serverthread.is_alive():
daemon_serverthread.join(timeout = 0.5)
def clean(self, daemon_serverthread):
server_terminate(daemon_serverthread, exit_server = True, exit_thread = True)
def server_main_terminal():
# Parse options.
server_options()
# Check options.
server_check()
serverthread.checked = True
# Run threaded server.
serverqueue.put('start')
# Wait to finish.
try:
while serverthread.is_alive():
serverthread.join(timeout = 0.5)
except (KeyboardInterrupt, SystemExit):
server_terminate(serverthread, exit_server = True, exit_thread = True)
class kmsServerHandler(socketserver.BaseRequestHandler):
def setup(self):
loggersrv.info("Connection accepted: %s:%d" %(self.client_address[0], self.client_address[1]))
srv_config['raddr'] = str(self.client_address[0])
def handle(self):
self.request.settimeout(srv_config['timeoutsndrcv'])
while True:
# self.request is the TCP socket connected to the client
try:
self.data = self.request.recv(1024)
if self.data == '' or not self.data:
pretty_printer(log_obj = loggersrv.debug, # use debug, as the healthcheck will spam this
put_text = "{reverse}{yellow}{bold}No data received.{end}")
break
except socket.error as e:
pretty_printer(log_obj = loggersrv.error,
put_text = "{reverse}{red}{bold}While receiving: %s{end}" %str(e))
break
packetType = MSRPCHeader(self.data)['type']
if packetType == rpcBase.packetType['bindReq']:
loggersrv.info("RPC bind request received.")
pretty_printer(num_text = [-2, 2], where = "srv")
handler = pykms_RpcBind.handler(self.data, srv_config)
elif packetType == rpcBase.packetType['request']:
loggersrv.info("Received activation request.")
pretty_printer(num_text = [-2, 13], where = "srv")
handler = pykms_RpcRequest.handler(self.data, srv_config)
else:
pretty_printer(log_obj = loggersrv.error,
put_text = "{reverse}{red}{bold}Invalid RPC request type %s.{end}" %packetType)
break
res = enco(str(handler.populate()), 'latin-1')
if packetType == rpcBase.packetType['bindReq']:
loggersrv.info("RPC bind acknowledged.")
pretty_printer(num_text = [-3, 5, 6], where = "srv")
elif packetType == rpcBase.packetType['request']:
loggersrv.info("Responded to activation request.")
pretty_printer(num_text = [-3, 18, 19], where = "srv")
try:
self.request.send(res)
if packetType == rpcBase.packetType['request']:
break
except socket.error as e:
pretty_printer(log_obj = loggersrv.error,
put_text = "{reverse}{red}{bold}While sending: %s{end}" %str(e))
break
def finish(self):
self.request.close()
loggersrv.info("Connection closed: %s:%d" %(self.client_address[0], self.client_address[1]))
serverqueue = Queue.Queue(maxsize = 0)
serverthread = server_thread(serverqueue, name = "Thread-Srv")
serverthread.daemon = True
serverthread.start()
if __name__ == "__main__":
server_main_terminal()

View File

@@ -1,142 +0,0 @@
#!/usr/bin/env python3
import datetime
import os
import logging
# sqlite3 is optional.
try:
import sqlite3
except ImportError:
pass
from pykms_Format import pretty_printer
#--------------------------------------------------------------------------------------------------------------------------------------------------------
loggersrv = logging.getLogger('logsrv')
def sql_initialize(dbName):
if not os.path.isfile(dbName):
# Initialize the database.
loggersrv.debug(f'Initializing database file "{dbName}"...')
con = None
try:
con = sqlite3.connect(dbName)
cur = con.cursor()
cur.execute("CREATE TABLE clients(clientMachineId TEXT , machineName TEXT, applicationId TEXT, skuId TEXT, licenseStatus TEXT, lastRequestTime INTEGER, kmsEpid TEXT, requestCount INTEGER, machineIp TEXT, PRIMARY KEY(clientMachineId, applicationId))")
except sqlite3.Error as e:
pretty_printer(log_obj = loggersrv.error, to_exit = True, put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
finally:
if con:
con.commit()
con.close()
else:
# Update the database.
loggersrv.debug(f'Updating database file "{dbName}"...')
con = None
try:
con = sqlite3.connect(dbName)
cur = con.cursor()
cur.execute("ALTER TABLE clients ADD COLUMN machineIp TEXT")
except sqlite3.Error as e:
pretty_printer(log_obj = loggersrv.debug, to_exit = False, put_text = "{reverse}Sqlite Error: %s.{end}" %str(e))
finally:
if con:
con.commit()
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 ORDER BY lastRequestTime DESC")
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],
'machineIp': row[8],
})
return clients
def sql_update(dbName, infoDict):
con = None
try:
con = sqlite3.connect(dbName)
cur = con.cursor()
cur.execute("SELECT * FROM clients WHERE clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
try:
data = cur.fetchone()
if not data:
# Insert row.
cur.execute("INSERT INTO clients (clientMachineId, machineName, applicationId, \
skuId, licenseStatus, lastRequestTime, requestCount, machineIp) VALUES (:clientMachineId, :machineName, :appId, :skuId, :licenseStatus, :requestTime, 1, :machineIp);", infoDict)
else:
# Update data.
if data[1] != infoDict["machineName"]:
cur.execute("UPDATE clients SET machineName=:machineName WHERE \
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
if data[2] != infoDict["appId"]:
cur.execute("UPDATE clients SET applicationId=:appId WHERE \
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
if data[3] != infoDict["skuId"]:
cur.execute("UPDATE clients SET skuId=:skuId WHERE \
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
if data[4] != infoDict["licenseStatus"]:
cur.execute("UPDATE clients SET licenseStatus=:licenseStatus WHERE \
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
if data[5] != infoDict["requestTime"]:
cur.execute("UPDATE clients SET lastRequestTime=:requestTime WHERE \
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
if data[8] != infoDict["machineIp"]:
cur.execute("UPDATE clients SET machineIp=:machineIp WHERE \
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
# Increment requestCount
cur.execute("UPDATE clients SET requestCount=requestCount+1 WHERE \
clientMachineId=:clientMachineId AND applicationId=:appId;", infoDict)
except sqlite3.Error as e:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
except sqlite3.Error as e:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
finally:
if con:
con.commit()
con.close()
def sql_update_epid(dbName, kmsRequest, response, appName):
cmid = str(kmsRequest['clientMachineId'].get())
con = None
try:
con = sqlite3.connect(dbName)
cur = con.cursor()
cur.execute("SELECT * FROM clients WHERE clientMachineId=? AND applicationId=?;", (cmid, appName))
try:
data = cur.fetchone()
cur.execute("UPDATE clients SET kmsEpid=? WHERE \
clientMachineId=? AND applicationId=?;", (str(response["kmsEpid"].decode('utf-16le')), cmid, appName))
except sqlite3.Error as e:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
except sqlite3.Error as e:
pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
finally:
if con:
con.commit()
con.close()

View File

@@ -4,15 +4,16 @@
if [ ! -z "${DEBUG}" ]; then
KMS_LOGLEVEL="DEBUG"
eleven log debug "setting kms log level to DEBUG"
else
KMS_LOGLEVEL="INFO"
fi
cd /opt/py-kms
set -- "python3" \
pykms_Server.py \
0.0.0.0 \
:: \
1688 \
-l ${KMS_LOCALE} \
-c ${KMS_CLIENTCOUNT} \
-a ${KMS_ACTIVATIONINTERVAL} \
-r ${KMS_RENEWALINTERVAL} \
-s /kms/var/kms.db \