diff --git a/.gitignore b/.gitignore index f3dfdbf..fbf476d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ -*.pyc -*.pyo -.DS* -.pylint_rc -/.idea -/.project -/.pydevproject -/.settings -Thumbs.db -*~ -.cache +*.pyc +*.pyo +.DS* +.pylint_rc +/.idea +/.project +/.pydevproject +/.settings +Thumbs.db +*~ +.cache diff --git a/.travis.yml b/.travis.yml index 07b6cfc..2769ea4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,20 @@ -language: python -matrix: - include: - - python: "2.6" - - python: "2.7" - - python: "2.7.10" - - python: "2.7.11" - allow_failures: - - python: "3.2" - - python: "3.3" - - python: "3.4" - - python: "3.5" - - python: "nightly" - -# command to install dependencies -install: - - pip install python-dateutil pytest -# command to run tests -script: py.test -v - +language: python +matrix: + include: + - python: "2.6" + - python: "2.7" + - python: "2.7.10" + - python: "2.7.11" + allow_failures: + - python: "3.2" + - python: "3.3" + - python: "3.4" + - python: "3.5" + - python: "nightly" + +# command to install dependencies +install: + - pip install python-dateutil pytest +# command to run tests +script: py.test -v + diff --git a/LICENSE b/LICENSE index 0f1d5b6..2a5e822 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 Guoqing Zhang (conw.net) +Copyright (c) 2019 Guoqing Zhang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 978e4af..be8c0f2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,40 @@ -# Shadowsocks-kodi - -Run Shadowsocks on kodi! - + +

Shadowsocks Kodi logo

+ + +

+ Run Shadowsocks on Kodi! +

