254 lines
13 KiB
Markdown
254 lines
13 KiB
Markdown
# Native Linux installation (no Docker)
|
|
|
|
This is a manual, single-host production recipe for a Linux server with systemd.
|
|
Neither Docker nor Portainer is required. For a terminal-only trial, Windows or
|
|
macOS, use [foreground installation](LOCAL_DEVELOPMENT.md). See the
|
|
[installation overview](INSTALLATION.md) for platform limits and networking.
|
|
|
|
The operator manages OS updates, Python/Node, private keys, the reverse proxy
|
|
and service lifecycle. The systemd files below are reviewable examples, not an
|
|
automatic installer; adapt executable paths to your host and validate locally.
|
|
|
|
## 1. Prerequisites and layout
|
|
|
|
Install **Python 3.14** with venv/pip, **Node.js 24** with npm, Git, curl and a
|
|
trusted TLS reverse proxy. Follow [Python](https://www.python.org/downloads/)
|
|
and [Node.js](https://nodejs.org/en/download) instructions for your OS; distribution
|
|
default packages may be older. Install both runtimes at service-accessible system
|
|
paths outside `/home` and `/root`; a venv linked to a user-private pyenv/uv Python
|
|
can be hidden by the units' `ProtectHome` setting. Do not replace the OS's own Python. Native wheels
|
|
are architecture-dependent; if pip/npm need compilation, install the appropriate
|
|
compiler/library prerequisites from the dependency maintainers rather than
|
|
silently changing pinned versions. Do not use `sudo pip install` into system Python.
|
|
|
|
```sh
|
|
python3.14 --version
|
|
python3.14 -m venv --help
|
|
node --version
|
|
npm --version
|
|
command -v node
|
|
```
|
|
|
|
This recipe uses:
|
|
|
|
| Path | Purpose |
|
|
| --- | --- |
|
|
| `/opt/magent` | Release source, Python venv, frontend dependencies/build |
|
|
| `/etc/magent/backend.env` | Private backend keys and deployment configuration |
|
|
| `/etc/magent/frontend.env` | Frontend address/bind settings; no backend secrets |
|
|
| `/var/lib/magent/data` | Persistent SQLite, logs, branding, artwork and restore staging |
|
|
|
|
Create a dedicated account and directories on a **fresh** host. If the account
|
|
or paths already exist, inspect and reuse the intended installation; do not
|
|
overwrite its configuration, clone into it or change ownership blindly.
|
|
|
|
```sh
|
|
sudo useradd --system --user-group --create-home --home-dir /var/lib/magent --shell /usr/sbin/nologin magent
|
|
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 -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
|
|
```
|
|
|
|
Record the checked-out commit. Use the release branch or a reviewed release
|
|
commit, not the private development/deployment branches. The native API needs
|
|
`backend/requirements.txt`, not Docker's Supervisor dependency file. Python
|
|
virtual environments should be recreated at their final path rather than moved.
|
|
See [Python venv documentation](https://docs.python.org/3.14/library/venv.html).
|
|
|
|
## 2. Create private configuration once
|
|
|
|
Choose the final URL first, for example `https://magent.example.com`. DNS and TLS
|
|
are configured separately in your reverse proxy. The native mode does not use
|
|
`container_bootstrap` or generate keys automatically.
|
|
|
|
For a **fresh installation only**, run this standard-library script to create
|
|
independent keys without printing them. It refuses to overwrite an existing file.
|
|
The default URL below is an example and must be edited before starting services.
|
|
|
|
```sh
|
|
sudo python3.14 - <<'PY'
|
|
import base64
|
|
import os
|
|
from pathlib import Path
|
|
import secrets
|
|
|
|
path = Path('/etc/magent/backend.env')
|
|
values = {
|
|
'JWT_SECRET': secrets.token_urlsafe(48),
|
|
'SETTINGS_ENCRYPTION_KEY': base64.urlsafe_b64encode(secrets.token_bytes(32)).decode(),
|
|
'SETUP_TOKEN': secrets.token_urlsafe(48),
|
|
'MAGENT_MANAGED_SECRETS': 'false',
|
|
'MAGENT_APPLICATION_URL': 'https://magent.example.com',
|
|
'CORS_ALLOW_ORIGIN': 'https://magent.example.com',
|
|
'AUTH_COOKIE_SECURE': 'true',
|
|
'API_DOCS_ENABLED': 'false',
|
|
'SQLITE_PATH': '/var/lib/magent/data/magent.db',
|
|
'LOG_FILE': '/var/lib/magent/data/magent.log',
|
|
'BRANDING_SOURCE': 'data',
|
|
}
|
|
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
with os.fdopen(fd, 'w') as stream:
|
|
stream.write(''.join(f'{name}={value}\n' for name, value in values.items()))
|
|
print('Created private backend configuration; edit the example URL before starting.')
|
|
PY
|
|
sudo install -o root -g root -m 0600 /opt/magent/deploy/native/magent-frontend.env.example /etc/magent/frontend.env
|
|
sudoedit /etc/magent/backend.env /etc/magent/frontend.env
|
|
```
|
|
|
|
The [backend template](../deploy/native/magent-backend.env.example) shows the
|
|
same fields with placeholders for reference; never run with those placeholders.
|
|
Keep both files private and keep an encrypted off-host copy of backend keys.
|
|
Run the `install` command above only for a new frontend environment file; during
|
|
upgrades retain your existing file rather than copying the example again.
|
|
|
|
Set `MAGENT_APPLICATION_URL` in **both files**, and backend `CORS_ALLOW_ORIGIN`,
|
|
to exactly the browser origin: scheme, hostname and any non-default port, without
|
|
a path or trailing slash. For HTTPS set `AUTH_COOKIE_SECURE=true`; for a deliberate
|
|
trusted-LAN HTTP installation set it `false` and use the explicit `http://` URL
|
|
in both files. The frontend uses this to avoid inappropriate CSP HTTPS upgrades.
|
|
Do not use wildcard CORS or disable CSRF protections to fix an address mismatch.
|
|
|
|
Leave `ADMIN_PASSWORD` unset so the wizard creates the first administrator.
|
|
Leave the internal `MAGENT_RUNTIME_MANAGED` flag unset. API docs remain disabled.
|
|
All optional integration/environment controls are in [ENVIRONMENT.md](ENVIRONMENT.md);
|
|
configure app credentials through the wizard instead of copying every example
|
|
variable into these files.
|
|
|
|
**A `.env` file is not automatically loaded by the backend.** The systemd unit
|
|
loads `backend.env` explicitly. The frontend must never load that backend file
|
|
or receive secrets in `NEXT_PUBLIC_*` variables.
|
|
|
|
## 3. Build the frontend
|
|
|
|
Run the build as the unprivileged service account. The backend address must be
|
|
present **at build time**: otherwise this project defaults to Docker's
|
|
`http://backend:8000`, which does not normally resolve on a native host.
|
|
|
|
```sh
|
|
sudo -u magent sh -c 'cd /opt/magent/frontend && npm ci --include=dev'
|
|
sudo -u magent sh -c 'cd /opt/magent/frontend && BACKEND_INTERNAL_URL=http://127.0.0.1:8000 NEXT_PUBLIC_API_BASE=/api NEXT_TELEMETRY_DISABLED=1 NODE_ENV=production npm run build'
|
|
sudo -u magent sh -c 'cd /opt/magent/frontend && cp -R public .next/standalone/ && cp -R .next/static .next/standalone/.next/'
|
|
sudo -u magent mkdir -p /opt/magent/frontend/.next/standalone/.next/cache
|
|
```
|
|
|
|
Keep the build tools/dev dependencies until the build is complete. Do not serve
|
|
the source with `next dev` in production. This project uses Next's **standalone**
|
|
output: run its `server.js`, with the `public` and `.next/static` directories
|
|
copied as above. Changing the backend host/port requires rebuilding this bundle,
|
|
not merely editing its runtime environment. See [Next standalone deployment](https://nextjs.org/docs/app/api-reference/config/next-config-js/output).
|
|
|
|
## 4. Install and validate the services
|
|
|
|
The supplied units run as `magent`, bind to loopback, use a read-only system view,
|
|
private temporary directories and restricted writable paths. The backend has
|
|
**one worker**. Its fixed working directory is important: artwork/branding use
|
|
`cwd/data`, independently of `SQLITE_PATH`. Do not replace that directory with a
|
|
symlink; portable backup rejects a symlinked asset root.
|
|
|
|
```sh
|
|
sudo install -o root -g root -m 0644 /opt/magent/deploy/native/magent-backend.service /etc/systemd/system/magent-backend.service
|
|
sudo install -o root -g root -m 0644 /opt/magent/deploy/native/magent-frontend.service /etc/systemd/system/magent-frontend.service
|
|
sudoedit /etc/systemd/system/magent-frontend.service
|
|
sudo systemd-analyze verify /etc/systemd/system/magent-backend.service /etc/systemd/system/magent-frontend.service
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now magent-backend.service magent-frontend.service
|
|
```
|
|
|
|
Before verification, make frontend `ExecStart` match the system-wide Node 24 path
|
|
reported by `command -v node` (`/usr/bin/node` in the template). Service managers
|
|
do not inherit an interactive nvm shell; install Node at a service-accessible
|
|
system path. Likewise adapt the source/venv paths if you changed this layout.
|
|
Do not remove hardening settings just to mask an incorrect writable path.
|
|
|
|
Inspect [systemd execution settings](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html)
|
|
when adapting units to your distribution. `After` sets ordering, not application
|
|
readiness: confirm the health checks below before exposing the site.
|
|
|
|
```sh
|
|
sudo systemctl status magent-backend.service magent-frontend.service --no-pager
|
|
curl --fail http://127.0.0.1:8000/health
|
|
curl --fail http://127.0.0.1:3000/api/health
|
|
curl --fail http://127.0.0.1:3000/api/setup/status
|
|
sudo journalctl -u magent-backend.service -u magent-frontend.service -n 100 --no-pager
|
|
```
|
|
|
|
Both health endpoints should report `{"status":"ok"}`. A fresh setup status has
|
|
`setup_required` and `needs_admin` set to `true`. Check logs locally, but sanitize
|
|
them before sharing. The frontend is not a standalone static site: both processes
|
|
must stay running. It is normal to see a temporary API error if the backend is
|
|
still starting.
|
|
|
|
## 5. Configure HTTPS and finish setup
|
|
|
|
Use the [reverse-proxy instructions](INSTALLATION.md#https-and-reverse-proxy) to
|
|
send your chosen HTTPS hostname to `127.0.0.1:3000`. Keep backend port 8000 private.
|
|
For a remote browser without a proxy, use an SSH tunnel for a local trial or
|
|
deliberately change the frontend bind address and matching URL for trusted-LAN
|
|
use; the production units intentionally are not LAN listeners by default.
|
|
|
|
Retrieve only the setup token in your private administrative terminal:
|
|
|
|
```sh
|
|
sudo sed -n 's/^SETUP_TOKEN=//p' /etc/magent/backend.env
|
|
```
|
|
|
|
Open the final browser URL, create the administrator, configure apps and finish
|
|
the wizard. Native installations use this manually persisted token, not the
|
|
container command shown in the managed-install part of the help dialog. After
|
|
administrator creation, remove `SETUP_TOKEN` using `sudoedit` and restart the
|
|
backend. **Do not remove/regenerate JWT_SECRET or SETTINGS_ENCRYPTION_KEY.**
|
|
Restarting never reopens first-admin signup on an existing database.
|
|
|
|
```sh
|
|
sudoedit /etc/magent/backend.env
|
|
sudo systemctl restart magent-backend.service
|
|
```
|
|
|
|
## Routine operation, backup and restore
|
|
|
|
- Start/stop/restart with `systemctl`; enablement starts services after boot.
|
|
Check the journal and `/var/lib/magent/data/magent.log` for backend errors.
|
|
- Use [encrypted application backups](installation-and-recovery.md). Portable
|
|
exports do not include the environment files, deployment keys or TLS keys;
|
|
back those up separately and protect their passphrases.
|
|
- For an offline disaster-recovery snapshot, stop both Magent services and back
|
|
up `/var/lib/magent/data`, `/etc/magent` and the exact source revision/unit
|
|
configuration using your trusted backup tool, then start services again.
|
|
Do not copy a live SQLite file and assume it is consistent.
|
|
- A staged UI restore takes effect when you restart **magent-backend.service**.
|
|
Keep one worker and no other writers, keep destination keys unchanged, then
|
|
verify restored accounts and integrations. Native/manual restore may import
|
|
the source's saved application URL: recheck **Hosting & proxy** against your
|
|
destination environment and frontend URL after recovery.
|
|
|
|
## Upgrade and roll back
|
|
|
|
Plan a maintenance window; this is not a rolling multi-worker deployment.
|
|
|
|
1. Record `git -C /opt/magent rev-parse HEAD`, back up the application and private
|
|
environment, and read migration/release notes. Keep the previous source and
|
|
runtime dependency versions available.
|
|
2. Stop both services. Fetch `release` as the source owner and review the exact
|
|
intended commit; use `git merge --ff-only origin/release` only for a clean
|
|
release checkout. Do not force-reset local changes.
|
|
3. Install the new pinned backend requirements into the existing venv, or create
|
|
a replacement venv at its final path if the Python version changed. Re-run
|
|
**all** frontend dependency/build/static-copy steps above. Keep the same data
|
|
directory and private environment; do not rerun initial key generation.
|
|
4. Review any unit changes, validate and `daemon-reload` if needed, start services
|
|
and check API health, setup/login, UI assets, integrations and backups.
|
|
|
|
For a rollback, stop both services, restore the recorded compatible source and
|
|
dependency/build artifacts, and keep the original keys/data. If the upgrade
|
|
changed the database incompatibly, use the matching pre-upgrade database backup
|
|
with that older version; code-only rollback is not always safe. Portable restore
|
|
should first use the same application version that created the backup.
|
|
|
|
To retire a native installation, disable/stop its two units first. Retain the
|
|
data and private environment until an off-host restore has been verified. Do not
|
|
delete `/var/lib/magent` as a troubleshooting step.
|