repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
d77b910
parent
85ad4b8
author
Eric Bower
date
2024-01-09 19:43:00 +0000 UTC
Refactor docs (#68)

Removed all docs associated with our services and moved them to a centralized source: https://pico.sh
55 files changed,  +201, -4112
D .github/workflows/pgs.yml
+0, -27
 1@@ -1,27 +0,0 @@
 2-name: pgs-static-site
 3-on:
 4-  push:
 5-    branches:
 6-      - main
 7-jobs:
 8-  static:
 9-    runs-on: ubuntu-latest
10-    steps:
11-      - uses: actions/checkout@v3
12-      - name: Set outputs
13-        id: vars
14-        run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
15-      - uses: actions/setup-go@v4
16-        with:
17-          go-version: '1.21'
18-      - name: build pgs site
19-        run: make pgs-static pgs-site
20-      - name: publish to pgs
21-        uses: picosh/pgs-action@v3
22-        with:
23-          user: hey
24-          key: ${{ secrets.PRIVATE_KEY }}
25-          src: './public/'
26-          project: "pgs-${{ steps.vars.outputs.sha_short }}"
27-          promote: "pgs-prod"
28-          retain: "pgs-"
M Makefile
+0, -20
 1@@ -76,26 +76,6 @@ build-%:
 2 build: build-prose build-lists build-pastes build-imgs build-feeds build-pgs build-auth
 3 .PHONY: build
 4 
 5-pgs-static:
 6-	go build -o "build/pgs-static" "./cmd/pgs/static"
 7-.PHONY: pgs-static
 8-
 9-pgs-site:
10-	rm -rf public
11-	mkdir public
12-	PGS_EMAIL=hello@pico.sh PGS_DOMAIN=pgs.sh PGS_PROTOCOL=https ./build/pgs-static -out ./public
13-	cp ./pgs/public/* ./public
14-.PHONY: pgs-site
15-
16-pgs-dev: pgs-static pgs-site
17-	rsync -rv -e "ssh -p 2222" ./public/ erock@localhost:/pgs-local
18-.PHONY: pgs-dev
19-
20-pgs-deploy: pgs-static pgs-site
21-	rsync -rv ./public/ hey@pgs.sh:/pgs-local
22-	ssh hey@pgs.sh link pgs-prod pgs-local --write
23-.PHONY: pgs-site-deploy
24-
25 store-clean:
26 	WRITE=$(WRITE) go run ./cmd/scripts/clean-object-store/clean.go
27 .PHONY: store-clean
D cmd/pgs/static/main.go
+0, -17
 1@@ -1,17 +0,0 @@
 2-package main
 3-
 4-import (
 5-	"flag"
 6-
 7-	"github.com/picosh/pico/pgs"
 8-)
 9-
10-func main() {
11-	out := flag.String("out", "./public", "output folder for static assets")
12-	flag.Parse()
13-	cfg := pgs.NewConfigSite()
14-	err := pgs.GenStaticSite(*out, cfg)
15-	if err != nil {
16-		panic(err)
17-	}
18-}
M feeds/api.go
+0, -4
 1@@ -26,10 +26,6 @@ func createStaticRoutes() []shared.Route {
 2 func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
 3 	routes := []shared.Route{
 4 		shared.NewRoute("GET", "/", shared.CreatePageHandler("html/marketing.page.tmpl")),
 5-		shared.NewRoute("GET", "/ops", shared.CreatePageHandler("html/ops.page.tmpl")),
 6-		shared.NewRoute("GET", "/privacy", shared.CreatePageHandler("html/privacy.page.tmpl")),
 7-		shared.NewRoute("GET", "/help", shared.CreatePageHandler("html/help.page.tmpl")),
 8-		shared.NewRoute("GET", "/check", shared.CheckHandler),
 9 	}
10 
11 	routes = append(
D feeds/html/help.page.tmpl
+0, -152
  1@@ -1,152 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}help -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="questions and answers" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Need help?</h1>
 15-    <p>Here are some common questions on using this platform that we would like to answer.</p>
 16-</header>
 17-<main>
 18-    <section id="permission-denied">
 19-        <h2 class="text-xl">
 20-            <a href="#permission-denied" rel="nofollow noopener">#</a>
 21-            I get a permission denied when trying to SSH
 22-        </h2>
 23-        <p>
 24-            Unfortunately SHA-2 RSA keys are <strong>not</strong> currently supported.
 25-        </p>
 26-        <p>
 27-            Unfortunately, due to a shortcoming in Go’s x/crypto/ssh package, Soft Serve does
 28-            not currently support access via new SSH RSA keys: only the old SHA-1 ones will work.
 29-            Until we sort this out you’ll either need an SHA-1 RSA key or a key with another
 30-            algorithm, e.g. Ed25519. Not sure what type of keys you have? You can check with the
 31-            following:
 32-        </p>
 33-        <pre>$ find ~/.ssh/id_*.pub -exec ssh-keygen -l -f {} \;</pre>
 34-        <p>If you’re curious about the inner workings of this problem have a look at:</p>
 35-        <ul>
 36-            <li><a href="https://github.com/golang/go/issues/37278">golang/go#37278</a></li>
 37-            <li><a href="https://go-review.googlesource.com/c/crypto/+/220037">go-review</a></li>
 38-            <li><a href="https://github.com/golang/crypto/pull/197">golang/crypto#197</a></li>
 39-        </ul>
 40-    </section>
 41-
 42-    <section id="ssh-key">
 43-        <h2 class="text-xl">
 44-            <a href="#ssh-key" rel="nofollow noopener">#</a>
 45-            Generating a new SSH key
 46-        </h2>
 47-        <p>
 48-            <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent">Github reference</a>
 49-        </p>
 50-        <pre>ssh-keygen -t ed25519 -C "your_email@example.com"</pre>
 51-        <ol>
 52-            <li>When you're prompted to "Enter a file in which to save the key," press Enter. This accepts the default file location.</li>
 53-            <li>At the prompt, type a secure passphrase.</li>
 54-        </ol>
 55-    </section>
 56-
 57-    <section id="digest-interval">
 58-        <h2 class="text-xl">
 59-            <a href="#digest-interval" rel="nofollow noopener">#</a>
 60-            Digest interval options
 61-        </h2>
 62-        <ul>
 63-            <li><code>10min</code></li>
 64-            <li><code>1hour</code></li>
 65-            <li><code>6hour</code></li>
 66-            <li><code>12hour</code></li>
 67-            <li><code>1day</code></li>
 68-            <li><code>7day</code></li>
 69-            <li><code>30day</code></li>
 70-        </ul>
 71-    </section>
 72-
 73-    <section id="multiple-feeds">
 74-        <h2 class="text-xl">
 75-            <a href="#multiple-feeds" rel="nofollow noopener">#</a>
 76-            Can I create multiple email digests?
 77-        </h2>
 78-        <p>
 79-           You are free to upload as many email digests as you like,
 80-           referencing different rss feeds, emails, and digest intervals.
 81-        </p>
 82-    </section>
 83-
 84-    <section id="inline-content">
 85-        <h2 class="text-xl">
 86-            <a href="#inline-content" rel="nofollow noopener">#</a>
 87-            Inline content
 88-        </h2>
 89-        <p>
 90-            By default we attempt to render all content within a feed as HTML inside an email digest.
 91-            Sometimes users just want us to send them the links so they can click on it and read the content
 92-            on the original website.
 93-        </p>
 94-        <p>If you don't want to see all the content, simply add a variable to your post:</p>
 95-        <pre>=: inline_content false</pre>
 96-    </section>
 97-
 98-    <section id="post-update">
 99-        <h2 class="text-xl">
100-            <a href="#post-update" rel="nofollow noopener">#</a>
101-            How do I update a list?
102-        </h2>
103-        <p>
104-            Updating a list requires that you update the source document and then run the <code>scp</code>
105-            command again.  If the filename remains the same, then the list will be updated.
106-        </p>
107-    </section>
108-
109-    <section id="post-delete">
110-        <h2 class="text-xl">
111-            <a href="#post-delete" rel="nofollow noopener">#</a>
112-            How do I delete a list?
113-        </h2>
114-        <p>
115-            Because <code>scp</code> does not natively support deleting files, I didn't want to bake
116-            that behavior into my ssh server.
117-        </p>
118-
119-        <p>
120-            However, if a user wants to delete a post they can delete the contents of the file and
121-            then upload it to our server.  If the file contains 0 bytes, we will remove the post.
122-            For example, if you want to delete <code>delete.txt</code> you could:
123-        </p>
124-
125-        <pre>
126-cp /dev/null delete.txt
127-scp ./delete.txt {{.Site.Domain}}:/</pre>
128-
129-        <p>
130-            Alternatively, you can go to <code>ssh {{.Site.Domain}}</code> and select "Manage posts."
131-            Then you can highlight the post you want to delete and then press "X."  It will ask for
132-            confirmation before actually removing the list.
133-        </p>
134-    </section>
135-
136-    <section id="multiple-accounts">
137-        <h2 class="text-xl">
138-            <a href="#multiple-accounts" rel="nofollow noopener">#</a>
139-            Can I create multiple accounts?
140-        </h2>
141-        <p>
142-           Yes!  You can either a) create a new keypair and use that for authentication
143-           or b) use the same keypair and ssh into our CMS using our special username
144-           <code>ssh new@{{.Site.Domain}}</code>.
145-        </p>
146-        <p>
147-            Please note that if you use the same keypair for multiple accounts, you will need to
148-            always specify the user when logging into our CMS.
149-        </p>
150-    </section>
151-</main>
152-{{template "marketing-footer" .}}
153-{{end}}
M feeds/html/marketing-footer.partial.tmpl
+0, -7
 1@@ -2,12 +2,5 @@
 2 <footer>
 3     <hr />
 4     <p class="font-italic">Built and maintained by <a href="https://pico.sh">pico.sh</a>.</p>
 5-    <div>
 6-        <a href="/">home</a> |
 7-        <a href="https://lists.sh/spec">spec</a> |
 8-        <a href="/ops">ops</a> |
 9-        <a href="/help">help</a> |
10-        <a href="https://github.com/picosh/pico">source</a>
11-    </div>
12 </footer>
13 {{end}}
M feeds/html/marketing.page.tmpl
+4, -78
 1@@ -28,86 +28,12 @@
 2 
 3 {{define "body"}}
 4 <header class="text-center">
 5-    <h1 class="text-2xl font-bold">{{.Site.Domain}} [beta]</h1>
 6+    <h1 class="text-2xl font-bold">{{.Site.Domain}}</h1>
 7     <p class="text-lg">An rss email notification service</p>
 8-    <hr />
 9+    <div>
10+      <a href="https://pico.sh/getting-started" class="btn-link mt inline-block">GET STARTED</a>
11+    </div>
12 </header>
13 
14-<main>
15-    <section>
16-        <h2 class="text-lg font-bold">Features</h2>
17-        <ul>
18-            <li>Receive email digests for your RSS feeds</li>
19-            <li>We try to render all content within the feed as HTML (<a href="/help#inline-content">with ability to disable it</a>)</li>
20-            <li>Create 1-to-many email digests</li>
21-            <li>Set digest interval from <a href="/help#digest-interval"><code>10min</code> to <code>30day</code></a></li>
22-        </ul>
23-    </section>
24-
25-    <section>
26-        <h2 class="text-lg font-bold">Create your account with Public-Key Cryptography</h2>
27-        <p>To get started, simply ssh into our content management system:</p>
28-        <pre>ssh new@{{.Site.Domain}}</pre>
29-        <div class="text-sm font-italic note">
30-            note: <code>new</code> is a special username that will always send you to account
31-            creation.
32-        </div>
33-        <div class="text-sm font-italic note">
34-            note: getting permission denied? <a href="/help#permission-denied">read this</a>
35-        </div>
36-        <p>
37-            After that, set a username and email and then you're ready!
38-        </p>
39-    </section>
40-
41-    <section>
42-        <h2 class="text-lg font-bold">Subscribe to feeds</h2>
43-        <p>
44-            Use <a href="https://lists.sh/spec">lists.sh/spec</a> to create a txt file
45-            named <code>daily.txt</code> (as an example).
46-        </p>
47-        <p>
48-            Then add your email, the digest interval, and the rss feeds for which you
49-            want to receive email notifications.
50-        </p>
51-        <pre>=: email rss@myemail.com
52-=: digest_interval 1day
53-=> https://hey.prose.sh/rss
54-=> https://hey.lists.sh/rss
55-=> https://erock.prose.sh/rss
56-</pre>
57-        <p>
58-            Then copy the file to our server
59-        </p>
60-        <pre>scp daily.txt {{.Site.Domain}}:/</pre>
61-        <p>Using this example, we will send a daily digest of new posts to the email specified.</p>
62-    </section>
63-
64-    <section>
65-        <h2 class="text-lg font-bold">Privacy</h2>
66-        <p>
67-            We don't do anything with your email besides send an email digest.
68-            If you delete the post containing your email address, we no longer have you email address.
69-        </p>
70-        <p>
71-            Posts are also not accessible by the public and we provide no endpoints to view these posts.
72-        </p>
73-    </section>
74-
75-    <section>
76-        <h2 class="text-lg font-bold">Plain text format</h2>
77-        <p>A simple list specification that is flexible and with no frills.</p>
78-        <p><a href="https://lists.sh/spec">specification</a></p>
79-    </section>
80-
81-    <section>
82-        <h2 class="text-lg font-bold">Roadmap</h2>
83-        <ol>
84-            <li>Require user to click link in digest to continue receiving digest</li>
85-            <li>Web RSS reader?</li>
86-        </ol>
87-    </section>
88-</main>
89-
90 {{template "marketing-footer" .}}
91 {{end}}
D feeds/html/ops.page.tmpl
+0, -126
  1@@ -1,126 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}operations -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="{{.Site.Domain}} operations" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Operations</h1>
 15-    <ul>
 16-        <li><a href="/privacy">privacy</a></li>
 17-    </ul>
 18-</header>
 19-<main>
 20-    <section>
 21-        <h2 class="text-xl">Purpose</h2>
 22-        <p>
 23-            {{.Site.Domain}} exists to allow people to receive daily email digests for their rss feeds.
 24-        </p>
 25-    </section>
 26-    <section>
 27-        <h2 class="text-xl">Ethics</h2>
 28-        <p>We are committed to:</p>
 29-        <ul>
 30-            <li>No browser-based tracking of visitor behavior.</li>
 31-            <li>No attempt to identify users.</li>
 32-            <li>Never sell any user or visitor data.</li>
 33-            <li>No ads — ever.</li>
 34-        </ul>
 35-    </section>
 36-    <section>
 37-        <h2 class="text-xl">Liability</h2>
 38-        <p>
 39-            The user expressly understands and agrees that Eric Bower, the operator of this website
 40-            shall not be liable, in law or in equity, to them or to any third party for any direct,
 41-            indirect, incidental, lost profits, special, consequential, punitive or exemplary damages.
 42-        </p>
 43-    </section>
 44-    <section>
 45-        <h2 class="text-xl">Analytics</h2>
 46-        <p>
 47-            We are committed to zero browser-based tracking or trying to identify visitors.  This
 48-            means we do not try to understand the user based on cookies or IP address.  We do not
 49-            store personally identifiable information.
 50-        </p>
 51-        <p>
 52-            However, in order to provide a better service, we do have some analytics on posts.
 53-            List of metrics we track for posts:
 54-        </p>
 55-        <ul>
 56-            <li>anonymous view counts</li>
 57-        </ul>
 58-        <p>
 59-            We might also inspect the headers of HTTP requests to determine some tertiary information
 60-            about the request.  For example we might inspect the <code>User-Agent</code> or
 61-            <code>Referer</code> to filter out requests from bots.
 62-        </p>
 63-    </section>
 64-    <section>
 65-        <h2 class="text-xl">Account Terms</h2>
 66-        <p>
 67-            <ul>
 68-                <li>
 69-                    The user is responsible for all content posted and all actions performed with
 70-                    their account.
 71-                </li>
 72-                <li>
 73-                    We reserve the right to disable or delete a user's account for any reason at
 74-                    any time. We have this clause because, statistically speaking, there will be
 75-                    people trying to do something nefarious.
 76-                </li>
 77-            </ul>
 78-        </p>
 79-    </section>
 80-    <section>
 81-        <h2 class="text-xl">Service Availability</h2>
 82-        <p>
 83-         We provide the {{.Site.Domain}} service on an "as is" and "as available" basis. We do not offer
 84-         service-level agreements but do take uptime seriously.
 85-        </p>
 86-    </section>
 87-    <section>
 88-        <h2 class="text-xl">Contact and Support</h2>
 89-        <p>
 90-            Email us at <a href="mailto:support@{{.Site.Domain}}">support@{{.Site.Domain}}</a>
 91-            with any questions.
 92-        </p>
 93-    </section>
 94-    <section>
 95-        <h2 class="text-xl">Acknowledgments</h2>
 96-        <p>
 97-            {{.Site.Domain}} is built with many open source technologies.
 98-        </p>
 99-        <p>
100-            In particular we would like to thank:
101-        </p>
102-        <ul>
103-            <li>
104-                <span>The </span>
105-                <a href="https://charm.sh">charm.sh</a>
106-                <span> community</span>
107-            </li>
108-            <li>
109-                <span>The </span>
110-                <a href="https://go.dev">golang</a>
111-                <span> community</span>
112-            </li>
113-            <li>
114-                <span>The </span>
115-                <a href="https://www.postgresql.org/">postgresql</a>
116-                <span> community</span>
117-            </li>
118-            <li>
119-                <span>The </span>
120-                <a href="https://github.com/caddyserver/caddy">caddy</a>
121-                <span> community</span>
122-            </li>
123-        </ul>
124-    </section>
125-</main>
126-{{template "marketing-footer" .}}
127-{{end}}
D feeds/html/privacy.page.tmpl
+0, -60
 1@@ -1,60 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}privacy -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="{{.Site.Domain}} privacy policy" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-    <h1 class="text-2xl">Privacy</h1>
15-    <p>Details on our privacy and security approach.</p>
16-</header>
17-<main>
18-    <section>
19-        <h2 class="text-xl">Account Data</h2>
20-        <p>
21-            In order to have a functional account at {{.Site.Domain}}, we need to store
22-            your public key.  That is the only piece of information we record for a user.
23-        </p>
24-        <p>
25-            In order to email the user their RSS digest, we need to capture an email address.
26-            We do not use this email address for anything other than sending digest emails.
27-            We do not scrape posts and keep any email addresses.
28-            If you delete a post containing an email address then that email address is no longer in our system.
29-        </p>
30-        <p>
31-            Because we use public-key cryptography, our security posture is a battle-tested
32-            and proven technique for authentication.
33-        </p>
34-    </section>
35-
36-    <section>
37-        <h2 class="text-xl">Third parties</h2>
38-        <p>
39-            We have a strong commitment to never share any user data with any third-parties.
40-        </p>
41-    </section>
42-
43-    <section>
44-        <h2 class="text-xl">Service Providers</h2>
45-        <ul>
46-            <li>
47-                <span>We host our server on </span>
48-                <a href="https://www.oracle.com/cloud/">oracle cloud</a>
49-            </li>
50-        </ul>
51-    </section>
52-
53-    <section>
54-        <h2 class="text-xl">Cookies</h2>
55-        <p>
56-            We do not use any cookies, not even for account authentication.
57-        </p>
58-    </section>
59-</main>
60-{{template "marketing-footer" .}}
61-{{end}}
M feeds/public/main.css
+23, -0
 1@@ -17,6 +17,7 @@
 2 
 3 @media (prefers-color-scheme: light) {
 4   :root {
 5+    --main-hue: 250;
 6     --white: #6a737d;
 7     --code: #fff8d3;
 8     --code-border: #f0d547;
 9@@ -34,6 +35,7 @@
10 
11 @media (prefers-color-scheme: dark) {
12   :root {
13+    --main-hue: 250;
14     --white: #f2f2f2;
15     --code: #414558;
16     --code-border: #252525;
17@@ -271,6 +273,23 @@ figure {
18   margin: 0;
19 }
20 
21+.btn-link {
22+  border: 3px solid hsl(var(--main-hue), 92%, 66%);
23+  background-color: hsl(var(--main-hue), 92%, 66%);
24+  padding: 0.5rem 1rem;
25+  border-radius: 0.25rem;
26+  box-shadow: 0px 1px 2px 0px black;
27+  color: var(--white);
28+  text-decoration: none;
29+  font-weight: bold;
30+}
31+
32+.btn-link:visited,
33+.btn-link:visited:hover,
34+.btn-link:hover {
35+  color: var(--white);
36+}
37+
38 .post-date {
39   width: 130px;
40 }
41@@ -319,6 +338,10 @@ figure {
42   display: inline;
43 }
44 
45+.inline-block {
46+  display: inline-block;
47+}
48+
49 .flex {
50   display: flex;
51 }
M imgs/api.go
+0, -38
 1@@ -421,40 +421,6 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
 2 	}
 3 }
 4 
 5-func transparencyHandler(w http.ResponseWriter, r *http.Request) {
 6-	dbpool := shared.GetDB(r)
 7-	logger := shared.GetLogger(r)
 8-	cfg := shared.GetCfg(r)
 9-
10-	analytics, err := dbpool.FindSiteAnalytics(cfg.Space)
11-	if err != nil {
12-		logger.Error(err)
13-		http.Error(w, err.Error(), http.StatusInternalServerError)
14-		return
15-	}
16-
17-	ts, err := template.ParseFiles(
18-		cfg.StaticPath("html/transparency.page.tmpl"),
19-		cfg.StaticPath("html/footer.partial.tmpl"),
20-		cfg.StaticPath("html/marketing-footer.partial.tmpl"),
21-		cfg.StaticPath("html/base.layout.tmpl"),
22-	)
23-
24-	if err != nil {
25-		http.Error(w, err.Error(), http.StatusInternalServerError)
26-	}
27-
28-	data := TransparencyPageData{
29-		Site:      *cfg.GetSiteData(),
30-		Analytics: analytics,
31-	}
32-	err = ts.Execute(w, data)
33-	if err != nil {
34-		logger.Error(err)
35-		http.Error(w, err.Error(), http.StatusInternalServerError)
36-	}
37-}
38-
39 func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
40 	username := shared.GetUsernameFromRequest(r)
41 	dbpool := shared.GetDB(r)
42@@ -647,10 +613,6 @@ func createStaticRoutes() []shared.Route {
43 func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
44 	routes := []shared.Route{
45 		shared.NewRoute("GET", "/", shared.CreatePageHandler("html/marketing.page.tmpl")),
46-		shared.NewRoute("GET", "/ops", shared.CreatePageHandler("html/ops.page.tmpl")),
47-		shared.NewRoute("GET", "/privacy", shared.CreatePageHandler("html/privacy.page.tmpl")),
48-		shared.NewRoute("GET", "/help", shared.CreatePageHandler("html/help.page.tmpl")),
49-		shared.NewRoute("GET", "/transparency", transparencyHandler),
50 		shared.NewRoute("GET", "/check", shared.CheckHandler),
51 	}
52 
D imgs/html/help.page.tmpl
+0, -172
  1@@ -1,172 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}help -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="questions and answers" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Need help?</h1>
 15-    <p>Here are some common questions on using this platform that we would like to answer.</p>
 16-</header>
 17-<main>
 18-    <section id="permission-denied">
 19-        <h2 class="text-xl">
 20-            <a href="#permission-denied" rel="nofollow noopener">#</a>
 21-            I get a permission denied when trying to SSH
 22-        </h2>
 23-        <p>
 24-            Unfortunately SHA-2 RSA keys are <strong>not</strong> currently supported.
 25-        </p>
 26-        <p>
 27-            Unfortunately, due to a shortcoming in Go’s x/crypto/ssh package, we
 28-            not currently support access via new SSH RSA keys: only the old SHA-1 ones will work.
 29-            Until we sort this out you’ll either need an SHA-1 RSA key or a key with another
 30-            algorithm, e.g. Ed25519. Not sure what type of keys you have? You can check with the
 31-            following:
 32-        </p>
 33-        <pre>$ find ~/.ssh/id_*.pub -exec ssh-keygen -l -f {} \;</pre>
 34-        <p>If you’re curious about the inner workings of this problem have a look at:</p>
 35-        <ul>
 36-            <li><a href="https://github.com/golang/go/issues/37278">golang/go#37278</a></li>
 37-            <li><a href="https://go-review.googlesource.com/c/crypto/+/220037">go-review</a></li>
 38-            <li><a href="https://github.com/golang/crypto/pull/197">golang/crypto#197</a></li>
 39-        </ul>
 40-    </section>
 41-
 42-    <section id="blog-ssh-key">
 43-        <h2 class="text-xl">
 44-            <a href="#blog-ssh-key" rel="nofollow noopener">#</a>
 45-            Generating a new SSH key
 46-        </h2>
 47-        <p>
 48-            <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent">Github reference</a>
 49-        </p>
 50-        <pre>ssh-keygen -t ed25519 -C "your_email@example.com"</pre>
 51-        <ol>
 52-            <li>When you're prompted to "Enter a file in which to save the key," press Enter. This accepts the default file location.</li>
 53-            <li>At the prompt, type a secure passphrase.</li>
 54-        </ol>
 55-    </section>
 56-
 57-    <section id="web-optimized">
 58-        <h2 class="text-xl">
 59-            <a href="#web-optimized" rel="nofollow noopener">#</a>
 60-            Web optimized
 61-        </h2>
 62-        <p>
 63-            When a user uploads an image, we immediately convert it to <code>webp</code>.
 64-            Then we have an API that serves those web optimized images.
 65-        </p>
 66-    </section>
 67-
 68-    <section id="integrations">
 69-        <h2 class="text-xl">
 70-            <a href="#integrations" rel="nofollow noopener">#</a>
 71-           How does imgs integrate with other pico services?
 72-        </h2>
 73-        <p>
 74-            We allow any of our other services to upload images from those services to imgs.
 75-            For example, if you want to upload images for prose.sh all you have to do is include
 76-            your images in the <code>rsync</code> or <code>scp</code> command.
 77-        </p>
 78-        <pre>scp profile.jpg user@prose.sh:/</pre>
 79-        <p>Then when you want to reference the file, you can reference it like so:</p>
 80-        <pre>![profile pic](/profile.jpg)</pre>
 81-    </section>
 82-
 83-    <section id="file-types">
 84-        <h2 class="text-xl">
 85-            <a href="#file-types" rel="nofollow noopener">#</a>
 86-            What file types are supported?
 87-        </h2>
 88-        <ul>
 89-            <li>jpg</li>
 90-            <li>png</li>
 91-            <li>gif</li>
 92-            <li>webp</li>
 93-            <li>svg</li>
 94-        </ul>
 95-    </section>
 96-
 97-    <section id="image-manipulation">
 98-        <h2 class="text-xl">
 99-            <a href="#image-manipulation" rel="nofollow noopener">#</a>
100-            Image manipulation
101-        </h2>
102-        <p>
103-            We have an API that allows users to resize images on-the-fly.  Currently
104-            we only support downscaling.
105-        </p>
106-
107-        <pre>[!profile](/profile/x500)     # auto scale width
108-[!profile](/profile/500x500)  # scale width and height
109-[!profile](/profile/500x)     # auto scale height</pre>
110-    </section>
111-
112-    <section id="img-update">
113-        <h2 class="text-xl">
114-            <a href="#img-update" rel="nofollow noopener">#</a>
115-            How do I update a img?
116-        </h2>
117-        <p>
118-            Updating a img requires that you update the source document and then run the <code>scp</code>
119-            command again.  If the filename remains the same, then the img will be updated.
120-        </p>
121-    </section>
122-
123-    <section id="img-delete">
124-        <h2 class="text-xl">
125-            <a href="#img-delete" rel="nofollow noopener">#</a>
126-            How do I delete a img?
127-        </h2>
128-        <p>
129-            Because <code>scp</code> does not natively support deleting files, I didn't want to bake
130-            that behavior into my ssh server.
131-        </p>
132-
133-        <p>
134-            However, if a user wants to delete a img they can delete the contents of the file and
135-            then upload it to our server.  If the file contains 0 bytes, we will remove the img.
136-            For example, if you want to delete <code>delete.jpg</code> you could:
137-        </p>
138-
139-        <pre>
140-cp /dev/null delete.jpg
141-scp ./delete.jpg {{.Site.Domain}}:/</pre>
142-
143-        <p>
144-            Alternatively, you can go to <code>ssh {{.Site.Domain}}</code> and select "Manage img."
145-            Then you can highlight the img you want to delete and then press "X."  It will ask for
146-            confirmation before actually removing the img.
147-        </p>
148-    </section>
149-
150-    <section id="custom-domain">
151-        <h2 class="text-xl">
152-            <a href="#custom-domain" rel="nofollow noopener">#</a>
153-            Setup a custom domain
154-        </h2>
155-        <p>
156-            A blog can be accessed from a custom domain.
157-            HTTPS will be automatically enabled and a certificate will be retrieved
158-            from <a href="https://letsencrypt.org/">Let's Encrypt</a>. In order for this to work,
159-            2 DNS records need to be created:
160-        </p>
161-
162-        <p>CNAME for the domain to imgs (subdomains or DNS hosting with CNAME flattening) or A record</p>
163-        <pre>CNAME subdomain.yourcustomdomain.com -> imgs.sh</pre>
164-        <p>Resulting in:</p>
165-        <pre>subdomain.yourcustomdomain.com.         300     IN      CNAME   imgs.sh.</pre>
166-        <p>And a TXT record to tell Prose what blog is hosted on that domain at the subdomain entry _imgs</p>
167-        <pre>TXT _imgs.subdomain.yourcustomdomain.com -> yourusername</pre>
168-        <p>Resulting in:</p>
169-        <pre>_imgs.subdomain.yourcustomdomain.com.         300     IN      TXT     "hey"</pre>
170-    </section>
171-</main>
172-{{template "marketing-footer" .}}
173-{{end}}
M imgs/html/marketing-footer.partial.tmpl
+1, -5
 1@@ -3,11 +3,7 @@
 2     <hr />
 3     <p class="font-italic">Built and maintained by <a href="https://pico.sh">pico.sh</a>.</p>
 4     <div>
 5-        <a href="/">home</a> |
 6-        <a href="/ops">ops</a> |
 7-        <a href="/help">help</a> |
 8-        <a href="/rss">rss</a> |
 9-        <a href="https://github.com/picosh/pico">source</a>
10+        <a href="/rss">rss</a>
11     </div>
12 </footer>
13 {{end}}
M imgs/html/marketing.page.tmpl
+3, -73
 1@@ -30,80 +30,10 @@
 2 <header class="text-center">
 3     <h1 class="text-2xl font-bold">{{.Site.Domain}}</h1>
 4     <p class="text-lg">image hosting for hackers</p>
 5-    <hr />
 6+    <div>
 7+      <a href="https://pico.sh/getting-started" class="btn-link mt inline-block">GET STARTED</a>
 8+    </div>
 9 </header>
10 
11-<main>
12-    <section>
13-        <h2 class="text-lg font-bold">Examples</h2>
14-        <p><a href="https://erock.imgs.sh">feed</a></p>
15-    </section>
16-
17-    <section>
18-        <h2 class="text-lg font-bold">Features</h2>
19-        <ul>
20-            <li>Delightful terminal workflow</li>
21-            <li>Share public images from the terminal</li>
22-            <li>Seamless integration with other pico services (e.g. <a href="https://prose.sh">prose</a>)</li>
23-            <li>Images are web optimized by default</li>
24-            <li>API to modify images on-the-fly (e.g. dimensions)</li>
25-            <li>Hotlinking encouraged!</li>
26-            <li>No javascript</li>
27-            <li>No ads</li>
28-            <li>10MB max image size</li>
29-            <li>1GB max storage</li>
30-        </ul>
31-    </section>
32-
33-    <section>
34-        <h2 class="text-lg font-bold">Create your account with Public-Key Cryptography</h2>
35-        <p>We don't want your email address.</p>
36-        <p>To get started, simply ssh into our content management system:</p>
37-        <pre>ssh new@{{.Site.Domain}}</pre>
38-        <div class="text-sm font-italic note">
39-            note: <code>new</code> is a special username that will always send you to account
40-            creation, even with multiple accounts associated with your key-pair.
41-        </div>
42-        <div class="text-sm font-italic note">
43-            note: getting permission denied? <a href="/help#permission-denied">read this</a>
44-        </div>
45-        <p>
46-            After that, just set a username and you're ready to start writing! When you SSH
47-            again, use your username that you set in the CMS.
48-        </p>
49-    </section>
50-
51-    <section>
52-        <h2 class="text-lg font-bold">Publish your images with one command</h2>
53-        <p>
54-            When your image is ready to be published, copy the file to our server with a familiar
55-            command:
56-        </p>
57-        <pre>rsync my-image.jpg {{.Site.Domain}}:/</pre>
58-        <p>We'll either create or update the images for you.</p>
59-    </section>
60-
61-    <section>
62-        <h2 class="text-lg font-bold">Philosophy</h2>
63-        <p>
64-            Sharing photos is such an important part of the web.  From writing a blog to creating
65-            memes, we want to enable hackers to quickly share images while never leaving the terminal.
66-        </p>
67-        <p>
68-            We built imgs out of necessity, it's something we really wanted as part of the pico
69-            ecosystem and are excited to share it with you.
70-        </p>
71-        <p>Read more about team pico's philosophy <a href="https://pico.sh">here</a>.</p>
72-    </section>
73-
74-    <section>
75-        <h2 class="text-lg font-bold">Roadmap</h2>
76-        <ol>
77-            <li>Ability to create albums</li>
78-            <li>Device optimized images</li>
79-        </ol>
80-    </section>
81-</main>
82-
83 {{template "marketing-footer" .}}
84 {{end}}
D imgs/html/ops.page.tmpl
+0, -147
  1@@ -1,147 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}operations -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="{{.Site.Domain}} operations" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Operations</h1>
 15-    <ul>
 16-        <li><a href="/privacy">privacy</a></li>
 17-        <li><a href="/transparency">transparency</a></li>
 18-    </ul>
 19-</header>
 20-<main>
 21-    <section>
 22-        <h2 class="text-xl">Purpose</h2>
 23-        <p>
 24-            {{.Site.Domain}} exists to allow people to create and share their thoughts
 25-            without the need to set up their own server or be part of a platform
 26-            that shows ads or tracks its users.
 27-        </p>
 28-    </section>
 29-    <section>
 30-        <h2 class="text-xl">Ethics</h2>
 31-        <p>We are committed to:</p>
 32-        <ul>
 33-            <li>No browser-based tracking of visitor behavior.</li>
 34-            <li>No attempt to identify users.</li>
 35-            <li>Never sell any user or visitor data.</li>
 36-            <li>No ads — ever.</li>
 37-        </ul>
 38-    </section>
 39-    <section>
 40-        <h2 class="text-xl">Code of Content Publication</h2>
 41-        <p>
 42-            Content in {{.Site.Domain}} blogs is unfiltered and unmonitored. Users are free to publish any
 43-            combination of words and pixels except for: content of animosity or disparagement of an
 44-            individual or a group on account of a group characteristic such as race, color, national
 45-            origin, sex, disability, religion, or sexual orientation, which will be taken down
 46-            immediately.
 47-        </p>
 48-        <p>
 49-            If one notices something along those lines in a blog please let us know at
 50-            <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>.
 51-        </p>
 52-    </section>
 53-    <section>
 54-        <h2 class="text-xl">Liability</h2>
 55-        <p>
 56-            The user expressly understands and agrees that Eric Bower and Antonio Mika, the operator of this website
 57-            shall not be liable, in law or in equity, to them or to any third party for any direct,
 58-            indirect, incidental, lost profits, special, consequential, punitive or exemplary damages.
 59-        </p>
 60-    </section>
 61-    <section>
 62-        <h2 class="text-xl">Analytics</h2>
 63-        <p>
 64-            We are committed to zero browser-based tracking or trying to identify visitors.  This
 65-            means we do not try to understand the user based on cookies or IP address.  We do not
 66-            store personally identifiable information.
 67-        </p>
 68-        <p>
 69-            However, in order to provide a better service, we do have some analytics on posts.
 70-            List of metrics we track for posts:
 71-        </p>
 72-        <ul>
 73-            <li>anonymous view counts</li>
 74-        </ul>
 75-        <p>
 76-            We might also inspect the headers of HTTP requests to determine some tertiary information
 77-            about the request.  For example we might inspect the <code>User-Agent</code> or
 78-            <code>Referer</code> to filter out requests from bots.
 79-        </p>
 80-    </section>
 81-    <section>
 82-        <h2 class="text-xl">Account Terms</h2>
 83-        <p>
 84-            <ul>
 85-                <li>
 86-                    The user is responsible for all content posted and all actions performed with
 87-                    their account.
 88-                </li>
 89-                <li>
 90-                    We reserve the right to disable or delete a user's account for any reason at
 91-                    any time. We have this clause because, statistically speaking, there will be
 92-                    people trying to do something nefarious.
 93-                </li>
 94-            </ul>
 95-        </p>
 96-    </section>
 97-    <section>
 98-        <h2 class="text-xl">Service Availability</h2>
 99-        <p>
100-         We provide the {{.Site.Domain}} service on an "as is" and "as available" basis. We do not offer
101-         service-level agreements but do take uptime seriously.
102-        </p>
103-    </section>
104-    <section>
105-        <h2 class="text-xl">Contact and Support</h2>
106-        <p>
107-            Email us at <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>
108-            with any questions.
109-        </p>
110-    </section>
111-    <section>
112-        <h2 class="text-xl">Acknowledgments</h2>
113-        <p>
114-            {{.Site.Domain}} was inspired by <a href="https://mataroa.blog">Mataroa Blog</a>
115-            and <a href="https://bearblog.dev/">Bear Blog</a>.
116-        </p>
117-        <p>
118-            {{.Site.Domain}} is built with many open source technologies.
119-        </p>
120-        <p>
121-            In particular we would like to thank:
122-        </p>
123-        <ul>
124-            <li>
125-                <span>The </span>
126-                <a href="https://charm.sh">charm.sh</a>
127-                <span> community</span>
128-            </li>
129-            <li>
130-                <span>The </span>
131-                <a href="https://go.dev">golang</a>
132-                <span> community</span>
133-            </li>
134-            <li>
135-                <span>The </span>
136-                <a href="https://www.postgresql.org/">postgresql</a>
137-                <span> community</span>
138-            </li>
139-            <li>
140-                <span>The </span>
141-                <a href="https://github.com/caddyserver/caddy">caddy</a>
142-                <span> community</span>
143-            </li>
144-        </ul>
145-    </section>
146-</main>
147-{{template "marketing-footer" .}}
148-{{end}}
D imgs/html/privacy.page.tmpl
+0, -54
 1@@ -1,54 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}privacy -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="{{.Site.Domain}} privacy policy" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-    <h1 class="text-2xl">Privacy</h1>
15-    <p>Details on our privacy and security approach.</p>
16-</header>
17-<main>
18-    <section>
19-        <h2 class="text-xl">Account Data</h2>
20-        <p>
21-            In order to have a functional account at {{.Site.Domain}}, we need to store
22-            your public key.  That is the only piece of information we record for a user.
23-        </p>
24-        <p>
25-            Because we use public-key cryptography, our security posture is a battle-tested
26-            and proven technique for authentication.
27-        </p>
28-    </section>
29-
30-    <section>
31-        <h2 class="text-xl">Third parties</h2>
32-        <p>
33-            We have a strong commitment to never share any user data with any third-parties.
34-        </p>
35-    </section>
36-
37-    <section>
38-        <h2 class="text-xl">Service Providers</h2>
39-        <ul>
40-            <li>
41-                <span>We host our server on </span>
42-                <a href="https://www.oracle.com/cloud/">oracle cloud</a>
43-            </li>
44-        </ul>
45-    </section>
46-
47-    <section>
48-        <h2 class="text-xl">Cookies</h2>
49-        <p>
50-            We do not use any cookies, not even account authentication.
51-        </p>
52-    </section>
53-</main>
54-{{template "marketing-footer" .}}
55-{{end}}
D imgs/html/transparency.page.tmpl
+0, -59
 1@@ -1,59 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}transparency -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="full transparency of analytics and cost at {{.Site.Domain}}" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-    <h1 class="text-2xl">Transparency</h1>
15-    <hr />
16-</header>
17-<main>
18-    <section>
19-        <h2 class="text-xl">Analytics</h2>
20-        <p>
21-            Here are some interesting stats on usage.
22-        </p>
23-
24-        <article>
25-            <h2 class="text-lg">Total users</h2>
26-            <div>{{.Analytics.TotalUsers}}</div>
27-        </article>
28-
29-        <article>
30-            <h2 class="text-lg">New users in the last month</h2>
31-            <div>{{.Analytics.UsersLastMonth}}</div>
32-        </article>
33-
34-        <article>
35-            <h2 class="text-lg">Total images</h2>
36-            <div>{{.Analytics.TotalPosts}}</div>
37-        </article>
38-
39-        <article>
40-            <h2 class="text-lg">New images in the last month</h2>
41-            <div>{{.Analytics.PostsLastMonth}}</div>
42-        </article>
43-
44-        <article>
45-            <h2 class="text-lg">Users with at least one image</h2>
46-            <div>{{.Analytics.UsersWithPost}}</div>
47-        </article>
48-    </section>
49-
50-    <section>
51-        <h2 class="text-xl">Service maintenance costs</h2>
52-        <ul>
53-            <li>Domain name $3.25/mo</li>
54-            <li>Server $0.00/mo</li>
55-            <li>Programmer $0.00/mo</li>
56-        </ul>
57-    </section>
58-</main>
59-{{template "marketing-footer" .}}
60-{{end}}
M imgs/public/main.css
+23, -0
 1@@ -17,6 +17,7 @@
 2 
 3 @media (prefers-color-scheme: light) {
 4   :root {
 5+    --main-hue: 250;
 6     --white: #6a737d;
 7     --code: #fff8d3;
 8     --code-border: #f0d547;
 9@@ -34,6 +35,7 @@
10 
11 @media (prefers-color-scheme: dark) {
12   :root {
13+    --main-hue: 250;
14     --white: #f2f2f2;
15     --code: #414558;
16     --code-border: #252525;
17@@ -271,6 +273,23 @@ figure {
18   margin: 0;
19 }
20 
21+.btn-link {
22+  border: 3px solid hsl(var(--main-hue), 92%, 66%);
23+  background-color: hsl(var(--main-hue), 92%, 66%);
24+  padding: 0.5rem 1rem;
25+  border-radius: 0.25rem;
26+  box-shadow: 0px 1px 2px 0px black;
27+  color: var(--white);
28+  text-decoration: none;
29+  font-weight: bold;
30+}
31+
32+.btn-link:visited,
33+.btn-link:visited:hover,
34+.btn-link:hover {
35+  color: var(--white);
36+}
37+
38 .post-date {
39   width: 130px;
40 }
41@@ -319,6 +338,10 @@ figure {
42   display: inline;
43 }
44 
45+.inline-block {
46+  display: inline-block;
47+}
48+
49 .flex {
50   display: flex;
51 }
M lists/api.go
+1, -40
 1@@ -383,40 +383,6 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
 2 	}
 3 }
 4 
 5-func transparencyHandler(w http.ResponseWriter, r *http.Request) {
 6-	dbpool := shared.GetDB(r)
 7-	logger := shared.GetLogger(r)
 8-	cfg := shared.GetCfg(r)
 9-
10-	analytics, err := dbpool.FindSiteAnalytics(cfg.Space)
11-	if err != nil {
12-		logger.Error(err)
13-		http.Error(w, err.Error(), http.StatusInternalServerError)
14-		return
15-	}
16-
17-	ts, err := template.ParseFiles(
18-		cfg.StaticPath("html/transparency.page.tmpl"),
19-		cfg.StaticPath("html/footer.partial.tmpl"),
20-		cfg.StaticPath("html/marketing-footer.partial.tmpl"),
21-		cfg.StaticPath("html/base.layout.tmpl"),
22-	)
23-
24-	if err != nil {
25-		http.Error(w, err.Error(), http.StatusInternalServerError)
26-	}
27-
28-	data := TransparencyPageData{
29-		Site:      *cfg.GetSiteData(),
30-		Analytics: analytics,
31-	}
32-	err = ts.Execute(w, data)
33-	if err != nil {
34-		logger.Error(err)
35-		http.Error(w, err.Error(), http.StatusInternalServerError)
36-	}
37-}
38-
39 func readHandler(w http.ResponseWriter, r *http.Request) {
40 	dbpool := shared.GetDB(r)
41 	logger := shared.GetLogger(r)
42@@ -689,12 +655,7 @@ func createStaticRoutes() []shared.Route {
43 
44 func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
45 	routes := []shared.Route{
46-		shared.NewRoute("GET", "/", shared.CreatePageHandler("html/marketing.page.tmpl")),
47-		shared.NewRoute("GET", "/spec", shared.CreatePageHandler("html/spec.page.tmpl")),
48-		shared.NewRoute("GET", "/ops", shared.CreatePageHandler("html/ops.page.tmpl")),
49-		shared.NewRoute("GET", "/privacy", shared.CreatePageHandler("html/privacy.page.tmpl")),
50-		shared.NewRoute("GET", "/help", shared.CreatePageHandler("html/help.page.tmpl")),
51-		shared.NewRoute("GET", "/transparency", transparencyHandler),
52+		shared.NewRoute("GET", "/", readHandler),
53 		shared.NewRoute("GET", "/read", readHandler),
54 		shared.NewRoute("GET", "/check", shared.CheckHandler),
55 	}
D lists/html/help.page.tmpl
+0, -232
  1@@ -1,232 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}help -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="questions and answers" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Need help?</h1>
 15-    <p>Here are some common questions on using this platform that we would like to answer.</p>
 16-</header>
 17-<main>
 18-    <section id="permission-denied">
 19-        <h2 class="text-xl">
 20-            <a href="#permission-denied" rel="nofollow noopener">#</a>
 21-            I get a permission denied when trying to SSH
 22-        </h2>
 23-        <p>
 24-            Unfortunately SHA-2 RSA keys are <strong>not</strong> currently supported.
 25-        </p>
 26-        <p>
 27-            Unfortunately, due to a shortcoming in Go’s x/crypto/ssh package, Soft Serve does
 28-            not currently support access via new SSH RSA keys: only the old SHA-1 ones will work.
 29-            Until we sort this out you’ll either need an SHA-1 RSA key or a key with another
 30-            algorithm, e.g. Ed25519. Not sure what type of keys you have? You can check with the
 31-            following:
 32-        </p>
 33-        <pre>$ find ~/.ssh/id_*.pub -exec ssh-keygen -l -f {} \;</pre>
 34-        <p>If you’re curious about the inner workings of this problem have a look at:</p>
 35-        <ul>
 36-            <li><a href="https://github.com/golang/go/issues/37278">golang/go#37278</a></li>
 37-            <li><a href="https://go-review.googlesource.com/c/crypto/+/220037">go-review</a></li>
 38-            <li><a href="https://github.com/golang/crypto/pull/197">golang/crypto#197</a></li>
 39-        </ul>
 40-    </section>
 41-
 42-    <section id="ssh-key">
 43-        <h2 class="text-xl">
 44-            <a href="#ssh-key" rel="nofollow noopener">#</a>
 45-            Generating a new SSH key
 46-        </h2>
 47-        <p>
 48-            <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent">Github reference</a>
 49-        </p>
 50-        <pre>ssh-keygen -t ed25519 -C "your_email@example.com"</pre>
 51-        <ol>
 52-            <li>When you're prompted to "Enter a file in which to save the key," press Enter. This accepts the default file location.</li>
 53-            <li>At the prompt, type a secure passphrase.</li>
 54-        </ol>
 55-    </section>
 56-
 57-    <section id="blog-structure">
 58-        <h2 class="text-xl">
 59-            <a href="#blog-structure" rel="nofollow noopener">#</a>
 60-            What should my blog folder look like?
 61-        </h2>
 62-        <p>
 63-            Currently {{.Site.Domain}} only supports a flat folder structure.  Therefore,
 64-            <code>scp -r</code> is not permitted.  We also only allow <code>.txt</code> files to be
 65-            uploaded.
 66-        </p>
 67-        <p>
 68-            <a href="https://github.com/neurosnap/lists-blog">Here is the source to my blog on this platform</a>
 69-        </p>
 70-        <p>
 71-        Below is an example of what your blog folder should look like:
 72-        </p>
 73-            <pre>blog/
 74-first-post.txt
 75-second-post.txt
 76-third-post.txt</pre>
 77-        </p>
 78-        <p>
 79-            Underscores and hyphens are permitted and will be automatically removed from the title of the list.
 80-        </p>
 81-    </section>
 82-
 83-    <section id="post-update">
 84-        <h2 class="text-xl">
 85-            <a href="#post-update" rel="nofollow noopener">#</a>
 86-            How do I update a list?
 87-        </h2>
 88-        <p>
 89-            Updating a list requires that you update the source document and then run the <code>scp</code>
 90-            command again.  If the filename remains the same, then the list will be updated.
 91-        </p>
 92-    </section>
 93-
 94-    <section id="post-delete">
 95-        <h2 class="text-xl">
 96-            <a href="#post-delete" rel="nofollow noopener">#</a>
 97-            How do I delete a list?
 98-        </h2>
 99-        <p>
100-            Because <code>scp</code> does not natively support deleting files, I didn't want to bake
101-            that behavior into my ssh server.
102-        </p>
103-
104-        <p>
105-            However, if a user wants to delete a post they can delete the contents of the file and
106-            then upload it to our server.  If the file contains 0 bytes, we will remove the post.
107-            For example, if you want to delete <code>delete.txt</code> you could:
108-        </p>
109-
110-        <pre>
111-cp /dev/null delete.txt
112-scp ./delete.txt {{.Site.Domain}}:/</pre>
113-
114-        <p>
115-            Alternatively, you can go to <code>ssh {{.Site.Domain}}</code> and select "Manage posts."
116-            Then you can highlight the post you want to delete and then press "X."  It will ask for
117-            confirmation before actually removing the list.
118-        </p>
119-    </section>
120-
121-    <section id="blog-upload-single-file">
122-        <h2 class="text-xl">
123-            <a href="#blog-upload-single-file" rel="nofollow noopener">#</a>
124-            When I want to publish a new post, do I have to upload all posts everytime?
125-        </h2>
126-        <p>
127-            Nope!  Just <code>scp</code> the file you want to publish.  For example, if you created
128-            a new post called <code>taco-tuesday.txt</code> then you would publish it like this:
129-        </p>
130-        <pre>scp ./taco-tuesday.txt {{.Site.Domain}}:</pre>
131-    </section>
132-
133-    <section id="blog-header">
134-        <h2 class="text-xl">
135-            <a href="#blog-header" rel="nofollow noopener">#</a>
136-            How do I change my blog's name?
137-        </h2>
138-        <p>
139-            All you have to do is create a post titled <code>_header.txt</code> and add some
140-            information to the list.
141-        </p>
142-        <pre>=: title My new blog!
143-=: description My blog description!
144-=> https://xyz.com website
145-=> https://twitter.com/xyz twitter</pre>
146-        <ul>
147-            <li><code>title</code> will change your blog name</li>
148-            <li><code>description</code> will add a blurb right under your blog name (and add meta descriptions)</li>
149-            <li>The links will show up next to the <code>rss</code> link to your blog
150-        </ul>
151-    </section>
152-
153-    <section id="blog-readme">
154-        <h2 class="text-xl">
155-            <a href="#blog-readme" rel="nofollow noopener">#</a>
156-            How do I add an introduction to my blog?
157-        </h2>
158-        <p>
159-            All you have to do is create a post titled <code>_readme.txt</code> and add some
160-            information to the list.
161-        </p>
162-        <pre>=: list_type none
163-# Hi my name is Bob!
164-I like to sing. Dance. And I like to have fun fun fun!</pre>
165-        <p>
166-            Whatever is inside the <code>_readme</code> file will get rendered (as a list) right above your
167-            blog posts. Neat!
168-        </p>
169-    </section>
170-
171-    <section id="blog-layout">
172-        <h2 class="text-xl">
173-            <a href="#blog-layout" rel="nofollow noopener">#</a>
174-            How can I change the layout of my blog?
175-        </h2>
176-        <p>
177-            Inside the <code>_header.txt</code> metadata file, there's a variable <code>layout</code>
178-            option that will change the layout of your blog index page.
179-        </p>
180-        <p>Currently supported options</p>
181-        <ul>
182-            <li>default</li>
183-            <li>aside</li>
184-        </ul>
185-    </section>
186-
187-    <section id="blog-url">
188-        <h2 class="text-xl">
189-            <a href="#blog-url" rel="nofollow noopener">#</a>
190-            What is my blog URL?
191-        </h2>
192-        <pre>https://{username}.{{.Site.Domain}}</pre>
193-    </section>
194-
195-    <section id="continuous-deployment">
196-        <h2 class="text-xl">
197-            <a href="#continuous-deployment" rel="nofollow noopener">#</a>
198-            How can I automatically publish my post?
199-        </h2>
200-        <p>
201-            There is a github action that we built to make it easy to publish your blog automatically.
202-        </p>
203-        <ul>
204-            <li>
205-                <a href="https://github.com/picosh/scp-publish-action">github action repo</a>
206-            </li>
207-            <li>
208-                <a href="https://github.com/neurosnap/lists-official-blog/blob/main/.github/workflows/publish.yml">example workflow</a>
209-            </li>
210-        </ul>
211-        <p>
212-            A user also created a systemd task to automatically publish new posts.  <a href="https://github.com/neurosnap/lists.sh/discussions/24">Check out this github discussion for more details.</a>
213-        </p>
214-    </section>
215-
216-    <section id="multiple-accounts">
217-        <h2 class="text-xl">
218-            <a href="#multiple-accounts" rel="nofollow noopener">#</a>
219-            Can I create multiple accounts?
220-        </h2>
221-        <p>
222-           Yes!  You can either a) create a new keypair and use that for authentication
223-           or b) use the same keypair and ssh into our CMS using our special username
224-           <code>ssh new@{{.Site.Domain}}</code>.
225-        </p>
226-        <p>
227-            Please note that if you use the same keypair for multiple accounts, you will need to
228-            always specify the user when logging into our CMS.
229-        </p>
230-    </section>
231-</main>
232-{{template "marketing-footer" .}}
233-{{end}}
M lists/html/marketing-footer.partial.tmpl
+0, -8
 1@@ -2,13 +2,5 @@
 2 <footer>
 3     <hr />
 4     <p class="font-italic">Built and maintained by <a href="https://pico.sh">pico.sh</a>.</p>
 5-    <div>
 6-        <a href="/">home</a> |
 7-        <a href="/spec">spec</a> |
 8-        <a href="/ops">ops</a> |
 9-        <a href="/help">help</a> |
10-        <a href="/rss">rss</a> |
11-        <a href="https://github.com/picosh/pico">source</a>
12-    </div>
13 </footer>
14 {{end}}
D lists/html/marketing.page.tmpl
+0, -155
  1@@ -1,155 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}{{.Site.Domain}} -- a microblog for lists{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="a microblog for lists" />
  8-
  9-<meta property="og:type" content="website">
 10-<meta property="og:site_name" content="{{.Site.Domain}}">
 11-<meta property="og:url" content="https://{{.Site.Domain}}">
 12-<meta property="og:title" content="{{.Site.Domain}}">
 13-<meta property="og:description" content="a microblog for lists">
 14-
 15-<meta name="twitter:card" content="summary" />
 16-<meta property="twitter:url" content="https://{{.Site.Domain}}">
 17-<meta property="twitter:title" content="{{.Site.Domain}}">
 18-<meta property="twitter:description" content="a microblog for lists">
 19-<meta name="twitter:image" content="https://{{.Site.Domain}}/card.png" />
 20-<meta name="twitter:image:src" content="https://{{.Site.Domain}}/card.png" />
 21-
 22-<meta property="og:image:width" content="300" />
 23-<meta property="og:image:height" content="300" />
 24-<meta itemprop="image" content="https://{{.Site.Domain}}/card.png" />
 25-<meta property="og:image" content="https://{{.Site.Domain}}/card.png" />
 26-{{end}}
 27-
 28-{{define "attrs"}}{{end}}
 29-
 30-{{define "body"}}
 31-<header class="text-center">
 32-    <h1 class="text-2xl font-bold">{{.Site.Domain}}</h1>
 33-    <p class="text-lg">A microblog for lists</p>
 34-    <p class="text-lg"><a href="/read">discover</a> some interesting lists</p>
 35-    <hr />
 36-</header>
 37-
 38-<main>
 39-    <section>
 40-        <h2 class="text-lg font-bold">Examples</h2>
 41-        <p>
 42-            <a href="//hey.{{.Site.Domain}}">official blog</a> |
 43-            <a href="https://github.com/picosh/lists-official-blog">blog source</a>
 44-        </p>
 45-    </section>
 46-
 47-    <section>
 48-        <h2 class="text-lg font-bold">Create your account with Public-Key Cryptography</h2>
 49-        <p>We don't want your email address.</p>
 50-        <p>To get started, simply ssh into our content management system:</p>
 51-        <pre>ssh new@{{.Site.Domain}}</pre>
 52-        <div class="text-sm font-italic note">
 53-            note: <code>new</code> is a special username that will always send you to account
 54-            creation.
 55-        </div>
 56-        <div class="text-sm font-italic note">
 57-            note: getting permission denied? <a href="/help#permission-denied">read this</a>
 58-        </div>
 59-        <p>
 60-            After that, just set a username and you're ready to start writing! When you SSH
 61-            again, use your username that you set in the CMS.
 62-        </p>
 63-    </section>
 64-
 65-    <section>
 66-        <h2 class="text-lg font-bold">You control the source files</h2>
 67-        <p>Create lists using your favorite editor in plain text files.</p>
 68-        <code>~/blog/days-in-week.txt</code>
 69-        <pre>Sunday
 70-Monday
 71-Tuesday
 72-Wednesday
 73-Thursday
 74-Friday
 75-Saturday</pre>
 76-    </section>
 77-
 78-    <section>
 79-        <h2 class="text-lg font-bold">Publish your posts with one command</h2>
 80-        <p>
 81-            When your post is ready to be published, copy the file to our server with a familiar
 82-            command:
 83-        </p>
 84-        <pre>scp ~/blog/*.txt {{.Site.Domain}}:/</pre>
 85-        <p>We'll either create or update the lists for you.</p>
 86-    </section>
 87-
 88-    <section>
 89-        <h2 class="text-lg font-bold">Terminal workflow without installation</h2>
 90-        <p>
 91-            Since we are leveraging tools you already have on your computer
 92-            (<code>ssh</code> and <code>scp</code>), there is nothing to install.
 93-        </p>
 94-        <p>
 95-            This provides the convenience of a web app, but from inside your terminal!
 96-        </p>
 97-    </section>
 98-
 99-    <section>
100-        <h2 class="text-lg font-bold">Plain text format</h2>
101-        <p>A simple specification that is flexible and with no frills.</p>
102-        <p><a href="/spec">specification</a></p>
103-    </section>
104-
105-    <section>
106-        <h2 class="text-lg font-bold">Features</h2>
107-        <ul>
108-            <li>Just lists</li>
109-            <li>Looks great on any device</li>
110-            <li>Bring your own editor</li>
111-            <li>You control the source files</li>
112-            <li>Terminal workflow with no installation</li>
113-            <li>Public-key based authentication</li>
114-            <li>No ads, zero browser-based tracking</li>
115-            <li>No platform lock-in</li>
116-            <li>No javascript</li>
117-            <li>Subscriptions via RSS</li>
118-            <li>Not a platform for todos</li>
119-            <li>Minimalist design</li>
120-            <li>100% open source</li>
121-        </ul>
122-    </section>
123-
124-    <section>
125-        <h2 class="text-lg font-bold">Philosophy</h2>
126-        <p>
127-            I love writing lists.  I think restricting writing to a set of lists can really
128-            help improve clarity in thought.  The goal of this blogging platform is to make it
129-            simple to use the tools you love to write and publish lists.  There is no installation,
130-            signup is as easy as SSH'ing into our CMS, and publishing content is as easy as
131-            copying files to our server.
132-        </p>
133-        <p>
134-            Another goal of this microblog platform is to satisfy my own needs.  I like to
135-            write and share lists with people because I find it's one of the best way to disseminate
136-            knowledge.  Whether it's a list of links or a list of paragraphs, writing in lists is
137-            very satisfying and I welcome you to explore it on this site!
138-        </p>
139-        <p>
140-            Other blogging platforms support writing lists, but they don't
141-            <span class="font-bold">emphasize</span> them.  Writing lists is pretty popular
142-            on Twitter, but discoverability is terrible.  Other blogging platforms focus on prose,
143-            but there really is nothing out there catered specifically for lists ... until now.
144-        </p>
145-    </section>
146-
147-    <section>
148-        <h2 class="text-lg font-bold">Roadmap</h2>
149-        <ol>
150-            <li>Feature complete?</li>
151-        </ol>
152-    </section>
153-</main>
154-
155-{{template "marketing-footer" .}}
156-{{end}}
D lists/html/ops.page.tmpl
+0, -147
  1@@ -1,147 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}operations -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="{{.Site.Domain}} operations" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Operations</h1>
 15-    <ul>
 16-        <li><a href="/privacy">privacy</a></li>
 17-        <li><a href="/transparency">transparency</a></li>
 18-    </ul>
 19-</header>
 20-<main>
 21-    <section>
 22-        <h2 class="text-xl">Purpose</h2>
 23-        <p>
 24-            {{.Site.Domain}} exists to allow people to create and share their lists
 25-            without the need to set up their own server or be part of a platform
 26-            that shows ads or tracks its users.
 27-        </p>
 28-    </section>
 29-    <section>
 30-        <h2 class="text-xl">Ethics</h2>
 31-        <p>We are committed to:</p>
 32-        <ul>
 33-            <li>No browser-based tracking of visitor behavior.</li>
 34-            <li>No attempt to identify users.</li>
 35-            <li>Never sell any user or visitor data.</li>
 36-            <li>No ads — ever.</li>
 37-        </ul>
 38-    </section>
 39-    <section>
 40-        <h2 class="text-xl">Code of Content Publication</h2>
 41-        <p>
 42-            Content in {{.Site.Domain}} blogs is unfiltered and unmonitored. Users are free to publish any
 43-            combination of words and pixels except for: content of animosity or disparagement of an
 44-            individual or a group on account of a group characteristic such as race, color, national
 45-            origin, sex, disability, religion, or sexual orientation, which will be taken down
 46-            immediately.
 47-        </p>
 48-        <p>
 49-            If one notices something along those lines in a blog please let us know at
 50-            <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>.
 51-        </p>
 52-    </section>
 53-    <section>
 54-        <h2 class="text-xl">Liability</h2>
 55-        <p>
 56-            The user expressly understands and agrees that Eric Bower, the operator of this website
 57-            shall not be liable, in law or in equity, to them or to any third party for any direct,
 58-            indirect, incidental, lost profits, special, consequential, punitive or exemplary damages.
 59-        </p>
 60-    </section>
 61-    <section>
 62-        <h2 class="text-xl">Analytics</h2>
 63-        <p>
 64-            We are committed to zero browser-based tracking or trying to identify visitors.  This
 65-            means we do not try to understand the user based on cookies or IP address.  We do not
 66-            store personally identifiable information.
 67-        </p>
 68-        <p>
 69-            However, in order to provide a better service, we do have some analytics on posts.
 70-            List of metrics we track for posts:
 71-        </p>
 72-        <ul>
 73-            <li>anonymous view counts</li>
 74-        </ul>
 75-        <p>
 76-            We might also inspect the headers of HTTP requests to determine some tertiary information
 77-            about the request.  For example we might inspect the <code>User-Agent</code> or
 78-            <code>Referer</code> to filter out requests from bots.
 79-        </p>
 80-    </section>
 81-    <section>
 82-        <h2 class="text-xl">Account Terms</h2>
 83-        <p>
 84-            <ul>
 85-                <li>
 86-                    The user is responsible for all content posted and all actions performed with
 87-                    their account.
 88-                </li>
 89-                <li>
 90-                    We reserve the right to disable or delete a user's account for any reason at
 91-                    any time. We have this clause because, statistically speaking, there will be
 92-                    people trying to do something nefarious.
 93-                </li>
 94-            </ul>
 95-        </p>
 96-    </section>
 97-    <section>
 98-        <h2 class="text-xl">Service Availability</h2>
 99-        <p>
100-         We provide the {{.Site.Domain}} service on an "as is" and "as available" basis. We do not offer
101-         service-level agreements but do take uptime seriously.
102-        </p>
103-    </section>
104-    <section>
105-        <h2 class="text-xl">Contact and Support</h2>
106-        <p>
107-            Email us at <a href="mailto:support@{{.Site.Domain}}">support@{{.Site.Domain}}</a>
108-            with any questions.
109-        </p>
110-    </section>
111-    <section>
112-        <h2 class="text-xl">Acknowledgments</h2>
113-        <p>
114-            {{.Site.Domain}} was inspired by <a href="https://mataroa.blog">Mataroa Blog</a>
115-            and <a href="https://bearblog.dev/">Bear Blog</a>.
116-        </p>
117-        <p>
118-            {{.Site.Domain}} is built with many open source technologies.
119-        </p>
120-        <p>
121-            In particular we would like to thank:
122-        </p>
123-        <ul>
124-            <li>
125-                <span>The </span>
126-                <a href="https://charm.sh">charm.sh</a>
127-                <span> community</span>
128-            </li>
129-            <li>
130-                <span>The </span>
131-                <a href="https://go.dev">golang</a>
132-                <span> community</span>
133-            </li>
134-            <li>
135-                <span>The </span>
136-                <a href="https://www.postgresql.org/">postgresql</a>
137-                <span> community</span>
138-            </li>
139-            <li>
140-                <span>The </span>
141-                <a href="https://github.com/caddyserver/caddy">caddy</a>
142-                <span> community</span>
143-            </li>
144-        </ul>
145-    </section>
146-</main>
147-{{template "marketing-footer" .}}
148-{{end}}
D lists/html/privacy.page.tmpl
+0, -54
 1@@ -1,54 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}privacy -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="{{.Site.Domain}} privacy policy" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-    <h1 class="text-2xl">Privacy</h1>
15-    <p>Details on our privacy and security approach.</p>
16-</header>
17-<main>
18-    <section>
19-        <h2 class="text-xl">Account Data</h2>
20-        <p>
21-            In order to have a functional account at {{.Site.Domain}}, we need to store
22-            your public key.  That is the only piece of information we record for a user.
23-        </p>
24-        <p>
25-            Because we use public-key cryptography, our security posture is a battle-tested
26-            and proven technique for authentication.
27-        </p>
28-    </section>
29-
30-    <section>
31-        <h2 class="text-xl">Third parties</h2>
32-        <p>
33-            We have a strong commitment to never share any user data with any third-parties.
34-        </p>
35-    </section>
36-
37-    <section>
38-        <h2 class="text-xl">Service Providers</h2>
39-        <ul>
40-            <li>
41-                <span>We host our server on </span>
42-                <a href="https://www.oracle.com/cloud/">oracle cloud</a>
43-            </li>
44-        </ul>
45-    </section>
46-
47-    <section>
48-        <h2 class="text-xl">Cookies</h2>
49-        <p>
50-            We do not use any cookies, not even account authentication.
51-        </p>
52-    </section>
53-</main>
54-{{template "marketing-footer" .}}
55-{{end}}
M lists/html/read.page.tmpl
+5, -2
 1@@ -10,8 +10,11 @@
 2 
 3 {{define "body"}}
 4 <header class="text-center">
 5-    <h1 class="text-2xl font-bold">read</h1>
 6-    <p class="text-lg">recently updated lists</p>
 7+    <h1 class="text-2xl font-bold">lists.sh</h1>
 8+    <p class="text-lg">A microblog for lists</p>
 9+    <div>
10+      <a href="https://pico.sh/getting-started" class="btn-link mt inline-block">GET STARTED</a>
11+    </div>
12     <hr />
13 </header>
14 <main>
D lists/html/spec.page.tmpl
+0, -224
  1@@ -1,224 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}specification -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="a specification for lists" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Plain text list</h1>
 15-    <h2 class="text-xl">Speculative specification</h2>
 16-    <dl>
 17-        <dt>Version</dt>
 18-        <dd>2022.08.05.dev</dd>
 19-
 20-        <dt>Status</dt>
 21-        <dd>Draft</dd>
 22-
 23-        <dt>Author</dt>
 24-        <dd>Eric Bower</dd>
 25-    </dl>
 26-</header>
 27-<main>
 28-    <section id="overview">
 29-        <p>
 30-            The goal of this specification is to understand how we render plain text lists.
 31-            The overall design of this format is to be easy to parse and render.
 32-        </p>
 33-
 34-        <p>
 35-            The format is line-oriented, and a satisfactory rendering can be achieved with a single
 36-            pass of a document, processing each line independently. As per gopher, links can only be
 37-            displayed one per line, encouraging neat, list-like structure.
 38-        </p>
 39-
 40-        <p>
 41-            Feedback on any part of this is extremely welcome, please email
 42-            <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>.
 43-        </p>
 44-
 45-        <p>
 46-            The source code for our parser can be found
 47-            <a href="https://github.com/picosh/pico/tree/main/item/lists/parser.go">here</a>.
 48-        </p>
 49-    </section>
 50-
 51-    <section id="parameters">
 52-        <h2 class="text-xl">
 53-            <a href="#parameters" rel="nofollow noopener">#</a>
 54-            Parameters
 55-        </h2>
 56-        <p>
 57-            As a subtype of the top-level media type "text", "text/plain" inherits the "charset"
 58-            parameter defined in <a href="https://datatracker.ietf.org/doc/html/rfc2046#section-4.1">RFC 2046</a>.
 59-            The default value of "charset" is "UTF-8" for "text" content.
 60-        </p>
 61-    </section>
 62-
 63-    <section id="line-orientation">
 64-        <h2 class="text-xl">
 65-            <a href="#line-orientation" rel="nofollow noopener">#</a>
 66-            Line orientation
 67-        </h2>
 68-        <p>
 69-            As mentioned, the text format is line-oriented. Each line of a document has a single
 70-            "line type". It is possible to unambiguously determine a line's type purely by
 71-            inspecting its first (3) characters. A line's type determines the manner in which it
 72-            should be presented to the user. Any details of presentation or rendering associated
 73-            with a particular line type are strictly limited in scope to that individual line.
 74-        </p>
 75-    </section>
 76-
 77-    <section id="file-extensions">
 78-        <h2 class="text-xl">
 79-            <a href="#file-extensions" rel="nofollow noopener">#</a>
 80-            File extension
 81-        </h2>
 82-        <p>
 83-            {{.Site.Domain}} only supports the <code>.txt</code> file extension and will
 84-            ignore all other file extensions.
 85-        </p>
 86-    </section>
 87-
 88-    <section id="list-item">
 89-        <h2 class="text-xl">
 90-            <a href="#list-item" rel="nofollow noopener">#</a>
 91-            List item
 92-        </h2>
 93-        <p>
 94-            List items are separated by newline characters <code>\n</code>.
 95-            Each list item is on its own line.  A list item does not require any special formatting.
 96-            A list item can contain as much text as it wants.  We encourage soft wrapping for readability
 97-            in your editor of choice.  Hard wrapping is not permitted as it will create a new list item.
 98-        </p>
 99-        <p>
100-            Empty lines will be completely removed and not rendered to the end user.
101-        </p>
102-    </section>
103-
104-    <section id="hyperlinks">
105-        <h2 class="text-xl">
106-            <a href="#hyperlinks" rel="nofollow noopener">#</a>
107-            Hyperlinks
108-        </h2>
109-        <p>
110-            Hyperlinks are denoted by the prefix <code>=></code>.  The following text should then be
111-            the hyperlink.
112-        </p>
113-        <pre>=> https://{{.Site.Domain}}</pre>
114-        <p>Optionally you can supply the hyperlink text immediately following the link.</p>
115-        <pre>=> https://{{.Site.Domain}} microblog for lists</pre>
116-    </section>
117-
118-    <section id="nested-lists">
119-        <h2 class="text-xl">
120-            <a href="#nested-lists" rel="nofollow noopener">#</a>
121-            Nested lists
122-        </h2>
123-        <p>
124-            Users can create nested lists.  Tabbing a list will nest it under the list item
125-            directly above it.  Both tab character <code>\t</code> or whitespace as tabs are permitted.
126-            Do not mix tabs and spaces because the nesting will yield unexpected results.
127-        </p>
128-        <pre>first item
129-    second item
130-        third item
131-last item</pre>
132-    </section>
133-
134-    <section id="images">
135-        <h2 class="text-xl">
136-            <a href="#images" rel="nofollow noopener">#</a>
137-            Images
138-        </h2>
139-        <p>
140-            List items can be represented as images by prefixing the line with <code>=<</code>.
141-        </p>
142-        <pre>=< https://i.imgur.com/iXMNUN5.jpg</pre>
143-        <p>Optionally you can supply the image alt text immediately following the link.</p>
144-        <pre>=< https://i.imgur.com/iXMNUN5.jpg I use arch, btw</pre>
145-    </section>
146-
147-    <section id="headers">
148-        <h2 class="text-xl">
149-            <a href="#headers" rel="nofollow noopener">#</a>
150-            Headers
151-        </h2>
152-        <p>
153-            List items can be represented as headers.  We support two headers currently.  Headers
154-            will end the previous list and then create a new one after it.  This allows a single
155-            document to contain multiple lists.
156-        </p>
157-        <pre># Header One
158-## Header Two</pre>
159-    </section>
160-
161-    <section id="blockquotes">
162-        <h2 class="text-xl">
163-            <a href="#blockquotes" rel="nofollow noopener">#</a>
164-            Blockquotes
165-        </h2>
166-        <p>
167-            List items can be represented as blockquotes.
168-        </p>
169-        <pre>> This is a blockquote.</pre>
170-    </section>
171-
172-    <section id="preformatted">
173-        <h2 class="text-xl">
174-            <a href="#preformatted" rel="nofollow noopener">#</a>
175-            Preformatted
176-        </h2>
177-        <p>
178-            List items can be represented as preformatted text where newline characters are not
179-            considered part of new list items.  They can be represented by prefixing the line with
180-            <code>```</code>.
181-        </p>
182-        <pre>```
183-#!/usr/bin/env bash
184-
185-set -x
186-
187-echo "this is a preformatted list item!
188-```</pre>
189-        <p>
190-            You must also close the preformatted text with another <code>```</code> on its own line. The
191-            next example with NOT work.
192-        </p>
193-        <pre>```
194-#!/usr/bin/env bash
195-
196-echo "This will not render properly"```</pre>
197-    </section>
198-
199-    <section id="variables">
200-        <h2 class="text-xl">
201-            <a href="#variables" rel="nofollow noopener">#</a>
202-            Variables
203-        </h2>
204-        <p>
205-            Variables allow us to store metadata within our system.  Variables are list items with
206-            key value pairs denoted by <code>=:</code> followed by the key, a whitespace character,
207-            and then the value.
208-        </p>
209-        <pre>=: publish_at 2022-04-20</pre>
210-        <p>These variables will not be rendered to the user inside the list.</p>
211-        <h3 class="text-lg">List of available variables:</h3>
212-        <ul>
213-            <li><code>title</code> (custom title not dependent on filename)</li>
214-            <li><code>description</code> (what is the purpose of this list?)</li>
215-            <li><code>publish_at</code> (format must be <code>YYYY-MM-DD</code>)</li>
216-            <li><code>tags</code> (format must be comma delimited: <code>feature, announcement</code>)</li>
217-            <li>
218-                <code>list_type</code> (customize bullets; value gets sent directly to css property
219-                <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type">list-style-type</a>)
220-            </li>
221-        </ul>
222-    </section>
223-</main>
224-{{template "marketing-footer" .}}
225-{{end}}
D lists/html/transparency.page.tmpl
+0, -59
 1@@ -1,59 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}transparency -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="full transparency of analytics and cost at {{.Site.Domain}}" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-    <h1 class="text-2xl">Transparency</h1>
15-    <hr />
16-</header>
17-<main>
18-    <section>
19-        <h2 class="text-xl">Analytics</h2>
20-        <p>
21-            Here are some interesting stats on usage.
22-        </p>
23-
24-        <article>
25-            <h2 class="text-lg">Total users</h2>
26-            <div>{{.Analytics.TotalUsers}}</div>
27-        </article>
28-
29-        <article>
30-            <h2 class="text-lg">New users in the last month</h2>
31-            <div>{{.Analytics.UsersLastMonth}}</div>
32-        </article>
33-
34-        <article>
35-            <h2 class="text-lg">Total posts</h2>
36-            <div>{{.Analytics.TotalPosts}}</div>
37-        </article>
38-
39-        <article>
40-            <h2 class="text-lg">New posts in the last month</h2>
41-            <div>{{.Analytics.PostsLastMonth}}</div>
42-        </article>
43-
44-        <article>
45-            <h2 class="text-lg">Users with at least one post</h2>
46-            <div>{{.Analytics.UsersWithPost}}</div>
47-        </article>
48-    </section>
49-
50-    <section>
51-        <h2 class="text-xl">Service maintenance costs</h2>
52-        <ul>
53-            <li>Domain name $3.25/mo</li>
54-            <li>Server $0.00/mo</li>
55-            <li>Programmer $0.00/mo</li>
56-        </ul>
57-    </section>
58-</main>
59-{{template "marketing-footer" .}}
60-{{end}}
M lists/public/main.css
+23, -0
 1@@ -17,6 +17,7 @@
 2 
 3 @media (prefers-color-scheme: light) {
 4   :root {
 5+    --main-hue: 250;
 6     --white: #6a737d;
 7     --code: #fff8d3;
 8     --code-border: #f0d547;
 9@@ -34,6 +35,7 @@
10 
11 @media (prefers-color-scheme: dark) {
12   :root {
13+    --main-hue: 250;
14     --white: #f2f2f2;
15     --code: #414558;
16     --code-border: #252525;
17@@ -271,6 +273,23 @@ figure {
18   margin: 0;
19 }
20 
21+.btn-link {
22+  border: 3px solid hsl(var(--main-hue), 92%, 66%);
23+  background-color: hsl(var(--main-hue), 92%, 66%);
24+  padding: 0.5rem 1rem;
25+  border-radius: 0.25rem;
26+  box-shadow: 0px 1px 2px 0px black;
27+  color: var(--white);
28+  text-decoration: none;
29+  font-weight: bold;
30+}
31+
32+.btn-link:visited,
33+.btn-link:visited:hover,
34+.btn-link:hover {
35+  color: var(--white);
36+}
37+
38 .post-date {
39   width: 130px;
40 }
41@@ -319,6 +338,10 @@ figure {
42   display: inline;
43 }
44 
45+.inline-block {
46+  display: inline-block;
47+}
48+
49 .flex {
50   display: flex;
51 }
M pastes/api.go
+0, -39
 1@@ -274,40 +274,6 @@ func postHandlerRaw(w http.ResponseWriter, r *http.Request) {
 2 	}
 3 }
 4 
 5-func transparencyHandler(w http.ResponseWriter, r *http.Request) {
 6-	dbpool := shared.GetDB(r)
 7-	logger := shared.GetLogger(r)
 8-	cfg := shared.GetCfg(r)
 9-
10-	analytics, err := dbpool.FindSiteAnalytics(cfg.Space)
11-	if err != nil {
12-		logger.Error(err)
13-		http.Error(w, err.Error(), http.StatusInternalServerError)
14-		return
15-	}
16-
17-	ts, err := template.ParseFiles(
18-		cfg.StaticPath("html/transparency.page.tmpl"),
19-		cfg.StaticPath("html/footer.partial.tmpl"),
20-		cfg.StaticPath("html/marketing-footer.partial.tmpl"),
21-		cfg.StaticPath("html/base.layout.tmpl"),
22-	)
23-
24-	if err != nil {
25-		http.Error(w, err.Error(), http.StatusInternalServerError)
26-	}
27-
28-	data := TransparencyPageData{
29-		Site:      *cfg.GetSiteData(),
30-		Analytics: analytics,
31-	}
32-	err = ts.Execute(w, data)
33-	if err != nil {
34-		logger.Error(err)
35-		http.Error(w, err.Error(), http.StatusInternalServerError)
36-	}
37-}
38-
39 func serveFile(file string, contentType string) http.HandlerFunc {
40 	return func(w http.ResponseWriter, r *http.Request) {
41 		logger := shared.GetLogger(r)
42@@ -345,11 +311,6 @@ func createStaticRoutes() []shared.Route {
43 func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
44 	routes := []shared.Route{
45 		shared.NewRoute("GET", "/", shared.CreatePageHandler("html/marketing.page.tmpl")),
46-		shared.NewRoute("GET", "/spec", shared.CreatePageHandler("html/spec.page.tmpl")),
47-		shared.NewRoute("GET", "/ops", shared.CreatePageHandler("html/ops.page.tmpl")),
48-		shared.NewRoute("GET", "/privacy", shared.CreatePageHandler("html/privacy.page.tmpl")),
49-		shared.NewRoute("GET", "/help", shared.CreatePageHandler("html/help.page.tmpl")),
50-		shared.NewRoute("GET", "/transparency", transparencyHandler),
51 		shared.NewRoute("GET", "/check", shared.CheckHandler),
52 	}
53 
D pastes/html/help.page.tmpl
+0, -154
  1@@ -1,154 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}help -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="questions and answers" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Need help?</h1>
 15-    <p>Here are some common questions on using this platform that we would like to answer.</p>
 16-</header>
 17-<main>
 18-    <section id="permission-denied">
 19-        <h2 class="text-xl">
 20-            <a href="#permission-denied" rel="nofollow noopener">#</a>
 21-            I get a permission denied when trying to SSH
 22-        </h2>
 23-        <p>
 24-            Unfortunately SHA-2 RSA keys are <strong>not</strong> currently supported.
 25-        </p>
 26-        <p>
 27-            Unfortunately, due to a shortcoming in Go’s x/crypto/ssh package, we do
 28-            not currently support access via new SSH RSA keys: only the old SHA-1 ones will work.
 29-            Until we sort this out you’ll either need an SHA-1 RSA key or a key with another
 30-            algorithm, e.g. Ed25519. Not sure what type of keys you have? You can check with the
 31-            following:
 32-        </p>
 33-        <pre>$ find ~/.ssh/id_*.pub -exec ssh-keygen -l -f {} \;</pre>
 34-        <p>If you’re curious about the inner workings of this problem have a look at:</p>
 35-        <ul>
 36-            <li><a href="https://github.com/golang/go/issues/37278">golang/go#37278</a></li>
 37-            <li><a href="https://go-review.googlesource.com/c/crypto/+/220037">go-review</a></li>
 38-            <li><a href="https://github.com/golang/crypto/pull/197">golang/crypto#197</a></li>
 39-        </ul>
 40-    </section>
 41-
 42-    <section id="ssh-key">
 43-        <h2 class="text-xl">
 44-            <a href="#ssh-key" rel="nofollow noopener">#</a>
 45-            Generating a new SSH key
 46-        </h2>
 47-        <p>
 48-            <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent">Github reference</a>
 49-        </p>
 50-        <pre>ssh-keygen -t ed25519 -C "your_email@example.com"</pre>
 51-        <ol>
 52-            <li>When you're prompted to "Enter a file in which to save the key," press Enter. This accepts the default file location.</li>
 53-            <li>At the prompt, type a secure passphrase.</li>
 54-        </ol>
 55-    </section>
 56-
 57-    <section id="paste-update">
 58-        <h2 class="text-xl">
 59-            <a href="#paste-update" rel="nofollow noopener">#</a>
 60-            How do I update a paste?
 61-        </h2>
 62-        <p>
 63-            Updating a paste requires that you update the source document and then run the <code>scp</code>
 64-            command again.  If the filename remains the same, then the paste will be updated.
 65-        </p>
 66-    </section>
 67-
 68-    <section id="paste-delete">
 69-        <h2 class="text-xl">
 70-            <a href="#paste-delete" rel="nofollow noopener">#</a>
 71-            How do I delete a paste?
 72-        </h2>
 73-        <p>
 74-            Because <code>scp</code> does not natively support deleting files, I didn't want to bake
 75-            that behavior into my ssh server.
 76-        </p>
 77-
 78-        <p>
 79-            However, if a user wants to delete a paste they can delete the contents of the file and
 80-            then upload it to our server.  If the file contains 0 bytes, we will remove the post.
 81-            For example, if you want to delete <code>delete.txt</code> you could:
 82-        </p>
 83-
 84-        <pre>
 85-cp /dev/null delete.txt
 86-scp ./delete.txt {{.Site.Domain}}:/</pre>
 87-
 88-        <p>
 89-            Alternatively, you can go to <code>ssh {{.Site.Domain}}</code> and select "Manage posts."
 90-            Then you can highlight the paste you want to delete and then press "X."  It will ask for
 91-            confirmation before actually removing the paste.
 92-        </p>
 93-    </section>
 94-
 95-    <section id="multiple-accounts">
 96-        <h2 class="text-xl">
 97-            <a href="#multiple-accounts" rel="nofollow noopener">#</a>
 98-            Can I create multiple accounts?
 99-        </h2>
100-        <p>
101-           Yes!  You can either a) create a new keypair and use that for authentication
102-           or b) use the same keypair and ssh into our CMS using our special username
103-           <code>ssh new@{{.Site.Domain}}</code>.
104-        </p>
105-        <p>
106-            Please note that if you use the same keypair for multiple accounts, you will need to
107-            always specify the user when logging into our CMS.
108-        </p>
109-    </section>
110-
111-    <section id="pipe">
112-        <h2 class="text-xl">
113-          <a href="#pipe" rel="nofollow noopener">#</a>
114-          Can I pipe my paste?
115-        </h2>
116-        <p>
117-           Yes!
118-        </p>
119-        <pre>echo "foobar" | ssh pastes.sh</pre>
120-        <pre>echo "foobar" | ssh pastes.sh FILENAME</pre>
121-        <pre># if the tty warning annoys you
122-echo "foobar" | ssh -T pastes.sh</pre>
123-    </section>
124-
125-    <section id="expires">
126-        <h2 class="text-xl">
127-          <a href="#expires" rel="nofollow noopener">#</a>
128-          Can I set the expiration date to a paste?
129-        </h2>
130-        <p>
131-          Yes.  The default expiration date for a paste is 90 days.
132-          We do allow the user to set the paste to never expire.
133-          We also allow custom <a href="https://pkg.go.dev/time#ParseDuration">duration</a>
134-          or <a href="https://pkg.go.dev/github.com/araddon/dateparse#ParseStrict">timestamp</a>.
135-        </p>
136-        <pre>echo "foobar" | ssh pastes.sh FILENAME expires=false</pre>
137-        <pre>echo "foobar" | ssh pastes.sh FILENAME expires=2023-12-12</pre>
138-        <pre>echo "foobar" | ssh pastes.sh FILENAME expires=1h</pre>
139-    </section>
140-
141-    <section id="unlisted">
142-        <h2 class="text-xl">
143-          <a href="#unlisted" rel="nofollow noopener">#</a>
144-          Can I hide pastes from my landing page?
145-        </h2>
146-        <p>
147-          Yes.  Unlisted in this context means it does not show up on
148-          your user landing page where we show all of your pastes.
149-          In this case, yes, you can "hide" it using a pipe command.
150-        </p>
151-        <pre>echo "foobar" | ssh pastes.sh FILENAME hidden=true</pre>
152-    </section>
153-</main>
154-{{template "marketing-footer" .}}
155-{{end}}
M pastes/html/marketing-footer.partial.tmpl
+0, -6
 1@@ -2,11 +2,5 @@
 2 <footer>
 3     <hr />
 4     <p class="font-italic">Built and maintained by <a href="https://pico.sh">pico.sh</a>.</p>
 5-    <div>
 6-        <a href="/">home</a> |
 7-        <a href="/ops">ops</a> |
 8-        <a href="/help">help</a> |
 9-        <a href="https://github.com/picosh/pico">source</a>
10-    </div>
11 </footer>
12 {{end}}
M pastes/html/marketing.page.tmpl
+3, -99
  1@@ -30,106 +30,10 @@
  2 <header class="text-center">
  3     <h1 class="text-2xl font-bold">{{.Site.Domain}}</h1>
  4     <p class="text-lg">a pastebin for hackers</p>
  5-    <hr />
  6+    <div>
  7+      <a href="https://pico.sh/getting-started" class="btn-link mt inline-block">GET STARTED</a>
  8+    </div>
  9 </header>
 10 
 11-<main>
 12-    <section>
 13-        <h2 class="text-lg font-bold">Create your account with Public-Key Cryptography</h2>
 14-        <p>We don't want your email address.</p>
 15-        <p>To get started, simply ssh into our content management system:</p>
 16-        <pre>ssh new@{{.Site.Domain}}</pre>
 17-        <div class="text-sm font-italic note">
 18-            note: <code>new</code> is a special username that will always send you to account
 19-            creation, even with multiple accounts associated with your key-pair.
 20-        </div>
 21-        <div class="text-sm font-italic note">
 22-            note: getting permission denied? <a href="/help#permission-denied">read this</a>
 23-        </div>
 24-        <p>
 25-            After that, just set a username and you're ready to start writing! When you SSH
 26-            again, use your username that you set in the CMS.
 27-        </p>
 28-    </section>
 29-
 30-    <section>
 31-        <h2 class="text-lg font-bold">Publish your pastes with one command</h2>
 32-        <p>
 33-            When your paste is ready to be published, copy the file to our server with a familiar
 34-            command:
 35-        </p>
 36-        <pre>scp ./changes.patch {{.Site.Domain}}:/</pre>
 37-        <p>We'll return the URL back to you so you never have to leave the terminal!</p>
 38-    </section>
 39-
 40-    <section>
 41-        <h2 class="text-lg font-bold">Pipe Support</h2>
 42-
 43-        <pre>echo "foobar" | ssh pastes.sh</pre>
 44-        <pre>echo "foobar" | ssh pastes.sh FILENAME</pre>
 45-        <pre># if the tty warning annoys you
 46-echo "foobar" | ssh -T pastes.sh</pre>
 47-    </section>
 48-
 49-    <section>
 50-        <h2 class="text-lg font-bold">Terminal workflow without installation</h2>
 51-        <p>
 52-            Since we are leveraging tools you already have on your computer
 53-            (<code>ssh</code>, <code>scp</code>, and <code>rsync</code>), there is nothing to install.
 54-        </p>
 55-        <p>
 56-            This provides the convenience of a web app, but from inside your terminal!
 57-        </p>
 58-    </section>
 59-
 60-    <section>
 61-        <h2 class="text-lg font-bold">Upload using <code>rsync</code></h2>
 62-        <pre>rsync hello-world.diff {{.Site.Domain}}:/</pre>
 63-    </section>
 64-
 65-    <section>
 66-        <h2 class="text-lg font-bold">Use <code>sftp</code> to manage your pastes</h2>
 67-        <p>
 68-          FTP is back in style.  List, upload, and delete pastes via <code>sftp</code>:
 69-        </p>
 70-        <pre>sftp {{.Site.Domain}}
 71-sftp> ls
 72-hello-world.diff
 73-sftp> rm hello-world.diff
 74-sftp> put hello-world.diff</pre>
 75-    </section>
 76-
 77-    <section>
 78-        <h2 class="text-lg font-bold">Features</h2>
 79-        <ul>
 80-            <li>Pastes last <strong>90 days</strong> by default</li>
 81-            <li><a href="/help#expires">Ability to set custom expiration</a></li>
 82-            <li><a href="/help#unlisted">Ability to "hide" pastes</a></li>
 83-            <li>Bring your own editor</li>
 84-            <li>Terminal workflow with no installation</li>
 85-            <li>Use sftp to manage pastes</li>
 86-            <li>Public-key based authentication</li>
 87-            <li>No ads, zero tracking</li>
 88-            <li>No javascript</li>
 89-            <li>Minimalist design</li>
 90-            <li>100% open source</li>
 91-        </ul>
 92-    </section>
 93-
 94-    <section>
 95-        <h2 class="text-lg font-bold">Read the docs</h2>
 96-        <p>
 97-            <a href="/help">docs</a>
 98-        </p>
 99-    </section>
100-
101-    <section>
102-        <h2 class="text-lg font-bold">Roadmap</h2>
103-        <ol>
104-          <li>Mobile support?</li>
105-        </ol>
106-    </section>
107-</main>
108-
109 {{template "marketing-footer" .}}
110 {{end}}
D pastes/html/ops.page.tmpl
+0, -126
  1@@ -1,126 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}operations -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="{{.Site.Domain}} operations" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Operations</h1>
 15-    <ul>
 16-        <li><a href="/privacy">privacy</a></li>
 17-        <li><a href="/transparency">transparency</a></li>
 18-    </ul>
 19-</header>
 20-<main>
 21-    <section>
 22-        <h2 class="text-xl">Purpose</h2>
 23-        <p>
 24-            {{.Site.Domain}} exists to allow people to create and share their thoughts
 25-            without the need to set up their own server or be part of a platform
 26-            that shows ads or tracks its users.
 27-        </p>
 28-    </section>
 29-    <section>
 30-        <h2 class="text-xl">Ethics</h2>
 31-        <p>We are committed to:</p>
 32-        <ul>
 33-            <li>No tracking of user or visitor behaviour.</li>
 34-            <li>Never sell any user or visitor data.</li>
 35-            <li>No ads — ever.</li>
 36-        </ul>
 37-    </section>
 38-    <section>
 39-        <h2 class="text-xl">Code of Content Publication</h2>
 40-        <p>
 41-            Content in {{.Site.Domain}} pastes is unfiltered and unmonitored. Users are free to publish any
 42-            combination of words and pixels except for: content of animosity or disparagement of an
 43-            individual or a group on account of a group characteristic such as race, color, national
 44-            origin, sex, disability, religion, or sexual orientation, which will be taken down
 45-            immediately.
 46-        </p>
 47-        <p>
 48-            If one notices something along those lines in a paste post please let us know at
 49-            <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>.
 50-        </p>
 51-    </section>
 52-    <section>
 53-        <h2 class="text-xl">Liability</h2>
 54-        <p>
 55-            The user expressly understands and agrees that Eric Bower and Antonio Mika, the operator of this website
 56-            shall not be liable, in law or in equity, to them or to any third party for any direct,
 57-            indirect, incidental, lost profits, special, consequential, punitive or exemplary damages.
 58-        </p>
 59-    </section>
 60-    <section>
 61-        <h2 class="text-xl">Account Terms</h2>
 62-        <p>
 63-            <ul>
 64-                <li>
 65-                    The user is responsible for all content posted and all actions performed with
 66-                    their account.
 67-                </li>
 68-                <li>
 69-                    We reserve the right to disable or delete a user's account for any reason at
 70-                    any time. We have this clause because, statistically speaking, there will be
 71-                    people trying to do something nefarious.
 72-                </li>
 73-            </ul>
 74-        </p>
 75-    </section>
 76-    <section>
 77-        <h2 class="text-xl">Service Availability</h2>
 78-        <p>
 79-         We provide the {{.Site.Domain}} service on an "as is" and "as available" basis. We do not offer
 80-         service-level agreements but do take uptime seriously.
 81-        </p>
 82-    </section>
 83-    <section>
 84-        <h2 class="text-xl">Contact and Support</h2>
 85-        <p>
 86-            Email us at <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>
 87-            with any questions.
 88-        </p>
 89-    </section>
 90-    <section>
 91-        <h2 class="text-xl">Acknowledgments</h2>
 92-        <p>
 93-            {{.Site.Domain}} was inspired by <a href="https://mataroa.blog">Mataroa Blog</a>
 94-            and <a href="https://bearblog.dev/">Bear Blog</a>.
 95-        </p>
 96-        <p>
 97-            {{.Site.Domain}} is built with many open source technologies.
 98-        </p>
 99-        <p>
100-            In particular we would like to thank:
101-        </p>
102-        <ul>
103-            <li>
104-                <span>The </span>
105-                <a href="https://charm.sh">charm.sh</a>
106-                <span> community</span>
107-            </li>
108-            <li>
109-                <span>The </span>
110-                <a href="https://go.dev">golang</a>
111-                <span> community</span>
112-            </li>
113-            <li>
114-                <span>The </span>
115-                <a href="https://www.postgresql.org/">postgresql</a>
116-                <span> community</span>
117-            </li>
118-            <li>
119-                <span>The </span>
120-                <a href="https://github.com/caddyserver/caddy">caddy</a>
121-                <span> community</span>
122-            </li>
123-        </ul>
124-    </section>
125-</main>
126-{{template "marketing-footer" .}}
127-{{end}}
D pastes/html/privacy.page.tmpl
+0, -54
 1@@ -1,54 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}privacy -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="{{.Site.Domain}} privacy policy" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-    <h1 class="text-2xl">Privacy</h1>
15-    <p>Details on our privacy and security approach.</p>
16-</header>
17-<main>
18-    <section>
19-        <h2 class="text-xl">Account Data</h2>
20-        <p>
21-            In order to have a functional account at {{.Site.Domain}}, we need to store
22-            your public key.  That is the only piece of information we record for a user.
23-        </p>
24-        <p>
25-            Because we use public-key cryptography, our security posture is a battle-tested
26-            and proven technique for authentication.
27-        </p>
28-    </section>
29-
30-    <section>
31-        <h2 class="text-xl">Third parties</h2>
32-        <p>
33-            We have a strong commitment to never share any user data with any third-parties.
34-        </p>
35-    </section>
36-
37-    <section>
38-        <h2 class="text-xl">Service Providers</h2>
39-        <ul>
40-            <li>
41-                <span>We host our server on </span>
42-                <a href="https://www.oracle.com/cloud/">oracle cloud</a>
43-            </li>
44-        </ul>
45-    </section>
46-
47-    <section>
48-        <h2 class="text-xl">Cookies</h2>
49-        <p>
50-            We do not use any cookies, not even account authentication.
51-        </p>
52-    </section>
53-</main>
54-{{template "marketing-footer" .}}
55-{{end}}
D pastes/html/transparency.page.tmpl
+0, -59
 1@@ -1,59 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}transparency -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="full transparency of analytics and cost at {{.Site.Domain}}" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-    <h1 class="text-2xl">Transparency</h1>
15-    <hr />
16-</header>
17-<main>
18-    <section>
19-        <h2 class="text-xl">Analytics</h2>
20-        <p>
21-            Here are some interesting stats on usage.
22-        </p>
23-
24-        <article>
25-            <h2 class="text-lg">Total users</h2>
26-            <div>{{.Analytics.TotalUsers}}</div>
27-        </article>
28-
29-        <article>
30-            <h2 class="text-lg">New users in the last month</h2>
31-            <div>{{.Analytics.UsersLastMonth}}</div>
32-        </article>
33-
34-        <article>
35-            <h2 class="text-lg">Total pastes</h2>
36-            <div>{{.Analytics.TotalPosts}}</div>
37-        </article>
38-
39-        <article>
40-            <h2 class="text-lg">New pastes in the last month</h2>
41-            <div>{{.Analytics.PostsLastMonth}}</div>
42-        </article>
43-
44-        <article>
45-            <h2 class="text-lg">Users with at least one paste</h2>
46-            <div>{{.Analytics.UsersWithPost}}</div>
47-        </article>
48-    </section>
49-
50-    <section>
51-        <h2 class="text-xl">Service maintenance costs</h2>
52-        <ul>
53-            <li>Domain name $3.25/mo</li>
54-            <li>Server $0.00/mo</li>
55-            <li>Programmer $0.00/mo</li>
56-        </ul>
57-    </section>
58-</main>
59-{{template "marketing-footer" .}}
60-{{end}}
M pastes/public/main.css
+23, -0
 1@@ -17,6 +17,7 @@
 2 
 3 @media (prefers-color-scheme: light) {
 4   :root {
 5+    --main-hue: 250;
 6     --white: #6a737d;
 7     --code: #fff8d3;
 8     --code-border: #f0d547;
 9@@ -34,6 +35,7 @@
10 
11 @media (prefers-color-scheme: dark) {
12   :root {
13+    --main-hue: 250;
14     --white: #f2f2f2;
15     --code: #414558;
16     --code-border: #252525;
17@@ -271,6 +273,23 @@ figure {
18   margin: 0;
19 }
20 
21+.btn-link {
22+  border: 3px solid hsl(var(--main-hue), 92%, 66%);
23+  background-color: hsl(var(--main-hue), 92%, 66%);
24+  padding: 0.5rem 1rem;
25+  border-radius: 0.25rem;
26+  box-shadow: 0px 1px 2px 0px black;
27+  color: var(--white);
28+  text-decoration: none;
29+  font-weight: bold;
30+}
31+
32+.btn-link:visited,
33+.btn-link:visited:hover,
34+.btn-link:hover {
35+  color: var(--white);
36+}
37+
38 .post-date {
39   width: 130px;
40 }
41@@ -319,6 +338,10 @@ figure {
42   display: inline;
43 }
44 
45+.inline-block {
46+  display: inline-block;
47+}
48+
49 .flex {
50   display: flex;
51 }
M pgs/api.go
+9, -7
 1@@ -339,11 +339,6 @@ func serveAsset(subdomain string, w http.ResponseWriter, r *http.Request) {
 2 	})
 3 }
 4 
 5-func marketingRequest(w http.ResponseWriter, r *http.Request) {
 6-	subdomain := "hey-pgs-prod"
 7-	serveAsset(subdomain, w, r)
 8-}
 9-
10 func assetRequest(w http.ResponseWriter, r *http.Request) {
11 	subdomain := shared.GetSubdomain(r)
12 	serveAsset(subdomain, w, r)
13@@ -374,10 +369,17 @@ func StartApiServer() {
14 	}
15 
16 	mainRoutes := []shared.Route{
17-		shared.NewRoute("GET", "/", marketingRequest),
18+		shared.NewRoute("GET", "/main.css", shared.ServeFile("main.css", "text/css")),
19+		shared.NewRoute("GET", "/card.png", shared.ServeFile("card.png", "image/png")),
20+		shared.NewRoute("GET", "/favicon-16x16.png", shared.ServeFile("favicon-16x16.png", "image/png")),
21+		shared.NewRoute("GET", "/apple-touch-icon.png", shared.ServeFile("apple-touch-icon.png", "image/png")),
22+		shared.NewRoute("GET", "/favicon.ico", shared.ServeFile("favicon.ico", "image/x-icon")),
23+		shared.NewRoute("GET", "/robots.txt", shared.ServeFile("robots.txt", "text/plain")),
24+
25+		shared.NewRoute("GET", "/", shared.CreatePageHandler("html/marketing.page.tmpl")),
26 		shared.NewRoute("GET", "/check", checkHandler),
27 		shared.NewRoute("GET", "/rss", rssHandler),
28-		shared.NewRoute("GET", "/(.+)", marketingRequest),
29+		shared.NewRoute("GET", "/(.+)", shared.CreatePageHandler("html/marketing.page.tmpl")),
30 	}
31 	subdomainRoutes := []shared.Route{
32 		shared.NewRoute("GET", "/", assetRequest),
D pgs/html/help.page.tmpl
+0, -172
  1@@ -1,172 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}help -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="questions and answers" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-  <h1 class="text-2xl">Need help?</h1>
 15-  <p>Here are some common questions on using this platform that we would like to answer.</p>
 16-</header>
 17-<main>
 18-  <section id="permission-denied">
 19-    <h2 class="text-xl">
 20-      <a href="#permission-denied" rel="nofollow noopener">#</a>
 21-      I get a permission denied when trying to SSH
 22-    </h2>
 23-    <p>
 24-      Unfortunately SHA-2 RSA keys are <strong>not</strong> currently supported.
 25-    </p>
 26-    <p>
 27-      Unfortunately, due to a shortcoming in Go’s x/crypto/ssh package, we
 28-      not currently support access via new SSH RSA keys: only the old SHA-1 ones will work.
 29-      Until we sort this out you’ll either need an SHA-1 RSA key or a key with another
 30-      algorithm, e.g. Ed25519. Not sure what type of keys you have? You can check with the
 31-      following:
 32-    </p>
 33-    <pre>$ find ~/.ssh/id_*.pub -exec ssh-keygen -l -f {} \;</pre>
 34-    <p>If you’re curious about the inner workings of this problem have a look at:</p>
 35-    <ul>
 36-      <li><a href="https://github.com/golang/go/issues/37278">golang/go#37278</a></li>
 37-      <li><a href="https://go-review.googlesource.com/c/crypto/+/220037">go-review</a></li>
 38-      <li><a href="https://github.com/golang/crypto/pull/197">golang/crypto#197</a></li>
 39-    </ul>
 40-  </section>
 41-
 42-  <section id="ssh-key">
 43-    <h2 class="text-xl">
 44-      <a href="#ssh-key" rel="nofollow noopener">#</a>
 45-      Generating a new SSH key
 46-    </h2>
 47-    <p>
 48-      <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent">Github reference</a>
 49-    </p>
 50-    <pre>ssh-keygen -t ed25519 -C "your_email@example.com"</pre>
 51-    <ol>
 52-      <li>When you're prompted to "Enter a file in which to save the key," press Enter. This accepts the default file location.</li>
 53-      <li>At the prompt, type a secure passphrase.</li>
 54-    </ol>
 55-  </section>
 56-
 57-  <section id="cli-commands">
 58-    <h2 class="text-xl">
 59-      <a href="#cli-commands" rel="nofollow noopener">#</a>
 60-      CLI Commands
 61-    </h2>
 62-    <p>
 63-      Our CLI commands are currently in active development so the list of available commands are changing.
 64-    </p>
 65-    <p>The best way to learn about all the commands we support is via an SSH command:</p>
 66-    <pre>ssh {{.Site.Domain}} help</pre>
 67-    <p>
 68-      Having said that, we do want to demonstrate the power of {{.Site.Domain}} by discussing design goals.
 69-      All of our SSH commands are safe-by-default.  Meaning, they never mutate server state by default.
 70-      This provides users an opportunity to experiment with our commands to see how they work.  In order to
 71-      actually trigger server mutations, every command must be appended with <code>--write</code>.
 72-    </p>
 73-    <p>
 74-      Further, we want to make sure users are able to manage their static sites exclusively from SSH commands.
 75-      Below is list of features we support via SSH commands:
 76-    </p>
 77-    <ul>
 78-      <li>Storage usage stats: <code>ssh <user>@{{.Site.Domain}} stats</code></li>
 79-      <li>List all projects (and their links): <code>ssh <user>@{{.Site.Domain}} ls</code></li>
 80-      <li>List all project dependencies: <code>ssh <user>@{{.Site.Domain}} depends project-x</code></li>
 81-      <li>Link a project: <code>ssh <user>@{{.Site.Domain}} link project-x project-y</code></li>
 82-      <li>Unlink a project: <code>ssh <user>@{{.Site.Domain}} unlink project-x</code></li>
 83-      <li>Delete a project (and all assets): <code>ssh <user>@{{.Site.Domain}} rm project-x</code></li>
 84-      <li>Delete all projects matching a prefix (except projects that have linked projects): <code>ssh <user>@{{.Site.Domain}} prune prefix</code></li>
 85-    </ul>
 86-  </section>
 87-
 88-  <section id="file-types">
 89-    <h2 class="text-xl">
 90-      <a href="#file-types" rel="nofollow noopener">#</a>
 91-      What file types are supported?
 92-    </h2>
 93-    <ul>
 94-      <li>html</li>
 95-      <li>htm</li>
 96-      <li>css</li>
 97-      <li>js</li>
 98-      <li>jpg</li>
 99-      <li>png</li>
100-      <li>gif</li>
101-      <li>webp</li>
102-      <li>svg</li>
103-      <li>ico</li>
104-      <li>pdf</li>
105-      <li>json</li>
106-      <li>txt</li>
107-      <li>otf</li>
108-      <li>ttf</li>
109-      <li>woff</li>
110-      <li>woff2</li>
111-      <li>md</li>
112-      <li>rss</li>
113-      <li>xml</li>
114-      <li>atom</li>
115-      <li>map</li>
116-      <li>webmanifest</li>
117-    </ul>
118-  </section>
119-
120-  <section id="redirects">
121-    <h2 class="text-xl">
122-      <a href="#redirects" rel="nofollow noopener">#</a>
123-      User-defined Redirects
124-    </h2>
125-    <p>
126-      We support custom redirects via a special file <code>_redirects</code>.
127-      Read more about it at <a href="https://docs.netlify.com/routing/redirects">netflify</a>
128-    </p>
129-  </section>
130-
131-  <section id="spa">
132-    <h2 class="text-xl">
133-      <a href="#spa" rel="nofollow noopener">#</a>
134-      Single-Page Applications (SPAs)
135-    </h2>
136-    <p>
137-      pgs supports SPAs!  Upload a <code>_redirects</code> file your project:
138-    </p>
139-    <pre>/*  /index.html  200</pre>
140-
141-    <p>
142-      Note that the Content Security Policy (CSP) for pages in the pgs.sh domain
143-      prohibits javascript from connecting to other origins.
144-      Pages served by custom domains do not have this restriction.
145-    </p>
146-
147-  </section>
148-
149-  <section id="custom-domain">
150-    <h2 class="text-xl">
151-      <a href="#custom-domain" rel="nofollow noopener">#</a>
152-      Setup a custom domain
153-    </h2>
154-    <p>
155-      A blog can be accessed from a custom domain.
156-      HTTPS will be automatically enabled and a certificate will be retrieved
157-      from <a href="https://letsencrypt.org/">Let's Encrypt</a>. In order for this to work,
158-      2 DNS records need to be created:
159-    </p>
160-
161-    <p>CNAME for the domain to pgs (subdomains or DNS hosting with CNAME flattening) or A record</p>
162-    <pre>CNAME subdomain.yourcustomdomain.com -> {{.Site.Domain}}</pre>
163-    <p>Resulting in:</p>
164-    <pre>subdomain.yourcustomdomain.com.         300     IN      CNAME   {{.Site.Domain}}.</pre>
165-    <p>And a TXT record to tell Prose what project is hosted on that domain at the subdomain entry _pgs</p>
166-    <pre>TXT _pgs.subdomain.yourcustomdomain.com -> username-myproject</pre>
167-    <p>E.g. for user 'erock' and project 'kittens' the result would be:</p>
168-    <p>Resulting in:</p>
169-    <pre>_pgs.subdomain.yourcustomdomain.com.         300     IN      TXT     "erock-kittens"</pre>
170-  </section>
171-</main>
172-{{template "marketing-footer" .}}
173-{{end}}
M pgs/html/marketing-footer.partial.tmpl
+0, -6
 1@@ -2,11 +2,5 @@
 2 <footer>
 3   <hr />
 4   <p class="font-italic">Built and maintained by <a href="https://pico.sh">pico.sh</a>.</p>
 5-  <div>
 6-    <a href="/">home</a> |
 7-    <a href="/ops.html">ops</a> |
 8-    <a href="/help.html">help</a> |
 9-    <a href="https://github.com/picosh/pico">source</a>
10-  </div>
11 </footer>
12 {{end}}
M pgs/html/marketing.page.tmpl
+6, -126
  1@@ -28,134 +28,14 @@
  2 
  3 {{define "body"}}
  4 <header class="text-center">
  5-  <h1 class="text-2xl font-bold">{{.Site.Domain}} [closed beta]</h1>
  6+  <h1 class="text-2xl font-bold">{{.Site.Domain}}</h1>
  7   <p class="text-lg">{{.Site.Description}}</p>
  8-  <hr />
  9+  <div>
 10+    <a href="https://pico.sh/pgs" class="btn-link mt inline-block">
 11+      LEARN MORE
 12+    </a>
 13+  </div>
 14 </header>
 15 
 16-<main>
 17-  <section>
 18-    <h2 class="text-lg font-bold">Closed beta</h2>
 19-    <p>Anyone can get an invite!</p>
 20-    <p>
 21-      The <strong>only</strong> requirement is to stay in our IRC channel
 22-      and be willing to provide feedback on how we can improve the service.
 23-    </p>
 24-    <p>Join our IRC channel #pico.sh @ libera.chat and ask for an invite.</p>
 25-  </section>
 26-
 27-  <section>
 28-    <h2 class="text-lg font-bold">Examples</h2>
 29-    <ul>
 30-      <li>The site you are reading right now</li>
 31-      <li><a href="https://git.erock.io">git web viewer</a></li>
 32-      <li><a href="https://neovimcraft.com">neovimcraft</a></li>
 33-    </ul>
 34-  </section>
 35-
 36-  <section>
 37-    <h2 class="text-lg font-bold">Features</h2>
 38-    <ul>
 39-      <li>Terminal workflow</li>
 40-      <li>No client-side installation required to fully manage static sites</li>
 41-      <li>Distinct static sites as <code>projects</code></li>
 42-      <li>Unlimited projects created on-the-fly (no need to create a project first)</li>
 43-      <li>Deploy via <code>scp -r ./public/* erock@{{.Site.Domain}}:/myproject</code></li>
 44-      <li>Promotion/rollback support (via symbolic linking from one project to another)</li>
 45-      <li>Managed HTTPS for all projects (e.g. https://erock-myproject.{{.Site.Domain}})</li>
 46-      <li><a href="/help#custom-domain">Custom domains</a> for projects (managed simply by `TXT` records)</li>
 47-      <li><a href="/help#redirects">User-defined redirects</a></li>
 48-      <li><a href="/help#spa">SPA support</a></li>
 49-      <li>1GB max storage</li>
 50-      <li>50MB max file size</li>
 51-      <li>All assets are public-only</li>
 52-      <li>Only web assets are supported</li>
 53-    </ul>
 54-  </section>
 55-
 56-  <section>
 57-    <h2 class="text-lg font-bold">Create your account with Public-Key Cryptography</h2>
 58-    <p>We don't want your email address.</p>
 59-    <p>To get started, simply ssh into our content management system:</p>
 60-    <pre>ssh new@{{.Site.Domain}}</pre>
 61-    <div class="text-sm font-italic note">
 62-      note: <code>new</code> is a special username that will always send you to account
 63-      creation, even with multiple accounts associated with your key-pair.
 64-    </div>
 65-    <div class="text-sm font-italic note">
 66-      note: getting permission denied? <a href="/help.html#permission-denied">read this</a>
 67-    </div>
 68-    <p>
 69-      After that, just set a username and you're ready to start writing! When you SSH
 70-      again, use your username that you set in the CMS.
 71-    </p>
 72-  </section>
 73-
 74-  <section>
 75-    <h2 class="text-lg font-bold">Publish your site with one command</h2>
 76-    <p>
 77-        When your site is ready to be published, copy the files to our server with a familiar
 78-        command:
 79-    </p>
 80-    <pre>scp -r * {{.Site.Domain}}:/myproject</pre>
 81-    <p>
 82-      That's it!  There's no need to formally create a project, we
 83-      create them on-the-fly.  Further, we provide TLS for every project
 84-      automatically.  In this case the url for the project above would
 85-      look something like <code>https://{username}-myproject.{{.Site.Domain}}</code>.
 86-    </p>
 87-  </section>
 88-
 89-  <section>
 90-    <h2 class="text-lg font-bold">Manage your projects with a remote CLI</h2>
 91-    <p>
 92-      Our management system is done via ssh commands.  Type the following command to learn more:
 93-    </p>
 94-    <pre>ssh {{.Site.Domain}} help</pre>
 95-  </section>
 96-
 97-  <section>
 98-    <h2 class="text-lg font-bold">Project promotion and rollbacks</h2>
 99-    <p>
100-      Additionally you can setup a pipeline for promotion and rollbacks,
101-      which will instantly update your project.
102-    </p>
103-    <pre>ssh {{.Site.Domain}} link project-prod project-d0131d4</pre>
104-    <p>
105-      A common way to perform promotions within {{.Site.Domain}} is to setup CI/CD so every
106-      push to <code>main</code> would trigger a build and create a new project
107-      based on the git commit hash (e.g. <code>project-d0131d4</code>).
108-    </p>
109-    <p>
110-      This command will create a symbolic link from <code>project-prod</code>
111-      to <code>project-d0131d4</code>. Want to rollback a release?
112-      Just change the link for <code>project-prod</code> to a previous project.
113-    </p>
114-    <p>
115-      We also built a <a href="https://github.com/picosh/pgs-action">github action</a>
116-      that handles all the logic for uploading to {{.Site.Domain}}.
117-      <a href="https://erock-git-neovimcraft.pgs.sh/tree/main/item/.github/workflows/deploy.yml.html#27">Here's an example of it in action</a>.
118-    </p>
119-  </section>
120-
121-  <section>
122-    <h2 class="text-lg font-bold">Philosophy</h2>
123-    <p>
124-      Creating a static website should be as simple as copying files from a local folder to a server.
125-      We also believe in creating limited but feature-rich products without the user needing to install
126-      anything on their computer.
127-    </p>
128-    <p>Read more about team pico's philosophy <a href="https://pico.sh">here</a>.</p>
129-  </section>
130-
131-  <section>
132-    <h2 class="text-lg font-bold">Roadmap</h2>
133-    <ol>
134-      <li>Should we charge customers for more storage?</li>
135-      <li>Github integration (e.g. post comment on PR with preview site)</li>
136-    </ol>
137-  </section>
138-</main>
139-
140 {{template "marketing-footer" .}}
141 {{end}}
D pgs/html/ops.page.tmpl
+0, -146
  1@@ -1,146 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}operations -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="{{.Site.Domain}} operations" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Operations</h1>
 15-    <ul>
 16-        <li><a href="/privacy.html">privacy</a></li>
 17-    </ul>
 18-</header>
 19-<main>
 20-  <section>
 21-    <h2 class="text-xl">Purpose</h2>
 22-    <p>
 23-      {{.Site.Domain}} exists to allow people to create and share their thoughts
 24-      without the need to set up their own server or be part of a platform
 25-      that shows ads or tracks its users.
 26-    </p>
 27-  </section>
 28-  <section>
 29-    <h2 class="text-xl">Ethics</h2>
 30-    <p>We are committed to:</p>
 31-    <ul>
 32-      <li>No browser-based tracking of visitor behavior.</li>
 33-      <li>No attempt to identify users.</li>
 34-      <li>Never sell any user or visitor data.</li>
 35-      <li>No ads — ever.</li>
 36-    </ul>
 37-  </section>
 38-  <section>
 39-    <h2 class="text-xl">Code of Content Publication</h2>
 40-    <p>
 41-      Content in {{.Site.Domain}} blogs is unfiltered and unmonitored. Users are free to publish any
 42-      combination of words and pixels except for: content of animosity or disparagement of an
 43-      individual or a group on account of a group characteristic such as race, color, national
 44-      origin, sex, disability, religion, or sexual orientation, which will be taken down
 45-      immediately.
 46-    </p>
 47-    <p>
 48-      If one notices something along those lines in a blog please let us know at
 49-      <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>.
 50-    </p>
 51-  </section>
 52-  <section>
 53-    <h2 class="text-xl">Liability</h2>
 54-    <p>
 55-      The user expressly understands and agrees that Eric Bower and Antonio Mika, the operator of this website
 56-      shall not be liable, in law or in equity, to them or to any third party for any direct,
 57-      indirect, incidental, lost profits, special, consequential, punitive or exemplary damages.
 58-    </p>
 59-  </section>
 60-  <section>
 61-    <h2 class="text-xl">Analytics</h2>
 62-    <p>
 63-      We are committed to zero browser-based tracking or trying to identify visitors.  This
 64-      means we do not try to understand the user based on cookies or IP address.  We do not
 65-      store personally identifiable information.
 66-    </p>
 67-    <p>
 68-      However, in order to provide a better service, we do have some analytics on posts.
 69-      List of metrics we track for posts:
 70-    </p>
 71-    <ul>
 72-      <li>anonymous view counts</li>
 73-    </ul>
 74-    <p>
 75-      We might also inspect the headers of HTTP requests to determine some tertiary information
 76-      about the request.  For example we might inspect the <code>User-Agent</code> or
 77-      <code>Referer</code> to filter out requests from bots.
 78-    </p>
 79-  </section>
 80-  <section>
 81-    <h2 class="text-xl">Account Terms</h2>
 82-    <p>
 83-      <ul>
 84-        <li>
 85-          The user is responsible for all content posted and all actions performed with
 86-          their account.
 87-        </li>
 88-        <li>
 89-          We reserve the right to disable or delete a user's account for any reason at
 90-          any time. We have this clause because, statistically speaking, there will be
 91-          people trying to do something nefarious.
 92-        </li>
 93-      </ul>
 94-    </p>
 95-  </section>
 96-  <section>
 97-    <h2 class="text-xl">Service Availability</h2>
 98-    <p>
 99-     We provide the {{.Site.Domain}} service on an "as is" and "as available" basis. We do not offer
100-     service-level agreements but do take uptime seriously.
101-    </p>
102-  </section>
103-  <section>
104-    <h2 class="text-xl">Contact and Support</h2>
105-    <p>
106-      Email us at <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>
107-      with any questions.
108-    </p>
109-  </section>
110-  <section>
111-    <h2 class="text-xl">Acknowledgments</h2>
112-    <p>
113-      {{.Site.Domain}} was inspired by <a href="https://mataroa.blog">Mataroa Blog</a>
114-      and <a href="https://bearblog.dev/">Bear Blog</a>.
115-    </p>
116-    <p>
117-      {{.Site.Domain}} is built with many open source technologies.
118-    </p>
119-    <p>
120-      In particular we would like to thank:
121-    </p>
122-    <ul>
123-      <li>
124-        <span>The </span>
125-        <a href="https://charm.sh">charm.sh</a>
126-        <span> community</span>
127-      </li>
128-      <li>
129-        <span>The </span>
130-        <a href="https://go.dev">golang</a>
131-        <span> community</span>
132-      </li>
133-      <li>
134-        <span>The </span>
135-        <a href="https://www.postgresql.org/">postgresql</a>
136-        <span> community</span>
137-      </li>
138-      <li>
139-        <span>The </span>
140-        <a href="https://github.com/caddyserver/caddy">caddy</a>
141-        <span> community</span>
142-      </li>
143-    </ul>
144-  </section>
145-</main>
146-{{template "marketing-footer" .}}
147-{{end}}
D pgs/html/privacy.page.tmpl
+0, -54
 1@@ -1,54 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}privacy -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="{{.Site.Domain}} privacy policy" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-  <h1 class="text-2xl">Privacy</h1>
15-  <p>Details on our privacy and security approach.</p>
16-</header>
17-<main>
18-  <section>
19-    <h2 class="text-xl">Account Data</h2>
20-    <p>
21-      In order to have a functional account at {{.Site.Domain}}, we need to store
22-      your public key.  That is the only piece of information we record for a user.
23-    </p>
24-    <p>
25-      Because we use public-key cryptography, our security posture is a battle-tested
26-      and proven technique for authentication.
27-    </p>
28-  </section>
29-
30-  <section>
31-    <h2 class="text-xl">Third parties</h2>
32-    <p>
33-      We have a strong commitment to never share any user data with any third-parties.
34-    </p>
35-  </section>
36-
37-  <section>
38-    <h2 class="text-xl">Service Providers</h2>
39-    <ul>
40-      <li>
41-        <span>We host our server on </span>
42-        <a href="https://www.oracle.com/cloud/">oracle cloud</a>
43-      </li>
44-    </ul>
45-  </section>
46-
47-  <section>
48-    <h2 class="text-xl">Cookies</h2>
49-    <p>
50-      We do not use any cookies, not even account authentication.
51-    </p>
52-  </section>
53-</main>
54-{{template "marketing-footer" .}}
55-{{end}}
M pgs/public/main.css
+23, -0
 1@@ -17,6 +17,7 @@
 2 
 3 @media (prefers-color-scheme: light) {
 4   :root {
 5+    --main-hue: 250;
 6     --white: #6a737d;
 7     --code: #fff8d3;
 8     --code-border: #f0d547;
 9@@ -34,6 +35,7 @@
10 
11 @media (prefers-color-scheme: dark) {
12   :root {
13+    --main-hue: 250;
14     --white: #f2f2f2;
15     --code: #414558;
16     --code-border: #252525;
17@@ -271,6 +273,23 @@ figure {
18   margin: 0;
19 }
20 
21+.btn-link {
22+  border: 3px solid hsl(var(--main-hue), 92%, 66%);
23+  background-color: hsl(var(--main-hue), 92%, 66%);
24+  padding: 0.5rem 1rem;
25+  border-radius: 0.25rem;
26+  box-shadow: 0px 1px 2px 0px black;
27+  color: var(--white);
28+  text-decoration: none;
29+  font-weight: bold;
30+}
31+
32+.btn-link:visited,
33+.btn-link:visited:hover,
34+.btn-link:hover {
35+  color: var(--white);
36+}
37+
38 .post-date {
39   width: 130px;
40 }
41@@ -319,6 +338,10 @@ figure {
42   display: inline;
43 }
44 
45+.inline-block {
46+  display: inline-block;
47+}
48+
49 .flex {
50   display: flex;
51 }
D pgs/static.go
+0, -66
 1@@ -1,66 +0,0 @@
 2-package pgs
 3-
 4-import (
 5-	"bytes"
 6-	"os"
 7-	"path/filepath"
 8-
 9-	"github.com/picosh/pico/shared"
10-)
11-
12-type PageData struct {
13-	Site shared.SitePageData
14-}
15-
16-type Page struct {
17-	src  string
18-	dest string
19-	cfg  *shared.ConfigSite
20-}
21-
22-func genPage(page *Page) error {
23-	ts, err := shared.RenderTemplate(page.cfg, []string{page.cfg.StaticPath(page.src)})
24-
25-	if err != nil {
26-		page.cfg.Logger.Error(err)
27-		return err
28-	}
29-
30-	data := PageData{
31-		Site: *page.cfg.GetSiteData(),
32-	}
33-	buf := new(bytes.Buffer)
34-	err = ts.Execute(buf, data)
35-	if err != nil {
36-		page.cfg.Logger.Error(err)
37-		return err
38-	}
39-
40-	err = os.WriteFile(page.dest, buf.Bytes(), 0644)
41-	if err != nil {
42-		page.cfg.Logger.Fatal(err)
43-	}
44-
45-	return nil
46-}
47-
48-func GenStaticSite(dir string, cfg *shared.ConfigSite) error {
49-	pages := [][2]string{
50-		{"html/marketing.page.tmpl", "index.html"},
51-		{"html/ops.page.tmpl", "ops.html"},
52-		{"html/privacy.page.tmpl", "privacy.html"},
53-		{"html/help.page.tmpl", "help.html"},
54-	}
55-	for _, page := range pages {
56-		err := genPage(&Page{
57-			src:  page[0],
58-			dest: filepath.Join(dir, page[1]),
59-			cfg:  cfg,
60-		})
61-		if err != nil {
62-			return err
63-		}
64-	}
65-
66-	return nil
67-}
M prose/api.go
+1, -40
 1@@ -468,40 +468,6 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
 2 	}
 3 }
 4 
 5-func transparencyHandler(w http.ResponseWriter, r *http.Request) {
 6-	dbpool := shared.GetDB(r)
 7-	logger := shared.GetLogger(r)
 8-	cfg := shared.GetCfg(r)
 9-
10-	analytics, err := dbpool.FindSiteAnalytics(cfg.Space)
11-	if err != nil {
12-		logger.Error(err)
13-		http.Error(w, err.Error(), http.StatusInternalServerError)
14-		return
15-	}
16-
17-	ts, err := template.ParseFiles(
18-		cfg.StaticPath("html/transparency.page.tmpl"),
19-		cfg.StaticPath("html/footer.partial.tmpl"),
20-		cfg.StaticPath("html/marketing-footer.partial.tmpl"),
21-		cfg.StaticPath("html/base.layout.tmpl"),
22-	)
23-
24-	if err != nil {
25-		http.Error(w, err.Error(), http.StatusInternalServerError)
26-	}
27-
28-	data := TransparencyPageData{
29-		Site:      *cfg.GetSiteData(),
30-		Analytics: analytics,
31-	}
32-	err = ts.Execute(w, data)
33-	if err != nil {
34-		logger.Error(err)
35-		http.Error(w, err.Error(), http.StatusInternalServerError)
36-	}
37-}
38-
39 func readHandler(w http.ResponseWriter, r *http.Request) {
40 	dbpool := shared.GetDB(r)
41 	logger := shared.GetLogger(r)
42@@ -832,12 +798,7 @@ func createStaticRoutes() []shared.Route {
43 
44 func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
45 	routes := []shared.Route{
46-		shared.NewRoute("GET", "/", shared.CreatePageHandler("html/marketing.page.tmpl")),
47-		shared.NewRoute("GET", "/spec", shared.CreatePageHandler("html/spec.page.tmpl")),
48-		shared.NewRoute("GET", "/ops", shared.CreatePageHandler("html/ops.page.tmpl")),
49-		shared.NewRoute("GET", "/privacy", shared.CreatePageHandler("html/privacy.page.tmpl")),
50-		shared.NewRoute("GET", "/help", shared.CreatePageHandler("html/help.page.tmpl")),
51-		shared.NewRoute("GET", "/transparency", transparencyHandler),
52+		shared.NewRoute("GET", "/", readHandler),
53 		shared.NewRoute("GET", "/read", readHandler),
54 		shared.NewRoute("GET", "/check", shared.CheckHandler),
55 	}
D prose/html/help.page.tmpl
+0, -564
  1@@ -1,564 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}help -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="questions and answers" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Need help?</h1>
 15-    <p>Here are some common questions on using this platform that we would like to answer.</p>
 16-</header>
 17-<main>
 18-    <section id="permission-denied">
 19-        <h2 class="text-xl">
 20-            <a href="#permission-denied" rel="nofollow noopener">#</a>
 21-            I get a permission denied when trying to SSH
 22-        </h2>
 23-        <p>
 24-            Unfortunately SHA-2 RSA keys are <strong>not</strong> currently supported.
 25-        </p>
 26-        <p>
 27-            Unfortunately, due to a shortcoming in Go’s x/crypto/ssh package, we do
 28-            not currently support access via new SSH RSA keys: only the old SHA-1 ones will work.
 29-            Until we sort this out you’ll either need an SHA-1 RSA key or a key with another
 30-            algorithm, e.g. Ed25519. Not sure what type of keys you have? You can check with the
 31-            following:
 32-        </p>
 33-        <pre>$ find ~/.ssh/id_*.pub -exec ssh-keygen -l -f {} \;</pre>
 34-        <p>If you’re curious about the inner workings of this problem have a look at:</p>
 35-        <ul>
 36-            <li><a href="https://github.com/golang/go/issues/37278">golang/go#37278</a></li>
 37-            <li><a href="https://go-review.googlesource.com/c/crypto/+/220037">go-review</a></li>
 38-            <li><a href="https://github.com/golang/crypto/pull/197">golang/crypto#197</a></li>
 39-        </ul>
 40-    </section>
 41-
 42-    <section id="blog-ssh-key">
 43-        <h2 class="text-xl">
 44-            <a href="#blog-ssh-key" rel="nofollow noopener">#</a>
 45-            Generating a new SSH key
 46-        </h2>
 47-        <p>
 48-            <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent">Github reference</a>
 49-        </p>
 50-        <pre>ssh-keygen -t ed25519 -C "your_email@example.com"</pre>
 51-        <ol>
 52-            <li>When you're prompted to "Enter a file in which to save the key," press Enter. This accepts the default file location.</li>
 53-            <li>At the prompt, type a secure passphrase.</li>
 54-        </ol>
 55-    </section>
 56-
 57-    <section id="blog-structure">
 58-        <h2 class="text-xl">
 59-            <a href="#blog-structure" rel="nofollow noopener">#</a>
 60-            What should my blog folder look like?
 61-        </h2>
 62-        <p>
 63-            Currently {{.Site.Domain}} only supports a flat folder structure.  Therefore,
 64-            <code>scp -r</code> is not permitted.  We also only allow <code>.md</code> files to be
 65-            uploaded.
 66-        </p>
 67-        <p>
 68-            <a href="https://github.com/neurosnap/prose-blog">Here is the source to my blog on this platform</a>
 69-        </p>
 70-        <p>
 71-        Below is an example of what your blog folder should look like:
 72-        </p>
 73-            <pre>blog/
 74-first-post.md
 75-second-post.md
 76-third-post.md</pre>
 77-        </p>
 78-        <p>
 79-            Underscores and hyphens are permitted and will be automatically removed from the title of the post.
 80-        </p>
 81-    </section>
 82-
 83-    <section id="post-metadata">
 84-        <h2 class="text-xl">
 85-            <a href="#post-metadata" rel="nofollow noopener">#</a>
 86-            How do I update metadata like publish date and title?
 87-        </h2>
 88-        <p>
 89-        We support adding frontmatter to the top of your markdown posts.  A frontmatter looks like the following:
 90-        <pre>---
 91-title: some title!
 92-description: this is a great description
 93-date: 2022-06-28
 94----</pre>
 95-        </p>
 96-        <p>
 97-            List of available variables can be found <a href="#hugo-support">here</a>.
 98-        </p>
 99-        <p>
100-            Common variables:
101-            <ul>
102-                <li>title (custom title not dependent on filename)</li>
103-                <li>description (what is the purpose of this post?  It's also added to meta tag)</li>
104-                <li>date (suggested format YYYY-MM-DD, if set in future post wil be unlisted)</li>
105-                <li>tags (<code>[feature, announcement]</code>)</li>
106-                <li>image (og image)</li>
107-                <li>card (og image twitter card: summary, summary_large_image, etc.)</li>
108-            </ul>
109-        </p>
110-    </section>
111-
112-    <section id="post-footer">
113-        <h2 class="text-xl">
114-            <a href="#post-footer" rel="nofollow noopener">#</a>
115-            How can I add a footer to all of my posts?
116-        </h2>
117-        <p>
118-            We have a special file <code>_footer.md</code> that will be appended to every
119-            single blog post.
120-
121-        </p>
122-        <p>
123-            There is nothing that differentiates itself from the rest of the
124-            post so it's up to you to style it.  For convenience we added an <code>id</code> to the
125-            containing element <code>post-footer</code>.
126-        </p>
127-    </section>
128-
129-    <section id="post-update">
130-        <h2 class="text-xl">
131-            <a href="#post-update" rel="nofollow noopener">#</a>
132-            How do I update a post?
133-        </h2>
134-        <p>
135-            Updating a post requires that you update the source document and then run the <code>rsync</code>
136-            command again.  If the filename remains the same, then the post will be updated.
137-        </p>
138-    </section>
139-
140-    <section id="post-delete">
141-        <h2 class="text-xl">
142-            <a href="#post-delete" rel="nofollow noopener">#</a>
143-            How do I delete a post?
144-        </h2>
145-
146-        <h3 class="text-lg">
147-          <a href="#post-delete-sftp" rel="nofollow noopener">#</a>
148-          sftp
149-        </h3>
150-
151-        <p>There are three ways to delete posts: <code>sftp</code>, upload a 0-byte file, or use our CMS.</p>
152-
153-        <pre>sftp {{.Site.Domain}}
154-sftp> rm delete.md</pre>
155-
156-        <h3 class="text-lg">
157-          <a href="#post-delete-0-byte-file" rel="nofollow noopener">#</a>
158-          0-byte file
159-        </h3>
160-
161-        <p>
162-            Because <code>scp</code> does not natively support deleting files, we didn't want to bake
163-            that behavior into my ssh server.
164-        </p>
165-
166-        <p>
167-            However, if a user wants to delete a post they can delete the contents of the file and
168-            then upload it to our server.  If the file contains 0 bytes, we will remove the post.
169-            For example, if you want to delete <code>delete.md</code> you could:
170-        </p>
171-
172-        <pre>
173-cp /dev/null delete.md
174-scp ./delete.md {{.Site.Domain}}:/</pre>
175-
176-        <h3 class="text-lg">
177-          <a href="#post-delete-cms" rel="nofollow noopener">#</a>
178-          CMS
179-        </h3>
180-
181-        <p>
182-            Alternatively, you can go to <code>ssh {{.Site.Domain}}</code> and select "Manage posts."
183-            Then you can highlight the post you want to delete and then press "X."  It will ask for
184-            confirmation before actually removing the post.
185-        </p>
186-    </section>
187-
188-    <section id="blog-upload-single-file">
189-        <h2 class="text-xl">
190-            <a href="#blog-upload-single-file" rel="nofollow noopener">#</a>
191-            When I want to publish a new post, do I have to upload all posts everytime?
192-        </h2>
193-        <p>
194-            Nope!  Just <code>rsync</code> the file you want to publish.  For example, if you created
195-            a new post called <code>taco-tuesday.md</code> then you would publish it like this:
196-        </p>
197-        <pre>rsync ./taco-tuesday.md {{.Site.Domain}}:</pre>
198-    </section>
199-
200-    <section id="blog-readme">
201-        <h2 class="text-xl">
202-            <a href="#blog-readme" rel="nofollow noopener">#</a>
203-            How can I customize my blog page?
204-        </h2>
205-        <p>
206-        There's a special file you can upload `_readme.md` which will allow
207-        users to add a bio and links to their blog landing page.
208-        <pre>---
209-title: some title!
210-description: this is a great description
211-nav:
212-    - google: https://google.com
213-    - site: https://some.site
214----
215-
216-Here is a quick intro to my personal blog!
217-This will show up on the blog landing page.
218-</pre>
219-        </p>
220-        <p>
221-            List of available variables:
222-            <ul>
223-                <li>title (name of the blog, default: "X's blog")</li>
224-                <li>description (description of blog)</li>
225-                <li>nav (key=value pair that corresponds to text=href in html)</li>
226-                <li>image (og image)</li>
227-                <li>card (og image twitter card: summary, summary_large_image, etc.)</li>
228-                <li>favicon</li>
229-                <li>aliases (list of alternate routes e.g. <code>2023/03/10/my-post</code>)</li>
230-            </ul>
231-        </p>
232-    </section>
233-
234-    <section id="blog-style">
235-        <h2 class="text-xl">
236-            <a href="#blog-style" rel="nofollow noopener">#</a>
237-            How can I change the theme of my blog?
238-        </h2>
239-        <p>
240-            There's a special file you can upload `_styles.css` which will allow
241-            users to add a CSS file to their page.  It will be the final CSS file
242-            loaded on the page so it will overwrite whatever styles have previously
243-            been added.  We've also added a couple of convenience id's attached to the
244-            body element for the blog and post pages.
245-        </p>
246-        <pre>/* _styles.css */
247-#post {
248-    color: green;
249-}
250-
251-#blog {
252-    color: tomato;
253-}</pre>
254-        <p>Then just upload the file:</p>
255-        <pre>sync _styles.css <username>@prose.sh:/</pre>
256-    </section>
257-
258-    <section id="blog-layout">
259-        <h2 class="text-xl">
260-            <a href="#blog-layout" rel="nofollow noopener">#</a>
261-            How can I change the layout of my blog?
262-        </h2>
263-        <p>
264-            Inside the <code>_header.txt</code> metadata file, there's a variable <code>layout</code>
265-            option that will change the layout of your blog index page.
266-        </p>
267-        <p>Currently supported options</p>
268-        <ul>
269-            <li>default</li>
270-            <li>aside</li>
271-        </ul>
272-    </section>
273-
274-    <section id="images">
275-        <h2 class="text-xl">
276-            <a href="#images" rel="nofollow noopener">#</a>
277-            How can I upload images?
278-        </h2>
279-        <p>
280-            We allow prose to upload images to imgs.
281-            For example, if you want to upload images for prose.sh all you have to do is include
282-            your images in the <code>rsync</code> or <code>scp</code> command.
283-        </p>
284-        <pre>rsync profile.jpg user@prose.sh:/</pre>
285-        <p>Then when you want to reference the file, you can reference it like so:</p>
286-        <pre>![profile pic](/profile.jpg)</pre>
287-    </section>
288-
289-    <section id="file-types">
290-        <h2 class="text-xl">
291-            <a href="#file-types" rel="nofollow noopener">#</a>
292-            What file types are supported?
293-        </h2>
294-        <p>
295-            <a href="https://imgs.sh/help">see imgs help page</a>
296-        </p>
297-    </section>
298-
299-    <section id="blog-url">
300-        <h2 class="text-xl">
301-            <a href="#blog-url" rel="nofollow noopener">#</a>
302-            What is my blog URL?
303-        </h2>
304-        <pre>https://{username}.{{.Site.Domain}}</pre>
305-    </section>
306-
307-    <section id="continuous-deployment">
308-        <h2 class="text-xl">
309-            <a href="#continuous-deployment" rel="nofollow noopener">#</a>
310-            How can I automatically publish my post?
311-        </h2>
312-        <p>
313-            There is a github action that we built to make it easy to publish your blog automatically.
314-        </p>
315-        <ul>
316-            <li>
317-                <a href="https://github.com/picosh/scp-publish-action">github action repo</a>
318-            </li>
319-            <li>
320-                <a href="https://github.com/neurosnap/lists-official-blog/blob/main/.github/workflows/publish.yml">example workflow</a>
321-            </li>
322-        </ul>
323-        <p>
324-            A user also created a systemd task to automatically publish new posts.  <a href="https://github.com/neurosnap/lists.sh/discussions/24">Check out this github discussion for more details.</a>
325-        </p>
326-    </section>
327-
328-    <section id="multiple-accounts">
329-        <h2 class="text-xl">
330-            <a href="#multiple-accounts" rel="nofollow noopener">#</a>
331-            Can I create multiple accounts?
332-        </h2>
333-        <p>
334-           Yes!  You can either a) create a new keypair and use that for authentication
335-           or b) use the same keypair and ssh into our CMS using our special username
336-           <code>ssh new@{{.Site.Domain}}</code>.
337-        </p>
338-        <p>
339-            Please note that if you use the same keypair for multiple accounts, you will need to
340-            always specify the user when logging into our CMS.
341-        </p>
342-    </section>
343-
344-    <section id="custom-domain">
345-        <h2 class="text-xl">
346-            <a href="#custom-domain" rel="nofollow noopener">#</a>
347-            Setup a custom domain
348-        </h2>
349-        <p>
350-            A blog can be accessed from a custom domain.
351-            HTTPS will be automatically enabled and a certificate will be retrieved
352-            from <a href="https://letsencrypt.org/">Let's Encrypt</a>. In order for this to work,
353-            2 DNS records need to be created:
354-        </p>
355-
356-        <p><code>CNAME</code> for the domain to prose (subdomains or DNS hosting with <code>CNAME</code> flattening) or <code>A</code> record</p>
357-        <pre>CNAME subdomain.yourcustomdomain.com -> prose.sh</pre>
358-        <p>Resulting in:</p>
359-
360-        <pre>subdomain.yourcustomdomain.com.         300     IN      CNAME   prose.sh.</pre>
361-
362-        <p>And a <code>TXT</code> record to tell Prose what blog is hosted on that domain at the subdomain entry <code>_prose</code></p>
363-        <pre>TXT _prose.subdomain.yourcustomdomain.com -> yourproseusername</pre>
364-        <p>Resulting in:</p>
365-        <pre>_prose.subdomain.yourcustomdomain.com.         300     IN      TXT     "hey"</pre>
366-
367-        <p>
368-            Depending on your DNS, this could take some time to fully switch over.  We have an endpoint
369-            to check whether or not custom domains are setup:
370-            <pre>curl -vvvv https://prose.sh/check?domain=<DOMAIN></pre>
371-        </p>
372-    </section>
373-
374-    <section id="hugo-support">
375-        <h2 class="text-xl">
376-            <a href="#hugo-support" rel="nofollow noopener">#</a>
377-            Hugo support
378-        </h2>
379-        <p>
380-            We support a subset of pre-defined hugo variables in our frontmatter.
381-            <a href="https://gohugo.io/content-management/front-matter/">Complete list at Hugo</a>
382-        </p>
383-        <table>
384-            <th>
385-                <tr>
386-                    <th align="left">Variable</th>
387-                    <th>Supported</th>
388-                    <th>Planned</th>
389-                </tr>
390-            </th>
391-
392-            <tbody>
393-                <tr>
394-                    <td>aliases</td>
395-                    <td>yes</td>
396-                    <td>-</td>
397-                </tr>
398-
399-                <tr>
400-                    <td>audio</td>
401-                    <td>no</td>
402-                    <td>no</td>
403-                </tr>
404-
405-                <tr>
406-                    <td>cascade</td>
407-                    <td>no</td>
408-                    <td>no</td>
409-                </tr>
410-
411-                <tr>
412-                    <td>date</td>
413-                    <td>yes</td>
414-                    <td>-</td>
415-                </tr>
416-
417-                <tr>
418-                    <td>description</td>
419-                    <td>yes</td>
420-                    <td>-</td>
421-                </tr>
422-
423-                <tr>
424-                    <td>draft</td>
425-                    <td>yes</td>
426-                    <td>-</td>
427-                </tr>
428-
429-                <tr>
430-                    <td>expiryDate</td>
431-                    <td>no</td>
432-                    <td>no</td>
433-                </tr>
434-
435-                <tr>
436-                    <td>headless</td>
437-                    <td>no</td>
438-                    <td>no</td>
439-                </tr>
440-
441-                <tr>
442-                    <td>images</td>
443-                    <td>no</td>
444-                    <td>no</td>
445-                </tr>
446-
447-                <tr>
448-                    <td>isCJKLanguage</td>
449-                    <td>no</td>
450-                    <td>no</td>
451-                </tr>
452-
453-                <tr>
454-                    <td>keywords</td>
455-                    <td>no</td>
456-                    <td>yes</td>
457-                </tr>
458-
459-                <tr>
460-                    <td>layout</td>
461-                    <td>no</td>
462-                    <td>maybe</td>
463-                </tr>
464-
465-                <tr>
466-                    <td>lastmod</td>
467-                    <td>no</td>
468-                    <td>maybe</td>
469-                </tr>
470-
471-                <tr>
472-                    <td>linkTitle</td>
473-                    <td>no</td>
474-                    <td>no</td>
475-                </tr>
476-
477-                <tr>
478-                    <td>markup</td>
479-                    <td>no</td>
480-                    <td>no</td>
481-                </tr>
482-
483-                <tr>
484-                    <td>outputs</td>
485-                    <td>no</td>
486-                    <td>no</td>
487-                </tr>
488-
489-                <tr>
490-                    <td>publishDate</td>
491-                    <td>no</td>
492-                    <td>no</td>
493-                </tr>
494-
495-                <tr>
496-                    <td>resources</td>
497-                    <td>no</td>
498-                    <td>no</td>
499-                </tr>
500-
501-                <tr>
502-                    <td>series</td>
503-                    <td>no</td>
504-                    <td>no</td>
505-                </tr>
506-
507-                <tr>
508-                    <td>slug</td>
509-                    <td>no</td>
510-                    <td>no</td>
511-                </tr>
512-
513-                <tr>
514-                    <td>summary</td>
515-                    <td>no</td>
516-                    <td>no</td>
517-                </tr>
518-
519-                <tr>
520-                    <td>tags</td>
521-                    <td>yes</td>
522-                    <td>-</td>
523-                </tr>
524-
525-                <tr>
526-                    <td>title</td>
527-                    <td>yes</td>
528-                    <td>-</td>
529-                </tr>
530-
531-                <tr>
532-                    <td>type</td>
533-                    <td>no</td>
534-                    <td>no</td>
535-                </tr>
536-
537-                <tr>
538-                    <td>url</td>
539-                    <td>no</td>
540-                    <td>no</td>
541-                </tr>
542-
543-                <tr>
544-                    <td>videos</td>
545-                    <td>no</td>
546-                    <td>no</td>
547-                </tr>
548-
549-                <tr>
550-                    <td>weight</td>
551-                    <td>no</td>
552-                    <td>no</td>
553-                </tr>
554-
555-                <tr>
556-                    <td>taxonomies</td>
557-                    <td>no</td>
558-                    <td>no</td>
559-                </tr>
560-            </tbody>
561-        </table>
562-    </section>
563-</main>
564-{{template "marketing-footer" .}}
565-{{end}}
M prose/html/marketing-footer.partial.tmpl
+1, -3
 1@@ -3,9 +3,7 @@
 2     <hr />
 3     <p class="font-italic">Built and maintained by <a href="https://pico.sh">pico.sh</a>.</p>
 4     <div>
 5-        <a href="/">home</a> |
 6-        <a href="/ops">ops</a> |
 7-        <a href="/help">help</a> |
 8+        <a href="/">discover</a> |
 9         <a href="/rss">rss</a> |
