Skip to content

Commit 767257f

Browse files
committed
feat(platforms): add platforms.sh wrapper
1 parent ade29b4 commit 767257f

22 files changed

Lines changed: 419 additions & 90 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ platforms/available/*
77
*.sh
88
!install.sh
99
!manage.sh
10+
!platforms.sh
1011
!docker-entrypoint.sh
12+
!scripts/*.sh
1113
credentials.json
1214
logs/
1315
logos/*.svg

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ RUN --mount=type=cache,sharing=locked,target=/var/cache/apt \
4242
COPY --from=builder /venv /venv
4343
COPY --from=builder /publisher /publisher
4444

45-
RUN chmod +x /publisher/docker-entrypoint.sh
45+
RUN chmod +x /publisher/docker-entrypoint.sh /publisher/scripts/run.sh
4646

4747
ENV PATH="/venv/bin:${PATH}"
4848
ENV MODE=production

INSTALL.md

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ Edit `.env` (see [Configuration](#configuration) below).
7575

7676
### Application Directories
7777

78-
Create the directories referenced in `.env` and assign ownership to the service user. Replace `$SERVICE_USER` with your username (or `relaysms` if you created that account):
78+
Create the directories referenced in `.env` and assign ownership to the service user. Replace `$SERVICE_USER` with your username (or `relaysms` if you created that account). By default, the SQLite database, Celery broker/result/beat files all live under `data/` and the adapters live under `platforms/`:
7979

8080
```bash
8181
SERVICE_USER=$(whoami)
8282

83-
# SQLite database directory (default)
83+
# SQLite database + Celery broker/result/beat schedule directory (default)
8484
sudo mkdir -p data
8585
sudo chown "$SERVICE_USER": data && sudo chmod 750 data
8686

@@ -90,7 +90,13 @@ sudo chown "$SERVICE_USER": platforms/adapters platforms/adapters_venv platforms
9090
sudo chmod 750 platforms/adapters platforms/adapters_venv platforms/adapters_assets
9191
```
9292

93-
If you changed any path variables in `.env`, create those directories instead.
93+
If you changed any of the following path variables in `.env`, create the parent directory of each instead, and see [Install Services](#install-services) below for how to keep the systemd sandbox in sync:
94+
95+
- `SQLITE_DATABASE_PATH`
96+
- `CELERY_BROKER_DB_PATH`, `CELERY_RESULT_DB_PATH` (sqlite broker only)
97+
- `CELERY_BEAT_SCHEDULE_PATH`
98+
- `PLATFORMS_ADAPTERS_DIR`, `PLATFORMS_ADAPTERS_VENV_DIR`, `PLATFORMS_ADAPTERS_ASSETS_DIR`
99+
- `PLATFORMS_REGISTRY_FILE`
94100

95101
### Run Migrations
96102

@@ -101,13 +107,26 @@ make migrate-up
101107

102108
### Install Services
103109

110+
The provided service unit files use a `__RW_PATHS__` placeholder for `ReadWritePaths=` instead of a hardcoded path, since all the paths listed above can be changed in `.env`. Substitute it with the actual, resolved directories before installing (`install.sh` does this for you automatically):
111+
104112
```bash
113+
RW_PATHS="$(pwd)/data $(pwd)/platforms/adapters $(pwd)/platforms/adapters_venv $(pwd)/platforms/adapters_assets"
114+
115+
sudo sed \
116+
-e "s/User=relaysms/User=$SERVICE_USER/" \
117+
-e "s|__RW_PATHS__|$RW_PATHS|" \
118+
-i relaysms-publisher-rest.service relaysms-publisher-grpc.service \
119+
relaysms-publisher-worker.service relaysms-publisher-beat.service
120+
105121
sudo cp relaysms-publisher.target relaysms-publisher-*.service /etc/systemd/system/
106122
sudo systemctl daemon-reload
107123
sudo systemctl enable relaysms-publisher.target
108124
sudo systemctl start relaysms-publisher.target
109125
```
110126

127+
> [!WARNING]
128+
> All four services (`rest`, `grpc`, `worker`, `beat`) run with `ProtectSystem=strict` and `ProtectHome=true`, which make the entire filesystem read-only except for paths explicitly listed in `ReadWritePaths`. If you moved the SQLite database, Celery broker/result/beat files, or any adapter directory outside the installation root, add each resolved parent directory to `RW_PATHS` above - a path missing from `ReadWritePaths` will silently fail to write.
129+
111130
## Service Management
112131

113132
```bash
@@ -122,6 +141,22 @@ sudo systemctl start relaysms-publisher.target
122141
./manage.sh uninstall # Remove installation
123142
```
124143

144+
## Managing Platform Adapters
145+
146+
Use `platforms.sh` instead of calling `python3 -m platforms.cli` directly. It automatically resolves the install directory, loads `.env`, uses the project venv, and runs as the correct service user so adapter files and the registry never end up with mismatched ownership:
147+
148+
```bash
149+
./platforms.sh add <GITHUB_URL> # Add an adapter
150+
./platforms.sh remove <NAME> # Remove an adapter
151+
./platforms.sh update [NAME] [--install] # Update one or all adapters
152+
./platforms.sh list # List registered adapters
153+
./platforms.sh recover # Rebuild registry from disk
154+
./platforms.sh env # Show resolved paths and service user
155+
./platforms.sh shell # Open a shell as the service user with .env loaded
156+
```
157+
158+
See [Platforms Documentation](platforms/README.md) for details.
159+
125160
## Configuration
126161

127162
Edit `/opt/relaysms/relaysms-publisher/.env`:
@@ -177,20 +212,46 @@ DATABASE_FIELD_ENCRYPTION_KEY=<64 hex chars>
177212
> - The `DATABASE_ENCRYPTION_KEY` is used to encrypt the entire database at rest (SQLite only).
178213
> - The `DATABASE_FIELD_ENCRYPTION_KEY` is used to encrypt specific sensitive fields in the database.
179214
215+
### Celery (Worker / Beat)
216+
217+
```bash
218+
# Broker/backend type: sqlite | redis | rabbitmq
219+
CELERY_BROKER_TYPE=sqlite
220+
221+
# sqlite (default)
222+
CELERY_BROKER_DB_PATH=data/celery_broker.db
223+
CELERY_RESULT_DB_PATH=data/celery_results.db
224+
225+
# redis
226+
# CELERY_REDIS_URL=redis://localhost:6379/0
227+
228+
# rabbitmq
229+
# CELERY_RABBITMQ_URL=amqp://user:pass@localhost:5672//
230+
231+
CELERY_BEAT_SCHEDULE_PATH=data/celerybeat-schedule
232+
```
233+
234+
> [!NOTE]
235+
> If you move any of the `sqlite`-backed Celery paths outside the default `data/` directory, remember to add the new parent directory to `ReadWritePaths` when [installing services](#install-services), since the worker and beat units run under `ProtectSystem=strict`.
236+
180237
### Platform Adapters
181238

182239
```bash
183240
PLATFORMS_ADAPTERS_DIR=platforms/adapters
184241
PLATFORMS_ADAPTERS_VENV_DIR=platforms/adapters_venv
185242
PLATFORMS_ADAPTERS_ASSETS_DIR=platforms/adapters_assets
243+
PLATFORMS_REGISTRY_FILE=platforms/registry.json
186244
```
187245

188246
See [Platforms Documentation](platforms/README.md) and individual adapter READMEs for setup.
189247

248+
> [!NOTE]
249+
> `PLATFORMS_REGISTRY_FILE` is written to by `platforms.cli add|remove|update` and read by the running services at startup. Always run `platforms.cli` as the service user (see [Platforms Documentation](platforms/README.md)) so the registry stays writable and readable across CLI runs and service restarts.
250+
190251
| Path | Description |
191252
|---|---|
192253
| `/opt/relaysms/relaysms-publisher/` | Installation root |
193254
| `/opt/relaysms/relaysms-publisher/.env` | Configuration (root:relaysms, 640) |
194-
| `/opt/relaysms/relaysms-publisher/data/` | SQLite database (relaysms, 750) |
255+
| `/opt/relaysms/relaysms-publisher/data/` | SQLite database, Celery broker/result/beat files (relaysms, 750) |
195256
| `/opt/relaysms/relaysms-publisher/platforms/` | Adapter data (relaysms, 750) |
196257
| `/etc/systemd/system/relaysms-publisher*` | Service units |

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ make build-setup
6060
# Run database migrations
6161
make migrate-up
6262

63-
# Start services
64-
python3 grpc_server.py # Terminal 1
65-
fastapi dev app.py # Terminal 2
63+
# Start gRPC, REST API, Celery worker, and Celery beat together
64+
./scripts/run.sh
6665
```
6766

6867
### Docker
@@ -79,14 +78,17 @@ cp template.env .env
7978
docker run -d \
8079
--name relaysms-publisher \
8180
--env-file .env \
82-
-p 9000:9000 \
81+
-p 16000:16000 \
8382
-p 6000:6000 \
8483
-v $(pwd)/data:/publisher/data \
84+
-v $(pwd)/platforms:/publisher/platforms \
8585
relaysms-publisher:latest
8686
```
8787

88+
The container entrypoint runs pending database migrations, then starts the gRPC server, REST API, Celery worker, and Celery beat scheduler together (via `scripts/run.sh`).
89+
8890
> [!TIP]
89-
> Update `GRPC_HOST=0.0.0.0` and `HOST=0.0.0.0` in `.env` for external container access.
91+
> `HOST` and `GRPC_HOST` default to `0.0.0.0` inside the container so it's reachable from outside. If your `.env` file explicitly sets `HOST=127.0.0.1` or `GRPC_HOST=127.0.0.1` (e.g. copied unedited from `template.env`), it will override the container default and the services won't be reachable - remove or update those lines in `.env` for Docker deployments.
9092
9193
## Configuration
9294

docker-entrypoint.sh

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
set -e
44

5-
python3 -u grpc_server.py &
6-
exec python3 -m uvicorn app:app \
7-
--host "${HOST:-0.0.0.0}" \
8-
--port "${PORT:-80}" \
9-
--workers "${WORKERS:-4}" \
10-
--proxy-headers \
11-
--forwarded-allow-ips "*"
5+
export HOST="${HOST:-0.0.0.0}"
6+
export PORT="${PORT:-80}"
7+
export WORKERS="${WORKERS:-4}"
8+
export GRPC_HOST="${GRPC_HOST:-0.0.0.0}"
9+
10+
python3 -m alembic upgrade head
11+
12+
exec ./scripts/run.sh

docs/rest.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ Displays the OAuth2 callback parameters returned by a platform. Intended as a re
129129

130130
**Query Parameters:** All query parameters forwarded by the OAuth provider (e.g., `code`, `state`) are captured and displayed in an HTML table.
131131

132-
**Response:** `200 OK` HTML page listing all callback parameters.
132+
**Response:** `200 OK`, HTML page listing all callback parameters.
133133

134134
**Error Responses:**
135135

install.sh

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -132,34 +132,60 @@ setup_env() {
132132
log "Edit $INSTALL_DIR/.env before starting services"
133133
}
134134

135-
create_app_directories() {
136-
log "Creating application directories"
135+
# Resolves app data directories from .env (absolute or relative to
136+
# INSTALL_DIR) into the global RW_DIRS array. Shared by create_app_directories
137+
# and install_services so systemd ReadWritePaths always matches reality.
138+
resolve_app_directories() {
137139
local envfile="$INSTALL_DIR/.env"
138140
[ -f "$envfile" ] || error ".env not found"
139141

140-
local sqlite_path adapters_dir adapters_venv adapters_assets
142+
local sqlite_path celery_broker_path celery_result_path celery_beat_path
143+
local adapters_dir adapters_venv adapters_assets registry_file
141144
sqlite_path=$(read_env_var "SQLITE_DATABASE_PATH" "$envfile")
145+
celery_broker_path=$(read_env_var "CELERY_BROKER_DB_PATH" "$envfile")
146+
celery_result_path=$(read_env_var "CELERY_RESULT_DB_PATH" "$envfile")
147+
celery_beat_path=$(read_env_var "CELERY_BEAT_SCHEDULE_PATH" "$envfile")
142148
adapters_dir=$(read_env_var "PLATFORMS_ADAPTERS_DIR" "$envfile")
143149
adapters_venv=$(read_env_var "PLATFORMS_ADAPTERS_VENV_DIR" "$envfile")
144150
adapters_assets=$(read_env_var "PLATFORMS_ADAPTERS_ASSETS_DIR" "$envfile")
151+
registry_file=$(read_env_var "PLATFORMS_REGISTRY_FILE" "$envfile")
145152

146-
_make_dir() {
147-
local dir="$1" label="${2:-}"
153+
RW_DIRS=()
154+
_resolve_dir() {
155+
local dir="$1"
148156
[ -z "$dir" ] && return
149157
[[ "$dir" = /* ]] || dir="$INSTALL_DIR/$dir"
150-
mkdir -p "$dir"
151-
chown "$SERVICE_USER:" "$dir"
152-
chmod 750 "$dir"
153-
log " $dir${label:+ ($label)}"
158+
# Skip duplicates (e.g. celery paths sharing the same directory as sqlite).
159+
local existing
160+
for existing in "${RW_DIRS[@]}"; do
161+
[ "$existing" = "$dir" ] && return
162+
done
163+
RW_DIRS+=("$dir")
154164
}
155165

156166
if [ -n "$sqlite_path" ] && [ "$sqlite_path" != ":memory:" ]; then
157-
_make_dir "$(dirname "$sqlite_path")" "sqlite"
167+
_resolve_dir "$(dirname "$sqlite_path")"
158168
fi
169+
[ -n "$celery_broker_path" ] && _resolve_dir "$(dirname "$celery_broker_path")"
170+
[ -n "$celery_result_path" ] && _resolve_dir "$(dirname "$celery_result_path")"
171+
[ -n "$celery_beat_path" ] && _resolve_dir "$(dirname "$celery_beat_path")"
172+
_resolve_dir "$adapters_dir"
173+
_resolve_dir "$adapters_venv"
174+
_resolve_dir "$adapters_assets"
175+
[ -n "$registry_file" ] && _resolve_dir "$(dirname "$registry_file")"
176+
}
159177

160-
_make_dir "$adapters_dir" "adapters"
161-
_make_dir "$adapters_venv" "adapters venv"
162-
_make_dir "$adapters_assets" "adapters assets"
178+
create_app_directories() {
179+
log "Creating application directories"
180+
resolve_app_directories
181+
182+
local dir
183+
for dir in "${RW_DIRS[@]}"; do
184+
mkdir -p "$dir"
185+
chown "$SERVICE_USER:" "$dir"
186+
chmod 750 "$dir"
187+
log " $dir"
188+
done
163189
}
164190

165191
run_migrations() {
@@ -177,9 +203,21 @@ run_migrations() {
177203
install_services() {
178204
log "Installing systemd services"
179205
cd "$INSTALL_DIR"
180-
for svc in relaysms-publisher.target relaysms-publisher-rest.service relaysms-publisher-grpc.service; do
206+
207+
resolve_app_directories
208+
local rw_paths
209+
rw_paths=$(
210+
IFS=' '
211+
echo "${RW_DIRS[*]}"
212+
)
213+
[ -n "$rw_paths" ] || error "No application directories resolved for ReadWritePaths"
214+
215+
for svc in relaysms-publisher.target relaysms-publisher-rest.service relaysms-publisher-grpc.service relaysms-publisher-worker.service relaysms-publisher-beat.service; do
181216
[ -f "$svc" ] || error "Service file not found: $svc"
182-
sed "s/User=relaysms/User=$SERVICE_USER/" "$svc" >"/etc/systemd/system/$svc" ||
217+
sed \
218+
-e "s/User=relaysms/User=$SERVICE_USER/" \
219+
-e "s|__RW_PATHS__|$rw_paths|" \
220+
"$svc" >"/etc/systemd/system/$svc" ||
183221
error "Failed to install $svc"
184222
done
185223
systemctl daemon-reload || error "Failed to reload systemd"
@@ -205,6 +243,7 @@ main() {
205243
log "Installation complete"
206244
log " Config : $INSTALL_DIR/.env"
207245
log " Manage : $INSTALL_DIR/manage.sh {start|stop|restart|status|logs|update}"
246+
log " Platforms : $INSTALL_DIR/platforms.sh {add|remove|update|list|recover|env|shell}"
208247
}
209248

210249
main "$@"

0 commit comments

Comments
 (0)