diff --git a/.frontmatter/database/taxonomyDb.json b/.frontmatter/database/taxonomyDb.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.frontmatter/database/taxonomyDb.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/frontmatter.json b/frontmatter.json new file mode 100644 index 0000000..177cbb1 --- /dev/null +++ b/frontmatter.json @@ -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" ] +} \ No newline at end of file diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..61e4f2f --- /dev/null +++ b/netlify.toml @@ -0,0 +1,3 @@ +[[redirects]] + from = "/.well-known/webfinger" + to = "/.well-known/webfinger.json" diff --git a/paste69.sql b/paste69.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Card.tsx b/src/components/Card.tsx index dd58a7f..8f99d9b 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -8,7 +8,7 @@ export interface Props { } export default function Card({ href, frontmatter, secHeading = true }: Props) { - const { title, pubDatetime, description } = frontmatter; + const { title, date, description } = frontmatter; return (
  • )} - +

    {description}

  • ); diff --git a/src/content/_schemas.ts b/src/content/_schemas.ts index 110010c..c41f460 100644 --- a/src/content/_schemas.ts +++ b/src/content/_schemas.ts @@ -3,7 +3,7 @@ import { z } from "astro:content"; export const blogSchema = z .object({ author: z.string().optional(), - pubDatetime: z.date(), + date: z.date(), title: z.string(), postSlug: z.string().optional(), featured: z.boolean().optional(), diff --git a/src/content/blog/introduction-to-the-crystal-standard-library-and-core-modules.md b/src/content/blog/introduction-to-the-crystal-standard-library-and-core-modules.md index a461c9f..4109483 100644 --- a/src/content/blog/introduction-to-the-crystal-standard-library-and-core-modules.md +++ b/src/content/blog/introduction-to-the-crystal-standard-library-and-core-modules.md @@ -1,12 +1,11 @@ --- author: Chris W -pubDatetime: 2022-12-07 +date: 2022-12-07 title: An introduction to the Crystal standard library and its core modules -description: - Introducing newcomers to the Crystal standard library and some of the most useful modules contained therein +description: Introducing newcomers to the Crystal standard library and some of the most useful modules contained therein tags: -- crystal -- programming + - crystal + - programming --- ## 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. -``` crystal +```crystal # A very basic HTTP server require "http/server" @@ -32,7 +31,7 @@ puts "Listening on http://127.0.0.1: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 @@ -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: -``` crystal +```crystal io = IO::Memory.new io.write_bytes(0x1234_i16, IO::ByteFormat::LittleEndian) 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 -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: -``` go +```go type User struct { Name string `json:"full_name"` 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: -``` crystal +```crystal struct User 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: -``` crystal +```crystal 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: -``` crystal +```crystal time = Time.utc(2016, 2, 15, 10, 20, 30) time.year # => 2016 time.month # => 2 @@ -127,7 +126,7 @@ time.time_of_day # => 10:20:30 You can also do math with time. -``` crystal +```crystal Time.utc + 3.days Time.utc - 14.years # etc etc @@ -135,7 +134,7 @@ Time.utc - 14.years 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.inspect # => "2016-02-15 10:20:30.0 +01:00 Europe/Berlin" ``` @@ -146,4 +145,4 @@ This isn't unique to the `Time` module. There is syntactic sugar all over the st If there's anything I wanted to convey in this post, it's that Crystal's standard library is awesome. I barely scratched the surface of the useful classes and modules that exist within. But with great power... No scratch that. With large standard libraries, come the pain of forgetting about all of those useful tools when you need them most. Some, the ones you use most often, you'll of course remember, but what about the ones you only find yourself needing once in a while? You're probably going to forget they even exist. -Is this a problem with the language? Or even the standard library itself? I don't think so. The human brain only has so much capacity for standard library documentation, and even with a smaller library, you'd probably still have trouble remembering the useful stuff when you need it. In the end, this is why we have documentation in the first place. Sometimes I'll just pick a random class or module and read through the API documentation to learn about some of the useful tools I didn't know exist. I'm just grateful that so much time and effort has been put into developer happiness when it comes to the standard library, and Crystal itself. \ No newline at end of file +Is this a problem with the language? Or even the standard library itself? I don't think so. The human brain only has so much capacity for standard library documentation, and even with a smaller library, you'd probably still have trouble remembering the useful stuff when you need it. In the end, this is why we have documentation in the first place. Sometimes I'll just pick a random class or module and read through the API documentation to learn about some of the useful tools I didn't know exist. I'm just grateful that so much time and effort has been put into developer happiness when it comes to the standard library, and Crystal itself. diff --git a/src/content/blog/migrating-your-redis-database-to-another-server.md b/src/content/blog/migrating-your-redis-database-to-another-server.md new file mode 100644 index 0000000..2be68bb --- /dev/null +++ b/src/content/blog/migrating-your-redis-database-to-another-server.md @@ -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 +``` + +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 +``` + +### 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. diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 173fcb6..28ddfa2 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -77,6 +77,7 @@ const socialImageURL = new URL( diff --git a/src/layouts/PostDetails.astro b/src/layouts/PostDetails.astro index 908122c..4524724 100644 --- a/src/layouts/PostDetails.astro +++ b/src/layouts/PostDetails.astro @@ -13,7 +13,7 @@ export interface 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(); @@ -37,7 +37,7 @@ const ogUrl = new URL(ogImage ? ogImage : `${title}.png`, Astro.url.origin)

    {title}

    - +
    @@ -45,6 +45,27 @@ const ogUrl = new URL(ogImage ? ogImage : `${title}.png`, Astro.url.origin)
      {tags.map(tag => )}
    + +
    + +