contrib/pkgutils/pkgmk
Han Boetes 8f5a3573ce bump
2006-11-15 19:07:52 +01:00

855 lines
20 KiB
Bash
Executable File

#!/bin/bash
#
# pkgutils
#
# Copyright (c) 2000-2004 Per Liden
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
# USA.
# zsh/bash portability stuff
if [ -z "$ZSH_VERSION" ]; then
port_read()
{
read -n 1 "$@"
}
port_type()
{
type -t "$@"
}
else
alias port_read='read -k 1'
alias port_type='type'
emulate sh
setopt braceexpand
fi
info()
{
echo "=======> $1"
}
warning()
{
info "WARNING: $1" >&2
}
error()
{
info "ERROR: $1" >&2
}
keep_work()
{
if [ "$PKGMK_KEEP_WORK" = no ]; then
rm -rf $PKGMK_WORK_DIR
fi
}
get_filename()
{
local FILE="$(echo $1 | sed 's|^.*://.*/||g')"
if [ "$FILE" != "$1" ]; then
FILE="$PKGMK_SOURCE_DIR/$FILE"
fi
echo $FILE
}
check_pkgfile()
{
if [ ! "$name" ]; then
error "Variable \`name' not specified in $PKGMK_PKGFILE."
exit 1
elif [ ! "$version" ]; then
error "Variable \`version' not specified in $PKGMK_PKGFILE."
exit 1
elif [ ! "$release" ]; then
error "Variable \`release' not specified in $PKGMK_PKGFILE."
exit 1
fi
case $(port_type build) in
*function) : ;;
*)
error "Function \`build' not specified in $PKGMK_PKGFILE."
exit 1
;;
esac
}
check_directory()
{
if [ ! -d $1 ]; then
error "Directory \`$1' does not exist."
exit 1
elif [ ! -w $1 ]; then
error "Directory \`$1' not writable."
exit 1
elif [ ! -x $1 ] || [ ! -r $1 ]; then
error "Directory \`$1' not readable."
exit 1
fi
}
download_file()
{
info "Downloading \`$1'."
if ! type -p wget > /dev/null 2>&1; then
error "Command \`wget' not found"
exit 1
fi
FILENAME=${1##*/}
PARTIALNAME=$FILENAME.$PART_EXT
DOWNLOAD_CMD="--passive-ftp --no-directories --tries=1
-O $PKGMK_SOURCE_DIR/$PARTIALNAME $1"
if [ -f $PKGMK_SOURCE_DIR/$PARTIALNAME ]; then
info "Partial download found, trying to continue..."
if ! wget -c $DOWNLOAD_CMD; then
# resume failed; check whether is there at all
if wget --tries=1 --passive-ftp --spider $1; then
info "Partial downloading failed, restarting..."
# it's there but resuming is not supported; restart
rm -f $1.$PART_EXT
wget $DOWNLOAD_CMD
fi
fi
else
wget $DOWNLOAD_CMD
fi
if [ $? != 0 -o ! -s $PKGMK_SOURCE_DIR/$PARTIALNAME ];then
error "Downloading \`$1' failed."
rm -f $PKGMK_SOURCE_DIR/$PARTIALNAME
exit 1
fi
# Test if file is a html file. Workaround buggy sourceforge mirrors.
if file $PKGMK_SOURCE_DIR/$PARTIALNAME | grep -q 'HTML document text' && [ $PARTIALNAME = "$(echo $PARTIALNAME|sed -e 's|html*$||')" ]; then
error "Downloading \`$1' failed."
rm -f $PKGMK_SOURCE_DIR/$PARTIALNAME
exit 1
fi
mv $PKGMK_SOURCE_DIR/$PARTIALNAME $PKGMK_SOURCE_DIR/$FILENAME
}
download_source()
{
local FILE LOCAL_FILENAME
for FILE in ${source[@]}; do
LOCAL_FILENAME=$(get_filename $FILE)
if [ ! -e $LOCAL_FILENAME ]; then
if [ "$LOCAL_FILENAME" = "$FILE" ]; then
error "Source file \`$LOCAL_FILENAME' not found (can not be downloaded, URL not specified)."
exit 1
else
if [ "$PKGMK_DOWNLOAD" = yes ]; then
download_file $FILE
else
error "Source file \`$LOCAL_FILENAME' not found (use option -d to download)."
exit 1
fi
fi
fi
done
}
unpack_source()
{
local FILE LOCAL_FILENAME COMMAND
for FILE in ${source[@]}; do
LOCAL_FILENAME=$(get_filename $FILE)
case $LOCAL_FILENAME in
*.tar.gz|*.tar.Z|*.tgz)
COMMAND="tar xzf $LOCAL_FILENAME -C $SRC" ;;
*.tar.bz2)
COMMAND="tar xjf $LOCAL_FILENAME -C $SRC" ;;
*.zip)
COMMAND="unzip -qq -o -d $SRC $LOCAL_FILENAME" ;;
*)
COMMAND="cp $LOCAL_FILENAME $SRC" ;;
esac
echo "$COMMAND"
if ! $COMMAND; then
keep_work
error "Building \`$TARGET' failed."
exit 1
fi
done
}
make_footprint()
{
pkginfo --footprint $TARGET | \
sed "s|\tlib/modules/$(uname -r)/|\tlib/modules/<kernel-version>/|g" | \
sort -k 3
}
make_md5sum()
{
local FILE LOCAL_FILENAMES
if [ "$source" ]; then
for FILE in ${source[@]}; do
LOCAL_FILENAMES="$LOCAL_FILENAMES $(get_filename $FILE)"
done
md5sum $LOCAL_FILENAMES | sed -e 's| .*/| |' | sort -k 2
fi
}
update_md5sum()
{
if ! make_md5sum > $PKGMK_MD5SUM; then
error "Can't update the md5sum."
keep_work
error "Building \`$TARGET' failed."
exit 1
fi
}
check_md5sum()
{
local TMPFILE="$PKGMK_WORK_DIR/.tmp"
cd $PKGMK_ROOT || error "Can't access directory $PKGMK_ROOT."
if [ -f $PKGMK_MD5SUM ]; then
make_md5sum > $TMPFILE.md5sum
sort -k 2 $PKGMK_MD5SUM > $TMPFILE.md5sum.orig
# Add a special test for the serious kind of md5sum differences
sort -k2 $TMPFILE.md5sum.orig $TMPFILE.md5sum |uniq -u|uniq -D -f1 > $TMPFILE.md5sum_secdiff
if [ -s $TMPFILE.md5sum_secdiff ]; then
error "Identical file with different md5sum found:"
echo
cat $TMPFILE.md5sum_secdiff
cat << EOF
This can mean a few things:
- The download of that sourcepackage failed. Remove the sourcefile
with:
$PKGMK -c
- If this results in the same error the sourcepackage may have been
tempered with. Send the maintainer of this package an email and ask
him to doublecheck the problem and to take necessary steps.
- You are maintaining this package and you just changed a file included
in the portsdir and you know what you are doing.
EOF
fi
diff -w -t -U 0 $TMPFILE.md5sum.orig $TMPFILE.md5sum | \
sed '/^@@/d;/^+++/d;/^---/d;s/^+/NEW /g;s/^-/MISSING /g' \
> $TMPFILE.md5sum.diff
if [ -s $TMPFILE.md5sum.diff ]; then
error "Md5sum mismatch found:"
cat $TMPFILE.md5sum.diff >&2
echo -n 'Do you want me to update the md5sum? [y/N] '
port_read answer
echo
if [ "$answer" = y -o "$answer" = Y ]; then
update_md5sum
else
keep_work
error "Building \`$TARGET' failed."
exit 1
fi
fi
else
warning "Md5sum not found, creating new."
update_md5sum
fi
}
strip_files()
{
local FILE FILTER
cd $PKG
if [ -f $PKGMK_ROOT/$PKGMK_NOSTRIP ]; then
FILTER="grep -v -f $PKGMK_ROOT/$PKGMK_NOSTRIP"
else
FILTER="cat"
fi
find .{,/usr,/usr/X11R6}/{bin,sbin,lib} -type f 2> /dev/null |$FILTER |while read FILE; do
case "$(file -b $FILE)" in
*statically*)
: ;; # ignore
*ELF*executable*not\ stripped)
strip --strip-all "$FILE" ;;
*ELF*shared*object*not\ stripped)
strip --strip-unneeded "$FILE" ;;
*current*ar\ archive)
strip --strip-debug "$FILE";;
esac
done
}
compress_manpages()
{
local FILE DIR TARGET
cd $PKG
find . -type f -path "*/man/man*/*" | while read FILE; do
if [ "$FILE" = "${FILE%%.gz}" ]; then
gzip -9 "$FILE"
fi
done
find . -type l -path "*/man/man*/*" | while read FILE; do
TARGET=$(readlink -n "$FILE")
TARGET="${TARGET##*/}"
TARGET="${TARGET%%.gz}.gz"
rm -f "$FILE"
FILE="${FILE%%.gz}.gz"
DIR="${FILE%/*}"
if [ -e "$DIR/$TARGET" ]; then
ln -sf "$TARGET" "$FILE"
fi
done
}
check_footprint()
{
local FILE="$PKGMK_WORK_DIR/.tmp"
cd $PKGMK_ROOT
if [ -f $TARGET ]; then
make_footprint > $FILE.footprint
# if [ -f $PKGMK_FOOTPRINT.gz ]; then
# gunzip $PKGMK_FOOTPRINT.gz
# fi
if [ -f $PKGMK_FOOTPRINT ]; then
sort -k 3 $PKGMK_FOOTPRINT > $FILE.footprint.orig
diff -w -t -U 0 $FILE.footprint.orig $FILE.footprint |\
sed '/^@@/d;/^+++/d;/^---/d;s/^+/NEW /g;s/^-/MISSING /g' \
> $FILE.footprint.diff
if [ -s $FILE.footprint.diff ]; then
warning "Footprint mismatch found:"
cat $FILE.footprint.diff >&2
false
until [ $? -eq 0 ]; do
echo -n '[u]pdate footprint/[e]rror/[i]gnore footprint mismatch [u/e/i] '
port_read answer
echo
case $answer in
u|U) update_footprint ;;
e|E)
touch -r $PKGMK_ROOT/$PKGMK_PKGFILE $TARGET
error "Building \`$TARGET' failed, keeping workdirectory for inspection."
exit 1
;;
i|I) warning "Footprint mismatch ignored." ;;
*) false ;;
esac
done
fi
# gzip $PKGMK_FOOTPRINT
else
warning "Footprint not found, creating new."
mv $FILE.footprint $PKGMK_FOOTPRINT
# gzip $PKGMK_FOOTPRINT
fi
else
error "Package \`$TARGET' was not found."
BUILD_SUCCESSFUL=no
fi
}
build_package()
{
local BUILD_SUCCESSFUL=no
info "Building \`$TARGET'."
export PKG="$PKGMK_WORK_DIR/pkg"
export SRC="$PKGMK_WORK_DIR/src"
umask 022
cd $PKGMK_ROOT
if [ "$SHORTCIRCUIT" != yes -a "$IN_FAKEROOT_MODE" != yes ]; then
rm -rf $PKGMK_WORK_DIR
mkdir -p $SRC $PKG
if [ "$PKGMK_IGNORE_MD5SUM" = no ]; then
check_md5sum
fi
unpack_source
fi
# Enter the fakeroot environment if necessary. This will
# call the pkgmk script again as the fake root user.
if [ $(whoami) != root ] && [ "$ROOT_BUILD" = no ]; then
if type fakeroot > /dev/null 2>&1; then
info "Entering fakeroot environment"
IN_FAKEROOT_MODE=yes fakeroot $PKGMK_COMMAND $PKGMK_ARGS
exitcode=$?
info 'Leaving fakeroot environment'
exit $exitcode
else
warning "\`fakeroot' is not installed. Building as an unprivileged user"
warning "will result in non-root ownership of the packaged files."
warning "Install the \`fakeroot' package to build correct packages as"
warning "non-root user."
sleep 5
fi
fi
cd $SRC
(set -e -x ; build)
if [ $? -eq 0 ]; then
if [ "$PKGMK_NO_STRIP" = no ]; then
strip_files
fi
compress_manpages
cd $PKG
info "Build result:"
rm -f $TARGET
tar czvvf $TARGET *
if [ $? -eq 0 ]; then
BUILD_SUCCESSFUL=yes
if [ "$PKGMK_IGNORE_FOOTPRINT" = yes ]; then
warning "Footprint ignored."
else
check_footprint
fi
fi
elif [ "$PKGMK_KEEP_ON_ERROR" = yes ]; then
error "Building \`$TARGET' failed, keeping workdirectory for inspection."
exit 1
fi
keep_work
if [ "$BUILD_SUCCESSFUL" = yes ]; then
info "Building \`$TARGET' succeeded."
else
if [ -f $TARGET ]; then
touch -r $PKGMK_ROOT/$PKGMK_PKGFILE $TARGET > /dev/null 2>&1
fi
error "Building \`$TARGET' failed."
exit 1
fi
}
install_package()
{
# No longer necessary.
unset LD_PRELOAD
local COMMAND OPTIONS TMP1
info "Installing \`$TARGET'."
case $PKGMK_INSTALL in
install)
OPTIONS=
;;
force_install)
OPTIONS="-f"
;;
upgrade)
OPTIONS="-u"
;;
force_upgrade)
OPTIONS="-u -f"
;;
esac
COMMAND="$SUDO pkgadd $OPTIONS $TARGET"
cd $PKGMK_ROOT
info "$COMMAND"
# If only pkgadd would return various values for various
# errors.
TMP1=$(mktemp pkgmk.XXXXXXXX) || {
echo "Can't create tempfile"
exit 1
}
$COMMAND 2> $TMP1
errorcode=$?
# Give proper errormessages.
if fgrep -q 'listed file(s) already installed' $TMP1; then
fgrep -v 'listed file(s) already installed' $TMP1 >&2
echo -n 'listed file(s) already installed. ' >&2
case $PKGMK_INSTALL in
install) echo "Use ${PKGMK##*/} -fi to ignore and overwrite." >&2 ;;
upgrade) echo "Use ${PKGMK##*/} -fu to ignore and overwrite." >&2 ;;
esac
elif grep -q 'package.*not previously installed' $TMP1; then
echo "Package $name not previously installed." >&2
echo "Use ${PKGMK##*/} -i to install."
else
cat $TMP1 >&2
fi
rm -f $TMP1
if [ $errorcode -eq 0 ]; then
info "Installing \`$TARGET' succeeded."
exit 0
else
error "Installing \`$TARGET' failed."
exit 1
fi
}
recursive()
{
local ARGS FILE DIR
ARGS=$(echo "$@" | sed -e "s/--recursive//g" -e "s/-r//g")
for FILE in $(find $PKGMK_ROOT -name $PKGMK_PKGFILE | sort); do
DIR="$(dirname $FILE)/"
if [ -d $DIR ]; then
info "Entering directory \`$DIR'."
(cd $DIR && $PKGMK_COMMAND $ARGS)
info "Leaving directory \`$DIR'."
fi
done
}
clean()
{
local FILE LOCAL_FILENAME
if [ -f $TARGET ]; then
info "Removing $TARGET"
rm -f $TARGET
fi
for FILE in ${source[@]}; do
LOCAL_FILENAME=$(get_filename $FILE)
if [ -e "$LOCAL_FILENAME" ] && [ "$LOCAL_FILENAME" != "$FILE" ]; then
info "Removing $LOCAL_FILENAME"
rm -f $LOCAL_FILENAME
fi
if [ -e "$LOCAL_FILENAME.$PART_EXT" ]; then
info "Removing $LOCAL_FILENAME.$PART_EXT"
rm -f "$LOCAL_FILENAME.$PART_EXT"
fi
done
if [ -d $PKGMK_WORK_DIR ]; then
info "Removing $PKGMK_WORK_DIR"
keep_work
fi
}
update_footprint()
{
if [ ! -f $TARGET ]; then
error "Unable to update footprint. File \`$TARGET' not found."
exit 1
fi
make_footprint > $PKGMK_FOOTPRINT
touch $TARGET
info "Footprint updated."
}
build_needed()
{
local FILE RESULT
RESULT=yes
if [ -f $TARGET ]; then
RESULT=no
for FILE in $PKGMK_PKGFILE ${source[@]}; do
FILE=$(get_filename $FILE)
if [ ! -e $FILE ] || [ ! $TARGET -nt $FILE ]; then
RESULT=yes
break
fi
done
fi
echo $RESULT
}
interrupted()
{
echo
error "Interrupted."
keep_work
exit 1
}
print_help()
{
cat << EOF
usage: ${PKGMK##*/} [options]
options:
-i, --install build and install packag
-fi, --force-install build and install package (ignore conflicts)
-u, --upgrade build and upgrade package
-fu, --force-upgrade build and upgrade package (ignore conflicts)
-r, --recursive search for and build packages recursively
-d, --download download missing source file(s)
-do, --download-only do not build, only download missing source file(s)
-utd, --up-to-date do not build, only check if package is up to date
-uf, --update-footprint update footprint using result from last build
-if, --ignore-footprint build package without checking footprint
-um, --update-md5sum update md5sum
-im, --ignore-md5sum build package without checking md5sum
-ns, --no-strip do not strip executable binaries or libraries
-f, --force build package even if it appears to be up to date
-rb, --root-build build package with root-permissions
-s, --short-circuit do not unpack the source before building
-c, --clean remove package and downloaded files
-up, --update-port clean, remove footprint, edit Pkgfile, build, upgrade
-kw, --keep-work keep temporary working directory
-cf, --config-file <file> use alternative configuration file
-v, --version print version and exit
-h, --help print help and exit
EOF
}
parse_options()
{
while [ $# -ge 1 ]; do
case $1 in
-i|--install)
PKGMK_INSTALL=install ;;
-fi|--force-install)
PKGMK_INSTALL=force_install ;;
-u|--upgrade)
PKGMK_INSTALL=upgrade ;;
-fu|--force-upgrade)
PKGMK_INSTALL=force_upgrade ;;
-r|--recursive)
PKGMK_RECURSIVE=yes ;;
-d|--download)
PKGMK_DOWNLOAD=yes ;;
-do|--download-only)
PKGMK_DOWNLOAD=yes
PKGMK_DOWNLOAD_ONLY=yes ;;
-utd|--up-to-date)
PKGMK_UP_TO_DATE=yes ;;
-uf|--update-footprint)
PKGMK_UPDATE_FOOTPRINT=yes ;;
-if|--ignore-footprint)
PKGMK_IGNORE_FOOTPRINT=yes ;;
-um|--update-md5sum)
PKGMK_UPDATE_MD5SUM=yes ;;
-up|--update-port)
if [ -r $PKGMK_FOOTPRINT ]; then
warning "Removing footprint \`$PKGMK_FOOTPRINT'"
rm $PKGMK_FOOTPRINT
fi
PKGMK_INSTALL=upgrade
PKGMK_UPDATE_PORT=yes ;;
-im|--ignore-md5sum)
PKGMK_IGNORE_MD5SUM=yes ;;
-ns|--no-strip)
PKGMK_NO_STRIP=yes ;;
-f|--force)
PKGMK_FORCE=yes ;;
-s|--short-circuit)
SHORTCIRCUIT=yes ;;
-rb|--root-build)
ROOT_BUILD=yes ;;
-c|--clean)
PKGMK_CLEAN=yes ;;
-kw|--keep-work)
PKGMK_KEEP_WORK=yes ;;
-cf|--config-file)
if [ ! "$2" ]; then
echo "$PKGMK: option \`$1' requires an argument"
exit 1
fi
PKGMK_CONFFILE="$2"
shift ;;
-v|--version)
echo "$PKGMK (pkgutils) $PKGMK_VERSION"
exit 0 ;;
-h|--help)
print_help
exit 0 ;;
*)
echo "$PKGMK: invalid option $1" >&2
echo "Use \`$PKGMK -h' for help."
exit 1 ;;
esac
shift
done
}
main()
{
local FILE TARGET
parse_options "$@"
if [ $ROOT_BUILD != yes ]; then
if touch /root/nop > /dev/null 2>&1; then
error 'I know it is hard to get used to, but...'
error 'Now you have to build packages as user and not as root.'
exit 1
fi
elif [ $(whoami) != root ]; then
error "You choose to build as root, but you don't have root permissions."
exit 1
fi
if [ "$PKGMK_RECURSIVE" = yes ]; then
recursive "$@"
exit 0
fi
for FILE in $PKGMK_CONFFILE $PKGMK_PKGFILE; do
if [ ! -f $FILE ]; then
error "File \`$FILE' not found."
exit 1
fi
source $FILE
done
check_directory "$PKGMK_SOURCE_DIR"
check_directory "$PKGMK_PACKAGE_DIR"
check_directory "${PKGMK_WORK_DIR%/*}"
check_pkgfile
TARGET="$PKGMK_PACKAGE_DIR/$name#$version-$release.pkg.tar.gz"
if [ "$PKGMK_UPDATE_PORT" = yes ] && [ -z "$IN_FAKEROOT_MODE" ]; then
if ! type -p "$EDITOR" > /dev/null 2>&1; then
echo "You haven't set your \$EDITOR environment variable."
exit 1
fi
clean
rm -f $PKGMK_MD5SUM
$EDITOR $PKGMK_PKGFILE
fi
if [ "$PKGMK_CLEAN" = yes ]; then
clean
exit 0
fi
if [ "$PKGMK_UPDATE_FOOTPRINT" = yes ]; then
update_footprint
exit 0
fi
if [ "$PKGMK_UPDATE_MD5SUM" = yes ]; then
download_source
make_md5sum > $PKGMK_MD5SUM
info "Md5sum updated."
exit 0
fi
if [ "$PKGMK_DOWNLOAD_ONLY" = yes ]; then
download_source
exit 0
fi
if [ "$PKGMK_UP_TO_DATE" = yes ]; then
if [ $(build_needed) = yes ]; then
info "Package \`$TARGET' is not up to date."
else
info "Package \`$TARGET' is up to date."
fi
exit 0
fi
if [ $(build_needed) = no ] && [ "$PKGMK_FORCE" = no ]; then
info "Package \`$TARGET' is up to date."
else
download_source
build_package
fi
if [ "$PKGMK_INSTALL" != no ]; then
install_package
fi
exit 0
}
trap "interrupted" 1 2 3 15
export LC_ALL=POSIX
readonly PKGMK_VERSION=5.18
readonly PKGMK_COMMAND=$0
readonly PKGMK=${PKGMK_COMMAND##*/}
readonly PKGMK_ROOT="$PWD"
# Bug in zsh
PKGMK_ARGS="$@"
readonly PKGMK_ARGS
readonly SUDO=/usr/bin/sudo
PKGMK_CONFFILE=/etc/pkgmk.conf
PKGMK_PKGFILE=Pkgfile
PKGMK_FOOTPRINT=.footprint
PKGMK_MD5SUM=.md5sum
PKGMK_NOSTRIP=.nostrip
PART_EXT=pkgmk-partial
PKGMK_SOURCE_DIR='/usr/pkgmk/source'
PKGMK_PACKAGE_DIR='/usr/pkgmk/package'
PKGMK_WORK_DIR="/usr/pkgmk/work/${PWD##*/}"
PKGMK_INSTALL=no
PKGMK_RECURSIVE=no
PKGMK_DOWNLOAD=yes
PKGMK_DOWNLOAD_ONLY=no
PKGMK_UP_TO_DATE=no
PKGMK_UPDATE_FOOTPRINT=no
PKGMK_IGNORE_FOOTPRINT=no
PKGMK_UPDATE_PORT=no
PKGMK_FORCE=no
PKGMK_KEEP_WORK=no
PKGMK_KEEP_ON_ERROR=no
PKGMK_UPDATE_MD5SUM=no
PKGMK_IGNORE_MD5SUM=no
PKGMK_NO_STRIP=no
PKGMK_CLEAN=no
SHORTCIRCUIT=no
ROOT_BUILD=no
GNOME_MIRROR='ftp://ftp.gnome.org/pub/GNOME'
GNU_MIRROR='ftp://ftp.gnu.org/gnu'
KDE_MIRROR='ftp://ftp.kde.org/pub/kde/'
PERL_MIRROR='ftp://ftp.cpan.org/pub/CPAN/modules/by-module'
SOURCEFORGE_MIRROR='http://dl.sourceforge.net/sourceforge'
main "$@"