Add Docker deploy and Gitea Actions CI, plus a local testing guide
All checks were successful
Docker / build-and-push (push) Successful in 1m51s
All checks were successful
Docker / build-and-push (push) Successful in 1m51s
Dockerfile/docker-compose.yml mirror keep's pattern — same better-sqlite3 native-addon build story, same pull_policy: always + named volume for the SQLite DB so redeploys don't lose captured articles. Gitea Actions workflow builds and pushes to the Gitea container registry on push to main (needs a TKNTKN repo secret, same as keep/wisp/npm-statuspage). TESTING.md is the step-by-step for running the server + loading the extension unpacked, written up from what was actually verified end-to-end in the previous session. Verified: image builds, server starts and responds inside the container, capture works, and data survives a container restart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
53
.gitea/workflows/docker.yml
Normal file
53
.gitea/workflows/docker.yml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
name: Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Derive image name and tags
|
||||||
|
id: meta
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
SERVER_URL="${GITHUB_SERVER_URL:-${GITEA_SERVER_URL:-}}"
|
||||||
|
REPO="${GITHUB_REPOSITORY:-${GITEA_REPOSITORY:-}}"
|
||||||
|
SHA="${GITHUB_SHA:-${GITEA_SHA:-}}"
|
||||||
|
if [[ -z "${SERVER_URL}" || -z "${REPO}" || -z "${SHA}" ]]; then
|
||||||
|
echo "Missing SERVER_URL/REPO/SHA env vars." >&2; exit 1
|
||||||
|
fi
|
||||||
|
HOST=$(echo "${SERVER_URL}" | sed 's|https://||;s|http://||')
|
||||||
|
IMAGE=$(echo "${HOST}/${REPO}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
SHORT_SHA=$(echo "${SHA}" | cut -c1-7)
|
||||||
|
echo "host=${HOST}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Log in to Gitea container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ steps.meta.outputs.host }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
# Create a Gitea PAT with packages:write scope and add it as a
|
||||||
|
# repository secret named TKNTKN (Settings → Secrets)
|
||||||
|
password: ${{ secrets.TKNTKN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ steps.meta.outputs.image }}:latest
|
||||||
|
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.short_sha }}
|
||||||
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
FROM node:22-alpine AS build
|
||||||
|
WORKDIR /app
|
||||||
|
# better-sqlite3 has a native addon with no prebuilt binary for this
|
||||||
|
# platform/Node combo yet — build it from source.
|
||||||
|
RUN apk add --no-cache python3 make g++
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:22-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
# Same native-addon rebuild needed for the production-only install below;
|
||||||
|
# the toolchain is purged again afterward to keep the runtime image lean.
|
||||||
|
RUN apk add --no-cache python3 make g++
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install --omit=dev && apk del python3 make g++
|
||||||
|
COPY --from=build /app/dist ./dist
|
||||||
|
|
||||||
|
# DATA_DIR (SQLite DB) is a mutable runtime volume — not baked into the
|
||||||
|
# image, so redeploys don't clobber captured articles.
|
||||||
|
ENV DATA_DIR=/app/data
|
||||||
|
|
||||||
|
EXPOSE 3070
|
||||||
|
CMD ["node", "dist/server/index.js"]
|
||||||
55
TESTING.md
Normal file
55
TESTING.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Trying stash locally
|
||||||
|
|
||||||
|
## 1. Start the server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/temp/stash
|
||||||
|
cp .env.example .env
|
||||||
|
npm install
|
||||||
|
npm run token -- my-laptop
|
||||||
|
```
|
||||||
|
|
||||||
|
`npm run token` prints a bearer token — copy it. Then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev:server
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave that running (listens on `:3070` by default).
|
||||||
|
|
||||||
|
## 2. Load the extension
|
||||||
|
|
||||||
|
- Open `chrome://extensions` (or `edge://extensions` — anything Chromium-based)
|
||||||
|
- Toggle **Developer mode** on (top right)
|
||||||
|
- Click **Load unpacked**, select `~/temp/stash/extension`
|
||||||
|
- Click the puzzle-piece icon in the toolbar → pin **stash** so it's visible
|
||||||
|
|
||||||
|
## 3. Configure it
|
||||||
|
|
||||||
|
- Right-click the stash icon → **Options** (or it'll auto-open the first
|
||||||
|
time it can't find a token)
|
||||||
|
- Server URL: `http://127.0.0.1:3070`
|
||||||
|
- Token: paste what `npm run token` printed
|
||||||
|
- Save
|
||||||
|
|
||||||
|
## 4. Try it
|
||||||
|
|
||||||
|
- Go to any real article (a blog post, news article, etc.)
|
||||||
|
- Click the stash toolbar icon, or right-click the page → **Send to stash**
|
||||||
|
- You should get a browser notification confirming the send
|
||||||
|
- Visit `http://127.0.0.1:3070/?t=<your-token>` in a tab — you'll see it
|
||||||
|
in the list, click through to the cleaned reader view
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Firefox is a bit different** — use
|
||||||
|
`about:debugging#/runtime/this-firefox` → "Load Temporary Add-on" → pick
|
||||||
|
`manifest.json`. Temporary add-ons vanish on browser restart, so
|
||||||
|
Chrome/Chromium's unpacked-extension loading (which persists) is the
|
||||||
|
easier one to actually live with day to day.
|
||||||
|
- **Notifications permission** — Chrome may prompt once to allow
|
||||||
|
notifications from the extension. Allow it, or you just won't get the
|
||||||
|
success/failure toast (capture still works either way).
|
||||||
|
- This is all local/loopback for now. Once a server is deployed
|
||||||
|
somewhere reachable, point the extension's Server URL at that instead
|
||||||
|
— nothing else changes.
|
||||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: ${IMAGE}
|
||||||
|
container_name: stash
|
||||||
|
restart: unless-stopped
|
||||||
|
pull_policy: always
|
||||||
|
ports:
|
||||||
|
- "${HOST_PORT:-3070}:3070"
|
||||||
|
volumes:
|
||||||
|
# Named volume, not a bind mount — the SQLite DB holding captured
|
||||||
|
# articles and tokens must survive redeploys.
|
||||||
|
- stash-data:/app/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
stash-data:
|
||||||
Reference in New Issue
Block a user