2020
2121# Git repository clone script for JupyterHub init container.
2222#
23- # Clones a repository onto the user's home PVC. The clone directory is
24- # cleaned up by a preStop lifecycle hook when the session ends, so the
25- # repository does not persist between sessions .
23+ # Clones a repository onto the user's home PVC. Existing clone directories are
24+ # reused or replaced only when repo-external AUPLC metadata proves they are
25+ # managed by this script .
2626#
2727# Environment variables (required):
2828# REPO_URL - HTTPS URL of the git repository to clone
3131#
3232# Environment variables (optional):
3333# BRANCH - Branch or tag to check out (default: repository's default branch)
34+ # PERSIST_CLONED_REPO - true to reuse a compatible managed clone (default: true)
35+ # AUPLC_GIT_METADATA_DIR - Directory for repo-external clone metadata
3436
3537export HOME=/tmp
3638
@@ -43,38 +45,173 @@ git config --global http.sslVerify true
4345git config --global user.email jupyterhub@local
4446git config --global user.name JupyterHub
4547
48+ case " ${PERSIST_CLONED_REPO:- true} " in
49+ true|True|TRUE|1|yes|Yes|YES)
50+ _persistence_mode=" persistent"
51+ ;;
52+ false|False|FALSE|0|no|No|NO)
53+ _persistence_mode=" ephemeral"
54+ ;;
55+ * )
56+ echo " Invalid PERSIST_CLONED_REPO value. Use true or false."
57+ exit 1
58+ ;;
59+ esac
60+
61+ _metadata_dir=${AUPLC_GIT_METADATA_DIR:- " $( dirname " $CLONE_DIR " ) /.auplc/git-clones" }
62+ _clone_base=$( basename " $CLONE_DIR " | tr -c ' A-Za-z0-9._-' ' _' | sed ' s/^_*//; s/_*$//' )
63+ if [ -z " $_clone_base " ]; then
64+ _clone_base=" clone"
65+ fi
66+ _clone_hash=$( printf ' %s' " $CLONE_DIR " | cksum | cut -d ' ' -f 1)
67+ _metadata_file=" $_metadata_dir /${_clone_base} -${_clone_hash} .metadata"
68+ _metadata_version=" 1"
69+ _branch_value=${BRANCH:- }
70+
71+ sanitize_repo_url () {
72+ _url=$1
73+ case " $_url " in
74+ https://* )
75+ _sanitize_scheme=" https://"
76+ _sanitize_rest=${_url# https:// }
77+ ;;
78+ http://* )
79+ _sanitize_scheme=" http://"
80+ _sanitize_rest=${_url# http:// }
81+ ;;
82+ * )
83+ printf ' %s' " $_url "
84+ return 0
85+ ;;
86+ esac
87+
88+ _sanitize_authority=${_sanitize_rest%%/* }
89+ case " $_sanitize_authority " in
90+ * @* )
91+ _sanitize_authority=${_sanitize_authority##*@ }
92+ ;;
93+ * )
94+ ;;
95+ esac
96+
97+ case " $_sanitize_rest " in
98+ * /* )
99+ printf ' %s%s/%s' " $_sanitize_scheme " " $_sanitize_authority " " ${_sanitize_rest#*/ } "
100+ ;;
101+ * )
102+ printf ' %s%s' " $_sanitize_scheme " " $_sanitize_authority "
103+ ;;
104+ esac
105+ }
106+
107+ metadata_value () {
108+ _key=$1
109+ _file=$2
110+ if [ ! -f " $_file " ]; then
111+ return 1
112+ fi
113+ sed -n " s/^${_key} =//p" " $_file " | sed -n ' 1p'
114+ }
115+
116+ is_metadata_compatible () {
117+ _file=$1
118+ [ " $( metadata_value state " $_file " ) " = " complete" ] || return 1
119+ [ " $( metadata_value version " $_file " ) " = " $_metadata_version " ] || return 1
120+ [ " $( metadata_value clone_dir " $_file " ) " = " $CLONE_DIR " ] || return 1
121+ [ " $( metadata_value repo_url " $_file " ) " = " $_sanitized_repo_url " ] || return 1
122+ [ " $( metadata_value branch " $_file " ) " = " $_branch_value " ] || return 1
123+ return 0
124+ }
125+
126+ write_metadata () {
127+ mkdir -p " $_metadata_dir "
128+ _tmp_metadata=" $_metadata_file .tmp.$$ "
129+ {
130+ printf ' version=%s\n' " $_metadata_version "
131+ printf ' state=complete\n'
132+ printf ' clone_dir=%s\n' " $CLONE_DIR "
133+ printf ' repo_url=%s\n' " $_sanitized_repo_url "
134+ printf ' branch=%s\n' " $_branch_value "
135+ printf ' persistence_mode=%s\n' " $_persistence_mode "
136+ date -u ' +timestamp=%Y-%m-%dT%H:%M:%SZ'
137+ } > " $_tmp_metadata "
138+ mv " $_tmp_metadata " " $_metadata_file "
139+ }
140+
141+ clone_repo () {
142+ rm -f " $_metadata_file " " $_metadata_file .tmp.$$ "
143+ mkdir -p " $( dirname " $CLONE_DIR " ) "
144+ if [ -n " ${BRANCH:- } " ]; then
145+ echo " Cloning $_sanitized_repo_url (branch: $BRANCH ) into $CLONE_DIR "
146+ if ! timeout " $MAX_CLONE_TIMEOUT " git clone --depth 1 --branch " $BRANCH " " $REPO_URL " " $CLONE_DIR " ; then
147+ rm -f " $_metadata_file .tmp.$$ "
148+ echo " Clone failed - check URL, branch name, and network access"
149+ exit 1
150+ fi
151+ else
152+ echo " Cloning $_sanitized_repo_url into $CLONE_DIR "
153+ if ! timeout " $MAX_CLONE_TIMEOUT " git clone --depth 1 " $REPO_URL " " $CLONE_DIR " ; then
154+ rm -f " $_metadata_file .tmp.$$ "
155+ echo " Clone failed - check URL and network access"
156+ exit 1
157+ fi
158+ fi
159+ write_metadata
160+ }
161+
162+ _sanitized_repo_url=$( sanitize_repo_url " $REPO_URL " )
163+
46164# Inject token into HTTPS URLs via git URL rewriting.
47165# This approach works for all token types (classic PAT, fine-grained PAT, OAuth)
48166# because the token is embedded directly in the URL rather than going through
49167# the credential challenge/response flow.
50168# The rewrite targets only the actual host in REPO_URL, so it works for any provider.
51169if [ -n " ${GIT_ACCESS_TOKEN:- } " ]; then
52- _repo_host=$( echo " $REPO_URL " | sed ' s|https://||' | cut -d/ -f1)
53- git config --global \
54- url." https://x-access-token:${GIT_ACCESS_TOKEN} @${_repo_host} /" .insteadOf \
55- " https://${_repo_host} /"
170+ case " $_sanitized_repo_url " in
171+ https://* )
172+ _repo_host=$( printf ' %s' " $_sanitized_repo_url " | sed ' s|https://||' | cut -d/ -f1)
173+ git config --global \
174+ url." https://x-access-token:${GIT_ACCESS_TOKEN} @${_repo_host} /" .insteadOf \
175+ " https://${_repo_host} /"
176+ ;;
177+ * )
178+ ;;
179+ esac
56180 unset _repo_host
57181fi
58182
59- # Remove leftover clone directory from a previous session (e.g. preStop hook
60- # was skipped due to force-delete or node failure).
61183if [ -d " $CLONE_DIR " ]; then
62- echo " Removing leftover directory $CLONE_DIR "
63- rm -rf " $CLONE_DIR "
64- fi
65-
66- if [ -n " ${BRANCH:- } " ]; then
67- echo " Cloning $REPO_URL (branch: $BRANCH ) into $CLONE_DIR "
68- if ! timeout " $MAX_CLONE_TIMEOUT " git clone --depth 1 --branch " $BRANCH " " $REPO_URL " " $CLONE_DIR " ; then
69- echo " Clone failed - check URL, branch name, and network access"
70- exit 1
71- fi
72- else
73- echo " Cloning $REPO_URL into $CLONE_DIR "
74- if ! timeout " $MAX_CLONE_TIMEOUT " git clone --depth 1 " $REPO_URL " " $CLONE_DIR " ; then
75- echo " Clone failed - check URL and network access"
184+ if ! is_metadata_compatible " $_metadata_file " ; then
185+ echo " Refusing to modify existing directory $CLONE_DIR without compatible AUPLC clone metadata"
76186 exit 1
77187 fi
188+
189+ _metadata_persistence_mode=$( metadata_value persistence_mode " $_metadata_file " )
190+ case " $_persistence_mode :$_metadata_persistence_mode " in
191+ persistent:persistent)
192+ echo " Reusing existing managed clone $CLONE_DIR "
193+ exit 0
194+ ;;
195+ persistent:ephemeral)
196+ write_metadata
197+ echo " Reusing existing managed clone $CLONE_DIR "
198+ exit 0
199+ ;;
200+ ephemeral:ephemeral)
201+ echo " Replacing existing managed ephemeral clone $CLONE_DIR "
202+ rm -rf " $CLONE_DIR "
203+ ;;
204+ ephemeral:persistent)
205+ echo " Refusing to replace persistent managed clone $CLONE_DIR for an ephemeral request"
206+ exit 1
207+ ;;
208+ * )
209+ echo " Refusing to modify existing directory $CLONE_DIR with unknown metadata persistence mode"
210+ exit 1
211+ ;;
212+ esac
78213fi
79214
215+ clone_repo
216+
80217echo " Done"
0 commit comments