Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ and on top of that:
formats.
- E-mail download completion notification via Postfix MTA (bound to
localhost).
- File sharing (`Transmission`_) configuration:
- Download server (`Transmission`_) configuration:

- Authenticated web interface and RPC endpoint on HTTPS port 12322.
- Downloads stored in */srv/storage/download* with incomplete data in
*/srv/storage/incoming*.

- Anti-virus / malware scanning via `ClamAV`_.

- Anti-virus signatures are auto-updated.
- Automatically quarantines unsafe downloads.
- Anti-virus logfile: */var/log/rtorrent/clamav.log*
- Anti-virus logfile: */var/log/transmission/clamav.log*

- File server (`Samba`_) configuration:

Expand Down Expand Up @@ -65,7 +69,7 @@ configuration details if behind a Firewall/Router/NAT
Credentials *(passwords set at first boot)*
-------------------------------------------

- Webmin, Webshell, SSH, Samba: username **root**
- Webmin, SSH, Samba: username **root**
- Transmission: username **admin**
- Web based file manager (WebDAV CGI):
- username **root** (or Samba users)
Expand Down
10 changes: 10 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
turnkey-torrentserver-19.0 (1) turnkey; urgency=low

* Update the appliance to Debian 13/Trixie and Debian's Transmission 4.1
package series.
* Keep Transmission credentials out of process arguments and configure its
authenticated RPC, storage, completion scan, and peer port explicitly.
* Replace stale rTorrent service residue with a Transmission lifecycle test.

-- TurnKey Linux release engineering <release@turnkeylinux.org> Tue, 25 Aug 2026 00:00:00 +0000

turnkey-torrentserver-18.0 (1) turnkey; urgency=low

* Add wsdd package for Windows Service for Devices - so Fileserver shows up
Expand Down
63 changes: 44 additions & 19 deletions conf.d/main
Original file line number Diff line number Diff line change
@@ -1,31 +1,59 @@
#!/bin/sh -ex
#!/bin/bash -e

STORAGE=/srv/storage
PASSWORD=turnkey

mkdir -p $STORAGE/incoming
mkdir -p $STORAGE/quarantine
mkdir -p $STORAGE/download
set -x

chown -R debian-transmission:users $STORAGE/incoming
chown -R debian-transmission:users $STORAGE/quarantine
chown -R debian-transmission:users $STORAGE/download
STORAGE=/srv/storage
install -d -o debian-transmission -g users -m 2770 \
"$STORAGE/incoming" "$STORAGE/quarantine" "$STORAGE/download"
install -d -o debian-transmission -g users -m 0750 /var/log/transmission

mkdir /var/log/rtorrent
# Permit the completion hook to submit files to clamd and let clamd read the
# shared storage tree after the next service start.
adduser debian-transmission clamav
adduser clamav users

# configure transmission
CONF=/etc/transmission-daemon/settings.json
sed -i 's|"download-dir": .*|"download-dir": "\/srv\/storage\/download",|' $CONF
service transmission-daemon stop || true
python3 - "$CONF" <<'PY'
import json
import sys

path = sys.argv[1]
with open(path, encoding='utf-8') as settings_file:
settings = json.load(settings_file)

settings.update({
'download-dir': '/srv/storage/download',
'incomplete-dir': '/srv/storage/incoming',
'incomplete-dir-enabled': True,
'peer-port': 6882,
'rpc-authentication-required': True,
'rpc-bind-address': '127.0.0.1',
'rpc-username': 'admin',
'rpc-whitelist': '127.0.0.1,::1',
'rpc-whitelist-enabled': True,
'script-torrent-done-enabled': True,
'script-torrent-done-filename': '/usr/local/bin/clamav-scan',
})

with open(path, 'w', encoding='utf-8') as settings_file:
json.dump(settings, settings_file, indent=4, sort_keys=True)
settings_file.write('\n')
PY
chown debian-transmission:debian-transmission "$CONF"
chmod 0660 "$CONF"

# configure clamav
CONF=/etc/clamav/clamd.conf
sed -i 's|\(ScanMail\) true|\1 false|; s|\(LogSyslog\) false|\1 true|' $CONF
sed -i 's|\(ScanMail\) true|\1 false|; s|\(LogSyslog\) false|\1 true|' "$CONF"
/usr/bin/systemctl enable clamav-freshclam.service clamav-daemon.path

