#!/bin/sh
#
# Tazdev - SliTaz developers and build host tool. System-wide config file
# is located at /etc/slitaz/tazdev.conf. Please, keep the code clear and
# well commented. Also keep in mind: configuration variables are $UPPERCASE
# and variables initialized by tazdev itself are $lowercase.
#
# (c) 2014-2017 SliTaz GNU/Linux - GNU GPL v3
#
# AUTHORS
#     Christophe Lincoln <pankso@slitaz.org>
#     Pascal Bellard <bellard@slitaz.org>
#     Eric Joseph-Alexandre <erjo@slitaz.org>
#

VERSION=2.3

. /lib/libtaz.sh

[ -f /etc/slitaz/tazdev.conf ] && . /etc/slitaz/tazdev.conf
# Only a config we own: root must not source a dev's ./tazdev.conf
[ -O ~/.config/slitaz/tazdev.conf ] && . ~/.config/slitaz/tazdev.conf
[ -O tazdev.conf ] && . ./tazdev.conf

[ -n "$SLITAZ_HOME" ] || die "No config file found"

# Coding jail defaults, for a tazdev.conf older than the jail
: ${JAIL_ROOT:="$SLITAZ_HOME/coding"}
: ${JAIL_MIRROR:="https://mirror.slitaz.org/x86_64/cooking/packages/"}
: ${JAIL_PKGS:="slitaz-base-files busybox bash glibc-base ncurses-common \
cacerts curl git mercurial less nano file diffutils patch \
slitaz-toolchain pkg-config autoconf automake libtool bison flex gdb \
ncursesw-dev"}
: ${JAIL_LOG:="/var/log/coding.log"}
: ${OPENCODE_VERSION:="latest"}


# Remove all --parameters from cmdline
IFS=$'\n'
set -- $(echo "$*" | sed '/^--/d')
unset IFS


usage() {
	cat <<EOT

$(boldify "Usage:") $(basename $0) <command> [<options>] [--options]

SliTaz developers and build host tool v$VERSION

$(boldify "Commands:")
       usage                     Print this short usage and command list
       help <tool>               Give help on a SliTaz tool or library
  -s   stats                     Display statistics for your projects
  -c   chroot [<ver>|<rootfs>]   Mount virtual FS and chroot into the build env
  -gc  gen-chroot [<ver>]        Generate a chroot using packages from config
                                 file
  -mc  mount-chroot [<ver>]      Mount virtual FS and binds without chrooting
  -uc  umchroot [<rootfs>]       Unmount chroot specified on cmdline
  -cc  clean-chroot [<ver>]      Clean a chroot environment (skip root/ and
                                 home/)
  -p   push     [<user> [<ver>]] Upload new packages to the main mirror
  -dp  dry-push [<user> [<ver>]] Show what will be uploaded to the mirror
       pull     [<user> [<ver>]] Download new packages from the main mirror
       dry-pull [<user> [<ver>]] Show what will be downloaded from the mirror
  -ur  up-repos                  Update all your SliTaz projects repos in one
                                 command
  -rp  relpkg <pkg> [<version>]  Archive and upload new package/project version
  -sc  setup-cgi                 Setup your CGI environment
  -gj  gen-jail                  Generate the AI coding jail with opencode
                                 (devs enter it with: coding)
  -ju  jail-up                   Upgrade the jail packages and opencode
  -uj  jail-umount               Unmount the jail (devs must be out)
  -jl  jail-log [<lines>]        Show who used the jail and when

$(boldify "Options:")
  --rootfs=            Path to the chroot to generate or clean
  --arch=              Specify the architecture type for cross-chroot, push/pull
  --clean or --forced  Clean chroot before generating a new one

$(boldify "Examples:")
  $(basename $0) gen-chroot undigest --clean
  $(basename $0) -c --arch=arm

EOT
}

