This is the last part of a five-part guide to running Jellyfin on your own server and connecting it to a Telegram bot. Parts 1 and 2 set up Jellyfin itself. 3 and 4 built the bot. This part makes all of it survive being left alone, which is the actual point of running it on a server instead of a laptop.
Everything in part 4 worked, right up until I closed the SSH session and the bot stopped answering. A script running in a terminal dies with that terminal. Getting it to survive a reboot, a crash, or an instance restart - and to leave a trail when something does go wrong - is three small pieces of configuration, not a rewrite.
The bot as a systemd service
Jellyfin already runs as a systemd service; you saw that in part 1 with
systemctl status jellyfin. Give the bot script the same treatment.
# /etc/systemd/system/telegram-jellyfin-bot.service
[Unit]
Description=Telegram to Jellyfin upload bot
After=network-online.target docker.service
Wants=network-online.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/telegram-bot
ExecStart=/home/ubuntu/telegram-bot/.venv/bin/python3 bot.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
A few of these lines are doing real work:
After=... docker.service- the bot depends on the local Telegram server from part 3, which is a Docker container. Starting the bot before Docker has come up just means its first few connection attempts fail;Restart=on-failurecovers that, but ordering it correctly avoids the noise.Restart=on-failurewithRestartSec=5- if the process crashes, wait five seconds and try again, rather than either giving up or hammering it in a tight loop.WorkingDirectorypointed at the script's own folder, so a relative.envpath (loaded byload_dotenv()in part 4) resolves correctly regardless of what directory systemd itself starts in.
sudo systemctl daemon-reload
sudo systemctl enable --now telegram-jellyfin-bot
sudo systemctl status telegram-jellyfin-bot
enable makes it start on boot. --now also starts it immediately, so you get
one command instead of two.
Logs
Once it's a systemd service, its output goes to the system journal automatically
- no extra logging code needed.
journalctl -u telegram-jellyfin-bot -f
-f follows it live, the same way tail -f would follow a log file. Send the
bot a file from another window and watch the same print() output from part 4
show up here instead of in a terminal you have to keep open.
Keeping the local Telegram server alive
The Docker container from part 3 already had --restart always in its run
command, which covers most of this on its own - Docker restarts it if it crashes,
and if you enabled the Docker service itself (systemctl enable docker, on by
default after apt install docker.io), it comes back after a reboot too.
Confirm both:
$ systemctl is-enabled docker
enabled
$ docker inspect telegram-bot-api --format '{{.HostConfig.RestartPolicy.Name}}'
always
If the second one doesn't say always, the container was started without that
flag at some point - remove it and re-run the docker run command from part 3
with --restart always included.
Log rotation
The Docker container's own logs, and the systemd journal for the bot, both grow without bound by default on a fresh Ubuntu install. On a 60 GB instance that matters eventually, not immediately, but it's a five-minute fix now versus a confusing "why is my disk full" later.
For the systemd journal, cap it in /etc/systemd/journald.conf:
SystemMaxUse=200M
sudo systemctl restart systemd-journald
For the Docker container, cap its log driver in the docker run command from
part 3 by adding:
--log-opt max-size=10m --log-opt max-file=3
A short security checklist
Pulling together what's been mentioned across the series, in one place:
- Bot token and Jellyfin API key live in
.env, mode 600, never in the script. Covered in part 4. If either one has ever been committed to a git repository, even privately, rotate it - BotFather's/revokefor the bot token, and a fresh key from Jellyfin's API Keys page for the other. ALLOWED_USER_IDis checked on every message. Without it the bot accepts uploads from anyone who finds its username.- Port 8081, the local Telegram server, is bound to
127.0.0.1only. Never0.0.0.0. It has no authentication of its own. - Port 8096, Jellyfin's own port, is closed at the Lightsail firewall. Traffic reaches it only via Caddy on 443, from part 2.
- If you went with the
sudo chmodfix in part 4, the sudoers rule is scoped to that one command, not blanket root access for the bot's user. - The Jellyfin admin password is a real one, not something left at a default - it's reachable from the whole internet as of part 2.
Where this actually leaves you
A file sent from a phone, anywhere, over Telegram, shows up as a playable title in Jellyfin within a few seconds, on a server that costs about as much as a single movie ticket a month, with nothing running in a terminal you have to keep open. That was the whole goal five parts ago.
The parts worth remembering if you're adapting this rather than copying it directly: the 20 MB Telegram limit isn't a bug to work around cleverly, it's a sign you want your own copy of the Bot API server; a bind mount is what makes a container's files visible to a plain Python script running next to it, not inside it; and almost every "it worked in testing" failure in this series traced back to a permissions boundary - Lightsail's firewall in part 1, file ownership in part 4 - that a quick local test never crosses but a real file, arriving for real, always does.