diff --git a/board/common/cleanups.sh b/board/common/cleanups.sh index 472269c36b..2df8a41740 100755 --- a/board/common/cleanups.sh +++ b/board/common/cleanups.sh @@ -191,7 +191,6 @@ rm -f $TARGET/usr/bin/v4l2-compliance rm -rf $TARGET/usr/lib/python2.7/site-packages/samba/ rm -rf $TARGET/usr/lib/python2.7/ensurepip/ rm -rf $TARGET/usr/lib/python2.7/config/ -rm -rf $TARGET/usr/lib/python2.7/distutils/ rm -rf $TARGET/usr/lib/python2.7/unittest/ # buildroot default startup scripts diff --git a/board/common/mkimage.sh b/board/common/mkimage.sh index 2b8a9def24..2b5e8e8600 100755 --- a/board/common/mkimage.sh +++ b/board/common/mkimage.sh @@ -8,7 +8,7 @@ fi test "root" != "$USER" && exec sudo -E $0 "$@" function msg() { - echo ":: $1" + echo " * $1" } function cleanup { @@ -36,7 +36,9 @@ ROOT_IMG=$IMG_DIR/root.img ROOT_SIZE="180" # MB DISK_SIZE="220" # MB -OS_NAME=$(source $IMG_DIR/../../../board/common/overlay/etc/version && echo $os_short_name) + +COMMON_DIR=$(cd $IMG_DIR/../../../board/common; pwd) +OS_NAME=$(source $COMMON_DIR/overlay/etc/version && echo $os_short_name) # boot filesystem msg "creating boot loop device" @@ -79,6 +81,26 @@ mount -o loop $loop_dev $ROOT msg "copying root filesystem contents" tar -xpsf $ROOT_SRC -C $ROOT +# set internal OS name, prefix and version according to env variables +if [ -f $ROOT/etc/version ]; then + if [ -n "$THINGOS_NAME" ]; then + msg "setting OS name to $THINGOS_NAME" + sed -ri "s/os_name=\".*\"/os_name=\"$THINGOS_NAME\"/" $ROOT/etc/version + fi + if [ -n "$THINGOS_SHORT_NAME" ]; then + msg "setting OS short name to $THINGOS_SHORT_NAME" + sed -ri "s/os_short_name=\".*\"/os_short_name=\"$THINGOS_SHORT_NAME\"/" $ROOT/etc/version + fi + if [ -n "$THINGOS_PREFIX" ]; then + msg "setting OS prefix to $THINGOS_PREFIX" + sed -ri "s/os_prefix=\".*\"/os_prefix=\"$THINGOS_PREFIX\"/" $ROOT/etc/version + fi + if [ -n "$THINGOS_VERSION" ]; then + msg "setting OS version to $THINGOS_VERSION" + sed -ri "s/os_version=\".*\"/os_version=\"$THINGOS_VERSION\"/" $ROOT/etc/version + fi +fi + msg "unmounting root filesystem" umount $ROOT diff --git a/board/common/overlay/sbin/fwupdate b/board/common/overlay/sbin/fwupdate index 7cec9b4617..9514b9a93e 100755 --- a/board/common/overlay/sbin/fwupdate +++ b/board/common/overlay/sbin/fwupdate @@ -44,7 +44,8 @@ ROOT_INFO_FILE=root_info BOOT_READY_FILE=boot_flash_ready FW_DIR=/data/.fwupdate -FW_FILE=firmware.img.gz +FW_FILE_GZ=firmware.img.gz +FW_FILE_XZ=firmware.img.xz FW_FILE_EXTR=firmware.img CURL_LOG_FILE=curl.log @@ -53,6 +54,9 @@ CURL_PID_FILE=curl.pid GUNZIP_LOG_FILE=gunzip.log GUNZIP_PID_FILE=gunzip.pid +XZCAT_LOG_FILE=xzcat.log +XZCAT_PID_FILE=xzcat.pid + DD_LOG_FILE=dd.log DD_PID_FILE=dd.pid @@ -101,7 +105,7 @@ function show_current() { function do_download() { echo "downloading..." - rm -f $FW_DIR/$FW_FILE + rm -f $FW_DIR/$FW_FILE_GZ $FW_DIR/$FW_FILE_XZ rm -f $FW_DIR/$FW_FILE_EXTR rm -f $FW_DIR/$BOOT_READY_FILE @@ -111,7 +115,7 @@ function do_download() { version=$1 if ! [[ "$url" == http* ]]; then # a version was given - url=$(show_versions true | grep "^$1" | cut -d ' ' -f 2) + url=$(show_versions true | sed -rn '/^'"$version"' http.*\.img\.[a-z]+$/ {; /.*\.xz$/ {;s/^'"$version"' (.*)/\1/ p;q;}; /.*\.gz$/ {;h;b finish;};}; :finish; $ {;x;s/^'"$version"' (.*)/\1/ p;}') else version="custom" fi @@ -127,6 +131,12 @@ function do_download() { exit 1 fi + outfile=$FW_DIR/$FW_FILE_GZ + format=$(echo $url | sed -rn 's/.*\.img\.([a-z]+)$/\1/ p') + if [ "$format" == "xz" ]; then + outfile=$FW_DIR/$FW_FILE_XZ + fi + rm -rf $FW_DIR/* mkdir -p $FW_DIR echo $version > $FW_DIR/$VER_FILE @@ -136,10 +146,15 @@ function do_download() { curl_opts+=" --user $os_firmware_username:$os_firmware_password" fi - curl $curl_opts -o $FW_DIR/$FW_FILE "$url" &> $FW_DIR/$CURL_LOG_FILE & + curl $curl_opts -o $outfile "$url" &> $FW_DIR/$CURL_LOG_FILE & pid=$! echo $pid > $FW_DIR/$CURL_PID_FILE wait $pid + + if [ "$?" != 0 ]; then + cat $FW_DIR/$CURL_LOG_FILE + exit 1 + fi } function download_status() { @@ -151,7 +166,7 @@ function download_status() { fi fi - if [ -f $FW_DIR/$FW_FILE ]; then + if [ -f $FW_DIR/$FW_FILE_GZ -o -f $FW_DIR/$FW_FILE_XZ ]; then echo "done" fi } @@ -165,23 +180,52 @@ function do_extract() { rm -f $FW_DIR/$FW_FILE_EXTR rm -f $FW_DIR/$BOOT_READY_FILE - if ! [ -f $FW_DIR/$FW_FILE ]; then + if ! [ -f $FW_DIR/$FW_FILE_GZ -o -f $FW_DIR/$FW_FILE_XZ ]; then echo "firmware file not downloaded" 1>&2 exit 1 fi - rm -f $FW_DIR/$FW_FILE_EXTR + format="gz" + if [ -f $FW_DIR/$FW_FILE_XZ ]; then + format="xz" + fi - gunzip -k -c $FW_DIR/$FW_FILE > $FW_DIR/$FW_FILE_EXTR 2>$FW_DIR/$GUNZIP_LOG_FILE & + rm -f $FW_DIR/$FW_FILE_EXTR + rm -f $FW_DIR/$GUNZIP_PID_FILE $FW_DIR/$XZCAT_PID_FILE + + if [ "$format" == "xz" ]; then + DECOMPRESS_LOG_FILE=$FW_DIR/$XZCAT_LOG_FILE + DECOMPRESS_PID_FILE=$FW_DIR/$XZCAT_PID_FILE + xzcat $FW_DIR/$FW_FILE_XZ > $FW_DIR/$FW_FILE_EXTR 2>$FW_DIR/$XZCAT_LOG_FILE & + elif [ "$format" == "gz" ]; then + DECOMPRESS_LOG_FILE=$FW_DIR/$GUNZIP_LOG_FILE + DECOMPRESS_PID_FILE=$FW_DIR/$GUNZIP_PID_FILE + gunzip -k -c $FW_DIR/$FW_FILE_GZ > $FW_DIR/$FW_FILE_EXTR 2>$FW_DIR/$GUNZIP_LOG_FILE & + else + echo "firmware compression format $format not supported" 1>&2 + exit 1 + fi + pid=$! - echo $pid > $FW_DIR/$GUNZIP_PID_FILE + echo $pid > $DECOMPRESS_PID_FILE wait $pid + if [ "$?" != 0 ]; then + cat $DECOMPRESS_LOG_FILE + exit 1 + fi + # TODO verify hash } function extract_status() { - if [ -f $FW_DIR/$GUNZIP_PID_FILE ]; then + if [ -f $FW_DIR/$XZCAT_PID_FILE ]; then + pid=$(cat $FW_DIR/$XZCAT_PID_FILE) + if kill -0 $pid &>/dev/null; then + echo "running" + return + fi + elif [ -f $FW_DIR/$GUNZIP_PID_FILE ]; then pid=$(cat $FW_DIR/$GUNZIP_PID_FILE) if kill -0 $pid &>/dev/null; then echo "running" diff --git a/board/common/overlay/usr/libexec/list-versions-bitbucket b/board/common/overlay/usr/libexec/list-versions-bitbucket index e54c188a84..044ea42791 100755 --- a/board/common/overlay/usr/libexec/list-versions-bitbucket +++ b/board/common/overlay/usr/libexec/list-versions-bitbucket @@ -5,10 +5,13 @@ if [ -z "$1" ]; then exit -1 fi +extensions=".img.gz .img.xz .img" opts="-s -S -f" test -n "$FW_USERNAME" && opts+=" --user $FW_USERNAME:$FW_PASSWORD" url="https://api.bitbucket.org/2.0/repositories/$1/downloads?pagelen=100&_=$(date +%s)" -jq_expr='.values[] | [{a: .name | split("-"), url: .links.self.href}] | map((.a[2] | rtrimstr(".img.gz") | rtrimstr(".img")), "false", .a[1], .url) | join("|")' + +rtrimstr=$(for e in $extensions; do echo -n " | rtrimstr(\"$e\")"; done) +jq_expr=".values[] | [{a: .name | split(\"-\"), url: .links.self.href}] | map((.a[2] $rtrimstr), \"false\", .a[1], .url) | join(\"|\")" curl $opts $url | jq --raw-output "$jq_expr" exit ${PIPESTATUS[0]} diff --git a/board/nanopineo2/boot-fwupdater.cmd b/board/nanopineo2/boot-fwupdater.cmd index e31c4405e9..1bb0d7388a 100644 --- a/board/nanopineo2/boot-fwupdater.cmd +++ b/board/nanopineo2/boot-fwupdater.cmd @@ -1,4 +1,4 @@ -bootargs console=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 rootwait ro no_console_suspend panic=10 quiet loglevel=1 ipv6.disable=1 +setenv bootargs console=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 rootwait ro no_console_suspend panic=10 quiet loglevel=1 ipv6.disable=1 fatload mmc 0 0x46000000 Image fatload mmc 0 0x47000000 rootfs.cpio.uboot diff --git a/board/nanopineo2/boot.cmd b/board/nanopineo2/boot.cmd index 462d0b93d7..c81f03256b 100644 --- a/board/nanopineo2/boot.cmd +++ b/board/nanopineo2/boot.cmd @@ -1,4 +1,4 @@ -bootargs console=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 rootwait ro no_console_suspend panic=10 quiet loglevel=1 ipv6.disable=1 +setenv bootargs console=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 rootwait ro no_console_suspend panic=10 quiet loglevel=1 ipv6.disable=1 fatload mmc 0 0x46000000 Image fatload mmc 0 0x48000000 sun50i-h5-nanopi-neo2.dtb diff --git a/build.sh b/build.sh index 039a06f983..c6fbb69458 100755 --- a/build.sh +++ b/build.sh @@ -1,7 +1,12 @@ #!/bin/bash if [ -z "$1" ]; then - echo "Usage: $0 [mkimage|mkrelease|make_targets...]" + echo "Usage: $0 [mkimage|mkrelease|clean-target|make-targets...]" + echo " mkimage - creates the OS image (.img)" + echo " mkrelease - creates the compressed OS image (.img.gz, .img.xz)" + echo " clean-target - removes the target dir, preserving the package build dirs" + echo "" + echo " for other make targets, see the BuildRoot manual" exit 1 fi @@ -15,6 +20,25 @@ osname=$(source $basedir/board/common/overlay/etc/version && echo $os_short_name osversion=$(source $basedir/board/common/overlay/etc/version && echo $os_version) gzip=$(which pigz 2> /dev/null || which gzip 2> /dev/null) +# extra environment from local file +test -f $basedir/.build-env && source $basedir/.build-env + +# OS name +if [ -n "$THINGOS_SHORT_NAME" ]; then + osname=$THINGOS_SHORT_NAME +else + osname=$(source $basedir/board/common/overlay/etc/version && echo $os_short_name) +fi + +# OS version +if [ -n "$THINGOS_VERSION" ]; then + osversion=$THINGOS_VERSION +else + osversion=$(source $basedir/board/common/overlay/etc/version && echo $os_version) +fi + +# when the special "all" keyword is used for board, +# all boards are processed, in turn if [ "$board" == "all" ]; then boards=$(ls $basedir/configs/*_defconfig | grep -v initramfs | grep -oE '\w+_defconfig$' | cut -d '_' -f 1) for b in $boards; do @@ -42,15 +66,59 @@ fi if [ "$target" == "mkimage" ]; then $boarddir/mkimage.sh + elif [ "$target" == "mkrelease" ]; then - $boarddir/mkimage.sh - cp $outputdir/images/$osname-$board.img $basedir - mv $basedir/$osname-$board.img $basedir/$osname-$board-$osversion.img - rm -f $basedir/$osname-$board-$osversion.img.gz - $gzip $basedir/$osname-$board-$osversion.img - echo "your image is ready at $basedir/$osname-$board-$osversion.img.gz" + test -f $outputdir/images/$osname-$board.img || $boarddir/mkimage.sh + cp $outputdir/images/$osname-$board.img $outputdir/images/$osname-$board-$osversion.img + + echo "preparing compressed xz image" + rm -f $outputdir/images/$osname-$board-$osversion.img.xz + xz -6ek -T 0 $outputdir/images/$osname-$board-$osversion.img + echo "your xz image is ready at $outputdir/images/$osname-$board-$osversion.img.xz" + + echo "preparing compressed gz image" + rm -f $outputdir/images/$osname-$board-$osversion.img.gz + $gzip $outputdir/images/$osname-$board-$osversion.img + echo "your gz image is ready at $outputdir/images/$osname-$board-$osversion.img.gz" + + rm -f $outputdir/images/$osname-$board-$osversion.img + +elif [ "$target" == "clean-target" ]; then + if [ -d $outputdir/target ]; then + echo "removing target directory" + rm -rf $outputdir/target/* + + echo "removing staging directory" + rm -rf $outputdir/staging/* + + echo "removing images directory" + rm -rf $outputdir/images/* + fi + + if [ -d $outputdir/build ]; then + echo "removing .stamp_target_installed files" + find $outputdir/build -name .stamp_target_installed | xargs -r rm + + echo "removing .stamp_staging_installed files" + find $outputdir/build -name .stamp_staging_installed | xargs -r rm + + echo "removing .stamp_host_installed files" + find $outputdir/build -name .stamp_host_installed | xargs -r rm + fi + + if [ -f $outputdir/.config ]; then + echo "removing .config file" + rm -f $outputdir/.config + fi + + echo "target is clean" + +elif [ "$target" == "all" ]; then + make O=$outputdir all + elif [ -n "$target" ]; then - make O=$outputdir $target + $0 $b all + else make O=$outputdir all echo "build successful" diff --git a/configs/orangepione_defconfig b/configs/orangepione_defconfig index e5c742d652..a4488c3676 100644 --- a/configs/orangepione_defconfig +++ b/configs/orangepione_defconfig @@ -104,6 +104,8 @@ BR2_PACKAGE_UTIL_LINUX_PARTX=y BR2_PACKAGE_NANO=y BR2_TARGET_UBOOT=y BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y +BR2_TARGET_UBOOT_CUSTOM_TARBALL=y +BR2_TARGET_UBOOT_CUSTOM_TARBALL_LOCATION="https://github.com/megous/u-boot/archive/8974e5998f60a6ba79d0ef9b9849602f804ee3d3.tar.gz" BR2_TARGET_UBOOT_BOARD_DEFCONFIG="orangepi_one" BR2_TARGET_UBOOT_NEEDS_DTC=y BR2_TARGET_UBOOT_NEEDS_PYLIBFDT=y diff --git a/configs/raspberrypi_defconfig b/configs/raspberrypi_defconfig index dc0a18873d..f73655770d 100644 --- a/configs/raspberrypi_defconfig +++ b/configs/raspberrypi_defconfig @@ -3,10 +3,15 @@ BR2_arm1176jzf_s=y BR2_CCACHE=y BR2_CCACHE_DIR="$(TOPDIR)/.buildroot-ccache-raspberrypi" BR2_OPTIMIZE_2=y -BR2_TOOLCHAIN_BUILDROOT_GLIBC=y -BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_4_9=y -BR2_GCC_VERSION_4_9_X=y -BR2_TOOLCHAIN_BUILDROOT_CXX=y +BR2_TOOLCHAIN_EXTERNAL=y +BR2_TOOLCHAIN_EXTERNAL_DOWNLOAD=y +BR2_TOOLCHAIN_EXTERNAL_URL="https://github.com/raspberrypi/tools/archive/5caa7046982f0539cf5380f94da04b31129ed521.tar.gz" +BR2_TOOLCHAIN_EXTERNAL_REL_BIN_PATH="arm-bcm2708/arm-linux-gnueabihf/bin" +BR2_TOOLCHAIN_EXTERNAL_CUSTOM_PREFIX="arm-linux-gnueabihf" +BR2_TOOLCHAIN_EXTERNAL_GCC_4_9=y +BR2_TOOLCHAIN_EXTERNAL_HEADERS_4_1=y +BR2_TOOLCHAIN_EXTERNAL_CUSTOM_GLIBC=y +BR2_TOOLCHAIN_EXTERNAL_CXX=y BR2_TARGET_OPTIMIZATION="-pipe" BR2_ROOTFS_SKELETON_CUSTOM=y BR2_ROOTFS_SKELETON_CUSTOM_PATH="board/common/skeleton" diff --git a/package/Config.in b/package/Config.in index 30afc3c82b..a2401611f3 100644 --- a/package/Config.in +++ b/package/Config.in @@ -882,6 +882,7 @@ menu "External python modules" source "package/python-pyxb/Config.in" source "package/python-pyyaml/Config.in" source "package/python-pyzmq/Config.in" + source "package/python-redis/Config.in" source "package/python-requests/Config.in" source "package/python-requests-toolbelt/Config.in" source "package/python-rpi-gpio/Config.in" diff --git a/package/libwebcam/libwebcam.mk b/package/libwebcam/libwebcam.mk index defdc8638c..090ef6883b 100644 --- a/package/libwebcam/libwebcam.mk +++ b/package/libwebcam/libwebcam.mk @@ -10,7 +10,6 @@ LIBWEBCAM_SITE = http://freefr.dl.sourceforge.net/project/libwebcam/source LIBWEBCAM_DEPENDENCIES = libxml2 define LIBWEBCAM_INSTALL_TARGET_CMDS - rm $(@D)/uvcdynctrl/uvcdynctrl-*.gz cp $(@D)/uvcdynctrl/uvcdynctrl $(TARGET_DIR)/usr/bin/uvcdynctrl cp -d $(@D)/libwebcam/libwebcam.so* $(TARGET_DIR)/usr/lib endef diff --git a/package/python-redis/Config.in b/package/python-redis/Config.in new file mode 100644 index 0000000000..e396ba0d9c --- /dev/null +++ b/package/python-redis/Config.in @@ -0,0 +1,6 @@ +config BR2_PACKAGE_PYTHON_REDIS + bool "python-redis" + depends on BR2_PACKAGE_PYTHON + help + Python driver for Redis + diff --git a/package/python-redis/python-redis.mk b/package/python-redis/python-redis.mk new file mode 100644 index 0000000000..31475ca463 --- /dev/null +++ b/package/python-redis/python-redis.mk @@ -0,0 +1,13 @@ +################################################################################ +# +# python-redis +# +################################################################################ + +PYTHON_REDIS_VERSION = 2.10.6 +PYTHON_REDIS_SOURCE = redis-$(PYTHON_REDIS_VERSION).tar.gz +PYTHON_REDIS_SITE = https://pypi.python.org/packages/09/8d/6d34b75326bf96d4139a2ddd8e74b80840f800a0a79f9294399e212cb9a7 +PYTHON_REDIS_SETUP_TYPE = setuptools + +$(eval $(python-package)) + diff --git a/toolchain/toolchain-external/pkg-toolchain-external.mk b/toolchain/toolchain-external/pkg-toolchain-external.mk index dc0588c536..393ff85c63 100644 --- a/toolchain/toolchain-external/pkg-toolchain-external.mk +++ b/toolchain/toolchain-external/pkg-toolchain-external.mk @@ -71,13 +71,18 @@ else TOOLCHAIN_EXTERNAL_INSTALL_DIR = $(call qstrip,$(BR2_TOOLCHAIN_EXTERNAL_PATH)) endif +# If binary path unset (known, supported external toolchains), use "bin". +TOOLCHAIN_EXTERNAL_REL_BIN_PATH = $(or \ + $(call qstrip,$(BR2_TOOLCHAIN_EXTERNAL_REL_BIN_PATH)), \ + bin) + ifeq ($(TOOLCHAIN_EXTERNAL_INSTALL_DIR),) ifneq ($(TOOLCHAIN_EXTERNAL_PREFIX),) # if no path set, figure it out from path TOOLCHAIN_EXTERNAL_BIN := $(dir $(shell which $(TOOLCHAIN_EXTERNAL_PREFIX)-gcc)) endif else -TOOLCHAIN_EXTERNAL_BIN := $(TOOLCHAIN_EXTERNAL_INSTALL_DIR)/bin +TOOLCHAIN_EXTERNAL_BIN := $(TOOLCHAIN_EXTERNAL_INSTALL_DIR)/$(TOOLCHAIN_EXTERNAL_REL_BIN_PATH) endif # If this is a buildroot toolchain, it already has a wrapper which we want to diff --git a/toolchain/toolchain-external/toolchain-external-custom/Config.in.options b/toolchain/toolchain-external/toolchain-external-custom/Config.in.options index a28534080d..e10cc857d8 100644 --- a/toolchain/toolchain-external/toolchain-external-custom/Config.in.options +++ b/toolchain/toolchain-external/toolchain-external-custom/Config.in.options @@ -12,6 +12,14 @@ config BR2_TOOLCHAIN_EXTERNAL_URL help URL of the custom toolchain tarball to download and install. +config BR2_TOOLCHAIN_EXTERNAL_REL_BIN_PATH + string "Toolchain relative bin path" + default "bin" + depends on BR2_TOOLCHAIN_EXTERNAL_DOWNLOAD + help + Path to where the binaries (e.g. the compiler) can be found, + relative to the downloaded toolchain root directory. + config BR2_TOOLCHAIN_EXTERNAL_CUSTOM_PREFIX string "Toolchain prefix" default "$(ARCH)-linux" diff --git a/writeimage.sh b/writeimage.sh index 12364dda7b..36946f8370 100755 --- a/writeimage.sh +++ b/writeimage.sh @@ -20,7 +20,7 @@ fi if [[ $(id -u) -ne 0 ]]; then echo "please run as root"; exit 1; fi function msg() { - echo ":: $1" + echo " * $1" } while getopts "a:d:f:h:i:lm:n:o:p:s:w" o; do @@ -76,12 +76,25 @@ if ! [ -f $DISK_IMG ]; then exit 1 fi -gunzip=$(which unpigz 2> /dev/null || which gunzip 2> /dev/null) +gunzip=$(which unpigz 2> /dev/null || which gunzip 2> /dev/null || true) +unxz=$(which unxz 2> /dev/null || true) if [[ $DISK_IMG == *.gz ]]; then - msg "decompressing the gzipped image" + if [ -z "$gunzip" ]; then + msg "make sure you have the gzip package installed" + exit 1 + fi + msg "decompressing the .gz compressed image" $gunzip -c $DISK_IMG > ${DISK_IMG%???} DISK_IMG=${DISK_IMG%???} +elif [[ $DISK_IMG == *.xz ]]; then + if [ -z "$unxz" ]; then + msg "make sure you have the xz package installed" + exit 1 + fi + msg "decompressing the .xz compressed image" + $unxz -T 0 -c $DISK_IMG > ${DISK_IMG%???} + DISK_IMG=${DISK_IMG%???} fi umount ${SDCARD_DEV}* 2>/dev/null || true