, 5 min read
Hosting Static Content with pico.sh
pico.sh is a similar blogging platform like Bearblog. In contrast to Bearblog the upload of Markdown content is done using ssh/scp/sftp.
I wrote about hosting static sites on various platforms:
- Hosting Static Content with surge.sh
- Hosting Static Content with now.sh, now.sh renamed themself to vercel.app
- Hosting Static Content with netlify.app
- Hosting Static Content with Cloudflare
- Hosting Static Content with Neocities
- Hosting Static Content with GitLab
- Hosting Static Content with GitHub
- Hosting Static Content with DigitalOcean
1. Installation
There is no installation. You just ssh to pico.sh:
ssh pico.sh
and register your public SSH keys, which is done automatically.
In the rare case that you have no private and public key, just start ssh-keygen
and generate those two files.
Once connected to pico.sh, you choose a subdomain beneath pico.sh
.
In my case I chose klm
, i.e., I end up at https://klm.prose.sh.
pico.sh uses a custom built sshd:
By using the SSH protocol and Go's implementation of SSH, we can create Go binaries that interface with SSH in unique ways. Further, we are inside the context of a Go binary, not a traditional SSH session where the user could figure out how to execute arbitrary commands.
charm.sh wish is the underlying library we use to enable all our SSH apps to work seamlessly with SSH clients. Wish lets end-developers construct an SSH server with a middleware stack. This makes it easy to programmatically compose and extend a traditional SSH server.
2. Blogging
You just write Markdown files, i.e., files ending with .md
and upload them via
scp your-markdown.md prose.sh:/
That's it.
You can also use rsync
or sftp
.
Remarkably, you can also use sshfs.
This allows you directly use all shell utilites like diff
, comm
, grep
on the files on the remote site.
In case you want to delete a file, use sftp
:
sftp prose.sh
rm your-markdown.md
Uploading your content via ssh is quite refreshing as everyone else is following the Git craze.
Git Out of Here
I also used to keep my website in a git repository. This made it so I could keep around a log of changes, dig around in the history to roll something back, accept changesets from people to fix things, and it acted kind of like another off-site backup.
But it was completely useless.
Maintaining good git hygiene really just became annoying. Checking in atomic changes and coming up with commit messages was way too onerous (I just resorted to “*“ after a while), and I’ve never needed to roll things back in the 5 years this site has been online. When I wanted to get rid of something it’s because I actually wanted it gone, and a git history negated that completely too.
...
Plus, absolutely nobody is collaborating with me on my own website—any time people have noticed spelling mistakes or something they’d just email me or tell me on Fedi rather than sending me an entire pull request e-mail changeset diff to fix one single missing letter.
The git hammer didn’t apply to this particular nail, and I could finally be released from yet another self-imposed obligation.
3. Migrating existing blog
My current blog contains a hierarchy of posts:
pico.sh does not allow a hierarchical structure.
You have to flatten the structure.
Below Perl program blogpicosh
does that and copes for another limitation in the description
field in the frontmatter.
#!/bin/perl -W
# Flatten directory structure, restrict to pico.sh acceptable Markdown
# Get list of files from command line and convert each
use strict;
my $t;
mkdir '/tmp/pico.sh' if (! -d '/tmp/pico.sh');
while (<@ARGV>) {
my ($ifn,$ofn) = ($_, $_);
$ofn =~ s/^\.\///; # strip ./
my $url = $ofn;
$url =~ s/(|index)\.md$//;
$ofn =~ s/\//\-/g; # change forward slash to dash
$ofn = '/tmp/pico.sh/' . $ofn;
open(F,"< $ifn") || die("Cannot open $ifn for reading");
open(G,"> $ofn") || die("Cannot open $ofn for writing");
my $flag = 0;
while (<F>) {
chomp;
s/\s+$//;
++$flag if (/^\-\-\-$/);
if (/description:\s+"([^"]+)"$/) {
my $desc = $1;
$desc = substr($desc,0,147) . '...' if (length($desc) > 150);
$_ = sprintf("description: \"%s\"", $desc);
} elsif (/^```\w+/ && ($t = index($_,'[data-line=')) > 0) {
$_ = substr($_,0,$t);
}
while ( /\(\*<\?=\$rbase\?>\*\/([^)]+)\)/g ) {
$t = $1;
if ($t =~ /(aux|blog|gallery|music)/) {
$t =~ s/\//\-/g; # change forward slash to dash for internal links
$t = '/' . $t;
} else {
$t = 'https://eklausmeier.goip.de/' . $t;
}
s/\(\*<\?=\$rbase\?>\*\/[^)]+\)/\($t\)/;
}
print G $_ . "\n";
if ($flag == 2) {
$flag += 10;
print G "\nOriginal post is here: [eklausmeier.goip.de](https://eklausmeier.goip.de/$url)\n\n\n";
}
}
close(G) || die("Cannot close output $ofn");
close(F) || die("Cannot close input $ifn");
}
Running this program goes like this:
blogpicosh `find . -iname \*.md`
Above Perl program stores all files in the /tmp/pico.sh
directory.
Finally you transfer all those files to pico.sh.
$ time scp *.md prose.sh:/
aux-about.md 100% 4040 41.4KB/s 00:00
https://klm.prose.sh/aux-about
aux-blogroll.md
...
music-2024-10-22-music-from-ernesto-lecuona.md 100% 674 7.1KB/s 00:00
https://klm.prose.sh/music-2024-10-22-music-from-ernesto-lecuona
music-2024-11-01-music-from-regino-sainz-de-la-maza.md 100% 659 6.9KB/s 00:00
https://klm.prose.sh/music-2024-11-01-music-from-regino-sainz-de-la-maza
music-2024-index.md 100% 1757 18.2KB/s 00:00
https://klm.prose.sh/music-2024-index
real 336.73s
user 0.16s
sys 0
swapped 0
total space 0
4. Limitations
Unfortunately, pico.sh in the form of prose.sh has a number of limitations:
description
tag must not contain more than 150 characters- Adding a programming language to triple backquote is fine, but adding something like
[data-line=...]
destroys all syntax coloring - No directory structure beneath root
- No MathJax, no Mermaid, no YouTube embeds, no markmap, etc.
- No HTML is honored in Markdown
- No multiblog functionality, i.e., multiple independent blogs under one umbrella
- Only 250 posts are shown in the index. If you have more posts, they are simply suppressed in the index, even though the Markdown files are actually stored on pico.sh. There is no previous or next paging implemented. However, all posts are theoretically accessible, if you know their URL.
- The standard CSS cuts off parts of the first digit and supresses second and higher digits for enumerations.
- The generated HTML produces HTML errors and warnings in W3 validator. See, for example, Hosting Static Content with pico.sh.