get_version() {
	# Stable, cooking or custom.
	version=${2:-cooking}
	if [ -n "$COOKER" ]; then
		# Arch-first layout (2026 build hosts): $COOKER/<arch>/<channel>/chroot
		# Same scheme as the mirror: /<arch>/<channel>/packages
		arch="${arch:-$ARCH}"
		slitaz="$COOKER/$arch/$version"
		rootfs="$slitaz/chroot"
	else
		slitaz="$SLITAZ_HOME/$version"
		if [ -n "$arch" ]; then
			rootfs="$slitaz/$arch/chroot"
		else
			rootfs="$slitaz/chroot"
		fi
	fi
}

check_mirror() {
	[ -n "$2" ] && user="$2"
	local repo="$3"
	case $repo in
		stable|backports|undigest)
			remote="$MIRROR_PKGS/$repo/"
			local="$SLITAZ_HOME/$repo/packages/"
			;;
		rpi)
			remote="$MIRROR_PKGS/cross/rpi/"
			local="$SLITAZ_HOME/cooking/arm/packages/"
			;;
		*)
			remote="$MIRROR_PKGS/cooking/"
			local="$SLITAZ_HOME/cooking/packages/"
			;;
	esac

	if [ -n "$arch" ]; then
		remote="$remote$arch/"
		local="$local$arch/"
	fi
}

# Bind a directory into the chroot
bind_chroot_dir()
{
	mkdir -p $1 $2
	mount -o bind $1 $2
}

# Mount virtual Kernel file systems and chroot, but check that nobody
# else has done mounts
mount_chroot()
{
	if [ ! -d "$rootfs/proc/1" ]; then
		newline
		action 'Mounting virtual filesystems...'
		mount -n -t tmpfs  none   $rootfs/dev
		mknod -m 622 $rootfs/dev/console c 5 1
		mknod -m 666 $rootfs/dev/null    c 1 3
		mknod -m 666 $rootfs/dev/zero    c 1 5
		mknod -m 666 $rootfs/dev/ptmx    c 5 2
		mknod -m 666 $rootfs/dev/tty     c 5 0
		mknod -m 444 $rootfs/dev/random  c 1 8
		mknod -m 444 $rootfs/dev/urandom c 1 9
		chown root:tty $rootfs/dev/console
		chown root:tty $rootfs/dev/ptmx
		chown root:tty $rootfs/dev/tty

		ln -s /proc/self/fd   $rootfs/dev/fd
		ln -s /proc/self/fd/0 $rootfs/dev/stdin
		ln -s /proc/self/fd/1 $rootfs/dev/stdout
		ln -s /proc/self/fd/2 $rootfs/dev/stderr
		ln -s /proc/kcore     $rootfs/dev/core
		mkdir $rootfs/dev/pts
		mkdir $rootfs/dev/shm

		mount -t devpts -o gid=4,mode=620 none $rootfs/dev/pts
		mount -t tmpfs                    none $rootfs/dev/shm

		mount    -t proc   proc   $rootfs/proc
		mount    -t sysfs  sysfs  $rootfs/sys
		status
	fi
	# Mount source, so they can be shared between arches and channels.
	# But do it only if it's a SliTaz development chroot, and only if not
	# already bound (re-entering a mounted chroot must not stack binds).
	fs="$rootfs/home/slitaz"
	if [ -d "$slitaz" ] && ! grep -q " $fs/src " /proc/mounts; then
		if [ -n "$COOKER" ]; then
			bind_chroot_dir $COOKER/src $fs/src
		else
			bind_chroot_dir $SLITAZ_HOME/src $fs/src
			[ -n "$arch" ] && slitaz="$slitaz/$arch"
		fi
		# Now mount package dir, so they are in /home/slitaz/packages
		# We may not mount cache wok or others: it has no point and if
		# one wants to use a shared wok, one can bind it manually.
		mkdir -p "$fs/packages" "$slitaz/packages"
		bind_chroot_dir "$slitaz/packages" "$fs/packages"
	fi
}

