diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index a4a65db4..324a8587 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -51,8 +51,9 @@ jobs: uses: actions/cache@v3 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} + key: ${{ runner.os }}-buildx-${{ github.ref_name }}-${{ hashFiles('docker/Dockerfile') }} restore-keys: | + ${{ runner.os }}-buildx-${{ github.ref_name }}- ${{ runner.os }}-buildx- - name: Login to Docker Registry @@ -65,10 +66,14 @@ jobs: - name: Determine Docker image tag run: | echo "REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - if [ "${{ github.event.inputs.tag_name }}" == "" ]; then - IMAGE_TAG="${{ github.ref_name }}-development-latest" - else + if [ "${{ github.event.inputs.tag_name }}" != "" ]; then IMAGE_TAG="${{ github.event.inputs.tag_name }}" + elif [ "${{ github.ref }}" == "refs/heads/main" ]; then + IMAGE_TAG="latest" + elif [ "${{ github.ref }}" == "refs/heads/development" ]; then + IMAGE_TAG="development-latest" + else + IMAGE_TAG="${{ github.ref_name }}-development-latest" fi echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV @@ -95,12 +100,6 @@ jobs: rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache - - name: Notify via ntfy - if: success() - run: | - curl -d "Docker image build and push completed successfully for tag: ${{ env.IMAGE_TAG }}" \ - https://ntfy.karmaa.site/termix-build - - name: Delete all untagged image versions if: success() uses: quartx-analytics/ghcr-cleaner@v1 diff --git a/docker/Dockerfile b/docker/Dockerfile index 70a55def..785bebc2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -19,7 +19,17 @@ COPY . . # Build frontend RUN npm run build -# Stage 3: Production dependencies +# Stage 3: Build backend TypeScript +FROM deps AS backend-builder +WORKDIR /app + +# Copy source files +COPY . . + +# Build backend TypeScript to JavaScript +RUN npm run build:backend + +# Stage 4: Production dependencies FROM node:18-alpine AS production-deps WORKDIR /app @@ -30,7 +40,7 @@ COPY package*.json ./ RUN npm ci --only=production --ignore-scripts --force && \ npm cache clean --force -# Stage 4: Build native modules +# Stage 5: Build native modules FROM node:18-alpine AS native-builder WORKDIR /app @@ -41,13 +51,14 @@ RUN apk add --no-cache python3 make g++ COPY package*.json ./ # Install only the native modules we need -RUN npm ci --only=production bcrypt better-sqlite3 --force && \ +RUN npm ci --only=production bcryptjs better-sqlite3 --force && \ npm cache clean --force -# Stage 5: Final image +# Stage 6: Final image FROM node:18-alpine ENV DATA_DIR=/app/data \ - PORT=8080 + PORT=8080 \ + NODE_ENV=production # Install dependencies in a single layer RUN apk add --no-cache nginx gettext su-exec && \ @@ -61,20 +72,24 @@ RUN chown -R nginx:nginx /usr/share/nginx/html # Setup backend WORKDIR /app -COPY package*.json ./ # Copy production dependencies and native modules COPY --from=production-deps /app/node_modules /app/node_modules -COPY --from=native-builder /app/node_modules/bcrypt /app/node_modules/bcrypt +COPY --from=native-builder /app/node_modules/bcryptjs /app/node_modules/bcryptjs COPY --from=native-builder /app/node_modules/better-sqlite3 /app/node_modules/better-sqlite3 -# Copy backend source -COPY src/backend/ ./src/backend/ +# Copy compiled backend JavaScript +COPY --from=backend-builder /app/dist/backend ./dist/backend + +# Copy package.json for scripts +COPY package.json ./ + RUN chown -R node:node /app VOLUME ["/app/data"] + # Expose ports -EXPOSE ${PORT} 8081 8082 8083 8084 8085 +EXPOSE ${PORT} 8081 8082 8083 8084 COPY docker/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 403ac71c..5512ac4e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,7 +1,7 @@ services: termix: #image: ghcr.io/lukegus/termix:latest - image: ghcr.io/lukegus/termix:dev-0.3-development-latest + image: ghcr.io/lukegus/termix:dev-1.0-development-latest container_name: termix restart: unless-stopped ports: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index fad376b7..8fc5adfe 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -4,9 +4,11 @@ set -e export PORT=${PORT:-8080} echo "Configuring web UI to run on port: $PORT" +# Configure nginx with the correct port envsubst '${PORT}' < /etc/nginx/nginx.conf > /etc/nginx/nginx.conf.tmp mv /etc/nginx/nginx.conf.tmp /etc/nginx/nginx.conf +# Setup data directory mkdir -p /app/data chown -R node:node /app/data chmod 755 /app/data @@ -19,12 +21,14 @@ echo "Starting backend services..." cd /app export NODE_ENV=production +# Start the compiled TypeScript backend if command -v su-exec > /dev/null 2>&1; then - su-exec node node src/backend/starter.cjs + su-exec node node dist/backend/starter.js else - su -s /bin/sh node -c "node src/backend/starter.cjs" + su -s /bin/sh node -c "node dist/backend/starter.js" fi echo "All services started" +# Keep container running tail -f /dev/null \ No newline at end of file diff --git a/docker/nginx.conf b/docker/nginx.conf index f40f55ae..82f9570f 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -18,6 +18,15 @@ http { index index.html index.htm; } + location /ssh/db/ { + proxy_pass http://127.0.0.1:8081; + proxy_http_version 1.1; + proxy_set_header Host $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; + } + location /ssh/websocket { proxy_pass http://127.0.0.1:8082; proxy_http_version 1.1; @@ -31,27 +40,19 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } - location /ssh_tunnel/websocket { + location /ssh/tunnel/ { proxy_pass http://127.0.0.1:8083; proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - 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; } - location /config_editor/websocket { + location /ssh/config_editor/ { proxy_pass http://127.0.0.1:8084; proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - 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; diff --git a/src/apps/SSH/Tunnel/SSHTunnelObject.tsx b/src/apps/SSH/Tunnel/SSHTunnelObject.tsx index 6b0c9140..96c14a26 100644 --- a/src/apps/SSH/Tunnel/SSHTunnelObject.tsx +++ b/src/apps/SSH/Tunnel/SSHTunnelObject.tsx @@ -59,7 +59,7 @@ interface SSHTunnelObjectProps { host: SSHHost; tunnelStatuses: Record; tunnelActions: Record; - onTunnelAction: (action: 'connect' | 'disconnect' | 'cancel', host: SSHHost, tunnelIndex: number) => Promise; + onTunnelAction: (action: 'connect' | 'disconnect' | 'cancel', host: SSHHost, tunnelIndex: number) => Promise; } export function SSHTunnelObject({ @@ -68,6 +68,7 @@ export function SSHTunnelObject({ tunnelActions, onTunnelAction }: SSHTunnelObjectProps): React.ReactElement { + const getTunnelStatus = (tunnelIndex: number): TunnelStatus | undefined => { const tunnel = host.tunnelConnections[tunnelIndex]; const tunnelName = `${host.name || `${host.username}@${host.ip}`}_${tunnel.sourcePort}_${tunnel.endpointPort}`; @@ -220,32 +221,34 @@ export function SSHTunnelObject({ -
+
{tunnel.autoStart && ( Auto )} - {/* Action Button */} + {/* Action Buttons */} {!isActionLoading && ( - <> +
{isConnected ? ( - + <> + + ) : isRetrying || isWaiting ? ( )} - +
)} {isActionLoading && (