mirror of https://github.com/SystemRage/py-kms.git
commit
314cefb773
|
@ -1,16 +1,13 @@
|
||||||
name: Build Image On Release
|
name: Build release-tags
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bake:
|
bake-latest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
|
@ -24,11 +21,6 @@ jobs:
|
||||||
platforms: all
|
platforms: all
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1.6.0
|
uses: docker/setup-buildx-action@v1.6.0
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1.10.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v1.10.0
|
uses: docker/login-action@v1.10.0
|
||||||
with:
|
with:
|
||||||
|
@ -42,7 +34,10 @@ jobs:
|
||||||
file: ./docker/docker-py3-kms/Dockerfile
|
file: ./docker/docker-py3-kms/Dockerfile
|
||||||
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||||
push: true
|
push: true
|
||||||
tags: pykmsorg/py-kms:python3,ghcr.io/py-kms-organization/py-kms:python3
|
tags: ghcr.io/py-kms-organization/py-kms:python3
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
|
@ -50,4 +45,7 @@ jobs:
|
||||||
file: ./docker/docker-py3-kms-minimal/Dockerfile
|
file: ./docker/docker-py3-kms-minimal/Dockerfile
|
||||||
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||||
push: true
|
push: true
|
||||||
tags: pykmsorg/py-kms:latest,ghcr.io/py-kms-organization/py-kms:latest,pykmsorg/py-kms:minimal,ghcr.io/py-kms-organization/py-kms:minimal
|
tags: ghcr.io/py-kms-organization/py-kms:latest,ghcr.io/py-kms-organization/py-kms:minimal
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: Build next-tags
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- next
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bake-next:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2.3.4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1.6.0
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v1.10.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/docker-py3-kms/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||||
|
push: true
|
||||||
|
tags: ghcr.io/py-kms-organization/py-kms:python3-next
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/docker-py3-kms-minimal/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||||
|
push: true
|
||||||
|
tags: ghcr.io/py-kms-organization/py-kms:latest-next,ghcr.io/py-kms-organization/py-kms:minimal-next
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
|
@ -0,0 +1,38 @@
|
||||||
|
name: Test-Build Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bake-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2.3.4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1.6.0
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/docker-py3-kms/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||||
|
push: false
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/docker-py3-kms-minimal/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||||
|
push: false
|
||||||
|
build-args: |
|
||||||
|
BUILD_COMMIT=${{ github.sha }}
|
||||||
|
BUILD_BRANCH=${{ github.ref_name }}
|
|
@ -2,7 +2,6 @@
|
||||||
pykms_logserver.log*
|
pykms_logserver.log*
|
||||||
pykms_logclient.log*
|
pykms_logclient.log*
|
||||||
pykms_database.db*
|
pykms_database.db*
|
||||||
etrigan.log*
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
44
CHANGELOG.md
44
CHANGELOG.md
|
@ -1,5 +1,49 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### py-kms_2022-12-16
|
||||||
|
- Added support for new web-gui into Docker
|
||||||
|
- Implemented whole-new web-based GUI with Flask
|
||||||
|
- Removed old GUI (Etrigan) from code and resources
|
||||||
|
- Removed sqliteweb
|
||||||
|
- Removed Etrigan (GUI)
|
||||||
|
|
||||||
|
### py-kms_2022-12-07
|
||||||
|
- Added warning about Etrigan (GUI) being deprecated
|
||||||
|
- More docs (do not run on same machine as client)
|
||||||
|
- Added Docker support for multiple listen IPs
|
||||||
|
- Added graceful Docker shutdowns
|
||||||
|
|
||||||
|
### py-kms_2021-12-23
|
||||||
|
- More Windows 10/11 keys
|
||||||
|
- Fixed some deprecation warnings
|
||||||
|
- Fixed SO_REUSEPORT platform checks
|
||||||
|
- Fixed loglevel "MININFO" with Docker
|
||||||
|
- Added Docker healthcheck
|
||||||
|
- Added UID/GID change support for Docker
|
||||||
|
- Dependabot alerts
|
||||||
|
|
||||||
|
### py-kms_2021-10-22
|
||||||
|
- Integrated Office 2021 GLVK keys & database
|
||||||
|
- Docker entrypoint fixes
|
||||||
|
- Updated docs to include SQLite stuff
|
||||||
|
- Fix for undefined timezones
|
||||||
|
- Removed LOGFILE extension checks
|
||||||
|
- Added support for Windows 11
|
||||||
|
|
||||||
|
### py-kms_2021-10-07
|
||||||
|
- Helm charts for Kubernetes deployment
|
||||||
|
- Windows 2022 updates
|
||||||
|
- Faster Github Action builds
|
||||||
|
|
||||||
|
### py-kms_2021-11-12
|
||||||
|
- Addded GHCR support
|
||||||
|
- Docs table reformatted
|
||||||
|
- Updated GUI
|
||||||
|
- Windows Sandbox fix
|
||||||
|
- Added contribution guidelines
|
||||||
|
- Docker multiarch
|
||||||
|
- Reshot screenshots in docs
|
||||||
|
|
||||||
### py-kms_2020-10-01
|
### py-kms_2020-10-01
|
||||||
- Sql database path customizable.
|
- Sql database path customizable.
|
||||||
- Sql database file keeps different AppId.
|
- Sql database file keeps different AppId.
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Matteo ℱan
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -2,7 +2,6 @@
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|
|
||||||

|

