shebangrun 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.
- shebangrun-0.1.0/PKG-INFO +299 -0
- shebangrun-0.1.0/README.md +276 -0
- shebangrun-0.1.0/pyproject.toml +34 -0
- shebangrun-0.1.0/setup.cfg +4 -0
- shebangrun-0.1.0/shebangrun/__init__.py +11 -0
- shebangrun-0.1.0/shebangrun/client.py +378 -0
- shebangrun-0.1.0/shebangrun.egg-info/PKG-INFO +299 -0
- shebangrun-0.1.0/shebangrun.egg-info/SOURCES.txt +9 -0
- shebangrun-0.1.0/shebangrun.egg-info/dependency_links.txt +1 -0
- shebangrun-0.1.0/shebangrun.egg-info/requires.txt +1 -0
- shebangrun-0.1.0/shebangrun.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shebangrun
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client library for shebang.run
|
|
5
|
+
Author-email: "shebang.run" <hello@shebang.run>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://shebang.run
|
|
8
|
+
Project-URL: Documentation, https://shebang.run/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/shebangrun/python-client
|
|
10
|
+
Keywords: shebang,scripts,automation,devops
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Requires-Python: >=3.7
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: requests>=2.25.0
|
|
23
|
+
|
|
24
|
+
# shebangrun Python Client
|
|
25
|
+
|
|
26
|
+
Python client library for [shebang.run](https://shebang.run) - a platform for hosting and sharing shell scripts with versioning, encryption, and signing.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install shebangrun
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or install from source:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd python
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Simple Script Fetching
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from shebangrun import run
|
|
47
|
+
|
|
48
|
+
# Fetch a script (returns content as string)
|
|
49
|
+
content = run(username="mpruitt", script="bashtest")
|
|
50
|
+
print(content)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Execute Python Scripts
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from shebangrun import run
|
|
57
|
+
|
|
58
|
+
# Fetch and execute with confirmation prompt
|
|
59
|
+
run(username="mpruitt", script="myscript", eval=True)
|
|
60
|
+
|
|
61
|
+
# Execute without confirmation (use with caution!)
|
|
62
|
+
run(username="mpruitt", script="myscript", eval=True, accept=True)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Working with Versions
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from shebangrun import run
|
|
69
|
+
|
|
70
|
+
# Get latest version
|
|
71
|
+
content = run(username="mpruitt", script="deploy", version="latest")
|
|
72
|
+
|
|
73
|
+
# Get specific version
|
|
74
|
+
content = run(username="mpruitt", script="deploy", version="v5")
|
|
75
|
+
|
|
76
|
+
# Get tagged version
|
|
77
|
+
content = run(username="mpruitt", script="deploy", version="dev")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Private Scripts
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from shebangrun import run
|
|
84
|
+
|
|
85
|
+
# Access private script with share token
|
|
86
|
+
content = run(
|
|
87
|
+
username="mpruitt",
|
|
88
|
+
script="private-script",
|
|
89
|
+
token="your-share-token-here"
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Full API Client
|
|
94
|
+
|
|
95
|
+
For more advanced usage, use the `ShebangClient` class:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from shebangrun import ShebangClient
|
|
99
|
+
|
|
100
|
+
# Initialize client
|
|
101
|
+
client = ShebangClient(url="shebang.run")
|
|
102
|
+
|
|
103
|
+
# Login
|
|
104
|
+
client.login(username="myuser", password="mypassword")
|
|
105
|
+
|
|
106
|
+
# Create a script
|
|
107
|
+
client.create_script(
|
|
108
|
+
name="hello",
|
|
109
|
+
content="#!/bin/bash\necho 'Hello World'",
|
|
110
|
+
description="My first script",
|
|
111
|
+
visibility="public"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# List your scripts
|
|
115
|
+
scripts = client.list_scripts()
|
|
116
|
+
for script in scripts:
|
|
117
|
+
print(f"{script['name']} - v{script['version']}")
|
|
118
|
+
|
|
119
|
+
# Update a script (creates new version)
|
|
120
|
+
client.update_script(
|
|
121
|
+
script_id=1,
|
|
122
|
+
content="#!/bin/bash\necho 'Hello World v2'",
|
|
123
|
+
tag="dev"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Generate share token for private script
|
|
127
|
+
token = client.generate_share_token(script_id=1)
|
|
128
|
+
print(f"Share URL: https://shebang.run/myuser/myscript?token={token}")
|
|
129
|
+
|
|
130
|
+
# Get script metadata
|
|
131
|
+
meta = client.get_metadata(username="mpruitt", script="bashtest")
|
|
132
|
+
print(f"Version: {meta['version']}, Size: {meta['size']} bytes")
|
|
133
|
+
|
|
134
|
+
# Verify signature
|
|
135
|
+
verification = client.verify_signature(username="mpruitt", script="bashtest")
|
|
136
|
+
print(f"Signed: {verification['signed']}")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Key Management
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from shebangrun import ShebangClient
|
|
143
|
+
|
|
144
|
+
client = ShebangClient(url="shebang.run")
|
|
145
|
+
client.login(username="myuser", password="mypassword")
|
|
146
|
+
|
|
147
|
+
# Generate a new keypair
|
|
148
|
+
key = client.generate_key(name="my-signing-key")
|
|
149
|
+
print(f"Public Key: {key['public_key']}")
|
|
150
|
+
print(f"Private Key: {key['private_key']}") # Save this securely!
|
|
151
|
+
|
|
152
|
+
# List keys
|
|
153
|
+
keys = client.list_keys()
|
|
154
|
+
for key in keys:
|
|
155
|
+
print(f"{key['name']} - Created: {key['created_at']}")
|
|
156
|
+
|
|
157
|
+
# Import existing public key
|
|
158
|
+
client.import_key(
|
|
159
|
+
name="imported-key",
|
|
160
|
+
public_key="-----BEGIN PUBLIC KEY-----\n..."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Delete a key
|
|
164
|
+
client.delete_key(key_id=1)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Account Management
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from shebangrun import ShebangClient
|
|
171
|
+
|
|
172
|
+
client = ShebangClient(url="shebang.run")
|
|
173
|
+
client.login(username="myuser", password="mypassword")
|
|
174
|
+
|
|
175
|
+
# Change password
|
|
176
|
+
client.change_password(
|
|
177
|
+
current_password="oldpass",
|
|
178
|
+
new_password="newpass"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Export all data (GDPR)
|
|
182
|
+
data = client.export_data()
|
|
183
|
+
print(f"Exported {len(data['scripts'])} scripts")
|
|
184
|
+
|
|
185
|
+
# Delete account (permanent!)
|
|
186
|
+
client.delete_account()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## API Reference
|
|
190
|
+
|
|
191
|
+
### `run()` Function
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
run(username, script, key=None, eval=False, accept=False,
|
|
195
|
+
url="shebang.run", version=None, token=None)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Parameters:**
|
|
199
|
+
- `username` (str, required): Script owner's username
|
|
200
|
+
- `script` (str, required): Script name
|
|
201
|
+
- `key` (str, optional): Private key for decryption (not yet implemented)
|
|
202
|
+
- `eval` (bool, optional): Execute the script in Python (default: False)
|
|
203
|
+
- `accept` (bool, optional): Skip confirmation when eval=True (default: False)
|
|
204
|
+
- `url` (str, optional): Base URL (default: "shebang.run")
|
|
205
|
+
- `version` (str, optional): Version tag (e.g., "latest", "v1", "dev")
|
|
206
|
+
- `token` (str, optional): Share token for private scripts
|
|
207
|
+
|
|
208
|
+
**Returns:**
|
|
209
|
+
- String content if `eval=False`
|
|
210
|
+
- Execution result if `eval=True`
|
|
211
|
+
|
|
212
|
+
### `ShebangClient` Class
|
|
213
|
+
|
|
214
|
+
#### Authentication
|
|
215
|
+
- `register(username, email, password)` - Register new user
|
|
216
|
+
- `login(username, password)` - Login and get JWT token
|
|
217
|
+
|
|
218
|
+
#### Script Management
|
|
219
|
+
- `list_scripts()` - List user's scripts
|
|
220
|
+
- `get_script(username, script, version=None, token=None)` - Fetch script content
|
|
221
|
+
- `get_metadata(username, script)` - Get script metadata
|
|
222
|
+
- `verify_signature(username, script)` - Verify script signature
|
|
223
|
+
- `create_script(name, content, description="", visibility="private", keypair_id=None)` - Create script
|
|
224
|
+
- `update_script(script_id, content=None, description=None, visibility=None, tag=None, keypair_id=None)` - Update script
|
|
225
|
+
- `delete_script(script_id)` - Delete script
|
|
226
|
+
- `generate_share_token(script_id)` - Generate share token
|
|
227
|
+
- `revoke_share_token(script_id, token)` - Revoke share token
|
|
228
|
+
|
|
229
|
+
#### Key Management
|
|
230
|
+
- `list_keys()` - List keypairs
|
|
231
|
+
- `generate_key(name)` - Generate new keypair
|
|
232
|
+
- `import_key(name, public_key)` - Import public key
|
|
233
|
+
- `delete_key(key_id)` - Delete keypair
|
|
234
|
+
|
|
235
|
+
#### Account Management
|
|
236
|
+
- `change_password(current_password, new_password)` - Change password
|
|
237
|
+
- `export_data()` - Export all data (GDPR)
|
|
238
|
+
- `delete_account()` - Delete account
|
|
239
|
+
|
|
240
|
+
## Security Notes
|
|
241
|
+
|
|
242
|
+
⚠️ **Warning:** Using `eval=True` with `accept=True` will execute remote code without confirmation. Only use this with scripts you trust completely.
|
|
243
|
+
|
|
244
|
+
✅ **Best Practices:**
|
|
245
|
+
- Always review scripts before executing with `eval=True`
|
|
246
|
+
- Use `accept=False` (default) to see the script before execution
|
|
247
|
+
- Store private keys securely, never commit them to version control
|
|
248
|
+
- Use environment variables for tokens and credentials
|
|
249
|
+
- Verify script signatures when available
|
|
250
|
+
|
|
251
|
+
## Examples
|
|
252
|
+
|
|
253
|
+
### Automation Script
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
#!/usr/bin/env python3
|
|
257
|
+
from shebangrun import run
|
|
258
|
+
|
|
259
|
+
# Fetch deployment script and execute with confirmation
|
|
260
|
+
run(
|
|
261
|
+
username="devops",
|
|
262
|
+
script="deploy-prod",
|
|
263
|
+
version="latest",
|
|
264
|
+
eval=True,
|
|
265
|
+
accept=False # Always confirm production deployments!
|
|
266
|
+
)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### CI/CD Integration
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
import os
|
|
273
|
+
from shebangrun import ShebangClient
|
|
274
|
+
|
|
275
|
+
client = ShebangClient()
|
|
276
|
+
client.login(
|
|
277
|
+
username=os.environ["SHEBANG_USER"],
|
|
278
|
+
password=os.environ["SHEBANG_PASS"]
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Update deployment script
|
|
282
|
+
client.update_script(
|
|
283
|
+
script_id=int(os.environ["DEPLOY_SCRIPT_ID"]),
|
|
284
|
+
content=open("deploy.sh").read(),
|
|
285
|
+
tag="latest"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
print("Deployment script updated!")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
MIT
|
|
294
|
+
|
|
295
|
+
## Links
|
|
296
|
+
|
|
297
|
+
- Website: https://shebang.run
|
|
298
|
+
- Documentation: https://shebang.run/docs
|
|
299
|
+
- GitHub: https://github.com/shebangrun/python-client
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# shebangrun Python Client
|
|
2
|
+
|
|
3
|
+
Python client library for [shebang.run](https://shebang.run) - a platform for hosting and sharing shell scripts with versioning, encryption, and signing.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install shebangrun
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install from source:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd python
|
|
15
|
+
pip install -e .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Simple Script Fetching
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from shebangrun import run
|
|
24
|
+
|
|
25
|
+
# Fetch a script (returns content as string)
|
|
26
|
+
content = run(username="mpruitt", script="bashtest")
|
|
27
|
+
print(content)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Execute Python Scripts
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from shebangrun import run
|
|
34
|
+
|
|
35
|
+
# Fetch and execute with confirmation prompt
|
|
36
|
+
run(username="mpruitt", script="myscript", eval=True)
|
|
37
|
+
|
|
38
|
+
# Execute without confirmation (use with caution!)
|
|
39
|
+
run(username="mpruitt", script="myscript", eval=True, accept=True)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Working with Versions
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from shebangrun import run
|
|
46
|
+
|
|
47
|
+
# Get latest version
|
|
48
|
+
content = run(username="mpruitt", script="deploy", version="latest")
|
|
49
|
+
|
|
50
|
+
# Get specific version
|
|
51
|
+
content = run(username="mpruitt", script="deploy", version="v5")
|
|
52
|
+
|
|
53
|
+
# Get tagged version
|
|
54
|
+
content = run(username="mpruitt", script="deploy", version="dev")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Private Scripts
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from shebangrun import run
|
|
61
|
+
|
|
62
|
+
# Access private script with share token
|
|
63
|
+
content = run(
|
|
64
|
+
username="mpruitt",
|
|
65
|
+
script="private-script",
|
|
66
|
+
token="your-share-token-here"
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Full API Client
|
|
71
|
+
|
|
72
|
+
For more advanced usage, use the `ShebangClient` class:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from shebangrun import ShebangClient
|
|
76
|
+
|
|
77
|
+
# Initialize client
|
|
78
|
+
client = ShebangClient(url="shebang.run")
|
|
79
|
+
|
|
80
|
+
# Login
|
|
81
|
+
client.login(username="myuser", password="mypassword")
|
|
82
|
+
|
|
83
|
+
# Create a script
|
|
84
|
+
client.create_script(
|
|
85
|
+
name="hello",
|
|
86
|
+
content="#!/bin/bash\necho 'Hello World'",
|
|
87
|
+
description="My first script",
|
|
88
|
+
visibility="public"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# List your scripts
|
|
92
|
+
scripts = client.list_scripts()
|
|
93
|
+
for script in scripts:
|
|
94
|
+
print(f"{script['name']} - v{script['version']}")
|
|
95
|
+
|
|
96
|
+
# Update a script (creates new version)
|
|
97
|
+
client.update_script(
|
|
98
|
+
script_id=1,
|
|
99
|
+
content="#!/bin/bash\necho 'Hello World v2'",
|
|
100
|
+
tag="dev"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Generate share token for private script
|
|
104
|
+
token = client.generate_share_token(script_id=1)
|
|
105
|
+
print(f"Share URL: https://shebang.run/myuser/myscript?token={token}")
|
|
106
|
+
|
|
107
|
+
# Get script metadata
|
|
108
|
+
meta = client.get_metadata(username="mpruitt", script="bashtest")
|
|
109
|
+
print(f"Version: {meta['version']}, Size: {meta['size']} bytes")
|
|
110
|
+
|
|
111
|
+
# Verify signature
|
|
112
|
+
verification = client.verify_signature(username="mpruitt", script="bashtest")
|
|
113
|
+
print(f"Signed: {verification['signed']}")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Key Management
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from shebangrun import ShebangClient
|
|
120
|
+
|
|
121
|
+
client = ShebangClient(url="shebang.run")
|
|
122
|
+
client.login(username="myuser", password="mypassword")
|
|
123
|
+
|
|
124
|
+
# Generate a new keypair
|
|
125
|
+
key = client.generate_key(name="my-signing-key")
|
|
126
|
+
print(f"Public Key: {key['public_key']}")
|
|
127
|
+
print(f"Private Key: {key['private_key']}") # Save this securely!
|
|
128
|
+
|
|
129
|
+
# List keys
|
|
130
|
+
keys = client.list_keys()
|
|
131
|
+
for key in keys:
|
|
132
|
+
print(f"{key['name']} - Created: {key['created_at']}")
|
|
133
|
+
|
|
134
|
+
# Import existing public key
|
|
135
|
+
client.import_key(
|
|
136
|
+
name="imported-key",
|
|
137
|
+
public_key="-----BEGIN PUBLIC KEY-----\n..."
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Delete a key
|
|
141
|
+
client.delete_key(key_id=1)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Account Management
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from shebangrun import ShebangClient
|
|
148
|
+
|
|
149
|
+
client = ShebangClient(url="shebang.run")
|
|
150
|
+
client.login(username="myuser", password="mypassword")
|
|
151
|
+
|
|
152
|
+
# Change password
|
|
153
|
+
client.change_password(
|
|
154
|
+
current_password="oldpass",
|
|
155
|
+
new_password="newpass"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Export all data (GDPR)
|
|
159
|
+
data = client.export_data()
|
|
160
|
+
print(f"Exported {len(data['scripts'])} scripts")
|
|
161
|
+
|
|
162
|
+
# Delete account (permanent!)
|
|
163
|
+
client.delete_account()
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## API Reference
|
|
167
|
+
|
|
168
|
+
### `run()` Function
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
run(username, script, key=None, eval=False, accept=False,
|
|
172
|
+
url="shebang.run", version=None, token=None)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Parameters:**
|
|
176
|
+
- `username` (str, required): Script owner's username
|
|
177
|
+
- `script` (str, required): Script name
|
|
178
|
+
- `key` (str, optional): Private key for decryption (not yet implemented)
|
|
179
|
+
- `eval` (bool, optional): Execute the script in Python (default: False)
|
|
180
|
+
- `accept` (bool, optional): Skip confirmation when eval=True (default: False)
|
|
181
|
+
- `url` (str, optional): Base URL (default: "shebang.run")
|
|
182
|
+
- `version` (str, optional): Version tag (e.g., "latest", "v1", "dev")
|
|
183
|
+
- `token` (str, optional): Share token for private scripts
|
|
184
|
+
|
|
185
|
+
**Returns:**
|
|
186
|
+
- String content if `eval=False`
|
|
187
|
+
- Execution result if `eval=True`
|
|
188
|
+
|
|
189
|
+
### `ShebangClient` Class
|
|
190
|
+
|
|
191
|
+
#### Authentication
|
|
192
|
+
- `register(username, email, password)` - Register new user
|
|
193
|
+
- `login(username, password)` - Login and get JWT token
|
|
194
|
+
|
|
195
|
+
#### Script Management
|
|
196
|
+
- `list_scripts()` - List user's scripts
|
|
197
|
+
- `get_script(username, script, version=None, token=None)` - Fetch script content
|
|
198
|
+
- `get_metadata(username, script)` - Get script metadata
|
|
199
|
+
- `verify_signature(username, script)` - Verify script signature
|
|
200
|
+
- `create_script(name, content, description="", visibility="private", keypair_id=None)` - Create script
|
|
201
|
+
- `update_script(script_id, content=None, description=None, visibility=None, tag=None, keypair_id=None)` - Update script
|
|
202
|
+
- `delete_script(script_id)` - Delete script
|
|
203
|
+
- `generate_share_token(script_id)` - Generate share token
|
|
204
|
+
- `revoke_share_token(script_id, token)` - Revoke share token
|
|
205
|
+
|
|
206
|
+
#### Key Management
|
|
207
|
+
- `list_keys()` - List keypairs
|
|
208
|
+
- `generate_key(name)` - Generate new keypair
|
|
209
|
+
- `import_key(name, public_key)` - Import public key
|
|
210
|
+
- `delete_key(key_id)` - Delete keypair
|
|
211
|
+
|
|
212
|
+
#### Account Management
|
|
213
|
+
- `change_password(current_password, new_password)` - Change password
|
|
214
|
+
- `export_data()` - Export all data (GDPR)
|
|
215
|
+
- `delete_account()` - Delete account
|
|
216
|
+
|
|
217
|
+
## Security Notes
|
|
218
|
+
|
|
219
|
+
⚠️ **Warning:** Using `eval=True` with `accept=True` will execute remote code without confirmation. Only use this with scripts you trust completely.
|
|
220
|
+
|
|
221
|
+
✅ **Best Practices:**
|
|
222
|
+
- Always review scripts before executing with `eval=True`
|
|
223
|
+
- Use `accept=False` (default) to see the script before execution
|
|
224
|
+
- Store private keys securely, never commit them to version control
|
|
225
|
+
- Use environment variables for tokens and credentials
|
|
226
|
+
- Verify script signatures when available
|
|
227
|
+
|
|
228
|
+
## Examples
|
|
229
|
+
|
|
230
|
+
### Automation Script
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
#!/usr/bin/env python3
|
|
234
|
+
from shebangrun import run
|
|
235
|
+
|
|
236
|
+
# Fetch deployment script and execute with confirmation
|
|
237
|
+
run(
|
|
238
|
+
username="devops",
|
|
239
|
+
script="deploy-prod",
|
|
240
|
+
version="latest",
|
|
241
|
+
eval=True,
|
|
242
|
+
accept=False # Always confirm production deployments!
|
|
243
|
+
)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### CI/CD Integration
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
import os
|
|
250
|
+
from shebangrun import ShebangClient
|
|
251
|
+
|
|
252
|
+
client = ShebangClient()
|
|
253
|
+
client.login(
|
|
254
|
+
username=os.environ["SHEBANG_USER"],
|
|
255
|
+
password=os.environ["SHEBANG_PASS"]
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Update deployment script
|
|
259
|
+
client.update_script(
|
|
260
|
+
script_id=int(os.environ["DEPLOY_SCRIPT_ID"]),
|
|
261
|
+
content=open("deploy.sh").read(),
|
|
262
|
+
tag="latest"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
print("Deployment script updated!")
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT
|
|
271
|
+
|
|
272
|
+
## Links
|
|
273
|
+
|
|
274
|
+
- Website: https://shebang.run
|
|
275
|
+
- Documentation: https://shebang.run/docs
|
|
276
|
+
- GitHub: https://github.com/shebangrun/python-client
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=45", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "shebangrun"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client library for shebang.run"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "shebang.run", email = "hello@shebang.run"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["shebang", "scripts", "automation", "devops"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.7",
|
|
22
|
+
"Programming Language :: Python :: 3.8",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"requests>=2.25.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://shebang.run"
|
|
33
|
+
Documentation = "https://shebang.run/docs"
|
|
34
|
+
Repository = "https://github.com/shebangrun/python-client"
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main client for interacting with shebang.run
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from typing import Optional, Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ShebangClient:
|
|
10
|
+
"""Client for interacting with shebang.run API"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, url: str = "shebang.run", token: Optional[str] = None):
|
|
13
|
+
"""
|
|
14
|
+
Initialize the client
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
url: Base URL (default: shebang.run)
|
|
18
|
+
token: JWT token for authenticated requests
|
|
19
|
+
"""
|
|
20
|
+
self.base_url = f"https://{url}"
|
|
21
|
+
self.token = token
|
|
22
|
+
self.session = requests.Session()
|
|
23
|
+
if token:
|
|
24
|
+
self.session.headers.update({"Authorization": f"Bearer {token}"})
|
|
25
|
+
|
|
26
|
+
def get_script(self, username: str, script: str, version: Optional[str] = None,
|
|
27
|
+
token: Optional[str] = None) -> tuple:
|
|
28
|
+
"""
|
|
29
|
+
Retrieve a script from shebang.run
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
username: Script owner's username
|
|
33
|
+
script: Script name
|
|
34
|
+
version: Optional version tag (@latest, @v1, @dev, etc.)
|
|
35
|
+
token: Optional share token for private scripts
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Tuple of (content, metadata) where metadata includes encryption info
|
|
39
|
+
"""
|
|
40
|
+
script_path = f"{script}@{version}" if version else script
|
|
41
|
+
url = f"{self.base_url}/{username}/{script_path}"
|
|
42
|
+
|
|
43
|
+
params = {}
|
|
44
|
+
if token:
|
|
45
|
+
params["token"] = token
|
|
46
|
+
|
|
47
|
+
response = self.session.get(url, params=params)
|
|
48
|
+
response.raise_for_status()
|
|
49
|
+
|
|
50
|
+
metadata = {
|
|
51
|
+
"encrypted": response.headers.get("X-Encrypted") == "true",
|
|
52
|
+
"version": response.headers.get("X-Script-Version"),
|
|
53
|
+
"checksum": response.headers.get("X-Script-Checksum"),
|
|
54
|
+
"key_id": response.headers.get("X-Encryption-KeyID"),
|
|
55
|
+
"wrapped_key": response.headers.get("X-Wrapped-Key")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return response.content if metadata["encrypted"] else response.text, metadata
|
|
59
|
+
|
|
60
|
+
def get_metadata(self, username: str, script: str) -> dict:
|
|
61
|
+
"""Get script metadata"""
|
|
62
|
+
url = f"{self.base_url}/{username}/{script}/meta"
|
|
63
|
+
response = self.session.get(url)
|
|
64
|
+
response.raise_for_status()
|
|
65
|
+
return response.json()
|
|
66
|
+
|
|
67
|
+
def verify_signature(self, username: str, script: str) -> dict:
|
|
68
|
+
"""Verify script signature"""
|
|
69
|
+
url = f"{self.base_url}/{username}/{script}/verify"
|
|
70
|
+
response = self.session.get(url)
|
|
71
|
+
response.raise_for_status()
|
|
72
|
+
return response.json()
|
|
73
|
+
|
|
74
|
+
# Authentication
|
|
75
|
+
def register(self, username: str, email: str, password: str) -> dict:
|
|
76
|
+
"""Register a new user"""
|
|
77
|
+
url = f"{self.base_url}/api/auth/register"
|
|
78
|
+
response = self.session.post(url, json={
|
|
79
|
+
"username": username,
|
|
80
|
+
"email": email,
|
|
81
|
+
"password": password
|
|
82
|
+
})
|
|
83
|
+
response.raise_for_status()
|
|
84
|
+
data = response.json()
|
|
85
|
+
if "token" in data:
|
|
86
|
+
self.token = data["token"]
|
|
87
|
+
self.session.headers.update({"Authorization": f"Bearer {self.token}"})
|
|
88
|
+
return data
|
|
89
|
+
|
|
90
|
+
def login(self, username: str, password: str) -> dict:
|
|
91
|
+
"""Login and get JWT token"""
|
|
92
|
+
url = f"{self.base_url}/api/auth/login"
|
|
93
|
+
response = self.session.post(url, json={
|
|
94
|
+
"username": username,
|
|
95
|
+
"password": password
|
|
96
|
+
})
|
|
97
|
+
response.raise_for_status()
|
|
98
|
+
data = response.json()
|
|
99
|
+
if "token" in data:
|
|
100
|
+
self.token = data["token"]
|
|
101
|
+
self.session.headers.update({"Authorization": f"Bearer {self.token}"})
|
|
102
|
+
return data
|
|
103
|
+
|
|
104
|
+
# Script Management
|
|
105
|
+
def list_scripts(self) -> list:
|
|
106
|
+
"""List user's scripts (requires authentication)"""
|
|
107
|
+
url = f"{self.base_url}/api/scripts"
|
|
108
|
+
response = self.session.get(url)
|
|
109
|
+
response.raise_for_status()
|
|
110
|
+
return response.json()
|
|
111
|
+
|
|
112
|
+
def create_script(self, name: str, content: str, description: str = "",
|
|
113
|
+
visibility: str = "private", keypair_id: Optional[int] = None) -> dict:
|
|
114
|
+
"""Create a new script"""
|
|
115
|
+
url = f"{self.base_url}/api/scripts"
|
|
116
|
+
data = {
|
|
117
|
+
"name": name,
|
|
118
|
+
"content": content,
|
|
119
|
+
"description": description,
|
|
120
|
+
"visibility": visibility
|
|
121
|
+
}
|
|
122
|
+
if keypair_id:
|
|
123
|
+
data["keypair_id"] = keypair_id
|
|
124
|
+
|
|
125
|
+
response = self.session.post(url, json=data)
|
|
126
|
+
response.raise_for_status()
|
|
127
|
+
return response.json()
|
|
128
|
+
|
|
129
|
+
def update_script(self, script_id: int, content: Optional[str] = None,
|
|
130
|
+
description: Optional[str] = None, visibility: Optional[str] = None,
|
|
131
|
+
tag: Optional[str] = None, keypair_id: Optional[int] = None) -> dict:
|
|
132
|
+
"""Update a script (creates new version if content changed)"""
|
|
133
|
+
url = f"{self.base_url}/api/scripts/{script_id}"
|
|
134
|
+
data = {}
|
|
135
|
+
if content:
|
|
136
|
+
data["content"] = content
|
|
137
|
+
if description:
|
|
138
|
+
data["description"] = description
|
|
139
|
+
if visibility:
|
|
140
|
+
data["visibility"] = visibility
|
|
141
|
+
if tag:
|
|
142
|
+
data["tag"] = tag
|
|
143
|
+
if keypair_id:
|
|
144
|
+
data["keypair_id"] = keypair_id
|
|
145
|
+
|
|
146
|
+
response = self.session.put(url, json=data)
|
|
147
|
+
response.raise_for_status()
|
|
148
|
+
return response.json() if response.text else {}
|
|
149
|
+
|
|
150
|
+
def delete_script(self, script_id: int):
|
|
151
|
+
"""Delete a script"""
|
|
152
|
+
url = f"{self.base_url}/api/scripts/{script_id}"
|
|
153
|
+
response = self.session.delete(url)
|
|
154
|
+
response.raise_for_status()
|
|
155
|
+
|
|
156
|
+
def generate_share_token(self, script_id: int) -> str:
|
|
157
|
+
"""Generate a share token for a private script"""
|
|
158
|
+
url = f"{self.base_url}/api/scripts/{script_id}/share"
|
|
159
|
+
response = self.session.post(url)
|
|
160
|
+
response.raise_for_status()
|
|
161
|
+
return response.json()["token"]
|
|
162
|
+
|
|
163
|
+
def revoke_share_token(self, script_id: int, token: str):
|
|
164
|
+
"""Revoke a share token"""
|
|
165
|
+
url = f"{self.base_url}/api/scripts/{script_id}/share/{token}"
|
|
166
|
+
response = self.session.delete(url)
|
|
167
|
+
response.raise_for_status()
|
|
168
|
+
|
|
169
|
+
# Key Management
|
|
170
|
+
def list_keys(self) -> list:
|
|
171
|
+
"""List user's keypairs"""
|
|
172
|
+
url = f"{self.base_url}/api/keys"
|
|
173
|
+
response = self.session.get(url)
|
|
174
|
+
response.raise_for_status()
|
|
175
|
+
return response.json()
|
|
176
|
+
|
|
177
|
+
def generate_key(self, name: str) -> dict:
|
|
178
|
+
"""Generate a new keypair (returns private key - save it!)"""
|
|
179
|
+
url = f"{self.base_url}/api/keys/generate"
|
|
180
|
+
response = self.session.post(url, json={"name": name})
|
|
181
|
+
response.raise_for_status()
|
|
182
|
+
return response.json()
|
|
183
|
+
|
|
184
|
+
def import_key(self, name: str, public_key: str) -> dict:
|
|
185
|
+
"""Import an existing public key"""
|
|
186
|
+
url = f"{self.base_url}/api/keys/import"
|
|
187
|
+
response = self.session.post(url, json={
|
|
188
|
+
"name": name,
|
|
189
|
+
"public_key": public_key
|
|
190
|
+
})
|
|
191
|
+
response.raise_for_status()
|
|
192
|
+
return response.json()
|
|
193
|
+
|
|
194
|
+
def delete_key(self, key_id: int):
|
|
195
|
+
"""Delete a keypair"""
|
|
196
|
+
url = f"{self.base_url}/api/keys/{key_id}"
|
|
197
|
+
response = self.session.delete(url)
|
|
198
|
+
response.raise_for_status()
|
|
199
|
+
|
|
200
|
+
# Account Management
|
|
201
|
+
def change_password(self, current_password: str, new_password: str):
|
|
202
|
+
"""Change account password"""
|
|
203
|
+
url = f"{self.base_url}/api/account/password"
|
|
204
|
+
response = self.session.put(url, json={
|
|
205
|
+
"current_password": current_password,
|
|
206
|
+
"new_password": new_password
|
|
207
|
+
})
|
|
208
|
+
response.raise_for_status()
|
|
209
|
+
|
|
210
|
+
def export_data(self) -> dict:
|
|
211
|
+
"""Export all user data (GDPR)"""
|
|
212
|
+
url = f"{self.base_url}/api/account/export"
|
|
213
|
+
response = self.session.get(url)
|
|
214
|
+
response.raise_for_status()
|
|
215
|
+
return response.json()
|
|
216
|
+
|
|
217
|
+
def delete_account(self):
|
|
218
|
+
"""Delete account permanently"""
|
|
219
|
+
url = f"{self.base_url}/api/account"
|
|
220
|
+
response = self.session.delete(url)
|
|
221
|
+
response.raise_for_status()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def run(username: str, script: str, key: Optional[str] = None,
|
|
225
|
+
eval: bool = False, accept: bool = False, url: str = "shebang.run",
|
|
226
|
+
version: Optional[str] = None, token: Optional[str] = None) -> Any:
|
|
227
|
+
"""
|
|
228
|
+
Convenience function to fetch and optionally execute a script
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
username: Script owner's username (required)
|
|
232
|
+
script: Script name (required)
|
|
233
|
+
key: Private key contents for decryption (optional)
|
|
234
|
+
eval: If True, evaluate the script in Python (default: False)
|
|
235
|
+
accept: If True, skip confirmation prompt when eval=True (default: False)
|
|
236
|
+
url: Base URL (default: shebang.run)
|
|
237
|
+
version: Version tag (optional, e.g., "latest", "v1", "dev")
|
|
238
|
+
token: Share token for private scripts (optional)
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Script content as string if eval=False, or result of eval() if eval=True
|
|
242
|
+
|
|
243
|
+
Examples:
|
|
244
|
+
# Just fetch the script
|
|
245
|
+
content = run(username="mpruitt", script="bashtest")
|
|
246
|
+
|
|
247
|
+
# Fetch encrypted script with private key
|
|
248
|
+
content = run(username="mpruitt", script="private", key="-----BEGIN PRIVATE KEY-----\\n...")
|
|
249
|
+
|
|
250
|
+
# Fetch and evaluate with confirmation
|
|
251
|
+
run(username="mpruitt", script="myscript", eval=True)
|
|
252
|
+
"""
|
|
253
|
+
client = ShebangClient(url=url)
|
|
254
|
+
|
|
255
|
+
# Fetch the script
|
|
256
|
+
content, metadata = client.get_script(username, script, version=version, token=token)
|
|
257
|
+
|
|
258
|
+
# If encrypted and key is provided, decrypt
|
|
259
|
+
if metadata.get("encrypted") and key:
|
|
260
|
+
try:
|
|
261
|
+
from cryptography.hazmat.primitives import serialization, hashes
|
|
262
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
263
|
+
from cryptography.hazmat.backends import default_backend
|
|
264
|
+
import nacl.bindings
|
|
265
|
+
|
|
266
|
+
# Parse private key
|
|
267
|
+
private_key = serialization.load_pem_private_key(
|
|
268
|
+
key.encode() if isinstance(key, str) else key,
|
|
269
|
+
password=None,
|
|
270
|
+
backend=default_backend()
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Get wrapped key from headers
|
|
274
|
+
wrapped_key_hex = metadata.get("wrapped_key")
|
|
275
|
+
if not wrapped_key_hex:
|
|
276
|
+
raise Exception("No wrapped key found in response")
|
|
277
|
+
|
|
278
|
+
# Unwrap symmetric key with RSA private key
|
|
279
|
+
wrapped_key = bytes.fromhex(wrapped_key_hex)
|
|
280
|
+
symmetric_key = private_key.decrypt(
|
|
281
|
+
wrapped_key,
|
|
282
|
+
padding.OAEP(
|
|
283
|
+
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
|
284
|
+
algorithm=hashes.SHA256(),
|
|
285
|
+
label=None
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Decrypt content with XChaCha20-Poly1305
|
|
290
|
+
nonce_size = 24
|
|
291
|
+
nonce = content[:nonce_size]
|
|
292
|
+
ciphertext = content[nonce_size:]
|
|
293
|
+
|
|
294
|
+
decrypted = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
295
|
+
ciphertext,
|
|
296
|
+
None, # no additional data
|
|
297
|
+
nonce,
|
|
298
|
+
symmetric_key
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
content = decrypted.decode('utf-8')
|
|
302
|
+
|
|
303
|
+
except ImportError:
|
|
304
|
+
raise ImportError("Decryption requires: pip install cryptography pynacl")
|
|
305
|
+
except Exception as e:
|
|
306
|
+
raise Exception(f"Decryption failed: {e}")
|
|
307
|
+
elif metadata.get("encrypted") and not key:
|
|
308
|
+
# Return encrypted bytes if no key provided
|
|
309
|
+
return content
|
|
310
|
+
|
|
311
|
+
# Convert bytes to string if needed
|
|
312
|
+
if isinstance(content, bytes):
|
|
313
|
+
content = content.decode('utf-8')
|
|
314
|
+
|
|
315
|
+
# If not evaluating, just return the content
|
|
316
|
+
if not eval:
|
|
317
|
+
return content
|
|
318
|
+
|
|
319
|
+
# If evaluating, show confirmation unless accept=True
|
|
320
|
+
if not accept:
|
|
321
|
+
print("=" * 60)
|
|
322
|
+
print(f"Script: {username}/{script}")
|
|
323
|
+
print("=" * 60)
|
|
324
|
+
print(content)
|
|
325
|
+
print("=" * 60)
|
|
326
|
+
response = input("Execute this script? (y/N): ").strip().lower()
|
|
327
|
+
if response != 'y':
|
|
328
|
+
print("Execution cancelled.")
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
# Execute the script
|
|
332
|
+
try:
|
|
333
|
+
import subprocess
|
|
334
|
+
import tempfile
|
|
335
|
+
import os
|
|
336
|
+
import sys
|
|
337
|
+
|
|
338
|
+
# Check for shebang
|
|
339
|
+
lines = content.split('\n')
|
|
340
|
+
if lines[0].startswith('#!'):
|
|
341
|
+
shebang = lines[0].lower()
|
|
342
|
+
|
|
343
|
+
# If shebang contains 'python', execute in current context with exec
|
|
344
|
+
if 'python' in shebang:
|
|
345
|
+
exec_globals = {}
|
|
346
|
+
exec(content, exec_globals)
|
|
347
|
+
return exec_globals
|
|
348
|
+
else:
|
|
349
|
+
# Execute with specified interpreter (bash, sh, etc.)
|
|
350
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.sh', delete=False) as f:
|
|
351
|
+
f.write(content)
|
|
352
|
+
temp_path = f.name
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
os.chmod(temp_path, 0o755)
|
|
356
|
+
result = subprocess.run(
|
|
357
|
+
[temp_path],
|
|
358
|
+
capture_output=True,
|
|
359
|
+
text=True,
|
|
360
|
+
shell=False
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
if result.stdout:
|
|
364
|
+
print(result.stdout, end='')
|
|
365
|
+
if result.stderr:
|
|
366
|
+
print(result.stderr, end='', file=sys.stderr)
|
|
367
|
+
|
|
368
|
+
return result.returncode
|
|
369
|
+
finally:
|
|
370
|
+
os.unlink(temp_path)
|
|
371
|
+
else:
|
|
372
|
+
# No shebang, execute as Python
|
|
373
|
+
exec_globals = {}
|
|
374
|
+
exec(content, exec_globals)
|
|
375
|
+
return exec_globals
|
|
376
|
+
except Exception as e:
|
|
377
|
+
print(f"Error executing script: {e}")
|
|
378
|
+
raise
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shebangrun
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client library for shebang.run
|
|
5
|
+
Author-email: "shebang.run" <hello@shebang.run>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://shebang.run
|
|
8
|
+
Project-URL: Documentation, https://shebang.run/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/shebangrun/python-client
|
|
10
|
+
Keywords: shebang,scripts,automation,devops
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Requires-Python: >=3.7
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: requests>=2.25.0
|
|
23
|
+
|
|
24
|
+
# shebangrun Python Client
|
|
25
|
+
|
|
26
|
+
Python client library for [shebang.run](https://shebang.run) - a platform for hosting and sharing shell scripts with versioning, encryption, and signing.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install shebangrun
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or install from source:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd python
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Simple Script Fetching
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from shebangrun import run
|
|
47
|
+
|
|
48
|
+
# Fetch a script (returns content as string)
|
|
49
|
+
content = run(username="mpruitt", script="bashtest")
|
|
50
|
+
print(content)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Execute Python Scripts
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from shebangrun import run
|
|
57
|
+
|
|
58
|
+
# Fetch and execute with confirmation prompt
|
|
59
|
+
run(username="mpruitt", script="myscript", eval=True)
|
|
60
|
+
|
|
61
|
+
# Execute without confirmation (use with caution!)
|
|
62
|
+
run(username="mpruitt", script="myscript", eval=True, accept=True)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Working with Versions
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from shebangrun import run
|
|
69
|
+
|
|
70
|
+
# Get latest version
|
|
71
|
+
content = run(username="mpruitt", script="deploy", version="latest")
|
|
72
|
+
|
|
73
|
+
# Get specific version
|
|
74
|
+
content = run(username="mpruitt", script="deploy", version="v5")
|
|
75
|
+
|
|
76
|
+
# Get tagged version
|
|
77
|
+
content = run(username="mpruitt", script="deploy", version="dev")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Private Scripts
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from shebangrun import run
|
|
84
|
+
|
|
85
|
+
# Access private script with share token
|
|
86
|
+
content = run(
|
|
87
|
+
username="mpruitt",
|
|
88
|
+
script="private-script",
|
|
89
|
+
token="your-share-token-here"
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Full API Client
|
|
94
|
+
|
|
95
|
+
For more advanced usage, use the `ShebangClient` class:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from shebangrun import ShebangClient
|
|
99
|
+
|
|
100
|
+
# Initialize client
|
|
101
|
+
client = ShebangClient(url="shebang.run")
|
|
102
|
+
|
|
103
|
+
# Login
|
|
104
|
+
client.login(username="myuser", password="mypassword")
|
|
105
|
+
|
|
106
|
+
# Create a script
|
|
107
|
+
client.create_script(
|
|
108
|
+
name="hello",
|
|
109
|
+
content="#!/bin/bash\necho 'Hello World'",
|
|
110
|
+
description="My first script",
|
|
111
|
+
visibility="public"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# List your scripts
|
|
115
|
+
scripts = client.list_scripts()
|
|
116
|
+
for script in scripts:
|
|
117
|
+
print(f"{script['name']} - v{script['version']}")
|
|
118
|
+
|
|
119
|
+
# Update a script (creates new version)
|
|
120
|
+
client.update_script(
|
|
121
|
+
script_id=1,
|
|
122
|
+
content="#!/bin/bash\necho 'Hello World v2'",
|
|
123
|
+
tag="dev"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Generate share token for private script
|
|
127
|
+
token = client.generate_share_token(script_id=1)
|
|
128
|
+
print(f"Share URL: https://shebang.run/myuser/myscript?token={token}")
|
|
129
|
+
|
|
130
|
+
# Get script metadata
|
|
131
|
+
meta = client.get_metadata(username="mpruitt", script="bashtest")
|
|
132
|
+
print(f"Version: {meta['version']}, Size: {meta['size']} bytes")
|
|
133
|
+
|
|
134
|
+
# Verify signature
|
|
135
|
+
verification = client.verify_signature(username="mpruitt", script="bashtest")
|
|
136
|
+
print(f"Signed: {verification['signed']}")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Key Management
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from shebangrun import ShebangClient
|
|
143
|
+
|
|
144
|
+
client = ShebangClient(url="shebang.run")
|
|
145
|
+
client.login(username="myuser", password="mypassword")
|
|
146
|
+
|
|
147
|
+
# Generate a new keypair
|
|
148
|
+
key = client.generate_key(name="my-signing-key")
|
|
149
|
+
print(f"Public Key: {key['public_key']}")
|
|
150
|
+
print(f"Private Key: {key['private_key']}") # Save this securely!
|
|
151
|
+
|
|
152
|
+
# List keys
|
|
153
|
+
keys = client.list_keys()
|
|
154
|
+
for key in keys:
|
|
155
|
+
print(f"{key['name']} - Created: {key['created_at']}")
|
|
156
|
+
|
|
157
|
+
# Import existing public key
|
|
158
|
+
client.import_key(
|
|
159
|
+
name="imported-key",
|
|
160
|
+
public_key="-----BEGIN PUBLIC KEY-----\n..."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Delete a key
|
|
164
|
+
client.delete_key(key_id=1)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Account Management
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from shebangrun import ShebangClient
|
|
171
|
+
|
|
172
|
+
client = ShebangClient(url="shebang.run")
|
|
173
|
+
client.login(username="myuser", password="mypassword")
|
|
174
|
+
|
|
175
|
+
# Change password
|
|
176
|
+
client.change_password(
|
|
177
|
+
current_password="oldpass",
|
|
178
|
+
new_password="newpass"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Export all data (GDPR)
|
|
182
|
+
data = client.export_data()
|
|
183
|
+
print(f"Exported {len(data['scripts'])} scripts")
|
|
184
|
+
|
|
185
|
+
# Delete account (permanent!)
|
|
186
|
+
client.delete_account()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## API Reference
|
|
190
|
+
|
|
191
|
+
### `run()` Function
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
run(username, script, key=None, eval=False, accept=False,
|
|
195
|
+
url="shebang.run", version=None, token=None)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Parameters:**
|
|
199
|
+
- `username` (str, required): Script owner's username
|
|
200
|
+
- `script` (str, required): Script name
|
|
201
|
+
- `key` (str, optional): Private key for decryption (not yet implemented)
|
|
202
|
+
- `eval` (bool, optional): Execute the script in Python (default: False)
|
|
203
|
+
- `accept` (bool, optional): Skip confirmation when eval=True (default: False)
|
|
204
|
+
- `url` (str, optional): Base URL (default: "shebang.run")
|
|
205
|
+
- `version` (str, optional): Version tag (e.g., "latest", "v1", "dev")
|
|
206
|
+
- `token` (str, optional): Share token for private scripts
|
|
207
|
+
|
|
208
|
+
**Returns:**
|
|
209
|
+
- String content if `eval=False`
|
|
210
|
+
- Execution result if `eval=True`
|
|
211
|
+
|
|
212
|
+
### `ShebangClient` Class
|
|
213
|
+
|
|
214
|
+
#### Authentication
|
|
215
|
+
- `register(username, email, password)` - Register new user
|
|
216
|
+
- `login(username, password)` - Login and get JWT token
|
|
217
|
+
|
|
218
|
+
#### Script Management
|
|
219
|
+
- `list_scripts()` - List user's scripts
|
|
220
|
+
- `get_script(username, script, version=None, token=None)` - Fetch script content
|
|
221
|
+
- `get_metadata(username, script)` - Get script metadata
|
|
222
|
+
- `verify_signature(username, script)` - Verify script signature
|
|
223
|
+
- `create_script(name, content, description="", visibility="private", keypair_id=None)` - Create script
|
|
224
|
+
- `update_script(script_id, content=None, description=None, visibility=None, tag=None, keypair_id=None)` - Update script
|
|
225
|
+
- `delete_script(script_id)` - Delete script
|
|
226
|
+
- `generate_share_token(script_id)` - Generate share token
|
|
227
|
+
- `revoke_share_token(script_id, token)` - Revoke share token
|
|
228
|
+
|
|
229
|
+
#### Key Management
|
|
230
|
+
- `list_keys()` - List keypairs
|
|
231
|
+
- `generate_key(name)` - Generate new keypair
|
|
232
|
+
- `import_key(name, public_key)` - Import public key
|
|
233
|
+
- `delete_key(key_id)` - Delete keypair
|
|
234
|
+
|
|
235
|
+
#### Account Management
|
|
236
|
+
- `change_password(current_password, new_password)` - Change password
|
|
237
|
+
- `export_data()` - Export all data (GDPR)
|
|
238
|
+
- `delete_account()` - Delete account
|
|
239
|
+
|
|
240
|
+
## Security Notes
|
|
241
|
+
|
|
242
|
+
⚠️ **Warning:** Using `eval=True` with `accept=True` will execute remote code without confirmation. Only use this with scripts you trust completely.
|
|
243
|
+
|
|
244
|
+
✅ **Best Practices:**
|
|
245
|
+
- Always review scripts before executing with `eval=True`
|
|
246
|
+
- Use `accept=False` (default) to see the script before execution
|
|
247
|
+
- Store private keys securely, never commit them to version control
|
|
248
|
+
- Use environment variables for tokens and credentials
|
|
249
|
+
- Verify script signatures when available
|
|
250
|
+
|
|
251
|
+
## Examples
|
|
252
|
+
|
|
253
|
+
### Automation Script
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
#!/usr/bin/env python3
|
|
257
|
+
from shebangrun import run
|
|
258
|
+
|
|
259
|
+
# Fetch deployment script and execute with confirmation
|
|
260
|
+
run(
|
|
261
|
+
username="devops",
|
|
262
|
+
script="deploy-prod",
|
|
263
|
+
version="latest",
|
|
264
|
+
eval=True,
|
|
265
|
+
accept=False # Always confirm production deployments!
|
|
266
|
+
)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### CI/CD Integration
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
import os
|
|
273
|
+
from shebangrun import ShebangClient
|
|
274
|
+
|
|
275
|
+
client = ShebangClient()
|
|
276
|
+
client.login(
|
|
277
|
+
username=os.environ["SHEBANG_USER"],
|
|
278
|
+
password=os.environ["SHEBANG_PASS"]
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Update deployment script
|
|
282
|
+
client.update_script(
|
|
283
|
+
script_id=int(os.environ["DEPLOY_SCRIPT_ID"]),
|
|
284
|
+
content=open("deploy.sh").read(),
|
|
285
|
+
tag="latest"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
print("Deployment script updated!")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
MIT
|
|
294
|
+
|
|
295
|
+
## Links
|
|
296
|
+
|
|
297
|
+
- Website: https://shebang.run
|
|
298
|
+
- Documentation: https://shebang.run/docs
|
|
299
|
+
- GitHub: https://github.com/shebangrun/python-client
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.25.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
shebangrun
|