runon 0.1.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.
runon-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ahmed Hashim
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.
runon-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,269 @@
1
+ Metadata-Version: 2.4
2
+ Name: runon
3
+ Version: 0.1.0
4
+ Summary: Keep your operational procedures as plain shell scripts, and run any of them identically on your laptop, one server, or a named group of servers.
5
+ Author: Ahmed Hashim
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ahmed-hashim-pro/runon
8
+ Project-URL: Repository, https://github.com/ahmed-hashim-pro/runon
9
+ Keywords: cli,ssh,ops,runbook,automation,fleet,shell
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: System :: Systems Administration
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest==9.1.1; extra == "dev"
23
+ Requires-Dist: ruff==0.16.6; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # runon
27
+
28
+ [![CI](https://github.com/ahmed-hashim-pro/runon/actions/workflows/ci.yml/badge.svg)](https://github.com/ahmed-hashim-pro/runon/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
29
+
30
+ Keep your operational procedures as plain shell scripts, and run any of them
31
+ identically on your laptop, on one server, or across a named group of servers.
32
+
33
+ Most teams end up with a `scripts/` folder nobody trusts and a wiki page that
34
+ went stale months ago. The alternatives are heavy: Ansible wants YAML, modules
35
+ and a mental model, which is a lot to adopt when the thing you actually have is
36
+ six shell scripts that work.
37
+
38
+ `runon` keeps the shell scripts. It adds the part that is genuinely annoying
39
+ to write yourself — getting a script and its helpers onto twenty machines,
40
+ running them, and telling you which ones failed.
41
+
42
+ ```
43
+ $ runon group --group production copy-run-program --program disk-report
44
+ web-1 ok
45
+ web-2 ok
46
+ db-1 FAILED (1)
47
+ [db-1] highest usage: 94% on /var
48
+ [db-1] OVER THRESHOLD
49
+
50
+ 2/3 ok
51
+ ```
52
+
53
+ Exit code is non-zero, because a rollout that worked on two of three machines
54
+ has not worked.
55
+
56
+ ## Quickstart
57
+
58
+ Python 3.11+. No runtime dependencies.
59
+
60
+ ```bash
61
+ pipx install git+https://github.com/ahmed-hashim-pro/runon.git
62
+ # or, into a virtualenv you manage:
63
+ # pip install git+https://github.com/ahmed-hashim-pro/runon.git
64
+
65
+ mkdir my-ops && cd my-ops
66
+ runon init # scaffolds programs/, functions/, layouts/, inventory.toml
67
+ runon list programs
68
+ runon local run-program --program hello-world --verbose
69
+ ```
70
+
71
+ > Not on PyPI yet, so install from the repository for now. Nothing else is
72
+ > needed — `runon` has no runtime dependencies, and the only external programs
73
+ > it uses are the `ssh` and `scp` you already have.
74
+
75
+ <details>
76
+ <summary>Releasing (for maintainers)</summary>
77
+
78
+ Publishing uses [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/):
79
+ GitHub Actions mints a short-lived OIDC token, so there is no API token stored
80
+ anywhere to leak.
81
+
82
+ ```bash
83
+ # rehearse against TestPyPI first
84
+ gh workflow run release.yml -f target=testpypi
85
+
86
+ # then release for real
87
+ git tag v0.1.0 && git push origin v0.1.0
88
+ ```
89
+
90
+ The workflow runs the suite, builds an sdist and a wheel, checks the metadata,
91
+ and refuses to publish if the tag does not match the version in `pyproject.toml`.
92
+
93
+ </details>
94
+
95
+ That last command works immediately, with no servers and no configuration.
96
+
97
+ ## The idea
98
+
99
+ A **program** is a directory with a `main.sh`:
100
+
101
+ ```
102
+ programs/
103
+ disk-report/
104
+ main.sh <- entry point
105
+ functions/
106
+ say.sh <- shared helpers, sourced by programs
107
+ layouts/
108
+ split.sh <- terminal layouts for the local machine
109
+ inventory.toml <- your machines
110
+ ```
111
+
112
+ Adding a capability means adding a directory. **The tool never changes.** That
113
+ is the whole design: `runon` knows how to *reach* machines, shell knows what
114
+ to *do* on them, and neither has to learn the other's job.
115
+
116
+ Programs get their context from the environment, so they stay runnable by hand:
117
+
118
+ | Variable | Is |
119
+ | --- | --- |
120
+ | `RUNON_HOST` | the host's name from the inventory |
121
+ | `RUNON_ADDRESS` | what ssh was given |
122
+ | `RUNON_PROGRAM` | the program's own name |
123
+ | `RUNON_FUNCTIONS` | where the helpers are — locally, your workspace; remotely, the copied cache |
124
+ | `RUNON_VAR_*` | anything you put in that host's `vars` |
125
+
126
+ Export those and `./main.sh` behaves exactly as `runon` would run it. That
127
+ matters at 3am.
128
+
129
+ ## Three scopes, the same verbs
130
+
131
+ Where the work happens and what the work is are separate questions, so they are
132
+ separate parts of the command line:
133
+
134
+ ```bash
135
+ runon local run-program --program disk-report
136
+ runon host --host web-1 run-program --program disk-report
137
+ runon group --group production run-program --program disk-report -j 8
138
+ ```
139
+
140
+ The remote scopes share four verbs:
141
+
142
+ | Verb | Does |
143
+ | --- | --- |
144
+ | `copy` | copy a local file or directory (`--local-dir`, `--remote-dir`) |
145
+ | `copy-program` | copy a program **and the functions library** to the target(s) |
146
+ | `run-program` | run an already-copied program |
147
+ | `copy-run-program` | both, in one step |
148
+
149
+ `copy-program` ships the functions library alongside the program deliberately: a
150
+ program that sources a helper is broken without it, and the target is the worst
151
+ place to discover that.
152
+
153
+ Omit `--program` and you get a picker. Add `--dry-run` to see which hosts would
154
+ be touched without touching them.
155
+
156
+ ## Inventory
157
+
158
+ One file, so you can read the whole thing in one screen and diff it in review:
159
+
160
+ ```toml
161
+ [hosts.web-1]
162
+ address = "web-1.example.com"
163
+ user = "deploy"
164
+ vars = { role = "web" }
165
+
166
+ [hosts.db-1]
167
+ address = "10.0.0.9"
168
+ port = 2222
169
+
170
+ [groups.production]
171
+ hosts = ["web-1", "db-1"]
172
+ ```
173
+
174
+ `address` is handed to ssh untouched, so a `Host` alias from your `~/.ssh/config`
175
+ works here. And `--host` falls back to treating an unknown name as an address,
176
+ so `--host root@10.0.0.4` needs no inventory entry at all.
177
+
178
+ A group naming a host that does not exist fails when the inventory loads —
179
+ before a rollout has half-finished on the hosts it could resolve.
180
+
181
+ ## It uses your ssh, on purpose
182
+
183
+ `runon` shells out to the system `ssh` and `scp`. It does **not** embed an SSH
184
+ library.
185
+
186
+ That means your `~/.ssh/config`, your agent, your keys, your `ProxyJump` and
187
+ your `known_hosts` all work exactly as they already do, and `runon` never has
188
+ to grow its own half-version of any of it. Connections run with `BatchMode=yes`,
189
+ so a missing key fails fast instead of hanging on a password prompt — which
190
+ across a group would otherwise mean twenty stuck connections.
191
+
192
+ One thing worth knowing: OpenSSH 9 moved `scp` onto the SFTP subsystem, so a
193
+ target with SFTP disabled fails a copy with an error that does not say so.
194
+ `runon` detects that and tells you the fix.
195
+
196
+ ## Testing your programs without servers
197
+
198
+ Everything that touches another machine goes through one `Transport` interface,
199
+ and the fake one is **public API** rather than a test fixture — the hard part of
200
+ adopting a tool like this is proving your programs do the right thing *before*
201
+ you point them at production:
202
+
203
+ ```python
204
+ from runon import FakeTransport, Host, Workspace
205
+ from runon import runner
206
+
207
+ fake = FakeTransport()
208
+ runner.run_program(fake, Host("web-1", "web-1.example.com"), workspace, program)
209
+
210
+ fake.calls # [("web-1", "cd ~/.runon/programs/... && ./main.sh")]
211
+ fake.copies # what would have been shipped
212
+ ```
213
+
214
+ ## Writing programs
215
+
216
+ Conventions that keep this pleasant, learned the hard way:
217
+
218
+ - **One job per function file.** `clone.sh` and `build.sh`, not `do_everything.sh`.
219
+ - **Functions do not call functions.** A call stack you have to unpick over ssh
220
+ at 3am is a call stack too deep. Keep the depth at one.
221
+ - **The first comment line is the description.** `runon list programs` shows
222
+ it, so it cannot drift out of date the way a separate metadata file would.
223
+ - **Take arguments, don't hardcode.** Arguments after the program name are
224
+ passed through, quoted: `run-program --program disk-report 80`.
225
+
226
+ ## Commands
227
+
228
+ ```
229
+ runon init scaffold a workspace here
230
+ runon new-program <name> create one from the template
231
+ runon list programs|hosts|groups|layouts
232
+
233
+ runon local run-program [--program P] [args...]
234
+ runon local run-layout [--layout L]
235
+
236
+ runon host --host H <verb> [--program P] [args...]
237
+ runon group --group G <verb> [--program P] [-j N] [args...]
238
+ ```
239
+
240
+ ## What this does not do
241
+
242
+ - **No rollback, no idempotency, no desired-state model.** It runs your script.
243
+ If you need convergence, you need Ansible or Chef, and you should use them.
244
+ - **No secrets management.** Put credentials in your own vault and have the
245
+ program fetch them; `runon` never asks for or stores one.
246
+ - **No inventory discovery.** No cloud APIs, no dynamic inventory — you write
247
+ the file.
248
+ - **No output streaming.** Results arrive when a host finishes, not as it goes.
249
+ For a long program, watch it on one host first.
250
+ - **Groups run over ssh only.** There is no agent to install, and no plan to add
251
+ one.
252
+
253
+ ## Tests
254
+
255
+ 60 tests. No servers, no SSH keys, no network.
256
+
257
+ ```bash
258
+ pip install -e ".[dev]"
259
+ pytest
260
+ ```
261
+
262
+ CI runs them on Linux and macOS across Python 3.11–3.13, then executes the
263
+ quickstart above from an empty directory — so `init` producing something that
264
+ actually runs is checked on every commit, rather than being discovered by the
265
+ first user.
266
+
267
+ ## License
268
+
269
+ MIT — see [LICENSE](LICENSE).
runon-0.1.0/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # runon
2
+
3
+ [![CI](https://github.com/ahmed-hashim-pro/runon/actions/workflows/ci.yml/badge.svg)](https://github.com/ahmed-hashim-pro/runon/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
+
5
+ Keep your operational procedures as plain shell scripts, and run any of them
6
+ identically on your laptop, on one server, or across a named group of servers.
7
+
8
+ Most teams end up with a `scripts/` folder nobody trusts and a wiki page that
9
+ went stale months ago. The alternatives are heavy: Ansible wants YAML, modules
10
+ and a mental model, which is a lot to adopt when the thing you actually have is
11
+ six shell scripts that work.
12
+
13
+ `runon` keeps the shell scripts. It adds the part that is genuinely annoying
14
+ to write yourself — getting a script and its helpers onto twenty machines,
15
+ running them, and telling you which ones failed.
16
+
17
+ ```
18
+ $ runon group --group production copy-run-program --program disk-report
19
+ web-1 ok
20
+ web-2 ok
21
+ db-1 FAILED (1)
22
+ [db-1] highest usage: 94% on /var
23
+ [db-1] OVER THRESHOLD
24
+
25
+ 2/3 ok
26
+ ```
27
+
28
+ Exit code is non-zero, because a rollout that worked on two of three machines
29
+ has not worked.
30
+
31
+ ## Quickstart
32
+
33
+ Python 3.11+. No runtime dependencies.
34
+
35
+ ```bash
36
+ pipx install git+https://github.com/ahmed-hashim-pro/runon.git
37
+ # or, into a virtualenv you manage:
38
+ # pip install git+https://github.com/ahmed-hashim-pro/runon.git
39
+
40
+ mkdir my-ops && cd my-ops
41
+ runon init # scaffolds programs/, functions/, layouts/, inventory.toml
42
+ runon list programs
43
+ runon local run-program --program hello-world --verbose
44
+ ```
45
+
46
+ > Not on PyPI yet, so install from the repository for now. Nothing else is
47
+ > needed — `runon` has no runtime dependencies, and the only external programs
48
+ > it uses are the `ssh` and `scp` you already have.
49
+
50
+ <details>
51
+ <summary>Releasing (for maintainers)</summary>
52
+
53
+ Publishing uses [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/):
54
+ GitHub Actions mints a short-lived OIDC token, so there is no API token stored
55
+ anywhere to leak.
56
+
57
+ ```bash
58
+ # rehearse against TestPyPI first
59
+ gh workflow run release.yml -f target=testpypi
60
+
61
+ # then release for real
62
+ git tag v0.1.0 && git push origin v0.1.0
63
+ ```
64
+
65
+ The workflow runs the suite, builds an sdist and a wheel, checks the metadata,
66
+ and refuses to publish if the tag does not match the version in `pyproject.toml`.
67
+
68
+ </details>
69
+
70
+ That last command works immediately, with no servers and no configuration.
71
+
72
+ ## The idea
73
+
74
+ A **program** is a directory with a `main.sh`:
75
+
76
+ ```
77
+ programs/
78
+ disk-report/
79
+ main.sh <- entry point
80
+ functions/
81
+ say.sh <- shared helpers, sourced by programs
82
+ layouts/
83
+ split.sh <- terminal layouts for the local machine
84
+ inventory.toml <- your machines
85
+ ```
86
+
87
+ Adding a capability means adding a directory. **The tool never changes.** That
88
+ is the whole design: `runon` knows how to *reach* machines, shell knows what
89
+ to *do* on them, and neither has to learn the other's job.
90
+
91
+ Programs get their context from the environment, so they stay runnable by hand:
92
+
93
+ | Variable | Is |
94
+ | --- | --- |
95
+ | `RUNON_HOST` | the host's name from the inventory |
96
+ | `RUNON_ADDRESS` | what ssh was given |
97
+ | `RUNON_PROGRAM` | the program's own name |
98
+ | `RUNON_FUNCTIONS` | where the helpers are — locally, your workspace; remotely, the copied cache |
99
+ | `RUNON_VAR_*` | anything you put in that host's `vars` |
100
+
101
+ Export those and `./main.sh` behaves exactly as `runon` would run it. That
102
+ matters at 3am.
103
+
104
+ ## Three scopes, the same verbs
105
+
106
+ Where the work happens and what the work is are separate questions, so they are
107
+ separate parts of the command line:
108
+
109
+ ```bash
110
+ runon local run-program --program disk-report
111
+ runon host --host web-1 run-program --program disk-report
112
+ runon group --group production run-program --program disk-report -j 8
113
+ ```
114
+
115
+ The remote scopes share four verbs:
116
+
117
+ | Verb | Does |
118
+ | --- | --- |
119
+ | `copy` | copy a local file or directory (`--local-dir`, `--remote-dir`) |
120
+ | `copy-program` | copy a program **and the functions library** to the target(s) |
121
+ | `run-program` | run an already-copied program |
122
+ | `copy-run-program` | both, in one step |
123
+
124
+ `copy-program` ships the functions library alongside the program deliberately: a
125
+ program that sources a helper is broken without it, and the target is the worst
126
+ place to discover that.
127
+
128
+ Omit `--program` and you get a picker. Add `--dry-run` to see which hosts would
129
+ be touched without touching them.
130
+
131
+ ## Inventory
132
+
133
+ One file, so you can read the whole thing in one screen and diff it in review:
134
+
135
+ ```toml
136
+ [hosts.web-1]
137
+ address = "web-1.example.com"
138
+ user = "deploy"
139
+ vars = { role = "web" }
140
+
141
+ [hosts.db-1]
142
+ address = "10.0.0.9"
143
+ port = 2222
144
+
145
+ [groups.production]
146
+ hosts = ["web-1", "db-1"]
147
+ ```
148
+
149
+ `address` is handed to ssh untouched, so a `Host` alias from your `~/.ssh/config`
150
+ works here. And `--host` falls back to treating an unknown name as an address,
151
+ so `--host root@10.0.0.4` needs no inventory entry at all.
152
+
153
+ A group naming a host that does not exist fails when the inventory loads —
154
+ before a rollout has half-finished on the hosts it could resolve.
155
+
156
+ ## It uses your ssh, on purpose
157
+
158
+ `runon` shells out to the system `ssh` and `scp`. It does **not** embed an SSH
159
+ library.
160
+
161
+ That means your `~/.ssh/config`, your agent, your keys, your `ProxyJump` and
162
+ your `known_hosts` all work exactly as they already do, and `runon` never has
163
+ to grow its own half-version of any of it. Connections run with `BatchMode=yes`,
164
+ so a missing key fails fast instead of hanging on a password prompt — which
165
+ across a group would otherwise mean twenty stuck connections.
166
+
167
+ One thing worth knowing: OpenSSH 9 moved `scp` onto the SFTP subsystem, so a
168
+ target with SFTP disabled fails a copy with an error that does not say so.
169
+ `runon` detects that and tells you the fix.
170
+
171
+ ## Testing your programs without servers
172
+
173
+ Everything that touches another machine goes through one `Transport` interface,
174
+ and the fake one is **public API** rather than a test fixture — the hard part of
175
+ adopting a tool like this is proving your programs do the right thing *before*
176
+ you point them at production:
177
+
178
+ ```python
179
+ from runon import FakeTransport, Host, Workspace
180
+ from runon import runner
181
+
182
+ fake = FakeTransport()
183
+ runner.run_program(fake, Host("web-1", "web-1.example.com"), workspace, program)
184
+
185
+ fake.calls # [("web-1", "cd ~/.runon/programs/... && ./main.sh")]
186
+ fake.copies # what would have been shipped
187
+ ```
188
+
189
+ ## Writing programs
190
+
191
+ Conventions that keep this pleasant, learned the hard way:
192
+
193
+ - **One job per function file.** `clone.sh` and `build.sh`, not `do_everything.sh`.
194
+ - **Functions do not call functions.** A call stack you have to unpick over ssh
195
+ at 3am is a call stack too deep. Keep the depth at one.
196
+ - **The first comment line is the description.** `runon list programs` shows
197
+ it, so it cannot drift out of date the way a separate metadata file would.
198
+ - **Take arguments, don't hardcode.** Arguments after the program name are
199
+ passed through, quoted: `run-program --program disk-report 80`.
200
+
201
+ ## Commands
202
+
203
+ ```
204
+ runon init scaffold a workspace here
205
+ runon new-program <name> create one from the template
206
+ runon list programs|hosts|groups|layouts
207
+
208
+ runon local run-program [--program P] [args...]
209
+ runon local run-layout [--layout L]
210
+
211
+ runon host --host H <verb> [--program P] [args...]
212
+ runon group --group G <verb> [--program P] [-j N] [args...]
213
+ ```
214
+
215
+ ## What this does not do
216
+
217
+ - **No rollback, no idempotency, no desired-state model.** It runs your script.
218
+ If you need convergence, you need Ansible or Chef, and you should use them.
219
+ - **No secrets management.** Put credentials in your own vault and have the
220
+ program fetch them; `runon` never asks for or stores one.
221
+ - **No inventory discovery.** No cloud APIs, no dynamic inventory — you write
222
+ the file.
223
+ - **No output streaming.** Results arrive when a host finishes, not as it goes.
224
+ For a long program, watch it on one host first.
225
+ - **Groups run over ssh only.** There is no agent to install, and no plan to add
226
+ one.
227
+
228
+ ## Tests
229
+
230
+ 60 tests. No servers, no SSH keys, no network.
231
+
232
+ ```bash
233
+ pip install -e ".[dev]"
234
+ pytest
235
+ ```
236
+
237
+ CI runs them on Linux and macOS across Python 3.11–3.13, then executes the
238
+ quickstart above from an empty directory — so `init` producing something that
239
+ actually runs is checked on every commit, rather than being discovered by the
240
+ first user.
241
+
242
+ ## License
243
+
244
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "runon"
7
+ version = "0.1.0"
8
+ description = "Keep your operational procedures as plain shell scripts, and run any of them identically on your laptop, one server, or a named group of servers."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Ahmed Hashim" }]
13
+ keywords = ["cli", "ssh", "ops", "runbook", "automation", "fleet", "shell"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: System Administrators",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: System :: Systems Administration",
23
+ ]
24
+
25
+ # No runtime dependencies. Everything it needs is in the standard library, and
26
+ # transport is the system's own ssh/scp — which is the point: your ~/.ssh/config,
27
+ # your agent, your jump hosts and your keys already work.
28
+ dependencies = []
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest==9.1.1", "ruff==0.16.6"]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/ahmed-hashim-pro/runon"
35
+ Repository = "https://github.com/ahmed-hashim-pro/runon"
36
+
37
+ [project.scripts]
38
+ runon = "runon.cli:main"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests"]
45
+ addopts = "-q"
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+ target-version = "py311"
50
+
51
+ [tool.ruff.lint]
52
+ select = ["E", "F", "W", "I", "UP", "B", "SIM", "C4"]
runon-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ """runon — run shell programs locally, on one host, or across a group."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .errors import (
6
+ ConfigError,
7
+ ProgramInvalid,
8
+ RunonError,
9
+ UnknownGroup,
10
+ UnknownHost,
11
+ UnknownProgram,
12
+ )
13
+ from .inventory import Group, Host, Inventory
14
+ from .program import Program, Workspace
15
+ from .transport import FakeTransport, LocalTransport, Result, SSHTransport, Transport
16
+
17
+ __all__ = [
18
+ "ConfigError",
19
+ "FakeTransport",
20
+ "Group",
21
+ "Host",
22
+ "Inventory",
23
+ "LocalTransport",
24
+ "Program",
25
+ "ProgramInvalid",
26
+ "Result",
27
+ "RunonError",
28
+ "SSHTransport",
29
+ "Transport",
30
+ "UnknownGroup",
31
+ "UnknownHost",
32
+ "UnknownProgram",
33
+ "Workspace",
34
+ "__version__",
35
+ ]