mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
Add unified install script and release packaging
This commit is contained in:
11
.env.example
11
.env.example
@@ -13,7 +13,7 @@ RUST_LOG=aether_gateway=info
|
|||||||
|
|
||||||
# CORS 配置(跨域带 Cookie 时不要写 *,必须显式列出前端源)
|
# CORS 配置(跨域带 Cookie 时不要写 *,必须显式列出前端源)
|
||||||
# 示例: http://localhost:5173,https://app.example.com
|
# 示例: http://localhost:5173,https://app.example.com
|
||||||
CORS_ORIGINS=http://localhost:5173
|
# CORS_ORIGINS=http://localhost:5173
|
||||||
# CORS_ALLOW_CREDENTIALS=true
|
# CORS_ALLOW_CREDENTIALS=true
|
||||||
# 如果前后端跨站并依赖登录刷新 Cookie,还要配合:
|
# 如果前后端跨站并依赖登录刷新 Cookie,还要配合:
|
||||||
# AUTH_REFRESH_COOKIE_SAMESITE=None
|
# AUTH_REFRESH_COOKIE_SAMESITE=None
|
||||||
@@ -31,7 +31,7 @@ REDIS_HOST=localhost
|
|||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=aether
|
REDIS_PASSWORD=aether
|
||||||
|
|
||||||
# JWT密钥(使用 python generate_keys.py 生成)
|
# JWT密钥(使用 ./generate_keys.sh 生成)
|
||||||
# 用于用户登录 token 签名,更换后所有用户需重新登录
|
# 用于用户登录 token 签名,更换后所有用户需重新登录
|
||||||
JWT_SECRET_KEY=change-this-to-a-secure-random-string
|
JWT_SECRET_KEY=change-this-to-a-secure-random-string
|
||||||
|
|
||||||
@@ -40,16 +40,13 @@ JWT_SECRET_KEY=change-this-to-a-secure-random-string
|
|||||||
ENCRYPTION_KEY=change-this-to-another-secure-random-string
|
ENCRYPTION_KEY=change-this-to-another-secure-random-string
|
||||||
|
|
||||||
# 启动自举管理员(仅在当前库里还没有活动管理员时生效)
|
# 启动自举管理员(仅在当前库里还没有活动管理员时生效)
|
||||||
|
# 手动部署时取消注释并设置;install.sh 首次生成配置时会提示输入。
|
||||||
ADMIN_EMAIL=admin@example.com
|
ADMIN_EMAIL=admin@example.com
|
||||||
ADMIN_USERNAME=admin
|
ADMIN_USERNAME=admin
|
||||||
ADMIN_PASSWORD=admin123456
|
# ADMIN_PASSWORD=
|
||||||
|
|
||||||
# ==================== 可选配置(有默认值) ====================
|
# ==================== 可选配置(有默认值) ====================
|
||||||
|
|
||||||
# 支付回调共享密钥(公开 /api/payment/callback/* 入口必须携带 x-payment-callback-token)
|
|
||||||
# 建议使用 32+ 位随机字符串
|
|
||||||
# PAYMENT_CALLBACK_SECRET=change-this-to-a-secure-callback-secret
|
|
||||||
|
|
||||||
# docker compose 下 app 启动前自动执行 pending migration/backfill(默认 true)
|
# docker compose 下 app 启动前自动执行 pending migration/backfill(默认 true)
|
||||||
# AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true
|
# AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true
|
||||||
|
|
||||||
|
|||||||
80
.github/workflows/docker-publish.yml
vendored
80
.github/workflows/docker-publish.yml
vendored
@@ -6,7 +6,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -135,7 +135,8 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=raw,value=pre,enable=${{ contains(github.ref, '-') }}
|
type=raw,value=rc,enable=${{ contains(github.ref, '-rc') }}
|
||||||
|
type=raw,value=pre,enable=${{ contains(github.ref, '-') && !contains(github.ref, '-rc') }}
|
||||||
type=raw,value=fix,enable=${{ contains(github.ref, '-fix') }}
|
type=raw,value=fix,enable=${{ contains(github.ref, '-fix') }}
|
||||||
type=sha,prefix=
|
type=sha,prefix=
|
||||||
flavor: |
|
flavor: |
|
||||||
@@ -150,3 +151,78 @@ jobs:
|
|||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
|
package:
|
||||||
|
name: Release tarballs
|
||||||
|
needs: [frontend, build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
- name: Build release packages
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
|
||||||
|
VERSION="${GITHUB_REF_NAME}"
|
||||||
|
else
|
||||||
|
VERSION="snapshot-${GITHUB_SHA::7}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p package release-assets
|
||||||
|
for arch in amd64 arm64; do
|
||||||
|
bundle="aether-${VERSION}-linux-${arch}"
|
||||||
|
root="package/${bundle}"
|
||||||
|
mkdir -p \
|
||||||
|
"${root}/bin" \
|
||||||
|
"${root}/frontend"
|
||||||
|
|
||||||
|
install -m 0755 "artifacts/aether-gateway-${arch}/aether-gateway" "${root}/bin/aether-gateway"
|
||||||
|
cp -R artifacts/frontend-dist/. "${root}/frontend/"
|
||||||
|
sed "s/^VERSION=\"\${AETHER_VERSION:-}\"/VERSION=\"\${AETHER_VERSION:-${VERSION}}\"/" install.sh > "${root}/install.sh"
|
||||||
|
chmod 0755 "${root}/install.sh"
|
||||||
|
install -m 0644 docker-compose.yml "${root}/docker-compose.yml"
|
||||||
|
install -m 0644 .env.example "${root}/.env.example"
|
||||||
|
install -m 0755 generate_keys.sh "${root}/generate_keys.sh"
|
||||||
|
install -m 0644 README.md "${root}/README.md"
|
||||||
|
install -m 0644 LICENSE "${root}/LICENSE"
|
||||||
|
|
||||||
|
tar -C package -czf "release-assets/${bundle}.tar.gz" "${bundle}"
|
||||||
|
done
|
||||||
|
|
||||||
|
sed "s/^VERSION=\"\${AETHER_VERSION:-}\"/VERSION=\"\${AETHER_VERSION:-${VERSION}}\"/" install.sh > release-assets/install.sh
|
||||||
|
chmod +x release-assets/install.sh
|
||||||
|
(cd release-assets && sha256sum *.tar.gz > SHA256SUMS)
|
||||||
|
|
||||||
|
- name: Upload release package artifact
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: release-assets
|
||||||
|
path: release-assets/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
github-release:
|
||||||
|
name: GitHub Release assets
|
||||||
|
needs: [docker, package]
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download release package artifact
|
||||||
|
uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
name: release-assets
|
||||||
|
path: release-assets
|
||||||
|
|
||||||
|
- name: Publish GitHub Release assets
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
generate_release_notes: true
|
||||||
|
files: |
|
||||||
|
release-assets/*.tar.gz
|
||||||
|
release-assets/SHA256SUMS
|
||||||
|
release-assets/install.sh
|
||||||
|
|||||||
69
README.md
69
README.md
@@ -44,7 +44,8 @@ cd Aether
|
|||||||
|
|
||||||
# 2. 配置环境变量
|
# 2. 配置环境变量
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
./generate_keys.sh # 生成密钥, 并将生成的密钥填入 .env
|
./generate_keys.sh # 生成 JWT_SECRET_KEY / ENCRYPTION_KEY, 并填入 .env
|
||||||
|
# 编辑 .env 设置 ADMIN_PASSWORD
|
||||||
|
|
||||||
# 3. 首次部署 / 更新
|
# 3. 首次部署 / 更新
|
||||||
docker compose pull && docker compose up -d
|
docker compose pull && docker compose up -d
|
||||||
@@ -68,13 +69,64 @@ cd Aether
|
|||||||
|
|
||||||
# 2. 配置环境变量
|
# 2. 配置环境变量
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
./generate_keys.sh # 生成密钥, 并将生成的密钥填入 .env
|
./generate_keys.sh # 生成 JWT_SECRET_KEY / ENCRYPTION_KEY, 并填入 .env
|
||||||
|
# 编辑 .env 设置 ADMIN_PASSWORD
|
||||||
|
|
||||||
# 3. 部署 / 更新(自动构建并启动)
|
# 3. 部署 / 更新(自动构建并启动)
|
||||||
git pull
|
git pull
|
||||||
./deploy.sh
|
./deploy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 一键安装(可选部署方式)
|
||||||
|
|
||||||
|
安装脚本先从 `aether-rust-pioneer` 分支下载,不依赖 GitHub Release 的 `latest` 脚本地址。运行后会先选择版本,再选择部署方式。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/fawney19/Aether/aether-rust-pioneer/install.sh | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
运行后按提示输入版本和部署方式。固定安装某个 tag 时,版本选择选 `2`,再输入类似 `v0.7.0-rc22` 的 tag。
|
||||||
|
如果安装目录里已经有配置,脚本会优先复用:Docker Compose 保留已有 `.env`,systemd 保留已有 `/etc/aether/aether-gateway.env`。只有首次生成新配置时才会提示输入管理员密码。
|
||||||
|
|
||||||
|
```text
|
||||||
|
Choose Aether version:
|
||||||
|
1) Latest rc tag
|
||||||
|
2) Exact tag, for example v0.7.0-rc22
|
||||||
|
|
||||||
|
Enter choice [1]:
|
||||||
|
|
||||||
|
Choose Aether deployment mode:
|
||||||
|
1) Docker Compose: app + Postgres + Redis
|
||||||
|
2) Single-node service: systemd + SQLite + in-process runtime
|
||||||
|
3) Cluster node service: systemd + shared database + Redis
|
||||||
|
|
||||||
|
Enter choice [2]:
|
||||||
|
```
|
||||||
|
|
||||||
|
安装后的常用命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status aether-gateway --no-pager
|
||||||
|
sudo journalctl -u aether-gateway -f
|
||||||
|
sudo systemctl restart aether-gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
默认单机数据和日志都在安装目录内:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/opt/aether/data/aether.db
|
||||||
|
/opt/aether/logs
|
||||||
|
```
|
||||||
|
|
||||||
|
多节点不能使用 SQLite 或 `AETHER_RUNTIME_BACKEND=memory`。如果先只生成了多节点模板,编辑 `/etc/aether/aether-gateway.env` 后重跑安装脚本即可:
|
||||||
|
|
||||||
|
```env
|
||||||
|
AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=multi-node
|
||||||
|
AETHER_GATEWAY_NODE_ROLE=frontdoor
|
||||||
|
DATABASE_URL=postgresql://...
|
||||||
|
REDIS_URL=redis://...
|
||||||
|
```
|
||||||
|
|
||||||
### 本地开发
|
### 本地开发
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -131,25 +183,26 @@ Aether Proxy 是配套的正向代理节点,部署在海外 VPS 上,为墙
|
|||||||
|
|
||||||
## 环境变量
|
## 环境变量
|
||||||
|
|
||||||
部署建议直接参考对应示例文件:
|
部署建议:
|
||||||
|
|
||||||
- Docker Compose:根目录 [`.env.example`](.env.example)
|
- Docker Compose:根目录 [`.env.example`](.env.example)
|
||||||
- systemd 二进制部署:[deploy/systemd/aether-gateway.env.example](deploy/systemd/aether-gateway.env.example)
|
- systemd 二进制部署:使用根目录 `install.sh` 生成 `/etc/aether/aether-gateway.env`
|
||||||
|
|
||||||
当前主链路真正要关注的是这组变量:
|
当前主链路真正要关注的是这组变量:
|
||||||
|
|
||||||
- `APP_PORT`:`aether-gateway` 唯一监听端口,固定绑定 `0.0.0.0:${APP_PORT}`
|
- `APP_PORT`:`aether-gateway` 唯一监听端口,固定绑定 `0.0.0.0:${APP_PORT}`
|
||||||
- `DATABASE_URL` / `REDIS_URL`:`aether-gateway` 直接读取的共享后端连接串
|
- `AETHER_DATABASE_DRIVER` / `AETHER_DATABASE_URL`:二进制单机部署可用 `sqlite`,例如 `sqlite:///opt/aether/data/aether.db`
|
||||||
|
- `DATABASE_URL` / `REDIS_URL`:`aether-gateway` 直接读取的共享后端连接串;多节点必须配置共享数据库和 Redis
|
||||||
|
- `AETHER_RUNTIME_BACKEND=memory|redis`:单机 SQLite 默认用 `memory`;多节点必须用 Redis
|
||||||
- `AETHER_GATEWAY_AUTO_PREPARE_DATABASE`:常规启动前自动执行挂起的 schema migration 和 backfill;仓库自带的 `docker-compose.yml` 和 `docker-compose.build.yml` 默认开启
|
- `AETHER_GATEWAY_AUTO_PREPARE_DATABASE`:常规启动前自动执行挂起的 schema migration 和 backfill;仓库自带的 `docker-compose.yml` 和 `docker-compose.build.yml` 默认开启
|
||||||
- `JWT_SECRET_KEY` / `ENCRYPTION_KEY`:认证和敏感数据加密所需密钥
|
- `JWT_SECRET_KEY` / `ENCRYPTION_KEY`:认证和敏感数据加密所需密钥
|
||||||
- `API_KEY_PREFIX`:用户和管理员新建 API Key 时使用的前缀,默认 `sk`
|
- `API_KEY_PREFIX`:用户和管理员新建 API Key 时使用的前缀,默认 `sk`
|
||||||
- `PAYMENT_CALLBACK_SECRET`:支付回调公开入口的共享密钥;未配置时相关路由保持禁用
|
- `ADMIN_USERNAME` / `ADMIN_PASSWORD` / `ADMIN_EMAIL`:首次启动时自举首个本地管理员;`install.sh` 会提示输入管理员密码
|
||||||
- `ADMIN_USERNAME` / `ADMIN_PASSWORD` / `ADMIN_EMAIL`:首次启动时自举首个本地管理员
|
|
||||||
- `CORS_ORIGINS` / `CORS_ALLOW_CREDENTIALS`:前端跨域来源控制;如果要跨域带登录 Cookie,`CORS_ORIGINS` 不能写 `*`
|
- `CORS_ORIGINS` / `CORS_ALLOW_CREDENTIALS`:前端跨域来源控制;如果要跨域带登录 Cookie,`CORS_ORIGINS` 不能写 `*`
|
||||||
- `AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=single-node|multi-node`
|
- `AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=single-node|multi-node`
|
||||||
- `AETHER_GATEWAY_NODE_ROLE=all|frontdoor|background`
|
- `AETHER_GATEWAY_NODE_ROLE=all|frontdoor|background`
|
||||||
- `RUST_LOG`:Rust 日志过滤,例如 `aether_gateway=info`、`aether_gateway=debug,sqlx=warn`
|
- `RUST_LOG`:Rust 日志过滤,例如 `aether_gateway=info`、`aether_gateway=debug,sqlx=warn`
|
||||||
- 如果使用仓库内置的数据栈 compose,再额外配置 `DB_PASSWORD` / `REDIS_PASSWORD`
|
- Docker Compose 的 `DB_PASSWORD` / `REDIS_PASSWORD` 默认使用 `aether`
|
||||||
|
|
||||||
systemd 的 `.env` 必须保持简单 `KEY=VALUE` 形式,不要写 `export`、`${VAR}` 或命令替换。
|
systemd 的 `.env` 必须保持简单 `KEY=VALUE` 形式,不要写 `export`、`${VAR}` 或命令替换。
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: ${POSTGRES_IMAGE:-postgres:15}
|
|
||||||
container_name: aether-postgres
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${DB_NAME:-aether}
|
|
||||||
POSTGRES_USER: ${DB_USER:-postgres}
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
||||||
TZ: ${TZ:-Asia/Shanghai}
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:${DB_PORT:-5432}:5432"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-aether}"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: ${REDIS_IMAGE:-redis:7-alpine}
|
|
||||||
container_name: aether-redis
|
|
||||||
environment:
|
|
||||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
|
||||||
TZ: ${TZ:-Asia/Shanghai}
|
|
||||||
volumes:
|
|
||||||
- redis_data:/data
|
|
||||||
command: >
|
|
||||||
sh -c 'exec redis-server
|
|
||||||
--appendonly yes
|
|
||||||
--appendfsync everysec
|
|
||||||
--save 60 1000
|
|
||||||
--requirepass "$$REDIS_PASSWORD"'
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:${REDIS_PORT:-6379}:6379"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" ping | grep -q PONG"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 5
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
mysql:
|
|
||||||
image: ${MYSQL_IMAGE:-mysql:8.0}
|
|
||||||
container_name: aether-mysql
|
|
||||||
profiles:
|
|
||||||
- mysql
|
|
||||||
environment:
|
|
||||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-aether}
|
|
||||||
MYSQL_USER: ${MYSQL_USER:-aether}
|
|
||||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-aether}
|
|
||||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-aether_root}
|
|
||||||
TZ: ${TZ:-Asia/Shanghai}
|
|
||||||
volumes:
|
|
||||||
- mysql_data:/var/lib/mysql
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:${MYSQL_PORT:-3306}:3306"
|
|
||||||
healthcheck:
|
|
||||||
test:
|
|
||||||
[
|
|
||||||
"CMD-SHELL",
|
|
||||||
"mysqladmin ping -h 127.0.0.1 -u$${MYSQL_USER} -p$${MYSQL_PASSWORD} --silent"
|
|
||||||
]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
mysql_data:
|
|
||||||
redis_data:
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# systemd 的 EnvironmentFile 只适合简单的 KEY=VALUE 行。
|
|
||||||
# 不要写 export、命令替换、变量引用或其他 shell 语法。
|
|
||||||
|
|
||||||
ENVIRONMENT=production
|
|
||||||
TZ=Asia/Shanghai
|
|
||||||
RUST_LOG=aether_gateway=info
|
|
||||||
AETHER_LOG_DESTINATION=both
|
|
||||||
AETHER_LOG_FORMAT=pretty
|
|
||||||
AETHER_LOG_DIR=/var/log/aether
|
|
||||||
AETHER_LOG_ROTATION=daily
|
|
||||||
AETHER_LOG_RETENTION_DAYS=7
|
|
||||||
AETHER_LOG_MAX_FILES=30
|
|
||||||
|
|
||||||
# aether-gateway 本体
|
|
||||||
APP_PORT=8084
|
|
||||||
AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=single-node
|
|
||||||
AETHER_GATEWAY_NODE_ROLE=all
|
|
||||||
AETHER_GATEWAY_STATIC_DIR=/opt/aether/current/frontend
|
|
||||||
AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE=rust-authoritative
|
|
||||||
API_KEY_PREFIX=sk
|
|
||||||
|
|
||||||
# 数据库 / Redis
|
|
||||||
DB_NAME=aether
|
|
||||||
DB_USER=postgres
|
|
||||||
DB_PORT=5432
|
|
||||||
DB_PASSWORD=change-me-postgres-password
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_PASSWORD=change-me-redis-password
|
|
||||||
|
|
||||||
# gateway 直接读取的连接串。请与上面的口令保持一致。
|
|
||||||
DATABASE_URL=postgresql://postgres:change-me-postgres-password@127.0.0.1:5432/aether
|
|
||||||
REDIS_URL=redis://:change-me-redis-password@127.0.0.1:6379/0
|
|
||||||
|
|
||||||
# 安全相关
|
|
||||||
JWT_SECRET_KEY=change-this-to-a-secure-random-jwt-secret
|
|
||||||
ENCRYPTION_KEY=change-this-to-a-secure-random-encryption-key
|
|
||||||
PAYMENT_CALLBACK_SECRET=change-this-to-a-secure-random-callback-secret
|
|
||||||
|
|
||||||
# 可选: 首次启动时按环境变量自举本地管理员。
|
|
||||||
# 仅在 users 表里还没有活动管理员时生效;已有管理员后会自动跳过。
|
|
||||||
# ADMIN_EMAIL=admin@example.com
|
|
||||||
# ADMIN_USERNAME=admin
|
|
||||||
# ADMIN_PASSWORD=change-this-admin-password
|
|
||||||
|
|
||||||
# 可选: 跨域前端源。若要跨域带登录 Cookie,不要写 *,必须显式列出源。
|
|
||||||
# CORS_ORIGINS=https://app.example.com
|
|
||||||
# CORS_ALLOW_CREDENTIALS=true
|
|
||||||
# AUTH_REFRESH_COOKIE_SAMESITE=None
|
|
||||||
# AUTH_REFRESH_COOKIE_SECURE=true
|
|
||||||
|
|
||||||
# 可选: 多实例/分布式并发时启用
|
|
||||||
# 切到 multi-node 后,启动会拒绝本地 video task 文件状态,并要求 Postgres/Redis 共享后端可用。
|
|
||||||
# AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=multi-node
|
|
||||||
# AETHER_GATEWAY_NODE_ROLE=frontdoor
|
|
||||||
# 另起一台后台节点时使用:
|
|
||||||
# AETHER_GATEWAY_NODE_ROLE=background
|
|
||||||
# AETHER_GATEWAY_INSTANCE_ID=gateway-node-1
|
|
||||||
# AETHER_TUNNEL_RELAY_BASE_URL=https://gateway-node-1.internal
|
|
||||||
# AETHER_GATEWAY_DISTRIBUTED_REQUEST_LIMIT=200
|
|
||||||
# AETHER_GATEWAY_DISTRIBUTED_REQUEST_REDIS_URL=redis://:change-me-redis-password@127.0.0.1:6379/0
|
|
||||||
# 不单独填写时,会回退复用 REDIS_URL / AETHER_GATEWAY_DATA_REDIS_URL。
|
|
||||||
|
|
||||||
# 可选: 仅单机时再启用本地文件状态。未来做集群时不要依赖本地磁盘。
|
|
||||||
# AETHER_GATEWAY_VIDEO_TASK_STORE_PATH=/var/lib/aether/video-tasks.json
|
|
||||||
|
|
||||||
# 单机极简模式:
|
|
||||||
# 不配置 DATABASE_URL / REDIS_URL 也可以启动,但会退化成“本地文件 + 内存兜底”,
|
|
||||||
# 管理/认证/账单/统计等需要共享持久层的功能不会完整可用。
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Aether Gateway
|
|
||||||
Documentation=https://github.com/fawney19/Aether
|
|
||||||
After=network-online.target
|
|
||||||
Wants=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=__SERVICE_USER__
|
|
||||||
Group=__SERVICE_GROUP__
|
|
||||||
WorkingDirectory=__INSTALL_ROOT__/current
|
|
||||||
EnvironmentFile=__ENV_TARGET__
|
|
||||||
ExecStart=__INSTALL_ROOT__/current/bin/aether-gateway
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=3
|
|
||||||
TimeoutStopSec=20
|
|
||||||
UMask=0027
|
|
||||||
LimitNOFILE=65535
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateTmp=true
|
|
||||||
StateDirectory=aether
|
|
||||||
StateDirectoryMode=0750
|
|
||||||
LogsDirectory=aether
|
|
||||||
LogsDirectoryMode=0750
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@@ -1,418 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SERVICE_NAME="aether-gateway"
|
|
||||||
SERVICE_USER="${SERVICE_USER:-aether}"
|
|
||||||
SERVICE_GROUP="${SERVICE_GROUP:-aether}"
|
|
||||||
INSTALL_ROOT="${INSTALL_ROOT:-/opt/aether}"
|
|
||||||
CONFIG_DIR="${CONFIG_DIR:-/etc/aether}"
|
|
||||||
ENV_TARGET="${CONFIG_DIR}/aether-gateway.env"
|
|
||||||
SYSTEMD_UNIT_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
|
|
||||||
|
|
||||||
BIN_SOURCE="${REPO_ROOT}/target/release/aether-gateway"
|
|
||||||
FRONTEND_SOURCE="${REPO_ROOT}/frontend/dist"
|
|
||||||
SERVICE_SOURCE="${REPO_ROOT}/deploy/systemd/aether-gateway.service"
|
|
||||||
ENV_EXAMPLE_SOURCE="${REPO_ROOT}/deploy/systemd/aether-gateway.env.example"
|
|
||||||
DATA_COMPOSE_SOURCE="${REPO_ROOT}/deploy/docker-compose.data.yml"
|
|
||||||
|
|
||||||
ENV_SOURCE=""
|
|
||||||
RELEASE_ID="${RELEASE_ID:-$(date +%Y%m%d%H%M%S)}"
|
|
||||||
SKIP_START="false"
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<'EOF'
|
|
||||||
Usage: sudo deploy/systemd/install-systemd.sh [options]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--env-file PATH Copy PATH to /etc/aether/aether-gateway.env before install
|
|
||||||
--release-id ID Release identifier under /opt/aether/releases/ (default: timestamp)
|
|
||||||
--bin-source PATH Built aether-gateway binary (default: target/release/aether-gateway)
|
|
||||||
--frontend-source PATH
|
|
||||||
Built frontend directory (default: frontend/dist)
|
|
||||||
--skip-start Install files and unit, but do not restart the service
|
|
||||||
-h, --help Show this help
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
die() {
|
|
||||||
echo "ERROR: $*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
info() {
|
|
||||||
echo ">>> $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
warn() {
|
|
||||||
echo "WARNING: $*" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
require_root() {
|
|
||||||
if [[ "${EUID}" -ne 0 ]]; then
|
|
||||||
die "run as root"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
require_linux_systemd() {
|
|
||||||
[[ "$(uname -s)" == "Linux" ]] || die "systemd deployment is only supported on Linux"
|
|
||||||
command -v systemctl >/dev/null 2>&1 || die "systemctl not found"
|
|
||||||
}
|
|
||||||
|
|
||||||
trim_whitespace() {
|
|
||||||
local value="$1"
|
|
||||||
value="${value#"${value%%[![:space:]]*}"}"
|
|
||||||
value="${value%"${value##*[![:space:]]}"}"
|
|
||||||
printf '%s' "${value}"
|
|
||||||
}
|
|
||||||
|
|
||||||
strip_optional_quotes() {
|
|
||||||
local value="$1"
|
|
||||||
if [[ ${#value} -ge 2 ]]; then
|
|
||||||
if [[ "${value:0:1}" == "\"" && "${value: -1}" == "\"" ]]; then
|
|
||||||
value="${value:1:${#value}-2}"
|
|
||||||
elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then
|
|
||||||
value="${value:1:${#value}-2}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
printf '%s' "${value}"
|
|
||||||
}
|
|
||||||
|
|
||||||
is_placeholder_value() {
|
|
||||||
local value="$1"
|
|
||||||
case "${value}" in
|
|
||||||
*change-me*|*change-this*|*your_secure_password_here*|*your_redis_password_here*)
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
find_nologin_shell() {
|
|
||||||
if [[ -x /usr/sbin/nologin ]]; then
|
|
||||||
echo "/usr/sbin/nologin"
|
|
||||||
elif [[ -x /sbin/nologin ]]; then
|
|
||||||
echo "/sbin/nologin"
|
|
||||||
else
|
|
||||||
echo "/bin/false"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_service_account() {
|
|
||||||
if ! getent group "${SERVICE_GROUP}" >/dev/null 2>&1; then
|
|
||||||
info "creating group ${SERVICE_GROUP}"
|
|
||||||
groupadd --system "${SERVICE_GROUP}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
|
|
||||||
info "creating user ${SERVICE_USER}"
|
|
||||||
useradd \
|
|
||||||
--system \
|
|
||||||
--gid "${SERVICE_GROUP}" \
|
|
||||||
--home-dir "${INSTALL_ROOT}" \
|
|
||||||
--shell "$(find_nologin_shell)" \
|
|
||||||
"${SERVICE_USER}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_sources_exist() {
|
|
||||||
[[ -x "${BIN_SOURCE}" ]] || die "binary not found or not executable: ${BIN_SOURCE}. Run: cargo build --release -p aether-gateway"
|
|
||||||
[[ -d "${FRONTEND_SOURCE}" ]] || die "frontend dist not found: ${FRONTEND_SOURCE}. Run: (cd frontend && npm ci && npm run build)"
|
|
||||||
[[ -f "${SERVICE_SOURCE}" ]] || die "service unit template not found: ${SERVICE_SOURCE}"
|
|
||||||
[[ -f "${ENV_EXAMPLE_SOURCE}" ]] || die "env example not found: ${ENV_EXAMPLE_SOURCE}"
|
|
||||||
[[ -f "${DATA_COMPOSE_SOURCE}" ]] || die "data compose file not found: ${DATA_COMPOSE_SOURCE}"
|
|
||||||
if [[ -n "${ENV_SOURCE}" ]]; then
|
|
||||||
[[ -f "${ENV_SOURCE}" ]] || die "env file not found: ${ENV_SOURCE}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_args() {
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
--env-file)
|
|
||||||
[[ $# -ge 2 ]] || die "--env-file requires a path"
|
|
||||||
ENV_SOURCE="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--release-id)
|
|
||||||
[[ $# -ge 2 ]] || die "--release-id requires a value"
|
|
||||||
RELEASE_ID="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--bin-source)
|
|
||||||
[[ $# -ge 2 ]] || die "--bin-source requires a path"
|
|
||||||
BIN_SOURCE="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--frontend-source)
|
|
||||||
[[ $# -ge 2 ]] || die "--frontend-source requires a path"
|
|
||||||
FRONTEND_SOURCE="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--skip-start)
|
|
||||||
SKIP_START="true"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
die "unknown argument: $1"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
install_env_file() {
|
|
||||||
install -d -m 0750 "${CONFIG_DIR}"
|
|
||||||
|
|
||||||
if [[ -n "${ENV_SOURCE}" ]]; then
|
|
||||||
info "installing env file to ${ENV_TARGET}"
|
|
||||||
install -m 0600 "${ENV_SOURCE}" "${ENV_TARGET}"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -f "${ENV_TARGET}" ]]; then
|
|
||||||
info "creating env template at ${ENV_TARGET}"
|
|
||||||
install -m 0600 "${ENV_EXAMPLE_SOURCE}" "${ENV_TARGET}"
|
|
||||||
cat >&2 <<EOF
|
|
||||||
|
|
||||||
The env file did not exist, so a template was created:
|
|
||||||
${ENV_TARGET}
|
|
||||||
|
|
||||||
Edit it, then rerun:
|
|
||||||
sudo deploy/systemd/install-systemd.sh --env-file ${ENV_TARGET}
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_env_file() {
|
|
||||||
local env_file="$1"
|
|
||||||
local raw_line=""
|
|
||||||
local line=""
|
|
||||||
local key=""
|
|
||||||
local value=""
|
|
||||||
local line_no=0
|
|
||||||
local topology="single-node"
|
|
||||||
local node_role="all"
|
|
||||||
local db_password=""
|
|
||||||
local redis_password=""
|
|
||||||
local database_url=""
|
|
||||||
local redis_url=""
|
|
||||||
local jwt_secret_key=""
|
|
||||||
local encryption_key=""
|
|
||||||
local payment_callback_secret=""
|
|
||||||
local video_task_store_path=""
|
|
||||||
local static_dir=""
|
|
||||||
|
|
||||||
[[ -f "${env_file}" ]] || die "env file not found: ${env_file}"
|
|
||||||
|
|
||||||
info "validating env file ${env_file}"
|
|
||||||
while IFS= read -r raw_line || [[ -n "${raw_line}" ]]; do
|
|
||||||
line_no=$((line_no + 1))
|
|
||||||
line="${raw_line%$'\r'}"
|
|
||||||
line="$(trim_whitespace "${line}")"
|
|
||||||
|
|
||||||
[[ -z "${line}" ]] && continue
|
|
||||||
[[ "${line:0:1}" == "#" ]] && continue
|
|
||||||
|
|
||||||
[[ "${line}" == export\ * ]] && die "env file ${env_file}:${line_no} must not use 'export'"
|
|
||||||
[[ "${line}" == *'${'* ]] && die "env file ${env_file}:${line_no} must not use variable expansion"
|
|
||||||
[[ "${line}" == *'$('* ]] && die "env file ${env_file}:${line_no} must not use command substitution"
|
|
||||||
[[ "${line}" == *'`'* ]] && die "env file ${env_file}:${line_no} must not use command substitution"
|
|
||||||
[[ "${line}" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]] || die "env file ${env_file}:${line_no} must be KEY=VALUE"
|
|
||||||
|
|
||||||
key="${line%%=*}"
|
|
||||||
value="${line#*=}"
|
|
||||||
value="$(strip_optional_quotes "${value}")"
|
|
||||||
|
|
||||||
case "${key}" in
|
|
||||||
AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY)
|
|
||||||
topology="${value}"
|
|
||||||
;;
|
|
||||||
AETHER_GATEWAY_NODE_ROLE)
|
|
||||||
node_role="${value}"
|
|
||||||
;;
|
|
||||||
DATABASE_URL|AETHER_GATEWAY_DATA_POSTGRES_URL)
|
|
||||||
[[ -n "${value}" ]] && database_url="${value}"
|
|
||||||
;;
|
|
||||||
REDIS_URL|AETHER_GATEWAY_DATA_REDIS_URL)
|
|
||||||
[[ -n "${value}" ]] && redis_url="${value}"
|
|
||||||
;;
|
|
||||||
DB_PASSWORD)
|
|
||||||
db_password="${value}"
|
|
||||||
;;
|
|
||||||
REDIS_PASSWORD)
|
|
||||||
redis_password="${value}"
|
|
||||||
;;
|
|
||||||
JWT_SECRET_KEY)
|
|
||||||
jwt_secret_key="${value}"
|
|
||||||
;;
|
|
||||||
ENCRYPTION_KEY|AETHER_GATEWAY_DATA_ENCRYPTION_KEY)
|
|
||||||
[[ -n "${value}" ]] && encryption_key="${value}"
|
|
||||||
;;
|
|
||||||
PAYMENT_CALLBACK_SECRET)
|
|
||||||
payment_callback_secret="${value}"
|
|
||||||
;;
|
|
||||||
AETHER_GATEWAY_VIDEO_TASK_STORE_PATH)
|
|
||||||
video_task_store_path="${value}"
|
|
||||||
;;
|
|
||||||
AETHER_GATEWAY_STATIC_DIR)
|
|
||||||
static_dir="${value}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done < "${env_file}"
|
|
||||||
|
|
||||||
case "${topology}" in
|
|
||||||
single-node|multi-node)
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
die "AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY must be single-node or multi-node"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case "${node_role}" in
|
|
||||||
all|frontdoor|background)
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
die "AETHER_GATEWAY_NODE_ROLE must be all, frontdoor, or background"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
[[ -n "${jwt_secret_key}" ]] || die "JWT_SECRET_KEY is required"
|
|
||||||
[[ -n "${encryption_key}" ]] || die "ENCRYPTION_KEY or AETHER_GATEWAY_DATA_ENCRYPTION_KEY is required"
|
|
||||||
|
|
||||||
is_placeholder_value "${jwt_secret_key}" && die "JWT_SECRET_KEY still uses the example placeholder"
|
|
||||||
is_placeholder_value "${encryption_key}" && die "ENCRYPTION_KEY still uses the example placeholder"
|
|
||||||
if [[ -n "${database_url}" ]] && is_placeholder_value "${database_url}"; then
|
|
||||||
die "DATABASE_URL still uses the example placeholder"
|
|
||||||
fi
|
|
||||||
if [[ -n "${redis_url}" ]] && is_placeholder_value "${redis_url}"; then
|
|
||||||
die "REDIS_URL still uses the example placeholder"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${topology}" == "multi-node" ]]; then
|
|
||||||
[[ "${node_role}" != "all" ]] || die "multi-node deployment requires AETHER_GATEWAY_NODE_ROLE=frontdoor or background"
|
|
||||||
[[ -n "${database_url}" ]] || die "multi-node deployment requires DATABASE_URL or AETHER_GATEWAY_DATA_POSTGRES_URL"
|
|
||||||
[[ -n "${redis_url}" ]] || die "multi-node deployment requires REDIS_URL or AETHER_GATEWAY_DATA_REDIS_URL"
|
|
||||||
[[ -z "${video_task_store_path}" ]] || die "multi-node deployment must not set AETHER_GATEWAY_VIDEO_TASK_STORE_PATH"
|
|
||||||
else
|
|
||||||
if [[ "${node_role}" != "all" ]]; then
|
|
||||||
warn "single-node deployment usually uses AETHER_GATEWAY_NODE_ROLE=all; split roles are only useful for cluster drills"
|
|
||||||
fi
|
|
||||||
if [[ -z "${database_url}" || -z "${redis_url}" ]]; then
|
|
||||||
warn "single-node env is running in minimal mode without full Postgres/Redis persistence"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${payment_callback_secret}" ]]; then
|
|
||||||
warn "PAYMENT_CALLBACK_SECRET is unset; public payment callback routes will stay disabled"
|
|
||||||
elif is_placeholder_value "${payment_callback_secret}"; then
|
|
||||||
warn "PAYMENT_CALLBACK_SECRET still uses the example placeholder"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if is_placeholder_value "${db_password}"; then
|
|
||||||
warn "DB_PASSWORD still uses the example placeholder"
|
|
||||||
fi
|
|
||||||
if is_placeholder_value "${redis_password}"; then
|
|
||||||
warn "REDIS_PASSWORD still uses the example placeholder"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${static_dir}" && "${static_dir}" != "${INSTALL_ROOT}/current/frontend" ]]; then
|
|
||||||
warn "AETHER_GATEWAY_STATIC_DIR points to ${static_dir}; install script still publishes frontend to ${INSTALL_ROOT}/current/frontend"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
install_release() {
|
|
||||||
local release_dir="${INSTALL_ROOT}/releases/${RELEASE_ID}"
|
|
||||||
local current_link="${INSTALL_ROOT}/current"
|
|
||||||
|
|
||||||
info "installing release ${RELEASE_ID} into ${release_dir}"
|
|
||||||
install -d -m 0755 "${INSTALL_ROOT}" "${INSTALL_ROOT}/releases" "${INSTALL_ROOT}/shared"
|
|
||||||
rm -rf "${release_dir}"
|
|
||||||
install -d -m 0755 "${release_dir}/bin" "${release_dir}/frontend"
|
|
||||||
install -m 0755 "${BIN_SOURCE}" "${release_dir}/bin/aether-gateway"
|
|
||||||
cp -R "${FRONTEND_SOURCE}/." "${release_dir}/frontend/"
|
|
||||||
chmod -R u=rwX,go=rX "${release_dir}"
|
|
||||||
chown -R root:root "${release_dir}"
|
|
||||||
ln -sfn "${release_dir}" "${current_link}"
|
|
||||||
|
|
||||||
install -m 0644 "${DATA_COMPOSE_SOURCE}" "${INSTALL_ROOT}/shared/docker-compose.data.yml"
|
|
||||||
install -d -o "${SERVICE_USER}" -g "${SERVICE_GROUP}" -m 0750 /var/lib/aether
|
|
||||||
}
|
|
||||||
|
|
||||||
install_systemd_unit() {
|
|
||||||
local rendered_unit
|
|
||||||
|
|
||||||
info "installing systemd unit to ${SYSTEMD_UNIT_PATH}"
|
|
||||||
rendered_unit="$(mktemp)"
|
|
||||||
sed \
|
|
||||||
-e "s|__SERVICE_USER__|${SERVICE_USER}|g" \
|
|
||||||
-e "s|__SERVICE_GROUP__|${SERVICE_GROUP}|g" \
|
|
||||||
-e "s|__INSTALL_ROOT__|${INSTALL_ROOT}|g" \
|
|
||||||
-e "s|__ENV_TARGET__|${ENV_TARGET}|g" \
|
|
||||||
"${SERVICE_SOURCE}" > "${rendered_unit}"
|
|
||||||
install -m 0644 "${rendered_unit}" "${SYSTEMD_UNIT_PATH}"
|
|
||||||
rm -f "${rendered_unit}"
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable "${SERVICE_NAME}" >/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
restart_service_if_requested() {
|
|
||||||
if [[ "${SKIP_START}" == "true" ]]; then
|
|
||||||
info "skipping service restart"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "restarting ${SERVICE_NAME}"
|
|
||||||
systemctl restart "${SERVICE_NAME}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_next_steps() {
|
|
||||||
local gateway_port
|
|
||||||
gateway_port="$(awk -F= '/^[[:space:]]*APP_PORT=/{print $2}' "${ENV_TARGET}" | tail -n1 | tr -d '[:space:]')"
|
|
||||||
gateway_port="${gateway_port:-8084}"
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
|
|
||||||
Install complete.
|
|
||||||
|
|
||||||
Gateway service:
|
|
||||||
sudo systemctl status ${SERVICE_NAME} --no-pager
|
|
||||||
sudo journalctl -u ${SERVICE_NAME} -n 100 --no-pager
|
|
||||||
sudo journalctl -u ${SERVICE_NAME} -f
|
|
||||||
|
|
||||||
Data services (Docker only for Postgres/Redis):
|
|
||||||
docker compose --env-file ${ENV_TARGET} -f ${INSTALL_ROOT}/shared/docker-compose.data.yml up -d
|
|
||||||
docker compose --env-file ${ENV_TARGET} -f ${INSTALL_ROOT}/shared/docker-compose.data.yml ps
|
|
||||||
|
|
||||||
Health checks:
|
|
||||||
curl -fsS http://127.0.0.1:${gateway_port}/_gateway/health
|
|
||||||
curl -fsS http://127.0.0.1:${gateway_port}/readyz
|
|
||||||
|
|
||||||
Database:
|
|
||||||
empty database: first service start auto-bootstraps to the current baseline
|
|
||||||
later schema upgrades: ${INSTALL_ROOT}/current/bin/aether-gateway --migrate
|
|
||||||
|
|
||||||
Current release:
|
|
||||||
${INSTALL_ROOT}/current
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_args "$@"
|
|
||||||
require_root
|
|
||||||
require_linux_systemd
|
|
||||||
ensure_sources_exist
|
|
||||||
ensure_service_account
|
|
||||||
install_env_file
|
|
||||||
validate_env_file "${ENV_TARGET}"
|
|
||||||
install_release
|
|
||||||
install_systemd_unit
|
|
||||||
restart_service_if_requested
|
|
||||||
print_next_steps
|
|
||||||
1169
install.sh
Executable file
1169
install.sh
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user