Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Dependencies (rebuilt in container)
node_modules/

# Git
.git/
.gitignore

# Data and databases
data/
*.db
*.db-journal
*.db-wal

# Environment and secrets
.env
.env.*
!.env.example

# Development and testing
test/
demo-assets/
docs/

# CI/CD
.github/

# IDE and editor
.vscode/
.idea/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db

# Logs
*.log
npm-debug.log*
62 changes: 62 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Build and Push Docker Image

on:
push:
branches:
- main
- 'release/**'
- 'dev/**'
tags:
- 'v*'

env:
REGISTRY: docker.io
IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}
USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Determine Docker Tags
id: docker_tags
run: |
TAGS=""
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
TAG_VERSION=${GITHUB_REF#refs/tags/}
TAGS="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG_VERSION},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
echo "Building release: $TAG_VERSION"
else
TAGS="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:devel"
echo "Building development version"
fi
echo "TAGS=$TAGS" >> $GITHUB_OUTPUT
echo "Docker tags: $TAGS"

- name: Login to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USERNAME }}
password: ${{ env.TOKEN }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_tags.outputs.TAGS }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
45 changes: 45 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Build stage
FROM node:20-alpine AS builder

WORKDIR /app

# Install build dependencies for better-sqlite3
RUN apk add --no-cache python3 make g++

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY src/ ./src/
COPY migrations/ ./migrations/
COPY templates/ ./templates/
COPY web/ ./web/

# Production stage
FROM node:20-alpine

WORKDIR /app

# Copy built artifacts from builder
COPY --from=builder /app ./

# Create data directory and set ownership
RUN mkdir -p /app/data && chown -R node:node /app

# Use non-root user
USER node

ENV NODE_ENV=production
ENV DIGEST_PORT=8767
ENV DIGEST_HOST=0.0.0.0

EXPOSE 8767

# Health check for orchestration environments
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q --spider http://localhost:8767/ || exit 1

CMD ["node", "src/server.mjs"]
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,25 @@ cd clawfeed
npm install
```

### Option 5: Docker
```bash
# Basic usage
docker run -d -p 8767:8767 kevinho/clawfeed

# With persistent data
docker run -d -p 8767:8767 -v clawfeed-data:/app/data kevinho/clawfeed

# With environment variables (recommended for production)
docker run -d -p 8767:8767 \
-v clawfeed-data:/app/data \
-e ALLOWED_ORIGINS=https://yourdomain.com \
-e API_KEY=your-api-key \
-e GOOGLE_CLIENT_ID=your-client-id \
-e GOOGLE_CLIENT_SECRET=your-client-secret \
-e SESSION_SECRET=your-session-secret \
kevinho/clawfeed
```

## Quick Start

```bash
Expand Down
5 changes: 3 additions & 2 deletions src/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { randomBytes, createHmac, timingSafeEqual } from 'crypto';
import { lookup } from 'dns/promises';
import { isIP } from 'net';
import { getDb, listDigests, getDigest, createDigest, listMarks, createMark, deleteMark, getConfig, setConfig, upsertUser, createSession, getSession, deleteSession, listSources, getSource, createSource, updateSource, deleteSource, getSourceByTypeConfig, getUserBySlug, listDigestsByUser, countDigestsByUser, createPack, getPack, getPackBySlug, listPacks, incrementPackInstall, deletePack, listSubscriptions, subscribe, unsubscribe, bulkSubscribe, isSubscribed, createFeedback, getUserFeedback, getAllFeedback, replyToFeedback, updateFeedbackStatus, markFeedbackRead, getUnreadFeedbackCount } from './db.mjs';

Check warning on line 10 in src/server.mjs

View workflow job for this annotation

GitHub Actions / Lint

'markFeedbackRead' is defined but never used

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
Expand Down Expand Up @@ -308,7 +308,7 @@
const preview = (j.items || []).slice(0, 5).map(i => ({ title: i.title || '(untitled)', url: i.url }));
return { name: j.title || new URL(url).hostname, type: 'digest_feed', config: { url }, icon: '📰', preview };
}
} catch {}

Check warning on line 311 in src/server.mjs

View workflow job for this annotation

GitHub Actions / Lint

Empty block statement
}

// HTML - extract title, treat as website
Expand Down Expand Up @@ -407,7 +407,7 @@
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(html);
return;
} catch (e) {

Check warning on line 410 in src/server.mjs

View workflow job for this annotation

GitHub Actions / Lint

'e' is defined but never used
res.writeHead(500); res.end('Internal error'); return;
}
}
Expand Down Expand Up @@ -767,7 +767,7 @@
const r = mod.request(u, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(notifBody) } });
r.on('error', () => {});
r.end(notifBody);
} catch {}

Check warning on line 770 in src/server.mjs

View workflow job for this annotation

GitHub Actions / Lint

Empty block statement
}
return json(res, { ok: true, id });
}
Expand Down Expand Up @@ -864,6 +864,7 @@
}
});

server.listen(PORT, '127.0.0.1', () => {
console.log(`🚀 ClawFeed API running on http://127.0.0.1:${PORT}`);
const HOST = process.env.DIGEST_HOST || '127.0.0.1';
server.listen(PORT, HOST, () => {
console.log(`🚀 ClawFeed API running on http://${HOST}:${PORT}`);
});
Loading