I'm a developer of QNAP packages. Can I get assistance from QNAP developers when I have questions or bugs to report?

To: QNAP staff.

The QNAP help desk isn’t cutting-it. :disappointed_face:

How can I get assistance from competent QNAP developers? I know they’re out-there.

I’m trying my best to continue supporting QNAP, but these interactions with the “help-desk” are just draining. It happens way too often. I hate to think what other users go through.

Have you tried turning it on and off again?

So far all my QNAP support interactions were for failed 10Gbit cards, but were pretty straight forward in terms of “script”.

Suprised QNAP does not have a developer channel to interact with.

I think his problem is not with the support app and help desk. I think the problem is that the normal support staff are not developers. Not what they are trained to do. So they approach the issue from “the user has something wrong with their system” instead of “this is development issue.”

How can I help you or what is your ticket number ? :wink:
I’m kidding.
But seriously. What is your ticket number let me see what I can do or let’s discuss what you need.

Thanks Silas. :+1:

I’m seeking a support channel for technical queries regarding QPKG management.

Any suggestions? If I sign-up to become a QNAP developer, do you know if it comes with any support from QNAP staff?

If you’d like to ask questions about the developer kit, you can do so through your original application ticket after being approved as one of our developers. Alternatively, you can open a support ticket — for development-related questions, our Support Team will help forward them to the appropriate department for clarification. Thanks!

I dont think so. But I’m pretty familar with QPKG system and I can help you.

However, to anyone developing any app or want to develop anything for QNAP, I will tell (dont know where), but compiling anything for QTS6+ is pretty easy:

1) QTS 6 and QuTS hero are Ubuntu 24.04 / glibc based.

Check it yourself on the box: ldd --version prints 2.39. That is exactly Ubuntu 24.04. So you do not need
an SDK, a cross toolchain or a container for x86_64 models. Install a plain Ubuntu 24.04 VM, build there,
copy the binary over, done. (This does NOT hold for QTS 5.x, which is much older glibc. Check first.)

2) Pick a destination directory outside /opt.

# /opt belongs to Entware. Turning Entware on/off will happily discard your symlinks.
# Give your app one fixed directory and put everything inside it:
MY_APP_DEST_DIR=/usr/bin/my_app

3) The only rule that really matters: static dependencies, dynamic glibc.

Build every library you need as a static .a and link it into your binary. Leave only glibc dynamic.

  • Do NOT link fully static (-static). It breaks NSS, so getaddrinfo() and user lookups die at runtime.
  • Do NOT drop your own .so files into /usr/lib on the NAS. You will collide with QNAP’s own libraries
    and eventually break something that is not yours.

4) A complete, real example: ncurses (library) + htop (app).

SYSROOT=$HOME/build/sysroot          # private prefix for libraries, never installed on the NAS
MY_APP_DEST_DIR=/usr/bin/my_app      # where the app will live on the NAS

# --- the library: ncurses, static only -------------------------------------
# --without-shared --with-normal : build .a only, no .so at all
# --enable-widec                 : libncursesw, UTF-8
# --with-fallbacks + --disable-db-install : compile the terminal descriptions INTO
#                                  the library, so you ship no terminfo database
#                                  and it still works on a bare NAS
cd ncurses-6.6
./configure --prefix=$SYSROOT \
  --without-shared --with-normal --enable-widec \
  --enable-pc-files --with-pkg-config-libdir=$SYSROOT/lib/pkgconfig \
  --with-fallbacks=xterm,xterm-256color,screen,linux,vt100,dumb \
  --disable-db-install \
  --without-debug --without-ada --without-cxx-binding --without-tests --without-manpages \
  CFLAGS="-O2 -pipe -fPIC"
make -j3 && make install             # lands in $SYSROOT, not in the system

