adjust frontmatter; new post

This commit is contained in:
Chris W 2023-10-17 13:01:10 -06:00
parent f12d5e7803
commit 4521766aba
13 changed files with 186 additions and 24 deletions

View File

@ -0,0 +1 @@
{}

37
frontmatter.json Normal file
View File

@ -0,0 +1,37 @@
{
"frontMatter.taxonomy.contentTypes": [
{
"name": "default",
"pageBundle": false,
"previewPath": "'blog'",
"filePrefix": null,
"fields": [
{
"title": "Title",
"name": "title",
"type": "string",
"single": true
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",
"name": "heroImage",
"type": "image",
"isPreviewImage": true
}
]
}
],
"frontMatter.content.supportedFileTypes": [ "md", "markdown", "mdx" ]
}

3
netlify.toml Normal file
View File

@ -0,0 +1,3 @@
[[redirects]]
from = "/.well-known/webfinger"
to = "/.well-known/webfinger.json"

0
paste69.sql Normal file
View File

View File

@ -8,7 +8,7 @@ export interface Props {
} }
export default function Card({ href, frontmatter, secHeading = true }: Props) { export default function Card({ href, frontmatter, secHeading = true }: Props) {
const { title, pubDatetime, description } = frontmatter; const { title, date, description } = frontmatter;
return ( return (
<li className="my-6"> <li className="my-6">
<a <a
@ -25,7 +25,7 @@ export default function Card({ href, frontmatter, secHeading = true }: Props) {
</h3> </h3>
)} )}
</a> </a>
<Datetime datetime={pubDatetime} /> <Datetime datetime={date} />
<p>{description}</p> <p>{description}</p>
</li> </li>
); );

View File

