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
23 changes: 23 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.git
.github
.pytest_cache
.venv
venv
__pycache__
*.py[cod]
*.log
.env
.env.*
!.env.example
htmlcov
coverage.xml
*.zip
*.tar.gz
tests
scripts
README.md
DEVELOPMENT.md
SECURITY.md
data
secrets
.webui-auth
35 changes: 30 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
# Environment Variables Configuration Example
# Copy this file to .env and adjust the values as needed
# No .env file is required for the default deployment.

# Required Settings (Defaults shown)
PORT=8000 # Port where Gunicorn will run and be exposed
STORAGE_PATH=$HOME/.LNReader # Local path to store LNReader data
# Published image. The default Compose file uses the upstream published image.
LNREADER_IMAGE=ghcr.io/lnreader/remote-service:latest

# Host settings.
HOST_PORT=8000
STORAGE_PATH=./data

# Optional Linux ownership overrides. Leave blank on most systems.
# When blank, the container uses a safe non-root default and will reuse a
# non-root owner already present on the mounted storage directory when possible.
PUID=
PGID=

# Runtime tuning.
WORKERS=2
THREADS=2
LOG_LEVEL=info
FIX_PERMISSIONS=true
MAX_UPLOAD_SIZE=20g

# Web UI.
WEB_UI_SLUG=lnr-vault-7f3c9
WEB_UI_USERNAME=admin

# Leave blank to generate and persist a random password automatically.
WEB_UI_PASSWORD=

# Optional explicit public URL shown by the status page.
# Example: https://lnreader.example.com
PUBLIC_URL=
134 changes: 134 additions & 0 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: Test and publish container

on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
workflow_dispatch:
schedule:
- cron: '17 5 * * 1'

permissions:
contents: read

concurrency:
group: container-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Python ${{ matrix.python-version }} tests
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
python-version: ['3.10.11', '3.11.9', '3.12.10', '3.13.15', '3.14.7']
steps:
- uses: actions/checkout@v7.0.1
- uses: actions/setup-python@v7.0.0
with:
python-version: ${{ matrix.python-version }}
check-latest: false
cache: pip
cache-dependency-path: requirements-test.txt
- name: Install test dependencies
run: python -m pip install --requirement requirements-test.txt
- name: Verbose API/static validation
env:
RUN_DOCKER_TESTS: '0'
RUN_WEBUI_TESTS: '0'
run: ./scripts/test-verbose.sh

webui:
name: PHP / htpasswd security tests
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7.0.1
- name: Install PHP and htpasswd tools
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends php-cli apache2-utils
- name: PHP, bcrypt, and automatic credential validation
run: ./scripts/webui-test.sh

container-smoke:
name: Full container / Nginx / PHP smoke test
needs: [test, webui]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7.0.1
- name: Validate Compose
run: docker compose -f docker-compose.yml config
- name: Build test image verbosely
run: docker build --progress=plain --tag lnreader-remote-service:test .
- name: Run complete container smoke test
run: ./scripts/container-smoke-test.sh lnreader-remote-service:test

build:
name: Publish amd64 + arm64 image
needs: container-smoke
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v7.0.1
- name: Set up QEMU
uses: docker/setup-qemu-action@v4.2.0
with:
image: tonistiigi/binfmt:qemu-v10.2.3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4.2.0
- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: meta
uses: docker/metadata-action@v6.2.0
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=sha,prefix=sha-
- name: Build and publish multi-arch image
uses: docker/build-push-action@v7.1.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ github.ref_name }}
VCS_REF=${{ github.sha }}
BUILD_DATE=${{ github.event.repository.updated_at }}
SOURCE_URL=${{ github.server_url }}/${{ github.repository }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: mode=max
sbom: true
- name: Prepare deployment bundle
if: github.event_name != 'pull_request'
shell: bash
run: |
set -euo pipefail
mkdir -p dist
cp docker-compose.yml .env.example README.md SECURITY.md LICENSE dist/
tar -C dist -czf lnreader-remote-service-deploy.tar.gz .
- name: Upload deployment bundle
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v7.0.1
with:
name: lnreader-remote-service-deploy
path: lnreader-remote-service-deploy.tar.gz
archive: false
if-no-files-found: error
retention-days: 30
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,12 @@ temp.py

# Pyannotate generated stubs
type_info.json

# LNReader container runtime
data/
.webui-auth/
test-results/
*.htpasswd

# Local Python virtual environments
.venv*/
121 changes: 121 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Development

This document covers development of LNReader Remote Service with emphasis on the self-hosted server and container path.

# Requirements

Python 3.10 or newer is required for the server test suite.

Docker Engine or Docker Desktop with Docker Compose is required for full container validation.

PHP CLI and apache2-utils are optional for local Web UI validation because the container smoke test also validates the runtime PHP and bcrypt configuration.

# Python Test Environment

Create an isolated environment and install the pinned test dependencies.

```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements-test.txt
```

Run the Python tests.

```bash
python -m pytest -vv -ra
```

Run static repository validation.

```bash
python scripts/validate.py
```

Run the live HTTP integration test.

```bash
python scripts/live-http-test.py
```

# Web UI Tests

Run the Web UI tests with:

```bash
./scripts/webui-test.sh
```

Run the automatic credential lifecycle tests with:

```bash
./scripts/webui-auth-test.sh
```

# Complete Validation

Run all locally available checks with:

```bash
./scripts/test-verbose.sh
```

When Docker is available this also builds the image and runs the complete container smoke test.

# Container Development

Build a local image with:

```bash
docker build --progress=plain -t lnreader-remote-service:local .
```

Run the container smoke test with:

```bash
./scripts/container-smoke-test.sh lnreader-remote-service:local
```

The smoke test verifies container health, non-root PID 1, Nginx configuration, API compatibility, dashboard authentication, active upload visibility, generated credential persistence, and explicit credential overrides.

# Docker Compose

Validate the Compose file with:

```bash
docker compose config
```

Start the normal service with:

```bash
docker compose up -d
```

Stop it with:

```bash
docker compose down
```

# Continuous Integration

The container workflow runs Python validation across the configured Python matrix, PHP and bcrypt tests, a full Docker smoke test, and a multi-architecture Buildx build.

Pull requests build the container without publishing it.

Pushes to the default branch and version tags can publish images to the repository GitHub Container Registry namespace when package write permission is available.

# Compatibility

Changes to the backup API should preserve the existing LNReader routes and response behavior unless a coordinated client change is planned.

The desktop GUI imports the server WSGI application and should remain usable after server changes.

The command-line entry point should continue to expose src.server.server:main.

# Security Review

Server changes should continue to reject path traversal, avoid buffering complete uploads in memory, clean incomplete temporary uploads, and avoid running container services as root.

The dashboard authentication boundary is separate from the LNReader backup API. Changes that add authentication to the backup API require explicit client compatibility review.
Loading