mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
feat: change default listen port from 3000 to 1852 (#756)
Updates the backend listen port, Vite dev proxy target, Docker EXPOSE, compose port mapping, .env.example default, GitHub Actions smoke-test default, healthcheck URLs, and every doc/example reference. Test fixtures that include example URLs were updated for consistency, though their assertions are port-agnostic. The rate-limit value of 3000 in middleware/rateLimiters.ts and the 3000 entry in WEB_UI_PORTS (which detects user containers like Grafana) are intentionally untouched.
This commit is contained in:
+2
-2
@@ -7,8 +7,8 @@ JWT_SECRET=your-secure-jwt-secret-here
|
||||
# Directory containing docker-compose files
|
||||
COMPOSE_DIR=/path/to/your/compose/files
|
||||
|
||||
# HTTP server port (default: 3000)
|
||||
PORT=3000
|
||||
# HTTP server port (default: 1852)
|
||||
PORT=1852
|
||||
|
||||
# Database and state directory inside the container (default: /app/data)
|
||||
DATA_DIR=/app/data
|
||||
|
||||
@@ -13,7 +13,7 @@ inputs:
|
||||
default: '/tmp/compose'
|
||||
port:
|
||||
required: false
|
||||
default: '3000'
|
||||
default: '1852'
|
||||
skip-backend-build:
|
||||
description: >
|
||||
When 'true', skip running `npm run build` in backend/ and assume the
|
||||
|
||||
@@ -126,12 +126,12 @@ jobs:
|
||||
# enough for `docker logs` in the trap to surface the stack trace.
|
||||
# The trap force-removes it explicitly whether it is still running
|
||||
# or already exited.
|
||||
docker run -d --name sencho-smoke -p 3000:3000 localhost/sencho:release-scan
|
||||
docker run -d --name sencho-smoke -p 1852:1852 localhost/sencho:release-scan
|
||||
trap 'docker logs sencho-smoke 2>&1 || true; docker rm -f sencho-smoke >/dev/null 2>&1 || true' EXIT
|
||||
for i in $(seq 1 30); do
|
||||
if curl -fsS http://localhost:3000/api/health >/dev/null 2>&1; then
|
||||
if curl -fsS http://localhost:1852/api/health >/dev/null 2>&1; then
|
||||
echo "Container healthy after ${i}s"
|
||||
curl -s http://localhost:3000/api/health
|
||||
curl -s http://localhost:1852/api/health
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ Thank you for your interest in contributing to Sencho!
|
||||
```
|
||||
5. Start the dev servers:
|
||||
```bash
|
||||
cd backend && npm run dev # Express + nodemon on :3000
|
||||
cd backend && npm run dev # Express + nodemon on :1852
|
||||
cd frontend && npm run dev # Vite on :5173
|
||||
```
|
||||
|
||||
|
||||
+2
-2
@@ -184,11 +184,11 @@ RUN sed -i 's/\r//' /usr/local/bin/docker-entrypoint.sh \
|
||||
&& chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
EXPOSE 1852
|
||||
|
||||
# Health check - polls the public /api/health endpoint every 30s
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD node -e "const h=require('http');h.get('http://localhost:3000/api/health',r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"
|
||||
CMD node -e "const h=require('http');h.get('http://localhost:1852/api/health',r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"
|
||||
|
||||
# Entrypoint ensures /app/data is writable and execs the CMD as root by default,
|
||||
# or drops to $SENCHO_USER via su-exec when that env var is set (see comment above).
|
||||
|
||||
@@ -28,7 +28,7 @@ services:
|
||||
container_name: sencho
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "1852:1852"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./data:/app/data
|
||||
@@ -43,7 +43,7 @@ services:
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Then open `http://your-server:3000` and create your admin account.
|
||||
Then open `http://your-server:1852` and create your admin account.
|
||||
|
||||
See the [full documentation](https://docs.sencho.io) for configuration details, multi-node setup, and more.
|
||||
|
||||
@@ -57,7 +57,7 @@ cd backend && npm install && npm run dev
|
||||
cd frontend && npm install && npm run dev
|
||||
```
|
||||
|
||||
The frontend dev server proxies `/api` requests to the backend on port 3000.
|
||||
The frontend dev server proxies `/api` requests to the backend on port 1852.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('POST /api/nodes - api_url SSRF validation (C2 fix)', () => {
|
||||
.send({
|
||||
name: 'lan-node',
|
||||
type: 'remote',
|
||||
api_url: 'http://192.168.1.50:3000',
|
||||
api_url: 'http://192.168.1.50:1852',
|
||||
api_token: 'sometoken',
|
||||
});
|
||||
// Should succeed (201 or 200) - not a validation error
|
||||
|
||||
@@ -1185,7 +1185,7 @@ describe('SchedulerService - executeUpdateRemote', () => {
|
||||
it('proxies update execution to remote node', async () => {
|
||||
mockGetNode.mockReturnValue({ id: 2, name: 'remote', type: 'remote', status: 'online' });
|
||||
mockGetProxyTarget.mockReturnValue({
|
||||
apiUrl: 'http://remote:3000',
|
||||
apiUrl: 'http://remote:1852',
|
||||
apiToken: 'test-token',
|
||||
});
|
||||
|
||||
@@ -1211,7 +1211,7 @@ describe('SchedulerService - executeUpdateRemote', () => {
|
||||
await svc.triggerTask(88);
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'http://remote:3000/api/auto-update/execute',
|
||||
'http://remote:1852/api/auto-update/execute',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ target: 'web-app' }),
|
||||
@@ -1226,7 +1226,7 @@ describe('SchedulerService - executeUpdateRemote', () => {
|
||||
it('records failure when remote node returns error', async () => {
|
||||
mockGetNode.mockReturnValue({ id: 2, name: 'remote', type: 'remote', status: 'online' });
|
||||
mockGetProxyTarget.mockReturnValue({
|
||||
apiUrl: 'http://remote:3000',
|
||||
apiUrl: 'http://remote:1852',
|
||||
apiToken: 'test-token',
|
||||
});
|
||||
|
||||
|
||||
@@ -210,11 +210,11 @@ describe('TemplateService', () => {
|
||||
it('handles values with special characters', () => {
|
||||
const result = service.generateEnvString({
|
||||
PASSWORD: 'p@ss=word!',
|
||||
URL: 'http://localhost:3000',
|
||||
URL: 'http://localhost:1852',
|
||||
});
|
||||
|
||||
expect(result).toContain('PASSWORD=p@ss=word!');
|
||||
expect(result).toContain('URL=http://localhost:3000');
|
||||
expect(result).toContain('URL=http://localhost:1852');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -568,7 +568,7 @@ describe('Orphaned role assignment cleanup', () => {
|
||||
it('deleting a node removes its role assignments', async () => {
|
||||
const db = DatabaseService.getInstance();
|
||||
// Create a test node
|
||||
const nodeId = db.addNode({ name: 'test-cleanup-node', type: 'remote', api_url: 'http://test:3000', api_token: '', compose_dir: '/tmp', is_default: false });
|
||||
const nodeId = db.addNode({ name: 'test-cleanup-node', type: 'remote', api_url: 'http://test:1852', api_token: '', compose_dir: '/tmp', is_default: false });
|
||||
// Create a role assignment for this node
|
||||
const hash = await bcrypt.hash('password123', 1);
|
||||
const userId = db.addUser({ username: 'nodeorphan', password_hash: hash, role: 'viewer' });
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('isValidStackName', () => {
|
||||
|
||||
describe('isValidRemoteUrl', () => {
|
||||
it('accepts valid http URLs', () => {
|
||||
const result = isValidRemoteUrl('http://192.168.1.10:3000');
|
||||
const result = isValidRemoteUrl('http://192.168.1.10:1852');
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
@@ -84,25 +84,25 @@ describe('isValidRemoteUrl', () => {
|
||||
});
|
||||
|
||||
it('rejects localhost', () => {
|
||||
expect(isValidRemoteUrl('http://localhost:3000').valid).toBe(false);
|
||||
expect(isValidRemoteUrl('http://LOCALHOST:3000').valid).toBe(false);
|
||||
expect(isValidRemoteUrl('http://localhost:1852').valid).toBe(false);
|
||||
expect(isValidRemoteUrl('http://LOCALHOST:1852').valid).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects loopback IPs', () => {
|
||||
expect(isValidRemoteUrl('http://127.0.0.1:3000').valid).toBe(false);
|
||||
expect(isValidRemoteUrl('http://127.0.0.1:1852').valid).toBe(false);
|
||||
expect(isValidRemoteUrl('http://127.1.2.3').valid).toBe(false);
|
||||
// Node.js URL.hostname preserves brackets: new URL('http://[::1]').hostname === '[::1]'
|
||||
expect(isValidRemoteUrl('http://[::1]:3000').valid).toBe(false);
|
||||
expect(isValidRemoteUrl('http://[::1]:1852').valid).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects 0.0.0.0', () => {
|
||||
expect(isValidRemoteUrl('http://0.0.0.0:3000').valid).toBe(false);
|
||||
expect(isValidRemoteUrl('http://0.0.0.0:1852').valid).toBe(false);
|
||||
});
|
||||
|
||||
it('allows LAN/private IPs (users need these for local network nodes)', () => {
|
||||
// Users legitimately run Sencho nodes on their LAN
|
||||
expect(isValidRemoteUrl('http://192.168.1.100:3000').valid).toBe(true);
|
||||
expect(isValidRemoteUrl('http://10.0.0.5:3000').valid).toBe(true);
|
||||
expect(isValidRemoteUrl('http://192.168.1.100:1852').valid).toBe(true);
|
||||
expect(isValidRemoteUrl('http://10.0.0.5:1852').valid).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// monolith.
|
||||
|
||||
// Server
|
||||
export const PORT = 3000;
|
||||
export const PORT = 1852;
|
||||
|
||||
// Password policy
|
||||
export const MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
@@ -37,7 +37,7 @@ function mintPilotEnrollment(nodeId: number, req: Request): { token: string; exp
|
||||
const forwardedProto = req.headers['x-forwarded-proto'];
|
||||
const protoHeader = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
|
||||
const protocol = protoHeader || req.protocol || 'http';
|
||||
const host = req.get('host') || 'localhost:3000';
|
||||
const host = req.get('host') || 'localhost:1852';
|
||||
const primaryUrl = `${protocol}://${host}`;
|
||||
|
||||
const dockerRun =
|
||||
|
||||
@@ -22,7 +22,7 @@ export function isValidRemoteUrl(
|
||||
console.warn('[Validation] URL parse failure:', (e as Error).message, '— input:', raw);
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'API URL must be a valid URL (e.g. https://my-server.example.com:3000)',
|
||||
reason: 'API URL must be a valid URL (e.g. https://my-server.example.com:1852)',
|
||||
};
|
||||
}
|
||||
if (!['http:', 'https:'].includes(url.protocol)) {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ services:
|
||||
container_name: sencho
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "1852:1852"
|
||||
volumes:
|
||||
# Required: Docker Socket for container orchestration
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
@@ -8,7 +8,7 @@ Sencho exposes a REST API for automating stack deployments, managing webhooks, m
|
||||
## Base URL
|
||||
|
||||
```
|
||||
https://your-sencho-instance:3000/api
|
||||
https://your-sencho-instance:1852/api
|
||||
```
|
||||
|
||||
Replace with your actual Sencho host and port. All API paths are prefixed with `/api`.
|
||||
@@ -19,7 +19,7 @@ Authenticated endpoints require a **Bearer token** in the `Authorization` header
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
https://your-sencho-instance:3000/api/stacks
|
||||
https://your-sencho-instance:1852/api/stacks
|
||||
```
|
||||
|
||||
<Note>
|
||||
@@ -51,7 +51,7 @@ If omitted, the request targets the default node.
|
||||
# Target node ID 2
|
||||
curl -H "Authorization: Bearer TOKEN" \
|
||||
-H "x-node-id: 2" \
|
||||
https://your-sencho-instance:3000/api/stacks
|
||||
https://your-sencho-instance:1852/api/stacks
|
||||
```
|
||||
|
||||
## Error format
|
||||
@@ -132,7 +132,7 @@ Sencho also provides real-time streaming via WebSocket connections. These are no
|
||||
|
||||
Stream live logs from a stack's containers.
|
||||
|
||||
**URL:** `wss://your-sencho-instance:3000/api/stacks/{stackName}/logs?nodeId={nodeId}`
|
||||
**URL:** `wss://your-sencho-instance:1852/api/stacks/{stackName}/logs?nodeId={nodeId}`
|
||||
|
||||
**Authentication:** Pass the token as a cookie (`sencho_token`) or Bearer token. For WebSocket connections, authentication is verified during the upgrade handshake.
|
||||
|
||||
@@ -142,7 +142,7 @@ Stream live logs from a stack's containers.
|
||||
import WebSocket from "ws";
|
||||
|
||||
const ws = new WebSocket(
|
||||
"wss://your-sencho-instance:3000/api/stacks/my-app/logs",
|
||||
"wss://your-sencho-instance:1852/api/stacks/my-app/logs",
|
||||
{ headers: { Cookie: "sencho_token=YOUR_JWT" } }
|
||||
);
|
||||
|
||||
@@ -156,7 +156,7 @@ curl -i -N \
|
||||
-H "Connection: Upgrade" \
|
||||
-H "Upgrade: websocket" \
|
||||
-H "Cookie: sencho_token=YOUR_JWT" \
|
||||
https://your-sencho-instance:3000/api/stacks/my-app/logs
|
||||
https://your-sencho-instance:1852/api/stacks/my-app/logs
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -165,7 +165,7 @@ curl -i -N \
|
||||
|
||||
Open an interactive shell session inside a running container.
|
||||
|
||||
**URL:** `wss://your-sencho-instance:3000/ws`
|
||||
**URL:** `wss://your-sencho-instance:1852/ws`
|
||||
|
||||
**Authentication:** Cookie-based JWT only. API tokens with `read-only` or `deploy-only` scope are blocked.
|
||||
|
||||
@@ -174,7 +174,7 @@ Open an interactive shell session inside a running container.
|
||||
```javascript Node.js
|
||||
import WebSocket from "ws";
|
||||
|
||||
const ws = new WebSocket("wss://your-sencho-instance:3000/ws", {
|
||||
const ws = new WebSocket("wss://your-sencho-instance:1852/ws", {
|
||||
headers: { Cookie: "sencho_token=YOUR_JWT" },
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Writes are admin-only and rejected on replica nodes (policies are managed on the
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
https://your-sencho-instance:3000/api/security/policies
|
||||
https://your-sencho-instance:1852/api/security/policies
|
||||
```
|
||||
|
||||
**Response:**
|
||||
@@ -60,7 +60,7 @@ curl -H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
| `node_id` | number or `null` | no | Scope the policy to one node. `null` applies the policy fleet-wide. |
|
||||
|
||||
```bash
|
||||
curl -X POST https://your-sencho-instance:3000/api/security/policies \
|
||||
curl -X POST https://your-sencho-instance:1852/api/security/policies \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -89,7 +89,7 @@ curl -X POST https://your-sencho-instance:3000/api/security/policies \
|
||||
Any of the create fields can be updated individually. Omitted fields are left unchanged.
|
||||
|
||||
```bash
|
||||
curl -X PUT https://your-sencho-instance:3000/api/security/policies/1 \
|
||||
curl -X PUT https://your-sencho-instance:1852/api/security/policies/1 \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "block_on_deploy": 0 }'
|
||||
@@ -106,7 +106,7 @@ curl -X PUT https://your-sencho-instance:3000/api/security/policies/1 \
|
||||
**License:** Skipper or Admiral · **Role:** Admin
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://your-sencho-instance:3000/api/security/policies/1 \
|
||||
curl -X DELETE https://your-sencho-instance:1852/api/security/policies/1 \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN"
|
||||
```
|
||||
|
||||
@@ -156,7 +156,7 @@ Response rows include an `active` boolean computed from the `expires_at` timesta
|
||||
| `expires_at` | number or `null` | no | Unix timestamp in milliseconds. `null` for an indefinite suppression. |
|
||||
|
||||
```bash
|
||||
curl -X POST https://your-sencho-instance:3000/api/security/suppressions \
|
||||
curl -X POST https://your-sencho-instance:1852/api/security/suppressions \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -179,7 +179,7 @@ curl -X POST https://your-sencho-instance:3000/api/security/suppressions \
|
||||
**License:** Skipper or Admiral · **Role:** Admin
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://your-sencho-instance:3000/api/security/suppressions/3 \
|
||||
curl -X DELETE https://your-sencho-instance:1852/api/security/suppressions/3 \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN"
|
||||
```
|
||||
|
||||
@@ -201,7 +201,7 @@ Accepts an image reference and starts an asynchronous scan. The response returns
|
||||
| `scanners` | `["vuln"]` or `["vuln","secret"]` | no | Omit for vuln-only. `secret` requires Skipper or Admiral. |
|
||||
|
||||
```bash
|
||||
curl -X POST https://your-sencho-instance:3000/api/security/scan \
|
||||
curl -X POST https://your-sencho-instance:1852/api/security/scan \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "imageRef": "nginx:1.27" }'
|
||||
@@ -221,7 +221,7 @@ Supports filters: `imageRef`, `imageRefLike`, `status`, `limit`, `offset`. Respo
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
"https://your-sencho-instance:3000/api/security/scans?imageRefLike=nginx&limit=20"
|
||||
"https://your-sencho-instance:1852/api/security/scans?imageRefLike=nginx&limit=20"
|
||||
```
|
||||
|
||||
## Stack deploy with policy
|
||||
@@ -266,7 +266,7 @@ HTTP 409 Conflict. The response body is parseable JSON so CI pipelines can branc
|
||||
Admins can bypass the gate on a per-deploy basis by passing `?ignorePolicy=true` on the deploy URL. The flag is only honored when the calling session resolves to an admin user; API tokens with read-only or deploy-only scope cannot bypass a policy.
|
||||
|
||||
```bash
|
||||
curl -X POST "https://your-sencho-instance:3000/api/stacks/myapp/deploy?ignorePolicy=true" \
|
||||
curl -X POST "https://your-sencho-instance:1852/api/stacks/myapp/deploy?ignorePolicy=true" \
|
||||
-H "Authorization: Bearer ADMIN_API_TOKEN"
|
||||
```
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ On your **primary** Sencho instance, open **Settings → Nodes** and click **+ A
|
||||
|-------|-------------|
|
||||
| **Name** | A display name (e.g. `Production VPS`, `media-box`) |
|
||||
| **Type** | Select **Remote** (or **Local** for an additional local Docker socket) |
|
||||
| **Sencho API URL** | The full HTTP/HTTPS URL of the remote instance (e.g. `http://192.168.1.50:3000`) |
|
||||
| **Sencho API URL** | The full HTTP/HTTPS URL of the remote instance (e.g. `http://192.168.1.50:1852`) |
|
||||
| **API Token** | The token you generated in Step 1 |
|
||||
| **Compose Directory** | The root directory where compose stack folders live on the remote node (defaults to `/app/compose`) |
|
||||
|
||||
@@ -156,8 +156,8 @@ There are three recommended approaches depending on your deployment:
|
||||
If all your Sencho instances are on the same local network, VPC, or subnet, HTTP is perfectly fine. The token never leaves the private network, so there is no interception risk.
|
||||
|
||||
```
|
||||
http://192.168.1.50:3000 ← safe on a private LAN
|
||||
http://10.0.1.20:3000 ← safe inside a VPC
|
||||
http://192.168.1.50:1852 ← safe on a private LAN
|
||||
http://10.0.1.20:1852 ← safe inside a VPC
|
||||
```
|
||||
|
||||
#### VPN tunnel (WireGuard, Tailscale)
|
||||
@@ -169,7 +169,7 @@ http://10.0.1.20:3000 ← safe inside a VPC
|
||||
With a mesh VPN like [Tailscale](https://tailscale.com) or [WireGuard](https://www.wireguard.com/), each server gets a private IP on the VPN. Use those IPs as your Sencho API URLs:
|
||||
|
||||
```
|
||||
http://100.64.0.2:3000 ← Tailscale IP, encrypted by the VPN tunnel
|
||||
http://100.64.0.2:1852 ← Tailscale IP, encrypted by the VPN tunnel
|
||||
```
|
||||
|
||||
All traffic between nodes is encrypted by the VPN. Sencho does not need to do anything additional.
|
||||
@@ -181,7 +181,7 @@ If you prefer TLS termination at each node, place a reverse proxy in front of ea
|
||||
<CodeGroup>
|
||||
```text Caddyfile
|
||||
sencho.example.com {
|
||||
reverse_proxy localhost:3000
|
||||
reverse_proxy localhost:1852
|
||||
}
|
||||
```
|
||||
|
||||
@@ -194,7 +194,7 @@ server {
|
||||
ssl_certificate_key /etc/letsencrypt/live/sencho.example.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_pass http://localhost:1852;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
@@ -186,7 +186,7 @@ Only one policy is evaluated per deploy; use a single tight pattern rather than
|
||||
Policy CRUD endpoints are documented in the [Security API reference](/api-reference/security). A typical create call from CI looks like this:
|
||||
|
||||
```bash
|
||||
curl -X POST https://your-sencho-instance:3000/api/security/policies \
|
||||
curl -X POST https://your-sencho-instance:1852/api/security/policies \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
||||
@@ -23,7 +23,7 @@ When you point `COMPOSE_DIR` at a directory, Sencho expects each stack to live i
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PORT` | `3000` | Port the Sencho HTTP server listens on. |
|
||||
| `PORT` | `1852` | Port the Sencho HTTP server listens on. |
|
||||
| `DATA_DIR` | `/app/data` | Directory where Sencho stores its SQLite database, node registry, and cached metrics. |
|
||||
| `FRONTEND_URL` | *(empty)* | Frontend origin for CORS. Only needed if the UI is served from a different domain than the API. Leave empty for same-origin setups. |
|
||||
| `NODE_ENV` | `production` | Set automatically in the Docker image. Only change this for local development. |
|
||||
@@ -140,7 +140,7 @@ services:
|
||||
image: saelix/sencho:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "1852:1852"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./sencho-data:/app/data
|
||||
@@ -180,7 +180,7 @@ server {
|
||||
server_name sencho.yourdomain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_pass http://localhost:1852;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# WebSocket support
|
||||
@@ -211,7 +211,7 @@ server {
|
||||
ssl_certificate_key /etc/letsencrypt/live/sencho.yourdomain.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_pass http://localhost:1852;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# WebSocket support
|
||||
@@ -235,7 +235,7 @@ Use [Certbot](https://certbot.eff.org/) to obtain and auto-renew certificates: `
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.sencho.rule=Host(`sencho.yourdomain.com`)"
|
||||
- "traefik.http.services.sencho.loadbalancer.server.port=3000"
|
||||
- "traefik.http.services.sencho.loadbalancer.server.port=1852"
|
||||
```
|
||||
|
||||
### Traefik with SSL (Let's Encrypt)
|
||||
@@ -246,7 +246,7 @@ labels:
|
||||
- "traefik.http.routers.sencho.rule=Host(`sencho.yourdomain.com`)"
|
||||
- "traefik.http.routers.sencho.entrypoints=websecure"
|
||||
- "traefik.http.routers.sencho.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.sencho.loadbalancer.server.port=3000"
|
||||
- "traefik.http.services.sencho.loadbalancer.server.port=1852"
|
||||
# HTTP to HTTPS redirect
|
||||
- "traefik.http.routers.sencho-http.rule=Host(`sencho.yourdomain.com`)"
|
||||
- "traefik.http.routers.sencho-http.entrypoints=web"
|
||||
@@ -262,7 +262,7 @@ labels:
|
||||
|
||||
```
|
||||
sencho.yourdomain.com {
|
||||
reverse_proxy localhost:3000
|
||||
reverse_proxy localhost:1852
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ description: Get Sencho running in under five minutes.
|
||||
```bash
|
||||
docker run -d \
|
||||
--name sencho \
|
||||
-p 3000:3000 \
|
||||
-p 1852:1852 \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /opt/compose:/opt/compose \
|
||||
-v sencho_data:/app/data \
|
||||
@@ -21,7 +21,7 @@ docker run -d \
|
||||
saelix/sencho:latest
|
||||
```
|
||||
|
||||
Open `http://localhost:3000` in your browser. On first boot you'll be prompted to create an admin account.
|
||||
Open `http://localhost:1852` in your browser. On first boot you'll be prompted to create an admin account.
|
||||
|
||||
<Note>
|
||||
Replace `/opt/compose` with the path to your Compose projects directory. Every subdirectory inside it becomes a stack in Sencho. A `JWT_SECRET` is generated automatically on first boot; you do not need to provide one.
|
||||
|
||||
@@ -162,7 +162,7 @@ services:
|
||||
image: saelix/sencho:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "1852:1852"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./sencho-data:/app/data
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ servers:
|
||||
host:
|
||||
default: localhost
|
||||
port:
|
||||
default: "3000"
|
||||
default: "1852"
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
@@ -1679,7 +1679,7 @@ paths:
|
||||
api_url:
|
||||
type: string
|
||||
description: Remote Sencho instance URL (required for remote nodes).
|
||||
example: https://sencho.example.com:3000
|
||||
example: https://sencho.example.com:1852
|
||||
api_token:
|
||||
type: string
|
||||
description: API token for authenticating with the remote Sencho instance.
|
||||
|
||||
@@ -70,7 +70,7 @@ Sencho itself is lightweight. The majority of resource usage on your host comes
|
||||
|
||||
## Networking
|
||||
|
||||
- **Listen port:** 3000 (fixed). Map it to any host port using Docker's `-p` flag or `ports` in your compose file
|
||||
- **Listen port:** 1852 (fixed). Map it to any host port using Docker's `-p` flag or `ports` in your compose file
|
||||
- **Inbound:** Only the listen port needs to be reachable (directly or through a reverse proxy)
|
||||
- **Outbound:** No outbound connections are required for local-only setups. If you use multi-node management, Sencho needs HTTP/HTTPS access to remote Sencho instances on their configured API URLs
|
||||
- **Health check:** `GET /api/health` returns `200` when the application is ready. The Docker image includes a built-in `HEALTHCHECK` that polls this endpoint every 30 seconds
|
||||
|
||||
@@ -557,7 +557,7 @@ docker compose pull && docker compose up -d
|
||||
Sencho exposes a health endpoint for monitoring and container health checks:
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/api/health
|
||||
curl http://localhost:1852/api/health
|
||||
# {"status":"ok","uptime":12345.67}
|
||||
```
|
||||
|
||||
|
||||
@@ -368,7 +368,7 @@ export function NodeManager() {
|
||||
<Label htmlFor="node-api-url">Sencho API URL</Label>
|
||||
<Input
|
||||
id="node-api-url"
|
||||
placeholder="http://192.168.1.50:3000"
|
||||
placeholder="http://192.168.1.50:1852"
|
||||
value={formData.api_url}
|
||||
onChange={(e) => setFormData({ ...formData, api_url: e.target.value })}
|
||||
/>
|
||||
|
||||
@@ -27,12 +27,12 @@ export default defineConfig({
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3000',
|
||||
target: 'http://localhost:1852',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
},
|
||||
'/ws': {
|
||||
target: 'http://localhost:3000',
|
||||
target: 'http://localhost:1852',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user