omicslab 2.1.0__tar.gz → 2.5.1__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.
- {omicslab-2.1.0 → omicslab-2.5.1}/PKG-INFO +14 -5
- {omicslab-2.1.0 → omicslab-2.5.1}/README.developer.md +43 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/README.md +12 -4
- {omicslab-2.1.0 → omicslab-2.5.1}/pyproject.toml +1 -0
- omicslab-2.5.1/scripts/bootstrap-env.sh +224 -0
- omicslab-2.5.1/src/omicslab/_version.py +1 -0
- omicslab-2.5.1/src/omicslab/cli/job_commands.py +441 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/main.py +162 -11
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/runner_commands.py +7 -1
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/studio_commands.py +53 -21
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/client.py +42 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/config.py +16 -1
- omicslab-2.5.1/src/omicslab/job/_run/_broker.py +235 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_exec.py +16 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_git.py +13 -3
- omicslab-2.5.1/src/omicslab/job/_run/_git_proxy.py +196 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_monitor.py +23 -5
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_nextflow.py +12 -7
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_proxy.py +9 -1
- omicslab-2.5.1/src/omicslab/job/_run/_s3_proxy.py +242 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_storage.py +19 -5
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_studio_prep.py +18 -8
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/analysis.py +42 -7
- omicslab-2.5.1/src/omicslab/job/_run/studio.py +1189 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/client.py +12 -2
- omicslab-2.5.1/src/omicslab/resources/storage.py +257 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/conftest.py +2 -1
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/e2e/conftest.py +2 -1
- omicslab-2.5.1/tests/job/_run/test__broker.py +146 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__engine_helpers.py +152 -1
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__git.py +13 -0
- omicslab-2.5.1/tests/job/_run/test__git_proxy.py +105 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__monitor.py +4 -1
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__nextflow.py +8 -5
- omicslab-2.5.1/tests/job/_run/test__s3_proxy.py +175 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__storage.py +88 -0
- omicslab-2.5.1/tests/job/_run/test__studio_docker.py +349 -0
- omicslab-2.5.1/tests/job/_run/test_studio_proxy_start.py +85 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_job_client_extra.py +41 -0
- omicslab-2.5.1/tests/job/test_snapshot_request.py +37 -0
- omicslab-2.5.1/tests/job/test_studio_image_download.py +66 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_cli.py +2 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_cli_storage.py +204 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_client.py +24 -0
- omicslab-2.5.1/tests/test_client_broker.py +51 -0
- omicslab-2.5.1/tests/test_config.py +20 -0
- omicslab-2.5.1/tests/test_job_heartbeat.py +84 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_resources.py +117 -5
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_runner_helpers.py +3 -0
- omicslab-2.5.1/tests/test_studio_snapshot.py +224 -0
- omicslab-2.5.1/tests/test_system_monitor_interval.py +36 -0
- omicslab-2.1.0/scripts/bootstrap-env.sh +0 -103
- omicslab-2.1.0/src/omicslab/_version.py +0 -1
- omicslab-2.1.0/src/omicslab/cli/job_commands.py +0 -238
- omicslab-2.1.0/src/omicslab/job/_run/studio.py +0 -526
- omicslab-2.1.0/src/omicslab/resources/storage.py +0 -143
- omicslab-2.1.0/tests/test_studio_snapshot.py +0 -106
- {omicslab-2.1.0 → omicslab-2.5.1}/.gitignore +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/Makefile +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/.gitignore +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/dev/hcm.pixi.lock +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/dev/hcm.pixi.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/dev/pixi.lock +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/dev/pixi.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/dev/us-east-1.pixi.lock +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/dev/us-east-1.pixi.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/pixi.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/prod/hcm.pixi.lock +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/prod/hcm.pixi.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/prod/pixi.lock +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/prod/pixi.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/envs/regions.json +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/pixi.config.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/scripts/check_publish.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/scripts/write_version_manifest.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/__init__.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/__init__.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/analysis_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/auth_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/context_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/credential_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/fuse_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/org_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/setup_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/token_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/utils.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/workspace_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/cli/ws_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/exceptions.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/__init__.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/__init__.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_auth.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_config_download.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_consts.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_data_prep.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_env.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_images.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_log.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_params.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_proc.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/_run/_spec.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/console.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/executor.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/paths.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/job/system.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/__init__.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/analysis.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/base.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/credentials.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/jobs.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/orgs.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/runners.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/studio.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/tokens.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/resources/workspaces.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/src/omicslab/selfupdate.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/__init__.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/e2e/__init__.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/e2e/test_cli_e2e.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/e2e/test_tokens.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/fixtures/nfcore-demo-custom.config +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/fixtures/studio_specs/jupyter.json +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/fixtures/studio_specs/rstudio.json +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__auth.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__data_prep.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__env.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__get_images_cli.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__images.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__log.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__params.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__proxy.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__proxy_extra.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/_run/test__spec.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_job_cli.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_job_client.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_job_executor.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_job_executor_extra.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_job_fuse.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_job_resource.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_job_system.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_studio_validate_cli.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/job/test_ws_helpers.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_cli_catalog.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_cli_context.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_cli_utils.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_fuse.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_job_exec.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_misc.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_runner_self_update.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_sandbox_report.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_selfupdate.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/test_ws_commands.py +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-nextflow/input.txt +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-nextflow/main.nf +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-nextflow/omicslab/main.sh +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-nextflow/pixi.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-nextflow/schema.json +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-simple/omicslab/main.sh +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-simple/pixi.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-simple/schema.json +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-snakemake/Snakefile +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-snakemake/input.txt +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-snakemake/omicslab/main.sh +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-snakemake/pixi.toml +0 -0
- {omicslab-2.1.0 → omicslab-2.5.1}/tests/tools/hello-snakemake/schema.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omicslab
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.5.1
|
|
4
4
|
Summary: Python SDK and CLI for the OmicsLab Platform
|
|
5
5
|
Project-URL: Homepage, https://platform.omicslab.io
|
|
6
6
|
Project-URL: Repository, https://github.com/vieomics/omicslab-platform
|
|
@@ -14,6 +14,7 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Requires-Python: >=3.12
|
|
16
16
|
Requires-Dist: aiohttp>=3.10
|
|
17
|
+
Requires-Dist: botocore<2,>=1.35
|
|
17
18
|
Requires-Dist: httpx>=0.27
|
|
18
19
|
Requires-Dist: psutil>=6
|
|
19
20
|
Requires-Dist: pydantic>=2
|
|
@@ -93,7 +94,7 @@ omicslab upload -y ./data.csv s3://my-bucket/data/
|
|
|
93
94
|
|
|
94
95
|
### Download
|
|
95
96
|
|
|
96
|
-
Download files or directories from workspace storage.
|
|
97
|
+
Download files or directories from workspace storage. A folder is detected automatically, so a trailing slash and `-r` are optional; recursive downloads preserve subdirectory structure and use parallel threads.
|
|
97
98
|
|
|
98
99
|
```bash
|
|
99
100
|
# Single file (defaults to filename in current directory)
|
|
@@ -105,7 +106,13 @@ omicslab download results/run1/data.csv ./local_copy.csv
|
|
|
105
106
|
# Download from s3:// path (auto-resolves workspace)
|
|
106
107
|
omicslab download s3://my-bucket/data/results/report.html
|
|
107
108
|
|
|
108
|
-
#
|
|
109
|
+
# Folder download (auto-detected; defaults to ./run1)
|
|
110
|
+
omicslab download results/run1
|
|
111
|
+
|
|
112
|
+
# Folder download with explicit destination
|
|
113
|
+
omicslab download results/run1 ./local_results/
|
|
114
|
+
|
|
115
|
+
# Same thing, explicit recursive (preserves subdirectory structure)
|
|
109
116
|
omicslab download -r s3://my-bucket/data/results/ ./local_results/
|
|
110
117
|
|
|
111
118
|
# Recursive download with custom thread count
|
|
@@ -119,12 +126,13 @@ omicslab download -y -r s3://my-bucket/data/results/ ./local_results/
|
|
|
119
126
|
|
|
120
127
|
| Flag | Description |
|
|
121
128
|
|------|-------------|
|
|
122
|
-
| `--recursive`, `-r` |
|
|
129
|
+
| `--recursive`, `-r` | Force recursive download (folders are detected automatically) |
|
|
130
|
+
| `--zip` | Download a folder as a server-built zip archive |
|
|
123
131
|
| `--threads` | Concurrent download threads (default: 4) |
|
|
124
132
|
| `--workspace-id`, `-w` | Workspace ID (inferred from s3:// path if provided) |
|
|
125
133
|
| `--yes`, `-y` | Skip confirmation prompt for s3:// path resolution |
|
|
126
134
|
|
|
127
|
-
**Recursive download** lists all objects under the prefix, then downloads them in parallel while preserving the original subdirectory structure under the destination directory.
|
|
135
|
+
**Recursive download** lists all objects under the prefix, then downloads them in parallel while preserving the original subdirectory structure under the destination directory. A folder key without a destination downloads into a local directory named after its last segment (`omicslab download snapshots` → `./snapshots/`). An exact object always wins over a same-named folder prefix, and a key that matches neither reports "File or folder not found" instead of a provider 403/404.
|
|
128
136
|
|
|
129
137
|
## Environment Variables
|
|
130
138
|
|
|
@@ -132,6 +140,7 @@ omicslab download -y -r s3://my-bucket/data/results/ ./local_results/
|
|
|
132
140
|
|---|---|
|
|
133
141
|
| `OMICSLAB_TOKEN` | API token (overrides config file) |
|
|
134
142
|
| `OMICSLAB_BASE_URL` | API base URL (default: `https://platform.omicslab.io/api`) |
|
|
143
|
+
| `OMICSLAB_CONFIG_DIR` | Directory holding `config.json` (default: `~/.omicslab`); used by studio sessions to read a container-private config without changing `$HOME` |
|
|
135
144
|
|
|
136
145
|
## Jobs API
|
|
137
146
|
|
|
@@ -463,6 +463,49 @@ Studio jobs are unchanged: they run as background sandbox services via
|
|
|
463
463
|
`executor.submit(...)` (spec/bash path), as do jobs dispatched to a runner
|
|
464
464
|
daemon over WebSocket. Only `job run --work-dir` analysis is native/synchronous.
|
|
465
465
|
|
|
466
|
+
Studio session mounts (no token exposure, snapshot-safe):
|
|
467
|
+
|
|
468
|
+
- The SDK environment is bind-mounted **read-only at its own absolute path**
|
|
469
|
+
(pixi/conda envs are path-sensitive), and `omicslab`/`omx` are exposed on
|
|
470
|
+
`PATH` via a dedicated `/opt/omicslab/bin` directory.
|
|
471
|
+
- `pixi` itself is mounted there too, together with the SDK project's
|
|
472
|
+
`pixi.toml`/`pixi.lock`, so `pixi run ...` works inside the session. A
|
|
473
|
+
writable copy of the env's small `conda-meta` directory is mounted over the
|
|
474
|
+
read-only one because pixi takes its install lock there and appends to
|
|
475
|
+
`history`; the copy lives in the job dir and is never snapshotted.
|
|
476
|
+
- Users can create their own project envs in the writable workspace
|
|
477
|
+
(`pixi init` + `pixi add`); those envs are in the writable layer and **are**
|
|
478
|
+
included in snapshots. The pixi package cache goes to a RAM-backed tmpfs
|
|
479
|
+
(`PIXI_CACHE_DIR=/run/omicslab-cache`) so downloads never bloat snapshots.
|
|
480
|
+
- The staged CLI config is mounted from the host into
|
|
481
|
+
`OMICSLAB_CONFIG_DIR=/run/omicslab` before the container starts. The token
|
|
482
|
+
is replaced with a placeholder first; the SDK talks to a host-side
|
|
483
|
+
credential broker over `OMICSLAB_BROKER_SOCKET`, and the broker injects the
|
|
484
|
+
real `Authorization` header upstream. The mount is writable so context
|
|
485
|
+
commands such as `omx cd` can persist, but it contains only the placeholder
|
|
486
|
+
token — the real token never appears in the container env, `docker inspect`,
|
|
487
|
+
the writable layer, or a snapshot.
|
|
488
|
+
Presigned S3 URLs are still fetched directly by the container. Commands
|
|
489
|
+
that bypass `Client` (for example the websocket log stream) are not proxied
|
|
490
|
+
yet.
|
|
491
|
+
- A selected **personal** GitHub credential is injected by a host-side Git
|
|
492
|
+
HTTPS proxy. Git inside the session rewrites `https://github.com/` to the
|
|
493
|
+
proxy URL, so no token or credential helper is present in the container.
|
|
494
|
+
Studio launches reject organization and marketplace credentials. The token
|
|
495
|
+
stays in the agent process and is never logged or returned to the session.
|
|
496
|
+
- When `STUDIO_EXPORT_AWS_CREDENTIALS` is enabled (Settings → Studio), the
|
|
497
|
+
backend mints short-lived STS credentials for the workspace storage and the
|
|
498
|
+
agent starts a host-side S3 signing proxy. The session receives only
|
|
499
|
+
`AWS_ACCESS_KEY_ID=proxy`, `AWS_SECRET_ACCESS_KEY=proxy`, and
|
|
500
|
+
`AWS_ENDPOINT_URL=<host proxy>`; the real STS never enters the container.
|
|
501
|
+
Only credentials carrying a session token are proxied; long-lived provider
|
|
502
|
+
keys are never exported by this path.
|
|
503
|
+
- The old implicit `/home/omicslab` bind is gone (opt-in via
|
|
504
|
+
`runtime.home_mount` only): user files stay in the container's writable layer
|
|
505
|
+
and are therefore included in `docker commit` snapshots.
|
|
506
|
+
- Bind mounts and tmpfs are never committed, so neither the SDK nor the token
|
|
507
|
+
can leak into a snapshot.
|
|
508
|
+
|
|
466
509
|
The SDK bootstrap URL is configurable:
|
|
467
510
|
|
|
468
511
|
- Env var `SDK_TOOLS_BASE` (backend `settings.py`), default
|
|
@@ -66,7 +66,7 @@ omicslab upload -y ./data.csv s3://my-bucket/data/
|
|
|
66
66
|
|
|
67
67
|
### Download
|
|
68
68
|
|
|
69
|
-
Download files or directories from workspace storage.
|
|
69
|
+
Download files or directories from workspace storage. A folder is detected automatically, so a trailing slash and `-r` are optional; recursive downloads preserve subdirectory structure and use parallel threads.
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
72
|
# Single file (defaults to filename in current directory)
|
|
@@ -78,7 +78,13 @@ omicslab download results/run1/data.csv ./local_copy.csv
|
|
|
78
78
|
# Download from s3:// path (auto-resolves workspace)
|
|
79
79
|
omicslab download s3://my-bucket/data/results/report.html
|
|
80
80
|
|
|
81
|
-
#
|
|
81
|
+
# Folder download (auto-detected; defaults to ./run1)
|
|
82
|
+
omicslab download results/run1
|
|
83
|
+
|
|
84
|
+
# Folder download with explicit destination
|
|
85
|
+
omicslab download results/run1 ./local_results/
|
|
86
|
+
|
|
87
|
+
# Same thing, explicit recursive (preserves subdirectory structure)
|
|
82
88
|
omicslab download -r s3://my-bucket/data/results/ ./local_results/
|
|
83
89
|
|
|
84
90
|
# Recursive download with custom thread count
|
|
@@ -92,12 +98,13 @@ omicslab download -y -r s3://my-bucket/data/results/ ./local_results/
|
|
|
92
98
|
|
|
93
99
|
| Flag | Description |
|
|
94
100
|
|------|-------------|
|
|
95
|
-
| `--recursive`, `-r` |
|
|
101
|
+
| `--recursive`, `-r` | Force recursive download (folders are detected automatically) |
|
|
102
|
+
| `--zip` | Download a folder as a server-built zip archive |
|
|
96
103
|
| `--threads` | Concurrent download threads (default: 4) |
|
|
97
104
|
| `--workspace-id`, `-w` | Workspace ID (inferred from s3:// path if provided) |
|
|
98
105
|
| `--yes`, `-y` | Skip confirmation prompt for s3:// path resolution |
|
|
99
106
|
|
|
100
|
-
**Recursive download** lists all objects under the prefix, then downloads them in parallel while preserving the original subdirectory structure under the destination directory.
|
|
107
|
+
**Recursive download** lists all objects under the prefix, then downloads them in parallel while preserving the original subdirectory structure under the destination directory. A folder key without a destination downloads into a local directory named after its last segment (`omicslab download snapshots` → `./snapshots/`). An exact object always wins over a same-named folder prefix, and a key that matches neither reports "File or folder not found" instead of a provider 403/404.
|
|
101
108
|
|
|
102
109
|
## Environment Variables
|
|
103
110
|
|
|
@@ -105,6 +112,7 @@ omicslab download -y -r s3://my-bucket/data/results/ ./local_results/
|
|
|
105
112
|
|---|---|
|
|
106
113
|
| `OMICSLAB_TOKEN` | API token (overrides config file) |
|
|
107
114
|
| `OMICSLAB_BASE_URL` | API base URL (default: `https://platform.omicslab.io/api`) |
|
|
115
|
+
| `OMICSLAB_CONFIG_DIR` | Directory holding `config.json` (default: `~/.omicslab`); used by studio sessions to read a container-private config without changing `$HOME` |
|
|
108
116
|
|
|
109
117
|
## Jobs API
|
|
110
118
|
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# bootstrap-env.sh — Install pixi + omicslab SDK from a public tools bucket.
|
|
3
|
+
#
|
|
4
|
+
# Environment variables:
|
|
5
|
+
# SDK_TOOLS_BASE — tools bucket base URL (default: R2 production). Override
|
|
6
|
+
# to point at any public S3-compatible provider (e.g.
|
|
7
|
+
# local S3: http://localhost:4566/omicslab/data/tools).
|
|
8
|
+
# OMICSLAB_HOME — where pixi.toml/lock live (default: $HOME)
|
|
9
|
+
# PIXI_TOML — manifest filename (default: pixi.toml, or {region}.pixi.toml)
|
|
10
|
+
# PIXI_LOCK — lock filename (default: pixi.lock, or {region}.pixi.lock)
|
|
11
|
+
# PIXI_VERSION — pixi release to fetch from the tools bucket
|
|
12
|
+
# (default: v0.76.1)
|
|
13
|
+
# INSTALL_DOCKER — set to 1 for studio jobs to install the docker engine
|
|
14
|
+
# (client + daemon) and start its daemon (default: 0)
|
|
15
|
+
#
|
|
16
|
+
# The script is idempotent: re-running it after a successful install is a
|
|
17
|
+
# no-op (pixi binary present, environment installed, SDK already loaded).
|
|
18
|
+
#
|
|
19
|
+
# It:
|
|
20
|
+
# 1. Installs pixi from the tools bucket (pixi/bin/pixi-<version>-linux-amd64.tar.gz)
|
|
21
|
+
# into an absolute path under $HOME — never hits pixi.sh / GitHub, so it
|
|
22
|
+
# works on air-gapped runners.
|
|
23
|
+
# 2. Downloads the wheel-free manifest from the same bucket.
|
|
24
|
+
# 3. Runs pixi install --frozen (all conda + pypi deps, both cached in the
|
|
25
|
+
# bucket channel/pypi mirrors).
|
|
26
|
+
# 4. Downloads omicslab-latest.whl, extracts version from wheel metadata,
|
|
27
|
+
# renames to a valid PEP 427 filename, pip installs.
|
|
28
|
+
# 5. Grants the pixi interpreter CAP_NET_BIND_SERVICE (falling back to
|
|
29
|
+
# lowering ip_unprivileged_port_start) so the studio TLS proxy can bind
|
|
30
|
+
# the privileged port 443.
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
|
|
33
|
+
TOOLS_BASE="${SDK_TOOLS_BASE:-https://s3-hcm-r2.s3cloud.vn/omicslab/data/tools}"
|
|
34
|
+
OMICSLAB_HOME="${OMICSLAB_HOME:-$HOME}"
|
|
35
|
+
PIXI_TOML="${PIXI_TOML:-pixi.toml}"
|
|
36
|
+
PIXI_LOCK="${PIXI_LOCK:-pixi.lock}"
|
|
37
|
+
PIXI_VERSION="${PIXI_VERSION:-v0.76.1}"
|
|
38
|
+
RETRIES=3
|
|
39
|
+
|
|
40
|
+
# ── 1. pixi (from the tools bucket, absolute path under $HOME) ────────────
|
|
41
|
+
PIXI_BIN="${HOME}/.pixi/bin/pixi"
|
|
42
|
+
if [ ! -x "${PIXI_BIN}" ]; then
|
|
43
|
+
PIXI_URL="$TOOLS_BASE/pixi/bin/pixi-${PIXI_VERSION}-linux-amd64.tar.gz"
|
|
44
|
+
echo "Installing pixi ${PIXI_VERSION} from: $PIXI_URL"
|
|
45
|
+
mkdir -p "$(dirname "${PIXI_BIN}")"
|
|
46
|
+
curl -fsSL "$PIXI_URL" -o /tmp/pixi.tar.gz
|
|
47
|
+
tar -xzf /tmp/pixi.tar.gz -C "$(dirname "${PIXI_BIN}")"
|
|
48
|
+
rm -f /tmp/pixi.tar.gz
|
|
49
|
+
else
|
|
50
|
+
echo "pixi already installed at ${PIXI_BIN}"
|
|
51
|
+
fi
|
|
52
|
+
export PATH="$(dirname "${PIXI_BIN}"):$PATH"
|
|
53
|
+
|
|
54
|
+
# ── 1b. FUSE helper for mountpoint-s3 (non-root mounts) ────────────────────
|
|
55
|
+
# mount-s3 performs unprivileged FUSE mounts via `fusermount3`, which must be
|
|
56
|
+
# root-owned + setuid. The pixi env ships a non-setuid copy under sbin/ (also
|
|
57
|
+
# not on PATH), so use the distro package when it is missing. Best-effort: on
|
|
58
|
+
# air-gapped hosts without apt, mounts still work when run as root.
|
|
59
|
+
if ! command -v fusermount3 >/dev/null 2>&1; then
|
|
60
|
+
if command -v apt-get >/dev/null 2>&1; then
|
|
61
|
+
SUDO=""
|
|
62
|
+
if [ "$(id -u)" -ne 0 ] && command -v sudo >/dev/null 2>&1; then
|
|
63
|
+
SUDO="sudo -n"
|
|
64
|
+
fi
|
|
65
|
+
echo "Installing fuse3 (fusermount3 helper for mountpoint-s3)..."
|
|
66
|
+
$SUDO apt-get update -qq >/dev/null 2>&1 || true
|
|
67
|
+
$SUDO apt-get install -y -qq fuse3 >/dev/null 2>&1 || \
|
|
68
|
+
echo "WARNING: fuse3 install failed; mountpoint-s3 mounts will require root"
|
|
69
|
+
else
|
|
70
|
+
echo "WARNING: no apt-get; mountpoint-s3 non-root mounts will require root"
|
|
71
|
+
fi
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# ── 1c. docker engine (studio jobs) ────────────────────────────────────────
|
|
75
|
+
# Studio jobs start their session container with `docker run`. On the
|
|
76
|
+
# Docker-simulated VM the host daemon answers `docker info` through the
|
|
77
|
+
# mounted /var/run/docker.sock, so only the client is needed; a real cloud VM
|
|
78
|
+
# (CloudFly / GreenNode) has no host socket, so install the engine (client +
|
|
79
|
+
# daemon), start the daemon, and let the job user reach its socket. Gated by
|
|
80
|
+
# INSTALL_DOCKER so analysis jobs pay nothing for it. Best-effort: on
|
|
81
|
+
# air-gapped hosts without apt the install is skipped and `job run --studio`
|
|
82
|
+
# reports the unreachable daemon.
|
|
83
|
+
if [ "${INSTALL_DOCKER:-0}" = "1" ]; then
|
|
84
|
+
SUDO=""
|
|
85
|
+
if [ "$(id -u)" -ne 0 ] && command -v sudo >/dev/null 2>&1; then
|
|
86
|
+
SUDO="sudo -n"
|
|
87
|
+
fi
|
|
88
|
+
if ! command -v docker >/dev/null 2>&1; then
|
|
89
|
+
if command -v apt-get >/dev/null 2>&1; then
|
|
90
|
+
echo "Installing docker engine (client + daemon)..."
|
|
91
|
+
$SUDO apt-get update -qq >/dev/null 2>&1 || true
|
|
92
|
+
$SUDO apt-get install -y -qq docker.io >/dev/null 2>&1 || \
|
|
93
|
+
echo "WARNING: docker install failed; the studio session cannot start"
|
|
94
|
+
else
|
|
95
|
+
echo "WARNING: no apt-get; cannot install docker for the studio session"
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
# Start the daemon: systemd where available, else a detached dockerd.
|
|
99
|
+
# Check for the socket (not `docker info`) so a permission problem on the
|
|
100
|
+
# socket is not mistaken for a stopped daemon and does not spawn a second
|
|
101
|
+
# dockerd next to the systemd unit.
|
|
102
|
+
if command -v systemctl >/dev/null 2>&1; then
|
|
103
|
+
$SUDO systemctl enable --now docker >/dev/null 2>&1 || true
|
|
104
|
+
fi
|
|
105
|
+
if [ ! -S /var/run/docker.sock ] && command -v dockerd >/dev/null 2>&1; then
|
|
106
|
+
echo "Starting dockerd..."
|
|
107
|
+
$SUDO dockerd >/dev/null 2>&1 &
|
|
108
|
+
fi
|
|
109
|
+
for _ in $(seq 1 15); do
|
|
110
|
+
[ -S /var/run/docker.sock ] && break
|
|
111
|
+
sleep 1
|
|
112
|
+
done
|
|
113
|
+
# The daemon socket is root:docker mode 0660. The studio agent runs as the
|
|
114
|
+
# (possibly non-root) SSH user and reuses this very session, so `usermod`
|
|
115
|
+
# membership alone would only apply to a later login. Grant the socket to
|
|
116
|
+
# the job user's own group for the current session instead — least
|
|
117
|
+
# privilege, never world-writable. The studio container itself is started
|
|
118
|
+
# with --security-opt no-new-privileges and without the host socket mounted,
|
|
119
|
+
# so the web app inside it cannot reach this daemon.
|
|
120
|
+
if [ "$(id -u)" -ne 0 ]; then
|
|
121
|
+
$SUDO usermod -aG docker "$USER" >/dev/null 2>&1 || true
|
|
122
|
+
$SUDO chgrp "$(id -gn)" /var/run/docker.sock >/dev/null 2>&1 || true
|
|
123
|
+
$SUDO chmod 660 /var/run/docker.sock >/dev/null 2>&1 || true
|
|
124
|
+
fi
|
|
125
|
+
if docker info >/dev/null 2>&1; then
|
|
126
|
+
echo "docker daemon ready"
|
|
127
|
+
else
|
|
128
|
+
echo "WARNING: docker daemon not reachable after install"
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# ── 2. manifest (from the tools bucket) ────────────────────────────────────
|
|
133
|
+
TOML_URL="$TOOLS_BASE/pixi/config/$PIXI_TOML"
|
|
134
|
+
LOCK_URL="$TOOLS_BASE/pixi/config/$PIXI_LOCK"
|
|
135
|
+
echo "Fetching manifest:"
|
|
136
|
+
echo " TOML: $TOML_URL"
|
|
137
|
+
echo " LOCK: $LOCK_URL"
|
|
138
|
+
mkdir -p "$OMICSLAB_HOME"
|
|
139
|
+
curl -fsSL "$TOML_URL" -o "$OMICSLAB_HOME/pixi.toml"
|
|
140
|
+
curl -fsSL "$LOCK_URL" -o "$OMICSLAB_HOME/pixi.lock"
|
|
141
|
+
|
|
142
|
+
# ── 3. pixi install + SDK wheel (with retries) ────────────────────────────
|
|
143
|
+
cd "$OMICSLAB_HOME"
|
|
144
|
+
for i in $(seq 1 $RETRIES); do
|
|
145
|
+
if "${PIXI_BIN}" install --frozen; then
|
|
146
|
+
break
|
|
147
|
+
fi
|
|
148
|
+
echo "bootstrap-retry-$i"
|
|
149
|
+
sleep 5
|
|
150
|
+
done
|
|
151
|
+
|
|
152
|
+
# Download the stable alias and resolve the real version from wheel
|
|
153
|
+
# metadata so pip gets a valid PEP 427 filename.
|
|
154
|
+
echo "Installing omicslab SDK wheel..."
|
|
155
|
+
curl -fsSL "$TOOLS_BASE/omicslab-latest.whl" -o /tmp/omicslab.whl
|
|
156
|
+
VERSION=$("${PIXI_BIN}" run python3 -c "
|
|
157
|
+
import zipfile, os
|
|
158
|
+
z = zipfile.ZipFile('/tmp/omicslab.whl')
|
|
159
|
+
meta = [n for n in z.namelist() if n.endswith('.dist-info/METADATA')][0]
|
|
160
|
+
ver = [l.split(': ',1)[1] for l in z.read(meta).decode().splitlines() if l.startswith('Version:')][0]
|
|
161
|
+
os.rename('/tmp/omicslab.whl', f'/tmp/omicslab-{ver}-py3-none-any.whl')
|
|
162
|
+
print(ver)
|
|
163
|
+
")
|
|
164
|
+
WHEEL_PATH="/tmp/omicslab-${VERSION}-py3-none-any.whl"
|
|
165
|
+
WHEEL_SHA256=$(sha256sum "$WHEEL_PATH" | awk '{print $1}')
|
|
166
|
+
if "${PIXI_BIN}" run python -m pip install --no-deps "$WHEEL_PATH"; then
|
|
167
|
+
# Record the installed wheel so the runner auto-updater can tell whether
|
|
168
|
+
# the tools bucket has a newer build (by SHA-256, not just version).
|
|
169
|
+
RECORD_DIR="${HOME}/.omicslab"
|
|
170
|
+
mkdir -p "$RECORD_DIR"
|
|
171
|
+
cat > "$RECORD_DIR/install.json" <<JSON
|
|
172
|
+
{
|
|
173
|
+
"version": "${VERSION}",
|
|
174
|
+
"sha256": "${WHEEL_SHA256}",
|
|
175
|
+
"wheel": "omicslab-${VERSION}-py3-none-any.whl",
|
|
176
|
+
"tools_base": "${TOOLS_BASE}",
|
|
177
|
+
"installed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
178
|
+
}
|
|
179
|
+
JSON
|
|
180
|
+
chmod 600 "$RECORD_DIR/install.json"
|
|
181
|
+
rm -f "$WHEEL_PATH"
|
|
182
|
+
echo "SDK ${VERSION} installed successfully"
|
|
183
|
+
else
|
|
184
|
+
echo "Bootstrap failed after $RETRIES attempts"
|
|
185
|
+
exit 1
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# ── 4. studio direct access: allow binding the privileged port 443 ─────────
|
|
189
|
+
# A studio session's in-VM TLS proxy binds 0.0.0.0:443 so
|
|
190
|
+
# {job_id}.<studio-domain> resolves straight to this host. An unprivileged
|
|
191
|
+
# user cannot bind ports below 1024 by default, so grant the pixi interpreter
|
|
192
|
+
# CAP_NET_BIND_SERVICE — a targeted, per-binary capability with no system-wide
|
|
193
|
+
# effect. Where setcap is unavailable (or the filesystem rejects capabilities)
|
|
194
|
+
# fall back to lowering the kernel's unprivileged port floor. This is
|
|
195
|
+
# provider-agnostic: it runs on every Linux host the SDK is bootstrapped on.
|
|
196
|
+
# Best-effort — `job run --studio` still starts, and the proxy logs an
|
|
197
|
+
# actionable error at startup if 443 turns out not to be bindable.
|
|
198
|
+
PIXI_PY="$(readlink -f "${OMICSLAB_HOME}/.pixi/envs/default/bin/python" 2>/dev/null || true)"
|
|
199
|
+
if [ ! -x "${PIXI_PY}" ]; then
|
|
200
|
+
PIXI_PY="$("${PIXI_BIN}" run python -c 'import sys; print(sys.executable)' 2>/dev/null | tail -n1)"
|
|
201
|
+
PIXI_PY="$(readlink -f "${PIXI_PY}" 2>/dev/null || true)"
|
|
202
|
+
fi
|
|
203
|
+
if [ -z "${PIXI_PY}" ] || [ ! -x "${PIXI_PY}" ]; then
|
|
204
|
+
echo "WARNING: could not locate the pixi python; studio proxy may be unable to bind 443"
|
|
205
|
+
else
|
|
206
|
+
SUDO_CAP=""
|
|
207
|
+
if [ "$(id -u)" -ne 0 ] && command -v sudo >/dev/null 2>&1; then
|
|
208
|
+
SUDO_CAP="sudo -n"
|
|
209
|
+
fi
|
|
210
|
+
if ! command -v setcap >/dev/null 2>&1 && command -v apt-get >/dev/null 2>&1; then
|
|
211
|
+
$SUDO_CAP apt-get update -qq >/dev/null 2>&1 || true
|
|
212
|
+
$SUDO_CAP apt-get install -y -qq libcap2-bin >/dev/null 2>&1 || true
|
|
213
|
+
fi
|
|
214
|
+
if command -v setcap >/dev/null 2>&1 && \
|
|
215
|
+
$SUDO_CAP setcap cap_net_bind_service=+ep "${PIXI_PY}" 2>/dev/null; then
|
|
216
|
+
echo "Granted CAP_NET_BIND_SERVICE to ${PIXI_PY}"
|
|
217
|
+
elif $SUDO_CAP sysctl -w net.ipv4.ip_unprivileged_port_start=0 >/dev/null 2>&1; then
|
|
218
|
+
echo "net.ipv4.ip_unprivileged_port_start=0" | \
|
|
219
|
+
$SUDO_CAP tee /etc/sysctl.d/99-omicslab-studio.conf >/dev/null 2>&1 || true
|
|
220
|
+
echo "Lowered net.ipv4.ip_unprivileged_port_start to 0"
|
|
221
|
+
else
|
|
222
|
+
echo "WARNING: could not enable binding port 443; direct studio access may be unavailable"
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.5.1"
|