diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..32472ec --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,53 @@ +name: Build and Push Docker Image + +on: + push: + tags: + - '*.*.*' + - '*.*.*-*' + +env: + IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/inker + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Test backend + run: cd backend && bun install --frozen-lockfile && bun test + + - name: Test frontend + run: cd frontend && bun install --frozen-lockfile && bun run test + + build-and-push: + needs: test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + ${{ env.IMAGE }}:${{ github.ref_name }} + ${{ env.IMAGE }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/README.md b/README.md index 08aa36f..1d65d9c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png)](https://buymeacoffee.com/wojo_o) -# Inker v0.3.0 +# Inker v0.3.1 Self-hosted e-ink device management server built for the homelab community. Works with [TRMNL](https://usetrmnl.com/) devices (supports firmware 1.7.8) and any BYOD e-ink display. Design screens, create custom widgets with live data from your local network, and manage your displays from a modern web interface. @@ -81,6 +81,7 @@ Open **http://your-server-ip** and log in with PIN `1111`. |----------|-------------|---------| | `ADMIN_PIN` | Login PIN | `1111` | | `TZ` | Timezone for widgets | `UTC` | +| `INKER_PORT` | External port (for custom port mapping, e.g. `INKER_PORT=800`) | `80` | | `CORS_ORIGINS` | Allowed CORS origins (comma-separated, or `*` for all) | same-origin | Pass with `-e`: diff --git a/backend/package.json b/backend/package.json index 9e736c4..d24d040 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "inker-backend", - "version": "0.3.0", + "version": "0.3.1", "description": "Inker Server Backend - E-ink Device Management", "main": "dist/main.js", "scripts": { diff --git a/backend/src/api/api.controller.ts b/backend/src/api/api.controller.ts index 465f799..59ff14f 100644 --- a/backend/src/api/api.controller.ts +++ b/backend/src/api/api.controller.ts @@ -82,15 +82,19 @@ export class ApiController { } // Fallback: derive from request headers (for LAN access where IP varies) - let host = headers['host'] || headers['Host'] || 'localhost:3002'; - const protocol = headers['x-forwarded-proto'] || 'http'; + let host = headers['host'] || headers['Host'] || 'localhost'; + const protocol = headers['x-forwarded-proto'] + || (headers['x-forwarded-ssl'] === 'on' ? 'https' : null) + || (host.endsWith(':443') ? 'https' : null) + || 'http'; - // Ensure INKER_PORT is used when it's non-standard (handles Docker port mapping) - const inkerPort = this.configService.get('inkerPort', 80); - if (inkerPort && inkerPort !== 80) { - // Strip any existing port from host and replace with INKER_PORT - const hostname = host.split(':')[0]; - host = `${hostname}:${inkerPort}`; + // If Host header already has a port, trust it (device/browser sent the correct port) + // If Host has no port and INKER_PORT is non-standard, append it + if (!host.includes(':')) { + const inkerPort = this.configService.get('inkerPort', 80); + if (inkerPort && inkerPort !== 80) { + host = `${host}:${inkerPort}`; + } } return `${protocol}://${host}`; diff --git a/backend/src/main.ts b/backend/src/main.ts index 2a8fd78..6b278f4 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -81,7 +81,7 @@ async function bootstrap() { const config = new DocumentBuilder() .setTitle('Inker API') .setDescription('API documentation for Inker e-ink device management server') - .setVersion('0.3.0') + .setVersion('0.3.1') .addBearerAuth() .addApiKey({ type: 'apiKey', name: 'X-Device-Key', in: 'header' }, 'device-key') .build(); diff --git a/docker/nginx.conf b/docker/nginx.conf index bdedbe4..e36da4d 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -1,3 +1,9 @@ +# Preserve X-Forwarded-Proto from upstream proxy, fall back to $scheme +map $http_x_forwarded_proto $real_scheme { + default $http_x_forwarded_proto; + '' $scheme; +} + # Custom log format: use $uri (path only) instead of $request to prevent # query string tokens (e.g., SSE ?token=...) from leaking into access logs # (must be at http level, not inside server block) @@ -76,7 +82,7 @@ server { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto $real_scheme; proxy_hide_header Content-Security-Policy; proxy_hide_header Strict-Transport-Security; @@ -105,7 +111,7 @@ server { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto $real_scheme; proxy_cache_bypass $http_upgrade; } @@ -126,7 +132,7 @@ server { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto $real_scheme; # Strip Helmet security headers from image responses — ESP32 devices # have limited HTTP buffer and excessive headers cause download failures @@ -157,7 +163,7 @@ server { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto $real_scheme; expires 7d; # Strip Helmet security headers — ESP32 devices have limited HTTP buffer diff --git a/frontend/package.json b/frontend/package.json index c75ffa0..734090c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "inker-frontend", "private": true, - "version": "0.3.0", + "version": "0.3.1", "type": "module", "scripts": { "dev": "bunx --bun vite", diff --git a/preview.png b/preview.png new file mode 100644 index 0000000..715eda1 Binary files /dev/null and b/preview.png differ