@ -3,7 +3,7 @@ import { z } from "astro:content";
export const blogSchema = z export const blogSchema = z
.object({ .object({
author: z.string().optional(), author: z.string().optional(),
pubDatetime: z.date(), date: z.date(),
title: z.string(), title: z.string(),
postSlug: z.string().optional(), postSlug: z.string().optional(),
featured: z.boolean().optional(), featured: z.boolean().optional(),

View File

@ -1,12 +1,11 @@
--- ---
author: Chris W author: Chris W
pubDatetime: 2022-12-07 date: 2022-12-07
title: An introduction to the Crystal standard library and its core modules title: An introduction to the Crystal standard library and its core modules
description: description: Introducing newcomers to the Crystal standard library and some of the most useful modules contained therein
Introducing newcomers to the Crystal standard library and some of the most useful modules contained therein
tags: tags:
- crystal - crystal
- programming - programming
--- ---
## Table of contents ## Table of contents
@ -19,7 +18,7 @@ One of the greatest strengths of Crystal lies in its standard library, or the mo
One of Crystal's biggest strengths is its ability to bridge the gap between dynamic languages like Ruby and Python, and typed languages like Rust and C when it comes to web development. The reason this is possible is because of its amazing HTTP module, which comes with a [Client](https://crystal-lang.org/api/1.6.2/HTTP/Client.html) for making requests, and a [Server](https://crystal-lang.org/api/1.6.2/HTTP/Server.html) for receiving them. As a matter of fact, the home page of Crystal's website shows a very simple HTTP server in action, and it really could not be simpler. One of Crystal's biggest strengths is its ability to bridge the gap between dynamic languages like Ruby and Python, and typed languages like Rust and C when it comes to web development. The reason this is possible is because of its amazing HTTP module, which comes with a [Client](https://crystal-lang.org/api/1.6.2/HTTP/Client.html) for making requests, and a [Server](https://crystal-lang.org/api/1.6.2/HTTP/Server.html) for receiving them. As a matter of fact, the home page of Crystal's website shows a very simple HTTP server in action, and it really could not be simpler.
``` crystal ```crystal
# A very basic HTTP server # A very basic HTTP server
require "http/server" require "http/server"
@ -32,7 +31,7 @@ puts "Listening on http://127.0.0.1:8080"
server.listen(8080) server.listen(8080)
``` ```
Running that code will leave you with a very basic server running on port `8080` which will return "Hello world, got _\[some path\]_!" for every path you hit. Of course, using the built-in HTTP server isn't the only way to use Crystal for a website. There are a myriad of options from [Kemal]((https://kemalcr.com/)) and [Grip](https://github.com/grip-framework/grip) which are both very Sinatra/Flask like, to [Lucky](https://luckyframework.org/) which is closer to Rails (not at all in design, but it takes the batteries included approach). Running that code will leave you with a very basic server running on port `8080` which will return "Hello world, got _\[some path\]_!" for every path you hit. Of course, using the built-in HTTP server isn't the only way to use Crystal for a website. There are a myriad of options from [Kemal](<(https://kemalcr.com/)>) and [Grip](https://github.com/grip-framework/grip) which are both very Sinatra/Flask like, to [Lucky](https://luckyframework.org/) which is closer to Rails (not at all in design, but it takes the batteries included approach).
Of course, the HTTP module would be nothing without Of course, the HTTP module would be nothing without
@ -42,7 +41,7 @@ Some may see this as one of the more boring classes, but I have used IO so much
`IO::Memory` can be used as a sort of in-memory file descriptor, but what I've found to be one of the most useful (albeit niche) parts of IO is `IO::ByteFormat` which allows you to encode and decode integers to/from `Bytes` and `IO`. This is extremely useful when implementing things such as protocol buffers and RPC. And look how easy it is: `IO::Memory` can be used as a sort of in-memory file descriptor, but what I've found to be one of the most useful (albeit niche) parts of IO is `IO::ByteFormat` which allows you to encode and decode integers to/from `Bytes` and `IO`. This is extremely useful when implementing things such as protocol buffers and RPC. And look how easy it is:
``` crystal ```crystal
io = IO::Memory.new io = IO::Memory.new
io.write_bytes(0x1234_i16, IO::ByteFormat::LittleEndian) io.write_bytes(0x1234_i16, IO::ByteFormat::LittleEndian)
io.to_slice # => Bytes[0x34, 0x12] io.to_slice # => Bytes[0x34, 0x12]
@ -55,11 +54,11 @@ A bit more verbose than I'd prefer, but you can't argue with results.
## JSON ## JSON
What would a web-centric language be without support for JSON. Now I'm not saying that Crystal is intentionally web-centric, but it is filling a hole that Ruby leaves by being slow as molasses, and Ruby is used *heavily* for web development. What would a web-centric language be without support for JSON. Now I'm not saying that Crystal is intentionally web-centric, but it is filling a hole that Ruby leaves by being slow as molasses, and Ruby is used _heavily_ for web development.
Working with JSON in statically typed languages can be a massive pain, because JSON is, by its very nature, untyped and kind of unsafe to deal with. Before finding Crystal I loved the way Go handled JSON (de)serialization. As an example for those unfamiliar: Working with JSON in statically typed languages can be a massive pain, because JSON is, by its very nature, untyped and kind of unsafe to deal with. Before finding Crystal I loved the way Go handled JSON (de)serialization. As an example for those unfamiliar:
``` go ```go
type User struct { type User struct {
Name string `json:"full_name"` Name string `json:"full_name"`
Age int `json:"age,omitempty"` Age int `json:"age,omitempty"`
@ -72,7 +71,7 @@ As you can see Go uses "tags" to change how the JSON data is transformed when it
Now I'll show a similar example using Crystal: Now I'll show a similar example using Crystal:
``` crystal ```crystal
struct User struct User
include JSON::Serializable include JSON::Serializable
@ -93,7 +92,7 @@ Things are a bit different here, partially because different assumptions have to
Parsing an incoming JSON object as a `User` would then be as simple as: Parsing an incoming JSON object as a `User` would then be as simple as:
``` crystal ```crystal
user = User.from_json(json_string) user = User.from_json(json_string)
``` ```
@ -109,7 +108,7 @@ One thing that's important to note is that Crystal does use an `Int64` for repre
The entire `Time` module is full of so much syntactic sugar you might leave with a toothache, but I find it to be extremely useful if you want to work with time in an idiomatic and easy to understand manner. For instance: The entire `Time` module is full of so much syntactic sugar you might leave with a toothache, but I find it to be extremely useful if you want to work with time in an idiomatic and easy to understand manner. For instance:
``` crystal ```crystal
time = Time.utc(2016, 2, 15, 10, 20, 30) time = Time.utc(2016, 2, 15, 10, 20, 30)
time.year # => 2016 time.year # => 2016
time.month # => 2 time.month # => 2
@ -127,7 +126,7 @@ time.time_of_day # => 10:20:30
You can also do math with time. You can also do math with time.
``` crystal ```crystal
Time.utc + 3.days Time.utc + 3.days
Time.utc - 14.years Time.utc - 14.years
# etc etc # etc etc
@ -135,7 +134,7 @@ Time.utc - 14.years
Need to localize things to a specific timezone? No problem. Need to localize things to a specific timezone? No problem.
``` crystal ```crystal
time = Time.local(2016, 2, 15, 10, 20, 30, location: Time::Location.load("Europe/Berlin")) time = Time.local(2016, 2, 15, 10, 20, 30, location: Time::Location.load("Europe/Berlin"))
time.inspect # => "2016-02-15 10:20:30.0 +01:00 Europe/Berlin" time.inspect # => "2016-02-15 10:20:30.0 +01:00 Europe/Berlin"
``` ```

View File

@ -0,0 +1,72 @@
---
author: Chris W
date: 2023-10-17T18:53:50.702Z
title: Migrating your Redis database to another server
description: How to migrate your Redis database to another server using replication
tags:
- redis
- servers
- databases
---
## Table of contents
Seeing this title you might be thinking to yourself, _why would I ever need to do that?_. After all, redis is meant to be used as a throwaway cache right? Caches by their very nature are generally disposable, so what would posess you to want to migrate that cache somewhere else? Well, I can't speak for you, but I can tell you why I needed to do it.
I currently host my own [Firefish](https://joinfirefish.org) instance and have been doing so for the last couple months. In that couple of months I have moved from [Caprover](https://caprover.com), to [Coolify](https://coolify.io), to finally running it in its own VPS. Why all the moving? Because as it turns out, something like Firefish likes to be on its own, especially if you plan on opening your instance up to other people.
Firefish relies on two databases. Postgres for standard data storage, and Redis for caching. The Postgres database is easy enough to migrate, I even wrote a [simple bash script](https://0x45.st/mostly-eventually-themselves.bash) to make it even easier. Redis, however, doesn't have an easy way to dump all of its data to a file, and it doesn't have a way to import that data either. It does have something that might be even easier though.
### Redis replication
Redis has a built-in replication system that allows you to replicate a master database to one or more slave databases. This is useful for a number of reasons, but the one we're interested in is the ability to replicate a database to another server. This is exactly what we need to do in order to migrate our Redis database to another server.
Thankfully this can also be done without editing any configuration files. All you need is access to `redis-cli` on the new server, and a way to connect to the old server. This means that your old server does have to, at least temporarily, be accessible from the outside world (though this can be done over a VPN, or using an internal network). Once you have access to both servers, open a shell in the new server and run the following command:
```bash
redis-cli
```
This will open the redis command line interface. From here we can run the `REPLICAOF` command to tell redis to replicate a master database. The syntax is as follows:
```bash
REPLICAOF <masterip> <masterport>
```
So if your master server is at `10.0.0.2` and is running on port `6379`, you would run the following command:
```bash
REPLICAOF 10.0.0.2 6379
```
This will immediately start the replication process. You can check the status of the replication by running the `INFO` command. This will give you a lot of information, but the part we're interested in is the `master_link_status` field. If this is `up` then the replication is working. If it's `down` then something went wrong.
You can also check the status of the incoming keys by listing all the keys:
```bash
KEYS *
```
This will list all the keys in the database. If you see the keys you expect to see, then the replication is working. If you don't see any keys, then something went wrong.
### Authentication
If you have authentication enabled on your master server via `requirepass`, you'll need to provide the password to the replica server. This can be done by running the following command:
```bash
config set masterauth <password>
```
### Stopping replication
Once you're done with the replication, you can stop it by running the following command:
```bash
REPLICAOF NO ONE
```
This will stop the replication process and allow you to use the replica server as a standalone server. From this point you should be free to delete the master server, or do whatever you want with it.
## Conclusion
I hope this post was helpful to you. I know it's a bit of a niche topic, but I had a hard time finding any information on how to do this, so I figured I'd write it up in case anyone else needs to do this in the future.

View File

@ -77,6 +77,7 @@ const socialImageURL = new URL(
<!-- Matomo --> <!-- Matomo -->
<script> <script>
// @ts-ignore
var _paq = window._paq = window._paq || []; var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */ /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']); _paq.push(['trackPageView']);
@ -86,6 +87,7 @@ const socialImageURL = new URL(
_paq.push(['setTrackerUrl', u+'matomo.php']); _paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '1']); _paq.push(['setSiteId', '1']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
// @ts-ignore
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})(); })();
</script> </script>

View File

@ -13,7 +13,7 @@ export interface Props {
const { post } = Astro.props; const { post } = Astro.props;
const { title, author, description, ogImage, canonicalURL, pubDatetime, tags } = post.data; const { title, author, description, ogImage, canonicalURL, date, tags } = post.data;
const { Content } = await post.render(); const { Content } = await post.render();
@ -37,7 +37,7 @@ const ogUrl = new URL(ogImage ? ogImage : `${title}.png`, Astro.url.origin)
</div> </div>
<main id="main-content"> <main id="main-content">
<h1 class="post-title">{title}</h1> <h1 class="post-title">{title}</h1>
<Datetime datetime={pubDatetime} size="lg" className="my-2" /> <Datetime datetime={date} size="lg" className="my-2" />
<article id="article" role="article" class="prose mx-auto mt-8 max-w-3xl"> <article id="article" role="article" class="prose mx-auto mt-8 max-w-3xl">
<Content /> <Content />
</article> </article>
@ -45,6 +45,27 @@ const ogUrl = new URL(ogImage ? ogImage : `${title}.png`, Astro.url.origin)
<ul class="tags-container"> <ul class="tags-container">
{tags.map(tag => <Tag name={slugifyStr(tag)} />)} {tags.map(tag => <Tag name={slugifyStr(tag)} />)}
</ul> </ul>
<div id="coral_thread"></div>
<script type="text/javascript" define:vars={{ canonicalURL, postID: post.id }}>
(function() {
var d = document, s = d.createElement('script');
s.src = 'https://coral.watzon.tech/assets/js/embed.js';
s.async = false;
s.defer = true;
s.onload = function() {
Coral.createStreamEmbed({
id: "coral_thread",
autoRender: true,
rootURL: 'https://coral.watzon.tech',
storyID: `${postID}`,
storyURL: `${canonicalURL}`,
});
};
(d.head || d.body).appendChild(s);
})();
</script>
</main> </main>
<Footer /> <Footer />
</Layout> </Layout>

View File

@ -15,7 +15,7 @@ export async function get() {
link: `posts/${slugify(data)}`, link: `posts/${slugify(data)}`,
title: data.title, title: data.title,
description: data.description, description: data.description,
pubDate: new Date(data.pubDatetime), pubDate: new Date(data.date),
})), })),
}); });
} }