# Unmount virtual Kernel file systems.
umount_chroot() {
	[ -n "$1" ] && rootfs="$1"
	fs="$rootfs/home/slitaz"
	newline
	action 'Unmounting virtual filesystems...'
	umount $rootfs/dev/shm
	umount $rootfs/dev/pts
	umount $rootfs/dev
	umount $rootfs/sys
	umount $rootfs/proc
	if mount | fgrep -q $fs/src; then
		umount $fs/src
		umount $fs/packages
	fi
	status
	newline
}

# Get the last cooking base rootfs, extract and configure.
gen_chroot() {
	title "Generating new chroot in $rootfs"

	mkdir -p $rootfs
	if [ -n "$COOKER" ]; then
		# Arch-first mirror layout. Note: stable repos are versioned on
		# the mirror (i486/stable/4.0) --> use --mirror= for stable.
		url="http://$MIRROR/$arch/$version/packages/"
	else
		# We may gen cooking chroot from a stable version or vice versa
		case "$version" in
			stable*|4.0*|backports)
				url="http://$MIRROR/packages/stable/" ;;
			*)
				url="http://$MIRROR/packages/cooking/" ;;
		esac
	fi

	# --mirror=
	[ -n "$mirror" ] && url="$mirror"
	mpath="/var/lib/tazpkg/mirror"
	mkdir -p $(dirname $rootfs$mpath)
	echo "$url" > $rootfs$mpath

	action "Mirror URL: ${url#http://}"
	tazpkg recharge --root="$rootfs" --quiet >/dev/null
	status

	# tazpkg sources $rootfs/etc/slitaz/slitaz.conf ENTIRELY (paths and
	# all, not only SLITAZ_ARCH): seed a complete conf with the target
	# arch BEFORE the first install, or a cross-arch chroot resolves its
	# first packages for the wrong arch (and a partial conf would blank
	# every other variable).
	if [ -n "$arch" ]; then
		mkdir -p $rootfs/etc/slitaz
		cp /etc/slitaz/slitaz.conf $rootfs/etc/slitaz/slitaz.conf
		sed -i "s|^SLITAZ_ARCH=.*|SLITAZ_ARCH=\"$arch\"|" \
			$rootfs/etc/slitaz/slitaz.conf
	fi

	for pkg in $CHROOT_PKGS; do
		action "Installing: $pkg $vers"
		tazpkg -gi $pkg --root="$rootfs" --quiet
		status
		# tazpkg reads $rootfs/etc/slitaz/slitaz.conf: the target arch
		# must survive the slitaz-base-files default (i486) or package
		# filenames get resolved for the wrong arch.
		[ -n "$arch" ] && sed -i "s|^SLITAZ_ARCH=.*|SLITAZ_ARCH=\"$arch\"|" \
			$rootfs/etc/slitaz/slitaz.conf 2>/dev/null
	done

	# 64-bit chroot: the dynamic loader lives in /lib but binaries are
	# linked against /lib64/ld-linux-x86-64.so.2 (installers create the
	# symlink, tazpkg --root does not).
	if [ "$arch" = "x86_64" ] && [ ! -e "$rootfs/lib64" ]; then
		ln -s lib $rootfs/lib64
	fi

	action "Creating resolv.conf..."
	cat /etc/resolv.conf > $rootfs/etc/resolv.conf
	status

	action "Creating TZ..."
	cat /etc/TZ > $rootfs/etc/TZ
	status
}

