pyarmor.cli 9.2.5__2-py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyarmor/cli/__init__.py +46 -0
- pyarmor/cli/__main__.py +812 -0
- pyarmor/cli/batch.py +62 -0
- pyarmor/cli/bootstrap.py +207 -0
- pyarmor/cli/bug.py +137 -0
- pyarmor/cli/command.py +563 -0
- pyarmor/cli/config.py +306 -0
- pyarmor/cli/context.py +790 -0
- pyarmor/cli/core.data.1 +0 -0
- pyarmor/cli/core.data.2 +0 -0
- pyarmor/cli/core.data.3 +0 -0
- pyarmor/cli/default.cfg +564 -0
- pyarmor/cli/docker.py +192 -0
- pyarmor/cli/generate.py +256 -0
- pyarmor/cli/hdinfo.py +81 -0
- pyarmor/cli/merge.py +232 -0
- pyarmor/cli/mixer.py +109 -0
- pyarmor/cli/model.py +460 -0
- pyarmor/cli/plugin.py +292 -0
- pyarmor/cli/project.py +1281 -0
- pyarmor/cli/public_capsule.zip +0 -0
- pyarmor/cli/register.py +1093 -0
- pyarmor/cli/repack.py +793 -0
- pyarmor/cli/resource.py +265 -0
- pyarmor/cli/shell.py +481 -0
- pyarmor_cli-9.2.5.dist-info/METADATA +74 -0
- pyarmor_cli-9.2.5.dist-info/RECORD +30 -0
- pyarmor_cli-9.2.5.dist-info/WHEEL +6 -0
- pyarmor_cli-9.2.5.dist-info/entry_points.txt +3 -0
- pyarmor_cli-9.2.5.dist-info/top_level.txt +1 -0
pyarmor/cli/register.py
ADDED
|
@@ -0,0 +1,1093 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
#############################################################
|
|
5
|
+
# #
|
|
6
|
+
# Copyright @ 2023 - Dashingsoft corp. #
|
|
7
|
+
# All rights reserved. #
|
|
8
|
+
# #
|
|
9
|
+
# Pyarmor #
|
|
10
|
+
# #
|
|
11
|
+
# Version: 8.0.1 - #
|
|
12
|
+
# #
|
|
13
|
+
#############################################################
|
|
14
|
+
#
|
|
15
|
+
#
|
|
16
|
+
# @File: cli/register.py
|
|
17
|
+
#
|
|
18
|
+
# @Author: Jondy Zhao (pyarmor@163.com)
|
|
19
|
+
#
|
|
20
|
+
# @Create Date: Mon Jan 2 15:39:08 CST 2023
|
|
21
|
+
#
|
|
22
|
+
import os
|
|
23
|
+
|
|
24
|
+
from base64 import b64decode, b64encode, urlsafe_b64encode
|
|
25
|
+
from json import loads as json_loads
|
|
26
|
+
from string import Template
|
|
27
|
+
|
|
28
|
+
from . import logger, CliError
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# All supported machine flags for group license: [11, 26)
|
|
32
|
+
MACHFLAGS = 22, 21, 18, 20, 16, 11
|
|
33
|
+
|
|
34
|
+
# Upgrade notes for Pyarmor 9
|
|
35
|
+
URL_UPGRADE_V9 = 'https://github.com/dashingsoft/pyarmor/issues/1958'
|
|
36
|
+
|
|
37
|
+
# Template for license info
|
|
38
|
+
LICENSE_INFO_TEMPLATE = '''$advanced
|
|
39
|
+
|
|
40
|
+
$notes
|
|
41
|
+
'''
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse_token(data):
|
|
45
|
+
from struct import unpack
|
|
46
|
+
|
|
47
|
+
if data and data.find(b' ') > 0:
|
|
48
|
+
try:
|
|
49
|
+
buf = b64decode(data.split()[0])
|
|
50
|
+
|
|
51
|
+
token, value = unpack('II', buf[:8])
|
|
52
|
+
rev, features = value & 0xff, value >> 8
|
|
53
|
+
licno = buf[16:34].decode('utf-8')
|
|
54
|
+
|
|
55
|
+
pstr = []
|
|
56
|
+
i = 64
|
|
57
|
+
for k in range(4):
|
|
58
|
+
n = buf[i]
|
|
59
|
+
i += 1
|
|
60
|
+
pstr.append(buf[i:i+n].decode('utf-8') if n
|
|
61
|
+
else '')
|
|
62
|
+
i += n
|
|
63
|
+
|
|
64
|
+
product = ('non-profits(TBD)' if pstr[2] in ('', 'TBD')
|
|
65
|
+
else pstr[2])
|
|
66
|
+
return {
|
|
67
|
+
'token': token,
|
|
68
|
+
'rev': rev,
|
|
69
|
+
'features': features,
|
|
70
|
+
'licno': licno,
|
|
71
|
+
'machine': pstr[0],
|
|
72
|
+
'regname': pstr[1],
|
|
73
|
+
'product': product,
|
|
74
|
+
'note': pstr[3],
|
|
75
|
+
}
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.warning('bad token: %s', str(e))
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
'token': 0,
|
|
81
|
+
'rev': 0,
|
|
82
|
+
'features': 0,
|
|
83
|
+
'licno': 'pyarmor-vax-000000',
|
|
84
|
+
'regname': '',
|
|
85
|
+
'product': 'non-profits',
|
|
86
|
+
'note': 'This is trial license'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def show_help_page(prompt, url):
|
|
91
|
+
choice = input('\n'.join(prompt)).lower()[:1]
|
|
92
|
+
if choice in ('h', 'y'):
|
|
93
|
+
import webbrowser
|
|
94
|
+
webbrowser.open(url)
|
|
95
|
+
return choice
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def check_license_version(ctx, silent=False):
|
|
99
|
+
licinfo = ctx.license_info
|
|
100
|
+
rev = licinfo.get('rev', 0)
|
|
101
|
+
token = licinfo.get('token', 0)
|
|
102
|
+
features = licinfo.get('features', 0)
|
|
103
|
+
if rev == 1 and features > 0 and token > 0:
|
|
104
|
+
logger.warning('this license is not ready for Pyarmor 9')
|
|
105
|
+
if silent:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
# Group License
|
|
109
|
+
if features == 15:
|
|
110
|
+
prompt = (
|
|
111
|
+
'',
|
|
112
|
+
'Pyarmor 9 has some changes on license policy',
|
|
113
|
+
'This group license is still available',
|
|
114
|
+
'But it need request new device regfile as before',
|
|
115
|
+
'Press "h" to check Pyarmor 9 Upgrade Notes',
|
|
116
|
+
'',
|
|
117
|
+
'Help (h), Quit (q): '
|
|
118
|
+
)
|
|
119
|
+
show_help_page(prompt, URL_UPGRADE_V9)
|
|
120
|
+
raise SystemExit('Quit')
|
|
121
|
+
|
|
122
|
+
prompt = (
|
|
123
|
+
'',
|
|
124
|
+
'Pyarmor 9 has big change on CI/CD pipeline',
|
|
125
|
+
'If not using Pyarmor License in CI/CD pipeline',
|
|
126
|
+
'Press "c" to continue',
|
|
127
|
+
'Otherwise press "h" to check Pyarmor 9.0 Upgrade Notes',
|
|
128
|
+
'',
|
|
129
|
+
'Continue (c), Help (h), Quit (q): '
|
|
130
|
+
)
|
|
131
|
+
if not show_help_page(prompt, URL_UPGRADE_V9) == 'c':
|
|
132
|
+
raise SystemExit('Quit')
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Register(object):
|
|
136
|
+
|
|
137
|
+
def __init__(self, ctx):
|
|
138
|
+
self.ctx = ctx
|
|
139
|
+
self.notes = []
|
|
140
|
+
|
|
141
|
+
def check_args(self, args):
|
|
142
|
+
if args.upgrade and args.keyfile.endswith('.zip'):
|
|
143
|
+
raise CliError('use .txt file to upgrade, not .zip file')
|
|
144
|
+
|
|
145
|
+
def _get_old_rcode(self):
|
|
146
|
+
old_license = self.ctx.read_license()
|
|
147
|
+
if not old_license:
|
|
148
|
+
logger.debug('no license file found')
|
|
149
|
+
return
|
|
150
|
+
if len(old_license) == 256:
|
|
151
|
+
logger.debug('no old purchased license')
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
data = b64decode(old_license)
|
|
155
|
+
i = data.find(b'pyarmor-vax-')
|
|
156
|
+
if i == -1:
|
|
157
|
+
raise CliError('no valid old license')
|
|
158
|
+
return data[i:i+18].decode()
|
|
159
|
+
|
|
160
|
+
def regurl(self, ucode, product=None, rcode=None, prepare=False):
|
|
161
|
+
url = self.ctx.cfg['pyarmor']['regurl'] % ucode
|
|
162
|
+
if product:
|
|
163
|
+
if len(product) > 60:
|
|
164
|
+
raise CliError('too long product name (length > 60)')
|
|
165
|
+
url += '&product=' + \
|
|
166
|
+
urlsafe_b64encode(product.encode('utf-8')).decode()
|
|
167
|
+
if rcode:
|
|
168
|
+
url += '&rcode=' + rcode
|
|
169
|
+
if prepare:
|
|
170
|
+
url += '&prepare=1'
|
|
171
|
+
return url
|
|
172
|
+
|
|
173
|
+
def update_token(self):
|
|
174
|
+
from pyarmor.cli.core import Pytransform3
|
|
175
|
+
assert Pytransform3._pytransform3 is None
|
|
176
|
+
with open(self.ctx.license_token, 'wb') as f:
|
|
177
|
+
f.close()
|
|
178
|
+
Pytransform3.init(self.ctx)
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def license_info(self):
|
|
182
|
+
return parse_token(self.ctx.read_token())
|
|
183
|
+
|
|
184
|
+
def _license_type(self, info):
|
|
185
|
+
return 'basic' if info['features'] in (1, 17) else \
|
|
186
|
+
'pro' if info['features'] == 7 else \
|
|
187
|
+
'group' if info['features'] == 15 else \
|
|
188
|
+
'ci' if info['features'] == 23 else \
|
|
189
|
+
'trial' if info['token'] == 0 else 'unknown'
|
|
190
|
+
|
|
191
|
+
def _license_to(self, info):
|
|
192
|
+
name = info['regname']
|
|
193
|
+
product = info['product']
|
|
194
|
+
return '%s (%s)' % (product, name) if name and product else \
|
|
195
|
+
'non-profits' if not name else 'non-profits (%s)' % name
|
|
196
|
+
|
|
197
|
+
def parse_keyfile(self, filename):
|
|
198
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
|
199
|
+
for line in f:
|
|
200
|
+
line = line.strip()
|
|
201
|
+
marker = 'Dear '
|
|
202
|
+
if line.startswith(marker):
|
|
203
|
+
regname = line[len(marker):].strip(' ,')
|
|
204
|
+
break
|
|
205
|
+
|
|
206
|
+
for line in f:
|
|
207
|
+
line = line.strip()
|
|
208
|
+
if len(line) == 192 and line.find(' ') == -1:
|
|
209
|
+
return regname, line
|
|
210
|
+
|
|
211
|
+
logger.error('please make sure the activation file is '
|
|
212
|
+
'pure text file, the content starts with `Dear `, '
|
|
213
|
+
'and includes the activation code'
|
|
214
|
+
'(its length is 192 and no line break)')
|
|
215
|
+
raise CliError('invalid activation file "%s"' % filename)
|
|
216
|
+
|
|
217
|
+
def _register_offline_license(self, fzip, namelist):
|
|
218
|
+
logger.info('machine id in group license: %s', ', '.join(
|
|
219
|
+
[x[7:] for x in namelist if x.startswith('tokens')]))
|
|
220
|
+
for idver in MACHFLAGS:
|
|
221
|
+
machid = self._get_machine_id(idver).decode('utf-8')
|
|
222
|
+
logger.debug('got machine id: %s', machid)
|
|
223
|
+
name = '/'.join(['tokens', machid])
|
|
224
|
+
if name in namelist:
|
|
225
|
+
logger.info('this machine id matchs group license')
|
|
226
|
+
break
|
|
227
|
+
else:
|
|
228
|
+
logger.info('no machine id matchs this group license')
|
|
229
|
+
logger.info('take this machine as docker container, and '
|
|
230
|
+
'connect to docker host for authentication...')
|
|
231
|
+
mlist = self._get_docker_hostname()
|
|
232
|
+
if not mlist:
|
|
233
|
+
logger.info(
|
|
234
|
+
'could not get docker host machine id\n%s',
|
|
235
|
+
'\n'.join([
|
|
236
|
+
'',
|
|
237
|
+
'if this machine is docker container, please '
|
|
238
|
+
'run command `pyarmor-auth` in docker host, '
|
|
239
|
+
'and try it again', '',
|
|
240
|
+
'otherwise please generate new group '
|
|
241
|
+
'device license for this machine', '',
|
|
242
|
+
'more information please check section '
|
|
243
|
+
'"using group license" in documentation '
|
|
244
|
+
'"how-to register" guide', ''
|
|
245
|
+
]))
|
|
246
|
+
raise CliError('this group device license is not for '
|
|
247
|
+
'this machine')
|
|
248
|
+
for machid in mlist:
|
|
249
|
+
hostname = '/'.join(['tokens', machid])
|
|
250
|
+
if hostname in namelist:
|
|
251
|
+
name = hostname
|
|
252
|
+
break
|
|
253
|
+
else:
|
|
254
|
+
logger.debug('docker host machine ids: %s', mlist)
|
|
255
|
+
raise CliError('this group device license is not for '
|
|
256
|
+
'this docker host')
|
|
257
|
+
|
|
258
|
+
logger.debug('extracting %s', name)
|
|
259
|
+
self.ctx.save_group_token(fzip.read(name))
|
|
260
|
+
|
|
261
|
+
def _init_token(self, reginfo):
|
|
262
|
+
from struct import pack
|
|
263
|
+
rev, licno, lictp, regname, product = (
|
|
264
|
+
reginfo['rev'], reginfo['rcode'], reginfo['type'],
|
|
265
|
+
reginfo['name'], reginfo['product']
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
token = 0
|
|
269
|
+
old_license = self.license_info
|
|
270
|
+
if old_license['licno'] == licno:
|
|
271
|
+
token = old_license['token']
|
|
272
|
+
|
|
273
|
+
features = (1 if lictp == 'J' else 7 if lictp == 'Z' else
|
|
274
|
+
15 if lictp == 'G' else 23 if lictp == 'C' else
|
|
275
|
+
0)
|
|
276
|
+
notes = (
|
|
277
|
+
'* Do not use this file in CI/CD pipeline directly\n'
|
|
278
|
+
'* Only use it to request CI regfile '
|
|
279
|
+
'"pyarmor-ci-%s.zip"' % licno[:6].lstrip('0')
|
|
280
|
+
if lictp == 'C' else ''
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
regname = regname.encode('utf-8')
|
|
284
|
+
product = product.encode('utf-8')
|
|
285
|
+
notes = notes.encode('utf-8')
|
|
286
|
+
sizes = len(regname), len(product), len(notes)
|
|
287
|
+
data = pack('<II8x20s28xBB{0}sB{1}sB{2}s'.format(*sizes),
|
|
288
|
+
token,
|
|
289
|
+
rev | features << 8,
|
|
290
|
+
licno.encode('utf-8'),
|
|
291
|
+
0,
|
|
292
|
+
sizes[0], regname,
|
|
293
|
+
sizes[1], product,
|
|
294
|
+
sizes[2], notes)
|
|
295
|
+
return b64encode(data) + b' *=='
|
|
296
|
+
|
|
297
|
+
def register_regfile(self, regfile, clean=True):
|
|
298
|
+
from zipfile import ZipFile
|
|
299
|
+
|
|
300
|
+
if os.path.exists(self.ctx.license_group_token):
|
|
301
|
+
os.remove(self.ctx.license_group_token)
|
|
302
|
+
|
|
303
|
+
path = self.ctx.reg_path
|
|
304
|
+
with ZipFile(regfile, 'r') as f:
|
|
305
|
+
for item in ('license.lic', '.pyarmor_capsule.zip'):
|
|
306
|
+
logger.debug('extracting %s', item)
|
|
307
|
+
f.extract(item, path=path)
|
|
308
|
+
namelist = f.namelist()
|
|
309
|
+
if 'group.tokens' in namelist:
|
|
310
|
+
self._register_offline_license(f, namelist)
|
|
311
|
+
elif 'ci.token' in namelist:
|
|
312
|
+
name = 'ci.token'
|
|
313
|
+
logger.debug('extracting %s', name)
|
|
314
|
+
self.ctx.save_token(f.read(name))
|
|
315
|
+
elif 'group.info' in namelist:
|
|
316
|
+
logger.info('this file is only used to '
|
|
317
|
+
'request device regfile:\n\n'
|
|
318
|
+
'\tpyarmor reg -g 1 %s\n', regfile)
|
|
319
|
+
raise CliError('wrong usage for Group License')
|
|
320
|
+
elif 'reg.info' in namelist:
|
|
321
|
+
data = f.read('reg.info')
|
|
322
|
+
info = json_loads(data)
|
|
323
|
+
if info.get('type', '') == 'C':
|
|
324
|
+
logger.info('this file is only used to '
|
|
325
|
+
'request ci regfile:\n\n'
|
|
326
|
+
'\tpyarmor reg -C %s\n', regfile)
|
|
327
|
+
raise CliError('wrong usage for CI License')
|
|
328
|
+
self.ctx.save_token(self._init_token(info))
|
|
329
|
+
else:
|
|
330
|
+
logger.error('this license is not ready for Pyarmor 9')
|
|
331
|
+
prompt = (
|
|
332
|
+
'',
|
|
333
|
+
'Pyarmor 9 has big change on CI/CD pipeline',
|
|
334
|
+
'Press "h" to check Pyarmor 9.0 Upgrade Notes',
|
|
335
|
+
'',
|
|
336
|
+
'Help (h), Quit (q): '
|
|
337
|
+
)
|
|
338
|
+
show_help_page(prompt, URL_UPGRADE_V9)
|
|
339
|
+
raise SystemExit()
|
|
340
|
+
|
|
341
|
+
def _get_docker_hostname(self):
|
|
342
|
+
try:
|
|
343
|
+
from socket import socket, AF_INET, SOCK_STREAM
|
|
344
|
+
host = os.getenv('PYARMOR_DOCKER_HOST', 'host.docker.internal')
|
|
345
|
+
port = 29092
|
|
346
|
+
rlist = []
|
|
347
|
+
with socket(AF_INET, SOCK_STREAM) as s:
|
|
348
|
+
s.connect((host, port))
|
|
349
|
+
logger.info('socket addr: %s', str(s.getsockname()))
|
|
350
|
+
logger.info('remote addr: %s', str(s.getpeername()))
|
|
351
|
+
s.sendall(b'PADH' + b'x' * 60)
|
|
352
|
+
while True:
|
|
353
|
+
flag = s.recv(1)
|
|
354
|
+
if ord(flag) - 87 in MACHFLAGS:
|
|
355
|
+
data = s.recv(32)
|
|
356
|
+
machid = (flag + data).decode('utf-8')
|
|
357
|
+
logger.info('got docker host machine id: %s', machid)
|
|
358
|
+
rlist.append(machid)
|
|
359
|
+
if s.recv(1) == b'\x00':
|
|
360
|
+
break
|
|
361
|
+
return rlist
|
|
362
|
+
except Exception as e:
|
|
363
|
+
logger.debug('%s:%d:%s', host, port, str(e))
|
|
364
|
+
|
|
365
|
+
def _get_machine_id(self, devflag=11):
|
|
366
|
+
from pyarmor.cli.core import Pytransform3
|
|
367
|
+
try:
|
|
368
|
+
return Pytransform3.get_hd_info(devflag)
|
|
369
|
+
except SystemError:
|
|
370
|
+
from time import time
|
|
371
|
+
return b'u-machine-id:%020.6f' % time()
|
|
372
|
+
|
|
373
|
+
def generate_group_device(self, devid):
|
|
374
|
+
from datetime import datetime
|
|
375
|
+
from platform import uname
|
|
376
|
+
path = self.ctx.group_device_file(devid)
|
|
377
|
+
logger.info('generating device file "%s"', path)
|
|
378
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
379
|
+
uinfo = uname()
|
|
380
|
+
devflag = self.ctx.cfg['builder'].getint('group_device_flag', 21)
|
|
381
|
+
machid = self._get_machine_id(devflag).decode('utf-8')
|
|
382
|
+
logger.info('current machine id is "%s"', machid)
|
|
383
|
+
tpl = Template('\n'.join([
|
|
384
|
+
'# Generated by Pyarmor $rev, $timestamp',
|
|
385
|
+
'host: $node',
|
|
386
|
+
'system: $host ($version)',
|
|
387
|
+
'machine: $machine'
|
|
388
|
+
])).substitute(
|
|
389
|
+
rev='.'.join(self.ctx.version),
|
|
390
|
+
node=uinfo.node,
|
|
391
|
+
host=uinfo.system,
|
|
392
|
+
version=uinfo.version,
|
|
393
|
+
timestamp=datetime.now().isoformat(),
|
|
394
|
+
machine=machid,
|
|
395
|
+
)
|
|
396
|
+
with open(path, "wb") as f:
|
|
397
|
+
f.write(tpl.encode('utf-8'))
|
|
398
|
+
return path
|
|
399
|
+
|
|
400
|
+
def check_group_license(self, silent=False):
|
|
401
|
+
licinfo = self.license_info
|
|
402
|
+
if licinfo['features'] & 8:
|
|
403
|
+
licmach = licinfo.get('machine', '')
|
|
404
|
+
if not licmach:
|
|
405
|
+
raise RuntimeError('no token machine')
|
|
406
|
+
|
|
407
|
+
idver = ord(licmach[0]) - 87
|
|
408
|
+
# This can't be called in "cmd_gen", otherwise crash
|
|
409
|
+
machid = self._get_machine_id(idver).decode('utf-8')
|
|
410
|
+
if machid == licmach:
|
|
411
|
+
return
|
|
412
|
+
|
|
413
|
+
mlist = self._get_docker_hostname()
|
|
414
|
+
if mlist and licmach in mlist:
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
logger.info('this license is for machine: %s', licmach)
|
|
418
|
+
|
|
419
|
+
if mlist:
|
|
420
|
+
logger.info('but docker host machine ids: %s',
|
|
421
|
+
', '.join(mlist))
|
|
422
|
+
raise RuntimeError(
|
|
423
|
+
'this group license is not for this docker host')
|
|
424
|
+
else:
|
|
425
|
+
logger.info('but this machine id: %s', machid)
|
|
426
|
+
raise RuntimeError(
|
|
427
|
+
'this group license is not for this machine')
|
|
428
|
+
|
|
429
|
+
def __str__(self):
|
|
430
|
+
info = self.license_info
|
|
431
|
+
lictype = self._license_type(info)
|
|
432
|
+
|
|
433
|
+
fmt = '%-16s: %s'
|
|
434
|
+
lines = [
|
|
435
|
+
fmt % ('License Type', 'pyarmor-' + lictype),
|
|
436
|
+
fmt % ('License No.', info['licno']),
|
|
437
|
+
fmt % ('License To', info['regname']),
|
|
438
|
+
fmt % ('License Product', info['product']),
|
|
439
|
+
'',
|
|
440
|
+
]
|
|
441
|
+
|
|
442
|
+
bccmode = info['features'] & 2
|
|
443
|
+
rftmode = info['features'] & 4
|
|
444
|
+
cidmode = info.get('machine', '').startswith('CITK')
|
|
445
|
+
advanced = [
|
|
446
|
+
fmt % ('BCC Mode', 'Yes' if bccmode else 'No'),
|
|
447
|
+
fmt % ('RFT Mode', 'Yes' if rftmode else 'No'),
|
|
448
|
+
fmt % ('CI/CD Mode', 'Yes' if cidmode else 'No'),
|
|
449
|
+
]
|
|
450
|
+
if lictype == 'trial':
|
|
451
|
+
self.notes.append('* Can\'t obfuscate big script and mix str')
|
|
452
|
+
elif lictype in ('basic', 'pro'):
|
|
453
|
+
self.notes.append('* Need verify license online '
|
|
454
|
+
'when obfuscating scripts')
|
|
455
|
+
elif lictype == 'group':
|
|
456
|
+
self.notes.append('* Offline obfuscation')
|
|
457
|
+
|
|
458
|
+
if info['note']:
|
|
459
|
+
self.notes.extend(info['note'].splitlines())
|
|
460
|
+
|
|
461
|
+
if self.notes:
|
|
462
|
+
self.notes.insert(0, 'Notes')
|
|
463
|
+
|
|
464
|
+
lines.append(Template(LICENSE_INFO_TEMPLATE).substitute(
|
|
465
|
+
advanced='\n'.join(advanced),
|
|
466
|
+
notes='\n'.join(self.notes),
|
|
467
|
+
))
|
|
468
|
+
|
|
469
|
+
return '\n'.join(lines)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
upgrade_to_basic_info = Template('''
|
|
473
|
+
You are about to upgrade old Pyarmor license to Pyarmor Basic License
|
|
474
|
+
|
|
475
|
+
The upgraded license information will be''')
|
|
476
|
+
|
|
477
|
+
upgrade_to_pro_info = Template('''
|
|
478
|
+
You are about to upgrade old Pyarmor license to Pyarmor Pro License
|
|
479
|
+
|
|
480
|
+
The original license no: $rcode
|
|
481
|
+
|
|
482
|
+
The upgraded license information will be''')
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class WebRegister(Register):
|
|
486
|
+
|
|
487
|
+
# Before v9.0: 1
|
|
488
|
+
# Pyarmor v9.0: 2
|
|
489
|
+
# Pyarmor v9.2: 3
|
|
490
|
+
LICENSE_REVSION = 3
|
|
491
|
+
|
|
492
|
+
# Before v9.0: no CI License
|
|
493
|
+
# Pyarmor v9.0: 1
|
|
494
|
+
# Pyarmor v9.2: 2
|
|
495
|
+
CI_LICENSE_REVSION = 2
|
|
496
|
+
|
|
497
|
+
def _request(self, url):
|
|
498
|
+
from http.client import HTTPSConnection
|
|
499
|
+
n = len('https://')
|
|
500
|
+
k = url.find('/', n)
|
|
501
|
+
conn = HTTPSConnection(url[n:k])
|
|
502
|
+
conn.request("GET", url[k:])
|
|
503
|
+
return conn.getresponse()
|
|
504
|
+
|
|
505
|
+
def check_request_interval(self, delta=30.0, activation=False):
|
|
506
|
+
"""Make sure no more than 2 requests in 1 minute"""
|
|
507
|
+
tspath = os.path.join(self.ctx.reg_path, 'last_register')
|
|
508
|
+
try:
|
|
509
|
+
st = os.stat(tspath)
|
|
510
|
+
except FileNotFoundError:
|
|
511
|
+
os.makedirs(tspath)
|
|
512
|
+
else:
|
|
513
|
+
from time import sleep, time
|
|
514
|
+
d = delta - (time() - st.st_mtime)
|
|
515
|
+
if d > 0:
|
|
516
|
+
if activation:
|
|
517
|
+
logger.warning(
|
|
518
|
+
'caution: this activation file "%s" can only '
|
|
519
|
+
'be used no more than 10 times', activation)
|
|
520
|
+
logger.info('waiting for %d seconds', d)
|
|
521
|
+
while time() - st.st_mtime < delta:
|
|
522
|
+
logger.info('waiting ...')
|
|
523
|
+
sleep(3.0)
|
|
524
|
+
os.utime(tspath)
|
|
525
|
+
|
|
526
|
+
def _send_request(self, url, timeout=6.0):
|
|
527
|
+
try:
|
|
528
|
+
return self._request(url)
|
|
529
|
+
except Exception as e:
|
|
530
|
+
logger.debug('direct request failed "%s"', str(e))
|
|
531
|
+
|
|
532
|
+
from urllib.request import urlopen
|
|
533
|
+
from ssl import _create_unverified_context
|
|
534
|
+
context = _create_unverified_context()
|
|
535
|
+
return urlopen(url, None, timeout, context=context)
|
|
536
|
+
|
|
537
|
+
def _remove_token(self):
|
|
538
|
+
if os.path.exists(self.ctx.license_token):
|
|
539
|
+
logger.debug('remove old token')
|
|
540
|
+
os.remove(self.ctx.license_token)
|
|
541
|
+
|
|
542
|
+
def _check_product_name(self, name):
|
|
543
|
+
name = name.lower()
|
|
544
|
+
if name.count(name[0]) == len(name) or name.find('pyarmor') >= 0:
|
|
545
|
+
raise RuntimeError('invalid product name "%s"' % name)
|
|
546
|
+
|
|
547
|
+
def prepare(self, keyfile, product, upgrade=False):
|
|
548
|
+
reginfo = self.parse_keyfile(keyfile)
|
|
549
|
+
logger.info('prepare "%s"', keyfile)
|
|
550
|
+
|
|
551
|
+
if product:
|
|
552
|
+
self._check_product_name(product)
|
|
553
|
+
|
|
554
|
+
rcode = self._get_old_rcode() if upgrade else None
|
|
555
|
+
if upgrade and not rcode and keyfile.endswith('regcode-to-pro.txt'):
|
|
556
|
+
logger.error('please use `pyarmor-7 -v` to check old license')
|
|
557
|
+
logger.error('this code is used to upgrade old license')
|
|
558
|
+
raise CliError('no found old license in this machine')
|
|
559
|
+
url = self.regurl(reginfo[1], rcode=rcode, prepare=True)
|
|
560
|
+
logger.debug('url: %s', url)
|
|
561
|
+
|
|
562
|
+
logger.info('query key file from server')
|
|
563
|
+
with self._send_request(url) as res:
|
|
564
|
+
if not res:
|
|
565
|
+
logger.error('please try it later')
|
|
566
|
+
raise CliError('no response from license server')
|
|
567
|
+
if res.code != 200:
|
|
568
|
+
logger.error('HTTP Error %s', res.code)
|
|
569
|
+
raise CliError(res.read().decode('utf-8'))
|
|
570
|
+
info = json_loads(res.read())
|
|
571
|
+
|
|
572
|
+
pname = info['product']
|
|
573
|
+
if pname in ('', 'TBD'):
|
|
574
|
+
info['product'] = product
|
|
575
|
+
elif pname != product:
|
|
576
|
+
logger.warning('this license has bound to product "%s"', pname)
|
|
577
|
+
logger.warning('it can not be changed to "%s"', product)
|
|
578
|
+
|
|
579
|
+
lines = []
|
|
580
|
+
if upgrade:
|
|
581
|
+
if rcode and not rcode.startswith('pyarmor-vax-'):
|
|
582
|
+
raise CliError('old license "%s" can not be upgraded' % rcode)
|
|
583
|
+
if info['upgrade']:
|
|
584
|
+
lines.append(upgrade_to_pro_info.substitute(rcode=rcode))
|
|
585
|
+
else:
|
|
586
|
+
lines.append(upgrade_to_basic_info.substitute())
|
|
587
|
+
else:
|
|
588
|
+
if info['lictype'] == 'OLD':
|
|
589
|
+
raise CliError('old license only works for Pyarmor <= 7.7.4')
|
|
590
|
+
if info['lictype'] not in ('BASIC', 'PRO', 'GROUP', 'CI'):
|
|
591
|
+
raise CliError('unknown license type %s' % info['lictype'])
|
|
592
|
+
lines.append('This license registration information will be')
|
|
593
|
+
|
|
594
|
+
if info['product'] in ('', 'TBD') and info['lictype'] == 'GROUP':
|
|
595
|
+
raise CliError('"TBD" is invalid product name for group license')
|
|
596
|
+
|
|
597
|
+
fmt = '%-16s: %s'
|
|
598
|
+
lines.extend([
|
|
599
|
+
'',
|
|
600
|
+
fmt % ('License Type', 'pyarmor-' + info['lictype'].lower()),
|
|
601
|
+
fmt % ('License To', info['regname']),
|
|
602
|
+
fmt % ('License Product', info['product']),
|
|
603
|
+
'',
|
|
604
|
+
])
|
|
605
|
+
if info['product'] == 'non-profits':
|
|
606
|
+
lines.append('This license is about to be used for non-profits')
|
|
607
|
+
if info['product'] in ('', 'TBD'):
|
|
608
|
+
lines.append('This license is bind to non-profits(TBD) '
|
|
609
|
+
'for the time being')
|
|
610
|
+
lines.append('If not change "TBD" to product name in 6 months, '
|
|
611
|
+
'it will be set to "non-profits" automatically')
|
|
612
|
+
else:
|
|
613
|
+
lines.append('This license is about to be used for product "%s"'
|
|
614
|
+
% info['product'])
|
|
615
|
+
if info['product'] not in ('', 'TBD'):
|
|
616
|
+
lines.append('')
|
|
617
|
+
lines.append('IMPORTANT: PRODUCT NAME CAN NOT BE CHANGED '
|
|
618
|
+
'AFTER INITIAL REGISTRATION')
|
|
619
|
+
|
|
620
|
+
lines.extend(['', ''])
|
|
621
|
+
return info, '\n'.join(lines)
|
|
622
|
+
|
|
623
|
+
def upgrade_to_pro(self, keyfile, product):
|
|
624
|
+
raise CliError('this feature is not available now')
|
|
625
|
+
|
|
626
|
+
logger.info('process upgrading file "%s"', keyfile)
|
|
627
|
+
reginfo = self.parse_keyfile(keyfile)
|
|
628
|
+
|
|
629
|
+
rcode = self._get_old_rcode()
|
|
630
|
+
logger.info('old license no: %s', rcode)
|
|
631
|
+
|
|
632
|
+
url = self.regurl(reginfo[1], product=product, rcode=rcode)
|
|
633
|
+
logger.debug('url: %s', url)
|
|
634
|
+
|
|
635
|
+
logger.info('send upgrade request to server')
|
|
636
|
+
res = self._send_request(url)
|
|
637
|
+
regfile, lictype = self._handle_response(res)
|
|
638
|
+
|
|
639
|
+
logger.info('update license token')
|
|
640
|
+
self.update_token()
|
|
641
|
+
logger.info('This license has been upgraded successfully')
|
|
642
|
+
|
|
643
|
+
notes = '* Please backup regfile "%s" carefully, and ' \
|
|
644
|
+
'use this file for subsequent registration' % regfile,
|
|
645
|
+
logger.info('Import Notes:\n\n%s\n', notes)
|
|
646
|
+
|
|
647
|
+
def register(self, keyfile, product, upgrade=False, group=False):
|
|
648
|
+
if keyfile.endswith('.zip'):
|
|
649
|
+
logger.info('register "%s"', keyfile)
|
|
650
|
+
self.register_regfile(keyfile)
|
|
651
|
+
return
|
|
652
|
+
|
|
653
|
+
logger.info('process activation file "%s"', keyfile)
|
|
654
|
+
reginfo = self.parse_keyfile(keyfile)
|
|
655
|
+
|
|
656
|
+
url = self.regurl(reginfo[1], product=product)
|
|
657
|
+
url += '&rev=' + str(self.LICENSE_REVSION)
|
|
658
|
+
if upgrade:
|
|
659
|
+
url += '&upgrade_to_basic=1'
|
|
660
|
+
logger.debug('url: %s', url)
|
|
661
|
+
|
|
662
|
+
logger.info('send request to server')
|
|
663
|
+
res = self._send_request(url)
|
|
664
|
+
regfile, lictype = self._handle_response(res)
|
|
665
|
+
|
|
666
|
+
logger.info('')
|
|
667
|
+
logger.info('the registration file "%s" has been generated', regfile)
|
|
668
|
+
logger.info('this license has been activated sucessfully')
|
|
669
|
+
|
|
670
|
+
notes = [
|
|
671
|
+
'* Please backup "%s", but do not use it to '
|
|
672
|
+
'register Pyarmor' % os.path.basename(keyfile),
|
|
673
|
+
'* Please backup regfile "%s", and '
|
|
674
|
+
'use this file for next any registration' % regfile,
|
|
675
|
+
'* Do not use this file in docker and CI/CD pipeline',
|
|
676
|
+
'',
|
|
677
|
+
]
|
|
678
|
+
logger.info('\n\nImport Notes:\n%s\n', '\n'.join(notes))
|
|
679
|
+
|
|
680
|
+
input('Type Enter to continue ...')
|
|
681
|
+
LicenseHelper(self).run(lictype, regfile)
|
|
682
|
+
|
|
683
|
+
def _handle_response(self, res):
|
|
684
|
+
if res and res.code == 200:
|
|
685
|
+
lictype = None
|
|
686
|
+
dis = res.headers.get('Content-Disposition')
|
|
687
|
+
filename = dis.split('"')[1] if dis else 'pyarmor-regfile.zip'
|
|
688
|
+
logger.info('write registration file "%s"', filename)
|
|
689
|
+
data = res.read()
|
|
690
|
+
if data.startswith(b'{"group":'):
|
|
691
|
+
lictype = 'G'
|
|
692
|
+
n = data.find(b'}') + 1
|
|
693
|
+
with open(filename, 'wb') as f:
|
|
694
|
+
f.write(data[n:])
|
|
695
|
+
self._write_group_info(filename, data[:n])
|
|
696
|
+
elif data.startswith(b'REGINFO:'):
|
|
697
|
+
i = len(b'REGINFO:')
|
|
698
|
+
n = data[i] + (data[i+1] << 8)
|
|
699
|
+
i += 2
|
|
700
|
+
n += i
|
|
701
|
+
with open(filename, 'wb') as f:
|
|
702
|
+
f.write(data[n:])
|
|
703
|
+
reginfo = data[i:n]
|
|
704
|
+
lictype = json_loads(reginfo).get('type', None)
|
|
705
|
+
self._write_reg_info(filename, reginfo)
|
|
706
|
+
else:
|
|
707
|
+
# Only for request group token
|
|
708
|
+
logger.debug('no REGINFO found')
|
|
709
|
+
with open(filename, 'wb') as f:
|
|
710
|
+
f.write(data)
|
|
711
|
+
return filename, lictype
|
|
712
|
+
|
|
713
|
+
elif res:
|
|
714
|
+
data = res.read()
|
|
715
|
+
logger.debug('server return(%d): %s', res.code, data)
|
|
716
|
+
try:
|
|
717
|
+
msg = data.decode('utf-8')
|
|
718
|
+
except Exception as e:
|
|
719
|
+
logger.debug('decode server data error "%s"', e)
|
|
720
|
+
msg = data
|
|
721
|
+
raise CliError(msg)
|
|
722
|
+
|
|
723
|
+
raise CliError('no response from license server')
|
|
724
|
+
|
|
725
|
+
def _write_reg_info(self, filename, data):
|
|
726
|
+
from zipfile import ZipFile
|
|
727
|
+
logger.info('write reg information')
|
|
728
|
+
with ZipFile(filename, 'a') as f:
|
|
729
|
+
f.writestr('reg.info', data)
|
|
730
|
+
|
|
731
|
+
def _write_group_info(self, filename, data):
|
|
732
|
+
from zipfile import ZipFile
|
|
733
|
+
logger.info('write group information')
|
|
734
|
+
with ZipFile(filename, 'a') as f:
|
|
735
|
+
f.writestr('group.info', data)
|
|
736
|
+
|
|
737
|
+
def request_device_regfile(self, regfile, devid):
|
|
738
|
+
from zipfile import ZipFile
|
|
739
|
+
devfile = self.ctx.group_device_file(devid)
|
|
740
|
+
rev = self.LICENSE_REVSION
|
|
741
|
+
logger.info('request device regfile "%s" (v%d)', devfile, rev)
|
|
742
|
+
logger.info('use group license "%s"', regfile)
|
|
743
|
+
if not os.path.exists(devfile):
|
|
744
|
+
logger.error('please generate device file in offline device by')
|
|
745
|
+
logger.error(' pyarmor reg -g %s', devid)
|
|
746
|
+
logger.error('and copy generated device file to this machine')
|
|
747
|
+
raise CliError('no group device file "%s"' % devfile)
|
|
748
|
+
|
|
749
|
+
with open(devfile) as f:
|
|
750
|
+
prefix = 'machine:'
|
|
751
|
+
for line in f:
|
|
752
|
+
if line.startswith(prefix):
|
|
753
|
+
machid = line[len(prefix):].strip()
|
|
754
|
+
break
|
|
755
|
+
else:
|
|
756
|
+
logger.error('no machid information in device file')
|
|
757
|
+
raise CliError('invalid device file "%s"' % devfile)
|
|
758
|
+
|
|
759
|
+
with ZipFile(regfile, 'r') as f:
|
|
760
|
+
if 'group.info' not in f.namelist():
|
|
761
|
+
logger.error('no group information in group license file')
|
|
762
|
+
raise CliError('invalid group license file "%s"' % regfile)
|
|
763
|
+
group = json_loads(f.read('group.info'))
|
|
764
|
+
licdata = f.read('license.lic')
|
|
765
|
+
capsule = f.read('.pyarmor_capsule.zip')
|
|
766
|
+
|
|
767
|
+
# Ignore token cache
|
|
768
|
+
tokencache = os.path.join(os.path.dirname(devfile), 'tokens', machid)
|
|
769
|
+
if False and os.path.exists(tokencache):
|
|
770
|
+
logger.info('read cached "%s"', tokencache)
|
|
771
|
+
with open(tokencache, 'rb') as f:
|
|
772
|
+
data = f.read()
|
|
773
|
+
filename = regfile.replace('pyarmor-', 'pyarmor-device-').replace(
|
|
774
|
+
'.zip', '.%s.zip' % devid)
|
|
775
|
+
logger.info('write registeration file "%s"', filename)
|
|
776
|
+
else:
|
|
777
|
+
logger.info('send request to server')
|
|
778
|
+
url = self.regurl('/'.join(['group', group['ucode']]))
|
|
779
|
+
paras = ('rev', str(rev)), ('group', str(group['group'])), \
|
|
780
|
+
('source', machid), ('devid', str(devid))
|
|
781
|
+
url += '&'.join(['='.join(x) for x in paras])
|
|
782
|
+
logger.debug('url: %s', url)
|
|
783
|
+
|
|
784
|
+
res = self._send_request(url)
|
|
785
|
+
filename, lictype = self._handle_response(res)
|
|
786
|
+
with open(filename, 'rb') as f:
|
|
787
|
+
data = f.read()
|
|
788
|
+
os.makedirs(os.path.dirname(tokencache), exist_ok=True)
|
|
789
|
+
with open(tokencache, 'wb') as f:
|
|
790
|
+
f.write(data)
|
|
791
|
+
|
|
792
|
+
with ZipFile(filename, 'w') as f:
|
|
793
|
+
f.writestr('license.lic', licdata)
|
|
794
|
+
f.writestr('.pyarmor_capsule.zip', capsule)
|
|
795
|
+
f.writestr('group.tokens', b'')
|
|
796
|
+
f.writestr('dev.info', ('%s,%s' % (devid, machid)).encode('utf-8'))
|
|
797
|
+
f.writestr('tokens/' + machid, data)
|
|
798
|
+
|
|
799
|
+
logger.info('please copy deivce regfile to offline device and run')
|
|
800
|
+
logger.info(' pyarmor reg %s', filename)
|
|
801
|
+
|
|
802
|
+
register_group_device = request_device_regfile
|
|
803
|
+
|
|
804
|
+
def _write_ci_info(self, filename, data):
|
|
805
|
+
from zipfile import ZipFile
|
|
806
|
+
logger.info('write ci information')
|
|
807
|
+
with ZipFile(filename, 'a') as f:
|
|
808
|
+
f.writestr('ci.token', data)
|
|
809
|
+
|
|
810
|
+
def request_ci_regfile(self, regfile):
|
|
811
|
+
rev = self.LICENSE_REVSION
|
|
812
|
+
cirev = self.CI_LICENSE_REVSION
|
|
813
|
+
logger.info('request ci regfile (v%d) by "%s"', cirev, regfile)
|
|
814
|
+
from zipfile import ZipFile
|
|
815
|
+
|
|
816
|
+
with ZipFile(regfile, 'r') as f:
|
|
817
|
+
if 'reg.info' not in f.namelist():
|
|
818
|
+
logger.error('missing reg.info in regfile, '
|
|
819
|
+
'this license may be out of date, '
|
|
820
|
+
'please check Pyarmor upgrade notes')
|
|
821
|
+
raise CliError('can not request CI license')
|
|
822
|
+
reginfo = json_loads(f.read('reg.info'))
|
|
823
|
+
|
|
824
|
+
ucode = reginfo['ucode']
|
|
825
|
+
rcode = reginfo['rcode']
|
|
826
|
+
if len(ucode) != 192:
|
|
827
|
+
raise CliError('invalid ci registration file "%s"', regfile)
|
|
828
|
+
|
|
829
|
+
url = self.regurl('ci/%s' % ucode)
|
|
830
|
+
paras = ('rev', str(rev)), ('cirev', str(cirev))
|
|
831
|
+
url += '&'.join(['='.join(x) for x in paras])
|
|
832
|
+
logger.debug('url: %s', url)
|
|
833
|
+
|
|
834
|
+
logger.info('send request to server')
|
|
835
|
+
res = self._send_request(url)
|
|
836
|
+
|
|
837
|
+
logger.info('handle response')
|
|
838
|
+
if res is None:
|
|
839
|
+
raise CliError('no response from license server')
|
|
840
|
+
|
|
841
|
+
elif res.code != 200:
|
|
842
|
+
raise CliError(res.read().decode('utf-8'))
|
|
843
|
+
|
|
844
|
+
data = res.read()
|
|
845
|
+
if not data.startswith(b'CITOKEN:'):
|
|
846
|
+
raise CliError('wrong server data: "%s"' % data)
|
|
847
|
+
|
|
848
|
+
i = len(b'CITOKEN:') + 2
|
|
849
|
+
n = data[i-2] + (data[i-1] << 8)
|
|
850
|
+
token = data[i:i+n]
|
|
851
|
+
|
|
852
|
+
rn = rcode[-6:].lstrip('0')
|
|
853
|
+
cifile = 'pyarmor-ci-%s.zip' % rn
|
|
854
|
+
|
|
855
|
+
with ZipFile(regfile, 'r') as src:
|
|
856
|
+
with ZipFile(cifile, 'w') as dst:
|
|
857
|
+
for x in src.namelist():
|
|
858
|
+
dst.writestr(x, src.read(x))
|
|
859
|
+
dst.writestr('ci.token', token)
|
|
860
|
+
|
|
861
|
+
logger.info('generate CI regfile "%s" successfully', cifile)
|
|
862
|
+
|
|
863
|
+
ver = '.'.join([str(x) for x in self.ctx.version])
|
|
864
|
+
logger.info('\n\nCheck CI license in local machine by:\n'
|
|
865
|
+
'\n\tpyarmor reg %s\n\n'
|
|
866
|
+
'Register Pyarmor in CI/CD pipeline by:\n'
|
|
867
|
+
'\n\tpip install pyarmor==%s\n'
|
|
868
|
+
'\tpyarmor reg %s\n',
|
|
869
|
+
cifile, ver, cifile)
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
BASIC_LICENSE_HELP_INFO = Template('''
|
|
873
|
+
Using Basic license in CI/CD pipeline or docker container need extra steps, please check this page
|
|
874
|
+
|
|
875
|
+
$docurl/how-to/ci.html
|
|
876
|
+
|
|
877
|
+
More usage about Basic License, check this page
|
|
878
|
+
|
|
879
|
+
$docurl/how-to/register.html
|
|
880
|
+
|
|
881
|
+
If need register Pyarmor in build device, run this command:
|
|
882
|
+
|
|
883
|
+
pyarmor reg $regfile
|
|
884
|
+
|
|
885
|
+
''')
|
|
886
|
+
|
|
887
|
+
PRO_LICENSE_HELP_INFO = Template('''
|
|
888
|
+
Pro license can't be used in CI/CD pipeline or docker container direclty.
|
|
889
|
+
|
|
890
|
+
A few times for debug purpose, about 60 runs per month, may work.
|
|
891
|
+
|
|
892
|
+
But there is one workaroud for Pro licnese in CI/CD pipeline, please check this page
|
|
893
|
+
|
|
894
|
+
$docurl/how-to/ci.html
|
|
895
|
+
|
|
896
|
+
More usage about Pro License, check this page
|
|
897
|
+
|
|
898
|
+
$docurl/how-to/register.html
|
|
899
|
+
|
|
900
|
+
If need register Pyarmor in build device, run this command:
|
|
901
|
+
|
|
902
|
+
pyarmor reg $regfile
|
|
903
|
+
|
|
904
|
+
''')
|
|
905
|
+
|
|
906
|
+
GROUP_LICENSE_HELP_INFO = Template('''
|
|
907
|
+
Group License file `$regfile` is only used to request device regfile
|
|
908
|
+
|
|
909
|
+
In order to register Pyarmor in offline device:
|
|
910
|
+
|
|
911
|
+
1. In the build device (may be offline), generate device info by this command
|
|
912
|
+
|
|
913
|
+
pyarmor reg -g 1
|
|
914
|
+
|
|
915
|
+
2. In any online device, copy device file generated by first step, then request device regfile for this device
|
|
916
|
+
|
|
917
|
+
cp pyarmor-group-device.1 .pyarmor/group/
|
|
918
|
+
pyarmor reg -g 1 $regfile
|
|
919
|
+
|
|
920
|
+
3. In the build device, register Pyarmor by device regfile
|
|
921
|
+
|
|
922
|
+
pyarmor reg pyarmor-device-regfile-xxxx.1.zip
|
|
923
|
+
|
|
924
|
+
More usage about CI License, check section `Using group licnese`
|
|
925
|
+
|
|
926
|
+
$docurl/how-to/register.html
|
|
927
|
+
''')
|
|
928
|
+
|
|
929
|
+
CI_LICENSE_HELP_INFO = Template('''
|
|
930
|
+
CI license file `$regfile` is only used to request CI regfile
|
|
931
|
+
|
|
932
|
+
CI regfile is used to register Pyarmor in the CI/CD pipeline
|
|
933
|
+
|
|
934
|
+
If need request CI regfile, run this command:
|
|
935
|
+
|
|
936
|
+
pyarmor reg -C $regfile
|
|
937
|
+
|
|
938
|
+
Note that CI regfile can NOT be used in physical machine, and there is rate limits to register CI regfile in CI/CD pipeline and docker container.
|
|
939
|
+
|
|
940
|
+
It may need request new CI regfile after Pyarmor is upgraded, please check section `When need to request new CI regfile` in this page
|
|
941
|
+
|
|
942
|
+
$docurl/how-to/ci.html
|
|
943
|
+
|
|
944
|
+
More usage about CI License, check these pages
|
|
945
|
+
|
|
946
|
+
$docurl/how-to/register.html
|
|
947
|
+
$docurl/how-to/ci.html
|
|
948
|
+
''')
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
class LicenseHelper(object):
|
|
952
|
+
"""Only used for first activate license.
|
|
953
|
+
|
|
954
|
+
Help the beginner to understand how to use different licenses.
|
|
955
|
+
|
|
956
|
+
"""
|
|
957
|
+
|
|
958
|
+
def __init__(self, parent):
|
|
959
|
+
self.parent = parent
|
|
960
|
+
self.docurl = parent.ctx.cfg.get(
|
|
961
|
+
'pyarmor', 'docurl').rstrip('/').replace('{lang}', 'en')
|
|
962
|
+
self.print = print
|
|
963
|
+
|
|
964
|
+
def run(self, lictype, regfile):
|
|
965
|
+
if lictype == 'G':
|
|
966
|
+
self._group_license_helper(regfile)
|
|
967
|
+
|
|
968
|
+
elif lictype == 'C':
|
|
969
|
+
self._ci_license_helper(regfile)
|
|
970
|
+
|
|
971
|
+
elif lictype == 'Z':
|
|
972
|
+
self._pro_license_helper(regfile)
|
|
973
|
+
|
|
974
|
+
elif lictype in ('J', 'B', 'P'):
|
|
975
|
+
self._basic_license_helper(regfile)
|
|
976
|
+
|
|
977
|
+
else:
|
|
978
|
+
raise CliError('unknown license type "%s"' % lictype)
|
|
979
|
+
|
|
980
|
+
def _basic_license_helper(self, regfile):
|
|
981
|
+
self.print(BASIC_LICENSE_HELP_INFO.substitute(
|
|
982
|
+
docurl=self.docurl, regfile=regfile))
|
|
983
|
+
|
|
984
|
+
prompt = 'Yes (y), No (n), Quit (q): '
|
|
985
|
+
self.print('Show basic license usage in webbrowser? (n)')
|
|
986
|
+
choice = show_help_page([prompt], self.docurl + '/how-to/register.html')
|
|
987
|
+
if choice == 'q':
|
|
988
|
+
return
|
|
989
|
+
|
|
990
|
+
self.print('Show basic license for CI/CD pipeline or docker? (n)')
|
|
991
|
+
choice = show_help_page([prompt], self.docurl + '/how-to/ci.html')
|
|
992
|
+
if choice == 'q':
|
|
993
|
+
return
|
|
994
|
+
|
|
995
|
+
self.print('Do you want register Pyarmor in this machine? (y)')
|
|
996
|
+
choice = input(prompt).lower()[:1]
|
|
997
|
+
if choice == 'q':
|
|
998
|
+
return
|
|
999
|
+
|
|
1000
|
+
if choice in ('y', ''):
|
|
1001
|
+
self.print('register "%s"' % regfile)
|
|
1002
|
+
self.parent.register_regfile(regfile)
|
|
1003
|
+
self.print('This license registration information:\n\n'
|
|
1004
|
+
'%s' % self.parent)
|
|
1005
|
+
|
|
1006
|
+
def _pro_license_helper(self, regfile):
|
|
1007
|
+
self.print(PRO_LICENSE_HELP_INFO.substitute(
|
|
1008
|
+
docurl=self.docurl, regfile=regfile))
|
|
1009
|
+
|
|
1010
|
+
prompt = 'Yes (y), No (n), Quit (q): '
|
|
1011
|
+
self.print('Show pro license usage in webbrowser? (n)')
|
|
1012
|
+
choice = show_help_page([prompt], self.docurl + '/how-to/register.html')
|
|
1013
|
+
if choice == 'q':
|
|
1014
|
+
return
|
|
1015
|
+
|
|
1016
|
+
self.print('Show pro license for CI/CD pipeline or docker? (n)')
|
|
1017
|
+
choice = show_help_page([prompt], self.docurl + '/how-to/ci.html')
|
|
1018
|
+
if choice == 'q':
|
|
1019
|
+
return
|
|
1020
|
+
|
|
1021
|
+
self.print('Do you want register Pyarmor in this machine? (y)')
|
|
1022
|
+
choice = input(prompt).lower()[:1]
|
|
1023
|
+
if choice == 'q':
|
|
1024
|
+
return
|
|
1025
|
+
|
|
1026
|
+
if choice in ('y', ''):
|
|
1027
|
+
self.print('register "%s"' % regfile)
|
|
1028
|
+
self.parent.register_regfile(regfile)
|
|
1029
|
+
self.print('This license registration information:\n\n'
|
|
1030
|
+
'%s' % self.parent)
|
|
1031
|
+
|
|
1032
|
+
def _group_license_helper(self, regfile):
|
|
1033
|
+
self.print(GROUP_LICENSE_HELP_INFO.substitute(
|
|
1034
|
+
docurl=self.docurl, regfile=regfile))
|
|
1035
|
+
|
|
1036
|
+
prompt = 'Yes (y), No (n), Quit (q): '
|
|
1037
|
+
self.print('Show group license usage in webbrowser? (n)')
|
|
1038
|
+
choice = show_help_page([prompt], self.docurl + '/how-to/register.html')
|
|
1039
|
+
if choice == 'q':
|
|
1040
|
+
return
|
|
1041
|
+
|
|
1042
|
+
self.print('Do you want register Pyarmor in this machine? (n)')
|
|
1043
|
+
choice = input(prompt).lower()[:1]
|
|
1044
|
+
if choice == 'q':
|
|
1045
|
+
return
|
|
1046
|
+
|
|
1047
|
+
if choice != 'y':
|
|
1048
|
+
return
|
|
1049
|
+
|
|
1050
|
+
self.print('Please assign one unused device no. to this device, '
|
|
1051
|
+
'starts from 1')
|
|
1052
|
+
devid = None
|
|
1053
|
+
while devid is None:
|
|
1054
|
+
a = input('Type device no. (default is 1): ')
|
|
1055
|
+
if a == '':
|
|
1056
|
+
devid = 1
|
|
1057
|
+
elif (not a.isdigit()) or int(a) < 1 or int(a) > 100:
|
|
1058
|
+
self.print('invalid input')
|
|
1059
|
+
else:
|
|
1060
|
+
devid = int(a)
|
|
1061
|
+
|
|
1062
|
+
if devid:
|
|
1063
|
+
sep = '-' * 16
|
|
1064
|
+
devinfo = self.parent.ctx.group_device_file(devid)
|
|
1065
|
+
self.print('%s 1. generate device info' % sep)
|
|
1066
|
+
if os.path.exists(devinfo):
|
|
1067
|
+
logger.warning('old device file has been exists')
|
|
1068
|
+
else:
|
|
1069
|
+
self.parent.generate_group_device(devid)
|
|
1070
|
+
self.print('%s 2. request device regfile' % sep)
|
|
1071
|
+
self.parent.request_device_regfile(regfile, devid)
|
|
1072
|
+
self.print('%s 3. register Pyarmor with device regfile' % sep)
|
|
1073
|
+
self.parent.register(regfile.
|
|
1074
|
+
replace('.zip', '.%d.zip' % devid).
|
|
1075
|
+
replace('-regfile', '-device-regfile'))
|
|
1076
|
+
|
|
1077
|
+
def _ci_license_helper(self, regfile):
|
|
1078
|
+
self.print(CI_LICENSE_HELP_INFO.substitute(
|
|
1079
|
+
docurl=self.docurl, regfile=regfile))
|
|
1080
|
+
|
|
1081
|
+
prompt = 'Yes (y), No (n), Quit (q): '
|
|
1082
|
+
self.print('Show ci license usage in webbrowser? (n)')
|
|
1083
|
+
choice = show_help_page([prompt], self.docurl + '/how-to/ci.html')
|
|
1084
|
+
if choice == 'q':
|
|
1085
|
+
return
|
|
1086
|
+
|
|
1087
|
+
self.print('Do you want request one CI Regfile now? (y)')
|
|
1088
|
+
choice = input(prompt).lower()[:1]
|
|
1089
|
+
if choice == 'q':
|
|
1090
|
+
return
|
|
1091
|
+
|
|
1092
|
+
if choice in ('y', ''):
|
|
1093
|
+
self.parent.request_ci_regfile(regfile)
|