Compare commits

...

148 Commits

Author SHA1 Message Date
185db82bf0 Update Docker setup by adding reticulum-config volume and creating setup-dirs task for required directories
All checks were successful
Safety / security (push) Successful in 24s
Create Release / Build and Release (push) Successful in 7s
Build and Publish Docker Image / build (push) Successful in 11m14s
Build and Publish Docker Image / build-dev (push) Has been skipped
2026-01-14 15:13:35 -06:00
86ddce80db Add entrypoint script for Docker container to manage permissions and command execution 2026-01-14 15:13:27 -06:00
d1676ce5ec Update Dockerfile 2026-01-14 15:13:22 -06:00
ec47ecd872 Update README 2026-01-14 15:09:31 -06:00
ed01ccccbb Update Docker workflow to trigger builds only on version tags and simplify image tagging configuration. 2026-01-14 15:09:20 -06:00
9ba9d277c2 Update requirements.txt to specify conditional dependencies for Python 3.9.2 and above, including cffi, pycparser, pyserial, and typing-extensions.
All checks were successful
Safety / security (push) Successful in 23s
Build and Publish Docker Image / build-dev (push) Successful in 4m31s
Build and Publish Docker Image / build (push) Successful in 11m14s
2026-01-14 14:58:08 -06:00
39dac5c2db Upgrade dependencies in Gitea safety workflow by adding filelock and virtualenv installations. 2026-01-14 14:57:54 -06:00
a40ba430b8 Add Trivy integration to Gitea safety workflow for repository scanning
Some checks failed
Safety / security (push) Failing after 22s
Build and Publish Docker Image / build-dev (push) Successful in 4m35s
Build and Publish Docker Image / build (push) Successful in 11m10s
2026-01-14 14:51:26 -06:00
70c8826af0 Update Gitea safety workflow to support both main and master branches, upgrade Python version to 3.13, and integrate Poetry for dependency management.
Some checks failed
Safety / security (push) Failing after 1m50s
Build and Publish Docker Image / build (push) Successful in 5m16s
Build and Publish Docker Image / build-dev (push) Successful in 11m12s
2026-01-14 14:45:07 -06:00
97d978611e update Docker workflow to add dev image and trivy for scanning.
Some checks failed
Safety / security (push) Failing after 8s
Build and Publish Docker Image / build-dev (push) Successful in 6m22s
Build and Publish Docker Image / build (push) Successful in 11m15s
2026-01-14 14:41:08 -06:00
a295a52904 Update package version to 1.3.1 in pyproject.toml and setup.py 2026-01-14 14:40:22 -06:00
64c016250a Remove rootless Docker image build and metadata extraction steps from Gitea workflow 2026-01-14 14:37:54 -06:00
457013b94a Update test suite by adding advanced tests for performance, memory leaks, fuzzing, and property-based testing. Update run_tests.sh to utilize Poetry for running tests and include advanced tests execution. 2026-01-14 14:37:21 -06:00
02af0e1ddf Update file handling in PageNode to only serve .mu files and add error handling for file access 2026-01-14 14:37:10 -06:00
438d12ab71 Refactor Dockerfile 2026-01-14 14:37:02 -06:00
4d1b49daa4 Update README 2026-01-14 14:36:50 -06:00
beab7b2565 Update rns package version to 1.1.2 in setup.py 2026-01-14 14:36:41 -06:00
2998b8d833 Update package versions for rns, jaraco-context, ruff, and urllib3 in poetry.lock, pyproject.toml, and requirements.txt 2026-01-14 14:36:26 -06:00
f09622ae76 Update Makefile and Taskfile 2026-01-14 14:36:18 -06:00
d7efe9de7f Add twine for publishing
All checks were successful
Safety / security (push) Successful in 19s
2026-01-05 10:18:14 -06:00
f49c6293f9 Update publish workflow for Gitea packages
All checks were successful
Safety / security (push) Successful in 9s
2026-01-04 22:20:13 -06:00
1b0aaad689 Update README.md
All checks were successful
Safety / security (push) Successful in 8s
2026-01-04 21:18:18 -06:00
6e28f908be Update rns package version to 1.1.0 in poetry.lock, pyproject.toml, requirements.txt, and setup.py 2026-01-04 21:17:08 -06:00
a32215f434 Update Gitea workflows to use custom action URLs and modify publish process for Gitea releases
All checks were successful
Safety / security (push) Successful in 40s
2025-12-28 23:13:29 -06:00
59e016815b Add Taskfile.yml
Some checks failed
Safety / security (push) Failing after 1s
2025-12-28 23:02:13 -06:00
6b8ce85ea2 Add go-task to buildInputs in flake.nix 2025-12-28 23:02:01 -06:00
62c280daf2 Add flake.lock and update flake.nix to use poetry directly
Some checks failed
Safety / security (push) Failing after 1s
2025-12-28 23:00:00 -06:00
e817238fb9 Remove Renovate workflow configuration file
Some checks failed
Safety / security (push) Failing after 5s
2025-12-28 22:58:39 -06:00
37bc4948d1 Add flake.nix 2025-12-28 22:58:35 -06:00
8080f2855f Merge pull request 'Update ghcr.io/renovatebot/renovate Docker tag to v42.66.11' (#6) from renovate/ghcr.io-renovatebot-renovate-42.x into main
Some checks failed
Safety / security (push) Failing after 2s
Reviewed-on: #6
2025-12-29 04:45:51 +00:00
Renovate Bot
95fa215162 Update ghcr.io/renovatebot/renovate Docker tag to v42.66.11
Some checks failed
Build and Publish Docker Image / build (pull_request) Failing after 2s
2025-12-29 00:02:37 +00:00
ivan
cd08064678 Merge pull request 'Update ghcr.io/renovatebot/renovate Docker tag to v42' (#5) from renovate/ghcr.io-renovatebot-renovate-42.x into main
Some checks failed
Safety / security (push) Failing after 55s
Reviewed-on: #5
2025-12-28 05:30:38 +00:00
Renovate Bot
01b3a54abf Update ghcr.io/renovatebot/renovate Docker tag to v42
All checks were successful
Build and Publish Docker Image / build (pull_request) Successful in 13m17s
2025-12-27 22:30:09 +00:00
ivan
cb41f89cc9 Merge pull request 'Update python Docker tag to v3.14' (#4) from renovate/python-3.x into main
All checks were successful
Safety / security (push) Successful in 41s
Reviewed-on: #4
2025-12-27 20:31:58 +00:00
ivan
d06a93995e Merge branch 'main' into renovate/python-3.x
All checks were successful
Build and Publish Docker Image / build (pull_request) Successful in 9m6s
2025-12-27 20:31:44 +00:00
ivan
dbfe2fd35c Merge pull request 'Update ghcr.io/renovatebot/renovate Docker tag to v37.440.7' (#3) from renovate/ghcr.io-renovatebot-renovate-37.x into main
All checks were successful
Safety / security (push) Successful in 18s
Reviewed-on: #3
2025-12-27 20:31:30 +00:00
Renovate Bot
07754bc9fa Update python Docker tag to v3.14
All checks were successful
Build and Publish Docker Image / build (pull_request) Successful in 10m28s
2025-12-27 20:30:21 +00:00
Renovate Bot
898919e160 Update ghcr.io/renovatebot/renovate Docker tag to v37.440.7
All checks were successful
Build and Publish Docker Image / build (pull_request) Successful in 10m46s
2025-12-27 20:30:18 +00:00
761c1b356c Update Docker workflow image name to use consistent casing for RNS-Things
All checks were successful
Safety / security (push) Successful in 16s
2025-12-27 13:28:30 -06:00
ivan
54b47c8eaf Merge pull request 'Configure Renovate' (#2) from renovate/configure into main
All checks were successful
Safety / security (push) Successful in 25s
Reviewed-on: #2
2025-12-27 19:26:59 +00:00
ivan
55343b7be2 Merge branch 'main' into renovate/configure
Some checks failed
Build and Publish Docker Image / build (pull_request) Has been cancelled
2025-12-27 19:26:36 +00:00
9c6da64cbe Add paths to Renovate workflow for specific dependency files
All checks were successful
Safety / security (push) Successful in 29s
2025-12-27 13:22:30 -06:00
d0a484f692 Remove 'main' and 'master' branch triggers from Docker workflow configuration to streamline push conditions for version tags only.
All checks were successful
Safety / security (push) Successful in 21s
renovate / renovate (push) Successful in 28s
2025-12-27 13:21:28 -06:00
Renovate Bot
31dd0828a2 Add renovate.json
All checks were successful
Build and Publish Docker Image / build (pull_request) Successful in 6m28s
2025-12-27 19:18:45 +00:00
954f6ecd36 Add Renovate workflow configuration for automated dependency updates
All checks were successful
Safety / security (push) Successful in 39s
renovate / renovate (push) Successful in 30s
Build and Publish Docker Image / build (push) Successful in 6m57s
2025-12-27 13:15:02 -06:00
85c8785502 Remove Docker build test workflow file from Gitea CI configuration
Some checks failed
Safety / security (push) Successful in 33s
Build and Publish Docker Image / build (push) Failing after 47s
2025-12-27 13:06:35 -06:00
112348d862 Refactor Docker workflow to conditionally push images based on version tags
Some checks failed
Docker Build Test / build (3.10) (push) Successful in 26s
Docker Build Test / build (3.13) (push) Successful in 31s
Build and Publish Docker Image / build (push) Has been cancelled
Docker Build Test / build (3.11) (push) Successful in 24s
Docker Build Test / build (3.12) (push) Successful in 33s
Docker Build Test / build (3.9) (push) Successful in 28s
Safety / security (push) Successful in 28s
- Updated the push condition for Docker builds to only trigger on version tags.
- Removed caching options from Docker build steps for both standard and rootless images.
2025-12-27 13:05:33 -06:00
4f8f2786ab Update Makefile to include versioning and build metadata for Docker images
- Extracted version, VCS reference, and build date from pyproject.toml for use in Docker builds.
- Updated Docker build commands to incorporate new build arguments for improved image metadata.
- Modified help output to display the current version for the docker-build command.
2025-12-27 13:04:53 -06:00
3e6e078367 Update Docker workflows and Dockerfiles to enhance image metadata and support multiple branches
- Added support for the 'master' branch in the Docker workflow.
- Updated registry and image name in the workflow environment variables.
- Enhanced Dockerfile and Dockerfile.rootless with additional metadata labels including build date, version, and VCS reference.
- Updated action versions for improved compatibility and functionality.
2025-12-27 13:04:42 -06:00
73c9d12f26 Add 'To Do' section in README.md for future improvements
Some checks failed
Docker Build Test / build (3.10) (push) Successful in 48s
Docker Build Test / build (3.11) (push) Successful in 47s
Docker Build Test / build (3.12) (push) Successful in 55s
Docker Build Test / build (3.13) (push) Successful in 53s
Docker Build Test / build (3.9) (push) Successful in 41s
Safety / security (push) Successful in 34s
Build and Publish Docker Image / build (push) Failing after 56s
2025-12-27 12:59:04 -06:00
fc50bc6fb5 Add setup.py 2025-12-27 12:58:18 -06:00
8f1d5ee02a Update Dockerfile and Dockerfile.rootless to reflect new repository location
Some checks failed
Docker Build Test / build (3.10) (push) Successful in 3m0s
Docker Build Test / build (3.12) (push) Successful in 2m58s
Docker Build Test / build (3.13) (push) Successful in 2m56s
Docker Build Test / build (3.11) (push) Successful in 3m7s
Build and Publish Docker Image / build (push) Failing after 54s
Safety / security (push) Successful in 1m3s
Docker Build Test / build (3.9) (push) Successful in 1m27s
2025-12-27 12:53:42 -06:00
86f0a687d2 Update README files to reflect new repository location and Docker image tags 2025-12-27 12:53:36 -06:00
694ab011ec Update Python version constraints in poetry.lock and pyproject.toml to require Python 3.9.2 or higher for better compatibility. 2025-12-27 12:53:22 -06:00
53d74a3732 Move to .gitea from .github workflows 2025-12-27 12:53:16 -06:00
30f050c8d4 Update dependencies in poetry.lock and pyproject.toml
- Upgraded `ruff` from version 0.13.3 to 0.14.10 for improved linting and formatting capabilities.
- Updated project URLs to point to the new repository location.
2025-12-27 12:50:48 -06:00
070157737b Remove GitHub Actions workflow for running tests 2025-12-27 12:47:54 -06:00
8538d9feb3 Update requirements.txt to include cryptography version 46.0.3 2025-12-27 12:47:41 -06:00
3438b271a5 Update dependencies in poetry.lock and pyproject.toml
Some checks failed
Build and Publish Docker Image / build (push) Failing after 3s
Run Tests / test (ubuntu-latest, 3.9) (push) Failing after 3s
Docker Build Test / build (3.10) (push) Successful in 56s
Run Tests / test (windows-latest, 3.13) (push) Has been cancelled
Run Tests / test (windows-latest, 3.9) (push) Has been cancelled
Docker Build Test / build (3.13) (push) Failing after 4s
Docker Build Test / build (3.9) (push) Failing after 4s
Safety / security (push) Failing after 3s
Run Tests / test (ubuntu-latest, 3.10) (push) Failing after 2s
Run Tests / test (ubuntu-latest, 3.11) (push) Failing after 2s
Run Tests / test (ubuntu-latest, 3.12) (push) Failing after 2s
Run Tests / test (ubuntu-latest, 3.13) (push) Failing after 3s
Docker Build Test / build (3.11) (push) Successful in 54s
Docker Build Test / build (3.12) (push) Successful in 52s
Run Tests / test (windows-latest, 3.10) (push) Has been cancelled
Run Tests / test (windows-latest, 3.11) (push) Has been cancelled
Run Tests / test (windows-latest, 3.12) (push) Has been cancelled
- Removed old version of `cryptography` (43.0.3) and updated to version 46.0.3 with adjusted markers.
- Added `typing-extensions` package (4.15.0) to support type hints for Python 3.9+.
- Updated Python version constraints in `pyproject.toml` for better compatibility.
2025-12-02 11:27:36 -06:00
ivan
d6228d6d63 Merge pull request 'refactor-pagenode-logic' (#1) from refactor-pagenode-logic into main
Some checks failed
Docker Build Test / build (3.10) (push) Failing after 3s
Run Tests / test (ubuntu-latest, 3.13) (push) Failing after 2s
Docker Build Test / build (3.11) (push) Failing after 2s
Docker Build Test / build (3.12) (push) Failing after 2s
Docker Build Test / build (3.13) (push) Failing after 2s
Docker Build Test / build (3.9) (push) Failing after 2s
Build and Publish Docker Image / build (push) Failing after 3s
Safety / security (push) Failing after 2s
Run Tests / test (ubuntu-latest, 3.10) (push) Failing after 2s
Run Tests / test (ubuntu-latest, 3.11) (push) Failing after 2s
Run Tests / test (ubuntu-latest, 3.12) (push) Failing after 1s
Run Tests / test (ubuntu-latest, 3.9) (push) Failing after 1s
Run Tests / test (windows-latest, 3.10) (push) Has been cancelled
Run Tests / test (windows-latest, 3.11) (push) Has been cancelled
Run Tests / test (windows-latest, 3.12) (push) Has been cancelled
Run Tests / test (windows-latest, 3.13) (push) Has been cancelled
Run Tests / test (windows-latest, 3.9) (push) Has been cancelled
Reviewed-on: Ivan/rns-page-node#1
2025-12-02 17:26:05 +00:00
ccf954681b Refactor path handling in PageNode class for improved reliability
Some checks failed
Docker Build Test / build (3.11) (pull_request) Successful in 1m9s
Docker Build Test / build (3.10) (pull_request) Successful in 1m12s
Docker Build Test / build (3.12) (pull_request) Successful in 1m8s
Docker Build Test / build (3.13) (pull_request) Failing after 2s
Build and Publish Docker Image / build (pull_request) Failing after 2s
Run Tests / test (windows-latest, 3.10) (pull_request) Has been cancelled
Run Tests / test (windows-latest, 3.11) (pull_request) Has been cancelled
Run Tests / test (windows-latest, 3.12) (pull_request) Has been cancelled
Run Tests / test (windows-latest, 3.13) (pull_request) Has been cancelled
Run Tests / test (windows-latest, 3.9) (pull_request) Has been cancelled
Docker Build Test / build (3.9) (pull_request) Failing after 2s
Run Tests / test (ubuntu-latest, 3.10) (pull_request) Failing after 2s
Run Tests / test (ubuntu-latest, 3.11) (pull_request) Failing after 2s
Run Tests / test (ubuntu-latest, 3.12) (pull_request) Failing after 1s
Run Tests / test (ubuntu-latest, 3.13) (pull_request) Failing after 1s
Run Tests / test (ubuntu-latest, 3.9) (pull_request) Failing after 2s
- Updated path resolution for pages and files to use `resolve()` method, ensuring absolute paths are handled correctly.
- Enhanced relative path calculation using `relative_to()` to improve robustness against invalid paths.
- Adjusted request path formatting to include a leading slash for consistency.
2025-12-02 11:03:58 -06:00
4ec44900cf add windows runner test 2025-12-02 11:02:01 -06:00
d4099fb9a2 Refactor _scan_pages method and enhance file reading logic in PageNode class
- Updated docstring for _scan_pages to clarify exclusion of .allowed files.
- Improved file reading logic to handle script detection and content retrieval more efficiently.
- Refined error handling during the announce process to catch specific exceptions.
2025-12-02 10:17:16 -06:00
1571b315b2 Add docstrings to PageNode methods for improved clarity 2025-12-02 10:06:56 -06:00
71bd49bd7d Refactor PageNode class to improve page and file registration logic
- Consolidated page and file scanning methods to return lists of served pages and files.
- Improved error handling in file reading operations.
- Updated the announce loop to use a more efficient waiting mechanism.
- Improved command-line argument handling for log level configuration.
2025-12-02 09:58:31 -06:00
382413dc08 Update to support immutable github releases/tags
Some checks failed
Docker Build Test / build (3.12) (push) Successful in 31s
Docker Build Test / build (3.13) (push) Successful in 37s
Docker Build Test / build (3.10) (push) Successful in 26s
Docker Build Test / build (3.11) (push) Successful in 25s
Docker Build Test / build (3.9) (push) Successful in 29s
Safety / security (push) Failing after 30s
Run Tests / test (3.10) (push) Successful in 40s
Run Tests / test (3.11) (push) Successful in 31s
Run Tests / test (3.12) (push) Successful in 35s
Build and Publish Docker Image / build (push) Failing after 1m15s
Run Tests / test (3.13) (push) Successful in 25s
Run Tests / test (3.9) (push) Successful in 23s
2025-11-23 11:45:41 -06:00
0621facc7d Add config example
Some checks failed
Docker Build Test / build (3.11) (push) Successful in 1m47s
Docker Build Test / build (3.13) (push) Successful in 1m44s
Docker Build Test / build (3.12) (push) Successful in 1m47s
Docker Build Test / build (3.10) (push) Successful in 1m51s
Docker Build Test / build (3.9) (push) Successful in 32s
Safety / security (push) Failing after 37s
Run Tests / test (3.10) (push) Successful in 1m9s
Run Tests / test (3.12) (push) Successful in 2m10s
Run Tests / test (3.11) (push) Successful in 2m18s
Run Tests / test (3.13) (push) Successful in 1m49s
Run Tests / test (3.9) (push) Successful in 49s
Publish Python 🐍 distribution 📦 to PyPI / Build distribution 📦 (push) Failing after 46s
Publish Python 🐍 distribution 📦 to PyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been skipped
Publish Python 🐍 distribution 📦 to PyPI / Sign the Python 🐍 distribution 📦 and create GitHub Release (push) Has been skipped
Build and Publish Docker Image / build (push) Failing after 1m7s
2025-11-23 11:20:52 -06:00
50cbfed5fa Add configuration loading from file and update CLI argument handling
- Implemented `load_config` function to read key-value pairs from a configuration file.
- Enhanced `main` function to accept a configuration file path as a command-line argument.
- Introduced `get_config_value` function to prioritize values from CLI arguments, config file, or defaults.
- Updated logic to retrieve various configuration settings, including paths and intervals, from the loaded config.
2025-11-23 11:20:41 -06:00
36d9a3350b Update README.ru 2025-11-23 11:20:23 -06:00
515a9d9dbf Update README with command-line options and configuration file details 2025-11-23 11:20:11 -06:00
3c27b4f9b8 Update README files to reflect changes in announce interval 2025-11-23 11:08:36 -06:00
851c8c05d4 Update announce interval documentation and logic in PageNode class to reflect minutes instead of seconds 2025-11-23 11:08:09 -06:00
8002a75e26 Update README.ru 2025-11-23 11:03:41 -06:00
06e6b55ecc Update Makefile 2025-11-23 11:02:29 -06:00
48e47bd0bd Update README 2025-11-23 11:02:22 -06:00
9c074a0333 remove 2025-11-23 11:02:13 -06:00
f2314f862c Update cryptography package to version 46.0.3 in poetry.lock; update rns package to version 1.0.4 in pyproject.toml; add project classifiers and URLs. 2025-11-23 11:02:04 -06:00
6e57536650 Update project version to 1.3.0 and dependencies to rns 1.0.4 in pyproject.toml and requirements.txt 2025-11-23 10:58:55 -06:00
5fd7551874 Update and format test_client.py and test_client2.py for improved readability and structure; added second dictionary data handling in tests. 2025-11-23 10:58:47 -06:00
62d592c4d0 Fix environment variable handling in PageNode class to support forums and chats 2025-11-23 10:58:29 -06:00
8af2a9abbb Update README.ru.md
Some checks failed
Safety / security (push) Failing after 26s
Docker Build Test / build (3.11) (push) Successful in 24s
Docker Build Test / build (3.12) (push) Successful in 29s
Docker Build Test / build (3.13) (push) Successful in 28s
Run Tests / test (3.10) (push) Successful in 27s
Docker Build Test / build (3.10) (push) Successful in 49s
Docker Build Test / build (3.9) (push) Successful in 22s
Run Tests / test (3.11) (push) Successful in 28s
Run Tests / test (3.12) (push) Successful in 28s
Run Tests / test (3.13) (push) Successful in 26s
Build and Publish Docker Image / build (push) Failing after 1m11s
Run Tests / test (3.9) (push) Successful in 24s
2025-11-12 18:51:39 -06:00
64ca8bd4d2 add safety workflow 2025-11-12 18:47:23 -06:00
f1d025bd0e remove old 2025-11-12 18:47:08 -06:00
087ff563a2 update
Some checks failed
Docker Build Test / build (3.10) (push) Failing after 14s
Docker Build Test / build (3.11) (push) Successful in 53s
Docker Build Test / build (3.12) (push) Successful in 1m0s
Docker Build Test / build (3.13) (push) Successful in 58s
Docker Build Test / build (3.9) (push) Successful in 55s
Run Tests / test (3.12) (push) Successful in 51s
Build and Publish Docker Image / build (push) Failing after 1m10s
Run Tests / test (3.13) (push) Successful in 36s
Run Tests / test (3.9) (push) Successful in 32s
Run Tests / test (3.10) (push) Successful in 28s
Run Tests / test (3.11) (push) Successful in 30s
2025-11-12 18:40:01 -06:00
882dacf2bb Update dependencies to rns 1.0.2 in pyproject.toml and requirements.txt; refine README for clarity and usage instructions.
Some checks failed
Docker Build Test / build (3.12) (push) Failing after 1m16s
Docker Build Test / build (3.13) (push) Failing after 1m15s
Docker Build Test / build (3.11) (push) Failing after 1m19s
Docker Build Test / build (3.10) (push) Failing after 1m22s
Docker Build Test / build (3.9) (push) Failing after 26s
Run Tests / test (3.10) (push) Successful in 1m7s
Run Tests / test (3.11) (push) Successful in 1m5s
Run Tests / test (3.13) (push) Has been cancelled
Run Tests / test (3.12) (push) Has been cancelled
Build and Publish Docker Image / build (push) Has been cancelled
Run Tests / test (3.9) (push) Has been cancelled
2025-11-12 18:37:12 -06:00
a2efdb136a Update GitHub Actions workflow to specify Dockerfile path in the build context
Some checks failed
Docker Build Test / build (3.10) (push) Successful in 19s
Docker Build Test / build (3.13) (push) Successful in 27s
Docker Build Test / build (3.9) (push) Successful in 27s
Run Tests / test (3.10) (push) Successful in 37s
Run Tests / test (3.11) (push) Successful in 34s
Run Tests / test (3.12) (push) Successful in 55s
Run Tests / test (3.9) (push) Successful in 35s
Run Tests / test (3.13) (push) Successful in 45s
Publish Python 🐍 distribution 📦 to PyPI / Build distribution 📦 (push) Failing after 36s
Docker Build Test / build (3.11) (push) Successful in 23s
Docker Build Test / build (3.12) (push) Successful in 25s
Publish Python 🐍 distribution 📦 to PyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been skipped
Publish Python 🐍 distribution 📦 to PyPI / Sign the Python 🐍 distribution 📦 and create GitHub Release (push) Has been skipped
Build and Publish Docker Image / build (push) Failing after 55s
2025-11-08 14:08:37 -06:00
001613b4fa add microvm
Some checks failed
Docker Build Test / build (3.10) (push) Successful in 3m4s
Docker Build Test / build (3.13) (push) Successful in 2m58s
Docker Build Test / build (3.11) (push) Successful in 3m3s
Docker Build Test / build (3.12) (push) Successful in 3m18s
Docker Build Test / build (3.9) (push) Successful in 1m32s
Build and Publish Docker Image / build (push) Has been cancelled
Run Tests / test (3.10) (push) Successful in 1m5s
Run Tests / test (3.11) (push) Successful in 1m19s
Run Tests / test (3.9) (push) Has been cancelled
Run Tests / test (3.12) (push) Has been cancelled
Run Tests / test (3.13) (push) Has been cancelled
2025-11-08 14:03:00 -06:00
74564d0ef2 Update Makefile to use docker buildx load command for building images and adjust Dockerfile paths to the new 'docker' directory. 2025-11-08 14:02:51 -06:00
81142ad194 Refactor GitHub Actions workflows to use Dockerfiles from the new 'docker' directory and add a new workflow for running tests across multiple Python versions. 2025-11-08 14:02:37 -06:00
fee1d2e2d6 Update package version to 1.2.0 and dependencies to rns 1.0.1 in pyproject.toml, requirements.txt, and setup.py; adjust poetry.lock accordingly. 2025-11-08 14:02:25 -06:00
7c93fdb71d move dockerfiles to docker folder 2025-11-08 14:02:06 -06:00
9e435eeebc Update test scripts to support environment variable processing and validate responses for different data types.
Some checks failed
Docker Build Test / build (3.11) (push) Successful in 9s
Docker Build Test / build (3.13) (push) Successful in 10s
Docker Build Test / build (3.12) (push) Successful in 39s
Docker Build Test / build (3.10) (push) Successful in 42s
Docker Build Test / build (3.9) (push) Successful in 38s
Build and Publish Docker Image / build (push) Failing after 9m35s
2025-10-05 16:41:01 -05:00
5dfcc1f2ce Improve data processing in PageNode class to handle both dictionary and bytes input.
Some checks failed
Docker Build Test / build (3.10) (push) Successful in 11s
Docker Build Test / build (3.12) (push) Successful in 11s
Docker Build Test / build (3.13) (push) Successful in 35s
Docker Build Test / build (3.11) (push) Successful in 37s
Docker Build Test / build (3.9) (push) Successful in 37s
Build and Publish Docker Image / build (push) Has been cancelled
2025-10-05 16:35:38 -05:00
2def60b457 Update GitHub Actions workflow to include Python 3.9 in the testing matrix
Some checks failed
Docker Build Test / build (3.11) (push) Successful in 28s
Docker Build Test / build (3.13) (push) Successful in 27s
Docker Build Test / build (3.10) (push) Successful in 3m19s
Docker Build Test / build (3.9) (push) Successful in 3m15s
Docker Build Test / build (3.12) (push) Successful in 3m26s
Build and Publish Docker Image / build (push) Failing after 9m33s
2025-10-05 16:10:18 -05:00
f708ad4ee1 Update python version requirement in setup.py to 3.9 2025-10-05 16:10:12 -05:00
f7568d81aa Adjust python version requirement to 3.9 2025-10-05 16:10:05 -05:00
251f9bacef Update .gitignore 2025-10-05 16:08:49 -05:00
07892dbfee Update README
Some checks failed
Docker Build Test / build (3.12) (push) Successful in 32s
Docker Build Test / build (3.10) (push) Successful in 36s
Docker Build Test / build (3.13) (push) Successful in 2m54s
Docker Build Test / build (3.11) (push) Successful in 2m57s
Build and Publish Docker Image / build (push) Has been cancelled
2025-10-05 16:04:33 -05:00
54e6849968 Improve path resolution in PageNode class to ensure security by validating file paths before serving. 2025-10-05 16:02:12 -05:00
ea27c380cb update version to 1.1.0 in setup.py 2025-10-05 15:49:10 -05:00
Sudo-Ivan
a338be85e1 update uv commands
Some checks failed
Docker Build Test / build (3.11) (push) Successful in 17s
Docker Build Test / build (3.12) (push) Successful in 16s
Docker Build Test / build (3.13) (push) Successful in 17s
Docker Build Test / build (3.10) (push) Successful in 40s
Build and Publish Docker Image / build (push) Failing after 9m34s
2025-10-01 03:03:53 -05:00
Sudo-Ivan
e31cb3418b Update
Some checks failed
Docker Build Test / build (3.11) (push) Successful in 34s
Docker Build Test / build (3.13) (push) Successful in 33s
Docker Build Test / build (3.12) (push) Has been cancelled
Docker Build Test / build (3.10) (push) Has been cancelled
Build and Publish Docker Image / build (push) Has been cancelled
2025-10-01 03:02:14 -05:00
Sudo-Ivan
798725dca6 Update
Some checks failed
Docker Build Test / build (3.12) (push) Successful in 51s
Docker Build Test / build (3.10) (push) Successful in 1m3s
Docker Build Test / build (3.11) (push) Successful in 1m38s
Docker Build Test / build (3.13) (push) Successful in 1m36s
Build and Publish Docker Image / build (push) Failing after 10m2s
2025-09-30 21:43:41 -05:00
Sudo-Ivan
6f393497f0 Add docstring 2025-09-30 21:37:51 -05:00
Sudo-Ivan
14b5aabf2b Improved PageNode class with documentation, error handling, and path management.
Update file and page serving methods to utilize pathlib for modern python path handling
2025-09-30 21:37:41 -05:00
fb36907447 Improve path handling in PageNode class to ensure consistent formatting of served pages and files. 2025-09-23 04:13:37 -05:00
62fde2617b Fix remote_identity assignment in PageNode class to use hash attribute 2025-09-23 03:00:06 -05:00
9f5ea23eb7 Improve request data parsing in PageNode class to support '|' delimiter and add handling for additional fields 2025-09-23 02:42:54 -05:00
19fad61706 Add Micron interactive features via environment variables 2025-09-23 02:28:55 -05:00
c900cf38c9 Bump to 1.1.0 2025-09-23 01:59:01 -05:00
014ebc25c6 Update default home page message and print node address in terminal output. 2025-09-23 01:57:19 -05:00
Sudo-Ivan
d5e9308fb5 Update GitHub Actions workflows to use updated action versions
- Updated actions/checkout to v5.0.0
- Updated actions/setup-python to v6.0.0
- Updated docker/build-push-action to v6.18.0
- Updated actions/upload-artifact to v4.6.2
- Updated actions/download-artifact to v5.0.0
- Updated sigstore/gh-action-sigstore-python to v3.0.1
2025-09-22 18:44:45 -05:00
Sudo-Ivan
7d5e891261 Update dependencies in poetry.lock and pyproject.toml
- Bump anyio from 4.9.0 to 4.10.0
- Bump authlib from 1.6.0 to 1.6.4
- Bump certifi from 2025.7.14 to 2025.8.3
- Bump cffi from 1.17.1 to 2.0.0
- Bump ruamel.yaml.clib from 0.2.12 to 0.2.13
- Bump ruff from 0.12.3 to 0.12.12
- Bump safety from 3.6.0 to 3.6.1
- Bump typer from 0.16.0 to 0.19.1
- Bump typing-extensions from 4.14.1 to 4.15.0
2025-09-22 14:24:57 -05:00
Sudo-Ivan
c382ed790f Update GitHub Actions workflows to use full-length commit hashes for actions 2025-09-22 14:24:40 -05:00
cb72e57da9 Merge pull request #2 from Sudo-Ivan/dependabot/github_actions/dot-github/workflows/pypa/gh-action-pypi-publish-1.13.0
Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.13.0 in /.github/workflows
2025-09-15 02:24:07 -05:00
dependabot[bot]
aaf5ad23e2 Bump pypa/gh-action-pypi-publish in /.github/workflows
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.3 to 1.13.0.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.3...v1.13.0)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-version: 1.13.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 15:36:31 +00:00
Sudo-Ivan
ce1b1dad7d Update Docker volume path 2025-08-18 03:17:23 -05:00
Sudo-Ivan
67ebc7e556 Fix formatting issues in test_client.py and test_client2.py for consistency in lambda function parameters. 2025-08-04 17:24:21 -05:00
Sudo-Ivan
b31fb748b8 Add node address to output and Fix formatting issues 2025-08-04 17:24:15 -05:00
Sudo-Ivan
eb27326763 Bump package version to 1.0.0. 2025-07-14 17:31:45 -05:00
Sudo-Ivan
f40d5a51ae Refactor main to improve readability and maintainability. 2025-07-14 17:27:17 -05:00
Sudo-Ivan
4aa83a2dfb Add badges to README.md. 2025-07-14 17:22:26 -05:00
deepsource-io[bot]
a8b09611a1 ci: add .deepsource.toml 2025-07-14 22:01:12 +00:00
Sudo-Ivan
d0dd9e88c4 Dynamically use docker buildx build if available, otherwise fallback to docker build. 2025-07-14 16:59:02 -05:00
Sudo-Ivan
09d84c2533 Allow passing build arguments to Docker build commands in Makefile. 2025-07-14 16:57:21 -05:00
Sudo-Ivan
06ab592cb9 Refactor Dockerfiles to improve Poetry virtual environment management and execution. 2025-07-14 16:57:02 -05:00
Sudo-Ivan
843c3a1a56 Update rns package version to 1.0.0 in requirements.txt. 2025-07-14 16:38:14 -05:00
Sudo-Ivan
37ac95753c Update package versions including rns and development tools. 2025-07-14 16:38:08 -05:00
Sudo-Ivan
a493c57ad2 Update package version and RNS dependency range. 2025-07-14 16:38:01 -05:00
Sudo-Ivan
698bfb2e81 Refactor Dockerfiles to use Poetry for dependency management. 2025-07-14 16:36:50 -05:00
Sudo-Ivan
eaf2e544c4 Add a GitHub Actions workflow to build and test Docker images for multiple Python versions. 2025-07-14 16:36:30 -05:00
Sudo-Ivan
89f88e24ea Add build and push for rootless Docker image in CI workflow. 2025-07-14 16:36:24 -05:00
Sudo-Ivan
a47b78c13d update 2025-07-14 16:31:44 -05:00
Sudo-Ivan
4831f5261d add 2025-07-14 16:31:38 -05:00
Sudo-Ivan
46f90e461f add tests 2025-07-14 16:31:34 -05:00
Sudo-Ivan
a1bbe8bc8a Add ARM64 docker support 2025-07-14 16:31:01 -05:00
Sudo-Ivan
7092883834 remove 2025-07-05 23:57:32 -05:00
Sudo-Ivan
74f5174254 update dependencies and add safety 2025-07-05 23:55:03 -05:00
Sudo-Ivan
f9699c060a add 2025-07-05 23:54:47 -05:00
Sudo-Ivan
a3ccd49439 update 2025-05-28 18:16:16 -05:00
Sudo-Ivan
ece0473beb update python requirement 2025-05-28 16:53:55 -05:00
Sudo-Ivan
89065f6e0a fix 2025-05-28 16:48:34 -05:00
Sudo-Ivan
e873d8e754 update 2025-05-28 16:45:46 -05:00
Sudo-Ivan
5561205b3e update 2025-05-28 16:39:15 -05:00
Sudo-Ivan
d6601adb38 bump to 0.1.2 2025-05-28 16:28:12 -05:00
Sudo-Ivan
98c71a888e add logging and graceful shutdown 2025-05-28 16:28:01 -05:00
Sudo-Ivan
65bd70c05a update instructions 2025-05-28 16:27:38 -05:00
Sudo-Ivan
b77b73576f add pypi workflow 2025-05-28 16:17:21 -05:00
35 changed files with 3425 additions and 455 deletions
+7
View File
@@ -0,0 +1,7 @@
version = 1
[[analyzers]]
name = "python"
[analyzers.meta]
runtime_version = "3.x.x"
+150
View File
@@ -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"
+67
View File
@@ -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
+42
View File
@@ -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 .
-68
View File
@@ -1,68 +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 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: .
file: Dockerfile
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: Build and push rootless Docker image
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile.rootless
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}-rootless
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Vendored
+9
View File
@@ -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
-17
View File
@@ -1,17 +0,0 @@
FROM python:3.13-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
COPY requirements.txt ./
COPY setup.py ./
COPY README.md ./
COPY rns_page_node ./rns_page_node
RUN pip install --upgrade pip setuptools wheel && pip install -r requirements.txt .
ENTRYPOINT ["rns-page-node"]
-18
View File
@@ -1,18 +0,0 @@
FROM python:3.13-alpine AS builder
RUN apk update
RUN apk add build-base libffi-dev cargo pkgconfig
WORKDIR /src
COPY setup.py ./
COPY README.md ./
COPY rns_page_node ./rns_page_node
RUN pip install --upgrade pip setuptools wheel
RUN pip wheel . --no-deps --wheel-dir /src/dist
FROM scratch AS dist
COPY --from=builder /src/dist .
-25
View File
@@ -1,25 +0,0 @@
FROM python:3.13-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
COPY requirements.txt setup.py README.md ./
COPY rns_page_node ./rns_page_node
RUN pip install --upgrade pip setuptools wheel && pip install -r requirements.txt .
USER app
ENTRYPOINT ["rns-page-node"]
+39 -25
View File
@@ -1,17 +1,31 @@
# Makefile for rns-page-node
.PHONY: all build sdist wheel clean install lint format docker-wheels docker-build docker-run docker-build-rootless docker-run-rootless help
# 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")
# 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
@@ -26,40 +40,39 @@ 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 -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 \
-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 \
--identity-dir /app/node-config \
--announce-interval 360
docker-build-rootless:
docker build -f Dockerfile.rootless -t rns-page-node-rootless:latest .
test:
bash tests/run_tests.sh
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 \
--node-name "Page Node" \
--pages-dir /app/pages \
--files-dir /app/files \
--identity-dir /app/node-config \
--announce-interval 360
test-advanced:
poetry run python3 tests/test_advanced.py
docker-test:
$(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:"
@@ -72,7 +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)"
+89 -25
View File
@@ -1,62 +1,126 @@
# 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 git+https://github.com/Sudo-Ivan/rns-page-node.git
# or
pipx install git+https://github.com/Sudo-Ivan/rns-page-node.git
```
```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`.
## Page formats
## Build
- Micron `.mu`
```bash
make build
```
Build wheels:
```bash
make wheel
```
### Build Wheels in Docker
```bash
make docker-wheels
```
## Pages
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
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.
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)
```
## To-Do
- [ ] Pypi
## License
This project incorporates portions of the [NomadNet](https://github.com/markqvist/NomadNet) codebase, which is licensed under the GNU General Public License v3.0 (GPL-3.0). As a derivative work, this project is also distributed under the terms of the GPL-3.0. See the [LICENSE](LICENSE) file for full license.
+179
View File
@@ -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]
+31
View File
@@ -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
+62
View File
@@ -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"]
+14
View File
@@ -0,0 +1,14 @@
FROM python:3.14-slim
RUN apt-get update && apt-get install -y build-essential libssl-dev && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir .
WORKDIR /app/tests
RUN chmod +x run_tests.sh
CMD ["bash", "run_tests.sh"]
+20
View File
@@ -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
+126
View File
@@ -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).
+126
View File
@@ -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.
+126
View File
@@ -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) ファイルを参照してください。
+118
View File
@@ -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).
+126
View File
@@ -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
View File
@@ -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
}
+35
View File
@@ -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
+838 -137
View File
File diff suppressed because it is too large Load Diff
+15 -6
View File
@@ -1,16 +1,25 @@
[project]
name = "rns-page-node"
version = "0.1.0"
license = {file = "LICENSE"}
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.9"
requires-python = ">=3.9.2"
dependencies = [
"rns (>=0.9.6,<0.10.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,5 +29,5 @@ requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.group.dev.dependencies]
ruff = "^0.11.11"
ruff = "^0.14.10"
twine = "^6.2.0"
+3
View File
@@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}
+6 -1
View File
@@ -1 +1,6 @@
rns==0.9.6
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"
+6 -2
View File
@@ -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"]
+494 -111
View File
@@ -1,28 +1,96 @@
#!/usr/bin/env python3
"""
Minimal Reticulum Page Node
"""Minimal Reticulum Page Node.
Serves .mu pages and files over RNS.
"""
import os
import time
import threading
import subprocess
import RNS
import argparse
import os
import subprocess
import threading
import time
from pathlib import Path
DEFAULT_INDEX = '''>Default Home Page
import RNS
This node is serving pages using page node, but the home page file (index.mu) was not found in the pages directory. Please add an index.mu file to customize the home page.
'''
DEFAULT_INDEX = """>Default Home Page
DEFAULT_NOTALLOWED = '''>Request Not Allowed
This node is serving pages using rns-page-node, but index.mu was not found.
Please add an index.mu file to customize the home page.
"""
DEFAULT_NOTALLOWED = """>Request Not Allowed
You are not authorised to carry out the request.
'''
"""
def load_config(config_file):
"""Load configuration from a plain text config file.
Config format is simple key=value pairs, one per line.
Lines starting with # are comments and are ignored.
Empty lines are ignored.
Args:
config_file: Path to the config file
Returns:
Dictionary of configuration values
"""
config = {}
try:
with open(config_file, encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
RNS.log(
f"Invalid config line {line_num} in {config_file}: {line}",
RNS.LOG_WARNING,
)
continue
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
if key and value:
config[key] = value
RNS.log(f"Loaded configuration from {config_file}", RNS.LOG_INFO)
except FileNotFoundError:
RNS.log(f"Config file not found: {config_file}", RNS.LOG_ERROR)
except Exception as e:
RNS.log(f"Error reading config file {config_file}: {e}", RNS.LOG_ERROR)
return config
class PageNode:
def __init__(self, identity, pagespath, filespath, announce_interval=360, name=None, page_refresh_interval=0, file_refresh_interval=0):
"""A Reticulum page node that serves .mu pages and files over RNS."""
def __init__(
self,
identity,
pagespath,
filespath,
announce_interval=360,
name=None,
page_refresh_interval=0,
file_refresh_interval=0,
):
"""Initialize the PageNode.
Args:
identity: RNS Identity for the node
pagespath: Path to directory containing .mu pages
filespath: Path to directory containing files to serve
announce_interval: Minutes between announcements (default: 360) == 6 hours
name: Display name for the node (optional)
page_refresh_interval: Seconds between page rescans (0 = disabled)
file_refresh_interval: Seconds between file rescans (0 = disabled)
"""
self._stop_event = threading.Event()
self._lock = threading.Lock()
self.identity = identity
self.name = name
self.pagespath = pagespath
@@ -32,7 +100,7 @@ class PageNode:
RNS.Destination.IN,
RNS.Destination.SINGLE,
"nomadnetwork",
"node"
"node",
)
self.announce_interval = announce_interval
self.last_announce = 0
@@ -46,154 +114,469 @@ class PageNode:
self.destination.set_link_established_callback(self.on_connect)
threading.Thread(target=self._announce_loop, daemon=True).start()
threading.Thread(target=self._refresh_loop, daemon=True).start()
self._announce_thread = threading.Thread(
target=self._announce_loop,
daemon=True,
)
self._announce_thread.start()
self._refresh_thread = threading.Thread(target=self._refresh_loop, daemon=True)
self._refresh_thread.start()
def register_pages(self):
self.servedpages = []
self._scan_pages(self.pagespath)
"""Scan pages directory and register request handlers for all .mu files."""
pages = self._scan_pages(self.pagespath)
if not os.path.isfile(os.path.join(self.pagespath, "index.mu")):
with self._lock:
self.servedpages = pages
pagespath = Path(self.pagespath).resolve()
if not (pagespath / "index.mu").is_file():
self.destination.register_request_handler(
"/page/index.mu",
response_generator=self.serve_default_index,
allow=RNS.Destination.ALLOW_ALL
allow=RNS.Destination.ALLOW_ALL,
)
for full_path in self.servedpages:
rel = full_path[len(self.pagespath):]
request_path = f"/page{rel}"
for full_path in pages:
page_path = Path(full_path).resolve()
try:
rel = page_path.relative_to(pagespath).as_posix()
except ValueError:
continue
request_path = f"/page/{rel}"
self.destination.register_request_handler(
request_path,
response_generator=self.serve_page,
allow=RNS.Destination.ALLOW_ALL
allow=RNS.Destination.ALLOW_ALL,
)
def register_files(self):
self.servedfiles = []
self._scan_files(self.filespath)
"""Scan files directory and register request handlers for all files."""
files = self._scan_files(self.filespath)
for full_path in self.servedfiles:
rel = full_path[len(self.filespath):]
request_path = f"/file{rel}"
with self._lock:
self.servedfiles = files
filespath = Path(self.filespath).resolve()
for full_path in files:
file_path = Path(full_path).resolve()
try:
rel = file_path.relative_to(filespath).as_posix()
except ValueError:
continue
request_path = f"/file/{rel}"
self.destination.register_request_handler(
request_path,
response_generator=self.serve_file,
allow=RNS.Destination.ALLOW_ALL,
auto_compress=32_000_000
auto_compress=32_000_000,
)
def _scan_pages(self, base):
for entry in os.listdir(base):
if entry.startswith('.'):
"""Return a list of page paths under the given directory, excluding .allowed files."""
base_path = Path(base)
if not base_path.exists():
return []
served = []
for entry in base_path.iterdir():
if entry.name.startswith("."):
continue
path = os.path.join(base, entry)
if os.path.isdir(path):
self._scan_pages(path)
elif os.path.isfile(path) and not entry.endswith(".allowed"):
self.servedpages.append(path)
if entry.is_dir():
served.extend(self._scan_pages(entry))
elif entry.is_file() and entry.name.endswith(".mu"):
served.append(str(entry))
return served
def _scan_files(self, base):
for entry in os.listdir(base):
if entry.startswith('.'):
"""Return all file paths under the given directory."""
base_path = Path(base)
if not base_path.exists():
return []
served = []
for entry in base_path.iterdir():
if entry.name.startswith("."):
continue
path = os.path.join(base, entry)
if os.path.isdir(path):
self._scan_files(path)
elif os.path.isfile(path):
self.servedfiles.append(path)
if entry.is_dir():
served.extend(self._scan_files(entry))
elif entry.is_file():
served.append(str(entry))
return served
def serve_default_index(self, path, data, request_id, link_id, remote_identity, requested_at):
return DEFAULT_INDEX.encode('utf-8')
@staticmethod
def serve_default_index(
_path,
_data,
_request_id,
_link_id,
_remote_identity,
_requested_at,
):
"""Serve the default index page when no index.mu file exists."""
return DEFAULT_INDEX.encode("utf-8")
def serve_page(self, path, data, request_id, link_id, remote_identity, requested_at):
file_path = path.replace("/page", self.pagespath, 1)
def serve_page(
self,
path,
data,
_request_id,
_link_id,
remote_identity,
_requested_at,
):
"""Serve a .mu page file, executing it as a script if it has a shebang."""
pagespath = Path(self.pagespath).resolve()
relative_path = path[6:] if path.startswith("/page/") else path[5:]
file_path = (pagespath / relative_path).resolve()
if not str(file_path).startswith(str(pagespath)):
return DEFAULT_NOTALLOWED.encode("utf-8")
is_script = False
file_content = None
try:
with open(file_path, 'rb') as _f:
first_line = _f.readline()
is_script = first_line.startswith(b'#!')
except Exception:
is_script = False
if is_script and os.access(file_path, os.X_OK):
# Note: You can remove the following try-except block and just serve the page content statically
try:
result = subprocess.run([file_path], stdout=subprocess.PIPE)
return result.stdout
except Exception:
pass
with open(file_path, 'rb') as f:
return f.read()
with file_path.open("rb") as file_handle:
first_line = file_handle.readline()
is_script = first_line.startswith(b"#!")
file_handle.seek(0)
if not is_script:
return file_handle.read()
file_content = file_handle.read()
except FileNotFoundError:
return DEFAULT_NOTALLOWED.encode("utf-8")
except OSError as err:
RNS.log(f"Error reading page {file_path}: {err}", RNS.LOG_ERROR)
return DEFAULT_NOTALLOWED.encode("utf-8")
def serve_file(self, path, data, request_id, link_id, remote_identity, requested_at):
file_path = path.replace("/file", self.filespath, 1)
return [open(file_path, 'rb'), {"name": os.path.basename(file_path).encode('utf-8')}]
if is_script and os.access(str(file_path), os.X_OK):
try:
env_map = os.environ.copy()
if _link_id is not None:
env_map["link_id"] = RNS.hexrep(_link_id, delimit=False)
if remote_identity is not None:
env_map["remote_identity"] = RNS.hexrep(
remote_identity.hash,
delimit=False,
)
if data is not None and isinstance(data, dict):
for e in data:
if isinstance(e, str) and (
e.startswith("field_") or e.startswith("var_")
):
env_map[e] = data[e]
result = subprocess.run( # noqa: S603
[str(file_path)],
stdout=subprocess.PIPE,
check=True,
env=env_map,
)
return result.stdout
except Exception as e:
RNS.log(f"Error executing script page: {e}", RNS.LOG_ERROR)
if file_content is not None:
return file_content
try:
return self._read_file_bytes(file_path)
except FileNotFoundError:
return DEFAULT_NOTALLOWED.encode("utf-8")
except OSError as err:
RNS.log(f"Error reading page fallback {file_path}: {err}", RNS.LOG_ERROR)
return DEFAULT_NOTALLOWED.encode("utf-8")
@staticmethod
def _read_file_bytes(file_path):
"""Read a file's bytes and return the contents."""
with file_path.open("rb") as file_handle:
return file_handle.read()
def serve_file(
self,
path,
_data,
_request_id,
_link_id,
_remote_identity,
_requested_at,
):
"""Serve a file from the files directory."""
filespath = Path(self.filespath).resolve()
relative_path = path[6:] if path.startswith("/file/") else path[5:]
file_path = (filespath / relative_path).resolve()
if not file_path.is_file() or not str(file_path).startswith(str(filespath)):
return DEFAULT_NOTALLOWED.encode("utf-8")
try:
return [
file_path.open("rb"),
{"name": file_path.name.encode("utf-8")},
]
except OSError as err:
RNS.log(f"Error opening file {file_path}: {err}", RNS.LOG_ERROR)
return DEFAULT_NOTALLOWED.encode("utf-8")
def on_connect(self, link):
pass
"""Handle new link connections."""
def _announce_loop(self):
while True:
if time.time() - self.last_announce > self.announce_interval:
if self.name:
self.destination.announce(app_data=self.name.encode('utf-8'))
else:
self.destination.announce()
self.last_announce = time.time()
time.sleep(1)
"""Periodically announce the node until shutdown is requested."""
interval_seconds = max(self.announce_interval, 0) * 60
try:
while not self._stop_event.is_set():
now = time.time()
if (
self.last_announce == 0
or now - self.last_announce >= interval_seconds
):
try:
if self.name:
self.destination.announce(
app_data=self.name.encode("utf-8"),
)
else:
self.destination.announce()
self.last_announce = time.time()
except (TypeError, ValueError) as announce_error:
RNS.log(
f"Error during announce: {announce_error}",
RNS.LOG_ERROR,
)
wait_time = max(
(self.last_announce + interval_seconds) - time.time()
if self.last_announce
else 0,
1,
)
self._stop_event.wait(min(wait_time, 60))
except Exception as e:
RNS.log(f"Error in announce loop: {e}", RNS.LOG_ERROR)
def _refresh_loop(self):
while True:
now = time.time()
if self.page_refresh_interval > 0 and now - self.last_page_refresh > self.page_refresh_interval:
self.register_pages()
self.last_page_refresh = now
if self.file_refresh_interval > 0 and now - self.last_file_refresh > self.file_refresh_interval:
self.register_files()
self.last_file_refresh = now
time.sleep(1)
"""Refresh page and file registrations at configured intervals."""
try:
while not self._stop_event.is_set():
now = time.time()
if (
self.page_refresh_interval > 0
and now - self.last_page_refresh >= self.page_refresh_interval
):
self.register_pages()
self.last_page_refresh = time.time()
if (
self.file_refresh_interval > 0
and now - self.last_file_refresh >= self.file_refresh_interval
):
self.register_files()
self.last_file_refresh = time.time()
wait_candidates = []
if self.page_refresh_interval > 0:
wait_candidates.append(
max(
(self.last_page_refresh + self.page_refresh_interval)
- time.time(),
0.5,
),
)
if self.file_refresh_interval > 0:
wait_candidates.append(
max(
(self.last_file_refresh + self.file_refresh_interval)
- time.time(),
0.5,
),
)
wait_time = min(wait_candidates) if wait_candidates else 1.0
self._stop_event.wait(min(wait_time, 60))
except Exception as e:
RNS.log(f"Error in refresh loop: {e}", RNS.LOG_ERROR)
def shutdown(self):
"""Gracefully shutdown the PageNode and cleanup resources."""
RNS.log("Shutting down PageNode...", RNS.LOG_INFO)
self._stop_event.set()
try:
self._announce_thread.join(timeout=5)
self._refresh_thread.join(timeout=5)
except Exception as e:
RNS.log(f"Error waiting for threads to shut down: {e}", RNS.LOG_ERROR)
try:
if hasattr(self.destination, "close"):
self.destination.close()
except Exception as e:
RNS.log(f"Error closing RNS destination: {e}", RNS.LOG_ERROR)
def main():
"""Run the RNS page node application."""
parser = argparse.ArgumentParser(description="Minimal Reticulum Page Node")
parser.add_argument('-c', '--config', dest='configpath', help='Reticulum config path', default=None)
parser.add_argument('-p', '--pages-dir', dest='pages_dir', help='Pages directory', default=os.path.join(os.getcwd(), 'pages'))
parser.add_argument('-f', '--files-dir', dest='files_dir', help='Files directory', default=os.path.join(os.getcwd(), 'files'))
parser.add_argument('-n', '--node-name', dest='node_name', help='Node display name', default=None)
parser.add_argument('-a', '--announce-interval', dest='announce_interval', type=int, help='Announce interval in seconds', default=360)
parser.add_argument('-i', '--identity-dir', dest='identity_dir', help='Directory to store node identity', default=os.path.join(os.getcwd(), 'node-config'))
parser.add_argument('--page-refresh-interval', dest='page_refresh_interval', type=int, default=0, help='Page refresh interval in seconds, 0 disables auto-refresh')
parser.add_argument('--file-refresh-interval', dest='file_refresh_interval', type=int, default=0, help='File refresh interval in seconds, 0 disables auto-refresh')
parser.add_argument(
"node_config",
nargs="?",
help="Path to rns-page-node config file",
default=None,
)
parser.add_argument(
"-c",
"--config",
dest="configpath",
help="Reticulum config path",
default=None,
)
parser.add_argument(
"-p",
"--pages-dir",
dest="pages_dir",
help="Pages directory",
default=str(Path.cwd() / "pages"),
)
parser.add_argument(
"-f",
"--files-dir",
dest="files_dir",
help="Files directory",
default=str(Path.cwd() / "files"),
)
parser.add_argument(
"-n",
"--node-name",
dest="node_name",
help="Node display name",
default=None,
)
parser.add_argument(
"-a",
"--announce-interval",
dest="announce_interval",
type=int,
help="Announce interval in minutes",
default=360,
)
parser.add_argument(
"-i",
"--identity-dir",
dest="identity_dir",
help="Directory to store node identity",
default=str(Path.cwd() / "node-config"),
)
parser.add_argument(
"--page-refresh-interval",
dest="page_refresh_interval",
type=int,
default=0,
help="Page refresh interval in seconds, 0 disables auto-refresh",
)
parser.add_argument(
"--file-refresh-interval",
dest="file_refresh_interval",
type=int,
default=0,
help="File refresh interval in seconds, 0 disables auto-refresh",
)
parser.add_argument(
"-l",
"--log-level",
dest="log_level",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="INFO",
help="Logging level",
)
args = parser.parse_args()
configpath = args.configpath
pages_dir = args.pages_dir
files_dir = args.files_dir
node_name = args.node_name
announce_interval = args.announce_interval
identity_dir = args.identity_dir
page_refresh_interval = args.page_refresh_interval
file_refresh_interval = args.file_refresh_interval
config = {}
if args.node_config:
config = load_config(args.node_config)
def get_config_value(arg_value, arg_default, config_key, value_type=str):
"""Get value from CLI args, config file, or default.
Priority: CLI arg > config file > default
"""
if arg_value != arg_default:
return arg_value
if config_key in config:
try:
if value_type is int:
return int(config[config_key])
return config[config_key]
except ValueError:
RNS.log(
f"Invalid {value_type.__name__} value for {config_key}: {config[config_key]}",
RNS.LOG_WARNING,
)
return arg_default
configpath = get_config_value(args.configpath, None, "reticulum-config")
pages_dir = get_config_value(args.pages_dir, str(Path.cwd() / "pages"), "pages-dir")
files_dir = get_config_value(args.files_dir, str(Path.cwd() / "files"), "files-dir")
node_name = get_config_value(args.node_name, None, "node-name")
announce_interval = get_config_value(
args.announce_interval,
360,
"announce-interval",
int,
)
identity_dir = get_config_value(
args.identity_dir,
str(Path.cwd() / "node-config"),
"identity-dir",
)
page_refresh_interval = get_config_value(
args.page_refresh_interval,
0,
"page-refresh-interval",
int,
)
file_refresh_interval = get_config_value(
args.file_refresh_interval,
0,
"file-refresh-interval",
int,
)
log_level = get_config_value(args.log_level, "INFO", "log-level")
# Set RNS log level based on command line argument
log_level_map = {
"CRITICAL": RNS.LOG_CRITICAL,
"ERROR": RNS.LOG_ERROR,
"WARNING": RNS.LOG_WARNING,
"INFO": RNS.LOG_INFO,
"DEBUG": RNS.LOG_DEBUG,
}
RNS.loglevel = log_level_map.get(log_level.upper(), RNS.LOG_INFO)
RNS.Reticulum(configpath)
os.makedirs(identity_dir, exist_ok=True)
identity_file = os.path.join(identity_dir, 'identity')
if os.path.isfile(identity_file):
identity = RNS.Identity.from_file(identity_file)
Path(identity_dir).mkdir(parents=True, exist_ok=True)
identity_file = Path(identity_dir) / "identity"
if identity_file.is_file():
identity = RNS.Identity.from_file(str(identity_file))
else:
identity = RNS.Identity()
identity.to_file(identity_file)
identity.to_file(str(identity_file))
os.makedirs(pages_dir, exist_ok=True)
os.makedirs(files_dir, exist_ok=True)
Path(pages_dir).mkdir(parents=True, exist_ok=True)
Path(files_dir).mkdir(parents=True, exist_ok=True)
node = PageNode(identity, pages_dir, files_dir, announce_interval, node_name, page_refresh_interval, file_refresh_interval)
print("Page node running. Press Ctrl-C to exit.")
node = PageNode(
identity,
pages_dir,
files_dir,
announce_interval,
node_name,
page_refresh_interval,
file_refresh_interval,
)
RNS.log("Page node running. Press Ctrl-C to exit.", RNS.LOG_INFO)
RNS.log(f"Node address: {RNS.prettyhexrep(node.destination.hash)}", RNS.LOG_INFO)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Shutting down.")
RNS.log("Keyboard interrupt received, shutting down...", RNS.LOG_INFO)
node.shutdown()
if __name__ == '__main__':
if __name__ == "__main__":
main()
+16 -20
View File
@@ -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.1.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(),
python_requires='>=3.9',
install_requires=[
'rns>=0.9.6,<0.10.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",
],
},
license='GPL-3.0',
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",
)
+4
View File
@@ -0,0 +1,4 @@
pages/
node-config/
node.log
config/
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "${BASH_SOURCE[0]}")"
# Remove previous test artifacts
rm -rf config node-config pages files node.log
# Create directories for config, node identity, pages, and files
mkdir -p config node-config pages files
# Create a sample page and a test file
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
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
echo "Waiting for node identity..."
for i in {1..40}; do
if [ -f node-config/identity ]; then
echo "Identity file found"
break
fi
sleep 0.25
done
if [ ! -f node-config/identity ]; then
echo "Error: node identity file not found" >&2
kill $NODE_PID
exit 1
fi
# Run the client test
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
+248
View File
@@ -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()
+229
View File
@@ -0,0 +1,229 @@
#!/usr/bin/env python3
import os
import sys
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")
# 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")
server_identity = RNS.Identity.from_file(identity_file)
# Create a destination to the server node
destination = RNS.Destination(
server_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
"nomadnetwork",
"node",
)
# Ensure we know a path to the destination
if not RNS.Transport.has_path(destination.hash):
RNS.Transport.request_path(destination.hash)
while not RNS.Transport.has_path(destination.hash):
time.sleep(0.1)
# Establish a link to the server
global_link = RNS.Link(destination)
# Containers for responses
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")
else:
text = str(data)
print("Received page (no data):")
print(text)
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"):
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")
# Handle response as a raw file object
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")
# Handle response as raw bytes
elif isinstance(data, bytes):
text = data.decode("utf-8")
print("Received file:")
print(text)
responses["file"] = text
else:
print("Received file (unhandled format):", data)
responses["file"] = str(data)
check_responses()
# Request the pages and file once the link is established
def on_link_established(link):
# 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 link: done_event.set())
# Wait for responses or timeout
if not done_event.wait(timeout=30):
print("Test timed out.", file=sys.stderr)
sys.exit(1)
# 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)
sys.exit(1)
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import os
import sys
import threading
import time
import RNS
dir_path = os.path.abspath(os.path.dirname(__file__))
config_dir = os.path.join(dir_path, "config")
RNS.Reticulum(config_dir)
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,
)
sys.exit(1)
destination_hash = bytes.fromhex(DESTINATION_HEX)
if not RNS.Transport.has_path(destination_hash):
print("Requesting path to server...")
RNS.Transport.request_path(destination_hash)
while not RNS.Transport.has_path(destination_hash):
time.sleep(0.1)
server_identity = RNS.Identity.recall(destination_hash)
print(f"Recalled server identity for {DESTINATION_HEX}")
destination = RNS.Destination(
server_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
"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")
else:
text = str(data)
print("Fetched page content:")
print(text)
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)
sys.exit(1)
print("Done fetching page.")
sys.exit(0)