carbon-core-system

Integration repository for versioned configuration and software on Carbon
git clone https://ccx.te2000.cz/git/carbon-core-system
Log | Files | Refs | Submodules

install (11648B)


      1 #!/bin/zsh
      2 setopt no_unset warn_create_global extended_glob
      3 zmodload -F zsh/stat b:zstat || exit $?
      4 zmodload -m -F zsh/files b:zf_\* || exit $?
      5 
      6 cd $0:h || exit $?
      7 umask 022
      8 
      9 ### Utility functions {{{1
     10 die_ret() {
     11 	local ret
     12 	ret=$1
     13 	shift
     14 	printf >&2 '%s\n' "$@"
     15 	exit $ret
     16 }
     17 die() {
     18 	die_ret 1 "$@"
     19 }
     20 die100() {  # 100: wrong usage
     21 	die_ret 100 "$@"
     22 }
     23 die111() {  # 111: system call failed
     24 	die_ret 111 "$@"
     25 }
     26 
     27 require() {
     28 	if ! check_$1; then
     29 		make_$1
     30 		if ! check_$1; then
     31 			die "ERROR: check_$1 failed after make_$1"
     32 		fi
     33 	fi
     34 }
     35 
     36 ### Configuration {{{1
     37 typeset -ga submodule_list=( package init scripts conf )
     38 typeset -gA submodule_ignore_changes=(
     39 	package 1
     40 )
     41 typeset -gA submodule_has_submodules=(
     42 	. 1
     43 	package 1
     44 )
     45 
     46 ### Checks {{{1
     47 check_submodule_init() {
     48 	local -a submodule_dirs
     49 	local sub
     50 	submodule_dirs=( ${(f)"$(sed -n '/^\tpath = /{s/^[^=]* = //;p}' <$submodule/.gitmodules)"} )
     51 	for sub in $submodule_dirs; do
     52 		[[ -f $submodule/$sub/.git ]] || return $?
     53 	done
     54 }
     55 make_submodule_init() {
     56 	(cd $submodule && git submodule init && git submodule update)
     57 }
     58 
     59 check_committed() {
     60 	local git_status
     61 	git_status=$(cd $submodule && git status -s --ignore-submodules=dirty) || return $?
     62 	[[ -z $git_status ]] && return 0
     63 	printf "Following changes found in %s:\n%s\n" $submodule "$git_status"
     64 	return 1
     65 }
     66 make_committed() {
     67 	local REPLY
     68 	local -a empty_dirs
     69 	echo "Uncommitted changes. Add and commit them?"
     70 	empty_dirs=( $submodule/(**~.git)/*(ND/^F) )
     71 	(( $#empty_dirs )) && echo "WARNING: $#empty_dirs empty directories found."
     72 	read -q || exit 0
     73 	( cd $submodule && git add . && git commit ) || exit $?
     74 	if [[ $submodule != . ]]; then
     75 		git commit -m "update ${(qqq)submodule} submodule" -- $submodule || exit $?
     76 	fi
     77 }
     78 
     79 check_current() {
     80 	local -A stat_out
     81 	[[ -L /current ]] || return 1
     82 	zstat -L -H stat_out /current || return $?
     83 	[[ $stat_out[link] == ${${sm_dst[.]}#/} ]]
     84 }
     85 make_current() {
     86 	preinstall
     87 	s6-ln -s -f -n "${${sm_dst[.]}#/}" /current
     88 	postinstall_or_rollback
     89 }
     90 
     91 recursive_immutable() {
     92 	## Noisy but hopefully appropriate exitcode
     93 	#chattr -R -f +i "$@"
     94 	## Skips warnings on symlinks but exitcode might not be useful
     95 	find "$@" -depth \! -type l -exec chattr +i '{}' +
     96 	## Skip lockfiles so they can be opened read-write
     97 	# find "$@" -depth -name lock \! -o -type l -exec chattr +i '{}' +
     98 }
     99 
    100 check_bzr_export() {
    101 	[[ -d ${bzr_dst} ]]
    102 }
    103 make_bzr_export() {
    104 	local tmp=$bzr_dst:h/.new.$bzr_dst:t
    105 	brz export --format=dir --revision=revid:$bzr_revid -d "$bzr_src" $tmp/ || exit $?
    106 	mv $tmp $bzr_dst || exit $?
    107 	recursive_immutable $bzr_dst
    108 }
    109 
    110 check_installed() {
    111 	[[ -f ${sm_dst[$submodule]}/.done ]]
    112 }
    113 make_installed() {
    114 	local dst=${sm_dst[$submodule]}
    115 	if [[ -e $dst ]]; then
    116 	  rm -r $dst || exit $?
    117 	fi
    118 	( cd $submodule && install_submodule ) || exit $?
    119 	touch $dst/.done
    120 	recursive_immutable $dst
    121 }
    122 
    123 ### Install functions {{{1
    124 install_submodule() {
    125 	case $submodule in
    126 		(package) install_package;;
    127 		(s6-rc) install_unpack;;
    128 		(init) install_unpack;;
    129 		(scripts) install_unpack;;
    130 		(conf) install_unpack;;
    131 		(.) install_dot;;
    132 		(*)
    133 			echo >&2 "unhandled submodule: ${(qqq)submodule}"
    134 			exit 1
    135 			;;
    136 	esac
    137 }
    138 
    139 install_package() {
    140 	git checkout --force || exit $?
    141 	git clean -fx || exit $?
    142 	git submodule update --init --force || exit $?
    143 	exec env slashpackage=${sm_dst[$submodule]} ./install-all
    144 }
    145 
    146 install_unpack() {
    147 	local dst=${sm_dst[$submodule]}
    148 	local cmd=${sm_dst[package]}/command
    149 	zf_mkdir -p $dst || exit $?
    150 	git archive --format=tar ${sm_commit[$submodule]} | tar --no-same-owner --no-same-permissions -xC $dst
    151 	exit $(( ${(j.|.)pipestatus} )) # error if git archive or tar exited non-zero
    152 }
    153 
    154 install_bzr_repo(){
    155 	typeset -g bzr_dst bzr_revid bzr_src
    156 	bzr_dst=/versions/$1.$2
    157 	bzr_revid=$2
    158 	bzr_src=$3
    159 
    160 	require bzr_export
    161 	s6-ln -ns ../../$1.$2 $sm_dst[.]/bzr/$1 || exit $?
    162 }
    163 install_bzr_repos(){
    164 	local name url revid
    165 	local -A repo_sources
    166 
    167 	mkdir -p $sm_dst[.]/bzr || exit $?
    168 
    169 	<./bzr/sources while IFS=$'\t' read name url; do
    170 		[[ -n "$name" && -n "$url" ]] || die "Malformed bzr/sources file"
    171 		(( $+repo_sources[$name] )) && die "Duplicate repo name: ${(qqq)name}"
    172 		repo_sources[$name]=$url
    173 	done
    174 	<./bzr/revisions while IFS=$'\t' read name revid; do
    175 		[[ -n "$name" && -n "$revid" ]] || die "Malformed revisions file"
    176 		install_bzr_repo "$name" "$revid" "$repo_sources[$name]"
    177 	done
    178 }
    179 
    180 # Utility for symlinking into /versions/all.*/
    181 ln_sub() {
    182 	s6-ln -ns ../${${sm_dst[$1]}:t} ${sm_dst[.]}/${2:-$1} || exit $?
    183 }
    184 ln_from_sub() {
    185 	s6-ln -ns ../${${sm_dst[$1]}:t}/$2 ${sm_dst[.]}/$3 || exit $?
    186 }
    187 ln_sub_command() {
    188 	(
    189 		cd ${sm_dst[.]} || exit $?
    190 		local cmd
    191 		for cmd in ../${${sm_dst[$1]}:t}/{command,bin}/*(N); do
    192 			s6-ln -s ../$cmd ${sm_dst[.]}/command/ || exit $?
    193 		done
    194 	) || exit $?
    195 }
    196 ln_sub_zsh_functions() {
    197 	local fun
    198 	for fun in ../${${sm_dst[$1]}:t}/zsh-functions/*(N); do
    199 		s6-ln -s ../$fun ${sm_dst[.]}/zsh-functions/ || exit $?
    200 	done
    201 }
    202 install_dot() {
    203 	local submodule cmd fun
    204 
    205 	# export sm_dst_* variables for each submodule
    206 	for submodule in "${(k@)sm_dst}"; do
    207 		if [[ $submodule == . ]]; then
    208 			export sm_dst_all=$sm_dst[.]
    209 		else
    210 			eval "export sm_dst_${submodule//-/_}"='$sm_dst[$submodule]'
    211 		fi
    212 	done
    213 
    214 	# create symlinks for submodules and populate ./command
    215 	zf_mkdir -p ${sm_dst[.]}/command ${sm_dst[.]}/zsh-functions || exit $?
    216 
    217 	ln_from_sub package package package
    218 	ln_sub_command package
    219 
    220 	install_bzr_repos
    221 	(
    222 		cd ${sm_dst[.]} || exit $?
    223 		for cmd in bzr/*/{command,bin,sbin}/*(N); do
    224 			s6-ln -s ../$cmd ./command/ || exit $?
    225 		done
    226 		for fun in bzr/*/zsh-functions/*(N); do
    227 			s6-ln -s ../$fun ./zsh-functions/ || exit $?
    228 		done
    229 	) || exit $?
    230 
    231 	# ln_sub s6-rc s6-rc-source
    232 	#./command/s6-rc-compile ./s6-rc-db ./s6-rc-source || exit $?
    233 
    234 	ln_sub init init-maker
    235 	ln_sub_command init
    236 	#./init-maker/install $PWD || exit $?
    237 
    238 	ln_sub scripts
    239 	ln_sub_command scripts
    240 	ln_sub_zsh_functions scripts
    241 
    242 	ln_sub conf
    243 	ln_sub_command conf
    244 	ln_sub_zsh_functions conf
    245 
    246 	# After all is symlinked the rest of installation is done by conf submodule
    247 	${sm_dst[.]}/conf/install || exit $?
    248 }
    249 
    250 ### Main code {{{1
    251 preinstall() {
    252 	typeset -g preinstall_current
    253 	[[ -e /current ]] || return 0
    254 
    255 	local -A statinfo
    256 	zstat -H statinfo +link /current
    257 	preinstall_current=$statinfo[link]
    258 	preinstall_current_p=${${:-/current}:P}
    259 	# for postinstall script
    260 	export preinstall_current preinstall_current_p
    261 	s6-rename /previous /previous.1
    262 	s6-ln -nsf $preinstall_current /previous || exit $?
    263 }
    264 
    265 postinstall_or_rollback() {
    266 	local l ret sub
    267 
    268 	if ! ${sm_dst[.]}/conf/postinstall; then
    269 		if [[ -n $preinstall_current ]]; then
    270 			echo >&2 "Fatal: postinstall script failed, rolling back to $preinstall_current"
    271 			s6-ln -nsf $preinstall_current /current || exit $?
    272 			s6-rename /previous.1 /previous
    273 		else
    274 			echo >&2 "Fatal: postinstall script failed for initial installation, removing /current"
    275 			zf_rm /current
    276 		fi
    277 		exit 1
    278 	fi
    279 	git tag --force deployed
    280 	# Mark current HEAD as master in submodules
    281 	for sub in $submodule_list; do
    282 		(cd $sub && git checkout -B master HEAD)
    283 	done
    284 }
    285 
    286 ensure_head() {
    287 	local line sub_state sub_commit sub_name ret git_sub_changes
    288 	local -a git_status git_log_to_head git_log_from_head
    289 
    290 	git_status=( ${(f)"$(git status -s --ignore-submodules=dirty)"}) \
    291 		|| die_ret $? "Fatal: 'git status' failed"
    292 	if (( $git_status[(I)???.gitmodules] )); then
    293 		die ".gitmodules uncommited, please make commit manually"
    294 	fi
    295 
    296 	git_status=( ${(f)"$(git submodule status --cached)"}) \
    297 		|| die_ret $? "Fatal: 'git submodule status' failed"
    298 	for line in $git_status; do
    299 		sub_state=${line[1]}
    300 		sub_commit=${line[2,41]}
    301 		sub_name=${line[43,-1]% \(*}
    302 		submodule=$sub_name
    303 		case $sub_state in
    304 			('U') die "Submodule ${(qqq)sub_name} has merge conflicts"
    305 				;;
    306 			('-') # Submodule not initialized
    307 				git submodule update --init -- $sub_name \
    308 					|| die_ret $? "Failed to initilize submodule ${(qqq)sub_name}"
    309 				if (($+submodule_has_submodules[$sub_name])); then
    310 					(cd $sub_name && git submodule update --init) || exit $?
    311 				fi
    312 				;;
    313 			('+') # Commit in submodule differs from expected one
    314 				# Check if desired commit is ancestor of current
    315 				# Note: might return 128 if revision wasn't fetched yet
    316 				git_log_to_head=( ${(f)"$( cd ./$sub_name && git log --format=oneline $sub_commit..HEAD )"} )
    317 				ret=$?
    318 				if (( $ret == 128 )); then  # likely: fatal: Invalid revision range
    319 					(cd $sub_name && git fetch --all)
    320 					git_log_to_head=( ${(f)"$( git log --format=oneline $sub_commit..HEAD )"} )
    321 					ret=$?
    322 				fi
    323 				(( $ret )) && die_ret $? "git log returned error"
    324 				git_log_from_head=( ${(f)"$( cd ./$sub_name && git log --format=oneline HEAD..$sub_commit )"} )
    325 				if (($#git_log_to_head && $#git_log_from_head == 0)); then
    326 					# Can advance directly from saved commit to HEAD
    327 					echo "$#git_log_to_head new commits in submodule. Add and commit them?"
    328 					printf 'last submodule commit: %s\n' $git_log_to_head[1]
    329 					local REPLY
    330 					read -q || exit 0
    331 					git add -- $sub_name || exit $?
    332 					git commit -m "update ${(qqq)sub_name} submodule" -- $sub_name || exit $?
    333 					if ! (($+submodule_ignore_changes[$sub_name])); then
    334 						require committed
    335 					fi
    336 				elif (($#git_log_to_head == 0 && $#git_log_from_head)); then
    337 					# Can advance directly from HEAD to saved commit
    338 					if ! (($+submodule_ignore_changes[$sub_name])); then
    339 						git_sub_changes=$(cd $sub_name && git status -s --ignore-submodules=dirty) \
    340 							|| die "Error from git status in ${(qqq)sub_name}"
    341 						[[ -z $git_sub_changes ]] \
    342 							|| die "Uncommitted changes in ${(qqq)sub_name}"
    343 					fi
    344 					(cd $sub_name && git reset --hard && git clean -fx) || exit $?
    345 					git submodule update --force -- $sub_name
    346 				elif (($#git_log_to_head && $#git_log_from_head)); then
    347 					# HEAD diverged from saved commit
    348 					die "Submodule ${(qqq)sub_name} HEAD diverged."
    349 				else
    350 					# No shared history!?
    351 					die "Submodule ${(qqq)sub_name} reports no shared history."
    352 				fi
    353 				;;
    354 			(' ') # Commit in submodule same as in tree
    355 				if ! (($+submodule_ignore_changes[$sub_name])); then
    356 					require committed
    357 				fi
    358 				;;
    359 			(*) die "Submodule ${(qqq)sub_name} has unknown state ${(qqq)sub_state}"
    360 				;;
    361 		esac
    362 	done
    363 	submodule=.
    364 	require committed
    365 }
    366 
    367 load_commits() {
    368 	typeset -gA sm_commit sm_dst
    369 	local line sub_state sub_commit sub_name
    370 	local -a git_status
    371 
    372 	sm_commit[.]=$(git show -s --pretty=format:%H%n) || exit $?
    373 	sm_dst[.]=/versions/all.${sm_commit[.]}
    374 
    375 	git_status=( ${(f)"$(git submodule status --cached)"}) \
    376 		|| die_ret $? "Fatal: 'git submodule status' failed"
    377 	for line in $git_status; do
    378 		sub_state=${line[1]}
    379 		sub_commit=${line[2,41]}
    380 		sub_name=${line[43,-1]% \(*}
    381 		case $sub_state in
    382 			(' ')
    383 				sm_commit[$sub_name]=$sub_commit
    384 				sm_dst[$sub_name]=/versions/$sub_name.${sm_commit[$sub_name]}
    385 				;;
    386 			(*) die "Submodule ${(qqq)sub_name} not clean (state ${(qqq)sub_state})"
    387 				;;
    388 		esac
    389 	done
    390 }
    391 
    392 main() {
    393 	local sub
    394 	ensure_head
    395 	load_commits
    396 
    397 	for sub in $submodule_list .; do
    398 		submodule=$sub
    399 		require installed
    400 		if [[ -d ${sm_dst[$sub]}/command ]]; then
    401 			PATH=${sm_dst[$sub]}/command:$PATH
    402 		fi
    403 	done
    404 
    405 	require current
    406 }
    407 
    408 if (($+trace_func)); then
    409 	# Use trace_func environment variable as a pattern and enable tracing
    410 	# on all functions that match it.
    411 	typeset -g function_to_trace
    412 	for function_to_trace in ${(k)functions}; do
    413 		if [[ $function_to_trace == $~trace_func ]]; then
    414 			typeset -f -t $function_to_trace || die "something wrong with typeset -f -t $function_to_trace"
    415 			printf >&2 '+++ tracing enabled for function %s\n' ${(qqq)function_to_trace}
    416 		fi
    417 	done
    418 	unset function_to_trace
    419 fi
    420 
    421 typeset -g submodule
    422 
    423 main