omicslab 2.0.0__tar.gz → 2.2.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.
- {omicslab-2.0.0 → omicslab-2.2.0}/Makefile +0 -2
- {omicslab-2.0.0 → omicslab-2.2.0}/PKG-INFO +1 -1
- {omicslab-2.0.0 → omicslab-2.2.0}/README.developer.md +3 -3
- omicslab-2.2.0/scripts/bootstrap-env.sh +224 -0
- omicslab-2.2.0/src/omicslab/_version.py +1 -0
- omicslab-2.2.0/src/omicslab/cli/job_commands.py +441 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/main.py +99 -7
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/runner_commands.py +7 -1
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/studio_commands.py +53 -21
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/client.py +32 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_data_prep.py +20 -12
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_exec.py +23 -1
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_git.py +57 -5
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_monitor.py +23 -5
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_nextflow.py +40 -16
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_proxy.py +9 -1
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_storage.py +61 -7
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_studio_prep.py +18 -8
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/analysis.py +50 -17
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/studio.py +116 -29
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/client.py +22 -3
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/executor.py +11 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/storage.py +109 -2
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/e2e/conftest.py +19 -9
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__data_prep.py +80 -5
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__engine_helpers.py +147 -1
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__git.py +81 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__monitor.py +4 -1
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__nextflow.py +42 -5
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__storage.py +59 -0
- omicslab-2.2.0/tests/job/_run/test__studio_docker.py +107 -0
- omicslab-2.2.0/tests/job/_run/test_studio_proxy_start.py +85 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_client.py +23 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_client_extra.py +41 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_executor_extra.py +13 -0
- omicslab-2.2.0/tests/job/test_snapshot_request.py +37 -0
- omicslab-2.2.0/tests/job/test_studio_image_download.py +66 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli_storage.py +37 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_client.py +24 -0
- omicslab-2.2.0/tests/test_job_heartbeat.py +84 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_resources.py +111 -3
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_runner_helpers.py +3 -0
- omicslab-2.2.0/tests/test_studio_snapshot.py +224 -0
- omicslab-2.2.0/tests/test_system_monitor_interval.py +36 -0
- omicslab-2.0.0/scripts/bootstrap-env.sh +0 -103
- omicslab-2.0.0/src/omicslab/_version.py +0 -1
- omicslab-2.0.0/src/omicslab/cli/job_commands.py +0 -238
- omicslab-2.0.0/tests/test_studio_snapshot.py +0 -106
- {omicslab-2.0.0 → omicslab-2.2.0}/.gitignore +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/README.md +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/.gitignore +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/hcm.pixi.lock +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/hcm.pixi.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/pixi.lock +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/pixi.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/us-east-1.pixi.lock +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/us-east-1.pixi.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/pixi.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/prod/hcm.pixi.lock +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/prod/hcm.pixi.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/prod/pixi.lock +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/prod/pixi.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/envs/regions.json +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/pixi.config.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/pyproject.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/scripts/check_publish.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/scripts/write_version_manifest.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/__init__.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/__init__.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/analysis_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/auth_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/context_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/credential_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/fuse_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/org_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/setup_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/token_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/utils.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/workspace_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/ws_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/config.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/exceptions.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/__init__.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/__init__.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_auth.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_config_download.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_consts.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_env.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_images.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_log.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_params.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_proc.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_spec.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/console.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/paths.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/system.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/__init__.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/analysis.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/base.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/credentials.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/jobs.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/orgs.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/runners.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/studio.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/tokens.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/workspaces.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/selfupdate.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/__init__.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/conftest.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/e2e/__init__.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/e2e/test_cli_e2e.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/e2e/test_tokens.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/fixtures/nfcore-demo-custom.config +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/fixtures/studio_specs/jupyter.json +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/fixtures/studio_specs/rstudio.json +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__auth.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__env.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__get_images_cli.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__images.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__log.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__params.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__proxy.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__proxy_extra.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__spec.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_cli.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_executor.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_fuse.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_resource.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_system.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_studio_validate_cli.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_ws_helpers.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli_catalog.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli_context.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli_utils.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_fuse.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_job_exec.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_misc.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_runner_self_update.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_sandbox_report.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_selfupdate.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_ws_commands.py +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/input.txt +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/main.nf +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/omicslab/main.sh +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/pixi.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/schema.json +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-simple/omicslab/main.sh +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-simple/pixi.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-simple/schema.json +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/Snakefile +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/input.txt +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/omicslab/main.sh +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/pixi.toml +0 -0
- {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/schema.json +0 -0
|
@@ -191,7 +191,7 @@ This:
|
|
|
191
191
|
4. Polls `http://localhost:8000/api/v1/health` until healthy.
|
|
192
192
|
|
|
193
193
|
Notes:
|
|
194
|
-
- **Storage is S3-compatible (SeaweedFS).** `
|
|
194
|
+
- **Storage is S3-compatible (SeaweedFS).** `AWS_ENDPOINT_URL=http://localhost:4566`
|
|
195
195
|
(from `.env.test`) with credentials `test`/`testtest` (`AWS_ACCESS_KEY_ID` /
|
|
196
196
|
`AWS_SECRET_ACCESS_KEY`).
|
|
197
197
|
- `APP_ENV=test` is set so the backend keeps the DB hermetic and uses test env
|
|
@@ -308,7 +308,7 @@ CI re-upload the wheel whenever SDK code changes without regenerating locks,
|
|
|
308
308
|
and refresh the pixi cache only when `sdk/envs/pixi.toml` changes (see the
|
|
309
309
|
gated `upload-bootstrap-env` target in the root `Makefile`).
|
|
310
310
|
|
|
311
|
-
Config overrides (env vars): `
|
|
311
|
+
Config overrides (env vars): `AWS_ENDPOINT_URL`, `AWS_REGION`, `S3_ENDPOINT_URL`, `S3_BUCKET_NAME`, `S3_PREFIX`, `S3_REGION`.
|
|
312
312
|
|
|
313
313
|
### 8.1 Runner SDK self-update
|
|
314
314
|
|
|
@@ -489,7 +489,7 @@ Cloud tests are slow (VM boot + pixi install); use generous timeouts.
|
|
|
489
489
|
- **Token scope == access.** An org-scoped token can't touch other orgs. Bootstrap
|
|
490
490
|
fresh users/orgs for isolated scenarios.
|
|
491
491
|
- **Storage is S3-compatible (SeaweedFS)** at `:4566`. Use `AWS_ACCESS_KEY_ID=test`,
|
|
492
|
-
`AWS_SECRET_ACCESS_KEY=testtest`, `
|
|
492
|
+
`AWS_SECRET_ACCESS_KEY=testtest`, `AWS_ENDPOINT_URL=http://localhost:4566`.
|
|
493
493
|
- **Empty credential listing**: the backend now returns `[]` (via `ok_json_list`)
|
|
494
494
|
even when no credentials exist; older behavior returned an empty body.
|
|
495
495
|
- **Single-get routes** for org/workspace/analysis don't exist; verify via lists.
|
|
@@ -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.2.0"
|