# --- the app: htop, linked against that static ncurses ---------------------
# PKG_CONFIG="pkg-config --static" : pull Libs.private, i.e. the full static tail
# PKG_CONFIG_LIBDIR=...            : look ONLY in the sysroot, never at host libs
# --prefix                         : the path the app will have ON THE NAS
# -static-libgcc                   : add -static-libstdc++ too for C++ apps
cd ../htop-3.3.0
PKG_CONFIG="pkg-config --static" \
PKG_CONFIG_LIBDIR="$SYSROOT/lib/pkgconfig" \
./configure --prefix=$MY_APP_DEST_DIR --enable-unicode \
  CFLAGS="-O2 -pipe" \
  CPPFLAGS="-I$SYSROOT/include -I$SYSROOT/include/ncursesw" \
  LDFLAGS="-L$SYSROOT/lib -static-libgcc"
make -j3
make install DESTDIR=$PWD/../out     # stage it; DESTDIR + prefix = the tree you ship

--prefix is the path the binary will have on the NAS, DESTDIR is where it gets staged on the build
machine. Keep them separate or your app will look for its config in the wrong place.

5) Verify before you copy anything to the NAS. Three commands, every binary.

B=out/usr/bin/my_app/bin/htop

readelf -h $B | grep Machine
#   Advanced Micro Devices X86-64        <- right architecture

readelf -d $B | grep NEEDED
#   libm.so.6, libc.so.6                 <- glibc only. ncurses is gone: it is inside the binary.

objdump -T $B | grep -oE 'GLIBC_[0-9.]+' | sort -uV | tail -1
#   GLIBC_2.38                           <- must be <= 2.39, else it will not start on the NAS

If NEEDED lists anything that is not libc/libm/libpthread/libdl/librt/libresolv/ld-linux/libgcc_s,
you forgot to link that dependency statically. Go back and fix the library, not the app.

6) patchelf: for the one library you cannot make static.

In the example above htop still picked up libcap.so.2 from the build host. Two honest options.

Option A, drop the feature: ./configure --disable-capabilities. Nothing left to ship.

Option B, ship the library inside your own directory and point the binary at it. This is what patchelf
is for, and $ORIGIN is the part people miss: it means “the directory this binary is in”, resolved at
runtime, so it keeps working no matter where the package ends up.

mkdir -p out/usr/bin/my_app/lib
cp -L /lib/x86_64-linux-gnu/libcap.so.2 out/usr/bin/my_app/lib/
patchelf --set-rpath '$ORIGIN/../lib' out/usr/bin/my_app/bin/htop
#         ^ single quotes. Let the shell NOT expand $ORIGIN.

readelf -d out/usr/bin/my_app/bin/htop | grep -E 'RUNPATH|NEEDED'
#   RUNPATH  [$ORIGIN/../lib]
ldd out/usr/bin/my_app/bin/htop | grep libcap
#   libcap.so.2 => .../my_app/bin/../lib/libcap.so.2      <- your copy, not the system one

Two more patchelf tricks worth knowing:

patchelf --remove-rpath  $B                       # strip the absolute build-machine RPATH that
                                                  # libtool loves to bake in (/home/you/build/...)
patchelf --replace-needed libfoo.so.1 libfoo.so.2 $B   # re-point a binary you cannot rebuild

Remember the RUNPATH only resolves libraries you actually ship. It is not a way to borrow QNAP’s
libraries, and you should not want to: they change between firmware versions.

7) ARM models.

Same recipe, one extra line. Ubuntu 24.04 ships the cross compilers, so install
gcc-aarch64-linux-gnu or gcc-arm-linux-gnueabihf, then add --host=aarch64-linux-gnu
(or arm-linux-gnueabihf) to every ./configure and export
CC/CXX/AR/RANLIB/STRIP with the matching prefix. Everything else, including the three verification
commands, is identical. One warning for armv7: the older Marvell units have no NEON, so keep any
hand-written NEON assembly runtime-detected or switched off.

Everything above was built and run end to end on Ubuntu 24.04 before posting: ncurses 6.6 static,
htop 3.3.0, GLIBC_2.38 max, libcap relocated via $ORIGIN/../lib, binary starts and prints its version.


If you need to understand whole QNAP QPKG just install StorageDiag Workbench & Utiltities and from CmdHelper please look closer on a workflow how start/stop scripts works:

