mirror of
https://github.com/11notes/docker-kms.git
synced 2025-10-23 16:14:05 +00:00
Compare commits
54 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
74f3f1a6d8 | ||
|
88106c5ab3 | ||
|
3c49769856 | ||
|
0731c67061 | ||
|
5ad13ddfeb | ||
|
3045fea5a5 | ||
|
98df1f7f0a | ||
|
803d20d5e0 | ||
|
cb4531c479 | ||
|
e340cb2fd5 | ||
|
6be75ef815 | ||
|
26c465e656 | ||
|
c36ab2d369 | ||
|
ea186dd607 | ||
|
5d47cf0b9f | ||
|
bad5f50548 | ||
|
e6bf310706 | ||
|
b9c5b148a1 | ||
|
46dab8b24f | ||
|
b154c116cc | ||
|
66090fdadb | ||
|
58910eb75d | ||
|
06e8f2a63e | ||
|
6ec2821901 | ||
|
a3a755b54e | ||
|
dd0025df2d | ||
|
23231c4cbb | ||
|
28586cccec | ||
|
ce51cbe448 | ||
|
c5b9d8f1fa | ||
|
bd566a8900 | ||
|
58a28d8852 | ||
|
44e604d964 | ||
|
c055cc3fb2 | ||
|
74661d19d9 | ||
|
ad35b06dc0 | ||
|
efccd9cdb3 | ||
|
5c6e416ce4 | ||
|
48a5ba320c | ||
|
78d0173da0 | ||
|
c157fc1094 | ||
|
b48eeb675e | ||
|
18c70eb586 | ||
|
b1ff4dc249 | ||
|
39c409583f | ||
|
7b2d310a77 | ||
|
33a59cfaa8 | ||
|
fdfa93cda8 | ||
|
8a2410c6dd | ||
|
976e5908d6 | ||
|
bbc70f4e85 | ||
|
5e99a47212 | ||
|
2bc18274b2 | ||
|
496ef93b83 |
@@ -1,5 +1,7 @@
|
||||
# default
|
||||
.git*
|
||||
*.md
|
||||
LICENSE
|
||||
maintain/
|
||||
project*
|
||||
LICENSE
|
||||
*.md
|
||||
img/
|
||||
node_modules/
|
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,2 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
* text=auto
|
||||
*.sh eol=lf
|
340
.github/workflows/docker.yml
vendored
340
.github/workflows/docker.yml
vendored
@@ -1,18 +1,57 @@
|
||||
name: create and publish docker image
|
||||
name: docker
|
||||
run-name: ${{ inputs.run-name }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
inputs:
|
||||
run-name:
|
||||
description: 'set run-name for workflow (multiple calls)'
|
||||
type: string
|
||||
required: false
|
||||
default: 'docker'
|
||||
|
||||
env:
|
||||
DOCKER_USERNAME: 11notes
|
||||
release:
|
||||
description: 'set WORKFLOW_GITHUB_RELEASE'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
readme:
|
||||
description: 'set WORKFLOW_GITHUB_README'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
image:
|
||||
description: 'set IMAGE'
|
||||
required: false
|
||||
|
||||
uid:
|
||||
description: 'set IMAGE_UID'
|
||||
required: false
|
||||
|
||||
gid:
|
||||
description: 'set IMAGE_GID'
|
||||
required: false
|
||||
|
||||
semverprefix:
|
||||
description: 'prefix for semver tags'
|
||||
required: false
|
||||
|
||||
semversuffix:
|
||||
description: 'suffix for semver tags'
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
docker:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
ports:
|
||||
- 5000:5000
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
packages: write
|
||||
security-events: write
|
||||
@@ -20,39 +59,111 @@ jobs:
|
||||
steps:
|
||||
- name: init / checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
|
||||
- name: init / .json to env
|
||||
uses: rgarcia-phi/json-to-variables@9835d537368468c4e4de5254dc3efeadda183793
|
||||
with:
|
||||
filename: '.json'
|
||||
ref: ${{ github.ref_name }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: init / setup environment
|
||||
run: |
|
||||
: # set default arch if not set
|
||||
echo "IMAGE_ARCH=${json_arch:-linux/amd64,linux/arm64}" >> $GITHUB_ENV
|
||||
uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298
|
||||
with:
|
||||
script: |
|
||||
const { existsSync, readFileSync } = require('node:fs');
|
||||
const { resolve } = require('node:path');
|
||||
const inputs = `${{ toJSON(github.event.inputs) }}`;
|
||||
const opt = {input:{}, dot:{}};
|
||||
|
||||
: # create tags for semver, stable and other shenanigans
|
||||
export LOCAL_SHA=$(git rev-parse --short HEAD)
|
||||
export LOCAL_SEMVER_MAJOR=$(awk -F. '{ print $1 }' <<< ${json_version})
|
||||
export LOCAL_SEMVER_MINOR=$(awk -F. '{ print $2 }' <<< ${json_version})
|
||||
export LOCAL_SEMVER_PATCH=$(awk -F. '{ print $3 }' <<< ${json_version})
|
||||
export LOCAL_TAGS="${json_image}:latest"
|
||||
if [ ! -z ${LOCAL_SEMVER_MAJOR} ]; then LOCAL_TAGS="${LOCAL_TAGS},${json_image}:${LOCAL_SEMVER_MAJOR}"; fi
|
||||
if [ ! -z ${LOCAL_SEMVER_MINOR} ]; then LOCAL_TAGS="${LOCAL_TAGS},${json_image}:${LOCAL_SEMVER_MAJOR}.${LOCAL_SEMVER_MINOR}"; fi
|
||||
if [ ! -z ${LOCAL_SEMVER_PATCH} ]; then LOCAL_TAGS="${LOCAL_TAGS},${json_image}:${LOCAL_SEMVER_MAJOR}.${LOCAL_SEMVER_MINOR}.${LOCAL_SEMVER_PATCH}"; fi
|
||||
if echo "${LOCAL_TAGS}" | grep -q "${json_stable}" ; then LOCAL_TAGS="${LOCAL_TAGS},${json_image}:stable"; fi
|
||||
if [ ! -z ${json_tags} ]; then SPECIAL_LOCAL_TAGS=$(echo ${json_tags} | sed 's/,/ /g'); for LOCAL_TAG in ${json_tags}; do LOCAL_TAGS="${LOCAL_TAGS},${json_image}:${LOCAL_TAG}"; done; fi
|
||||
LOCAL_TAGS="${LOCAL_TAGS},${json_image}:${LOCAL_SHA}"
|
||||
echo "IMAGE_TAGS=${LOCAL_TAGS}" >> $GITHUB_ENV
|
||||
try{
|
||||
if(inputs.length > 0){
|
||||
opt.input = JSON.parse(inputs);
|
||||
}
|
||||
}catch(e){
|
||||
core.warning('could not parse github.event.inputs');
|
||||
}
|
||||
|
||||
: # if for whatever reason UID/GID must be changed at build time
|
||||
echo "IMAGE_UID=${json_uid:-1000}" >> $GITHUB_ENV
|
||||
echo "IMAGE_GID=${json_gid:-1000}" >> $GITHUB_ENV
|
||||
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);
|
||||
}
|
||||
|
||||
const docker = {
|
||||
image:{
|
||||
name:(opt.input?.image || opt.dot.image),
|
||||
arch:(opt.dot.arch || 'linux/amd64,linux/arm64'),
|
||||
prefix:((opt.input?.semverprefix) ? `${opt.input?.semverprefix}-` : ''),
|
||||
suffix:((opt.input?.semversuffix) ? `-${opt.input?.semversuffix}` : ''),
|
||||
description:(opt.dot?.readme?.description || ''),
|
||||
tags:[],
|
||||
},
|
||||
app:{
|
||||
name:opt.dot.name,
|
||||
version:opt.dot.semver.version,
|
||||
root:opt.dot.root,
|
||||
UID:(opt.input?.uid || 1000),
|
||||
GID:(opt.input?.gid || 1000),
|
||||
no_cache:new Date().getTime(),
|
||||
},
|
||||
cache:{
|
||||
registry:'localhost:5000/',
|
||||
}
|
||||
};
|
||||
|
||||
docker.cache.name = `${docker.image.name}:${docker.image.prefix}buildcache${docker.image.suffix}`;
|
||||
docker.cache.grype = `${docker.cache.registry}${docker.image.name}:${docker.image.prefix}grype${docker.image.suffix}`;
|
||||
|
||||
// setup tags
|
||||
const semver = opt.dot.semver.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');
|
||||
|
||||
for(let i=0; i<docker.image.tags.length; i++){
|
||||
docker.image.tags[i] = `${docker.image.name}:${docker.image.prefix}${docker.image.tags[i]}${docker.image.suffix}`;
|
||||
}
|
||||
|
||||
// setup build arguments
|
||||
const arguments = [];
|
||||
for(const argument in docker.app){
|
||||
arguments.push(`APP_${argument.toUpperCase()}=${docker.app[argument]}`);
|
||||
}
|
||||
|
||||
// export to environment
|
||||
core.exportVariable('DOCKER_CACHE_REGISTRY', docker.cache.registry);
|
||||
core.exportVariable('DOCKER_CACHE_NAME', docker.cache.name);
|
||||
core.exportVariable('DOCKER_CACHE_GRYPE', docker.cache.grype);
|
||||
|
||||
core.exportVariable('DOCKER_IMAGE_NAME', docker.image.name);
|
||||
core.exportVariable('DOCKER_IMAGE_ARCH', docker.image.arch);
|
||||
core.exportVariable('DOCKER_IMAGE_TAGS', docker.image.tags.join(','));
|
||||
core.exportVariable('DOCKER_IMAGE_DESCRIPTION', docker.image.description);
|
||||
core.exportVariable('DOCKER_IMAGE_ARGUMENTS', arguments.join("\r\n"));
|
||||
|
||||
core.exportVariable('WORKFLOW_CREATE_RELEASE', (opt.input?.release || true));
|
||||
core.exportVariable('WORKFLOW_CREATE_README', (opt.input?.readme || true));
|
||||
core.exportVariable('WORKFLOW_GRYPE_FAIL_ON_SEVERITY', (opt.json?.grpye?.fail || true));
|
||||
core.exportVariable('WORKFLOW_GRYPE_SEVERITY_CUTOFF', (opt.json?.grpye?.severity || 'high'));
|
||||
|
||||
|
||||
|
||||
# DOCKER
|
||||
- name: docker / login to hub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
|
||||
with:
|
||||
username: ${{ env.DOCKER_USERNAME }}
|
||||
username: 11notes
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: docker / setup qemu
|
||||
@@ -60,50 +171,45 @@ jobs:
|
||||
|
||||
- name: docker / setup buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: grype / build & push
|
||||
- name: docker / build & push & tag grype
|
||||
id: docker-build
|
||||
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
|
||||
with:
|
||||
context: .
|
||||
file: arch.dockerfile
|
||||
push: true
|
||||
platforms: ${{ env.IMAGE_ARCH }}
|
||||
cache-from: type=registry,ref=${{ env.json_image }}:buildcache
|
||||
cache-to: type=registry,ref=${{ env.json_image }}:buildcache,mode=max,compression=zstd,force-compression=true
|
||||
platforms: ${{ env.DOCKER_IMAGE_ARCH }}
|
||||
cache-from: type=registry,ref=${{ env.DOCKER_CACHE_NAME }}
|
||||
cache-to: type=registry,ref=${{ env.DOCKER_CACHE_REGISTRY }}${{ env.DOCKER_CACHE_NAME }},mode=max,compression=zstd,force-compression=true
|
||||
build-args: |
|
||||
APP_IMAGE=${{ env.json_image }}
|
||||
APP_NAME=${{ env.json_name }}
|
||||
APP_VERSION=${{ env.json_version }}
|
||||
APP_ROOT=${{ env.json_root }}
|
||||
APP_UID=${{ env.IMAGE_UID }}
|
||||
APP_GID=${{ env.IMAGE_GID }}
|
||||
NO_CACHE=$(date +%s)
|
||||
${{ env.DOCKER_IMAGE_ARGUMENTS }}
|
||||
tags: |
|
||||
${{ env.json_image }}:grype
|
||||
${{ env.DOCKER_CACHE_GRYPE }}
|
||||
|
||||
- name: grype / scan
|
||||
id: scan
|
||||
id: grype
|
||||
uses: anchore/scan-action@abae793926ec39a78ab18002bc7fc45bbbd94342
|
||||
with:
|
||||
image: ${{ env.json_image }}:grype
|
||||
severity-cutoff: high
|
||||
image: ${{ env.DOCKER_CACHE_GRYPE }}
|
||||
fail-build: ${{ env.WORKFLOW_GRYPE_FAIL_ON_SEVERITY }}
|
||||
severity-cutoff: ${{ env.WORKFLOW_GRYPE_SEVERITY_CUTOFF }}
|
||||
output-format: 'sarif'
|
||||
by-cve: true
|
||||
cache-db: true
|
||||
|
||||
- name: grype / delete tag
|
||||
if: success() || failure()
|
||||
run: |
|
||||
curl --request DELETE \
|
||||
--url https://hub.docker.com/v2/repositories/${{ env.json_image }}/tags/grype/ \
|
||||
--header 'authorization: jwt ${{ secrets.DOCKER_TOKEN }}' \
|
||||
--header 'content-type: application/json' \
|
||||
--fail
|
||||
|
||||
- name: grype / report / upload
|
||||
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169
|
||||
- name: grype / fail
|
||||
if: failure() || steps.grype.outcome == 'failure'
|
||||
uses: anchore/scan-action@abae793926ec39a78ab18002bc7fc45bbbd94342
|
||||
with:
|
||||
sarif_file: ${{ steps.scan.outputs.sarif }}
|
||||
|
||||
- name: grype / report / print
|
||||
run: cat ${{ steps.scan.outputs.sarif }}
|
||||
image: ${{ env.DOCKER_CACHE_GRYPE }}
|
||||
fail-build: false
|
||||
severity-cutoff: ${{ env.WORKFLOW_GRYPE_SEVERITY_CUTOFF }}
|
||||
output-format: 'table'
|
||||
by-cve: true
|
||||
cache-db: true
|
||||
|
||||
- name: docker / build & push
|
||||
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
|
||||
@@ -113,25 +219,92 @@ jobs:
|
||||
push: true
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
platforms: ${{ env.IMAGE_ARCH }}
|
||||
cache-from: type=registry,ref=${{ env.json_image }}:buildcache
|
||||
cache-to: type=registry,ref=${{ env.json_image }}:buildcache,mode=max,compression=zstd,force-compression=true
|
||||
platforms: ${{ env.DOCKER_IMAGE_ARCH }}
|
||||
cache-from: type=registry,ref=${{ env.DOCKER_CACHE_REGISTRY }}${{ env.DOCKER_CACHE_NAME }}
|
||||
cache-to: type=registry,ref=${{ env.DOCKER_CACHE_NAME }},mode=max,compression=zstd,force-compression=true
|
||||
build-args: |
|
||||
APP_IMAGE=${{ env.json_image }}
|
||||
APP_NAME=${{ env.json_name }}
|
||||
APP_VERSION=${{ env.json_version }}
|
||||
APP_ROOT=${{ env.json_root }}
|
||||
APP_UID=${{ env.IMAGE_UID }}
|
||||
APP_GID=${{ env.IMAGE_GID }}
|
||||
NO_CACHE=$(date +%s)
|
||||
${{ env.DOCKER_IMAGE_ARGUMENTS }}
|
||||
tags: |
|
||||
${{ env.IMAGE_TAGS }}
|
||||
${{ env.DOCKER_IMAGE_TAGS }}
|
||||
|
||||
- name: github / create release notes
|
||||
|
||||
|
||||
# RELEASE
|
||||
- name: github / release / log
|
||||
continue-on-error: true
|
||||
id: git-log
|
||||
run: |
|
||||
LOCAL_LAST_TAG=$(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`)
|
||||
echo "using last tag: ${LOCAL_LAST_TAG}"
|
||||
LOCAL_COMMITS=$(git log ${LOCAL_LAST_TAG}..HEAD --oneline)
|
||||
|
||||
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
|
||||
echo "commits<<${EOF}" >> ${GITHUB_OUTPUT}
|
||||
echo "${LOCAL_COMMITS}" >> ${GITHUB_OUTPUT}
|
||||
echo "${EOF}" >> ${GITHUB_OUTPUT}
|
||||
|
||||
- name: github / release / markdown
|
||||
if: env.WORKFLOW_CREATE_RELEASE == 'true' && steps.git-log.outcome == 'success'
|
||||
id: git-release
|
||||
uses: 11notes/action-docker-release@v1
|
||||
with:
|
||||
git_log: ${{ steps.git-log.outputs.commits }}
|
||||
|
||||
- name: github / release / create
|
||||
if: env.WORKFLOW_CREATE_RELEASE == 'true' && steps.git-release.outcome == 'success'
|
||||
uses: actions/create-release@4c11c9fe1dcd9636620a16455165783b20fc7ea0
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release create ${{ github.ref_name }} -F RELEASE.md
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
body: ${{ steps.git-release.outputs.release }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
|
||||
|
||||
# README
|
||||
- name: github / checkout master
|
||||
continue-on-error: true
|
||||
run: |
|
||||
git checkout master
|
||||
|
||||
- name: github / create README.md
|
||||
id: github-readme
|
||||
continue-on-error: true
|
||||
if: env.WORKFLOW_CREATE_README == 'true' && steps.docker-build.outcome == 'success'
|
||||
uses: 11notes/action-docker-readme@v1
|
||||
with:
|
||||
sarif_file: ${{ steps.grype.outputs.sarif }}
|
||||
build_output_metadata: ${{ steps.docker-build.outputs.metadata }}
|
||||
|
||||
- name: github / commit & push
|
||||
continue-on-error: true
|
||||
if: steps.github-readme.outcome == 'success' && hashFiles('README.md') != ''
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add README.md
|
||||
git commit -m "auto update README.md"
|
||||
git push
|
||||
|
||||
- name: docker / push README.md to docker hub
|
||||
continue-on-error: true
|
||||
if: steps.github-readme.outcome == 'success' && hashFiles('README.md') != ''
|
||||
uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8
|
||||
env:
|
||||
DOCKER_USER: 11notes
|
||||
DOCKER_PASS: ${{ secrets.DOCKER_TOKEN }}
|
||||
with:
|
||||
destination_container_repo: ${{ env.DOCKER_IMAGE_NAME }}
|
||||
provider: dockerhub
|
||||
short_description: ${{ env.DOCKER_IMAGE_DESCRIPTION }}
|
||||
readme_file: 'README.md'
|
||||
|
||||
|
||||
|
||||
# REPOSITORY SETTINGS
|
||||
- name: github / update description and set repo defaults
|
||||
run: |
|
||||
curl --request PATCH \
|
||||
@@ -139,22 +312,11 @@ jobs:
|
||||
--header 'authorization: Bearer ${{ secrets.REPOSITORY_TOKEN }}' \
|
||||
--header 'content-type: application/json' \
|
||||
--data '{
|
||||
"description":"${{ env.json_description }}",
|
||||
"description":"${{ env.DOCKER_IMAGE_DESCRIPTION }}",
|
||||
"homepage":"",
|
||||
"has_issues":true,
|
||||
"has_discussions":true,
|
||||
"has_projects":false,
|
||||
"has_wiki":false
|
||||
}' \
|
||||
--fail
|
||||
|
||||
- name: docker / push README.md to docker hub
|
||||
uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8
|
||||
env:
|
||||
DOCKER_USER: ${{ env.DOCKER_USERNAME }}
|
||||
DOCKER_PASS: ${{ secrets.DOCKER_TOKEN }}
|
||||
with:
|
||||
destination_container_repo: ${{ env.json_image }}
|
||||
provider: dockerhub
|
||||
short_description: ${{ env.json_description }}
|
||||
readme_file: 'README.md'
|
||||
--fail
|
51
.github/workflows/tags.yml
vendored
Normal file
51
.github/workflows/tags.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: tags
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: build docker image
|
||||
uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7
|
||||
with:
|
||||
workflow: docker.yml
|
||||
token: "${{ secrets.REPOSITORY_TOKEN }}"
|
||||
inputs: '{ "release":"true", "readme":"true" }'
|
||||
|
||||
docker-unraid:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: build docker image for unraid community
|
||||
uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7
|
||||
with:
|
||||
workflow: docker.yml
|
||||
token: "${{ secrets.REPOSITORY_TOKEN }}"
|
||||
inputs: '{ "release":"false", "readme":"false", "uid":"99", "gid":"100", "semversuffix":"unraid", "run-name":"docker-unraid" }'
|
||||
|
||||
kms-gui:
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker
|
||||
steps:
|
||||
- name: build downstream kms gui
|
||||
uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7
|
||||
with:
|
||||
workflow: docker.yml
|
||||
token: "${{ secrets.REPOSITORY_TOKEN }}"
|
||||
repo: 11notes/docker-kms-gui
|
||||
ref: master
|
||||
inputs: '{ "release":"false", "readme":"true" }'
|
||||
|
||||
kms-gui-unraid:
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker-unraid
|
||||
steps:
|
||||
- name: build downstream kms gui for unraid community
|
||||
uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7
|
||||
with:
|
||||
workflow: docker.yml
|
||||
token: "${{ secrets.REPOSITORY_TOKEN }}"
|
||||
repo: 11notes/docker-kms-gui
|
||||
ref: master
|
||||
inputs: '{ "release":"false", "readme":"false", "uid":"99", "gid":"100", "semversuffix":"unraid", "run-name":"docker-unraid" }'
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
# default
|
||||
maintain/
|
||||
project*
|
||||
node_modules/
|
19
.json
19
.json
@@ -1,10 +1,21 @@
|
||||
{
|
||||
"image":"11notes/kms",
|
||||
"description":"Activate any version of Windows and Office, forever",
|
||||
"name":"kms",
|
||||
"version":"646f476",
|
||||
"root":"/kms",
|
||||
|
||||
"stable":"646f476",
|
||||
"parent":"11notes/alpine:stable"
|
||||
"semver":{
|
||||
"version":"465f4d1",
|
||||
"stable":"465f4d1",
|
||||
"latest":"465f4d1"
|
||||
},
|
||||
|
||||
"readme":{
|
||||
"description":"Activate any version of Windows and Office, forever",
|
||||
"parent":{
|
||||
"image":"11notes/alpine:stable"
|
||||
},
|
||||
"built":{
|
||||
"py-kms":"https://github.com/Py-KMS-Organization/py-kms"
|
||||
}
|
||||
}
|
||||
}
|
67
README.md
67
README.md
@@ -1,14 +1,29 @@
|
||||

|
||||

|
||||
|
||||
# 🏔️ kms on Alpine
|
||||
[<img src="https://img.shields.io/badge/github-source-blue?logo=github&color=040308">](https://github.com/11notes/docker-kms)[<img src="https://img.shields.io/github/issues/11notes/docker-kms?color=7842f5">](https://github.com/11notes/docker-kms/issues)
|
||||
# ⛰️ kms
|
||||
[<img src="https://img.shields.io/badge/github-source-blue?logo=github&color=040308">](https://github.com/11notes/docker-kms)[<img src="https://img.shields.io/github/issues/11notes/docker-kms?color=7842f5">](https://github.com/11notes/docker-kms/issues)
|
||||
|
||||
**Activate any version of Windows and Office, forever**
|
||||
Activate any version of Windows and Office, forever
|
||||
|
||||

|
||||
# 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)
|
||||
* [stable](https://hub.docker.com/r/11notes/kms/tags?name=stable)
|
||||
* [latest](https://hub.docker.com/r/11notes/kms/tags?name=latest)
|
||||
* [465f4d1-unraid](https://hub.docker.com/r/11notes/kms/tags?name=465f4d1-unraid)
|
||||
* [stable-unraid](https://hub.docker.com/r/11notes/kms/tags?name=stable-unraid)
|
||||
* [latest-unraid](https://hub.docker.com/r/11notes/kms/tags?name=latest-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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
# SYNOPSIS 📖
|
||||
**What can I do with this?** This image will run a KMS server you can use to activate any version of Windows and Office, forever. If you need a GUI, simply add [11notes/kms-gui](https://github.com/11notes/docker-kms-gui) to your compose.
|
||||
**What can I do with this?** This image will run a KMS server you can use to activate any version of Windows and Office, forever.
|
||||
|
||||
Works with:
|
||||
- Windows Vista
|
||||
@@ -40,7 +55,7 @@ Works with:
|
||||
name: "kms"
|
||||
services:
|
||||
kms:
|
||||
image: "11notes/kms:646f476"
|
||||
image: "11notes/kms:465f4d1"
|
||||
container_name: "kms"
|
||||
environment:
|
||||
TZ: "Europe/Zurich"
|
||||
@@ -48,9 +63,9 @@ services:
|
||||
- "var:/kms/var"
|
||||
ports:
|
||||
- "1688:1688/tcp"
|
||||
restart: always
|
||||
restart: "always"
|
||||
kms-gui:
|
||||
image: "11notes/kms-gui:latest"
|
||||
image: "11notes/kms-gui:stable"
|
||||
container_name: "kms-gui"
|
||||
environment:
|
||||
TZ: "Europe/Zurich"
|
||||
@@ -58,20 +73,21 @@ services:
|
||||
- "var:/kms/var"
|
||||
ports:
|
||||
- "8080:8080/tcp"
|
||||
restart: always
|
||||
restart: "always"
|
||||
volumes:
|
||||
var:
|
||||
```
|
||||
|
||||
|
||||
# EXAMPLE
|
||||
## Windows Server 2025 Datacenter. List of [GVLK](https://learn.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys)
|
||||
```cmd
|
||||
slmgr /ipk D764K-2NDRG-47T6Q-P8T8W-YP6DF
|
||||
```
|
||||
Add your KMS server information to server
|
||||
Add your KMS server information to server via registry
|
||||
```powershell
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareProtectionPlatform" -Name "KeyManagementServiceName" -Value "KMS_IP"
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareProtectionPlatform" -Name "KeyManagementServicePort" -Value "KMS_PORT"
|
||||
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\OfficeSoftwareProtectionPlatform" -Name "KeyManagementServiceName" -Value "KMS_IP"
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\OfficeSoftwareProtectionPlatform" -Name "KeyManagementServicePort" -Value "KMS_PORT"
|
||||
```
|
||||
@@ -93,11 +109,9 @@ slmgr /ato
|
||||
| Parameter | Value | Default |
|
||||
| --- | --- | --- |
|
||||
| `TZ` | [Time Zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | |
|
||||
| `DEBUG` | Show debug messages from image **not** app | |
|
||||
| `KMS_IP` | localhost or 127.0.0.1 or a dedicated IP | 0.0.0.0 |
|
||||
| `KMS_PORT` | any port > 1024 | 1688 |
|
||||
| `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 | 25 |
|
||||
| `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 |
|
||||
@@ -110,12 +124,21 @@ slmgr /ato
|
||||
|
||||
# BUILT WITH 🧰
|
||||
* [py-kms](https://github.com/Py-KMS-Organization/py-kms)
|
||||
* [alpine](https://alpinelinux.org)
|
||||
|
||||
# TIPS 📌
|
||||
* Use a reverse proxy like Traefik, Nginx, HAproxy to terminate TLS with a valid certificate
|
||||
* Use Let’s Encrypt certificates to protect your SSL endpoints
|
||||
# GENERAL TIPS 📌
|
||||
* Use a reverse proxy like Traefik, Nginx, HAproxy to terminate TLS and to protect your endpoints
|
||||
* Use Let’s Encrypt DNS-01 challenge to obtain valid SSL certificates for your services
|
||||
* Do not expose this image to WAN! You will get notified from Microsoft via your ISP to terminate the service if you do so
|
||||
* [Microsoft LICD](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a)
|
||||
|
||||
* Use [11notes/kms-gui](https://github.com/11notes/docker-kms-gui) if you want to see the clients you activated in a nice web GUI
|
||||
|
||||
# SECURITY VULNERABILITIES REPORT ⚡
|
||||
| Severity | Package | Version | Fix Version | Type | Location | Data Namespace | Link |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 4.7 (Medium) | linux-pam | 1.6.1-r1 | | apk | /lib/apk/db/installed | nvd:cpe | [CVE-2024-10041](https://nvd.nist.gov/vuln/detail/CVE-2024-10041) |
|
||||
|
||||
|
||||
# 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 . You can find all my repositories on [github](https://github.com/11notes?tab=repositories).
|
||||
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 Fri, 21 Feb 2025 05:56:22 GMT*
|
@@ -1,2 +0,0 @@
|
||||
### 🪄 Features
|
||||
* switch to new github workflow and build process
|
@@ -1,29 +1,15 @@
|
||||
# :: Util
|
||||
FROM alpine AS util
|
||||
FROM 11notes/util AS util
|
||||
|
||||
ARG NO_CACHE
|
||||
|
||||
RUN set -ex; \
|
||||
apk --no-cache --update add \
|
||||
git; \
|
||||
git clone https://github.com/11notes/docker-util.git;
|
||||
|
||||
# :: Build / redis
|
||||
FROM python:3.12-alpine AS build
|
||||
|
||||
ARG TARGETARCH
|
||||
# :: Build / py-kms
|
||||
FROM alpine/git AS build
|
||||
ARG APP_VERSION
|
||||
|
||||
USER root
|
||||
|
||||
RUN set -ex; \
|
||||
apk --update --no-cache add \
|
||||
git; \
|
||||
mkdir -p /opt/py-kms; \
|
||||
git clone https://github.com/Py-KMS-Organization/py-kms.git; \
|
||||
cd /py-kms/py-kms; \
|
||||
git clone https://github.com/Py-KMS-Organization/py-kms.git -b next; \
|
||||
cd /git/py-kms; \
|
||||
git checkout ${APP_VERSION}; \
|
||||
cp -R /py-kms/py-kms/* /opt/py-kms;
|
||||
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;
|
||||
|
||||
# :: Header
|
||||
FROM 11notes/alpine:stable
|
||||
@@ -34,6 +20,8 @@
|
||||
ARG APP_NAME
|
||||
ARG APP_VERSION
|
||||
ARG APP_ROOT
|
||||
ARG APP_UID
|
||||
ARG APP_GID
|
||||
|
||||
# :: environment
|
||||
ENV APP_IMAGE=${APP_IMAGE}
|
||||
@@ -41,35 +29,31 @@
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV APP_ROOT=${APP_ROOT}
|
||||
|
||||
ENV KMS_IP=0.0.0.0
|
||||
ENV KMS_PORT=1688
|
||||
ENV KMS_LOCALE=1033
|
||||
ENV KMS_CLIENTCOUNT=26
|
||||
ENV KMS_ACTIVATIONINTERVAL=120
|
||||
ENV KMS_RENEWALINTERVAL=10080
|
||||
ENV KMS_RENEWALINTERVAL=259200
|
||||
ENV KMS_LOGLEVEL="INFO"
|
||||
|
||||
# :: multi-stage
|
||||
COPY --from=util /docker-util/src/ /usr/local/bin
|
||||
COPY --from=build /opt/py-kms/ /opt/py-kms
|
||||
COPY --from=util /usr/local/bin/ /usr/local/bin
|
||||
COPY --from=build /git/py-kms/py-kms/ /opt/py-kms
|
||||
|
||||
# :: Run
|
||||
# :: Run
|
||||
USER root
|
||||
RUN eleven printenv;
|
||||
|
||||
# :: install application
|
||||
RUN set -ex; \
|
||||
apk --no-cache --update add \
|
||||
python3=3.12.8-r1; \
|
||||
python3; \
|
||||
apk --no-cache --update --virtual .build add \
|
||||
py3-pip;
|
||||
|
||||
RUN set -ex; \
|
||||
mkdir -p ${APP_ROOT}/var; \
|
||||
touch /var/log/kms.log; \
|
||||
ln -sf /dev/stdout /var/log/kms.log; \
|
||||
pip3 install --no-cache-dir --break-system-packages \
|
||||
tzlocal \
|
||||
pytz; \
|
||||
pip3 install --no-cache-dir -r /opt/py-kms/requirements.txt --break-system-packages; \
|
||||
pip3 install --no-cache-dir pytz --break-system-packages; \
|
||||
apk del --no-network .build;
|
||||
|
||||
# :: copy filesystem changes and set correct permissions
|
||||
@@ -77,13 +61,18 @@
|
||||
RUN set -ex; \
|
||||
chmod +x -R /usr/local/bin; \
|
||||
chown -R 1000:1000 \
|
||||
${APP_ROOT};
|
||||
${APP_ROOT} \
|
||||
/opt/py-kms;
|
||||
|
||||
# :: support unraid
|
||||
RUN set -ex; \
|
||||
eleven unraid
|
||||
|
||||
# :: Volumes
|
||||
VOLUME ["${APP_ROOT}/var"]
|
||||
|
||||
# :: Monitor
|
||||
HEALTHCHECK --interval=5s --timeout=2s CMD /usr/local/bin/healthcheck.sh || exit 1
|
||||
HEALTHCHECK --interval=5s --timeout=2s CMD netstat -an | grep -q 1688 || exit 1
|
||||
|
||||
# :: Start
|
||||
USER docker
|
@@ -1,7 +1,7 @@
|
||||
name: "kms"
|
||||
services:
|
||||
kms:
|
||||
image: "11notes/kms:646f476"
|
||||
image: "11notes/kms:465f4d1"
|
||||
container_name: "kms"
|
||||
environment:
|
||||
TZ: "Europe/Zurich"
|
||||
@@ -9,9 +9,9 @@ services:
|
||||
- "var:/kms/var"
|
||||
ports:
|
||||
- "1688:1688/tcp"
|
||||
restart: always
|
||||
restart: "always"
|
||||
kms-gui:
|
||||
image: "11notes/kms-gui:latest"
|
||||
image: "11notes/kms-gui:stable"
|
||||
container_name: "kms-gui"
|
||||
environment:
|
||||
TZ: "Europe/Zurich"
|
||||
@@ -19,6 +19,6 @@ services:
|
||||
- "var:/kms/var"
|
||||
ports:
|
||||
- "8080:8080/tcp"
|
||||
restart: always
|
||||
restart: "always"
|
||||
volumes:
|
||||
var:
|
BIN
img/Office.png
Normal file
BIN
img/Office.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
img/Windows11ENTLTSC.png
Normal file
BIN
img/Windows11ENTLTSC.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
img/WindowsSRV2025.png
Normal file
BIN
img/WindowsSRV2025.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
BIN
img/webGUICustomIcon.png
Normal file
BIN
img/webGUICustomIcon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
71
project.md
Normal file
71
project.md
Normal file
@@ -0,0 +1,71 @@
|
||||

|
||||
|
||||

|
||||
|
||||
${{ content_synopsis }} This image will run a KMS server you can use to activate any version of Windows and Office, forever.
|
||||
|
||||
Works with:
|
||||
- Windows Vista
|
||||
- Windows 7
|
||||
- Windows 8
|
||||
- Windows 8.1
|
||||
- Windows 10
|
||||
- Windows 11
|
||||
- Windows Server 2008
|
||||
- Windows Server 2008 R2
|
||||
- Windows Server 2012
|
||||
- Windows Server 2012 R2
|
||||
- Windows Server 2016
|
||||
- Windows Server 2019
|
||||
- Windows Server 2022
|
||||
- Windows Server 2025
|
||||
- Microsoft Office 2010 ( Volume License )
|
||||
- Microsoft Office 2013 ( Volume License )
|
||||
- Microsoft Office 2016 ( Volume License )
|
||||
- Microsoft Office 2019 ( Volume License )
|
||||
- Microsoft Office 2021 ( Volume License )
|
||||
- Microsoft Office 2024 ( Volume License )
|
||||
|
||||
${{ title_volumes }}
|
||||
* **${{ json_root }}/var** - Directory of the activation database
|
||||
|
||||
${{ content_compose }}
|
||||
|
||||
# EXAMPLE
|
||||
## Windows Server 2025 Datacenter. List of [GVLK](https://learn.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys)
|
||||
```cmd
|
||||
slmgr /ipk D764K-2NDRG-47T6Q-P8T8W-YP6DF
|
||||
```
|
||||
Add your KMS server information to server via registry
|
||||
```powershell
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareProtectionPlatform" -Name "KeyManagementServiceName" -Value "KMS_IP"
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareProtectionPlatform" -Name "KeyManagementServicePort" -Value "KMS_PORT"
|
||||
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\OfficeSoftwareProtectionPlatform" -Name "KeyManagementServiceName" -Value "KMS_IP"
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\OfficeSoftwareProtectionPlatform" -Name "KeyManagementServicePort" -Value "KMS_PORT"
|
||||
```
|
||||
Activate server
|
||||
```cmd
|
||||
slmgr /ato
|
||||
```
|
||||
|
||||
${{ content_defaults }}
|
||||
| `database` | /kms/var/kms.db | SQlite database holding all client data |
|
||||
|
||||
${{ 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 }}
|
||||
|
||||
${{ content_parent }}
|
||||
|
||||
${{ content_built }}
|
||||
|
||||
${{ content_tips }}
|
||||
* Do not expose this image to WAN! You will get notified from Microsoft via your ISP to terminate the service if you do so
|
||||
* [Microsoft LICD](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a)
|
||||
* Use [11notes/kms-gui](https://github.com/11notes/docker-kms-gui) if you want to see the clients you activated in a nice web GUI
|
@@ -546,6 +546,10 @@
|
||||
<Activate KmsItem="02000000-0000-0000-0000-000000000000" />
|
||||
</CsvlkItem>
|
||||
|
||||
<CsvlkItem DisplayName="Office 2024" VlmcsdIndex="6" GroupId="206" MinKeyId="571000000" MaxKeyId="590999999" IniFileName="Office2024" EPid="05426-00206-456-03-1033-9100.0000-2602024" Id="f3d89bbf-c0ec-47ce-a8fa-e5a5f97e447f" InvalidWinBuild="[0,1]">
|
||||
<Activate KmsItem="1b4db7eb-4057-5ddf-91e0-36dec72071f5" />
|
||||
</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>
|
||||
@@ -609,12 +613,12 @@
|
||||
</KmsItem>
|
||||
|
||||
<KmsItem DisplayName="Windows 10 2019 (Volume)" Id="11b15659-e603-4cf1-9c1f-f0ec01b81888" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
||||
<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/2021 N" Id="7103a333-b8c8-49cc-93ce-d37c09687f92" Gvlk="92NFX-8DJQP-P6BBQ-THF9C-7CG2H" />
|
||||
<SkuItem DisplayName="Windows 10 Enterprise LTSC 2019/2021/2024" Id="32d2fab3-e4a8-42c2-923b-4bf4fd13e6ee" Gvlk="M7XTQ-FN8P6-TTKYV-9D4CC-J462D" />
|
||||
<SkuItem DisplayName="Windows 10 Enterprise LTSC 2019/2021/2024 N" Id="7103a333-b8c8-49cc-93ce-d37c09687f92" Gvlk="92NFX-8DJQP-P6BBQ-THF9C-7CG2H" />
|
||||
</KmsItem>
|
||||
|
||||
<KmsItem DisplayName="Windows 10 Unknown (Volume)" Id="d27cd636-1962-44e9-8b4f-27b6c23efb85" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
||||
</KmsItem>
|
||||
</KmsItem>
|
||||
|
||||
<KmsItem DisplayName="Windows 10/11 China Government" Id="7ba0bf23-d0f5-4072-91d9-d55af5a481b6" CanMapToDefaultCsvlk="false" DefaultKmsProtocol="6.0" NCountPolicy="25">
|
||||
<SkuItem DisplayName="Windows 10/11 Enterprise G" Id="e0b2d383-d112-413f-8a80-97f373a5820c" Gvlk="YYVX9-NTFWV-6MDM3-9PT4T-4M68B" />
|
||||
@@ -934,7 +938,7 @@
|
||||
|
||||
</AppItem>
|
||||
|
||||
<AppItem DisplayName="Office 15 (2013) / 16 (2016) / 17 (2019)" VlmcsdIndex="5" MinActiveClients="10" Id="0ff1ce15-a989-479d-af46-f275c6370663">
|
||||
<AppItem DisplayName="Office 2013 / 2016 / 2019 / LTSC 2021 / LTSC 2024" VlmcsdIndex="5" MinActiveClients="10" Id="0ff1ce15-a989-479d-af46-f275c6370663">
|
||||
|
||||
<KmsItem DisplayName="Office 2013" Id="e6a6f1bf-9d40-40c3-aa9f-c77ba21578c0" CanMapToDefaultCsvlk="false" DefaultKmsProtocol="5.0" NCountPolicy="5">
|
||||
<SkuItem DisplayName="Office Access 2013" Id="6ee7622c-18d8-4005-9fb7-92db644a279b" Gvlk="NG2JY-H4JBT-HQXYP-78QH9-4JM2D" />
|
||||
@@ -1020,21 +1024,35 @@
|
||||
<SkuItem DisplayName="Office Word 2019" Id="059834fe-a8ea-4bff-b67b-4d006b5447d3" Gvlk="PBX3G-NWMT6-Q7XBW-PYJGG-WXD33" />
|
||||
</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 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="1b4db7eb-4057-5ddf-91e0-36dec72071f5" IsPreview="false" CanMapToDefaultCsvlk="false" DefaultKmsprotocol="6.0" NCountPolicy="5">
|
||||
<SkuItem DisplayName="Office LTSC Professional Plus 2024" Id="8d368fc1-9470-4be2-8d66-90e836cbb051" Gvlk="XJ2XN-FW8RK-P4HMP-DKDBV-GCVGB" />
|
||||
<SkuItem DisplayName="Office LTSC Standard 2024" Id="bbac904f-6a7e-418a-bb4b-24c85da06187" Gvlk="V28N4-JG22K-W66P8-VTMGK-H6HGR" />
|
||||
<SkuItem DisplayName="Office Access LTSC 2024" Id="72e9faa7-ead1-4f3d-9f6e-3abc090a81d7" Gvlk="82FTR-NCHR7-W3944-MGRHM-JMCWD" />
|
||||
<SkuItem DisplayName="Office Excel LTSC 2024" Id="cbbba2c3-0ff5-4558-846a-043ef9d78559" Gvlk="F4DYN-89BP2-WQTWJ-GR8YC-CKGJG" />
|
||||
<SkuItem DisplayName="Office Outlook LTSC 2024" Id="bef3152a-8a04-40f2-a065-340c3f23516d" Gvlk="D2F8D-N3Q3B-J28PV-X27HD-RJWB9" />
|
||||
<SkuItem DisplayName="Office PowerPoint LTSC 2024" Id="b63626a4-5f05-4ced-9639-31ba730a127e" 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="9f144f27-2ac5-40b9-899d-898c2b8b4f81" Gvlk="PD3TT-NTHQQ-VC7CY-MFXK3-G87F8" />
|
||||
<SkuItem DisplayName="Office Skype for Business LSTC 2024" Id="0002290a-2091-4324-9e53-3cfe28884cde" Gvlk="4NKHF-9HBQF-Q3B6C-7YV34-F64P3" />
|
||||
<SkuItem DisplayName="Office Visio LTSC Professional 2024" Id="fa187091-8246-47b1-964f-80a0b1e5d69a" Gvlk="B7TN8-FJ8V3-7QYCP-HQPMV-YY89G" />
|
||||
<SkuItem DisplayName="Office Visio LTSC Standard 2024" Id="923fa470-aa71-4b8b-b35c-36b79bf9f44b" Gvlk="JMMVY-XFNQC-KK4HK-9H7R3-WQQTV" />
|
||||
</KmsItem>
|
||||
</AppItem>
|
||||
|
||||
</AppItems>
|
||||
|
268
rootfs/opt/py-kms/pykms_Base.py
Normal file
268
rootfs/opt/py-kms/pykms_Base.py
Normal file
@@ -0,0 +1,268 @@
|
||||
#!/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()
|
545
rootfs/opt/py-kms/pykms_Server.py
Normal file
545
rootfs/opt/py-kms/pykms_Server.py
Normal file
@@ -0,0 +1,545 @@
|
||||
#!/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()
|
142
rootfs/opt/py-kms/pykms_Sql.py
Normal file
142
rootfs/opt/py-kms/pykms_Sql.py
Normal file
@@ -0,0 +1,142 @@
|
||||
#!/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()
|
@@ -1,10 +1,16 @@
|
||||
#!/bin/ash
|
||||
if [ -z "${1}" ]; then
|
||||
|
||||
if [ ! -z "${DEBUG}" ]; then
|
||||
KMS_LOGLEVEL="DEBUG"
|
||||
eleven log debug "setting kms log level to DEBUG"
|
||||
fi
|
||||
|
||||
cd /opt/py-kms
|
||||
set -- "python3" \
|
||||
pykms_Server.py \
|
||||
${KMS_IP} \
|
||||
${KMS_PORT} \
|
||||
0.0.0.0 \
|
||||
1688 \
|
||||
-l ${KMS_LOCALE} \
|
||||
-c ${KMS_CLIENTCOUNT} \
|
||||
-a ${KMS_ACTIVATIONINTERVAL} \
|
||||
@@ -12,7 +18,7 @@
|
||||
-s /kms/var/kms.db \
|
||||
-w RANDOM \
|
||||
-V ${KMS_LOGLEVEL} \
|
||||
-F /var/log/kms.log \
|
||||
-F STDOUT \
|
||||
-y
|
||||
|
||||
eleven log start
|
||||
|
@@ -1,2 +0,0 @@
|
||||
#!/bin/ash
|
||||
netstat -an | grep -q ${KMS_PORT}
|
Reference in New Issue
Block a user