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.
Files changed (155) hide show
  1. {omicslab-2.0.0 → omicslab-2.2.0}/Makefile +0 -2
  2. {omicslab-2.0.0 → omicslab-2.2.0}/PKG-INFO +1 -1
  3. {omicslab-2.0.0 → omicslab-2.2.0}/README.developer.md +3 -3
  4. omicslab-2.2.0/scripts/bootstrap-env.sh +224 -0
  5. omicslab-2.2.0/src/omicslab/_version.py +1 -0
  6. omicslab-2.2.0/src/omicslab/cli/job_commands.py +441 -0
  7. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/main.py +99 -7
  8. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/runner_commands.py +7 -1
  9. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/studio_commands.py +53 -21
  10. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/client.py +32 -0
  11. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_data_prep.py +20 -12
  12. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_exec.py +23 -1
  13. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_git.py +57 -5
  14. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_monitor.py +23 -5
  15. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_nextflow.py +40 -16
  16. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_proxy.py +9 -1
  17. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_storage.py +61 -7
  18. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_studio_prep.py +18 -8
  19. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/analysis.py +50 -17
  20. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/studio.py +116 -29
  21. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/client.py +22 -3
  22. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/executor.py +11 -0
  23. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/storage.py +109 -2
  24. {omicslab-2.0.0 → omicslab-2.2.0}/tests/e2e/conftest.py +19 -9
  25. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__data_prep.py +80 -5
  26. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__engine_helpers.py +147 -1
  27. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__git.py +81 -0
  28. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__monitor.py +4 -1
  29. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__nextflow.py +42 -5
  30. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__storage.py +59 -0
  31. omicslab-2.2.0/tests/job/_run/test__studio_docker.py +107 -0
  32. omicslab-2.2.0/tests/job/_run/test_studio_proxy_start.py +85 -0
  33. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_client.py +23 -0
  34. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_client_extra.py +41 -0
  35. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_executor_extra.py +13 -0
  36. omicslab-2.2.0/tests/job/test_snapshot_request.py +37 -0
  37. omicslab-2.2.0/tests/job/test_studio_image_download.py +66 -0
  38. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli_storage.py +37 -0
  39. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_client.py +24 -0
  40. omicslab-2.2.0/tests/test_job_heartbeat.py +84 -0
  41. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_resources.py +111 -3
  42. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_runner_helpers.py +3 -0
  43. omicslab-2.2.0/tests/test_studio_snapshot.py +224 -0
  44. omicslab-2.2.0/tests/test_system_monitor_interval.py +36 -0
  45. omicslab-2.0.0/scripts/bootstrap-env.sh +0 -103
  46. omicslab-2.0.0/src/omicslab/_version.py +0 -1
  47. omicslab-2.0.0/src/omicslab/cli/job_commands.py +0 -238
  48. omicslab-2.0.0/tests/test_studio_snapshot.py +0 -106
  49. {omicslab-2.0.0 → omicslab-2.2.0}/.gitignore +0 -0
  50. {omicslab-2.0.0 → omicslab-2.2.0}/README.md +0 -0
  51. {omicslab-2.0.0 → omicslab-2.2.0}/envs/.gitignore +0 -0
  52. {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/hcm.pixi.lock +0 -0
  53. {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/hcm.pixi.toml +0 -0
  54. {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/pixi.lock +0 -0
  55. {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/pixi.toml +0 -0
  56. {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/us-east-1.pixi.lock +0 -0
  57. {omicslab-2.0.0 → omicslab-2.2.0}/envs/dev/us-east-1.pixi.toml +0 -0
  58. {omicslab-2.0.0 → omicslab-2.2.0}/envs/pixi.toml +0 -0
  59. {omicslab-2.0.0 → omicslab-2.2.0}/envs/prod/hcm.pixi.lock +0 -0
  60. {omicslab-2.0.0 → omicslab-2.2.0}/envs/prod/hcm.pixi.toml +0 -0
  61. {omicslab-2.0.0 → omicslab-2.2.0}/envs/prod/pixi.lock +0 -0
  62. {omicslab-2.0.0 → omicslab-2.2.0}/envs/prod/pixi.toml +0 -0
  63. {omicslab-2.0.0 → omicslab-2.2.0}/envs/regions.json +0 -0
  64. {omicslab-2.0.0 → omicslab-2.2.0}/pixi.config.toml +0 -0
  65. {omicslab-2.0.0 → omicslab-2.2.0}/pyproject.toml +0 -0
  66. {omicslab-2.0.0 → omicslab-2.2.0}/scripts/check_publish.py +0 -0
  67. {omicslab-2.0.0 → omicslab-2.2.0}/scripts/write_version_manifest.py +0 -0
  68. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/__init__.py +0 -0
  69. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/__init__.py +0 -0
  70. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/analysis_commands.py +0 -0
  71. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/auth_commands.py +0 -0
  72. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/context_commands.py +0 -0
  73. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/credential_commands.py +0 -0
  74. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/fuse_commands.py +0 -0
  75. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/org_commands.py +0 -0
  76. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/setup_commands.py +0 -0
  77. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/token_commands.py +0 -0
  78. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/utils.py +0 -0
  79. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/workspace_commands.py +0 -0
  80. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/cli/ws_commands.py +0 -0
  81. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/config.py +0 -0
  82. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/exceptions.py +0 -0
  83. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/__init__.py +0 -0
  84. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/__init__.py +0 -0
  85. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_auth.py +0 -0
  86. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_config_download.py +0 -0
  87. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_consts.py +0 -0
  88. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_env.py +0 -0
  89. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_images.py +0 -0
  90. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_log.py +0 -0
  91. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_params.py +0 -0
  92. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_proc.py +0 -0
  93. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/_run/_spec.py +0 -0
  94. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/console.py +0 -0
  95. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/paths.py +0 -0
  96. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/job/system.py +0 -0
  97. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/__init__.py +0 -0
  98. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/analysis.py +0 -0
  99. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/base.py +0 -0
  100. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/credentials.py +0 -0
  101. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/jobs.py +0 -0
  102. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/orgs.py +0 -0
  103. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/runners.py +0 -0
  104. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/studio.py +0 -0
  105. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/tokens.py +0 -0
  106. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/resources/workspaces.py +0 -0
  107. {omicslab-2.0.0 → omicslab-2.2.0}/src/omicslab/selfupdate.py +0 -0
  108. {omicslab-2.0.0 → omicslab-2.2.0}/tests/__init__.py +0 -0
  109. {omicslab-2.0.0 → omicslab-2.2.0}/tests/conftest.py +0 -0
  110. {omicslab-2.0.0 → omicslab-2.2.0}/tests/e2e/__init__.py +0 -0
  111. {omicslab-2.0.0 → omicslab-2.2.0}/tests/e2e/test_cli_e2e.py +0 -0
  112. {omicslab-2.0.0 → omicslab-2.2.0}/tests/e2e/test_tokens.py +0 -0
  113. {omicslab-2.0.0 → omicslab-2.2.0}/tests/fixtures/nfcore-demo-custom.config +0 -0
  114. {omicslab-2.0.0 → omicslab-2.2.0}/tests/fixtures/studio_specs/jupyter.json +0 -0
  115. {omicslab-2.0.0 → omicslab-2.2.0}/tests/fixtures/studio_specs/rstudio.json +0 -0
  116. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__auth.py +0 -0
  117. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__env.py +0 -0
  118. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__get_images_cli.py +0 -0
  119. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__images.py +0 -0
  120. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__log.py +0 -0
  121. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__params.py +0 -0
  122. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__proxy.py +0 -0
  123. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__proxy_extra.py +0 -0
  124. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/_run/test__spec.py +0 -0
  125. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_cli.py +0 -0
  126. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_executor.py +0 -0
  127. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_fuse.py +0 -0
  128. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_resource.py +0 -0
  129. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_job_system.py +0 -0
  130. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_studio_validate_cli.py +0 -0
  131. {omicslab-2.0.0 → omicslab-2.2.0}/tests/job/test_ws_helpers.py +0 -0
  132. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli.py +0 -0
  133. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli_catalog.py +0 -0
  134. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli_context.py +0 -0
  135. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_cli_utils.py +0 -0
  136. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_fuse.py +0 -0
  137. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_job_exec.py +0 -0
  138. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_misc.py +0 -0
  139. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_runner_self_update.py +0 -0
  140. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_sandbox_report.py +0 -0
  141. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_selfupdate.py +0 -0
  142. {omicslab-2.0.0 → omicslab-2.2.0}/tests/test_ws_commands.py +0 -0
  143. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/input.txt +0 -0
  144. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/main.nf +0 -0
  145. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/omicslab/main.sh +0 -0
  146. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/pixi.toml +0 -0
  147. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-nextflow/schema.json +0 -0
  148. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-simple/omicslab/main.sh +0 -0
  149. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-simple/pixi.toml +0 -0
  150. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-simple/schema.json +0 -0
  151. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/Snakefile +0 -0
  152. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/input.txt +0 -0
  153. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/omicslab/main.sh +0 -0
  154. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/pixi.toml +0 -0
  155. {omicslab-2.0.0 → omicslab-2.2.0}/tests/tools/hello-snakemake/schema.json +0 -0
@@ -1,6 +1,4 @@
1
1
  # === Config (all overridable via environment / CLI) ===
2
- ENDPOINT_URL ?= http://localhost:4566
3
- REGION_NAME ?= us-east-1
4
2
 
5
3
  S3_BUCKET_NAME ?= omicslab
6
4
  S3_PREFIX ?= data/tools
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omicslab
3
- Version: 2.0.0
3
+ Version: 2.2.0
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
@@ -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).** `ENDPOINT_URL=http://localhost:4566`
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): `ENDPOINT_URL`, `S3_ENDPOINT_URL`, `S3_BUCKET_NAME`, `S3_PREFIX`, `S3_REGION`.
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`, `ENDPOINT_URL=http://localhost:4566`.
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"