initial commit

This commit is contained in:
zackartz 2024-05-05 16:19:04 -04:00
commit 8a8f7c6f62
No known key found for this signature in database
GPG key ID: 5B53E53A9A514DBA
146 changed files with 8471 additions and 0 deletions

View file

@ -0,0 +1,45 @@
---
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import '../styles/global.css';
interface Props {
title: string;
description: string;
image?: string;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = '/blog-placeholder-2.jpg' } = Astro.props;
---
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<link rel="stylesheet" href="/fonts/Iosevka.css">
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.url)} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, Astro.url)} />

View file

@ -0,0 +1,48 @@
---
const today = new Date();
---
<footer>
&copy; {today.getFullYear()} Zachary Myers. All rights reserved.
<div class="social-links">
<a href="https://twitter.com/_zackartz" target="_blank">
<span class="sr-only">Follow Zack on Twitter</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/twitter"
><path
fill="currentColor"
d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
></path></svg
>
</a>
<a href="https://github.com/zackartz" target="_blank">
<span class="sr-only">Go to Zacks's GitHub repo</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/github"
><path
fill="currentColor"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
></path></svg
>
</a>
</div>
</footer>
<style>
footer {
padding: 2em 1em 6em 1em;
background: linear-gradient(var(--gray-gradient)) no-repeat;
color: rgb(var(--gray));
text-align: center;
}
.social-links {
display: flex;
justify-content: center;
gap: 1em;
margin-top: 1em;
}
.social-links a {
text-decoration: none;
color: rgb(var(--gray));
}
.social-links a:hover {
color: rgb(var(--gray-dark));
}
</style>

View file