Ask me what you need and i’ll try to explain this to you.

Thanks Silas, some great information there. I wish more folks would post about how to do things like this. If QNAP were this helpful, I think there would be a lot more QPKGs available for installation. :nerd:

My own packages are much simpler though. I generally don’t compile, but I do need to learn how to package a more up-to-date glibc, as that is stopping me from distributing a few other applications.

libicu is another one I need to figure-out.

Thanks Steve. :+1:

While we’re on the topic (as a partner developer), I assume this means QPKGs I submit have to be approved/rejected by someone at QNAP. My QPKGs often contain hacky workarounds to solve problems I’ve encountered over the years in different versions of QTS. Is someone at QNAP going to object to these?

In the old days of QTS 4.x and 5.x I simply shipped the whole glibc (taken from a Debian VM) with the application I was going to distribute and it was the same glibc where I compiled the application against.

This is how, for example, I did for TVHeadend QPKG. Probably it wasn’t the most efficient way “space-wise” but it worked :grinning_face:

I like it! :grin:

How do you get the application to use your specific glibc instead of the system one? That’s something I’m kinda hazy on.

i got your point,
i would recommend you recompile instead going that way, because in such case you has to supply ld-library together and do some workaround…hmmm

# cat SoulseekCloud.sh
#!/bin/sh

NAME=SoulseekCloud
FRAMEWORK=QX11

export _DISPLAY=99

LOCK=/tmp/${NAME}.lock
_DEBUG=1

# Symbols
DOUBLE_LEFT_QUOTE_SYMBOL=$(echo -e "\xab")	; ASC_DLQ=$DOUBLE_LEFT_QUOTE_SYMBOL
DOUBLE_RIGHT_QUOTE_SYMBOL=$(echo -e "\xbb")	; ASC_DRQ=$DOUBLE_RIGHT_QUOTE_SYMBOL
GREATER_SYMBOL=$(echo -e "\x3e")		; ASC_GT=$GREATER_SYMBOL
LESS_SYMBOL=$(echo -e "\x3c")			; ASC_LT=$LESS_SYMBOL

# Normal Colors
black='\e[0;30m'        # Black
red='\e[0;31m'          # Red
green='\e[0;32m'        # Green
yellow='\e[0;33m'       # Yellow
blue='\e[0;34m'         # Blue
purple='\e[0;35m'       # Purple
cyan='\e[0;36m'         # Cyan
white='\e[0;37m'        # White

# Bold
bblack='\e[1;30m'       # Black
bred='\e[1;31m'         # Red
bgreen='\e[1;32m'       # Green
byellow='\e[1;33m'      # Yellow
bblue='\e[1;34m'        # Blue
bpurple='\e[1;35m'      # Purple
bcyan='\e[1;36m'        # Cyan
bwhite='\e[1;37m'       # White

# Background
on_black='\e[40m'       # Black
on_red='\e[41m'         # Red
on_green='\e[42m'       # Green
on_yellow='\e[43m'      # Yellow
on_blue='\e[44m'        # Blue
on_purple='\e[45m'      # Purple
on_cyan='\e[46m'        # Cyan
on_white='\e[47m'       # White

nc="\e[m"               # Color Reset

alert=${bwhite}${on_red} # Bold White on red background
warn=${black}${on_yellow} # Bold White on red background
notice=${black}${on_cyan} # Bold White on red background
info=${black}${on_green} # Bold White on red background

fd=0                    # stdin

DATE=`date +%Y-%m-%d`
NOW=`date +%Y%m%d_%H%M%S`

alias strip_esc='sed -r "s/\x1B[\[|\(]([0-9]{1,2}(;[0-9]{1,2})?)?[m|K|A|B|C|E|J|S|Z|H]//g"'

# interactive shell? then keep user attention on the err msg
if [ -t "$fd" ] || [ -p /dev/stdin ]; then
	echo -n ""
fi


function DEBUG()
{
	[ "$_DEBUG" == "1" ] && $@
}

