adjust frontmatter; new post
This commit is contained in:
parent
f12d5e7803
commit
4521766aba
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -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" ]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
[[redirects]]
|
||||||
|
from = "/.well-known/webfinger"
|
||||||
|
to = "/.well-known/webfinger.json"
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
---
|
---
|
||||||
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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -55,7 +54,7 @@ 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:
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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),
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue