#!/bin/bash set -euo pipefail if [[ "$(id -u)" -ne 0 ]]; then echo "start_server.sh must run as root to prepare the read-only workspace." >&2 exit 1 fi JUPYTER_TOKEN="${JUPYTER_TOKEN:-huggingface}" SCCAFM_SOURCE_URL="${SCCAFM_SOURCE_URL:-https://github.com/Catchxu/scCAFM.git}" SCCAFM_SOURCE_REVISION="${SCCAFM_SOURCE_REVISION:-main}" SCCAFM_MODEL_ID="${SCCAFM_MODEL_ID:-kaichenxu/scCAFM}" SCCAFM_MODEL_REVISION="${SCCAFM_MODEL_REVISION:-main}" SCCAFM_DATASET_ID="${SCCAFM_DATASET_ID:-kaichenxu/scCAFM-data}" SCCAFM_DATASET_REVISION="${SCCAFM_DATASET_REVISION:-main}" readonly SCCAFM_WORKSPACE="/workspace/scCAFM" readonly NOTEBOOK_DIR="$SCCAFM_WORKSPACE" SCCAFM_ASSETS_DIR="$SCCAFM_WORKSPACE/assets" SCCAFM_DATA_DIR="$SCCAFM_WORKSPACE/tutorial_data" JUPYTER_RUNTIME_ROOT="$(mktemp -d /tmp/sccafm-jupyter.XXXXXX)" resolve_hf_revision() { local repo_type="$1" local repo_id="$2" local revision="$3" /home/user/miniconda/bin/python -c ' import sys from huggingface_hub import HfApi repo_type, repo_id, revision = sys.argv[1:] api = HfApi() if repo_type == "model": info = api.model_info(repo_id, revision=revision) elif repo_type == "dataset": info = api.dataset_info(repo_id, revision=revision) else: raise ValueError(f"Unsupported repository type: {repo_type}") print(info.sha) ' "$repo_type" "$repo_id" "$revision" } require_commit_sha() { local name="$1" local revision="$2" if [[ ! "$revision" =~ ^[0-9a-f]{40}$ ]]; then echo "Could not resolve $name to a 40-character commit SHA: $revision" >&2 exit 1 fi } mkdir -p "$(dirname "$SCCAFM_WORKSPACE")" if [[ ! -d "$SCCAFM_WORKSPACE/.git" ]]; then if [[ -e "$SCCAFM_WORKSPACE" ]] && [[ -n "$(ls -A "$SCCAFM_WORKSPACE")" ]]; then echo "Cannot prepare scCAFM: $SCCAFM_WORKSPACE exists and is not an empty Git checkout." >&2 exit 1 fi git clone "$SCCAFM_SOURCE_URL" "$SCCAFM_WORKSPACE" fi git -C "$SCCAFM_WORKSPACE" fetch --depth 1 origin "$SCCAFM_SOURCE_REVISION" git -C "$SCCAFM_WORKSPACE" checkout --detach FETCH_HEAD SCCAFM_SOURCE_RESOLVED_REVISION="$(git -C "$SCCAFM_WORKSPACE" rev-parse HEAD)" require_commit_sha "scCAFM source" "$SCCAFM_SOURCE_RESOLVED_REVISION" pip install --no-deps "$SCCAFM_WORKSPACE" SCCAFM_MODEL_RESOLVED_REVISION="$(resolve_hf_revision model "$SCCAFM_MODEL_ID" "$SCCAFM_MODEL_REVISION")" require_commit_sha "scCAFM model" "$SCCAFM_MODEL_RESOLVED_REVISION" mkdir -p "$SCCAFM_ASSETS_DIR" hf download "$SCCAFM_MODEL_ID" \ --repo-type model \ --revision "$SCCAFM_MODEL_RESOLVED_REVISION" \ --local-dir "$SCCAFM_ASSETS_DIR" SCCAFM_DATASET_RESOLVED_REVISION="$(resolve_hf_revision dataset "$SCCAFM_DATASET_ID" "$SCCAFM_DATASET_REVISION")" require_commit_sha "scCAFM dataset" "$SCCAFM_DATASET_RESOLVED_REVISION" mkdir -p "$SCCAFM_DATA_DIR" hf download "$SCCAFM_DATASET_ID" \ --repo-type dataset \ --revision "$SCCAFM_DATASET_RESOLVED_REVISION" \ --local-dir "$SCCAFM_DATA_DIR" cp /home/user/app/README.md "$SCCAFM_WORKSPACE/SPACE_README.md" { printf 'component\trepository\tselector\tresolved_revision\n' printf 'source\t%s\t%s\t%s\n' "$SCCAFM_SOURCE_URL" "$SCCAFM_SOURCE_REVISION" "$SCCAFM_SOURCE_RESOLVED_REVISION" printf 'model\t%s\t%s\t%s\n' "$SCCAFM_MODEL_ID" "$SCCAFM_MODEL_REVISION" "$SCCAFM_MODEL_RESOLVED_REVISION" printf 'dataset\t%s\t%s\t%s\n' "$SCCAFM_DATASET_ID" "$SCCAFM_DATASET_REVISION" "$SCCAFM_DATASET_RESOLVED_REVISION" } > "$SCCAFM_WORKSPACE/SPACE_VERSIONS.tsv" echo "Resolved source revision: $SCCAFM_SOURCE_RESOLVED_REVISION" echo "Resolved model revision: $SCCAFM_MODEL_RESOLVED_REVISION" echo "Resolved dataset revision: $SCCAFM_DATASET_RESOLVED_REVISION" jupyter labextension disable "@jupyterlab/apputils-extension:announcements" chown user:user "$JUPYTER_RUNTIME_ROOT" chmod 0700 "$JUPYTER_RUNTIME_ROOT" install -d -o user -g user -m 0700 \ "$JUPYTER_RUNTIME_ROOT/cache" \ "$JUPYTER_RUNTIME_ROOT/config" \ "$JUPYTER_RUNTIME_ROOT/data" \ "$JUPYTER_RUNTIME_ROOT/ipython" \ "$JUPYTER_RUNTIME_ROOT/runtime" chown -R root:root "$SCCAFM_WORKSPACE" /home/user /data/huggingface chmod -R a-w "$SCCAFM_WORKSPACE" /home/user /data/huggingface chmod 0755 /tmp /var/tmp if ! runuser -u user -- test -w "$JUPYTER_RUNTIME_ROOT/runtime"; then echo "Refusing to start: the Jupyter runtime directory is not writable by the Jupyter user." >&2 exit 1 fi if runuser -u user -- test -w "$SCCAFM_WORKSPACE"; then echo "Refusing to start: the scCAFM workspace is still writable by the Jupyter user." >&2 exit 1 fi echo "Read-only workspace verified at $SCCAFM_WORKSPACE" exec runuser -u user -- env -i \ HOME=/home/user \ PATH=/home/user/miniconda/bin:/usr/local/bin:/usr/bin:/bin \ PYTHONPATH=/home/user/app \ PYTHONDONTWRITEBYTECODE=1 \ LANG=C.UTF-8 \ LC_ALL=C.UTF-8 \ LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}" \ NVIDIA_VISIBLE_DEVICES="${NVIDIA_VISIBLE_DEVICES:-}" \ NVIDIA_DRIVER_CAPABILITIES="${NVIDIA_DRIVER_CAPABILITIES:-}" \ HF_HOME="$JUPYTER_RUNTIME_ROOT/cache/huggingface" \ HF_HUB_DISABLE_TELEMETRY=1 \ XDG_CACHE_HOME="$JUPYTER_RUNTIME_ROOT/cache" \ JUPYTER_CONFIG_DIR="$JUPYTER_RUNTIME_ROOT/config" \ JUPYTER_DATA_DIR="$JUPYTER_RUNTIME_ROOT/data" \ JUPYTER_RUNTIME_DIR="$JUPYTER_RUNTIME_ROOT/runtime" \ IPYTHONDIR="$JUPYTER_RUNTIME_ROOT/ipython" \ TMPDIR="$JUPYTER_RUNTIME_ROOT" \ /home/user/miniconda/bin/jupyter-lab \ --ip 0.0.0.0 \ --port 7860 \ --no-browser \ --ServerApp.token="$JUPYTER_TOKEN" \ --ServerApp.contents_manager_class=readonly_contents.ReadOnlyContentsManager \ --ServerApp.terminals_enabled=False \ --ServerApp.tornado_settings="{'headers': {'Content-Security-Policy': 'frame-ancestors *'}}" \ --ServerApp.cookie_options="{'SameSite': 'None', 'Secure': True}" \ --ServerApp.disable_check_xsrf=True \ --LabApp.news_url=None \ --LabApp.check_for_updates_class="jupyterlab.NeverCheckForUpdate" \ --notebook-dir="$NOTEBOOK_DIR"