_exit()
{
	echo -e "$*"
	echo

	# interactive shell? then keep user attention on the err msg
	if [ -t "$fd" ] || [ -p /dev/stdin ]; then
		echo -n "" ; sleep 1
		echo -n "" ; sleep 1
		echo -n "" ; sleep 1
		echo -n "" ; sleep 1
		echo -n "" ; sleep 1
	fi

	exit 1
}

available() {
	type -t "$1" >/dev/null && return 0
	return 1
}



# Message to terminal and system log
# ###########################################################################
_log() {
	local msg_type="info"
	local write_msg="/sbin/log_tool -t0 -u$NAME -p127.0.0.1 -mlocalhost -a"
	local message="$NAME: ${*:-"Unspecified Notice"}"

	[ "$_QUIET" != "1" ] && echo -e "(${green}$msg_type${nc}) $message"

#	message=`echo -e $message | strip_esc`

	# save to system Event Log only when in DEBUG mode
	[ "$_DEBUG" == "1" ] && $write_msg "($msg_type) $(echo -e $message | strip_esc)"
}

# Warning message to terminal and system log
# ###########################################################################
__warn() {
	local msg_type="warn"
	local message="$NAME: ${*:-"Unknown Warning"}"
	echo -e "(${red}$msg_type${nc}) "
}
_warn() {
	local msg_type="warn"
	local write_warn="/sbin/log_tool -t1 -u$NAME -p127.0.0.1 -mlocalhost -a"
	local message="$NAME: ${*:-"Unknown Warning"}" && $write_warn "($msg_type) $(echo -e $message | strip_esc)"

	echo -e "(${red}$msg_type${nc}) $message"
}

# Write error log message and exit
# ###########################################################################
__err(){
	local msg_type="err!"
	local message="$NAME: ${*:-"Unknown Error"}"

	_exit "(${alert}$msg_type${nc}) $message \n"
}
_err(){
	local msg_type="err!"
	local write_err="/sbin/log_tool -t2 -u$NAME -p127.0.0.1 -mlocalhost -a"
	local message="$NAME: ${*:-"Unknown Error"}" && $write_err "($msg_type) $(echo -e $message | strip_esc)"

	_exit "(${alert}$msg_type${nc}) $message \n"
}


function help()
{
	echo -e "------------------------------------------------------------------------------"
	echo -e " (${NAME}) PID: $$; (parent:${PPID})"
	echo -e "---------------------------------------------------------- || Hello World! ---"
	echo
	echo -e "  Usage:"
	echo -e "          ${white}$0${nc} (start|stop|restart)"
	echo
	echo -e "  Available options:"
	echo -e "    start/stop/restart        - start or stop chroot environment"
}


# Helper: Get script location
# ########################################
function _get_cwd()
{
	if [[ ${0:0:1} != "/" ]]; then
		CWD="$PWD"/$(dirname "$0")
	else
		CWD=$(dirname "$0")
	fi

	CWD=`cd "${CWD}" 2>/dev/null && pwd || echo "${CWD}"`
	ROOT="${CWD}"
}

_get_cwd
DEBUG echo Found current directory: "$CWD"
CWD=`getcfg -f /etc/config/qpkg.conf $NAME Install_Path`
DEBUG echo And app is installed in: "$CWD"
_PWD=`pwd`
cd "$CWD" 2>/dev/null 1>/dev/null

ROOTFS="${CWD}/rootfs"

SHARE_DOWNLOAD=/share/Download
SHARE_MULTIMEDIA=/share/Multimedia
$(cd "${SHARE_DOWNLOAD}" 2>/dev/null || _err Download share does not exist. Please enable multimedia functions or create it manually.)
$(cd "${SHARE_MULTIMEDIA}" 2>/dev/null || _err Multimedia share does not exist. Please enable multimedia functions or create it manually.)
SOULSEEK_DOWNLOAD_DIR="${SHARE_DOWNLOAD}/SoulseekCloud"
mkdir -p $SOULSEEK_DOWNLOAD_DIR 2>/dev/null 1>/dev/null
SOULSEEK_MULTIMEDIA_DIR="${SHARE_MULTIMEDIA}"


