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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
build:
name: Build (${{ matrix.os }})
runs-on: ${{ matrix.os }}
env:
GOCACHE: ${{ runner.temp }}/go-build-cache
strategy:
fail-fast: false
matrix:
Expand All @@ -34,6 +36,12 @@ jobs:
node-version: 22
cache: npm

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: apps/server/go.mod
cache-dependency-path: apps/server/go.sum

- name: Install dependencies
run: npm ci

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
node_modules
out
dist
.turbo
apps/*/.turbo
apps/web/dist
apps/server/bin
.DS_Store
*.log
.env
Expand All @@ -11,3 +15,4 @@ dist
.idea
.claude/
*.tsbuildinfo
data/
64 changes: 64 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# syntax=docker/dockerfile:1.7

# ZenNotes self-hosted build from the monorepo.
#
# Stages:
# 1. web-build -> npm workspace install + Vite build for apps/web
# 2. server-build -> Go build for apps/server with the web bundle embedded
# 3. runtime -> minimal image with only the server binary

FROM node:22-alpine AS web-build
WORKDIR /app

COPY package.json package-lock.json turbo.json tsconfig.base.json tsconfig.json tailwind.config.js postcss.config.js ./
COPY apps/web/package.json apps/web/package.json
COPY packages/app-core/package.json packages/app-core/package.json
COPY packages/bridge-contract/package.json packages/bridge-contract/package.json
COPY packages/shared-domain/package.json packages/shared-domain/package.json
COPY packages/shared-ui/package.json packages/shared-ui/package.json
COPY apps/desktop/package.json apps/desktop/package.json
COPY apps/server/package.json apps/server/package.json

RUN npm ci --no-audit --no-fund --loglevel=error

COPY apps apps
COPY packages packages

RUN npm run build --workspace @zennotes/web

FROM golang:1.22-alpine AS server-build
WORKDIR /app

COPY apps/server/go.mod apps/server/go.sum ./apps/server/
WORKDIR /app/apps/server
RUN go mod download

WORKDIR /app
COPY apps/server apps/server
COPY --from=web-build /app/apps/web/dist/ /app/apps/server/web/dist/

ENV CGO_ENABLED=0 \
GOOS=linux \
GOFLAGS=-trimpath

WORKDIR /app/apps/server
RUN go build -ldflags="-s -w" -o /out/zennotes-server ./cmd/zennotes-server

FROM scratch
LABEL org.opencontainers.image.title="ZenNotes" \
org.opencontainers.image.description="Self-hosted ZenNotes web/server bundle from the monorepo." \
org.opencontainers.image.source="https://github.com/ZenNotes/zennotes"

COPY --from=server-build /out/zennotes-server /zennotes-server

ENV ZENNOTES_BIND=0.0.0.0:7878 \
ZENNOTES_CONFIG_PATH=/data/server.json \
ZENNOTES_DEFAULT_VAULT_PATH=/workspace \
ZENNOTES_BROWSE_ROOTS=/workspace

USER 65532:65532

EXPOSE 7878
VOLUME ["/workspace", "/data"]

ENTRYPOINT ["/zennotes-server"]
163 changes: 163 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
IMAGE ?= zennotes-selfhosted:local
PORT ?= 7878
CONTENT_ROOT ?= ./vault
DATA ?= ./data
APP_URL ?= http://localhost:$(PORT)
ALLOW_INSECURE_NOAUTH ?= 0
COMPOSE := $(shell docker compose version >/dev/null 2>&1 && echo "docker compose" || echo "docker-compose")
OPEN_BROWSER := $(shell command -v open 2>/dev/null || command -v xdg-open 2>/dev/null)

.PHONY: help install dev desktop web-dev server-dev web-stack \
build desktop-build web-build server-build \
up down restart logs status open rebuild nuke clean

help:
@echo ""
@echo " Setup"
@echo " make install — install npm workspace dependencies"
@echo ""
@echo " Local development"
@echo " make desktop — run the Electron desktop app in dev mode"
@echo " make web-dev — run the Vite web client in dev mode"
@echo " make server-dev — run the Go server in dev mode"
@echo " make web-stack — run server + web dev together"
@echo ""
@echo " Local builds"
@echo " make build — build the full monorepo"
@echo " make desktop-build — build the Electron desktop app"
@echo " make web-build — build apps/web"
@echo " make server-build — build apps/server with the latest embedded web bundle"
@echo ""
@echo " Docker"
@echo " make up — build and start the self-hosted server"
@echo " make down — stop the container"
@echo " make restart — restart the container"
@echo " make logs — follow logs"
@echo " make status — show compose status"
@echo " make open — open the app in your browser"
@echo " make rebuild — force a full rebuild"
@echo " make nuke — tear down and remove local image/build output"
@echo " make clean — remove local web/server build output"
@echo ""
@echo " Useful Docker vars"
@echo " CONTENT_ROOT=~/iCloud Drive/Obsidian — host folder used as the live vault root"
@echo " PORT=7878 — host port"
@echo " ALLOW_INSECURE_NOAUTH=1 — opt out of generated auth token (not recommended)"
@echo ""

install:
npm ci

dev: desktop

desktop:
npm run dev:desktop

web-dev:
npm run dev:web

server-dev:
npm run dev:server

web-stack:
npm run dev:web-stack

build:
npm run build

desktop-build:
npm run build --workspace @zennotes/desktop

web-build:
npm run build --workspace @zennotes/web

up:
@mkdir -p "$(CONTENT_ROOT)" "$(DATA)"
@ABS_CONTENT_ROOT="$$(cd "$(CONTENT_ROOT)" && pwd)"; \
ABS_DATA="$$(cd "$(DATA)" && pwd)"; \
AUTH_TOKEN=""; \
if [ "$(ALLOW_INSECURE_NOAUTH)" != "1" ]; then \
AUTH_TOKEN_FILE="$$ABS_DATA/auth-token"; \
if [ ! -f "$$AUTH_TOKEN_FILE" ]; then \
node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('hex'))" > "$$AUTH_TOKEN_FILE"; \
chmod 600 "$$AUTH_TOKEN_FILE"; \
fi; \
AUTH_TOKEN="$$(cat "$$AUTH_TOKEN_FILE")"; \
fi; \
ZENNOTES_IMAGE="$(IMAGE)" \
ZENNOTES_HOST_PORT="$(PORT)" \
ZENNOTES_HOST_CONTENT_ROOT="$$ABS_CONTENT_ROOT" \
ZENNOTES_CONTAINER_CONTENT_ROOT="$$ABS_CONTENT_ROOT" \
ZENNOTES_CONTAINER_DEFAULT_VAULT_PATH="$$ABS_CONTENT_ROOT" \
ZENNOTES_BROWSE_ROOTS="$$ABS_CONTENT_ROOT" \
ZENNOTES_ALLOWED_ORIGINS="http://localhost:$(PORT),http://127.0.0.1:$(PORT)" \
ZENNOTES_AUTH_TOKEN="$$AUTH_TOKEN" \
ZENNOTES_ALLOW_INSECURE_NOAUTH="$(ALLOW_INSECURE_NOAUTH)" \
ZENNOTES_HOST_DATA="$$ABS_DATA" \
ZENNOTES_CONTAINER_UID="$$(id -u)" \
ZENNOTES_CONTAINER_GID="$$(id -g)" \
$(COMPOSE) up --build -d
@printf "\nZenNotes is running at $(APP_URL)\n\n"
ifneq ($(ALLOW_INSECURE_NOAUTH),1)
@printf "Auth token: $(DATA)/auth-token\n\n"
endif
ifneq ($(strip $(OPEN_BROWSER)),)
@$(OPEN_BROWSER) $(APP_URL) >/dev/null 2>&1 || true
endif

down:
@$(COMPOSE) down

restart:
@$(COMPOSE) restart zennotes 2>/dev/null || $(MAKE) --no-print-directory up

logs:
@$(COMPOSE) logs -f

status:
@$(COMPOSE) ps

open:
ifneq ($(strip $(OPEN_BROWSER)),)
@$(OPEN_BROWSER) $(APP_URL)
else
@echo "Open $(APP_URL) manually."
endif

rebuild:
@mkdir -p "$(CONTENT_ROOT)" "$(DATA)"
@ABS_CONTENT_ROOT="$$(cd "$(CONTENT_ROOT)" && pwd)"; \
ABS_DATA="$$(cd "$(DATA)" && pwd)"; \
AUTH_TOKEN=""; \
if [ "$(ALLOW_INSECURE_NOAUTH)" != "1" ]; then \
AUTH_TOKEN_FILE="$$ABS_DATA/auth-token"; \
if [ ! -f "$$AUTH_TOKEN_FILE" ]; then \
node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('hex'))" > "$$AUTH_TOKEN_FILE"; \
chmod 600 "$$AUTH_TOKEN_FILE"; \
fi; \
AUTH_TOKEN="$$(cat "$$AUTH_TOKEN_FILE")"; \
fi; \
ZENNOTES_IMAGE="$(IMAGE)" \
ZENNOTES_HOST_PORT="$(PORT)" \
ZENNOTES_HOST_CONTENT_ROOT="$$ABS_CONTENT_ROOT" \
ZENNOTES_CONTAINER_CONTENT_ROOT="$$ABS_CONTENT_ROOT" \
ZENNOTES_CONTAINER_DEFAULT_VAULT_PATH="$$ABS_CONTENT_ROOT" \
ZENNOTES_BROWSE_ROOTS="$$ABS_CONTENT_ROOT" \
ZENNOTES_ALLOWED_ORIGINS="http://localhost:$(PORT),http://127.0.0.1:$(PORT)" \
ZENNOTES_AUTH_TOKEN="$$AUTH_TOKEN" \
ZENNOTES_ALLOW_INSECURE_NOAUTH="$(ALLOW_INSECURE_NOAUTH)" \
ZENNOTES_HOST_DATA="$$ABS_DATA" \
ZENNOTES_CONTAINER_UID="$$(id -u)" \
ZENNOTES_CONTAINER_GID="$$(id -g)" \
$(COMPOSE) build --no-cache
@$(MAKE) --no-print-directory up

nuke:
@$(COMPOSE) down --rmi local --volumes || true
@rm -rf apps/web/dist apps/server/bin apps/server/web/dist $(DATA)

server-build: web-build
npm run build --workspace @zennotes/server

clean:
rm -rf apps/web/dist apps/server/bin apps/server/web/dist
Loading
Loading