View File

@ -0,0 +1,27 @@
export async function get() {
return {
body: JSON.stringify({
subject: "acct:watzon@watzonmanor.com",
aliases: [
"https://watzonmanor.com/@watzon",
"https://watzonmanor.com/users/watzon",
],
links: [
{
rel: "http://webfinger.net/rel/profile-page",
type: "text/html",
href: "https://watzonmanor.com/@watzon",
},
{
rel: "self",
type: "application/activity+json",
href: "https://watzonmanor.com/users/watzon",
},
{
rel: "http://ostatus.org/schema/1.0/subscribe",
template: "https://watzonmanor.com/authorize_interaction?uri={uri}",
},
],
}),
};
}

View File

@ -5,8 +5,8 @@ const getSortedPosts = (posts: CollectionEntry<"blog">[]) =>
.filter(({ data }) => !data.draft) .filter(({ data }) => !data.draft)
.sort( .sort(
(a, b) => (a, b) =>
Math.floor(new Date(b.data.pubDatetime).getTime() / 1000) - Math.floor(new Date(b.data.date).getTime() / 1000) -
Math.floor(new Date(a.data.pubDatetime).getTime() / 1000) Math.floor(new Date(a.data.date).getTime() / 1000)
); );
export default getSortedPosts; export default getSortedPosts;