# Install opencode in the coding jail. OpenCode 2 is only published on
# npm: the tarball holds a single binary that only needs glibc.
jail_opencode() {
	npm="https://registry.npmjs.org/@opencode"
	ocver="$OPENCODE_VERSION"
	[ "$ocver" = 'latest' ] && ocver=$(wget -q -O - $npm/cli/latest | \
		grep -o '"version": *"[^"]*"' | head -n1 | sed 's|.*"\([^"]*\)"$|\1|')
	[ -n "$ocver" ] || die "Unable to find the latest opencode version"
	stamp="$rootfs/var/lib/opencode.version"
	if [ "$(cat $stamp 2>/dev/null)" = "$ocver" ]; then
		echo "opencode $ocver is already installed"
		return
	fi

	# Old CPUs without AVX2 need the baseline build
	build="linux-x64"
	grep -qw avx2 /proc/cpuinfo || build="linux-x64-baseline"

	action "Installing: opencode $ocver ($build)"
	tmp=$(mktemp -d)
	sum=$(wget -q -O - $npm/cli-$build/$ocver | \
		grep -o '"shasum": *"[0-9a-f]*"' | sed 's|.*"\([0-9a-f]*\)"$|\1|')
	wget -q -O $tmp/opencode.tgz \
		"$npm/cli-$build/-/cli-$build-$ocver.tgz" &&
	echo "$sum  $tmp/opencode.tgz" | sha1sum -c -s &&
	tar -xzf $tmp/opencode.tgz -C $tmp &&
	install -D -m 0755 $tmp/package/bin/opencode \
		$rootfs/usr/libexec/opencode &&
	echo "$ocver" > $stamp
	status
	rm -rf $tmp

	# By default opencode leaves a background server running for each dev.
	# With --standalone the server is a child of the session and dies
	# with it. Subcommands take it last, except the service tools.
	cat > $rootfs/usr/bin/opencode <<'EOT'
#!/bin/sh
# opencode wrapper for the SliTaz coding jail: no background server.
bin=/usr/libexec/opencode
case "$1" in
	""|-*)
		exec $bin --standalone "$@" ;;
	upgrade|update|uninstall|service|serve|acp|api|pair|reload|debug)
		exec $bin "$@" ;;
esac
# A directory to start the TUI in, or a subcommand
[ -d "$1" ] && exec $bin --standalone "$@"
exec $bin "$@" --standalone
EOT
	chmod 0755 $rootfs/usr/bin/opencode
}

# Default opencode config, coding rules and welcome message for new devs.
# The jail entry copies /etc/skel in a dev home on first login.
jail_skel() {
	action 'Creating opencode default config...'
	skel="$rootfs/etc/skel/.config/opencode"
	mkdir -p $skel
	# Agent temp files in a private dir, not in the shared /tmp
	cat > $rootfs/etc/skel/.profile <<'EOT'
mkdir -p -m 0700 "$HOME/.tmp"
export TMPDIR="$HOME/.tmp"
EOT
	# Devs can't write /usr/bin: tazdev jail-up upgrades opencode
	cat > $skel/opencode.json <<EOT
{
	"\$schema": "https://opencode.ai/config.json",
	"autoupdate": false
}
EOT
	cat > $skel/AGENTS.md <<'EOT'
# SliTaz coding rules

You work with a SliTaz GNU/Linux developer, in a shared coding jail on a
SliTaz host. The developer has the ideas and reviews, you write the code.

- KISS: simple and readable POSIX shell. SliTaz runs a busybox userland,
  so no GNU only options (stat -c, grep -P, ls --time-style).
- Match the surrounding code: style, tabs, naming, comment density.
- Receipts are plain shell: no macros, no abstraction layers.
- Code, comments and commit messages in English, one line commits.
- Never commit without the developer's OK on the diff, never push.
- You are in a jail: no root, no sudo, no package install. If a tool is
  missing, ask the developer.
- Before the first commit, if 'hg config ui.username' is empty, ask the
  developer for "Name <email>" and set it in ~/.hgrc (and git config
  --global user.name/user.email for git repos). Never commit as
  user@localhost: the developer pushes these commits to hg.slitaz.org.
EOT
	# UTF-8 for everyone: accents in nano, less and hg log
	echo 'LANG="en_US.UTF-8"' > $rootfs/etc/locale.conf
	# The coding entry replaces <user> with the dev login name
	cat > $rootfs/etc/motd <<EOT

  SliTaz AI coding jail - your home is yours, nobody else can read it.

    hg clone https://hg.slitaz.org/<project>
    cd <project> && opencode

  Back later? cd <project> && opencode -c to continue your last session.

  Type /models in opencode to pick another model. Free models may use
  your prompts to train: never paste a password, a key or a secret.

  On the host, your jail home is: $rootfs/home/<user>
  Get your work from there with cp, rsync or hg pull, and push it from
  your host account: your ssh keys never enter the jail.

EOT
	status
}