@ -0,0 +1,17 @@
---
interface Props {
date: Date;
}
const { date } = Astro.props;
---
<time datetime={date.toISOString()}>
{
date.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
</time>

View file

@ -0,0 +1,37 @@
---
import HeaderLink from './HeaderLink.astro';
import { SITE_TITLE } from '../consts';
---
<header>
<nav>
<h2 class="p-5 font-bold"><a href="/">{SITE_TITLE}</a></h2>
<div class="absolute w-screen flex justify-center top-0 p-5 space-x-4">
<HeaderLink href="/">Home</HeaderLink>
<HeaderLink href="/blog">Blog</HeaderLink>
<HeaderLink href="/about">About</HeaderLink>
<HeaderLink target="_blank" href="/cv.pdf">CV</HeaderLink>
</div>
<div class="p-5 flex absolute top-0 right-0 space-x-4">
<a href="https://twitter.com/_zackartz" target="_blank">
<span class="sr-only">Follow Zack on Twitter</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="19" height="19" astro-icon="social/twitter"
><path
fill="currentColor"
d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
></path></svg
>
</a>
<a href="https://github.com/zackartz" target="_blank">
<span class="sr-only">Go to Zacks's GitHub repo</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="19" height="19" astro-icon="social/github"
><path
fill="currentColor"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
></path></svg
>
</a>
</div>
</nav>
</header>

View file

@ -0,0 +1,24 @@
---
import type { HTMLAttributes } from 'astro/types';
type Props = HTMLAttributes<'a'>;
const { href, class: className, ...props } = Astro.props;
const { pathname } = Astro.url;
const subpath = pathname.match(/[^\/]+/g);
const isActive = href === pathname || href === '/' + subpath?.[0];
---
<a href={href} class:list={[className, { active: isActive }]} {...props}>
<slot />
</a>
<style>
a {
@apply inline-block no-underline;
}
a.active {
font-weight: bolder;
text-decoration: underline;
}
</style>

5
src/consts.ts Normal file
View file

@ -0,0 +1,5 @@
// Place any global data in this file.
// You can import this data from anywhere in your site by using the `import` keyword.
export const SITE_TITLE = "Zachary Myers";
export const SITE_DESCRIPTION = "Software Engineer";

131
src/content/blog/01.md Normal file
View file

@ -0,0 +1,131 @@
# Deploying an Astro project on NixOS
[Nix](https://nixos.org) is an incredible project and has completely change the way I think about configuring linux and macOS environments. Recently, I moved my personal server from Ubuntu to NixOS to match my desktop environment. ([dotfiles here!](https://github.com/zackartz/nixos-dots)). In doing so, I realized I needed to move this blog over, too. I could simply deploy a docker container like I did before, but I think it would be interesting and informative to try and build a NixOS module around it. Hopefully you find it useful :)
## The `flake.nix` file.
Nix has a experimental feature called [flakes](https://nixos.wiki/wiki/Flakes), if you've been in the NixOS space long enough you've undoubtably heard of them. Flakes are a new way of writing your package configuration, you expose two objects, `inputs` and `outputs`. Your `inputs` would be things your application depends on, for example, `nixpkgs` (the package repository of Nix). Inputs can be a variety of different things, but typically are git repos. A `flake.lock` file is generated automatically so any consumers of your flake get the exact revisions of each input to guarantee reproducability. The `outputs` section of your flake can contain many things, from `devShells` to entire `nixosConfigurations`, but we are interested in `packages` and `nixosModules` today.
A example for a starter flake for a Astro project may look like this:
```nix
{
nputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
systems.url = "github:nix-systems/default";
;
utputs = {
systems,
nixpkgs,
...
@ inputs: let
eachSystem = f:
nixpkgs.lib.genAttrs (import systems) (
system:
f nixpkgs.legacyPackages.${system}
);
n {
devShells = eachSystem (pkgs: {
default = pkgs.mkShell {
buildInputs = [
pkgs.nodejs
pkgs.nodePackages.pnpm
pkgs.nodePackages.typescript
pkgs.nodePackages.typescript-language-server
pkgs.nodePackages."@tailwindcss/language-server"
pkgs.nodePackages."@astrojs/language-server"
];
};
});
;
```
You can see for my inputs I am taking in `nixpkgs` and `nix-systems`, which I am using to generate a devShell for every system architechture supported by NixOS. The `devShells` all import the following nix packages, `nodejs`, `pnpm`, `typescript`, `typescript-language-server`, `tailwindcss-language-server`, `astrojs-lanaguage-server`. Lets add add a package!
I use [pnpm](https://pnpm.io) as my package manager of choice, and as such we have to add the `pnpm2nix` flake to our inputs, which we can do with the following line.
```nix
pnpm2nix.url = "github:nzbr/pnpm2nix-nzbr";
```
Now, lets add the package spec:
```nix
...
n {
# add packages :)
packages = eachSystem (pkgs: {
default = inputs.pnpm2nix.packages.${pkgs.system}.mkPnpmPackage {
name = "zm-blog";
src = ./.;
packageJSON = ./package.json;
pnpmLock = ./pnpm-lock.yaml;
};
});
...
;
```
Now, when we run `nix build`, everything works as expected, great! But how can we see the outputs of our build? If we run the following command:
```bash
󰘧 | nix eval --raw .#packages.x86_64-linux.default
/nix/store/ik5nmb60qrgib5knp4b538axwdxykc8z-zm-blog
```
Awesome, if we ls this path, we get exactly what we are expecting from the build output.
```bash
󰘧 | ls /nix/store/ik5nmb60qrgib5knp4b538axwdxykc8z-zm-blog nix-shell-env
 _astro  fonts  blog-placeholder-2.jpg  blog-placeholder-5.jpg 󰗀 rss.xml
 about  index.html  blog-placeholder-3.jpg  blog-placeholder-about.jpg 󰗀 sitemap-0.xml
 blog  blog-placeholder-1.jpg  blog-placeholder-4.jpg 󰕙 favicon.svg 󰗀 sitemap-index.xml
```
At this point, we could add this repo as a input to the flake configuring our server, add a new `virtualHost` for the domain we want this to run on, point the root at this package and call it a day, but I want to take it one step further. I want to write a NixOS module to make the configuration server side even easier.
Add the following code to the outputs section of your `flake.nix`.
```nix
nixosModule = {
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.zmio.blog;
in {
options.zmio.blog = {
enable = mkEnableOption "Enables the Blog Site";
domain = mkOption rec {
type = type.str;
default = "zackmyers.io";
example = default;
description = "The domain name for the website";
};
ssl = mkOption rec {
type = type.bool;
default = true;
example = default;
description = "Whether to enable SSL on the domain or not";
};
};
config = mkIf cfg.enable {
services.nginx.virtualHosts.${cfg.domain} = {
forceSSL = cfg.ssl;
enableACME = cfg.ssl;
root = "${packages.${pkgs.system}.default}";
};
};
};
```

16
src/content/config.ts Normal file
View file

@ -0,0 +1,16 @@
import { defineCollection, z } from "astro:content";
const blog = defineCollection({
type: "content",
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
}),
});
export const collections = { blog };

2
src/env.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View file

@ -0,0 +1,84 @@
---
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import FormattedDate from '../components/FormattedDate.astro';
type Props = any;
const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<style>
main {
width: calc(100% - 2em);
max-width: 100%;
margin: 0;
}
.hero-image {
width: 100%;
}
.hero-image img {
display: block;
margin: 0 auto;
border-radius: 12px;
box-shadow: var(--box-shadow);
}
.prose {
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
padding: 1em;
color: rgb(var(--gray-dark));
}
.title {
margin-bottom: 1em;
padding: 1em 0;
text-align: center;
line-height: 1;
}
.title h1 {
margin: 0 0 0.5em 0;
}
.date {
margin-bottom: 0.5em;
color: rgb(var(--gray));
}
.last-updated-on {
font-style: italic;
}
</style>
</head>
<body>
<Header />
<main>
<article>
<div class="hero-image">
{heroImage && <img width={1020} height={510} src={heroImage} alt="" />}
</div>
<div class="prose lg:prose-xl">
<div class="title">
<div class="date">
<FormattedDate date={pubDate} />
{
updatedDate && (
<div class="last-updated-on">
Last updated on <FormattedDate date={updatedDate} />
</div>
)
}
</div>
<h1>{title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</main>
<Footer />
</body>
</html>

14
src/pages/about.astro Normal file
View file

@ -0,0 +1,14 @@
---
import Layout from '../layouts/BlogPost.astro';
---
<Layout
title="About Me"
description="About Zachary Myers"
pubDate={new Date('April 13 2024')}
>
<article>
<h3>Hey there!</h3>
<p>My name is Zachary Myers, I am a software engineer from Honolulu, now living in Saint Joseph, MI. I am most interested in backend programming, and primarily use languages like Rust and Go, though also have plenty of experience with JavaScript and TypeScript as well. I am looking for work! Feel free to <a href="/cv.pdf" target="_blank">check out my cv</a> and <a href="mailto:me@zackmyers.io">reach out</a> if you are interested!</p>
</article>
</Layout>

View file

@ -0,0 +1,19 @@
---
import { getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
export async function getStaticPaths() {
const posts: any[] = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}));
}
const post: any = Astro.props;
const { Content } = await post.render();
---
<BlogPost class="prose lg:prose-xl" {...post.data}>
<Content />
</BlogPost>

View file

@ -0,0 +1,43 @@
---
import BaseHead from '../../components/BaseHead.astro';
import Header from '../../components/Header.astro';
import Footer from '../../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
import { getCollection } from 'astro:content';
import FormattedDate from '../../components/FormattedDate.astro';
const posts: any[] = (await getCollection('blog')).sort(
(a: any, b: any) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()
);
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
</head>
<body>
<Header />
<main class="w-full flex justify-center">
{posts.length === 0 && <p class="py-48">Sorry! No posts at the minute, coming soon!</p>}
<section>
<ul class="space-y-4">
{
posts.sort((a, b) => b.data.pubDate - a.data.pubDate).map((post) => (
<li class="p-4 rounded-xl shadow-lg border border-gray-300">
<a href={`/blog/${post.slug}/`}>
<img class="rounded-lg" width={720} height={360} src={post.data.heroImage} alt="" />
<h4 class="text-2xl p-3 font-bold text-center">{post.data.title}</h4>
<p class="text-gray-700 text-center">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
</ul>
</section>
</main>
<Footer />
</body>
</html>

32
src/pages/index.astro Normal file
View file

@ -0,0 +1,32 @@
---
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
</head>
<body class="h-screen relative">
<Header />
<main class="p-16 flex flex-col w-full h-[84%]
justify-center items-center">
<div class="flex flex-col items-center space-y-4">
<h1 class="font-bold text-5xl">Zachary Myers</h1>
<p class="py-4">Software Engineer, Hardware Hacker, Photographer</p>
</div>
<a href="/about">
<button class="rounded-full bg-black text-white w-32 mt-4 flex px-4 py-3 justify-center">About me <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3" />
</svg>
</button>
</a>
<div class="absolute bottom-5">
<Footer />
</div>
</main>
</body>
</html>

16
src/pages/rss.xml.js Normal file
View file

@ -0,0 +1,16 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
export async function GET(context) {
const posts = await getCollection("blog");
return rss({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/blog/${post.slug}/`,
})),
});
}

0
src/styles/global.css Normal file
View file