wnm 0.0.12__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.

Potentially problematic release.


This version of wnm might be problematic. Click here for more details.

wnm/wallets.py ADDED
@@ -0,0 +1,177 @@
1
+ """Wallet address resolution and weighted distribution.
2
+
3
+ This module provides functionality for:
4
+ 1. Resolving named wallets (faucet/donate) to actual addresses
5
+ 2. Parsing weighted wallet lists
6
+ 3. Selecting wallets based on weights for node assignment
7
+ """
8
+
9
+ import logging
10
+ import random
11
+ import re
12
+ from typing import List, Optional, Tuple
13
+
14
+ from wnm.common import FAUCET
15
+
16
+
17
+ def resolve_wallet_name(name: str, donate_address: str) -> str:
18
+ """Resolve named wallet to actual address.
19
+
20
+ Args:
21
+ name: Wallet name or address. Can be:
22
+ - "faucet" (case-insensitive) -> FAUCET constant (not changeable by user)
23
+ - "donate" (case-insensitive) -> donate_address from machine config (user can override)
24
+ - Ethereum address (0x...) -> returned as-is
25
+ donate_address: The donate address from machine config
26
+
27
+ Returns:
28
+ Resolved Ethereum address
29
+
30
+ Raises:
31
+ ValueError: If name is neither a valid address nor known name
32
+ """
33
+ name_lower = name.lower().strip()
34
+
35
+ # Check for named wallets
36
+ if name_lower == "faucet":
37
+ return FAUCET # Always use the constant (not changeable)
38
+ if name_lower == "donate":
39
+ return donate_address # Use machine config value (user can override)
40
+
41
+ # Validate as Ethereum address (0x followed by 40 hex chars)
42
+ if re.match(r"^0x[0-9A-Fa-f]{40}$", name.strip()):
43
+ return name.strip()
44
+
45
+ raise ValueError(
46
+ f"Invalid wallet identifier: '{name}'. "
47
+ f"Must be 'donate', 'faucet', or a valid Ethereum address (0x...)"
48
+ )
49
+
50
+
51
+ def parse_weighted_wallets(
52
+ wallet_string: str, donate_address: str
53
+ ) -> List[Tuple[str, int]]:
54
+ """Parse comma-separated weighted wallet list.
55
+
56
+ Supports formats:
57
+ - Single wallet: "0xABC" or "donate"
58
+ - Weighted list: "0xABC:100,faucet:1,donate:10"
59
+
60
+ Args:
61
+ wallet_string: Wallet specification string
62
+ donate_address: The donate address from machine config for resolving names
63
+
64
+ Returns:
65
+ List of (address, weight) tuples. For single wallet, weight=1.
66
+
67
+ Raises:
68
+ ValueError: If format is invalid or wallets cannot be resolved
69
+ """
70
+ if not wallet_string or not wallet_string.strip():
71
+ raise ValueError("Wallet string cannot be empty")
72
+
73
+ wallet_string = wallet_string.strip()
74
+
75
+ # Check if this is a weighted list (contains comma or colon)
76
+ if "," not in wallet_string and ":" not in wallet_string:
77
+ # Single wallet without weight
78
+ address = resolve_wallet_name(wallet_string, donate_address)
79
+ return [(address, 1)]
80
+
81
+ # Parse weighted list
82
+ weighted_wallets = []
83
+ parts = wallet_string.split(",")
84
+
85
+ for part in parts:
86
+ part = part.strip()
87
+ if not part:
88
+ continue
89
+
90
+ # Check for weight suffix
91
+ if ":" in part:
92
+ wallet_part, weight_part = part.rsplit(":", 1)
93
+ wallet_part = wallet_part.strip()
94
+ weight_part = weight_part.strip()
95
+
96
+ try:
97
+ weight = int(weight_part)
98
+ except ValueError:
99
+ raise ValueError(f"Invalid weight in '{part}': {weight_part}")
100
+
101
+ if weight <= 0:
102
+ raise ValueError(f"Weight must be positive: {part}")
103
+
104
+ address = resolve_wallet_name(wallet_part, donate_address)
105
+ weighted_wallets.append((address, weight))
106
+ else:
107
+ # No weight specified, default to 1
108
+ address = resolve_wallet_name(part, donate_address)
109
+ weighted_wallets.append((address, 1))
110
+
111
+ if not weighted_wallets:
112
+ raise ValueError("No valid wallets found in wallet string")
113
+
114
+ return weighted_wallets
115
+
116
+
117
+ def select_wallet_for_node(
118
+ wallet_string: str, donate_address: str, seed: Optional[int] = None
119
+ ) -> str:
120
+ """Select a wallet address for a node based on weighted distribution.
121
+
122
+ Uses random weighted selection to choose from the wallet list.
123
+ Each node creation will randomly pick according to weights.
124
+
125
+ Args:
126
+ wallet_string: Wallet specification (single or weighted list)
127
+ donate_address: The donate address from machine config
128
+ seed: Optional random seed for deterministic testing
129
+
130
+ Returns:
131
+ Selected Ethereum address
132
+
133
+ Raises:
134
+ ValueError: If wallet_string is invalid
135
+ """
136
+ weighted_wallets = parse_weighted_wallets(wallet_string, donate_address)
137
+
138
+ # If only one wallet, return it
139
+ if len(weighted_wallets) == 1:
140
+ return weighted_wallets[0][0]
141
+
142
+ # Extract addresses and weights
143
+ addresses = [addr for addr, _ in weighted_wallets]
144
+ weights = [weight for _, weight in weighted_wallets]
145
+
146
+ # Set seed if provided (for testing)
147
+ if seed is not None:
148
+ random.seed(seed)
149
+
150
+ # Randomly select based on weights
151
+ selected = random.choices(addresses, weights=weights, k=1)[0]
152
+
153
+ logging.debug(
154
+ f"Selected wallet {selected} from {len(weighted_wallets)} options "
155
+ f"with weights {weights}"
156
+ )
157
+
158
+ return selected
159
+
160
+
161
+ def validate_rewards_address(
162
+ rewards_address: str, donate_address: str
163
+ ) -> Tuple[bool, Optional[str]]:
164
+ """Validate a rewards address string.
165
+
166
+ Args:
167
+ rewards_address: The rewards address string to validate
168
+ donate_address: The donate address for resolving named wallets
169
+
170
+ Returns:
171
+ Tuple of (is_valid, error_message). error_message is None if valid.
172
+ """
173
+ try:
174
+ parse_weighted_wallets(rewards_address, donate_address)
175
+ return (True, None)
176
+ except ValueError as e:
177
+ return (False, str(e))
@@ -0,0 +1,367 @@
1
+ Metadata-Version: 2.4
2
+ Name: wnm
3
+ Version: 0.0.12
4
+ Summary: Manager for Autonomi nodes
5
+ Author-email: Troy Johnson <troy@weave.sh>
6
+ License: GPL-3.0
7
+ Project-URL: Repository, https://github.com/iweave/weave-node-manager.git
8
+ Project-URL: Issues, https://github.com/iweave/weave-node-manager/issues
9
+ Keywords: Autonomi,antnode,weave,xd7
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Topic :: System :: Distributed Computing
18
+ Requires-Python: >=3.12.3
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: python-dotenv
21
+ Requires-Dist: requests
22
+ Requires-Dist: packaging
23
+ Requires-Dist: sqlalchemy
24
+ Requires-Dist: alembic
25
+ Requires-Dist: json-fix
26
+ Requires-Dist: psutil
27
+ Requires-Dist: configargparse
28
+ Provides-Extra: dev
29
+ Requires-Dist: black; extra == "dev"
30
+ Requires-Dist: isort; extra == "dev"
31
+ Requires-Dist: build; extra == "dev"
32
+ Requires-Dist: twine; extra == "dev"
33
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
34
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
35
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
36
+ Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
37
+ Requires-Dist: pytest-docker>=3.1.0; extra == "dev"
38
+ Requires-Dist: docker>=7.0.0; extra == "dev"
39
+
40
+ # Weave Node Manager
41
+
42
+ ## Overview
43
+ Weave Node Manager (wnm) is a Python application designed to manage Autonomi nodes on Linux and macOS systems.
44
+
45
+ **Platforms**:
46
+ - **Linux**: systemd or setsid for process management, UFW for firewall
47
+ - **macOS**: launchd for process management (native support)
48
+ - **Python 3.12.3+** required
49
+
50
+ ## Features
51
+ - Automatic node lifecycle management (create, start, stop, upgrade, remove)
52
+ - Resource-based decision engine (CPU, memory, disk, I/O, load average thresholds)
53
+ - Platform-specific process management (systemd on Linux, launchd on macOS)
54
+ - Per-node binary copies for independent upgrades
55
+ - SQLite database for configuration and state tracking
56
+ - Support for configuration via environment variables, config files, or command-line parameters
57
+
58
+ ## Warning - Alpha Software
59
+
60
+ This code is Alpha. On Linux, it can migrate from an existing [anm](https://github.com/safenetforum-community/NTracking/tree/main/anm) installation. On macOS, it provides native development and testing support using launchd.
61
+
62
+ ## Installation
63
+
64
+ ### macOS (Development & Testing)
65
+
66
+ macOS support uses launchd for process management and is ideal for development and testing.
67
+
68
+ #### 1. Install antup (Autonomi binary manager)
69
+ ```bash
70
+ curl -sSL https://raw.githubusercontent.com/maidsafe/antup/main/install.sh | bash
71
+ ```
72
+
73
+ #### 2. Download antnode binary
74
+ ```bash
75
+ ~/.local/bin/antup node
76
+ ```
77
+
78
+ #### 3. Install WNM from PyPI
79
+ ```bash
80
+ pip3 install weave-node-manager
81
+ ```
82
+
83
+ #### 4. Or install from source
84
+ ```bash
85
+ git clone https://github.com/dawn-code/weave-node-manager.git
86
+ cd weave-node-manager
87
+ pip3 install -e .
88
+ ```
89
+
90
+ #### 5. Initialize and configure
91
+ ```bash
92
+ # Initialize with your rewards address
93
+ wnm --init --rewards_address 0xYourEthereumAddress
94
+
95
+ # Run in dry-run mode to test
96
+ wnm --dry_run
97
+
98
+ # Or run normally to start managing nodes
99
+ wnm
100
+ ```
101
+
102
+ #### 6. Optional: Add to cron for automatic management
103
+ ```bash
104
+ # Add to crontab (runs every minute)
105
+ crontab -e
106
+
107
+ # Add this line:
108
+ */1 * * * * /usr/local/bin/wnm >> ~/Library/Logs/autonomi/wnm-cron.log 2>&1
109
+ ```
110
+
111
+ **macOS Notes:**
112
+ - Uses `~/Library/Application Support/autonomi/node/` for data
113
+ - Uses `~/Library/Logs/autonomi/` for logs
114
+ - Nodes managed via launchd (`~/Library/LaunchAgents/`)
115
+ - No root/sudo access required
116
+
117
+ ### Linux (User-Level, Recommended)
118
+
119
+ Non-root installation using setsid for process management.
120
+
121
+ #### 1. Install antup (Autonomi binary manager)
122
+ ```bash
123
+ curl -sSL https://raw.githubusercontent.com/maidsafe/antup/main/install.sh | bash
124
+ ```
125
+
126
+ #### 2. Download antnode binary
127
+ ```bash
128
+ ~/.local/bin/antup node
129
+ ```
130
+
131
+ #### 3. Install WNM from PyPI
132
+ ```bash
133
+ pip3 install weave-node-manager
134
+ ```
135
+
136
+ #### 4. Or install from source
137
+ ```bash
138
+ git clone https://github.com/dawn-code/weave-node-manager.git
139
+ cd weave-node-manager
140
+ pip3 install -e .
141
+ ```
142
+
143
+ #### 5. Initialize and configure
144
+ ```bash
145
+ # Initialize with your rewards address
146
+ wnm --init --rewards_address 0xYourEthereumAddress
147
+
148
+ # Run in dry-run mode to test
149
+ wnm --dry_run
150
+
151
+ # Or run normally
152
+ wnm
153
+ ```
154
+
155
+ #### 6. Optional: Add to cron
156
+ ```bash
157
+ crontab -e
158
+
159
+ # Add this line:
160
+ */1 * * * * ~/.local/bin/wnm >> ~/.local/share/autonomi/logs/wnm-cron.log 2>&1
161
+ ```
162
+
163
+ **Linux User-Level Notes:**
164
+ - Uses `~/.local/share/autonomi/node/` for data (XDG spec)
165
+ - Uses `~/.local/share/autonomi/logs/` for logs
166
+ - Nodes run as background processes (setsid)
167
+ - No root/sudo required
168
+
169
+ ### Linux (Root-Level, Production)
170
+
171
+ Root installation using systemd for process management.
172
+
173
+ #### 1. Install system dependencies
174
+ ```bash
175
+ sudo apt install -y python3.12-venv python3-pip
176
+ ```
177
+
178
+ #### 2. Install antup and antnode
179
+ ```bash
180
+ curl -sSL https://raw.githubusercontent.com/maidsafe/antup/main/install.sh | bash
181
+ ~/.local/bin/antup node
182
+ sudo cp ~/.local/bin/antnode /usr/local/bin/
183
+ ```
184
+
185
+ #### 3. Install WNM
186
+ ```bash
187
+ sudo pip3 install weave-node-manager
188
+ ```
189
+
190
+ #### 4. Initialize as root
191
+ ```bash
192
+ # Migrate from existing anm installation
193
+ sudo wnm --init --migrate_anm
194
+
195
+ # Or initialize fresh
196
+ sudo wnm --init --rewards_address 0xYourEthereumAddress
197
+ ```
198
+
199
+ #### 5. Add to cron
200
+ ```bash
201
+ sudo crontab -e
202
+
203
+ # Add this line:
204
+ */1 * * * * /usr/local/bin/wnm >> /var/log/antnode/wnm-cron.log 2>&1
205
+ ```
206
+
207
+ **Linux Root-Level Notes:**
208
+ - Uses `/var/antctl/` for data (legacy anm compatibility)
209
+ - Uses `/var/log/antnode/` for logs
210
+ - Nodes managed via systemd (`/etc/systemd/system/`)
211
+ - Requires root/sudo access
212
+ - Supports migration from anm
213
+
214
+ ## Configuration
215
+
216
+ Configuration follows a multi-layer priority system (highest to lowest):
217
+
218
+ 1. **Command-line arguments**: `wnm --cpu_less_than 70 --node_cap 50`
219
+ 2. **Environment variables**: Set in `.env` file or shell
220
+ 3. **Config files**:
221
+ - macOS: `~/Library/Application Support/autonomi/config`
222
+ - Linux (user): `~/.local/share/autonomi/config`
223
+ - Linux (root): `/var/antctl/config`
224
+ 4. **Database-stored config**: Persisted in `colony.db` after initialization
225
+ 5. **Default values**: Built-in defaults
226
+
227
+ ### Key Configuration Parameters
228
+
229
+ Resource thresholds control when nodes are added or removed:
230
+
231
+ - `--cpu_less_than` / `--cpu_remove`: CPU percentage thresholds (default: 70% / 80%)
232
+ - `--mem_less_than` / `--mem_remove`: Memory percentage thresholds (default: 70% / 80%)
233
+ - `--hd_less_than` / `--hd_remove`: Disk usage percentage thresholds (default: 70% / 80%)
234
+ - `--desired_load_average` / `--max_load_average_allowed`: Load average thresholds
235
+ - `--node_cap`: Maximum number of nodes (default: 50)
236
+ - `--rewards_address`: Wallet address(es) for node rewards (required) - see Wallet Configuration below
237
+ - `--node_storage`: Root directory for node data (auto-detected per platform)
238
+
239
+ ### Wallet Configuration
240
+
241
+ The `--rewards_address` parameter supports multiple formats for flexible reward distribution:
242
+
243
+ #### Single Wallet
244
+ Use a single Ethereum address or named wallet:
245
+ ```bash
246
+ # Ethereum address
247
+ wnm --init --rewards_address 0xYourEthereumAddress
248
+
249
+ # Named wallet: "donate" (uses your custom donate_address or the community foucet if not deefined)
250
+ wnm --init --rewards_address donate
251
+
252
+ # Named wallet: "faucet" (always uses the autonomi community faucet address)
253
+ wnm --init --rewards_address faucet
254
+ ```
255
+
256
+ #### Weighted Distribution
257
+ Distribute rewards across multiple wallets using weighted random selection:
258
+ ```bash
259
+ # Format: wallet1:weight1,wallet2:weight2,...
260
+ wnm --init --rewards_address "0xYourAddress:100,faucet:1,donate:10"
261
+ ```
262
+
263
+ In this example:
264
+ - Your address receives ~90% of nodes (100 out of 111 weight)
265
+ - Faucet receives ~1% of nodes (1 out of 111 weight)
266
+ - Donate address receives ~9% of nodes (10 out of 111 weight)
267
+
268
+ **Key Features:**
269
+ - **Random per node**: Each new node randomly selects a wallet based on weights
270
+ - **Named wallets**: Use `faucet` (project faucet) or `donate` (your custom donation address)
271
+ - **Case-insensitive**: `faucet`, `FAUCET`, and `Faucet` all work
272
+ - **Mix addresses and names**: Combine Ethereum addresses with named wallets
273
+ - **Changeable**: Update `--rewards_address` anytime to change distribution for new nodes
274
+
275
+ **Examples:**
276
+ ```bash
277
+ # 50/50 split between your address and faucet
278
+ wnm --rewards_address "0xYourAddress:1,faucet:1"
279
+
280
+ # Your address only
281
+ wnm --rewards_address 0xYourAddress
282
+
283
+ # Mostly yours, small donation to faucet
284
+ wnm --rewards_address "0xYourAddress:99,faucet:1"
285
+
286
+ # Multiple addresses with custom weights
287
+ wnm --rewards_address "0xAddress1:100,0xAddress2:50,faucet:10"
288
+ ```
289
+
290
+ ### anm Migration (Linux Only)
291
+
292
+ Upon finding an existing [anm](https://github.com/safenetforum-community/NTracking/tree/main/anm) installation, wnm will:
293
+ 1. Disable anm by removing `/etc/cron.d/anm`
294
+ 2. Import configuration from `/var/antctl/config`
295
+ 3. Discover and import existing nodes from systemd
296
+ 4. Take over management of the cluster
297
+
298
+ Use `wnm --init --migrate_anm` to trigger migration.
299
+
300
+ ## Usage
301
+
302
+ ### Run Once
303
+ ```bash
304
+ # macOS or Linux
305
+ wnm
306
+
307
+ # With dry-run (no actual changes)
308
+ wnm --dry_run
309
+
310
+ # Initialize first time
311
+ wnm --init --rewards_address 0xYourEthereumAddress
312
+ ```
313
+
314
+ ### Run via Cron (Recommended)
315
+
316
+ WNM is designed to run every minute via cron, making one decision per cycle:
317
+
318
+ **macOS:**
319
+ ```bash
320
+ crontab -e
321
+ # Add: */1 * * * * /usr/local/bin/wnm >> ~/Library/Logs/autonomi/wnm-cron.log 2>&1
322
+ ```
323
+
324
+ **Linux (user):**
325
+ ```bash
326
+ crontab -e
327
+ # Add: */1 * * * * ~/.local/bin/wnm >> ~/.local/share/autonomi/logs/wnm-cron.log 2>&1
328
+ ```
329
+
330
+ **Linux (root):**
331
+ ```bash
332
+ sudo crontab -e
333
+ # Add: */1 * * * * /usr/local/bin/wnm >> /var/log/antnode/wnm-cron.log 2>&1
334
+ ```
335
+
336
+ ### Development Mode
337
+
338
+ For development with live code reloading:
339
+
340
+ **macOS (native):**
341
+ ```bash
342
+ python3 -m wnm --dry_run
343
+ ```
344
+
345
+ **Linux (Docker):**
346
+ ```bash
347
+ ./scripts/dev.sh
348
+ # Inside container:
349
+ python3 -m wnm --dry_run
350
+ ```
351
+
352
+ See `DOCKER-DEV.md` for comprehensive Docker development workflow.
353
+
354
+ ## Platform Support
355
+
356
+ See `PLATFORM-SUPPORT.md` for detailed information about:
357
+ - Platform-specific process managers (systemd, launchd, setsid)
358
+ - Firewall management (UFW, null)
359
+ - Path conventions per platform
360
+ - Binary management and upgrades
361
+ - Testing strategies
362
+
363
+ ## Contributing
364
+ Contributions are welcome! Please submit a pull request or open an issue for any enhancements or bug fixes.
365
+
366
+ ## License
367
+ This project is licensed under the MIT License. See the LICENSE file for more details.
@@ -0,0 +1,29 @@
1
+ wnm/__init__.py,sha256=7iYQD6abjHiZLWlLDOBAFIcPQNdSQpPKfUtgN418lM0,92
2
+ wnm/__main__.py,sha256=fMlM5gQIFGeFp19oowxmSKwtAE7-xS5-_ThTPaO7J-0,7989
3
+ wnm/actions.py,sha256=h3ieTbQQIggKHqCLVWe5eph5YGif39tZRW3ch82tqys,1314
4
+ wnm/common.py,sha256=mjKPTCN1pSkxRqQJ6sUnB2JRcXGxRJ4D_eHK6LW7SnE,1116
5
+ wnm/config.py,sha256=Jt9CtaNwSThg5RvQ_rxjZPtmf4TysXD5wg07_RlQKbg,27272
6
+ wnm/decision_engine.py,sha256=l_vqrw7imj9EsueLKWkz94P0TtA7UCyNPxBCfgkmNUg,13817
7
+ wnm/executor.py,sha256=eNn4cLkHyhqlYQd5KrKIw3qs9FEdzC4-MrFmYpH1uKI,52519
8
+ wnm/migration.py,sha256=QcfvN9S0FIrEDq68IrgM1JBO288ub1tiSXdsrg53eSQ,1389
9
+ wnm/models.py,sha256=qCCNu-Beg5TSQtIi7_1qnfuv5YmzzMq3We9PGektR1E,16684
10
+ wnm/reports.py,sha256=m_EW_-_CjlKPE6uXMWC_02RDPMlL0LirdctmgnGfpEM,9133
11
+ wnm/utils.py,sha256=Zmzg729olHfjDBc5XFCu30b9RR70sjNxIvx_PzGTn7c,15897
12
+ wnm/wallets.py,sha256=cITjHPHXJ2BVKIDzcZzwLzgBYbI54mlFvNzD3D2rQZM,5554
13
+ wnm/firewall/__init__.py,sha256=2Ur3cSiP2hKVJp9guuuyDDgE2vklyU46KnstDfRq12Q,471
14
+ wnm/firewall/base.py,sha256=cQQ8v7pYgVIjDcXj2jsU4_iZDXRyw7xtlfoJhcy8Gps,1882
15
+ wnm/firewall/factory.py,sha256=zm_SVd1d_NaKkTaj73ai_4oMOhTb1SoHOu0-ylpu2M4,2870
16
+ wnm/firewall/null_firewall.py,sha256=ENmQM0UIubhuqpSqyjSRBCxb3GRPIOYVkMzdZyhUE0Q,1834
17
+ wnm/firewall/ufw_manager.py,sha256=CqkjSr4j8kdIsOdjUbj0h0ZeWhrvsxh_cLvxWUAWaoc,3171
18
+ wnm/process_managers/__init__.py,sha256=ohFXPcqpSBHEkclSaVMfPZdym2KNjcrCfxV6Kbl_3ZY,739
19
+ wnm/process_managers/base.py,sha256=kODNaXY302bCR8BRCISe6g47uDUjA4T7yPKP2s7DlIg,5721
20
+ wnm/process_managers/docker_manager.py,sha256=U-_VMkWK7WzxpWGtMStIRsXBKez8QOcCLZ0pHiHJzZY,11015
21
+ wnm/process_managers/factory.py,sha256=TVkcN-o3kRtf1VylsTXMvpcTM2rwfcZT6QY3JsG5HkQ,2437
22
+ wnm/process_managers/launchd_manager.py,sha256=c0QV_0eowCaNga81mNINvJM3We8BJm2_UvPX0v6-4v4,19860
23
+ wnm/process_managers/setsid_manager.py,sha256=DW3BylkmmdUa4_mk68wpdwy5kqG-IJ_s13vG5eKE_Rs,10650
24
+ wnm/process_managers/systemd_manager.py,sha256=dzEKngo2Bz5562h8PZRA1-m51JMcqU5Qc-f9CullNzk,18718
25
+ wnm-0.0.12.dist-info/METADATA,sha256=cKawc6sumk4JjiADIS-paSA8lRCz2ByM-L7dLaGrQ8o,10617
26
+ wnm-0.0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
+ wnm-0.0.12.dist-info/entry_points.txt,sha256=jfoemjoLVPeeiBMHKqAExrHQ4Rhf9IXxL4JCnS7ZYFo,42
28
+ wnm-0.0.12.dist-info/top_level.txt,sha256=E6dTE5k6efMEB9LaJAZSBu8zzs__l4R55t0-F-LwufI,4
29
+ wnm-0.0.12.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ wnm = wnm.__main__:main
@@ -0,0 +1 @@
1
+ wnm