offbox 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- offbox/__init__.py +2 -0
- offbox/__main__.py +4 -0
- offbox/_data/host/cloud-init.yaml.j2 +15 -0
- offbox/_data/host/host-init.sh +230 -0
- offbox/_data/images/universal/Dockerfile +101 -0
- offbox/_data/images/universal/offbox-init +75 -0
- offbox/_data/images/universal/offbox-run +131 -0
- offbox/_data/providers/gcp/.terraform.lock.hcl +52 -0
- offbox/_data/providers/gcp/main.tf +67 -0
- offbox/_data/providers/gcp/outputs.tf +7 -0
- offbox/_data/providers/gcp/provider.toml +9 -0
- offbox/_data/providers/gcp/variables.tf +33 -0
- offbox/_data/providers/gcp/versions.tf +12 -0
- offbox/_data/providers/hetzner/.terraform.lock.hcl +51 -0
- offbox/_data/providers/hetzner/main.tf +32 -0
- offbox/_data/providers/hetzner/outputs.tf +7 -0
- offbox/_data/providers/hetzner/provider.toml +9 -0
- offbox/_data/providers/hetzner/variables.tf +28 -0
- offbox/_data/providers/hetzner/versions.tf +12 -0
- offbox/_data/providers/ssh/provider.toml +4 -0
- offbox/cli/__init__.py +117 -0
- offbox/cli/config.py +39 -0
- offbox/cli/env.py +109 -0
- offbox/cli/exec.py +50 -0
- offbox/cli/host.py +313 -0
- offbox/cli/misc.py +132 -0
- offbox/cli/ports.py +44 -0
- offbox/config.py +189 -0
- offbox/console.py +63 -0
- offbox/doctor.py +174 -0
- offbox/drivers/__init__.py +0 -0
- offbox/drivers/base.py +40 -0
- offbox/drivers/none.py +114 -0
- offbox/drivers/tofu.py +59 -0
- offbox/errors.py +45 -0
- offbox/execute.py +772 -0
- offbox/host/__init__.py +89 -0
- offbox/host/activity.py +58 -0
- offbox/host/auto.py +322 -0
- offbox/host/cloudinit.py +44 -0
- offbox/host/create.py +198 -0
- offbox/host/destroy.py +151 -0
- offbox/host/init.py +196 -0
- offbox/host/ready.py +106 -0
- offbox/host/timer.py +227 -0
- offbox/host/usage.py +123 -0
- offbox/host/warnings.py +61 -0
- offbox/locking.py +27 -0
- offbox/logging.py +154 -0
- offbox/logs.py +135 -0
- offbox/naming.py +67 -0
- offbox/paths.py +55 -0
- offbox/ports.py +610 -0
- offbox/project.py +187 -0
- offbox/providers.py +117 -0
- offbox/py.typed +0 -0
- offbox/runner.py +291 -0
- offbox/runtime.py +887 -0
- offbox/setup.py +181 -0
- offbox/share.py +286 -0
- offbox/ssh.py +179 -0
- offbox/state.py +157 -0
- offbox/status.py +378 -0
- offbox/sync.py +380 -0
- offbox/timings.py +69 -0
- offbox/tools.py +227 -0
- offbox/uninstall.py +114 -0
- offbox-0.1.0.dist-info/METADATA +140 -0
- offbox-0.1.0.dist-info/RECORD +72 -0
- offbox-0.1.0.dist-info/WHEEL +4 -0
- offbox-0.1.0.dist-info/entry_points.txt +2 -0
- offbox-0.1.0.dist-info/licenses/LICENSE +21 -0
offbox/uninstall.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import stat
|
|
5
|
+
from contextlib import ExitStack
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from offbox import console
|
|
9
|
+
from offbox.errors import OffboxError, PreconditionError
|
|
10
|
+
from offbox.host import host_operation
|
|
11
|
+
from offbox.host.timer import remove as remove_reaper
|
|
12
|
+
from offbox.logging import close_logs
|
|
13
|
+
from offbox.paths import Paths
|
|
14
|
+
from offbox.providers import load_provider
|
|
15
|
+
from offbox.runner import Runner
|
|
16
|
+
from offbox.ssh import close_master, remove_include
|
|
17
|
+
from offbox.state import HostState, StateStore
|
|
18
|
+
from offbox.sync import SyncManager
|
|
19
|
+
from offbox.tools import cached_mutagen
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _validate_owned_directory(path: Path, config: Path) -> None:
|
|
25
|
+
if path.resolve().is_relative_to(config.resolve()) or config.resolve().is_relative_to(path.resolve()):
|
|
26
|
+
raise PreconditionError(
|
|
27
|
+
f"Cleanup directory overlaps user configuration: {path}", "Separate offbox config, state, cache and control directories."
|
|
28
|
+
)
|
|
29
|
+
try:
|
|
30
|
+
metadata = path.lstat()
|
|
31
|
+
except FileNotFoundError:
|
|
32
|
+
return
|
|
33
|
+
except OSError as exc:
|
|
34
|
+
raise PreconditionError(f"Cannot inspect cleanup directory {path}", "Check local directory permissions.") from exc
|
|
35
|
+
if not stat.S_ISDIR(metadata.st_mode) or metadata.st_uid != os.getuid():
|
|
36
|
+
raise PreconditionError(f"Unsafe cleanup directory {path}", "Restore an owned directory without symlinks before uninstalling.")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _is_cloud(host: HostState) -> bool:
|
|
40
|
+
if host.provider_dir is not None:
|
|
41
|
+
try:
|
|
42
|
+
return load_provider(host.provider_dir).manifest.driver != "none"
|
|
43
|
+
except OffboxError:
|
|
44
|
+
logger.warning("cannot read provider workspace for host %s", host.name, exc_info=True)
|
|
45
|
+
return host.provider != "ssh"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _describe(host: HostState) -> str:
|
|
49
|
+
return f"{host.name} ({host.provider}, {host.address or 'no address yet'}, label offbox-host={host.name})"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _stop_cached_mutagen_daemon(paths: Paths, runner: Runner) -> None:
|
|
53
|
+
executable = cached_mutagen(paths.cache)
|
|
54
|
+
if executable is None or shutil.which("mutagen") is not None:
|
|
55
|
+
return
|
|
56
|
+
result = runner.run([str(executable), "daemon", "stop"], check=False, timeout=30)
|
|
57
|
+
if result.returncode != 0:
|
|
58
|
+
logger.warning("cannot stop the downloaded Mutagen daemon: %s", result.stderr.strip())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def uninstall(
|
|
62
|
+
paths: Paths, runner: Runner, *, yes: bool = False, abandon_cloud_hosts: bool = False, ssh_config: Path | None = None
|
|
63
|
+
) -> None:
|
|
64
|
+
for path in (paths.state, paths.cache, paths.control):
|
|
65
|
+
_validate_owned_directory(path, paths.config)
|
|
66
|
+
if paths.config.is_symlink():
|
|
67
|
+
raise PreconditionError("Offbox config directory is a symlink", "Restore the owned config directory before uninstalling.")
|
|
68
|
+
state = StateStore(paths.state).read()
|
|
69
|
+
if state.hosts:
|
|
70
|
+
names = ", ".join(sorted(state.hosts))
|
|
71
|
+
fix = "; ".join(f"offbox host destroy {name}" for name in sorted(state.hosts))
|
|
72
|
+
cloud = [host for _, host in sorted(state.hosts.items()) if _is_cloud(host)]
|
|
73
|
+
retain = "offbox uninstall --yes --abandon-cloud-hosts" if cloud else "offbox uninstall --yes"
|
|
74
|
+
if not yes:
|
|
75
|
+
raise PreconditionError(
|
|
76
|
+
f"Hosts remain: {names}. Uninstall does not destroy remote hosts.",
|
|
77
|
+
f"Run {fix} first, or {retain} to retain the hosts.",
|
|
78
|
+
)
|
|
79
|
+
if cloud and not abandon_cloud_hosts:
|
|
80
|
+
raise PreconditionError(
|
|
81
|
+
"Cloud hosts remain and keep billing after uninstall: " + "; ".join(_describe(host) for host in cloud),
|
|
82
|
+
"Run "
|
|
83
|
+
+ "; ".join(f"offbox host destroy {host.name} --yes" for host in cloud)
|
|
84
|
+
+ " first, or add --abandon-cloud-hosts to keep them running without local records.",
|
|
85
|
+
)
|
|
86
|
+
for host in cloud:
|
|
87
|
+
console.warn(f"Abandoning cloud host {_describe(host)}; delete it in the provider console to stop billing.")
|
|
88
|
+
console.warn(f"Hosts remain remotely: {names}. Their local provisioning state will be removed.")
|
|
89
|
+
if remove_reaper(paths, runner):
|
|
90
|
+
console.info("Removed the idle reaper timer")
|
|
91
|
+
with ExitStack() as locks:
|
|
92
|
+
for name in sorted(state.hosts):
|
|
93
|
+
locks.enter_context(host_operation(paths, name))
|
|
94
|
+
SyncManager(runner, cache=paths.cache).terminate_all()
|
|
95
|
+
for host in state.hosts.values():
|
|
96
|
+
if host.address:
|
|
97
|
+
close_master(runner, host.name)
|
|
98
|
+
if StateStore(paths.state).read() != state:
|
|
99
|
+
raise PreconditionError(
|
|
100
|
+
"Local state changed during uninstall", "Wait for other offbox operations to finish and retry uninstall."
|
|
101
|
+
)
|
|
102
|
+
_stop_cached_mutagen_daemon(paths, runner)
|
|
103
|
+
try:
|
|
104
|
+
close_logs()
|
|
105
|
+
for path in dict.fromkeys((paths.cache, paths.control, paths.state)):
|
|
106
|
+
if path.exists():
|
|
107
|
+
shutil.rmtree(path)
|
|
108
|
+
remove_include(paths, ssh_config)
|
|
109
|
+
(paths.config / "ssh_config").unlink(missing_ok=True)
|
|
110
|
+
except OSError as exc:
|
|
111
|
+
raise PreconditionError(
|
|
112
|
+
"Cannot remove offbox local resources", "Check directory permissions and rerun offbox uninstall --yes."
|
|
113
|
+
) from exc
|
|
114
|
+
console.success("Removed offbox local resources; remote hosts and user configuration are retained")
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: offbox
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Give every git worktree its own Docker stack on one cloud VM.
|
|
5
|
+
Project-URL: Homepage, https://github.com/o-avdieiev/offbox
|
|
6
|
+
Project-URL: Repository, https://github.com/o-avdieiev/offbox
|
|
7
|
+
Project-URL: Documentation, https://github.com/o-avdieiev/offbox/tree/master/docs
|
|
8
|
+
Project-URL: Changelog, https://github.com/o-avdieiev/offbox/blob/master/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/o-avdieiev/offbox/issues
|
|
10
|
+
Author-email: Oleksii Avdieiev <50865612+o-avdieiev@users.noreply.github.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: coding-agents,docker,docker-compose,gcp,git-worktree,hetzner,mutagen,remote-development,ssh
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: jinja2<4,>=3.1
|
|
26
|
+
Requires-Dist: pydantic<3,>=2
|
|
27
|
+
Requires-Dist: rich<16,>=13
|
|
28
|
+
Requires-Dist: typer<0.28,>=0.27
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# offbox
|
|
32
|
+
|
|
33
|
+
[](https://github.com/o-avdieiev/offbox/actions/workflows/test.yaml)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](pyproject.toml)
|
|
36
|
+
|
|
37
|
+
Give every git worktree its own Docker stack on one cloud VM you own. Your compose files don't change.
|
|
38
|
+
|
|
39
|
+
Running several coding agents or worktrees in parallel means several full Docker Compose stacks: databases, browsers, builds and test runs competing for one laptop's CPU, memory and ports. Offbox moves those stacks to a single VM and gives each worktree its own Docker daemon, volumes and port forwards. Your editor, agents and Git stay on the laptop.
|
|
40
|
+
|
|
41
|
+
On a Google Cloud `e2-standard-4` VM, a warm `offbox exec -- true` took a median **1.07 s** end to end, including the sync flush and dependency checks; a first `up` of the FastAPI/Postgres sample took **39 s**, and the VM cost about **$0.15/hour**. See the [Google Cloud](docs/benchmarks/2026-10-23-gcp.md) and [Hetzner](docs/benchmarks/2026-10-23-hetzner.md) benchmark reports for method and limits.
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
macOS, Linux, or Windows via WSL2 · git and ssh — `offbox setup` installs the rest.
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
curl -LsSf https://raw.githubusercontent.com/o-avdieiev/offbox/master/install.sh | sh # installs uv + offbox, runs offbox setup
|
|
49
|
+
cd your-repo
|
|
50
|
+
offbox up
|
|
51
|
+
offbox exec -- pytest
|
|
52
|
+
offbox ports
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`setup` checks your tools, downloads verified Mutagen and OpenTofu when they are missing, offers a dedicated SSH key if you have none, and then creates a Hetzner or Google Cloud host in your own account or registers an existing Ubuntu 24.04 server. Cloud credentials come from your environment, never from a prompt. Creating a cloud host is billable; review its size and current price before approving. The [installation guide](docs/installation.md) walks through each platform and provider step by step, including a manual install without the script.
|
|
56
|
+
|
|
57
|
+
When finished with a worktree, use `offbox rm --yes`. To delete a cloud host and stop its server charges:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
offbox host list
|
|
61
|
+
offbox host destroy main --yes
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`offbox stop` retains the environment's data. It does not delete the server or stop cloud billing. Hetzner bills servers until deletion, including powered-off servers. See [current pricing](https://www.hetzner.com/cloud/) and [billing rules](https://docs.hetzner.com/cloud/billing/faq/).
|
|
65
|
+
|
|
66
|
+
## How it works
|
|
67
|
+
|
|
68
|
+
```mermaid
|
|
69
|
+
flowchart LR
|
|
70
|
+
subgraph Laptop
|
|
71
|
+
Editor[Editor and coding agents]
|
|
72
|
+
A[Git worktree A]
|
|
73
|
+
B[Git worktree B]
|
|
74
|
+
CLI[offbox CLI and local state]
|
|
75
|
+
end
|
|
76
|
+
subgraph Host[Personal SSH host]
|
|
77
|
+
WA[Work directory A]
|
|
78
|
+
WB[Work directory B]
|
|
79
|
+
EA[Environment A: Docker daemon + volumes]
|
|
80
|
+
EB[Environment B: Docker daemon + volumes]
|
|
81
|
+
WA --> EA
|
|
82
|
+
WB --> EB
|
|
83
|
+
end
|
|
84
|
+
A <-->|Mutagen: source files| WA
|
|
85
|
+
B <-->|Mutagen: source files| WB
|
|
86
|
+
CLI -->|SSH: commands and port forwards| EA
|
|
87
|
+
CLI -->|SSH: commands and port forwards| EB
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Both worktrees can publish remote port 8000 and use the same Compose container names. Offbox gives them separate Docker daemons and local forwards such as `localhost:8000` and `localhost:8001`. `offbox ports --swap 8000` exchanges the preferred local port when an app needs a fixed callback URL.
|
|
91
|
+
|
|
92
|
+
`up` creates or starts the environment, synchronizes files, installs declared dependencies, starts the declared stack and restores forwards. `exec` flushes sync, checks dependency hashes and streams the requested command with its actual exit code. Git metadata is excluded from sync; run Git locally.
|
|
93
|
+
|
|
94
|
+
Use `offbox --timings up` or `offbox --timings exec -- COMMAND` to see stage durations on stderr. Timing totals exclude Python/CLI startup; command output and exit codes are preserved.
|
|
95
|
+
|
|
96
|
+
## Why not DevPod, Codespaces or Docker over SSH?
|
|
97
|
+
|
|
98
|
+
- **Worktree-native.** Each worktree gets its own Docker daemon, volumes and port forwards on the shared VM, so several worktrees can run the same Compose project, with the same container names and ports, at the same time.
|
|
99
|
+
- **Nothing to adopt.** No `devcontainer.json`, no changes to your compose files and no offbox daemon or control plane to run. An optional `offbox.toml` declares install and stack commands when the defaults are not enough.
|
|
100
|
+
- **Your editor, agents and Git stay local.** Mutagen keeps files in sync both ways; commands and port forwards go over SSH.
|
|
101
|
+
- **One VM you own.** Hetzner, Google Cloud or any Ubuntu 24.04 server you can reach over SSH. You pay the provider directly.
|
|
102
|
+
|
|
103
|
+
Use DevPod or Codespaces if you want a full remote editor environment defined by `devcontainer.json`. Use sandbox APIs such as [E2B](https://e2b.dev/docs) for untrusted code.
|
|
104
|
+
|
|
105
|
+
## Requirements and scope
|
|
106
|
+
|
|
107
|
+
- macOS or Linux client, Python 3.12+, Git 2.30+, OpenSSH 8+ and Mutagen 0.18.x; `offbox setup` downloads a verified Mutagen 0.18.1 if none is installed. Windows clients use WSL2.
|
|
108
|
+
- An Ubuntu 24.04 amd64 host. The universal image is Linux amd64; an Apple Silicon laptop is supported as a client.
|
|
109
|
+
- OpenTofu 1.8+ or Terraform 1.6+ for cloud provisioning. If neither is installed, Offbox can download verified pinned OpenTofu.
|
|
110
|
+
- Trusted personal projects. Environment containers are privileged and share the host kernel; they are not a sandbox for untrusted code. Two-way sync also lets code on the VM change files in your worktree. Read the [security model](docs/security.md).
|
|
111
|
+
|
|
112
|
+
See [limits](docs/limits.md) for the full operating model.
|
|
113
|
+
|
|
114
|
+
## FAQ
|
|
115
|
+
|
|
116
|
+
**Does it work on Apple Silicon or arm64?** An arm64 laptop works as a client. Hosts must be amd64, because the universal image is built only for `linux/amd64`; arm64 host images are not provided.
|
|
117
|
+
|
|
118
|
+
**Does an idle host stop automatically?** No. There is no automatic idle stop or destroy. `offbox stop` stops an environment but not the VM or its billing; run `offbox host destroy NAME --yes` when you are done.
|
|
119
|
+
|
|
120
|
+
**What does it cost?** Whatever your provider charges for the VM. The benchmarked Google Cloud `e2-standard-4` was about $0.15/hour plus disk and IP charges; the default Hetzner size is `ccx33`. Check current provider pricing before creating a host.
|
|
121
|
+
|
|
122
|
+
**Can I use another cloud?** The built-in providers are Hetzner, Google Cloud and existing SSH servers. Any Ubuntu 24.04 amd64 server works through `ssh`, and you can add an OpenTofu/Terraform provider; see [writing a provider](docs/writing-a-provider.md).
|
|
123
|
+
|
|
124
|
+
**Does it support Windows?** Use WSL2 as the client; see [Windows (WSL2)](docs/installation.md#windows-wsl2).
|
|
125
|
+
|
|
126
|
+
**Do I need a devcontainer?** No. Offbox runs your existing Compose files in the universal image. Set `env.image` or `env.dockerfile` in `offbox.toml` to use your own image.
|
|
127
|
+
|
|
128
|
+
## Configuration and examples
|
|
129
|
+
|
|
130
|
+
An `offbox.toml` at the Git worktree root configures resources, dependency installation, stack commands, forwards, sync ignores, forwarded variables, copied files and the git mirror. Every key is optional, and each can also come from your user config (for every repository), an uncommitted `offbox.local.toml` or an `OFFBOX_*` variable; `offbox config` shows the result and where each value came from. The environment gets a real Git repository by default. With `auto = true` on a host preset, offbox creates the VM when a worktree first needs it and destroys it after the last one is removed or it sits idle, so a forgotten VM does not keep billing. See the [complete configuration reference](docs/configuration.md).
|
|
131
|
+
|
|
132
|
+
- [FastAPI + Postgres](examples/python-compose/): real database-backed API and tests, with ports 8000 and 5432.
|
|
133
|
+
- [Vite + Playwright](examples/node-playwright/): a small web app and browser test, with port 3000.
|
|
134
|
+
- [Agent instructions and JSON contracts](docs/agents.md).
|
|
135
|
+
- [Architecture and lifecycle](docs/architecture.md).
|
|
136
|
+
- [Installation](docs/installation.md), [providers](docs/providers.md), [writing a provider](docs/writing-a-provider.md), and [troubleshooting](docs/troubleshooting.md).
|
|
137
|
+
|
|
138
|
+
## Contributing
|
|
139
|
+
|
|
140
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, checks and CI, and [SECURITY.md](SECURITY.md) for private vulnerability reporting. Offbox is not yet published to PyPI; install it from this repository as shown above. Licensed under [MIT](LICENSE).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
offbox/_data/host/cloud-init.yaml.j2,sha256=HKanv9TfhoNVigsLi0SBaaLGkYDZc_nARyjhbulyiss,380
|
|
2
|
+
offbox/_data/host/host-init.sh,sha256=pNq9crwnnFOK7C9CIpcN9qGT1d4IVW9FvJiGHvAOQuM,9839
|
|
3
|
+
offbox/_data/images/universal/Dockerfile,sha256=sn9SthjeGvdsW5kViIYodMWgOaqfsrKeJmVz1cnqoGk,6401
|
|
4
|
+
offbox/_data/images/universal/offbox-init,sha256=VnaKl28by8VDMsuorynGuE4FXOt6mMxhVQOEE-BR19A,2606
|
|
5
|
+
offbox/_data/images/universal/offbox-run,sha256=U-Tz95WpL5Gt2QtW2ozj1QeG2DefnUmfWT24hPdaSD0,4819
|
|
6
|
+
offbox/_data/providers/gcp/.terraform.lock.hcl,sha256=z2tzPeXQkyiZV3mpBoPAuOV88y61EXkr6t7_MqmcmM4,3029
|
|
7
|
+
offbox/_data/providers/gcp/main.tf,sha256=rwTkSLtPRRoeCKwgCYNskcgO_xh374VNA1_uqBAYsuo,1487
|
|
8
|
+
offbox/_data/providers/gcp/outputs.tf,sha256=ipKUu-pKs6VJsDozKPeNbAX5osjBePaYUhzjzTCKXDw,142
|
|
9
|
+
offbox/_data/providers/gcp/provider.toml,sha256=BrZBg3beP6DG3kOxFLAX9C4Gso-PCJrTYp95vRobjNY,221
|
|
10
|
+
offbox/_data/providers/gcp/variables.tf,sha256=PWVlkoBAZHizXU7SwsWuzR0hiVxPAhCGHghlS2OGsh8,486
|
|
11
|
+
offbox/_data/providers/gcp/versions.tf,sha256=dqNKO6p81YkBn4pgVeeG9Ou55mvWQzZn7fSrMtvPU6o,182
|
|
12
|
+
offbox/_data/providers/hetzner/.terraform.lock.hcl,sha256=MbwFIdQMyIHcmTJruPMATQ6DsFGAsByuVNjJDCHmAp8,2966
|
|
13
|
+
offbox/_data/providers/hetzner/main.tf,sha256=J24Gz0pHbGqVhLKh3GrqLvlnqiYjZe1QEz-BE66dh00,613
|
|
14
|
+
offbox/_data/providers/hetzner/outputs.tf,sha256=S36Y3RbBZPbPrg7KA_8_MfCjdRtHFHJ-JfUDlBCGzR4,100
|
|
15
|
+
offbox/_data/providers/hetzner/provider.toml,sha256=3231K9IdsXc4hEmre7rHD9RhLGCXpHJTNSfK_Es5Ajw,176
|
|
16
|
+
offbox/_data/providers/hetzner/variables.tf,sha256=GnsQt0DxqUTpvVOQxkQ6pp0ypfMfoP7_OqaJ8_Om5zM,307
|
|
17
|
+
offbox/_data/providers/hetzner/versions.tf,sha256=IE2K4JLJmPO9i0zaEzD20bmvTUzi1MRPEHpEAmg1WsY,187
|
|
18
|
+
offbox/_data/providers/ssh/provider.toml,sha256=tmjCcRERtlIYPiS03HqdhzzwHVeteecItSKa7-9TbRU,107
|
|
19
|
+
offbox/__init__.py,sha256=uCndGMXbtIILNRoTDoorykyTWw0u2Bm-PQGb22XHozk,78
|
|
20
|
+
offbox/__main__.py,sha256=xOgH_sN3gNv2w8SdCgAd9039inLr53cvRvp3iazeztw,65
|
|
21
|
+
offbox/config.py,sha256=li1lqrGnBPfu3EUm-0ct84YRglxp02N698Lk0q11K4I,7076
|
|
22
|
+
offbox/console.py,sha256=djTVX4cgtStJf8XPMvkzvWqCfKCWVR2Jk43V3pE8Xzs,1250
|
|
23
|
+
offbox/doctor.py,sha256=kb8FlJI0DbPws5Jxo8SngiVLO0_z241AHFP6lDeONLs,8070
|
|
24
|
+
offbox/errors.py,sha256=c7QzKB9lnxXgO0xxGqdRInUGrN_4X7SRF_wcEKomIoI,1451
|
|
25
|
+
offbox/execute.py,sha256=3A9KC1uIZJ1hGSL-lN-NRkrl-RJ4L2ARtaBtSgUxXnk,33661
|
|
26
|
+
offbox/locking.py,sha256=LjfeOdf9wyVbibYrQ_v2zLo-eZGYpmJhrTdoFzE_71Y,944
|
|
27
|
+
offbox/logging.py,sha256=Ze-mlHxfyWF9MflEc2bbM6R4P5gWeo8-DR-REghwdYM,5405
|
|
28
|
+
offbox/logs.py,sha256=3_o-r5Jvus78aO7G8YnjazvJllAP1aBEiAwPwlOMAUQ,5351
|
|
29
|
+
offbox/naming.py,sha256=sJmevG5HWbYBZoesWN4ywi3lGSCdlrD7n1bbBTSVJHs,2016
|
|
30
|
+
offbox/paths.py,sha256=NITNMft56jxL1yzmzRtKnvZA1f4vaHlLoLHCeWcZurk,1834
|
|
31
|
+
offbox/ports.py,sha256=hVj1uSVg7x4eKySMwMI3nymm13ieo35CC9B7hNjg81w,30006
|
|
32
|
+
offbox/project.py,sha256=DTtj7XLK4WxCTLlW_Ri6ijTdoIA4ImDeAT6pFTwFoD4,7334
|
|
33
|
+
offbox/providers.py,sha256=crvnCK80BrBdN0ww4NQML7qrMUbIneraikzx0BEV9wE,4903
|
|
34
|
+
offbox/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
+
offbox/runner.py,sha256=BrXjoKJO1ZLfmyfoYxVIOpBQtXr1JFqsfB9qhqzLIeI,12699
|
|
36
|
+
offbox/runtime.py,sha256=LPhFvbcpjxRqhler8sJAseGqVmSzL_4JB9u-f0xizXY,42071
|
|
37
|
+
offbox/setup.py,sha256=OcJAjKEkN2h90f2teARCbD-PMD6L9XkXOI75oPhymeo,8081
|
|
38
|
+
offbox/share.py,sha256=zPuQHR39qxp88QwL801-xNLqQsUtIy_KHE6BAV7lv9U,14013
|
|
39
|
+
offbox/ssh.py,sha256=WhFMZKute5xbjGZR95udje8VL334RqU1Jz2rlzIZrMk,7476
|
|
40
|
+
offbox/state.py,sha256=4wcD-2Frdd8LRqotzHNzS02s83pqTiijaz_nXvPcpPI,5823
|
|
41
|
+
offbox/status.py,sha256=rZbSzR3-ioyxDeSN8Uan3eFsFZTinMW6wfAU2BI1qu8,16579
|
|
42
|
+
offbox/sync.py,sha256=RUS0lQbBZ82tvSwz-JPI6LVOhwQJ9ROOHUXAIDd_cbY,15553
|
|
43
|
+
offbox/timings.py,sha256=DWgtgbUuELCc2jtyMv9LGQdQWP-3BwXeFm6YtLtM9rc,1823
|
|
44
|
+
offbox/tools.py,sha256=G69CWPMvM56Pbr7fZ1-3K23ap96c2n6mBEY0ZFM3cqA,10075
|
|
45
|
+
offbox/uninstall.py,sha256=Ul7eW_kCxv4xKAC8XkWriOKYBHc-edkgbyMywP04wgE,5364
|
|
46
|
+
offbox/cli/__init__.py,sha256=iqxnLDjyQN5ev0AL4lhg8VKf7lh5sOT5SB3YSDqkpNY,4552
|
|
47
|
+
offbox/cli/config.py,sha256=y-jg9KZAYrQ0Yg29j3PNMxE6o_EaHB73kW8bLrj-X80,1632
|
|
48
|
+
offbox/cli/env.py,sha256=eoMiYxlWpbPUwTpCBa-OO5I9OKfgEhz5Nfdf2fdlaB0,4348
|
|
49
|
+
offbox/cli/exec.py,sha256=Kro6BX9n-OzYphm_ESgvywIDK1__RG1Y-ShgXGxJ4sA,2320
|
|
50
|
+
offbox/cli/host.py,sha256=uGenSAcx1tTe9j45mTIRyBxn9uTquI0UCGCko90tvR8,13883
|
|
51
|
+
offbox/cli/misc.py,sha256=cT4eQnpsmHerGLffcSRmJc5Hx-P7eOJVDpVzKxIf3L0,5688
|
|
52
|
+
offbox/cli/ports.py,sha256=86kKNlsHynV0vBPE8HWgBaql4y6jdFJynIy_0yX-eXg,1565
|
|
53
|
+
offbox/drivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
|
+
offbox/drivers/base.py,sha256=YIOSY8D0v1cWAmFWJO5GwpzKsXxdDe3-xwpc2_80Vuo,775
|
|
55
|
+
offbox/drivers/none.py,sha256=UtCNHSqzbUQUe_WX7RFo977NS-tqHEjWF2mRotBm0aY,6391
|
|
56
|
+
offbox/drivers/tofu.py,sha256=XLUMeApkC3WREa0IyEWDOHXeLg-W0-n1lxA2NQlyQDI,2524
|
|
57
|
+
offbox/host/__init__.py,sha256=vaSN_H2kccqIxtR6w_77dH33ZXg2IcVijKK1Kb7UgwE,4243
|
|
58
|
+
offbox/host/activity.py,sha256=UHh4BYh7WHqgCp0iVMP4G-qjc0zqW_OkYJpuCAH5lws,1856
|
|
59
|
+
offbox/host/auto.py,sha256=Xe7H9Ms-L94q8BII1_-TV9u-9Dj4FqzhbX4ZqQiFGbc,14139
|
|
60
|
+
offbox/host/cloudinit.py,sha256=PLOfeI4nHgFszUMO--LT79VM0Bg4qwtOfyxmWLC0XFg,1782
|
|
61
|
+
offbox/host/create.py,sha256=UHeyAKbEM_n02vT_X-RN43tyEzLxITDqJl1KShZvNG4,8674
|
|
62
|
+
offbox/host/destroy.py,sha256=mVOWH_eRx-WdDEonvsTh9K6pXVz6dEZpNxWtdWV8nXs,7356
|
|
63
|
+
offbox/host/init.py,sha256=GTi44-Uh9rtqwcn0Pkb9w9VTeNTY-ioEX2RiMwH1J_0,8517
|
|
64
|
+
offbox/host/ready.py,sha256=H_zO8BGrPEW4QDwTfvWVH1-0eORIKNYxRTL6BTFrw9Y,3411
|
|
65
|
+
offbox/host/timer.py,sha256=ngOPX_nGMp8FqchsrQIIQuIwN3U1d37ipPMMcL09Ubc,10537
|
|
66
|
+
offbox/host/usage.py,sha256=ZhCLR430d9LuwHv9psmtBzUQ3SmODGvd5i_-U7j3QwM,4015
|
|
67
|
+
offbox/host/warnings.py,sha256=SQZCqJKWw5zdssR4CNZIWHeB-xP9mGOZFzNMpiKua5Q,1868
|
|
68
|
+
offbox-0.1.0.dist-info/METADATA,sha256=Wty2mKfkHj-u_6Thr5lgPL6O_4YLJ4XA_vIlQIIk3Bk,9699
|
|
69
|
+
offbox-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
70
|
+
offbox-0.1.0.dist-info/entry_points.txt,sha256=yE2XDjo-7se4laV1Qs1HEYeFgWDkDOWnp7Xe2exwWv8,42
|
|
71
|
+
offbox-0.1.0.dist-info/licenses/LICENSE,sha256=sOmgVHMB3UnFbkHVFmmRWlLD_g6A7AZmFks0M8B7pYQ,1073
|
|
72
|
+
offbox-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oleksii Avdieiev
|
|
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.
|