Skip to content
Draft
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
22 changes: 22 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
**/node_modules/
**/build/
**/.svelte-kit/
**/.vite-cache/
frontend/build/
backend/data/
data/
app/binaries/
app/icons/
app/gen/
docker/config/
docker/storage/
docker/certs/
*.log
*.stackdump
*.bun-build
**/*.lish
**/*.lishnet
**/settings.json
**/peer-id.json
**/datastore/
.git/
18 changes: 13 additions & 5 deletions backend/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ export interface SettingsData {
};
}

function storagePath(envName: string, defaultRelative: string, fallback: string): string {
const explicit = process.env[envName];
if (explicit) return explicit;
const root = process.env['LIBERSHARE_STORAGE_ROOT'];
if (!root) return fallback;
return `${root.replace(/[\\/]+$/, '')}/${defaultRelative}/`;
}

const DEFAULT_SETTINGS: SettingsData = {
language: '',
ui: {
Expand Down Expand Up @@ -135,11 +143,11 @@ const DEFAULT_SETTINGS: SettingsData = {
volume: 50,
},
storage: {
downloadPath: '~/LiberShare/finished/',
tempPath: '~/LiberShare/temp/',
lishPath: '~/LiberShare/lish/',
lishnetPath: '~/LiberShare/lishnet/',
backupPath: '~/LiberShare/backup/',
downloadPath: storagePath('LIBERSHARE_DOWNLOAD_PATH', 'finished', '~/LiberShare/finished/'),
tempPath: storagePath('LIBERSHARE_TEMP_PATH', 'temp', '~/LiberShare/temp/'),
lishPath: storagePath('LIBERSHARE_LISH_PATH', 'lish', '~/LiberShare/lish/'),
lishnetPath: storagePath('LIBERSHARE_LISHNET_PATH', 'lishnet', '~/LiberShare/lishnet/'),
backupPath: storagePath('LIBERSHARE_BACKUP_PATH', 'backup', '~/LiberShare/backup/'),
},
network: {
incomingPort: 9090,
Expand Down
47 changes: 47 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# syntax=docker/dockerfile:1

FROM oven/bun:1.3.13-debian AS build

WORKDIR /src

COPY backend/package.json backend/bun.lock ./backend/
COPY shared/package.json ./shared/

WORKDIR /src/backend
RUN bun install --frozen-lockfile

WORKDIR /src
COPY backend ./backend
COPY shared ./shared

ARG TARGETARCH
# Two-step build: the backend binary and the checksum worker must be separate
# artifacts. At runtime app.ts resolves the worker via
# pathToFileURL(dirname(execPath) + '/lish/checksum-worker.js') — a single
# `bun build --compile` with multiple entrypoints would not produce that sidecar
# file structure and LISH creation would hang trying to spawn the worker.
RUN set -eu; \
case "$TARGETARCH" in \
amd64) BUN_TARGET="bun-linux-x64" ;; \
arm64) BUN_TARGET="bun-linux-arm64" ;; \
*) echo "Unsupported Docker target architecture: $TARGETARCH" >&2; exit 1 ;; \
esac; \
cd backend; \
bun build --compile --target "$BUN_TARGET" ./src/app.ts --outfile build/lish-backend; \
mkdir -p build/lish; \
bun build ./src/lish/checksum-worker.ts --target bun --outfile build/lish/checksum-worker.js

FROM debian:trixie-slim AS runtime

RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY --from=build /src/backend/build/lish-backend /app/lish-backend
COPY --from=build /src/backend/build/lish /app/lish
RUN chmod 0755 /app/lish-backend

EXPOSE 1158/tcp 9090/tcp
ENTRYPOINT ["/app/lish-backend"]
CMD ["--datadir", "/app/data", "--host", "0.0.0.0", "--port", "1158"]
166 changes: 166 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# LiberShare Docker

This compose setup runs LiberShare as two containers:

