├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build-package ├── debian ├── conffiles ├── control ├── postinst └── prerm ├── systemd ├── system.service └── user.service └── tests.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | build: 10 | name: Node ${{ matrix.node_version }} 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node_version: [ 15 | 18.x, # EOL: April 2025 16 | 20.x, # EOL: April 2026 17 | ] 18 | 19 | steps: 20 | - uses: actions/checkout@master 21 | 22 | - name: Setup Node.js 23 | env: 24 | NODE_VERSION: ${{ matrix.node_version }} 25 | run: | 26 | curl -sL https://deb.nodesource.com/setup_$NODE_VERSION | sudo -E bash - 27 | sudo apt-get install -y nodejs 28 | 29 | - name: Build package 30 | run: ./build-package 31 | 32 | - name: Install package 33 | run: sudo dpkg -i deb/*.deb 34 | 35 | - name: Test 36 | run: ./tests.sh 37 | 38 | - name: Journal 39 | run: sudo systemctl --full status thelounge.service 40 | 41 | - name: Upload artifact 42 | if: ${{ matrix.node_version == '18.x' }} 43 | uses: actions/upload-artifact@v3 44 | with: 45 | name: thelounge.deb 46 | path: deb/*.deb 47 | 48 | - name: Release 49 | if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && matrix.node_version == '18.x' }} 50 | uses: softprops/action-gh-release@v1 51 | with: 52 | files: deb/*.deb 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /pkg/ 2 | /deb/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 The Lounge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Debian/Ubuntu package for The Lounge 2 | 3 | Build Status 6 | 7 | This repository holds out the build scripts that generates our `.deb` precompiled packages and also tracks Debian-specific issues in relation to the packaging. 8 | 9 | ## Building and installing the package 10 | 11 | If you are looking to simply install The Lounge, please use our pre-compiled binary .deb files available in the releases section of the main project. This section assumes you want to build a Debian package from sources. 12 | 13 | ```sh 14 | # Clone the repository 15 | git clone https://github.com/thelounge/thelounge-deb.git 16 | cd thelounge-deb 17 | 18 | # Call the build script 19 | ./build-package 20 | ``` 21 | 22 | After this, you should have a nice `.deb` file in the `deb/` output folder! This file can then be installed: 23 | 24 | ``` 25 | # dpkg -i deb/*.deb 26 | ``` 27 | 28 | ### Configuration 29 | 30 | The default system-wide configuration file is located at `/etc/thelounge/config.js`. Please note that user profiles and their IRC passwords are also stored there, so the directory is only readable by the `thelounge` user. 31 | 32 | ### Running 33 | 34 | The Lounge provides both a system-wide and per-user systemd unit. If you installed the package, The Lounge should already be running and accessible on `http://127.0.0.1:9000`. 35 | 36 | #### System 37 | 38 | Simply enable the `thelounge.service` unit, and your server should be up and running: 39 | 40 | ```sh 41 | systemctl enable --now thelounge.service 42 | ``` 43 | 44 | #### User 45 | 46 | If you do not want to run the software system-wide, or host multiple users that wish to host their own instance of The Lounge, it can also be launched per user: 47 | 48 | ```sh 49 | systemctl --user enable --now thelounge.service 50 | ``` 51 | 52 | Please note that for The Lounge to start on boot in this scenario, you will also require to have [lingering](https://wiki.archlinux.org/index.php/Systemd/User#Automatic_start-up_of_systemd_user_instances) enabled for this user: 53 | 54 | ```sh 55 | loginctl enable-linger $username 56 | ``` 57 | -------------------------------------------------------------------------------- /build-package: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit the script if any of the commands fail 4 | set -e 5 | set -u 6 | set -o pipefail 7 | 8 | # Set working directory to the location of this script 9 | cd "$(dirname "${BASH_SOURCE[0]}")" 10 | 11 | # Use "~" in place of "-" in NPMVERSION to make dpkg sort pre-releases correctly 12 | # See https://github.com/thelounge/thelounge-deb/issues/58 13 | 14 | # Some variables 15 | NPMVERSION=$(grep Version debian/control | awk -F': ' '{print $2}' | sed -E 's/-[0-9]+$//' | sed -E 's/~/-/') 16 | STARTDIR="$(pwd)" 17 | DESTDIR="$STARTDIR/pkg" 18 | OUTDIR="$STARTDIR/deb" 19 | CACHE="$STARTDIR/cache" 20 | MODULES_DIR="$DESTDIR/usr/lib/thelounge/node_modules" 21 | export HOME="$STARTDIR" # because yarn is stupid and tries to mess with the user config files 22 | 23 | echo "Building $NPMVERSION..." 24 | 25 | # Remove potential leftovers from a previous build 26 | rm -rf "$DESTDIR" "$OUTDIR" 27 | 28 | install -dm755 "$DESTDIR/usr/bin" 29 | install -dm755 "$MODULES_DIR/thelounge" 30 | 31 | # Fetch the lock file so that we actually get the pinned deps that we want 32 | curl -O "https://raw.githubusercontent.com/thelounge/thelounge/$NPMVERSION/yarn.lock" 33 | 34 | # Install the package itself 35 | # we on purposes don't use yarn global add, because with it --ignore-scripts 36 | # is pointless: https://github.com/yarnpkg/yarn/issues/8291 but we tried 37 | yarn add --no-default-rc --frozen-lockfile \ 38 | --prod --non-interactive --ignore-scripts \ 39 | --cache-folder "$CACHE" --modules-folder "$MODULES_DIR" \ 40 | "thelounge@${NPMVERSION}" 41 | 42 | # Write .thelounge_home to set correct system config directory 43 | echo "/etc/thelounge" > "$MODULES_DIR/thelounge/.thelounge_home" 44 | 45 | # manually write the binary link 46 | ln -s "${MODULES_DIR#$DESTDIR}/thelounge/index.js" "$DESTDIR/usr/bin/thelounge" 47 | 48 | # Install configuration/home directory 49 | install -dm775 "$DESTDIR/etc/thelounge" 50 | install -dm770 "$DESTDIR/etc/thelounge/users" 51 | install -Dm660 \ 52 | "$MODULES_DIR/thelounge/dist/defaults/config.js" \ 53 | "$DESTDIR/etc/thelounge/config.js" 54 | 55 | # Install systemd units 56 | install -Dm644 "$STARTDIR/systemd/system.service" \ 57 | "$DESTDIR/lib/systemd/system/thelounge.service" 58 | install -Dm644 "$STARTDIR/systemd/user.service" \ 59 | "$DESTDIR/usr/lib/systemd/user/thelounge.service" 60 | 61 | # Build .deb 62 | mkdir "$DESTDIR/DEBIAN" "$OUTDIR" 63 | cp "$STARTDIR/debian/"* "$DESTDIR/DEBIAN/" 64 | dpkg-deb -Z xz --root-owner-group --build "$DESTDIR" "$OUTDIR" 65 | -------------------------------------------------------------------------------- /debian/conffiles: -------------------------------------------------------------------------------- 1 | /etc/thelounge/config.js 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Package: thelounge 2 | Version: 4.4.3 3 | Section: net 4 | Priority: optional 5 | Architecture: all 6 | Depends: nodejs (>= 18.0.0), bash, init-system-helpers 7 | Recommends: python3, build-essential 8 | Maintainer: The Lounge maintainers team 9 | Description: A self-hosted, web-based IRC client 10 | Homepage: https://thelounge.chat 11 | Bugs: https://github.com/thelounge/thelounge/issues 12 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | [[ "$1" == "configure" ]] || exit 0 3 | 4 | echo 'Downloading or building sqlite3 module for your specific installation' 5 | echo This might take several minutes, depending on your processor speed 6 | pushd /usr/lib/thelounge/node_modules/sqlite3 || exit 1 7 | garbage_folder=./garbage42 8 | install -dm 750 "$garbage_folder" 9 | # make sure we aren't writing to any other dir 10 | # in theory the devdir should be enough but let's future proof it 11 | export npm_config_devdir="$garbage_folder" 12 | export HOME="$garbage_folder" 13 | /usr/lib/thelounge/node_modules/yarn/bin/yarn run install # download or build the c dep 14 | ret=$? 15 | rm -rf "$garbage_folder" 16 | popd || exit 1 17 | if [ $ret -ne 0 ] 18 | then 19 | echo '[!!] Failed to install sqlite3 module correctly, The Lounge will continue working, but you might want to fix this.' 20 | fi 21 | 22 | if ! getent group thelounge >/dev/null; then 23 | echo 'Creating thelounge group' 24 | addgroup --quiet --system thelounge 25 | fi 26 | 27 | if ! getent passwd thelounge >/dev/null; then 28 | echo 'Creating thelounge user' 29 | adduser --quiet --system thelounge \ 30 | --ingroup thelounge \ 31 | --no-create-home \ 32 | --gecos "System user for The Lounge (IRC client)" 33 | fi 34 | 35 | chown -R thelounge:thelounge /etc/thelounge 36 | 37 | deb-systemd-helper enable thelounge.service 38 | deb-systemd-invoke start thelounge.service || echo "could not start thelounge.service automatically" 39 | -------------------------------------------------------------------------------- /debian/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | deb-systemd-invoke stop thelounge.service 3 | if [ "$1" = "remove" ]; then 4 | deb-systemd-helper purge thelounge.service 5 | fi 6 | -------------------------------------------------------------------------------- /systemd/system.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=The Lounge (IRC client) 3 | After=network-online.target 4 | Wants=network-online.target 5 | 6 | 7 | [Service] 8 | User=thelounge 9 | Group=thelounge 10 | Type=simple 11 | ExecStart=/usr/bin/thelounge start 12 | ProtectSystem=yes 13 | ProtectHome=yes 14 | NoNewPrivileges=yes 15 | PrivateTmp=yes 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /systemd/user.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=The Lounge (IRC client) 3 | 4 | [Service] 5 | Type=simple 6 | Environment=THELOUNGE_HOME=~/.thelounge 7 | ExecStart=/usr/bin/thelounge start 8 | 9 | [Install] 10 | WantedBy=default.target 11 | -------------------------------------------------------------------------------- /tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Extract version to build from the repo 5 | DEBVERSION=$(grep Version debian/control | awk -F': ' '{print $2}') 6 | DEBARCH=$(grep Architecture debian/control | awk -F': ' '{print $2}') 7 | DEBFILE="deb/thelounge_${DEBVERSION}_${DEBARCH}.deb" 8 | NPMVERSION=$(echo "${DEBVERSION}" | sed -E 's/-[0-9]+$//' | sed -E 's/~/-/') 9 | 10 | # Exit status code to update if there is a failure 11 | CODE=0 12 | 13 | echo 14 | echo "$DEBFILE" 15 | 16 | # The deb file should correctly exist 17 | if [ -e "$DEBFILE" ]; then 18 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mwas correctly built\\x1B[0m" 19 | else 20 | echo -e " \\x1B[31m✗ was not built\\x1B[0m" 21 | CODE=1 22 | fi 23 | 24 | # The file should have a minimum size for safety (ensures we did not create an 25 | # empty file), and a maximum size (ensures we did not load way too much 26 | # third-party code. 27 | if [ -e "$DEBFILE" ]; then 28 | FILESIZE=$(ls -l "$DEBFILE" | awk '{print $5}') 29 | HUMANSIZE=$(ls -lh "$DEBFILE" | awk '{print $5}') 30 | MINSIZE=3 31 | MAXSIZE=10 32 | 33 | if [ "$FILESIZE" -gt "$((MINSIZE * 1024 * 1024))" ] && 34 | [ "$FILESIZE" -lt "$((MAXSIZE * 1024 * 1024))" ]; then 35 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mhas a valid file size ($HUMANSIZE)\\x1B[0m" 36 | else 37 | echo -e " \\x1B[31m✗ has an invalid file size\\x1B[0m" 38 | echo -e " \\x1B[32mminimum: ${MINSIZE}M\\x1B[0m" 39 | echo -e " \\x1B[32mmaximum: ${MAXSIZE}M\\x1B[0m" 40 | echo -e " \\x1B[31mactual: ${HUMANSIZE}\\x1B[0m" 41 | echo 42 | CODE=1 43 | fi 44 | else 45 | echo -e " \\x1B[36m- file size could not be checked\\x1B[0m" 46 | fi 47 | 48 | # sqlite should be installed correctly at runtime 49 | # the glob is the name of the api/os/arch triplet, say napi-v3-linux-arm 50 | if ls /usr/lib/thelounge/node_modules/sqlite3/build/Release/node_sqlite3.node >/dev/null 2>&1 ; then 51 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90msqlite was installed correctly at runtime\\x1B[0m" 52 | else 53 | echo -e " \\x1B[31m✗ sqlite was not installed at runtime\\x1B[0m" 54 | CODE=1 55 | fi 56 | 57 | # If the service was correctly set up with systemd, it should show in the big 58 | # `sudo systemctl` list. 59 | SYSTEMCTL_LIST=$(sudo systemctl | grep "thelounge.service") 60 | if [[ "$SYSTEMCTL_LIST" = *"The Lounge (IRC client)"* ]]; then 61 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mcorrectly shows up in systemctl list\\x1B[0m" 62 | else 63 | echo -e " \\x1B[31m✗ was not found or incorrectly listed\\x1B[0m" 64 | echo -e " \\x1B[32mexpected: The Lounge (IRC client)\\x1B[0m" 65 | echo -e " \\x1B[31mactual: ${SYSTEMCTL_LIST}\\x1B[0m" 66 | echo 67 | CODE=1 68 | fi 69 | 70 | # Wait until The Lounge is actually fully started 71 | sleep 2 72 | 73 | # Entire entry for the service. We'll use this to see if everything is in order. 74 | SYSTEMCTL_STATUS=$(sudo systemctl status --full thelounge.service) 75 | 76 | # `systemctl status` should report `Active: active (running) since ...` 77 | SYSTEMCTL_ACTIVE=$(echo "${SYSTEMCTL_STATUS}" | grep "Active:") 78 | if [[ "$SYSTEMCTL_ACTIVE" = *"active (running)"* ]]; then 79 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mis reported as active and running by systemctl status\\x1B[0m" 80 | else 81 | echo -e " \\x1B[31m✗ does not have a status of active and running\\x1B[0m" 82 | echo -e " \\x1B[32mexpected: Active: active (running)\\x1B[0m" 83 | echo -e " \\x1B[31mactual: ${SYSTEMCTL_ACTIVE}\\x1B[0m" 84 | echo 85 | CODE=1 86 | fi 87 | 88 | SYSTEMCTL_STARTED=$(echo "${SYSTEMCTL_STATUS}" | grep "systemd\\[") 89 | if [[ "$SYSTEMCTL_STARTED" = *"Started The Lounge (IRC client)"* ]]; then 90 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mshows up as started in systemctl logs\\x1B[0m" 91 | else 92 | echo -e " \\x1B[31m✗ does not show up as started in systemctl\\x1B[0m" 93 | echo -e " \\x1B[32mexpected: Started The Lounge (IRC client)\\x1B[0m" 94 | echo -e " \\x1B[31mactual: ${SYSTEMCTL_STARTED}\\x1B[0m" 95 | echo 96 | CODE=1 97 | fi 98 | 99 | SYSTEMCTL_VERSION=$(echo "${SYSTEMCTL_STATUS}" | grep "The Lounge v") 100 | if [[ "$SYSTEMCTL_VERSION" = *"$NPMVERSION"* ]]; then 101 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mshows correct version in systemctl logs\\x1B[0m" 102 | else 103 | echo -e " \\x1B[31m✗ does not show up correct version in systemctl\\x1B[0m" 104 | echo -e " \\x1B[32mexpected: The Lounge v$NPMVERSION\\x1B[0m" 105 | echo -e " \\x1B[31mactual: ${SYSTEMCTL_VERSION}\\x1B[0m" 106 | echo 107 | CODE=1 108 | fi 109 | 110 | SYSTEMCTL_CONFIG=$(echo "${SYSTEMCTL_STATUS}" | grep "Configuration file:") 111 | if [[ "$SYSTEMCTL_CONFIG" = *"/etc/thelounge/config.js"* ]]; then 112 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mshows correct configuration path in systemctl logs\\x1B[0m" 113 | else 114 | echo -e " \\x1B[31m✗ does not show up correct version in systemctl logs\\x1B[0m" 115 | echo -e " \\x1B[32mexpected: Configuration file: /etc/thelounge/config.js\\x1B[0m" 116 | echo -e " \\x1B[31mactual: ${SYSTEMCTL_CONFIG}\\x1B[0m" 117 | echo 118 | CODE=1 119 | fi 120 | 121 | SYSTEMCTL_URL=$(echo "${SYSTEMCTL_STATUS}" | grep "Available at") 122 | if [[ "$SYSTEMCTL_URL" = *"http://[::]:9000/"* ]]; then 123 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mshows correct URL in systemctl logs\\x1B[0m" 124 | else 125 | echo -e " \\x1B[31m✗ does not show up correct URL in systemctl logs\\x1B[0m" 126 | echo -e " \\x1B[32mexpected: Available at http://[::]:9000/\\x1B[0m" 127 | echo -e " \\x1B[31mactual: ${SYSTEMCTL_URL}\\x1B[0m" 128 | echo 129 | CODE=1 130 | fi 131 | 132 | SYSTEMCTL_LOGS=$(echo "${SYSTEMCTL_STATUS}" | grep "thelounge\\[") 133 | 134 | if [[ "$SYSTEMCTL_LOGS" != *"[WARN]"* ]]; then 135 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mdoes not have any warnings in systemctl logs\\x1B[0m" 136 | else 137 | echo -e " \\x1B[31m✗ has warnings in systemctl in systemctl logs\\x1B[0m" 138 | echo -e " \\x1B[31mactual: $(echo "${SYSTEMCTL_LOGS}" | grep "\\[WARN\\]")\\x1B[0m" 139 | echo 140 | CODE=1 141 | fi 142 | 143 | if [[ "$SYSTEMCTL_LOGS" != *"[ERROR]"* ]]; then 144 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mdoes not have any errors in systemctl logs\\x1B[0m" 145 | else 146 | echo -e " \\x1B[31m✗ has errors in systemctl in systemctl logs\\x1B[0m" 147 | echo -e " \\x1B[31mactual: $(echo "${SYSTEMCTL_LOGS}" | grep "\\[ERROR\\]")\\x1B[0m" 148 | echo 149 | CODE=1 150 | fi 151 | 152 | THELOUNGE_HTML=$(curl --silent http://localhost:9000) 153 | if [[ "$THELOUNGE_HTML" = *"The Lounge"* ]]; then 154 | echo -e " \\x1B[32m✓\\x1B[0m \\x1B[90mreturns correct HTML markup when calling the webserver\\x1B[0m" 155 | else 156 | echo -e " \\x1B[31m✗ does not return correct HTML markup when calling the webserver\\x1B[0m" 157 | echo -e " \\x1B[32mexpected: The Lounge\\x1B[0m" 158 | echo -e " \\x1B[31mactual:\\x1B[0m" 159 | echo "$THELOUNGE_HTML" 160 | CODE=1 161 | fi 162 | 163 | exit $CODE 164 | --------------------------------------------------------------------------------