# configure samba
CONF=/etc/samba/smb.conf
NETBIOS_NAME=$(echo $HOSTNAME | tr [a-z] [A-Z])
sed -i "s|\(netbios name =\) \(.*\)|\1 $NETBIOS_NAME|" $CONF
sed -i "s|\(server string =\) \(.*\)|\1 $CREDIT_ANCHORTEXT|" $CONF
NETBIOS_NAME=${HOSTNAME^^}
sed -i "s|\(netbios name =\) \(.*\)|\1 $NETBIOS_NAME|" "$CONF"
sed -i "s|\(server string =\) \(.*\)|\1 $CREDIT_ANCHORTEXT|" "$CONF"

# Rename the file server for WebDAVCGI
sed -i 's/FILESERVER/TORRENTSERVER/' /var/www/webdavcgi/webdav.conf
Expand All @@ -38,6 +66,3 @@ a2dissite 000-default
a2ensite tkl-webcp
a2ensite webdavcgi
a2ensite transmission

# initscript
update-rc.d transmission-daemon defaults 99
55 changes: 55 additions & 0 deletions docs/v19.0-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Torrentserver 19.0 test evidence

## Dependency decision

Torrentserver uses Debian Trixie packages for Transmission, Samba, Apache,
ClamAV, Postfix, and the rest of its runtime. The source probe tested Debian's
`transmission-daemon` 4.1.0 beta package series. No separately downloaded
application artifact or third-party repository is required. Debian APT is the
maintained updater and supplies signed repository metadata.

## Primary-flow crosswalk

| README claim | Focused check | Expected result | Current evidence |
|---|---|---|---|
| Transmission administrator and HTTPS web UI on 12322 | Use the real firstboot password through a private netrc file for RPC `session-info` and `/transmission/web/` | Authentication succeeds, RPC reports peer port 6882, and the page identifies Transmission | Passed in exact run `20260826t051321z-3895-9520` |
| Torrent download lifecycle and storage | Create a trackerless local torrent, add it paused, verify existing local data, read its RPC state, then remove it | Name, 50-byte verified size, zero bytes left, and `/srv/storage/download` all match | Passed in the exact appliance through authenticated Transmission RPC |
| Samba root account and public storage | Put, get, compare, and delete a fixture through `//127.0.0.1/storage` using the firstboot password | The returned bytes match the source | Passed in the exact appliance |
| Automatic ClamAV scan, quarantine path, log, and completion mail | Inspect live RPC configuration and service state | Completion hook is enabled at `/usr/local/bin/clamav-scan`; ClamAV and Postfix are active; storage paths and permissions exist | Passed configuration and supervision checks; freshclam downloaded current databases and the database watcher started clamd |
| Control panel and supervised services | Read the HTTP control panel and check Transmission, Apache, Samba, ClamAV, and Postfix | Page identifies TurnKey Torrent Server and services are active | Passed in the exact appliance |
| Debian update path | Refresh metadata, inspect `apt-cache policy`, and run a simulated only-upgrade install | A signed Trixie candidate is eligible and no package or data changes occur | Passed; APT authenticated current metadata and selected `4.1.0~beta2+dfsg-3+deb13u1` |
| Inherited Core and Fileserver behavior | Cite the accepted Core 19 and Fileserver baseline | Unchanged ISO, SSH, Webmin, WebDAV CGI, and common Fileserver behavior remains covered | Reused by contract; this probe covers Torrentserver customizations |

## Acceptance command and status

```bash
TKLDEV_CONTAINER=tkldev19-wave2 \
TKL_HARNESS_STATE_DIR=/home/agent/.local/state/turnkey-v19-harness-wave2 \
TKL_HARNESS_LOCK_FILE=/home/agent/.local/state/turnkey-v19-harness-wave2/build.lock \
TKL_HARNESS_LOCK_TIMEOUT=3600 \
TKL_HARNESS_DOCKER_LIMIT_BYTES=42949672960 \
/sandboxed-git/turnkey/tools/test-v19-appliance torrentserver \
--source /home/agent/.local/worktrees/turnkey-apps/torrentserver/wish-torrentserver-v19-trixie
```

Exact run `20260826t051321z-3895-9520` passed from source commit
`b8a18fe3aab39e6871d304f440bf1b2b27b9939a`, with source archive SHA-256
`939f0c8255c7a5d4198d7034d68937972cb81c7ab3fa3c1e8b61960c666beb60`.
Build, configured-root import, boot health, focused runtime checks, cleanup, and
source identity all passed. The result records Debian Trixie
`transmission-daemon 4.1.0~beta2+dfsg-3+deb13u1`, authenticated APT metadata,
the same eligible candidate, and a successful non-mutating upgrade simulation.

## Deferred issues

