| #!/usr/bin/env bash |
| set -euo pipefail |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| : "${CONFIG_DATASET:?CONFIG_DATASET is required, e.g. yourname/ubuntu}" |
| : "${HF_TOKEN:?HF_TOKEN secret is required}" |
| : "${CODE_SERVER_PASSWORD:?CODE_SERVER_PASSWORD secret is required}" |
| : "${BASIC_AUTH_USER:?BASIC_AUTH_USER variable is required}" |
| : "${BASIC_AUTH_PASSWORD:?BASIC_AUTH_PASSWORD secret is required}" |
| : "${SYNC_INTERVAL_SECONDS:=300}" |
|
|
| |
| PY="/opt/venv/bin/python" |
|
|
| |
| export HOME="/home/coder" |
|
|
| |
| export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-$HOME/.npm}" |
| export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-$HOME/.npm-global}" |
| export PATH="$HOME/.npm-global/bin:$PATH" |
| mkdir -p "$NPM_CONFIG_CACHE" "$NPM_CONFIG_PREFIX" |
| chown -R coder:coder "$HOME" || true |
|
|
| |
| cat >/etc/profile.d/00-dev-env.sh <<'EOF' |
| export HOME=/home/coder |
| export NPM_CONFIG_PREFIX="$HOME/.npm-global" |
| export PATH="$HOME/.npm-global/bin:$PATH" |
| EOF |
| chmod +x /etc/profile.d/00-dev-env.sh |
|
|
| |
| if ! grep -q 'npm-global/bin' /home/coder/.bashrc 2>/dev/null; then |
| cat >>/home/coder/.bashrc <<'EOF' |
|
|
| |
| export HOME=/home/coder |
| export NPM_CONFIG_PREFIX="$HOME/.npm-global" |
| case ":$PATH:" in |
| *":$HOME/.npm-global/bin:"*) ;; |
| *) export PATH="$HOME/.npm-global/bin:$PATH" ;; |
| esac |
| |
| EOF |
| fi |
| chown coder:coder /home/coder/.bashrc 2>/dev/null || true |
|
|
| |
| |
| export HF_TOKEN="${HF_TOKEN}" |
| export HF_HOME="/tmp/hf_home" |
| export HF_TOKEN_PATH="/tmp/hf_home/token" |
| export HF_HUB_CACHE="/tmp/hf_home/hub" |
| export HF_ASSETS_CACHE="/tmp/hf_home/assets" |
|
|
| rm -rf "${HF_HOME}" 2>/dev/null || true |
| mkdir -p "${HF_HUB_CACHE}" "${HF_ASSETS_CACHE}" |
| chmod -R 777 "${HF_HOME}" 2>/dev/null || true |
|
|
| echo "[debug] Using PY=${PY}" |
| "${PY}" -c "import huggingface_hub; print('[debug] huggingface_hub=', huggingface_hub.__version__)" |
|
|
| |
| PERSIST="/persist_repo" |
| mkdir -p "${PERSIST}" |
|
|
| echo "[boot] Pull dataset snapshot -> ${PERSIST}" |
| "${PY}" /sync_home.py pull --repo "${CONFIG_DATASET}" --dst "${PERSIST}" |
|
|
| |
| |
| if [ -d "${PERSIST}/home" ]; then |
| echo "[boot] Restore ${PERSIST}/home -> ${HOME} (NO delete)" |
| "${PY}" /sync_home.py rsync_in --src "${PERSIST}/home" --dst "${HOME}" |
| else |
| echo "[boot] Dataset has no 'home/' yet. Initializing..." |
| mkdir -p "${PERSIST}/home" |
| fi |
|
|
| |
| chown -R coder:coder "${HOME}" || true |
|
|
| |
| |
| if [ "${RESET_CLI_CONFIG:-0}" = "1" ]; then |
| if [ ! -d "${PERSIST}/home/.claude" ]; then rm -rf /home/coder/.claude 2>/dev/null || true; fi |
| if [ ! -d "${PERSIST}/home/.codex" ]; then rm -rf /home/coder/.codex 2>/dev/null || true; fi |
| if [ ! -f "${PERSIST}/home/.claude.json" ]; then rm -f /home/coder/.claude.json 2>/dev/null || true; fi |
| fi |
|
|
| |
| if [ -d "${PERSIST}/home/.claude" ]; then |
| echo "[fix] Restore ~/.claude from dataset (authoritative)" |
| mkdir -p /home/coder/.claude |
| rsync -a --delete "${PERSIST}/home/.claude/" "/home/coder/.claude/" |
| chown -R coder:coder /home/coder/.claude || true |
| else |
| echo "[fix] Dataset has no .claude -> skip restore" |
| fi |
|
|
| if [ -d "${PERSIST}/home/.codex" ]; then |
| echo "[fix] Restore ~/.codex from dataset (authoritative)" |
| mkdir -p /home/coder/.codex |
| rsync -a --delete "${PERSIST}/home/.codex/" "/home/coder/.codex/" |
| chown -R coder:coder /home/coder/.codex || true |
| else |
| echo "[fix] Dataset has no .codex -> skip restore" |
| fi |
|
|
| |
| |
| |
| if [ ! -f /home/coder/.claude.json ]; then |
| cat >/home/coder/.claude.json <<'EOF' |
| { "hasCompletedOnboarding": true } |
| EOF |
| else |
| |
| if ! grep -q '"hasCompletedOnboarding"[[:space:]]*:[[:space:]]*true' /home/coder/.claude.json; then |
| cat >/home/coder/.claude.json <<'EOF' |
| { "hasCompletedOnboarding": true } |
| EOF |
| fi |
| fi |
| chown coder:coder /home/coder/.claude.json || true |
|
|
| |
| mkdir -p /home/coder/.codex |
| if [ ! -f /home/coder/.codex/config.toml ]; then |
| cat >/home/coder/.codex/config.toml <<'EOF' |
| |
| |
| model_provider = "openai" |
| |
| |
| |
| EOF |
| fi |
| chown -R coder:coder /home/coder/.codex || true |
|
|
| |
| rm -rf /root/.claude /root/.codex 2>/dev/null || true |
| ln -sfn /home/coder/.claude /root/.claude |
| ln -sfn /home/coder/.codex /root/.codex |
| ln -sfn /home/coder/.claude.json /root/.claude.json |
|
|
| |
| CODEX_BIN="/home/coder/.npm-global/lib/node_modules/@cometix/codex/vendor/x86_64-unknown-linux-musl/codex/codex" |
| if [ -f "$CODEX_BIN" ]; then |
| echo "[fix] chmod +x codex vendor binary: $CODEX_BIN" |
| chmod 755 "$CODEX_BIN" || true |
| chmod 755 "$(dirname "$CODEX_BIN")" 2>/dev/null || true |
| fi |
|
|
| |
| CLAUDE_JS="/home/coder/.npm-global/lib/node_modules/@anthropic-ai/claude-code/cli.js" |
| CODEX_JS="/home/coder/.npm-global/lib/node_modules/@cometix/codex/bin/codex.js" |
|
|
| cat >/usr/local/bin/claude <<EOF |
| #!/usr/bin/env bash |
| exec /usr/bin/node "${CLAUDE_JS}" "\$@" |
| EOF |
| chmod 755 /usr/local/bin/claude |
|
|
| cat >/usr/local/bin/codex <<EOF |
| #!/usr/bin/env bash |
| exec /usr/bin/node "${CODEX_JS}" "\$@" |
| EOF |
| chmod 755 /usr/local/bin/codex |
|
|
| |
| echo "Adding password for user ${BASIC_AUTH_USER}" |
| htpasswd -bc /etc/nginx/.htpasswd "${BASIC_AUTH_USER}" "${BASIC_AUTH_PASSWORD}" |
|
|
| if [ -f /etc/nginx/templates/nginx.conf.template ]; then |
| cp /etc/nginx/templates/nginx.conf.template /etc/nginx/nginx.conf |
| fi |
|
|
| |
| USER_DATA_DIR="/home/coder/.local/share/code-server" |
| EXT_DIR="/home/coder/.local/share/code-server/extensions" |
| mkdir -p "${USER_DATA_DIR}/User" "${EXT_DIR}" |
| chown -R coder:coder "${USER_DATA_DIR}" "${EXT_DIR}" || true |
|
|
| |
| |
| SETTINGS_JSON="${USER_DATA_DIR}/User/settings.json" |
| mkdir -p "${USER_DATA_DIR}/User" |
| chown -R coder:coder "${USER_DATA_DIR}/User" || true |
|
|
| if [ ! -f "${SETTINGS_JSON}" ]; then |
| cat > "${SETTINGS_JSON}" <<'EOF' |
| { |
| "terminal.integrated.defaultProfile.linux": "SAFE_BASH", |
| "terminal.integrated.profiles.linux": { |
| "SAFE_BASH": { "path": "/bin/bash", "args": ["--noprofile", "--norc"] } |
| }, |
| "terminal.integrated.cwd": "/home/coder", |
| "terminal.integrated.env.linux": { |
| "HOME": "/home/coder", |
| "NPM_CONFIG_PREFIX": "/home/coder/.npm-global", |
| "PATH": "/home/coder/.npm-global/bin:${env:PATH}" |
| }, |
| "window.restoreWindows": "none" |
| } |
| EOF |
| chown coder:coder "${SETTINGS_JSON}" || true |
| else |
| echo "[boot] VS Code settings.json exists -> keep user customizations (no overwrite)" |
| fi |
|
|
| |
| export PASSWORD="${CODE_SERVER_PASSWORD}" |
| echo "[boot] Start code-server with explicit user-data-dir/extensions-dir" |
| su -p coder -c "export HOME=/home/coder; export PATH=/home/coder/.npm-global/bin:\$PATH; \ |
| /usr/bin/code-server \ |
| --bind-addr 127.0.0.1:8080 \ |
| --auth password \ |
| --ignore-last-opened \ |
| --user-data-dir /home/coder/.local/share/code-server \ |
| --extensions-dir /home/coder/.local/share/code-server/extensions \ |
| /home/coder" & |
| CODE_PID=$! |
|
|
| |
| nginx -g "daemon off;" & |
| NGINX_PID=$! |
|
|
| |
| "${PY}" /sync_home.py daemon \ |
| --repo "${CONFIG_DATASET}" \ |
| --home "${HOME}" \ |
| --persist "${PERSIST}" \ |
| --interval "${SYNC_INTERVAL_SECONDS}" & |
| SYNC_PID=$! |
|
|
| final_sync() { |
| echo "[sync] Final sync..." |
| "${PY}" /sync_home.py push --repo "${CONFIG_DATASET}" --home "${HOME}" --persist "${PERSIST}" || true |
| } |
|
|
| shutdown() { |
| echo "[signal] termination received" |
| final_sync |
| kill "${SYNC_PID}" 2>/dev/null || true |
| kill "${CODE_PID}" 2>/dev/null || true |
| kill "${NGINX_PID}" 2>/dev/null || true |
| exit 0 |
| } |
|
|
| trap shutdown SIGTERM SIGINT |
|
|
| wait "${NGINX_PID}" |