pyarmor icon indicating copy to clipboard operation
pyarmor copied to clipboard

SOLUTION - Group PyArmor license on Windows Host - Run unlimited dockers in offline device

Open DarioMagniPLRM opened this issue 2 months ago • 13 comments

After reading the specific documentation https://pyarmor.readthedocs.io/en/stable/how-to/register.html#run-unlimited-dockers-in-offline-device, I encountered several problems trying to apply it to my environments (Windows host, WSL & docker desktop, Linux containers). I read the previous open issues but did not find a working solution. So, after several attempts, I found a working solution, which I describe below, hoping it will be useful.

Linked issues:

  • https://github.com/dashingsoft/pyarmor/issues/2217
  • https://github.com/dashingsoft/pyarmor/issues/2214
  • https://github.com/dashingsoft/pyarmor/issues/2212
  • https://github.com/dashingsoft/pyarmor/issues/2196
  • https://github.com/dashingsoft/pyarmor/issues/2141

and more...

PyArmor Group on Windows + Docker Desktop (centralized proxy)

This guide explains how to reliably use a PyArmor Group license with Docker Desktop on Windows using the official "Run unlimited dockers in offline device" approach. It describes the initial scenario, the centralized proxy solution implemented in this repo, and the essential operational steps.

Initial scenario

  • Windows host with Docker Desktop (Linux containers), registered as an offline PyArmor device (Windows device regfile).
  • Requirement: run PyArmor inside a Docker container while reusing the host license.
  • Problem: on Docker Desktop, host.docker.internal is not in the same L3 subnet as containers; PyArmor requires an on-net (same-subnet) auth endpoint for the unlimited-dockers mode.

Adopted solution (in this project)

  1. pyarmor-auth runs on Windows using the Windows device regfile.
  2. A centralized TCP proxy (socat-based) runs in a shared Docker network pyarmor-net (172.30.0.0/24) with a fixed IP 172.30.0.10.
  3. Each application stack attaches to pyarmor-net and maps host.docker.internal -> 172.30.0.10. This makes the auth endpoint appear on-net and license checks succeed.

Repo components:

  • Substack: pyarmor-proxy/docker-compose.yml (creates pyarmor-net and starts the central proxy at 172.30.0.10)
  • Application stacks: your-app-image, your-app-image2,... (attached to pyarmor-net with extra_hosts pointing to the proxy)

Prerequisites

  • PyArmor installed on Windows and a valid Group license (activation/registration files).
  • Docker Desktop running on Windows.
  • PowerShell (pwsh) recommended as your shell.

Step-by-step

  1. Start the PyArmor auth server on Windows (using the Windows device regfile):
pyarmor-auth C:\path\to\pyarmor-device-regfile-xxxx.N.zip
# Expected: "listen container auth request on 0.0.0.0:29092"
  1. Start the centralized proxy (also creates the shared pyarmor-net if missing):

pyarmor-proxy/docker-compose.yml

FROM alpine:3.19
RUN apk add --no-cache socat
EXPOSE 29092
# Forward local 29092 to Windows host via Docker Desktop mapping
ENTRYPOINT ["socat","-d","-d","TCP-LISTEN:29092,fork,reuseaddr","TCP:host.docker.internal:29092"]
(docker network inspect pyarmor-net >/dev/null 2>&1) || docker network create --subnet=172.30.0.0/24 pyarmor-net
docker compose -f ./pyarmor-proxy/docker-compose.yml up -d --build
  1. Start an application stack (already configured to use the proxy):
docker compose -f ./your-app-image/docker-compose.yml up -d --build
  1. Register PyArmor inside the container with the Windows device regfile (if not already done there):
docker cp "C:\path\to\pyarmor-device-regfile-xxxx.N.zip" your-app-image:/
docker exec -it your-app-image bash
pyarmor reg /home/coder/project/pyarmor-device-regfile-xxxx.N.zip
pyarmor -v
  1. Quick checks
ping -c 1 host.docker.internal
curl -v telnet://host.docker.internal:29092
echo "print('hello world')" > foo.py
pyarmor gen --enable-rft foo.py