# Nobody may become root in the jail: locked root, no setuid/setgid.
jail_harden() {
	action 'Hardening the jail...'
	sed -i 's|^root:[^:]*:|root:!:|' $rootfs/etc/shadow
	# System dirs only: walking dev homes as root would follow their links
	find $rootfs/bin $rootfs/sbin $rootfs/lib $rootfs/usr -xdev -type f \
		\( -perm -4000 -o -perm -2000 \) -exec chmod ug-s {} \;
	# Busybox is no longer setuid: it must read its suid config itself
	# or every applet complains
	chmod 0644 $rootfs/etc/busybox.conf
	status
}

# The coding jail is a shared x86_64 chroot, not a build env
jail_env() {
	rootfs="$JAIL_ROOT"
	[ "$(uname -m)" = 'x86_64' ] || die "The coding jail needs an x86_64 host"
	command -v spk-add >/dev/null || die "The coding jail is installed with spk"
}

# Install the jail packages with spk from the host (--root). Also used by
# jail-up: packages added to JAIL_PKGS get installed, others are skipped.
jail_install() {
	mkdir -p $rootfs/var/lib/tazpkg
	echo "${mirror:-$JAIL_MIRROR}" > $rootfs/var/lib/tazpkg/mirror
	# x86_64 binaries want /lib64/ld-linux-x86-64.so.2: packages that
	# chroot in post_install (glibc locale-gen) need it from the start
	[ -e "$rootfs/lib64" ] || ln -sf lib $rootfs/lib64
	spk-up --list --root=$rootfs
	spk-add $JAIL_PKGS --root=$rootfs
	cat /etc/resolv.conf > $rootfs/etc/resolv.conf
	cat /etc/TZ > $rootfs/etc/TZ
}

