this repo has no description

init

ai.syui.ai ad7c3a86

+382
+14
.gitignore
···
··· 1 + /tmp 2 + /claude.md 3 + /CLAUDE.md 4 + .claude 5 + .DS_Store 6 + .env 7 + /envs/knot 8 + /envs/appview 9 + /deploy.yml 10 + /install.zsh 11 + /keys 12 + /data 13 + /repositories 14 + /server
+44
compose.yml
···
··· 1 + services: 2 + redis: 3 + image: redis:alpine 4 + restart: always 5 + volumes: 6 + - ./data/redis:/data 7 + healthcheck: 8 + test: ["CMD", "redis-cli", "ping", "|", "grep", "PONG"] 9 + interval: 1s 10 + timeout: 5s 11 + retries: 5 12 + 13 + knot: 14 + build: 15 + context: ./repos/knot-docker/ 16 + args: 17 + UID: 1000 18 + GID: 1000 19 + restart: always 20 + ports: 21 + - "2588:5555" 22 + - "2222:22" 23 + volumes: 24 + - ./keys:/etc/ssh/keys 25 + - ./repositories:/home/git/repositories 26 + - ./server:/app 27 + env_file: 28 + - ./envs/knot 29 + 30 + appview: 31 + build: 32 + context: . 33 + dockerfile: docker/appview/Dockerfile 34 + target: appview 35 + restart: always 36 + ports: 37 + - "2589:3000" 38 + volumes: 39 + - ./data/appview:/data 40 + env_file: 41 + - ./envs/appview 42 + depends_on: 43 + redis: 44 + condition: service_healthy
+49
docker/appview/Dockerfile
···
··· 1 + FROM golang:1.25-bookworm AS builder 2 + RUN apt-get update && apt-get install -y git gcc libc6-dev nodejs npm curl unzip && rm -rf /var/lib/apt/lists/* 3 + WORKDIR /build 4 + COPY repos/core/ . 5 + 6 + # static assets required for go:embed 7 + RUN mkdir -p appview/pages/static/fonts appview/pages/static/icons appview/pages/static/logos 8 + 9 + # JS 10 + RUN curl -sLo appview/pages/static/htmx.min.js https://unpkg.com/[email protected]/dist/htmx.min.js && \ 11 + curl -sLo appview/pages/static/htmx-ext-ws.min.js https://unpkg.com/[email protected]/ws.js && \ 12 + curl -sLo appview/pages/static/mermaid.min.js https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js && \ 13 + touch appview/pages/static/actor-typeahead.js 14 + 15 + # Fonts 16 + RUN curl -sLo /tmp/inter.zip https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip && \ 17 + cd /tmp && unzip -q inter.zip -d inter && \ 18 + find /tmp/inter -name 'InterVariable*.woff2' -exec cp {} /build/appview/pages/static/fonts/ \; && \ 19 + find /tmp/inter -name 'InterDisplay*.woff2' -exec cp {} /build/appview/pages/static/fonts/ \; || true 20 + 21 + RUN curl -sLo /tmp/plex.zip https://github.com/IBM/plex/releases/download/v6.4.0/IBM-Plex-Mono.zip && \ 22 + cd /tmp && unzip -q plex.zip -d plex && \ 23 + find /tmp/plex -name 'IBMPlexMono*.woff2' -exec cp {} /build/appview/pages/static/fonts/ \; || true 24 + 25 + # Lucide icons 26 + RUN curl -sLo /tmp/lucide.zip https://github.com/lucide-icons/lucide/releases/download/0.344.0/lucide-icons-0.344.0.zip && \ 27 + cd /tmp && unzip -q lucide.zip -d lucide && \ 28 + find /tmp/lucide -name '*.svg' -exec cp {} /build/appview/pages/static/icons/ \; || true 29 + 30 + # Placeholder logos 31 + RUN touch appview/pages/static/logos/dolly.png appview/pages/static/logos/dolly.ico appview/pages/static/logos/dolly.svg 32 + 33 + # Custom logos (place files in docker/appview/logos/) 34 + COPY docker/appview/logos/ appview/pages/static/logos/ 35 + 36 + # Tailwind CSS (v3 - matches tailwind.config.js) 37 + RUN cd /build && npm install tailwindcss@3 @tailwindcss/typography && \ 38 + npx tailwindcss -c tailwind.config.js -i input.css -o appview/pages/static/tw.css --minify 2>&1 && \ 39 + echo "tw.css size: $(wc -c < appview/pages/static/tw.css) bytes" 40 + 41 + # Build 42 + RUN CGO_ENABLED=1 go build -o appview-bin ./cmd/appview/ 43 + 44 + # --- AppView --- 45 + FROM debian:bookworm-slim AS appview 46 + RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* 47 + COPY --from=builder /build/appview-bin /usr/local/bin/appview 48 + EXPOSE 3000 49 + CMD ["appview"]
+19
docker/appview/logos/ai.svg
···
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024"> 2 + <path fill-rule="evenodd" fill="#F6E717" d=" 3 + M 619,232 4 + L 512,7 5 + L 405,232 6 + A 300,300 0 0,0 216,559 7 + L 75,765 8 + L 323,745 9 + A 300,300 0 0,0 701,745 10 + L 949,765 11 + L 808,559 12 + A 300,300 0 0,0 619,232 13 + Z 14 + M 512,337 15 + A 175,175 0 0,0 512,687 16 + A 175,175 0 0,0 512,337 17 + Z 18 + "/> 19 + </svg>
+41
envs/appview.example
···
··· 1 + # core 2 + TANGLED_APPVIEW_HOST=git.example.com 3 + TANGLED_APPVIEW_NAME=Example Git 4 + TANGLED_DB_PATH=/data/appview.db 5 + TANGLED_LISTEN_ADDR=0.0.0.0:3000 6 + TANGLED_DEV=false 7 + TANGLED_COOKIE_SECRET=change-me-32-char-random-string! 8 + 9 + # at protocol 10 + TANGLED_PLC_URL=https://plc.directory 11 + TANGLED_JETSTREAM_ENDPOINT=wss://jetstream1.us-east.bsky.network/subscribe 12 + 13 + # oauth (generate with: goat key generate -t P-256) 14 + TANGLED_OAUTH_CLIENT_SECRET=z-multibase-p256-secret-key 15 + TANGLED_OAUTH_CLIENT_KID=1234567890 16 + 17 + # redis 18 + TANGLED_REDIS_ADDR=tangled-redis:6379 19 + # TANGLED_REDIS_PASS= 20 + # TANGLED_REDIS_DB=0 21 + 22 + # labels (empty for self-host, default requires tangled.sh DID) 23 + TANGLED_LABEL_DEFAULTS= 24 + TANGLED_LABEL_GFI= 25 + 26 + # email (optional, uses resend.com) 27 + # TANGLED_RESEND_API_KEY=re_xxxxx 28 + # [email protected] 29 + 30 + # optional services 31 + # TANGLED_CAMO_HOST=https://camo.example.com 32 + # TANGLED_CAMO_SHARED_SECRET= 33 + # TANGLED_AVATAR_HOST=https://avatar.example.com 34 + # TANGLED_AVATAR_SHARED_SECRET= 35 + # TANGLED_PDS_HOST=https://pds.example.com 36 + # TANGLED_PDS_ADMIN_SECRET= 37 + # TANGLED_CLOUDFLARE_API_TOKEN= 38 + # TANGLED_CLOUDFLARE_ZONE_ID= 39 + # TANGLED_CLOUDFLARE_TURNSTILE_SITE_KEY= 40 + # TANGLED_CLOUDFLARE_TURNSTILE_SECRET_KEY= 41 + # TANGLED_POSTHOG_API_KEY=
+16
envs/knot.example
···
··· 1 + # required 2 + KNOT_SERVER_HOSTNAME=knot.example.com 3 + KNOT_SERVER_OWNER=did:plc:xxxxx 4 + 5 + # network 6 + KNOT_SERVER_JETSTREAM_ENDPOINT=wss://jetstream.example.com/subscribe 7 + APPVIEW_ENDPOINT=http://appview:3000 8 + 9 + # optional (defaults shown) 10 + # KNOT_SERVER_LISTEN_ADDR=0.0.0.0:5555 11 + # KNOT_SERVER_INTERNAL_LISTEN_ADDR=0.0.0.0:5444 12 + # KNOT_SERVER_DB_PATH=/app/knotserver.db 13 + # KNOT_REPO_SCAN_PATH=/home/git/repositories 14 + # KNOT_REPO_MAIN_BRANCH=main 15 + # KNOT_SERVER_DEV=false 16 + # KNOT_SERVER_LOG_DIDS=true
+52
patching/100-appview-avatar-pds-blob.patch
···
··· 1 + --- a/appview/pages/funcmap.go 2 + +++ b/appview/pages/funcmap.go 3 + @@ -505,34 +505,26 @@ 4 + did = identity.DID.String() 5 + } 6 + 7 + - secret := p.avatar.SharedSecret 8 + - h := hmac.New(sha256.New, []byte(secret)) 9 + - h.Write([]byte(did)) 10 + - signature := hex.EncodeToString(h.Sum(nil)) 11 + - 12 + - // Get avatar CID for cache busting 13 + + // Get avatar CID from profile DB 14 + profile, err := db.GetProfile(p.db, did) 15 + - version := "" 16 + - if err == nil && profile != nil && profile.Avatar != "" { 17 + - // Use first 8 chars of avatar CID as version 18 + - if len(profile.Avatar) > 8 { 19 + - version = profile.Avatar[:8] 20 + - } else { 21 + - version = profile.Avatar 22 + + if err == nil && profile != nil && profile.Avatar != "" && identity != nil { 23 + + // Get PDS endpoint from DID document 24 + + if svc, ok := identity.Services["atproto_pds"]; ok { 25 + + pdsUrl := strings.TrimRight(svc.URL, "/") 26 + + return fmt.Sprintf("%s/xrpc/com.atproto.sync.getBlob?did=%s&cid=%s", pdsUrl, did, profile.Avatar) 27 + } 28 + } 29 + 30 + - baseUrl := fmt.Sprintf("%s/%s/%s", p.avatar.Host, signature, did) 31 + - if size != "" { 32 + - if version != "" { 33 + - return fmt.Sprintf("%s?size=%s&v=%s", baseUrl, size, version) 34 + - } 35 + - return fmt.Sprintf("%s?size=%s", baseUrl, size) 36 + + // Fallback to avatar proxy if configured 37 + + if p.avatar.Host != "" && p.avatar.SharedSecret != "" { 38 + + secret := p.avatar.SharedSecret 39 + + h := hmac.New(sha256.New, []byte(secret)) 40 + + h.Write([]byte(did)) 41 + + signature := hex.EncodeToString(h.Sum(nil)) 42 + + return fmt.Sprintf("%s/%s/%s", p.avatar.Host, signature, did) 43 + } 44 + - if version != "" { 45 + - return fmt.Sprintf("%s?v=%s", baseUrl, version) 46 + - } 47 + - return baseUrl 48 + + 49 + + return "" 50 + } 51 + 52 + func (p *Pages) icon(name string, classes []string) (template.HTML, error) {
+114
patching/110-appview-custom-footer.patch
···
··· 1 + --- a/appview/pages/templates/layouts/fragments/footer.html 2026-03-10 19:59:35 2 + +++ b/appview/pages/templates/layouts/fragments/footer.html 2026-03-10 20:00:03 3 + @@ -1,102 +1,13 @@ 4 + {{ define "layouts/fragments/footer" }} 5 + -<div class="w-full p-8 bg-white dark:bg-gray-800"> 6 + - <div class="mx-auto px-4"> 7 + - <div class="flex flex-col text-gray-600 dark:text-gray-400 gap-8"> 8 + - <!-- Desktop layout: grid with 3 columns --> 9 + - <div class="hidden lg:grid lg:grid-cols-[1fr_minmax(0,1024px)_1fr] lg:gap-8 lg:items-start"> 10 + - <!-- Left section --> 11 + - <div> 12 + - <a href="/" hx-boost="true" class="flex gap-2 font-semibold italic no-underline hover:no-underline"> 13 + - {{ template "fragments/logotypeSmall" }} 14 + - </a> 15 + - </div> 16 + - 17 + - {{ $headerStyle := "text-gray-900 dark:text-gray-200 font-bold text-sm uppercase tracking-wide mb-1" }} 18 + - {{ $linkStyle := "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:underline inline-flex gap-1 items-center" }} 19 + - {{ $iconStyle := "w-4 h-4 flex-shrink-0" }} 20 + - 21 + - <!-- Center section with max-width --> 22 + - <div class="grid grid-cols-4 gap-2"> 23 + - <div class="flex flex-col gap-1"> 24 + - <div class="{{ $headerStyle }}">legal</div> 25 + - <a href="/terms" class="{{ $linkStyle }}">{{ i "file-text" $iconStyle }} terms of service</a> 26 + - <a href="/privacy" class="{{ $linkStyle }}">{{ i "shield" $iconStyle }} privacy policy</a> 27 + - </div> 28 + - 29 + - <div class="flex flex-col gap-1"> 30 + - <div class="{{ $headerStyle }}">resources</div> 31 + - <a href="https://blog.tangled.org" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ i "book-open" $iconStyle }} blog</a> 32 + - <a href="https://docs.tangled.org" class="{{ $linkStyle }}">{{ i "book" $iconStyle }} docs</a> 33 + - <a href="https://tangled.org/@tangled.org/core" class="{{ $linkStyle }}">{{ i "code" $iconStyle }} source</a> 34 + - <a href="https://tangled.org/brand" class="{{ $linkStyle }}">{{ i "paintbrush" $iconStyle }} brand</a> 35 + - </div> 36 + - 37 + - <div class="flex flex-col gap-1"> 38 + - <div class="{{ $headerStyle }}">social</div> 39 + - <a href="https://chat.tangled.org" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ i "message-circle" $iconStyle }} discord</a> 40 + - <a href="https://web.libera.chat/#tangled" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ i "hash" $iconStyle }} irc</a> 41 + - <a href="https://bsky.app/profile/tangled.org" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ template "user/fragments/bluesky" $iconStyle }} bluesky</a> 42 + - </div> 43 + - 44 + - <div class="flex flex-col gap-1"> 45 + - <div class="{{ $headerStyle }}">contact</div> 46 + - <a href="mailto:[email protected]" class="{{ $linkStyle }}">{{ i "mail" "w-4 h-4 flex-shrink-0" }} [email protected]</a> 47 + - <a href="mailto:[email protected]" class="{{ $linkStyle }}">{{ i "shield-check" "w-4 h-4 flex-shrink-0" }} [email protected]</a> 48 + - </div> 49 + - </div> 50 + - 51 + - <!-- Right section --> 52 + - <div class="text-right"> 53 + - <div class="text-xs">&copy; 2026 Tangled Labs Oy. All rights reserved.</div> 54 + - </div> 55 + - </div> 56 + - 57 + - <!-- Mobile layout: stacked --> 58 + - <div class="lg:hidden flex flex-col gap-8"> 59 + - {{ $headerStyle := "text-gray-900 dark:text-gray-200 font-bold text-xs uppercase tracking-wide mb-1" }} 60 + - {{ $linkStyle := "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:underline inline-flex gap-1 items-center" }} 61 + - {{ $iconStyle := "w-4 h-4 flex-shrink-0" }} 62 + - 63 + - <div class="mb-4 md:mb-0"> 64 + - <a href="/" hx-boost="true" class="flex gap-2 font-semibold italic no-underline hover:no-underline"> 65 + - {{ template "fragments/logotypeSmall" }} 66 + - </a> 67 + - </div> 68 + - 69 + - <div class="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-4 sm:gap-6 md:gap-2 gap-6"> 70 + - <div class="flex flex-col gap-1"> 71 + - <div class="{{ $headerStyle }}">legal</div> 72 + - <a href="/terms" class="{{ $linkStyle }}">{{ i "file-text" $iconStyle }} terms of service</a> 73 + - <a href="/privacy" class="{{ $linkStyle }}">{{ i "shield" $iconStyle }} privacy policy</a> 74 + - </div> 75 + - 76 + - <div class="flex flex-col gap-1"> 77 + - <div class="{{ $headerStyle }}">resources</div> 78 + - <a href="https://blog.tangled.org" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ i "book-open" $iconStyle }} blog</a> 79 + - <a href="https://docs.tangled.org" class="{{ $linkStyle }}">{{ i "book" $iconStyle }} docs</a> 80 + - <a href="https://tangled.org/@tangled.org/core" class="{{ $linkStyle }}">{{ i "code" $iconStyle }} source</a> 81 + - <a href="https://tangled.org/brand" class="{{ $linkStyle }}">{{ i "paintbrush" $iconStyle }} brand</a> 82 + - </div> 83 + - 84 + - <div class="flex flex-col gap-1"> 85 + - <div class="{{ $headerStyle }}">social</div> 86 + - <a href="https://chat.tangled.org" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ i "message-circle" $iconStyle }} discord</a> 87 + - <a href="https://web.libera.chat/#tangled" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ i "hash" $iconStyle }} irc</a> 88 + - <a href="https://bsky.app/profile/tangled.org" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ template "user/fragments/bluesky" $iconStyle }} bluesky</a> 89 + - </div> 90 + - 91 + - <div class="flex flex-col gap-1"> 92 + - <div class="{{ $headerStyle }}">contact</div> 93 + - <a href="mailto:[email protected]" class="{{ $linkStyle }}">{{ i "mail" "w-4 h-4 flex-shrink-0" }} [email protected]</a> 94 + - <a href="mailto:[email protected]" class="{{ $linkStyle }}">{{ i "shield-check" "w-4 h-4 flex-shrink-0" }} [email protected]</a> 95 + - </div> 96 + - </div> 97 + - 98 + - <div class="text-center"> 99 + - <div class="text-xs">&copy; 2026 Tangled Labs Oy. All rights reserved.</div> 100 + - </div> 101 + - </div> 102 + - </div> 103 + +<div class="w-full py-6 bg-white dark:bg-gray-800"> 104 + + <div class="mx-auto px-4 flex items-center justify-center gap-4 text-sm text-gray-500 dark:text-gray-400"> 105 + + <a href="https://tangled.org/@tangled.org" class="hover:text-gray-900 dark:hover:text-gray-200 hover:underline inline-flex gap-1 items-center"> 106 + + {{ i "code" "w-4 h-4" }} tangled 107 + + </a> 108 + + <span class="text-gray-300 dark:text-gray-600">|</span> 109 + + <a href="https://tangled.org/@tangled.org/core/blob/master/license" class="hover:text-gray-900 dark:hover:text-gray-200 hover:underline inline-flex gap-1 items-center"> 110 + + {{ i "file-text" "w-4 h-4" }} license 111 + + </a> 112 + </div> 113 + </div> 114 + {{ end }}
+15
patching/120-appview-custom-header-logo.patch
···
··· 1 + --- a/appview/pages/templates/fragments/logotypeSmall.html 2026-03-10 20:00:17 2 + +++ b/appview/pages/templates/fragments/logotypeSmall.html 2026-03-10 20:00:23 3 + @@ -1,9 +1,9 @@ 4 + {{ define "fragments/logotypeSmall" }} 5 + <span class="flex items-center gap-2"> 6 + - {{ template "fragments/dolly/logo" (dict "Classes" "size-8 text-black dark:text-white")}} 7 + - <span class="font-bold text-xl not-italic">tangled</span> 8 + + <img src="/static/logos/ai.svg" alt="logo" class="size-8" /> 9 + + <span class="font-bold text-xl not-italic">git</span> 10 + <span class="font-normal not-italic text-xs rounded bg-gray-100 dark:bg-gray-700 px-1"> 11 + - alpha 12 + + syu.is 13 + </span> 14 + <span> 15 + {{ end }}
+18
readme.md
···
··· 1 + # tangled 2 + 3 + git + atproto 4 + 5 + 1. start knot (knot.example.com) 6 + 2. `tangled.org` oauth login (user.bsky.social) 7 + 3. `knot.example.com` verifiyed 8 + 9 + - https://docs.tangled.org/spindles.html#self-hosting-guide 10 + 11 + ## self-hosting 12 + 13 + 1. knot, `git clone https://tangled.org/tangled.org/knot-docker` 14 + 2. appviewe, `git clone https://tangled.org/tangled.org/infra` 15 + 16 + ## lexicon 17 + 18 + `at://did:plc:6qyecktefllvenje24fcxnie/sh.tangled.knot/knot.syu.is`