mrrl-containers

MRRL version of container scripts
git clone https://ccx.te2000.cz/git/mrrl-containers
Log | Files | Refs

ns_run_svc_gen (16642B)


      1 #!/bin/zsh
      2 setopt no_unset warn_create_global extended_glob
      3 zmodload zsh/system || exit $?
      4 zmodload -m -F zsh/files b:zf_\* || exit $?
      5 typeset -g scriptname=$0
      6 
      7 typeset -g container_image_dir=/mnt/volumes/containers/systems
      8 typeset -g container_user_dir=/mnt/volumes/containers/user
      9 typeset -g service_script_dir=${0:P:h:h}/service_scripts
     10 
     11 # message helpers
     12 typeset -g hl_fatal hl_warn hl_reset
     13 if (( $terminfo[colors] >= 8 )); then
     14   hl_fatal='%F{red}%B'; hl_fatal=${(%)hl_fatal}
     15   hl_warn='%F{yellow}%B'; hl_warn=${(%)hl_warn}
     16   hl_reset='%b%f'; hl_reset=${(%)hl_reset}
     17 fi
     18 
     19 err_msg() {
     20   local first=$1
     21   shift
     22   printf >&2 '%s%s%s%s\n' "$hl_fatal" "$first" "$hl_reset" "$*"
     23 }
     24 
     25 warn_msg() {
     26   local first=$1
     27   shift
     28   printf >&2 '%s%s%s%s\n' "$hl_warn" "$first" "$hl_reset" "$*"
     29 }
     30 
     31 # helper that prints out stack, error message and exits
     32 die_ret() {
     33   set +x
     34   local ret n
     35   ret=$1
     36   shift
     37   print -r - >&2 "${hl_fatal}Fatal$hl_reset error occurend in:"
     38   for n in {${#funcfiletrace}..1}; do
     39     printf >&2 '%d> %s (%s)\n' $n "$funcfiletrace[$n]" "$functrace[$n]"
     40   done
     41   printf >&2 '%s\n' "${hl_fatal}*$hl_reset $^@"
     42   exit $ret
     43 }
     44 
     45 # useful exit aliases
     46 die() {
     47   set +x
     48   die_ret 1 "$@"
     49 }
     50 die100() {  # 100: wrong usage
     51   set +x
     52   die_ret 100 "$@"
     53 }
     54 die101() {  # 101: can't happen / internal error
     55   set +x
     56   die_ret 101 "$@"
     57 }
     58 die102() {  # 102: config / data file error
     59   set +x
     60   die_ret 102 "$@"
     61 }
     62 die111() {  # 111: system call failed / transient error
     63   set +x
     64   die_ret 111 "$@"
     65 }
     66 
     67 -() {  # Run command and die on nonzero exitcode
     68 	"$@" || die_ret $? "command failed with exitcode $?: ${(j: :)${(q)@}}"
     69 }
     70 
     71 die_usage() {
     72 	die100 "$@" "usage: ns_run_svc_gen TODO"
     73 }
     74 
     75 # ---
     76 
     77 typeset -g config_section_regex='(mountpoints|profile/[-a-zA-Z0-9][-.a-zA-Z0-9]*)'
     78 typeset -g mountpoint_name_regex='[-a-zA-Z0-9][-.a-zA-Z0-9]*'
     79 typeset -g seccomp_profile_regex='[-a-zA-Z0-9][-.a-zA-Z0-9]*'
     80 
     81 typeset -g config_current_section
     82 typeset -gA config_values config_sections
     83 config_section() {
     84 	local match mbegin mend  # for regex match
     85 	[[ $1 =~ "$config_section_regex" ]] ||
     86 		die102 "invalid config section: ${(qqq)1}"
     87 	config_current_section=$1
     88 	config_sections[$1]=1
     89 }
     90 config_assign() {
     91 	local key=$config_current_section/$1
     92 	if (($+config_values[$key])); then
     93 		die102 "Attempting to redefine $key" \
     94 			"from ${(qqq)config_values[$key]}" \
     95 			"  to ${(qqq)2}"
     96 	fi
     97 	config_values[$key]=$2
     98 	config_check $key
     99 }
    100 config_append() {
    101 	local key=$config_current_section/$1
    102 	if (($+config_values[$key])); then
    103 		config_values[$key]+=$'\0'$2
    104 	else
    105 		config_values[$key]=$2
    106 	fi
    107 	config_check $key
    108 }
    109 # typeset -f -t config_section config_assign config_append
    110 config_check() {
    111 	local value
    112 	local match mbegin mend  # for regex match
    113 	value=$config_values[$1]
    114 	if [[ $config_current_section == mountpoints ]]; then
    115 		[[ ${1:t} =~ "$mountpoint_name_regex" ]] ||
    116 			die102 "invalid mountpoint name: ${(qqq)1:t}"
    117 		return 0
    118 	fi
    119 	case ${1:t} in
    120 		(seccomp_profile)
    121 			[[ $value =~ "$seccomp_profile_regex" ]] ||
    122 				die102 "invalid seccomp profile: ${(qqq)value}"
    123 			;;
    124 		(container_type)
    125 			case $value in
    126 				(generic|ephemeral|alsa|xsession|xorg) ;;
    127 				(*) die102 "unknown container type: ${(qqq)value}" ;;
    128 			esac
    129 			;;
    130 		(el_pid1) ;;
    131 		(el_prepare) ;;
    132 		(*) die102 "unsupported parameter ${(qqq)1:t} in section ${(qqq)config_current_section}";;
    133 	esac
    134 }
    135 
    136 parse_config() {
    137 	local line lineno=1
    138 	while IFS= read -r "$@" line; do
    139 		line=${line//#[ 	]##}  # strip leading whitespace
    140 		case $line in
    141 			('#'*) ;;  # comment
    142 			(';'*) ;;  # comment
    143 			('') ;; # empty
    144 			(\[[^=]##\])  # section
    145 				config_section ${${line%\]}#\[}
    146 				;;
    147 			([^\]\[=+]##=*)  # assign
    148 				config_assign ${line%%=*} ${line#*=}
    149 				;;
    150 			([^\]\[=+]##+=*)  # append
    151 				config_append ${line%%+=*} ${line#*+=}
    152 				;;
    153 			(*)  # error
    154 				die102 "invalid config line $lineno: ${(qqq)line}"
    155 				;;
    156 		esac
    157 		((lineno++))
    158 	done
    159 }
    160 
    161 # ---
    162 
    163 typeset -g cdefs_current_section
    164 typeset -gA cdefs_values cdefs_sections
    165 
    166 typeset -g container_name_regex='[-a-zA-Z0-9]+'
    167 typeset -g image_name_regex='[-a-zA-Z0-9][-.a-zA-Z0-9]*'
    168 
    169 cdefs_section() {
    170 	[[ -n $cdefs_current_section ]] &&
    171 		- cdef_validate_section
    172 	local match mbegin mend  # for regex match
    173 	[[ $1 =~ "$container_name_regex" ]] ||
    174 		die102 "invalid container name: ${(qqq)1}"
    175 	cdefs_current_section=$1
    176 	cdefs_sections[$1]=1
    177 }
    178 cdef_validate_section() {
    179 	(($+cdefs_values[$cdefs_current_section/profile])) ||
    180 		die102 "container ${(qqq)cdefs_current_section} missing definition of \"profile\""
    181 	(($+cdefs_values[$cdefs_current_section/image])) ||
    182 		die102 "container ${(qqq)cdefs_current_section} missing definition of \"image\""
    183 }
    184 cdefs_check() {
    185 	local value
    186 	local match mbegin mend  # for regex match
    187 	value=$cdefs_values[$1]
    188 	case ${1:t} in
    189 		(profile)
    190 			(($+config_sections[profile/$value])) ||
    191 				die102 "undefined container profile: ${(qqq)value}"
    192 			;;
    193 		(image)
    194 			[[ $value =~ "$image_name_regex" ]] ||
    195 				die102 "invalid container image: ${(qqq)value}"
    196 			;;
    197 		(mount_rw)
    198 			local mtp
    199 			for mtp in "${(0@)value}"; do
    200 				(($+config_values[mountpoints/$mtp])) ||
    201 					die102 "undefined mountpoint: ${(qqq)mtp}"
    202 			done
    203 			;;
    204 		(mount_ro) 
    205 			local mtp
    206 			for mtp in "${(0@)value}"; do
    207 				(($+config_values[mountpoints/$mtp])) ||
    208 					die102 "undefined mountpoint: ${(qqq)mtp}"
    209 			done
    210 			;;
    211 		(*) die102 "unsupported parameter ${(qqq)1:t} in section ${(qqq)cdefs_current_section}";;
    212 	esac
    213 }
    214 cdefs_assign() {
    215 	if [[ $1 = image_prefix ]]; then
    216 		cdefs_assign image $2$cdefs_current_section
    217 		return $?
    218 	fi
    219 	local key=$cdefs_current_section/$1
    220 	if (($+cdefs_values[$key])); then
    221 		die102 "Attempting to redefine $key" \
    222 			"from ${(qqq)cdefs_values[$key]}" \
    223 			"  to ${(qqq)2}"
    224 	fi
    225 	cdefs_values[$key]=$2
    226 	cdefs_check $key
    227 }
    228 cdefs_append() {
    229 	local key=$cdefs_current_section/$1
    230 	if (($+cdefs_values[$key])); then
    231 		cdefs_values[$key]+=$'\0'$2
    232 	else
    233 		cdefs_values[$key]=$2
    234 	fi
    235 	cdefs_check $key
    236 }
    237 # typeset -f -t cdefs_section cdefs_assign cdefs_append
    238 
    239 parse_cdefs() {
    240 	local line lineno=1
    241 	while IFS= read -r "$@" line; do
    242 		line=${line//#[ 	]##}  # strip leading whitespace
    243 		case $line in
    244 			('#'*) ;;  # comment
    245 			(';'*) ;;  # comment
    246 			('') ;; # empty
    247 			(\[[^=]##\])  # section
    248 				cdefs_section ${${line%\]}#\[}
    249 				;;
    250 			([^\]\[=+]##=*)  # assign
    251 				cdefs_assign ${line%%=*} ${line#*=}
    252 				;;
    253 			([^\]\[=+]##+=*)  # append
    254 				cdefs_append ${line%%+=*} ${line#*+=}
    255 				;;
    256 			(*)  # error
    257 				die102 "invalid cdefs line $lineno: ${(qqq)line}"
    258 				;;
    259 		esac
    260 		((lineno++))
    261 	done
    262 }
    263 
    264 # service builder
    265 
    266 sv_start() {
    267   (($+sv_name)) && \
    268     die100 "sv_start: previous service definition ${(qqq)sv_name} wasn't ended properly"
    269   (( $# != 1 )) && die100 "sv_start: incorrect arguments:" "${(qqq)@}"
    270   typeset -g sv_name=$1
    271   [[ $sv_name == */* ]] && die100 "sv_start: invalid service name: ${(qqq)1}"
    272   typeset -g sv_dir=$build/$1
    273   - mkdir -p $sv_dir
    274   - touch $sv_dir/down
    275 }
    276 
    277 sv_cond_file() {
    278   local varname=sv_${1}_lines
    279   if (( ${#${(P)varname}} == 0 )); then
    280      unset "$varname"
    281      return 0
    282   fi
    283   [[ -e $sv_dir/$1 ]] && die "error: $sv_dir/$1 already exists"
    284   - sv_write_lines $1 "${(P@)varname}"
    285   if [[ $1 == (run|finish) ]]; then
    286     - chmod +x $sv_dir/$1
    287   fi
    288   unset "$varname"
    289 }
    290 #typeset -f -t sv_cond_file
    291 
    292 sv_end() {
    293   (($+sv_name)) || die100 "$0: no service definition started"
    294   (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
    295   (( $# != 0 )) && die100 "$0: incorrect arguments:" "${(qqq)@}"
    296 
    297   #if ! [[ -L $scandir/$sv_name ]]; then
    298   #  new_services+=( $scandir/$sv_name )
    299   #  # - s6-mkfifodir $sv_dir/event
    300   #  - s6-svlink -t 3000 $scandir $sv_dir
    301   #  # TODO: chmod
    302   #fi
    303   unset sv_name sv_dir
    304 }
    305 
    306 sv_link_command() {
    307   (($+sv_name)) || die100 "$0: no service definition started"
    308   (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
    309   (( $# != 2 )) && die100 "$0: incorrect arguments:" "${(qqq)@}"
    310 
    311   local script_path=$sv_dir/$1
    312   - s6-ln -s -f -n ${commands[$2]} $script_path
    313 }
    314 
    315 sv_write_lines() {
    316   (($+sv_name)) || die100 "$0: no service definition started"
    317   (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
    318   (( $# < 2 )) && die100 "$0: incorrect arguments:" "${(qqq)@}"
    319 
    320   local file_path=$sv_dir/$1
    321   local tmp_path=${file_path:h}/.new.${file_path:t}
    322   shift
    323   printf '%s\n' >$tmp_path "$@" || \
    324     die111 "Error writing to ${(qqq)tmp_path}"
    325   - s6-rename $tmp_path $file_path
    326 }
    327 
    328 sv_el_script() {
    329   (($+sv_name)) || die100 "$0: no service definition started"
    330   (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
    331 
    332   local file_path=$sv_dir/$1
    333   local tmp_path=${file_path:h}/.new.${file_path:t}
    334   shift
    335   printf '%s\n' >$tmp_path "#!$commands[execlineb] -P"  "$@" || \
    336     die111 "Error writing to ${(qqq)tmp_path}"
    337   - chmod +x $tmp_path
    338   - s6-rename $tmp_path $file_path
    339 }
    340 
    341 sv_notification_fd() {
    342   (($+sv_name)) || die100 "$0: no service definition started"
    343   (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
    344   (( $# != 1 )) && die100 "$0: incorrect arguments:" "${(qqq)@}"
    345 
    346   - sv_write_lines notification-fd $1
    347 }
    348 
    349 sv_env() {
    350   (($+sv_name)) || die100 "$0: no service definition started"
    351   (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
    352   (( $# < 2 )) && die100 "$0: incorrect arguments:" "${(qqq)@}"
    353 
    354   - mkdir -p $sv_dir/env
    355   local k v
    356   for k v in "$@"; do
    357     [[ $k == */* ]] && die100 "$0: invalid env variable ${(qqq)k}"
    358     - sv_write_lines env/$k "$v"
    359   done
    360 }
    361 
    362 sv_mkdir() {
    363   (($+sv_name)) || die100 "$0: no service definition started"
    364   (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
    365   (( $# < 1 )) && die100 "$0: incorrect arguments"
    366 
    367   - mkdir -p $sv_dir/${^@}
    368 }
    369 
    370 sv_rm() {
    371   (($+sv_name)) || die100 "$0: no service definition started"
    372   (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
    373   (( $# != 1 )) && die100 "$0: incorrect arguments"
    374 
    375   if [[ -e $sv_dir/$1 ]]; then
    376     - rm -r $sv_dir/$1
    377   fi
    378 }
    379 
    380 sv_log() {
    381   (($+sv_name)) || die100 "$0: no service definition started"
    382   (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
    383   (( $# != 0 )) && die100 "$0: incorrect arguments"
    384 
    385   - sv_mkdir log log/env
    386   - printf '%s' $sv_name >$sv_dir/log/env/sv_name
    387   - sv_el_script log/run \
    388     's6-envdir env' \
    389     'importas -i sv_name sv_name' \
    390     'if { mkdir -p /run/log/${sv_name} }' \
    391     'pipeline { s6-log -b -- 1 n10 s10240000 t /run/log/${sv_name} } awk "{print ENVIRON[\"sv_name\"]\": \"$0}"'
    392 }
    393 
    394 # end service builder
    395 
    396 erase_previous() {
    397   local s rundir
    398   for s in $build/*(N); do
    399     if (($+new_services[${s:t}])); then
    400     else
    401       rundir=$scandir/${s:t}
    402       if [[ ${rundir:P} != ${s:P} ]]; then
    403         die102 "mismatched service symlink:" \
    404           "${(qqq)rundir} -> ${(qqq)rundir:P} != ${(qqq)s:P}"
    405       fi
    406       - s6-svunlink -t 1000 $scandir ${s:t}
    407       - warn_msg 'rm ' $s
    408       - rm -rv $s
    409     fi
    410   done
    411 }
    412 
    413 sv_common_finish() {
    414   local -a lines=(
    415     'fdmove -c 2 1' \
    416     'backtick -E up { s6-svstat -o up ../'${(qqq)sv_name_server}' }'
    417     'backtick -E wantedup { s6-svstat -o wantedup ../'${(qqq)sv_name_server}' }'
    418     ## down this service if server is neither up nor wanted up
    419     'if -n { if { $up } $wantedup }'
    420     # 'ifelse { eltest true == $up -o true == $wantedup } { }'
    421     's6-svc -d .'
    422   )
    423   - sv_el_script finish $lines
    424 }
    425 
    426 
    427 write_sv_definitions() {
    428 	typeset -g build
    429 	build=/tmp/user_containers.$USER
    430 	local c
    431 	for c in "${(@k)cdefs_sections}"; do
    432 		- container_sv $c
    433 	done
    434 }
    435 
    436 container_sv() {
    437 	local p
    438 	local container_type seccomp_profile image
    439 	local user userdir rundir src dst rw src_esc dst_esc mnt_dirs
    440 	local -a el_pid1 el_prepare bind_mounts
    441 	p=profile/${cdefs_values[$1/profile]:-default}
    442 	image=${cdefs_values[$1/image]}
    443 	container_type=${config_values[$p/container_type]:-generic}
    444 	seccomp_profile=${config_values[$p/seccomp_profile]:-default}
    445 	(($+config_values[$p/el_pid1])) &&
    446 		el_pid1=( "${(0@)config_values[$p/el_pid1]}" )
    447 	(($+config_values[$p/el_prepare])) &&
    448 		el_prepare=( "${(0@)config_values[$p/el_prepare]}" )
    449 
    450 	user=ccx
    451 	userdir=$container_user_dir/$user/$1
    452 	rundir=/run/containers/$1.$user
    453 	bind_mounts=(
    454 		$container_image_dir/$image
    455 		$userdir/root
    456 		ro
    457 
    458 		$userdir/home
    459 		$userdir/root/home
    460 		rw
    461 
    462 		$rundir/run
    463 		$userdir/root/run
    464 		rw
    465 
    466 		$rundir/tmp
    467 		$userdir/root/tmp
    468 		rw
    469 
    470 		$rundir/mnt
    471 		$userdir/root/mnt
    472 		rw
    473 	)
    474 	(($+cdefs_values[$1/mount_ro])) && for dst in "${(0@)cdefs_values[$1/mount_ro]}"; do
    475 		bind_mounts+=( $config_values[mountpoints/$dst] $userdir/root/mnt/$dst ro )
    476 		mnt_dirs+=( $dst )
    477 	done
    478 	(($+cdefs_values[$1/mount_rw])) && for dst in "${(0@)cdefs_values[$1/mount_rw]}"; do
    479 		bind_mounts+=( $config_values[mountpoints/$dst] $userdir/root/mnt/$dst rw )
    480 		mnt_dirs+=( $dst )
    481 	done
    482 	for src dst rw in "$bind_mounts[@]"; do
    483 		#fstab+=( $src$'\t'$dst$'\tnone\tbind,'$rw$',nosuid,nodev,slave\t0 0' )
    484 		src_esc=\"${${src//\\/\\\\}//\"/\\\"}\"
    485 		dst_esc=\"${${dst//\\/\\\\}//\"/\\\"}\"
    486 		el_prepare+=(
    487 			"if { s6-mount -o bind,$rw,nodev,nosuid,slave $src_esc $dst_esc }"
    488 			"if { s6-mount -o remount,bind,$rw,nodev,nosuid . $dst_esc }"
    489 		)
    490 	done
    491 
    492 	- sv_start container.$1.$user
    493 	- s6-ln -s -f -n $service_script_dir/generic/run $sv_dir/run
    494 	- s6-ln -s -f -n $service_script_dir/generic/finish $sv_dir/finish
    495 	- sv_mkdir data
    496 
    497 	if (($#el_pid1)); then
    498 		sv_el_script data/pid1_exec "${(0@)el_pid1}"
    499 	else
    500 		sv_rm data/pid1_exec
    501 	fi
    502 
    503 	if (($#el_prepare)); then
    504 		sv_el_script data/prepare_chroot "${(0@)el_prepare}"
    505 	else
    506 		sv_rm data/prepare_chroot
    507 	fi
    508 
    509 	- sv_env CONTAINER_NAME $1
    510 	- sv_env CONTAINER_USER $user
    511 	- sv_env CONTAINER_CAPS ''
    512 	- sv_env CONTAINER_MNT_DIRS "ns $mnt_dirs"
    513 	- sv_env CONTAINER_SECCOMP_PROFILE $seccomp_profile
    514 
    515 	- sv_end
    516 }
    517 #typeset -f -t container_sv
    518 
    519 # ---
    520 
    521 main.ns_run_svc_gen() {
    522 	- parse_config <<EOF
    523 [profile/network]
    524 seccomp_profile=default
    525 
    526 [profile/default]
    527 seccomp_profile=default
    528 el_pid1+=unshare -n  # make new network namespace
    529 el_pid1+=if { ip addr add 127.0.0.1/8 dev lo }
    530 el_pid1+=if { ip addr add ::1/128 dev lo }
    531 el_pid1+=if { ip link set lo up }
    532 
    533 [profile/dev]
    534 seccomp_profile=ptrace
    535 el_pid1+=unshare -n  # make new network namespace
    536 el_pid1+=if { ip addr add 127.0.0.1/8 dev lo }
    537 el_pid1+=if { ip addr add ::1/128 dev lo }
    538 el_pid1+=if { ip link set lo up }
    539 
    540 [profile/audio]
    541 seccomp_profile=default
    542 el_pid1+=unshare -n  # make new network namespace
    543 el_pid1+=if { ip addr add 127.0.0.1/8 dev lo }
    544 el_pid1+=if { ip addr add ::1/128 dev lo }
    545 el_pid1+=if { ip link set lo up }
    546 el_prepare+=if { mount -o bind,ro /dev/snd dev/snd }
    547 el_prepare+=mount -t sysfs sysfs sys  # maybe not necessary?
    548 
    549 [profile/pthbs]
    550 seccomp_profile=build
    551 el_pid1+=unshare -n  # make new network namespace
    552 el_pid1+=if { ip addr add 127.0.0.1/8 dev lo }
    553 el_pid1+=if { ip addr add ::1/128 dev lo }
    554 el_pid1+=if { ip link set lo up }
    555 el_pid1+=zsh -c "ulimit -hn 16384 && exec \"$@\"" --
    556 
    557 [profile/usb]
    558 seccomp_profile=default
    559 el_pid1+=unshare -n  # make new network namespace
    560 el_pid1+=if { ip addr add 127.0.0.1/8 dev lo }
    561 el_pid1+=if { ip addr add ::1/128 dev lo }
    562 el_pid1+=if { ip link set lo up }
    563 el_prepare+=if { mount -o bind,ro /dev/bus/usb dev/bus/usb }
    564 el_prepare+=mount -t sysfs sysfs sys
    565 
    566 [profile/xpra]
    567 seccomp_profile=xpra
    568 container_type=ephemeral
    569 el_pid1+=unshare -n  # make new network namespace
    570 el_pid1+=if { ip addr add 127.0.0.1/8 dev lo }
    571 el_pid1+=if { ip addr add ::1/128 dev lo }
    572 el_pid1+=if { ip link set lo up }
    573 
    574 [profile/openssh]
    575 seccomp_profile=setuidgid
    576 el_pid1+=zsh -c "ulimit -hn 16384 && exec \"$@\"" --
    577 
    578 [mountpoints]
    579 init=/home/ccx/bzr/container-user-init
    580 ccx-bzr=/home/ccx/bzr
    581 ccx-dotfiles=/home/ccx/bzr/container-dotfiles
    582 ccx-scripts=/home/ccx/bzr/container-scripts
    583 ccx-password-store=/home/ccx/bzr/password-store
    584 ccx-development=/home/ccx/development
    585 ccx-baregit=/home/ccx/baregit
    586 rcm-devops=/mnt/volumes/containers/user/ccx/git/home/ccx/git/rcm-devops
    587 ccx-task=/home/ccx/task
    588 pthbs=/usr/src/pthbs
    589 mrrl=/usr/src/mrrl
    590 audio=/mnt/volumes/audio
    591 video=/mnt/volumes/video
    592 photos=/mnt/volumes/photos
    593 versions=/versions
    594 mail-te2000.cz-ccx=/home/ccx/mail/te2000.cz/ccx
    595 mail-disroot.org-ccx=/home/ccx/mail/disroot.org/ccx
    596 mail-recombee.com-jan.pobrislo=/home/ccx/mail/recombee.com/jan.pobrislo
    597 EOF
    598 	local key
    599 	for key in "${(ko@)config_values}"; do
    600 		- printf >&2 "conf: %s=%s\n" $key ${(j:; :)${(qqq0@)config_values[$key]}}
    601 	done
    602 	- parse_cdefs <ccx.cdefs
    603 	for key in "${(ko@)cdefs_values}"; do
    604 		- printf >&2 "cdef: %s=%s\n" $key ${(j:; :)${(qqq0@)cdefs_values[$key]}}
    605 	done
    606 	- write_sv_definitions
    607 }
    608 #typeset -f -t main.ns_run_svc_gen
    609 
    610 main.ns_run_svc_gen "$@"
    611 #  vim: ft=zsh noet ts=4 sts=4 sw=4