diff --git a/.env.example b/.env.example index fd61a1af..35346e40 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/.github/actions/start-app/action.yml b/.github/actions/start-app/action.yml index 89f12498..4898190d 100644 --- a/.github/actions/start-app/action.yml +++ b/.github/actions/start-app/action.yml @@ -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 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d94c4131..4c94563a 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65bbed85..1aa304e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 ``` diff --git a/Dockerfile b/Dockerfile index 6d426e5b..8a90c722 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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). diff --git a/README.md b/README.md index 47c04ef6..39816c96 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/backend/src/__tests__/nodes.test.ts b/backend/src/__tests__/nodes.test.ts index 09d2f1cf..9f8ad3b2 100644 --- a/backend/src/__tests__/nodes.test.ts +++ b/backend/src/__tests__/nodes.test.ts @@ -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 diff --git a/backend/src/__tests__/scheduler-service.test.ts b/backend/src/__tests__/scheduler-service.test.ts index f58e5f48..daf824ea 100644 --- a/backend/src/__tests__/scheduler-service.test.ts +++ b/backend/src/__tests__/scheduler-service.test.ts @@ -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', }); diff --git a/backend/src/__tests__/template-service.test.ts b/backend/src/__tests__/template-service.test.ts index 2ba41660..964d8eec 100644 --- a/backend/src/__tests__/template-service.test.ts +++ b/backend/src/__tests__/template-service.test.ts @@ -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'); }); }); diff --git a/backend/src/__tests__/users-rbac.test.ts b/backend/src/__tests__/users-rbac.test.ts index f268f7d0..d12ea767 100644 --- a/backend/src/__tests__/users-rbac.test.ts +++ b/backend/src/__tests__/users-rbac.test.ts @@ -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' }); diff --git a/backend/src/__tests__/validation.test.ts b/backend/src/__tests__/validation.test.ts index 0ce3d954..173aaaea 100644 --- a/backend/src/__tests__/validation.test.ts +++ b/backend/src/__tests__/validation.test.ts @@ -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); }); }); diff --git a/backend/src/helpers/constants.ts b/backend/src/helpers/constants.ts index b024eaaa..b8276056 100644 --- a/backend/src/helpers/constants.ts +++ b/backend/src/helpers/constants.ts @@ -3,7 +3,7 @@ // monolith. // Server -export const PORT = 3000; +export const PORT = 1852; // Password policy export const MIN_PASSWORD_LENGTH = 8; diff --git a/backend/src/routes/nodes.ts b/backend/src/routes/nodes.ts index ef6c77c6..f63d9ac6 100644 --- a/backend/src/routes/nodes.ts +++ b/backend/src/routes/nodes.ts @@ -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 = diff --git a/backend/src/utils/validation.ts b/backend/src/utils/validation.ts index 789ed4c5..217ede68 100644 --- a/backend/src/utils/validation.ts +++ b/backend/src/utils/validation.ts @@ -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)) { diff --git a/docker-compose.yml b/docker-compose.yml index 7636a6b2..b51424f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/docs/api-reference/overview.mdx b/docs/api-reference/overview.mdx index 60113ac7..250fa4e5 100644 --- a/docs/api-reference/overview.mdx +++ b/docs/api-reference/overview.mdx @@ -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 ``` @@ -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 ``` @@ -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" }, }); diff --git a/docs/api-reference/security.mdx b/docs/api-reference/security.mdx index 07b2b05d..ce452caf 100644 --- a/docs/api-reference/security.mdx +++ b/docs/api-reference/security.mdx @@ -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" ``` diff --git a/docs/features/multi-node.mdx b/docs/features/multi-node.mdx index 82df9ae0..f6f31df7 100644 --- a/docs/features/multi-node.mdx +++ b/docs/features/multi-node.mdx @@ -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 ```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; diff --git a/docs/features/vulnerability-scanning.mdx b/docs/features/vulnerability-scanning.mdx index a371b34e..d6a8b12a 100644 --- a/docs/features/vulnerability-scanning.mdx +++ b/docs/features/vulnerability-scanning.mdx @@ -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 '{ diff --git a/docs/getting-started/configuration.mdx b/docs/getting-started/configuration.mdx index ae1e1935..6be7c200 100644 --- a/docs/getting-started/configuration.mdx +++ b/docs/getting-started/configuration.mdx @@ -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 } ``` diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx index 49de298b..ef11311f 100644 --- a/docs/getting-started/quickstart.mdx +++ b/docs/getting-started/quickstart.mdx @@ -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. 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. diff --git a/docs/getting-started/sso-quickstart.mdx b/docs/getting-started/sso-quickstart.mdx index a694690a..3eb4d2d3 100644 --- a/docs/getting-started/sso-quickstart.mdx +++ b/docs/getting-started/sso-quickstart.mdx @@ -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 diff --git a/docs/openapi.yaml b/docs/openapi.yaml index c72f6bb6..220091c3 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -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. diff --git a/docs/operations/self-hosting.mdx b/docs/operations/self-hosting.mdx index e99436f1..165e9e0f 100644 --- a/docs/operations/self-hosting.mdx +++ b/docs/operations/self-hosting.mdx @@ -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 diff --git a/docs/operations/troubleshooting.mdx b/docs/operations/troubleshooting.mdx index cb018b57..ade20a7e 100644 --- a/docs/operations/troubleshooting.mdx +++ b/docs/operations/troubleshooting.mdx @@ -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} ``` diff --git a/frontend/src/components/NodeManager.tsx b/frontend/src/components/NodeManager.tsx index 17159f15..916ec602 100644 --- a/frontend/src/components/NodeManager.tsx +++ b/frontend/src/components/NodeManager.tsx @@ -368,7 +368,7 @@ export function NodeManager() { setFormData({ ...formData, api_url: e.target.value })} /> diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 592fafa3..2a43c9e3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -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, },