wolsocketproxy 0.1.1__tar.gz → 0.3.0__tar.gz
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.
- wolsocketproxy-0.3.0/PKG-INFO +164 -0
- wolsocketproxy-0.3.0/README.md +133 -0
- wolsocketproxy-0.3.0/pyproject.toml +75 -0
- wolsocketproxy-0.3.0/setup.cfg +4 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy/__init__.py +75 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy/keepalive.py +153 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy/monitor.py +129 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy/proxy.py +318 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy/utils.py +21 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy.egg-info/PKG-INFO +164 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy.egg-info/SOURCES.txt +15 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy.egg-info/dependency_links.txt +1 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy.egg-info/entry_points.txt +2 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy.egg-info/requires.txt +17 -0
- wolsocketproxy-0.3.0/src/wolsocketproxy.egg-info/top_level.txt +1 -0
- wolsocketproxy-0.1.1/PKG-INFO +0 -94
- wolsocketproxy-0.1.1/README.md +0 -69
- wolsocketproxy-0.1.1/pyproject.toml +0 -69
- wolsocketproxy-0.1.1/wolsocketproxy/__init__.py +0 -44
- wolsocketproxy-0.1.1/wolsocketproxy/monitor.py +0 -48
- wolsocketproxy-0.1.1/wolsocketproxy/proxy.py +0 -175
- {wolsocketproxy-0.1.1 → wolsocketproxy-0.3.0}/LICENSE +0 -0
- {wolsocketproxy-0.1.1 → wolsocketproxy-0.3.0/src}/wolsocketproxy/__main__.py +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wolsocketproxy
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A socket proxy with wake-on-lan feature.
|
|
5
|
+
Author-email: Song Fuchang <song.fc@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: source, https://github.com/notsyncing/wolsocketproxy
|
|
8
|
+
Project-URL: issues, https://github.com/notsyncing/wol-socket-proxy/issues
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: Topic :: System :: Networking
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: aiohttp==3.13.5
|
|
17
|
+
Requires-Dist: dataclass-wizard==0.39.1
|
|
18
|
+
Requires-Dist: icmplib==3.0.4
|
|
19
|
+
Requires-Dist: redfish==3.3.5
|
|
20
|
+
Requires-Dist: setproctitle==1.3.7
|
|
21
|
+
Requires-Dist: wakeonlan==3.1.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: wolsocketproxy[lint]; extra == "dev"
|
|
24
|
+
Requires-Dist: wolsocketproxy[build]; extra == "dev"
|
|
25
|
+
Provides-Extra: lint
|
|
26
|
+
Requires-Dist: ty; extra == "lint"
|
|
27
|
+
Requires-Dist: ruff; extra == "lint"
|
|
28
|
+
Provides-Extra: build
|
|
29
|
+
Requires-Dist: build[virtualenv]==1.5.0; extra == "build"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# wol-socket-proxy
|
|
33
|
+
|
|
34
|
+
A socket proxy with wake-on-lan and IPMI power control feature.
|
|
35
|
+
|
|
36
|
+
It can forward TCP(bidirectional) and UDP(send-only) traffic from local machine to remote machine, and:
|
|
37
|
+
|
|
38
|
+
- send a magic packet to wake it (wake-on-lan)
|
|
39
|
+
- invoke IPMI power on to wake it
|
|
40
|
+
|
|
41
|
+
before forwarding traffic if the remote machine does not respond to ICMP ping or some HTTP URL.
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
Python 3.12+
|
|
46
|
+
|
|
47
|
+
The remote machine must accept and respond to ICMP ping requests if you use `ping` method.
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
You can install it from PyPI or use a prebuilt docker image.
|
|
52
|
+
|
|
53
|
+
### Install from PyPI
|
|
54
|
+
|
|
55
|
+
Simply install it:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
pip install wolsocketproxy
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then create a config file:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
cp wolsocketproxy.conf.example /etc/wolsocketproxy.conf
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Modify the config file as your requirements.
|
|
68
|
+
|
|
69
|
+
Finally, run it:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
wolsocketproxy
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Use prebuilt docker image
|
|
76
|
+
|
|
77
|
+
See `docker/docker-compose.yml` for more details.
|
|
78
|
+
|
|
79
|
+
## Config
|
|
80
|
+
|
|
81
|
+
The `wolsocketproxy.conf` has the following structure:
|
|
82
|
+
|
|
83
|
+
```json5
|
|
84
|
+
{
|
|
85
|
+
// NOTE: Comments are not allowed in actual config file
|
|
86
|
+
|
|
87
|
+
// Define machines
|
|
88
|
+
"machines": {
|
|
89
|
+
"some-server": {
|
|
90
|
+
"ip_address": "192.168.1.124", // the IP address of this machine, optional
|
|
91
|
+
"mac_address": "11:22:33:44:55:66", // The MAC address of this machine, optional
|
|
92
|
+
"wake_up_method": "ipmi", // How to wake up this machine
|
|
93
|
+
// "ipmi" - You must specify "ipmi_config_name"
|
|
94
|
+
// "wol" - You must specify "mac_address"
|
|
95
|
+
"ipmi_config_name": "some-server-ipmi", // IPMI config of this machine, must match what defined in "ipmi_configs"
|
|
96
|
+
"online_check_method": "http", // How to check this machine is online
|
|
97
|
+
// "http" - You must specify "online_check_http_url"
|
|
98
|
+
// "ping" - You must specify "ip_address"
|
|
99
|
+
"online_check_http_url": "http://192.168.1.124/health", // The URL used to check if machine is online, optional
|
|
100
|
+
"online_check_timeout": 300, // Timeout when checking machine is online, seconds, optional, default is 60
|
|
101
|
+
"online_check_http_expected_code": 200, // Expected HTTP status code when using "http" method, optional, default is 200
|
|
102
|
+
"ipmi_force_reset_if_power_up_failed": true, // Use IPMI power reset if this machine is not online after timeout, optional, default is false
|
|
103
|
+
"ipmi_max_reset_try_count": 3 // Max IPMI power reset retry count before giving-up, optional, default is 3
|
|
104
|
+
},
|
|
105
|
+
// ... more machines ...
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Define forwarding routes
|
|
109
|
+
"routes": [
|
|
110
|
+
{
|
|
111
|
+
"local_address": "0.0.0.0", // The local address to listen
|
|
112
|
+
"local_port": 12345, // The local port to listen
|
|
113
|
+
"target_machine_name": "some-server", // The target machine name, must match what defined in "machines"
|
|
114
|
+
"target_address": "192.168.1.124", // The target address forwarding to
|
|
115
|
+
"target_port": 12345, // The target port forwarding to
|
|
116
|
+
"protocol": "tcp" // The protocol to use, can choose "tcp" or "udp"
|
|
117
|
+
},
|
|
118
|
+
// ... more routes ...
|
|
119
|
+
],
|
|
120
|
+
|
|
121
|
+
// Define IPMI configs, optional
|
|
122
|
+
"ipmi_configs": [
|
|
123
|
+
{
|
|
124
|
+
"name": "some-server-ipmi", // IPMI config name
|
|
125
|
+
"redfish_url": "https://192.168.1.123/", // IPMI Redfish API base URL (should not contain /redfish/v1)
|
|
126
|
+
"username": "admin", // IPMI Redfish API login username
|
|
127
|
+
"password": "123456" // IPMI Redfish API login password
|
|
128
|
+
},
|
|
129
|
+
// ... more IPMI configs ...
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Additional modes
|
|
135
|
+
|
|
136
|
+
### Keep-alive daemon mode
|
|
137
|
+
|
|
138
|
+
This mode is used for running on target machine with auto-suspend (e.g. running `circadian` or similar), to keep the target
|
|
139
|
+
machine from auto suspending when running tasks.
|
|
140
|
+
|
|
141
|
+
Use the following command on the target machine to enter this mode:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
wolsocketproxy -k
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The config file `wolsocketproxy.conf` should look like the following:
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
```json5
|
|
151
|
+
// Keep-alive daemon mode
|
|
152
|
+
{
|
|
153
|
+
"listen_address": "0.0.0.0", // The address to listen for HTTP server, default is 127.0.0.1
|
|
154
|
+
"listen_port": 18080, // The port to listen for HTTP server, default is 18080
|
|
155
|
+
"watchdog_feed_interval": 60, // Watchdog feed interval, seconds, default is 600
|
|
156
|
+
"keep_alive_method": "special_process", // How to keep target machine alive, default is "special_process"
|
|
157
|
+
// "special_process" - Fork a child process with specified name
|
|
158
|
+
"special_process_name": "wsp-keepalive", // The display name of the forked child process, default is "wsp-keepalive"
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
You need to periodically send an HTTP GET request to `/watchdog/feed` at the `listen_port` in `watchdog_feed_interval` time,
|
|
163
|
+
or the special process will be killed.
|
|
164
|
+
You can combine this mode with `circadian`'s `process_block` config to keep it from auto-suspending.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# wol-socket-proxy
|
|
2
|
+
|
|
3
|
+
A socket proxy with wake-on-lan and IPMI power control feature.
|
|
4
|
+
|
|
5
|
+
It can forward TCP(bidirectional) and UDP(send-only) traffic from local machine to remote machine, and:
|
|
6
|
+
|
|
7
|
+
- send a magic packet to wake it (wake-on-lan)
|
|
8
|
+
- invoke IPMI power on to wake it
|
|
9
|
+
|
|
10
|
+
before forwarding traffic if the remote machine does not respond to ICMP ping or some HTTP URL.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
Python 3.12+
|
|
15
|
+
|
|
16
|
+
The remote machine must accept and respond to ICMP ping requests if you use `ping` method.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
You can install it from PyPI or use a prebuilt docker image.
|
|
21
|
+
|
|
22
|
+
### Install from PyPI
|
|
23
|
+
|
|
24
|
+
Simply install it:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
pip install wolsocketproxy
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then create a config file:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
cp wolsocketproxy.conf.example /etc/wolsocketproxy.conf
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Modify the config file as your requirements.
|
|
37
|
+
|
|
38
|
+
Finally, run it:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
wolsocketproxy
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Use prebuilt docker image
|
|
45
|
+
|
|
46
|
+
See `docker/docker-compose.yml` for more details.
|
|
47
|
+
|
|
48
|
+
## Config
|
|
49
|
+
|
|
50
|
+
The `wolsocketproxy.conf` has the following structure:
|
|
51
|
+
|
|
52
|
+
```json5
|
|
53
|
+
{
|
|
54
|
+
// NOTE: Comments are not allowed in actual config file
|
|
55
|
+
|
|
56
|
+
// Define machines
|
|
57
|
+
"machines": {
|
|
58
|
+
"some-server": {
|
|
59
|
+
"ip_address": "192.168.1.124", // the IP address of this machine, optional
|
|
60
|
+
"mac_address": "11:22:33:44:55:66", // The MAC address of this machine, optional
|
|
61
|
+
"wake_up_method": "ipmi", // How to wake up this machine
|
|
62
|
+
// "ipmi" - You must specify "ipmi_config_name"
|
|
63
|
+
// "wol" - You must specify "mac_address"
|
|
64
|
+
"ipmi_config_name": "some-server-ipmi", // IPMI config of this machine, must match what defined in "ipmi_configs"
|
|
65
|
+
"online_check_method": "http", // How to check this machine is online
|
|
66
|
+
// "http" - You must specify "online_check_http_url"
|
|
67
|
+
// "ping" - You must specify "ip_address"
|
|
68
|
+
"online_check_http_url": "http://192.168.1.124/health", // The URL used to check if machine is online, optional
|
|
69
|
+
"online_check_timeout": 300, // Timeout when checking machine is online, seconds, optional, default is 60
|
|
70
|
+
"online_check_http_expected_code": 200, // Expected HTTP status code when using "http" method, optional, default is 200
|
|
71
|
+
"ipmi_force_reset_if_power_up_failed": true, // Use IPMI power reset if this machine is not online after timeout, optional, default is false
|
|
72
|
+
"ipmi_max_reset_try_count": 3 // Max IPMI power reset retry count before giving-up, optional, default is 3
|
|
73
|
+
},
|
|
74
|
+
// ... more machines ...
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Define forwarding routes
|
|
78
|
+
"routes": [
|
|
79
|
+
{
|
|
80
|
+
"local_address": "0.0.0.0", // The local address to listen
|
|
81
|
+
"local_port": 12345, // The local port to listen
|
|
82
|
+
"target_machine_name": "some-server", // The target machine name, must match what defined in "machines"
|
|
83
|
+
"target_address": "192.168.1.124", // The target address forwarding to
|
|
84
|
+
"target_port": 12345, // The target port forwarding to
|
|
85
|
+
"protocol": "tcp" // The protocol to use, can choose "tcp" or "udp"
|
|
86
|
+
},
|
|
87
|
+
// ... more routes ...
|
|
88
|
+
],
|
|
89
|
+
|
|
90
|
+
// Define IPMI configs, optional
|
|
91
|
+
"ipmi_configs": [
|
|
92
|
+
{
|
|
93
|
+
"name": "some-server-ipmi", // IPMI config name
|
|
94
|
+
"redfish_url": "https://192.168.1.123/", // IPMI Redfish API base URL (should not contain /redfish/v1)
|
|
95
|
+
"username": "admin", // IPMI Redfish API login username
|
|
96
|
+
"password": "123456" // IPMI Redfish API login password
|
|
97
|
+
},
|
|
98
|
+
// ... more IPMI configs ...
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Additional modes
|
|
104
|
+
|
|
105
|
+
### Keep-alive daemon mode
|
|
106
|
+
|
|
107
|
+
This mode is used for running on target machine with auto-suspend (e.g. running `circadian` or similar), to keep the target
|
|
108
|
+
machine from auto suspending when running tasks.
|
|
109
|
+
|
|
110
|
+
Use the following command on the target machine to enter this mode:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
wolsocketproxy -k
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The config file `wolsocketproxy.conf` should look like the following:
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
```json5
|
|
120
|
+
// Keep-alive daemon mode
|
|
121
|
+
{
|
|
122
|
+
"listen_address": "0.0.0.0", // The address to listen for HTTP server, default is 127.0.0.1
|
|
123
|
+
"listen_port": 18080, // The port to listen for HTTP server, default is 18080
|
|
124
|
+
"watchdog_feed_interval": 60, // Watchdog feed interval, seconds, default is 600
|
|
125
|
+
"keep_alive_method": "special_process", // How to keep target machine alive, default is "special_process"
|
|
126
|
+
// "special_process" - Fork a child process with specified name
|
|
127
|
+
"special_process_name": "wsp-keepalive", // The display name of the forked child process, default is "wsp-keepalive"
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
You need to periodically send an HTTP GET request to `/watchdog/feed` at the `listen_port` in `watchdog_feed_interval` time,
|
|
132
|
+
or the special process will be killed.
|
|
133
|
+
You can combine this mode with `circadian`'s `process_block` config to keep it from auto-suspending.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wolsocketproxy"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "A socket proxy with wake-on-lan feature."
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "Song Fuchang", email = "song.fc@gmail.com"}
|
|
11
|
+
]
|
|
12
|
+
license = "Apache-2.0"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: System Administrators",
|
|
18
|
+
"Topic :: System :: Networking"
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.12"
|
|
21
|
+
dependencies = [
|
|
22
|
+
"aiohttp==3.13.5",
|
|
23
|
+
"dataclass-wizard==0.39.1",
|
|
24
|
+
"icmplib==3.0.4",
|
|
25
|
+
"redfish==3.3.5",
|
|
26
|
+
"setproctitle==1.3.7",
|
|
27
|
+
"wakeonlan==3.1.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = [
|
|
32
|
+
"wolsocketproxy[lint]",
|
|
33
|
+
"wolsocketproxy[build]",
|
|
34
|
+
]
|
|
35
|
+
lint = [
|
|
36
|
+
"ty",
|
|
37
|
+
"ruff",
|
|
38
|
+
]
|
|
39
|
+
build = [
|
|
40
|
+
"build[virtualenv]==1.5.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
source = "https://github.com/notsyncing/wolsocketproxy"
|
|
45
|
+
issues = "https://github.com/notsyncing/wol-socket-proxy/issues"
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
wolsocketproxy = "wolsocketproxy:main"
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
line-length = 120
|
|
52
|
+
src = ["src"]
|
|
53
|
+
target-version = "py312"
|
|
54
|
+
lint.select = ["ALL"]
|
|
55
|
+
lint.ignore = [
|
|
56
|
+
"ASYNC110",
|
|
57
|
+
"COM812",
|
|
58
|
+
"D100",
|
|
59
|
+
"D101",
|
|
60
|
+
"D102",
|
|
61
|
+
"D103",
|
|
62
|
+
"D104",
|
|
63
|
+
"D107",
|
|
64
|
+
"D203",
|
|
65
|
+
"D211",
|
|
66
|
+
"D213",
|
|
67
|
+
"DTZ005",
|
|
68
|
+
"S101",
|
|
69
|
+
"EM102",
|
|
70
|
+
"FBT001",
|
|
71
|
+
"FBT003",
|
|
72
|
+
"ISC001",
|
|
73
|
+
"TRY003",
|
|
74
|
+
"TRY201",
|
|
75
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from argparse import ArgumentParser
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from sys import stdout
|
|
6
|
+
|
|
7
|
+
import dataclass_wizard
|
|
8
|
+
|
|
9
|
+
from wolsocketproxy.keepalive import KeepAliveConfig, KeepAliveDaemon
|
|
10
|
+
from wolsocketproxy.proxy import Proxy, ProxyConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> None:
|
|
14
|
+
logging.basicConfig(
|
|
15
|
+
format="%(asctime)s %(levelname)-8s %(name)s %(message)s",
|
|
16
|
+
level=logging.INFO,
|
|
17
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
18
|
+
handlers=[
|
|
19
|
+
logging.StreamHandler(
|
|
20
|
+
stream=stdout,
|
|
21
|
+
),
|
|
22
|
+
],
|
|
23
|
+
force=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
log = logging.getLogger()
|
|
27
|
+
|
|
28
|
+
parser = ArgumentParser(prog="wolsocketproxy", description="A socket proxy with wake-on-lan feature.")
|
|
29
|
+
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"-k",
|
|
32
|
+
"--keep-alive",
|
|
33
|
+
dest="keep_alive_mode",
|
|
34
|
+
default=False,
|
|
35
|
+
action="store_true",
|
|
36
|
+
help="Start in keep-alive daemon mode",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"-c",
|
|
41
|
+
"--config",
|
|
42
|
+
help="The config file to use, default lookup order: /etc/wolsocketproxy.conf, ./wolsocketproxy.conf",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
args = parser.parse_args()
|
|
46
|
+
config_path: Path
|
|
47
|
+
|
|
48
|
+
if "config" in args and args.config is not None:
|
|
49
|
+
config_path = Path(args.config)
|
|
50
|
+
else:
|
|
51
|
+
config_path = Path("/etc/wolsocketproxy.conf")
|
|
52
|
+
|
|
53
|
+
if not config_path.exists():
|
|
54
|
+
config_path = Path("./wolsocketproxy.conf")
|
|
55
|
+
|
|
56
|
+
if not config_path.exists():
|
|
57
|
+
log.error("Config file path %s does not exist!", config_path)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
config_data = json.loads(config_path.read_text())
|
|
61
|
+
|
|
62
|
+
if args.keep_alive_mode:
|
|
63
|
+
config = dataclass_wizard.fromdict(KeepAliveConfig, config_data)
|
|
64
|
+
|
|
65
|
+
log.info("Loaded keep-alive mode config from %s", config_path)
|
|
66
|
+
|
|
67
|
+
keep_alive_daemon = KeepAliveDaemon(config)
|
|
68
|
+
keep_alive_daemon.start()
|
|
69
|
+
else:
|
|
70
|
+
config = dataclass_wizard.fromdict(ProxyConfig, config_data)
|
|
71
|
+
|
|
72
|
+
log.info("Loaded proxy mode config from %s", config_path)
|
|
73
|
+
|
|
74
|
+
proxy = Proxy(config)
|
|
75
|
+
proxy.start()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from threading import Thread
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
from aiohttp import web
|
|
12
|
+
from aiohttp.web import Request, Response
|
|
13
|
+
from setproctitle import setproctitle
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class KeepAliveConfig:
|
|
18
|
+
listen_address: str = "127.0.0.1"
|
|
19
|
+
listen_port: int = 18080
|
|
20
|
+
|
|
21
|
+
watchdog_feed_interval: int = 600
|
|
22
|
+
|
|
23
|
+
keep_alive_method: Literal["special_process"] = "special_process"
|
|
24
|
+
|
|
25
|
+
special_process_name: str | None = "wsp-keepalive"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class KeepAliveDaemon:
|
|
29
|
+
_log: logging.Logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
_config: KeepAliveConfig
|
|
32
|
+
_web_app: web.Application
|
|
33
|
+
|
|
34
|
+
_watchdog_last_feed_time: datetime
|
|
35
|
+
_watchdog_is_hungry: bool = False
|
|
36
|
+
|
|
37
|
+
_special_process_id: int = -1
|
|
38
|
+
|
|
39
|
+
def __init__(self, config: KeepAliveConfig) -> None:
|
|
40
|
+
self._config = config
|
|
41
|
+
|
|
42
|
+
self._web_app = web.Application()
|
|
43
|
+
|
|
44
|
+
self._web_app.add_routes(
|
|
45
|
+
[
|
|
46
|
+
web.get("/watchdog/feed", self._handle_watchdog_feed)
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
async def _watchdog_timer(self) -> None:
|
|
51
|
+
while True:
|
|
52
|
+
previous_is_hungry = self._watchdog_is_hungry
|
|
53
|
+
last_feed_interval = datetime.now() - self._watchdog_last_feed_time
|
|
54
|
+
|
|
55
|
+
if last_feed_interval.total_seconds() > self._config.watchdog_feed_interval:
|
|
56
|
+
self._watchdog_is_hungry = True
|
|
57
|
+
else:
|
|
58
|
+
self._watchdog_is_hungry = False
|
|
59
|
+
|
|
60
|
+
if not previous_is_hungry and self._watchdog_is_hungry:
|
|
61
|
+
Thread(target=self._on_watchdog_hungry, daemon=True).start()
|
|
62
|
+
elif previous_is_hungry and not self._watchdog_is_hungry:
|
|
63
|
+
Thread(target=self._on_watchdog_feed, daemon=True).start()
|
|
64
|
+
|
|
65
|
+
await asyncio.sleep(1)
|
|
66
|
+
|
|
67
|
+
def _on_watchdog_hungry(self) -> None:
|
|
68
|
+
self._log.warning("Watchdog is hungry!")
|
|
69
|
+
|
|
70
|
+
if self._special_process_id > 0:
|
|
71
|
+
os.kill(self._special_process_id, signal.SIGKILL)
|
|
72
|
+
os.waitpid(self._special_process_id, 0)
|
|
73
|
+
self._log.warning("Killed special process PID %d", self._special_process_id)
|
|
74
|
+
self._special_process_id = -1
|
|
75
|
+
|
|
76
|
+
def _on_watchdog_feed(self) -> None:
|
|
77
|
+
self._log.info("Watchdog is feed.")
|
|
78
|
+
|
|
79
|
+
if self._config.keep_alive_method == "special_process" and self._special_process_id < 0:
|
|
80
|
+
self._start_special_process()
|
|
81
|
+
|
|
82
|
+
def _special_process(self) -> None:
|
|
83
|
+
assert self._config.special_process_name is not None
|
|
84
|
+
setproctitle(self._config.special_process_name)
|
|
85
|
+
|
|
86
|
+
while True:
|
|
87
|
+
time.sleep(60)
|
|
88
|
+
|
|
89
|
+
if os.getppid() == 1:
|
|
90
|
+
os._exit(0)
|
|
91
|
+
|
|
92
|
+
def _start_special_process(self) -> bool:
|
|
93
|
+
self._special_process_id = os.fork()
|
|
94
|
+
|
|
95
|
+
if self._special_process_id == 0:
|
|
96
|
+
self._special_process()
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
self._log.info(
|
|
100
|
+
"Started special process with name %s, PID %d",
|
|
101
|
+
self._config.special_process_name, self._special_process_id
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
def start(self) -> None:
|
|
107
|
+
self._watchdog_last_feed_time = datetime.now()
|
|
108
|
+
self._watchdog_is_hungry = False
|
|
109
|
+
|
|
110
|
+
if self._config.keep_alive_method == "special_process": # noqa: SIM102
|
|
111
|
+
if self._start_special_process():
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
loop = asyncio.new_event_loop()
|
|
115
|
+
|
|
116
|
+
def _start_loop() -> None:
|
|
117
|
+
asyncio.set_event_loop(loop)
|
|
118
|
+
loop.run_forever()
|
|
119
|
+
|
|
120
|
+
Thread(target=_start_loop, daemon=True).start()
|
|
121
|
+
|
|
122
|
+
watchdog_task = asyncio.run_coroutine_threadsafe(self._watchdog_timer(), loop)
|
|
123
|
+
|
|
124
|
+
self._log.info(
|
|
125
|
+
"Keep-alive daemon started at %s:%d, watchdog feed interval %ds.",
|
|
126
|
+
self._config.listen_address, self._config.listen_port, self._config.watchdog_feed_interval
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
web.run_app(self._web_app, host=self._config.listen_address, port=self._config.listen_port)
|
|
130
|
+
|
|
131
|
+
self._log.info("Stopping...")
|
|
132
|
+
|
|
133
|
+
watchdog_task.cancel()
|
|
134
|
+
loop.stop()
|
|
135
|
+
|
|
136
|
+
if self._special_process_id > 0:
|
|
137
|
+
os.kill(self._special_process_id, signal.SIGKILL)
|
|
138
|
+
os.waitpid(self._special_process_id, 0)
|
|
139
|
+
|
|
140
|
+
self._log.info("Stopped.")
|
|
141
|
+
|
|
142
|
+
async def _handle_watchdog_feed(self, _: Request) -> Response:
|
|
143
|
+
last_feed = self._watchdog_last_feed_time
|
|
144
|
+
self._watchdog_last_feed_time = datetime.now()
|
|
145
|
+
interval = (self._watchdog_last_feed_time - last_feed).total_seconds()
|
|
146
|
+
|
|
147
|
+
return web.json_response(
|
|
148
|
+
{
|
|
149
|
+
"status": "ok",
|
|
150
|
+
"last_feed": last_feed.strftime("%Y-%m-%d %H:%M:%S"),
|
|
151
|
+
"interval": interval,
|
|
152
|
+
}
|
|
153
|
+
)
|