# spk never runs in the jail: drop the package cache and the derived
# SQLite index (spk-up builds it when the host has sqlite3)
jail_clean() {
	rm -rf $rootfs/var/cache/tazpkg/*
	rm -f $rootfs/var/lib/tazpkg/packages.sql
	# Nobody is inside (the jail is not mounted): no stale shared tmp files
	rm -rf $rootfs/tmp/* $rootfs/var/tmp/*
}

#
# Commands
#

case "$1" in
	stats|-s)
		title "Statistics for $PROJECTS"
		printf "%-23s %-13s %-15s %s\n" 'Project' 'Revision' 'Version' 'Files'
		separator '-'
		cd $PROJECTS
		for proj in *; do
			rev=""
			if [ -d "$PROJECTS/$proj/.hg" ]; then
				cd $PROJECTS/$proj
				rev=$(hg head --template '{rev};' | sed 's|;$||')	# we have multiple heads in some projects
				vers=$(hg tags | grep -v tip | head -n1 | cut -d" " -f1)	# some projects don't have any version tags
				files=$(find . -type f | wc -l)
				printf "%-23s %-13s %-15s %s\n" "$proj" "$rev" "${vers:--}" "$files"
			fi
		done
		footer
		;;

	chroot|-c)
		# Chroot into a build env. Default to cooking configured in tazdev.conf
		check_root $1
		get_version $@
		[ -d "$2" ] && rootfs="$2"
		# Only unmount on exit what this invocation mounted itself: on a
		# build host the chroot is permanently mounted (boot scripts, cron
		# jobs inside) and entering/leaving a shell must not tear it down.
		premounted=""
		[ -d "$rootfs/proc/1" ] && premounted="yes"
		mount_chroot
		echo -e "Chrooting to: $rootfs\n"
		chroot $rootfs /bin/sh --login
		if [ -n "$premounted" ]; then
			echo "Leaving mounts in place (chroot was already mounted)."
		else
			umount_chroot
		fi
		;;

	mount-chroot|-mc)
		# Mount virtual FS and bind mounts without entering the chroot:
		# lets a cron or a build bot chroot directly afterwards.
		check_root $1
		get_version $@
		[ -d "$2" ] && rootfs="$2"
		mount_chroot
		;;

	umchroot|-uc)
		check_root $1
		get_version $@
		[ -d "$2" ] && rootfs="$2"
		umount_chroot $rootfs
		;;

	gen-chroot|-gc)
		check_root $1
		# We can use: --rootfs=/path/to/chroot
		[ -n "$rootfs" ] || get_version $@

		# If --clean or --forced option given
		[ -n "$clean$forced" ] && $0 -cc --rootfs=$rootfs

		# Don't break another env.
		[ -d "$rootfs/bin" ] &&
			die "A chroot environment already exists in $rootfs.\nUse --clean or --forced to clean this chroot."

		gen_chroot
		case "$version" in
			cooking) version="" ;;
		esac
		[ "$arch" ] && version="$version --arch=$arch"
		footer "Ready to chroot with: $(colorize 34 "tazdev -c $version")"
		;;

	gen-jail|-gj)
		# Shared jail for AI coding, devs keep their homes on --clean
		check_root $1
		jail_env
		[ -d "$rootfs/proc/1" ] &&
			die "The jail is mounted, first run: tazdev jail-umount"
		[ -n "$clean$forced" ] && $0 -cc --rootfs=$rootfs
		[ -d "$rootfs/bin" ] &&
			die "A jail already exists in $rootfs.\nUse --clean or --forced to clean it."

		title "Generating coding jail in $rootfs"
		jail_install
		jail_skel
		jail_opencode
		jail_harden
		jail_clean
		footer "Devs enter the jail with: $(colorize 34 coding)"
		;;

	jail-up|-ju)
		check_root $1
		jail_env
		[ -d "$rootfs/bin" ] ||
			die "No jail in $rootfs, create it with: tazdev gen-jail"
		# Package scripts run as root in the jail: devs must be out
		mountpoint -q $rootfs/proc &&
			die "The jail is mounted, first run: tazdev jail-umount"
		title "Upgrading coding jail $rootfs"
		jail_install
		spk-up --add --root=$rootfs
		jail_opencode
		# A busybox upgrade brings its setuid bit back
		jail_harden
		jail_clean
		footer
		;;

	jail-umount|-uj)
		check_root $1
		mountpoint -q $JAIL_ROOT/proc || die "The jail is not mounted"
		# Same lock as the coding entry: nobody enters while we unmount
		exec 9> /var/lock/coding.lock
		flock 9
		# Unmounting under a dev would break their session (no /dev, /proc)
		jail=$(readlink -f $JAIL_ROOT)
		devs=""
		for root in /proc/[0-9]*/root; do
			[ "$(readlink $root)" = "$jail" ] || continue
			pid=${root#/proc/}; pid=${pid%/root}
			devs="$devs $(ls -ld /proc/$pid 2>/dev/null | awk '{print $3}')"
		done
		devs=$(echo $(echo $devs | tr ' ' '\n' | sort -u))
		[ -n "$devs" ] && die "Devs still in the jail: $devs"
		umount_chroot $JAIL_ROOT
		;;

	jail-log|-jl)
		# Written by the coding jail entry: date time user enter|leave|hangup
		[ -f "$JAIL_LOG" ] || die "No jail log: $JAIL_LOG"
		tail -n ${2:-40} $JAIL_LOG
		;;

	clean-chroot|-cc)
		check_root $1
		# We can use: --rootfs=/path/to/chroot
		[ -n "$rootfs" ] || get_version $@

		[ ! -d "$rootfs" ] && die "Chroot '$rootfs' doesn't exist"

		[ -d "$rootfs/proc/1" ] && die "WARNING: $rootfs/proc mounted!"

		cd $rootfs || exit 1

		title "Cleaning chroot $rootfs"
		# Keep root/ and /home they may have a build wok, custom scripts, etc.
		for i in boot bin dev etc lib media mnt proc sbin sys tmp usr var run; do
			if [ -d "$i" ]; then
				action "Removing: $i ($(du -sh $i | awk '{ print $1 }'))..."
				rm -rf $i
				status
			fi
		done
		rm -f init
		footer
		;;

	push|-p)
		check_mirror $@
		rsync -r -t -O -l -v -z --delete \
			$local -e ssh $user@$MIRROR:$remote
		;;

	dry-push|-dp)
		check_mirror $@
		rsync -r -t -O -l -v -z --delete --dry-run \
			$local -e ssh $user@$MIRROR:$remote
		;;

	pull)
		check_mirror $@
		rsync -r -t -l -v -z --delete \
			-e ssh $user@$MIRROR:$remote $local
		;;

	dry-pull)
		check_mirror $@
		rsync -r -t -l -v -z --delete --dry-run \
			-e ssh $user@$MIRROR:$remote $local
		;;

	up-repos|-ur)
		# Update all at once.
		title "Update all SliTaz Hg repos"
		for p in $(ls $PROJECTS); do
			title "Project $p"
			cd $PROJECTS/$p
			hg pull -u
		done
		footer
		;;

	relpkg|-rp)
		# Release a slitaz sub-project and upload tarball to mirror
		[ -z "$MIRROR_SOURCES" ] && MIRROR_SOURCES="/var/www/slitaz/mirror/sources"
		[ -z "$2" ] && die "Usage: $0 relpkg package [version]"

		pkg=$2
		version="$3"

		# We can get the last found version in .hgtags
		[ -n "$version" ] ||
			version=$(awk 'END{print $2}' $PROJECTS/$pkg/.hgtags)

		newline
		cd $PROJECTS/$pkg

		# Sanity check
		grep -q $version$ .hgtags ||
			die "Missing Hg tag for version $version\nYou may want to: hg tag $version; hg push\n"

		# Archive
		action "Creating tarball and md5sum for $pkg-$version..."
		hg archive -t tgz $SOURCE/$pkg-$version.tar.gz
		( cd $SOURCE; md5sum $pkg-$version.tar.gz > $pkg-$version.md5 )
		status

		# Upload
		confirm 'Do you wish to upload tarball to the mirror?' 'n' &&
		{
			cd $SOURCE
			echo "Uploading to $MIRROR/sources/${pkg#slitaz-}"
			scp "$pkg-$version.tar.gz" "$pkg-$version.md5" \
				$MIRROR:$MIRROR_SOURCES/${pkg#slitaz-}
		}

		# Update pkg in wok
		confirm "Do you wish to update $pkg in wok?" 'n' &&
		{
			action "Updating $pkg: $version"
			cd $PROJECTS/wok
			sed -i s"/VERSION=.*/VERSION=\"$version\"/" $pkg*/receipt
			status
		}
		;;

	setup-cgi|-sc)
		. /etc/slitaz/slitaz.conf
		echo "$(boldify 'Public:') $PUBLIC"
		mkdir -p $PUBLIC/cgi-bin
		cd $PUBLIC/cgi-bin
		for proj in tazbug tinycm mediabox; do
			[ -d "$proj" ] || hg clone http://hg.slitaz.org/$proj
		done
		[ -d "$INSTALLED/lighttpd" ] || sudo tazpkg -gi lighttpd

		echo
		echo "TODO: setup SCN (tinycm install + plugins from slitaz-forge)"
		echo
		echo "$(boldify 'URL    :') http://localhost/~$USER/cgi-bin/"
		;;

	help)
		doc='/usr/share/doc/slitaz/'
		topics="Available help topics: $(cd $doc; ls *.txt | sed 's|.txt$||' | tr '\n' ' ')"
		[ -n "$2" ] ||
		die "Missing tool/library name\nExample: tazdev help httphelper\n\n$topics"

		if [ -f "$doc$2.txt" ]; then
			{
				output='raw' title "Help for $2"
				cat $doc$2.txt
				footer
			} | less -M
		else
			echo "No help found for '$2'"
			longline "$topics"
		fi
		;;

	usage|*)
		usage
		;;

esac

exit 0
