initial commit

This commit is contained in:
wes
2025-05-29 11:07:25 -04:00
parent 0e97054273
commit aa6719663a
10 changed files with 7694 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# .gitignore
# Node
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build output
public/
# Script generated/temporary files
content/ # If the script creates it and doesn't always clean it up
.last_build_content_timestamp
cron_quartz_update.log
update_quartz_docker.log # Or whatever your script log file is named
compose_down_output.tmp # If you kept temporary files for compose output
compose_up_output.tmp # If you kept temporary files for compose output
# OS generated files
.DS_Store
Thumbs.db
# Editor/IDE specific
.vscode/
.idea/
Executable
+43
View File
@@ -0,0 +1,43 @@
# Stage 1: Build the Quartz site
FROM node:lts-slim AS builder
WORKDIR /app
# Copy package.json and package-lock.json (or yarn.lock)
COPY package*.json ./
# Install dependencies
RUN npm i
# Copy the rest of the Quartz project files
COPY . .
# Build the Quartz site (adjust if your build command is different)
# This usually outputs to a 'public' directory
RUN npx quartz build
# ---- START DEBUG ----
RUN echo "Listing contents of /app/public after build:" && ls -lA /app/public
RUN echo "Checking for /app/public/index.html:" && \
if [ -f /app/public/index.html ]; then \
echo "index.html found. First few lines:"; \
head -n 10 /app/public/index.html; \
else \
echo "index.html NOT FOUND in /app/public!"; \
fi
# ---- END DEBUG ----
# Stage 2: Serve the static files with NGINX
FROM nginx:alpine
# Remove default NGINX configuration
RUN rm /etc/nginx/conf.d/default.conf
# Copy custom NGINX configuration from your project
COPY custom_nginx.conf /etc/nginx/conf.d/default.conf
# Copy only the built static files from the 'builder' stage's 'public' folder
COPY --from=builder /app/public /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+11
View File
@@ -0,0 +1,11 @@
# ./custom-nginx.conf (on your host)
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html; # Path INSIDE the container where site files will be
index index.html;
location / {
try_files $uri $uri.html $uri/ =404;
}
}
+11
View File
@@ -0,0 +1,11 @@
# docker-compose.yml
version: '3.8'
services:
quartz-site:
build: .
image: my-quartz-site-prod
container_name: quartz-prod-container
ports:
- "18000:80"
restart: unless-stopped
Generated Executable
+7145
View File
File diff suppressed because it is too large Load Diff
Executable
+115
View File
@@ -0,0 +1,115 @@
{
"name": "@jackyzha0/quartz",
"description": "🌱 publish your digital garden and notes as a website",
"private": true,
"version": "4.5.1",
"type": "module",
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
"license": "MIT",
"homepage": "https://quartz.jzhao.xyz",
"repository": {
"type": "git",
"url": "https://github.com/jackyzha0/quartz.git"
},
"scripts": {
"quartz": "./quartz/bootstrap-cli.mjs",
"docs": "npx quartz build --serve -d docs",
"check": "tsc --noEmit && npx prettier . --check",
"format": "npx prettier . --write",
"test": "tsx --test",
"profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1"
},
"engines": {
"npm": ">=10.9.2",
"node": ">=22"
},
"keywords": [
"site generator",
"ssg",
"digital-garden",
"markdown",
"blog",
"quartz"
],
"bin": {
"quartz": "./quartz/bootstrap-cli.mjs"
},
"dependencies": {
"@clack/prompts": "^0.11.0",
"@floating-ui/dom": "^1.7.0",
"@myriaddreamin/rehype-typst": "^0.6.0",
"@napi-rs/simple-git": "0.1.19",
"@tweenjs/tween.js": "^25.0.0",
"@webgpu/types": "^0.1.61",
"ansi-truncate": "^1.2.0",
"async-mutex": "^0.5.0",
"chokidar": "^4.0.3",
"cli-spinner": "^0.2.10",
"d3": "^7.9.0",
"esbuild-sass-plugin": "^3.3.1",
"flexsearch": "0.7.43",
"github-slugger": "^2.0.0",
"globby": "^14.1.0",
"gray-matter": "^4.0.3",
"hast-util-to-html": "^9.0.5",
"hast-util-to-jsx-runtime": "^2.3.6",
"hast-util-to-string": "^3.0.1",
"is-absolute-url": "^4.0.1",
"js-yaml": "^4.1.0",
"lightningcss": "^1.30.1",
"mdast-util-find-and-replace": "^3.0.2",
"mdast-util-to-hast": "^13.2.0",
"mdast-util-to-string": "^4.0.0",
"micromorph": "^0.4.5",
"minimatch": "^10.0.1",
"pixi.js": "^8.9.2",
"preact": "^10.26.7",
"preact-render-to-string": "^6.5.13",
"pretty-bytes": "^7.0.0",
"pretty-time": "^1.1.0",
"reading-time": "^1.5.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-citation": "^2.3.1",
"rehype-katex": "^7.0.1",
"rehype-mathjax": "^7.1.0",
"rehype-pretty-code": "^0.14.1",
"rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0",
"remark": "^15.0.1",
"remark-breaks": "^4.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"remark-smartypants": "^3.0.2",
"rfdc": "^1.4.1",
"satori": "^0.13.1",
"serve-handler": "^6.1.6",
"sharp": "^0.34.2",
"shiki": "^1.26.2",
"source-map-support": "^0.5.21",
"to-vfile": "^8.0.0",
"toml": "^3.0.0",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.3",
"workerpool": "^9.2.0",
"ws": "^8.18.2",
"yargs": "^18.0.0"
},
"devDependencies": {
"@types/d3": "^7.4.3",
"@types/hast": "^3.0.4",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.15.23",
"@types/pretty-time": "^1.1.5",
"@types/source-map-support": "^0.5.10",
"@types/ws": "^8.18.1",
"@types/yargs": "^17.0.33",
"esbuild": "^0.25.5",
"prettier": "^3.5.3",
"tsx": "^4.19.4",
"typescript": "^5.8.3"
}
}
+97
View File
@@ -0,0 +1,97 @@
import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
/**
* Quartz 4 Configuration
*
* See https://quartz.jzhao.xyz/configuration for more information.
*/
const config: QuartzConfig = {
configuration: {
pageTitle: "Wesley Ray",
pageTitleSuffix: " // Wes // Portfolio // Datascrolls",
enableSPA: true,
enablePopovers: true,
analytics: {
provider: "plausible",
},
locale: "en-US",
baseUrl: "garden.c0smere.net",
ignorePatterns: ["private", "templates", ".obsidian"],
defaultDateType: "modified",
theme: {
fontOrigin: "googleFonts",
cdnCaching: true,
typography: {
header: "Schibsted Grotesk",
body: "Source Sans Pro",
code: "IBM Plex Mono",
},
colors: {
lightMode: {
light: "#faf8f8",
lightgray: "#e5e5e5",
gray: "#b8b8b8",
darkgray: "#4e4e4e",
dark: "#2b2b2b",
secondary: "#284b63",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
textHighlight: "#fff23688",
},
darkMode: {
light: "#161618",
lightgray: "#393639",
gray: "#646464",
darkgray: "#d4d4d4",
dark: "#ebebec",
secondary: "#7b97aa",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
textHighlight: "#b3aa0288",
},
},
},
},
plugins: {
transformers: [
Plugin.FrontMatter(),
Plugin.CreatedModifiedDate({
priority: ["frontmatter", "git", "filesystem"],
}),
Plugin.SyntaxHighlighting({
theme: {
light: "github-light",
dark: "github-dark",
},
keepBackground: false,
}),
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(),
Plugin.TableOfContents(),
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
Plugin.Description(),
Plugin.Latex({ renderEngine: "katex" }),
],
filters: [Plugin.RemoveDrafts()],
emitters: [
Plugin.AliasRedirects(),
Plugin.ComponentResources(),
Plugin.ContentPage(),
Plugin.FolderPage(),
Plugin.TagPage(),
Plugin.ContentIndex({
enableSiteMap: true,
enableRSS: true,
}),
Plugin.Assets(),
Plugin.Static(),
Plugin.Favicon(),
Plugin.NotFoundPage(),
// Comment out CustomOgImages to speed up build time
Plugin.CustomOgImages(),
],
},
}
export default config
+68
View File
@@ -0,0 +1,68 @@
import { PageLayout, SharedLayout } from "./quartz/cfg"
import * as Component from "./quartz/components"
// components shared across all pages
export const sharedPageComponents: SharedLayout = {
head: Component.Head(),
header: [],
afterBody: [],
footer: Component.Footer({
links: {
GitHub: "https://github.com/jackyzha0/quartz",
"Discord Community": "https://discord.gg/cRFFHYye7t",
},
}),
}
// components for pages that display a single page (e.g. a single note)
export const defaultContentPageLayout: PageLayout = {
beforeBody: [
Component.ConditionalRender({
component: Component.Breadcrumbs(),
condition: (page) => page.fileData.slug !== "index",
}),
Component.ArticleTitle(),
Component.ContentMeta(),
Component.TagList(),
],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Flex({
components: [
{
Component: Component.Search(),
grow: true,
},
{ Component: Component.Darkmode() },
{ Component: Component.ReaderMode() },
],
}),
Component.Explorer(),
],
right: [
Component.Graph(),
Component.DesktopOnly(Component.TableOfContents()),
Component.Backlinks(),
],
}
// components for pages that display lists of pages (e.g. tags or folders)
export const defaultListPageLayout: PageLayout = {
beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta()],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Flex({
components: [
{
Component: Component.Search(),
grow: true,
},
{ Component: Component.Darkmode() },
],
}),
Component.Explorer(),
],
right: [],
}
Executable
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"lib": ["esnext", "DOM", "DOM.Iterable"],
"experimentalDecorators": true,
"module": "esnext",
"target": "esnext",
"moduleResolution": "node",
"strict": true,
"incremental": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"include": ["**/*.ts", "**/*.tsx", "./package.json"],
"exclude": ["build/**/*.d.ts"]
}
+156
View File
@@ -0,0 +1,156 @@
#!/bin/bash
# --- Configuration ---
# IMPORTANT: Set this to the ABSOLUTE path of your Quartz project directory on the host
QUARTZ_PROJECT_DIR="/home/hoid/projects/portfolio/quartz"
# IMPORTANT: Set this to the ABSOLUTE path of the ACTUAL FOLDER in your Obsidian vault
# that contains the Markdown files and assets for your Quartz site.
# This is the SOURCE of your content.
OBSIDIAN_CONTENT_SOURCE="/home/hoid/docker/obsidian/vaults/weeslahw_coppermind/Digital_Garden"
# Directory name within your QUARTZ_PROJECT_DIR that Quartz uses for content
# This is where the script will temporarily place the real content for the Docker build.
# The Dockerfile's `COPY . .` will then pick this up.
LOCAL_CONTENT_TARGET_DIR_NAME="content"
# Timestamp file to track the last successful build's content state
# This file will be created in the QUARTZ_PROJECT_DIR
LAST_BUILD_TIMESTAMP_FILE=".last_build_content_timestamp"
# Log file for this script's output (recommended when run by cron)
LOG_FILE="${QUARTZ_PROJECT_DIR}/update_quartz_docker.log"
# --- Script Logic ---
# Function for logging
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] - $1" | tee -a "$LOG_FILE"
}
cd "$QUARTZ_PROJECT_DIR" || {
log_message "FATAL: Failed to cd to project directory: ${QUARTZ_PROJECT_DIR}"
exit 1
}
log_message "--- Quartz Docker Update Script Started ---"
# --- Change Detection ---
NEEDS_REBUILD=false
if [ ! -d "$OBSIDIAN_CONTENT_SOURCE" ]; then
log_message "ERROR: Obsidian content source directory does not exist: ${OBSIDIAN_CONTENT_SOURCE}"
NEEDS_REBUILD=false # Or set to true if you want to try building with an empty content dir
elif [ ! -f "$LAST_BUILD_TIMESTAMP_FILE" ]; then
log_message "No last build timestamp found at ${LAST_BUILD_TIMESTAMP_FILE}. Triggering initial rebuild."
NEEDS_REBUILD=true
else
# Efficiently check if any file within OBSIDIAN_CONTENT_SOURCE is newer than the timestamp file
if find "$OBSIDIAN_CONTENT_SOURCE" -type f -newer "$LAST_BUILD_TIMESTAMP_FILE" -print -quit | grep -q .; then
log_message "Changes detected in Obsidian content directory (${OBSIDIAN_CONTENT_SOURCE}) since last build. Triggering rebuild."
NEEDS_REBUILD=true
else
log_message "No changes detected in Obsidian content directory since last build."
fi
fi
if [ "$NEEDS_REBUILD" = false ]; then
log_message "Skipping Docker rebuild and deploy."
log_message "--- Script Finished (No Changes) ---"
exit 0
fi
# --- Prepare Content for Docker Build ---
LOCAL_CONTENT_FULL_PATH="${QUARTZ_PROJECT_DIR}/${LOCAL_CONTENT_TARGET_DIR_NAME}"
log_message "Preparing content directory: ${LOCAL_CONTENT_FULL_PATH}"
# Remove existing local content directory/symlink in project dir (if any)
if [ -L "${LOCAL_CONTENT_FULL_PATH}" ] || [ -d "${LOCAL_CONTENT_FULL_PATH}" ]; then
log_message "Removing existing ./${LOCAL_CONTENT_TARGET_DIR_NAME}"
rm -rf "${LOCAL_CONTENT_FULL_PATH}"
fi
log_message "Creating new ./${LOCAL_CONTENT_TARGET_DIR_NAME} directory"
mkdir "${LOCAL_CONTENT_FULL_PATH}"
if [ $? -ne 0 ]; then
log_message "ERROR: Failed to create local content directory: ${LOCAL_CONTENT_FULL_PATH}"
log_message "--- Script Finished (Error) ---"
exit 1
fi
log_message "Copying content from ${OBSIDIAN_CONTENT_SOURCE} to ${LOCAL_CONTENT_FULL_PATH}"
# Using rsync for efficient copying; ensure it's installed (sudo apt install rsync)
# The trailing slash on OBSIDIAN_CONTENT_SOURCE/ is important for rsync
rsync -a --delete "${OBSIDIAN_CONTENT_SOURCE}/" "${LOCAL_CONTENT_FULL_PATH}/"
if [ $? -ne 0 ]; then
log_message "ERROR: Failed to copy content from Obsidian source to local content directory."
log_message "--- Script Finished (Error) ---"
exit 1
fi
log_message "Content prepared successfully for Docker build."
# --- Docker Build & Deploy using Docker Compose ---
# Use absolute path for docker-compose for robustness in cron
DOCKER_COMPOSE_CMD="/usr/local/bin/docker-compose"
log_message "Tearing down existing Docker Compose services (if any) for a clean deployment..."
# Create a temporary file to capture output from 'docker-compose down'
COMPOSE_DOWN_OUTPUT_FILE="${QUARTZ_PROJECT_DIR}/compose_down_output.tmp"
# Execute 'docker-compose down' and capture all its output
$DOCKER_COMPOSE_CMD down --remove-orphans --volumes > "$COMPOSE_DOWN_OUTPUT_FILE" 2>&1
DOWN_EXIT_CODE=$?
if [ $DOWN_EXIT_CODE -ne 0 ]; then
log_message "WARNING: '$DOCKER_COMPOSE_CMD down' failed with exit code $DOWN_EXIT_CODE. Output from command:"
# Append the captured output from docker-compose down to the main log
# Ensure there's a newline before catting, in case log_message doesn't add one after its prefix
echo "" >> "$LOG_FILE"
cat "$COMPOSE_DOWN_OUTPUT_FILE" >> "$LOG_FILE"
echo "" >> "$LOG_FILE" # Add a newline after the output
log_message "(End of '$DOCKER_COMPOSE_CMD down' output for this attempt)"
log_message "Attempting to proceed with 'up' command despite 'down' issues..." # This was your script's original logic
else
log_message "'$DOCKER_COMPOSE_CMD down' executed successfully."
fi
rm -f "$COMPOSE_DOWN_OUTPUT_FILE" # Clean up the temporary file
log_message "Building and deploying with Docker Compose..."
# Create a temporary file to capture output from 'docker-compose up'
COMPOSE_UP_OUTPUT_FILE="${QUARTZ_PROJECT_DIR}/compose_up_output.tmp"
$DOCKER_COMPOSE_CMD up -d --build --remove-orphans > "$COMPOSE_UP_OUTPUT_FILE" 2>&1
UP_EXIT_CODE=$?
if [ $UP_EXIT_CODE -ne 0 ]; then
log_message "ERROR: '$DOCKER_COMPOSE_CMD up -d --build --remove-orphans' failed with exit code $UP_EXIT_CODE! Output from command:"
echo "" >> "$LOG_FILE"
cat "$COMPOSE_UP_OUTPUT_FILE" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
log_message "(End of '$DOCKER_COMPOSE_CMD up' output for this attempt)"
log_message "--- Script Finished (Error) ---"
rm -f "$COMPOSE_UP_OUTPUT_FILE" # Clean up
exit 1
else
log_message "'$DOCKER_COMPOSE_CMD up -d --build --remove-orphans' executed successfully."
fi
rm -f "$COMPOSE_UP_OUTPUT_FILE" # Clean up
# Update the timestamp file on successful deployment
touch "$LAST_BUILD_TIMESTAMP_FILE"
log_message "Updated last build timestamp: ${LAST_BUILD_TIMESTAMP_FILE}"
# Optional: Clean up by removing the copied content and restoring symlink if needed for local dev
# This is useful if your local Quartz dev workflow relies on the symlink.
# If the cron job is the ONLY way this is built, cleanup might not be strictly necessary,
# but it keeps the project dir clean of copied data between runs.
log_message "Cleaning up temporary local content directory..."
rm -rf "${LOCAL_CONTENT_FULL_PATH}"
# If you use a symlink for local dev, uncomment and adjust to restore it:
#ln -s "${OBSIDIAN_CONTENT_SOURCE}" "${LOCAL_CONTENT_FULL_PATH}"
log_message "Local content directory cleaned up."
log_message "--- Script Finished Successfully ---"