├── .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 |
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 |
--------------------------------------------------------------------------------