kinenix-worker 0.2.0b1__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.
- kinenix_worker-0.2.0b1/LICENSE +21 -0
- kinenix_worker-0.2.0b1/PKG-INFO +236 -0
- kinenix_worker-0.2.0b1/README.md +207 -0
- kinenix_worker-0.2.0b1/kinenix_worker/__init__.py +5 -0
- kinenix_worker-0.2.0b1/kinenix_worker/cli.py +186 -0
- kinenix_worker-0.2.0b1/kinenix_worker/display.py +89 -0
- kinenix_worker-0.2.0b1/kinenix_worker/hub_client.py +163 -0
- kinenix_worker-0.2.0b1/kinenix_worker/runner.py +158 -0
- kinenix_worker-0.2.0b1/kinenix_worker/triggers/__init__.py +9 -0
- kinenix_worker-0.2.0b1/kinenix_worker/triggers/cron.py +75 -0
- kinenix_worker-0.2.0b1/kinenix_worker/triggers/manager.py +101 -0
- kinenix_worker-0.2.0b1/kinenix_worker/triggers/scheduler.py +105 -0
- kinenix_worker-0.2.0b1/kinenix_worker/triggers/watcher.py +125 -0
- kinenix_worker-0.2.0b1/kinenix_worker.egg-info/PKG-INFO +236 -0
- kinenix_worker-0.2.0b1/kinenix_worker.egg-info/SOURCES.txt +25 -0
- kinenix_worker-0.2.0b1/kinenix_worker.egg-info/dependency_links.txt +1 -0
- kinenix_worker-0.2.0b1/kinenix_worker.egg-info/entry_points.txt +2 -0
- kinenix_worker-0.2.0b1/kinenix_worker.egg-info/requires.txt +7 -0
- kinenix_worker-0.2.0b1/kinenix_worker.egg-info/top_level.txt +1 -0
- kinenix_worker-0.2.0b1/pyproject.toml +49 -0
- kinenix_worker-0.2.0b1/setup.cfg +4 -0
- kinenix_worker-0.2.0b1/tests/test_cron.py +65 -0
- kinenix_worker-0.2.0b1/tests/test_display.py +53 -0
- kinenix_worker-0.2.0b1/tests/test_hub_client.py +109 -0
- kinenix_worker-0.2.0b1/tests/test_runner_flow_names.py +29 -0
- kinenix_worker-0.2.0b1/tests/test_triggers.py +115 -0
- kinenix_worker-0.2.0b1/tests/test_worker.py +46 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 arttopix / Kinenix
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kinenix-worker
|
|
3
|
+
Version: 0.2.0b1
|
|
4
|
+
Summary: Kinenix Worker: runs Kinenix flows unattended on schedules, cron, or file triggers, and reports to the Kinenix Hub
|
|
5
|
+
Author: arttopix
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/arttopix/Kinenix
|
|
8
|
+
Project-URL: Documentation, https://github.com/arttopix/Kinenix/blob/main/kinenix-worker/README.md
|
|
9
|
+
Project-URL: Issues, https://github.com/arttopix/Kinenix/issues
|
|
10
|
+
Keywords: rpa,automation,raspberry-pi,scheduler,worker
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Office/Business
|
|
18
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: kinenix>=0.2.0b1
|
|
23
|
+
Requires-Dist: psutil>=5.9.0
|
|
24
|
+
Requires-Dist: requests>=2.28.0
|
|
25
|
+
Requires-Dist: rich>=13.0.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# kinenix-worker
|
|
31
|
+
|
|
32
|
+
The unattended robot worker daemon and edge execution engine for **[Kinenix](https://github.com/arttopix/Kinenix)**, an open-source, local AI-native RPA framework in Python.
|
|
33
|
+
|
|
34
|
+
## Install from PyPI
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install "kinenix[worker]"
|
|
38
|
+
kinenix install-browsers # Playwright Chromium, needed for web actions
|
|
39
|
+
kinenix-worker info
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Requires Python 3.10 or newer. To report to a Kinenix Hub, set `KINENIX_HUB_URL` and `KINENIX_HUB_API_KEY` (section 3D). The Raspberry Pi quickstart below installs from the repository instead and also sets up a systemd service.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 1. Overview
|
|
47
|
+
|
|
48
|
+
`kinenix-worker` is designed to run unattended automation workloads on edge hardware, dedicated worker machines, virtual machines, and low-power devices such as **Raspberry Pi 4 Model B**.
|
|
49
|
+
|
|
50
|
+
### Key Capabilities
|
|
51
|
+
- **Lightweight Execution:** Minimal overhead with direct integration into `kinenix-core`.
|
|
52
|
+
- **Sandbox Workspace Isolation:** Executes project bundles inside disposable, per-job sandbox environments.
|
|
53
|
+
- **Hardware Telemetry:** Gathers CPU, memory, architecture, and platform metrics at runtime.
|
|
54
|
+
- **Structured JSON Auditing:** Stores hierarchical execution traces and error telemetry locally.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 2. Raspberry Pi 4 Model B (ARM64) Quickstart
|
|
59
|
+
|
|
60
|
+
### Prerequisites
|
|
61
|
+
- **Hardware:** Raspberry Pi 4 Model B (Rev 1.4 or compatible) with 2GB, 4GB, or 8GB RAM.
|
|
62
|
+
- **Operating System:** Raspberry Pi OS (64-bit Debian bookworm / bullseye) or Ubuntu Server ARM64.
|
|
63
|
+
- **Network:** Internet access for initial setup and downloading benchmark web pages.
|
|
64
|
+
|
|
65
|
+
### Automated Setup
|
|
66
|
+
Clone the repository and run the setup script:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 1. Clone repository onto Raspberry Pi
|
|
70
|
+
git clone -b dev https://github.com/arttopix/Kinenix.git kinenix
|
|
71
|
+
cd kinenix
|
|
72
|
+
|
|
73
|
+
# 2. Run the setup script (it is already executable; do not chmod it, or git pull will see a local change)
|
|
74
|
+
./kinenix-worker/scripts/setup_rpi.sh
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The script automatically:
|
|
78
|
+
1. Installs required Linux shared libraries for Chromium on aarch64.
|
|
79
|
+
2. Creates a Python 3.10+ virtual environment (`.venv`).
|
|
80
|
+
3. Installs `kinenix-core` and `kinenix-worker` in editable mode.
|
|
81
|
+
4. Installs the ARM64 Playwright Chromium browser.
|
|
82
|
+
5. Verifies the installation with `kinenix-worker info`.
|
|
83
|
+
6. Installs and starts the `kinenix-worker` systemd service (see [section 3E](#e-run-as-a-systemd-service)). Pass `--no-service` to skip it.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 3. Running Workflows on Raspberry Pi
|
|
88
|
+
|
|
89
|
+
### A. Activate Environment
|
|
90
|
+
```bash
|
|
91
|
+
source .venv/bin/activate
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### B. Verify System Information
|
|
95
|
+
```bash
|
|
96
|
+
kinenix-worker info
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Expected output on Raspberry Pi 4:
|
|
100
|
+
```text
|
|
101
|
+
Kinenix Worker System Information:
|
|
102
|
+
-------------------------------
|
|
103
|
+
os: Linux
|
|
104
|
+
os_release: 6.6.x+rpt-rpi-v8
|
|
105
|
+
machine: aarch64
|
|
106
|
+
processor: aarch64
|
|
107
|
+
python_version: 3.11.x
|
|
108
|
+
cpu_count: 4
|
|
109
|
+
memory_total_mb: 3884.2
|
|
110
|
+
memory_available_mb: 3200.1
|
|
111
|
+
memory_used_percent: 17.6
|
|
112
|
+
worker_id: raspberrypi
|
|
113
|
+
hub_url: (not set)
|
|
114
|
+
hub_api_key: (not set)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### C. Run RPA Challenge Example Flow
|
|
118
|
+
```bash
|
|
119
|
+
kinenix-worker run flows/examples/rpachallenge/flow.json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
To run inside an isolated sandbox directory:
|
|
123
|
+
```bash
|
|
124
|
+
kinenix-worker run flows/examples/rpachallenge/flow.json --sandbox
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### D. Connect to the Hub
|
|
128
|
+
|
|
129
|
+
The worker reports to a Kinenix Hub when these environment variables are set (see the [Hub guide](https://github.com/arttopix/Kinenix/blob/main/docs/hub.md) for the server side):
|
|
130
|
+
|
|
131
|
+
| Variable | Default | Purpose |
|
|
132
|
+
| :--- | :--- | :--- |
|
|
133
|
+
| `KINENIX_HUB_URL` | *(unset: no reporting)* | Hub base URL, for example `http://192.168.1.132:8080` |
|
|
134
|
+
| `KINENIX_HUB_API_KEY` | *(unset)* | The Hub's worker key; the same variable holds the same value on the Hub |
|
|
135
|
+
| `KINENIX_WORKER_ID` | host name | Name shown on the dashboard and attached to every execution |
|
|
136
|
+
| `KINENIX_HEARTBEAT_INTERVAL` | `30` | Seconds between heartbeats while a worker command runs |
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
export KINENIX_HUB_URL=http://192.168.1.132:8080
|
|
140
|
+
export KINENIX_HUB_API_KEY="<key>"
|
|
141
|
+
export KINENIX_WORKER_ID=rpi4-01
|
|
142
|
+
|
|
143
|
+
kinenix-worker ping # checks reachability and the API key; exit code 1 on failure
|
|
144
|
+
kinenix-worker run flows/examples/rpachallenge
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
With the URL set:
|
|
148
|
+
|
|
149
|
+
- Every run sends its execution log to the Hub. `--log-dir` is not needed; it only controls whether a local log file is also written.
|
|
150
|
+
- Every run reports the worker as busy while the flow runs and online when it finishes.
|
|
151
|
+
- Every command that runs flows (`run`, `watch`, `schedule`, `daemon`) also sends a heartbeat every `KINENIX_HEARTBEAT_INTERVAL` seconds, including during long flows. The dashboard shows the worker as offline when heartbeats stop.
|
|
152
|
+
- If the Hub is unreachable, flows still run. The worker logs one warning, then logs again when the connection recovers.
|
|
153
|
+
|
|
154
|
+
### E. Run as a systemd Service
|
|
155
|
+
|
|
156
|
+
The service keeps `kinenix-worker daemon` running in the background, starts it at boot, and restarts it within 10 seconds if it stops. It runs as the user who installed it, from the repository directory, so relative flow paths such as `flows/examples/rpachallenge` work. While it runs, the worker sends heartbeats and shows as online in the Hub.
|
|
157
|
+
|
|
158
|
+
`setup_rpi.sh` installs it. To install or update it on an existing setup, run as your normal user (not root):
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
./kinenix-worker/scripts/install_service.sh
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
It reads two files in `~/.kinenix/`, creating them if they do not exist (existing files are never overwritten):
|
|
165
|
+
|
|
166
|
+
| File | Contents |
|
|
167
|
+
| :--- | :--- |
|
|
168
|
+
| `worker.env` | `KINENIX_HUB_URL`, `KINENIX_HUB_API_KEY`, `KINENIX_WORKER_ID` as `export` lines; owner-readable only. Also put the secrets your flows read here, for example `GMAIL_USER` and `GMAIL_APP_PASSWORD` for `email.send`: the service does not see variables set in your shell. Add `source ~/.kinenix/worker.env` to `~/.bashrc` to use the same values in your shell. |
|
|
169
|
+
| `triggers.json` | Triggers for the daemon. The default `{"triggers": []}` runs no flows and only sends heartbeats. |
|
|
170
|
+
|
|
171
|
+
**Your own flows** belong in your flows folder, `~/kinenix-flows`, not in this repository (see [Where your flows live](https://github.com/arttopix/Kinenix/blob/main/docs/quickstart.md#2-where-your-flows-live)). Keep them in a private git repository and clone it on the Pi:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
git clone git@github.com:<you>/kinenix-flows.git ~/kinenix-flows # your private repository
|
|
175
|
+
cd ~/kinenix-flows && git pull # later, to update the flows
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
A trigger can then name a flow by its folder name; the worker looks it up in the flows folder (or in `KINENIX_FLOWS_DIR`). Paths still work too, relative to this repository.
|
|
179
|
+
|
|
180
|
+
Example `triggers.json`:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"triggers": [
|
|
185
|
+
{ "type": "scheduler", "flow": "get_stock_data", "cron": "0 8 * * 1-5" },
|
|
186
|
+
{ "type": "scheduler", "flow": "flows/examples/bot_fx_rate", "cron": "0 8 1 * *" },
|
|
187
|
+
{ "type": "file_watcher", "flow": "invoice_check", "watch_dir": "/home/pi/inbox", "pattern": "*.xlsx" }
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
A scheduler runs either every `interval_seconds` or, when `cron` is set, at the minutes matching a five-field cron expression in the Pi's local time (`minute hour day-of-month month day-of-week`, Sunday is 0 or 7):
|
|
193
|
+
|
|
194
|
+
| `cron` | Runs |
|
|
195
|
+
| :--- | :--- |
|
|
196
|
+
| `0 8 1 * *` | 08:00 on the 1st of every month |
|
|
197
|
+
| `30 7 * * 1-5` | 07:30 Monday to Friday |
|
|
198
|
+
| `*/15 * * * *` | Every 15 minutes |
|
|
199
|
+
| `0 9 1,15 * *` | 09:00 on the 1st and 15th |
|
|
200
|
+
|
|
201
|
+
An invalid expression stops the daemon at startup with the field that is wrong. Check the Pi's time zone with `timedatectl` (set it with `sudo timedatectl set-timezone Asia/Bangkok`).
|
|
202
|
+
|
|
203
|
+
| Task | Command |
|
|
204
|
+
| :--- | :--- |
|
|
205
|
+
| Apply changes to `worker.env` or `triggers.json` | `sudo systemctl restart kinenix-worker` |
|
|
206
|
+
| Status | `systemctl status kinenix-worker` |
|
|
207
|
+
| Follow logs | `journalctl -u kinenix-worker -f` |
|
|
208
|
+
| Stop until next boot | `sudo systemctl stop kinenix-worker` |
|
|
209
|
+
| Remove the service | `./kinenix-worker/scripts/install_service.sh --uninstall` |
|
|
210
|
+
|
|
211
|
+
Jobs sent from the Hub are not supported yet; each worker runs the triggers in its own `triggers.json`.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 4. CLI Reference
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
usage: kinenix-worker [-h] [--version] {info,ping,run,watch,schedule,daemon} ...
|
|
219
|
+
|
|
220
|
+
positional arguments:
|
|
221
|
+
info Display worker machine hardware, architecture, and runtime stats
|
|
222
|
+
ping Check the connection and API key to the Hub (KINENIX_HUB_URL)
|
|
223
|
+
run Execute a flow or project bundle on this worker
|
|
224
|
+
watch Watch a directory and automatically trigger a flow when new files appear
|
|
225
|
+
schedule Execute a flow on an interval (--interval) or a cron expression (--cron "0 8 1 * *")
|
|
226
|
+
daemon Run multi-trigger daemon using a configuration file
|
|
227
|
+
|
|
228
|
+
options:
|
|
229
|
+
--version, -v
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### `kinenix-worker run` Options
|
|
233
|
+
- `flow_path`: Path to `flow.json` or project bundle directory.
|
|
234
|
+
- `--sandbox`: Execute inside an isolated temporary sandbox (`~/.kinenix/workspaces/<job_id>`).
|
|
235
|
+
- `--vars`: JSON string of variables to override.
|
|
236
|
+
- `--log-dir`: Also write a local JSON execution log to this directory. Telemetry to the Hub does not depend on it.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# kinenix-worker
|
|
2
|
+
|
|
3
|
+
The unattended robot worker daemon and edge execution engine for **[Kinenix](https://github.com/arttopix/Kinenix)**, an open-source, local AI-native RPA framework in Python.
|
|
4
|
+
|
|
5
|
+
## Install from PyPI
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install "kinenix[worker]"
|
|
9
|
+
kinenix install-browsers # Playwright Chromium, needed for web actions
|
|
10
|
+
kinenix-worker info
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Python 3.10 or newer. To report to a Kinenix Hub, set `KINENIX_HUB_URL` and `KINENIX_HUB_API_KEY` (section 3D). The Raspberry Pi quickstart below installs from the repository instead and also sets up a systemd service.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Overview
|
|
18
|
+
|
|
19
|
+
`kinenix-worker` is designed to run unattended automation workloads on edge hardware, dedicated worker machines, virtual machines, and low-power devices such as **Raspberry Pi 4 Model B**.
|
|
20
|
+
|
|
21
|
+
### Key Capabilities
|
|
22
|
+
- **Lightweight Execution:** Minimal overhead with direct integration into `kinenix-core`.
|
|
23
|
+
- **Sandbox Workspace Isolation:** Executes project bundles inside disposable, per-job sandbox environments.
|
|
24
|
+
- **Hardware Telemetry:** Gathers CPU, memory, architecture, and platform metrics at runtime.
|
|
25
|
+
- **Structured JSON Auditing:** Stores hierarchical execution traces and error telemetry locally.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Raspberry Pi 4 Model B (ARM64) Quickstart
|
|
30
|
+
|
|
31
|
+
### Prerequisites
|
|
32
|
+
- **Hardware:** Raspberry Pi 4 Model B (Rev 1.4 or compatible) with 2GB, 4GB, or 8GB RAM.
|
|
33
|
+
- **Operating System:** Raspberry Pi OS (64-bit Debian bookworm / bullseye) or Ubuntu Server ARM64.
|
|
34
|
+
- **Network:** Internet access for initial setup and downloading benchmark web pages.
|
|
35
|
+
|
|
36
|
+
### Automated Setup
|
|
37
|
+
Clone the repository and run the setup script:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 1. Clone repository onto Raspberry Pi
|
|
41
|
+
git clone -b dev https://github.com/arttopix/Kinenix.git kinenix
|
|
42
|
+
cd kinenix
|
|
43
|
+
|
|
44
|
+
# 2. Run the setup script (it is already executable; do not chmod it, or git pull will see a local change)
|
|
45
|
+
./kinenix-worker/scripts/setup_rpi.sh
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The script automatically:
|
|
49
|
+
1. Installs required Linux shared libraries for Chromium on aarch64.
|
|
50
|
+
2. Creates a Python 3.10+ virtual environment (`.venv`).
|
|
51
|
+
3. Installs `kinenix-core` and `kinenix-worker` in editable mode.
|
|
52
|
+
4. Installs the ARM64 Playwright Chromium browser.
|
|
53
|
+
5. Verifies the installation with `kinenix-worker info`.
|
|
54
|
+
6. Installs and starts the `kinenix-worker` systemd service (see [section 3E](#e-run-as-a-systemd-service)). Pass `--no-service` to skip it.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 3. Running Workflows on Raspberry Pi
|
|
59
|
+
|
|
60
|
+
### A. Activate Environment
|
|
61
|
+
```bash
|
|
62
|
+
source .venv/bin/activate
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### B. Verify System Information
|
|
66
|
+
```bash
|
|
67
|
+
kinenix-worker info
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Expected output on Raspberry Pi 4:
|
|
71
|
+
```text
|
|
72
|
+
Kinenix Worker System Information:
|
|
73
|
+
-------------------------------
|
|
74
|
+
os: Linux
|
|
75
|
+
os_release: 6.6.x+rpt-rpi-v8
|
|
76
|
+
machine: aarch64
|
|
77
|
+
processor: aarch64
|
|
78
|
+
python_version: 3.11.x
|
|
79
|
+
cpu_count: 4
|
|
80
|
+
memory_total_mb: 3884.2
|
|
81
|
+
memory_available_mb: 3200.1
|
|
82
|
+
memory_used_percent: 17.6
|
|
83
|
+
worker_id: raspberrypi
|
|
84
|
+
hub_url: (not set)
|
|
85
|
+
hub_api_key: (not set)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### C. Run RPA Challenge Example Flow
|
|
89
|
+
```bash
|
|
90
|
+
kinenix-worker run flows/examples/rpachallenge/flow.json
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To run inside an isolated sandbox directory:
|
|
94
|
+
```bash
|
|
95
|
+
kinenix-worker run flows/examples/rpachallenge/flow.json --sandbox
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### D. Connect to the Hub
|
|
99
|
+
|
|
100
|
+
The worker reports to a Kinenix Hub when these environment variables are set (see the [Hub guide](https://github.com/arttopix/Kinenix/blob/main/docs/hub.md) for the server side):
|
|
101
|
+
|
|
102
|
+
| Variable | Default | Purpose |
|
|
103
|
+
| :--- | :--- | :--- |
|
|
104
|
+
| `KINENIX_HUB_URL` | *(unset: no reporting)* | Hub base URL, for example `http://192.168.1.132:8080` |
|
|
105
|
+
| `KINENIX_HUB_API_KEY` | *(unset)* | The Hub's worker key; the same variable holds the same value on the Hub |
|
|
106
|
+
| `KINENIX_WORKER_ID` | host name | Name shown on the dashboard and attached to every execution |
|
|
107
|
+
| `KINENIX_HEARTBEAT_INTERVAL` | `30` | Seconds between heartbeats while a worker command runs |
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
export KINENIX_HUB_URL=http://192.168.1.132:8080
|
|
111
|
+
export KINENIX_HUB_API_KEY="<key>"
|
|
112
|
+
export KINENIX_WORKER_ID=rpi4-01
|
|
113
|
+
|
|
114
|
+
kinenix-worker ping # checks reachability and the API key; exit code 1 on failure
|
|
115
|
+
kinenix-worker run flows/examples/rpachallenge
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
With the URL set:
|
|
119
|
+
|
|
120
|
+
- Every run sends its execution log to the Hub. `--log-dir` is not needed; it only controls whether a local log file is also written.
|
|
121
|
+
- Every run reports the worker as busy while the flow runs and online when it finishes.
|
|
122
|
+
- Every command that runs flows (`run`, `watch`, `schedule`, `daemon`) also sends a heartbeat every `KINENIX_HEARTBEAT_INTERVAL` seconds, including during long flows. The dashboard shows the worker as offline when heartbeats stop.
|
|
123
|
+
- If the Hub is unreachable, flows still run. The worker logs one warning, then logs again when the connection recovers.
|
|
124
|
+
|
|
125
|
+
### E. Run as a systemd Service
|
|
126
|
+
|
|
127
|
+
The service keeps `kinenix-worker daemon` running in the background, starts it at boot, and restarts it within 10 seconds if it stops. It runs as the user who installed it, from the repository directory, so relative flow paths such as `flows/examples/rpachallenge` work. While it runs, the worker sends heartbeats and shows as online in the Hub.
|
|
128
|
+
|
|
129
|
+
`setup_rpi.sh` installs it. To install or update it on an existing setup, run as your normal user (not root):
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
./kinenix-worker/scripts/install_service.sh
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
It reads two files in `~/.kinenix/`, creating them if they do not exist (existing files are never overwritten):
|
|
136
|
+
|
|
137
|
+
| File | Contents |
|
|
138
|
+
| :--- | :--- |
|
|
139
|
+
| `worker.env` | `KINENIX_HUB_URL`, `KINENIX_HUB_API_KEY`, `KINENIX_WORKER_ID` as `export` lines; owner-readable only. Also put the secrets your flows read here, for example `GMAIL_USER` and `GMAIL_APP_PASSWORD` for `email.send`: the service does not see variables set in your shell. Add `source ~/.kinenix/worker.env` to `~/.bashrc` to use the same values in your shell. |
|
|
140
|
+
| `triggers.json` | Triggers for the daemon. The default `{"triggers": []}` runs no flows and only sends heartbeats. |
|
|
141
|
+
|
|
142
|
+
**Your own flows** belong in your flows folder, `~/kinenix-flows`, not in this repository (see [Where your flows live](https://github.com/arttopix/Kinenix/blob/main/docs/quickstart.md#2-where-your-flows-live)). Keep them in a private git repository and clone it on the Pi:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
git clone git@github.com:<you>/kinenix-flows.git ~/kinenix-flows # your private repository
|
|
146
|
+
cd ~/kinenix-flows && git pull # later, to update the flows
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
A trigger can then name a flow by its folder name; the worker looks it up in the flows folder (or in `KINENIX_FLOWS_DIR`). Paths still work too, relative to this repository.
|
|
150
|
+
|
|
151
|
+
Example `triggers.json`:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"triggers": [
|
|
156
|
+
{ "type": "scheduler", "flow": "get_stock_data", "cron": "0 8 * * 1-5" },
|
|
157
|
+
{ "type": "scheduler", "flow": "flows/examples/bot_fx_rate", "cron": "0 8 1 * *" },
|
|
158
|
+
{ "type": "file_watcher", "flow": "invoice_check", "watch_dir": "/home/pi/inbox", "pattern": "*.xlsx" }
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
A scheduler runs either every `interval_seconds` or, when `cron` is set, at the minutes matching a five-field cron expression in the Pi's local time (`minute hour day-of-month month day-of-week`, Sunday is 0 or 7):
|
|
164
|
+
|
|
165
|
+
| `cron` | Runs |
|
|
166
|
+
| :--- | :--- |
|
|
167
|
+
| `0 8 1 * *` | 08:00 on the 1st of every month |
|
|
168
|
+
| `30 7 * * 1-5` | 07:30 Monday to Friday |
|
|
169
|
+
| `*/15 * * * *` | Every 15 minutes |
|
|
170
|
+
| `0 9 1,15 * *` | 09:00 on the 1st and 15th |
|
|
171
|
+
|
|
172
|
+
An invalid expression stops the daemon at startup with the field that is wrong. Check the Pi's time zone with `timedatectl` (set it with `sudo timedatectl set-timezone Asia/Bangkok`).
|
|
173
|
+
|
|
174
|
+
| Task | Command |
|
|
175
|
+
| :--- | :--- |
|
|
176
|
+
| Apply changes to `worker.env` or `triggers.json` | `sudo systemctl restart kinenix-worker` |
|
|
177
|
+
| Status | `systemctl status kinenix-worker` |
|
|
178
|
+
| Follow logs | `journalctl -u kinenix-worker -f` |
|
|
179
|
+
| Stop until next boot | `sudo systemctl stop kinenix-worker` |
|
|
180
|
+
| Remove the service | `./kinenix-worker/scripts/install_service.sh --uninstall` |
|
|
181
|
+
|
|
182
|
+
Jobs sent from the Hub are not supported yet; each worker runs the triggers in its own `triggers.json`.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 4. CLI Reference
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
usage: kinenix-worker [-h] [--version] {info,ping,run,watch,schedule,daemon} ...
|
|
190
|
+
|
|
191
|
+
positional arguments:
|
|
192
|
+
info Display worker machine hardware, architecture, and runtime stats
|
|
193
|
+
ping Check the connection and API key to the Hub (KINENIX_HUB_URL)
|
|
194
|
+
run Execute a flow or project bundle on this worker
|
|
195
|
+
watch Watch a directory and automatically trigger a flow when new files appear
|
|
196
|
+
schedule Execute a flow on an interval (--interval) or a cron expression (--cron "0 8 1 * *")
|
|
197
|
+
daemon Run multi-trigger daemon using a configuration file
|
|
198
|
+
|
|
199
|
+
options:
|
|
200
|
+
--version, -v
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### `kinenix-worker run` Options
|
|
204
|
+
- `flow_path`: Path to `flow.json` or project bundle directory.
|
|
205
|
+
- `--sandbox`: Execute inside an isolated temporary sandbox (`~/.kinenix/workspaces/<job_id>`).
|
|
206
|
+
- `--vars`: JSON string of variables to override.
|
|
207
|
+
- `--log-dir`: Also write a local JSON execution log to this directory. Telemetry to the Hub does not depend on it.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from . import __version__, display
|
|
9
|
+
from .hub_client import HeartbeatThread, HubClient
|
|
10
|
+
from .runner import WorkerRunner
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
prog="kinenix-worker",
|
|
16
|
+
description="Kinenix Worker: Unattended Robot Daemon and Edge Execution Engine"
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--version", "-v",
|
|
20
|
+
action="version",
|
|
21
|
+
version=f"kinenix-worker {__version__}"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
25
|
+
|
|
26
|
+
# Command: info
|
|
27
|
+
subparsers.add_parser("info", help="Display worker machine hardware, architecture, and runtime stats")
|
|
28
|
+
|
|
29
|
+
# Command: ping
|
|
30
|
+
subparsers.add_parser("ping", help="Check the connection and API key to the Hub (KINENIX_HUB_URL)")
|
|
31
|
+
|
|
32
|
+
# Command: run
|
|
33
|
+
run_parser = subparsers.add_parser("run", help="Execute a flow or project bundle on this worker")
|
|
34
|
+
run_parser.add_argument("flow_path", help="Path to flow.json or project bundle directory")
|
|
35
|
+
run_parser.add_argument("--sandbox", action="store_true", help="Execute inside an isolated temporary sandbox workspace")
|
|
36
|
+
run_parser.add_argument("--vars", type=str, help="JSON string of variables to inject (e.g. '{\"env\":\"prod\"}')")
|
|
37
|
+
run_parser.add_argument("--log-dir", type=str, help="Custom directory path to store execution logs")
|
|
38
|
+
|
|
39
|
+
# Command: watch
|
|
40
|
+
watch_parser = subparsers.add_parser("watch", help="Watch a directory and automatically trigger a flow when new files appear")
|
|
41
|
+
watch_parser.add_argument("directory", help="Directory path to watch")
|
|
42
|
+
watch_parser.add_argument("--flow", required=True, help="Path to flow.json or bundle to execute")
|
|
43
|
+
watch_parser.add_argument("--pattern", default="*.*", help="File matching pattern (e.g. '*.xlsx', '*.csv')")
|
|
44
|
+
watch_parser.add_argument("--interval", type=float, default=2.0, help="Polling interval in seconds")
|
|
45
|
+
watch_parser.add_argument("--sandbox", action="store_true", help="Execute in isolated sandbox workspace")
|
|
46
|
+
watch_parser.add_argument("--vars", type=str, help="JSON string of variables to inject")
|
|
47
|
+
|
|
48
|
+
# Command: schedule
|
|
49
|
+
sched_parser = subparsers.add_parser("schedule", help="Execute a flow on a scheduled time interval")
|
|
50
|
+
sched_parser.add_argument("--flow", required=True, help="Path to flow.json or bundle to execute")
|
|
51
|
+
sched_parser.add_argument("--interval", type=float, default=60.0, help="Interval in seconds between runs")
|
|
52
|
+
sched_parser.add_argument("--cron", type=str, default=None,
|
|
53
|
+
help="Five-field cron expression in local time, e.g. '0 8 1 * *' (overrides --interval)")
|
|
54
|
+
sched_parser.add_argument("--sandbox", action="store_true", help="Execute in isolated sandbox workspace")
|
|
55
|
+
sched_parser.add_argument("--vars", type=str, help="JSON string of variables to inject")
|
|
56
|
+
|
|
57
|
+
# Command: daemon
|
|
58
|
+
daemon_parser = subparsers.add_parser("daemon", help="Run multi-trigger daemon using a configuration file")
|
|
59
|
+
daemon_parser.add_argument("--config", default="triggers.json", help="Path to triggers.json configuration file")
|
|
60
|
+
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
|
|
63
|
+
if not args.command:
|
|
64
|
+
parser.print_help()
|
|
65
|
+
sys.exit(0)
|
|
66
|
+
|
|
67
|
+
console = Console()
|
|
68
|
+
client = HubClient()
|
|
69
|
+
runner = WorkerRunner(client=client)
|
|
70
|
+
|
|
71
|
+
if args.command == "info":
|
|
72
|
+
display.print_info(console, runner.get_system_info(), client)
|
|
73
|
+
sys.exit(0)
|
|
74
|
+
|
|
75
|
+
if args.command == "ping":
|
|
76
|
+
with console.status(f"Contacting {client.url or 'Hub'}..."):
|
|
77
|
+
result = client.ping()
|
|
78
|
+
display.print_ping(console, client, result)
|
|
79
|
+
sys.exit(0 if result["authorized"] else 1)
|
|
80
|
+
|
|
81
|
+
def start_heartbeats() -> None:
|
|
82
|
+
if client.enabled:
|
|
83
|
+
HeartbeatThread(client).start()
|
|
84
|
+
console.print(f"[dim]Sending heartbeats to {client.url} as '{client.worker_id}'[/]")
|
|
85
|
+
|
|
86
|
+
if args.command == "run":
|
|
87
|
+
extra_vars = {}
|
|
88
|
+
if args.vars:
|
|
89
|
+
try:
|
|
90
|
+
extra_vars = json.loads(args.vars)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"Error parsing --vars JSON: {e}", file=sys.stderr)
|
|
93
|
+
sys.exit(1)
|
|
94
|
+
|
|
95
|
+
# Keep heartbeats going during long flows so the worker is not shown as offline mid-run
|
|
96
|
+
start_heartbeats()
|
|
97
|
+
try:
|
|
98
|
+
res = runner.execute_flow(
|
|
99
|
+
flow_path_or_alias=args.flow_path,
|
|
100
|
+
extra_vars=extra_vars,
|
|
101
|
+
use_sandbox=args.sandbox,
|
|
102
|
+
log_dir=args.log_dir
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
display.print_run_result(console, res)
|
|
106
|
+
sys.exit(1 if res["has_error"] else 0)
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"Worker execution failed with error: {e}", file=sys.stderr)
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
|
|
112
|
+
if args.command == "watch":
|
|
113
|
+
from .triggers.watcher import FileWatcherTrigger
|
|
114
|
+
|
|
115
|
+
extra_vars = {}
|
|
116
|
+
if args.vars:
|
|
117
|
+
try:
|
|
118
|
+
extra_vars = json.loads(args.vars)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
print(f"Error parsing --vars JSON: {e}", file=sys.stderr)
|
|
121
|
+
sys.exit(1)
|
|
122
|
+
|
|
123
|
+
watcher = FileWatcherTrigger(
|
|
124
|
+
watch_dir=args.directory,
|
|
125
|
+
flow_path=args.flow,
|
|
126
|
+
pattern=args.pattern,
|
|
127
|
+
poll_interval=args.interval,
|
|
128
|
+
use_sandbox=args.sandbox,
|
|
129
|
+
extra_vars=extra_vars,
|
|
130
|
+
runner=runner
|
|
131
|
+
)
|
|
132
|
+
print(f"Watching directory '{args.directory}' for pattern '{args.pattern}'...")
|
|
133
|
+
print(f"Target flow: {args.flow}")
|
|
134
|
+
print("Press Ctrl+C to stop.")
|
|
135
|
+
start_heartbeats()
|
|
136
|
+
watcher.run_loop()
|
|
137
|
+
sys.exit(0)
|
|
138
|
+
|
|
139
|
+
if args.command == "schedule":
|
|
140
|
+
from .triggers.scheduler import CronSchedulerTrigger
|
|
141
|
+
|
|
142
|
+
extra_vars = {}
|
|
143
|
+
if args.vars:
|
|
144
|
+
try:
|
|
145
|
+
extra_vars = json.loads(args.vars)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
print(f"Error parsing --vars JSON: {e}", file=sys.stderr)
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
scheduler = CronSchedulerTrigger(
|
|
152
|
+
flow_path=args.flow,
|
|
153
|
+
interval_seconds=args.interval,
|
|
154
|
+
use_sandbox=args.sandbox,
|
|
155
|
+
extra_vars=extra_vars,
|
|
156
|
+
runner=runner,
|
|
157
|
+
cron=args.cron
|
|
158
|
+
)
|
|
159
|
+
except ValueError as e:
|
|
160
|
+
print(f"Invalid --cron: {e}", file=sys.stderr)
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
print(f"Scheduled flow '{args.flow}' to run {scheduler.describe()}...")
|
|
163
|
+
print("Press Ctrl+C to stop.")
|
|
164
|
+
start_heartbeats()
|
|
165
|
+
scheduler.run_loop()
|
|
166
|
+
sys.exit(0)
|
|
167
|
+
|
|
168
|
+
if args.command == "daemon":
|
|
169
|
+
from .triggers.manager import TriggerManager
|
|
170
|
+
|
|
171
|
+
manager = TriggerManager(runner=runner)
|
|
172
|
+
try:
|
|
173
|
+
manager.load_from_config(args.config)
|
|
174
|
+
print(f"Loaded triggers from '{args.config}'. Running daemon...")
|
|
175
|
+
print("Press Ctrl+C to stop.")
|
|
176
|
+
start_heartbeats()
|
|
177
|
+
manager.start_all(blocking=True)
|
|
178
|
+
sys.exit(0)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
print(f"Failed to start trigger daemon: {e}", file=sys.stderr)
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
main()
|
|
186
|
+
|