Compare commits

...

4 commits

Author SHA1 Message Date
Akemi Izuko 0f61869ff6
Header: add unix 2023-12-29 16:58:29 -07:00
Akemi Izuko 74d727d2ea
Unix: add new content schema for unix 2023-12-29 16:58:14 -07:00
Akemi Izuko c8ffa65beb
Unix: add pages for unix 2023-12-29 16:57:55 -07:00
Akemi Izuko 1a093481c0
Unix: add a few unix notes 2023-12-29 16:57:17 -07:00
7 changed files with 502 additions and 3 deletions

View file

@ -27,6 +27,7 @@ import { SITE_TITLE } from '../consts';
</a> </a>
<HeaderLink href="/">Home</HeaderLink> <HeaderLink href="/">Home</HeaderLink>
<HeaderLink href="/blog">Blog</HeaderLink> <HeaderLink href="/blog">Blog</HeaderLink>
<HeaderLink href="/unix">Unix</HeaderLink>
<HeaderLink href="/about">About</HeaderLink> <HeaderLink href="/about">About</HeaderLink>
</div> </div>
<div class="fun-quote" style="margin: 0"> <div class="fun-quote" style="margin: 0">

View file

@ -13,4 +13,16 @@ const blog = defineCollection({
}), }),
}); });
export const collections = { blog }; const unix = defineCollection({
type: 'content',
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
updateDate: z.coerce.date(),
heroImage: z.string().optional(),
}),
});
export const collections = { blog, unix };

View file

