manage.py 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261
  1. #!/usr/bin/env python3
  2. import shutil
  3. import sys
  4. import datetime
  5. import platform
  6. import os
  7. from urllib.request import urlopen, HTTPError
  8. import re
  9. import subprocess
  10. import string
  11. import time
  12. import random
  13. import socket
  14. texts = {
  15. 'hello1': {
  16. 'en': 'SafeLine is a self-hosted WAF(Web Application Firewall) to protect your web apps from attacks and exploits.',
  17. 'zh': 'SafeLine,中文名 "雷池",是一款简单好用, 效果突出的 Web 应用防火墙(WAF),可以保护 Web 服务不受黑客攻击。'
  18. },
  19. 'hello2': {
  20. 'en': 'A web application firewall helps protect web apps by filtering and monitoring HTTP traffic between a web application and the Internet. It typically protects web apps from attacks such as SQL injection, XSS, code injection, os command injection, CRLF injection, ldap injection, xpath injection, RCE, XXE, SSRF, path traversal, backdoor, bruteforce, http-flood, bot abused, among others.',
  21. 'zh': '雷池通过过滤和监控 Web 应用与互 联网之间的 HTTP 流量来保护 Web 服务。可以保护 Web 服务免受 SQL 注入、XSS 、 代码注入、命令注入、CRLF 注入、ldap 注入、xpath 注入、RCE、XXE、SSRF、路径遍历、后门、暴力破解、CC、爬虫 等攻击。'
  22. },
  23. 'talking-group': {
  24. 'en': '\n'
  25. 'https://discord.gg/SVnZGzHFvn\n'
  26. '\n'
  27. 'Join discord group for more informations of SafeLine by above address',
  28. 'zh': '\n'
  29. '▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄\n'
  30. '█ ▄▄▄▄▄ █▀ █▀▀██▀▄▀▀▄▀▄▀▄██ ▄▄▄▄▄ █\n'
  31. '█ █ █ █▀ ▄ █▀▄▄▀▀ ▄█▄ ▀█ █ █ █\n'
  32. '█ █▄▄▄█ █▀█ █▄█▄▀▀▄▀▄ ▀▀▄▄█ █▄▄▄█ █\n'
  33. '█▄▄▄▄▄▄▄█▄█▄█ █▄▀ █ ▀▄▀ █▄█▄▄▄▄▄▄▄█\n'
  34. '█▄ ▄▄ █▄▄ ▄█▄▄▄▄▀▄▀▀▄██ ▄▄▀▄█▄▀ ▀█\n'
  35. '█▄ ▄▀▄ ▄▀▄ ▀ ▄█▀ ▀▄ █▀▀ ▀█▀▄██▄▀▄██\n'
  36. '██ ▀▄█ ▄ ▄▄▀▄▀▀█▄▀▄▄▀▄▀▄ ▄ ▀▄▄▄█▀▀█\n'
  37. '█ █▀▄▀ ▄▀▄▄▀█▀ ▄▄ █▄█▀▀▄▀▀█▄█▄█▀▄██\n'
  38. '█ █ ▀ ▄▀▀ ██▄█▄▄▄▄▄▀▄▀▀▀▄▄▀█▄▀█ ▀█\n'
  39. '█ █ ▀▄ ▄██▀▀ ▄█▀ ▀███▄ ▀▄▀▄▄ ▄▀▄██\n'
  40. '█▀▄▄█ ▄▀▄▀ ▄▀▀▀▄▀▄▀ ▄▀▄ ▄▀ ▄▀█ ▀█\n'
  41. '█ █ █ █▄▀ █▄█▀ ▄▄███▀▀▀▄█▀▄ ▀ ▀▄██\n'
  42. '█▄███▄█▄▄▀▄ █▄█▄▄▄▄▀▀▄█▀▀ ▄▄▄ ▀█ █\n'
  43. '█ ▄▄▄▄▄ █▄▀█ ▄█▀▄ █▀█▄ ▀ █▄█ ▀▄▀█\n'
  44. '█ █ █ █ █▄▀▀▀▄▄▄▀▀▀▀▀▀ ▄▄ ▀█ █\n'
  45. '█ █▄▄▄█ █ ▀█▀ ▄▄▄▄ ▀█ ▀▀▄▀ ▀▀ ▀███\n'
  46. '█▄▄▄▄▄▄▄█▄▄██▄█▄▄█▄██▄██▄▄█▄▄█▄█▄██\n'
  47. '\n'
  48. '微信扫描上方二维码加入雷池项目讨论组'
  49. },
  50. 'input-target-path': {
  51. 'en': 'Input the path to install SafeLine WAF',
  52. 'zh': '请输入雷池 WAF 的安装目录'
  53. },
  54. 'input-mgt-port': {
  55. 'en': 'Input the mgt port',
  56. 'zh': '请输入雷池 WAF 的管理端口'
  57. },
  58. 'python-version-too-low': {
  59. 'en': 'The Python version is too low, Python 3.5 above is required',
  60. 'zh': 'Python 版本过低, 脚本无法运行, 需要 Python 3.5 以上'
  61. },
  62. 'not-a-tty': {
  63. 'en': 'Stdin is not a standard TTY',
  64. 'zh': '运行脚本的方式不对, STDIN 不是标准的 TTY'
  65. },
  66. 'not-root': {
  67. 'en': 'Requires root privileges to run',
  68. 'zh': '需要 root 权限才能运行'
  69. },
  70. 'not-linux': {
  71. 'en': 'SafeLine WAF does not support %s OS yet',
  72. 'zh': '雷池 WAF 暂时不支持 %s 操作系统'
  73. },
  74. 'unsupported-arch': {
  75. 'en': 'SafeLine WAF does not support %s processor yet',
  76. 'zh': '雷池 WAF 暂时不支持 %s 处理器'
  77. },
  78. 'prepare-to-install': {
  79. 'en': 'Will be going to installing SafeLine WAF for you.',
  80. 'zh': '即将为您安装雷池 WAF'
  81. },
  82. 'choice-action': {
  83. 'en': 'Choice what do you want to do',
  84. 'zh': '选择你要执行的动作'
  85. },
  86. 'default-value': {
  87. 'en': 'Keep blank default to',
  88. 'zh': '留空则为默认值'
  89. },
  90. 'ssse3-not-support': {
  91. 'en': 'SSSE3 instruction set not enabled in your CPU',
  92. 'zh': '当前 CPU 未启用 SSSE3 指令集'
  93. },
  94. 'precheck-failed': {
  95. 'en': 'The environment does not meet the installation conditions of SafeLine WAF',
  96. 'zh': '当前环境不符合雷池 WAF 的安装条件'
  97. },
  98. 'precheck-passed': {
  99. 'en': 'Installation environment check passed',
  100. 'zh': '检查安装环境已完成'
  101. },
  102. 'insufficient-memory': {
  103. 'en': 'Remaining memory is less than 1 GB',
  104. 'zh': '剩余内存不足 1 GB'
  105. },
  106. 'docker-not-installed': {
  107. 'en': 'Running SafeLine WAF requires Docker, but Docker is not installed',
  108. 'zh': '运行雷池 WAF 依赖 Docker, 但是 Docker 没安装'
  109. },
  110. 'docker-compose-not-installed': {
  111. 'en': 'Running SafeLine WAF requires Docker Compose, but Docker Compose is not installed',
  112. 'zh': '运行雷池 WAF 依赖 Docker Compose, 但是 Docker Compose 没安装'
  113. },
  114. 'docker-version-too-low': {
  115. 'en': 'Docker version is too low, it does not match SafeLine WAF',
  116. 'zh': 'Docker 版本太低, 不满足雷池 WAF 的安装需求'
  117. },
  118. 'if-install-docker': {
  119. 'en': 'Do you want the latest version of Docker to be automatically installed for you?',
  120. 'zh': '是否需要为你自动安装 Docker 的最新版本'
  121. },
  122. 'if-restart-docker': {
  123. 'en': 'Do you want to restart SafeLine WAF Docker container',
  124. 'zh': '是否需要重启雷池 WAF 的容器'
  125. },
  126. 'if-update-docker': {
  127. 'en': 'Do you want to update your Docker version?',
  128. 'zh': '是否需要为你自动更新 Docker 版本'
  129. },
  130. 'install-docker-failed': {
  131. 'en': 'Failed to install Docker. Please try to install Docker manually before installing SafeLine WAF',
  132. 'zh': '安装 Docker 失败, 请尝试手动安装 Docker 后再来安装雷池 WAF'
  133. },
  134. 'install-docker': {
  135. 'en': 'Docker is being installed for you. It will take a few minutes. Please wait patiently.',
  136. 'zh': '正在为你安装 Docker, 需要几分钟时间, 请耐心等待'
  137. },
  138. 'get-space-failed': {
  139. 'en': 'Unable to query disk capacity of "%s"',
  140. 'zh': '无法查询 "%s" 的磁盘容量'
  141. },
  142. 'remain-disk-capacity': {
  143. 'en': 'Disk capacity of "%s" has %s avaiable',
  144. 'zh': '"%s" 路径有 %s 的空间可用'
  145. },
  146. 'insufficient-disk-capacity': {
  147. 'en': 'Insufficient disk capacity of "%s", at least 5 GB is required to install SafeLine WAF',
  148. 'zh': '"%s" 的磁盘容量不足,安装雷池 WAF 至少需要 5 GB'
  149. },
  150. 'pg-pass-contains-invalid-char': {
  151. 'en': 'The POSTGRES_PASSWORD variable contains special characters and cannot be started normally',
  152. 'zh': 'POSTGRES_PASSWORD 变量包含特殊字符, 无法正常启动'
  153. },
  154. 'invalid-path': {
  155. 'en': '"%s" is not a valid absolute path',
  156. 'zh': '"%s" 不是合法的绝对路径'
  157. },
  158. 'path-exists': {
  159. 'en': '"%s" already exists, please select a new directory',
  160. 'zh': '路径 "%s" 已存在,请选择一个全新的目录'
  161. },
  162. 'fail-to-parse-route': {
  163. 'en': 'Unable to parse /proc/net/route file',
  164. 'zh': '无法解析 /proc/net/route 文件'
  165. },
  166. 'fail-to-download-compose': {
  167. 'en': 'Failed to download docker compose script',
  168. 'zh': '下载 docker compose 脚本失败'
  169. },
  170. 'fail-to-create-dir': {
  171. 'en': 'Unable to create the "%s" directory',
  172. 'zh': '无法创建 "%s" 目录'
  173. },
  174. 'docker-pull': {
  175. 'en': 'Pulling Docker image',
  176. 'zh': '正在拉取 Docker 镜像'
  177. },
  178. 'try-another-image-source': {
  179. 'en': 'Try another image source',
  180. 'zh': '尝试使用其他镜像源'
  181. },
  182. 'image-clean': {
  183. 'en': 'Cleaning Docker image',
  184. "zh": '正在清理 Docker 镜像'
  185. },
  186. 'update-config': {
  187. 'en': 'Updating .env configuration files',
  188. 'zh': '正在更新 .env 配置文件'
  189. },
  190. 'download-compose': {
  191. 'en': 'Downloading the compose.yaml file',
  192. 'zh': '正在下载 compose.yaml 文件'
  193. },
  194. 'download-reset-tengine': {
  195. 'en': 'Downloading the reset_tengine script',
  196. 'zh': '正在下载 reset_tengine.sh 文件'
  197. },
  198. 'fail-to-pull-image': {
  199. 'en': 'Failed to pull Docker image',
  200. 'zh': '拉取 Docker 镜像失败'
  201. },
  202. 'docker-up': {
  203. 'en': 'Starting Docker containers',
  204. 'zh': '正在启动 Docker 容器'
  205. },
  206. 'fail-to-up': {
  207. 'en': 'Failed to start Docker containers',
  208. 'zh': '启动 Docker 容器失败'
  209. },
  210. 'fail-to-down': {
  211. 'en': 'Failed to stop Docker containers',
  212. 'zh': '停止 Docker 容器失败'
  213. },
  214. 'install-finish': {
  215. 'en': 'SafeLine WAF installation completed',
  216. 'zh': '雷池 WAF 安装完成'
  217. },
  218. 'upgrade-finish': {
  219. 'en': 'SafeLine WAF upgrade completed',
  220. 'zh': '雷池 WAF 升级完成'
  221. },
  222. 'go-to-panel': {
  223. 'en': 'SafeLine WAF management panel: https://%s:%s/',
  224. 'zh': '雷池 WAF 管理面板: https://%s:%s/'
  225. }
  226. ,'install': {
  227. 'en': 'INSTALL',
  228. 'zh': '安装'
  229. },
  230. 'repair': {
  231. 'en': 'REPAIR',
  232. 'zh': '修复'
  233. },
  234. 'uninstall': {
  235. 'en': 'UNINSTALL',
  236. 'zh': '卸载'
  237. },
  238. 'upgrade': {
  239. 'en': 'UPGRADE',
  240. 'zh': '升级'
  241. },
  242. 'backup': {
  243. 'en': 'BACKUP',
  244. 'zh': '备份'
  245. },
  246. 'yes': {
  247. 'en': 'Yes',
  248. 'zh': '是'
  249. },
  250. 'no': {
  251. 'en': 'No',
  252. 'zh': '否'
  253. },
  254. 'fail-to-get-installed-dir': {
  255. 'en': 'Failed to get installed dir',
  256. 'zh': '未能找到安装目录',
  257. },
  258. 'fail-to-connect-image-source': {
  259. 'en': 'Failed to connect image source',
  260. 'zh': '无法连接到任何镜像源'
  261. },
  262. 'fail-to-connect-docker-source': {
  263. 'en': 'Failed to connect docker source',
  264. 'zh': '无法连接到任何 docker 源'
  265. },
  266. 'fail-to-download-docker-installation': {
  267. 'en': 'Failed to download docker installation',
  268. "zh": '下载 docker 安装脚本失败'
  269. },
  270. 'docker-source': {
  271. 'en': 'Docker source',
  272. "zh": 'docker 源'
  273. },
  274. 'reset-admin': {
  275. 'en': 'Setup admin',
  276. "zh": '设置 admin'
  277. },
  278. 'docker-version': {
  279. 'en': 'Checking docker version',
  280. 'zh': '检查 docker 版本'
  281. },
  282. 'docker-compose-version': {
  283. 'en': 'Checking docker compose version',
  284. 'zh': '检查 docker compose 版本'
  285. },
  286. 'keyboard-interrupt': {
  287. 'en': 'Installation cancelled',
  288. "zh": '取消安装'
  289. },
  290. 'docker-up-iptables-failed': {
  291. 'en': 'Iptables policy error, try to restart docker',
  292. 'zh': 'iptables 规则错误,尝试重启 docker'
  293. },
  294. 'install-channel': {
  295. 'en': 'Installing',
  296. 'zh': '安装通道'
  297. },
  298. 'preview-release': {
  299. 'en': 'Preview',
  300. 'zh': '预览版'
  301. },
  302. 'lts-release': {
  303. 'en': 'LTS',
  304. 'zh': 'LTS 版'
  305. },
  306. 'fail-to-docker-down': {
  307. 'en': 'Failed to stop container',
  308. 'zh': '停止 docker 容器失败'
  309. },
  310. 'fail-to-remove-dir': {
  311. 'en': 'Failed to remove safeline installation directory',
  312. 'zh': '删除雷池安装目录失败'
  313. },
  314. 'uninstall-finish': {
  315. 'en': 'SafeLine WAF uninstall completed',
  316. 'zh': '雷池 WAF 卸载完成'
  317. },
  318. 'docker-down': {
  319. 'en': 'Stopping SafeLine WAF container',
  320. 'zh': '正在停止雷池 WAF 容器'
  321. },
  322. 'reset-tengine': {
  323. 'en': 'RESET TENGINE CONFIG',
  324. 'zh': '重置 tengine 配置',
  325. },
  326. 'reset-postgres': {
  327. 'en': 'RESET DATABASE PASSWORD',
  328. 'zh': '重置数据库密码'
  329. },
  330. 'fail-to-find-nginx': {
  331. 'en': 'Failed to find tengine config path',
  332. 'zh': '未找到 tengine 配置目录'
  333. },
  334. 'nginx-backup-dir': {
  335. 'en': 'Tengine config backup directory',
  336. 'zh': 'tengine 配置备份目录'
  337. },
  338. 'fail-to-backup-nginx': {
  339. 'en': 'Failed to backup tengine config',
  340. 'zh': '备份 tengine 目录失败'
  341. },
  342. 'docker-restart': {
  343. 'en': 'Restart docker container',
  344. 'zh': '重启 docker 容器'
  345. },
  346. 'docker-exec': {
  347. 'en': 'Executing docker command',
  348. 'zh': '执行 docker 命令'
  349. },
  350. 'fail-to-recover-static': {
  351. 'en': 'Failed to recover tengine static config',
  352. 'zh': '恢复 tengine 静态站点资源失败'
  353. },
  354. 'fail-to-find-env': {
  355. 'en': 'Failed to find .env file',
  356. 'zh': '未找到 .env 文件'
  357. },
  358. 'fail-to-find-postgres-password': {
  359. 'en': 'Failed to find postgres password',
  360. 'zh': '未找到数据库密码'
  361. },
  362. 'fail-to-reset-postgres-password': {
  363. 'en': 'Failed to reset postgres password',
  364. 'zh': '重置数据库密码失败'
  365. },
  366. 'reset-postgres-password-finish': {
  367. 'en': 'Reset postgres password completed',
  368. 'zh': '重置数据库密码完成'
  369. },
  370. 'reset-tengine-finish': {
  371. 'en': 'Reset tengine finish completed',
  372. 'zh': '重置 tengine 配置完成'
  373. },
  374. 'if-remove-waf': {
  375. 'en': 'Do you want to uninstall SafeLine WAF, this operation will delete all data in the directory',
  376. 'zh': '是否确认卸载雷池,该操作会删除目录下所有数据'
  377. },
  378. 'restart-docker-finish': {
  379. 'en': 'Restart SafeLine WAF docker container completed',
  380. 'zh': '重启雷池 WAF 容器完成'
  381. },
  382. 'restart': {
  383. 'en': 'RESTART',
  384. 'zh': '重启'
  385. },
  386. 'wait-mgt-health': {
  387. 'en': 'Wait for safeline-mgt healthy',
  388. 'zh': '等待 safeline-mgt 启动'
  389. }
  390. }
  391. lang = ''
  392. def text(label, var=()):
  393. t = texts.get(label, {
  394. 'en': 'Unknown "%s" (%s)' % (label, var),
  395. 'zh': '未知变量 "%s" (%s)' % (label, var)
  396. })
  397. return t[lang if lang in t else 'en'] % var
  398. BOLD = 1
  399. DIM = 1
  400. BLINK = 5
  401. REVERSE = 7
  402. RED = 31
  403. GREEN = 32
  404. YELLOW = 33
  405. BLUE = 34
  406. CYAN = 36
  407. DEBUG = False
  408. LTS = False
  409. IMAGE_CLEAN = False
  410. EN = False
  411. INSTALL = False
  412. DOMAIN = 'waf-ce.chaitin.cn'
  413. def color(t, attrs=[], end=True):
  414. t = '\x1B[%sm%s' % (';'.join([str(i) for i in attrs]), t)
  415. if end:
  416. t = t + '\x1B[m'
  417. return t
  418. def banner():
  419. t = r'''
  420. ______ ___ _____ _ ____ ____ _ ________
  421. .' ____ \ .' ..] |_ _| (_) |_ _| |_ _| / \ |_ __ |
  422. | (___ \_| ,--. _| |_ .---. | | __ _ .--. .---. \ \ /\ / / / _ \ | |_ \_|
  423. _.____`. `'_\ : '-| |-' / /__\\ | | _ [ | [ `.-. | / /__\\ \ \/ \/ / / ___ \ | _|
  424. | \____) | // | |, | | | \__., _| |__/ | | | | | | | | \__., \ /\ / _/ / \ \_ _| |_
  425. \______.' \'-;__/ [___] '.__.' |________| [___] [___||__] '.__.' \/ \/ |____| |____| |_____|
  426. '''.strip('\n')
  427. print(color(t + '\n', [GREEN, BLINK]))
  428. class log():
  429. @staticmethod
  430. def _log(c, l, s):
  431. t = datetime.datetime.now().strftime('%H:%M:%S')
  432. print('\r\033[0;%dm[%-5s %s]: %s\033[0m' % (c, l, t, s))
  433. @staticmethod
  434. def debug(s):
  435. if DEBUG:
  436. log._log(DIM, 'DEBUG', s)
  437. @staticmethod
  438. def info(s):
  439. log._log(CYAN, 'INFO', s)
  440. @staticmethod
  441. def warning(s):
  442. log._log(YELLOW, 'WARN', s)
  443. @staticmethod
  444. def error(s):
  445. log._log(RED, 'ERROR', s)
  446. def get_url(url):
  447. try:
  448. response = urlopen(url)
  449. content = response.read()
  450. return content.decode('utf-8')
  451. except Exception as e:
  452. log.error(e)
  453. def ui_read(question, default):
  454. while True:
  455. if default is None:
  456. sys.stdout.write('%s: ' % (
  457. color(question, [GREEN]),
  458. ))
  459. else:
  460. sys.stdout.write('%s %s: ' % (
  461. color(question, [GREEN]),
  462. color('(%s %s)' % (text('default-value'), default), [YELLOW]),
  463. ))
  464. r = input().strip()
  465. if len(r) == 0:
  466. if default is None or len(default) == 0:
  467. continue
  468. r = default
  469. return r
  470. def ui_choice(question, options):
  471. while True:
  472. s_options = '[ %s ]' % ' '.join(['%s.%s' % option for option in options])
  473. s_choices = '(%s)' % '/'.join([option[0] for option in options])
  474. sys.stdout.write('%s %s %s: ' % (color(question, [GREEN]), color(s_options, [YELLOW]), color(s_choices, [YELLOW])))
  475. r = input().strip()
  476. if r in [i[0] for i in options]:
  477. return r
  478. def humen_size(x):
  479. if x >= 1024 * 1024 * 1024 * 1024:
  480. return '%.02f TB' % (x / 1024 / 1024 / 1024 / 1024)
  481. elif x >= 1024 * 1024 * 1024:
  482. return '%.02f GB' % (x / 1024 / 1024 / 1024)
  483. elif x >= 1024 * 1024:
  484. return '%.02f MB' % (x / 1024 / 1024)
  485. elif x >= 1024:
  486. return '%.02f KB' % (x / 1024)
  487. else:
  488. return '%d Bytes'
  489. def rand_subnet():
  490. routes = []
  491. try:
  492. with open('/proc/net/route', 'r') as f:
  493. next(f)
  494. for line in f:
  495. parts = line.split()
  496. if len(parts) < 8:
  497. continue
  498. destination = int(parts[1], 0x10)
  499. if destination == 0:
  500. continue
  501. mask = int(parts[7], 0x10)
  502. routes.append((destination, mask))
  503. except Exception as e:
  504. log.error(text('fail-to-parse-route') + ' ' + str(e))
  505. for i in range(256):
  506. t = 192
  507. t += 168 << 8
  508. t += i << 16
  509. for route in routes:
  510. if t & route[1] == route[0]:
  511. break
  512. else:
  513. return '%d.%d.%d' % (t & 0xFF, (t >> 8) & 0xFF, (t >> 16) & 0xFF)
  514. return '172.22.222'
  515. def free_space(path):
  516. while not os.path.exists(path) and path != '/':
  517. path = os.path.dirname(path)
  518. try:
  519. st = os.statvfs(path)
  520. free_bytes = st.f_bavail * st.f_frsize
  521. return free_bytes
  522. except Exception as e:
  523. log.error(text('get-space-failed', path) + ' ' + str(e))
  524. return None
  525. def free_memory():
  526. t = filter(lambda x: 'MemAvailable' in x, open('/proc/meminfo', 'r').readlines())
  527. return int(next(t).split()[1]) * 1024
  528. def exec_command(*args,shell=False):
  529. try:
  530. proc = subprocess.run(args, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True,shell=shell)
  531. subprocess_output(proc.stdout.strip())
  532. return proc.returncode, proc.stdout, proc.stderr
  533. except Exception as e:
  534. return -1, '', str(e)
  535. def exec_command_with_loading(*args, cwd=None, env=None):
  536. try:
  537. with subprocess.Popen(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=env, cwd=cwd) as proc:
  538. if not DEBUG:
  539. loading = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]
  540. iloading = 0
  541. while proc.poll() is None:
  542. sys.stderr.write('\r' + loading[iloading])
  543. sys.stderr.flush()
  544. iloading = (iloading + 1) % len(loading)
  545. time.sleep(0.1)
  546. sys.stderr.write('\r')
  547. sys.stderr.flush()
  548. else:
  549. for line in iter(proc.stdout.readline, b''):
  550. if line.strip() != '':
  551. log.debug(" -->> "+line.strip())
  552. if proc.poll() is not None and line == '':
  553. break
  554. return proc.returncode, proc.stdout.read(), proc.stderr.read()
  555. except Exception as e:
  556. return -1, '', str(e)
  557. def subprocess_output(stdout):
  558. if stdout != '':
  559. log.debug(" -->> "+stdout)
  560. else:
  561. log.debug(" -->> subprocess empty output")
  562. def start_docker():
  563. return exec_command('systemctl enable docker && systemctl daemon-reload && systemctl restart docker',shell=True)
  564. def check_port(port):
  565. if not INSTALL:
  566. return True
  567. try:
  568. with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
  569. s.bind(('0.0.0.0', int(port)))
  570. return True
  571. except Exception as e:
  572. log.debug("try listen mgt port failed: "+str(e))
  573. return False
  574. def install_docker():
  575. log.info(text('install-docker'))
  576. log.debug("downloading get-docker.sh")
  577. if not save_file_from_url('https://'+DOMAIN+'/release/latest/get-docker.sh','get-docker.sh'):
  578. raise Exception(text('fail-to-download-docker-installation'))
  579. source = docker_source()
  580. if source == '':
  581. raise Exception(text('fail-to-connect-docker-source'))
  582. log.debug(text('docker-source')+': '+source)
  583. env = {
  584. "DOWNLOAD_URL": source,
  585. "https_proxy": os.environ.get('https_proxy',''),
  586. }
  587. log.debug("installing docker")
  588. r = exec_command_with_loading('bash get-docker.sh',env=env)
  589. if r[0] == 0:
  590. p = start_docker()
  591. subprocess_output(p[1].strip())
  592. if p[0] != 0:
  593. log.error("start docker failed: "+p[2].strip())
  594. return p[0] == 0
  595. else:
  596. log.error("install docker error: "+r[2].strip())
  597. return False
  598. compose_command = ''
  599. def precheck_docker_compose():
  600. log.info(text("docker-compose-version"))
  601. global compose_command
  602. while True:
  603. version_output = ''
  604. proc = exec_command('docker', 'compose', 'version')
  605. if proc[0] == 0:
  606. help_proc = exec_command('docker', 'compose', 'up', '--help')
  607. if help_proc[0] == 0 and '--detach' in help_proc[1]:
  608. compose_command = 'docker compose'
  609. version_output = proc[1].strip()
  610. else:
  611. log.debug('docker compose can not find detach argument')
  612. else:
  613. compose_proc = exec_command('docker-compose', 'version')
  614. if compose_proc[0] == 0:
  615. help_proc = exec_command('docker-compose', 'up', '--help')
  616. if help_proc[0] == 0 and '--detach' in help_proc[1]:
  617. compose_command = 'docker-compose'
  618. version_output = compose_proc[1].strip()
  619. else:
  620. log.debug('docker-compose can not find detach argument')
  621. else:
  622. log.warning(text('docker-compose-not-installed'))
  623. if version_output != '':
  624. t = re.findall(r'^Docker Compose version v?(\d+)\.', version_output)
  625. if len(t) == 0:
  626. log.warning(text('docker-compose-not-installed'))
  627. elif int(t[0]) < 2:
  628. log.warning(text('docker-version-too-low'))
  629. else:
  630. return True
  631. action = ui_choice(text('if-update-docker'), [
  632. ('y', text('yes')),
  633. ('n', text('no')),
  634. ])
  635. if action.lower() == 'n':
  636. return False
  637. elif action.lower() == 'y':
  638. if not install_docker():
  639. log.warning(text('install-docker-failed'))
  640. return False
  641. def precheck():
  642. if platform.machine() in ('x86_64', 'AMD64') and 'ssse3' not in open('/proc/cpuinfo', 'r').read().lower():
  643. log.warning(text('ssse3-not-support'))
  644. return False
  645. if free_memory() < 1024 * 1024 * 1024:
  646. log.warning(text('insufficient-memory'))
  647. return False
  648. log.info(text("docker-version"))
  649. while True:
  650. proc = exec_command('docker', '--version')
  651. if proc[0] == 0:
  652. t = re.findall(r'^Docker version (\d+)\.', proc[1])
  653. if len(t) == 0:
  654. log.warning(text('docker-not-installed'))
  655. elif int(t[0]) < 20:
  656. log.warning(text('docker-version-too-low'))
  657. else:
  658. break
  659. else:
  660. log.warning(text('docker-not-installed'))
  661. action = ui_choice(text('if-install-docker'), [
  662. ('y', text('yes')),
  663. ('n', text('no')),
  664. ])
  665. if action.lower() == 'n':
  666. return False
  667. elif action.lower() == 'y':
  668. if not install_docker():
  669. log.warning(text('install-docker-failed'))
  670. return False
  671. if not precheck_docker_compose():
  672. return False
  673. return True
  674. def docker_pull(cwd):
  675. log.info(text('docker-pull'))
  676. try:
  677. subprocess.check_call(compose_command+' pull', cwd=cwd, shell=True)
  678. return True
  679. except Exception as e:
  680. log.warning("docker pull error: "+str(e))
  681. return False
  682. def docker_restart(container):
  683. log.info(text('docker-restart')+": "+container)
  684. try:
  685. subprocess.check_call('docker restart '+container, shell=True)
  686. return True
  687. except Exception as e:
  688. log.error("docker restart error: "+str(e))
  689. return False
  690. def docker_exec(container, command):
  691. log.info(text('docker-exec')+": ("+container+") "+command)
  692. try:
  693. subprocess.check_call('docker exec '+container+' '+command, shell=True)
  694. return True
  695. except Exception as e:
  696. log.error("docker exec error: "+str(e))
  697. return False
  698. def image_clean():
  699. log.info(text('image-clean'))
  700. proc = exec_command('docker image prune -f --filter="label=maintainer=SafeLine-CE"', shell=True)
  701. if proc[0] != 0:
  702. log.warning("remove docker image failed: "+proc[2])
  703. def docker_up(cwd):
  704. log.info(text('docker-up'))
  705. while True:
  706. p = exec_command_with_loading(compose_command+' up -d --remove-orphans', cwd=cwd)
  707. if p[0] == 0:
  708. return True
  709. if 'iptables failed' in p[2]:
  710. log.warning("docker up error: "+p[2])
  711. while True:
  712. action = ui_choice(text('docker-up-iptables-failed'),[
  713. ('y', text('yes')),
  714. ('n', text('no')),
  715. ])
  716. if action.lower() == 'y':
  717. start_docker()
  718. break
  719. elif action.lower() == 'n':
  720. return False
  721. else:
  722. log.error("docker up error: "+p[2])
  723. def docker_down(cwd):
  724. log.info(text('docker-down'))
  725. try:
  726. subprocess.check_call(compose_command+' down', cwd=cwd, shell=True)
  727. return True
  728. except Exception:
  729. return False
  730. def get_url_time(url):
  731. now = datetime.datetime.now()
  732. try:
  733. urlopen(url)
  734. except HTTPError as e:
  735. log.debug("get url "+url+" status: "+str(e.status))
  736. if e.status > 499:
  737. return 999999
  738. except Exception as e:
  739. log.debug("get url "+url+" failed: "+str(e))
  740. return 999999
  741. return (datetime.datetime.now() - now).microseconds / 1000
  742. def get_avg_delay(url):
  743. log.debug("test url avg delay: "+url)
  744. total_delay = 0
  745. for i in range(3):
  746. total_delay += get_url_time(url)
  747. avg_delay = total_delay / 3
  748. log.debug("url "+url+" avg delay: "+str(avg_delay))
  749. return avg_delay
  750. pull_failed_prefix = []
  751. def image_source():
  752. source = {
  753. 'https://registry-1.docker.io': 'chaitin',
  754. "https://swr.cn-east-3.myhuaweicloud.com": 'swr.cn-east-3.myhuaweicloud.com/chaitin-safeline'
  755. }
  756. min_delay = -1
  757. image_prefix = ''
  758. for url, prefix in source.items():
  759. if prefix in pull_failed_prefix:
  760. continue
  761. delay = get_avg_delay(url)
  762. if delay > 0 and (min_delay < 0 or delay < min_delay):
  763. min_delay = delay
  764. image_prefix = prefix
  765. log.debug("use image_prefix: "+image_prefix)
  766. return image_prefix
  767. def docker_source():
  768. sources = [
  769. "https://mirrors.aliyun.com/docker-ce/",
  770. "https://mirrors.tencent.com/docker-ce/",
  771. "https://download.docker.com"
  772. ]
  773. min_delay = -1
  774. source = ''
  775. for v in sources:
  776. delay = get_avg_delay(v)
  777. if delay > 0 and (min_delay < 0 or delay < min_delay):
  778. min_delay = delay
  779. source = v
  780. return source
  781. def read_config(path,config):
  782. with open(path, 'r') as f:
  783. for line in f.readlines():
  784. if line.strip() == '':
  785. continue
  786. try:
  787. s = line.index('=')
  788. if s > 0:
  789. k = line[:s].strip()
  790. v = line[s + 1:].strip()
  791. config[k] = v
  792. except ValueError:
  793. continue
  794. def generate_config(path):
  795. log.info(text('update-config'))
  796. config = {
  797. 'SAFELINE_DIR': path,
  798. 'POSTGRES_PASSWORD': '',
  799. 'MGT_PORT': '',
  800. 'RELEASE': '',
  801. 'CHANNEL': '',
  802. 'REGION': '',
  803. 'IMAGE_PREFIX': '',
  804. 'IMAGE_TAG': '',
  805. 'SUBNET_PREFIX': '',
  806. 'ARCH_SUFFIX': ''
  807. }
  808. env_path = os.path.join(path,'.env')
  809. if os.path.exists(env_path):
  810. read_config(env_path,config)
  811. if config['ARCH_SUFFIX'] == '':
  812. if platform.machine() == 'aarch64':
  813. config['ARCH_SUFFIX'] = '-arm'
  814. if config['POSTGRES_PASSWORD'] == '':
  815. config['POSTGRES_PASSWORD'] = ''.join([random.choice(string.ascii_letters + string.digits) for i in range(20)])
  816. if config['SUBNET_PREFIX'] == '':
  817. config['SUBNET_PREFIX'] = rand_subnet()
  818. if config['RELEASE'] == '' and LTS:
  819. config['RELEASE'] = '-lts'
  820. config['CHANNEL'] = '-lts'
  821. default_try = False
  822. if config['MGT_PORT'] == '9443':
  823. default_try = True
  824. while not config['MGT_PORT'].isnumeric() or int(config['MGT_PORT']) >= 65536 or int(config['MGT_PORT']) <= 0 or not check_port(config['MGT_PORT']):
  825. if not default_try:
  826. config['MGT_PORT'] = '9443'
  827. default_try = True
  828. else:
  829. config['MGT_PORT'] = ui_read(text('input-mgt-port'),None)
  830. if config['REGION'] == '' and EN:
  831. config['REGION'] = '-g'
  832. if not config['POSTGRES_PASSWORD'].isalnum():
  833. log.info(text('pg-pass-contains-invalid-char'))
  834. raise Exception(text('pg-pass-contains-invalid-char'))
  835. if config['IMAGE_PREFIX'] == '' or config['IMAGE_PREFIX'] in pull_failed_prefix:
  836. config['IMAGE_PREFIX'] = image_source()
  837. if config['IMAGE_PREFIX'] == '':
  838. raise Exception(text('fail-to-connect-image-source'))
  839. config['IMAGE_TAG'] = 'latest'
  840. with open(env_path, 'w') as f:
  841. for k in config:
  842. f.write('%s=%s\n' % (k, config[k]))
  843. return config
  844. def show_address(mgt_port):
  845. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  846. s.connect(("8.8.8.8", 80))
  847. local_ip = s.getsockname()[0]
  848. log.info(text('go-to-panel', (local_ip, mgt_port)))
  849. log.info(text('go-to-panel', ('0.0.0.0', mgt_port)))
  850. def reset_admin():
  851. log.info(text('reset-admin'))
  852. while True:
  853. p = exec_command('docker', 'inspect','--format=\'{{.State.Health.Status}}\'', 'safeline-mgt')
  854. if p[0] == 0 and p[1].strip().replace("'",'') == 'healthy':
  855. break
  856. elif p[0] != 0:
  857. log.debug("get safeline-mgt status error: "+str(p[2]))
  858. log.info(text('wait-mgt-health'))
  859. time.sleep(5)
  860. proc = exec_command('docker exec safeline-mgt /app/mgt-cli reset-admin --once',shell=True)
  861. if proc[0] != 0:
  862. log.warning(proc[2])
  863. elif proc[1].strip() != '':
  864. log.info('\n'+proc[1].strip())
  865. def install():
  866. global INSTALL
  867. INSTALL = True
  868. log.info(text('prepare-to-install'))
  869. if not precheck():
  870. log.error(text('precheck-failed'))
  871. return
  872. log.info(text('precheck-passed'))
  873. while True:
  874. safeline_path = ui_read(text('input-target-path'), '/data/safeline')
  875. if not safeline_path.startswith('/'):
  876. log.warning(text('invalid-path', safeline_path))
  877. continue
  878. if os.path.exists(safeline_path):
  879. log.warning(text('path-exists', safeline_path))
  880. continue
  881. if free_space(safeline_path) < 5 * 1024 * 1024 * 1024:
  882. log.warning(text('insufficient-disk-capacity', safeline_path))
  883. continue
  884. break
  885. try:
  886. os.makedirs(safeline_path)
  887. except Exception as e:
  888. log.error(text('fail-to-create-dir', safeline_path) + ' ' + str(e))
  889. return
  890. log.info(text('remain-disk-capacity', (safeline_path, humen_size(free_space(safeline_path)))))
  891. log.info(text('download-compose'))
  892. if not save_file_from_url('https://'+DOMAIN+'/release/latest/compose.yaml',os.path.join(safeline_path, 'docker-compose.yaml')):
  893. log.error(text('fail-to-download-compose'))
  894. return
  895. if os.path.exists(os.path.join(safeline_path, 'compose.yaml')):
  896. os.rename(os.path.join(safeline_path, 'compose.yaml'),os.path.join(safeline_path, 'compose.yaml.bak'))
  897. while True:
  898. config = generate_config(safeline_path)
  899. if docker_pull(safeline_path):
  900. break
  901. pull_failed_prefix.append(config['IMAGE_PREFIX'])
  902. log.info(text('try-another-image-source'))
  903. if not docker_up(safeline_path):
  904. log.error(text('fail-to-up'))
  905. return
  906. log.info(text('install-finish'))
  907. reset_admin()
  908. show_address(config['MGT_PORT'])
  909. def get_installed_dir():
  910. safeline_path = ''
  911. safeline_path_proc = exec_command('docker','inspect','--format','\'{{index .Config.Labels "com.docker.compose.project.working_dir"}}\'', 'safeline-mgt')
  912. if safeline_path_proc[0] == 0:
  913. safeline_path = safeline_path_proc[1].strip().replace("'",'')
  914. else:
  915. log.debug("get installed dir error: "+ safeline_path_proc[2])
  916. log.debug("find safeline installed path: " + safeline_path)
  917. if safeline_path == '' or not os.path.exists(safeline_path):
  918. log.warning(text('fail-to-get-installed-dir'))
  919. return ui_read(text('input-target-path'),None)
  920. return safeline_path
  921. def save_file_from_url(url, path):
  922. log.debug('saving '+url+' to '+path)
  923. data = get_url(url)
  924. if data is None:
  925. return False
  926. with open(path, 'w') as f:
  927. f.write(data)
  928. return True
  929. def upgrade():
  930. safeline_path = get_installed_dir()
  931. if not precheck_docker_compose():
  932. log.error(text('precheck-failed'))
  933. return
  934. log.info(text('download-compose'))
  935. if not save_file_from_url('https://'+DOMAIN+'/release/latest/compose.yaml', os.path.join(safeline_path, 'docker-compose.yaml')):
  936. log.error(text('fail-to-download-compose'))
  937. return
  938. if os.path.exists(os.path.join(safeline_path, 'compose.yaml')):
  939. os.rename(os.path.join(safeline_path, 'compose.yaml'),os.path.join(safeline_path, 'compose.yaml.bak'))
  940. while True:
  941. config = generate_config(safeline_path)
  942. if docker_pull(safeline_path):
  943. break
  944. pull_failed_prefix.append(config['IMAGE_PREFIX'])
  945. log.info(text('try-another-image-source'))
  946. if not docker_up(safeline_path):
  947. log.error(text('fail-to-up'))
  948. return
  949. if IMAGE_CLEAN:
  950. image_clean()
  951. log.info(text('upgrade-finish'))
  952. reset_admin()
  953. show_address(config['MGT_PORT'])
  954. pass
  955. def reset_tengine():
  956. safeline_path = get_installed_dir()
  957. resources_path = os.path.join(safeline_path, 'resources')
  958. nginx_path = os.path.join(resources_path,'nginx')
  959. if not os.path.exists(nginx_path):
  960. log.error(text('fail-to-find-nginx'))
  961. return
  962. backup_path = os.path.join(resources_path, 'nginx.'+str(datetime.datetime.now().timestamp()))
  963. log.info(text('nginx-backup-dir') +': '+ backup_path)
  964. try:
  965. shutil.move(nginx_path, backup_path)
  966. except Exception as e:
  967. log.error(text('fail-to-backup-nginx')+': '+str(e))
  968. return
  969. if docker_restart('safeline-tengine'):
  970. docker_exec('safeline-mgt', 'gentenginewebsite')
  971. if os.path.exists(os.path.join(backup_path, 'static')):
  972. try:
  973. shutil.copy(os.path.join(backup_path, 'static'), os.path.join(nginx_path, 'static'))
  974. except Exception as e:
  975. log.error(text('fail-to-recover-static')+': '+str(e))
  976. return
  977. log.info(text('reset-tengine-finish'))
  978. def docker_restart_all(cwd):
  979. if not docker_down(cwd):
  980. log.error(text('fail-to-down'))
  981. return False
  982. if not docker_up(cwd):
  983. log.error(text('fail-to-up'))
  984. return False
  985. return True
  986. def reset_postgres():
  987. safeline_path = get_installed_dir()
  988. if not precheck_docker_compose():
  989. log.error(text('precheck-failed'))
  990. return
  991. env_file = os.path.join(safeline_path, '.env')
  992. if not os.path.exists(env_file):
  993. log.error(text('fail-to-find-env'))
  994. return
  995. config = {}
  996. read_config(env_file, config)
  997. if config['POSTGRES_PASSWORD'] == '':
  998. log.error(text('fail-to-find-postgres-password'))
  999. return
  1000. if not docker_exec('safeline-pg','psql -U safeline-ce -c "ALTER USER \\"safeline-ce\\" WITH PASSWORD \''+config['POSTGRES_PASSWORD']+'\';"'):
  1001. log.error(text('fail-to-reset-postgres-password'))
  1002. return
  1003. action = ui_choice(text('if-restart-docker'), [
  1004. ('y', text('yes')),
  1005. ('n', text('no')),
  1006. ])
  1007. if action.lower() == 'y':
  1008. if not docker_restart_all(safeline_path):
  1009. return
  1010. log.info(text('reset-postgres-password-finish'))
  1011. def repair():
  1012. action = ui_choice(text('choice-action'),[
  1013. ('1', text('reset-tengine')),
  1014. ('2', text('reset-postgres')),
  1015. ])
  1016. if action =='1':
  1017. reset_tengine()
  1018. elif action =='2':
  1019. reset_postgres()
  1020. def restart():
  1021. safeline_path = get_installed_dir()
  1022. if not precheck_docker_compose():
  1023. log.error(text('precheck-failed'))
  1024. return
  1025. if not docker_restart_all(safeline_path):
  1026. return
  1027. log.info(text('restart-docker-finish'))
  1028. def backup():
  1029. pass
  1030. def uninstall():
  1031. safeline_path = get_installed_dir()
  1032. action = ui_choice(text('if-remove-waf')+": "+safeline_path,[
  1033. ('y', text('yes')),
  1034. ('n', text('no')),
  1035. ])
  1036. if action == 'n':
  1037. return
  1038. if not precheck_docker_compose():
  1039. log.error(text('precheck-failed'))
  1040. return
  1041. if not docker_down(safeline_path):
  1042. log.error(text('fail-to-docker-down'))
  1043. return
  1044. image_clean()
  1045. try:
  1046. shutil.rmtree(safeline_path)
  1047. except Exception as e:
  1048. log.debug("remove dir failed: "+str(e))
  1049. log.error(text('fail-to-remove-dir'))
  1050. log.info(text('uninstall-finish'))
  1051. def init_global_config():
  1052. global lang, DEBUG, LTS, IMAGE_CLEAN, EN, DOMAIN
  1053. lang = 'zh'
  1054. if '--debug' in sys.argv:
  1055. DEBUG = True
  1056. if '--lts' in sys.argv:
  1057. LTS = True
  1058. if '--image-clean' in sys.argv:
  1059. IMAGE_CLEAN = True
  1060. if '--en' in sys.argv:
  1061. EN = True
  1062. lang = 'en'
  1063. DOMAIN = 'waf.chaitin.com'
  1064. def main():
  1065. init_global_config()
  1066. banner()
  1067. log.info(text('hello1'))
  1068. log.info(text('hello2'))
  1069. print()
  1070. if LTS:
  1071. log.info(text('install-channel')+": "+text('lts-release'))
  1072. if sys.version_info.major == 2 or (sys.version_info.major == 3 and sys.version_info.minor <= 5):
  1073. log.error(text('python-version-too-low'))
  1074. return
  1075. if not sys.stdin.isatty():
  1076. log.error(text('not-a-tty'))
  1077. return
  1078. if os.geteuid() != 0:
  1079. log.error(text('not-root'))
  1080. return
  1081. if platform.system() != 'Linux':
  1082. log.error(text('not-linux', platform.system()))
  1083. return
  1084. if platform.machine() not in ('aarch64', 'x86_64', 'AMD64'):
  1085. log.error(text('unsupported-arch', platform.machine()))
  1086. return
  1087. action = ui_choice(text('choice-action'), [
  1088. ('1', text('install')),
  1089. ('2', text('upgrade')),
  1090. ('3', text('uninstall')),
  1091. ('4', text('repair')),
  1092. ('5', text('restart')),
  1093. # ('4', text('backup'))
  1094. ])
  1095. if action == '1':
  1096. install()
  1097. elif action == '2':
  1098. upgrade()
  1099. elif action == '3':
  1100. uninstall()
  1101. elif action == '4':
  1102. repair()
  1103. elif action == '5':
  1104. restart()
  1105. # elif action == '4':
  1106. # backup()
  1107. if __name__ == '__main__':
  1108. try:
  1109. main()
  1110. except KeyboardInterrupt:
  1111. log.warning(text('keyboard-interrupt'))
  1112. pass
  1113. except Exception as e:
  1114. log.error(e)
  1115. finally:
  1116. print(color(text('talking-group') + '\n', [GREEN]))