How it works (why the proxy is needed)

  • On Docker Desktop, the IP behind host.docker.internal is typically not in the same subnet as containers. PyArmor requires an on-net endpoint for unlimited-dockers.
  • The centralized proxy lives in the same Docker network as the app containers (pyarmor-net) and forwards to host.docker.internal:29092 (routed to Windows by Docker Desktop). PyArmor thus sees a peer in the same network and license verification succeeds.

Minimal example (app service)

Application stacks use a shared external network and map the host to the proxy:

services:
  app:
    image: your-app-image
    extra_hosts:
      # Map host.docker.internal to the on-net PyArmor proxy (same-subnet requirement)
      - "host.docker.internal:172.30.0.10"
    networks:
      # Attach pyarmor-net first (primary) so default route matches the host mapping
      - pyarmor-net

networks:
  pyarmor-net:
    external: true

Notes:

  • If 172.30.0.0/24 conflicts in your environment, adjust the subnet in pyarmor-proxy/docker-compose.yml and update extra_hosts in your stacks.
  • Ensure pyarmor-auth is reachable on TCP/29092 (Windows firewall rules).

Troubleshooting

  • "this license is not for this machine" during gen:

    • Verify reachability to the proxy: ping host.docker.internal, curl -v telnet://host.docker.internal:29092.
    • Ensure pyarmor-auth uses the Windows device regfile.
    • Inside the container, pyarmor -v should show a valid Group license for your product.
  • "out of license":

    • PyArmor is not registered in the container or cannot reach the auth server. Register with the correct device regfile and check connectivity.
  • Versions:

    • Keep PyArmor versions aligned between host and container (same major recommended). Aligning Python versions is not strictly required for this solution, but generally reduces runtime differences.

DarioMagniPLRM avatar Oct 21 '25 07:10 DarioMagniPLRM

@DarioMagniPLRM Thanks for your contributions.

jondy avatar Oct 22 '25 07:10 jondy

While I reproduce this on MacOS, I encounter some problems. I am using orbstack which is a lightweight docker implementation. The pyarmor container ip is 192.168.139.149, proxy container ip is 192.168.139.164. And a socat is running on proxy container to forward traffic to macos.