@ -0,0 +1,116 @@
---
title: 'Send emails using Curl'
description: 'Examples on how to automate emails with Curl'
updateDate: 'Jul 08 2022'
heroImage: '/curl-logo.webp'
---
# Emailing with Curl
`curl` is an amazing networking tool. It can even automate sending emails!
## Email just a file
For most basic emails, we can stuff the headers into the email content file
itself. Here's a basic example:
```bash
# This line writes the content to a file called email.txt.
# Usually you should use a text editor to write to a file yourself.
cat <<EMAIL > email.txt
From: Emiliko Mirror <emiliko@cs.ox.ac.uk>
To: Emiliko Mirror <emiliko@gmail.com>
Subject: Sleep triggered on $(hostname)
Date: $(date)
Dear me,
The system went to sleep! I thought I should notify my gmail account when it
happened.
cheers,
Emiliko
EMAIL
# Sends the file called `email.txt` as an email, not an email attachment.
# Note that you need to replace <my-password-here> with your gmail password.
curl --silent --ssl-reqd \
--url 'smtps://smtp.gmail.com:465' \
--user 'emiliko@cs.ox.ac.uk:<my-password-here>' \
--mail-from 'emiliko@cs.ox.ac.uk' \
--mail-rcpt 'emiliko@gmail.com' \
--upload-file email.txt
```
The `From:` - `Date:` headers at the top are very important. Gmail seems to
insert them automatically if they're missing, though other mail providers might
not, so make sure to leave them in.
If you use 2fa, you won't be able to use your account password to authenticate.
Instead, you'll need to [obtain an app
password](https://support.google.com/accounts/answer/185833?hl=en) for your
account.
## Email with attachments
We can also `CC`, `BCC` and attach files with our curl emails.
Attachments need a MIME type for the file, which is easiest obtained with the
`file` command. In this example, we also remove the headers from the email file,
opting to use the more explicit `-H` curl option.
```bash
# Setup variables for attachment file. Not necessary but much cleaner
declare -r attach_file=mirrors_house.png
declare -r mime_type="$(file --mime-type "$attach_file" | sed 's/.*: //')"
# Fill a file called `email.txt` with the email content
cat <<EMAIL > email.txt
Dear classmates,
I just bought a new house! Check out the picture I attached below
OMGOMGOMG, I'm so excited!
cheers,
Kate Mirror
EMAIL
# Sends email.txt as the email content and attaches mirrors_house.png
curl --silent --ssl-reqd \
--url 'smtps://smtp.fastmail.com:465' \
--user 'kate%40mirror.house:<my-password>' \
--mail-from "kate@mirror.house" \
--mail-rcpt 'emiliko@cs.ox.ac.uk' \
--mail-rcpt 'lou@lou.me' \
--mail-rcpt 'louis@louis.me' \
-F '=(;type=multipart/mixed' \
-F "=$(cat email.txt);type=text/plain" \
-F "file=@${attach_file};filename=home.png;type=${mime_type};encoder=base64" \
-F '=)' \
-H "From: Kate Mirror <kate@mirror.house>" \
-H "Subject: Check out my new place!" \
-H "To: Emiliko Mirror <emiliko@cs.ox.ac.uk>" \
-H "CC: Lou Mirror <lou@lou.me>" \
-H "Date: $(date)"
```
Notice that for `BCC` the user isn't mentioned in `-H` at all, though you'll
still need a `--mail-rcpt` line for them.
Oddly, some mail servers seem sensitive to the filename field. My university
server would consistently bounce emails when I tried to have a filename field.
Consider removing it to use the file's actual name. In the example above, we'd
use `mirrors_house.png` instead of `home.png`.
### Further reading
- [A basic official guide](https://everything.curl.dev/usingcurl/smtp)
- [Sending a simple email with
curl](https://stackoverflow.com/questions/14722556/using-curl-to-send-email)
- [An example on using the -F option with
curl](https://blog.ambor.com/2021/08/using-curl-to-send-e-mail-with.html)
- [Backup of the above on
github](https://github.com/ambanmba/send-ses/blob/main/send-ses.sh)
- [A hint at using the filename field in
forums](https://stackoverflow.com/questions/15127732/change-name-of-upload-file-in-curl)

View file

@ -1,8 +1,7 @@
--- ---
title: 'SSH Quick Start Guide' title: 'SSH Quick Start Guide'
description: 'The fastest way to get all the SSH essentials out of the way' description: 'The fastest way to get all the SSH essentials out of the way'
pubDate: 'December 26 2023' updateDate: 'December 26 2023'
heroImage: '/blog-placeholder-3.jpg'
--- ---
# SSH # SSH

245
src/layouts/UnixPost.astro Normal file
View file

@ -0,0 +1,245 @@
---
import type { CollectionEntry } from 'astro:content';
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import FormattedDate from '../components/FormattedDate.astro';
import { Code } from 'astro:components';
type Props = CollectionEntry<'unix'>['data'];
const { title, description, updateDate, updatedDate, heroImage } = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<style is:inline>
/**** Outer body (not part of the markdown) ****/
main {
max-width: 900px;
margin: auto;
}
div.markdown-title {
text-align: center;
margin-bottom: 20px;
}
h1.markdown-title {
font-family: var(--font-title);
font-size: 40px;
}
.hero-image {
width: 100%;
}
.hero-image img {
display: block;
margin: 30px auto;
border-radius: 22px;
filter: drop-shadow(0 4px 6px #000);
}
/**** Headings ****/
.prose h1,
.prose h2,
.prose h3,
.prose h4,
.prose h5,
.prose h6 {
margin: calc(var(--font-size) / 3) 0;
font-family: var(--font-title);
text-align: left;
padding-left: 20px;
margin-left: -20px;
}
.prose h1 a,
.prose h2 a,
.prose h3 a,
.prose h4 a,
.prose h5 a,
.prose h6 a {
width: 16px;
height: 64px;
position: absolute;
float: left;
margin-top: -2px;
margin-left: -20px;
visibility: hidden;
}
.prose h1:hover a svg.icon-link,
.prose h2:hover a svg.icon-link,
.prose h3:hover a svg.icon-link,
.prose h4:hover a svg.icon-link,
.prose h5:hover a svg.icon-link,
.prose h6:hover a svg.icon-link {
visibility: visible;
}
.prose h1 a svg.icon-link,
.prose h2 a svg.icon-link,
.prose h3 a svg.icon-link,
.prose h4 a svg.icon-link,
.prose h5 a svg.icon-link,
.prose h6 a svg.icon-link {
display: inline-block;
}
.prose h1 {
font-size: 40px;
margin-bottom: calc(var(--font-size) / 3);
}
.prose h1:after { /* Psuedo-element prevents extending to padding */
content: "";
display: block;
border-bottom: 2px solid #000;
}
.prose h2 {
font-size: 30px;
}
.prose h2:after { /* Psuedo-element prevents extending to padding */
content: "";
display: block;
border-bottom: 1px solid #aaa;
}
.prose h3 {
font-size: 26px;
}
.prose h4 {
font-size: 22px;
}
.prose h5 {
font-size: 20px;
}
.prose h6 {
color: #888;
font-size: 18px;
}
/**** Paragraphs ****/
.prose p {
line-height: 1.5;
font-family: var(--font-sans);
margin: 16px 0;
}
/**** URLs ****/
.prose a {
text-decoration: none;
color: var(--accent-color);
}
.prose a:hover {
text-decoration: underline;
}
/**** Lists ****/
.prose ul {
padding-left: 30px;
}
.prose ul li {
list-style-type: disc;
}
.prose ol {
padding-left: 30px;
}
.prose ol li {
list-style-type: decimal;
}
/**** Images ****/
.prose img {
display: block;
margin: 30px auto;
border-radius: 22px;
filter: drop-shadow(0 4px 6px #444);
}
/**** Tables ****/
.prose table {
border-radius: 20px;
margin: calc(var(--font-size) / 3) 0;
}
.prose table th {
text-align: center;
}
.prose table thead {
border-bottom: 2px solid black;
}
.prose table th, .prose table td {
padding: 6px 14px;
margin: 1px;
border: 1px solid black;
}
/**** Block quotes ****/
.prose blockquote {
margin-left: 3px; /* Needs to be equal to border-left width */
padding-left: 15px;
border-left: 3px solid black;
}
/**** Code block ****/
.prose code:not(pre > code) {
background-color: #f6f6f6;
/*border-radius: 6px;*/
border-radius: 6px;
/*padding: 16px;*/
padding: 2px 4px;
margin: 16px 0;
font-family: var(--font-mono);
/*filter: drop-shadow(0 2px 6px #ddd);*/
}
.prose pre {
background-color: #f6f6f6;
border-radius: 6px;
padding: 16px;
margin: 16px 0;
font-family: var(--font-mono);
filter: drop-shadow(0 2px 6px #ddd);
}
.prose pre > code[data-line-numbers] {
counter-reset: line;
}
.prose pre > code[data-line-numbers] > .line::before {
counter-increment: line;
content: counter(line);
display: inline-block;
margin-right: 2rem
width: 1rem;
text-align: left;
color: #7ca2dfad;
}
.prose pre > code > .line::before {
content: "";
display: inline-block;
width: 1rem;
text-align: right;
}
/**** Other elements ****/
.prose mark {
background-color: white;
font-style: italic;
color: var(--accent-color);
}
</style>
</head>
<body>
<Header />
<main>
<article>
<div class="markdown-title">
<h1 class="markdown-title">{title}</h1>
<div class="date">
Updated on <FormattedDate date={updateDate} />
</div>
</div>
<div class="prose">
<div class="hero-image">
{heroImage && <img width={1020} height={510} src={heroImage} alt="" />}
</div>
<slot />
</div>
</article>
</main>
<Footer />
</body>
</html>

View file

@ -0,0 +1,20 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import UnixPost from '../../layouts/UnixPost.astro';
export async function getStaticPaths() {
const posts = await getCollection('unix');
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}));
}
type Props = CollectionEntry<'unix'>;
const post = Astro.props;
const { Content } = await post.render();
---
<UnixPost {...post.data}>
<Content />
</UnixPost>

106
src/pages/unix/index.astro Normal file
View file

@ -0,0 +1,106 @@
---
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 = (await getCollection('unix')).sort(
(a, b) => b.data.updateDate.valueOf() - a.data.updateDate.valueOf()
);
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
main {
width: 960px;
margin: auto;
}
ul {
display: flex;
flex-wrap: wrap;
gap: 2rem;
list-style-type: none;
margin: 0;
padding: 0;
}
ul li {
width: calc(50% - 1rem);
}
img {
height: 232px;
width: 464px;
object-fit: contain;
}
@media (max-width: 720px) {
ul {
gap: 0.5em;
}
ul li {
width: 100%;
text-align: center;
}
ul li:first-child {
margin-bottom: 0;
}
ul li:first-child .title {
font-size: 1.563em;
}
}
</style>
</head>
<body>
<Header />
<main>
<section>
<h1
class="text-center mt-8 mb-4 text-6xl"
style="font-family: var(--font-title);">
Unix Guides!
</h1>
<p
class="text-center mb-4 text-xl">
Over the past few years, I've extenstively configured at
learnt about using MacOS, Linux, and even some BSD systems.
Along the way I documented some of the trickier aspects of
learning Unix.
</p>
<p
class="text-center mb-4 text-xl">
These guides are mostly relevant to Linux, especially new
enthusiasts.
</p>
<hr class="m-4" style="border: 1px solid black;" />
<ul>
{
posts.map((post) => (
<li>
<a href={`/unix/${post.slug}/`}>
<img
width={720}
height={360}
class="rounded-2xl grayscale"
src={ post.data.heroImage ? post.data.heroImage : "/terminal.webp" }
alt="" />
<h4
class="title text-xl">
{post.data.title}
</h4>
<p class="date">
Updated on <FormattedDate date={post.data.updateDate} />
</p>
</a>
</li>
))
}
</ul>
</section>
</main>
<Footer />
</body>
</html>