diff --git a/.dockerignore b/.dockerignore index faa48c8..46c6932 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,7 @@ !Dockerfile !.dockerignore !LICENSE +!THIRD_PARTY_NOTICES.md !backend/ !backend/requirements.txt !backend/app/ diff --git a/.gitattributes b/.gitattributes index aa6354f..e3133b7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,6 +7,7 @@ *.png binary *.jpg binary *.jpeg binary +*.webp binary *.gif binary *.ico binary *.pdf binary diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.md b/.gitea/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d18094c --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Report reproducible behavior that looks wrong +title: "[Bug] " +labels: "" +--- + +## What happened? + + + +## What did you expect? + + + +## How can we reproduce it? + +1. +2. +3. + +## Environment + +- Magent version/tag: +- Container image digest, if applicable: +- Installation method (Compose, Portainer, docker run, native): +- Host OS and CPU architecture: +- Browser and version: +- Reverse proxy, if applicable: +- Connected app and version involved: + +## Sanitized diagnostics + + + +- [ ] I searched existing issues and the troubleshooting guides. +- [ ] I reproduced this on the current public preview. +- [ ] I removed passwords, tokens, API keys, cookies, email addresses, private URLs and personal data. +- [ ] I did not attach an `.env`, database, backup or generated secrets file. +- [ ] This is not a security vulnerability. Security reports follow `SECURITY.md`. diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.md b/.gitea/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..11c78ef --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,26 @@ +--- +name: Feature request +about: Suggest an improvement to Magent +title: "[Feature] " +labels: "" +--- + +## Problem + + + +## Desired outcome + + + +## Current workaround + + + +## Relevant integration + + + +## Additional context + + diff --git a/.gitea/ISSUE_TEMPLATE/installation_help.md b/.gitea/ISSUE_TEMPLATE/installation_help.md new file mode 100644 index 0000000..e1fa2cb --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/installation_help.md @@ -0,0 +1,29 @@ +--- +name: Installation help +about: Ask for help with a documented installation path +title: "[Install] " +labels: "" +--- + +## Goal + + + +## Method and environment + +- Installation guide followed: +- Host OS and CPU architecture: +- Docker/Compose/Portainer/Python/Node versions, as applicable: +- Browser-facing URL scheme (`http` or `https`; redact private hostnames if needed): +- Reverse proxy, if applicable: + +## Where it stopped + + + +## Checks already tried + +- [ ] I checked the matching troubleshooting table. +- [ ] I confirmed the container/service health and retained the same data path. +- [ ] I removed credentials, tokens, cookies, private URLs and personal data. +- [ ] I did not attach an `.env`, database, backup or generated secrets file. diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..95d7f27 --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +## What and why + + + +## Validation + + + +- [ ] Backend tests pass or are not affected. +- [ ] Frontend tests, lint, format, typecheck and build pass or are not affected. +- [ ] Documentation and changelog are updated where needed. +- [ ] New behavior has regression coverage. +- [ ] No credentials, user data, databases, backups or private deployment details are included. + +## Compatibility + + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c657614 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog + +This file records public Magent releases. The in-application changelog contains +the shorter user-facing highlights. + +## Unreleased + +- No unreleased changes recorded. + +## 0.1.0-preview.1 — 2026-09-22 + +First public preview. + +### Included + +- Guided first-install setup for the administrator, application URL and optional integrations. +- Request discovery, submission and status tracking across Seerr and Arr services. +- Jellyfin playback links, issue reporting and repair follow-up. +- Jellystat viewing insights, monthly reports and optional email recaps. +- Local/Jellyfin sign-in, invitations, account linking and feature controls. +- Encrypted configuration/database backups with staged, restart-only restore. +- Docker Compose, Docker CLI, Portainer, native Linux and foreground setup guides. +- Hardened non-root container defaults and a documented release smoke harness. + +### Known limits + +- The published image is validated for Linux/amd64 only. +- One writable SQLite instance and one backend worker are supported; HA is not. +- Native Linux services have static systemd validation, not broad distribution certification. +- This preview does not promise an LTS window or security-response SLA. + +### Upgrade note + +Existing installations must retain their data mount and signing/encryption keys. +Create an encrypted backup, record the running image digest, and follow the +[upgrade guidance](docs/DOCKER.md#updates-and-rollback). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bdf7b35 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,21 @@ +# Repository guide + +Magent combines a FastAPI backend, Next.js frontend and SQLite database. Read +`CONTRIBUTING.md` before changing code and `docs/INSTALLATION.md` before changing +deployment behavior. + +## Important paths + +- `backend/app/`: API, integrations, setup, persistence and background work. +- `frontend/app/`: application routes and interface components. +- `backend/tests/` and frontend `*.test.*`: synthetic regression coverage. +- `compose.yml`: public prebuilt-image deployment. +- `Dockerfile`: pinned multi-stage production build. + +## Working rules + +- Never read, commit or print real `.env`, database, backup or credential data. +- Keep SQLite to one backend worker and one writable application instance. +- Preserve origin checks, CSP nonces, non-root execution and persistent key/data paths. +- Add regression tests and run the checks in `CONTRIBUTING.md`. +- Use synthetic fixtures; do not call live media services from tests. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3439a1b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,23 @@ +# Community Code of Conduct + +Magent's community should be practical, welcoming and safe for people with +different backgrounds and levels of experience. + +## Expected behavior + +- Be respectful and specific when discussing code, ideas or mistakes. +- Assume good intent while accepting corrections and evidence. +- Give useful context and protect other people's privacy. +- Welcome questions without mocking a person's experience level. +- Keep feedback about the work, not the person. + +Harassment, threats, discrimination, sexualized attention, deliberate exposure +of private information, and repeated disruptive behavior are not acceptable. + +Maintainers may edit or remove contributions and temporarily or permanently +restrict participation when needed to protect the community. Non-sensitive +conduct concerns can be raised through the issue tracker. The public preview +does not yet publish a monitored private conduct-reporting channel; do not put +private evidence or personal information in a public issue. + +This code applies in project spaces and when representing the project elsewhere. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..333e0f9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,65 @@ +# Contributing to Magent + +Thanks for helping improve Magent. Small, reviewable changes with a clear reason +are the easiest to test and merge. + +## Before opening an issue + +- Use [SUPPORT.md](SUPPORT.md) to choose the right report type. +- Search existing issues and read the relevant installation guide. +- Remove credentials, tokens, email addresses, database content and private URLs. +- Use [SECURITY.md](SECURITY.md) for vulnerabilities; do not publish exploit details. + +## Development setup + +Use Python 3.14 and Node.js 24. Follow +[the foreground development guide](docs/LOCAL_DEVELOPMENT.md), use synthetic +data, and never connect tests to someone else's services. + +## Quality checks + +Backend: + +```sh +python -m venv .venv +. .venv/bin/activate +pip install -r backend/requirements-dev.txt +python -m unittest discover -s backend/tests -p 'test_*.py' +python scripts/check_environment_docs.py +``` + +Frontend: + +```sh +cd frontend +npm ci +npm test +npm run lint +npm run format:check +npm run typecheck +npm run build +``` + +Container changes should also pass both isolated smoke modes: + +```sh +docker build -t magent:review . +bash scripts/ci_container_smoke.sh magent:review +MAGENT_SMOKE_MANAGED=true bash scripts/ci_container_smoke.sh magent:review +``` + +The smoke harness creates disposable containers, networks and volumes. Do not +aim it at a live instance or reuse a production volume. + +## Pull requests + +1. Base public work on the `release` branch. +2. Keep each pull request focused and explain the user-visible reason. +3. Add or update tests for behavior changes. +4. Update documentation and `CHANGELOG.md` when behavior or setup changes. +5. State exactly which checks ran and any platform limits or skipped checks. +6. Do not commit generated builds, `.env` files, logs, databases, backups or secrets. + +By contributing, you agree that your contribution is licensed under the project's +[MIT License](LICENSE). Participation must follow the +[Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/Dockerfile b/Dockerfile index 25adbb5..7c57ba3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,6 +54,8 @@ RUN node --version ARG MAGENT_UID=1000 ARG MAGENT_GID=1000 +ARG MAGENT_VERSION=0.1.0-preview.1 +ARG MAGENT_REVISION=unknown RUN addgroup -g ${MAGENT_GID} magent \ && adduser -D -u ${MAGENT_UID} -G magent -s /sbin/nologin magent \ && install -d -o magent -g magent -m 0700 /app/data \ @@ -76,12 +78,16 @@ COPY --chown=magent:magent --from=frontend-builder /frontend/.next/static /app/f COPY --chown=magent:magent --from=frontend-builder /frontend/public /app/frontend/public COPY docker/supervisord.conf /etc/supervisor/conf.d/magent.conf -COPY LICENSE /usr/share/licenses/magent/LICENSE +COPY --chown=magent:magent LICENSE /usr/share/licenses/magent/LICENSE +COPY --chown=magent:magent THIRD_PARTY_NOTICES.md /usr/share/licenses/magent/THIRD_PARTY_NOTICES.md COPY --from=frontend-builder /licenses /usr/share/licenses/magent/frontend LABEL org.opencontainers.image.title="Magent" \ org.opencontainers.image.description="Self-hosted media requests, issues and viewing insights" \ - org.opencontainers.image.licenses="MIT" + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.source="https://git.amslabs.net/Rephl3x/Magent-Public" \ + org.opencontainers.image.version="${MAGENT_VERSION}" \ + org.opencontainers.image.revision="${MAGENT_REVISION}" USER magent:magent diff --git a/README.md b/README.md index 0972732..6ad31a2 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,111 @@ # Magent -Self-hosted media requests, viewing stats and issue management for Jellyfin, -Seerr, Sonarr, Radarr and related services. Magent combines a Python/FastAPI API, -a Next.js frontend and SQLite. Run the prebuilt non-root container or install -the Python and Node.js services directly—Portainer is optional. +Magent is a self-hosted home for media requests, viewing insights and issue +follow-up. It sits in front of Jellyfin, Seerr, Sonarr, Radarr and related apps, +then turns their technical state into a clearer experience for the people using +your server. -## Install +**Public preview:** `0.1.0-preview.1` · [Source code](https://git.amslabs.net/Rephl3x/Magent-Public) · +[Docker Hub](https://hub.docker.com/r/rephl3xnz/magent) · [MIT license](LICENSE) -Start with the [installation guide](docs/INSTALLATION.md) to choose a method: +![Magent's first-run setup wizard](docs/images/setup-wizard.webp) -| Method | Instructions | -| --- | --- | -| Docker Compose or `docker run` | [Docker installation](docs/DOCKER.md) | -| Portainer | [Single-file stack](docs/PORTAINER.md) | -| Linux without Docker | [Native install and systemd services](docs/NATIVE_INSTALL.md) | -| Windows/macOS/Linux foreground | [Source installation and development](docs/LOCAL_DEVELOPMENT.md) | +Magent does not store or stream media, replace your existing applications, or +provide content. You bring and operate those services; Magent connects to them. -For a fresh **Docker Compose** install, download [compose.yml](compose.yml) into -its own deployment directory and run: +## Why Magent? + +Media automation is powerful, but its status messages are written for operators. +Magent gives users one place to request something, understand what is happening, +see when it is ready, report a problem and follow the response. + +- Search for and submit movie or TV requests through Seerr. +- Follow a request across approval, Arr monitoring, download and Jellyfin availability. +- Open ready titles in Jellyfin and report playback or library problems. +- View private Jellystat-powered viewing insights and monthly reports. +- Offer local or Jellyfin sign-in, invite-only registration and per-user features. +- Manage integrations, users, branding, notifications and diagnostics from the UI. +- Export an encrypted backup of configuration, SQLite data and optional artwork. + +## Current scope + +- The published container is validated on **Linux/amd64**. +- Magent is a single-instance application using one writable SQLite database. It + is not an HA, Kubernetes or multi-replica deployment. +- Native Windows foreground operation has been smoke-tested. Linux systemd files + are statically validated; macOS instructions are reviewed but not runtime-tested. +- This is an early public release. Pin an immutable image tag or digest, retain + backups, and test upgrades before using them on your main instance. + +See [release notes](CHANGELOG.md) and [installation support boundaries](docs/INSTALLATION.md#platform-and-support-boundaries). + +## Quick start with Docker Compose + +Docker Compose is the recommended first installation. Create an empty directory, +download the public Compose file, and start Magent: ```sh +curl --fail --location --output compose.yml \ + https://git.amslabs.net/Rephl3x/Magent-Public/raw/branch/release/compose.yml +docker compose -f compose.yml -p magent pull +docker compose -f compose.yml -p magent up -d --wait --wait-timeout 120 +docker compose -f compose.yml -p magent exec --user magent magent \ + python -m app.container_bootstrap setup-token +``` + +PowerShell: + +```powershell +Invoke-WebRequest -Uri 'https://git.amslabs.net/Rephl3x/Magent-Public/raw/branch/release/compose.yml' -OutFile compose.yml docker compose -f compose.yml -p magent pull docker compose -f compose.yml -p magent up -d --wait --wait-timeout 120 -docker compose -f compose.yml -p magent ps docker compose -f compose.yml -p magent exec --user magent magent python -m app.container_bootstrap setup-token ``` -Keep that directory/project name and its persistent volume unchanged for updates. -This uses `rephl3xnz/magent:latest` with no environment variables, local source -build or Dockerfile. Portainer users can paste the same file into a Docker -Standalone stack instead. Native users follow their separate guide for explicit -environment configuration and service management. +Open `http://SERVER_IP:3000/setup`, enter the console-only token and confirm the +browser-facing URL. Then create the first administrator and connect the apps you +use. Optional integrations can be skipped. The managed image creates and retains +its own private keys; this path needs no `.env` or Dockerfile. -**Image availability:** the managed-install image is published on Docker Hub. -Only Linux/amd64 has been validated. `latest` is mutable; record the resolved -image digest before updating, or pin an immutable release tag. +If you cloned the repository, the optional helper exposes the same operations: +`./setup.sh check`, `./setup.sh install`, and `./setup.sh token`. Read a script +before running it; the documented Compose commands remain the canonical path. -1. Wait for the container to become healthy and retrieve the token using the - command above (or the Portainer console instructions in its guide). -2. Open the Docker host's address on port 3000. Confirm the browser-facing URL - in setup and use the token to create the first administrator. - The **Get setup token** button shows the console instructions and lets you - copy the command; it never reveals the token to public visitors. -3. Connect your apps, choose preferences and finish setup. Optional apps can - be skipped. Save an encrypted backup afterwards. +For public access, configure DNS and HTTPS before creating the administrator. +Do not forward the plain HTTP port directly to the internet. -Keep the Compose security block unchanged. Database storage is fixed at -`/app/data/magent.db` and API docs are disabled in managed installs. CORS and -cookie security follow the confirmed URL. Use HTTPS before public access. +## Other installation methods -See [all installation methods](docs/INSTALLATION.md), -[all environment options](docs/ENVIRONMENT.md), -[backup and restore](docs/installation-and-recovery.md) and -[advanced installation/upgrades](docs/PUBLIC_RELEASE.md). -Existing installations must retain their original data volume and signing/ -encryption keys; this fresh-install template is not an automatic migration. +| Method | Guide | +| --- | --- | +| Docker Compose | [Complete Docker guide](docs/DOCKER.md#docker-compose) | +| Docker CLI without Compose | [`docker run` guide](docs/DOCKER.md#docker-cli-without-compose) | +| Portainer | [Paste-and-deploy stack](docs/PORTAINER.md) | +| Build the container from source | [Source-build override](docs/DOCKER.md#build-the-container-from-source) | +| Native Linux | [Python/Node and systemd](docs/NATIVE_INSTALL.md) | +| Windows, macOS or Linux foreground | [Local/source installation](docs/LOCAL_DEVELOPMENT.md) | -## Build and test +Start with the [installation overview](docs/INSTALLATION.md) if you are unsure. +The [environment reference](docs/ENVIRONMENT.md) documents every supported +variable, while [backup and recovery](docs/installation-and-recovery.md) covers +portable exports, offline snapshots and safe restore. -The source tree contains everything needed to build the application: +### Existing installations + +Keep the same Compose project name, data mount and signing/encryption keys. The +fresh-install named volume is not an automatic migration of an existing bind +mount. Record your working image digest and make an encrypted backup before an +upgrade. Never use `docker compose down -v` on data you want to keep. + +## Build and test from source ```sh +git clone --branch release --single-branch https://git.amslabs.net/Rephl3x/Magent-Public.git +cd Magent-Public docker compose -f compose.yml -f compose.build.yml -p magent up -d --build ``` -For a disposable verification run, without touching an existing installation: - -```sh -docker build -t magent:review . -bash scripts/ci_container_smoke.sh magent:review -MAGENT_SMOKE_MANAGED=true bash scripts/ci_container_smoke.sh magent:review -``` - -Unit checks require Python 3.14 and Node 24: +The full quality gate uses Python 3.14 and Node.js 24: ```sh python -m venv .venv @@ -86,37 +119,37 @@ npm test npm run lint npm run format:check npm run typecheck +npm run build ``` -On Windows, activate `.venv\Scripts\Activate.ps1` instead. Do not point tests -at live services or use production credentials. +On Windows, use `.venv\Scripts\Activate.ps1`. Tests must use synthetic data and +must never point at live media services. -## How it is organised +## Project layout -- `backend/app/routers/`: authenticated API endpoints and administration. -- `backend/app/clients/`: media-service clients; `services/`: request states, - synchronisation, notifications, setup and encrypted backups. -- `backend/app/db.py` and `schema_migrations.py`: SQLite persistence/migrations. -- `frontend/app/`: pages and shared interface components; `frontend/proxy.ts`: - browser security headers and request nonces. +- `backend/app/`: FastAPI routes, service clients, request state, authentication, + setup, notifications and encrypted backups. +- `frontend/app/`: Next.js pages, shared UI, account portal and administration. - `backend/tests/` and frontend `*.test.*`: synthetic regression tests. -- `Dockerfile` and `docker/`: multi-stage build and process supervision. -- `compose.yml`: prebuilt-image install; `compose.build.yml`: source override. -- `deploy/native/`: example Linux service units and backend/frontend configuration. +- `compose.yml`: prebuilt-image install; `compose.build.yml`: source-build override. +- `deploy/native/`: example Linux service units and private configuration templates. +- `docs/`: installation, environment, HTTPS, backup, upgrade and rollback guidance. -Requests are cached from Seerr, joined to collector/download/library evidence, -normalised into a user-facing state and displayed by the frontend. App settings -are stored in SQLite; sensitive settings are encrypted with installation-specific -keys. Integrations are optional and are configured through the setup wizard. +The `release` branch is a deliberately small public export. It excludes private +deployment automation, credentials, runtime data and internal Git history. -This `release` branch intentionally excludes internal deployment scripts, -environment files, runtime data, development reports and prior Git history. -It contains no workflow that automatically deploys or publishes an image. +## Help, feedback and contributions -## Contributing and security +- Read [SUPPORT.md](SUPPORT.md) before opening an installation or bug report. +- See [CONTRIBUTING.md](CONTRIBUTING.md) for development and pull-request checks. +- Report security concerns using [SECURITY.md](SECURITY.md); never post secrets or + exploit details in a public issue. +- Community participation follows [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). -Keep changes focused, add regression tests and run the checks above. Never -commit tokens, database exports, backups or real user information. -See [SECURITY.md](SECURITY.md) for reporting guidance and deployment precautions. +Magent was developed through an iterative, AI-assisted workflow and then checked +with automated regression tests, isolated container smoke tests and a sanitized +public-release pass. Treat generated code like any other code: inspect it, test +it and report anything that does not make sense. -Licensed under [MIT](LICENSE). Third-party dependency licences remain applicable. +Magent is licensed under the [MIT License](LICENSE). Dependencies and base-image +components retain their own licenses; see [third-party notices](THIRD_PARTY_NOTICES.md). diff --git a/SECURITY.md b/SECURITY.md index 6b0003f..96690a7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,11 +4,16 @@ Do not post passwords, access tokens, encryption keys, database exports, backup files or live exploit details in public issues, discussions or container logs. -Use the repository hosting platform's private vulnerability-reporting feature -if the release owner has enabled it. Otherwise contact the maintainer privately -through the platform where you obtained this release before sending sensitive -details. This repository does not currently advertise a dedicated reporting -address; the release owner must establish one before a broad public launch. +Non-sensitive security hardening reports may be filed at +https://git.amslabs.net/Rephl3x/Magent-Public/issues/new after removing all +deployment details and secrets. The public preview does not yet publish a +monitored confidential-reporting channel. Do not put embargoed vulnerabilities, +live exploits or sensitive evidence in a public issue. If a report cannot be +made safely in public, retain it until a private contact is listed here. + +Reports are handled on a best-effort basis. The project does not promise a +specific response time or security-support window. The newest public preview is +the only version considered for security fixes unless a release note says otherwise. Include the image tag/digest, affected version, a minimal reproduction using synthetic data, and the security impact. Remove deployment credentials and diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..44d0f81 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,31 @@ +# Support + +Magent is an early public release maintained on a best-effort basis. There is no +paid support or guaranteed response time. + +## Start here + +- Installation choice: [docs/INSTALLATION.md](docs/INSTALLATION.md) +- Docker and Compose: [docs/DOCKER.md](docs/DOCKER.md) +- Portainer: [docs/PORTAINER.md](docs/PORTAINER.md) +- Native Linux: [docs/NATIVE_INSTALL.md](docs/NATIVE_INSTALL.md) +- Configuration: [docs/ENVIRONMENT.md](docs/ENVIRONMENT.md) +- Backup and restore: [docs/installation-and-recovery.md](docs/installation-and-recovery.md) + +## Ask for help or report a problem + +Use the matching issue template at +https://git.amslabs.net/Rephl3x/Magent-Public/issues/new. + +Include the Magent version/tag and image digest, CPU architecture, installation +method, browser, exact reproduction steps, expected result and sanitized logs. +Say whether the problem survives a normal restart. Never attach `.env` files, +databases, backups, generated secret files, API keys, passwords, access tokens, +private URLs or personal viewing data. + +Feature requests should describe the problem and intended user, not only a +proposed implementation. Integration requests should link to the upstream +application's public API documentation. + +For a vulnerability, follow [SECURITY.md](SECURITY.md) instead of opening a +detailed public report. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..8d7504a --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,27 @@ +# Third-party notices + +Magent's original source is licensed under the [MIT License](LICENSE). Third-party +software and assets retain their own copyright and license terms. + +The exact JavaScript dependency graph and resolved versions are recorded in +`frontend/package-lock.json`. Production runtime packages include Next.js, +React, React DOM, Sharp and their transitive dependencies. Their license terms +come from the installed package metadata and license files, not from fields in +the lockfile. The release build collects discoverable production package +license and notice files under `/usr/share/licenses/magent/frontend`. + +The exact direct Python versions are recorded in `backend/requirements.txt` and +`docker/requirements-runtime.txt`. Python package metadata and license files are +installed with those packages and remain subject to their respective terms. + +The release container also contains components from its pinned Python, Node.js +and Alpine base images. Those components are not relicensed by Magent. The image +includes this notice, Magent's MIT license, Node.js's license, installed Python +package license files, discoverable production frontend license/notice files, +and any license files supplied by the Alpine packages in the runtime image under +`/usr/share/licenses`. + +Before redistributing a modified image, regenerate and review its software bill +of materials and preserve every applicable license, copyright and notice file. +Lockfiles are the version inventory; this summary is not a replacement for the +complete license texts supplied by each dependency. diff --git a/backend/app/build_info.py b/backend/app/build_info.py index 8593f44..b0780b6 100644 --- a/backend/app/build_info.py +++ b/backend/app/build_info.py @@ -1,2 +1,5 @@ -BUILD_NUMBER = "0803262237" -CHANGELOG = '2026-09-19|Initial minimal release source snapshot' +BUILD_NUMBER = "0.1.0-preview.1" +CHANGELOG = """2026-09-22|First public preview +2026-09-22|Guided setup for Docker, Portainer and native installs +2026-09-22|Encrypted backup and staged restore +2026-09-22|Requests, viewing insights, invites and issue follow-up""" diff --git a/backend/tests/test_backend_quality.py b/backend/tests/test_backend_quality.py index 3cd4c6d..819d5c9 100644 --- a/backend/tests/test_backend_quality.py +++ b/backend/tests/test_backend_quality.py @@ -515,7 +515,7 @@ class SiteInfoTests(unittest.TestCase): "site_banner_tone": "warning", "site_banner_background_color": "#123ABC", "site_banner_border_color": "red", - "site_login_message": "Use your Grizzlyflix account to sign in.", + "site_login_message": "Use your ExampleFlix account to sign in.", }) with patch.object(site_router, "get_runtime_settings", return_value=runtime): @@ -523,7 +523,7 @@ class SiteInfoTests(unittest.TestCase): self.assertEqual(info["banner"]["backgroundColor"], "#123abc") self.assertIsNone(info["banner"]["borderColor"]) - self.assertEqual(info["login"]["message"], "Use your Grizzlyflix account to sign in.") + self.assertEqual(info["login"]["message"], "Use your ExampleFlix account to sign in.") class SiteSettingValidationTests(unittest.IsolatedAsyncioTestCase): diff --git a/backend/tests/test_issue_acceptance.py b/backend/tests/test_issue_acceptance.py index 005e0e6..0c340d3 100644 --- a/backend/tests/test_issue_acceptance.py +++ b/backend/tests/test_issue_acceptance.py @@ -41,7 +41,7 @@ class IssueAcceptanceTests(TempDatabaseMixin, unittest.IsolatedAsyncioTestCase): content = email.await_args.kwargs self.assertEqual(content["subject"], f"Ready to try again? Magent issue #{item['id']}") self.assertIn("affected content in Jellyfin", content["body_text"]) - self.assertNotIn("grizzlyflix", content["body_html"].lower()) + self.assertNotIn("exampleflix", content["body_html"].lower()) self.assertIn("YES — it works", content["body_html"]) self.assertIn("NO — still broken", content["body_html"]) self.assertIn(f"/issues/confirm/{item['id']}#yes", content["body_html"]) diff --git a/backend/tests/test_newsletters.py b/backend/tests/test_newsletters.py index 623b8dd..2610ac5 100644 --- a/backend/tests/test_newsletters.py +++ b/backend/tests/test_newsletters.py @@ -93,7 +93,7 @@ class NewsletterConsentTests(NewsletterFixture, unittest.IsolatedAsyncioTestCase rendered = sender.call_args.args[1] self.assertEqual(rendered['subject'], 'Confirm your Magent newsletter subscription') self.assertIn('NEW IN YOUR LIBRARY', rendered['body_html']) - self.assertNotIn('grizzlyflix', rendered['body_html'].lower()) + self.assertNotIn('exampleflix', rendered['body_html'].lower()) self.assertNotIn('Arrival', rendered['body_html']) url = re.search(r'https://[^\s]+', rendered['body_text']).group(0) self.assertEqual(urlsplit(url).path, '/newsletter-subscription') @@ -393,7 +393,7 @@ class NewsletterDeliveryTests(NewsletterFixture, unittest.IsolatedAsyncioTestCas self.assertEqual(draft['intro'], custom_intro) def test_generic_email_template_preserves_custom_subject_and_intro(self): - custom_subject = 'Grizzlyflix weekend discoveries' + custom_subject = 'ExampleFlix weekend discoveries' custom_intro = 'Welcome to our own community.' rendered = template.render( {**content(), 'subject': custom_subject, 'intro': custom_intro}, {}, @@ -406,7 +406,7 @@ class NewsletterDeliveryTests(NewsletterFixture, unittest.IsolatedAsyncioTestCas self.assertIn('Welcome to our own <media> community.', rendered['body_html']) self.assertIn('Watch on Jellyfin', rendered['body_html']) self.assertIn('Explore Jellyfin', rendered['body_html']) - self.assertNotIn('grizzlyflix', rendered['body_html'].lower()) + self.assertNotIn('exampleflix', rendered['body_html'].lower()) async def test_weekly_worker_collects_once_and_delivers_to_confirmed_subscriber(self): now = datetime.now(timezone.utc) diff --git a/docs/DOCKER.md b/docs/DOCKER.md index d8e7722..9656727 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -64,7 +64,7 @@ container in either case, not a native Windows-container or macOS application. ### 1. Save the deployment file Create a new directory for this installation and save the release branch's -[raw compose.yml](https://git.amslabs.net/Rephl3x/Magent/raw/branch/release/compose.yml) +[raw compose.yml](https://git.amslabs.net/Rephl3x/Magent-Public/raw/branch/release/compose.yml) there. Keep your customized copy for future operations instead of overwriting it when updating. You need only this file for the prebuilt image. @@ -73,7 +73,7 @@ Linux, macOS or WSL: ```sh mkdir magent-install cd magent-install -curl --fail --location --output compose.yml https://git.amslabs.net/Rephl3x/Magent/raw/branch/release/compose.yml +curl --fail --location --output compose.yml https://git.amslabs.net/Rephl3x/Magent-Public/raw/branch/release/compose.yml ``` PowerShell: @@ -81,7 +81,7 @@ PowerShell: ```powershell New-Item -ItemType Directory -Path magent-install Set-Location magent-install -Invoke-WebRequest -Uri 'https://git.amslabs.net/Rephl3x/Magent/raw/branch/release/compose.yml' -OutFile compose.yml +Invoke-WebRequest -Uri 'https://git.amslabs.net/Rephl3x/Magent-Public/raw/branch/release/compose.yml' -OutFile compose.yml ``` Alternatively, open the raw link in your browser and save the plain text as @@ -236,7 +236,7 @@ It requires the full source tree and Docker build support; the prebuilt-image installation does not. Use a separate checkout directory: ```sh -git clone --branch release --single-branch https://git.amslabs.net/Rephl3x/Magent.git Magent-source +git clone --branch release --single-branch https://git.amslabs.net/Rephl3x/Magent-Public.git Magent-source cd Magent-source docker compose -f compose.yml -f compose.build.yml -p magent config --quiet docker compose -f compose.yml -f compose.build.yml -p magent build magent diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 37f4cc4..bbfe9bd 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -238,6 +238,8 @@ consumer; builder values and supervisor values are not user-editable app setting | `PYTHONUNBUFFERED` | Image: `1` | Flush Python stdout/stderr without buffering. | | `MAGENT_UID` | Build argument: `1000` | Image runtime user's UID. Not a runtime environment switch; rebuilding with a different UID also requires matching volume/tmpfs ownership. | | `MAGENT_GID` | Build argument: `1000` | Image runtime user's GID; same ownership caveat as UID. | +| `MAGENT_VERSION` | Build argument: `0.1.0-preview.1` | OCI image version label. Release builds set this to their public version; it does not change runtime data. | +| `MAGENT_REVISION` | Build argument: `unknown` | OCI image source-revision label. Release builds set this to the full source commit. | ## Manual Compose and test tooling @@ -249,6 +251,8 @@ verification tools, not the zero-input public Compose install. | `MAGENT_IMAGE` | Required by `docker-compose.hub.yml` | Explicit published image tag/digest for manual-secret installations. | | `MAGENT_BIND_ADDRESS` | `127.0.0.1` | Manual template frontend bind address. | | `MAGENT_HTTP_PORT` | `3000` | Manual template frontend host port. | +| `MAGENT_COMPOSE_FILE` | `compose.yml` | Optional `setup.sh` Compose filename override. Use a trusted local file. | +| `MAGENT_PROJECT_NAME` | `magent` | Optional `setup.sh` Compose project name. Keep it stable to retain the same named volume. | | `PYTHON_BIN` | `python3` | Interpreter used by the backend quality gate. | | `MAGENT_IMAGE_MAX_MB` | `350` | Container smoke-test unpacked image size budget in MiB. | | `MAGENT_SMOKE_MANAGED` | `false` | Test generated managed secrets when true, or synthetic manual keys when false. | diff --git a/docs/LOCAL_DEVELOPMENT.md b/docs/LOCAL_DEVELOPMENT.md index f372171..7827f1f 100644 --- a/docs/LOCAL_DEVELOPMENT.md +++ b/docs/LOCAL_DEVELOPMENT.md @@ -15,7 +15,7 @@ connect a development checkout to production credentials/databases. Linux/macOS (Bash/zsh): ```sh -git clone --branch release --single-branch https://git.amslabs.net/Rephl3x/Magent.git magent +git clone --branch release --single-branch https://git.amslabs.net/Rephl3x/Magent-Public.git magent cd magent python3.14 -m venv .venv .venv/bin/python -m pip install -r backend/requirements.txt @@ -24,7 +24,7 @@ python3.14 -m venv .venv Windows (PowerShell, with Python's `py` launcher installed): ```powershell -git clone --branch release --single-branch https://git.amslabs.net/Rephl3x/Magent.git magent +git clone --branch release --single-branch https://git.amslabs.net/Rephl3x/Magent-Public.git magent Set-Location magent py -3.14 -m venv .venv & .\.venv\Scripts\python.exe -m pip install -r backend\requirements.txt diff --git a/docs/NATIVE_INSTALL.md b/docs/NATIVE_INSTALL.md index ade28f2..7da013e 100644 --- a/docs/NATIVE_INSTALL.md +++ b/docs/NATIVE_INSTALL.md @@ -47,7 +47,7 @@ sudo useradd --system --user-group --create-home --home-dir /var/lib/magent --sh sudo install -d -o magent -g magent -m 0755 /opt/magent sudo install -d -o root -g root -m 0700 /etc/magent sudo install -d -o magent -g magent -m 0700 /var/lib/magent/data -sudo -u magent git clone --branch release --single-branch https://git.amslabs.net/Rephl3x/Magent.git /opt/magent +sudo -u magent git clone --branch release --single-branch https://git.amslabs.net/Rephl3x/Magent-Public.git /opt/magent sudo -u magent git -C /opt/magent rev-parse HEAD sudo -u magent python3.14 -m venv /opt/magent/.venv sudo -u magent /opt/magent/.venv/bin/python -m pip install -r /opt/magent/backend/requirements.txt diff --git a/docs/REDDIT_LAUNCH_POST.md b/docs/REDDIT_LAUNCH_POST.md new file mode 100644 index 0000000..ee41796 --- /dev/null +++ b/docs/REDDIT_LAUNCH_POST.md @@ -0,0 +1,96 @@ +# Reddit launch post + +For `r/vibecoding`, use the **Vibe-Coded Projects** category/flair and retain the +build-process section below. The community's current self-promotion guidance says +project posts need educational detail about tools, workflow and lessons rather +than only a link. Recheck its rules immediately before posting. Add sanitized +screenshots if available; never show real users, domains, libraries, service URLs +or credentials. + +## Suggested title + +I vibe-coded Magent: a self-hosted request, stats and issue portal for Jellyfin stacks + +## Alternative titles + +- Magent is ready for testers — Docker, Portainer and native installs +- Sharing Magent: one place for media requests, viewing stats and issue follow-up + +## Post + +Hey everyone, + +I’ve been vibe-coding a self-hosted project called **Magent**, and its first +public preview is ready for people to inspect, install and break: + +https://git.amslabs.net/Rephl3x/Magent-Public + +Magent is the user-facing layer I wanted around a Jellyfin media stack. It brings +requests, request progress, viewing stats and issue follow-up into one place, +while Jellyfin, Seerr, Sonarr, Radarr and the other connected apps keep doing the +jobs they already do. + +What it currently includes: + +- Search for and submit movie or TV requests through Seerr +- A plain-English request timeline across approval, Arr, downloads and Jellyfin +- “Watch now” links when something is available +- Issue reporting and repair follow-up +- Personal Jellystat viewing insights and monthly reports +- Local/Jellyfin sign-in, invitations and per-user feature controls +- Encrypted backup and restore for configuration and the SQLite database + +It does **not** provide, store or stream media. You need to operate and connect +your own services. + +Docker Compose is the recommended setup, but the repository also documents +Portainer, plain `docker run`, source-built containers, native Linux/systemd and +foreground Windows/macOS/Linux installs. A fresh managed Docker install creates +its own private keys and setup token, then walks you through the administrator +and app connections in the browser. + +Docker image: https://hub.docker.com/r/rephl3xnz/magent + +This is deliberately labelled a public preview. The container is currently +validated on Linux/amd64, Magent expects one writable SQLite instance, and it is +not presented as an HA/Kubernetes deployment. The repo documents HTTPS, reverse +proxies, origins/cookies, backups, upgrades and rollback rather than pretending +those details disappear. + +The project grew through an iterative AI-assisted/vibe-coding workflow, followed +by regression tests, isolated container smoke tests and a separate sanitization +pass for the public repository. I’m sharing the source under MIT because I want +the implementation to be inspectable, not just the result. + +### How I built it + +My workflow was much closer to directing a small engineering loop than writing +one giant prompt. I described a user problem, had Codex work across the FastAPI, +Next.js and Docker layers, then reviewed the result in a running Beta instance. +Screenshots and real failure reports became the next round of requirements. + +The useful discipline was making every vibe pass leave evidence behind. Backend +behavior got regression tests, interface changes got browser checks, and the +container got a disposable smoke harness covering fresh setup, login, security +headers, assets, encrypted backup/restore, restart and recreation. I also used +the ECC Codex plugin as a structured security/release review rather than treating +“it works on my stack” as a release gate. + +The biggest release lesson was that a Git branch is not a privacy boundary. My +first idea was a sanitized public branch alongside private Beta/Prod branches. +Before launch I caught that every branch and old tag in a public repository is +still public, so I split the three-commit sanitized export into its own repository. +That was more valuable than another round of UI polish. + +The most useful early feedback would be: + +- where setup assumes too much knowledge +- installation steps that are unclear or wrong on your platform +- integrations that behave differently on another stack +- request, stats or issue flows that confuse real users +- backup or restore problems + +Please use synthetic examples and sanitized logs in public reports—no tokens, +databases, backup files or private service details. + +Repository: https://git.amslabs.net/Rephl3x/Magent-Public diff --git a/docs/images/setup-wizard.webp b/docs/images/setup-wizard.webp new file mode 100644 index 0000000..2c9cade Binary files /dev/null and b/docs/images/setup-wizard.webp differ diff --git a/frontend/app/ui/branding.test.tsx b/frontend/app/ui/branding.test.tsx index bcd89a2..60a037b 100644 --- a/frontend/app/ui/branding.test.tsx +++ b/frontend/app/ui/branding.test.tsx @@ -16,7 +16,7 @@ describe("portable default branding", () => { expect(html).toContain("Welcome to our library"); expect(html).toContain("A custom message"); expect(html).toContain("Local help"); - expect(html).not.toMatch(/grizzlyflix/i); + expect(html).not.toMatch(/exampleflix/i); expect(html).not.toContain(">Beta<"); }); @@ -25,13 +25,13 @@ describe("portable default branding", () => { expect(html).toContain("MAGENT"); expect(html).toContain("Your media member portal"); - expect(html).not.toMatch(/grizzlyflix/i); + expect(html).not.toMatch(/exampleflix/i); }); it("explains the Jellyfin integration without a deployment-specific service name", () => { const html = renderToStaticMarkup(); expect(html).toContain("Jellyfin is where you watch them."); - expect(html).not.toMatch(/grizzlyflix/i); + expect(html).not.toMatch(/exampleflix/i); }); }); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cec6d4b..f118616 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "magent-frontend", - "version": "0803262237", + "version": "0.1.0-preview.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "magent-frontend", - "version": "0803262237", + "version": "0.1.0-preview.1", "dependencies": { "next": "16.3.5", "react": "19.2.4", diff --git a/frontend/package.json b/frontend/package.json index 76c55fa..8395e47 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "magent-frontend", "private": true, - "version": "0803262237", + "version": "0.1.0-preview.1", "scripts": { "dev": "next dev", "build": "next build", diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..1de02c4 --- /dev/null +++ b/setup.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env sh +set -eu + +compose_file="${MAGENT_COMPOSE_FILE:-compose.yml}" +project_name="${MAGENT_PROJECT_NAME:-magent}" + +usage() { + echo "Usage: ./setup.sh check|install|status|token|logs" + echo "Run this script from a directory containing the public compose.yml." +} + +compose() { + docker compose -f "$compose_file" -p "$project_name" "$@" +} + +case "${1:-}" in + check) + docker version >/dev/null + docker compose version >/dev/null + compose config --quiet + echo "Docker and compose.yml are ready." + ;; + install) + compose config --quiet + compose pull + compose up -d --wait --wait-timeout 120 + compose ps + echo "Open http://SERVER_IP:3000/setup, then run: ./setup.sh token" + ;; + status) + compose ps + ;; + token) + compose exec --user magent magent python -m app.container_bootstrap setup-token + ;; + logs) + compose logs --tail=150 magent + ;; + *) + usage + exit 2 + ;; +esac