- `libershare-backend`: Bun-compiled backend, WebSocket API, libp2p node
- `libershare-frontend`: static Svelte frontend served by a small Bun HTTPS server

Run commands from this `docker/` directory.

## Defaults

- Compose project name: `libershare`
- Backend API/WebSocket: `${BACKEND_PORT:-1158}:${BACKEND_PORT:-1158}`
- libp2p TCP: `9091:9090`
- Frontend HTTPS: `6003:6003`
- Browser URL: `https://<docker-host>:6003/`
- Docker network: `libershare-net`, created automatically by compose

Default writable paths are local directories next to `docker-compose.yml`:

- `./config` -> `/app/config`
- `./storage` -> `/app/storage`
- `./certs` -> `/app/certs`

`./config` contains backend runtime state: `settings.json`, `libershare.db`,
`libershare.log`, and libp2p datastore. `./storage` is used by default for
finished downloads, temp files, LISH files, LISH network files, and backups.
`./certs` contains frontend TLS certificate files.

## Start

```sh
docker compose config
docker compose up -d --build
docker compose logs -f
```

## Storage

For a fresh config, backend storage settings default to:

- `/app/storage/finished/`
- `/app/storage/temp/`
- `/app/storage/lish/`
- `/app/storage/lishnet/`
- `/app/storage/backup/`

To put config and storage on specific host disks:

```sh
mkdir -p /mnt/ssd/libershare-config /mnt/big/libershare-storage
LIBERSHARE_CONFIG_SOURCE=/mnt/ssd/libershare-config \
LIBERSHARE_STORAGE_SOURCE=/mnt/big/libershare-storage \
docker compose up -d
```

To use Docker named volumes instead of local directories:

```sh
LIBERSHARE_CONFIG_SOURCE=my-libershare-config \
LIBERSHARE_STORAGE_SOURCE=my-libershare-storage \
docker compose up -d
```

When migrating an existing node, keep its old config/datastore/database mounted
as `/app/config`; otherwise the backend generates a new peer identity and starts
as a different node.

## Ports

Set `BACKEND_PORT` to run the backend API/WebSocket on a different port:

```sh
BACKEND_PORT=2158 docker compose up -d
```

The frontend never hardcodes the backend browser port. Browser WebSocket traffic
goes to same-origin `/ws`, and the frontend container proxies it to:

```sh
BACKEND_WS_URL=ws://backend:${BACKEND_PORT:-1158}
```

## TLS

The frontend serves HTTPS. On first start it generates a self-signed certificate
in `./certs` unless `TLS_CERT_FILE` and `TLS_KEY_FILE` point to existing files.

Default self-signed SAN:

```sh
DNS:localhost,IP:127.0.0.1
```

Set `TLS_CERT_SAN` before the first frontend start when the self-signed
certificate must be valid for a LAN IP or DNS name:

```sh
TLS_CERT_SAN=DNS:localhost,IP:127.0.0.1,IP:192.168.2.9 docker compose up -d
```

To use a real certificate for a hostname, mount a cert directory and point the
container paths at the cert/key:

```sh
TLS_CERT_SOURCE=/etc/libershare/certs \
TLS_CERT_FILE=/app/certs/fullchain.pem \
TLS_KEY_FILE=/app/certs/privkey.pem \
docker compose up -d
```

The browser hostname must match the certificate SAN, for example
`https://lish.example.net:6003/`. For Let's Encrypt live directories, mount or
copy real files; symlinks under `/etc/letsencrypt/live/...` also need their
`archive` target available inside the container.

## Logs

Backend application logs are written to:

```sh
./config/libershare.log
```

The app rotates `libershare.log` at 10 MB and keeps 3 rotated files.

Docker stdout/stderr logs are rotated by compose:

```sh
LOG_MAX_SIZE=10m
LOG_MAX_FILE=3
```

Backend memory tracing is disabled by default:

```sh
LIBERSHARE_MEMTRACE=0
```

Set `LIBERSHARE_MEMTRACE=1` only while collecting diagnostics. Memory trace
output is an application file, not a Docker log, so Docker log rotation does not
rotate `memory-trace.jsonl`.