On pyarmor container: pyarmor reg ./pyarmor-device-regfile-6694.8.zip INFO Python 3.12.12 INFO Pyarmor 9.1.8 (group), 006694, xxx INFO Platform linux.aarch64 INFO register "./pyarmor-device-regfile-6694.8.zip" INFO machine id in group license: m71da5a46c4b68b964df1768169779c0a INFO no machine id matchs this group license INFO take this machine as docker container, and connect to docker host for authentication... INFO socket addr: ('192.168.139.149', 51040) INFO remote addr: ('192.168.139.164', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e INFO This license registration information:

License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx

BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No

Notes

  • Offline obfuscation 4140.8

seems reg success.

pyarmor -v Pyarmor 9.1.8 (group), 006694, xxx

License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx

BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No

Notes

  • Offline obfuscation 4140.8

INFO socket addr: ('192.168.139.149', 53320) INFO remote addr: ('192.168.139.164', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e

However, pyarmor gen --enable-rft foo.py INFO Python 3.12.12 INFO Pyarmor 9.1.8 (group), 006694, xxx INFO Platform linux.aarch64 INFO search inputs ... INFO find script foo.py INFO find 1 top resources ERROR this license is not for this machine

cat pyarmor.bug.log [BUG]: this license is not for this machine

Command Line

xxx/pyarmor gen --enable-rft foo.py

Environments

Python 3.12.12 Pyarmor 9.1.8 (group), 006694, xxx Platform linux.aarch64 Native linux.aarch64 Home /home/xxx/.pyarmor

Traceback

Traceback (most recent call last): File "xxx/lib/python3.12/site-packages/pyarmor/cli/main.py", line 793, in main main_entry(sys.argv[1:]) File "xxx/lib/python3.12/site-packages/pyarmor/cli/main.py", line 786, in main_entry return args.func(ctx, args) ^^^^^^^^^^^^^^^^^^^^ File "xxx/lib/python3.12/site-packages/pyarmor/cli/main.py", line 248, in cmd_gen builder.process(options) File "xxx/lib/python3.12/site-packages/pyarmor/cli/generate.py", line 179, in process Pytransform3.pre_build(self.ctx) File "xxx/lib/python3.12/site-packages/pyarmor/cli/core/init.py", line 110, in pre_build return m.pre_build(ctx) ^^^^^^^^^^^^^^^^ File "", line 778, in pre_build File "", line 658, in build File "", line 637, in init_rft_mode RuntimeError: this license is not for this machine

As far as I know, the connectivity is ok, and they share same subnet mask. Any suggestions?

BCDS-haoran avatar Nov 11 '25 03:11 BCDS-haoran

I found that I can directly access host(macos) from pyarmor docker, however the output is still the same.

ifconfig on docker: docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255 ether f2:95:70:49:95:f2 txqueuelen 0 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 2 overruns 0 carrier 0 collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.139.149 netmask 255.255.255.0 broadcast 192.168.139.255 inet6 fe80::3c09:d2ff:feec:308a prefixlen 64 scopeid 0x20 inet6 fd07:b51a:cc66:0:3c09:d2ff:feec:308a prefixlen 64 scopeid 0x0 ether 3e:09:d2:ec:30:8a txqueuelen 1000 (Ethernet) RX packets 38 bytes 4380 (4.3 KB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 28 bytes 2738 (2.7 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10 loop txqueuelen 1000 (Local Loopback) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

ifconfig on macos: bridge100: flags=8a63<UP,BROADCAST,SMART,RUNNING,ALLMULTI,SIMPLEX,MULTICAST> mtu 1500 options=63<RXCSUM,TXCSUM,TSO4,TSO6> ether 7e:61:30:0e:53:64 inet 192.168.139.3 netmask 0xfffffe00 broadcast 192.168.139.255 inet6 fe80::7c61:30ff:fe0e:5364%bridge100 prefixlen 64 scopeid 0x14 inet6 fd07:b51a:cc66:0:a617:db5e:ab7:e9f1 prefixlen 64 Configuration: id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0 maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200 root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0 ipfilter disabled flags 0x0 member: vmenet0 flags=10003<LEARNING,DISCOVER,CSUM> ifmaxaddr 0 port 19 priority 0 path cost 0 nd6 options=201<PERFORMNUD,DAD> media: autoselect status: active

pyarmor -v Pyarmor 9.1.8 (group), 006694, xxx

License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx

BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No

Notes

  • Offline obfuscation 4140.9

INFO socket addr: ('192.168.139.149', 43582) INFO remote addr: ('192.168.139.3', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e

every reboot the docker machine id will change. does that means I need to add it someway to the host regfile? it is not in the list above.

BCDS-haoran avatar Nov 11 '25 04:11 BCDS-haoran

@BCDS-haoran

Please check this guide https://pyarmor.readthedocs.io/en/latest/how-to/register.html#run-unlimited-dockers-in-offline-device

jondy avatar Nov 11 '25 07:11 jondy

@jondy Hi, thanks for quick reply. I've already checked the docs and existing issues. However, it seems the situation is still the same. Does that means pyarmor-auth just cannot running on Macos, even I have same network mask with the docker container? Or did I miss something? :)

BCDS-haoran avatar Nov 11 '25 09:11 BCDS-haoran

@BCDS-haoran

I don't what's exactly your problem.

pyarmor-auth must be run in one machine which machine id is stable, and it must be in this machine to run docker container.

And the network mask should be same, and ttl no more than 2

jondy avatar Nov 11 '25 12:11 jondy

@jondy the pyarmor-auth is running in macos host machine, and its machine id is stable. The host machine(192.168.139.3) runs docker container(192.168.139.149), they share same network mask as shown in ifconfig above.

Inside docker container, I ran traceroute:

traceroute 192.168.139.3 traceroute to 192.168.139.3 (192.168.139.3), 30 hops max, 60 byte packets 1 192.168.139.3 (192.168.139.3) 0.288 ms 0.195 ms 0.271 ms

It seems all config is ok, but I got "RuntimeError: this license is not for this machine"

BCDS-haoran avatar Nov 12 '25 03:11 BCDS-haoran

In docker container, try to export one environment variable

export PYARMOR_DOCKER_HOST=192.168.139.3

Or make sure domain name host.docker.internal is parsed as 192.168.139.3

Then run pyarmor gen ... to check it.

jondy avatar Nov 12 '25 07:11 jondy

@jondy Yes I have already set the host.docker.internal to 192.168.139.3 through editing /etc/hosts, and also export the env variable.

on host machine, I ran pyarmor-auth: pyarmor-auth ./pyarmor-device-regfile-6694.8.zip 2025-11-12 16:08:41,637: work path: /Users/xxx/.pyarmor/docker 2025-11-12 16:08:41,638: register "pyarmor-device-regfile-6694.8.zip" 2025-11-12 16:08:41,645: machine id in group license: m71da5a46c4b68b964df1768169779c0a 2025-11-12 16:08:41,647: this machine id matchs group license 2025-11-12 16:08:41,650: listen container auth request on 0.0.0.0:29092 2025-11-12 16:09:11,562: receive request from ('192.168.139.57', 39044) 2025-11-12 16:09:11,563: send auth result to ('192.168.139.57', 39044) 2025-11-12 16:09:17,065: receive request from ('192.168.139.57', 39056) 2025-11-12 16:09:17,065: send auth result to ('192.168.139.57', 39056)

on docker container, I ran

pyarmor reg ./pyarmor-device-regfile-6694.8.zip INFO Python 3.12.12 INFO Pyarmor 9.1.8 (trial), 000000, non-profits INFO Platform linux.x86_64 INFO register "./pyarmor-device-regfile-6694.8.zip" INFO machine id in group license: m71da5a46c4b68b964df1768169779c0a INFO no machine id matchs this group license INFO take this machine as docker container, and connect to docker host for authentication... INFO socket addr: ('192.168.139.57', 39044) INFO remote addr: ('192.168.139.3', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e INFO This license registration information:

License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx

BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No

Notes

  • Offline obfuscation 4140.8

pyarmor -v Pyarmor 9.1.8 (group), 006694, xxx

License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx

BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No

Notes

  • Offline obfuscation 4140.8

INFO socket addr: ('192.168.139.57', 39056) INFO remote addr: ('192.168.139.3', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e

pyarmor gen --enable-rft foo.py INFO Python 3.12.12 INFO Pyarmor 9.1.8 (group), 006694, xxx INFO Platform linux.x86_64 INFO search inputs ... INFO find script foo.py INFO find 1 top resources ERROR this license is not for this machine

Connection is ok, but it just cannot recognize the license.

BCDS-haoran avatar Nov 12 '25 08:11 BCDS-haoran

In the docker container, what's the output of tracert:

$ tracert 192.168.139.3

It still has TTL limits for pyarmor gen command, but pyarmor reg no this limits.

jondy avatar Nov 13 '25 02:11 jondy

Here's the output of traceroute traceroute 192.168.139.3 traceroute to 192.168.139.3 (192.168.139.3), 30 hops max, 60 byte packets 1 192.168.139.3 (192.168.139.3) 0.618 ms 0.259 ms 0.185 ms

BCDS-haoran avatar Nov 13 '25 02:11 BCDS-haoran

Is there /dev/disk in the docker container?

If it exists, rename it to any other name. For example, mv /dev/disk /dev/no-disk

jondy avatar Nov 13 '25 04:11 jondy

after that, the pyarmor gen command run succeed

pyarmor gen --enable-rft foo.py INFO Python 3.12.12 INFO Pyarmor 9.1.8 (group), 006694, xxx INFO Platform linux.x86_64 INFO search inputs ... INFO find script foo.py INFO find 1 top resources INFO start to generate runtime files INFO target platforms {'linux.x86_64'} INFO write dist/pyarmor_runtime_006694/pyarmor_runtime.so INFO generate runtime files OK INFO start to obfuscate scripts INFO process resource "foo" INFO obfuscating file foo.py INFO write dist/foo.py INFO obfuscate scripts OK

thanks a lot!

BCDS-haoran avatar Nov 13 '25 05:11 BCDS-haoran