# Install my icons ...
# ###########################################################################
install_icons(){
	cp -af "$CWD/.qpkg_icon.gif" "/home/httpd/RSS/images/${NAME}.gif"		2>/dev/null
	cp -af "$CWD/.qpkg_icon_gray.gif" "/home/httpd/RSS/images/${NAME}_gray.gif"	2>/dev/null
	cp -af "$CWD/.qpkg_icon_80.gif" "/home/httpd/RSS/images/${NAME}_80.gif"		2>/dev/null
}
install_icons


RESULT=`/sbin/getcfg ${FRAMEWORK} Enable -u -d TRUE -f /etc/config/qpkg.conf`
if  [ "$RESULT" = "FALSE" ] ; then
	_err " ${FRAMEWORK} is disabled"
	exit 1
fi

RESULT=`/sbin/getcfg ${FRAMEWORK} Install_Path -d /null -f /etc/config/qpkg.conf`
QX11_PATH=${RESULT}/rootfs
if  [ ! -d "${QX11_PATH}" ] ; then
	_err " ${FRAMEWORK} is missing or not installed!. Please install ${FRAMEWORK} first! "
	exit 1
fi


case "$1" in
	"enable")
		/sbin/setcfg ${NAME} Enable TRUE -f /etc/config/qpkg.conf
	;;
	"disable")
		/sbin/setcfg ${NAME} Enable FALSE -f /etc/config/qpkg.conf
	;;
	"start")
		echo "- $NAME -- # QPKG Enable check"
		RESULT=`/sbin/getcfg ${NAME} Enable -u -d TRUE -f /etc/config/qpkg.conf`
		if  [ "$RESULT" = "FALSE" ] ; then
			echo " ${NAME} is disabled"
			exit 1
		fi

		echo "- $NAME -- # QPKG Running check"
		if [ -e ${LOCK} ] ; then
			echo " ${NAME} already Started $BASE "
			exit 1
		fi

		touch ${LOCK}

		[ -f "/etc/machine-id" ] || dbus-uuidgen > /etc/machine-id
		#locale-gen en_US.UTF-8

		SHARE_SOULSEEK=/share/Download/SoulseekCloud
		mkdir -p ${SHARE_SOULSEEK}/Music
		mkdir -p ${SHARE_SOULSEEK}/Soulseek\ Chat\ Logs
		mkdir -p ${SHARE_SOULSEEK}/Soulseek\ Downloads
		#[ -L /usr/share/novnc/logs ] || ln -s ${SHARE_SOULSEEK}/Soulseek\ Chat\ Logs /usr/share/novnc/logs
		#[ -L /usr/share/novnc/downloads ] || ln -s ${SHARE_SOULSEEK}/Soulseek\ Downloads /usr/share/novnc/downloads

		[ -d "${SHARE_SOULSEEK}/.SoulseekQt" ] || cp -af ${CWD}/config/default/.SoulseekQt "${SHARE_SOULSEEK}/"

		echo "- $NAME -- # QX11_framework_enable" ; /etc/init.d/QX11.sh enable
		echo "- $NAME -- # QX11_framework_start..." ; /etc/init.d/QX11.sh start
		/sbin/log_tool -t 0 -a "${NAME} in : Framework started on port 601${_DISPLAY} -> forwarded to 259${_DISPLAY}"

		#${CWD}/init.sh
		export pgid=1000
		export puid=1000
		export resize=scale
		export resolution=1920x1040

		export DISPLAY=":${_DISPLAY}"
		#[ -f /tmp/.X${_DISPLAY}-lock ] && rm /tmp/.${_DISPLAY}-lock 2>/dev/null 1>/dev/null

		export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
		export TEMP="/tmp"
		export TMP="/tmp"
		export TMPDIR="/tmp"
		export TERM="xterm"
		export EDITOR="/usr/bin/mcedit"
		export SHELL="/bin/sh"
		export LD_LIBRARY_PATH
		export LD_RUN_PATH
		export MC_HOME
		export MC_TMPDIR
		export WGETRC
		unset -v LD_LIBRARY_PATH
		unset -v LD_RUN_PATH
		unset -v MC_HOME
		unset -v MC_TMPDIR
		unset -v WGETRC


		export LANG=en_US.UTF-8
		export LANGUAGE=en_US:en
		export LC_ALL=en_US.UTF-8
		export XDG_RUNTIME_DIR=/share/Download/SoulseekCloud

		export FONTCONFIG_PATH="/usr/bin/QX11/etc/fonts/"

		export HOME="/share/Download/SoulseekCloud"
		export LD_RUN_PATH="${CWD}/app/lib"
		export LD_RUN="${CWD}/app/lib"
		export LD_LIBRARY_PATH="${CWD}/libc:${CWD}/app/lib:${QX11_PATH}/usr/lib/x86_64-linux-gnu/mesa:${QX11_PATH}/usr/lib/x86_64-linux-gnu:${QX11_PATH}/lib/x86_64-linux-gnu"
		#export LD_RUN_PATH=$LD_LIBRARY_PATH
		#echo $LD_LIBRARY_PATH

		#${CWD}/app/ld-2.19.so ${CWD}/app/SoulseekQt &
		${CWD}/app/ld-2.27.so ${CWD}/app/SoulseekQt &
		#${CWD}/app/ld-2.31.so ${CWD}/app/SoulseekQt &

		echo "app/SoulseekQt" > /var/run/QX11.SoulseekQt

		;;
	"stop")
		if [ ! -e ${LOCK} ] ; then
			echo " ( lock file not exist: ${LOCK} ) "
