Compare commits
1 Commits
renovate/h
...
Stats
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d50dcf5b4 |
@@ -1,68 +0,0 @@
|
|||||||
name: Create Release
|
|
||||||
|
|
||||||
# This workflow creates releases:
|
|
||||||
# 1. Build packages
|
|
||||||
# 2. Create Gitea release with all artifacts atomically
|
|
||||||
# This ensures releases cannot be modified once published.
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: 'Version to release (e.g., 0.6.8)'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build distribution 📦
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: https://git.quad4.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: Set up Python
|
|
||||||
uses: https://git.quad4.io/actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v5
|
|
||||||
with:
|
|
||||||
python-version: "3.13"
|
|
||||||
- name: Install pypa/build
|
|
||||||
run: python3 -m pip install build --user
|
|
||||||
- name: Build a binary wheel and a source tarball
|
|
||||||
run: python3 -m build
|
|
||||||
- name: Store the distribution packages
|
|
||||||
uses: https://git.quad4.io/actions/upload-artifact@8689daa8608e46baf41e4786cb83fbc0dea972cd # v4
|
|
||||||
with:
|
|
||||||
name: python-package-distributions
|
|
||||||
path: dist/
|
|
||||||
|
|
||||||
gitea-release:
|
|
||||||
name: Create Gitea Release
|
|
||||||
needs:
|
|
||||||
- build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download all the dists
|
|
||||||
uses: https://git.quad4.io/actions/download-artifact@10979da4ee3096dd7ca8d9a35c72871335fee704 # v5
|
|
||||||
with:
|
|
||||||
name: python-package-distributions
|
|
||||||
path: dist/
|
|
||||||
- name: Create Gitea Release with artifacts
|
|
||||||
uses: https://git.quad4.io/actions/gitea-release-action@4875285c0950474efb7ca2df55233c51333eeb74
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref_name }}
|
|
||||||
name: Release ${{ github.ref_name }}
|
|
||||||
files: |
|
|
||||||
dist/*.tar.gz
|
|
||||||
dist/*.whl
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
name: Safety
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * 0' # weekly
|
|
||||||
jobs:
|
|
||||||
security:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: https://git.quad4.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
||||||
- name: Set up Python
|
|
||||||
uses: https://git.quad4.io/actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v5
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install .
|
|
||||||
- name: Run pip-audit
|
|
||||||
uses: https://git.quad4.io/actions/gh-action-pip-audit@66a6ee35b1b25f89c6bdc9f7c11284f08061823a # v1.1.0
|
|
||||||
27
.github/workflows/docker-test.yml
vendored
Normal file
27
.github/workflows/docker-test.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Docker Build Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Build Docker Image
|
||||||
|
run: docker build . --file Dockerfile --build-arg PYTHON_VERSION=${{ matrix.python-version }} --tag lxmfy-test:${{ matrix.python-version }}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
name: Build and Publish Docker Image
|
name: Build and Publish Docker Image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
|
branches: [ main ]
|
||||||
tags: [ 'v*' ]
|
tags: [ 'v*' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main, master ]
|
branches: [ main ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: git.quad4.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: RNS-Things/rns-page-node
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -17,32 +17,29 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
outputs:
|
|
||||||
image_digest: ${{ steps.build.outputs.digest }}
|
|
||||||
image_tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: https://git.quad4.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: https://git.quad4.io/actions/setup-qemu-action@3a1695b1353f9f8868722ffaafc1f164ef35fa5e # v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
platforms: amd64,arm64
|
platforms: amd64,arm64
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: https://git.quad4.io/actions/setup-buildx-action@7fbd262f0ca05b45700d8eaaf71f40837d036cc7 # v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to the Container registry
|
- name: Log in to the Container registry
|
||||||
uses: https://git.quad4.io/actions/login-action@bb91d7e20cfedb030a44164cb558bba899e1010a # v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: https://git.quad4.io/actions/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
tags: |
|
tags: |
|
||||||
@@ -54,23 +51,19 @@ jobs:
|
|||||||
type=sha,format=short
|
type=sha,format=short
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
id: build
|
uses: docker/build-push-action@v5
|
||||||
uses: https://git.quad4.io/actions/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./docker/Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
build-args: |
|
cache-from: type=gha
|
||||||
BUILD_DATE=${{ github.event.head_commit.timestamp }}
|
cache-to: type=gha,mode=max
|
||||||
VCS_REF=${{ github.sha }}
|
|
||||||
VERSION=${{ steps.meta.outputs.version }}
|
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker (rootless)
|
- name: Extract metadata (tags, labels) for Docker (rootless)
|
||||||
id: meta_rootless
|
id: meta_rootless
|
||||||
uses: https://git.quad4.io/actions/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-rootless
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-rootless
|
||||||
tags: |
|
tags: |
|
||||||
@@ -81,15 +74,13 @@ jobs:
|
|||||||
type=sha,format=short,suffix=-rootless
|
type=sha,format=short,suffix=-rootless
|
||||||
|
|
||||||
- name: Build and push rootless Docker image
|
- name: Build and push rootless Docker image
|
||||||
uses: https://git.quad4.io/actions/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./docker/Dockerfile.rootless
|
file: ./Dockerfile.rootless
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
tags: ${{ steps.meta_rootless.outputs.tags }}
|
||||||
labels: ${{ steps.meta_rootless.outputs.labels }}
|
labels: ${{ steps.meta_rootless.outputs.labels }}
|
||||||
build-args: |
|
cache-from: type=gha
|
||||||
BUILD_DATE=${{ github.event.head_commit.timestamp }}
|
cache-to: type=gha,mode=max
|
||||||
VCS_REF=${{ github.sha }}
|
|
||||||
VERSION=${{ steps.meta_rootless.outputs.version }}
|
|
||||||
100
.github/workflows/publish.yml
vendored
Normal file
100
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
name: Publish Python 🐍 distribution 📦 to PyPI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Version to release (e.g., 0.6.8)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build distribution 📦
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4.2.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5.3.0
|
||||||
|
with:
|
||||||
|
python-version: "3.13"
|
||||||
|
- name: Install pypa/build
|
||||||
|
run: python3 -m pip install build --user
|
||||||
|
- name: Build a binary wheel and a source tarball
|
||||||
|
run: python3 -m build
|
||||||
|
- name: Store the distribution packages
|
||||||
|
uses: actions/upload-artifact@v4.5.0
|
||||||
|
with:
|
||||||
|
name: python-package-distributions
|
||||||
|
path: dist/
|
||||||
|
|
||||||
|
publish-to-pypi:
|
||||||
|
name: Publish Python 🐍 distribution 📦 to PyPI
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment:
|
||||||
|
name: pypi
|
||||||
|
url: https://pypi.org/p/rns-page-node
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download all the dists
|
||||||
|
uses: actions/download-artifact@v4.1.8
|
||||||
|
with:
|
||||||
|
name: python-package-distributions
|
||||||
|
path: dist/
|
||||||
|
- name: Publish distribution 📦 to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@v1.12.3
|
||||||
|
|
||||||
|
github-release:
|
||||||
|
name: Sign the Python 🐍 distribution 📦 and create GitHub Release
|
||||||
|
needs:
|
||||||
|
- publish-to-pypi
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download all the dists
|
||||||
|
uses: actions/download-artifact@v4.1.8
|
||||||
|
with:
|
||||||
|
name: python-package-distributions
|
||||||
|
path: dist/
|
||||||
|
- name: Sign the dists with Sigstore
|
||||||
|
uses: sigstore/gh-action-sigstore-python@v3.0.0
|
||||||
|
with:
|
||||||
|
inputs: >-
|
||||||
|
./dist/*.tar.gz
|
||||||
|
./dist/*.whl
|
||||||
|
- name: Create GitHub Release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
run: >-
|
||||||
|
gh release create
|
||||||
|
"$GITHUB_REF_NAME"
|
||||||
|
--repo "$GITHUB_REPOSITORY"
|
||||||
|
--notes ""
|
||||||
|
- name: Upload artifact signatures to GitHub Release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
run: >-
|
||||||
|
gh release upload
|
||||||
|
"$GITHUB_REF_NAME" dist/**
|
||||||
|
--repo "$GITHUB_REPOSITORY"
|
||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -3,12 +3,3 @@ node-config/
|
|||||||
files/
|
files/
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
dist/
|
|
||||||
*.egg-info/
|
|
||||||
.ruff_cache/
|
|
||||||
.venv/
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
ARG PYTHON_VERSION=3.13
|
||||||
|
FROM python:${PYTHON_VERSION}-alpine
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/Sudo-Ivan/rns-page-node"
|
||||||
|
LABEL org.opencontainers.image.description="A simple way to serve pages and files over the Reticulum network."
|
||||||
|
LABEL org.opencontainers.image.licenses="GPL-3.0"
|
||||||
|
LABEL org.opencontainers.image.authors="Sudo-Ivan"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk add --no-cache gcc python3-dev musl-dev linux-headers
|
||||||
|
|
||||||
|
RUN pip install poetry
|
||||||
|
ENV POETRY_VIRTUALENVS_IN_PROJECT=true
|
||||||
|
|
||||||
|
COPY pyproject.toml poetry.lock* ./
|
||||||
|
COPY README.md ./
|
||||||
|
COPY rns_page_node ./rns_page_node
|
||||||
|
|
||||||
|
RUN poetry install --no-interaction --no-ansi
|
||||||
|
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
ENTRYPOINT ["poetry", "run", "rns-page-node"]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.14-alpine AS builder
|
FROM python:3.13-alpine AS builder
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
RUN apk add --no-cache build-base libffi-dev cargo pkgconfig gcc python3-dev musl-dev linux-headers
|
RUN apk add --no-cache build-base libffi-dev cargo pkgconfig gcc python3-dev musl-dev linux-headers
|
||||||
28
Dockerfile.rootless
Normal file
28
Dockerfile.rootless
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
ARG PYTHON_VERSION=3.13
|
||||||
|
FROM python:${PYTHON_VERSION}-alpine
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/Sudo-Ivan/rns-page-node"
|
||||||
|
LABEL org.opencontainers.image.description="A simple way to serve pages and files over the Reticulum network."
|
||||||
|
LABEL org.opencontainers.image.licenses="GPL-3.0"
|
||||||
|
LABEL org.opencontainers.image.authors="Sudo-Ivan"
|
||||||
|
|
||||||
|
RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk add --no-cache gcc python3-dev musl-dev linux-headers
|
||||||
|
|
||||||
|
RUN pip install poetry
|
||||||
|
ENV POETRY_VIRTUALENVS_IN_PROJECT=true
|
||||||
|
|
||||||
|
COPY pyproject.toml poetry.lock* ./
|
||||||
|
COPY README.md ./
|
||||||
|
COPY rns_page_node ./rns_page_node
|
||||||
|
|
||||||
|
RUN poetry install --no-interaction --no-ansi
|
||||||
|
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
USER app
|
||||||
|
|
||||||
|
ENTRYPOINT ["poetry", "run", "rns-page-node"]
|
||||||
31
Makefile
31
Makefile
@@ -1,31 +1,20 @@
|
|||||||
# Makefile for rns-page-node
|
# Makefile for rns-page-node
|
||||||
|
|
||||||
# Extract version from pyproject.toml
|
|
||||||
VERSION := $(shell grep "^version =" pyproject.toml | cut -d '"' -f 2)
|
|
||||||
VCS_REF := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
||||||
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
|
|
||||||
# Detect if docker buildx is available
|
# Detect if docker buildx is available
|
||||||
DOCKER_BUILD := $(shell docker buildx version >/dev/null 2>&1 && echo "docker buildx build" || echo "docker build")
|
DOCKER_BUILD := $(shell docker buildx version >/dev/null 2>&1 && echo "docker buildx build" || echo "docker build")
|
||||||
DOCKER_BUILD_LOAD := $(shell docker buildx version >/dev/null 2>&1 && echo "docker buildx build --load" || echo "docker build")
|
|
||||||
|
|
||||||
# Build arguments for Docker
|
|
||||||
DOCKER_BUILD_ARGS := --build-arg VERSION=$(VERSION) \
|
|
||||||
--build-arg VCS_REF=$(VCS_REF) \
|
|
||||||
--build-arg BUILD_DATE=$(BUILD_DATE)
|
|
||||||
|
|
||||||
.PHONY: all build sdist wheel clean install lint format docker-wheels docker-build docker-run docker-build-rootless docker-run-rootless help test docker-test
|
.PHONY: all build sdist wheel clean install lint format docker-wheels docker-build docker-run docker-build-rootless docker-run-rootless help test docker-test
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
build: clean
|
build: clean
|
||||||
python3 -m build
|
python3 setup.py sdist bdist_wheel
|
||||||
|
|
||||||
sdist:
|
sdist:
|
||||||
python3 -m build --sdist
|
python3 setup.py sdist
|
||||||
|
|
||||||
wheel:
|
wheel:
|
||||||
python3 -m build --wheel
|
python3 setup.py bdist_wheel
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf build dist *.egg-info
|
rm -rf build dist *.egg-info
|
||||||
@@ -40,20 +29,20 @@ format:
|
|||||||
ruff check --fix .
|
ruff check --fix .
|
||||||
|
|
||||||
docker-wheels:
|
docker-wheels:
|
||||||
$(DOCKER_BUILD) --target builder -f docker/Dockerfile.build -t rns-page-node-builder .
|
$(DOCKER_BUILD) --target builder -f Dockerfile.build -t rns-page-node-builder .
|
||||||
docker create --name builder-container rns-page-node-builder true
|
docker create --name builder-container rns-page-node-builder true
|
||||||
docker cp builder-container:/src/dist ./dist
|
docker cp builder-container:/src/dist ./dist
|
||||||
docker rm builder-container
|
docker rm builder-container
|
||||||
|
|
||||||
docker-build:
|
docker-build:
|
||||||
$(DOCKER_BUILD_LOAD) $(DOCKER_BUILD_ARGS) $(BUILD_ARGS) -f docker/Dockerfile -t git.quad4.io/rns-things/rns-page-node:latest -t git.quad4.io/rns-things/rns-page-node:$(VERSION) .
|
$(DOCKER_BUILD) $(BUILD_ARGS) -f Dockerfile -t rns-page-node:latest .
|
||||||
|
|
||||||
docker-run:
|
docker-run:
|
||||||
docker run --rm -it \
|
docker run --rm -it \
|
||||||
-v ./pages:/app/pages \
|
-v ./pages:/app/pages \
|
||||||
-v ./files:/app/files \
|
-v ./files:/app/files \
|
||||||
-v ./node-config:/app/node-config \
|
-v ./node-config:/app/node-config \
|
||||||
git.quad4.io/rns-things/rns-page-node:latest \
|
rns-page-node:latest \
|
||||||
--node-name "Page Node" \
|
--node-name "Page Node" \
|
||||||
--pages-dir /app/pages \
|
--pages-dir /app/pages \
|
||||||
--files-dir /app/files \
|
--files-dir /app/files \
|
||||||
@@ -61,14 +50,14 @@ docker-run:
|
|||||||
--announce-interval 360
|
--announce-interval 360
|
||||||
|
|
||||||
docker-build-rootless:
|
docker-build-rootless:
|
||||||
$(DOCKER_BUILD_LOAD) $(DOCKER_BUILD_ARGS) $(BUILD_ARGS) -f docker/Dockerfile.rootless -t git.quad4.io/rns-things/rns-page-node:latest-rootless -t git.quad4.io/rns-things/rns-page-node:$(VERSION)-rootless .
|
$(DOCKER_BUILD) $(BUILD_ARGS) -f Dockerfile.rootless -t rns-page-node-rootless:latest .
|
||||||
|
|
||||||
docker-run-rootless:
|
docker-run-rootless:
|
||||||
docker run --rm -it \
|
docker run --rm -it \
|
||||||
-v ./pages:/app/pages \
|
-v ./pages:/app/pages \
|
||||||
-v ./files:/app/files \
|
-v ./files:/app/files \
|
||||||
-v ./node-config:/app/node-config \
|
-v ./node-config:/app/node-config \
|
||||||
git.quad4.io/rns-things/rns-page-node:latest-rootless \
|
rns-page-node-rootless:latest \
|
||||||
--node-name "Page Node" \
|
--node-name "Page Node" \
|
||||||
--pages-dir /app/pages \
|
--pages-dir /app/pages \
|
||||||
--files-dir /app/files \
|
--files-dir /app/files \
|
||||||
@@ -79,7 +68,7 @@ test:
|
|||||||
bash tests/run_tests.sh
|
bash tests/run_tests.sh
|
||||||
|
|
||||||
docker-test:
|
docker-test:
|
||||||
$(DOCKER_BUILD_LOAD) -f docker/Dockerfile.tests -t rns-page-node-tests .
|
$(DOCKER_BUILD) -f tests/Dockerfile.tests -t rns-page-node-tests .
|
||||||
docker run --rm rns-page-node-tests
|
docker run --rm rns-page-node-tests
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@@ -93,7 +82,7 @@ help:
|
|||||||
@echo " lint - run ruff linter"
|
@echo " lint - run ruff linter"
|
||||||
@echo " format - run ruff --fix"
|
@echo " format - run ruff --fix"
|
||||||
@echo " docker-wheels - build Python wheels in Docker"
|
@echo " docker-wheels - build Python wheels in Docker"
|
||||||
@echo " docker-build - build runtime Docker image (version: $(VERSION))"
|
@echo " docker-build - build runtime Docker image"
|
||||||
@echo " docker-run - run runtime Docker image"
|
@echo " docker-run - run runtime Docker image"
|
||||||
@echo " docker-build-rootless - build rootless runtime Docker image"
|
@echo " docker-build-rootless - build rootless runtime Docker image"
|
||||||
@echo " docker-run-rootless - run rootless runtime Docker image"
|
@echo " docker-run-rootless - run rootless runtime Docker image"
|
||||||
|
|||||||
131
README.md
131
README.md
@@ -1,84 +1,27 @@
|
|||||||
# RNS Page Node
|
# RNS Page Node
|
||||||
|
|
||||||
[Русская](README.ru.md)
|
|
||||||
|
|
||||||
A simple way to serve pages and files over the [Reticulum network](https://reticulum.network/). Drop-in replacement for [NomadNet](https://github.com/markqvist/NomadNet) nodes that primarily serve pages and files.
|
A simple way to serve pages and files over the [Reticulum network](https://reticulum.network/). Drop-in replacement for [NomadNet](https://github.com/markqvist/NomadNet) nodes that primarily serve pages and files.
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Serves pages and files over RNS
|
|
||||||
- Dynamic page support with environment variables
|
|
||||||
- Form data and request parameter parsing
|
|
||||||
|
|
||||||
## To Do
|
|
||||||
|
|
||||||
- [ ] Move to single small and rootless docker image
|
|
||||||
- [ ] Codebase cleanup
|
|
||||||
- [ ] Update PyPI publishing workflow
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Pip
|
|
||||||
# May require --break-system-packages
|
|
||||||
|
|
||||||
pip install rns-page-node
|
pip install rns-page-node
|
||||||
|
|
||||||
# Pipx
|
|
||||||
|
|
||||||
pipx install rns-page-node
|
|
||||||
|
|
||||||
# uv
|
|
||||||
|
|
||||||
uv venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
uv pip install rns-page-node
|
|
||||||
|
|
||||||
# Pipx via Git
|
|
||||||
|
|
||||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# will use current directory for pages and files
|
|
||||||
rns-page-node
|
rns-page-node
|
||||||
```
|
```
|
||||||
|
|
||||||
or with command-line options:
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rns-page-node --node-name "Page Node" --pages-dir ./pages --files-dir ./files --identity-dir ./node-config --announce-interval 360
|
rns-page-node --node-name "Page Node" --pages-dir ./pages --files-dir ./files --identity-dir ./node-config --announce-interval 360
|
||||||
```
|
```
|
||||||
|
|
||||||
or with a config file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rns-page-node /path/to/config.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration File
|
|
||||||
|
|
||||||
You can use a configuration file to persist settings. See `config.example` for an example.
|
|
||||||
|
|
||||||
Config file format is simple `key=value` pairs:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Comment lines start with #
|
|
||||||
node-name=My Page Node
|
|
||||||
pages-dir=./pages
|
|
||||||
files-dir=./files
|
|
||||||
identity-dir=./node-config
|
|
||||||
announce-interval=360
|
|
||||||
```
|
|
||||||
|
|
||||||
Priority order: Command-line arguments > Config file > Defaults
|
|
||||||
|
|
||||||
### Docker/Podman
|
### Docker/Podman
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./config:/root/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
docker run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./config:/app/config ghcr.io/sudo-ivan/rns-page-node:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker/Podman Rootless
|
### Docker/Podman Rootless
|
||||||
@@ -86,7 +29,7 @@ docker run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config
|
|||||||
```bash
|
```bash
|
||||||
mkdir -p ./pages ./files ./node-config ./config
|
mkdir -p ./pages ./files ./node-config ./config
|
||||||
chown -R 1000:1000 ./pages ./files ./node-config ./config
|
chown -R 1000:1000 ./pages ./files ./node-config ./config
|
||||||
podman run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./config:/app/config git.quad4.io/rns-things/rns-page-node:latest-rootless
|
podman run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./config:/app/config ghcr.io/sudo-ivan/rns-page-node:latest-rootless
|
||||||
```
|
```
|
||||||
|
|
||||||
Mounting volumes are optional, you can also copy pages and files to the container `podman cp` or `docker cp`.
|
Mounting volumes are optional, you can also copy pages and files to the container `podman cp` or `docker cp`.
|
||||||
@@ -111,30 +54,60 @@ make docker-wheels
|
|||||||
|
|
||||||
## Pages
|
## Pages
|
||||||
|
|
||||||
Supports dynamic executable pages with full request data parsing. Pages can receive:
|
Supports Micron `.mu` and dynamic pages with `#!` in the micron files.
|
||||||
- Form fields via `field_*` environment variables
|
|
||||||
- Link variables via `var_*` environment variables
|
|
||||||
- Remote identity via `remote_identity` environment variable
|
|
||||||
- Link ID via `link_id` environment variable
|
|
||||||
|
|
||||||
This enables forums, chats, and other interactive applications compatible with NomadNet clients.
|
## Statistics Tracking
|
||||||
|
|
||||||
|
The node now includes comprehensive statistics tracking for monitoring peer connections and page/file requests:
|
||||||
|
|
||||||
|
### Command Line Options for Stats
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Print stats every 60 seconds
|
||||||
|
rns-page-node --stats-interval 60
|
||||||
|
|
||||||
|
# Save stats to JSON file on shutdown
|
||||||
|
rns-page-node --save-stats node_stats.json
|
||||||
|
|
||||||
|
# Actively write stats to file (live updates)
|
||||||
|
rns-page-node --stats-file stats.json
|
||||||
|
|
||||||
|
# Combined: live stats file + periodic display + final save
|
||||||
|
rns-page-node --stats-file stats.json --stats-interval 300 --save-stats final_stats.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Stats Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# With periodic stats display
|
||||||
|
docker run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./config:/app/config ghcr.io/sudo-ivan/rns-page-node:latest --stats-interval 60
|
||||||
|
|
||||||
|
# Save stats to mounted volume
|
||||||
|
docker run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./config:/app/config -v ./stats:/app/stats ghcr.io/sudo-ivan/rns-page-node:latest --save-stats /app/stats/node_stats.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tracked Metrics
|
||||||
|
|
||||||
|
- **Connection Statistics**: Total connections, active connections, peer tracking
|
||||||
|
- **Request Statistics**: Page requests, file requests, requests by path and peer
|
||||||
|
- **Performance Metrics**: Requests per hour, uptime, response patterns
|
||||||
|
- **Historical Data**: Recent request history, hourly/daily aggregations
|
||||||
|
- **Top Content**: Most requested pages and files, most active peers
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
```
|
```
|
||||||
Positional arguments:
|
-c, --config: The path to the Reticulum config file.
|
||||||
node_config Path to rns-page-node config file
|
-n, --node-name: The name of the node.
|
||||||
|
-p, --pages-dir: The directory to serve pages from.
|
||||||
Optional arguments:
|
-f, --files-dir: The directory to serve files from.
|
||||||
-c, --config Path to the Reticulum config file
|
-i, --identity-dir: The directory to persist the node's identity.
|
||||||
-n, --node-name Name of the node
|
-a, --announce-interval: The interval to announce the node's presence.
|
||||||
-p, --pages-dir Directory to serve pages from
|
--page-refresh-interval: The interval to refresh pages (seconds, 0 disables).
|
||||||
-f, --files-dir Directory to serve files from
|
--file-refresh-interval: The interval to refresh files (seconds, 0 disables).
|
||||||
-i, --identity-dir Directory to persist the node's identity
|
-l, --log-level: The logging level.
|
||||||
-a, --announce-interval Interval to announce the node's presence (in minutes, default: 360 = 6 hours)
|
--stats-interval: Print stats every N seconds (0 disables).
|
||||||
--page-refresh-interval Interval to refresh pages (in seconds, 0 = disabled)
|
--save-stats: Save stats to JSON file on shutdown.
|
||||||
--file-refresh-interval Interval to refresh files (in seconds, 0 = disabled)
|
|
||||||
-l, --log-level Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
124
README.ru.md
124
README.ru.md
@@ -1,124 +0,0 @@
|
|||||||
# RNS Page Node
|
|
||||||
|
|
||||||
[English](README.md)
|
|
||||||
|
|
||||||
Простой способ для раздачи страниц и файлов через сеть [Reticulum](https://reticulum.network/). Прямая замена для узлов [NomadNet](https://github.com/markqvist/NomadNet), которые в основном служат для раздачи страниц и файлов.
|
|
||||||
|
|
||||||
## Особенности
|
|
||||||
|
|
||||||
- Раздача страниц и файлов через RNS
|
|
||||||
- Поддержка динамических страниц с переменными окружения
|
|
||||||
- Разбор данных форм и параметров запросов
|
|
||||||
|
|
||||||
## Установка
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Pip
|
|
||||||
# Может потребоваться --break-system-packages
|
|
||||||
pip install rns-page-node
|
|
||||||
|
|
||||||
# Pipx
|
|
||||||
pipx install rns-page-node
|
|
||||||
|
|
||||||
# uv
|
|
||||||
uv venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
uv pip install rns-page-node
|
|
||||||
|
|
||||||
# Pipx через Git
|
|
||||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
|
||||||
|
|
||||||
```
|
|
||||||
## Использование
|
|
||||||
```bash
|
|
||||||
# будет использовать текущий каталог для страниц и файлов
|
|
||||||
rns-page-node
|
|
||||||
```
|
|
||||||
|
|
||||||
или с параметрами командной строки:
|
|
||||||
```bash
|
|
||||||
rns-page-node --node-name "Page Node" --pages-dir ./pages --files-dir ./files --identity-dir ./node-config --announce-interval 360
|
|
||||||
```
|
|
||||||
|
|
||||||
или с файлом конфигурации:
|
|
||||||
```bash
|
|
||||||
rns-page-node /путь/к/config.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
### Файл Конфигурации
|
|
||||||
|
|
||||||
Вы можете использовать файл конфигурации для сохранения настроек. См. `config.example` для примера.
|
|
||||||
|
|
||||||
Формат файла конфигурации - простые пары `ключ=значение`:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Строки комментариев начинаются с #
|
|
||||||
node-name=Мой Page Node
|
|
||||||
pages-dir=./pages
|
|
||||||
files-dir=./files
|
|
||||||
identity-dir=./node-config
|
|
||||||
announce-interval=360
|
|
||||||
```
|
|
||||||
|
|
||||||
Порядок приоритета: Аргументы командной строки > Файл конфигурации > Значения по умолчанию
|
|
||||||
|
|
||||||
### Docker/Podman
|
|
||||||
```bash
|
|
||||||
docker run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./config:/root/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker/Podman без root-доступа
|
|
||||||
```bash
|
|
||||||
mkdir -p ./pages ./files ./node-config ./config
|
|
||||||
chown -R 1000:1000 ./pages ./files ./node-config ./config
|
|
||||||
podman run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./config:/app/config git.quad4.io/rns-things/rns-page-node:latest-rootless
|
|
||||||
```
|
|
||||||
|
|
||||||
Монтирование томов необязательно, вы также можете скопировать страницы и файлы в контейнер с помощью `podman cp` или `docker cp`.
|
|
||||||
|
|
||||||
## Сборка
|
|
||||||
```bash
|
|
||||||
make build
|
|
||||||
```
|
|
||||||
|
|
||||||
Сборка wheels:
|
|
||||||
```bash
|
|
||||||
make wheel
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сборка Wheels в Docker
|
|
||||||
```bash
|
|
||||||
make docker-wheels
|
|
||||||
```
|
|
||||||
|
|
||||||
## Страницы
|
|
||||||
|
|
||||||
Поддержка динамических исполняемых страниц с полным разбором данных запросов. Страницы могут получать:
|
|
||||||
- Поля форм через переменные окружения `field_*`
|
|
||||||
- Переменные ссылок через переменные окружения `var_*`
|
|
||||||
- Удаленную идентификацию через переменную окружения `remote_identity`
|
|
||||||
- ID соединения через переменную окружения `link_id`
|
|
||||||
|
|
||||||
Это позволяет создавать форумы, чаты и другие интерактивные приложения, совместимые с клиентами NomadNet.
|
|
||||||
|
|
||||||
## Параметры
|
|
||||||
|
|
||||||
```
|
|
||||||
Позиционные аргументы:
|
|
||||||
node_config Путь к файлу конфигурации rns-page-node
|
|
||||||
|
|
||||||
Необязательные аргументы:
|
|
||||||
-c, --config Путь к файлу конфигурации Reticulum
|
|
||||||
-n, --node-name Имя узла
|
|
||||||
-p, --pages-dir Каталог для раздачи страниц
|
|
||||||
-f, --files-dir Каталог для раздачи файлов
|
|
||||||
-i, --identity-dir Каталог для сохранения идентификационных данных узла
|
|
||||||
-a, --announce-interval Интервал анонсирования присутствия узла (в минутах, по умолчанию: 360 = 6 часов)
|
|
||||||
--page-refresh-interval Интервал обновления страниц (в секундах, 0 = отключено)
|
|
||||||
--file-refresh-interval Интервал обновления файлов (в секундах, 0 = отключено)
|
|
||||||
-l, --log-level Уровень логирования (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Лицензия
|
|
||||||
|
|
||||||
Этот проект включает части кодовой базы [NomadNet](https://github.com/markqvist/NomadNet), которая лицензирована под GNU General Public License v3.0 (GPL-3.0). Как производная работа, этот проект также распространяется на условиях GPL-3.0. Полный текст лицензии смотрите в файле [LICENSE](LICENSE).
|
|
||||||
201
Taskfile.yml
201
Taskfile.yml
@@ -1,201 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
vars:
|
|
||||||
VERSION:
|
|
||||||
sh: grep "^version =" pyproject.toml | cut -d '"' -f 2
|
|
||||||
VCS_REF:
|
|
||||||
sh: git rev-parse --short HEAD 2>/dev/null || echo "unknown"
|
|
||||||
BUILD_DATE:
|
|
||||||
sh: date -u +"%Y-%m-%dT%H:%M:%SZ"
|
|
||||||
DOCKER_BUILD:
|
|
||||||
sh: docker buildx version >/dev/null 2>&1 && echo "docker buildx build" || echo "docker build"
|
|
||||||
DOCKER_BUILD_LOAD:
|
|
||||||
sh: docker buildx version >/dev/null 2>&1 && echo "docker buildx build --load" || echo "docker build"
|
|
||||||
IMAGE_NAME: git.quad4.io/rns-things/rns-page-node
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
desc: Show available tasks
|
|
||||||
cmds:
|
|
||||||
- task --list
|
|
||||||
|
|
||||||
build:
|
|
||||||
desc: Clean and build sdist and wheel
|
|
||||||
deps: [clean]
|
|
||||||
cmds:
|
|
||||||
- python3 -m build
|
|
||||||
|
|
||||||
sdist:
|
|
||||||
desc: Build source distribution
|
|
||||||
cmds:
|
|
||||||
- python3 -m build --sdist
|
|
||||||
|
|
||||||
wheel:
|
|
||||||
desc: Build wheel
|
|
||||||
cmds:
|
|
||||||
- python3 -m build --wheel
|
|
||||||
|
|
||||||
clean:
|
|
||||||
desc: Remove build artifacts
|
|
||||||
cmds:
|
|
||||||
- rm -rf build dist *.egg-info
|
|
||||||
|
|
||||||
install:
|
|
||||||
desc: Install built wheel
|
|
||||||
deps: [build]
|
|
||||||
cmds:
|
|
||||||
- pip install dist/*.whl
|
|
||||||
|
|
||||||
install-dev:
|
|
||||||
desc: Install package in development mode
|
|
||||||
cmds:
|
|
||||||
- pip install -e .
|
|
||||||
|
|
||||||
lint:
|
|
||||||
desc: Run ruff linter
|
|
||||||
cmds:
|
|
||||||
- ruff check .
|
|
||||||
|
|
||||||
format:
|
|
||||||
desc: Run ruff formatter with auto-fix
|
|
||||||
cmds:
|
|
||||||
- ruff check --fix .
|
|
||||||
|
|
||||||
check:
|
|
||||||
desc: Run all code quality checks
|
|
||||||
deps: [lint]
|
|
||||||
|
|
||||||
test:
|
|
||||||
desc: Run local integration tests
|
|
||||||
cmds:
|
|
||||||
- bash tests/run_tests.sh
|
|
||||||
|
|
||||||
docker-wheels:
|
|
||||||
desc: Build Python wheels in Docker
|
|
||||||
cmds:
|
|
||||||
- '{{.DOCKER_BUILD}} --target builder -f docker/Dockerfile.build -t rns-page-node-builder .'
|
|
||||||
- docker create --name builder-container rns-page-node-builder true
|
|
||||||
- docker cp builder-container:/src/dist ./dist
|
|
||||||
- docker rm builder-container
|
|
||||||
|
|
||||||
docker-build:
|
|
||||||
desc: Build runtime Docker image
|
|
||||||
cmds:
|
|
||||||
- >
|
|
||||||
{{.DOCKER_BUILD_LOAD}}
|
|
||||||
--build-arg VERSION={{.VERSION}}
|
|
||||||
--build-arg VCS_REF={{.VCS_REF}}
|
|
||||||
--build-arg BUILD_DATE={{.BUILD_DATE}}
|
|
||||||
-f docker/Dockerfile
|
|
||||||
-t {{.IMAGE_NAME}}:latest
|
|
||||||
-t {{.IMAGE_NAME}}:{{.VERSION}}
|
|
||||||
.
|
|
||||||
|
|
||||||
docker-run:
|
|
||||||
desc: Run runtime Docker image
|
|
||||||
cmds:
|
|
||||||
- >
|
|
||||||
docker run --rm -it
|
|
||||||
-v ./pages:/app/pages
|
|
||||||
-v ./files:/app/files
|
|
||||||
-v ./node-config:/app/node-config
|
|
||||||
{{.IMAGE_NAME}}:latest
|
|
||||||
--node-name "Page Node"
|
|
||||||
--pages-dir /app/pages
|
|
||||||
--files-dir /app/files
|
|
||||||
--identity-dir /app/node-config
|
|
||||||
--announce-interval 360
|
|
||||||
|
|
||||||
docker-build-rootless:
|
|
||||||
desc: Build rootless runtime Docker image
|
|
||||||
cmds:
|
|
||||||
- >
|
|
||||||
{{.DOCKER_BUILD_LOAD}}
|
|
||||||
--build-arg VERSION={{.VERSION}}
|
|
||||||
--build-arg VCS_REF={{.VCS_REF}}
|
|
||||||
--build-arg BUILD_DATE={{.BUILD_DATE}}
|
|
||||||
-f docker/Dockerfile.rootless
|
|
||||||
-t {{.IMAGE_NAME}}:latest-rootless
|
|
||||||
-t {{.IMAGE_NAME}}:{{.VERSION}}-rootless
|
|
||||||
.
|
|
||||||
|
|
||||||
docker-run-rootless:
|
|
||||||
desc: Run rootless runtime Docker image
|
|
||||||
cmds:
|
|
||||||
- >
|
|
||||||
docker run --rm -it
|
|
||||||
-v ./pages:/app/pages
|
|
||||||
-v ./files:/app/files
|
|
||||||
-v ./node-config:/app/node-config
|
|
||||||
{{.IMAGE_NAME}}:latest-rootless
|
|
||||||
--node-name "Page Node"
|
|
||||||
--pages-dir /app/pages
|
|
||||||
--files-dir /app/files
|
|
||||||
--identity-dir /app/node-config
|
|
||||||
--announce-interval 360
|
|
||||||
|
|
||||||
docker-test:
|
|
||||||
desc: Build and run integration tests in Docker
|
|
||||||
cmds:
|
|
||||||
- '{{.DOCKER_BUILD_LOAD}} -f docker/Dockerfile.tests -t rns-page-node-tests .'
|
|
||||||
- docker run --rm rns-page-node-tests
|
|
||||||
|
|
||||||
docker-clean:
|
|
||||||
desc: Remove Docker images and containers
|
|
||||||
cmds:
|
|
||||||
- docker rmi {{.IMAGE_NAME}}:latest {{.IMAGE_NAME}}:{{.VERSION}} 2>/dev/null || true
|
|
||||||
- docker rmi {{.IMAGE_NAME}}:latest-rootless {{.IMAGE_NAME}}:{{.VERSION}}-rootless 2>/dev/null || true
|
|
||||||
- docker rmi rns-page-node-builder rns-page-node-tests 2>/dev/null || true
|
|
||||||
|
|
||||||
run:
|
|
||||||
desc: Run rns-page-node locally
|
|
||||||
cmds:
|
|
||||||
- python3 -m rns_page_node.main
|
|
||||||
|
|
||||||
run-dev:
|
|
||||||
desc: Run rns-page-node with development settings
|
|
||||||
cmds:
|
|
||||||
- python3 -m rns_page_node.main --log-level DEBUG
|
|
||||||
|
|
||||||
venv:
|
|
||||||
desc: Create Python virtual environment
|
|
||||||
cmds:
|
|
||||||
- python3 -m venv .venv
|
|
||||||
- 'echo "Virtual environment created. Activate with: source .venv/bin/activate"'
|
|
||||||
|
|
||||||
deps-install:
|
|
||||||
desc: Install dependencies using pip
|
|
||||||
cmds:
|
|
||||||
- pip install -r requirements.txt || pip install -e .
|
|
||||||
|
|
||||||
deps-update:
|
|
||||||
desc: Update dependencies
|
|
||||||
cmds:
|
|
||||||
- pip install --upgrade -r requirements.txt || pip install --upgrade -e .
|
|
||||||
|
|
||||||
version:
|
|
||||||
desc: Show current version
|
|
||||||
cmds:
|
|
||||||
- 'echo "Version: {{.VERSION}}"'
|
|
||||||
- 'echo "VCS Ref: {{.VCS_REF}}"'
|
|
||||||
- 'echo "Build Date: {{.BUILD_DATE}}"'
|
|
||||||
|
|
||||||
setup-dirs:
|
|
||||||
desc: Create required directories for running the node
|
|
||||||
cmds:
|
|
||||||
- mkdir -p pages files node-config
|
|
||||||
|
|
||||||
nix-shell:
|
|
||||||
desc: Enter Nix development shell
|
|
||||||
cmds:
|
|
||||||
- nix develop
|
|
||||||
|
|
||||||
nix-build:
|
|
||||||
desc: Build with Nix
|
|
||||||
cmds:
|
|
||||||
- nix build
|
|
||||||
|
|
||||||
all:
|
|
||||||
desc: Run build, lint, and test
|
|
||||||
deps: [build, check, test]
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# rns-page-node configuration file
|
|
||||||
# Lines starting with # are comments
|
|
||||||
# Format: key=value
|
|
||||||
|
|
||||||
# Reticulum config directory path
|
|
||||||
# reticulum-config=/path/to/reticulum/config
|
|
||||||
|
|
||||||
# Node display name
|
|
||||||
node-name=My Page Node
|
|
||||||
|
|
||||||
# Pages directory
|
|
||||||
pages-dir=./pages
|
|
||||||
|
|
||||||
# Files directory
|
|
||||||
files-dir=./files
|
|
||||||
|
|
||||||
# Node identity directory
|
|
||||||
identity-dir=./node-config
|
|
||||||
|
|
||||||
# Announce interval in minutes (default: 360 = 6 hours)
|
|
||||||
announce-interval=360
|
|
||||||
|
|
||||||
# Page refresh interval in seconds (0 = disabled)
|
|
||||||
page-refresh-interval=300
|
|
||||||
|
|
||||||
# File refresh interval in seconds (0 = disabled)
|
|
||||||
file-refresh-interval=300
|
|
||||||
|
|
||||||
# Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
||||||
log-level=INFO
|
|
||||||
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
ARG PYTHON_VERSION=3.13
|
|
||||||
FROM python:${PYTHON_VERSION}-alpine
|
|
||||||
|
|
||||||
ARG BUILD_DATE
|
|
||||||
ARG VCS_REF
|
|
||||||
ARG VERSION
|
|
||||||
|
|
||||||
LABEL org.opencontainers.image.created=$BUILD_DATE \
|
|
||||||
org.opencontainers.image.title="RNS Page Node" \
|
|
||||||
org.opencontainers.image.description="A simple way to serve pages and files over the Reticulum network." \
|
|
||||||
org.opencontainers.image.url="https://git.quad4.io/RNS-Things/rns-page-node" \
|
|
||||||
org.opencontainers.image.documentation="https://git.quad4.io/RNS-Things/rns-page-node/src/branch/main/README.md" \
|
|
||||||
org.opencontainers.image.source="https://git.quad4.io/RNS-Things/rns-page-node" \
|
|
||||||
org.opencontainers.image.version=$VERSION \
|
|
||||||
org.opencontainers.image.revision=$VCS_REF \
|
|
||||||
org.opencontainers.image.vendor="RNS-Things" \
|
|
||||||
org.opencontainers.image.licenses="GPL-3.0" \
|
|
||||||
org.opencontainers.image.authors="Sudo-Ivan" \
|
|
||||||
org.opencontainers.image.base.name="python:${PYTHON_VERSION}-alpine"
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN apk add --no-cache gcc python3-dev musl-dev linux-headers
|
|
||||||
|
|
||||||
RUN pip install poetry
|
|
||||||
ENV POETRY_VIRTUALENVS_IN_PROJECT=true
|
|
||||||
|
|
||||||
COPY pyproject.toml poetry.lock* ./
|
|
||||||
COPY README.md ./
|
|
||||||
COPY rns_page_node ./rns_page_node
|
|
||||||
|
|
||||||
RUN poetry install --no-interaction --no-ansi
|
|
||||||
|
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
|
||||||
|
|
||||||
ENTRYPOINT ["poetry", "run", "rns-page-node"]
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
ARG PYTHON_VERSION=3.13
|
|
||||||
FROM python:${PYTHON_VERSION}-alpine
|
|
||||||
|
|
||||||
ARG BUILD_DATE
|
|
||||||
ARG VCS_REF
|
|
||||||
ARG VERSION
|
|
||||||
|
|
||||||
LABEL org.opencontainers.image.created=$BUILD_DATE \
|
|
||||||
org.opencontainers.image.title="RNS Page Node (Rootless)" \
|
|
||||||
org.opencontainers.image.description="A simple way to serve pages and files over the Reticulum network." \
|
|
||||||
org.opencontainers.image.url="https://git.quad4.io/RNS-Things/rns-page-node" \
|
|
||||||
org.opencontainers.image.documentation="https://git.quad4.io/RNS-Things/rns-page-node/src/branch/main/README.md" \
|
|
||||||
org.opencontainers.image.source="https://git.quad4.io/RNS-Things/rns-page-node" \
|
|
||||||
org.opencontainers.image.version=$VERSION \
|
|
||||||
org.opencontainers.image.revision=$VCS_REF \
|
|
||||||
org.opencontainers.image.vendor="RNS-Things" \
|
|
||||||
org.opencontainers.image.licenses="GPL-3.0" \
|
|
||||||
org.opencontainers.image.authors="Sudo-Ivan" \
|
|
||||||
org.opencontainers.image.base.name="python:${PYTHON_VERSION}-alpine"
|
|
||||||
|
|
||||||
RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN apk add --no-cache gcc python3-dev musl-dev linux-headers
|
|
||||||
|
|
||||||
RUN pip install poetry
|
|
||||||
ENV POETRY_VIRTUALENVS_IN_PROJECT=true
|
|
||||||
|
|
||||||
COPY pyproject.toml poetry.lock* ./
|
|
||||||
COPY README.md ./
|
|
||||||
COPY rns_page_node ./rns_page_node
|
|
||||||
|
|
||||||
RUN poetry install --no-interaction --no-ansi
|
|
||||||
|
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
|
||||||
|
|
||||||
USER app
|
|
||||||
|
|
||||||
ENTRYPOINT ["poetry", "run", "rns-page-node"]
|
|
||||||
61
flake.lock
generated
61
flake.lock
generated
@@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1766902085,
|
|
||||||
"narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
35
flake.nix
35
flake.nix
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
description = "A simple way to serve pages and files over the Reticulum network";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs {
|
|
||||||
inherit system;
|
|
||||||
};
|
|
||||||
|
|
||||||
python = pkgs.python3;
|
|
||||||
|
|
||||||
pythonPackages = python.pkgs;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
buildInputs = with pkgs; [
|
|
||||||
python
|
|
||||||
poetry
|
|
||||||
go-task
|
|
||||||
pythonPackages.build
|
|
||||||
pythonPackages.pip
|
|
||||||
pythonPackages.setuptools
|
|
||||||
pythonPackages.wheel
|
|
||||||
pythonPackages.ruff
|
|
||||||
];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
1543
poetry.lock
generated
1543
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,16 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "rns-page-node"
|
name = "rns-page-node"
|
||||||
version = "1.3.0"
|
version = "0.2.0"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
description = "A simple way to serve pages and files over the Reticulum network."
|
description = "A simple way to serve pages and files over the Reticulum network."
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Sudo-Ivan"}
|
{name = "Sudo-Ivan"}
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9.2"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rns (>=1.0.4,<1.5.0)",
|
"rns (>=1.0.0,<1.5.0)"
|
||||||
"cryptography>=46.0.3"
|
|
||||||
]
|
]
|
||||||
classifiers = [
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.urls]
|
|
||||||
Homepage = "https://git.quad4.io/RNS-Things/rns-page-node"
|
|
||||||
Repository = "https://git.quad4.io/RNS-Things/rns-page-node"
|
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
rns-page-node = "rns_page_node.main:main"
|
rns-page-node = "rns_page_node.main:main"
|
||||||
@@ -29,4 +20,6 @@ requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "^0.14.10"
|
ruff = "^0.12.3"
|
||||||
|
safety = "^3.6.0"
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
|
||||||
}
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
rns=1.0.4
|
rns=1.0.0
|
||||||
cryptography==46.0.3
|
|
||||||
@@ -1,6 +1,2 @@
|
|||||||
"""RNS Page Node package.
|
# rns_page_node package
|
||||||
|
__all__ = ['main']
|
||||||
A minimal Reticulum page node that serves .mu pages and files over RNS.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ["main"]
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
35
setup.py
35
setup.py
@@ -1,28 +1,31 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
with open('README.md', 'r', encoding='utf-8') as fh:
|
||||||
|
long_description = fh.read()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="rns-page-node",
|
name='rns-page-node',
|
||||||
version="1.3.0",
|
version='0.2.0',
|
||||||
description="A simple way to serve pages and files over the Reticulum network.",
|
author='Sudo-Ivan',
|
||||||
long_description=open("README.md").read(),
|
author_email='',
|
||||||
long_description_content_type="text/markdown",
|
description='A simple way to serve pages and files over the Reticulum network.',
|
||||||
author="Sudo-Ivan",
|
long_description=long_description,
|
||||||
url="https://git.quad4.io/RNS-Things/rns-page-node",
|
long_description_content_type='text/markdown',
|
||||||
|
url='https://github.com/Sudo-Ivan/rns-page-node',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
|
license="GPL-3.0",
|
||||||
|
python_requires='>=3.10',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"rns>=1.0.4,<1.5.0",
|
'rns>=1.0.0,<1.5.0',
|
||||||
"cryptography>=46.0.3",
|
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
'console_scripts': [
|
||||||
"rns-page-node=rns_page_node.main:main",
|
'rns-page-node=rns_page_node.main:main',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Programming Language :: Python :: 3",
|
'Programming Language :: Python :: 3',
|
||||||
"Operating System :: OS Independent",
|
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
'Operating System :: OS Independent',
|
||||||
],
|
],
|
||||||
python_requires=">=3.9.2",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.14-slim
|
FROM python:3.10-slim
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y build-essential libssl-dev && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y build-essential libssl-dev && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
28
tests/run_tests.sh
Executable file → Normal file
28
tests/run_tests.sh
Executable file → Normal file
@@ -9,33 +9,11 @@ rm -rf config node-config pages files node.log
|
|||||||
mkdir -p config node-config pages files
|
mkdir -p config node-config pages files
|
||||||
|
|
||||||
# Create a sample page and a test file
|
# Create a sample page and a test file
|
||||||
cat > pages/index.mu << 'EOF'
|
cat > pages/index.mu << EOF
|
||||||
#!/usr/bin/env python3
|
>Test Page
|
||||||
import os
|
This is a test page.
|
||||||
|
|
||||||
print("`F0f0`_`Test Page`_")
|
|
||||||
print("This is a test page with environment variable support.")
|
|
||||||
print()
|
|
||||||
|
|
||||||
print("`F0f0`_`Environment Variables`_")
|
|
||||||
params = []
|
|
||||||
for key, value in os.environ.items():
|
|
||||||
if key.startswith(('field_', 'var_')):
|
|
||||||
params.append(f"- `Faaa`{key}`f: `F0f0`{value}`f")
|
|
||||||
|
|
||||||
if params:
|
|
||||||
print("\n".join(params))
|
|
||||||
else:
|
|
||||||
print("- No parameters received")
|
|
||||||
|
|
||||||
print()
|
|
||||||
print("`F0f0`_`Remote Identity`_")
|
|
||||||
remote_id = os.environ.get('remote_identity', '33aff86b736acd47dca07e84630fd192') # Mock for testing
|
|
||||||
print(f"`Faaa`{remote_id}`f")
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod +x pages/index.mu
|
|
||||||
|
|
||||||
cat > files/text.txt << EOF
|
cat > files/text.txt << EOF
|
||||||
This is a test file.
|
This is a test file.
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import threading
|
|
||||||
import time
|
import time
|
||||||
|
import threading
|
||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
# Determine base directory for tests
|
# Determine base directory for tests
|
||||||
dir_path = os.path.abspath(os.path.dirname(__file__))
|
dir_path = os.path.abspath(os.path.dirname(__file__))
|
||||||
config_dir = os.path.join(dir_path, "config")
|
config_dir = os.path.join(dir_path, 'config')
|
||||||
identity_dir = os.path.join(dir_path, "node-config")
|
identity_dir = os.path.join(dir_path, 'node-config')
|
||||||
|
|
||||||
# Initialize Reticulum with shared config
|
# Initialize Reticulum with shared config
|
||||||
RNS.Reticulum(config_dir)
|
RNS.Reticulum(config_dir)
|
||||||
|
|
||||||
# Load server identity (created by the page node)
|
# Load server identity (created by the page node)
|
||||||
identity_file = os.path.join(identity_dir, "identity")
|
identity_file = os.path.join(identity_dir, 'identity')
|
||||||
server_identity = RNS.Identity.from_file(identity_file)
|
server_identity = RNS.Identity.from_file(identity_file)
|
||||||
|
|
||||||
# Create a destination to the server node
|
# Create a destination to the server node
|
||||||
@@ -23,8 +22,8 @@ destination = RNS.Destination(
|
|||||||
server_identity,
|
server_identity,
|
||||||
RNS.Destination.OUT,
|
RNS.Destination.OUT,
|
||||||
RNS.Destination.SINGLE,
|
RNS.Destination.SINGLE,
|
||||||
"nomadnetwork",
|
'nomadnetwork',
|
||||||
"node",
|
'node'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure we know a path to the destination
|
# Ensure we know a path to the destination
|
||||||
@@ -40,190 +39,66 @@ global_link = RNS.Link(destination)
|
|||||||
responses = {}
|
responses = {}
|
||||||
done_event = threading.Event()
|
done_event = threading.Event()
|
||||||
|
|
||||||
# Test data for environment variables
|
|
||||||
test_data_dict = {
|
|
||||||
"var_field_test": "dictionary_value",
|
|
||||||
"var_field_message": "hello_world",
|
|
||||||
"var_action": "test_action",
|
|
||||||
}
|
|
||||||
test_data_dict2 = {
|
|
||||||
"field_username": "testuser",
|
|
||||||
"field_message": "hello_from_form",
|
|
||||||
"var_action": "submit",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Callback for page response
|
# Callback for page response
|
||||||
def on_page(response):
|
def on_page(response):
|
||||||
data = response.response
|
data = response.response
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
text = data.decode("utf-8")
|
text = data.decode('utf-8')
|
||||||
else:
|
else:
|
||||||
text = str(data)
|
text = str(data)
|
||||||
print("Received page (no data):")
|
print('Received page:')
|
||||||
print(text)
|
print(text)
|
||||||
responses["page"] = text
|
responses['page'] = text
|
||||||
check_responses()
|
if 'file' in responses:
|
||||||
|
|
||||||
|
|
||||||
# Callback for page response with dictionary data
|
|
||||||
def on_page_dict(response):
|
|
||||||
data = response.response
|
|
||||||
if isinstance(data, bytes):
|
|
||||||
text = data.decode("utf-8")
|
|
||||||
else:
|
|
||||||
text = str(data)
|
|
||||||
print("Received page (dict data):")
|
|
||||||
print(text)
|
|
||||||
responses["page_dict"] = text
|
|
||||||
check_responses()
|
|
||||||
|
|
||||||
|
|
||||||
# Callback for page response with second dict data
|
|
||||||
def on_page_dict2(response):
|
|
||||||
data = response.response
|
|
||||||
if isinstance(data, bytes):
|
|
||||||
text = data.decode("utf-8")
|
|
||||||
else:
|
|
||||||
text = str(data)
|
|
||||||
print("Received page (dict2 data):")
|
|
||||||
print(text)
|
|
||||||
responses["page_dict2"] = text
|
|
||||||
check_responses()
|
|
||||||
|
|
||||||
|
|
||||||
def check_responses():
|
|
||||||
if (
|
|
||||||
"page" in responses
|
|
||||||
and "page_dict" in responses
|
|
||||||
and "page_dict2" in responses
|
|
||||||
and "file" in responses
|
|
||||||
):
|
|
||||||
done_event.set()
|
done_event.set()
|
||||||
|
|
||||||
|
|
||||||
# Callback for file response
|
# Callback for file response
|
||||||
def on_file(response):
|
def on_file(response):
|
||||||
data = response.response
|
data = response.response
|
||||||
# Handle response as [fileobj, headers]
|
# Handle response as [fileobj, headers]
|
||||||
if isinstance(data, list) and len(data) == 2 and hasattr(data[0], "read"):
|
if isinstance(data, list) and len(data) == 2 and hasattr(data[0], 'read'):
|
||||||
fileobj, headers = data
|
fileobj, headers = data
|
||||||
file_data = fileobj.read()
|
file_data = fileobj.read()
|
||||||
filename = headers.get(b"name", b"").decode("utf-8")
|
filename = headers.get(b'name', b'').decode('utf-8')
|
||||||
print(f"Received file ({filename}):")
|
print(f'Received file ({filename}):')
|
||||||
print(file_data.decode("utf-8"))
|
print(file_data.decode('utf-8'))
|
||||||
responses["file"] = file_data.decode("utf-8")
|
responses['file'] = file_data.decode('utf-8')
|
||||||
# Handle response as a raw file object
|
# Handle response as a raw file object
|
||||||
elif hasattr(data, "read"):
|
elif hasattr(data, 'read'):
|
||||||
file_data = data.read()
|
file_data = data.read()
|
||||||
filename = os.path.basename("text.txt")
|
filename = os.path.basename('text.txt')
|
||||||
print(f"Received file ({filename}):")
|
print(f'Received file ({filename}):')
|
||||||
print(file_data.decode("utf-8"))
|
print(file_data.decode('utf-8'))
|
||||||
responses["file"] = file_data.decode("utf-8")
|
responses['file'] = file_data.decode('utf-8')
|
||||||
# Handle response as raw bytes
|
# Handle response as raw bytes
|
||||||
elif isinstance(data, bytes):
|
elif isinstance(data, bytes):
|
||||||
text = data.decode("utf-8")
|
text = data.decode('utf-8')
|
||||||
print("Received file:")
|
print('Received file:')
|
||||||
print(text)
|
print(text)
|
||||||
responses["file"] = text
|
responses['file'] = text
|
||||||
else:
|
else:
|
||||||
print("Received file (unhandled format):", data)
|
print('Received file (unhandled format):', data)
|
||||||
responses["file"] = str(data)
|
responses['file'] = str(data)
|
||||||
check_responses()
|
if 'page' in responses:
|
||||||
|
done_event.set()
|
||||||
|
|
||||||
|
# Request the page and file once the link is established
|
||||||
# Request the pages and file once the link is established
|
|
||||||
def on_link_established(link):
|
def on_link_established(link):
|
||||||
# Test page without data
|
link.request('/page/index.mu', None, response_callback=on_page)
|
||||||
link.request("/page/index.mu", None, response_callback=on_page)
|
link.request('/file/text.txt', None, response_callback=on_file)
|
||||||
# Test page with dictionary data (simulates var_ prefixed data)
|
|
||||||
link.request("/page/index.mu", test_data_dict, response_callback=on_page_dict)
|
|
||||||
# Test page with form field data (simulates field_ prefixed data)
|
|
||||||
link.request("/page/index.mu", test_data_dict2, response_callback=on_page_dict2)
|
|
||||||
# Test file serving
|
|
||||||
link.request("/file/text.txt", None, response_callback=on_file)
|
|
||||||
|
|
||||||
|
|
||||||
# Register callbacks
|
# Register callbacks
|
||||||
global_link.set_link_established_callback(on_link_established)
|
global_link.set_link_established_callback(on_link_established)
|
||||||
global_link.set_link_closed_callback(lambda link: done_event.set())
|
global_link.set_link_closed_callback(lambda l: done_event.set())
|
||||||
|
|
||||||
# Wait for responses or timeout
|
# Wait for responses or timeout
|
||||||
if not done_event.wait(timeout=30):
|
if not done_event.wait(timeout=30):
|
||||||
print("Test timed out.", file=sys.stderr)
|
print('Test timed out.', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
if responses.get('page') and responses.get('file'):
|
||||||
# Validate test results
|
print('Tests passed!')
|
||||||
def validate_test_results():
|
|
||||||
"""Validate that all responses contain expected content"""
|
|
||||||
# Check basic page response (no data)
|
|
||||||
if "page" not in responses:
|
|
||||||
print("ERROR: No basic page response received", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
page_content = responses["page"]
|
|
||||||
if "No parameters received" not in page_content:
|
|
||||||
print("ERROR: Basic page should show 'No parameters received'", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
if "33aff86b736acd47dca07e84630fd192" not in page_content:
|
|
||||||
print("ERROR: Basic page should show mock remote identity", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check page with dictionary data
|
|
||||||
if "page_dict" not in responses:
|
|
||||||
print("ERROR: No dictionary data page response received", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
dict_content = responses["page_dict"]
|
|
||||||
if "var_field_test" not in dict_content or "dictionary_value" not in dict_content:
|
|
||||||
print(
|
|
||||||
"ERROR: Dictionary data page should contain processed environment variables",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
if "33aff86b736acd47dca07e84630fd192" not in dict_content:
|
|
||||||
print(
|
|
||||||
"ERROR: Dictionary data page should show mock remote identity",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check page with second dictionary data (form fields)
|
|
||||||
if "page_dict2" not in responses:
|
|
||||||
print("ERROR: No dict2 data page response received", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
dict2_content = responses["page_dict2"]
|
|
||||||
if "field_username" not in dict2_content or "testuser" not in dict2_content:
|
|
||||||
print(
|
|
||||||
"ERROR: Dict2 data page should contain processed environment variables",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
if "33aff86b736acd47dca07e84630fd192" not in dict2_content:
|
|
||||||
print(
|
|
||||||
"ERROR: Dict2 data page should show mock remote identity",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check file response
|
|
||||||
if "file" not in responses:
|
|
||||||
print("ERROR: No file response received", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
file_content = responses["file"]
|
|
||||||
if "This is a test file" not in file_content:
|
|
||||||
print("ERROR: File content doesn't match expected content", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
if validate_test_results():
|
|
||||||
print("All tests passed! Environment variable processing works correctly.")
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
print("Tests failed.", file=sys.stderr)
|
print('Tests failed.', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import threading
|
|
||||||
import time
|
import time
|
||||||
|
import threading
|
||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
dir_path = os.path.abspath(os.path.dirname(__file__))
|
dir_path = os.path.abspath(os.path.dirname(__file__))
|
||||||
config_dir = os.path.join(dir_path, "config")
|
config_dir = os.path.join(dir_path, 'config')
|
||||||
|
|
||||||
RNS.Reticulum(config_dir)
|
RNS.Reticulum(config_dir)
|
||||||
|
|
||||||
DESTINATION_HEX = (
|
DESTINATION_HEX = '49b2d959db8528347d0a38083aec1042' # Ivans Node that runs rns-page-node
|
||||||
"49b2d959db8528347d0a38083aec1042" # Ivans Node that runs rns-page-node
|
|
||||||
)
|
|
||||||
|
|
||||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH // 8) * 2
|
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH // 8) * 2
|
||||||
if len(DESTINATION_HEX) != dest_len:
|
if len(DESTINATION_HEX) != dest_len:
|
||||||
print(
|
print(f"Invalid destination length (got {len(DESTINATION_HEX)}, expected {dest_len})", file=sys.stderr)
|
||||||
f"Invalid destination length (got {len(DESTINATION_HEX)}, expected {dest_len})",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
destination_hash = bytes.fromhex(DESTINATION_HEX)
|
destination_hash = bytes.fromhex(DESTINATION_HEX)
|
||||||
|
|
||||||
@@ -37,33 +31,29 @@ destination = RNS.Destination(
|
|||||||
server_identity,
|
server_identity,
|
||||||
RNS.Destination.OUT,
|
RNS.Destination.OUT,
|
||||||
RNS.Destination.SINGLE,
|
RNS.Destination.SINGLE,
|
||||||
"nomadnetwork",
|
'nomadnetwork',
|
||||||
"node",
|
'node'
|
||||||
)
|
)
|
||||||
link = RNS.Link(destination)
|
link = RNS.Link(destination)
|
||||||
|
|
||||||
done_event = threading.Event()
|
done_event = threading.Event()
|
||||||
|
|
||||||
|
|
||||||
def on_page(response):
|
def on_page(response):
|
||||||
data = response.response
|
data = response.response
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
text = data.decode("utf-8")
|
text = data.decode('utf-8')
|
||||||
else:
|
else:
|
||||||
text = str(data)
|
text = str(data)
|
||||||
print("Fetched page content:")
|
print('Fetched page content:')
|
||||||
print(text)
|
print(text)
|
||||||
done_event.set()
|
done_event.set()
|
||||||
|
|
||||||
|
link.set_link_established_callback(lambda l: l.request('/page/index.mu', None, response_callback=on_page))
|
||||||
link.set_link_established_callback(
|
link.set_link_closed_callback(lambda l: done_event.set())
|
||||||
lambda link: link.request("/page/index.mu", None, response_callback=on_page),
|
|
||||||
)
|
|
||||||
link.set_link_closed_callback(lambda link: done_event.set())
|
|
||||||
|
|
||||||
if not done_event.wait(timeout=30):
|
if not done_event.wait(timeout=30):
|
||||||
print("Timed out waiting for page", file=sys.stderr)
|
print('Timed out waiting for page', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print("Done fetching page.")
|
print('Done fetching page.')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|||||||
Reference in New Issue
Block a user