## Hardening

Both services run with:

- `no-new-privileges`
- all Linux capabilities dropped
- read-only root filesystem
- writable state only through explicit mounts and `/tmp`

## Verification

Run project checks inside Docker/Bun instead of relying on host Node/Bun:

```sh
docker run --rm -v "$PWD/..:/src" -w /src/frontend oven/bun:1.3.13-debian \
sh -lc "bun install --frozen-lockfile && bun test tests/api-url.test.ts && bun --bun run check && bun --bun run build"
```

Build both images:

```sh
docker compose build backend frontend
```
2 changes: 2 additions & 0 deletions docker/certs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions docker/config/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
74 changes: 74 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: libershare

services:
backend:
build:
context: ..
dockerfile: docker/Dockerfile
image: docker-libershare-backend:local
container_name: libershare-backend
restart: unless-stopped
init: true
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
command: ["--datadir", "/app/config", "--host", "0.0.0.0", "--port", "${BACKEND_PORT:-1158}"]
environment:
LIBERSHARE_MEMTRACE: ${LIBERSHARE_MEMTRACE:-0}
LIBERSHARE_STORAGE_ROOT: /app/storage
logging:
driver: json-file
options:
max-size: ${LOG_MAX_SIZE:-10m}
max-file: ${LOG_MAX_FILE:-3}
ports:
- "${BACKEND_PORT:-1158}:${BACKEND_PORT:-1158}"
- "9091:9090"
volumes:
- ${LIBERSHARE_CONFIG_SOURCE:-./config}:/app/config
- ${LIBERSHARE_STORAGE_SOURCE:-./storage}:/app/storage
tmpfs:
- /tmp
networks:
- libershare-net

frontend:
build:
context: ..
dockerfile: docker/frontend.Dockerfile
image: docker-libershare-frontend:local
container_name: libershare-frontend
restart: unless-stopped
init: true
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
environment:
BACKEND_WS_URL: ws://backend:${BACKEND_PORT:-1158}
TLS_CERT_DIR: /app/certs
TLS_CERT_FILE: ${TLS_CERT_FILE:-/app/certs/pubkey.pem}
TLS_KEY_FILE: ${TLS_KEY_FILE:-/app/certs/privkey.pem}
TLS_CERT_SAN: ${TLS_CERT_SAN:-DNS:localhost,IP:127.0.0.1}
logging:
driver: json-file
options:
max-size: ${LOG_MAX_SIZE:-10m}
max-file: ${LOG_MAX_FILE:-3}
ports:
- "6003:6003"
depends_on:
- backend
volumes:
- ${TLS_CERT_SOURCE:-./certs}:/app/certs
tmpfs:
- /tmp
networks:
- libershare-net

networks:
libershare-net:
name: libershare-net
28 changes: 28 additions & 0 deletions docker/frontend-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/sh
set -eu

cert_dir="${TLS_CERT_DIR:-/app/certs}"
key_file="${TLS_KEY_FILE:-$cert_dir/privkey.pem}"
cert_file="${TLS_CERT_FILE:-$cert_dir/pubkey.pem}"
cert_days="${TLS_CERT_DAYS:-3650}"
cert_subject="${TLS_CERT_SUBJECT:-/CN=libershare.local}"
cert_san="${TLS_CERT_SAN:-DNS:localhost,IP:127.0.0.1}"

if [ ! -s "$key_file" ] || [ ! -s "$cert_file" ]; then
mkdir -p "$cert_dir"
openssl req \
-x509 \
-nodes \
-newkey rsa:2048 \
-sha256 \
-days "$cert_days" \
-keyout "$key_file" \
-out "$cert_file" \
-subj "$cert_subject" \
-addext "subjectAltName=$cert_san"
fi

export TLS_KEY_FILE="$key_file"
export TLS_CERT_FILE="$cert_file"

exec bun frontend-server.ts
Loading