Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
185db82bf0
|
|||
|
86ddce80db
|
|||
|
d1676ce5ec
|
|||
|
ec47ecd872
|
|||
|
ed01ccccbb
|
|||
|
9ba9d277c2
|
|||
|
39dac5c2db
|
|||
|
a40ba430b8
|
|||
|
70c8826af0
|
|||
|
97d978611e
|
|||
|
a295a52904
|
|||
|
64c016250a
|
|||
|
457013b94a
|
|||
|
02af0e1ddf
|
|||
|
438d12ab71
|
|||
|
4d1b49daa4
|
|||
|
beab7b2565
|
|||
|
2998b8d833
|
|||
|
f09622ae76
|
|||
|
d7efe9de7f
|
|||
|
f49c6293f9
|
|||
|
1b0aaad689
|
|||
|
6e28f908be
|
|||
|
a32215f434
|
|||
|
59e016815b
|
|||
|
6b8ce85ea2
|
|||
|
62c280daf2
|
|||
|
e817238fb9
|
|||
|
37bc4948d1
|
|||
| 8080f2855f | |||
|
|
95fa215162 | ||
|
|
cd08064678 | ||
|
|
01b3a54abf | ||
|
|
cb41f89cc9 | ||
|
|
d06a93995e | ||
|
|
dbfe2fd35c | ||
|
|
07754bc9fa | ||
|
|
898919e160 | ||
|
761c1b356c
|
|||
|
|
54b47c8eaf | ||
|
|
55343b7be2 | ||
|
9c6da64cbe
|
|||
|
d0a484f692
|
|||
|
|
31dd0828a2 | ||
|
954f6ecd36
|
|||
|
85c8785502
|
|||
|
112348d862
|
|||
|
4f8f2786ab
|
|||
|
3e6e078367
|
|||
|
73c9d12f26
|
|||
|
fc50bc6fb5
|
|||
|
8f1d5ee02a
|
|||
|
86f0a687d2
|
|||
|
694ab011ec
|
|||
|
53d74a3732
|
|||
|
30f050c8d4
|
|||
|
070157737b
|
|||
|
8538d9feb3
|
|||
|
3438b271a5
|
|||
|
|
d6228d6d63 | ||
|
ccf954681b
|
|||
|
4ec44900cf
|
|||
|
d4099fb9a2
|
|||
|
1571b315b2
|
|||
|
71bd49bd7d
|
|||
|
382413dc08
|
|||
|
0621facc7d
|
|||
|
50cbfed5fa
|
|||
|
36d9a3350b
|
|||
|
515a9d9dbf
|
|||
|
3c27b4f9b8
|
|||
|
851c8c05d4
|
|||
|
8002a75e26
|
|||
|
06e6b55ecc
|
|||
|
48e47bd0bd
|
|||
|
9c074a0333
|
|||
|
f2314f862c
|
|||
|
6e57536650
|
|||
|
5fd7551874
|
|||
|
62d592c4d0
|
|||
|
8af2a9abbb
|
|||
|
64ca8bd4d2
|
|||
|
f1d025bd0e
|
|||
|
087ff563a2
|
|||
|
882dacf2bb
|
|||
|
a2efdb136a
|
|||
|
001613b4fa
|
|||
|
74564d0ef2
|
|||
|
81142ad194
|
|||
|
fee1d2e2d6
|
|||
|
7c93fdb71d
|
|||
| 9e435eeebc | |||
| 5dfcc1f2ce | |||
| 2def60b457 | |||
| f708ad4ee1 | |||
| f7568d81aa | |||
| 251f9bacef | |||
| 07892dbfee | |||
| 54e6849968 | |||
| ea27c380cb | |||
|
|
a338be85e1 | ||
|
|
e31cb3418b | ||
|
|
798725dca6 | ||
|
|
6f393497f0 | ||
|
|
14b5aabf2b | ||
| fb36907447 | |||
| 62fde2617b | |||
| 9f5ea23eb7 | |||
| 19fad61706 | |||
| c900cf38c9 | |||
| 014ebc25c6 | |||
|
|
d5e9308fb5 | ||
|
|
7d5e891261 | ||
|
|
c382ed790f | ||
| cb72e57da9 | |||
|
|
aaf5ad23e2 | ||
|
|
ce1b1dad7d | ||
|
|
67ebc7e556 | ||
|
|
b31fb748b8 | ||
|
|
eb27326763 | ||
|
|
f40d5a51ae | ||
|
|
4aa83a2dfb |
@@ -0,0 +1,150 @@
|
||||
name: Build and Publish Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
REGISTRY: git.quad4.io
|
||||
IMAGE_NAME: RNS-Things/rns-page-node
|
||||
DEV_IMAGE_NAME: RNS-Things/rns-page-node-dev
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
image_digest: ${{ steps.build.outputs.digest }}
|
||||
image_tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: https://git.quad4.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: https://git.quad4.io/actions/setup-qemu-action@3a1695b1353f9f8868722ffaafc1f164ef35fa5e # v3
|
||||
with:
|
||||
platforms: amd64,arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: https://git.quad4.io/actions/setup-buildx-action@7fbd262f0ca05b45700d8eaaf71f40837d036cc7 # v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: https://git.quad4.io/actions/login-action@bb91d7e20cfedb030a44164cb558bba899e1010a # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: https://git.quad4.io/actions/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,format=short
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build
|
||||
uses: https://git.quad4.io/actions/build-push-action@dc0c2d97df39a6939d9db7d572445529e2365ec6 # v6
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
no-cache: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
BUILD_DATE=${{ github.event.head_commit.timestamp }}
|
||||
VCS_REF=${{ github.sha }}
|
||||
VERSION=${{ steps.meta.outputs.version }}
|
||||
|
||||
- name: Download Trivy
|
||||
run: |
|
||||
curl -L -o /tmp/trivy.deb https://git.quad4.io/Quad4-Software/Trivy-Assets/raw/commit/917e0e52b2f663cbbe13e63b7176262e248265ae/trivy_0.68.2_Linux-64bit.deb
|
||||
sudo dpkg -i /tmp/trivy.deb || sudo apt-get install -f -y
|
||||
|
||||
- name: Scan Docker image
|
||||
run: |
|
||||
# Extract the first tag from the multi-line tags output
|
||||
IMAGE_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n 1)
|
||||
trivy image --exit-code 1 "$IMAGE_TAG"
|
||||
|
||||
build-dev:
|
||||
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: https://git.quad4.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: https://git.quad4.io/actions/setup-qemu-action@3a1695b1353f9f8868722ffaafc1f164ef35fa5e # v3
|
||||
with:
|
||||
platforms: amd64,arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: https://git.quad4.io/actions/setup-buildx-action@7fbd262f0ca05b45700d8eaaf71f40837d036cc7 # v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: https://git.quad4.io/actions/login-action@bb91d7e20cfedb030a44164cb558bba899e1010a # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Extract DEV metadata (tags, labels) for Docker
|
||||
id: meta-dev
|
||||
uses: https://git.quad4.io/actions/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.DEV_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=dev
|
||||
type=sha,format=short
|
||||
|
||||
- name: Build and push dev Docker image
|
||||
id: build-dev
|
||||
uses: https://git.quad4.io/actions/build-push-action@dc0c2d97df39a6939d9db7d572445529e2365ec6 # v6
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
no-cache: true
|
||||
tags: ${{ steps.meta-dev.outputs.tags }}
|
||||
labels: ${{ steps.meta-dev.outputs.labels }}
|
||||
build-args: |
|
||||
BUILD_DATE=${{ github.event.head_commit.timestamp }}
|
||||
VCS_REF=${{ github.sha }}
|
||||
VERSION=${{ steps.meta-dev.outputs.version }}
|
||||
|
||||
- name: Download Trivy
|
||||
run: |
|
||||
curl -L -o /tmp/trivy.deb https://git.quad4.io/Quad4-Software/Trivy-Assets/raw/commit/917e0e52b2f663cbbe13e63b7176262e248265ae/trivy_0.68.2_Linux-64bit.deb
|
||||
sudo dpkg -i /tmp/trivy.deb || sudo apt-get install -f -y
|
||||
|
||||
- name: Scan Docker image (dev)
|
||||
run: |
|
||||
# Extract the first tag from the multi-line tags output
|
||||
IMAGE_TAG=$(echo "${{ steps.meta-dev.outputs.tags }}" | head -n 1)
|
||||
trivy image --exit-code 1 "$IMAGE_TAG"
|
||||
@@ -0,0 +1,67 @@
|
||||
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:
|
||||
release:
|
||||
name: Build and Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
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 build and twine
|
||||
run: python3 -m pip install build twine --user
|
||||
|
||||
- name: Build a binary wheel and a source tarball
|
||||
run: python3 -m build
|
||||
|
||||
- name: Generate SHA256 checksums
|
||||
run: |
|
||||
sha256sum dist/*.tar.gz dist/*.whl | sed 's|dist/||g' > dist/SHA256SUMS
|
||||
echo "### SHA256 Checksums" > release_notes.md
|
||||
echo '```' >> release_notes.md
|
||||
cat dist/SHA256SUMS >> release_notes.md
|
||||
echo '```' >> release_notes.md
|
||||
|
||||
- name: Publish to Gitea PyPI registry
|
||||
run: python3 -m twine upload --repository-url ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/pypi -u ${{ secrets.REGISTRY_USERNAME }} -p ${{ secrets.REGISTRY_PASSWORD }} dist/*.tar.gz dist/*.whl
|
||||
continue-on-error: true
|
||||
|
||||
- name: Create Gitea Release with artifacts
|
||||
uses: https://git.quad4.io/actions/gitea-release-action@4875285c0950474efb7ca2df55233c51333eeb74
|
||||
with:
|
||||
tag_name: ${{ inputs.version || github.ref_name }}
|
||||
name: Release ${{ inputs.version || github.ref_name }}
|
||||
body_path: release_notes.md
|
||||
files: |
|
||||
dist/*.tar.gz
|
||||
dist/*.whl
|
||||
dist/SHA256SUMS
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Safety
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
schedule:
|
||||
- cron: "0 0 * * 0" # weekly
|
||||
workflow_dispatch:
|
||||
|
||||
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.13"
|
||||
|
||||
- name: Install Poetry
|
||||
run: |
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry config virtualenvs.create false
|
||||
poetry install --no-interaction --no-ansi
|
||||
pip install --upgrade filelock virtualenv
|
||||
|
||||
- name: Run pip-audit
|
||||
uses: https://git.quad4.io/actions/gh-action-pip-audit@66a6ee35b1b25f89c6bdc9f7c11284f08061823a # v1.1.0
|
||||
|
||||
- name: Download Trivy
|
||||
run: |
|
||||
curl -L -o /tmp/trivy.deb https://git.quad4.io/Quad4-Software/Trivy-Assets/raw/commit/917e0e52b2f663cbbe13e63b7176262e248265ae/trivy_0.68.2_Linux-64bit.deb
|
||||
sudo dpkg -i /tmp/trivy.deb || sudo apt-get install -f -y
|
||||
|
||||
- name: Scan Repository
|
||||
run: |
|
||||
trivy fs --exit-code 1 .
|
||||
Vendored
-27
@@ -1,27 +0,0 @@
|
||||
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 }}
|
||||
Vendored
-86
@@ -1,86 +0,0 @@
|
||||
name: Build and Publish Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
tags: [ 'v*' ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: amd64,arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=ref,event=branch,prefix=,suffix=,enable={{is_default_branch}}
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,format=short
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker (rootless)
|
||||
id: meta_rootless
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-rootless
|
||||
tags: |
|
||||
type=raw,value=latest-rootless,enable={{is_default_branch}}
|
||||
type=ref,event=branch,prefix=,suffix=-rootless,enable={{is_default_branch}}
|
||||
type=semver,pattern={{version}},suffix=-rootless
|
||||
type=semver,pattern={{major}}.{{minor}},suffix=-rootless
|
||||
type=sha,format=short,suffix=-rootless
|
||||
|
||||
- name: Build and push rootless Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.rootless
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
||||
labels: ${{ steps.meta_rootless.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
Vendored
-100
@@ -1,100 +0,0 @@
|
||||
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"
|
||||
Vendored
+9
@@ -3,3 +3,12 @@ node-config/
|
||||
files/
|
||||
.ruff_cache/
|
||||
__pycache__/
|
||||
dist/
|
||||
*.egg-info/
|
||||
.ruff_cache/
|
||||
.venv/
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
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,18 +0,0 @@
|
||||
FROM python:3.13-alpine AS builder
|
||||
|
||||
RUN apk update
|
||||
RUN apk add --no-cache build-base libffi-dev cargo pkgconfig gcc python3-dev musl-dev linux-headers
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
RUN pip install poetry
|
||||
|
||||
COPY pyproject.toml ./
|
||||
COPY README.md ./
|
||||
COPY rns_page_node ./rns_page_node
|
||||
|
||||
RUN poetry build --format wheel
|
||||
|
||||
FROM scratch AS dist
|
||||
|
||||
COPY --from=builder /src/dist .
|
||||
@@ -1,28 +0,0 @@
|
||||
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"]
|
||||
@@ -1,20 +1,31 @@
|
||||
# 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
|
||||
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")
|
||||
|
||||
.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
|
||||
# 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 help test docker-test test-advanced
|
||||
|
||||
all: build
|
||||
|
||||
build: clean
|
||||
python3 setup.py sdist bdist_wheel
|
||||
poetry run python3 -m build
|
||||
|
||||
sdist:
|
||||
python3 setup.py sdist
|
||||
poetry run python3 -m build --sdist
|
||||
|
||||
wheel:
|
||||
python3 setup.py bdist_wheel
|
||||
poetry run python3 -m build --wheel
|
||||
|
||||
clean:
|
||||
rm -rf build dist *.egg-info
|
||||
@@ -29,35 +40,21 @@ format:
|
||||
ruff check --fix .
|
||||
|
||||
docker-wheels:
|
||||
$(DOCKER_BUILD) --target builder -f Dockerfile.build -t rns-page-node-builder .
|
||||
$(DOCKER_BUILD) --target builder -f docker/Dockerfile -t rns-page-node-builder .
|
||||
docker create --name builder-container rns-page-node-builder true
|
||||
docker cp builder-container:/src/dist ./dist
|
||||
docker cp builder-container:/app/dist ./dist
|
||||
docker rm builder-container
|
||||
|
||||
docker-build:
|
||||
$(DOCKER_BUILD) $(BUILD_ARGS) -f Dockerfile -t rns-page-node:latest .
|
||||
$(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-run:
|
||||
docker-run: setup-dirs
|
||||
docker run --rm -it \
|
||||
-v ./pages:/app/pages \
|
||||
-v ./files:/app/files \
|
||||
-v ./node-config:/app/node-config \
|
||||
rns-page-node:latest \
|
||||
--node-name "Page Node" \
|
||||
--pages-dir /app/pages \
|
||||
--files-dir /app/files \
|
||||
--identity-dir /app/node-config \
|
||||
--announce-interval 360
|
||||
|
||||
docker-build-rootless:
|
||||
$(DOCKER_BUILD) $(BUILD_ARGS) -f Dockerfile.rootless -t rns-page-node-rootless:latest .
|
||||
|
||||
docker-run-rootless:
|
||||
docker run --rm -it \
|
||||
-v ./pages:/app/pages \
|
||||
-v ./files:/app/files \
|
||||
-v ./node-config:/app/node-config \
|
||||
rns-page-node-rootless:latest \
|
||||
-v ./reticulum-config:/home/app/.reticulum \
|
||||
git.quad4.io/rns-things/rns-page-node:latest \
|
||||
--node-name "Page Node" \
|
||||
--pages-dir /app/pages \
|
||||
--files-dir /app/files \
|
||||
@@ -67,10 +64,16 @@ docker-run-rootless:
|
||||
test:
|
||||
bash tests/run_tests.sh
|
||||
|
||||
test-advanced:
|
||||
poetry run python3 tests/test_advanced.py
|
||||
|
||||
docker-test:
|
||||
$(DOCKER_BUILD) -f tests/Dockerfile.tests -t rns-page-node-tests .
|
||||
$(DOCKER_BUILD_LOAD) -f docker/Dockerfile.tests -t rns-page-node-tests .
|
||||
docker run --rm rns-page-node-tests
|
||||
|
||||
setup-dirs:
|
||||
mkdir -p pages files node-config reticulum-config
|
||||
|
||||
help:
|
||||
@echo "Makefile commands:"
|
||||
@echo " all - alias for build"
|
||||
@@ -82,9 +85,8 @@ help:
|
||||
@echo " lint - run ruff linter"
|
||||
@echo " format - run ruff --fix"
|
||||
@echo " docker-wheels - build Python wheels in Docker"
|
||||
@echo " docker-build - build runtime Docker image"
|
||||
@echo " docker-build - build runtime Docker image (version: $(VERSION))"
|
||||
@echo " docker-run - run runtime Docker image"
|
||||
@echo " docker-build-rootless - build rootless runtime Docker image"
|
||||
@echo " docker-run-rootless - run rootless runtime Docker image"
|
||||
@echo " test - run local integration tests"
|
||||
@echo " docker-test - build and run integration tests in Docker"
|
||||
@echo " test-advanced - run advanced tests (smoke, performance, leak, etc)"
|
||||
@@ -1,35 +1,76 @@
|
||||
# RNS Page Node
|
||||
|
||||
[Русский](docs/languages/README.ru.md) | [中文](docs/languages/README.zh.md) | [日本語](docs/languages/README.ja.md) | [Italiano](docs/languages/README.it.md) | [Deutsch](docs/languages/README.de.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.
|
||||
|
||||
## Features
|
||||
|
||||
- Serves pages and files over RNS
|
||||
- Dynamic page support with environment variables
|
||||
- Form data and request parameter parsing
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Pip
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# Pipx via Git
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# UV
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pip install rns-page-node
|
||||
```
|
||||
|
||||
```bash
|
||||
# will use current directory for pages and files
|
||||
rns-page-node
|
||||
```
|
||||
|
||||
## Usage
|
||||
or with command-line options:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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 run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
### Docker/Podman Rootless
|
||||
|
||||
```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 ghcr.io/sudo-ivan/rns-page-node:latest-rootless
|
||||
mkdir -p ./pages ./files ./node-config ./reticulum-config
|
||||
chown -R 1000:1000 ./pages ./files ./node-config ./reticulum-config
|
||||
podman run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
Mounting volumes are optional, you can also copy pages and files to the container `podman cp` or `docker cp`.
|
||||
@@ -54,60 +95,30 @@ make docker-wheels
|
||||
|
||||
## Pages
|
||||
|
||||
Supports Micron `.mu` and dynamic pages with `#!` in the micron files.
|
||||
Supports dynamic executable pages with full request data parsing. Pages can receive:
|
||||
- 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
|
||||
|
||||
## 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
|
||||
This enables forums, chats, and other interactive applications compatible with NomadNet clients.
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
-c, --config: The path to the Reticulum config file.
|
||||
-n, --node-name: The name of the node.
|
||||
-p, --pages-dir: The directory to serve pages from.
|
||||
-f, --files-dir: The directory to serve files from.
|
||||
-i, --identity-dir: The directory to persist the node's identity.
|
||||
-a, --announce-interval: The interval to announce the node's presence.
|
||||
--page-refresh-interval: The interval to refresh pages (seconds, 0 disables).
|
||||
--file-refresh-interval: The interval to refresh files (seconds, 0 disables).
|
||||
-l, --log-level: The logging level.
|
||||
--stats-interval: Print stats every N seconds (0 disables).
|
||||
--save-stats: Save stats to JSON file on shutdown.
|
||||
Positional arguments:
|
||||
node_config Path to rns-page-node config file
|
||||
|
||||
Optional arguments:
|
||||
-c, --config Path to the Reticulum config file
|
||||
-n, --node-name Name of the node
|
||||
-p, --pages-dir Directory to serve pages from
|
||||
-f, --files-dir Directory to serve files from
|
||||
-i, --identity-dir Directory to persist the node's identity
|
||||
-a, --announce-interval Interval to announce the node's presence (in minutes, default: 360 = 6 hours)
|
||||
--page-refresh-interval Interval to refresh pages (in seconds, 0 = disabled)
|
||||
--file-refresh-interval Interval to refresh files (in seconds, 0 = disabled)
|
||||
-l, --log-level Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
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:
|
||||
- poetry run python3 -m build
|
||||
|
||||
sdist:
|
||||
desc: Build source distribution
|
||||
cmds:
|
||||
- poetry run python3 -m build --sdist
|
||||
|
||||
wheel:
|
||||
desc: Build wheel
|
||||
cmds:
|
||||
- poetry run 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
|
||||
|
||||
test-advanced:
|
||||
desc: Run advanced tests (smoke, performance, leak, fuzzing, property-based)
|
||||
cmds:
|
||||
- poetry run python3 tests/test_advanced.py
|
||||
|
||||
docker-wheels:
|
||||
desc: Build Python wheels in Docker
|
||||
cmds:
|
||||
- '{{.DOCKER_BUILD}} --target builder -f docker/Dockerfile -t rns-page-node-builder .'
|
||||
- docker create --name builder-container rns-page-node-builder true
|
||||
- docker cp builder-container:/app/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
|
||||
deps: [setup-dirs]
|
||||
cmds:
|
||||
- >
|
||||
docker run --rm -it
|
||||
-v ./pages:/app/pages
|
||||
-v ./files:/app/files
|
||||
-v ./node-config:/app/node-config
|
||||
-v ./reticulum-config:/home/app/.reticulum
|
||||
{{.IMAGE_NAME}}:latest
|
||||
--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 rns-page-node-builder rns-page-node-tests 2>/dev/null || true
|
||||
|
||||
run:
|
||||
desc: Run rns-page-node locally
|
||||
cmds:
|
||||
- poetry run python3 -m rns_page_node.main
|
||||
|
||||
run-dev:
|
||||
desc: Run rns-page-node with development settings
|
||||
cmds:
|
||||
- poetry run 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 reticulum-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]
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
ARG PYTHON_VERSION=3.13
|
||||
FROM python:${PYTHON_VERSION}-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache \
|
||||
gcc \
|
||||
musl-dev \
|
||||
libffi-dev \
|
||||
cargo \
|
||||
pkgconfig \
|
||||
python3-dev \
|
||||
linux-headers
|
||||
|
||||
RUN pip install --no-cache-dir poetry
|
||||
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml poetry.lock* README.md ./
|
||||
COPY rns_page_node ./rns_page_node
|
||||
|
||||
RUN poetry config virtualenvs.in-project true && \
|
||||
poetry install --no-interaction --no-ansi --only main && \
|
||||
poetry build --format wheel && \
|
||||
.venv/bin/pip install dist/*.whl
|
||||
|
||||
FROM python:${PYTHON_VERSION}-alpine
|
||||
|
||||
ARG VERSION
|
||||
ARG VCS_REF
|
||||
ARG BUILD_DATE
|
||||
|
||||
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"
|
||||
|
||||
RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app && \
|
||||
apk add --no-cache su-exec
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/.venv /app/.venv
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
RUN mkdir -p pages files node-config && \
|
||||
chown -R app:app /app && \
|
||||
mkdir -p /home/app/.reticulum && \
|
||||
chown -R app:app /home/app/.reticulum
|
||||
|
||||
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
VOLUME ["/app/pages", "/app/files", "/app/node-config", "/home/app/.reticulum"]
|
||||
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
CMD ["rns-page-node"]
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.10-slim
|
||||
FROM python:3.14-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y build-essential libssl-dev && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Fix permissions if they are wrong (e.g. volume mounts)
|
||||
# We only do this if we are root
|
||||
if [ "$(id -u)" = '0' ]; then
|
||||
chown -R app:app /app /home/app/.reticulum
|
||||
fi
|
||||
|
||||
# If the first argument is an option (starts with a dash), prepend the app command
|
||||
if [ "${1#-}" != "$1" ]; then
|
||||
set -- rns-page-node "$@"
|
||||
fi
|
||||
|
||||
# If we are root, drop privileges and run the command
|
||||
if [ "$(id -u)" = '0' ]; then
|
||||
exec su-exec app "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
||||
@@ -0,0 +1,126 @@
|
||||
# RNS Page Node
|
||||
|
||||
[English](../../README.md) | [Русский](README.ru.md) | [中文](README.zh.md) | [日本語](README.ja.md) | [Italiano](README.it.md)
|
||||
|
||||
Ein einfacher Weg, um Seiten und Dateien über das [Reticulum-Netzwerk](https://reticulum.network/) bereitzustellen. Drop-in-Ersatz für [NomadNet](https://github.com/markqvist/NomadNet)-Knoten, die hauptsächlich Seiten und Dateien bereitstellen.
|
||||
|
||||
## Funktionen
|
||||
|
||||
- Bereitstellung von Seiten und Dateien über RNS
|
||||
- Unterstützung dynamischer Seiten mit Umgebungsvariablen
|
||||
- Parsing von Formulardaten und Anfrageparametern
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Pip
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# Pipx via Git
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# UV
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
```bash
|
||||
# verwendet das aktuelle Verzeichnis für Seiten und Dateien
|
||||
rns-page-node
|
||||
```
|
||||
|
||||
oder mit Befehlszeilenoptionen:
|
||||
|
||||
```bash
|
||||
rns-page-node --node-name "Page Node" --pages-dir ./pages --files-dir ./files --identity-dir ./node-config --announce-interval 360
|
||||
```
|
||||
|
||||
oder mit einer Konfigurationsdatei:
|
||||
|
||||
```bash
|
||||
rns-page-node /pfad/zur/config.conf
|
||||
```
|
||||
|
||||
### Konfigurationsdatei
|
||||
|
||||
Sie können eine Konfigurationsdatei verwenden, um Einstellungen dauerhaft zu speichern. Siehe `config.example` für ein Beispiel.
|
||||
|
||||
Das Format der Konfigurationsdatei besteht aus einfachen `Schlüssel=Wert`-Paaren:
|
||||
|
||||
```
|
||||
# Kommentarzeilen beginnen mit #
|
||||
node-name=Mein Seitenknoten
|
||||
pages-dir=./pages
|
||||
files-dir=./files
|
||||
identity-dir=./node-config
|
||||
announce-interval=360
|
||||
```
|
||||
|
||||
Prioritätsreihenfolge: Befehlszeilenargumente > Konfigurationsdatei > Standardwerte
|
||||
|
||||
### Docker/Podman
|
||||
|
||||
```bash
|
||||
docker run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
### Docker/Podman Rootless (ohne Root)
|
||||
|
||||
```bash
|
||||
mkdir -p ./pages ./files ./node-config ./reticulum-config
|
||||
chown -R 1000:1000 ./pages ./files ./node-config ./reticulum-config
|
||||
podman run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
Das Einbinden von Volumes ist optional, Sie können Seiten und Dateien auch mit `podman cp` oder `docker cp` in den Container kopieren.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
Wheels bauen:
|
||||
|
||||
```bash
|
||||
make wheel
|
||||
```
|
||||
|
||||
### Build Wheels in Docker
|
||||
|
||||
```bash
|
||||
make docker-wheels
|
||||
```
|
||||
|
||||
## Seiten
|
||||
|
||||
Unterstützt dynamische ausführbare Seiten mit vollständigem Parsing der Anfragedaten. Seiten können Folgendes empfangen:
|
||||
- Formularfelder über `field_*` Umgebungsvariablen
|
||||
- Verknüpfungsvariablen über `var_*` Umgebungsvariablen
|
||||
- Remote-Identität über die Umgebungsvariable `remote_identity`
|
||||
- Link-ID über die Umgebungsvariable `link_id`
|
||||
|
||||
Dies ermöglicht die Erstellung von Foren, Chats und anderen interaktiven Anwendungen, die mit NomadNet-Clients kompatibel sind.
|
||||
|
||||
## Optionen
|
||||
|
||||
```
|
||||
Positionsargumente:
|
||||
node_config Pfad zur rns-page-node-Konfigurationsdatei
|
||||
|
||||
Optionale Argumente:
|
||||
-c, --config Pfad zur Reticulum-Konfigurationsdatei
|
||||
-n, --node-name Name des Knotens
|
||||
-p, --pages-dir Verzeichnis, aus dem Seiten bereitgestellt werden
|
||||
-f, --files-dir Verzeichnis, aus dem Dateien bereitgestellt werden
|
||||
-i, --identity-dir Verzeichnis zum Speichern der Identität des Knotens
|
||||
-a, --announce-interval Intervall zur Bekanntgabe der Anwesenheit des Knotens (in Minuten, Standard: 360 = 6 Stunden)
|
||||
--page-refresh-interval Intervall zum Aktualisieren von Seiten (in Sekunden, 0 = deaktiviert)
|
||||
--file-refresh-interval Intervall zum Aktualisieren von Dateien (in Sekunden, 0 = deaktiviert)
|
||||
-l, --log-level Protokollierungsebene (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
```
|
||||
|
||||
## Lizenz
|
||||
|
||||
Dieses Projekt enthält Teile der Codebasis von [NomadNet](https://github.com/markqvist/NomadNet), die unter der GNU General Public License v3.0 (GPL-3.0) lizenziert ist. Als abgeleitetes Werk wird dieses Projekt ebenfalls unter den Bedingungen der GPL-3.0 verbreitet. Die vollständige Lizenz finden Sie in der Datei [LICENSE](LICENSE).
|
||||
@@ -0,0 +1,126 @@
|
||||
# RNS Page Node
|
||||
|
||||
[English](../../README.md) | [Русский](README.ru.md) | [中文](README.zh.md) | [日本語](README.ja.md) | [Deutsch](README.de.md)
|
||||
|
||||
Un modo semplice per servire pagine e file sulla [rete Reticulum](https://reticulum.network/). Sostituto drop-in per i nodi [NomadNet](https://github.com/markqvist/NomadNet) che servono principalmente pagine e file.
|
||||
|
||||
## Caratteristiche
|
||||
|
||||
- Serve pagine e file su RNS
|
||||
- Supporto per pagine dinamiche con variabili d'ambiente
|
||||
- Parsing dei dati dei moduli e dei parametri di richiesta
|
||||
|
||||
## Installazione
|
||||
|
||||
```bash
|
||||
# Pip
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# Pipx via Git
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# UV
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
```
|
||||
|
||||
## Utilizzo
|
||||
|
||||
```bash
|
||||
# userà la directory corrente per pagine e file
|
||||
rns-page-node
|
||||
```
|
||||
|
||||
o con le opzioni della riga di comando:
|
||||
|
||||
```bash
|
||||
rns-page-node --node-name "Page Node" --pages-dir ./pages --files-dir ./files --identity-dir ./node-config --announce-interval 360
|
||||
```
|
||||
|
||||
o con un file di configurazione:
|
||||
|
||||
```bash
|
||||
rns-page-node /percorso/del/config.conf
|
||||
```
|
||||
|
||||
### File di configurazione
|
||||
|
||||
È possibile utilizzare un file di configurazione per rendere persistenti le impostazioni. Vedere `config.example` per un esempio.
|
||||
|
||||
Il formato del file di configurazione consiste in semplici coppie `chiave=valore`:
|
||||
|
||||
```
|
||||
# Le righe di commento iniziano con #
|
||||
node-name=Mio Nodo Pagina
|
||||
pages-dir=./pages
|
||||
files-dir=./files
|
||||
identity-dir=./node-config
|
||||
announce-interval=360
|
||||
```
|
||||
|
||||
Ordine di priorità: Argomenti della riga di comando > File di configurazione > Predefiniti
|
||||
|
||||
### Docker/Podman
|
||||
|
||||
```bash
|
||||
docker run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
### Docker/Podman Rootless
|
||||
|
||||
```bash
|
||||
mkdir -p ./pages ./files ./node-config ./reticulum-config
|
||||
chown -R 1000:1000 ./pages ./files ./node-config ./reticulum-config
|
||||
podman run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
Il montaggio dei volumi è opzionale, è anche possibile copiare pagine e file nel container con `podman cp` o `docker cp`.
|
||||
|
||||
## Compilazione
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
Costruire le Wheels:
|
||||
|
||||
```bash
|
||||
make wheel
|
||||
```
|
||||
|
||||
### Costruire le Wheels in Docker
|
||||
|
||||
```bash
|
||||
make docker-wheels
|
||||
```
|
||||
|
||||
## Pagine
|
||||
|
||||
Supporta pagine dinamiche eseguibili con parsing completo dei dati di richiesta. Le pagine possono ricevere:
|
||||
- Campi del modulo tramite variabili d'ambiente `field_*`
|
||||
- Variabili di collegamento tramite variabili d'ambiente `var_*`
|
||||
- Identità remota tramite la variabile d'ambiente `remote_identity`
|
||||
- ID collegamento tramite la variabile d'ambiente `link_id`
|
||||
|
||||
Ciò consente la creazione di forum, chat e altre applicazioni interattive compatibili con i client NomadNet.
|
||||
|
||||
## Opzioni
|
||||
|
||||
```
|
||||
Argomenti posizionali:
|
||||
node_config Percorso del file di configurazione di rns-page-node
|
||||
|
||||
Argomenti opzionali:
|
||||
-c, --config Percorso del file di configurazione di Reticulum
|
||||
-n, --node-name Nome del nodo
|
||||
-p, --pages-dir Directory da cui servire le pagine
|
||||
-f, --files-dir Directory da cui servire i file
|
||||
-i, --identity-dir Directory per rendere persistente l'identità del nodo
|
||||
-a, --announce-interval Intervallo per annunciare la presenza del nodo (in minuti, predefinito: 360 = 6 ore)
|
||||
--page-refresh-interval Intervallo per aggiornare le pagine (in secondi, 0 = disabilitato)
|
||||
--file-refresh-interval Intervallo per aggiornare i file (in secondi, 0 = disabilitato)
|
||||
-l, --log-level Livello di logging (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
```
|
||||
|
||||
## Licenza
|
||||
|
||||
Questo progetto incorpora parti della base di codice di [NomadNet](https://github.com/markqvist/NomadNet), che è concesso in licenza con la GNU General Public License v3.0 (GPL-3.0). Come opera derivata, questo progetto è distribuito anche secondo i termini della licenza GPL-3.0. Vedere il file [LICENSE](LICENSE) per la licenza completa.
|
||||
@@ -0,0 +1,126 @@
|
||||
# RNS Page Node
|
||||
|
||||
[English](../../README.md) | [Русский](README.ru.md) | [中文](README.zh.md) | [Italiano](README.it.md) | [Deutsch](README.de.md)
|
||||
|
||||
[Reticulum ネットワーク](https://reticulum.network/)を介してページやファイルを提供するためのシンプルな方法です。主にページやファイルを提供する [NomadNet](https://github.com/markqvist/NomadNet) ノードのドロップイン代替品です。
|
||||
|
||||
## 特徴
|
||||
|
||||
- RNS を介したページおよびファイルの提供
|
||||
- 環境変数による動的ページのサポート
|
||||
- フォームデータとリクエストパラメータの解析
|
||||
|
||||
## インストール
|
||||
|
||||
```bash
|
||||
# Pip
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# Pipx via Git
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# UV
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip 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 /path/to/config.conf
|
||||
```
|
||||
|
||||
### 設定ファイル
|
||||
|
||||
設定を永続化するために設定ファイルを使用できます。例については `config.example` を参照してください。
|
||||
|
||||
設定ファイルの形式は単純な `key=value` のペアです:
|
||||
|
||||
```
|
||||
# # で始まる行はコメントです
|
||||
node-name=My 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 ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
### Docker/Podman ルートレス (Rootless)
|
||||
|
||||
```bash
|
||||
mkdir -p ./pages ./files ./node-config ./reticulum-config
|
||||
chown -R 1000:1000 ./pages ./files ./node-config ./reticulum-config
|
||||
podman run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
ボリュームのマウントはオプションです。`podman cp` または `docker cp` を使用してページやファイルをコンテナにコピーすることもできます。
|
||||
|
||||
## ビルド
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
Wheels のビルド:
|
||||
|
||||
```bash
|
||||
make wheel
|
||||
```
|
||||
|
||||
### Docker での Wheels のビルド
|
||||
|
||||
```bash
|
||||
make docker-wheels
|
||||
```
|
||||
|
||||
## ページ
|
||||
|
||||
完全なリクエストデータ解析を備えた動的実行可能ページをサポートします。ページは以下を受け取ることができます:
|
||||
- `field_*` 環境変数を介したフォームフィールド
|
||||
- `var_*` 環境変数を介したリンク変数
|
||||
- `remote_identity` 環境変数を介したリモート ID
|
||||
- `link_id` 環境変数を介したリンク ID
|
||||
|
||||
これにより、NomadNet クライアントと互換性のあるフォーラム、チャット、その他のインタラクティブなアプリケーションの作成が可能になります。
|
||||
|
||||
## オプション
|
||||
|
||||
```
|
||||
位置引数:
|
||||
node_config rns-page-node 設定ファイルのパス
|
||||
|
||||
オプション引数:
|
||||
-c, --config Reticulum 設定ファイルのパス
|
||||
-n, --node-name ノードの名前
|
||||
-p, --pages-dir ページを提供するディレクトリ
|
||||
-f, --files-dir ファイルを提供するディレクトリ
|
||||
-i, --identity-dir ノードの ID を永続化するディレクトリ
|
||||
-a, --announce-interval ノードの存在をアナウンスする間隔(分単位、デフォルト:360 = 6 時間)
|
||||
--page-refresh-interval ページを更新する間隔(秒単位、0 = 無効)
|
||||
--file-refresh-interval ファイルを更新する間隔(秒単位、0 = 無効)
|
||||
-l, --log-level ログレベル (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
```
|
||||
|
||||
## ライセンス
|
||||
|
||||
このプロジェクトには、GNU General Public License v3.0 (GPL-3.0) の下でライセンスされている [NomadNet](https://github.com/markqvist/NomadNet) コードベースの一部が組み込まれています。派生作品として、このプロジェクトも GPL-3.0 の条項に基づいて配布されます。完全なライセンスについては、[LICENSE](LICENSE) ファイルを参照してください。
|
||||
@@ -0,0 +1,118 @@
|
||||
# RNS Page Node
|
||||
|
||||
[English](../../README.md) | [中文](README.zh.md) | [日本語](README.ja.md) | [Italiano](README.it.md) | [Deutsch](README.de.md)
|
||||
|
||||
Простой способ для раздачи страниц и файлов через сеть [Reticulum](https://reticulum.network/). Прямая замена для узлов [NomadNet](https://github.com/markqvist/NomadNet), которые в основном служат для раздачи страниц и файлов.
|
||||
|
||||
## Особенности
|
||||
|
||||
- Раздача страниц и файлов через RNS
|
||||
- Поддержка динамических страниц с переменными окружения
|
||||
- Разбор данных форм и параметров запросов
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
# Pip
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# Pipx через Git
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# uv
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip 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 ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
### Docker/Podman без root-доступа
|
||||
```bash
|
||||
mkdir -p ./pages ./files ./node-config ./reticulum-config
|
||||
chown -R 1000:1000 ./pages ./files ./node-config ./reticulum-config
|
||||
podman run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
Монтирование томов необязательно, вы также можете скопировать страницы и файлы в контейнер с помощью `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).
|
||||
@@ -0,0 +1,126 @@
|
||||
# RNS Page Node
|
||||
|
||||
[English](../../README.md) | [Русский](README.ru.md) | [日本語](README.ja.md) | [Italiano](README.it.md) | [Deutsch](README.de.md)
|
||||
|
||||
一种通过 [Reticulum 网络](https://reticulum.network/) 提供页面和文件的简单方法。主要用于提供页面和文件的 [NomadNet](https://github.com/markqvist/NomadNet) 节点的掉入式替代方案。
|
||||
|
||||
## 特性
|
||||
|
||||
- 通过 RNS 提供页面和文件
|
||||
- 支持带有环境变量的动态页面
|
||||
- 表单数据和请求参数解析
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# Pip
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# Pipx via Git
|
||||
pipx install git+https://git.quad4.io/RNS-Things/rns-page-node.git
|
||||
# UV
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip 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 /path/to/config.conf
|
||||
```
|
||||
|
||||
### 配置文件
|
||||
|
||||
您可以使用配置文件来持久化设置。请参阅 `config.example` 获取示例。
|
||||
|
||||
配置文件格式为简单的 `key=value` 键值对:
|
||||
|
||||
```
|
||||
# 以 # 开头的行为注释
|
||||
node-name=我的页面节点
|
||||
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 ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
### Docker/Podman 无根模式 (Rootless)
|
||||
|
||||
```bash
|
||||
mkdir -p ./pages ./files ./node-config ./reticulum-config
|
||||
chown -R 1000:1000 ./pages ./files ./node-config ./reticulum-config
|
||||
podman run -it --rm -v ./pages:/app/pages -v ./files:/app/files -v ./node-config:/app/node-config -v ./reticulum-config:/home/app/.reticulum git.quad4.io/rns-things/rns-page-node:latest
|
||||
```
|
||||
|
||||
挂载卷是可选的,您也可以使用 `podman cp` 或 `docker cp` 将页面和文件复制到容器中。
|
||||
|
||||
## 编译
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
编译 Wheels:
|
||||
|
||||
```bash
|
||||
make wheel
|
||||
```
|
||||
|
||||
### 在 Docker 中编译 Wheels
|
||||
|
||||
```bash
|
||||
make docker-wheels
|
||||
```
|
||||
|
||||
## 页面
|
||||
|
||||
支持具有完整请求数据解析的动态可执行页面。页面可以接收:
|
||||
- 通过 `field_*` 环境变量接收表单字段
|
||||
- 通过 `var_*` 环境变量接收链接变量
|
||||
- 通过 `remote_identity` 环境变量接收远程身份
|
||||
- 通过 `link_id` 环境变量接收链接 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) 文件。
|
||||
Generated
+61
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
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
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Generated
+622
-1149
File diff suppressed because it is too large
Load Diff
+14
-6
@@ -1,16 +1,25 @@
|
||||
[project]
|
||||
name = "rns-page-node"
|
||||
version = "0.2.0"
|
||||
version = "1.3.1"
|
||||
license = "GPL-3.0-only"
|
||||
description = "A simple way to serve pages and files over the Reticulum network."
|
||||
authors = [
|
||||
{name = "Sudo-Ivan"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.9.2"
|
||||
dependencies = [
|
||||
"rns (>=1.0.0,<1.5.0)"
|
||||
"rns (>=1.1.2,<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]
|
||||
rns-page-node = "rns_page_node.main:main"
|
||||
@@ -20,6 +29,5 @@ requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "^0.12.3"
|
||||
safety = "^3.6.0"
|
||||
|
||||
ruff = "^0.14.10"
|
||||
twine = "^6.2.0"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||
}
|
||||
+6
-1
@@ -1 +1,6 @@
|
||||
rns=1.0.0
|
||||
cffi==2.0.0 ; python_full_version >= "3.9.2" and platform_python_implementation != "PyPy"
|
||||
cryptography==46.0.3 ; python_full_version >= "3.9.2"
|
||||
pycparser==2.23 ; platform_python_implementation != "PyPy" and implementation_name != "PyPy" and python_full_version >= "3.9.2"
|
||||
pyserial==3.5 ; python_full_version >= "3.9.2"
|
||||
rns==1.1.2 ; python_full_version >= "3.9.2"
|
||||
typing-extensions==4.15.0 ; python_full_version >= "3.9.2" and python_full_version < "3.11.0"
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
# rns_page_node package
|
||||
__all__ = ['main']
|
||||
"""RNS Page Node package.
|
||||
|
||||
A minimal Reticulum page node that serves .mu pages and files over RNS.
|
||||
"""
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
+474
-517
File diff suppressed because it is too large
Load Diff
@@ -1,31 +1,27 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
with open('README.md', 'r', encoding='utf-8') as fh:
|
||||
long_description = fh.read()
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name='rns-page-node',
|
||||
version='0.2.0',
|
||||
author='Sudo-Ivan',
|
||||
author_email='',
|
||||
description='A simple way to serve pages and files over the Reticulum network.',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://github.com/Sudo-Ivan/rns-page-node',
|
||||
name="rns-page-node",
|
||||
version="1.3.1",
|
||||
description="A simple way to serve pages and files over the Reticulum network.",
|
||||
long_description=open("README.md").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
author="Sudo-Ivan",
|
||||
url="https://git.quad4.io/RNS-Things/rns-page-node",
|
||||
packages=find_packages(),
|
||||
license="GPL-3.0",
|
||||
python_requires='>=3.10',
|
||||
install_requires=[
|
||||
'rns>=1.0.0,<1.5.0',
|
||||
"rns>=1.1.2,<1.5.0",
|
||||
"cryptography>=46.0.3",
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'rns-page-node=rns_page_node.main:main',
|
||||
"console_scripts": [
|
||||
"rns-page-node=rns_page_node.main:main",
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||
'Operating System :: OS Independent',
|
||||
"Programming Language :: Python :: 3",
|
||||
"Operating System :: OS Independent",
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
],
|
||||
python_requires=">=3.9.2",
|
||||
)
|
||||
|
||||
Regular → Executable
+31
-5
@@ -9,17 +9,39 @@ rm -rf config node-config pages files node.log
|
||||
mkdir -p config node-config pages files
|
||||
|
||||
# Create a sample page and a test file
|
||||
cat > pages/index.mu << EOF
|
||||
>Test Page
|
||||
This is a test page.
|
||||
cat > pages/index.mu << 'EOF'
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
|
||||
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
|
||||
|
||||
chmod +x pages/index.mu
|
||||
|
||||
cat > files/text.txt << EOF
|
||||
This is a test file.
|
||||
EOF
|
||||
|
||||
# Start the page node in the background
|
||||
python3 ../rns_page_node/main.py -c config -i node-config -p pages -f files > node.log 2>&1 &
|
||||
poetry run python3 ../rns_page_node/main.py -c config -i node-config -p pages -f files > node.log 2>&1 &
|
||||
NODE_PID=$!
|
||||
|
||||
# Wait for node to generate its identity file
|
||||
@@ -38,7 +60,11 @@ if [ ! -f node-config/identity ]; then
|
||||
fi
|
||||
|
||||
# Run the client test
|
||||
python3 test_client.py
|
||||
poetry run python3 test_client.py
|
||||
|
||||
# Run advanced tests
|
||||
echo "Running advanced tests (smoke, performance, leak, fuzzing, property-based)..."
|
||||
poetry run python3 test_advanced.py
|
||||
|
||||
# Clean up
|
||||
kill $NODE_PID
|
||||
Executable
+248
@@ -0,0 +1,248 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import shutil
|
||||
import string
|
||||
import threading
|
||||
import time
|
||||
import tracemalloc
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import RNS
|
||||
|
||||
from rns_page_node.main import PageNode
|
||||
|
||||
|
||||
class AdvancedTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.test_dir = Path("./test_advanced_tmp")
|
||||
cls.test_dir.mkdir(exist_ok=True)
|
||||
cls.pages_dir = cls.test_dir / "pages"
|
||||
cls.files_dir = cls.test_dir / "files"
|
||||
cls.identity_dir = cls.test_dir / "node-config"
|
||||
|
||||
cls.pages_dir.mkdir(exist_ok=True)
|
||||
cls.files_dir.mkdir(exist_ok=True)
|
||||
cls.identity_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Create test files
|
||||
(cls.pages_dir / "index.mu").write_text("Hello World")
|
||||
(cls.files_dir / "test.txt").write_bytes(b"File content")
|
||||
|
||||
# Initialize RNS
|
||||
RNS.Reticulum(str(cls.test_dir / "config"))
|
||||
cls.identity = RNS.Identity()
|
||||
|
||||
cls.node = PageNode(
|
||||
cls.identity,
|
||||
str(cls.pages_dir),
|
||||
str(cls.files_dir),
|
||||
announce_interval=0,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.node.shutdown()
|
||||
# Small sleep to allow threads to exit
|
||||
time.sleep(0.5)
|
||||
shutil.rmtree(cls.test_dir, ignore_errors=True)
|
||||
|
||||
def test_smoke(self):
|
||||
"""Basic smoke test to ensure node is initialized and has handlers."""
|
||||
self.assertIsNotNone(self.node.destination)
|
||||
self.assertTrue(len(self.node.servedpages) >= 1)
|
||||
|
||||
def test_performance(self):
|
||||
"""Measure performance of request handlers."""
|
||||
start_time = time.time()
|
||||
iterations = 100
|
||||
for _ in range(iterations):
|
||||
# Simulate a request to serve_page
|
||||
self.node.serve_page("/page/index.mu", None, None, None, None, None)
|
||||
|
||||
duration = time.time() - start_time
|
||||
avg_time = duration / iterations
|
||||
print(
|
||||
f"\n[Performance] Avg serve_page time: {avg_time:.6f}s over {iterations} iterations",
|
||||
)
|
||||
self.assertLess(avg_time, 0.01, "Performance too slow")
|
||||
|
||||
def test_leaks(self):
|
||||
"""Test for memory and thread leaks."""
|
||||
tracemalloc.start()
|
||||
initial_threads = threading.active_count()
|
||||
|
||||
# Perform some operations
|
||||
for _ in range(50):
|
||||
self.node.register_pages()
|
||||
self.node.register_files()
|
||||
self.node.serve_page("/page/index.mu", None, None, None, None, None)
|
||||
|
||||
current_threads = threading.active_count()
|
||||
_, peak = tracemalloc.get_traced_memory()
|
||||
tracemalloc.stop()
|
||||
|
||||
print(f"\n[Leak Test] Peak memory: {peak / 1024 / 1024:.2f} MB")
|
||||
print(f"[Leak Test] Thread count change: {current_threads - initial_threads}")
|
||||
|
||||
# Allow some thread variation but not excessive growth
|
||||
self.assertLessEqual(
|
||||
current_threads,
|
||||
initial_threads + 5,
|
||||
"Potential thread leak detected",
|
||||
)
|
||||
|
||||
def test_fuzzing(self):
|
||||
"""Fuzz request handlers with random inputs."""
|
||||
print("\n[Fuzzing] Starting fuzzing of request handlers...")
|
||||
for _ in range(100):
|
||||
# Random path fuzzing
|
||||
random_path = "/" + "".join(
|
||||
random.choices(string.ascii_letters + string.digits + "/.", k=20),
|
||||
)
|
||||
# Should not crash
|
||||
res_p = self.node.serve_page(random_path, None, None, None, None, None)
|
||||
res_f = self.node.serve_file(random_path, None, None, None, None, None)
|
||||
|
||||
# Close file handles if returned to avoid ResourceWarnings
|
||||
if isinstance(res_f, list) and len(res_f) > 0 and hasattr(res_f[0], "close"):
|
||||
res_f[0].close()
|
||||
|
||||
# Random data fuzzing
|
||||
random_data = {
|
||||
"field_" + "".join(random.choices(string.ascii_letters, k=5)): "".join(
|
||||
random.choices(string.ascii_letters + string.digits, k=20),
|
||||
)
|
||||
for _ in range(3)
|
||||
}
|
||||
self.node.serve_page("/page/index.mu", random_data, None, None, None, None)
|
||||
|
||||
def test_property_based(self):
|
||||
"""Property-based testing for path traversal and response types."""
|
||||
# Property: serve_page should never return contents from outside pages_dir
|
||||
traversal_paths = [
|
||||
"/page/../../etc/passwd",
|
||||
"/page/../main.py",
|
||||
"/page/./index.mu/../../../",
|
||||
]
|
||||
for path in traversal_paths:
|
||||
response = self.node.serve_page(path, None, None, None, None, None)
|
||||
self.assertIn(
|
||||
b"Not Allowed",
|
||||
response,
|
||||
f"Path traversal succeeded for {path}",
|
||||
)
|
||||
|
||||
# Property: serve_file should always return a list with [fileobj, headers] or bytes
|
||||
response = self.node.serve_file("/file/test.txt", None, None, None, None, None)
|
||||
try:
|
||||
self.assertTrue(isinstance(response, list) or isinstance(response, bytes))
|
||||
if isinstance(response, list):
|
||||
self.assertEqual(len(response), 2)
|
||||
self.assertTrue(hasattr(response[0], "read"))
|
||||
finally:
|
||||
if isinstance(response, list) and len(response) > 0 and hasattr(response[0], "close"):
|
||||
response[0].close()
|
||||
|
||||
def test_property_config_loading(self):
|
||||
"""Property-based testing for configuration loading."""
|
||||
from rns_page_node.main import load_config
|
||||
|
||||
config_file = self.test_dir / "prop_config"
|
||||
|
||||
for _ in range(50):
|
||||
# Generate random valid and invalid config lines
|
||||
expected = {}
|
||||
lines = []
|
||||
for i in range(10):
|
||||
if random.random() > 0.3:
|
||||
# Valid line
|
||||
key = f"key_{i}_{''.join(random.choices(string.ascii_letters, k=5))}"
|
||||
val = f"val_{i}_{''.join(random.choices(string.ascii_letters, k=5))}"
|
||||
lines.append(f"{key} = {val}")
|
||||
expected[key] = val
|
||||
else:
|
||||
# Invalid line (comment or no =)
|
||||
if random.random() > 0.5:
|
||||
lines.append(f"# comment {''.join(random.choices(string.ascii_letters, k=10))}")
|
||||
else:
|
||||
lines.append("".join(random.choices(string.ascii_letters, k=15)))
|
||||
|
||||
config_file.write_text("\n".join(lines))
|
||||
loaded = load_config(str(config_file))
|
||||
self.assertEqual(loaded, expected)
|
||||
|
||||
def test_property_scanning(self):
|
||||
"""Property-based testing for directory scanning."""
|
||||
scan_test_dir = self.test_dir / "scan_test"
|
||||
if scan_test_dir.exists():
|
||||
shutil.rmtree(scan_test_dir)
|
||||
scan_test_dir.mkdir()
|
||||
|
||||
expected_pages = []
|
||||
expected_files = []
|
||||
|
||||
for i in range(20):
|
||||
name = "".join(random.choices(string.ascii_letters, k=8))
|
||||
if random.random() > 0.5:
|
||||
# Page scenario
|
||||
if random.random() > 0.2:
|
||||
# Normal page
|
||||
f = scan_test_dir / f"{name}.mu"
|
||||
f.touch()
|
||||
expected_pages.append(str(f))
|
||||
else:
|
||||
# .allowed file (should be ignored by pages)
|
||||
f = scan_test_dir / f"{name}.allowed"
|
||||
f.touch()
|
||||
else:
|
||||
# File scenario
|
||||
if random.random() > 0.2:
|
||||
# Normal file
|
||||
f = scan_test_dir / name
|
||||
f.touch()
|
||||
expected_files.append(str(f))
|
||||
else:
|
||||
# Hidden file (should be ignored by both)
|
||||
f = scan_test_dir / f".{name}"
|
||||
f.touch()
|
||||
|
||||
# We need to test the methods on a PageNode instance
|
||||
# Pages scan
|
||||
found_pages = self.node._scan_pages(str(scan_test_dir))
|
||||
self.assertCountEqual(found_pages, expected_pages)
|
||||
|
||||
# Files scan (files scan includes .mu files too as they are just files)
|
||||
# but excludes hidden files.
|
||||
found_files = self.node._scan_files(str(scan_test_dir))
|
||||
# Our expected_files only tracked "normal" files, but _scan_files
|
||||
# includes everything that isn't hidden and isn't a directory.
|
||||
actual_expected_files = [str(f) for f in scan_test_dir.iterdir()
|
||||
if not f.name.startswith(".") and f.is_file()]
|
||||
self.assertCountEqual(found_files, actual_expected_files)
|
||||
|
||||
def test_property_script_execution(self):
|
||||
"""Property-based testing for script execution vs reading."""
|
||||
script_path = self.pages_dir / "prop_script.mu"
|
||||
|
||||
# Property: File with shebang AND executable bit -> Executed
|
||||
script_path.write_text("#!/bin/sh\necho 'script output'")
|
||||
script_path.chmod(0o755)
|
||||
response = self.node.serve_page("/page/prop_script.mu", None, None, None, None, None)
|
||||
self.assertEqual(response.strip(), b"script output")
|
||||
|
||||
# Property: File with shebang but NO executable bit -> Read as text
|
||||
script_path.chmod(0o644)
|
||||
response = self.node.serve_page("/page/prop_script.mu", None, None, None, None, None)
|
||||
self.assertIn(b"#!/bin/sh", response)
|
||||
|
||||
# Property: File without shebang -> Read as text even if executable
|
||||
script_path.write_text("plain text content")
|
||||
script_path.chmod(0o755)
|
||||
response = self.node.serve_page("/page/prop_script.mu", None, None, None, None, None)
|
||||
self.assertEqual(response, b"plain text content")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+160
-35
@@ -1,20 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import time
|
||||
|
||||
import RNS
|
||||
|
||||
# Determine base directory for tests
|
||||
dir_path = os.path.abspath(os.path.dirname(__file__))
|
||||
config_dir = os.path.join(dir_path, 'config')
|
||||
identity_dir = os.path.join(dir_path, 'node-config')
|
||||
config_dir = os.path.join(dir_path, "config")
|
||||
identity_dir = os.path.join(dir_path, "node-config")
|
||||
|
||||
# Initialize Reticulum with shared config
|
||||
RNS.Reticulum(config_dir)
|
||||
|
||||
# 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)
|
||||
|
||||
# Create a destination to the server node
|
||||
@@ -22,8 +23,8 @@ destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
'nomadnetwork',
|
||||
'node'
|
||||
"nomadnetwork",
|
||||
"node",
|
||||
)
|
||||
|
||||
# Ensure we know a path to the destination
|
||||
@@ -39,66 +40,190 @@ global_link = RNS.Link(destination)
|
||||
responses = {}
|
||||
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
|
||||
def on_page(response):
|
||||
data = response.response
|
||||
if isinstance(data, bytes):
|
||||
text = data.decode('utf-8')
|
||||
text = data.decode("utf-8")
|
||||
else:
|
||||
text = str(data)
|
||||
print('Received page:')
|
||||
print("Received page (no data):")
|
||||
print(text)
|
||||
responses['page'] = text
|
||||
if 'file' in responses:
|
||||
responses["page"] = text
|
||||
check_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()
|
||||
|
||||
|
||||
# Callback for file response
|
||||
def on_file(response):
|
||||
data = response.response
|
||||
# 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
|
||||
file_data = fileobj.read()
|
||||
filename = headers.get(b'name', b'').decode('utf-8')
|
||||
print(f'Received file ({filename}):')
|
||||
print(file_data.decode('utf-8'))
|
||||
responses['file'] = file_data.decode('utf-8')
|
||||
filename = headers.get(b"name", b"").decode("utf-8")
|
||||
print(f"Received file ({filename}):")
|
||||
print(file_data.decode("utf-8"))
|
||||
responses["file"] = file_data.decode("utf-8")
|
||||
# Handle response as a raw file object
|
||||
elif hasattr(data, 'read'):
|
||||
elif hasattr(data, "read"):
|
||||
file_data = data.read()
|
||||
filename = os.path.basename('text.txt')
|
||||
print(f'Received file ({filename}):')
|
||||
print(file_data.decode('utf-8'))
|
||||
responses['file'] = file_data.decode('utf-8')
|
||||
filename = os.path.basename("text.txt")
|
||||
print(f"Received file ({filename}):")
|
||||
print(file_data.decode("utf-8"))
|
||||
responses["file"] = file_data.decode("utf-8")
|
||||
# Handle response as raw bytes
|
||||
elif isinstance(data, bytes):
|
||||
text = data.decode('utf-8')
|
||||
print('Received file:')
|
||||
text = data.decode("utf-8")
|
||||
print("Received file:")
|
||||
print(text)
|
||||
responses['file'] = text
|
||||
responses["file"] = text
|
||||
else:
|
||||
print('Received file (unhandled format):', data)
|
||||
responses['file'] = str(data)
|
||||
if 'page' in responses:
|
||||
done_event.set()
|
||||
print("Received file (unhandled format):", data)
|
||||
responses["file"] = str(data)
|
||||
check_responses()
|
||||
|
||||
# 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):
|
||||
link.request('/page/index.mu', None, response_callback=on_page)
|
||||
link.request('/file/text.txt', None, response_callback=on_file)
|
||||
# Test page without data
|
||||
link.request("/page/index.mu", None, response_callback=on_page)
|
||||
# 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
|
||||
global_link.set_link_established_callback(on_link_established)
|
||||
global_link.set_link_closed_callback(lambda l: done_event.set())
|
||||
global_link.set_link_closed_callback(lambda link: done_event.set())
|
||||
|
||||
# Wait for responses or timeout
|
||||
if not done_event.wait(timeout=30):
|
||||
print('Test timed out.', file=sys.stderr)
|
||||
print("Test timed out.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if responses.get('page') and responses.get('file'):
|
||||
print('Tests passed!')
|
||||
|
||||
# Validate test results
|
||||
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)
|
||||
else:
|
||||
print('Tests failed.', file=sys.stderr)
|
||||
print("Tests failed.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
+22
-12
@@ -1,20 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import time
|
||||
|
||||
import RNS
|
||||
|
||||
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)
|
||||
|
||||
DESTINATION_HEX = '49b2d959db8528347d0a38083aec1042' # Ivans Node that runs rns-page-node
|
||||
DESTINATION_HEX = (
|
||||
"49b2d959db8528347d0a38083aec1042" # Ivans Node that runs rns-page-node
|
||||
)
|
||||
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH // 8) * 2
|
||||
if len(DESTINATION_HEX) != dest_len:
|
||||
print(f"Invalid destination length (got {len(DESTINATION_HEX)}, expected {dest_len})", file=sys.stderr)
|
||||
print(
|
||||
f"Invalid destination length (got {len(DESTINATION_HEX)}, expected {dest_len})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
destination_hash = bytes.fromhex(DESTINATION_HEX)
|
||||
|
||||
@@ -31,29 +37,33 @@ destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
'nomadnetwork',
|
||||
'node'
|
||||
"nomadnetwork",
|
||||
"node",
|
||||
)
|
||||
link = RNS.Link(destination)
|
||||
|
||||
done_event = threading.Event()
|
||||
|
||||
|
||||
def on_page(response):
|
||||
data = response.response
|
||||
if isinstance(data, bytes):
|
||||
text = data.decode('utf-8')
|
||||
text = data.decode("utf-8")
|
||||
else:
|
||||
text = str(data)
|
||||
print('Fetched page content:')
|
||||
print("Fetched page content:")
|
||||
print(text)
|
||||
done_event.set()
|
||||
|
||||
link.set_link_established_callback(lambda l: l.request('/page/index.mu', None, response_callback=on_page))
|
||||
link.set_link_closed_callback(lambda l: done_event.set())
|
||||
|
||||
link.set_link_established_callback(
|
||||
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):
|
||||
print('Timed out waiting for page', file=sys.stderr)
|
||||
print("Timed out waiting for page", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print('Done fetching page.')
|
||||
print("Done fetching page.")
|
||||
sys.exit(0)
|
||||
|
||||
Reference in New Issue
Block a user