1.5. Customization and Extension

Users could write any plugin script or hook script to extend Pyarmor features.

1.5.1. Using plugin to fix loading issue in darwin

New in version 8.2.

In darwin, if Python is not installed in the standard path, the obfuscated scripts may not work because extension module pyarmor_runtime in the runtime package could not be loaded.

Let’s check the dependencies of pyarmor_runtime.so:

$ otool -L dist/pyarmor_runtime_000000/pyarmor_runtime.so

dist/pyarmor_runtime_000000/pyarmor_runtime.so:

    pyarmor_runtime.so (compatibility version 0.0.0, current version 1.0.0)
    ...
    @rpath/lib/libpython3.9.dylib (compatibility version 3.9.0, current version 3.9.0)
    ...

Suppose target device has no @rpath/lib/libpython3.9.dylib, but @rpath/lib/libpython3.9.so, in this case pyarmor_runtime.so could not be loaded.

We can create a plugin script .pyarmor/conda.py to fix this problem

__all__ = ['CondaPlugin']

class CondaPlugin:

    def _fixup(self, target):
        from subprocess import check_call
        check_call('install_name_tool -change @rpath/lib/libpython3.9.dylib @rpath/lib/libpython3.9.so %s' % target)
        check_call('codesign -f -s - %s' % target)

    @staticmethod
    def post_runtime(ctx, source, target, platform):
        if platform.startswith('darwin.'):
            print('using install_name_tool to fix %s' % target)
            self._fixup(target)

Enable this plugin and generate the obfusated script again:

$ pyarmor cfg plugins + "conda"
$ pyarmor gen foo.py

See also

Plugins

1.5.2. Using hook to bind script to docker id

New in version 8.2.

Suppose we need bind script app.py to 2 dockers which id are docker-a1 and docker-b2

First create hook script .pyarmor/hooks/app.py

def _pyarmor_check_docker():
    cid = None
    with open("/proc/self/cgroup") as f:
        for line in f:
            if line.split(':', 2)[1] == 'name=systemd':
                cid = line.strip().split('/')[-1]
                break

    docker_ids = __pyarmor__(0, None, b'keyinfo', 1).decode('utf-8')
    if cid is None or cid not in docker_ids.split(','):
        raise RuntimeError('license is not for this machine')

_pyarmor_check_docker()

Then generate the obfuscated script, store docker ids to runtime key as private data at the same time:

$ pyarmor gen --bind-data "docker-a1,docker-b2" app.py

Run the obfuscated script to check it, please add print statements in the hook script to debug it.

See also

Hooks __pyarmor__()

1.5.3. Using hook to check network time by other service

New in version 8.2.

If NTP is not available in the target device and the obfuscated scripts has expired date, it may raise RuntimeError: Resource temporarily unavailable.

In this case, using hook script to verify expired data by other time service.

First create hook script in the .pyarmor/hooks/foo.py:

def _pyarmor_check_worldtime(host, path):
    from http.client import HTTPSConnection
    expired = __pyarmor__(1, None, b'keyinfo', 1)
    conn = HTTPSConnection(host)
    conn.request("GET", path)
    res = conn.getresponse()
    if res.code == 200:
        data = res.read()
        s = data.find(b'"unixtime":')
        n = data.find(b',', s)
        current = int(data[s+11:n])
        if current > expire:
            raise RuntimeError('license is expired')
     else:
         raise RuntimeError('got network time failed')
_pyarmor_check_worldtime('worldtimeapi.org', '/api/timezone/Europe/Paris')

Then generate script with local expired date:

$ pyarmor gen -e .30 foo.py

Thus the obfuscated script could verify network time by itself.

See also

Hooks __pyarmor__()