|
||||||
***
|
***
|
||||||
|
|
||||||
|
@ -42,10 +41,9 @@ This version of _py-kms_ is for itself a fork of the original implementation by
|
||||||
The wiki has been completly reworked and is now available on [readthedocs.com](https://py-kms.readthedocs.io/en/latest/). It should you provide all necessary information how to setup and to use _py-kms_ , all without clumping this readme. The documentation also houses more details about activation with _py-kms_ and how to get GVLK keys.
|
The wiki has been completly reworked and is now available on [readthedocs.com](https://py-kms.readthedocs.io/en/latest/). It should you provide all necessary information how to setup and to use _py-kms_ , all without clumping this readme. The documentation also houses more details about activation with _py-kms_ and how to get GVLK keys.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
- To start the server, execute `python3 pykms_Server.py [IPADDRESS] [PORT]`, the default _IPADDRESS_ is `0.0.0.0` ( all interfaces ) and the default _PORT_ is `1688`. Note that both the address and port are optional. It's allowed to use IPv4 and IPv6 addresses. If you have a IPv6-capable dual-stack OS, a dual-stack socket is created when using a IPv6 address.
|
- To start the server, execute `python3 pykms_Server.py [IPADDRESS] [PORT]`, the default _IPADDRESS_ is `::` ( all interfaces ) and the default _PORT_ is `1688`. Note that both the address and port are optional. It's allowed to use IPv4 and IPv6 addresses. If you have a IPv6-capable dual-stack OS, a dual-stack socket is created when using a IPv6 address. **In case your OS does not support IPv6, make sure to explicitly specify the legacy IPv4 of `0.0.0.0`!**
|
||||||
- To start the server automatically using Docker, execute `docker run -d --name py-kms --restart always -p 1688:1688 ghcr.io/py-kms-organization/py-kms`.
|
- To start the server automatically using Docker, execute `docker run -d --name py-kms --restart always -p 1688:1688 ghcr.io/py-kms-organization/py-kms`.
|
||||||
- To show the help pages type: `python3 pykms_Server.py -h` and `python3 pykms_Client.py -h`.
|
- To show the help pages type: `python3 pykms_Server.py -h` and `python3 pykms_Client.py -h`.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
- _py-kms_ is [](https://github.com/SystemRage/py-kms/blob/master/LICENSE)
|
- _py-kms_ is [](https://github.com/SystemRage/py-kms/blob/master/LICENSE)
|
||||||
- _py-kms GUI_ is [](https://github.com/SystemRage/py-kms/blob/master/LICENSE.gui.md) © Matteo ℱan
|
|
|
@ -29,7 +29,7 @@ For more information please refer to the Helm Install command documentation loca
|
||||||
| autoscaling.targetCPUUtilizationPercentage | int | `80` | |
|
| autoscaling.targetCPUUtilizationPercentage | int | `80` | |
|
||||||
| fullnameOverride | string | `""` | |
|
| fullnameOverride | string | `""` | |
|
||||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||||
| image.repository | string | `"pykmsorg/py-kms"` | |
|
| image.repository | string | `"ghcr.io/py-kms-organization/py-kms"` | |
|
||||||
| image.tag | string | `"python3"` | |
|
| image.tag | string | `"python3"` | |
|
||||||
| imagePullSecrets | list | `[]` | |
|
| imagePullSecrets | list | `[]` | |
|
||||||
| ingress.annotations | object | `{}` | |
|
| ingress.annotations | object | `{}` | |
|
||||||
|
@ -44,10 +44,9 @@ For more information please refer to the Helm Install command documentation loca
|
||||||
| podAnnotations | object | `{}` | |
|
| podAnnotations | object | `{}` | |
|
||||||
| podSecurityContext | object | `{}` | |
|
| podSecurityContext | object | `{}` | |
|
||||||
| py-kms.environment.HWID | string | `"RANDOM"` | |
|
| py-kms.environment.HWID | string | `"RANDOM"` | |
|
||||||
| py-kms.environment.IP | string | `"0.0.0.0"` | |
|
| py-kms.environment.IP | string | `"::"` | |
|
||||||
| py-kms.environment.LOGLEVEL | string | `"INFO"` | |
|
| py-kms.environment.LOGLEVEL | string | `"INFO"` | |
|
||||||
| py-kms.environment.LOGSIZE | int | `2` | |
|
| py-kms.environment.LOGSIZE | int | `2` | |
|
||||||
| py-kms.environment.SQLITE | bool | `true` | |
|
|
||||||
| replicaCount | int | `1` | |
|
| replicaCount | int | `1` | |
|
||||||
| resources | object | `{}` | |
|
| resources | object | `{}` | |
|
||||||
| securityContext | object | `{}` | |
|
| securityContext | object | `{}` | |
|
||||||
|
|
|
@ -45,14 +45,17 @@ spec:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
port: http
|
||||||
|
path: /readyz
|
||||||
|
failureThreshold: 30 # 30 seconds seem to be enough under heavy workloads
|
||||||
|
periodSeconds: 1
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /livez
|
||||||
port: http
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: http
|
port: http
|
||||||
|
periodSeconds: 20
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
{{- with .Values.nodeSelector }}
|
{{- with .Values.nodeSelector }}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
image:
|
image:
|
||||||
repository: pykmsorg/py-kms
|
repository: ghcr.io/py-kms-organization/py-kms
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
# Overrides the image tag whose default is the chart appVersion.
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
tag: python3
|
tag: python3
|
||||||
|
@ -20,8 +20,7 @@ py-kms:
|
||||||
LOGSIZE: 2
|
LOGSIZE: 2
|
||||||
LOGFILE: /var/log/py-kms.log
|
LOGFILE: /var/log/py-kms.log
|
||||||
HWID: RANDOM
|
HWID: RANDOM
|
||||||
SQLITE: true
|
IP: '::'
|
||||||
IP: 0.0.0.0
|
|
||||||
|
|
||||||
serviceAccount: {}
|
serviceAccount: {}
|
||||||
# # Specifies whether a service account should be created
|
# # Specifies whether a service account should be created
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size
|
# This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size
|
||||||
FROM alpine:3.15
|
FROM alpine:3.15
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
ENV IP ::
|
||||||
ENV PORT 1688
|
ENV PORT 1688
|
||||||
ENV EPID ""
|
ENV EPID ""
|
||||||
ENV LCID 1033
|
ENV LCID 1033
|
||||||
|
@ -12,15 +12,12 @@ ENV HWID RANDOM
|
||||||
ENV LOGLEVEL INFO
|
ENV LOGLEVEL INFO
|
||||||
ENV LOGFILE STDOUT
|
ENV LOGFILE STDOUT
|
||||||
ENV LOGSIZE ""
|
ENV LOGSIZE ""
|
||||||
ENV TYPE MINIMAL
|
|
||||||
|
|
||||||
COPY ./py-kms /home/py-kms
|
COPY docker/docker-py3-kms-minimal/requirements.txt /home/py-kms/requirements.txt
|
||||||
COPY docker/requirements_minimal.txt /home/py-kms/requirements.txt
|
|
||||||
RUN apk add --no-cache --update \
|
RUN apk add --no-cache --update \
|
||||||
bash \
|
bash \
|
||||||
python3 \
|
python3 \
|
||||||
py3-pip \
|
py3-pip \
|
||||||
python3-tkinter \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
shadow \
|
shadow \
|
||||||
tzdata \
|
tzdata \
|
||||||
|
@ -31,6 +28,7 @@ bash \
|
||||||
# Fix undefined timezone, in case the user did not mount the /etc/localtime
|
# Fix undefined timezone, in case the user did not mount the /etc/localtime
|
||||||
&& ln -sf /usr/share/zoneinfo/UTC /etc/localtime
|
&& ln -sf /usr/share/zoneinfo/UTC /etc/localtime
|
||||||
|
|
||||||
|
COPY ./py-kms /home/py-kms
|
||||||
COPY docker/entrypoint.py /usr/bin/entrypoint.py
|
COPY docker/entrypoint.py /usr/bin/entrypoint.py
|
||||||
COPY docker/start.py /usr/bin/start.py
|
COPY docker/start.py /usr/bin/start.py
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
dnspython==2.2.1
|
||||||
|
tzlocal==4.2
|
|
@ -1,28 +1,27 @@
|
||||||
# Switch to the target image
|
# Switch to the target image
|
||||||
FROM alpine:3.15
|
FROM alpine:3.15
|
||||||
|
|
||||||
ENV IP 0.0.0.0
|
ARG BUILD_COMMIT=unknown
|
||||||
|
ARG BUILD_BRANCH=unknown
|
||||||
|
|
||||||
|
ENV IP ::
|
||||||
ENV PORT 1688
|
ENV PORT 1688
|
||||||
ENV EPID ""
|
ENV EPID ""
|
||||||
ENV LCID 1033
|
ENV LCID 1033
|
||||||
ENV CLIENT_COUNT 26
|
ENV CLIENT_COUNT 26
|
||||||
ENV ACTIVATION_INTERVAL 120
|
ENV ACTIVATION_INTERVAL 120
|
||||||
ENV RENEWAL_INTERVAL 10080
|
ENV RENEWAL_INTERVAL 10080
|
||||||
ENV SQLITE true
|
|
||||||
ENV SQLITE_PORT 8080
|
|
||||||
ENV HWID RANDOM
|
ENV HWID RANDOM
|
||||||
ENV LOGLEVEL INFO
|
ENV LOGLEVEL INFO
|
||||||
ENV LOGFILE STDOUT
|
ENV LOGFILE STDOUT
|
||||||
ENV LOGSIZE ""
|
ENV LOGSIZE ""
|
||||||
ENV TZ America/Chicago
|
ENV TZ America/Chicago
|
||||||
|
|
||||||
COPY py-kms /home/py-kms/
|
COPY docker/docker-py3-kms/requirements.txt /home/py-kms/
|
||||||
COPY docker/requirements.txt /home/py-kms/
|
|
||||||
RUN apk add --no-cache --update \
|
RUN apk add --no-cache --update \
|
||||||
bash \
|
bash \
|
||||||
python3 \
|
python3 \
|
||||||
py3-pip \
|
py3-pip \
|
||||||
python3-tkinter \
|
|
||||||
sqlite-libs \
|
sqlite-libs \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
|
@ -36,15 +35,20 @@ RUN apk add --no-cache --update \
|
||||||
# Fix undefined timezone, in case the user did not mount the /etc/localtime
|
# Fix undefined timezone, in case the user did not mount the /etc/localtime
|
||||||
&& ln -sf /usr/share/zoneinfo/UTC /etc/localtime
|
&& ln -sf /usr/share/zoneinfo/UTC /etc/localtime
|
||||||
|
|
||||||
|
COPY py-kms /home/py-kms/
|
||||||
COPY docker/entrypoint.py /usr/bin/entrypoint.py
|
COPY docker/entrypoint.py /usr/bin/entrypoint.py
|
||||||
COPY docker/start.py /usr/bin/start.py
|
COPY docker/start.py /usr/bin/start.py
|
||||||
|
|
||||||
|
# Web-interface specifics
|
||||||
|
COPY LICENSE /LICENSE
|
||||||
|
RUN echo "$BUILD_COMMIT" > /VERSION && echo "$BUILD_BRANCH" >> /VERSION
|
||||||
|
|
||||||
RUN chmod 755 /usr/bin/entrypoint.py
|
RUN chmod 755 /usr/bin/entrypoint.py
|
||||||
|
|
||||||
WORKDIR /home/py-kms
|
WORKDIR /home/py-kms
|
||||||
|
|
||||||
EXPOSE ${PORT}/tcp
|
EXPOSE ${PORT}/tcp
|
||||||
EXPOSE 8080
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
HEALTHCHECK --interval=5m --timeout=3s --start-period=10s --retries=4 CMD echo | nc -z ${IP%% *} ${PORT} || exit 1
|
HEALTHCHECK --interval=5m --timeout=3s --start-period=10s --retries=4 CMD echo | nc -z ${IP%% *} ${PORT} || exit 1
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Flask==2.1.2
|
|
||||||
Pygments==2.12.0
|
|
||||||
dnspython==2.2.1
|
dnspython==2.2.1
|
||||||
tzlocal==4.2
|
tzlocal==4.2
|
||||||
sqlite-web==0.4.0
|
|
||||||
|
Flask==2.1.2
|
||||||
|
gunicorn==20.1.0
|
|
@ -1,4 +0,0 @@
|
||||||
Flask==2.1.2
|
|
||||||
Pygments==2.12.0
|
|
||||||
dnspython==2.2.1
|
|
||||||
tzlocal==4.2
|
|
|
@ -20,35 +20,20 @@ argumentVariableMapping = {
|
||||||
'-e': 'EPID'
|
'-e': 'EPID'
|
||||||
}
|
}
|
||||||
|
|
||||||
sqliteWebPath = '/home/sqlite_web/sqlite_web.py'
|
db_path = os.path.join(os.sep, 'home', 'py-kms', 'db', 'pykms_database.db')
|
||||||
enableSQLITE = os.environ.get('SQLITE', 'false').lower() == 'true' and os.environ.get('TYPE') != 'MINIMAL'
|
|
||||||
dbPath = os.path.join(os.sep, 'home', 'py-kms', 'db', 'pykms_database.db')
|
|
||||||
log_level_bootstrap = log_level = os.environ.get('LOGLEVEL', 'INFO')
|
log_level_bootstrap = log_level = os.environ.get('LOGLEVEL', 'INFO')
|
||||||
if log_level_bootstrap == "MININFO":
|
if log_level_bootstrap == "MININFO":
|
||||||
log_level_bootstrap = "INFO"
|
log_level_bootstrap = "INFO"
|
||||||
log_file = os.environ.get('LOGFILE', 'STDOUT')
|
log_file = os.environ.get('LOGFILE', 'STDOUT')
|
||||||
listen_ip = os.environ.get('IP', '0.0.0.0').split()
|
listen_ip = os.environ.get('IP', '::').split()
|
||||||
listen_port = os.environ.get('PORT', '1688')
|
listen_port = os.environ.get('PORT', '1688')
|
||||||
sqlite_port = os.environ.get('SQLITE_PORT', '8080')
|
want_webui = os.environ.get('WEBUI', '0')
|
||||||
|
|
||||||
|
|
||||||
def start_kms_client():
|
|
||||||
if not os.path.isfile(dbPath):
|
|
||||||
# Start a dummy activation to ensure the database file is created
|
|
||||||
client_cmd = [PYTHON3, '-u', 'pykms_Client.py', listen_ip[0], listen_port,
|
|
||||||
'-m', 'Windows10', '-n', 'DummyClient', '-c', 'ae3a27d1-b73a-4734-9878-70c949815218',
|
|
||||||
'-V', log_level, '-F', log_file]
|
|
||||||
if os.environ.get('LOGSIZE', '') != "":
|
|
||||||
client_cmd.append('-S')
|
|
||||||
client_cmd.append(os.environ.get('LOGSIZE'))
|
|
||||||
loggersrv.info("Starting a dummy activation to ensure the database file is created")
|
|
||||||
loggersrv.debug("client_cmd: %s" % (" ".join(str(x) for x in client_cmd).strip()))
|
|
||||||
|
|
||||||
subprocess.run(client_cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def start_kms():
|
def start_kms():
|
||||||
sqlite_process = None
|
# Make sure the full path to the db exists
|
||||||
|
if want_webui and not os.path.exists(os.path.dirname(db_path)):
|
||||||
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||||
|
|
||||||
# Build the command to execute
|
# Build the command to execute
|
||||||
command = [PYTHON3, '-u', 'pykms_Server.py', listen_ip[0], listen_port]
|
command = [PYTHON3, '-u', 'pykms_Server.py', listen_ip[0], listen_port]
|
||||||
for (arg, env) in argumentVariableMapping.items():
|
for (arg, env) in argumentVariableMapping.items():
|
||||||
|
@ -60,25 +45,25 @@ def start_kms():
|
||||||
for i in range(1, len(listen_ip)):
|
for i in range(1, len(listen_ip)):
|
||||||
command.append("-n")
|
command.append("-n")
|
||||||
command.append(listen_ip[i] + "," + listen_port)
|
command.append(listen_ip[i] + "," + listen_port)
|
||||||
|
if want_webui:
|
||||||
if enableSQLITE:
|
|
||||||
loggersrv.info("Storing database file to %s" % dbPath)
|
|
||||||
command.append('-s')
|
command.append('-s')
|
||||||
command.append(dbPath)
|
command.append(db_path)
|
||||||
os.makedirs(os.path.dirname(dbPath), exist_ok=True)
|
|
||||||
|
|
||||||
loggersrv.debug("server_cmd: %s" % (" ".join(str(x) for x in command).strip()))
|
loggersrv.debug("server_cmd: %s" % (" ".join(str(x) for x in command).strip()))
|
||||||
pykms_process = subprocess.Popen(command)
|
pykms_process = subprocess.Popen(command)
|
||||||
|
pykms_webui_process = None
|
||||||
|
|
||||||
# In case SQLITE is defined: Start the web interface
|
try:
|
||||||
if enableSQLITE:
|
if want_webui:
|
||||||
time.sleep(5) # The server may take a while to start
|
time.sleep(2) # Wait for the server to start up
|
||||||
start_kms_client()
|
pykms_webui_env = os.environ.copy()
|
||||||
sqlite_cmd = ['sqlite_web', '-H', listen_ip[0], '--read-only', '-x',
|
pykms_webui_env['PYKMS_SQLITE_DB_PATH'] = db_path
|
||||||
dbPath, '-p', sqlite_port]
|
pykms_webui_env['PORT'] = '8080'
|
||||||
|
pykms_webui_env['PYKMS_LICENSE_PATH'] = '/LICENSE'
|
||||||
loggersrv.debug("sqlite_cmd: %s" % (" ".join(str(x) for x in sqlite_cmd).strip()))
|
pykms_webui_env['PYKMS_VERSION_PATH'] = '/VERSION'
|
||||||
sqlite_process = subprocess.Popen(sqlite_cmd)
|
pykms_webui_process = subprocess.Popen(['gunicorn', '--log-level', os.environ.get('LOGLEVEL'), 'pykms_WebUI:app'], env=pykms_webui_env)
|
||||||
|
except Exception as e:
|
||||||
|
loggersrv.error("Failed to start webui: %s" % e)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pykms_process.wait()
|
pykms_process.wait()
|
||||||
|
@ -88,9 +73,9 @@ def start_kms():
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if enableSQLITE:
|
if pykms_webui_process:
|
||||||
if None != sqlite_process: sqlite_process.terminate()
|
pykms_webui_process.terminate()
|
||||||
pykms_process.terminate()
|
pykms_process.terminate()
|
||||||
|
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
|
|
|
@ -6,23 +6,23 @@ What follows are some guides how to start the `pykms_Server.py` script, which pr
|
||||||
You can simply manage a daemon that runs as a background process. This can be achieved by using any of the notes below or by writing your own solution.
|
You can simply manage a daemon that runs as a background process. This can be achieved by using any of the notes below or by writing your own solution.
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||

|
|
||||||

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

|
||||||
|
|
||||||
#### Architectures
|
#### Architectures
|
||||||
There are currently the following architectures available (if you need an other, feel free to open an issue):
|
There are currently the following architectures available (if you need an other, feel free to open an issue):
|
||||||
|
@ -46,8 +46,7 @@ services:
|
||||||
- 1688:1688
|
- 1688:1688
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
environment:
|
environment:
|
||||||
- IP=0.0.0.0
|
- IP='::'
|
||||||
- SQLITE=true
|
|
||||||
- HWID=RANDOM
|
- HWID=RANDOM
|
||||||
- LOGLEVEL=INFO
|
- LOGLEVEL=INFO
|
||||||
restart: always
|
restart: always
|
||||||
|
@ -62,11 +61,10 @@ Below is a little bit more extended run command, detailing all the different sup
|
||||||
docker run -it -d --name py3-kms \
|
docker run -it -d --name py3-kms \
|
||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-p 1688:1688 \
|
-p 1688:1688 \
|
||||||
-e SQLITE=true \
|
|
||||||
-v /etc/localtime:/etc/localtime:ro \
|
-v /etc/localtime:/etc/localtime:ro \
|
||||||
--restart unless-stopped ghcr.io/py-kms-organization/py-kms:[TAG]
|
--restart unless-stopped ghcr.io/py-kms-organization/py-kms:[TAG]
|
||||||
```
|
```
|
||||||
You can omit the `-e SQLITE=...` and `-p 8080:8080` option if you plan to use the `minimal` or `latest` image, which does not include the respective module support.
|
You can omit the `-p 8080:8080` option if you plan to use the `minimal` or `latest` image, which does not include the `sqlite` module support.
|
||||||
|
|
||||||
### Systemd
|
### Systemd
|
||||||
If you are running a Linux distro using `systemd`, create the file: `sudo nano /etc/systemd/system/py3-kms.service`, then add the following (change it where needed) and save:
|
If you are running a Linux distro using `systemd`, create the file: `sudo nano /etc/systemd/system/py3-kms.service`, then add the following (change it where needed) and save:
|
||||||
|
@ -82,7 +80,7 @@ Restart=always
|
||||||
RestartSec=1
|
RestartSec=1
|
||||||
KillMode=process
|
KillMode=process
|
||||||
User=root
|
User=root
|
||||||
ExecStart=/usr/bin/python3 </path/to/your/pykms/files/folder>/py-kms/pykms_Server.py 0.0.0.0 1688 -V DEBUG -F </path/to/your/log/files/folder>/pykms_logserver.log
|
ExecStart=/usr/bin/python3 </path/to/your/pykms/files/folder>/py-kms/pykms_Server.py :: 1688 -V DEBUG -F </path/to/your/log/files/folder>/pykms_logserver.log
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -91,10 +89,6 @@ Check syntax with `sudo systemd-analyze verify py3-kms.service`, correct file pe
|
||||||
start the daemon `sudo systemctl start py3-kms.service` and view its status `sudo systemctl status py3-kms.service`. Check if daemon is correctly running with `cat </path/to/your/log/files/folder>/pykms_logserver.log`. Finally a
|
start the daemon `sudo systemctl start py3-kms.service` and view its status `sudo systemctl status py3-kms.service`. Check if daemon is correctly running with `cat </path/to/your/log/files/folder>/pykms_logserver.log`. Finally a
|
||||||
few generic commands useful for interact with your daemon [here](https://linoxide.com/linux-how-to/enable-disable-services-ubuntu-systemd-upstart/).
|
few generic commands useful for interact with your daemon [here](https://linoxide.com/linux-how-to/enable-disable-services-ubuntu-systemd-upstart/).
|
||||||
|
|
||||||
### Etrigan (deprecated)
|
|
||||||
You can run py-kms daemonized (via [Etrigan](https://github.com/SystemRage/Etrigan)) using a command like `python3 pykms_Server.py etrigan start` and stop it with `python3 pykms_Server.py etrigan stop`. With Etrigan you have another
|
|
||||||
way to launch py-kms GUI (specially suitable if you're using a virtualenv), so `python3 pykms_Server.py etrigan start -g` and stop the GUI with `python3 pykms_Server.py etrigan stop` (or interact with the `EXIT` button).
|
|
||||||
|
|
||||||
### Upstart (deprecated)
|
### Upstart (deprecated)
|
||||||
If you are running a Linux distro using `upstart` (deprecated), create the file: `sudo nano /etc/init/py3-kms.conf`, then add the following (change it where needed) and save:
|
If you are running a Linux distro using `upstart` (deprecated), create the file: `sudo nano /etc/init/py3-kms.conf`, then add the following (change it where needed) and save:
|
||||||
```
|
```
|
||||||
|
@ -105,7 +99,7 @@ env PYKMSPATH=</path/to/your/pykms/files/folder>/py-kms
|
||||||
env LOGPATH=</path/to/your/log/files/folder>/pykms_logserver.log
|
env LOGPATH=</path/to/your/log/files/folder>/pykms_logserver.log
|
||||||
start on runlevel [2345]
|
start on runlevel [2345]
|
||||||
stop on runlevel [016]
|
stop on runlevel [016]
|
||||||
exec $PYTHONPATH/python3 $PYKMSPATH/pykms_Server.py 0.0.0.0 1688 -V DEBUG -F $LOGPATH
|
exec $PYTHONPATH/python3 $PYKMSPATH/pykms_Server.py :: 1688 -V DEBUG -F $LOGPATH
|
||||||
respawn
|
respawn
|
||||||
```
|
```
|
||||||
Check syntax with `sudo init-checkconf -d /etc/init/py3-kms.conf`, then reload upstart to recognise this process `sudo initctl reload-configuration`. Now start the service `sudo start py3-kms`, and you can see the logfile
|
Check syntax with `sudo init-checkconf -d /etc/init/py3-kms.conf`, then reload upstart to recognise this process `sudo initctl reload-configuration`. Now start the service `sudo start py3-kms`, and you can see the logfile
|
||||||
|
@ -125,7 +119,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
|
||||||
_svc_name_ = "py-kms"
|
_svc_name_ = "py-kms"
|
||||||
_svc_display_name_ = "py-kms"
|
_svc_display_name_ = "py-kms"
|
||||||
_proc = None
|
_proc = None
|
||||||
_cmd = ["C:\Windows\Python27\python.exe", "C:\Windows\Python27\py-kms\pykms_Server.py"]
|
_cmd = ["C:\Windows\Python27\python.exe", "C:\Windows\Python27\py-kms\pykms_Server.py"] # UPDATE THIS - because Python 2.7 is end of life and you will use other parameters anyway
|
||||||
|
|
||||||
def __init__(self,args):
|
def __init__(self,args):
|
||||||
win32serviceutil.ServiceFramework.__init__(self,args)
|
win32serviceutil.ServiceFramework.__init__(self,args)
|
||||||
|
@ -168,10 +162,10 @@ They might be useful to you:
|
||||||
- Python 3.x.
|
- Python 3.x.
|
||||||
- If the `tzlocal` module is installed, the "Request Time" in the verbose output will be converted into local time. Otherwise, it will be in UTC.
|
- If the `tzlocal` module is installed, the "Request Time" in the verbose output will be converted into local time. Otherwise, it will be in UTC.
|
||||||
- It can use the `sqlite3` module, storing activation data in a database so it can be recalled again.
|
- It can use the `sqlite3` module, storing activation data in a database so it can be recalled again.
|
||||||
- Installation example on Ubuntu / Mint:
|
- Installation example on Ubuntu / Mint (`requirements.txt` is from the sources):
|
||||||
- `sudo apt-get update`
|
- `sudo apt-get update`
|
||||||
- `sudo apt-get install python3-tk python3-pip`
|
- `sudo apt-get install python3-pip`
|
||||||
- `sudo pip3 install tzlocal pysqlite3` (on Ubuntu Server 22, you'll need `pysqlite3-binary` - see [this issue](https://github.com/Py-KMS-Organization/py-kms/issues/76))
|
- `pip3 install -r requirements.txt` (on Ubuntu Server 22, you'll need `pysqlite3-binary` - see [this issue](https://github.com/Py-KMS-Organization/py-kms/issues/76))
|
||||||
|
|
||||||
### Startup
|
### Startup
|
||||||
A Linux user with `ip addr` command can get his KMS IP (Windows users can try `ipconfig /all`).
|
A Linux user with `ip addr` command can get his KMS IP (Windows users can try `ipconfig /all`).
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
Follows a list of usable parameters:
|
Follows a list of usable parameters:
|
||||||
|
|
||||||
ip <IPADDRESS>
|
ip <IPADDRESS>
|
||||||
> Instructs py-kms to listen on _IPADDRESS_ (can be an hostname too). If this option is not specified, _IPADDRESS_ 0.0.0.0 is used.
|
> Instructs py-kms to listen on _IPADDRESS_ (can be an hostname too). If this option is not specified, _IPADDRESS_ `::` is used.
|
||||||
|
|
||||||
port <PORT>
|
port <PORT>
|
||||||
> Define TCP _PORT_ the KMS service is listening on. Default is 1688.
|
> Define TCP _PORT_ the KMS service is listening on. Default is 1688.
|
||||||
|
@ -53,7 +53,6 @@ e.g. because it could not reach the server. The default is 120 minutes (2 hours)
|
||||||
|
|
||||||
-s or --sqlite [<SQLFILE>]
|
-s or --sqlite [<SQLFILE>]
|
||||||
> Use this option to store request information from unique clients in an SQLite database. Deactivated by default.
|
> Use this option to store request information from unique clients in an SQLite database. Deactivated by default.
|
||||||
If enabled the default database file is _pykms_database.db_. You can also provide a specific location.
|
|
||||||
|
|
||||||
-t0 or --timeout-idle <TIMEOUTIDLE>
|
-t0 or --timeout-idle <TIMEOUTIDLE>
|
||||||
> Maximum inactivity time (in seconds) after which the connection with the client is closed.
|
> Maximum inactivity time (in seconds) after which the connection with the client is closed.
|
||||||
|
@ -75,7 +74,7 @@ user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py -V INFO
|
||||||
```
|
```
|
||||||
creates _pykms_logserver.log_ with these initial messages:
|
creates _pykms_logserver.log_ with these initial messages:
|
||||||
```
|
```
|
||||||
Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 0.0.0.0 on port 1688.
|
Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at :: on port 1688.
|
||||||
Mon, 12 Jun 2017 22:09:00 INFO HWID: 364F463A8863D35F
|
Mon, 12 Jun 2017 22:09:00 INFO HWID: 364F463A8863D35F
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -83,11 +82,11 @@ Mon, 12 Jun 2017 22:09:00 INFO HWID: 364F463A8863D35F
|
||||||
> Creates a _LOGFILE.log_ logging file. The default is named _pykms_logserver.log_.
|
> Creates a _LOGFILE.log_ logging file. The default is named _pykms_logserver.log_.
|
||||||
example:
|
example:
|
||||||
```
|
```
|
||||||
user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 8080 -F ~/path/to/folder/py-kms/newlogfile.log -V INFO -w RANDOM
|
user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 1688 -F ~/path/to/folder/py-kms/newlogfile.log -V INFO -w RANDOM
|
||||||
```
|
```
|
||||||
creates _newlogfile.log_ with these initial messages:
|
creates _newlogfile.log_ with these initial messages:
|
||||||
```
|
```
|
||||||
Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 192.168.1.102 on port 8080.
|
Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 192.168.1.102 on port 1688.
|
||||||
Mon, 12 Jun 2017 22:09:00 INFO HWID: 58C4F4E53AE14224
|
Mon, 12 Jun 2017 22:09:00 INFO HWID: 58C4F4E53AE14224
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -125,14 +124,14 @@ examples (with fictitious addresses and ports):
|
||||||
|
|
||||||
| command | address (main) | backlog (main) | reuse port (main) | address (listen) | backlog (listen) | reuse port (listen) | dualstack (main / listen) |
|
| command | address (main) | backlog (main) | reuse port (main) | address (listen) | backlog (listen) | reuse port (listen) | dualstack (main / listen) |
|
||||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
| `python3 pykms_Server.py connect -b 12` | ('0.0.0.0', 1688) | 12 | True | [] | [] | [] | False |
|
| `python3 pykms_Server.py connect -b 12` | ('::', 1688) | 12 | True | [] | [] | [] | False |
|
||||||
| `python3 pykms_Server.py :: connect -b 12 -u -d` | ('::', 1688) | 12 | False | [] | [] | [] | True |
|
| `python3 pykms_Server.py :: connect -b 12 -u -d` | ('::', 1688) | 12 | False | [] | [] | [] | True |
|
||||||
| `python3 pykms_Server.py connect -n 1.1.1.1,1699 -b 10` | ('0.0.0.0', 1688) | 5 | True | [('1.1.1.1', 1699)] | [10] | [True] | False |
|
| `python3 pykms_Server.py connect -n 1.1.1.1,1699 -b 10` | ('::', 1688) | 5 | True | [('1.1.1.1', 1699)] | [10] | [True] | False |
|
||||||
| `python3 pykms_Server.py :: 1655 connect -n 2001:db8:0:200::7,1699 -d -b 10 -n 2.2.2.2,1677 -u` | ('::', 1655) | 5 | True | [('2001:db8:0:200::7', 1699), ('2.2.2.2', 1677)] | [10, 5] | [True, False] | True |
|
| `python3 pykms_Server.py :: 1655 connect -n 2001:db8:0:200::7,1699 -d -b 10 -n 2.2.2.2,1677 -u` | ('::', 1655) | 5 | True | [('2001:db8:0:200::7', 1699), ('2.2.2.2', 1677)] | [10, 5] | [True, False] | True |
|
||||||
| `python3 pykms_Server.py connect -b 12 -u -n 1.1.1.1,1699 -b 10 -n 2.2.2.2,1677 -b 15` | ('0.0.0.0', 1688) | 12 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [10, 15] | [False, False] | False |
|
| `python3 pykms_Server.py connect -b 12 -u -n 1.1.1.1,1699 -b 10 -n 2.2.2.2,1677 -b 15` | ('::', 1688) | 12 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [10, 15] | [False, False] | False |
|
||||||
| `python3 pykms_Server.py connect -b 12 -n 1.1.1.1,1699 -u -n 2.2.2.2,1677` | ('0.0.0.0', 1688) | 12 | True | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [12, 12] | [False, True] | False |
|
| `python3 pykms_Server.py connect -b 12 -n 1.1.1.1,1699 -u -n 2.2.2.2,1677` | ('::', 1688) | 12 | True | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [12, 12] | [False, True] | False |
|
||||||
| `python3 pykms_Server.py connect -d -u -b 8 -n 1.1.1.1,1699 -n 2.2.2.2,1677 -b 12` | ('0.0.0.0', 1688) | 8 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [8, 12] | [False, False] | True |
|
| `python3 pykms_Server.py connect -d -u -b 8 -n 1.1.1.1,1699 -n 2.2.2.2,1677 -b 12` | ('::', 1688) | 8 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [8, 12] | [False, False] | True |
|
||||||
| `python3 pykms_Server.py connect -b 11 -u -n ::,1699 -n 2.2.2.2,1677` | ('0.0.0.0', 1688) | 11 | False | [('::', 1699), ('2.2.2.2', 1677)] | [11, 11] | [False, False] | False |
|
| `python3 pykms_Server.py connect -b 11 -u -n ::,1699 -n 2.2.2.2,1677` | ('::', 1688) | 11 | False | [('::', 1699), ('2.2.2.2', 1677)] | [11, 11] | [False, False] | False |
|
||||||
|
|
||||||
### pykms_Client.py
|
### pykms_Client.py
|
||||||
If _py-kms_ server doesn't works correctly, you can test it with the KMS client `pykms_Client.py`, running on the same machine where you started `pykms_Server.py`.
|
If _py-kms_ server doesn't works correctly, you can test it with the KMS client `pykms_Client.py`, running on the same machine where you started `pykms_Server.py`.
|
||||||
|
@ -202,8 +201,8 @@ You can enable same _pykms_Server.py_ suboptions of `-F`.
|
||||||
This are the currently used `ENV` statements from the Dockerfile(s). For further references what exactly the parameters mean, please see the start parameters for the [server](Usage.html#pykms-server-py).
|
This are the currently used `ENV` statements from the Dockerfile(s). For further references what exactly the parameters mean, please see the start parameters for the [server](Usage.html#pykms-server-py).
|
||||||
```
|
```
|
||||||
# IP-address
|
# IP-address
|
||||||
# The IP address to listen on. The default is "0.0.0.0" (all interfaces).
|
# The IP address to listen on. The default is "::" (all interfaces).
|
||||||
ENV IP 0.0.0.0
|
ENV IP ::
|
||||||
|
|
||||||
# TCP-port
|
# TCP-port
|
||||||
# The network port to listen on. The default is "1688".
|
# The network port to listen on. The default is "1688".
|
||||||
|
@ -230,14 +229,6 @@ ENV ACTIVATION_INTERVAL 120
|
||||||
# Use this flag to specify the renewal interval (in minutes). Default is 10080 minutes (7 days).
|
# Use this flag to specify the renewal interval (in minutes). Default is 10080 minutes (7 days).
|
||||||
ENV RENEWAL_INTERVAL 10080
|
ENV RENEWAL_INTERVAL 10080
|
||||||
|
|
||||||
# Use SQLITE
|
|
||||||
# Use this flag to store request information from unique clients in an SQLite database.
|
|
||||||
ENV SQLITE false
|
|
||||||
|
|
||||||
# TCP-port
|
|
||||||
# The network port to listen with the web interface on. The default is "8080".
|
|
||||||
ENV SQLITE_PORT 8080
|
|
||||||
|
|
||||||
# hwid
|
# hwid
|
||||||
# Use this flag to specify a HWID.
|
# Use this flag to specify a HWID.
|
||||||
# The HWID must be an 16-character string of hex characters.
|
# The HWID must be an 16-character string of hex characters.
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
|
@ -1,609 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import atexit
|
|
||||||
import errno
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import signal
|
|
||||||
import logging
|
|
||||||
import argparse
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
__license__ = "MIT License"
|
|
||||||
__author__ = u"Matteo ℱan <SystemRage@protonmail.com>"
|
|
||||||
__copyright__ = "© Copyright 2020"
|
|
||||||
__url__ = "https://github.com/SystemRage/Etrigan"
|
|
||||||
__description__ = "Etrigan: a python daemonizer that rocks."
|
|
||||||
|
|
||||||
|
|
||||||
class Etrigan(object):
|
|
||||||
"""
|
|
||||||
Daemonizer based on double-fork method
|
|
||||||
--------------------------------------
|
|
||||||
Each option can be passed as a keyword argument or modified by assigning
|
|
||||||
to an attribute on the instance:
|
|
||||||
|
|
||||||
jasonblood = Etrigan(pidfile,
|
|
||||||
argument_example_1 = foo,
|
|
||||||
argument_example_2 = bar)
|
|
||||||
|
|
||||||
that is equivalent to:
|
|
||||||
|
|
||||||
jasonblood = Etrigan(pidfile)
|
|
||||||
jasonblood.argument_example_1 = foo
|
|
||||||
jasonblood.argument_example_2 = bar
|
|
||||||
|
|
||||||
Object constructor expects always `pidfile` argument.
|
|
||||||
`pidfile`
|
|
||||||
Path to the pidfile.
|
|
||||||
|
|
||||||
The following other options are defined:
|
|
||||||
`stdin`
|
|
||||||
`stdout`
|
|
||||||
`stderr`
|
|
||||||
:Default: `os.devnull`
|
|
||||||
File objects used as the new file for the standard I/O streams
|
|
||||||
`sys.stdin`, `sys.stdout`, and `sys.stderr` respectively.
|
|
||||||
|
|
||||||
`funcs_to_daemonize`
|
|
||||||
:Default: `[]`
|
|
||||||
Define a list of your custom functions
|
|
||||||
which will be executed after daemonization.
|
|
||||||
If None, you have to subclass Etrigan `run` method.
|
|
||||||
Note that these functions can return elements that will be
|
|
||||||
added to Etrigan object (`etrigan_add` list) so the other subsequent
|
|
||||||
ones can reuse them for further processing.
|
|
||||||
You only have to provide indexes of `etrigan_add` list,
|
|
||||||
(an int (example: 2) for single index or a string (example: '1:4') for slices)
|
|
||||||
as first returning element.
|
|
||||||
|
|
||||||
`want_quit`
|
|
||||||
:Default: `False`
|
|
||||||
If `True`, runs Etrigan `quit_on_start` or `quit_on_stop`
|
|
||||||
lists of your custom functions at the end of `start` or `stop` operations.
|
|
||||||
These can return elements as `funcs_to_daemonize`.
|
|
||||||
|
|
||||||
`logfile`
|
|
||||||
:Default: `None`
|
|
||||||
Path to the output log file.
|
|
||||||
|
|
||||||
`loglevel`
|
|
||||||
:Default: `None`
|
|
||||||
Set the log level of logging messages.
|
|
||||||
|
|
||||||
`mute`
|
|
||||||
:Default: `False`
|
|
||||||
Disable all stdout and stderr messages (before double forking).
|
|
||||||
|
|
||||||
`pause_loop`
|
|
||||||
:Default: `None`
|
|
||||||
Seconds of pause between the calling, in an infinite loop,
|
|
||||||
of every function in `funcs_to_daemonize` list.
|
|
||||||
If `-1`, no pause between the calling, in an infinite loop,
|
|
||||||
of every function in `funcs_to_daemonize` list.
|
|
||||||
If `None`, only one run (no infinite loop) of functions in
|
|
||||||
`funcs_to_daemonize` list, without pause.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, pidfile,
|
|
||||||
stdin = os.devnull, stdout = os.devnull, stderr = os.devnull,
|
|
||||||
funcs_to_daemonize = [], want_quit = False,
|
|
||||||
logfile = None, loglevel = None,
|
|
||||||
mute = False, pause_loop = None):
|
|
||||||
|
|
||||||
self.pidfile = pidfile
|
|
||||||
self.funcs_to_daemonize = funcs_to_daemonize
|
|
||||||
self.stdin = stdin
|
|
||||||
self.stdout = stdout
|
|
||||||
self.stderr = stderr
|
|
||||||
self.logfile = logfile
|
|
||||||
self.loglevel = loglevel
|
|
||||||
self.mute = mute
|
|
||||||
self.want_quit = want_quit
|
|
||||||
self.pause_loop = pause_loop
|
|
||||||
# internal only.
|
|
||||||
self.homedir = '/'
|
|
||||||
self.umask = 0o22
|
|
||||||
self.etrigan_restart, self.etrigan_reload = (False for _ in range(2))
|
|
||||||
self.etrigan_alive = True
|
|
||||||
self.etrigan_add = []
|
|
||||||
self.etrigan_index = None
|
|
||||||
# seconds of pause between stop and start during the restart of the daemon.
|
|
||||||
self.pause_restart = 5
|
|
||||||
# when terminate a process, seconds to wait until kill the process with signal.
|
|
||||||
# self.pause_kill = 3
|
|
||||||
|
|
||||||
# create logfile.
|
|
||||||
self.setup_files()
|
|
||||||
|
|
||||||
def handle_terminate(self, signum, frame):
|
|
||||||
if os.path.exists(self.pidfile):
|
|
||||||
self.etrigan_alive = False
|
|
||||||
# eventually run quit (on stop) function/s.
|
|
||||||
if self.want_quit:
|
|
||||||
if not isinstance(self.quit_on_stop, (list, tuple)):
|
|
||||||
self.quit_on_stop = [self.quit_on_stop]
|
|
||||||
self.execute(self.quit_on_stop)
|
|
||||||
# then always run quit standard.
|
|
||||||
self.quit_standard()
|
|
||||||
else:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: can't find PIDFILE '%s'" %self.pidfile)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def handle_reload(self, signum, frame):
|
|
||||||
self.etrigan_reload = True
|
|
||||||
|
|
||||||
def setup_files(self):
|
|
||||||
self.pidfile = os.path.abspath(self.pidfile)
|
|
||||||
|
|
||||||
if self.logfile is not None:
|
|
||||||
self.logdaemon = logging.getLogger('logdaemon')
|
|
||||||
self.logdaemon.setLevel(self.loglevel)
|
|
||||||
|
|
||||||
filehandler = logging.FileHandler(self.logfile)
|
|
||||||
filehandler.setLevel(self.loglevel)
|
|
||||||
formatter = logging.Formatter(fmt = '[%(asctime)s] [%(levelname)8s] --- %(message)s',
|
|
||||||
datefmt = '%Y-%m-%d %H:%M:%S')
|
|
||||||
filehandler.setFormatter(formatter)
|
|
||||||
self.logdaemon.addHandler(filehandler)
|
|
||||||
else:
|
|
||||||
nullhandler = logging.NullHandler()
|
|
||||||
self.logdaemon.addHandler(nullhandler)
|
|
||||||
|
|
||||||
def emit_error(self, message, to_exit = True):
|
|
||||||
""" Print an error message to STDERR. """
|
|
||||||
if not self.mute:
|
|
||||||
sys.stderr.write(message + '\n')
|
|
||||||
sys.stderr.flush()
|
|
||||||
if to_exit:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def emit_message(self, message, to_exit = False):
|
|
||||||
""" Print a message to STDOUT. """
|
|
||||||
if not self.mute:
|
|
||||||
sys.stdout.write(message + '\n')
|
|
||||||
sys.stdout.flush()
|
|
||||||
if to_exit:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def view(self, logobj, emitobj, msg, **kwargs):
|
|
||||||
options = {'to_exit' : False,
|
|
||||||
'silent' : False
|
|
||||||
}
|
|
||||||
options.update(kwargs)
|
|
||||||
|
|
||||||
if logobj:
|
|
||||||
logobj(msg)
|
|
||||||
if emitobj:
|
|
||||||
if not options['silent']:
|
|
||||||
emitobj(msg, to_exit = options['to_exit'])
|
|
||||||
|
|
||||||
def daemonize(self):
|
|
||||||
"""
|
|
||||||
Double-forks the process to daemonize the script.
|
|
||||||
see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177)
|
|
||||||
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
|
||||||
"""
|
|
||||||
self.view(self.logdaemon.debug, None, "Attempting to daemonize the process...")
|
|
||||||
|
|
||||||
# First fork.
|
|
||||||
self.fork(msg = "First fork")
|
|
||||||
# Decouple from parent environment.
|
|
||||||
self.detach()
|
|
||||||
# Second fork.
|
|
||||||
self.fork(msg = "Second fork")
|
|
||||||
# Write the PID file.
|
|
||||||
self.create_pidfile()
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "The daemon process has started.")
|
|
||||||
# Redirect standard file descriptors.
|
|
||||||
sys.stdout.flush()
|
|
||||||
sys.stderr.flush()
|
|
||||||
self.attach('stdin', mode = 'r')
|
|
||||||
self.attach('stdout', mode = 'a+')
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.attach('stderr', mode = 'a+', buffering = 0)
|
|
||||||
except ValueError:
|
|
||||||
# Python 3 can't have unbuffered text I/O.
|
|
||||||
self.attach('stderr', mode = 'a+', buffering = 1)
|
|
||||||
|
|
||||||
# Handle signals.
|
|
||||||
signal.signal(signal.SIGINT, self.handle_terminate)
|
|
||||||
signal.signal(signal.SIGTERM, self.handle_terminate)
|
|
||||||
signal.signal(signal.SIGHUP, self.handle_reload)
|
|
||||||
#signal.signal(signal.SIGKILL....)
|
|
||||||
|
|
||||||
def fork(self, msg):
|
|
||||||
try:
|
|
||||||
pid = os.fork()
|
|
||||||
if pid > 0:
|
|
||||||
self.view(self.logdaemon.debug, None, msg + " success with PID %d." %pid)
|
|
||||||
# Exit from parent.
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception as e:
|
|
||||||
msg += " failed: %s." %str(e)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def detach(self):
|
|
||||||
# cd to root for a guarenteed working dir.
|
|
||||||
try:
|
|
||||||
os.chdir(self.homedir)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "Unable to change working directory: %s." %str(e)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
# clear the session id to clear the controlling tty.
|
|
||||||
pid = os.setsid()
|
|
||||||
if pid == -1:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# set the umask so we have access to all files created by the daemon.
|
|
||||||
try:
|
|
||||||
os.umask(self.umask)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "Unable to change file creation mask: %s." %str(e)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def attach(self, name, mode, buffering = -1):
|
|
||||||
with open(getattr(self, name), mode, buffering) as stream:
|
|
||||||
os.dup2(stream.fileno(), getattr(sys, name).fileno())
|
|
||||||
|
|
||||||
def checkfile(self, path, typearg, typefile):
|
|
||||||
filename = os.path.basename(path)
|
|
||||||
pathname = os.path.dirname(path)
|
|
||||||
if not os.path.isdir(pathname):
|
|
||||||
msg = "argument %s: invalid directory: '%s'. Exiting..." %(typearg, pathname)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
elif not filename.lower().endswith(typefile):
|
|
||||||
msg = "argument %s: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, filename)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def create_pidfile(self):
|
|
||||||
atexit.register(self.delete_pidfile)
|
|
||||||
pid = os.getpid()
|
|
||||||
try:
|
|
||||||
with open(self.pidfile, 'w+') as pf:
|
|
||||||
pf.write("%s\n" %pid)
|
|
||||||
self.view(self.logdaemon.debug, None, "PID %d written to '%s'." %(pid, self.pidfile))
|
|
||||||
except Exception as e:
|
|
||||||
msg = "Unable to write PID to PIDFILE '%s': %s" %(self.pidfile, str(e))
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def delete_pidfile(self, pid):
|
|
||||||
# Remove the PID file.
|
|
||||||
try:
|
|
||||||
os.remove(self.pidfile)
|
|
||||||
self.view(self.logdaemon.debug, None, "Removing PIDFILE '%s' with PID %d." %(self.pidfile, pid))
|
|
||||||
except Exception as e:
|
|
||||||
if e.errno != errno.ENOENT:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, str(e))
|
|
||||||
|
|
||||||
def get_pidfile(self):
|
|
||||||
# Get the PID from the PID file.
|
|
||||||
if self.pidfile is None:
|
|
||||||
return None
|
|
||||||
if not os.path.isfile(self.pidfile):
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(self.pidfile, 'r') as pf:
|
|
||||||
pid = int(pf.read().strip())
|
|
||||||
self.view(self.logdaemon.debug, None, "Found PID %d in PIDFILE '%s'" %(pid, self.pidfile))
|
|
||||||
except Exception as e:
|
|
||||||
self.view(self.logdaemon.warning, None, "Empty or broken PIDFILE")
|
|
||||||
pid = None
|
|
||||||
|
|
||||||
def pid_exists(pid):
|
|
||||||
# psutil _psposix.py.
|
|
||||||
if pid == 0:
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
os.kill(pid, 0)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == errno.ESRCH:
|
|
||||||
return False
|
|
||||||
elif e.errno == errno.EPERM:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, str(e))
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if pid is not None and pid_exists(pid):
|
|
||||||
return pid
|
|
||||||
else:
|
|
||||||
# Remove the stale PID file.
|
|
||||||
self.delete_pidfile(pid)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
""" Start the daemon. """
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "Starting the daemon process...", silent = self.etrigan_restart)
|
|
||||||
|
|
||||||
# Check for a PID file to see if the Daemon is already running.
|
|
||||||
pid = self.get_pidfile()
|
|
||||||
if pid is not None:
|
|
||||||
msg = "A previous daemon process with PIDFILE '%s' already exists. Daemon already running ?" %self.pidfile
|
|
||||||
self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Daemonize the main process.
|
|
||||||
self.daemonize()
|
|
||||||
# Start a infinitive loop that periodically runs `funcs_to_daemonize`.
|
|
||||||
self.loop()
|
|
||||||
# eventualy run quit (on start) function/s.
|
|
||||||
if self.want_quit:
|
|
||||||
if not isinstance(self.quit_on_start, (list, tuple)):
|
|
||||||
self.quit_on_start = [self.quit_on_start]
|
|
||||||
self.execute(self.quit_on_start)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
""" Stop the daemon. """
|
|
||||||
self.view(None, self.emit_message, "Stopping the daemon process...", silent = self.etrigan_restart)
|
|
||||||
|
|
||||||
self.logdaemon.disabled = True
|
|
||||||
pid = self.get_pidfile()
|
|
||||||
self.logdaemon.disabled = False
|
|
||||||
if not pid:
|
|
||||||
# Just to be sure. A ValueError might occur
|
|
||||||
# if the PIDFILE is empty but does actually exist.
|
|
||||||
if os.path.exists(self.pidfile):
|
|
||||||
self.delete_pidfile(pid)
|
|
||||||
|
|
||||||
msg = "Can't find the daemon process with PIDFILE '%s'. Daemon not running ?" %self.pidfile
|
|
||||||
self.view(self.logdaemon.warning, self.emit_error, msg, to_exit = False)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Try to kill the daemon process.
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
os.kill(pid, signal.SIGTERM)
|
|
||||||
time.sleep(0.1)
|
|
||||||
except Exception as e:
|
|
||||||
if (e.errno != errno.ESRCH):
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Failed to stop the daemon process: %s" %str(e))
|
|
||||||
else:
|
|
||||||
self.view(None, self.emit_message, "The daemon process has ended correctly.", silent = self.etrigan_restart)
|
|
||||||
|
|
||||||
def restart(self):
|
|
||||||
""" Restart the daemon. """
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "Restarting the daemon process...")
|
|
||||||
self.etrigan_restart = True
|
|
||||||
self.stop()
|
|
||||||
if self.pause_restart:
|
|
||||||
time.sleep(self.pause_restart)
|
|
||||||
self.etrigan_alive = True
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
def reload(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def status(self):
|
|
||||||
""" Get status of the daemon. """
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "Viewing the daemon process status...")
|
|
||||||
|
|
||||||
if self.pidfile is None:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Cannot get the status of daemon without PIDFILE.")
|
|
||||||
|
|
||||||
pid = self.get_pidfile()
|
|
||||||
if pid is None:
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "The daemon process is not running.", to_exit = True)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
with open("/proc/%d/status" %pid, 'r') as pf:
|
|
||||||
pass
|
|
||||||
self.view(self.logdaemon.info, self.emit_message, "The daemon process is running.", to_exit = True)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "There is not a process with the PIDFILE '%s': %s" %(self.pidfile, str(e))
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def flatten(self, alistoflists, ltypes = Sequence):
|
|
||||||
# https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists/2158532#2158532
|
|
||||||
alistoflists = list(alistoflists)
|
|
||||||
while alistoflists:
|
|
||||||
while alistoflists and isinstance(alistoflists[0], ltypes):
|
|
||||||
alistoflists[0:1] = alistoflists[0]
|
|
||||||
if alistoflists: yield alistoflists.pop(0)
|
|
||||||
|
|
||||||
def exclude(self, func):
|
|
||||||
from inspect import getargspec
|
|
||||||
args = getargspec(func)
|
|
||||||
if callable(func):
|
|
||||||
try:
|
|
||||||
args[0].pop(0)
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
return args
|
|
||||||
else:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Not a function.")
|
|
||||||
return
|
|
||||||
|
|
||||||
def execute(self, some_functions):
|
|
||||||
returned = None
|
|
||||||
if isinstance(some_functions, (list, tuple)):
|
|
||||||
for func in some_functions:
|
|
||||||
l_req = len(self.exclude(func)[0])
|
|
||||||
|
|
||||||
if l_req == 0:
|
|
||||||
returned = func()
|
|
||||||
else:
|
|
||||||
l_add = len(self.etrigan_add)
|
|
||||||
if l_req > l_add:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error,
|
|
||||||
"Can't evaluate function: given %s, required %s." %(l_add, l_req))
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
arguments = self.etrigan_add[self.etrigan_index]
|
|
||||||
l_args = (len(arguments) if isinstance(arguments, list) else 1)
|
|
||||||
if (l_args > l_req) or (l_args < l_req):
|
|
||||||
self.view(self.logdaemon.error, self.emit_error,
|
|
||||||
"Can't evaluate function: given %s, required %s." %(l_args, l_req))
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
if isinstance(arguments, list):
|
|
||||||
returned = func(*arguments)
|
|
||||||
else:
|
|
||||||
returned = func(arguments)
|
|
||||||
|
|
||||||
if returned:
|
|
||||||
if isinstance(returned, (list, tuple)):
|
|
||||||
if isinstance(returned[0], int):
|
|
||||||
self.etrigan_index = returned[0]
|
|
||||||
else:
|
|
||||||
self.etrigan_index = slice(*map(int, returned[0].split(':')))
|
|
||||||
if returned[1:] != []:
|
|
||||||
self.etrigan_add.append(returned[1:])
|
|
||||||
self.etrigan_add = list(self.flatten(self.etrigan_add))
|
|
||||||
else:
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, "Function should return list or tuple.")
|
|
||||||
returned = None
|
|
||||||
else:
|
|
||||||
if some_functions is None:
|
|
||||||
self.run()
|
|
||||||
|
|
||||||
def loop(self):
|
|
||||||
try:
|
|
||||||
if self.pause_loop is None:
|
|
||||||
# one-shot.
|
|
||||||
self.execute(self.funcs_to_daemonize)
|
|
||||||
else:
|
|
||||||
if self.pause_loop >= 0:
|
|
||||||
# infinite with pause.
|
|
||||||
time.sleep(self.pause_loop)
|
|
||||||
while self.etrigan_alive:
|
|
||||||
self.execute(self.funcs_to_daemonize)
|
|
||||||
time.sleep(self.pause_loop)
|
|
||||||
elif self.pause_loop == -1:
|
|
||||||
# infinite without pause.
|
|
||||||
while self.etrigan_alive:
|
|
||||||
self.execute(self.funcs_to_daemonize)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "The daemon process start method failed: %s" %str(e)
|
|
||||||
self.view(self.logdaemon.error, self.emit_error, msg)
|
|
||||||
|
|
||||||
def quit_standard(self):
|
|
||||||
self.view(self.logdaemon.info, None, "Stopping the daemon process...")
|
|
||||||
self.delete_pidfile(self.get_pidfile())
|
|
||||||
self.view(self.logdaemon.info, None, "The daemon process has ended correctly.")
|
|
||||||
|
|
||||||
def quit_on_start(self):
|
|
||||||
"""
|
|
||||||
Override this method when you subclass Daemon.
|
|
||||||
"""
|
|
||||||
self.quit_standard()
|
|
||||||
|
|
||||||
def quit_on_stop(self):
|
|
||||||
"""
|
|
||||||
Override this method when you subclass Daemon.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""
|
|
||||||
Override this method when you subclass Daemon.
|
|
||||||
It will be called after the process has been
|
|
||||||
daemonized by start() or restart().
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class JasonBlood(Etrigan):
|
|
||||||
def run(self):
|
|
||||||
jasonblood_func()
|
|
||||||
|
|
||||||
def jasonblood_func():
|
|
||||||
with open(os.path.join('.', 'etrigan_test.txt'), 'a') as file:
|
|
||||||
file.write("Yarva Demonicus Etrigan " + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) + '\n')
|
|
||||||
|
|
||||||
def Etrigan_parser(parser = None):
|
|
||||||
if parser is None:
|
|
||||||
# create a new parser.
|
|
||||||
parser = argparse.ArgumentParser(description = __description__, epilog = __version__)
|
|
||||||
if not parser.add_help:
|
|
||||||
# create help argument.
|
|
||||||
parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
|
||||||
|
|
||||||
# attach to an existent parser.
|
|
||||||
parser.add_argument("operation", action = "store", choices = ["start", "stop", "restart", "status", "reload"],
|
|
||||||
help = "Select an operation for daemon.", type = str)
|
|
||||||
parser.add_argument("--etrigan-pid",
|
|
||||||
action = "store", dest = "etriganpid", default = "/tmp/etrigan.pid",
|
|
||||||
help = "Choose a pidfile path. Default is \"/tmp/etrigan.pid\".", type = str) #'/var/run/etrigan.pid'
|
|
||||||
parser.add_argument("--etrigan-log",
|
|
||||||
action = "store", dest = "etriganlog", default = os.path.join('.', "etrigan.log"),
|
|
||||||
help = "Use this option to choose an output log file; for not logging don't select it. Default is \"etrigan.log\".", type = str)
|
|
||||||
parser.add_argument("--etrigan-lev",
|
|
||||||
action = "store", dest = "etriganlev", default = "DEBUG",
|
|
||||||
choices = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
|
|
||||||
help = "Use this option to set a log level. Default is \"DEBUG\".", type = str)
|
|
||||||
parser.add_argument("--etrigan-mute",
|
|
||||||
action = "store_const", dest = 'etriganmute', const = True, default = False,
|
|
||||||
help = "Disable all stdout and stderr messages.")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
class Etrigan_check(object):
|
|
||||||
def emit_opt_err(self, msg):
|
|
||||||
print(msg)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def checkfile(self, path, typearg, typefile):
|
|
||||||
filename, extension = os.path.splitext(path)
|
|
||||||
pathname = os.path.dirname(path)
|
|
||||||
if not os.path.isdir(pathname):
|
|
||||||
msg = "argument `%s`: invalid directory: '%s'. Exiting..." %(typearg, pathname)
|
|
||||||
self.emit_opt_err(msg)
|
|
||||||
elif not extension == typefile:
|
|
||||||
msg = "argument `%s`: not a %s file, invalid extension: '%s'. Exiting..." %(typearg, typefile, extension)
|
|
||||||
self.emit_opt_err(msg)
|
|
||||||
|
|
||||||
def checkfunction(self, funcs, booleans):
|
|
||||||
if not isinstance(funcs, (list, tuple)):
|
|
||||||
if funcs is not None:
|
|
||||||
msg = "argument `funcs_to_daemonize`: provide list, tuple or None"
|
|
||||||
self.emit_opt_err(msg)
|
|
||||||
|
|
||||||
for elem in booleans:
|
|
||||||
if not type(elem) == bool:
|
|
||||||
msg = "argument `want_quit`: not a boolean."
|
|
||||||
self.emit_opt_err(msg)
|
|
||||||
|
|
||||||
def Etrigan_job(type_oper, daemon_obj):
|
|
||||||
Etrigan_check().checkfunction(daemon_obj.funcs_to_daemonize,
|
|
||||||
[daemon_obj.want_quit])
|
|
||||||
if type_oper == "start":
|
|
||||||
daemon_obj.start()
|
|
||||||
elif type_oper == "stop":
|
|
||||||
daemon_obj.stop()
|
|
||||||
elif type_oper == "restart":
|
|
||||||
daemon_obj.restart()
|
|
||||||
elif type_oper == "status":
|
|
||||||
daemon_obj.status()
|
|
||||||
elif type_oper == "reload":
|
|
||||||
daemon_obj.reload()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Parse arguments.
|
|
||||||
parser = Etrigan_parser()
|
|
||||||
args = vars(parser.parse_args())
|
|
||||||
# Check arguments.
|
|
||||||
Etrigan_check().checkfile(args['etriganpid'], '--etrigan-pid', '.pid')
|
|
||||||
Etrigan_check().checkfile(args['etriganlog'], '--etrigan-log', '.log')
|
|
||||||
|
|
||||||
# Setup daemon.
|
|
||||||
jasonblood_1 = Etrigan(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'],
|
|
||||||
mute = args['etriganmute'],
|
|
||||||
funcs_to_daemonize = [jasonblood_func], pause_loop = 5)
|
|
||||||
|
|
||||||
## jasonblood_2 = JasonBlood(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'],
|
|
||||||
## mute = args['etriganmute'],
|
|
||||||
## funcs_to_daemonize = None, pause_loop = 5)
|
|
||||||
# Do job.
|
|
||||||
Etrigan_job(args['operation'], jasonblood_1)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
Binary file not shown.
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
Before Width: | Height: | Size: 524 KiB |
|
@ -4,13 +4,12 @@ import binascii
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
import socket
|
|
||||||
|
|
||||||
from pykms_Structure import Structure
|
from pykms_Structure import Structure
|
||||||
from pykms_DB2Dict import kmsDB2Dict
|
from pykms_DB2Dict import kmsDB2Dict
|
||||||
from pykms_PidGenerator import epidGenerator
|
from pykms_PidGenerator import epidGenerator
|
||||||
from pykms_Filetimes import filetime_to_dt
|
from pykms_Filetimes import filetime_to_dt
|
||||||
from pykms_Sql import sql_initialize, sql_update, sql_update_epid
|
from pykms_Sql import sql_update, sql_update_epid
|
||||||
from pykms_Format import justify, byterize, enco, deco, pretty_printer
|
from pykms_Format import justify, byterize, enco, deco, pretty_printer
|
||||||
|
|
||||||
#--------------------------------------------------------------------------------------------------------------------------------------------------------
|
#--------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -214,7 +213,6 @@ could be detected as not genuine !{end}" %currentClientCount)
|
||||||
'product' : infoDict["skuId"]})
|
'product' : infoDict["skuId"]})
|
||||||
# Create database.
|
# Create database.
|
||||||
if self.srv_config['sqlite']:
|
if self.srv_config['sqlite']:
|
||||||
sql_initialize(self.srv_config['sqlite'])
|
|
||||||
sql_update(self.srv_config['sqlite'], infoDict)
|
sql_update(self.srv_config['sqlite'], infoDict)
|
||||||
|
|
||||||
return self.createKmsResponse(kmsRequest, currentClientCount, appName)
|
return self.createKmsResponse(kmsRequest, currentClientCount, appName)
|
||||||
|
|
|
@ -45,10 +45,9 @@ class client_thread(threading.Thread):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.with_gui = False
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
clt_main(with_gui = self.with_gui)
|
clt_main()
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -56,7 +55,7 @@ loggerclt = logging.getLogger('logclt')
|
||||||
|
|
||||||
# 'help' string - 'default' value - 'dest' string.
|
# 'help' string - 'default' value - 'dest' string.
|
||||||
clt_options = {
|
clt_options = {
|
||||||
'ip' : {'help' : 'The IP address or hostname of the KMS server.', 'def' : "0.0.0.0", 'des' : "ip"},
|
'ip' : {'help' : 'The IP address or hostname of the KMS server.', 'def' : "::", 'des' : "ip"},
|
||||||
'port' : {'help' : 'The port the KMS service is listening on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
|
'port' : {'help' : 'The port the KMS service is listening on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
|
||||||
'mode' : {'help' : 'Use this flag to manually specify a Microsoft product for testing the server. The default is \"Windows81\"',
|
'mode' : {'help' : 'Use this flag to manually specify a Microsoft product for testing the server. The default is \"Windows81\"',
|
||||||
'def' : "Windows8.1", 'des' : "mode",
|
'def' : "Windows8.1", 'des' : "mode",
|
||||||
|
@ -297,11 +296,10 @@ def client_create(clt_sock):
|
||||||
pretty_printer(log_obj = loggerclt.warning, to_exit = True, where = "clt",
|
pretty_printer(log_obj = loggerclt.warning, to_exit = True, where = "clt",
|
||||||
put_text = "{reverse}{magenta}{bold}Something went wrong. Exiting...{end}")
|
put_text = "{reverse}{magenta}{bold}Something went wrong. Exiting...{end}")
|
||||||
|
|
||||||
def clt_main(with_gui = False):
|
def clt_main():
|
||||||
try:
|
try:
|
||||||
if not with_gui:
|
# Parse options.
|
||||||
# Parse options.
|
client_options()
|
||||||
client_options()
|
|
||||||
|
|
||||||
# Check options.
|
# Check options.
|
||||||
client_check()
|
client_check()
|
||||||
|
@ -393,4 +391,4 @@ def readKmsResponseV6(data):
|
||||||
return message
|
return message
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
clt_main(with_gui = False)
|
clt_main()
|
||||||
|
|
|
@ -274,9 +274,7 @@ class ShellMessage(object):
|
||||||
ShellMessage.indx += 1
|
ShellMessage.indx += 1
|
||||||
|
|
||||||
def print_logging_setup(self, logger, async_flag, formatter = logging.Formatter('%(name)s %(message)s')):
|
def print_logging_setup(self, logger, async_flag, formatter = logging.Formatter('%(name)s %(message)s')):
|
||||||
from pykms_GuiBase import gui_redirector
|
handler = logging.StreamHandler(StringIO())
|
||||||
stream = gui_redirector(StringIO())
|
|
||||||
handler = logging.StreamHandler(stream)
|
|
||||||
handler.name = 'LogStream'
|
handler.name = 'LogStream'
|
||||||
handler.setLevel(logging.INFO)
|
handler.setLevel(logging.INFO)
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
|
@ -293,9 +291,6 @@ class ShellMessage(object):
|
||||||
|
|
||||||
def print_logging(self, toprint):
|
def print_logging(self, toprint):
|
||||||
if (self.nshell and ((0 in self.nshell) or (2 in self.nshell and not ShellMessage.viewclt))) or ShellMessage.indx == 0:
|
if (self.nshell and ((0 in self.nshell) or (2 in self.nshell and not ShellMessage.viewclt))) or ShellMessage.indx == 0:
|
||||||
from pykms_GuiBase import gui_redirector_setup, gui_redirector_clear
|
|
||||||
gui_redirector_setup()
|
|
||||||
gui_redirector_clear()
|
|
||||||
self.print_logging_setup(ShellMessage.loggersrv_pty, ShellMessage.asyncmsgsrv)
|
self.print_logging_setup(ShellMessage.loggersrv_pty, ShellMessage.asyncmsgsrv)
|
||||||
self.print_logging_setup(ShellMessage.loggerclt_pty, ShellMessage.asyncmsgclt)
|
self.print_logging_setup(ShellMessage.loggerclt_pty, ShellMessage.asyncmsgclt)
|
||||||
|
|
||||||
|
@ -405,7 +400,6 @@ def pretty_printer(**kwargs):
|
||||||
if None `put_text` must be defined for printing process.
|
if None `put_text` must be defined for printing process.
|
||||||
`to_exit ` --> if True system exit is called.
|
`to_exit ` --> if True system exit is called.
|
||||||
`where` --> specifies if message is server-side or client-side
|
`where` --> specifies if message is server-side or client-side
|
||||||
(useful for GUI redirect).
|
|
||||||
"""
|
"""
|
||||||
# Set defaults for not defined options.
|
# Set defaults for not defined options.
|
||||||
options = {'log_obj' : None,
|
options = {'log_obj' : None,
|
||||||
|
|
|
@ -1,948 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
from time import sleep
|
|
||||||
import tkinter as tk
|
|
||||||
from tkinter import ttk
|
|
||||||
from tkinter import messagebox
|
|
||||||
from tkinter import filedialog
|
|
||||||
import tkinter.font as tkFont
|
|
||||||
|
|
||||||
from pykms_Server import srv_options, srv_version, srv_config, server_terminate, serverqueue, serverthread
|
|
||||||
from pykms_GuiMisc import ToolTip, TextDoubleScroll, TextRedirect, ListboxOfRadiobuttons
|
|
||||||
from pykms_GuiMisc import custom_background, custom_pages
|
|
||||||
from pykms_Client import clt_options, clt_version, clt_config, client_thread
|
|
||||||
|
|
||||||
gui_version = "py-kms_gui_v3.0"
|
|
||||||
__license__ = "MIT License"
|
|
||||||
__author__ = u"Matteo ℱan <SystemRage@protonmail.com>"
|
|
||||||
__copyright__ = "© Copyright 2020"
|
|
||||||
__url__ = "https://github.com/SystemRage/py-kms"
|
|
||||||
gui_description = "A GUI for py-kms."
|
|
||||||
|
|
||||||
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
def get_ip_address():
|
|
||||||
if os.name == 'posix':
|
|
||||||
import subprocess
|
|
||||||
ip = subprocess.getoutput("hostname -I")
|
|
||||||
elif os.name == 'nt':
|
|
||||||
import socket
|
|
||||||
ip = socket.gethostbyname(socket.gethostname())
|
|
||||||
else:
|
|
||||||
ip = 'Unknown'
|
|
||||||
return ip
|
|
||||||
|
|
||||||
def gui_redirector(stream, redirect_to = TextRedirect.Pretty, redirect_conditio = True, stderr_side = "srv"):
|
|
||||||
global txsrv, txclt, txcol
|
|
||||||
if redirect_conditio:
|
|
||||||
if stream == 'stdout':
|
|
||||||
sys.stdout = redirect_to(txsrv, txclt, txcol)
|
|
||||||
elif stream == 'stderr':
|
|
||||||
sys.stderr = redirect_to(txsrv, txclt, txcol, stderr_side)
|
|
||||||
else:
|
|
||||||
stream = redirect_to(txsrv, txclt, txcol)
|
|
||||||
return stream
|
|
||||||
|
|
||||||
def gui_redirector_setup():
|
|
||||||
TextRedirect.Pretty.tag_num = 0
|
|
||||||
TextRedirect.Pretty.newlinecut = [-1, -2, -4, -5]
|
|
||||||
|
|
||||||
def gui_redirector_clear():
|
|
||||||
global txsrv, oysrv
|
|
||||||
try:
|
|
||||||
if oysrv:
|
|
||||||
txsrv.configure(state = 'normal')
|
|
||||||
txsrv.delete('1.0', 'end')
|
|
||||||
txsrv.configure(state = 'disabled')
|
|
||||||
except:
|
|
||||||
# self.onlysrv not defined (menu not used)
|
|
||||||
pass
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class KmsGui(tk.Tk):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
tk.Tk.__init__(self, *args, **kwargs)
|
|
||||||
self.wraplength = 200
|
|
||||||
serverthread.with_gui = True
|
|
||||||
self.validation_int = (self.register(self.validate_int), "%S")
|
|
||||||
self.validation_float = (self.register(self.validate_float), "%P")
|
|
||||||
|
|
||||||
## Define fonts and colors.
|
|
||||||
self.customfonts = {'btn' : tkFont.Font(family = 'Fixedsys', size = 11, weight = 'bold'),
|
|
||||||
'oth' : tkFont.Font(family = 'Times', size = 9, weight = 'bold'),
|
|
||||||
'opt' : tkFont.Font(family = 'Fixedsys', size = 9, weight = 'bold'),
|
|
||||||
'lst' : tkFont.Font(family = 'Fixedsys', size = 8, weight = 'bold', slant = 'italic'),
|
|
||||||
'msg' : tkFont.Font(family = 'Monospace', size = 6), # need a monospaced type (like courier, etc..).
|
|
||||||
}
|
|
||||||
|
|
||||||
self.customcolors = { 'black' : '#000000',
|
|
||||||
'white' : '#FFFFFF',
|
|
||||||
'green' : '#00EE76',
|
|
||||||
'yellow' : '#FFFF00',
|
|
||||||
'magenta' : '#CD00CD',
|
|
||||||
'orange' : '#FFA500',
|
|
||||||
'red' : '#FF4500',
|
|
||||||
'blue' : '#1E90FF',
|
|
||||||
'cyan' : '#AFEEEE',
|
|
||||||
'lavender': '#E6E6FA',
|
|
||||||
'brown' : '#A52A2A',
|
|
||||||
}
|
|
||||||
|
|
||||||
self.option_add('*TCombobox*Listbox.font', self.customfonts['lst'])
|
|
||||||
|
|
||||||
self.gui_create()
|
|
||||||
|
|
||||||
def invert(self, widgets = []):
|
|
||||||
for widget in widgets:
|
|
||||||
if widget['state'] == 'normal':
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
elif widget['state'] == 'disabled':
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
|
|
||||||
def gui_menu(self):
|
|
||||||
self.onlysrv, self.onlyclt = (False for _ in range(2))
|
|
||||||
menubar = tk.Menu(self)
|
|
||||||
prefmenu = tk.Menu(menubar, tearoff = 0, font = ("Noto Sans Regular", 10), borderwidth = 3, relief = 'ridge')
|
|
||||||
menubar.add_cascade(label = 'Preferences', menu = prefmenu)
|
|
||||||
prefmenu.add_command(label = 'Enable server-side mode', command = lambda: self.pref_onlysrv(prefmenu))
|
|
||||||
prefmenu.add_command(label = 'Enable client-side mode', command = lambda: self.pref_onlyclt(prefmenu))
|
|
||||||
self.config(menu = menubar)
|
|
||||||
|
|
||||||
def pref_onlysrv(self, menu):
|
|
||||||
global oysrv
|
|
||||||
|
|
||||||
if self.onlyclt or serverthread.is_running_server:
|
|
||||||
return
|
|
||||||
self.onlysrv = not self.onlysrv
|
|
||||||
if self.onlysrv:
|
|
||||||
menu.entryconfigure(0, label = 'Disable server-side mode')
|
|
||||||
self.clt_on_show(force_remove = True)
|
|
||||||
else:
|
|
||||||
menu.entryconfigure(0, label = 'Enable server-side mode')
|
|
||||||
self.invert(widgets = [self.shbtnclt])
|
|
||||||
oysrv = self.onlysrv
|
|
||||||
|
|
||||||
def pref_onlyclt(self, menu):
|
|
||||||
if self.onlysrv or serverthread.is_running_server:
|
|
||||||
return
|
|
||||||
self.onlyclt = not self.onlyclt
|
|
||||||
if self.onlyclt:
|
|
||||||
menu.entryconfigure(1, label = 'Disable client-side mode')
|
|
||||||
if self.shbtnclt['text'] == 'SHOW\nCLIENT':
|
|
||||||
self.clt_on_show(force_view = True)
|
|
||||||
self.optsrvwin.grid_remove()
|
|
||||||
self.msgsrvwin.grid_remove()
|
|
||||||
gui_redirector('stderr', redirect_to = TextRedirect.Stderr, stderr_side = "clt")
|
|
||||||
else:
|
|
||||||
menu.entryconfigure(1, label = 'Enable client-side mode')
|
|
||||||
self.optsrvwin.grid()
|
|
||||||
self.msgsrvwin.grid()
|
|
||||||
gui_redirector('stderr', redirect_to = TextRedirect.Stderr)
|
|
||||||
|
|
||||||
self.invert(widgets = [self.runbtnsrv, self.shbtnclt, self.runbtnclt])
|
|
||||||
|
|
||||||
def gui_create(self):
|
|
||||||
## Create server gui
|
|
||||||
self.gui_srv()
|
|
||||||
## Create client gui + other operations.
|
|
||||||
self.gui_complete()
|
|
||||||
## Create menu.
|
|
||||||
self.gui_menu()
|
|
||||||
## Create globals for printing process (redirect stdout).
|
|
||||||
global txsrv, txclt, txcol
|
|
||||||
txsrv = self.textboxsrv.get()
|
|
||||||
txclt = self.textboxclt.get()
|
|
||||||
txcol = self.customcolors
|
|
||||||
## Redirect stderr.
|
|
||||||
gui_redirector('stderr', redirect_to = TextRedirect.Stderr)
|
|
||||||
|
|
||||||
def gui_pages_show(self, pagename, side):
|
|
||||||
# https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter
|
|
||||||
# https://www.reddit.com/r/learnpython/comments/7xxtsy/trying_to_understand_tkinter_and_how_to_switch/
|
|
||||||
pageside = self.pagewidgets[side]
|
|
||||||
tk.Misc.lift(pageside["PageWin"][pagename], aboveThis = None)
|
|
||||||
keylist = list(pageside["PageWin"].keys())
|
|
||||||
|
|
||||||
for elem in [pageside["BtnAni"], pageside["LblAni"]]:
|
|
||||||
if pagename == "PageStart":
|
|
||||||
elem["Left"].config(state = "disabled")
|
|
||||||
if len(keylist) == 2:
|
|
||||||
elem["Right"].config(state = "normal")
|
|
||||||
elif pagename == "PageEnd":
|
|
||||||
elem["Right"].config(state = "disabled")
|
|
||||||
if len(keylist) == 2:
|
|
||||||
elem["Left"].config(state = "normal")
|
|
||||||
else:
|
|
||||||
for where in ["Left", "Right"]:
|
|
||||||
elem[where].config(state = "normal")
|
|
||||||
|
|
||||||
if pagename != "PageStart":
|
|
||||||
page_l = keylist[keylist.index(pagename) - 1]
|
|
||||||
pageside["BtnAni"]["Left"]['command'] = lambda pag=page_l, pos=side: self.gui_pages_show(pag, pos)
|
|
||||||
if pagename != "PageEnd":
|
|
||||||
page_r = keylist[keylist.index(pagename) + 1]
|
|
||||||
pageside["BtnAni"]["Right"]['command'] = lambda pag=page_r, pos=side: self.gui_pages_show(pag, pos)
|
|
||||||
|
|
||||||
def gui_pages_buttons(self, parent, side):
|
|
||||||
btnwin = tk.Canvas(parent, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
btnwin.grid(row = 14, column = 2, padx = 2, pady = 2, sticky = 'nsew')
|
|
||||||
btnwin.grid_columnconfigure(1, weight = 1)
|
|
||||||
self.pagewidgets[side]["BtnWin"] = btnwin
|
|
||||||
|
|
||||||
for position in ["Left", "Right"]:
|
|
||||||
if position == "Left":
|
|
||||||
col = [0, 0, 1]
|
|
||||||
stick = 'e'
|
|
||||||
elif position == "Right":
|
|
||||||
col = [2, 1, 0]
|
|
||||||
stick = 'w'
|
|
||||||
|
|
||||||
aniwin = tk.Canvas(btnwin, background = self.customcolors['white'], borderwidth = 0, relief = 'ridge')
|
|
||||||
aniwin.grid(row = 0, column = col[0], padx = 5, pady = 5, sticky = 'nsew')
|
|
||||||
self.pagewidgets[side]["AniWin"][position] = aniwin
|
|
||||||
|
|
||||||
lblani = tk.Label(aniwin, width = 1, height = 1)
|
|
||||||
lblani.grid(row = 0, column = col[1], padx = 2, pady = 2, sticky = stick)
|
|
||||||
self.pagewidgets[side]["LblAni"][position] = lblani
|
|
||||||
|
|
||||||
btnani = tk.Button(aniwin)
|
|
||||||
btnani.grid(row = 0, column = col[2], padx = 2, pady = 2, sticky = stick)
|
|
||||||
self.pagewidgets[side]["BtnAni"][position] = btnani
|
|
||||||
## Customize buttons.
|
|
||||||
custom_pages(self, side)
|
|
||||||
|
|
||||||
def gui_pages_create(self, parent, side, create = {}):
|
|
||||||
self.pagewidgets.update({side : {"PageWin" : create,
|
|
||||||
"BtnWin" : None,
|
|
||||||
"BtnAni" : {"Left" : None,
|
|
||||||
"Right" : None},
|
|
||||||
"AniWin" : {"Left" : None,
|
|
||||||
"Right" : None},
|
|
||||||
"LblAni" : {"Left" : None,
|
|
||||||
"Right" : None},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for pagename in self.pagewidgets[side]["PageWin"].keys():
|
|
||||||
page = tk.Canvas(parent, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
self.pagewidgets[side]["PageWin"][pagename] = page
|
|
||||||
page.grid(row = 0, column = 2, padx = 2, pady = 2, sticky = "nsew")
|
|
||||||
page.grid_columnconfigure(1, weight = 1)
|
|
||||||
self.gui_pages_buttons(parent = parent, side = side)
|
|
||||||
self.gui_pages_show("PageStart", side = side)
|
|
||||||
|
|
||||||
def gui_store(self, side, typewidgets):
|
|
||||||
stored = []
|
|
||||||
for pagename in self.pagewidgets[side]["PageWin"].keys():
|
|
||||||
for widget in self.pagewidgets[side]["PageWin"][pagename].winfo_children():
|
|
||||||
if widget.winfo_class() in typewidgets:
|
|
||||||
stored.append(widget)
|
|
||||||
return stored
|
|
||||||
|
|
||||||
def gui_srv(self):
|
|
||||||
## Create main containers. ------------------------------------------------------------------------------------------------------------------
|
|
||||||
self.masterwin = tk.Canvas(self, borderwidth = 3, relief = tk.RIDGE)
|
|
||||||
self.btnsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
self.optsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
self.msgsrvwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200)
|
|
||||||
|
|
||||||
## Layout main containers.
|
|
||||||
self.masterwin.grid(row = 0, column = 0, sticky = 'nsew')
|
|
||||||
self.btnsrvwin.grid(row = 0, column = 1, padx = 2, pady = 2, sticky = 'nw')
|
|
||||||
self.optsrvwin.grid(row = 0, column = 2, padx = 2, pady = 2, sticky = 'nsew')
|
|
||||||
self.optsrvwin.grid_rowconfigure(0, weight = 1)
|
|
||||||
self.optsrvwin.grid_columnconfigure(1, weight = 1)
|
|
||||||
|
|
||||||
self.pagewidgets = {}
|
|
||||||
|
|
||||||
## Subpages of "optsrvwin".
|
|
||||||
self.gui_pages_create(parent = self.optsrvwin, side = "Srv", create = {"PageStart": None,
|
|
||||||
"PageEnd": None})
|
|
||||||
|
|
||||||
## Continue to grid.
|
|
||||||
self.msgsrvwin.grid(row = 1, column = 2, padx = 1, pady = 1, sticky = 'nsew')
|
|
||||||
self.msgsrvwin.grid_propagate(False)
|
|
||||||
self.msgsrvwin.grid_columnconfigure(0, weight = 1)
|
|
||||||
self.msgsrvwin.grid_rowconfigure(0, weight = 1)
|
|
||||||
|
|
||||||
## Create widgets (btnsrvwin) ---------------------------------------------------------------------------------------------------------------
|
|
||||||
self.statesrv = tk.Label(self.btnsrvwin, text = 'Server\nState:\nStopped', font = self.customfonts['oth'],
|
|
||||||
foreground = self.customcolors['red'])
|
|
||||||
self.runbtnsrv = tk.Button(self.btnsrvwin, text = 'START\nSERVER', background = self.customcolors['green'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = self.srv_on_start)
|
|
||||||
self.shbtnclt = tk.Button(self.btnsrvwin, text = 'SHOW\nCLIENT', background = self.customcolors['magenta'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = self.clt_on_show)
|
|
||||||
self.defaubtnsrv = tk.Button(self.btnsrvwin, text = 'DEFAULTS', background = self.customcolors['brown'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = self.on_defaults)
|
|
||||||
self.clearbtnsrv = tk.Button(self.btnsrvwin, text = 'CLEAR', background = self.customcolors['orange'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = lambda: self.on_clear([txsrv, txclt]))
|
|
||||||
self.exitbtnsrv = tk.Button(self.btnsrvwin, text = 'EXIT', background = self.customcolors['black'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
command = self.on_exit)
|
|
||||||
|
|
||||||
## Layout widgets (btnsrvwin)
|
|
||||||
self.statesrv.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.runbtnsrv.grid(row = 1, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.shbtnclt.grid(row = 2, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.defaubtnsrv.grid(row = 3, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.clearbtnsrv.grid(row = 4, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
self.exitbtnsrv.grid(row = 5, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
|
|
||||||
## Create widgets (optsrvwin:Srv:PageWin:PageStart) -----------------------------------------------------------------------------------------
|
|
||||||
# Version.
|
|
||||||
ver = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"],
|
|
||||||
text = 'You are running server version: ' + srv_version, font = self.customfonts['oth'],
|
|
||||||
foreground = self.customcolors['red'])
|
|
||||||
# Ip Address.
|
|
||||||
srvipaddlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'IP Address: ', font = self.customfonts['opt'])
|
|
||||||
self.srvipadd = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'ip')
|
|
||||||
self.srvipadd.insert('end', srv_options['ip']['def'])
|
|
||||||
ToolTip(self.srvipadd, text = srv_options['ip']['help'], wraplength = self.wraplength)
|
|
||||||
myipadd = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Your IP address is: {}'.format(get_ip_address()),
|
|
||||||
font = self.customfonts['oth'], foreground = self.customcolors['red'])
|
|
||||||
# Port.
|
|
||||||
srvportlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Port: ', font = self.customfonts['opt'])
|
|
||||||
self.srvport = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'port',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.srvport.insert('end', str(srv_options['port']['def']))
|
|
||||||
ToolTip(self.srvport, text = srv_options['port']['help'], wraplength = self.wraplength)
|
|
||||||
# EPID.
|
|
||||||
epidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'EPID: ', font = self.customfonts['opt'])
|
|
||||||
self.epid = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'epid')
|
|
||||||
self.epid.insert('end', str(srv_options['epid']['def']))
|
|
||||||
ToolTip(self.epid, text = srv_options['epid']['help'], wraplength = self.wraplength)
|
|
||||||
# LCID.
|
|
||||||
lcidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'LCID: ', font = self.customfonts['opt'])
|
|
||||||
self.lcid = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lcid',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.lcid.insert('end', str(srv_options['lcid']['def']))
|
|
||||||
ToolTip(self.lcid, text = srv_options['lcid']['help'], wraplength = self.wraplength)
|
|
||||||
# HWID.
|
|
||||||
hwidlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'HWID: ', font = self.customfonts['opt'])
|
|
||||||
self.hwid = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = (str(srv_options['hwid']['def']), 'RANDOM'),
|
|
||||||
width = 17, height = 10, font = self.customfonts['lst'], name = 'hwid')
|
|
||||||
self.hwid.set(str(srv_options['hwid']['def']))
|
|
||||||
ToolTip(self.hwid, text = srv_options['hwid']['help'], wraplength = self.wraplength)
|
|
||||||
# Client Count
|
|
||||||
countlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Client Count: ', font = self.customfonts['opt'])
|
|
||||||
self.count = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'count')
|
|
||||||
self.count.insert('end', str(srv_options['count']['def']))
|
|
||||||
ToolTip(self.count, text = srv_options['count']['help'], wraplength = self.wraplength)
|
|
||||||
# Activation Interval.
|
|
||||||
activlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Activation Interval: ', font = self.customfonts['opt'])
|
|
||||||
self.activ = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'activation',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.activ.insert('end', str(srv_options['activation']['def']))
|
|
||||||
ToolTip(self.activ, text = srv_options['activation']['help'], wraplength = self.wraplength)
|
|
||||||
# Renewal Interval.
|
|
||||||
renewlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Renewal Interval: ', font = self.customfonts['opt'])
|
|
||||||
self.renew = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'renewal',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.renew.insert('end', str(srv_options['renewal']['def']))
|
|
||||||
ToolTip(self.renew, text = srv_options['renewal']['help'], wraplength = self.wraplength)
|
|
||||||
# Logfile.
|
|
||||||
srvfilelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logfile Path / Name: ', font = self.customfonts['opt'])
|
|
||||||
self.srvfile = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lfile')
|
|
||||||
self.srvfile.insert('end', srv_options['lfile']['def'])
|
|
||||||
self.srvfile.xview_moveto(1)
|
|
||||||
ToolTip(self.srvfile, text = srv_options['lfile']['help'], wraplength = self.wraplength)
|
|
||||||
srvfilebtnwin = tk.Button(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Browse', font = self.customfonts['opt'],
|
|
||||||
command = lambda: self.on_browse(self.srvfile, srv_options))
|
|
||||||
# Loglevel.
|
|
||||||
srvlevellbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.customfonts['opt'])
|
|
||||||
self.srvlevel = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = tuple(srv_options['llevel']['choi']),
|
|
||||||
width = 10, height = 10, font = self.customfonts['lst'], state = "readonly", name = 'llevel')
|
|
||||||
self.srvlevel.set(srv_options['llevel']['def'])
|
|
||||||
ToolTip(self.srvlevel, text = srv_options['llevel']['help'], wraplength = self.wraplength)
|
|
||||||
# Logsize.
|
|
||||||
srvsizelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.customfonts['opt'])
|
|
||||||
self.srvsize = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lsize',
|
|
||||||
validate = "key", validatecommand = self.validation_float)
|
|
||||||
self.srvsize.insert('end', srv_options['lsize']['def'])
|
|
||||||
ToolTip(self.srvsize, text = srv_options['lsize']['help'], wraplength = self.wraplength)
|
|
||||||
# Asynchronous messages.
|
|
||||||
self.chkvalsrvasy = tk.BooleanVar()
|
|
||||||
self.chkvalsrvasy.set(srv_options['asyncmsg']['def'])
|
|
||||||
chksrvasy = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Async\nMsg',
|
|
||||||
font = self.customfonts['opt'], var = self.chkvalsrvasy, relief = 'groove', name = 'asyncmsg')
|
|
||||||
ToolTip(chksrvasy, text = srv_options['asyncmsg']['help'], wraplength = self.wraplength)
|
|
||||||
|
|
||||||
# Listbox radiobuttons server.
|
|
||||||
self.chksrvfile = ListboxOfRadiobuttons(self.pagewidgets["Srv"]["PageWin"]["PageStart"],
|
|
||||||
['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'],
|
|
||||||
self.customfonts['lst'],
|
|
||||||
changed = [(self.srvfile, srv_options['lfile']['def']),
|
|
||||||
(srvfilebtnwin, ''),
|
|
||||||
(self.srvsize, srv_options['lsize']['def']),
|
|
||||||
(self.srvlevel, srv_options['llevel']['def'])],
|
|
||||||
width = 10, height = 1, borderwidth = 2, relief = 'ridge')
|
|
||||||
|
|
||||||
## Layout widgets (optsrvwin:Srv:PageWin:PageStart)
|
|
||||||
ver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
myipadd.grid(row = 2, column = 1, columnspan = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvportlbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvport.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
epidlbl.grid(row = 4, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.epid.grid(row = 4, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
lcidlbl.grid(row = 5, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.lcid.grid(row = 5, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
hwidlbl.grid(row = 6, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.hwid.grid(row = 6, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
countlbl.grid(row = 7, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.count.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
activlbl.grid(row = 8, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.activ.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
renewlbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.renew.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvfilelbl.grid(row = 10, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvfile.grid(row = 10, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvfilebtnwin.grid(row = 10, column = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
self.chksrvfile.grid(row = 11, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
chksrvasy.grid(row = 11, column = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvlevellbl.grid(row = 12, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvlevel.grid(row = 12, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
srvsizelbl.grid(row = 13, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvsize.grid(row = 13, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
|
|
||||||
## Create widgets (optsrvwin:Srv:PageWin:PageEnd)-------------------------------------------------------------------------------------------
|
|
||||||
# Timeout connection.
|
|
||||||
srvtimeout0lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.customfonts['opt'])
|
|
||||||
self.srvtimeout0 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time0')
|
|
||||||
self.srvtimeout0.insert('end', str(srv_options['time0']['def']))
|
|
||||||
ToolTip(self.srvtimeout0, text = srv_options['time0']['help'], wraplength = self.wraplength)
|
|
||||||
# Timeout send/recv.
|
|
||||||
srvtimeout1lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout send-recv: ', font = self.customfonts['opt'])
|
|
||||||
self.srvtimeout1 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time1')
|
|
||||||
self.srvtimeout1.insert('end', str(srv_options['time1']['def']))
|
|
||||||
ToolTip(self.srvtimeout1, text = srv_options['time1']['help'], wraplength = self.wraplength)
|
|
||||||
# Sqlite database.
|
|
||||||
self.chkvalsql = tk.BooleanVar()
|
|
||||||
self.chkvalsql.set(srv_options['sql']['def'])
|
|
||||||
self.chkfilesql = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'sql')
|
|
||||||
self.chkfilesql.insert('end', srv_options['sql']['file'])
|
|
||||||
self.chkfilesql.xview_moveto(1)
|
|
||||||
self.chkfilesql.configure(state = 'disabled')
|
|
||||||
|
|
||||||
chksql = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Create Sqlite\nDatabase',
|
|
||||||
font = self.customfonts['opt'], var = self.chkvalsql, relief = 'groove',
|
|
||||||
command = lambda: self.sql_status())
|
|
||||||
ToolTip(chksql, text = srv_options['sql']['help'], wraplength = self.wraplength)
|
|
||||||
|
|
||||||
## Layout widgets (optsrvwin:Srv:PageWin:PageEnd)
|
|
||||||
# a label for vertical aligning with PageStart
|
|
||||||
tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 0,
|
|
||||||
height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw')
|
|
||||||
srvtimeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvtimeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
srvtimeout1lbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.srvtimeout1.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
chksql.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.chkfilesql.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
|
|
||||||
# Store server-side widgets.
|
|
||||||
self.storewidgets_srv = self.gui_store(side = "Srv", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton'])
|
|
||||||
self.storewidgets_srv.append(self.chksrvfile)
|
|
||||||
|
|
||||||
## Create widgets and layout (msgsrvwin) ---------------------------------------------------------------------------------------------------
|
|
||||||
self.textboxsrv = TextDoubleScroll(self.msgsrvwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled',
|
|
||||||
relief = 'ridge', font = self.customfonts['msg'])
|
|
||||||
self.textboxsrv.put()
|
|
||||||
|
|
||||||
def sql_status(self):
|
|
||||||
if self.chkvalsql.get():
|
|
||||||
self.chkfilesql.configure(state = 'normal')
|
|
||||||
else:
|
|
||||||
self.chkfilesql.insert('end', srv_options['sql']['file'])
|
|
||||||
self.chkfilesql.xview_moveto(1)
|
|
||||||
self.chkfilesql.configure(state = 'disabled')
|
|
||||||
|
|
||||||
def always_centered(self, geo, centered, refs):
|
|
||||||
x = (self.winfo_screenwidth() // 2) - (self.winfo_width() // 2)
|
|
||||||
y = (self.winfo_screenheight() // 2) - (self.winfo_height() // 2)
|
|
||||||
w, h, dx, dy = geo.split('+')[0].split('x') + geo.split('+')[1:]
|
|
||||||
|
|
||||||
if w == refs[1]:
|
|
||||||
if centered:
|
|
||||||
self.geometry('+%d+%d' %(x, y))
|
|
||||||
centered = False
|
|
||||||
elif w == refs[0]:
|
|
||||||
if not centered:
|
|
||||||
self.geometry('+%d+%d' %(x, y))
|
|
||||||
centered = True
|
|
||||||
|
|
||||||
if dx != str(x) or dy != str(y):
|
|
||||||
self.geometry('+%d+%d' %(x, 0))
|
|
||||||
|
|
||||||
self.after(200, self.always_centered, self.geometry(), centered, refs)
|
|
||||||
|
|
||||||
def gui_complete(self):
|
|
||||||
## Create client widgets (optcltwin, msgcltwin, btncltwin)
|
|
||||||
self.update_idletasks() # update Gui to get btnsrvwin values --> btncltwin.
|
|
||||||
minw, minh = self.winfo_width(), self.winfo_height()
|
|
||||||
self.iconify()
|
|
||||||
self.gui_clt()
|
|
||||||
maxw, minh = self.winfo_width(), self.winfo_height()
|
|
||||||
## Main window custom background.
|
|
||||||
self.update_idletasks() # update Gui for custom background
|
|
||||||
self.iconify()
|
|
||||||
custom_background(self)
|
|
||||||
## Main window other modifications.
|
|
||||||
self.eval('tk::PlaceWindow %s center' %self.winfo_pathname(self.winfo_id()))
|
|
||||||
self.wm_attributes("-topmost", True)
|
|
||||||
self.protocol("WM_DELETE_WINDOW", lambda: 0)
|
|
||||||
## Disable maximize button.
|
|
||||||
self.resizable(False, False)
|
|
||||||
## Centered window.
|
|
||||||
self.always_centered(self.geometry(), False, [minw, maxw])
|
|
||||||
|
|
||||||
def get_position(self, widget):
|
|
||||||
x, y = (widget.winfo_x(), widget.winfo_y())
|
|
||||||
w, h = (widget.winfo_width(), widget.winfo_height())
|
|
||||||
return x, y, w, h
|
|
||||||
|
|
||||||
def gui_clt(self):
|
|
||||||
self.count_clear, self.keep_clear = (0, '0.0')
|
|
||||||
self.optcltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
self.msgcltwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200)
|
|
||||||
self.btncltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge')
|
|
||||||
|
|
||||||
xb, yb, wb, hb = self.get_position(self.btnsrvwin)
|
|
||||||
self.btncltwin_X = xb
|
|
||||||
self.btncltwin_Y = yb + hb + 6
|
|
||||||
self.btncltwin.place(x = self.btncltwin_X, y = self.btncltwin_Y, bordermode = 'outside', anchor = 'center')
|
|
||||||
|
|
||||||
self.optcltwin.grid(row = 0, column = 4, padx = 2, pady = 2, sticky = 'nsew')
|
|
||||||
self.optcltwin.grid_rowconfigure(0, weight = 1)
|
|
||||||
self.optcltwin.grid_columnconfigure(1, weight = 1)
|
|
||||||
|
|
||||||
## Subpages of "optcltwin".
|
|
||||||
self.gui_pages_create(parent = self.optcltwin, side = "Clt", create = {"PageStart": None,
|
|
||||||
"PageEnd": None})
|
|
||||||
|
|
||||||
## Continue to grid.
|
|
||||||
self.msgcltwin.grid(row = 1, column = 4, padx = 1, pady = 1, sticky = 'nsew')
|
|
||||||
self.msgcltwin.grid_propagate(False)
|
|
||||||
self.msgcltwin.grid_columnconfigure(0, weight = 1)
|
|
||||||
self.msgcltwin.grid_rowconfigure(0, weight = 1)
|
|
||||||
|
|
||||||
## Create widgets (btncltwin) ----------------------------------------------------------------------------------------------------------------
|
|
||||||
self.runbtnclt = tk.Button(self.btncltwin, text = 'START\nCLIENT', background = self.customcolors['blue'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised', font = self.customfonts['btn'],
|
|
||||||
state = 'disabled', command = self.clt_on_start, width = 8, height = 2)
|
|
||||||
|
|
||||||
## Layout widgets (btncltwin)
|
|
||||||
self.runbtnclt.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew')
|
|
||||||
|
|
||||||
## Create widgets (optcltwin:Clt:PageWin:PageStart) ------------------------------------------------------------------------------------------
|
|
||||||
# Version.
|
|
||||||
cltver = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'You are running client version: ' + clt_version,
|
|
||||||
font = self.customfonts['oth'], foreground = self.customcolors['red'])
|
|
||||||
# Ip Address.
|
|
||||||
cltipaddlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'IP Address: ', font = self.customfonts['opt'])
|
|
||||||
self.cltipadd = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'ip')
|
|
||||||
self.cltipadd.insert('end', clt_options['ip']['def'])
|
|
||||||
ToolTip(self.cltipadd, text = clt_options['ip']['help'], wraplength = self.wraplength)
|
|
||||||
# Port.
|
|
||||||
cltportlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Port: ', font = self.customfonts['opt'])
|
|
||||||
self.cltport = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'port',
|
|
||||||
validate = "key", validatecommand = self.validation_int)
|
|
||||||
self.cltport.insert('end', str(clt_options['port']['def']))
|
|
||||||
ToolTip(self.cltport, text = clt_options['port']['help'], wraplength = self.wraplength)
|
|
||||||
# Mode.
|
|
||||||
cltmodelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Mode: ', font = self.customfonts['opt'])
|
|
||||||
self.cltmode = ttk.Combobox(self.pagewidgets["Clt"]["PageWin"]["PageStart"], values = tuple(clt_options['mode']['choi']),
|
|
||||||
width = 17, height = 10, font = self.customfonts['lst'], state = "readonly", name = 'mode')
|
|
||||||
self.cltmode.set(clt_options['mode']['def'])
|
|
||||||
ToolTip(self.cltmode, text = clt_options['mode']['help'], wraplength = self.wraplength)
|
|
||||||
# CMID.
|
|
||||||
cltcmidlbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'CMID: ', font = self.customfonts['opt'])
|
|
||||||
self.cltcmid = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'cmid')
|
|
||||||
self.cltcmid.insert('end', str(clt_options['cmid']['def']))
|
|
||||||
ToolTip(self.cltcmid, text = clt_options['cmid']['help'], wraplength = self.wraplength)
|
|
||||||
# Machine Name.
|
|
||||||
cltnamelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Machine Name: ', font = self.customfonts['opt'])
|
|
||||||
self.cltname = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'name')
|
|
||||||
self.cltname.insert('end', str(clt_options['name']['def']))
|
|
||||||
ToolTip(self.cltname, text = clt_options['name']['help'], wraplength = self.wraplength)
|
|
||||||
# Logfile.
|
|
||||||
cltfilelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logfile Path / Name: ', font = self.customfonts['opt'])
|
|
||||||
self.cltfile = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lfile')
|
|
||||||
self.cltfile.insert('end', clt_options['lfile']['def'])
|
|
||||||
self.cltfile.xview_moveto(1)
|
|
||||||
ToolTip(self.cltfile, text = clt_options['lfile']['help'], wraplength = self.wraplength)
|
|
||||||
cltfilebtnwin = tk.Button(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Browse', font = self.customfonts['opt'],
|
|
||||||
command = lambda: self.on_browse(self.cltfile, clt_options))
|
|
||||||
# Loglevel.
|
|
||||||
cltlevellbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.customfonts['opt'])
|
|
||||||
self.cltlevel = ttk.Combobox(self.pagewidgets["Clt"]["PageWin"]["PageStart"], values = tuple(clt_options['llevel']['choi']),
|
|
||||||
width = 10, height = 10, font = self.customfonts['lst'], state = "readonly", name = 'llevel')
|
|
||||||
self.cltlevel.set(clt_options['llevel']['def'])
|
|
||||||
ToolTip(self.cltlevel, text = clt_options['llevel']['help'], wraplength = self.wraplength)
|
|
||||||
# Logsize.
|
|
||||||
cltsizelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.customfonts['opt'])
|
|
||||||
self.cltsize = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.customfonts['opt'], name = 'lsize',
|
|
||||||
validate = "key", validatecommand = self.validation_float)
|
|
||||||
self.cltsize.insert('end', clt_options['lsize']['def'])
|
|
||||||
ToolTip(self.cltsize, text = clt_options['lsize']['help'], wraplength = self.wraplength)
|
|
||||||
# Asynchronous messages.
|
|
||||||
self.chkvalcltasy = tk.BooleanVar()
|
|
||||||
self.chkvalcltasy.set(clt_options['asyncmsg']['def'])
|
|
||||||
chkcltasy = tk.Checkbutton(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Async\nMsg',
|
|
||||||
font = self.customfonts['opt'], var = self.chkvalcltasy, relief = 'groove', name = 'asyncmsg')
|
|
||||||
ToolTip(chkcltasy, text = clt_options['asyncmsg']['help'], wraplength = self.wraplength)
|
|
||||||
|
|
||||||
# Listbox radiobuttons client.
|
|
||||||
self.chkcltfile = ListboxOfRadiobuttons(self.pagewidgets["Clt"]["PageWin"]["PageStart"],
|
|
||||||
['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'],
|
|
||||||
self.customfonts['lst'],
|
|
||||||
changed = [(self.cltfile, clt_options['lfile']['def']),
|
|
||||||
(cltfilebtnwin, ''),
|
|
||||||
(self.cltsize, clt_options['lsize']['def']),
|
|
||||||
(self.cltlevel, clt_options['llevel']['def'])],
|
|
||||||
width = 10, height = 1, borderwidth = 2, relief = 'ridge')
|
|
||||||
|
|
||||||
## Layout widgets (optcltwin:Clt:PageWin:PageStart)
|
|
||||||
cltver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltportlbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltport.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltmodelbl.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltmode.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltcmidlbl.grid(row = 4, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltcmid.grid(row = 4, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltnamelbl.grid(row = 5, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltname.grid(row = 5, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltfilelbl.grid(row = 6, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltfile.grid(row = 6, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltfilebtnwin.grid(row = 6, column = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
self.chkcltfile.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
chkcltasy.grid(row = 7, column = 2, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltlevellbl.grid(row = 8, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltlevel.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
cltsizelbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.cltsize.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew')
|
|
||||||
|
|
||||||
# ugly fix when client-side mode is activated.
|
|
||||||
templbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"],
|
|
||||||
bg = self.customcolors['lavender']).grid(row = 10, column = 0,
|
|
||||||
padx = 35, pady = 54, sticky = 'e')
|
|
||||||
|
|
||||||
## Create widgets (optcltwin:Clt:PageWin:PageEnd) -------------------------------------------------------------------------------------------
|
|
||||||
# Timeout connection.
|
|
||||||
clttimeout0lbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.customfonts['opt'])
|
|
||||||
self.clttimeout0 = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time0')
|
|
||||||
self.clttimeout0.insert('end', str(clt_options['time0']['def']))
|
|
||||||
ToolTip(self.clttimeout0, text = clt_options['time0']['help'], wraplength = self.wraplength)
|
|
||||||
# Timeout send/recv.
|
|
||||||
clttimeout1lbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], text = 'Timeout send-recv: ', font = self.customfonts['opt'])
|
|
||||||
self.clttimeout1 = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 16, font = self.customfonts['opt'], name = 'time1')
|
|
||||||
self.clttimeout1.insert('end', str(clt_options['time1']['def']))
|
|
||||||
ToolTip(self.clttimeout1, text = clt_options['time1']['help'], wraplength = self.wraplength)
|
|
||||||
|
|
||||||
## Layout widgets (optcltwin:Clt:PageWin:PageEnd)
|
|
||||||
# a label for vertical aligning with PageStart
|
|
||||||
tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 0,
|
|
||||||
height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw')
|
|
||||||
clttimeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.clttimeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
clttimeout1lbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e')
|
|
||||||
self.clttimeout1.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w')
|
|
||||||
|
|
||||||
## Store client-side widgets.
|
|
||||||
self.storewidgets_clt = self.gui_store(side = "Clt", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton'])
|
|
||||||
self.storewidgets_clt.append(self.chkcltfile)
|
|
||||||
|
|
||||||
## Create widgets and layout (msgcltwin) -----------------------------------------------------------------------------------------------------
|
|
||||||
self.textboxclt = TextDoubleScroll(self.msgcltwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled',
|
|
||||||
relief = 'ridge', font = self.customfonts['msg'])
|
|
||||||
self.textboxclt.put()
|
|
||||||
|
|
||||||
def prep_option(self, value):
|
|
||||||
try:
|
|
||||||
# is an INT
|
|
||||||
return int(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
try:
|
|
||||||
# is a FLOAT
|
|
||||||
return float(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
# is a STRING.
|
|
||||||
return value
|
|
||||||
|
|
||||||
def prep_logfile(self, filepath, status):
|
|
||||||
# FILE (pretty on, log view off, logfile yes)
|
|
||||||
# FILEOFF (pretty on, log view off, no logfile)
|
|
||||||
# STDOUT (pretty off, log view on, no logfile)
|
|
||||||
# STDOUTOFF (pretty off, log view off, logfile yes)
|
|
||||||
# FILESTDOUT (pretty off, log view on, logfile yes)
|
|
||||||
|
|
||||||
if status == 'FILE':
|
|
||||||
return filepath
|
|
||||||
elif status in ['FILESTDOUT', 'STDOUTOFF']:
|
|
||||||
return [status, filepath]
|
|
||||||
elif status in ['STDOUT', 'FILEOFF']:
|
|
||||||
return status
|
|
||||||
|
|
||||||
def validate_int(self, value):
|
|
||||||
return value == "" or value.isdigit()
|
|
||||||
|
|
||||||
def validate_float(self, value):
|
|
||||||
if value == "":
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
float(value)
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def clt_on_show(self, force_remove = False, force_view = False):
|
|
||||||
if self.optcltwin.winfo_ismapped() or force_remove:
|
|
||||||
self.shbtnclt.configure(text = 'SHOW\nCLIENT', relief = 'raised')
|
|
||||||
self.optcltwin.grid_remove()
|
|
||||||
self.msgcltwin.grid_remove()
|
|
||||||
self.btncltwin.place_forget()
|
|
||||||
elif not self.optcltwin.winfo_ismapped() or force_view:
|
|
||||||
self.shbtnclt.configure(text = 'HIDE\nCLIENT', relief = 'sunken')
|
|
||||||
self.optcltwin.grid()
|
|
||||||
self.msgcltwin.grid()
|
|
||||||
self.btncltwin.place(x = self.btncltwin_X, y = self.btncltwin_Y, bordermode = 'inside', anchor = 'nw')
|
|
||||||
|
|
||||||
def srv_on_start(self):
|
|
||||||
if self.runbtnsrv['text'] == 'START\nSERVER':
|
|
||||||
self.on_clear([txsrv, txclt])
|
|
||||||
self.srv_actions_start()
|
|
||||||
# wait for switch.
|
|
||||||
while not serverthread.is_running_server:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.srv_toggle_all(on_start = True)
|
|
||||||
# run thread for interrupting server when an error happens.
|
|
||||||
self.srv_eject_thread = threading.Thread(target = self.srv_eject, name = "Thread-SrvEjt")
|
|
||||||
self.srv_eject_thread.setDaemon(True)
|
|
||||||
self.srv_eject_thread.start()
|
|
||||||
|
|
||||||
elif self.runbtnsrv['text'] == 'STOP\nSERVER':
|
|
||||||
serverthread.terminate_eject()
|
|
||||||
|
|
||||||
def srv_eject(self):
|
|
||||||
while not serverthread.eject:
|
|
||||||
sleep(0.1)
|
|
||||||
self.srv_actions_stop()
|
|
||||||
|
|
||||||
def srv_actions_start(self):
|
|
||||||
srv_config[srv_options['ip']['des']] = self.srvipadd.get()
|
|
||||||
srv_config[srv_options['port']['des']] = self.prep_option(self.srvport.get())
|
|
||||||
srv_config[srv_options['epid']['des']] = self.epid.get()
|
|
||||||
srv_config[srv_options['lcid']['des']] = self.prep_option(self.lcid.get())
|
|
||||||
srv_config[srv_options['hwid']['des']] = self.hwid.get()
|
|
||||||
srv_config[srv_options['count']['des']] = self.prep_option(self.count.get())
|
|
||||||
srv_config[srv_options['activation']['des']] = self.prep_option(self.activ.get())
|
|
||||||
srv_config[srv_options['renewal']['des']] = self.prep_option(self.renew.get())
|
|
||||||
srv_config[srv_options['lfile']['des']] = self.prep_logfile(self.srvfile.get(), self.chksrvfile.state())
|
|
||||||
srv_config[srv_options['asyncmsg']['des']] = self.chkvalsrvasy.get()
|
|
||||||
srv_config[srv_options['llevel']['des']] = self.srvlevel.get()
|
|
||||||
srv_config[srv_options['lsize']['des']] = self.prep_option(self.srvsize.get())
|
|
||||||
|
|
||||||
srv_config[srv_options['time0']['des']] = self.prep_option(self.srvtimeout0.get())
|
|
||||||
srv_config[srv_options['time1']['des']] = self.prep_option(self.srvtimeout1.get())
|
|
||||||
srv_config[srv_options['sql']['des']] = (self.chkfilesql.get() if self.chkvalsql.get() else self.chkvalsql.get())
|
|
||||||
|
|
||||||
## Redirect stdout.
|
|
||||||
gui_redirector('stdout', redirect_to = TextRedirect.Log,
|
|
||||||
redirect_conditio = (srv_config[srv_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT']))
|
|
||||||
serverqueue.put('start')
|
|
||||||
|
|
||||||
def srv_actions_stop(self):
|
|
||||||
if serverthread.is_running_server:
|
|
||||||
if serverthread.server is not None:
|
|
||||||
server_terminate(serverthread, exit_server = True)
|
|
||||||
# wait for switch.
|
|
||||||
while serverthread.is_running_server:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
serverthread.is_running_server = False
|
|
||||||
self.srv_toggle_all(on_start = False)
|
|
||||||
self.count_clear, self.keep_clear = (0, '0.0')
|
|
||||||
|
|
||||||
def srv_toggle_all(self, on_start = True):
|
|
||||||
self.srv_toggle_state()
|
|
||||||
if on_start:
|
|
||||||
self.runbtnsrv.configure(text = 'STOP\nSERVER', background = self.customcolors['red'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'sunken')
|
|
||||||
for widget in self.storewidgets_srv:
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
self.runbtnclt.configure(state = 'normal')
|
|
||||||
else:
|
|
||||||
self.runbtnsrv.configure(text = 'START\nSERVER', background = self.customcolors['green'],
|
|
||||||
foreground = self.customcolors['white'], relief = 'raised')
|
|
||||||
for widget in self.storewidgets_srv:
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
if isinstance(widget, ListboxOfRadiobuttons):
|
|
||||||
widget.change()
|
|
||||||
self.runbtnclt.configure(state = 'disabled')
|
|
||||||
|
|
||||||
def srv_toggle_state(self):
|
|
||||||
if serverthread.is_running_server:
|
|
||||||
txt, color = ('Server\nState:\nServing', self.customcolors['green'])
|
|
||||||
else:
|
|
||||||
txt, color = ('Server\nState:\nStopped', self.customcolors['red'])
|
|
||||||
|
|
||||||
self.statesrv.configure(text = txt, foreground = color)
|
|
||||||
|
|
||||||
def clt_on_start(self):
|
|
||||||
if self.onlyclt:
|
|
||||||
self.on_clear([txclt])
|
|
||||||
else:
|
|
||||||
rng, add_newline = self.on_clear_setup()
|
|
||||||
self.on_clear([txsrv, txclt], clear_range = [rng, None], newline_list = [add_newline, False])
|
|
||||||
|
|
||||||
self.runbtnclt.configure(relief = 'sunken')
|
|
||||||
self.clt_actions_start()
|
|
||||||
# run thread for disabling interrupt server and client, when client running.
|
|
||||||
self.clt_eject_thread = threading.Thread(target = self.clt_eject, name = "Thread-CltEjt")
|
|
||||||
self.clt_eject_thread.setDaemon(True)
|
|
||||||
self.clt_eject_thread.start()
|
|
||||||
|
|
||||||
for widget in self.storewidgets_clt + [self.runbtnsrv, self.runbtnclt, self.defaubtnsrv]:
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
self.runbtnclt.configure(relief = 'raised')
|
|
||||||
|
|
||||||
def clt_actions_start(self):
|
|
||||||
clt_config[clt_options['ip']['des']] = self.cltipadd.get()
|
|
||||||
clt_config[clt_options['port']['des']] = self.prep_option(self.cltport.get())
|
|
||||||
clt_config[clt_options['mode']['des']] = self.cltmode.get()
|
|
||||||
clt_config[clt_options['cmid']['des']] = self.cltcmid.get()
|
|
||||||
clt_config[clt_options['name']['des']] = self.cltname.get()
|
|
||||||
clt_config[clt_options['lfile']['des']] = self.prep_logfile(self.cltfile.get(), self.chkcltfile.state())
|
|
||||||
clt_config[clt_options['asyncmsg']['des']] = self.chkvalcltasy.get()
|
|
||||||
clt_config[clt_options['llevel']['des']] = self.cltlevel.get()
|
|
||||||
clt_config[clt_options['lsize']['des']] = self.prep_option(self.cltsize.get())
|
|
||||||
|
|
||||||
clt_config[clt_options['time0']['des']] = self.prep_option(self.clttimeout0.get())
|
|
||||||
clt_config[clt_options['time1']['des']] = self.prep_option(self.clttimeout1.get())
|
|
||||||
|
|
||||||
## Redirect stdout.
|
|
||||||
gui_redirector('stdout', redirect_to = TextRedirect.Log,
|
|
||||||
redirect_conditio = (clt_config[clt_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT']))
|
|
||||||
|
|
||||||
# run client (in a thread).
|
|
||||||
self.clientthread = client_thread(name = "Thread-Clt")
|
|
||||||
self.clientthread.setDaemon(True)
|
|
||||||
self.clientthread.with_gui = True
|
|
||||||
self.clientthread.start()
|
|
||||||
|
|
||||||
def clt_eject(self):
|
|
||||||
while self.clientthread.is_alive():
|
|
||||||
sleep(0.1)
|
|
||||||
|
|
||||||
widgets = self.storewidgets_clt + [self.runbtnclt] + [self.defaubtnsrv]
|
|
||||||
if not self.onlyclt:
|
|
||||||
widgets += [self.runbtnsrv]
|
|
||||||
|
|
||||||
for widget in widgets:
|
|
||||||
if isinstance(widget, ttk.Combobox):
|
|
||||||
widget.configure(state = 'readonly')
|
|
||||||
else:
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
if isinstance(widget, ListboxOfRadiobuttons):
|
|
||||||
widget.change()
|
|
||||||
|
|
||||||
def on_browse(self, entrywidget, options):
|
|
||||||
path = filedialog.askdirectory()
|
|
||||||
if os.path.isdir(path):
|
|
||||||
entrywidget.delete('0', 'end')
|
|
||||||
entrywidget.insert('end', path + os.sep + os.path.basename(options['lfile']['def']))
|
|
||||||
|
|
||||||
def on_exit(self):
|
|
||||||
if serverthread.is_running_server:
|
|
||||||
if serverthread.server is not None:
|
|
||||||
server_terminate(serverthread, exit_server = True)
|
|
||||||
else:
|
|
||||||
serverthread.is_running_server = False
|
|
||||||
server_terminate(serverthread, exit_thread = True)
|
|
||||||
self.destroy()
|
|
||||||
|
|
||||||
def on_clear_setup(self):
|
|
||||||
if any(opt in ['STDOUT', 'FILESTDOUT'] for opt in srv_config[srv_options['lfile']['des']]):
|
|
||||||
add_newline = True
|
|
||||||
if self.count_clear == 0:
|
|
||||||
self.keep_clear = txsrv.index('end-1c')
|
|
||||||
else:
|
|
||||||
add_newline = False
|
|
||||||
if self.count_clear == 0:
|
|
||||||
self.keep_clear = txsrv.index('end')
|
|
||||||
|
|
||||||
rng = [self.keep_clear, 'end']
|
|
||||||
self.count_clear += 1
|
|
||||||
|
|
||||||
return rng, add_newline
|
|
||||||
|
|
||||||
def on_clear(self, widget_list, clear_range = None, newline_list = []):
|
|
||||||
if newline_list == []:
|
|
||||||
newline_list = len(widget_list) * [False]
|
|
||||||
|
|
||||||
for num, couple in enumerate(zip(widget_list, newline_list)):
|
|
||||||
widget, add_n = couple
|
|
||||||
try:
|
|
||||||
ini, fin = clear_range[num]
|
|
||||||
except TypeError:
|
|
||||||
ini, fin = '1.0', 'end'
|
|
||||||
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
widget.delete(ini, fin)
|
|
||||||
if add_n:
|
|
||||||
widget.insert('end', '\n')
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
|
|
||||||
def on_defaults(self):
|
|
||||||
|
|
||||||
def put_defaults(widgets, chkasy, listofradio, options):
|
|
||||||
for widget in widgets:
|
|
||||||
wclass, wname = widget.winfo_class(), widget.winfo_name()
|
|
||||||
if wname == '!checkbutton':
|
|
||||||
continue
|
|
||||||
|
|
||||||
opt = options[wname]['def']
|
|
||||||
if wclass == 'Entry':
|
|
||||||
widget.delete(0, 'end')
|
|
||||||
if wname == 'sql':
|
|
||||||
self.chkvalsql.set(opt)
|
|
||||||
self.sql_status()
|
|
||||||
else:
|
|
||||||
widget.insert('end', (opt if isinstance(opt, str) else str(opt)))
|
|
||||||
elif wclass == 'Checkbutton':
|
|
||||||
if wname == 'asyncmsg':
|
|
||||||
chkasy.set(opt)
|
|
||||||
elif wclass == 'TCombobox':
|
|
||||||
widget.set(str(opt))
|
|
||||||
|
|
||||||
# ListboxOfRadiobuttons default.
|
|
||||||
listofradio.radiovar.set('FILE')
|
|
||||||
listofradio.textbox.yview_moveto(0)
|
|
||||||
listofradio.change()
|
|
||||||
|
|
||||||
if self.runbtnsrv['text'] == 'START\nSERVER':
|
|
||||||
apply_default = zip(["Srv", "Clt"],
|
|
||||||
[self.chkvalsrvasy, self.chkvalcltasy],
|
|
||||||
[self.chksrvfile, self.chkcltfile],
|
|
||||||
[srv_options, clt_options])
|
|
||||||
elif self.runbtnsrv['text'] == 'STOP\nSERVER':
|
|
||||||
apply_default = zip(*[("Clt",),
|
|
||||||
(self.chkvalcltasy,),
|
|
||||||
(self.chkcltfile,),
|
|
||||||
(clt_options,)])
|
|
||||||
|
|
||||||
for side, chkasy, listofradio, options in apply_default:
|
|
||||||
widgets = self.gui_store(side = side, typewidgets = ['Entry', 'TCombobox', 'Checkbutton'])
|
|
||||||
put_defaults(widgets, chkasy, listofradio, options)
|
|
|
@ -1,517 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from collections import Counter
|
|
||||||
from time import sleep
|
|
||||||
import threading
|
|
||||||
import tkinter as tk
|
|
||||||
from tkinter import ttk
|
|
||||||
import tkinter.font as tkFont
|
|
||||||
|
|
||||||
from pykms_Format import MsgMap, unshell_message, unformat_message
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/3221956/how-do-i-display-tooltips-in-tkinter
|
|
||||||
class ToolTip(object):
|
|
||||||
""" Create a tooltip for a given widget """
|
|
||||||
def __init__(self, widget, bg = '#FFFFEA', pad = (5, 3, 5, 3), text = 'widget info', waittime = 400, wraplength = 250):
|
|
||||||
self.waittime = waittime # ms
|
|
||||||
self.wraplength = wraplength # pixels
|
|
||||||
self.widget = widget
|
|
||||||
self.text = text
|
|
||||||
self.widget.bind("<Enter>", self.onEnter)
|
|
||||||
self.widget.bind("<Leave>", self.onLeave)
|
|
||||||
self.widget.bind("<ButtonPress>", self.onLeave)
|
|
||||||
self.bg = bg
|
|
||||||
self.pad = pad
|
|
||||||
self.id = None
|
|
||||||
self.tw = None
|
|
||||||
|
|
||||||
def onEnter(self, event = None):
|
|
||||||
self.schedule()
|
|
||||||
|
|
||||||
def onLeave(self, event = None):
|
|
||||||
self.unschedule()
|
|
||||||
self.hide()
|
|
||||||
|
|
||||||
def schedule(self):
|
|
||||||
self.unschedule()
|
|
||||||
self.id = self.widget.after(self.waittime, self.show)
|
|
||||||
|
|
||||||
def unschedule(self):
|
|
||||||
id_ = self.id
|
|
||||||
self.id = None
|
|
||||||
if id_:
|
|
||||||
self.widget.after_cancel(id_)
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
def tip_pos_calculator(widget, label, tip_delta = (10, 5), pad = (5, 3, 5, 3)):
|
|
||||||
w = widget
|
|
||||||
s_width, s_height = w.winfo_screenwidth(), w.winfo_screenheight()
|
|
||||||
width, height = (pad[0] + label.winfo_reqwidth() + pad[2],
|
|
||||||
pad[1] + label.winfo_reqheight() + pad[3])
|
|
||||||
mouse_x, mouse_y = w.winfo_pointerxy()
|
|
||||||
x1, y1 = mouse_x + tip_delta[0], mouse_y + tip_delta[1]
|
|
||||||
x2, y2 = x1 + width, y1 + height
|
|
||||||
|
|
||||||
x_delta = x2 - s_width
|
|
||||||
if x_delta < 0:
|
|
||||||
x_delta = 0
|
|
||||||
y_delta = y2 - s_height
|
|
||||||
if y_delta < 0:
|
|
||||||
y_delta = 0
|
|
||||||
|
|
||||||
offscreen = (x_delta, y_delta) != (0, 0)
|
|
||||||
|
|
||||||
if offscreen:
|
|
||||||
if x_delta:
|
|
||||||
x1 = mouse_x - tip_delta[0] - width
|
|
||||||
if y_delta:
|
|
||||||
y1 = mouse_y - tip_delta[1] - height
|
|
||||||
|
|
||||||
offscreen_again = y1 < 0 # out on the top
|
|
||||||
|
|
||||||
if offscreen_again:
|
|
||||||
# No further checks will be done.
|
|
||||||
|
|
||||||
# TIP:
|
|
||||||
# A further mod might automagically augment the
|
|
||||||
# wraplength when the tooltip is too high to be
|
|
||||||
# kept inside the screen.
|
|
||||||
y1 = 0
|
|
||||||
|
|
||||||
return x1, y1
|
|
||||||
|
|
||||||
bg = self.bg
|
|
||||||
pad = self.pad
|
|
||||||
widget = self.widget
|
|
||||||
|
|
||||||
# creates a toplevel window
|
|
||||||
self.tw = tk.Toplevel(widget)
|
|
||||||
|
|
||||||
# leaves only the label and removes the app window
|
|
||||||
self.tw.wm_overrideredirect(True)
|
|
||||||
|
|
||||||
win = tk.Frame(self.tw, background = bg, borderwidth = 0)
|
|
||||||
label = ttk.Label(win, text = self.text, justify = tk.LEFT, background = bg, relief = tk.SOLID, borderwidth = 0,
|
|
||||||
wraplength = self.wraplength)
|
|
||||||
label.grid(padx = (pad[0], pad[2]), pady = (pad[1], pad[3]), sticky=tk.NSEW)
|
|
||||||
win.grid()
|
|
||||||
|
|
||||||
x, y = tip_pos_calculator(widget, label)
|
|
||||||
|
|
||||||
self.tw.wm_geometry("+%d+%d" % (x, y))
|
|
||||||
|
|
||||||
def hide(self):
|
|
||||||
tw = self.tw
|
|
||||||
if tw:
|
|
||||||
tw.destroy()
|
|
||||||
self.tw = None
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class TextRedirect(object):
|
|
||||||
class Pretty(object):
|
|
||||||
grpmsg = unformat_message([MsgMap[1], MsgMap[7], MsgMap[12], MsgMap[20]])
|
|
||||||
arrows = [ item[0] for item in grpmsg ]
|
|
||||||
clt_msg_nonewline = [ item[1] for item in grpmsg ]
|
|
||||||
arrows = list(set(arrows))
|
|
||||||
lenarrow = len(arrows[0])
|
|
||||||
srv_msg_nonewline = [ item[0] for item in unformat_message([MsgMap[2], MsgMap[5], MsgMap[13], MsgMap[18]]) ]
|
|
||||||
msg_align = [ msg[0].replace('\t', '').replace('\n', '') for msg in unformat_message([MsgMap[-2], MsgMap[-4]]) ]
|
|
||||||
|
|
||||||
def __init__(self, srv_text_space, clt_text_space, customcolors):
|
|
||||||
self.srv_text_space = srv_text_space
|
|
||||||
self.clt_text_space = clt_text_space
|
|
||||||
self.customcolors = customcolors
|
|
||||||
|
|
||||||
def textbox_write(self, tag, message, color, extras):
|
|
||||||
widget = self.textbox_choose(message)
|
|
||||||
self.w_maxpix, self.h_maxpix = widget.winfo_width(), widget.winfo_height()
|
|
||||||
self.xfont = tkFont.Font(font = widget['font'])
|
|
||||||
widget.configure(state = 'normal')
|
|
||||||
widget.insert('end', self.textbox_format(message), tag)
|
|
||||||
self.textbox_color(tag, widget, color, self.customcolors['black'], extras)
|
|
||||||
widget.after(100, widget.see('end'))
|
|
||||||
widget.configure(state = 'disabled')
|
|
||||||
|
|
||||||
def textbox_choose(self, message):
|
|
||||||
if any(item.startswith('logsrv') for item in [message, self.str_to_print]):
|
|
||||||
self.srv_text_space.focus_set()
|
|
||||||
self.where = "srv"
|
|
||||||
return self.srv_text_space
|
|
||||||
elif any(item.startswith('logclt') for item in [message, self.str_to_print]):
|
|
||||||
self.clt_text_space.focus_set()
|
|
||||||
self.where = "clt"
|
|
||||||
return self.clt_text_space
|
|
||||||
|
|
||||||
def textbox_color(self, tag, widget, forecolor = 'white', backcolor = 'black', extras = []):
|
|
||||||
for extra in extras:
|
|
||||||
if extra == 'bold':
|
|
||||||
self.xfont.configure(weight = "bold")
|
|
||||||
elif extra == 'italic':
|
|
||||||
self.xfont.configure(slant = "italic")
|
|
||||||
elif extra == 'underlined':
|
|
||||||
self.xfont.text_font.configure(underline = True)
|
|
||||||
elif extra == 'strike':
|
|
||||||
self.xfont.configure(overstrike = True)
|
|
||||||
elif extra == 'reverse':
|
|
||||||
forecolor, backcolor = backcolor, forecolor
|
|
||||||
|
|
||||||
widget.tag_configure(tag, foreground = forecolor, background = backcolor, font = self.xfont)
|
|
||||||
widget.tag_add(tag, "insert linestart", "insert lineend")
|
|
||||||
|
|
||||||
def textbox_newline(self, message):
|
|
||||||
if not message.endswith('\n'):
|
|
||||||
return message + '\n'
|
|
||||||
else:
|
|
||||||
return message
|
|
||||||
|
|
||||||
def textbox_format(self, message):
|
|
||||||
# vertical align.
|
|
||||||
self.w_maxpix = self.w_maxpix - 5 # pixel reduction for distance from border.
|
|
||||||
w_fontpix, h_fontpix = (self.xfont.measure('0'), self.xfont.metrics('linespace'))
|
|
||||||
msg_unformat = message.replace('\t', '').replace('\n', '')
|
|
||||||
lenfixed_chars = int((self.w_maxpix / w_fontpix) - len(msg_unformat))
|
|
||||||
|
|
||||||
if message in self.srv_msg_nonewline + self.clt_msg_nonewline:
|
|
||||||
lung = lenfixed_chars - self.lenarrow
|
|
||||||
if message in self.clt_msg_nonewline:
|
|
||||||
message = self.textbox_newline(message)
|
|
||||||
else:
|
|
||||||
lung = lenfixed_chars
|
|
||||||
if (self.where == "srv") or (self.where == "clt" and message not in self.arrows):
|
|
||||||
message = self.textbox_newline(message)
|
|
||||||
# horizontal align.
|
|
||||||
if msg_unformat in self.msg_align:
|
|
||||||
msg_strip = message.lstrip('\n')
|
|
||||||
message = '\n' * (len(message) - len(msg_strip) + TextRedirect.Pretty.newlinecut[0]) + msg_strip
|
|
||||||
TextRedirect.Pretty.newlinecut.pop(0)
|
|
||||||
|
|
||||||
count = Counter(message)
|
|
||||||
countab = (count['\t'] if count['\t'] != 0 else 1)
|
|
||||||
message = message.replace('\t' * countab, ' ' * lung)
|
|
||||||
return message
|
|
||||||
|
|
||||||
def textbox_do(self):
|
|
||||||
msgs, TextRedirect.Pretty.tag_num = unshell_message(self.str_to_print, TextRedirect.Pretty.tag_num)
|
|
||||||
for tag in msgs:
|
|
||||||
self.textbox_write(tag, msgs[tag]['text'], self.customcolors[msgs[tag]['color']], msgs[tag]['extra'])
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def write(self, string):
|
|
||||||
if string != '\n':
|
|
||||||
self.str_to_print = string
|
|
||||||
self.textbox_do()
|
|
||||||
|
|
||||||
class Stderr(Pretty):
|
|
||||||
def __init__(self, srv_text_space, clt_text_space, customcolors, side):
|
|
||||||
self.srv_text_space = srv_text_space
|
|
||||||
self.clt_text_space = clt_text_space
|
|
||||||
self.customcolors = customcolors
|
|
||||||
self.side = side
|
|
||||||
self.tag_err = 'STDERR'
|
|
||||||
self.xfont = tkFont.Font(font = self.srv_text_space['font'])
|
|
||||||
|
|
||||||
def textbox_choose(self, message):
|
|
||||||
if self.side == "srv":
|
|
||||||
return self.srv_text_space
|
|
||||||
elif self.side == "clt":
|
|
||||||
return self.clt_text_space
|
|
||||||
|
|
||||||
def write(self, string):
|
|
||||||
widget = self.textbox_choose(string)
|
|
||||||
self.textbox_color(self.tag_err, widget, self.customcolors['red'], self.customcolors['black'])
|
|
||||||
self.srv_text_space.configure(state = 'normal')
|
|
||||||
self.srv_text_space.insert('end', string, self.tag_err)
|
|
||||||
self.srv_text_space.see('end')
|
|
||||||
self.srv_text_space.configure(state = 'disabled')
|
|
||||||
|
|
||||||
class Log(Pretty):
|
|
||||||
def textbox_format(self, message):
|
|
||||||
if message.startswith('logsrv'):
|
|
||||||
message = message.replace('logsrv ', '')
|
|
||||||
if message.startswith('logclt'):
|
|
||||||
message = message.replace('logclt ', '')
|
|
||||||
return message + '\n'
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
class TextDoubleScroll(tk.Frame):
|
|
||||||
def __init__(self, master, **kwargs):
|
|
||||||
""" Initialize.
|
|
||||||
- horizontal scrollbar
|
|
||||||
- vertical scrollbar
|
|
||||||
- text widget
|
|
||||||
"""
|
|
||||||
tk.Frame.__init__(self, master)
|
|
||||||
self.master = master
|
|
||||||
|
|
||||||
self.textbox = tk.Text(self.master, **kwargs)
|
|
||||||
self.sizegrip = ttk.Sizegrip(self.master)
|
|
||||||
self.hs = ttk.Scrollbar(self.master, orient = "horizontal", command = self.on_scrollbar_x)
|
|
||||||
self.vs = ttk.Scrollbar(self.master, orient = "vertical", command = self.on_scrollbar_y)
|
|
||||||
self.textbox.configure(yscrollcommand = self.on_textscroll, xscrollcommand = self.hs.set)
|
|
||||||
|
|
||||||
def on_scrollbar_x(self, *args):
|
|
||||||
""" Horizontally scrolls text widget. """
|
|
||||||
self.textbox.xview(*args)
|
|
||||||
|
|
||||||
def on_scrollbar_y(self, *args):
|
|
||||||
""" Vertically scrolls text widget. """
|
|
||||||
self.textbox.yview(*args)
|
|
||||||
|
|
||||||
def on_textscroll(self, *args):
|
|
||||||
""" Moves the scrollbar and scrolls text widget when the mousewheel is moved on a text widget. """
|
|
||||||
self.vs.set(*args)
|
|
||||||
self.on_scrollbar_y('moveto', args[0])
|
|
||||||
|
|
||||||
def put(self, **kwargs):
|
|
||||||
""" Grid the scrollbars and textbox correctly. """
|
|
||||||
self.textbox.grid(row = 0, column = 0, padx = 3, pady = 3, sticky = "nsew")
|
|
||||||
self.vs.grid(row = 0, column = 1, sticky = "ns")
|
|
||||||
self.hs.grid(row = 1, column = 0, sticky = "we")
|
|
||||||
self.sizegrip.grid(row = 1, column = 1, sticky = "news")
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
""" Return the "frame" useful to place inner controls. """
|
|
||||||
return self.textbox
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
def custom_background(window):
|
|
||||||
# first level canvas.
|
|
||||||
allwidgets = window.grid_slaves(0,0)[0].grid_slaves() + window.grid_slaves(0,0)[0].place_slaves()
|
|
||||||
widgets_alphalow = [ widget for widget in allwidgets if widget.winfo_class() == 'Canvas']
|
|
||||||
widgets_alphahigh = []
|
|
||||||
# sub-level canvas.
|
|
||||||
for side in ["Srv", "Clt"]:
|
|
||||||
widgets_alphahigh.append(window.pagewidgets[side]["BtnWin"])
|
|
||||||
for position in ["Left", "Right"]:
|
|
||||||
widgets_alphahigh.append(window.pagewidgets[side]["AniWin"][position])
|
|
||||||
for pagename in window.pagewidgets[side]["PageWin"].keys():
|
|
||||||
widgets_alphalow.append(window.pagewidgets[side]["PageWin"][pagename])
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PIL import Image, ImageTk
|
|
||||||
|
|
||||||
# Open Image.
|
|
||||||
img = Image.open(os.path.dirname(os.path.abspath( __file__ )) + "/graphics/pykms_Keys.gif")
|
|
||||||
img = img.convert('RGBA')
|
|
||||||
# Resize image.
|
|
||||||
img.resize((window.winfo_width(), window.winfo_height()), Image.ANTIALIAS)
|
|
||||||
# Put semi-transparent background chunks.
|
|
||||||
window.backcrops_alphalow, window.backcrops_alphahigh = ([] for _ in range(2))
|
|
||||||
|
|
||||||
def cutter(master, image, widgets, crops, alpha):
|
|
||||||
for widget in widgets:
|
|
||||||
x, y, w, h = master.get_position(widget)
|
|
||||||
cropped = image.crop((x, y, x + w, y + h))
|
|
||||||
cropped.putalpha(alpha)
|
|
||||||
crops.append(ImageTk.PhotoImage(cropped))
|
|
||||||
# Not in same loop to prevent reference garbage.
|
|
||||||
for crop, widget in zip(crops, widgets):
|
|
||||||
widget.create_image(1, 1, image = crop, anchor = 'nw')
|
|
||||||
|
|
||||||
cutter(window, img, widgets_alphalow, window.backcrops_alphalow, 36)
|
|
||||||
cutter(window, img, widgets_alphahigh, window.backcrops_alphahigh, 96)
|
|
||||||
|
|
||||||
# Put semi-transparent background overall.
|
|
||||||
img.putalpha(128)
|
|
||||||
window.backimg = ImageTk.PhotoImage(img)
|
|
||||||
window.masterwin.create_image(1, 1, image = window.backimg, anchor = 'nw')
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
for widget in widgets_alphalow + widgets_alphahigh:
|
|
||||||
widget.configure(background = window.customcolors['lavender'])
|
|
||||||
|
|
||||||
# Hide client.
|
|
||||||
window.clt_on_show(force_remove = True)
|
|
||||||
# Show Gui.
|
|
||||||
window.deiconify()
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
class Animation(object):
|
|
||||||
def __init__(self, gifpath, master, widget, loop = False):
|
|
||||||
from PIL import Image, ImageTk, ImageSequence
|
|
||||||
|
|
||||||
self.master = master
|
|
||||||
self.widget = widget
|
|
||||||
self.loop = loop
|
|
||||||
self.cancelid = None
|
|
||||||
self.flagstop = False
|
|
||||||
self.index = 0
|
|
||||||
self.frames = []
|
|
||||||
|
|
||||||
img = Image.open(gifpath)
|
|
||||||
size = img.size
|
|
||||||
for frame in ImageSequence.Iterator(img):
|
|
||||||
static_img = ImageTk.PhotoImage(frame.convert('RGBA'))
|
|
||||||
try:
|
|
||||||
static_img.delay = int(frame.info['duration'])
|
|
||||||
except KeyError:
|
|
||||||
static_img.delay = 100
|
|
||||||
self.frames.append(static_img)
|
|
||||||
|
|
||||||
self.widget.configure(width = size[0], height = size[1])
|
|
||||||
self.initialize()
|
|
||||||
|
|
||||||
def initialize(self):
|
|
||||||
self.widget.configure(image = self.frames[0])
|
|
||||||
self.widget.image = self.frames[0]
|
|
||||||
|
|
||||||
def deanimate(self):
|
|
||||||
while not self.flagstop:
|
|
||||||
pass
|
|
||||||
self.flagstop = False
|
|
||||||
self.index = 0
|
|
||||||
self.widget.configure(relief = "raised")
|
|
||||||
|
|
||||||
def animate(self):
|
|
||||||
frame = self.frames[self.index]
|
|
||||||
self.widget.configure(image = frame, relief = "sunken")
|
|
||||||
self.index += 1
|
|
||||||
self.cancelid = self.master.after(frame.delay, self.animate)
|
|
||||||
if self.index == len(self.frames):
|
|
||||||
if self.loop:
|
|
||||||
self.index = 0
|
|
||||||
else:
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
def start(self, event = None):
|
|
||||||
if str(self.widget['state']) != 'disabled':
|
|
||||||
if self.cancelid is None:
|
|
||||||
if not self.loop:
|
|
||||||
self.btnani_thread = threading.Thread(target = self.deanimate, name = "Thread-BtnAni")
|
|
||||||
self.btnani_thread.setDaemon(True)
|
|
||||||
self.btnani_thread.start()
|
|
||||||
self.cancelid = self.master.after(self.frames[0].delay, self.animate)
|
|
||||||
|
|
||||||
def stop(self, event = None):
|
|
||||||
if self.cancelid:
|
|
||||||
self.master.after_cancel(self.cancelid)
|
|
||||||
self.cancelid = None
|
|
||||||
self.flagstop = True
|
|
||||||
self.initialize()
|
|
||||||
|
|
||||||
|
|
||||||
def custom_pages(window, side):
|
|
||||||
buttons = window.pagewidgets[side]["BtnAni"]
|
|
||||||
labels = window.pagewidgets[side]["LblAni"]
|
|
||||||
|
|
||||||
for position in buttons.keys():
|
|
||||||
buttons[position].config(anchor = "center",
|
|
||||||
font = window.customfonts['btn'],
|
|
||||||
background = window.customcolors['white'],
|
|
||||||
activebackground = window.customcolors['white'],
|
|
||||||
borderwidth = 2)
|
|
||||||
|
|
||||||
try:
|
|
||||||
anibtn = Animation(os.path.dirname(os.path.abspath( __file__ )) + "/graphics/pykms_Keyhole_%s.gif" %position,
|
|
||||||
window, buttons[position], loop = False)
|
|
||||||
anilbl = Animation(os.path.dirname(os.path.abspath( __file__ )) + "/graphics/pykms_Arrow_%s.gif" %position,
|
|
||||||
window, labels[position], loop = True)
|
|
||||||
|
|
||||||
def animationwait(master, button, btn_animation, lbl_animation):
|
|
||||||
while btn_animation.cancelid:
|
|
||||||
pass
|
|
||||||
sleep(1)
|
|
||||||
x, y = master.winfo_pointerxy()
|
|
||||||
if master.winfo_containing(x, y) == button:
|
|
||||||
lbl_animation.start()
|
|
||||||
|
|
||||||
def animationcombo(master, button, btn_animation, lbl_animation):
|
|
||||||
wait_thread = threading.Thread(target = animationwait,
|
|
||||||
args = (master, button, btn_animation, lbl_animation),
|
|
||||||
name = "Thread-WaitAni")
|
|
||||||
wait_thread.setDaemon(True)
|
|
||||||
wait_thread.start()
|
|
||||||
lbl_animation.stop()
|
|
||||||
btn_animation.start()
|
|
||||||
|
|
||||||
buttons[position].bind("<ButtonPress>", lambda event, anim1 = anibtn, anim2 = anilbl,
|
|
||||||
bt = buttons[position], win = window:
|
|
||||||
animationcombo(win, bt, anim1, anim2))
|
|
||||||
buttons[position].bind("<Enter>", anilbl.start)
|
|
||||||
buttons[position].bind("<Leave>", anilbl.stop)
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
buttons[position].config(activebackground = window.customcolors['blue'],
|
|
||||||
foreground = window.customcolors['blue'])
|
|
||||||
labels[position].config(background = window.customcolors['lavender'])
|
|
||||||
|
|
||||||
if position == "Left":
|
|
||||||
buttons[position].config(text = '<<')
|
|
||||||
elif position == "Right":
|
|
||||||
buttons[position].config(text = '>>')
|
|
||||||
|
|
||||||
##-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
class ListboxOfRadiobuttons(tk.Frame):
|
|
||||||
def __init__(self, master, radios, font, changed, **kwargs):
|
|
||||||
tk.Frame.__init__(self, master)
|
|
||||||
|
|
||||||
self.master = master
|
|
||||||
self.radios = radios
|
|
||||||
self.font = font
|
|
||||||
self.changed = changed
|
|
||||||
|
|
||||||
self.scrollv = tk.Scrollbar(self, orient = "vertical")
|
|
||||||
self.textbox = tk.Text(self, yscrollcommand = self.scrollv.set, **kwargs)
|
|
||||||
self.scrollv.config(command = self.textbox.yview)
|
|
||||||
# layout.
|
|
||||||
self.scrollv.pack(side = "right", fill = "y")
|
|
||||||
self.textbox.pack(side = "left", fill = "both", expand = True)
|
|
||||||
# create radiobuttons.
|
|
||||||
self.radiovar = tk.StringVar()
|
|
||||||
self.radiovar.set('FILE')
|
|
||||||
self.create()
|
|
||||||
|
|
||||||
def create(self):
|
|
||||||
self.rdbtns = []
|
|
||||||
for n, nameradio in enumerate(self.radios):
|
|
||||||
rdbtn = tk.Radiobutton(self, text = nameradio, value = nameradio, variable = self.radiovar,
|
|
||||||
font = self.font, indicatoron = 0, width = 15,
|
|
||||||
borderwidth = 3, selectcolor = 'yellow', command = self.change)
|
|
||||||
self.textbox.window_create("end", window = rdbtn)
|
|
||||||
# to force one checkbox per line
|
|
||||||
if n != len(self.radios) - 1:
|
|
||||||
self.textbox.insert("end", "\n")
|
|
||||||
self.rdbtns.append(rdbtn)
|
|
||||||
self.textbox.configure(state = "disabled")
|
|
||||||
|
|
||||||
def change(self):
|
|
||||||
st = self.state()
|
|
||||||
for widget, default in self.changed:
|
|
||||||
wclass = widget.winfo_class()
|
|
||||||
if st in ['STDOUT', 'FILEOFF']:
|
|
||||||
if wclass == 'Entry':
|
|
||||||
widget.delete(0, 'end')
|
|
||||||
widget.configure(state = "disabled")
|
|
||||||
elif wclass == 'TCombobox':
|
|
||||||
if st == 'STDOUT':
|
|
||||||
widget.set(default)
|
|
||||||
widget.configure(state = "readonly")
|
|
||||||
elif st == 'FILEOFF':
|
|
||||||
widget.set('')
|
|
||||||
widget.configure(state = "disabled")
|
|
||||||
elif st in ['FILE', 'FILESTDOUT', 'STDOUTOFF']:
|
|
||||||
if wclass == 'Entry':
|
|
||||||
widget.configure(state = "normal")
|
|
||||||
widget.delete(0, 'end')
|
|
||||||
widget.insert('end', default)
|
|
||||||
widget.xview_moveto(1)
|
|
||||||
elif wclass == 'TCombobox':
|
|
||||||
widget.configure(state = "readonly")
|
|
||||||
widget.set(default)
|
|
||||||
elif wclass == 'Button':
|
|
||||||
widget.configure(state = "normal")
|
|
||||||
|
|
||||||
def configure(self, state):
|
|
||||||
for rb in self.rdbtns:
|
|
||||||
rb.configure(state = state)
|
|
||||||
|
|
||||||
def state(self):
|
|
||||||
return self.radiovar.get()
|
|
|
@ -194,9 +194,6 @@ def logger_create(log_obj, config, mode = 'a'):
|
||||||
frmt_name = '%(name)s '
|
frmt_name = '%(name)s '
|
||||||
|
|
||||||
from pykms_Server import serverthread
|
from pykms_Server import serverthread
|
||||||
if serverthread.with_gui:
|
|
||||||
frmt_std = frmt_name + frmt_std
|
|
||||||
frmt_min = frmt_name + frmt_min
|
|
||||||
|
|
||||||
def apply_formatter(levelnum, formats, handler, color = False):
|
def apply_formatter(levelnum, formats, handler, color = False):
|
||||||
levelformdict = {}
|
levelformdict = {}
|
||||||
|
@ -521,7 +518,7 @@ def check_setup(config, options, logger, where):
|
||||||
# Check logfile.
|
# Check logfile.
|
||||||
config['logfile'] = check_logfile(config['logfile'], options['lfile']['def'], where = where)
|
config['logfile'] = check_logfile(config['logfile'], options['lfile']['def'], where = where)
|
||||||
|
|
||||||
# Check logsize (py-kms Gui).
|
# Check logsize
|
||||||
if config['logsize'] == "":
|
if config['logsize'] == "":
|
||||||
if any(opt in ['STDOUT', 'FILEOFF'] for opt in config['logfile']):
|
if any(opt in ['STDOUT', 'FILEOFF'] for opt in config['logfile']):
|
||||||
# set a recognized size never used.
|
# set a recognized size never used.
|
||||||
|
@ -530,7 +527,7 @@ def check_setup(config, options, logger, where):
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}argument `-S/--logsize`: invalid with: '%s'. Exiting...{end}" %config['logsize'],
|
pretty_printer(put_text = "{reverse}{red}{bold}argument `-S/--logsize`: invalid with: '%s'. Exiting...{end}" %config['logsize'],
|
||||||
where = where, to_exit = True)
|
where = where, to_exit = True)
|
||||||
|
|
||||||
# Check loglevel (py-kms Gui).
|
# Check loglevel
|
||||||
if config['loglevel'] == "":
|
if config['loglevel'] == "":
|
||||||
# set a recognized level never used.
|
# set a recognized level never used.
|
||||||
config['loglevel'] = 'ERROR'
|
config['loglevel'] = 'ERROR'
|
||||||
|
|
|
@ -9,22 +9,20 @@ import uuid
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import pickle
|
|
||||||
import socketserver
|
import socketserver
|
||||||
import queue as Queue
|
import queue as Queue
|
||||||
import selectors
|
import selectors
|
||||||
from tempfile import gettempdir
|
|
||||||
from time import monotonic as time
|
from time import monotonic as time
|
||||||
|
|
||||||
import pykms_RpcBind, pykms_RpcRequest
|
import pykms_RpcBind, pykms_RpcRequest
|
||||||
from pykms_RpcBase import rpcBase
|
from pykms_RpcBase import rpcBase
|
||||||
from pykms_Dcerpc import MSRPCHeader
|
from pykms_Dcerpc import MSRPCHeader
|
||||||
from pykms_Misc import check_setup, check_lcid, check_dir, check_other
|
from pykms_Misc import check_setup, check_lcid, check_other
|
||||||
from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp
|
from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp
|
||||||
from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals, kms_parser_check_connect
|
from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals, kms_parser_check_connect
|
||||||
from pykms_Format import enco, deco, pretty_printer, justify
|
from pykms_Format import enco, deco, pretty_printer, justify
|
||||||
from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job
|
|
||||||
from pykms_Connect import MultipleListener
|
from pykms_Connect import MultipleListener
|
||||||
|
from pykms_Sql import sql_initialize
|
||||||
|
|
||||||
srv_version = "py-kms_2020-10-01"
|
srv_version = "py-kms_2020-10-01"
|
||||||
__license__ = "The Unlicense"
|
__license__ = "The Unlicense"
|
||||||
|
@ -135,7 +133,8 @@ class server_thread(threading.Thread):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.server = None
|
self.server = None
|
||||||
self.is_running_server, self.with_gui, self.checked = [False for _ in range(3)]
|
self.is_running_server = False
|
||||||
|
self.checked = False
|
||||||
self.is_running_thread = threading.Event()
|
self.is_running_thread = threading.Event()
|
||||||
|
|
||||||
def terminate_serve(self):
|
def terminate_serve(self):
|
||||||
|
@ -170,13 +169,7 @@ class server_thread(threading.Thread):
|
||||||
self.server.pykms_serve()
|
self.server.pykms_serve()
|
||||||
except (SystemExit, Exception) as e:
|
except (SystemExit, Exception) as e:
|
||||||
self.eject = True
|
self.eject = True
|
||||||
if not self.with_gui:
|
raise
|
||||||
raise
|
|
||||||
else:
|
|
||||||
if isinstance(e, SystemExit):
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
##---------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -184,7 +177,7 @@ loggersrv = logging.getLogger('logsrv')
|
||||||
|
|
||||||
# 'help' string - 'default' value - 'dest' string.
|
# 'help' string - 'default' value - 'dest' string.
|
||||||
srv_options = {
|
srv_options = {
|
||||||
'ip' : {'help' : 'The IP address (IPv4 or IPv6) to listen on. The default is \"0.0.0.0\" (all interfaces).', 'def' : "0.0.0.0", 'des' : "ip"},
|
'ip' : {'help' : 'The IP address (IPv4 or IPv6) to listen on. The default is \"::\" (all interfaces).', 'def' : "::", 'des' : "ip"},
|
||||||
'port' : {'help' : 'The network port to listen on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
|
'port' : {'help' : 'The network port to listen on. The default is \"1688\".', 'def' : 1688, 'des' : "port"},
|
||||||
'epid' : {'help' : 'Use this option to manually specify an ePID to use. If no ePID is specified, a random ePID will be auto generated.',
|
'epid' : {'help' : 'Use this option to manually specify an ePID to use. If no ePID is specified, a random ePID will be auto generated.',
|
||||||
'def' : None, 'des' : "epid"},
|
'def' : None, 'des' : "epid"},
|
||||||
|
@ -196,8 +189,7 @@ for server OSes and Office >=5', 'def' : None, 'des' : "clientcount"},
|
||||||
'def' : 120, 'des': "activation"},
|
'def' : 120, 'des': "activation"},
|
||||||
'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).',
|
'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).',
|
||||||
'def' : 1440 * 7, 'des' : "renewal"},
|
'def' : 1440 * 7, 'des' : "renewal"},
|
||||||
'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default. \
|
'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default.', 'def' : False,
|
||||||
If enabled the default .db file is \"pykms_database.db\". You can also provide a specific location.', 'def' : False,
|
|
||||||
'file': os.path.join('.', 'pykms_database.db'), 'des' : "sqlite"},
|
'file': os.path.join('.', 'pykms_database.db'), 'des' : "sqlite"},
|
||||||
'hwid' : {'help' : 'Use this option to specify a HWID. The HWID must be an 16-character string of hex characters. \
|
'hwid' : {'help' : 'Use this option to specify a HWID. The HWID must be an 16-character string of hex characters. \
|
||||||
The default is \"364F463A8863D35F\" or type \"RANDOM\" to auto generate the HWID.',
|
The default is \"364F463A8863D35F\" or type \"RANDOM\" to auto generate the HWID.',
|
||||||
|
@ -220,7 +212,7 @@ Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to
|
||||||
'reuse' : {'help' : 'Do not allows binding / listening to the same address and port. Reusing port is activated by default.', 'def' : True,
|
'reuse' : {'help' : 'Do not allows binding / listening to the same address and port. Reusing port is activated by default.', 'def' : True,
|
||||||
'des': "reuse"},
|
'des': "reuse"},
|
||||||
'dual' : {'help' : 'Allows listening to an IPv6 address also accepting connections via IPv4. Deactivated by default.',
|
'dual' : {'help' : 'Allows listening to an IPv6 address also accepting connections via IPv4. Deactivated by default.',
|
||||||
'def' : False, 'des': "dual"}
|
'def' : True, 'des': "dual"}
|
||||||
}
|
}
|
||||||
|
|
||||||
def server_options():
|
def server_options():
|
||||||
|
@ -256,15 +248,6 @@ def server_options():
|
||||||
|
|
||||||
server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
|
||||||
|
|
||||||
## Daemon (Etrigan) parsing.
|
|
||||||
daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False)
|
|
||||||
daemon_subparser = daemon_parser.add_subparsers(dest = "mode")
|
|
||||||
|
|
||||||
etrigan_parser = daemon_subparser.add_parser("etrigan", add_help = False)
|
|
||||||
etrigan_parser.add_argument("-g", "--gui", action = "store_const", dest = 'gui', const = True, default = False,
|
|
||||||
help = "Enable py-kms GUI usage.")
|
|
||||||
etrigan_parser = Etrigan_parser(parser = etrigan_parser)
|
|
||||||
|
|
||||||
## Connection parsing.
|
## Connection parsing.
|
||||||
connection_parser = KmsParser(description = "connect options", add_help = False)
|
connection_parser = KmsParser(description = "connect options", add_help = False)
|
||||||
connection_subparser = connection_parser.add_subparsers(dest = "mode")
|
connection_subparser = connection_parser.add_subparsers(dest = "mode")
|
||||||
|
@ -284,16 +267,14 @@ def server_options():
|
||||||
|
|
||||||
# Run help.
|
# Run help.
|
||||||
if any(arg in ["-h", "--help"] for arg in userarg):
|
if any(arg in ["-h", "--help"] for arg in userarg):
|
||||||
KmsParserHelp().printer(parsers = [server_parser, (daemon_parser, etrigan_parser),
|
KmsParserHelp().printer(parsers = [server_parser, (connection_parser, connect_parser)])
|
||||||
(connection_parser, connect_parser)])
|
|
||||||
|
|
||||||
# Get stored arguments.
|
# Get stored arguments.
|
||||||
pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser)
|
pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser)
|
||||||
etrigan_zeroarg, etrigan_onearg = kms_parser_get(etrigan_parser)
|
|
||||||
connect_zeroarg, connect_onearg = kms_parser_get(connect_parser)
|
connect_zeroarg, connect_onearg = kms_parser_get(connect_parser)
|
||||||
subdict = {'etrigan' : (etrigan_zeroarg, etrigan_onearg, daemon_parser.parse_args),
|
subdict = {
|
||||||
'connect' : (connect_zeroarg, connect_onearg, connection_parser.parse_args)
|
'connect' : (connect_zeroarg, connect_onearg, connection_parser.parse_args)
|
||||||
}
|
}
|
||||||
subpars = list(subdict.keys())
|
subpars = list(subdict.keys())
|
||||||
pykmssrv_zeroarg += subpars # add subparsers
|
pykmssrv_zeroarg += subpars # add subparsers
|
||||||
|
|
||||||
|
@ -309,14 +290,7 @@ def server_options():
|
||||||
if subindx:
|
if subindx:
|
||||||
# Set `daemon options` and/or `connect options` for server dict config.
|
# Set `daemon options` and/or `connect options` for server dict config.
|
||||||
# example cases:
|
# example cases:
|
||||||
# 1 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] etrigan daemon_positional [--daemon_optionals] \
|
# 1 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals]
|
||||||
# connect [--connect_optionals]
|
|
||||||
#
|
|
||||||
# 2 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals] etrigan \
|
|
||||||
# daemon_positional [--daemon_optionals]
|
|
||||||
#
|
|
||||||
# 3 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] etrigan daemon_positional [--daemon_optionals]
|
|
||||||
# 4 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals] connect [--connect_optionals]
|
|
||||||
first = subindx[0][0]
|
first = subindx[0][0]
|
||||||
# initial.
|
# initial.
|
||||||
kms_parser_check_optionals(userarg[0 : first], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
|
kms_parser_check_optionals(userarg[0 : first], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
|
||||||
|
@ -338,7 +312,7 @@ def server_options():
|
||||||
else:
|
else:
|
||||||
# Update `pykms options` for server dict config.
|
# Update `pykms options` for server dict config.
|
||||||
# example case:
|
# example case:
|
||||||
# 5 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals]
|
# 2 python3 pykms_Server.py [1.2.3.4] [1234] [--pykms_optionals]
|
||||||
kms_parser_check_optionals(userarg, pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
|
kms_parser_check_optionals(userarg, pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = exclude_kms)
|
||||||
kms_parser_check_positionals(srv_config, server_parser.parse_args)
|
kms_parser_check_positionals(srv_config, server_parser.parse_args)
|
||||||
|
|
||||||
|
@ -347,63 +321,6 @@ def server_options():
|
||||||
except KmsParserException as e:
|
except KmsParserException as e:
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
|
pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True)
|
||||||
|
|
||||||
class Etrigan_Check(Etrigan_check):
|
|
||||||
def emit_opt_err(self, msg):
|
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}%s{end}" %msg, to_exit = True)
|
|
||||||
|
|
||||||
class Etrigan(Etrigan):
|
|
||||||
def emit_message(self, message, to_exit = False):
|
|
||||||
if not self.mute:
|
|
||||||
pretty_printer(put_text = "{reverse}{green}{bold}%s{end}" %message)
|
|
||||||
if to_exit:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def emit_error(self, message, to_exit = True):
|
|
||||||
if not self.mute:
|
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}%s{end}" %message, to_exit = True)
|
|
||||||
|
|
||||||
def server_daemon():
|
|
||||||
if 'etrigan' in srv_config.values():
|
|
||||||
path = os.path.join(gettempdir(), 'pykms_config.pickle')
|
|
||||||
|
|
||||||
if srv_config['operation'] in ['stop', 'restart', 'status'] and len(sys.argv[1:]) > 2:
|
|
||||||
pretty_printer(put_text = "{reverse}{red}{bold}too much arguments with etrigan '%s'. Exiting...{end}" %srv_config['operation'],
|
|
||||||
to_exit = True)
|
|
||||||
|
|
||||||
# Check file arguments.
|
|
||||||
Etrigan_Check().checkfile(srv_config['etriganpid'], '--etrigan-pid', '.pid')
|
|
||||||
Etrigan_Check().checkfile(srv_config['etriganlog'], '--etrigan-log', '.log')
|
|
||||||
|
|
||||||
if srv_config['gui']:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if srv_config['operation'] == 'start':
|
|
||||||
with open(path, 'wb') as file:
|
|
||||||
pickle.dump(srv_config, file, protocol = pickle.HIGHEST_PROTOCOL)
|
|
||||||
elif srv_config['operation'] in ['stop', 'status', 'restart']:
|
|
||||||
with open(path, 'rb') as file:
|
|
||||||
old_srv_config = pickle.load(file)
|
|
||||||
old_srv_config = {x: old_srv_config[x] for x in old_srv_config if x not in ['operation']}
|
|
||||||
srv_config.update(old_srv_config)
|
|
||||||
|
|
||||||
serverdaemon = Etrigan(srv_config['etriganpid'],
|
|
||||||
logfile = srv_config['etriganlog'], loglevel = srv_config['etriganlev'],
|
|
||||||
mute = srv_config['etriganmute'], pause_loop = None)
|
|
||||||
|
|
||||||
if srv_config['operation'] in ['start', 'restart']:
|
|
||||||
serverdaemon.want_quit = True
|
|
||||||
if srv_config['gui']:
|
|
||||||
serverdaemon.funcs_to_daemonize = [server_with_gui]
|
|
||||||
else:
|
|
||||||
server_without_gui = ServerWithoutGui()
|
|
||||||
serverdaemon.funcs_to_daemonize = [server_without_gui.start, server_without_gui.join]
|
|
||||||
indx_for_clean = lambda: (0, )
|
|
||||||
serverdaemon.quit_on_stop = [indx_for_clean, server_without_gui.clean]
|
|
||||||
elif srv_config['operation'] == 'stop':
|
|
||||||
os.remove(path)
|
|
||||||
|
|
||||||
Etrigan_job(srv_config['operation'], serverdaemon)
|
|
||||||
|
|
||||||
def server_check():
|
def server_check():
|
||||||
# Setup and some checks.
|
# Setup and some checks.
|
||||||
check_setup(srv_config, srv_options, loggersrv, where = "srv")
|
check_setup(srv_config, srv_options, loggersrv, where = "srv")
|
||||||
|
@ -445,25 +362,25 @@ def server_check():
|
||||||
|
|
||||||
# Check sqlite.
|
# Check sqlite.
|
||||||
if srv_config['sqlite']:
|
if srv_config['sqlite']:
|
||||||
if isinstance(srv_config['sqlite'], str):
|
if srv_config['sqlite'] is True: # Resolve bool to the default path
|
||||||
check_dir(srv_config['sqlite'], 'srv', log_obj = loggersrv.error, argument = '-s/--sqlite')
|
|
||||||
elif srv_config['sqlite'] is True:
|
|
||||||
srv_config['sqlite'] = srv_options['sql']['file']
|
srv_config['sqlite'] = srv_options['sql']['file']
|
||||||
|
if os.path.isdir(srv_config['sqlite']):
|
||||||
|
pretty_printer(log_obj = loggersrv.warning,
|
||||||
|
put_text = "{reverse}{yellow}{bold}You specified a folder instead of a database file! This behavior is not officially supported anymore, please change your start parameters soon.{end}")
|
||||||
|
srv_config['sqlite'] = os.path.join(srv_config['sqlite'], 'pykms_database.db')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
sql_initialize(srv_config['sqlite'])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pretty_printer(log_obj = loggersrv.warning,
|
pretty_printer(log_obj = loggersrv.warning,
|
||||||
put_text = "{reverse}{yellow}{bold}Module 'sqlite3' not installed, database support disabled.{end}")
|
put_text = "{reverse}{yellow}{bold}Module 'sqlite3' not installed, database support disabled.{end}")
|
||||||
srv_config['sqlite'] = False
|
srv_config['sqlite'] = False
|
||||||
|
|
||||||
# Check other specific server options.
|
# Check other specific server options.
|
||||||
opts = [('clientcount', '-c/--client-count'),
|
opts = [('clientcount', '-c/--client-count'),
|
||||||
('timeoutidle', '-t0/--timeout-idle'),
|
('timeoutidle', '-t0/--timeout-idle'),
|
||||||
('timeoutsndrcv', '-t1/--timeout-sndrcv')]
|
('timeoutsndrcv', '-t1/--timeout-sndrcv')]
|
||||||
if serverthread.with_gui:
|
|
||||||
opts += [('activation', '-a/--activation-interval'),
|
|
||||||
('renewal', '-r/--renewal-interval')]
|
|
||||||
check_other(srv_config, opts, loggersrv, where = 'srv')
|
check_other(srv_config, opts, loggersrv, where = 'srv')
|
||||||
|
|
||||||
# Check further addresses / ports.
|
# Check further addresses / ports.
|
||||||
|
@ -543,35 +460,14 @@ def server_main_terminal():
|
||||||
server_check()
|
server_check()
|
||||||
serverthread.checked = True
|
serverthread.checked = True
|
||||||
|
|
||||||
if 'etrigan' not in srv_config.values():
|
# Run threaded server.
|
||||||
# (without GUI) and (without daemon).
|
serverqueue.put('start')
|
||||||
# Run threaded server.
|
# Wait to finish.
|
||||||
serverqueue.put('start')
|
try:
|
||||||
# Wait to finish.
|
while serverthread.is_alive():
|
||||||
try:
|
serverthread.join(timeout = 0.5)
|
||||||
while serverthread.is_alive():
|
except (KeyboardInterrupt, SystemExit):
|
||||||
serverthread.join(timeout = 0.5)
|
server_terminate(serverthread, exit_server = True, exit_thread = True)
|
||||||
except (KeyboardInterrupt, SystemExit):
|
|
||||||
server_terminate(serverthread, exit_server = True, exit_thread = True)
|
|
||||||
else:
|
|
||||||
# (with or without GUI) and (with daemon)
|
|
||||||
# Setup daemon (eventually).
|
|
||||||
pretty_printer(log_obj = loggersrv.warning, put_text = "{reverse}{yellow}{bold}Etrigan support is deprecated and will be removed in the future!{end}")
|
|
||||||
server_daemon()
|
|
||||||
|
|
||||||
def server_with_gui():
|
|
||||||
import pykms_GuiBase
|
|
||||||
|
|
||||||
pretty_printer(log_obj = loggersrv.warning, put_text = "{reverse}{yellow}{bold}Etrigan GUI support is deprecated and will be removed in the future!{end}")
|
|
||||||
|
|
||||||
root = pykms_GuiBase.KmsGui()
|
|
||||||
root.title(pykms_GuiBase.gui_description + ' (' + pykms_GuiBase.gui_version + ')')
|
|
||||||
root.mainloop()
|
|
||||||
|
|
||||||
def server_main_no_terminal():
|
|
||||||
# Run tkinter GUI.
|
|
||||||
# (with GUI) and (without daemon).
|
|
||||||
server_with_gui()
|
|
||||||
|
|
||||||
class kmsServerHandler(socketserver.BaseRequestHandler):
|
class kmsServerHandler(socketserver.BaseRequestHandler):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
|
@ -636,10 +532,4 @@ serverthread.daemon = True
|
||||||
serverthread.start()
|
serverthread.start()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if sys.stdout.isatty():
|
server_main_terminal()
|
||||||
server_main_terminal()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
server_main_no_terminal()
|
|
||||||
except:
|
|
||||||
server_main_terminal()
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import datetime
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -23,17 +24,35 @@ def sql_initialize(dbName):
|
||||||
try:
|
try:
|
||||||
con = sqlite3.connect(dbName)
|
con = sqlite3.connect(dbName)
|
||||||
cur = con.cursor()
|
cur = con.cursor()
|
||||||
cur.execute("CREATE TABLE clients(clientMachineId TEXT, machineName TEXT, applicationId TEXT, skuId TEXT, \
|
cur.execute("CREATE TABLE clients(clientMachineId TEXT , machineName TEXT, applicationId TEXT, skuId TEXT, licenseStatus TEXT, lastRequestTime INTEGER, kmsEpid TEXT, requestCount INTEGER, PRIMARY KEY(clientMachineId, applicationId))")
|
||||||
licenseStatus TEXT, lastRequestTime INTEGER, kmsEpid TEXT, requestCount INTEGER)")
|
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
pretty_printer(log_obj = loggersrv.error, to_exit = True,
|
pretty_printer(log_obj = loggersrv.error, to_exit = True, put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
|
||||||
put_text = "{reverse}{red}{bold}Sqlite Error: %s. Exiting...{end}" %str(e))
|
|
||||||
finally:
|
finally:
|
||||||
if con:
|
if con:
|
||||||
con.commit()
|
con.commit()
|
||||||
con.close()
|
con.close()
|
||||||
|
|
||||||
|
def sql_get_all(dbName):
|
||||||
|
if not os.path.isfile(dbName):
|
||||||
|
return None
|
||||||
|
with sqlite3.connect(dbName) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
cur.execute("SELECT * FROM clients")
|
||||||
|
clients = []
|
||||||
|
for row in cur.fetchall():
|
||||||
|
clients.append({
|
||||||
|
'clientMachineId': row[0],
|
||||||
|
'machineName': row[1],
|
||||||
|
'applicationId': row[2],
|
||||||
|
'skuId': row[3],
|
||||||
|
'licenseStatus': row[4],
|
||||||
|
'lastRequestTime': datetime.datetime.fromtimestamp(row[5]).isoformat(),
|
||||||
|
'kmsEpid': row[6],
|
||||||
|
'requestCount': row[7]
|
||||||
|
})
|
||||||
|
return clients
|
||||||
|
|
||||||
def sql_update(dbName, infoDict):
|
def sql_update(dbName, infoDict):
|
||||||
con = None
|
con = None
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
import os, uuid, datetime
|
||||||
|
from flask import Flask, render_template
|
||||||
|
from pykms_Sql import sql_get_all
|
||||||
|
from pykms_DB2Dict import kmsDB2Dict
|
||||||
|
|
||||||
|
def _random_uuid():
|
||||||
|
return str(uuid.uuid4()).replace('-', '_')
|
||||||
|
|
||||||
|
_serve_count = 0
|
||||||
|
def _increase_serve_count():
|
||||||
|
global _serve_count
|
||||||
|
_serve_count += 1
|
||||||
|
|
||||||
|
def _get_serve_count():
|
||||||
|
return _serve_count
|
||||||
|
|
||||||
|
_kms_items = None
|
||||||
|
_kms_items_ignored = None
|
||||||
|
def _get_kms_items_cache():
|
||||||
|
global _kms_items, _kms_items_ignored
|
||||||
|
if _kms_items is None:
|
||||||
|
_kms_items = {}
|
||||||
|
_kms_items_ignored = 0
|
||||||
|
queue = [kmsDB2Dict()]
|
||||||
|
while len(queue):
|
||||||
|
item = queue.pop(0)
|
||||||
|
if isinstance(item, list):
|
||||||
|
for i in item:
|
||||||
|
queue.append(i)
|
||||||
|
elif isinstance(item, dict):
|
||||||
|
if 'KmsItems' in item:
|
||||||
|
queue.append(item['KmsItems'])
|
||||||
|
elif 'SkuItems' in item:
|
||||||
|
queue.append(item['SkuItems'])
|
||||||
|
elif 'Gvlk' in item:
|
||||||
|
if len(item['Gvlk']):
|
||||||
|
_kms_items[item['DisplayName']] = item['Gvlk']
|
||||||
|
else:
|
||||||
|
_kms_items_ignored += 1
|
||||||
|
#else:
|
||||||
|
# print(item)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f'Unknown type: {type(item)}')
|
||||||
|
return _kms_items, _kms_items_ignored
|
||||||
|
|
||||||
|
app = Flask('pykms_webui')
|
||||||
|
app.jinja_env.globals['start_time'] = datetime.datetime.now()
|
||||||
|
app.jinja_env.globals['get_serve_count'] = _get_serve_count
|
||||||
|
app.jinja_env.globals['random_uuid'] = _random_uuid
|
||||||
|
app.jinja_env.globals['version_info'] = None
|
||||||
|
|
||||||
|
_version_info_path = os.environ.get('PYKMS_VERSION_PATH', '../VERSION')
|
||||||
|
if os.path.exists(_version_info_path):
|
||||||
|
with open(_version_info_path, 'r') as f:
|
||||||
|
app.jinja_env.globals['version_info'] = {
|
||||||
|
'hash': f.readline().strip(),
|
||||||
|
'branch': f.readline().strip()
|
||||||
|
}
|
||||||
|
|
||||||
|
_dbEnvVarName = 'PYKMS_SQLITE_DB_PATH'
|
||||||
|
def _env_check():
|
||||||
|
if _dbEnvVarName not in os.environ:
|
||||||
|
raise Exception(f'Environment variable is not set: {_dbEnvVarName}')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def root():
|
||||||
|
_increase_serve_count()
|
||||||
|
error = None
|
||||||
|
# Get the db name / path
|
||||||
|
dbPath = None
|
||||||
|
if _dbEnvVarName in os.environ:
|
||||||
|
dbPath = os.environ.get(_dbEnvVarName)
|
||||||
|
else:
|
||||||
|
error = f'Environment variable is not set: {_dbEnvVarName}'
|
||||||
|
# Fetch all clients from the database.
|
||||||
|
clients = None
|
||||||
|
try:
|
||||||
|
if dbPath:
|
||||||
|
clients = sql_get_all(dbPath)
|
||||||
|
except Exception as e:
|
||||||
|
error = f'Error while loading database: {e}'
|
||||||
|
countClients = len(clients) if clients else 0
|
||||||
|
countClientsWindows = len([c for c in clients if c['applicationId'] == 'Windows']) if clients else 0
|
||||||
|
countClientsOffice = countClients - countClientsWindows
|
||||||
|
return render_template(
|
||||||
|
'clients.html',
|
||||||
|
path='/',
|
||||||
|
error=error,
|
||||||
|
clients=clients,
|
||||||
|
count_clients=countClients,
|
||||||
|
count_clients_windows=countClientsWindows,
|
||||||
|
count_clients_office=countClientsOffice,
|
||||||
|
count_projects=len(_get_kms_items_cache()[0])
|
||||||
|
), 200 if error is None else 500
|
||||||
|
|
||||||
|
@app.route('/readyz')
|
||||||
|
def readyz():
|
||||||
|
try:
|
||||||
|
_env_check()
|
||||||
|
except Exception as e:
|
||||||
|
return f'Whooops! {e}', 503
|
||||||
|
if (datetime.datetime.now() - app.jinja_env.globals['start_time']).seconds > 10: # Wait 10 seconds before accepting requests
|
||||||
|
return 'OK', 200
|
||||||
|
else:
|
||||||
|
return 'Not ready', 503
|
||||||
|
|
||||||
|
@app.route('/livez')
|
||||||
|
def livez():
|
||||||
|
try:
|
||||||
|
_env_check()
|
||||||
|
return 'OK', 200 # There are no checks for liveness, so we just return OK
|
||||||
|
except Exception as e:
|
||||||
|
return f'Whooops! {e}', 503
|
||||||
|
|
||||||
|
@app.route('/license')
|
||||||
|
def license():
|
||||||
|
_increase_serve_count()
|
||||||
|
with open(os.environ.get('PYKMS_LICENSE_PATH', '../LICENSE'), 'r') as f:
|
||||||
|
return render_template(
|
||||||
|
'license.html',
|
||||||
|
path='/license/',
|
||||||
|
license=f.read()
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route('/products')
|
||||||
|
def products():
|
||||||
|
_increase_serve_count()
|
||||||
|
items, ignored = _get_kms_items_cache()
|
||||||
|
countProducts = len(items)
|
||||||
|
countProductsWindows = len([i for i in items if 'windows' in i.lower()])
|
||||||
|
countProductsOffice = len([i for i in items if 'office' in i.lower()])
|
||||||
|
return render_template(
|
||||||
|
'products.html',
|
||||||
|
path='/products/',
|
||||||
|
products=items,
|
||||||
|
filtered=ignored,
|
||||||
|
count_products=countProducts,
|
||||||
|
count_products_windows=countProductsWindows,
|
||||||
|
count_products_office=countProductsOffice
|
||||||
|
)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,56 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>py-kms {% block title %}{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename= 'css/bulma.min.css') }}">
|
||||||
|
<style>
|
||||||
|
#content {
|
||||||
|
margin: 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
{% if path != '/' %}
|
||||||
|
div.backtohome {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
{% block style %}{% endblock %}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
{% if path != '/' %}
|
||||||
|
<div class="block backtohome">
|
||||||
|
<a class="button is-normal is-responsive" href="/">
|
||||||
|
Back to home
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="content has-text-centered">
|
||||||
|
<p>
|
||||||
|
<strong>py-kms</strong> is online since <span class="convert_timestamp">{{ start_time }}</span>.
|
||||||
|
This instance was accessed {{ get_serve_count() }} times. View this softwares license <a href="/license">here</a>.
|
||||||
|
{% if version_info %}
|
||||||
|
<br>This instance is running version "{{ version_info['hash'] }}" from branch "{{ version_info['branch'] }}" of py-kms.
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
for(let element of document.getElementsByClassName('convert_timestamp')) {
|
||||||
|
element.innerText = new Date(element.innerText).toLocaleString();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,103 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}clients{% endblock %}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
th {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if error %}
|
||||||
|
<article class="message is-danger">
|
||||||
|
<div class="message-header">
|
||||||
|
Whoops! Something went wrong...
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% else %}
|
||||||
|
<nav class="level">
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Clients</p>
|
||||||
|
<p class="title">{{ count_clients }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Windows</p>
|
||||||
|
<p class="title">{{ count_clients_windows }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Office</p>
|
||||||
|
<p class="title">{{ count_clients_office }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Products</p>
|
||||||
|
<p class="title"><a href="/products">{{ count_projects }}</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{% if clients %}
|
||||||
|
<table class="table is-striped is-hoverable is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Client ID</th>
|
||||||
|
<th>Machine Name</th>
|
||||||
|
<th>Application ID</th>
|
||||||
|
<th><abbr title="Stock Keeping Unit">SKU</abbr> ID</th>
|
||||||
|
<th>License Status</th>
|
||||||
|
<th>Last Seen</th>
|
||||||
|
<th>KMS <abbr title="Enhanced Privacy ID">EPID</abbr></th>
|
||||||
|
<th>Seen Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for client in clients %}
|
||||||
|
<tr>
|
||||||
|
<th><pre class="clientMachineId">{{ client.clientMachineId }}</pre></th>
|
||||||
|
<td class="machineName">
|
||||||
|
{% if client.machineName | length > 16 %}
|
||||||
|
<abbr title="{{ client.machineName }}">{{ client.machineName | truncate(16, True, '...') }}</abbr>
|
||||||
|
{% else %}
|
||||||
|
{{ client.machineName }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ client.applicationId }}</td>
|
||||||
|
<td>{{ client.skuId }}</td>
|
||||||
|
<td>{{ client.licenseStatus }}</td>
|
||||||
|
<td class="convert_timestamp">{{ client.lastRequestTime }}</td>
|
||||||
|
<td>
|
||||||
|
{% if client.kmsEpid | length > 16 %}
|
||||||
|
<abbr title="{{ client.kmsEpid }}">{{ client.kmsEpid | truncate(16, True, '...') }}</abbr>
|
||||||
|
{% else %}
|
||||||
|
{{ client.kmsEpid }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ client.requestCount }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<article class="message is-warning">
|
||||||
|
<div class="message-header">
|
||||||
|
<p>Whoops?</p>
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
This page seems to be empty, because no clients are available. Try to use the server with a compartible client to add it to the database.
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}license{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="block">
|
||||||
|
<pre>{{ license }}</pre>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,53 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}clients{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<nav class="level">
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Products</p>
|
||||||
|
<p class="title">{{ count_products + filtered }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Windows</p>
|
||||||
|
<p class="title">{{ count_products_windows }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Office</p>
|
||||||
|
<p class="title">{{ count_products_office }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading"><abbr title="Empty GLVK or not recognized">Other</abbr></p>
|
||||||
|
<p class="title">{{ filtered }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<table class="table is-bordered is-striped is-hoverable is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th><abbr title="Group Volume License Key">GVLK</abbr></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for name, gvlk in products | dictsort %}
|
||||||
|
{% if gvlk %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ name }}</td>
|
||||||
|
<td><pre>{{ gvlk }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1 @@
|
||||||
|
docker/docker-py3-kms/requirements.txt
|
Loading…
Reference in New Issue