- **MEDIUM:** The focused probe validates the configured ClamAV completion
hook and supervised daemon but does not exercise a malware signature. The
local torrent lifecycle remains independent of signature freshness.
- **ENVIRONMENT:** Docker rejected the configured-root overlay mount with
`EINVAL`, so the harness used its recorded `root.patched` fallback. Docker
acceptance does not exercise installer, kernel, or hardware behavior.

Product fix loops used: 2 of 3. The fixes start ClamAV after its initial
signature download and explicitly supervise freshclam. Shared lock waits,
environment boot timing, and acceptance assertion corrections did not consume
product loops.
72 changes: 0 additions & 72 deletions overlay/etc/init.d/rtorrent

This file was deleted.

17 changes: 0 additions & 17 deletions overlay/etc/nginx/sites-available/transmission

This file was deleted.

11 changes: 11 additions & 0 deletions overlay/etc/systemd/system/clamav-daemon.path
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=Start ClamAV daemon after the initial signature download
Documentation=man:systemd.path(5)
Before=clamav-freshclam.service

[Path]
PathChanged=/var/lib/clamav
Unit=clamav-daemon.service

[Install]
WantedBy=multi-user.target
93 changes: 52 additions & 41 deletions overlay/usr/lib/inithooks/bin/torrentserver.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,70 @@
#!/usr/bin/python3
# Copyright (c) 2010 Alon Swartz <alon@turnkeylinux.org> - all rights reserved
# Updated 2016 by Anton Pyrogovskyi <anton@turnkeylinux.org>
"""Configure admin password for Transmission
"""Configure the Transmission administrator password."""

Options:
-p --pass= if not provided, will ask interactively
"""

import sys
import getopt
import subprocess
from time import sleep
import argparse
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile

from libinithooks.dialog_wrapper import Dialog

def fatal(s):
print("Error:", s, file=sys.stderr)
sys.exit(1)

def usage(s=None):
if s:
print("Error:", s, file=sys.stderr)
print("Syntax: %s [options]" % sys.argv[0], file=sys.stderr)
print(__doc__, file=sys.stderr)
sys.exit(1)

def main():
try:
opts, args = getopt.gnu_getopt(sys.argv[1:], "hp:", ['help', 'pass='])
except getopt.GetoptError as e:
usage(e)
parser = argparse.ArgumentParser(description=__doc__)
source = parser.add_mutually_exclusive_group()
source.add_argument('-p', '--pass', dest='password', help=argparse.SUPPRESS)
source.add_argument('--pass-stdin', action='store_true',
help='read the password from standard input')
args = parser.parse_args()

password = ""
for opt, val in opts:
if opt in ('-h', '--help'):
usage()
elif opt in ('-p', '--pass'):
password = val

if not password:
if args.pass_stdin:
password = sys.stdin.read()
elif args.password is not None:
password = args.password
else:
d = Dialog('TurnKey Linux - First boot configuration')
password = d.get_password(
"Torrent Server Password",
"Enter new admin password for Transmission.")

subprocess.run(['service', 'transmission-daemon', 'stop'])
with open('/etc/transmission-daemon/settings.json', 'r') as fob:
settings = json.load(fob)
settings['rpc-username'] = 'admin'
settings['rpc-password'] = password
with open('/etc/transmission-daemon/settings.json', 'w') as fob:
json.dump(settings, fob)
subprocess.run(['service', 'transmission-daemon', 'start'])
if not password:
parser.error('password must not be empty')

settings_path = Path('/etc/transmission-daemon/settings.json')
subprocess.run(['service', 'transmission-daemon', 'stop'], check=True)
try:
with settings_path.open(encoding='utf-8') as settings_file:
settings = json.load(settings_file)
settings['rpc-authentication-required'] = True
settings['rpc-username'] = 'admin'
settings['rpc-password'] = password

original = settings_path.stat()
descriptor, temporary_name = tempfile.mkstemp(
dir=settings_path.parent,
prefix='.settings.json.',
)
try:
os.fchmod(descriptor, original.st_mode & 0o777)
os.fchown(descriptor, original.st_uid, original.st_gid)
with os.fdopen(descriptor, 'w', encoding='utf-8') as settings_file:
json.dump(settings, settings_file, indent=4, sort_keys=True)
settings_file.write('\n')
settings_file.flush()
os.fsync(settings_file.fileno())
os.replace(temporary_name, settings_path)
except BaseException:
try:
os.unlink(temporary_name)
except FileNotFoundError:
pass
raise
finally:
subprocess.run(['service', 'transmission-daemon', 'start'], check=True)

if __name__ == "__main__":
main()

Loading