+ +## Get Started + +You can download the latest version from [Github Releases](https://github.com/conwnet/shadowsocks-kodi/releases) + +1. Install the addon on your kodi. +2. Configure your Shadowsocks. +3. Restart services to take effect. + +## Check up + +You can login your kodi with ssh and display the ports usage. + +As shown below, we can see our Shadowsocks services running at PID 653. + +``` +LibreELEC:~ # netstat -lntp +Active Internet connections (only servers) +Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name +... +tcp 0 0 127.0.0.1:1080 0.0.0.0:* LISTEN 653/kodi.bin +... +``` + +You can test whether your services work as your expected use `curl`. + +``` +LibreELEC:~ # curl -x socks5://127.0.0.1:1080 ifconfig.io/ip +``` + +## Screenshots + +![Shadowsocks kodi](./resources/screenshot-01.png) diff --git a/addon.xml b/addon.xml index e403f96..8d33301 100644 --- a/addon.xml +++ b/addon.xml @@ -1,26 +1,35 @@ - - - - - - - - - - Shadowsocks Client - - - linux - MIT - - conw.net - netcon@live.com - - - - - resources/icon.png - resources/fanart.jpg - - - + + + + + + + + Shadowsocks for Kodi + + Usage: + 1. Click `Configure` to fill in your server config + 2. Click `Disable` to stop existing service + 3. Click `Enable` to start service with the current config + + You have already established a SOCKS5 proxy on your kodi! + Github: https://github.com/conwnet/shadowsocks-kodi + + + linux + MIT + + conw.net + netcon@live.com + + + + + resources/icon.png + resources/fanart.jpg + resources/screenshot-01.png + resources/screenshot-02.png + resources/screenshot-03.png + + + diff --git a/changelog.txt b/changelog.txt index acfa6a3..d4ee003 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,2 +1,2 @@ -v0.0.1 +v0.0.1 - Initial version \ No newline at end of file diff --git a/main.py b/main.py index ca6ec1c..2e4c495 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,41 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# run service - -from resources import service - -if __name__ == '__main__': - service.run() - +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# run service + +from src import service, async_task, helpers +import logging +import xbmc + +# set logging to xbmc.log +def set_logging(): + class Handler(logging.Handler): + def __init__(self): + logging.Handler.__init__(self) + def emit(self, record): + message = self.format(record) + xbmc.log(message, level=xbmc.LOGNOTICE) + + logger = logging.getLogger() + logger.addHandler(Handler()) + +if __name__ == '__main__': + set_logging() + config = helpers.get_config() + + def run_service(): + if not config['server']: + return + service.run(config) + + task = async_task.AsyncTask(run_service, config['pid-file']) + + # try stop existing process, + # on normal case it not required + task.stop() + task.start() + + monitor = xbmc.Monitor() + while not monitor.abortRequested(): + if monitor.waitForAbort(10): + task.stop() + break diff --git a/resources/fanart.jpg b/resources/fanart.jpg new file mode 100644 index 0000000..362b1f7 Binary files /dev/null and b/resources/fanart.jpg differ diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000..cd8925d Binary files /dev/null and b/resources/icon.png differ diff --git a/resources/screenshot-01.png b/resources/screenshot-01.png new file mode 100644 index 0000000..47cc4cd Binary files /dev/null and b/resources/screenshot-01.png differ diff --git a/resources/screenshot-02.png b/resources/screenshot-02.png new file mode 100644 index 0000000..11d49ee Binary files /dev/null and b/resources/screenshot-02.png differ diff --git a/resources/screenshot-03.png b/resources/screenshot-03.png new file mode 100644 index 0000000..bf99423 Binary files /dev/null and b/resources/screenshot-03.png differ diff --git a/resources/service.py b/resources/service.py deleted file mode 100644 index 76b990c..0000000 --- a/resources/service.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# shadowsocks service - -from __future__ import absolute_import, division, print_function, \ - with_statement - -import sys -import os -import logging -import xbmcaddon - -from shadowsocks import eventloop, tcprelay, udprelay, asyncdns - -config = { - 'log-file': '/var/log/shadowsocks.log', - 'verbose': False, - 'tunnel_remote_port': 53, - 'libmbedtls': None, - 'tunnel_port': 53, - 'local_port': 1080, - 'workers': 1, - 'fast_open': False, - 'server_port': 8388, - 'local_address': '127.0.0.1', - 'method': 'aes-256-cfb', - 'libsodium': None, - 'tunnel_remote': '8.8.8.8', - 'crypto_path': { - 'mbedtls': None, - 'openssl': None, - 'sodium': None - }, - 'password': '', - 'libopenssl': None, - 'dns_server': None, - 'prefer_ipv6': False, - 'port_password': None, - 'server': 'bash.pub', - 'timeout': 300, - 'one_time_auth': False -} - -def check_python(): - info = sys.version_info - if info[0] == 2 and not info[1] >= 6: - print('Python 2.6+ required') - sys.exit(1) - elif info[0] == 3 and not info[1] >= 3: - print('Python 3.3+ required') - sys.exit(1) - elif info[0] not in [2, 3]: - print('Python version not supported') - sys.exit(1) - -def run(): - check_python() - - # fix py2exe - # in fact, i don't think this may run on windows - if hasattr(sys, "frozen") and sys.frozen in \ - ("windows_exe", "console_exe"): - p = os.path.dirname(os.path.abspath(sys.executable)) - os.chdir(p) - - addon = xbmcaddon.Addon() - config['server'] = addon.getSetting('server_addr') - config['server_port'] = int(addon.getSetting('server_port')) - config['method'] = addon.getSetting('method') - config['password'] = addon.getSetting('password') - config['local_address'] = addon.getSetting('local_addr') - config['local_port'] = int(addon.getSetting('local_port')) - config['timeout'] = int(addon.getSetting('timeout')) - config['one_time_auto'] = addon.getSetting('one_time_auto') == 'True' - config['fast_open'] = addon.getSetting('tcp_fast_open') == 'True' - - if config['server'] == '': - logging.error('No SERVER_ADDR specified') - sys.exit(1) - - logging.info("starting local at %s:%d" % - (config['local_address'], config['local_port'])) - - dns_resolver = asyncdns.DNSResolver() - tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) - udp_server = udprelay.UDPRelay(config, dns_resolver, True) - loop = eventloop.EventLoop() - dns_resolver.add_to_loop(loop) - tcp_server.add_to_loop(loop) - udp_server.add_to_loop(loop) - - loop.run() - diff --git a/resources/settings.xml b/resources/settings.xml index 127948b..6cd7df1 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/resources/__init__.py b/src/__init__.py similarity index 100% rename from resources/__init__.py rename to src/__init__.py diff --git a/src/async_task.py b/src/async_task.py new file mode 100644 index 0000000..18dcbbf --- /dev/null +++ b/src/async_task.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# run async task + +import random +import time +import os +import logging +import signal + +class AsyncTask: + def __init__(self, task, pid_file = None): + self.pid_file = pid_file + if not pid_file: + unique_id = str(int(time.time() * 10000)) + self.pid_file = '/tmp/async-task-' + unique_id + '.pid' + self.task = task + + def start(self): + pid = os.fork() + assert pid != -1 + + # main process record process pid + if pid > 0: + with open(self.pid_file, 'w') as f: + f.write(str(pid)) + f.close() + + # child process run task + if pid == 0: + self.task() + try: + os.unlink(self.pid_file) + except: + return + + def stop(self): + try: + with open(self.pid_file, 'r') as f: + pid = int(f.read()) + os.kill(pid, signal.SIGKILL) + os.unlink(self.pid_file) + except: + logging.error('stop task failed') diff --git a/src/helpers.py b/src/helpers.py new file mode 100644 index 0000000..90c660c --- /dev/null +++ b/src/helpers.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# helpers + +import xbmcaddon + +default_config = { + 'log-file': '/tmp/shadowsocks.log', + 'verbose': False, + 'tunnel_remote_port': 53, + 'libmbedtls': None, + 'tunnel_port': 53, + 'local_port': 1080, + 'workers': 1, + 'fast_open': False, + 'server_port': 8388, + 'local_address': '127.0.0.1', + 'method': 'aes-256-cfb', + 'libsodium': None, + 'tunnel_remote': '8.8.8.8', + 'crypto_path': { + 'mbedtls': None, + 'openssl': None, + 'sodium': None + }, + 'pid-file': '/tmp/shadowsocks.pid', + 'password': '', + 'libopenssl': None, + 'dns_server': None, + 'prefer_ipv6': False, + 'port_password': None, + 'server': '', + 'timeout': 300, + 'one_time_auth': False +} + +def get_config(): + config = default_config.copy() + + addon = xbmcaddon.Addon() + config['server'] = addon.getSetting('server_addr') + config['server_port'] = int(addon.getSetting('server_port')) + config['method'] = addon.getSetting('method') + config['password'] = addon.getSetting('password') + config['local_address'] = addon.getSetting('local_addr') + config['local_port'] = int(addon.getSetting('local_port')) + config['timeout'] = int(addon.getSetting('timeout')) + config['one_time_auto'] = addon.getSetting('one_time_auto') == 'True' + config['fast_open'] = addon.getSetting('tcp_fast_open') == 'True' + + return config + diff --git a/src/service.py b/src/service.py new file mode 100644 index 0000000..e247b05 --- /dev/null +++ b/src/service.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# shadowsocks service + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import sys +import os +import logging + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) +from shadowsocks import shell, eventloop, tcprelay, udprelay, asyncdns + +@shell.exception_handle(self_=False, exit_code=1) +def run(config): + shell.check_python() + + # fix py2exe + if hasattr(sys, "frozen") and sys.frozen in \ + ("windows_exe", "console_exe"): + p = os.path.dirname(os.path.abspath(sys.executable)) + os.chdir(p) + + logging.info("starting local at %s:%d" % + (config['local_address'], config['local_port'])) + + dns_resolver = asyncdns.DNSResolver() + tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) + udp_server = udprelay.UDPRelay(config, dns_resolver, True) + loop = eventloop.EventLoop() + dns_resolver.add_to_loop(loop) + tcp_server.add_to_loop(loop) + udp_server.add_to_loop(loop) + + loop.run() + diff --git a/tests/README.md b/tests/README.md index 4dda3ac..ad2917c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,2 +1,2 @@ -# Tests +# Tests This folder should be the home for your unit tests \ No newline at end of file