If you still run or maintain Eggdrop bots in 2026, chances are you are dealing with more than just an IRC bot binary. You are also dealing with an ecosystem: Tcl itself, TLS support, auxiliary Tcl packages, legacy scripts, and all the small runtime assumptions that tend to accumulate over years.
That is exactly why a source-based, isolated build still matters.
This guide walks through a clean deployment of Eggdrop 1.10.1 on Debian using a private stack built under /opt/eggdrop-lab, with:
The goal is not just “make it run.” The goal is to make it predictable, reproducible, and easy to troubleshoot later.
This procedure was validated in a real lab on Debian 12 and Debian 13. It is also designed as a strong technical base for people who want to revisit older Tcl-based Eggdrop scripts without depending on Debian’s stock Tcl packages or on whichever OpenSSL version happens to be installed system-wide.
Because control matters.
A stock Debian system may be perfectly fine for many modern packages, but if your target includes older Tcl scripts, custom HTTP wrappers, TLS behavior, or a migration path from older Windows or Cygwin-based Eggdrop environments, relying on the distribution defaults quickly becomes messy.
With a source-built stack, you get:
That means less guesswork, fewer silent mismatches, and a much clearer platform for testing real-world Tcl scripts.
This matters a lot more than it may seem at first glance.
Many long-lived Eggdrop deployments are not limited to core modules. They also carry years of Tcl scripts for HTTP requests, feeds, APIs, text processing, helpers, formatting, hashing, encoding, or ad hoc utility code. Those scripts often assume a particular combination of:
auto_path and package resolution rulesWhen such scripts start breaking, the visible symptom is often misleading. A bot may appear to “have a Tcl problem” when the real issue is:
A clean stack like the one described here gives you a stable foundation for revisiting older Tcl code with much less ambiguity. It does not magically fix legacy scripts, but it removes a large class of environmental uncertainty.
The reference directory layout is:
/opt/eggdrop-lab
/opt/eggdrop-lab/src
/opt/eggdrop-lab/openssl361
/opt/eggdrop-lab/tcl903
/opt/eggdrop-lab/eggdrop1101
Create it with:
mkdir -p \
/opt/eggdrop-lab/src \
/opt/eggdrop-lab/openssl361 \
/opt/eggdrop-lab/tcl903 \
/opt/eggdrop-lab/eggdrop1101
Install only generic build dependencies. Do not install Debian Tcl/Tk packages for this stack.
apt update
apt install -y \
build-essential \
pkg-config \
autoconf \
automake \
libtool \
make \
gcc \
g++ \
libc6-dev \
zlib1g-dev \
libbz2-dev \
libssl-dev \
wget \
curl \
ca-certificates \
git \
unzip \
xz-utils
Use the following source archives:
OpenSSL 3.6.1
https://github.com/openssl/openssl/releases/download/openssl-3.6.1/openssl-3.6.1.tar.gz
Tcl 9.0.3
https://prdownloads.sourceforge.net/tcl/tcl9.0.3-src.tar.gz
TclTLS 2.0
https://core.tcl-lang.org/tcltls/uv/tcltls-2.0-src.tar.gz
Tcllib 2.0
https://core.tcl-lang.org/tcllib/tarball/tcllib-2.0.tar.gz?r=tcllib-2-0
Eggdrop 1.10.1
https://ftp.eggheads.org/pub/eggdrop/source/1.10/eggdrop-1.10.1.tar.gz
Download them into /opt/eggdrop-lab/src:
cd /opt/eggdrop-lab/src
wget -O openssl-3.6.1.tar.gz \
"https://github.com/openssl/openssl/releases/download/openssl-3.6.1/openssl-3.6.1.tar.gz"
wget -O tcl9.0.3-src.tar.gz \
"https://prdownloads.sourceforge.net/tcl/tcl9.0.3-src.tar.gz"
wget -O tcltls-2.0-src.tar.gz \
"https://core.tcl-lang.org/tcltls/uv/tcltls-2.0-src.tar.gz"
wget -O tcllib-2.0.tar.gz \
"https://core.tcl-lang.org/tcllib/tarball/tcllib-2.0.tar.gz?r=tcllib-2-0"
wget -O eggdrop-1.10.1.tar.gz \
"https://ftp.eggheads.org/pub/eggdrop/source/1.10/eggdrop-1.10.1.tar.gz"
Do not use ambiguous wildcards such as:
cd tcltls-*
This can fail if multiple matching paths exist.
Use this pattern instead:
TARBALL="name.tar.gz"
SRCDIR="$(tar tzf "$TARBALL" | head -1 | cut -d/ -f1)"
tar xzf "$TARBALL"
cd "$SRCDIR"
This is the recommended method throughout this guide.
cd /opt/eggdrop-lab/src
OPENSSL_TARBALL="openssl-3.6.1.tar.gz"
OPENSSL_SRCDIR="$(tar tzf "$OPENSSL_TARBALL" | head -1 | cut -d/ -f1)"
rm -rf "$OPENSSL_SRCDIR"
tar xzf "$OPENSSL_TARBALL"
cd "$OPENSSL_SRCDIR"
./Configure \
--prefix=/opt/eggdrop-lab/openssl361 \
--openssldir=/opt/eggdrop-lab/openssl361/ssl \
shared \
zlib \
linux-x86_64 \
"-Wl,-rpath,/opt/eggdrop-lab/openssl361/lib64" \
"-Wl,-rpath,/opt/eggdrop-lab/openssl361/lib"
make -j"$(nproc)"
make install_sw
Verify it:
LD_LIBRARY_PATH=/opt/eggdrop-lab/openssl361/lib64:/opt/eggdrop-lab/openssl361/lib \
/opt/eggdrop-lab/openssl361/bin/openssl version -a
cd /opt/eggdrop-lab/src
TCL_TARBALL="tcl9.0.3-src.tar.gz"
TCL_SRCDIR="$(tar tzf "$TCL_TARBALL" | head -1 | cut -d/ -f1)"
rm -rf "$TCL_SRCDIR"
tar xzf "$TCL_TARBALL"
cd "$TCL_SRCDIR/unix"
./configure --prefix=/opt/eggdrop-lab/tcl903
make -j"$(nproc)"
make install
ln -sf /opt/eggdrop-lab/tcl903/bin/tclsh9.0 /opt/eggdrop-lab/tcl903/bin/tclsh
Verify it:
/opt/eggdrop-lab/tcl903/bin/tclsh9.0 <<< 'puts [info patchlevel]'
Expected:
9.0.3
Important: in the validated procedure, do not use --with-ssl-dir.
Use the OpenSSL and Tcl private paths through build flags and environment variables instead.
cd /opt/eggdrop-lab/src
TLS_TARBALL="tcltls-2.0-src.tar.gz"
TLS_SRCDIR="$(tar tzf "$TLS_TARBALL" | head -1 | cut -d/ -f1)"
rm -rf "$TLS_SRCDIR"
tar xzf "$TLS_TARBALL"
cd "$TLS_SRCDIR"
make distclean 2>/dev/null || true
export PKG_CONFIG_PATH="/opt/eggdrop-lab/openssl361/lib64/pkgconfig:/opt/eggdrop-lab/openssl361/lib/pkgconfig"
export LD_LIBRARY_PATH="/opt/eggdrop-lab/openssl361/lib64:/opt/eggdrop-lab/openssl361/lib:/opt/eggdrop-lab/tcl903/lib"
./configure \
--prefix=/opt/eggdrop-lab/tcl903 \
--with-tcl=/opt/eggdrop-lab/tcl903/lib \
--with-tclinclude=/opt/eggdrop-lab/tcl903/include \
CPPFLAGS="-I/opt/eggdrop-lab/openssl361/include -I/opt/eggdrop-lab/tcl903/include" \
LDFLAGS="-L/opt/eggdrop-lab/openssl361/lib64 -L/opt/eggdrop-lab/openssl361/lib -L/opt/eggdrop-lab/tcl903/lib -Wl,-rpath,/opt/eggdrop-lab/openssl361/lib64 -Wl,-rpath,/opt/eggdrop-lab/openssl361/lib -Wl,-rpath,/opt/eggdrop-lab/tcl903/lib"
make -j"$(nproc)"
make install
Verify it:
export TCLLIBPATH="/opt/eggdrop-lab/tcl903/lib"
export LD_LIBRARY_PATH="/opt/eggdrop-lab/openssl361/lib64:/opt/eggdrop-lab/openssl361/lib:/opt/eggdrop-lab/tcl903/lib"
/opt/eggdrop-lab/tcl903/bin/tclsh9.0 <<'EOF'
puts "Tcl=[info patchlevel]"
puts "tls=[package require tls]"
EOF
Expected:
Tcl=9.0.3
tls=2.0
Important: use make install-tcl.
Do not use make install here if you want to avoid optional Critcl/TcllibC errors.
cd /opt/eggdrop-lab/src
TCLLIB_TARBALL="tcllib-2.0.tar.gz"
TCLLIB_SRCDIR="$(tar tzf "$TCLLIB_TARBALL" | head -1 | cut -d/ -f1)"
rm -rf "$TCLLIB_SRCDIR"
tar xzf "$TCLLIB_TARBALL"
cd "$TCLLIB_SRCDIR"
./configure --prefix=/opt/eggdrop-lab/tcl903
make -j"$(nproc)"
make install-tcl
Verify Tcllib:
/opt/eggdrop-lab/tcl903/bin/tclsh9.0 <<'EOF'
lappend auto_path /opt/eggdrop-lab/tcl903/lib/tcllib2.0
foreach p {md5 sha1 struct::list uri base64 json} {
if {[catch {package require $p} v]} {
puts "$p => ERR: $v"
} else {
puts "$p => OK: $v"
}
}
EOF
The validated runtime layout uses:
OPENSSL_HOME="/opt/eggdrop-lab/openssl361"
TCL_HOME="/opt/eggdrop-lab/tcl903"
And the runtime Tcl package path must include both:
$TCL_HOME/lib
$TCL_HOME/lib/tcllib2.0
That means:
export TCLLIBPATH="$TCL_HOME/lib $TCL_HOME/lib/tcllib2.0"
Note carefully: TCLLIBPATH is a Tcl list, not a colon-separated shell PATH.
Do not run your bot as root.
Use a dedicated unprivileged user. The examples below use generic variables so you can rename the account and the bot instance later without rewriting the whole procedure.
BOT_USER="eggbot"
BOT_HOME="/home/$BOT_USER"
BOT_INSTANCE="EggdropBot"
OPENSSL_HOME="/opt/eggdrop-lab/openssl361"
TCL_HOME="/opt/eggdrop-lab/tcl903"
SRC_HOME="/opt/eggdrop-lab/src"
Create the account as root:
useradd -m -d "$BOT_HOME" -s /bin/bash "$BOT_USER"
passwd "$BOT_USER"
chown -R "$BOT_USER:$BOT_USER" /opt/eggdrop-lab
Choose BOT_USER carefully. This can matter for operational clarity and, in some environments, for ident or identd visibility.
Switch to the runtime user:
su - "$BOT_USER"
Re-export the variables:
BOT_USER="eggbot"
BOT_HOME="/home/$BOT_USER"
BOT_INSTANCE="EggdropBot"
OPENSSL_HOME="/opt/eggdrop-lab/openssl361"
TCL_HOME="/opt/eggdrop-lab/tcl903"
SRC_HOME="/opt/eggdrop-lab/src"
Remove any stale staged instance first:
rm -rf "$BOT_HOME/$BOT_INSTANCE"
mkdir -p "$BOT_HOME/$BOT_INSTANCE"
Extract Eggdrop cleanly:
cd "$SRC_HOME"
EGGDROP_TARBALL="eggdrop-1.10.1.tar.gz"
EGGDROP_SRCDIR="$(tar tzf "$EGGDROP_TARBALL" | head -1 | cut -d/ -f1)"
rm -rf "$EGGDROP_SRCDIR"
tar xzf "$EGGDROP_TARBALL"
cd "$EGGDROP_SRCDIR"
make distclean 2>/dev/null || true
Export a complete build environment so the Eggdrop modules inherit the OpenSSL paths too:
export CPPFLAGS="-I$TCL_HOME/include -I$OPENSSL_HOME/include"
export LDFLAGS="-L$TCL_HOME/lib -L$OPENSSL_HOME/lib64 -L$OPENSSL_HOME/lib -Wl,-rpath,$TCL_HOME/lib -Wl,-rpath,$OPENSSL_HOME/lib64 -Wl,-rpath,$OPENSSL_HOME/lib"
export LIBS="-lssl -lcrypto -lz -lpthread -lm -lresolv"
export LD_LIBRARY_PATH="$OPENSSL_HOME/lib64:$OPENSSL_HOME/lib:$TCL_HOME/lib"
export PKG_CONFIG_PATH="$OPENSSL_HOME/lib64/pkgconfig:$OPENSSL_HOME/lib/pkgconfig"
Configure, build and install:
./configure \
--prefix=/opt/eggdrop-lab/eggdrop1101 \
--with-tcllib="$TCL_HOME/lib" \
--with-tclinc="$TCL_HOME/include"
make config
make -j"$(nproc)"
make install DEST="$BOT_HOME/$BOT_INSTANCE"
Check the installed files:
find "$BOT_HOME/$BOT_INSTANCE" -maxdepth 2 -type f -name 'eggdrop*' -ls
file "$BOT_HOME/$BOT_INSTANCE/eggdrop-1.10.1"
ldd "$BOT_HOME/$BOT_INSTANCE/eggdrop-1.10.1"
The binary should be a real ELF executable, not a shell script.
The linker output should show:
libtcl9.0.so from /opt/eggdrop-lab/tcl903/liblibssl.so.3 from /opt/eggdrop-lab/openssl361/lib64libcrypto.so.3 from /opt/eggdrop-lab/openssl361/lib64Verify a few modules too:
ldd "$BOT_HOME/$BOT_INSTANCE/modules-1.10.1/assoc.so"
ldd "$BOT_HOME/$BOT_INSTANCE/modules-1.10.1/blowfish.so"
ldd "$BOT_HOME/$BOT_INSTANCE/modules-1.10.1/server.so"
eggdrop symlink before creating your own launcherThis is a real pitfall.
After staged installation, Eggdrop may create:
eggdrop -> eggdrop-1.10.1
If you want to create your own custom launcher script named eggdrop, remove that symlink first:
rm -f "$BOT_HOME/$BOT_INSTANCE/eggdrop"
Verify the directory again:
ls -l "$BOT_HOME/$BOT_INSTANCE"
The validated launcher is intentionally simple:
cat > "$BOT_HOME/$BOT_INSTANCE/eggdrop" <<EOF2
#!/bin/bash
set -e
export EGGROOT="$BOT_HOME/$BOT_INSTANCE"
export OPENSSL_HOME="$OPENSSL_HOME"
export TCL_HOME="$TCL_HOME"
export PATH="\$OPENSSL_HOME/bin:\$TCL_HOME/bin:/usr/bin:/bin"
export TCLLIBPATH="\$TCL_HOME/lib \$TCL_HOME/lib/tcllib2.0"
export LD_LIBRARY_PATH="\$OPENSSL_HOME/lib64:\$OPENSSL_HOME/lib:\$TCL_HOME/lib:\${LD_LIBRARY_PATH:-}"
cd "\$EGGROOT"
"\$EGGROOT/eggdrop-1.10.1" \$*
EOF2
chmod +x "$BOT_HOME/$BOT_INSTANCE/eggdrop"
Yes, some people prefer "$@" over $*. This example intentionally keeps a simpler shell style consistent with the validated lab workflow.
Verify the launcher:
cat "$BOT_HOME/$BOT_INSTANCE/eggdrop"
ls -l "$BOT_HOME/$BOT_INSTANCE/eggdrop"
Do not test Tcl through the Eggdrop launcher.
Test Tcl directly with tclsh9.0 and the right environment:
export PATH="$OPENSSL_HOME/bin:$TCL_HOME/bin:/usr/bin:/bin"
export TCLLIBPATH="$TCL_HOME/lib $TCL_HOME/lib/tcllib2.0"
export LD_LIBRARY_PATH="$OPENSSL_HOME/lib64:$OPENSSL_HOME/lib:$TCL_HOME/lib:${LD_LIBRARY_PATH:-}"
"$TCL_HOME/bin/tclsh9.0" <<'EOF'
puts "Tcl=[info patchlevel]"
puts "tls=[package require tls]"
puts "TCLLIBPATH=$::env(TCLLIBPATH)"
puts "auto_path=$auto_path"
foreach p {md5 sha1 struct::list uri base64 json} {
if {[catch {package require $p} v]} {
puts "$p => ERR: $v"
} else {
puts "$p => OK: $v"
}
}
EOF
Now test Eggdrop through the launcher:
"$BOT_HOME/$BOT_INSTANCE/eggdrop" -v
Do not use the stock eggdrop.conf as your final live config.
Make a dedicated instance config:
cp "$BOT_HOME/$BOT_INSTANCE/eggdrop.conf" \
"$BOT_HOME/$BOT_INSTANCE/${BOT_INSTANCE}.conf"
Edit:
$BOT_HOME/$BOT_INSTANCE/${BOT_INSTANCE}.conf
Create the first owner and password:
"$BOT_HOME/$BOT_INSTANCE/eggdrop" -m "${BOT_INSTANCE}.conf"
Then start it in foreground for the first real test:
"$BOT_HOME/$BOT_INSTANCE/eggdrop" -n "${BOT_INSTANCE}.conf"
Keep these in mind:
tar xzf for .tar.gz archives.cd something-* patterns.--with-ssl-dir in the validated TclTLS procedure.make install-tcl for Tcllib if you want a clean HOWTO without optional Critcl/TcllibC failures.TCLLIBPATH is a Tcl list, not a colon-separated shell PATH.eggdrop is a symlink to eggdrop-1.10.1, remove it before writing your launcher script.tclsh9.0, not through the Eggdrop launcher.BOT_USER carefully, especially if identd is involved.This guide intentionally favors explicit paths, simple launcher logic, and a clean separation between build-time and runtime concerns.
That makes it useful not only for a fresh Eggdrop deployment, but also for anyone trying to revisit an older Tcl-based bot environment with fewer unknowns.
From here, the next natural step is to validate real-world Tcl scripts against this stack and document the behavior differences, if any, between older deployments and this cleaner Debian-based runtime.
You must be logged in to reply.