From ab85de9b35aabdca1bf210f108a11b2fe3499ff0 Mon Sep 17 00:00:00 2001 From: "nekomimi.pet" Date: Wed, 5 Nov 2025 06:19:39 -0500 Subject: [PATCH] add deploy wisp spindle, add jacquard submodule for local builds (for now till jacquard 0.9) add local oauth loopback for dev color rework pull submodules init submodules rustup pls work meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow --- .gitmodules | 3 ++ .tangled/workflows/deploy-wisp.yml | 52 ++++++++++++++++++++++ cli/jacquard | 1 + public/styles/global.css | 58 +++++++++++++++---------- src/index.ts | 2 +- src/lib/db.ts | 69 +++++++++++++++++++++--------- src/lib/oauth-client.ts | 34 +++++++++++++-- src/lib/types.ts | 4 +- testDeploy/index.html | 40 +++++++++++++++++ 9 files changed, 214 insertions(+), 49 deletions(-) create mode 100644 .gitmodules create mode 100644 .tangled/workflows/deploy-wisp.yml create mode 160000 cli/jacquard create mode 100644 testDeploy/index.html diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..784460f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cli/jacquard"] + path = cli/jacquard + url = https://tangled.org/@nonbinary.computer/jacquard diff --git a/.tangled/workflows/deploy-wisp.yml b/.tangled/workflows/deploy-wisp.yml new file mode 100644 index 0000000..f77c379 --- /dev/null +++ b/.tangled/workflows/deploy-wisp.yml @@ -0,0 +1,52 @@ +# Deploy to Wisp.place +# This workflow builds your site and deploys it to Wisp.place using the wisp-cli +when: + - event: ['push'] + branch: ['main'] + - event: ['manual'] +engine: 'nixery' +clone: + skip: false + depth: 1 + submodules: true +dependencies: + nixpkgs: + - git + - gcc + github:NixOS/nixpkgs/nixpkgs-unstable: + - rustc + - cargo +environment: + # Customize these for your project + SITE_PATH: 'testDeploy' + SITE_NAME: 'wispPlaceDocs' +steps: + - name: 'Initialize submodules' + command: | + git submodule update --init --recursive + + - name: 'Build wisp-cli' + command: | + cd cli + export PATH="$HOME/.nix-profile/bin:$PATH" + nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs + nix-channel --update + nix-shell -p pkg-config openssl --run ' + export PKG_CONFIG_PATH="$(pkg-config --variable pc_path pkg-config)" + export OPENSSL_DIR="$(nix-build --no-out-link "" -A openssl.dev)" + export OPENSSL_NO_VENDOR=1 + export OPENSSL_LIB_DIR="$(nix-build --no-out-link "" -A openssl.out)/lib" + cargo build --release + ' + cd .. + + - name: 'Deploy to Wisp.place' + command: | + ./cli/target/release/wisp-cli \ + "$WISP_HANDLE" \ + --path "$SITE_PATH" \ + --site "$SITE_NAME" \ + --password "$WISP_APP_PASSWORD" + environment: + WISP_HANDLE: '${{ secrets.WISP_HANDLE }}' + WISP_APP_PASSWORD: '${{ secrets.WISP_APP_PASSWORD }}' diff --git a/cli/jacquard b/cli/jacquard new file mode 160000 index 0000000..d533482 --- /dev/null +++ b/cli/jacquard @@ -0,0 +1 @@ +Subproject commit d533482a61f540586b1eea620b8e9a01a59d5650 diff --git a/public/styles/global.css b/public/styles/global.css index 5a01356..a0e817d 100644 --- a/public/styles/global.css +++ b/public/styles/global.css @@ -4,36 +4,37 @@ @custom-variant dark (&:is(.dark *)); :root { - /* #F2E7C9 - parchment background */ - --background: oklch(0.93 0.03 85); - /* #413C58 - violet for text */ - --foreground: oklch(0.32 0.04 285); + /* Warm beige background inspired by Sunset design #E9DDD8 */ + --background: oklch(0.90 0.012 35); + /* Very dark brown text for strong contrast #2A2420 */ + --foreground: oklch(0.18 0.01 30); - --card: oklch(0.98 0.01 85); - --card-foreground: oklch(0.32 0.04 285); + /* Slightly lighter card background */ + --card: oklch(0.93 0.01 35); + --card-foreground: oklch(0.18 0.01 30); - --popover: oklch(0.98 0.01 85); - --popover-foreground: oklch(0.32 0.04 285); + --popover: oklch(0.93 0.01 35); + --popover-foreground: oklch(0.18 0.01 30); - /* #413C58 - violet primary */ - --primary: oklch(0.32 0.04 285); - --primary-foreground: oklch(0.98 0.01 85); + /* Dark brown primary inspired by #645343 */ + --primary: oklch(0.35 0.02 35); + --primary-foreground: oklch(0.95 0.01 35); - /* #FFAAD2 - pink accent */ + /* Bright pink accent for links #FFAAD2 */ --accent: oklch(0.78 0.15 345); - --accent-foreground: oklch(0.32 0.04 285); + --accent-foreground: oklch(0.18 0.01 30); - /* #348AA7 - blue secondary */ - --secondary: oklch(0.56 0.08 220); - --secondary-foreground: oklch(0.98 0.01 85); + /* Medium taupe secondary inspired by #867D76 */ + --secondary: oklch(0.52 0.015 30); + --secondary-foreground: oklch(0.95 0.01 35); - /* #CCD7C5 - ash muted */ - --muted: oklch(0.85 0.02 130); - --muted-foreground: oklch(0.45 0.03 285); + /* Light warm muted background */ + --muted: oklch(0.88 0.01 35); + --muted-foreground: oklch(0.42 0.015 30); - --border: oklch(0.75 0.02 285); - --input: oklch(0.75 0.02 285); - --ring: oklch(0.78 0.15 345); + --border: oklch(0.75 0.015 30); + --input: oklch(0.92 0.01 35); + --ring: oklch(0.72 0.08 15); --destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.985 0 0); @@ -150,3 +151,16 @@ @apply bg-background text-foreground; } } + +@keyframes arrow-bounce { + 0%, 100% { + transform: translateX(0); + } + 50% { + transform: translateX(4px); + } +} + +.arrow-animate { + animation: arrow-bounce 1.5s ease-in-out infinite; +} diff --git a/src/index.ts b/src/index.ts index 40201c5..ba58063 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,7 @@ import { promptAdminSetup } from './lib/admin-auth' import { adminRoutes } from './routes/admin' const config: Config = { - domain: (Bun.env.DOMAIN ?? `https://${BASE_HOST}`) as `https://${string}`, + domain: (Bun.env.DOMAIN ?? `https://${BASE_HOST}`) as Config['domain'], clientName: Bun.env.CLIENT_NAME ?? 'PDS-View' } diff --git a/src/lib/db.ts b/src/lib/db.ts index f13664e..f86fa4c 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -337,25 +337,54 @@ export const cleanupExpiredSessions = async () => { } }; -export const createClientMetadata = (config: { domain: `https://${string}`, clientName: string }): ClientMetadata => ({ - client_id: `${config.domain}/client-metadata.json`, - client_name: config.clientName, - client_uri: config.domain, - logo_uri: `${config.domain}/logo.png`, - tos_uri: `${config.domain}/tos`, - policy_uri: `${config.domain}/policy`, - redirect_uris: [`${config.domain}/api/auth/callback`], - grant_types: ['authorization_code', 'refresh_token'], - response_types: ['code'], - application_type: 'web', - token_endpoint_auth_method: 'private_key_jwt', - token_endpoint_auth_signing_alg: "ES256", - scope: "atproto transition:generic", - dpop_bound_access_tokens: true, - jwks_uri: `${config.domain}/jwks.json`, - subject_type: 'public', - authorization_signed_response_alg: 'ES256' -}); +export const createClientMetadata = (config: { domain: `http://${string}` | `https://${string}`, clientName: string }): ClientMetadata => { + const isLocalDev = process.env.LOCAL_DEV === 'true'; + + if (isLocalDev) { + // Loopback client for local development + // For loopback, scopes and redirect_uri must be in client_id query string + const redirectUri = 'http://127.0.0.1:8000/api/auth/callback'; + const scope = 'atproto transition:generic'; + const params = new URLSearchParams(); + params.append('redirect_uri', redirectUri); + params.append('scope', scope); + + return { + client_id: `http://localhost?${params.toString()}`, + client_name: config.clientName, + client_uri: config.domain, + redirect_uris: [redirectUri], + grant_types: ['authorization_code', 'refresh_token'], + response_types: ['code'], + application_type: 'web', + token_endpoint_auth_method: 'none', + scope: scope, + dpop_bound_access_tokens: false, + subject_type: 'public' + }; + } + + // Production client with private_key_jwt + return { + client_id: `${config.domain}/client-metadata.json`, + client_name: config.clientName, + client_uri: config.domain, + logo_uri: `${config.domain}/logo.png`, + tos_uri: `${config.domain}/tos`, + policy_uri: `${config.domain}/policy`, + redirect_uris: [`${config.domain}/api/auth/callback`], + grant_types: ['authorization_code', 'refresh_token'], + response_types: ['code'], + application_type: 'web', + token_endpoint_auth_method: 'private_key_jwt', + token_endpoint_auth_signing_alg: "ES256", + scope: "atproto transition:generic", + dpop_bound_access_tokens: true, + jwks_uri: `${config.domain}/jwks.json`, + subject_type: 'public', + authorization_signed_response_alg: 'ES256' + }; +}; const persistKey = async (key: JoseKey) => { const priv = key.privateJwk; @@ -443,7 +472,7 @@ export const rotateKeysIfNeeded = async (): Promise => { } }; -export const getOAuthClient = async (config: { domain: `https://${string}`, clientName: string }) => { +export const getOAuthClient = async (config: { domain: `http://${string}` | `https://${string}`, clientName: string }) => { const keys = await ensureKeys(); return new NodeOAuthClient({ diff --git a/src/lib/oauth-client.ts b/src/lib/oauth-client.ts index da0a1dc..a087c80 100644 --- a/src/lib/oauth-client.ts +++ b/src/lib/oauth-client.ts @@ -103,12 +103,38 @@ export const cleanupExpiredSessions = async () => { } }; -export const createClientMetadata = (config: { domain: `https://${string}`, clientName: string }): ClientMetadata => { - // Use editor.wisp.place for OAuth endpoints since that's where the API routes live +export const createClientMetadata = (config: { domain: `http://${string}` | `https://${string}`, clientName: string }): ClientMetadata => { + const isLocalDev = Bun.env.LOCAL_DEV === 'true'; + + if (isLocalDev) { + // Loopback client for local development + // For loopback, scopes and redirect_uri must be in client_id query string + const redirectUri = 'http://127.0.0.1:8000/api/auth/callback'; + const scope = 'atproto transition:generic'; + const params = new URLSearchParams(); + params.append('redirect_uri', redirectUri); + params.append('scope', scope); + + return { + client_id: `http://localhost?${params.toString()}`, + client_name: config.clientName, + client_uri: `https://wisp.place`, + redirect_uris: [redirectUri], + grant_types: ['authorization_code', 'refresh_token'], + response_types: ['code'], + application_type: 'web', + token_endpoint_auth_method: 'none', + scope: scope, + dpop_bound_access_tokens: false, + subject_type: 'public' + }; + } + + // Production client with private_key_jwt return { client_id: `${config.domain}/client-metadata.json`, client_name: config.clientName, - client_uri: `https://wisp.place`, + client_uri: `https://wisp.place`, logo_uri: `${config.domain}/logo.png`, tos_uri: `${config.domain}/tos`, policy_uri: `${config.domain}/policy`, @@ -212,7 +238,7 @@ export const rotateKeysIfNeeded = async (): Promise => { } }; -export const getOAuthClient = async (config: { domain: `https://${string}`, clientName: string }) => { +export const getOAuthClient = async (config: { domain: `http://${string}` | `https://${string}`, clientName: string }) => { const keys = await ensureKeys(); return new NodeOAuthClient({ diff --git a/src/lib/types.ts b/src/lib/types.ts index c37918c..4453a26 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -3,8 +3,8 @@ * @typeParam Config */ export type Config = { - /** The base domain URL with HTTPS protocol */ - domain: `https://${string}`, + /** The base domain URL with HTTP or HTTPS protocol */ + domain: `http://${string}` | `https://${string}`, /** Name of the client application */ clientName: string }; diff --git a/testDeploy/index.html b/testDeploy/index.html new file mode 100644 index 0000000..181af0c --- /dev/null +++ b/testDeploy/index.html @@ -0,0 +1,40 @@ + + + + + + Wisp.place Test Site + + + +

Hello from Wisp.place!

+

This is a test deployment using the wisp-cli and Tangled Spindles CI/CD.

+ +
+

About this deployment

+

This site was deployed to the AT Protocol using:

+
    +
  • Wisp.place CLI (Rust)
  • +
  • Tangled Spindles CI/CD
  • +
  • AT Protocol for decentralized hosting
  • +
+
+ + -- 2.54.0