#			echo "Stop : Nothing to do ${POOL_NAME} not started."
#			exit 0
		fi

		kill `ps ax | grep SoulseekQt | grep -v grep | awk '{print $1}' 2>/dev/null`  2>/dev/null 1>/dev/null
		sleep 1
		sync
		kill -9 `ps ax | grep SoulseekQt | grep -v grep | awk '{print $1}' 2>/dev/null`  2>/dev/null 1>/dev/null
		sync

		rm ${LOCK} 2>/dev/null 1>/dev/null

		/sbin/log_tool -t 0 -a "${NAME} Stopped"
		;;
	"status")
		echo Process:
		ps ax | grep Soulseek | grep -v grep | grep -v container | grep -v "$(basename $0)"
		;;
	"restart")
		${CWD}/$0 stop
		/bin/sleep 2
		${CWD}/$0 start
		;;
	*)
		help
		echo
		echo Status
		${CWD}/$0 status
		exit 1
esac

# Parent process has died so we also better die.
exit 0

please look at start sequence
so i shipped my app together with own libc from other system


listen to me… this was for QTS <6.0
from now QTS is Ubuntu24 and you can compile anything you want and even just copy from Ubuntu24
if you wish me i can even writte some skills for claude/codex/copilot and others with instruction how to exactly compile and write compilation scripts if you want

the other way too do such things is native chroot and run app from chroot

but as i said - please avoid those two methods, are legacy from the moment when qts 6 goes out

Cheers mate. :nerd_face:

BTW: have you been able to test on QTS 6? Seems it’s not available yet, even as a beta.

Would you please answer my pm on forum.qnap.net.pl ?

anyway answering to your question
QuTS hero 6 is already new glibc
so we can expect QTS 6 to be the same :wink:

Ah, sorry mate, I don’t really spend any time there, so I didn’t know you’d sent a message. I’ve just responded to it.

You should be able to PM on this forum now too. :nerd_face:

For those following along, I had some minor success today.

I copied glibc (and the linker) from my Debian Trixie, and was able to run a small binary on QTS 5.2.10 that required a glibc version unavailable in QTS. :tada:

Binary works fine so-far. More testing to do though.

The basic syntax was:

/path/to/new/linker --library-path /path/to/custom/libs/ /path/to/executable

Does this look right?