diff options
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rwxr-xr-x | build-repo.sh | 180 | ||||
-rw-r--r-- | index.html | 15 | ||||
-rw-r--r-- | style.css | 36 | ||||
-rw-r--r-- | xi_profile.sh | 48 | ||||
-rwxr-xr-x | xibuild | 271 | ||||
-rw-r--r-- | xibuild.sh | 190 |
8 files changed, 243 insertions, 504 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9f9058c --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +PREFIX=/usr + +install: + install -Dm755 xibuild.sh ${DESTDIR}${PREFIX}/bin/xibuild + install -Dm755 xi_profile.sh ${DESTDIR}${PREFIX}/lib/xibuild/xi_profile.sh diff --git a/README.md b/README.md deleted file mode 100644 index 42f3c4f..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# xibuild -The build system to make xipkgs for xilinux diff --git a/build-repo.sh b/build-repo.sh deleted file mode 100755 index d6ac627..0000000 --- a/build-repo.sh +++ /dev/null @@ -1,180 +0,0 @@ -#!/bin/bash - -XIBUILD=./xibuild - -fetch () { - git clone https://git.davidovski.xyz/xilinux/buildfiles - mkdir dist -} - -build () { - - REPOS_INDEX=dist/repo/index.html - rm $REPOS_INDEX - - echo-head "repo" >> $REPOS_INDEX - echo "<h1>repo</h1>" >> $REPOS_INDEX - - for REPO in $(du -h buildfiles/repo/* | awk '{print $2}' | sort -r | grep -v skip); do - REPO_NAME=$(echo $REPO | cut -d"/" -f2-) - REPO_DIR=$(realpath dist/$REPO_NAME) - - REPO_INDEX=$REPO_DIR/index.html - REPO_LIST_OLD=$REPO_DIR/packages.txt - REPO_LIST=$REPO_DIR/packages.list - - echo "<a href='/$REPO_NAME'><h2>$REPO_NAME</h2><a> " >> $REPOS_INDEX - - mkdir -pv $REPO_DIR - mkdir -pv $REPO_DIR/logs - #mkdir -pv dist/$REPO_NAME/src - touch $REPO_INDEX - touch $REPO_LIST_OLD - touch $REPO_LIST - - start-index $REPO_NAME $REPO_INDEX - - printf "" > xibuild.report.log - for BUILD_FILE in $REPO/*.xibuild; do - if [ ${#ONLY[@]} == 0 ] || ( echo ${ONLY[*]} | grep -q $(basename -s .xibuild $BUILD_FILE)); then - - DEST=dist/$REPO_NAME - $XIBUILD -o $DEST $BUILD_FILE - fi - extend-index $BUILD_FILE $REPO_INDEX - done; - - rm xibuild.report.log - conclude-index $REPO_INDEX - - generate-package-list - add-additional - - - echo "<p>package count: <strong>$(ls dist/$REPO_NAME/*.xipkg | wc -l)</strong></p>" >> $REPOS_INDEX - done; -} - -echo-head () { - echo "<html> - <head> - <title>$1</title> - <style>$(cat style.css)</style> - </head> - <body>" -} - -start-index() { - echo-head "packages for $1" > $2 - echo " - <h1>Packages in <a href='../'>$1</a></h1> - <table> - <tr> - <td>name</td> - <td>xibuild</td> - <td>build log</td> - <td>description</td> - <td>file</td> - <td>info file</td> - </tr> - " >> $2 -} - -extend-index () { - PKG_NAME=$(basename $1 .xibuild) - DESC=$(grep $PKG_NAME xibuild.report.log | cut -d" " -f3-) - - COLOR="none" - if tail -1 xibuild.report.log | grep -q "^new"; then - COLOR="pass" - fi - if tail -1 xibuild.report.log | grep -q "^fail"; then - if [ -f dist/$REPO_NAME/$PKG_NAME.xipkg ]; then - COLOR="warning" - else - COLOR="fail" - fi - fi - echo " - <tr class='$COLOR'> - <td>$PKG_NAME</td> - <td><a href='$PKG_NAME.xibuild'>src</a></td> - <td><a href='logs/$PKG_NAME.log'>log</a></td> - <td>$DESC</td> - <td><a href='$PKG_NAME.xipkg'>$PKG_NAME.xipkg</a></td> - <td><a href='$PKG_NAME.xipkg.info'>.info</a></td> - </tr> - " >> $2 -} - -conclude-index () { - echo "</table> - - <p>Latest builds: <b>$(date)</b></p> - - <h3>Legend:</h3> - <ul> - <li>build skipped; no updates</li> - <li class='fail'>build failed; no previous version</li> - <li class='warning'>build failed; previous version exists</li> - <li class='pass'>build passed: new update</li> - </ul> - </body> - </html> - " >> $1 -} - -generate-package-list () { - cd dist/$REPO_NAME - ls -1 *.xipkg.info > packages.txt - - echo "" > packages.list - for file in $(ls -1 *.xipkg); do - echo "$file $(md5sum $file | awk '{print $1}') $(du -s $file | awk '{print $1}') $(gzip -cd $file | tar -tvv | grep -c ^-)" >> packages.list - done; - cd - -} - -add-additional () { - # move logs and sources - mkdir -p dist/$REPO_NAME/logs - mv logs/* dist/$REPO_NAME/logs - - #mkdir -p dist/$REPO_NAME/src - #mv $REPO/* dist/$REPO_NAME/src/ - - # add key for whole repo - mkdir dist/keychain - cp keychain/*.pub dist/keychain/ -} - -clean () { - rm -rf buildfiles - rm -rf logs - rm -rf tmp - rm -rf xibuild.log -} - -sync () { - for i in $@; do - echo "syncing to $i" - [[ $# = 0 ]] || rsync -Lta --no-perms --no-owner --no-group --delete -z -e ssh ./dist/ $i - done; -} - -index () { - INDEX=dist/index.html - rm $INDEX - - echo-head "xilinux" >> $INDEX - cat index.html >> $INDEX -} - - -# update the repository - -clean -fetch -build -index -clean diff --git a/index.html b/index.html deleted file mode 100644 index e832fc6..0000000 --- a/index.html +++ /dev/null @@ -1,15 +0,0 @@ -<h1>xilinux</h1> - -<p>xilinux is a linux distro that currently has no reason to exist other than just a fun project of mine. Maybe it will do something unique to other distros in the future.</p> - - -<p>If you are interesting in setting up a repo, helping with development, maintaining the repos, or even just interested in this distro, contact davidovski at <a href="mailto:xi@davidovski.xyz">xi@davidovski.xyz</a> or on matrix <strong>davidovski@matrix.org</strong> Any help in any form would greatly be appreciated.</p> - -<h2>This is davidovski's xilinux repo</h2> - -<h3><strong><a href="/repo">Explore packages</a></strong></h3> - -<p>This repo is a mirror of <a href="https://xi.davidovski.xyz">xi.davidovski.xyz</a>, the development build system for xilinux.</p> - -<p>Currently there are no other build systems in action and all other "sources" are just mirrors of this one; being a development system, updates will only be made whenever I manually build all packages</p> - diff --git a/style.css b/style.css deleted file mode 100644 index d88512d..0000000 --- a/style.css +++ /dev/null @@ -1,36 +0,0 @@ -body { - padding: 1em; - background-color: #202020; - color: #bbbbbb; -} - -a,p,td,th,li { - color: #bbbbbb; -} - -h1,h2,h3,h4 { - background-color: #202020; - color: #b8ddea; -} - -th, td { - border: 1px solid #bbbbbb; - padding: 0.5em; -} - -th { - font-weight: bold; -} - -.pass { - background-color: #004400; -} - -.fail { - background-color: #aa0000; -} - -.warning { - background-color: #aa4400; - -} diff --git a/xi_profile.sh b/xi_profile.sh new file mode 100644 index 0000000..d88ed80 --- /dev/null +++ b/xi_profile.sh @@ -0,0 +1,48 @@ +#!/bin/sh +cd $1 + +prepare () { + echo "passing prepare" +} + +build () { + echo "passing build" +} + +check () { + echo "passing check" +} + +package () { + echo "passing package" +} + +for xibuild in *.xibuild; do + PKG_NAME=$(basename $xibuild .xibuild) + export PKG_DEST=./xipkg/$PKG_NAME + mkdir -p $PKG_DEST + + . ./$xibuild + + echo "==========================PREPARE STAGE==========================" + prepare || exit 1 + echo "==========================BUILD STAGE==========================" + build || exit 1 + echo "==========================CHECK STAGE==========================" + check || exit 1 + echo "==========================PACKAGE STAGE==========================" + package || exit 1 + + printf "checking for postinstall... " + if command -v postinstall > /dev/null; then + echo "adding postinstall" + POST_DIR=$PKG_DEST/var/lib/xipkg/postinstall + mkdir -p $POST_DIR + cat /build/$PKG_NAME.xibuild > $POST_DIR/$PKG_NAME.sh + echo "" >> $POST_DIR/$PKG_NAME.sh + echo "postinstall" >> $POST_DIR/$PKG_NAME.sh + else + echo "no postinstall" + fi + +done diff --git a/xibuild b/xibuild deleted file mode 100755 index af3ea88..0000000 --- a/xibuild +++ /dev/null @@ -1,271 +0,0 @@ -#!/bin/bash - -XI_ROOT=$(pwd) -PKGS_OUTPUT=$XI_ROOT/xipkgs -PRIV_KEY=$XI_ROOT/keychain/xi.pem - -ERROR="\033[0;31m" -INFO="\033[0;34m" -PASS="\033[0;32m" -NEUTRAL="\033[0;33m" -RESET="\033[0m" - -MAKEFLAGS="-j11" -alias make="make $MAKEFLAGS" - -extract () { - FILE=$1 - echo extracting $FILE - case "${FILE#*.}" in - "tar.gz" ) - tar -zxf $FILE - ;; - "tar.lz" ) - tar --lzip -xf "$FILE" - ;; - "zip" ) - unzip $FILE - ;; - * ) - tar -xf $FILE - ;; - esac -} - -pkgname () { - echo $(basename $1 .xibuild) -} - -xibuild () { - BUILD_FILE=${@: -1} - BUILD_HASH=$(md5sum $BUILD_FILE | cut -f1 -d" ") - - cd $XI_ROOT - - [[ $# = 0 ]] && usage && return 1 - [ ! -f "$BUILD_FILE" ] && echo "$BUILD_FILE not found" && return 1 - - BUILD_FILE_PATH=$(realpath $BUILD_FILE) - - clean () { - # clean up - rm -rf $PKG_BUILD_DIR - rmdir $XI_ROOT/tmp > /dev/null 2>&1 - } - - prepare () { - echo "Passing missing prepare stage" - } - build () { - printf "\tpassing missing build stage..." - } - check () { - echo "Passing missing check stage" - } - package () { - echo "Passing missing package stage" - } - - BRANCH=HEAD - - source $BUILD_FILE - - PKG_NAME=$(pkgname $BUILD_FILE) - - LOGFILE=$XI_ROOT/logs/$PKG_NAME.log - PKG_FILE=$PKGS_OUTPUT/$PKG_NAME.xipkg - - PKG_BUILD_DIR=$XI_ROOT/tmp/$PKG_NAME - PKG_DEST=$XI_ROOT/tmp/$PKG_NAME.package - - # make sure build dir is clean before starting - rm -rf $PKG_BUILD_DIR - - # make the directories - mkdir -p $PKG_BUILD_DIR - mkdir -p $PKG_DEST - mkdir -p $PKGS_OUTPUT - mkdir -p $XI_ROOT/logs - - date > $LOGFILE - echo "Build log for $PKG_NAME from $BUILD_FILE\n" >> $LOGFILE - printf "\033[0;36m====> $PKG_NAME.xipkg$RESET\n" | tee -a $LOGFILE - - cd $PKG_BUILD_DIR - - # fetch, build then package the package - ############ - - if [ ! -z ${SOURCE+x} ]; then - # try get the commit hash for the package - if git ls-remote -q $SOURCE &> /dev/null; then - VER_HASH=$(git ls-remote $SOURCE $BRANCH ) - elif hg identify $SOURCE &> /dev/null; then - VER_HASH=$(hg identify $SOURCE) - else - VER_HASH=$(curl -Ls $SOURCE | md5sum) - fi - - VER_HASH=$(echo $VER_HASH | awk '{ print $1 }') - - # If we already have this package, don't waste our time - if [ -f "$PKG_FILE.info" ] && [ -f "$PKG_FILE" ]; then - EXISTING_HASH=$(grep -a "VER_HASH" $PKG_FILE.info | sed "s/VER_HASH=//") - - echo "Comparing $EXISTING_HASH to $VER_HASH" >> $LOGFILE - - printf "$INFO\tvalidating commit hash..."; - - EXISTING_BUILD_HASH=$(md5sum $PKGS_OUTPUT/$PKG_NAME.xibuild | cut -f1 -d" ") - if [ "$EXISTING_HASH" = "$VER_HASH" ] && [ "$EXISTING_BUILD_HASH" = "$BUILD_HASH" ]; then - printf "$NEUTRAL package exists$RESET\n" - echo "exists $PKG_NAME $DESC" >> $REPORT_LOG - rm $LOGFILE - return; - else - printf "$NEUTRAL package outdated\n" - fi - fi - - printf "$INFO\tfetching package..."; - if git ls-remote -q $SOURCE $BRANCH &> /dev/null; then - git clone $SOURCE . >> $LOGFILE 2>&1 && printf "$PASS fetched $(du -sh $PKG_BUILD_DIR | awk '{ print $1 }') source\n" || return 1; - git checkout $BRANCH >> $LOGFILE 2>&1 - - elif hg identify $SOURCE &> /dev/null; then - hg clone $SOURCE . >> $LOGFILE 2>&1 && printf "$PASS fetched $(du -sh $PKG_BUILD_DIR | awk '{ print $1 }') source\n" || return 1; - else - DOWNLOADED=$(basename $SOURCE) - curl -Ls $SOURCE > $DOWNLOADED - extract $DOWNLOADED >> $LOGFILE 2>&1 && printf "$PASS fetched $(du -sh $PKG_BUILD_DIR | awk '{ print $1 }') source\n" || return 1; - cd $(ls -d */) - fi - else - SOURCE="none" - fi - - printf "\033[0;34m\tpreparing package...\033[0m"; - prepare >> $LOGFILE 2>&1 && printf "$PASS prepared\n" || return 1; - - printf "$INFO\tbuilding package..."; - build >> $LOGFILE 2>&1 && printf "$PASS built\n" || return 1; - - printf "\033[0;34m\ttesting package...\033[0m"; - check >> $LOGFILE 2>&1 && printf "$PASS checked\n" || return 1; - - printf "\033[0;34m\tpackaging package...\033[0m"; - package >> $LOGFILE 2>&1 && printf "$PASS packaged\n" || return 1; - - # add postinstall script - if command -v postinstall > /dev/null; then - POSTINSTALL=$(type postinstall | sed '1,3d;$d') - if [ ${#POSTINSTALL} != 0 ]; then - POST_DIR=$PKG_DEST/var/lib/xipkg/postinstall - mkdir -p $POST_DIR - echo "#!/bin/sh" > $POST_DIR/$PKG_NAME.sh - echo $POSTINSTALL >> $POST_DIR/$PKG_NAME.sh - fi - fi - - # go back to root, make things easier - cd $XI_ROOT - - if [ -z "$(ls -A $PKG_DEST)" ] && [ ! $SOURCE = "none" ]; then - printf "$FAIL!!!Package is empty!!!$RESET\n" - return 1; - fi - - printf "$INFO\tarchiving package..."; - tar -C $PKG_DEST -cvzf $PKG_FILE ./ >> $LOGFILE 2>&1 && printf "$PASS archived to $(du -sh $PKG_FILE | awk '{ print $1 }')\n" || return 1; - - - # create info file - printf "$INFO\tcreating xipkg.info..."; - PKG_INFO=$PKGS_OUTPUT/$PKG_NAME.xipkg.info - - cp $BUILD_FILE $PKGS_OUTPUT/$PKG_NAME.xibuild - - echo "" > $PKG_INFO - echo "NAME=$PKG_NAME" >> $PKG_INFO - echo "DESCRIPTION=$DESC" >> $PKG_INFO - echo "PKG_FILE=$PKG_NAME.xipkg" >> $PKG_INFO - echo "CHECKSUM=$(md5sum $PKG_FILE | awk '{ print $1 }')" >> $PKG_INFO - echo "VER_HASH=$VER_HASH" >> $PKG_INFO - echo "SOURCE=$SOURCE" >> $PKG_INFO - echo "DATE=$(date)" >> $PKG_INFO - echo "DEPS=(${DEPS[*]})" >> $PKG_INFO - - printf "$INFOsigning..."; - # sign the package - - if [ -f "$PRIV_KEY" ]; then - echo "SIGNATURE=" >> $PKG_INFO - openssl dgst -sign $PRIV_KEY $PKG_FILE >> $PKG_INFO - else - echo "SIGNATURE=">> $PKG_INFO - echo "unsigned">> $PKG_INFO - >&2 printf "$ERROR WARNING! No private key: unsigned packages!\n" - fi - printf "$PASS signed\n"; - - printf "$PASS successfully built $PKG_NAME to $(basename $PKG_FILE)$RESET\n" - - clean - - echo "new $PKG_NAME $DESC" >> $REPORT_LOG - - unset SOURCE DESC DEPS install package check - return 0 -} - -usage () { - cat << EOF -usage: $0 [-h] [-o output-dir] [-r working_root] [-k private_key.pem] build_file.xibuild - -h display this help message - -o set the output dir for the xipkg files (default: $PWD/xipkgs) - -r set the working root for all log and tmp files (default: $PWD) - -k set the private key used to sign packages (default: $PWD/keychain/xi.pem) -EOF -} - -build-all () { - FILES=$@ - - while test $# -gt 0; do - case "$1" in - "-o" ) - shift - PKGS_OUTPUT=$PWD/$1 - ;; - "-r" ) - shift - XI_ROOT=$PWD/$1 - ;; - "-k" ) - shift - PRIV_KEY=$PWD/$1 - ;; - "-h" ) - usage - ;; - * ) - REPORT_LOG=$XI_ROOT/xibuild.report.log - BUILD_FILE=$1 - if xibuild $BUILD_FILE; then - printf "$RESET" - else - printf "$ERROR error! See log$RESET\n" - echo "fail $PKG_NAME $DESC" >> $REPORT_LOG - fi - cd $XI_ROOT - ;; - esac - - shift - done -} -if [ $# -gt 0 ]; then - build-all $@ | tee -a xibuild.log -else - usage; return 1 -fi diff --git a/xibuild.sh b/xibuild.sh new file mode 100644 index 0000000..a5aaa8c --- /dev/null +++ b/xibuild.sh @@ -0,0 +1,190 @@ +#!/bin/sh + +[ -f /usr/lib/colors.sh ] && . /usr/lib/colors.sh +[ -f /usr/lib/glyphs.sh ] && . /usr/lib/glyphs.sh + +textout=/dev/null +src_dir="$(pwd)" +out_dir="$(pwd)" + +xibuild_dir="/var/lib/xibuild" +build_dir="$xibuild_dir/build" +export_dir="$xibuild_dir/build/xipkg" + +logfile="$xibuild_dir/build.log" +root="/" + +xibuild_profile="/usr/lib/xibuild/xi_profile.sh" + +usage () { + cat << EOF +${LIGHT_RED}Usage: ${RED}xibuild [path/command] +${BLUE}Avaiable Options: + ${BLUE}-r ${LIGHT_BLUE}[path] + ${LIGHT_CYAN}specify the chroot to use when building packages${LIGHT_WHITE}[default: /] + ${BLUE}-d ${LIGHT_BLUE}[path] + ${LIGHT_CYAN}specify the output directory to put xipkg files ${LIGHT_WHITE}[default: ./] + ${BLUE}-c ${LIGHT_BLUE}[path] + ${LIGHT_CYAN}specify the directory to find xibuild files${LIGHT_WHITE}[default: ./] + ${BLUE}-b ${LIGHT_BLUE}[path] + ${LIGHT_CYAN}specify the directory to build things in ${LIGHT_WHITE}[default: /var/lib/xibuild] + ${BLUE}-p ${LIGHT_BLUE}[file] + ${LIGHT_CYAN}specify a non-default xi_profile script, to run inside the chroot ${LIGHT_WHITE}[default: /usr/lib/xibuild/xi_profile.sh] + + ${BLUE}-v + ${LIGHT_CYAN}verbose: print logs to stdout + +${BLUE}Available Commands: + ${LIGHT_GREEN}prepare + ${LIGHT_CYAN}prepare the build directory + ${LIGHT_GREEN}fetch + ${LIGHT_CYAN}fetch the sources required for the build + ${LIGHT_GREEN}build + ${LIGHT_CYAN}build the package, chroot if requested + ${LIGHT_GREEN}strip + ${LIGHT_CYAN}strip unecessary symbols from any binaries + ${LIGHT_GREEN}package + ${LIGHT_CYAN}package the build packages into .xipkg files + ${LIGHT_GREEN}describe + ${LIGHT_CYAN}create .xipkg.info files for each built package + ${LIGHT_GREEN}sign + ${LIGHT_CYAN}sign a package with a private key +EOF +} + +extract () { + FILE=$1 + case "${FILE##*.}" in + "gz" ) + tar -zxf $FILE + ;; + "lz" ) + tar --lzip -xf "$FILE" + ;; + "zip" ) + unzip -qq -o $FILE + ;; + * ) + tar -xf $FILE + ;; + esac +} + +xibuild_prepare () { + rm -rf $root/$build_dir + rm -rf $root/$export_dir + mkdir -p $root/$export_dir + install -Dm755 $xibuild_profile $root/$build_dir/xi_profile.sh + +} + +xibuild_fetch () { + cd $root/$build_dir + [ ! -z "$SOURCE" ] && { + git ls-remote -q $SOURCE $BRANCH >/dev/null 2>&1 && { + git clone $SOURCE . >/dev/null 2>&1 + git checkout $BRANCH >/dev/null 2>&1 + } || { + local downloaded=$(basename $SOURCE) + curl -SsL $SOURCE > $downloaded + extract $downloaded + + [ "$(ls -1 | wc -l)" = "2" ] && { + for file in */* */.*; do + echo $file | grep -q '\.$' || mv $file . + done; + } + } + } + + [ ! -z "$ADDITIONAL" ] && { + for url in "$ADDITIONAL"; do + case $url in + http*|ftp*) + curl -SsL $url> $root/$build_dir/$(basename $url);; + esac + done + } + + cp -r $src_dir/* $root/$build_dir/ +} + +xibuild_build () { + [ "$root" = "/" ] && { + $build_dir/xi_profile.sh $build_dir + } || { + xichroot "$root" $builddir/xi_profile $builddir + } +} + +xibuild_strip () { + for file in \ + $(find $export_dir/ -type f -name \*.so* ! -name \*dbg) \ + $(find $export_dir/ -type f -name \*.a) \ + $(find $export_dir/ -type f -executable ); do + strip --strip-unneeded $file + done + + find $export_dir -name \*.la -delete +} + +xibuild_package () { + for pkg in $(ls -1 $export_dir); do + cd $root/$export_dir/$pkg + [ "$(ls -1 $root/$export_dir/$pkg| wc -l)" = "0" ] && { + echo "package $pkg is empty" + [ ! -z ${SOURCE} ] || exit 1 + } + tar -C $root/$export_dir/$pkg -czf $out_dir/$pkg.xipkg ./ + done +} + +xibuild_describe () { + +} + + +while getopts ":r:c:p:b:d:qh" opt; do + case "${opt}" in + r) + root=$(realpath ${OPTARG});; + d) + out_dir=$(realpath ${OPTARG});; + c) + src_dir=$(realpath ${OPTARG});; + b) + build_dir=$(realpath ${OPTARG});; + p) + xibuild_profile=$(realpath ${OPTARG});; + v) + textout=/dev/stdout;; + h) + usage; exit 0;; + esac +done + +shift $((OPTIND-1)) + +tasks="prepare fetch build strip package" + +[ "$#" = "1" ] && { + [ -d "$1" ] && { + src_dir=$(realpath $1) + } || { + tasks="$(echo $tasks | grep $1)" + } +} + +NAME=$(basename $(realpath "$src_dir")) +trap "{printf \"${RED}${CROSS}\n\"}" 1 + +. $src_dir/$NAME.xibuild + +printf "${BLUE}${NAME}\n" +for task in $tasks; do + printf "${BLUE}${TABCHAR}$task " + + xibuild_$task 2>&1 | tee -a $logfile > $textout || exit 1 + + printf "${GREEN}${CHECKMARK}\n" +done |