10         <a href="https://github.com/picosh/pico">source</a>
11     </div>
D prose/html/marketing.page.tmpl
+0, -167
  1@@ -1,167 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}{{.Site.Domain}} -- a blog platform for hackers{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="a blog platform for hackers" />
  8-
  9-<meta property="og:type" content="website">
 10-<meta property="og:site_name" content="{{.Site.Domain}}">
 11-<meta property="og:url" content="https://{{.Site.Domain}}">
 12-<meta property="og:title" content="{{.Site.Domain}}">
 13-<meta property="og:description" content="a blog platform for hackers">
 14-
 15-<meta name="twitter:card" content="summary" />
 16-<meta property="twitter:url" content="https://{{.Site.Domain}}">
 17-<meta property="twitter:title" content="{{.Site.Domain}}">
 18-<meta property="twitter:description" content="a blog platform for hackers">
 19-<meta name="twitter:image" content="https://{{.Site.Domain}}/card.png" />
 20-<meta name="twitter:image:src" content="https://{{.Site.Domain}}/card.png" />
 21-
 22-<meta property="og:image:width" content="300" />
 23-<meta property="og:image:height" content="300" />
 24-<meta itemprop="image" content="https://{{.Site.Domain}}/card.png" />
 25-<meta property="og:image" content="https://{{.Site.Domain}}/card.png" />
 26-{{end}}
 27-
 28-{{define "attrs"}}{{end}}
 29-
 30-{{define "body"}}
 31-<header class="text-center">
 32-    <h1 class="text-2xl font-bold">{{.Site.Domain}}</h1>
 33-    <p class="text-lg">a blog platform for hackers</p>
 34-    <p class="text-lg"><a href="/read">discover</a> some interesting posts</p>
 35-    <hr />
 36-</header>
 37-
 38-<main>
 39-    <section>
 40-        <h2 class="text-lg font-bold">Create your account with Public-Key Cryptography</h2>
 41-        <p>We don't want your email address.</p>
 42-        <p>To get started, simply ssh into our content management system:</p>
 43-        <pre>ssh new@{{.Site.Domain}}</pre>
 44-        <div class="text-sm font-italic note">
 45-            note: <code>new</code> is a special username that will always send you to account
 46-            creation, even with multiple accounts associated with your key-pair.
 47-        </div>
 48-        <div class="text-sm font-italic note">
 49-            note: getting permission denied? <a href="/help#permission-denied">read this</a>
 50-        </div>
 51-        <p>
 52-            After that, just set a username and you're ready to start writing! When you SSH
 53-            again, use your username that you set in the CMS.
 54-        </p>
 55-    </section>
 56-
 57-    <section>
 58-        <h2 class="text-lg font-bold">You control the source files</h2>
 59-        <p>Create posts using your favorite editor in plain text files.</p>
 60-        <code>~/blog/hello-world.md</code>
 61-        <pre># hello world!
 62-
 63-This is my first blog post.
 64-
 65-Check out some resources:
 66-
 67-- [pico.sh](https://pico.sh)
 68-- [lists.sh](https://lists.sh)
 69-- [antoniomika](https://antoniomika.me)
 70-- [erock.io](https://erock.io)
 71-
 72-Cya!
 73-</pre>
 74-    </section>
 75-
 76-    <section>
 77-        <h2 class="text-lg font-bold">Upload images for your blog</h2>
 78-        <p>
 79-            We also support image uploading (jpg, png, gif, webp, svg).  Simply upload your
 80-            images alongside your markdown files and then reference them from root `/`:
 81-        </p>
 82-        <pre>---
 83-title: hello world!
 84----
 85-
 86-        ![profile pic](/profile.jpg)</pre>
 87-        <pre>rsync ~/blog/hello-world.md ~/blog/profile.jpg {{.Site.Domain}}:/</pre>
 88-        <p>Read more about image uploading at [imgs.sh](https://imgs.sh) and our [help page](/help)</p>
 89-    </section>
 90-
 91-    <section>
 92-        <h2 class="text-lg font-bold">Publish your posts with one command</h2>
 93-        <p>
 94-            When your post is ready to be published, copy the file to our server with a familiar
 95-            command:
 96-        </p>
 97-        <pre>rsync ~/blog/*.md {{.Site.Domain}}:/</pre>
 98-        <p>We'll either create or update the posts for you.</p>
 99-    </section>
100-
101-    <section>
102-        <h2 class="text-lg font-bold">Terminal workflow without installation</h2>
103-        <p>
104-            Since we are leveraging tools you already have on your computer
105-            (<code>ssh</code> and <code>rsync</code>), there is nothing to install.
106-        </p>
107-        <p>
108-            This provides the convenience of a web app, but from inside your terminal!
109-        </p>
110-    </section>
111-
112-    <section>
113-        <h2 class="text-lg font-bold">Use <code>sftp</code> to manage your posts</h2>
114-        <p>
115-          FTP is back in style.  List, upload, and delete posts via <code>sftp</code>:
116-        </p>
117-        <pre>sftp {{.Site.Domain}}
118-sftp> ls
119-hello-world.md
120-sftp> rm hello-world.md
121-sftp> put hello-world.md</pre>
122-    </section>
123-
124-    <section>
125-        <h2 class="text-lg font-bold">Features</h2>
126-        <ul>
127-            <li>Github flavor markdown</li>
128-            <li><a href="/help#custom-domain">Custom domains</a></li>
129-            <li>Looks great on any device</li>
130-            <li>Bring your own editor</li>
131-            <li>You control the source files</li>
132-            <li>Terminal workflow with no installation</li>
133-            <li>Public-key based authentication</li>
134-            <li>Use sftp to manage blog</li>
135-            <li>No ads, zero browser-based tracking</li>
136-            <li>No attempt to identify users</li>
137-            <li>No platform lock-in</li>
138-            <li>No javascript</li>
139-            <li>Subscriptions via RSS</li>
140-            <li>Minimalist design</li>
141-            <li>100% open source</li>
142-        </ul>
143-    </section>
144-
145-    <section>
146-        <h2 class="text-lg font-bold">Philosophy</h2>
147-        <p>
148-            The goal of this blogging platform is to make it simple to use the tools you love to
149-            write and publish your thoughts.  There is no installation, signup is as easy as SSH'ing
150-            into our CMS, and publishing content is as easy as copying files to our server.
151-        </p>
152-
153-        <p>
154-            If you'd like to read more about our group, please read our profile at <a href="https://pico.sh">pico.sh</a>.
155-        </p>
156-    </section>
157-
158-    <section>
159-        <h2 class="text-lg font-bold">Roadmap</h2>
160-        <ol>
161-            <li>Writing competitions</li>
162-            <li><a href="/help#hugo-support">Limited compatibility</a> with <a href="https://gohugo.io">hugo</a></li>
163-        </ol>
164-    </section>
165-</main>
166-
167-{{template "marketing-footer" .}}
168-{{end}}
D prose/html/ops.page.tmpl
+0, -147
  1@@ -1,147 +0,0 @@
  2-{{template "base" .}}
  3-
  4-{{define "title"}}operations -- {{.Site.Domain}}{{end}}
  5-
  6-{{define "meta"}}
  7-<meta name="description" content="{{.Site.Domain}} operations" />
  8-{{end}}
  9-
 10-{{define "attrs"}}{{end}}
 11-
 12-{{define "body"}}
 13-<header>
 14-    <h1 class="text-2xl">Operations</h1>
 15-    <ul>
 16-        <li><a href="/privacy">privacy</a></li>
 17-        <li><a href="/transparency">transparency</a></li>
 18-    </ul>
 19-</header>
 20-<main>
 21-    <section>
 22-        <h2 class="text-xl">Purpose</h2>
 23-        <p>
 24-            {{.Site.Domain}} exists to allow people to create and share their thoughts
 25-            without the need to set up their own server or be part of a platform
 26-            that shows ads or tracks its users.
 27-        </p>
 28-    </section>
 29-    <section>
 30-        <h2 class="text-xl">Ethics</h2>
 31-        <p>We are committed to:</p>
 32-        <ul>
 33-            <li>No browser-based tracking of visitor behavior.</li>
 34-            <li>No attempt to identify users.</li>
 35-            <li>Never sell any user or visitor data.</li>
 36-            <li>No ads — ever.</li>
 37-        </ul>
 38-    </section>
 39-    <section>
 40-        <h2 class="text-xl">Code of Content Publication</h2>
 41-        <p>
 42-            Content in {{.Site.Domain}} blogs is unfiltered and unmonitored. Users are free to publish any
 43-            combination of words and pixels except for: content of animosity or disparagement of an
 44-            individual or a group on account of a group characteristic such as race, color, national
 45-            origin, sex, disability, religion, or sexual orientation, which will be taken down
 46-            immediately.
 47-        </p>
 48-        <p>
 49-            If one notices something along those lines in a blog please let us know at
 50-            <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>.
 51-        </p>
 52-    </section>
 53-    <section>
 54-        <h2 class="text-xl">Liability</h2>
 55-        <p>
 56-            The user expressly understands and agrees that Eric Bower and Antonio Mika, the operator of this website
 57-            shall not be liable, in law or in equity, to them or to any third party for any direct,
 58-            indirect, incidental, lost profits, special, consequential, punitive or exemplary damages.
 59-        </p>
 60-    </section>
 61-    <section>
 62-        <h2 class="text-xl">Analytics</h2>
 63-        <p>
 64-            We are committed to zero browser-based tracking or trying to identify visitors.  This
 65-            means we do not try to understand the user based on cookies or IP address.  We do not
 66-            store personally identifiable information.
 67-        </p>
 68-        <p>
 69-            However, in order to provide a better service, we do have some analytics on posts.
 70-            List of metrics we track for posts:
 71-        </p>
 72-        <ul>
 73-            <li>anonymous view counts</li>
 74-        </ul>
 75-        <p>
 76-            We might also inspect the headers of HTTP requests to determine some tertiary information
 77-            about the request.  For example we might inspect the <code>User-Agent</code> or
 78-            <code>Referer</code> to filter out requests from bots.
 79-        </p>
 80-    </section>
 81-    <section>
 82-        <h2 class="text-xl">Account Terms</h2>
 83-        <p>
 84-            <ul>
 85-                <li>
 86-                    The user is responsible for all content posted and all actions performed with
 87-                    their account.
 88-                </li>
 89-                <li>
 90-                    We reserve the right to disable or delete a user's account for any reason at
 91-                    any time. We have this clause because, statistically speaking, there will be
 92-                    people trying to do something nefarious.
 93-                </li>
 94-            </ul>
 95-        </p>
 96-    </section>
 97-    <section>
 98-        <h2 class="text-xl">Service Availability</h2>
 99-        <p>
100-         We provide the {{.Site.Domain}} service on an "as is" and "as available" basis. We do not offer
101-         service-level agreements but do take uptime seriously.
102-        </p>
103-    </section>
104-    <section>
105-        <h2 class="text-xl">Contact and Support</h2>
106-        <p>
107-            Email us at <a href="mailto:{{.Site.Email}}">{{.Site.Email}}</a>
108-            with any questions.
109-        </p>
110-    </section>
111-    <section>
112-        <h2 class="text-xl">Acknowledgments</h2>
113-        <p>
114-            {{.Site.Domain}} was inspired by <a href="https://mataroa.blog">Mataroa Blog</a>
115-            and <a href="https://bearblog.dev/">Bear Blog</a>.
116-        </p>
117-        <p>
118-            {{.Site.Domain}} is built with many open source technologies.
119-        </p>
120-        <p>
121-            In particular we would like to thank:
122-        </p>
123-        <ul>
124-            <li>
125-                <span>The </span>
126-                <a href="https://charm.sh">charm.sh</a>
127-                <span> community</span>
128-            </li>
129-            <li>
130-                <span>The </span>
131-                <a href="https://go.dev">golang</a>
132-                <span> community</span>
133-            </li>
134-            <li>
135-                <span>The </span>
136-                <a href="https://www.postgresql.org/">postgresql</a>
137-                <span> community</span>
138-            </li>
139-            <li>
140-                <span>The </span>
141-                <a href="https://github.com/caddyserver/caddy">caddy</a>
142-                <span> community</span>
143-            </li>
144-        </ul>
145-    </section>
146-</main>
147-{{template "marketing-footer" .}}
148-{{end}}
D prose/html/privacy.page.tmpl
+0, -54
 1@@ -1,54 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}privacy -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="{{.Site.Domain}} privacy policy" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-    <h1 class="text-2xl">Privacy</h1>
15-    <p>Details on our privacy and security approach.</p>
16-</header>
17-<main>
18-    <section>
19-        <h2 class="text-xl">Account Data</h2>
20-        <p>
21-            In order to have a functional account at {{.Site.Domain}}, we need to store
22-            your public key.  That is the only piece of information we record for a user.
23-        </p>
24-        <p>
25-            Because we use public-key cryptography, our security posture is a battle-tested
26-            and proven technique for authentication.
27-        </p>
28-    </section>
29-
30-    <section>
31-        <h2 class="text-xl">Third parties</h2>
32-        <p>
33-            We have a strong commitment to never share any user data with any third-parties.
34-        </p>
35-    </section>
36-
37-    <section>
38-        <h2 class="text-xl">Service Providers</h2>
39-        <ul>
40-            <li>
41-                <span>We host our server on </span>
42-                <a href="https://www.oracle.com/cloud/">oracle cloud</a>
43-            </li>
44-        </ul>
45-    </section>
46-
47-    <section>
48-        <h2 class="text-xl">Cookies</h2>
49-        <p>
50-            We do not use any cookies, not even account authentication.
51-        </p>
52-    </section>
53-</main>
54-{{template "marketing-footer" .}}
55-{{end}}
M prose/html/read.page.tmpl
+5, -3
 1@@ -11,9 +11,11 @@
 2 
 3 {{define "body"}}
 4 <header class="text-center">
 5-    <h1 class="text-2xl font-bold">read</h1>
 6-    <p class="text-lg">recent posts</p>
 7-    <p class="text-lg"><a href="/rss">rss</a></p>
 8+    <h1 class="text-2xl font-bold">prose.sh</h1>
 9+    <p class="text-lg">A blog platform for hackers</p>
10+    <div>
11+      <a href="https://pico.sh/getting-started" class="btn-link mt inline-block">GET STARTED</a>
12+    </div>
13     <hr />
14 </header>
15 <main>
D prose/html/transparency.page.tmpl
+0, -59
 1@@ -1,59 +0,0 @@
 2-{{template "base" .}}
 3-
 4-{{define "title"}}transparency -- {{.Site.Domain}}{{end}}
 5-
 6-{{define "meta"}}
 7-<meta name="description" content="full transparency of analytics and cost at {{.Site.Domain}}" />
 8-{{end}}
 9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header>
14-    <h1 class="text-2xl">Transparency</h1>
15-    <hr />
16-</header>
17-<main>
18-    <section>
19-        <h2 class="text-xl">Analytics</h2>
20-        <p>
21-            Here are some interesting stats on usage.
22-        </p>
23-
24-        <article>
25-            <h2 class="text-lg">Total users</h2>
26-            <div>{{.Analytics.TotalUsers}}</div>
27-        </article>
28-
29-        <article>
30-            <h2 class="text-lg">New users in the last month</h2>
31-            <div>{{.Analytics.UsersLastMonth}}</div>
32-        </article>
33-
34-        <article>
35-            <h2 class="text-lg">Total posts</h2>
36-            <div>{{.Analytics.TotalPosts}}</div>
37-        </article>
38-
39-        <article>
40-            <h2 class="text-lg">New posts in the last month</h2>
41-            <div>{{.Analytics.PostsLastMonth}}</div>
42-        </article>
43-
44-        <article>
45-            <h2 class="text-lg">Users with at least one post</h2>
46-            <div>{{.Analytics.UsersWithPost}}</div>
47-        </article>
48-    </section>
49-
50-    <section>
51-        <h2 class="text-xl">Service maintenance costs</h2>
52-        <ul>
53-            <li>Domain name $3.25/mo</li>
54-            <li>Server $0.00/mo</li>
55-            <li>Programmer $0.00/mo</li>
56-        </ul>
57-    </section>
58-</main>
59-{{template "marketing-footer" .}}
60-{{end}}
M prose/public/main.css
+23, -0
 1@@ -17,6 +17,7 @@
 2 
 3 @media (prefers-color-scheme: light) {
 4   :root {
 5+    --main-hue: 250;
 6     --white: #6a737d;
 7     --code: #fff8d3;
 8     --code-border: #f0d547;
 9@@ -34,6 +35,7 @@
10 
11 @media (prefers-color-scheme: dark) {
12   :root {
13+    --main-hue: 250;
14     --white: #f2f2f2;
15     --code: #414558;
16     --code-border: #252525;
17@@ -271,6 +273,23 @@ figure {
18   margin: 0;
19 }
20 
21+.btn-link {
22+  border: 3px solid hsl(var(--main-hue), 92%, 66%);
23+  background-color: hsl(var(--main-hue), 92%, 66%);
24+  padding: 0.5rem 1rem;
25+  border-radius: 0.25rem;
26+  box-shadow: 0px 1px 2px 0px black;
27+  color: var(--white);
28+  text-decoration: none;
29+  font-weight: bold;
30+}
31+
32+.btn-link:visited,
33+.btn-link:visited:hover,
34+.btn-link:hover {
35+  color: var(--white);
36+}
37+
38 .post-date {
39   width: 130px;
40 }
41@@ -319,6 +338,10 @@ figure {
42   display: inline;
43 }
44 
45+.inline-block {
46+  display: inline-block;
47+}
48+
49 .flex {
50   display: flex;
51 }
M shared/router.go
+1, -1
1@@ -142,7 +142,7 @@ func GetCustomDomain(logger *zap.SugaredLogger, host string, space string) strin
2 	txt := fmt.Sprintf("_%s.%s", space, host)
3 	records, err := net.LookupTXT(txt)
4 	if err != nil {
5-		logger.Error(err)
6+		logger.Info(err)
7 		return ""
8 	}
9 
M smol.css
+23, -0
 1@@ -17,6 +17,7 @@
 2 
 3 @media (prefers-color-scheme: light) {
 4   :root {
 5+    --main-hue: 250;
 6     --white: #6a737d;
 7     --code: #fff8d3;
 8     --code-border: #f0d547;
 9@@ -34,6 +35,7 @@
10 
11 @media (prefers-color-scheme: dark) {
12   :root {
13+    --main-hue: 250;
14     --white: #f2f2f2;
15     --code: #414558;
16     --code-border: #252525;
17@@ -271,6 +273,23 @@ figure {
18   margin: 0;
19 }
20 
21+.btn-link {
22+  border: 3px solid hsl(var(--main-hue), 92%, 66%);
23+  background-color: hsl(var(--main-hue), 92%, 66%);
24+  padding: 0.5rem 1rem;
25+  border-radius: 0.25rem;
26+  box-shadow: 0px 1px 2px 0px black;
27+  color: var(--white);
28+  text-decoration: none;
29+  font-weight: bold;
30+}
31+
32+.btn-link:visited,
33+.btn-link:visited:hover,
34+.btn-link:hover {
35+  color: var(--white);
36+}
37+
38 .post-date {
39   width: 130px;
40 }
41@@ -319,6 +338,10 @@ figure {
42   display: inline;
43 }
44 
45+.inline-block {
46+  display: inline-block;
47+}
48+
49 .flex {
50   display: flex;
51 }