feat: move to astro (#10)

* initial commit

* add shadcn ui

* only main page is left!

* ready to pr?

* change font
This commit is contained in:
2024-05-25 16:09:59 +02:00
committed by GitHub
parent 2f5caaf01a
commit b2e7ffb670
91 changed files with 6333 additions and 7627 deletions

View File

@@ -1,6 +0,0 @@
{
"extends": "next/core-web-vitals",
"rules": {
"react/no-unescaped-entities": "off"
}
}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

42
.gitignore vendored
View File

@@ -1,38 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# build output
dist/
# generated types
.astro/
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
node_modules/
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# environment variables
.env
.env.production
# typescript
*.tsbuildinfo
next-env.d.ts
# macOS-specific files
.DS_Store
blogPosts.json
# jetbrains setting folder
.idea/

View File

@@ -1,11 +0,0 @@
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml)
# and commit this file to your remote git repository to share the goodness with others.
# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
tasks:
- init: yarn install && yarn run build
command: yarn run dev

5
.idea/.gitignore generated vendored
View File

@@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

7
.idea/discord.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

12
.idea/mainwebsite.iml generated
View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/mainwebsite.iml" filepath="$PROJECT_DIR$/.idea/mainwebsite.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

9
.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"useTabs": false,
"printWidth": 800,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"semi": false,
"plugins": ["prettier-plugin-astro"]
}

4
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View File

@@ -1,3 +1,10 @@
{
"javascript.preferences.importModuleSpecifierEnding": "minimal"
"css.customData": [".vscode/tailwind.json"],
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.detectIndentation": false,
"prettier.documentSelectors": ["**/*.astro"],
"[astro]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

55
.vscode/tailwind.json vendored Normal file
View File

@@ -0,0 +1,55 @@
{
"version": 1.1,
"atDirectives": [
{
"name": "@tailwind",
"description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
}
]
},
{
"name": "@apply",
"description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that youd like to extract to a new component.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#apply"
}
]
},
{
"name": "@responsive",
"description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
}
]
},
{
"name": "@screen",
"description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#screen"
}
]
},
{
"name": "@variants",
"description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#variants"
}
]
}
]
}

View File

@@ -1 +0,0 @@
nodeLinker: node-modules

View File

@@ -1,7 +1,68 @@
# Welcome to Sr Izan's website
# Astro Starter Kit: Blog
My public face on the wild internet.
```sh
npm create astro@latest -- --template blog
```
Hosted on Vercel. Made with Next.js, Typescript, Markdown and Material UI.
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/blog)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/blog)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/blog/devcontainer.json)
URL: https://srizan.dev
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
![blog](https://github.com/withastro/astro/assets/2244813/ff10799f-a816-4703-b967-c78997e8323d)
Features:
- ✅ Minimal styling (make it your own!)
- ✅ 100/100 Lighthouse performance
- ✅ SEO-friendly with canonical URLs and OpenGraph data
- ✅ Sitemap support
- ✅ RSS Feed support
- ✅ Markdown & MDX support
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
├── public/
├── src/
│   ├── components/
│   ├── content/
│   ├── layouts/
│   └── pages/
├── astro.config.mjs
├── README.md
├── package.json
└── tsconfig.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
## Credit
This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/).

12
astro.config.mjs Normal file
View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import react from "@astrojs/react";
import tailwind from "@astrojs/tailwind";
// https://astro.build/config
export default defineConfig({
site: 'https://example.com',
integrations: [mdx(), sitemap(), react(), tailwind()]
});

17
components.json Normal file
View File

@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.mjs",
"css": "./src/styles/global.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

View File

@@ -1,58 +0,0 @@
{
"$schema": "https://frontmatter.codes/frontmatter.schema.json",
"frontMatter.taxonomy.contentTypes": [
{
"name": "default",
"pageBundle": false,
"previewPath": null,
"fields": [
{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",
"name": "preview",
"type": "image"
},
{
"title": "Is in draft",
"name": "draft",
"type": "draft"
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
},
{
"title": "Categories",
"name": "categories",
"type": "categories"
}
]
}
],
"frontMatter.framework.id": "next",
"frontMatter.content.publicFolder": "public",
"frontMatter.preview.host": "http://localhost:3000",
"frontMatter.content.pageFolders": [
{
"title": "blog",
"path": "[[workspace]]/src/blog"
}
]
}

View File

@@ -1,14 +0,0 @@
/** @type {import('next').NextConfig} */
export const nextConfig = {
reactStrictMode: false,
images: {
remotePatterns: [
{
hostname: 'img.srizan.dev'
},
{
hostname: 'res.cloudinary.com'
}
]
}
}

View File

@@ -1,40 +1,42 @@
{
"name": "njs-move",
"version": "0.1.0",
"private": true,
"name": "mainwebsite",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "node src/blogPostGenerator.js && next dev",
"build": "node src/blogPostGenerator.js && next build",
"start": "node src/blogPostGenerator.js;next start",
"lint": "next lint"
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/lab": "^5.0.0-alpha.153",
"@mui/material": "^5.14.18",
"dayjs": "^1.11.10",
"feed": "^4.2.2",
"glob": "^10.3.10",
"gray-matter": "^4.0.3",
"marked": "^10.0.0",
"next": "^14.0.3",
"react": "^18",
"react-dom": "^18",
"react-icons": "^4.12.0",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^4.0.0"
"@astrojs/check": "^0.7.0",
"@astrojs/mdx": "^3.0.1",
"@astrojs/react": "^3.3.4",
"@astrojs/rss": "^4.0.6",
"@astrojs/sitemap": "^3.1.5",
"@astrojs/tailwind": "^5.1.0",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-slot": "^1.0.2",
"@tryghost/content-api": "^1.11.21",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"astro": "^4.8.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.379.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.2.1",
"tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.3",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.4.5"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.10",
"eslint": "^8",
"eslint-config-next": "14.0.2",
"typescript": "^5"
},
"packageManager": "yarn@4.1.1"
"@tailwindcss/typography": "^0.5.13",
"@types/tryghost__content-api": "^1.3.16",
"prettier": "^3.2.5",
"prettier-plugin-astro": "^0.14.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

View File

@@ -1,361 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://srizan.dev/blog</id>
<title>Sr Izan's Blog</title>
<updated>2024-05-08T19:45:59.407Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<author>
<name>Sr Izan</name>
<email>izan@srizan.dev</email>
<uri>https://srizan.dev</uri>
</author>
<link rel="alternate" href="https://srizan.dev/blog"/>
<link rel="self" href="https://srizan.dev/blog/atom.xml"/>
<subtitle>My little donowall place on the net</subtitle>
<logo>https://srizan.dev/pfp.png</logo>
<icon>https://srizan.dev/pfp.png</icon>
<rights>Copyleft 2023, Sr Izan</rights>
<entry>
<title type="html"><![CDATA[My tales of MongoDB migration]]></title>
<id>https://srizan.dev/blog/2</id>
<link href="https://srizan.dev/blog/2"/>
<updated>2023-11-12T00:00:00.000Z</updated>
<summary type="html"><![CDATA[Here I ramble about the last service migration I did, MongoDB, and all the difficulties that came with it.]]></summary>
<content type="html"><![CDATA[<h2>Introduction</h2>
<p>So, the last few months I&#39;ve been migrating services from my good old Raspberry Pi into my new HP server and the last service I migrated was MongoDB.</p>
<p>I&#39;ve been using MongoDB for a while now and I&#39;ve been using it for a few things, like my discord bots, <a href="https://github.com/SrIzan10/webhooks-ui">webhooks-ui</a> and probably other projects I don&#39;t remember right now.</p>
<p>So, let&#39;s get started!</p>
<h2>Testing the plan</h2>
<p>My database instance is on Docker with a replica set of 1 node (itself) so <a href="https://www.prisma.io/">Prisma</a> works.</p>
<p>My idea is to add the HP server as a secondary replica and then promote it to be the primary one, but I don&#39;t know if that will work, so we need to test some stuff.</p>
<p>I first created 2 docker containers on my <a href="https://gist.github.com/SrIzan10/50bc2ba689a4cc43bcbac2799cc733c9">main Ryzen machine</a>&#39;s WSL Ubuntu instance.</p>
<p>I created a <code>docker-compose.yml</code> file with the following content:</p>
<pre><code class="language-yml">version: &quot;3.8&quot;
services:
mongo1:
image: mongo:4.4.17-rc0-focal
container_name: mongo1
restart: always
ports:
- 27017:27017
volumes:
- ./mongo1:/data/db
command: mongod --replSet mongoset
networks:
- mongo
mongo2:
image: mongo:4.4.17-rc0-focal
container_name: mongo2
restart: always
ports:
- 27018:27017
volumes:
- ./mongo2:/data/db
command: mongod --replSet mongoset
networks:
- mongo
networks:
mongo:
</code></pre>
<p>and ran it with <code>docker compose up -d</code>.</p>
<p>I went to connect with MongoDB Compass and it didn&#39;t work for some reason. I asked GPT and nothing. It looks like it accepted the connection but it won&#39;t connect, so I installed <code>mongosh</code> and tried to connect with that.</p>
<pre><code class="language-bash">$ mongosh mongodb://localhost:27017
</code></pre>
<p>...and it worked! That didn&#39;t make any sense, but okay, we can work with it.</p>
<p>I then connected to the <code>mongo1</code> instance and ran the following commands:</p>
<pre><code class="language-bash">&gt; rs.initiate()
</code></pre>
<p>and it worked, but only that same database connected. Before adding the second database to the replica, I went ahead and pinged it from the first container (just to check if the network configuration worked):</p>
<pre><code class="language-bash">docker exec mongo1 sh -c &quot;rm /bin/ping;apt update;apt install inetutils-ping -y;ping mongo2&quot;
</code></pre>
<p>I removed /bin/ping because I tried to transfer the binary from WSL to the container but it still needed some libraries and I didn&#39;t want to bother, so I just installed the package.</p>
<p>It worked, so I went ahead and added the second database to the replica set:</p>
<pre><code class="language-bash">&gt; rs.add(&quot;mongo2&quot;)
</code></pre>
<p>After waiting for it, the second database connected and everything was working fine. Let&#39;s create a collection and some documents on the primary replica (mongo1):</p>
<pre><code class="language-bash">&gt; use test
&gt; db.createCollection(&quot;test&quot;)
&gt; db.test.insertOne({ name: &quot;test&quot; })
</code></pre>
<p>and then, let&#39;s check if it&#39;s on the second replica (mongo2):</p>
<pre><code class="language-bash">$ mongosh mongodb://localhost:27017
</code></pre>
<pre><code class="language-bash">&gt; use test
&gt; db.getMongo().setReadPref(&quot;secondaryPreferred&quot;)
&gt; db.test.find()
</code></pre>
<p>and, yeah, that worked.</p>
<p>I don&#39;t really know if ORMs will read when connecting to the second replica, but for now it&#39;s fine as the main plan is on track.<br>So, to promote I connected to the primary replica (mongo1) and ran the following command:</p>
<pre><code class="language-bash">&gt; rs.stepDown()
</code></pre>
<p>And that worked! Woo! The second replica is now the primary one. We can now start <em>drum rolls please</em>:</p>
<h2>The migration</h2>
<p>This is it. We&#39;re doing it.</p>
<p>I went ahead and created a new docker-compose file on my server with the following content:</p>
<pre><code class="language-yml">version: &quot;3.8&quot;
services:
mongo:
image: mongo:4.4.17-rc0-focal
container_name: mongodb
restart: unless-stopped
ports:
- 27017:27017
volumes:
- ./mongo:/data/db
command: mongod --replSet rs0
</code></pre>
<p>After deploying the stack, I connected using mongosh to the primary db and ran the following command:</p>
<pre><code class="language-bash">&gt; rs.add(&quot;ip&quot;)
</code></pre>
<p>and after waiting for a while it looked like it worked. I then connected to the new database and ran the following command to check if the replica cloned fine:</p>
<pre><code class="language-bash">&gt; db.getMongo().setReadPref(&quot;secondaryPreferred&quot;)
</code></pre>
<p>and let&#39;s just let the results speak for themselves:</p>
<pre><code class="language-bash">rs0 [direct: secondary] test&gt; show dbs
# author&#39;s note: some dbs are redacted for privacy reasons
admin 80.00 KiB
api 80.00 KiB
ava 40.00 KiB
bask 168.00 KiB
config 144.00 KiB
local 348.00 KiB
vinci 428.00 KiB
rs0 [direct: secondary] test&gt; use vinci
switched to db vinci
rs0 [direct: secondary] vinci&gt; show tables
afk
birthdays
chatgpt
giveaways-enters
giveaways-message
padyama
suggestions
twitter
warns
youtube
rs0 [direct: secondary] vinci&gt; db.afk.find()
[
{
_id: ObjectId(&quot;sadfsad fsadfsdf&quot;),
id: &#39;redacted&#39;,
reason: &#39;redacted&#39;,
__v: 0
},
{
_id: ObjectId(&quot;asdfsadfadf&quot;),
id: &#39;redacted&#39;,
reason: &#39;readacted&#39;,
__v: 0
}
]
rs0 [direct: secondary] vinci&gt;
</code></pre>
<p>Nice. let&#39;s now try to write something to the database from Vinci:<br><img src="https://img.srizan.dev/Discord_a2iXkWYxwn.png" alt=""><br>That just worked and we can see it on the secondary replica:</p>
<pre><code class="language-bash">rs0 [direct: secondary] vinci&gt; db.afk.find({ id: &#39;703974042700611634&#39; })
[
{
_id: ObjectId(&quot;6550eccc6154a8c9030fe76a&quot;),
id: &#39;703974042700611634&#39;,
reason: &#39;test&#39;,
__v: 0
}
]
</code></pre>
<p>Let&#39;s now edit all .envs and change the database url to the new secondary one. For this I checked all dbs that I have and then go from top to bottom editing the secrets.</p>
<p>After that was done I needed to deploy all changes. I went ahead and created too many tabs on my terminal and ran the all deployment commands on each tab. At the same time.<br>I really hope that doesn&#39;t make my server run out of ram, because I&#39;m really short on that.</p>
<p>After executing all the commands I <code>rs.stepDown()</code>&#39;ed the primary Raspberry Pi replica and, as expected, the HP Server took over.</p>
<p>The last command of the day:</p>
<pre><code class="language-bash">&gt; rs.remove(&quot;ip&quot;)
</code></pre>
<p>...SIKE! I needed to check the logs of the containers to see if everything was working fine. The <code>api</code> and <code>vinci</code> to be exact.<br>This is because <code>api</code> runs Prisma and <code>vinci</code> runs the now defunct in my stack, <a href="https://mongoosejs.com/">mongoose</a>.</p>
<p>Luckily enough, both were fine, so I was free. Yay!</p>
<h2>Conclusion</h2>
<p>Welp, that was a lot of work. I&#39;m glad it&#39;s over. I got my HP server on July and it&#39;s now November and I just finished migrating.<br>Could I have done it in less time? Yes.<br>Was I lazy? Also yes.</p>
<p>So that answers all your questions.</p>
<p>I hope you enjoyed this my first blog post, and thankfully it was a big one.<br>This took 3 hours in total, but at the end of the day, it was worth it.</p>
<p>I&#39;ll see you in the next one!</p>
]]></content>
<author>
<name>Sr Izan</name>
<uri>https://srizan.dev</uri>
</author>
</entry>
<entry>
<title type="html"><![CDATA[Install osu! on Endeavour OS]]></title>
<id>https://srizan.dev/blog/4</id>
<link href="https://srizan.dev/blog/4"/>
<updated>2023-12-17T00:00:00.000Z</updated>
<summary type="html"><![CDATA[A guide on how to install osu! on Endeavour OS]]></summary>
<content type="html"><![CDATA[<p>Alright, so you want to install osu! on Endeavour OS. I just reinstalled my system. Two birds with one stone!<br>Based on <a href="https://wiki.archlinux.org/title/User:Katoumegumi">https://wiki.archlinux.org/title/User:Katoumegumi</a></p>
<h1>Backstory</h1>
<p>My Windows installation has been unstable since year 1 of my computer, and it even went to the point that when I open osu! sometimes it would just not work, black out my screen and play the last sound it was playing (see audio when a BSOD happens)<br>Yesterday I went to install <a href="https://osu.ppy.sh/home/changelog/stable40/20231217.1">the latest update</a>, click on restart, yadiyadiyada and unfortunately my computer CRASHED. I didn&#39;t feel nervous until I opened up the game to play a bit and find out that my skin was the default one.</p>
<p>Okay, strange, lemme open up the game...</p>
<p>It starts importing all beatmaps.</p>
<p>Okay then, a bit understandable because the update is all about difficulty calculator.</p>
<p>When it finishes, I go to my most recent plays.<br>Nothing.<br>Never played.<br>What?</p>
<p>I go into my collections.<br>They&#39;re still there.<br>Okay, but are the scores there?<br>NO!</p>
<p>Thus, after recovering my replays using a batch scripts that opens all <code>.osr</code> files in my <code>Data\r</code> folder, I just moved to linux, and this is a writeup on how I&#39;ve been installing the game since my first time in April.</p>
<p>Enjoy!</p>
<h1>Installation</h1>
<h2>Installing Wine</h2>
<p>We first need to install wine and winetricks, to do so run:</p>
<pre><code class="language-sh">sudo pacman -S wine
wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
chmod +x winetricks
sudo mv ./winetricks /usr/local/bin/winetricks
</code></pre>
<h2>Preparing wineprefix</h2>
<p>We now need to install dependencies like fonts or dotnet to make osu! run correctly</p>
<pre><code class="language-sh">WINEARCH=win32 WINEPREFIX=~/.wineosu winetricks dotnet40 dotnet45 cjkfonts gdiplus
</code></pre>
<p><strong>(update 12/19/2023)</strong>: added <code>dotnet45</code> to make it work with <a href="https://github.com/l3lackShark/gosumemory/issues/140#issuecomment-1179663744">gosumemory</a></p>
<h2>Installing osu!</h2>
<p>Skipping this part, but basically download the installer and run it.<br>I already have my own 3-year-old installation with all my everything on it, so I&#39;m bind-mounting it.</p>
<p>Create a directory:</p>
<pre><code class="language-sh">mkdir ~/.wineosu/osu
</code></pre>
<p>And then I added the following fstab line:</p>
<pre><code class="language-sh">/mnt/HDD/osu! /home/srizan/.wineosu/osu none defaults,bind 0 0
</code></pre>
<h2>Trying to open it up</h2>
<p>Running <code>WINEARCH=win32 WINEPREFIX=~/.wineosu wine ~/.wineosu/osu/osu\!.exe</code> twice leads us in the osu! installer with no network connection found.</p>
<p>This is a bit easy to fix, as Wine itself yields this error if you scroll up a bit:</p>
<pre><code class="language-sh">01b0:err:winediag:process_attach Failed to load libgnutls, secure connections will not be available.
</code></pre>
<p>Looking the package up in the <a href="https://archlinux.org/packages/?q=libgnutls">Arch Linux repos</a>, a package called <code>lib32-gnutls</code> shows up which looks to be exactly what we want.</p>
<p>After installing it, the SSL connection worked and the game is going to sta- too bad!
<img src="/blog/img/osu-eOS/graphicsContext.png" alt=""><br>You thought that was gonna be IT!</p>
<p>It wants a GL context, which we don&#39;t have, so installing <a href="https://archlinux.org/packages/multilib-testing/x86_64/lib32-mesa/"><code>lib32-mesa</code></a> fixes it. Easy!</p>
<p>Now, the window is... dark? WHEN ARE WE DONE MAN?<br>Welp, when going to almost the top of the file, we can see this:</p>
<pre><code class="language-sh">0024:err:winediag:create_gl_drawable XComposite is not available, using GLXPixmap hack.
</code></pre>
<p>&quot;GLXPixmap hack&quot;?<br>The game is, according to peppy, held with duct tape, so no hacks are really going to work.</p>
<p>Here we go, <a href="https://archlinux.org/packages/multilib/x86_64/lib32-libxcomposite/">yet ANOTHER lib32 package</a> should fix it.</p>
<p>It opens up!<br><img src="/blog/img/osu-eOS/noaudio.mp4" alt=""></p>
<p>Yeah. Nice. No audio.<br>Browsing through the Arch forums I found <img src="https://bbs.archlinux.org/viewtopic.php?id=135032" alt="this post">, and I installed the three <code>lib32-alsa-plugins lib32-libpulse lib32-openal</code> packages.</p>
<p>And it started up! (too lazy to screenshot)</p>
<h2>Setting up the start script</h2>
<p>edit a file in <code>~/.wineosu/osu/start.sh</code> with the following contents:</p>
<pre><code class="language-sh">#!/usr/bin/env bash
# props to Katoumegumi for the original script, this is exactly the same one and it works wonders.
#export PATH=&quot;$HOME/.wineosu/osuwine/bin:$PATH&quot; #Use custom WINE version to run osu!
export WINEARCH=win32
export WINEPREFIX=&quot;$HOME/.wineosu&quot;
#export WINEFSYNC=1
#VSync. For some reason, some people had been getting input latency issues and for some reason, the fix is to set VSync to off.
export vblank_mode=0 #For AMD, Intel and others
export __GL_SYNC_TO_VBLANK=0 #For NVIDIA proprietary and open source &gt;=500
#export STAGING_AUDIO_PERIOD=10000
#start osu!
wine osu\!.exe
</code></pre>
<h2>Setting up the freedesktop entry</h2>
<p>Download the osu! logo:</p>
<pre><code class="language-sh">wget --output-document ~/.wineosu/osu/icon.png https://github.com/ppy/osu-wiki/raw/master/wiki/Brand_identity_guidelines/img/usage-full-colour.png
</code></pre>
<p>and finally edit a new file in the path <code>~/.local/share/applications/osu.desktop</code></p>
<pre><code>[Desktop Entry]
Type=Application
Comment=A free-to-play rhythm game inspired in Osu! Tataekae! Ouendan!
Icon=/home/&lt;username&gt;/.wineosu/osu/icon.png
Exec=/home/&lt;username&gt;/.wineosu/osu/start.sh
Path=/home/&lt;username&gt;/.wineosu/osu
GenericName=osu!
Name=osu!
StartupNotify=true
</code></pre>
<h1>that&#39;s it</h1>
<p>Way harder than I thought. If you want the command with all dependencies, type in:</p>
<pre><code class="language-sh">sudo pacman -S lib32-gnutls lib32-mesa lib32-alsa-plugins lib32-libpulse lib32-openal
</code></pre>
]]></content>
<author>
<name>Sr Izan</name>
<uri>https://srizan.dev</uri>
</author>
</entry>
<entry>
<title type="html"><![CDATA[How to change the user password in Arch if you forgot it]]></title>
<id>https://srizan.dev/blog/3</id>
<link href="https://srizan.dev/blog/3"/>
<updated>2023-11-23T00:00:00.000Z</updated>
<summary type="html"><![CDATA[This post was made for a certain person who loves to lose passwords]]></summary>
<content type="html"><![CDATA[<p>Alright, let&#39;s do this. Fast.<br>Disclaimer: this only works when the /home directory is on the same partition, which is the default option if you don&#39;t specify.</p>
<h1>Step 1: Boot up a live environment.</h1>
<p>For the sake of simplicity, I&#39;ll be using the Endeavour OS Galileo installation media, but <a href="https://command-not-found.com/arch-chroot">any linux distro should work</a></p>
<p>When you&#39;re in, open the terminal:<br><img src="https://img.srizan.dev/vmware_zCwt9ac9KE.png" alt=""></p>
<h1>Step 2: Mounting the linux distro</h1>
<p>Type in <code>lsblk</code>. This will show all mounted drives.<br><img src="https://img.srizan.dev/vmware_LPBNlTo9BI.png" alt=""></p>
<p>Locate the drive and partition where your installation is.<br>It&#39;s usually the partition with the most space. The space is on the size row (duh)<br>If you have multiple drives with the same size and want more info about the volumes, type in <code>fdisk -l</code>.</p>
<p>In my case it&#39;s <code>/dev/sda1</code>.</p>
<p>So let&#39;s mount the partition to the <code>/mnt</code> directory with <code>sudo mount /dev/sda1 /mnt</code>. </p>
<h1>Step 3: Chrootin&#39;</h1>
<p>Chroot is a linux tool which basically changes the root directory to whatever directory you specify. This will be used to run the <code>passwd</code> command inside your installation&#39;s context.</p>
<p>Arch Linux has it&#39;s own chroot command which does some magic in the background to make it useable on this distro&#39;s environments.</p>
<pre><code class="language-sh">sudo arch-chroot /mnt
</code></pre>
<p>should chroot into your installation and after a few seconds a shell will show up!<br><img src="https://img.srizan.dev/vmware_nyyqOA9ELo.png" alt=""></p>
<p>And now one last command, the one that actually changes the password:</p>
<pre><code class="language-sh">passwd yourusername
</code></pre>
<p>and boom! that&#39;s it! impressive, right? <code>exit</code> off the console and then reboot.</p>
<h1>The end</h1>
<p>That was quick.</p>
]]></content>
<author>
<name>Sr Izan</name>
<uri>https://srizan.dev</uri>
</author>
</entry>
<entry>
<title type="html"><![CDATA[Welcome to my new blog!]]></title>
<id>https://srizan.dev/blog/1</id>
<link href="https://srizan.dev/blog/1"/>
<updated>2024-08-08T00:00:00.000Z</updated>
<summary type="html"><![CDATA[This post welcomes you to my new blog]]></summary>
<content type="html"><![CDATA[<h1>Hey!</h1>
<p>This is probably the last time I&#39;m going to make a blog. I&#39;ve made a few in the past, but I&#39;ve never really stuck to them. I&#39;m hoping that this time will be different.<br>This one was made entirely from scratch using React and Markdown, initially trying to use MDX, but it was a pain to set up, and it didn&#39;t end up working in the end.<br>I&#39;m hoping to post about my projects, and maybe some other stuff too. I&#39;m not sure yet, but I&#39;ll figure it out as I go along.<br>Anyways, thank you for reading. I hope you enjoyed my UX/UI for this one!</p>
<p>PD: I need some help for making the blog text look good and readable, so hit me up on my Discord if you have any ideas.</p>
]]></content>
<author>
<name>Sr Izan</name>
<uri>https://srizan.dev</uri>
</author>
</entry>
<entry>
<title type="html"><![CDATA[Releasing sern Automata v2]]></title>
<id>https://srizan.dev/blog/5</id>
<link href="https://srizan.dev/blog/5"/>
<updated>2024-05-08T18:46:01.405Z</updated>
<content type="html"><![CDATA[<p>As once our lord and savior Terry A. Davis said, <a href="https://www.goodreads.com/quotes/10480697-an-idiot-admires-complexity-a-genius-admires-simplicity-a-physicist">&quot;An idiot admires complexity, a genius admires simplicity.&quot;</a><br>And, welp, that&#39;s what I just did with my github bot Automata.</p>
<h1>v1</h1>
<p>Automata v1 was a real mess. We were supposed to publish a next.js website, a jobs system to make our lives easier, and a large etc.</p>
<p>Now, I&#39;ve been working on this for quite a while (about a year) and I felt like I wasn&#39;t going anywhere. So I just thought about it we could run the automation scripts on Github Actions, right?</p>
<h1>So I got to work</h1>
<p>And in 4 days I got the whole thing working. I&#39;m really proud of it.</p>
<p>We send API requests to Github whenever we want to trigger a workflow, on demand, by using the webhook system and handling everything from there.<br>As a bonus I also added a command, so we could merge on a more controlled way, specially on larger PRs.</p>
<h2>Unexpected bugs on prod just after the PR merge</h2>
<p>There were some bugs on launch.</p>
<ul>
<li>I didn&#39;t have a start script nor a license <a href="https://github.com/sern-handler/automata/commit/05c6ac3b81740ab9dcf86155b16afc84e7c850c8">(commit)</a></li>
<li>I forgot to change some environment variable names on index.ts <a href="https://github.com/sern-handler/automata/commit/6e5d21ce548db04e9926ef7dfc3c1a5945d61aed">(commit)</a></li>
<li>A day later, forgot to listen on the <code>PORT</code> environment variable <a href="https://github.com/sern-handler/automata/commit/622d6e72ded4330e06e802432677855a5397cedc">(commit)</a></li>
<li>Squash and merge by default (ended up regretting it, see below) <a href="https://github.com/sern-handler/automata/commit/65ccede477f509c2e2de27be40a78281b79cda18">(commit)</a></li>
<li>I FORGOT TO CHECK IF THE COMMAND MENTIONED @SERNBOT <a href="https://github.com/sern-handler/automata/commit/a5e58a415423eace3a8b0d6fbc4fe43e050c0624">(commit)</a></li>
</ul>
<p>That last one was a real pain, as I detected the bug in production, on the most important PR of the week, if not the month: the website&#39;s move to starlight docs.
<img src="/blog/img/sernAutomataV2/prMerge1.png" alt=""><br><img src="/blog/img/sernAutomataV2/prMerge2.png" alt=""></p>
<h1>Conclusion</h1>
<p>Wow, a project with good DevEx? SIGN ME UP!</p>
<p>PD: I created a next.js template with my preferred tech stack, make sure to check it out! <a href="https://stack.srizan.dev">link</a></p>
]]></content>
<author>
<name>Sr Izan</name>
<uri>https://srizan.dev</uri>
</author>
</entry>
</feed>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -1,342 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Sr Izan's Blog</title>
<link>https://srizan.dev/blog</link>
<description>My little donowall place on the net</description>
<lastBuildDate>Wed, 08 May 2024 19:45:59 GMT</lastBuildDate>
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
<generator>https://github.com/jpmonette/feed</generator>
<language>en</language>
<image>
<title>Sr Izan's Blog</title>
<url>https://srizan.dev/pfp.png</url>
<link>https://srizan.dev/blog</link>
</image>
<copyright>Copyleft 2023, Sr Izan</copyright>
<atom:link href="https://srizan.dev/blog/rss.xml" rel="self" type="application/rss+xml"/>
<item>
<title><![CDATA[My tales of MongoDB migration]]></title>
<link>https://srizan.dev/blog/2</link>
<guid>https://srizan.dev/blog/2</guid>
<pubDate>Sun, 12 Nov 2023 00:00:00 GMT</pubDate>
<description><![CDATA[Here I ramble about the last service migration I did, MongoDB, and all the difficulties that came with it.]]></description>
<content:encoded><![CDATA[<h2>Introduction</h2>
<p>So, the last few months I&#39;ve been migrating services from my good old Raspberry Pi into my new HP server and the last service I migrated was MongoDB.</p>
<p>I&#39;ve been using MongoDB for a while now and I&#39;ve been using it for a few things, like my discord bots, <a href="https://github.com/SrIzan10/webhooks-ui">webhooks-ui</a> and probably other projects I don&#39;t remember right now.</p>
<p>So, let&#39;s get started!</p>
<h2>Testing the plan</h2>
<p>My database instance is on Docker with a replica set of 1 node (itself) so <a href="https://www.prisma.io/">Prisma</a> works.</p>
<p>My idea is to add the HP server as a secondary replica and then promote it to be the primary one, but I don&#39;t know if that will work, so we need to test some stuff.</p>
<p>I first created 2 docker containers on my <a href="https://gist.github.com/SrIzan10/50bc2ba689a4cc43bcbac2799cc733c9">main Ryzen machine</a>&#39;s WSL Ubuntu instance.</p>
<p>I created a <code>docker-compose.yml</code> file with the following content:</p>
<pre><code class="language-yml">version: &quot;3.8&quot;
services:
mongo1:
image: mongo:4.4.17-rc0-focal
container_name: mongo1
restart: always
ports:
- 27017:27017
volumes:
- ./mongo1:/data/db
command: mongod --replSet mongoset
networks:
- mongo
mongo2:
image: mongo:4.4.17-rc0-focal
container_name: mongo2
restart: always
ports:
- 27018:27017
volumes:
- ./mongo2:/data/db
command: mongod --replSet mongoset
networks:
- mongo
networks:
mongo:
</code></pre>
<p>and ran it with <code>docker compose up -d</code>.</p>
<p>I went to connect with MongoDB Compass and it didn&#39;t work for some reason. I asked GPT and nothing. It looks like it accepted the connection but it won&#39;t connect, so I installed <code>mongosh</code> and tried to connect with that.</p>
<pre><code class="language-bash">$ mongosh mongodb://localhost:27017
</code></pre>
<p>...and it worked! That didn&#39;t make any sense, but okay, we can work with it.</p>
<p>I then connected to the <code>mongo1</code> instance and ran the following commands:</p>
<pre><code class="language-bash">&gt; rs.initiate()
</code></pre>
<p>and it worked, but only that same database connected. Before adding the second database to the replica, I went ahead and pinged it from the first container (just to check if the network configuration worked):</p>
<pre><code class="language-bash">docker exec mongo1 sh -c &quot;rm /bin/ping;apt update;apt install inetutils-ping -y;ping mongo2&quot;
</code></pre>
<p>I removed /bin/ping because I tried to transfer the binary from WSL to the container but it still needed some libraries and I didn&#39;t want to bother, so I just installed the package.</p>
<p>It worked, so I went ahead and added the second database to the replica set:</p>
<pre><code class="language-bash">&gt; rs.add(&quot;mongo2&quot;)
</code></pre>
<p>After waiting for it, the second database connected and everything was working fine. Let&#39;s create a collection and some documents on the primary replica (mongo1):</p>
<pre><code class="language-bash">&gt; use test
&gt; db.createCollection(&quot;test&quot;)
&gt; db.test.insertOne({ name: &quot;test&quot; })
</code></pre>
<p>and then, let&#39;s check if it&#39;s on the second replica (mongo2):</p>
<pre><code class="language-bash">$ mongosh mongodb://localhost:27017
</code></pre>
<pre><code class="language-bash">&gt; use test
&gt; db.getMongo().setReadPref(&quot;secondaryPreferred&quot;)
&gt; db.test.find()
</code></pre>
<p>and, yeah, that worked.</p>
<p>I don&#39;t really know if ORMs will read when connecting to the second replica, but for now it&#39;s fine as the main plan is on track.<br>So, to promote I connected to the primary replica (mongo1) and ran the following command:</p>
<pre><code class="language-bash">&gt; rs.stepDown()
</code></pre>
<p>And that worked! Woo! The second replica is now the primary one. We can now start <em>drum rolls please</em>:</p>
<h2>The migration</h2>
<p>This is it. We&#39;re doing it.</p>
<p>I went ahead and created a new docker-compose file on my server with the following content:</p>
<pre><code class="language-yml">version: &quot;3.8&quot;
services:
mongo:
image: mongo:4.4.17-rc0-focal
container_name: mongodb
restart: unless-stopped
ports:
- 27017:27017
volumes:
- ./mongo:/data/db
command: mongod --replSet rs0
</code></pre>
<p>After deploying the stack, I connected using mongosh to the primary db and ran the following command:</p>
<pre><code class="language-bash">&gt; rs.add(&quot;ip&quot;)
</code></pre>
<p>and after waiting for a while it looked like it worked. I then connected to the new database and ran the following command to check if the replica cloned fine:</p>
<pre><code class="language-bash">&gt; db.getMongo().setReadPref(&quot;secondaryPreferred&quot;)
</code></pre>
<p>and let&#39;s just let the results speak for themselves:</p>
<pre><code class="language-bash">rs0 [direct: secondary] test&gt; show dbs
# author&#39;s note: some dbs are redacted for privacy reasons
admin 80.00 KiB
api 80.00 KiB
ava 40.00 KiB
bask 168.00 KiB
config 144.00 KiB
local 348.00 KiB
vinci 428.00 KiB
rs0 [direct: secondary] test&gt; use vinci
switched to db vinci
rs0 [direct: secondary] vinci&gt; show tables
afk
birthdays
chatgpt
giveaways-enters
giveaways-message
padyama
suggestions
twitter
warns
youtube
rs0 [direct: secondary] vinci&gt; db.afk.find()
[
{
_id: ObjectId(&quot;sadfsad fsadfsdf&quot;),
id: &#39;redacted&#39;,
reason: &#39;redacted&#39;,
__v: 0
},
{
_id: ObjectId(&quot;asdfsadfadf&quot;),
id: &#39;redacted&#39;,
reason: &#39;readacted&#39;,
__v: 0
}
]
rs0 [direct: secondary] vinci&gt;
</code></pre>
<p>Nice. let&#39;s now try to write something to the database from Vinci:<br><img src="https://img.srizan.dev/Discord_a2iXkWYxwn.png" alt=""><br>That just worked and we can see it on the secondary replica:</p>
<pre><code class="language-bash">rs0 [direct: secondary] vinci&gt; db.afk.find({ id: &#39;703974042700611634&#39; })
[
{
_id: ObjectId(&quot;6550eccc6154a8c9030fe76a&quot;),
id: &#39;703974042700611634&#39;,
reason: &#39;test&#39;,
__v: 0
}
]
</code></pre>
<p>Let&#39;s now edit all .envs and change the database url to the new secondary one. For this I checked all dbs that I have and then go from top to bottom editing the secrets.</p>
<p>After that was done I needed to deploy all changes. I went ahead and created too many tabs on my terminal and ran the all deployment commands on each tab. At the same time.<br>I really hope that doesn&#39;t make my server run out of ram, because I&#39;m really short on that.</p>
<p>After executing all the commands I <code>rs.stepDown()</code>&#39;ed the primary Raspberry Pi replica and, as expected, the HP Server took over.</p>
<p>The last command of the day:</p>
<pre><code class="language-bash">&gt; rs.remove(&quot;ip&quot;)
</code></pre>
<p>...SIKE! I needed to check the logs of the containers to see if everything was working fine. The <code>api</code> and <code>vinci</code> to be exact.<br>This is because <code>api</code> runs Prisma and <code>vinci</code> runs the now defunct in my stack, <a href="https://mongoosejs.com/">mongoose</a>.</p>
<p>Luckily enough, both were fine, so I was free. Yay!</p>
<h2>Conclusion</h2>
<p>Welp, that was a lot of work. I&#39;m glad it&#39;s over. I got my HP server on July and it&#39;s now November and I just finished migrating.<br>Could I have done it in less time? Yes.<br>Was I lazy? Also yes.</p>
<p>So that answers all your questions.</p>
<p>I hope you enjoyed this my first blog post, and thankfully it was a big one.<br>This took 3 hours in total, but at the end of the day, it was worth it.</p>
<p>I&#39;ll see you in the next one!</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Install osu! on Endeavour OS]]></title>
<link>https://srizan.dev/blog/4</link>
<guid>https://srizan.dev/blog/4</guid>
<pubDate>Sun, 17 Dec 2023 00:00:00 GMT</pubDate>
<description><![CDATA[A guide on how to install osu! on Endeavour OS]]></description>
<content:encoded><![CDATA[<p>Alright, so you want to install osu! on Endeavour OS. I just reinstalled my system. Two birds with one stone!<br>Based on <a href="https://wiki.archlinux.org/title/User:Katoumegumi">https://wiki.archlinux.org/title/User:Katoumegumi</a></p>
<h1>Backstory</h1>
<p>My Windows installation has been unstable since year 1 of my computer, and it even went to the point that when I open osu! sometimes it would just not work, black out my screen and play the last sound it was playing (see audio when a BSOD happens)<br>Yesterday I went to install <a href="https://osu.ppy.sh/home/changelog/stable40/20231217.1">the latest update</a>, click on restart, yadiyadiyada and unfortunately my computer CRASHED. I didn&#39;t feel nervous until I opened up the game to play a bit and find out that my skin was the default one.</p>
<p>Okay, strange, lemme open up the game...</p>
<p>It starts importing all beatmaps.</p>
<p>Okay then, a bit understandable because the update is all about difficulty calculator.</p>
<p>When it finishes, I go to my most recent plays.<br>Nothing.<br>Never played.<br>What?</p>
<p>I go into my collections.<br>They&#39;re still there.<br>Okay, but are the scores there?<br>NO!</p>
<p>Thus, after recovering my replays using a batch scripts that opens all <code>.osr</code> files in my <code>Data\r</code> folder, I just moved to linux, and this is a writeup on how I&#39;ve been installing the game since my first time in April.</p>
<p>Enjoy!</p>
<h1>Installation</h1>
<h2>Installing Wine</h2>
<p>We first need to install wine and winetricks, to do so run:</p>
<pre><code class="language-sh">sudo pacman -S wine
wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
chmod +x winetricks
sudo mv ./winetricks /usr/local/bin/winetricks
</code></pre>
<h2>Preparing wineprefix</h2>
<p>We now need to install dependencies like fonts or dotnet to make osu! run correctly</p>
<pre><code class="language-sh">WINEARCH=win32 WINEPREFIX=~/.wineosu winetricks dotnet40 dotnet45 cjkfonts gdiplus
</code></pre>
<p><strong>(update 12/19/2023)</strong>: added <code>dotnet45</code> to make it work with <a href="https://github.com/l3lackShark/gosumemory/issues/140#issuecomment-1179663744">gosumemory</a></p>
<h2>Installing osu!</h2>
<p>Skipping this part, but basically download the installer and run it.<br>I already have my own 3-year-old installation with all my everything on it, so I&#39;m bind-mounting it.</p>
<p>Create a directory:</p>
<pre><code class="language-sh">mkdir ~/.wineosu/osu
</code></pre>
<p>And then I added the following fstab line:</p>
<pre><code class="language-sh">/mnt/HDD/osu! /home/srizan/.wineosu/osu none defaults,bind 0 0
</code></pre>
<h2>Trying to open it up</h2>
<p>Running <code>WINEARCH=win32 WINEPREFIX=~/.wineosu wine ~/.wineosu/osu/osu\!.exe</code> twice leads us in the osu! installer with no network connection found.</p>
<p>This is a bit easy to fix, as Wine itself yields this error if you scroll up a bit:</p>
<pre><code class="language-sh">01b0:err:winediag:process_attach Failed to load libgnutls, secure connections will not be available.
</code></pre>
<p>Looking the package up in the <a href="https://archlinux.org/packages/?q=libgnutls">Arch Linux repos</a>, a package called <code>lib32-gnutls</code> shows up which looks to be exactly what we want.</p>
<p>After installing it, the SSL connection worked and the game is going to sta- too bad!
<img src="/blog/img/osu-eOS/graphicsContext.png" alt=""><br>You thought that was gonna be IT!</p>
<p>It wants a GL context, which we don&#39;t have, so installing <a href="https://archlinux.org/packages/multilib-testing/x86_64/lib32-mesa/"><code>lib32-mesa</code></a> fixes it. Easy!</p>
<p>Now, the window is... dark? WHEN ARE WE DONE MAN?<br>Welp, when going to almost the top of the file, we can see this:</p>
<pre><code class="language-sh">0024:err:winediag:create_gl_drawable XComposite is not available, using GLXPixmap hack.
</code></pre>
<p>&quot;GLXPixmap hack&quot;?<br>The game is, according to peppy, held with duct tape, so no hacks are really going to work.</p>
<p>Here we go, <a href="https://archlinux.org/packages/multilib/x86_64/lib32-libxcomposite/">yet ANOTHER lib32 package</a> should fix it.</p>
<p>It opens up!<br><img src="/blog/img/osu-eOS/noaudio.mp4" alt=""></p>
<p>Yeah. Nice. No audio.<br>Browsing through the Arch forums I found <img src="https://bbs.archlinux.org/viewtopic.php?id=135032" alt="this post">, and I installed the three <code>lib32-alsa-plugins lib32-libpulse lib32-openal</code> packages.</p>
<p>And it started up! (too lazy to screenshot)</p>
<h2>Setting up the start script</h2>
<p>edit a file in <code>~/.wineosu/osu/start.sh</code> with the following contents:</p>
<pre><code class="language-sh">#!/usr/bin/env bash
# props to Katoumegumi for the original script, this is exactly the same one and it works wonders.
#export PATH=&quot;$HOME/.wineosu/osuwine/bin:$PATH&quot; #Use custom WINE version to run osu!
export WINEARCH=win32
export WINEPREFIX=&quot;$HOME/.wineosu&quot;
#export WINEFSYNC=1
#VSync. For some reason, some people had been getting input latency issues and for some reason, the fix is to set VSync to off.
export vblank_mode=0 #For AMD, Intel and others
export __GL_SYNC_TO_VBLANK=0 #For NVIDIA proprietary and open source &gt;=500
#export STAGING_AUDIO_PERIOD=10000
#start osu!
wine osu\!.exe
</code></pre>
<h2>Setting up the freedesktop entry</h2>
<p>Download the osu! logo:</p>
<pre><code class="language-sh">wget --output-document ~/.wineosu/osu/icon.png https://github.com/ppy/osu-wiki/raw/master/wiki/Brand_identity_guidelines/img/usage-full-colour.png
</code></pre>
<p>and finally edit a new file in the path <code>~/.local/share/applications/osu.desktop</code></p>
<pre><code>[Desktop Entry]
Type=Application
Comment=A free-to-play rhythm game inspired in Osu! Tataekae! Ouendan!
Icon=/home/&lt;username&gt;/.wineosu/osu/icon.png
Exec=/home/&lt;username&gt;/.wineosu/osu/start.sh
Path=/home/&lt;username&gt;/.wineosu/osu
GenericName=osu!
Name=osu!
StartupNotify=true
</code></pre>
<h1>that&#39;s it</h1>
<p>Way harder than I thought. If you want the command with all dependencies, type in:</p>
<pre><code class="language-sh">sudo pacman -S lib32-gnutls lib32-mesa lib32-alsa-plugins lib32-libpulse lib32-openal
</code></pre>
]]></content:encoded>
</item>
<item>
<title><![CDATA[How to change the user password in Arch if you forgot it]]></title>
<link>https://srizan.dev/blog/3</link>
<guid>https://srizan.dev/blog/3</guid>
<pubDate>Thu, 23 Nov 2023 00:00:00 GMT</pubDate>
<description><![CDATA[This post was made for a certain person who loves to lose passwords]]></description>
<content:encoded><![CDATA[<p>Alright, let&#39;s do this. Fast.<br>Disclaimer: this only works when the /home directory is on the same partition, which is the default option if you don&#39;t specify.</p>
<h1>Step 1: Boot up a live environment.</h1>
<p>For the sake of simplicity, I&#39;ll be using the Endeavour OS Galileo installation media, but <a href="https://command-not-found.com/arch-chroot">any linux distro should work</a></p>
<p>When you&#39;re in, open the terminal:<br><img src="https://img.srizan.dev/vmware_zCwt9ac9KE.png" alt=""></p>
<h1>Step 2: Mounting the linux distro</h1>
<p>Type in <code>lsblk</code>. This will show all mounted drives.<br><img src="https://img.srizan.dev/vmware_LPBNlTo9BI.png" alt=""></p>
<p>Locate the drive and partition where your installation is.<br>It&#39;s usually the partition with the most space. The space is on the size row (duh)<br>If you have multiple drives with the same size and want more info about the volumes, type in <code>fdisk -l</code>.</p>
<p>In my case it&#39;s <code>/dev/sda1</code>.</p>
<p>So let&#39;s mount the partition to the <code>/mnt</code> directory with <code>sudo mount /dev/sda1 /mnt</code>. </p>
<h1>Step 3: Chrootin&#39;</h1>
<p>Chroot is a linux tool which basically changes the root directory to whatever directory you specify. This will be used to run the <code>passwd</code> command inside your installation&#39;s context.</p>
<p>Arch Linux has it&#39;s own chroot command which does some magic in the background to make it useable on this distro&#39;s environments.</p>
<pre><code class="language-sh">sudo arch-chroot /mnt
</code></pre>
<p>should chroot into your installation and after a few seconds a shell will show up!<br><img src="https://img.srizan.dev/vmware_nyyqOA9ELo.png" alt=""></p>
<p>And now one last command, the one that actually changes the password:</p>
<pre><code class="language-sh">passwd yourusername
</code></pre>
<p>and boom! that&#39;s it! impressive, right? <code>exit</code> off the console and then reboot.</p>
<h1>The end</h1>
<p>That was quick.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Welcome to my new blog!]]></title>
<link>https://srizan.dev/blog/1</link>
<guid>https://srizan.dev/blog/1</guid>
<pubDate>Thu, 08 Aug 2024 00:00:00 GMT</pubDate>
<description><![CDATA[This post welcomes you to my new blog]]></description>
<content:encoded><![CDATA[<h1>Hey!</h1>
<p>This is probably the last time I&#39;m going to make a blog. I&#39;ve made a few in the past, but I&#39;ve never really stuck to them. I&#39;m hoping that this time will be different.<br>This one was made entirely from scratch using React and Markdown, initially trying to use MDX, but it was a pain to set up, and it didn&#39;t end up working in the end.<br>I&#39;m hoping to post about my projects, and maybe some other stuff too. I&#39;m not sure yet, but I&#39;ll figure it out as I go along.<br>Anyways, thank you for reading. I hope you enjoyed my UX/UI for this one!</p>
<p>PD: I need some help for making the blog text look good and readable, so hit me up on my Discord if you have any ideas.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Releasing sern Automata v2]]></title>
<link>https://srizan.dev/blog/5</link>
<guid>https://srizan.dev/blog/5</guid>
<pubDate>Wed, 08 May 2024 18:46:01 GMT</pubDate>
<content:encoded><![CDATA[<p>As once our lord and savior Terry A. Davis said, <a href="https://www.goodreads.com/quotes/10480697-an-idiot-admires-complexity-a-genius-admires-simplicity-a-physicist">&quot;An idiot admires complexity, a genius admires simplicity.&quot;</a><br>And, welp, that&#39;s what I just did with my github bot Automata.</p>
<h1>v1</h1>
<p>Automata v1 was a real mess. We were supposed to publish a next.js website, a jobs system to make our lives easier, and a large etc.</p>
<p>Now, I&#39;ve been working on this for quite a while (about a year) and I felt like I wasn&#39;t going anywhere. So I just thought about it we could run the automation scripts on Github Actions, right?</p>
<h1>So I got to work</h1>
<p>And in 4 days I got the whole thing working. I&#39;m really proud of it.</p>
<p>We send API requests to Github whenever we want to trigger a workflow, on demand, by using the webhook system and handling everything from there.<br>As a bonus I also added a command, so we could merge on a more controlled way, specially on larger PRs.</p>
<h2>Unexpected bugs on prod just after the PR merge</h2>
<p>There were some bugs on launch.</p>
<ul>
<li>I didn&#39;t have a start script nor a license <a href="https://github.com/sern-handler/automata/commit/05c6ac3b81740ab9dcf86155b16afc84e7c850c8">(commit)</a></li>
<li>I forgot to change some environment variable names on index.ts <a href="https://github.com/sern-handler/automata/commit/6e5d21ce548db04e9926ef7dfc3c1a5945d61aed">(commit)</a></li>
<li>A day later, forgot to listen on the <code>PORT</code> environment variable <a href="https://github.com/sern-handler/automata/commit/622d6e72ded4330e06e802432677855a5397cedc">(commit)</a></li>
<li>Squash and merge by default (ended up regretting it, see below) <a href="https://github.com/sern-handler/automata/commit/65ccede477f509c2e2de27be40a78281b79cda18">(commit)</a></li>
<li>I FORGOT TO CHECK IF THE COMMAND MENTIONED @SERNBOT <a href="https://github.com/sern-handler/automata/commit/a5e58a415423eace3a8b0d6fbc4fe43e050c0624">(commit)</a></li>
</ul>
<p>That last one was a real pain, as I detected the bug in production, on the most important PR of the week, if not the month: the website&#39;s move to starlight docs.
<img src="/blog/img/sernAutomataV2/prMerge1.png" alt=""><br><img src="/blog/img/sernAutomataV2/prMerge2.png" alt=""></p>
<h1>Conclusion</h1>
<p>Wow, a project with good DevEx? SIGN ME UP!</p>
<p>PD: I created a next.js template with my preferred tech stack, make sure to check it out! <a href="https://stack.srizan.dev">link</a></p>
]]></content:encoded>
</item>
</channel>
</rss>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 KiB

9
public/favicon.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 KiB

View File

@@ -1,27 +0,0 @@
import '../../_css/Root.css';
import React from 'react';
import { SiOsu } from 'react-icons/si';
import { FaDiscord, FaGithub, FaMastodon, FaTwitter, FaBlog } from 'react-icons/fa6';
import Link from "next/link";
import Image from 'next/image';
import RandomBackground from '@/app/_components/RandomBackground';
export default function Page() {
return (
<div>
<RandomBackground />
<div className='aboutMeBox'>
<Image src='/pfp.webp' alt='main profile picture' width='200' height='200' style={{ borderRadius: '100px' }} />
<p>Hobbyist developer & <Link href={'https://sern.dev'}>sern</Link> lead developer team member</p>
<div className='icons'>
<Link href='https://github.com/SrIzan10'><FaGithub /></Link>
<Link href='/blog'><FaBlog /></Link>
<Link href='https://discord.com/users/703974042700611634'><FaDiscord /></Link>
<Link href='https://social.kalico.moe/@srizan'><FaMastodon /></Link>
<Link href='https://twitter.com/itssrizan'><FaTwitter /></Link>
<Link href='https://osu.ppy.sh/users/25350735'><SiOsu /></Link>
</div>
</div>
</div>
)
}

View File

@@ -1,19 +0,0 @@
import Image from "next/image";
export default function Collab() {
return (
<div>
<Image src="/collab.webp" useMap="#image-map" alt="Collab image" />
<map name="image-map">
<area target="_blank" alt="Sr Izan" title="Sr Izan" href="https://srizan.dev" coords="0,0,280,316" shape="rect" />
<area target="_blank" alt="Willysuna" title="Willysuna" href="https://willysuna.dev" coords="716,0,1050,317" shape="rect" />
<area target="_blank" alt="Aleandro" title="Aleandro" href="https://www.youtube.com/watch?v=G7QME0bzZuA" coords="663,319,355,0" shape="rect" />
<area target="_blank" alt="Nerticel" title="Nerticel" href="https://www.youtube.com/shorts/-uAZdIJIl8o" coords="1,340,307,633" shape="rect" />
<area target="_blank" alt="Orchuna" title="Orchuna" href="https://www.tumblr.com/orchunaxd" coords="333,328,679,637" shape="rect" />
<area target="_blank" alt="XaviXE" title="XaviXE" href="https://srizan.dev/xavixe" coords="761,403,1045,571" shape="rect" />
</map>
<p>click on each person haha yes </p>
</div>
)
}

View File

@@ -1,111 +0,0 @@
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
import BlogNavBar from "../../../_components/NavBar";
import '../../../_css/BlogPost.css';
import React from "react";
import jsonDataArray from '@/blogPosts.json';
import { redirect } from "next/navigation";
import { Metadata } from 'next';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat)
export const runtime = 'edge';
export default async function Page({ params }: { params: { id: string } }) {
const id = parseInt(params.id);
if (Number.isNaN(id)) redirect('/blog')
let jsonData = {
id: 0,
title: '',
description: '',
date: '',
fileName: '',
fileContent: ''
}
const filteredPost = jsonDataArray.filter((post) => post.id === id)[0];
if (filteredPost) {
jsonData = filteredPost;
} else {
redirect('/blog')
}
return (
<div>
<BlogNavBar title={jsonData.title} isBlog />
<div className={'blogPostContent'}>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={{
code({node, className, children, ...props}) {
const match = /language-(\w+)/.exec(className || '')
return match ? (
<SyntaxHighlighter
style={atomDark}
customStyle={{ backgroundColor: '#171717', outline: 'solid' }}
codeTagProps={{ className: 'codeHighlighter' }}
language={match[1]}
// eslint-disable-next-line react/no-children-prop
children={String(children).replace(/\n$/, '')}
/>
) : (
<code className={className} {...props} style={{
fontSize: '1rem',
backgroundColor: '#171717',
outline: '3px solid #171717'
}}>
{children}
</code>
)
},
img(props) {
// eslint-disable-next-line jsx-a11y/alt-text
return <img {...props} style={{ maxWidth: '100%' }} />
}
}}>
{jsonData.fileContent}
</ReactMarkdown>
</div>
</div>
);
}
export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
const id = parseInt(params.id);
if (Number.isNaN(id)) redirect('/blog')
let jsonData = {
id: 0,
title: '',
description: '',
date: '',
fileName: '',
fileContent: ''
}
const filteredPost = jsonDataArray.filter((post) => post.id === id)[0];
if (!filteredPost) redirect('/blog')
jsonData = filteredPost;
return {
title: jsonData.title,
description: jsonData.description,
openGraph: {
title: jsonData.title,
description: jsonData.description,
authors: ['Sr Izan'],
type: 'article',
url: `https://srizan.dev/blog/${id}`,
publishedTime: dayjs(jsonData.date).toISOString(),
siteName: 'Sr Izan\'s blog',
}
}
}
type BlogPostJSONResponse = {
id: number;
title: string;
description: string;
date: string;
fileName: string;
fileContent: string;
}

View File

@@ -1,25 +0,0 @@
import '../../../app/_css/Blog.css';
import blogPosts from '@/blogPosts.json'
import BlogPostCard from '../../../app/_components/BlogPostCard';
import BlogNavBar from '../../_components/NavBar';
import BlogRssDial from '@/app/_components/BlogRssDial';
function Blog() {
return (
<div>
<BlogNavBar isBlog />
<div className="blogPosts">
{blogPosts.map((post) => {
return (
<BlogPostCard {...post} key={post.id} />
);
})}
</div>
<div className="bottomRight">
<BlogRssDial />
</div>
</div>
);
}
export default Blog;

View File

@@ -1,40 +0,0 @@
"use client"
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Typography from '@mui/material/Typography';
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
import '../_css/BlogPostCard.css'
import Link from "next/link";
import React from 'react';
import { LoadingButton } from '@mui/lab';
dayjs.extend(customParseFormat)
export default function BlogPostCard(props: Props) {
const [loading, setLoading] = React.useState(false);
return (
<Card>
<Box className={'cardBox'}>
<CardContent>
<Typography variant="h5">
{props.title}
</Typography>
<Typography variant="subtitle1" color="text.secondary" component="div" suppressHydrationWarning>
{dayjs(props.date).toDate().toLocaleDateString()}
</Typography>
<Typography variant="body2" color="text.secondary" component="div">
{props.description}
</Typography>
</CardContent>
<CardActions className={'actions'}>
<Link href={`/blog/${props.id}`}><LoadingButton size="small" loading={loading} onClick={() => setLoading(true)}>Read</LoadingButton></Link>
</CardActions>
</Box>
</Card>
);
}
type Props = { title: string, description: string, date: string, id: number }

View File

@@ -1,44 +0,0 @@
'use client'
import SpeedDial from "@mui/material/SpeedDial";
import SpeedDialAction from "@mui/material/SpeedDialAction";
import { FaRss, FaAtom } from "react-icons/fa6";
import { MdDataObject } from 'react-icons/md'
const actions = [
{ icon: <FaRss />, name: 'RSS' },
{ icon: <MdDataObject />, name: 'JSON' },
{ icon: <FaAtom />, name: 'Atom' }
];
export default function BlogRssDial() {
const handleChange = (event: string) => {
switch (event) {
case 'RSS':
window.location.href = '/blog/rss.xml'
break;
case 'JSON':
window.location.href = '/blog/feed.json'
break;
case 'Atom':
window.location.href = '/blog/atom.xml'
break;
}
}
return (
<SpeedDial
ariaLabel="SpeedDial basic example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<FaRss />}
>
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => handleChange(action.name)}
/>
))}
</SpeedDial>
)
}

View File

@@ -1,20 +0,0 @@
import Link from "next/link";
import '../_css/ImageAuthor.css'
import { FaExternalLinkAlt } from "react-icons/fa";
export default function ImageAuthor(props: Props) {
return (
<div className='imageAuthor'>
<Link href={props.location} target="_blank"><p>{props.description}</p></Link>
<p>by {props.author}</p>
<Link href={props.url} target="_blank"><FaExternalLinkAlt /></Link>
</div>
)
}
type Props = {
url: string;
author: string;
description: string;
location: string;
}

View File

@@ -1,44 +0,0 @@
// sadge
'use client';
import { useEffect, useState } from 'react';
import '../_css/BlogNavBar.css'
import Link from 'next/link';
import { useRouter } from 'next/navigation';
export default function NavBar(props: Props) {
const [isScrolled, setIsScrolled] = useState(false);
const [title, setTitle] = useState(props.title || 'Unnamed page');
const router = useRouter()
useEffect(() => {
if (props.isBlog) {
setTitle('Blog');
}
let prevScrollpos = window.scrollY;
const handleScroll = () => {
const currentScrollPos = window.scrollY;
setIsScrolled(prevScrollpos < currentScrollPos);
prevScrollpos = currentScrollPos;
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className={`navBar ${isScrolled ? 'hide' : ''}`}>
<div className="iconContainer">
<img src="https://res.cloudinary.com/mainwebsite/image/upload/v1707926066/assets/emmhztassq5zyol9gubk.webp" alt="main profile picture" height="50vh" />
<p>{title}</p>
</div>
<Link href='#' onClick={() => router.back()} className="backHomeLink">Go back</Link>
</div>
)
}
type Props = { title?: string, isBlog?: boolean }

View File

@@ -1,21 +0,0 @@
'use client';
import Backgrounds from '../../backgrounds.json'
import ImageAuthor from '@/app/_components/ImageAuthor';
import '../_css/RandomBackground.css'
import Image from 'next/image'
import React from 'react';
export default function RandomBackground() {
const [randomBg, setRandomBg] = React.useState<typeof Backgrounds[0]>()
React.useEffect(() => {
const random = Math.floor(Math.random() * Backgrounds.length)
setRandomBg(Backgrounds[random])
}, [])
return randomBg ? (
<div>
<Image className='bgImage' loader={() => randomBg.url} src={randomBg.url} fill={true} alt={randomBg.description} />
<ImageAuthor {...randomBg} />
</div>
) : null
}

View File

@@ -1,56 +0,0 @@
"use client";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import { useServerInsertedHTML } from "next/navigation";
import { ReactNode, useState } from "react";
import { ThemeProvider } from "@mui/material/styles";
import theme from "./theme";
export function RootStyleRegistry({
children,
}: {
children: ReactNode;
}) {
const [{ cache, flush }] = useState(() => {
const cache = createCache({ key: "my" });
cache.compat = true;
const prevInsert = cache.insert;
let inserted: string[] = [];
cache.insert = (...args) => {
const serialized = args[1];
if (cache.inserted[serialized.name] === undefined) {
inserted.push(serialized.name);
}
return prevInsert(...args);
};
const flush = () => {
const prevInserted = inserted;
inserted = [];
return prevInserted;
};
return { cache, flush };
});
useServerInsertedHTML(() => {
const names = flush();
if (names.length === 0) return null;
let styles = "";
for (const name of names) {
styles += cache.inserted[name];
}
return (
<style
data-emotion={`${cache.key} ${names.join(" ")}`}
dangerouslySetInnerHTML={{
__html: styles,
}}
/>
);
});
return (
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</CacheProvider>
);
}

View File

@@ -1,25 +0,0 @@
import { Roboto } from 'next/font/google';
import { createTheme } from '@mui/material/styles';
const roboto = Roboto({
weight: ['300', '400', '500', '700'],
subsets: ['latin'],
display: 'swap',
});
const theme = createTheme({
palette: {
mode: 'dark',
background: {
default: '#0d0d0d',
paper: '#0d0d0d',
},
primary: {
main: '#646cff',
},
},
typography: {
fontFamily: roboto.style.fontFamily,
},
});
export default theme;

View File

@@ -1,35 +0,0 @@
/* All navbar code was refactored to now be in the BlogNavBar.css file alongside with the component. */
/* I adjust the card's width on the BlogPostCard.css file instead of here */
.blogPosts {
margin-top: 80px;
z-index: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.blogPosts > * {
margin-top: 20px;
}
.bottomRight {
position: fixed;
bottom: 0;
right: 0;
margin: 20px;
z-index: 1;
}
::-webkit-scrollbar {
width: 20px;
}
::-webkit-scrollbar-track {
background-color: transparent;
}
::-webkit-scrollbar-thumb {
background-color: #191919;
border-radius: 20px;
border: 6px solid transparent;
background-clip: content-box;
}

View File

@@ -1,53 +0,0 @@
.navBar {
width: 40vw;
height: 60px;
background-color: #0d0d0d;
border-radius: 15px;
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
justify-content: space-between;
z-index: 1;
transition: top 0.2s;
}
.hide {
top: -100px;
}
.iconContainer {
margin-left: 20px;
display: flex;
align-items: center;
flex-direction: row;
}
.iconContainer img {
border-radius: 50px;
}
.iconContainer p {
margin-left: 10px;
}
.backHomeLink {
justify-content: flex-end;
margin-right: 20px;
}
/* some fixes for mobile and small viewports */
@media (max-width: 1160px) {
.navBar {
width: 100vw;
top: 0;
border-radius: 0;
height: 60px;
}
}
@media (prefers-color-scheme: light) {
.navBar {
background-color: #0d0d0d;
}
}

View File

@@ -1,24 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap');
.blogPostContent {
margin-top: 100px;
z-index: 0;
padding: 0 20vw 0 20vw;
line-height: 2;
}
code .codeHighlighter {
font-family: var(--font-mono);
}
@media (max-width: 900px) {
.blogPostContent {
padding: 0;
}
}
@media (prefers-color-scheme: light) {
code {
color: #FFF
}
}

View File

@@ -1,14 +0,0 @@
.cardBox {
width: 40vw;
text-align: center;
}
.actions {
float: right
}
@media (max-width: 800px) {
.cardBox {
width: 100vw;
}
}

View File

@@ -1,24 +0,0 @@
.imageAuthor {
position: absolute;
bottom: 0;
right: 0;
font-size: 0.8em;
color: white;
background-color: black;
opacity: .4;
transition: opacity .1s ease-out;
user-select: none;
display: flex;
}
.imageAuthor:hover {
opacity: 1;
}
.imageAuthor p {
margin-right: 0.2em;
margin-top: 0.1em;
margin-bottom: 0.1em;
}
.imageAuthor p:first-child {
margin-left: 0.2em;
}

View File

@@ -1,3 +0,0 @@
.center {
text-align: center;
}

View File

@@ -1,12 +0,0 @@
.bgImage {
width: 100vw;
height: 100vh;
filter: blur(5px);
filter: brightness(30%);
object-fit: cover;
z-index: -1;
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}

View File

@@ -1,38 +0,0 @@
.aboutMeBox {
width: 480px;
height: 360px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
overflow: visible;
}
body {
margin: 0;
padding: 0;
}
.icons {
align-self: flex-end;
}
.icons * {
margin-right: 20px;
}
.icons svg {
/* change from 1em */
width: 1.5em;
height: 1.5em;
}
.icons *:last-child {
margin-right: 0;
}
@media (max-width: 480px) {
.aboutMeBox {
width: 100%;
}
}

View File

@@ -1,69 +0,0 @@
/* Yes, this is a kinda unmodified index.css file from the vite template. I like the colors and stuff so I'll keep it. */
@import url("https://fonts.googleapis.com/css2?family=Inter&display=swap");
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color: rgba(255, 255, 255, 0.87);
background-color: #1d1d1d;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
--link-color: #646cff;
--link-color-hover: #535bf2;
--link-color-hover-light: #747bff;
--font-mono: ui-monospace, 'JetBrains Mono', Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
}
.navbarMargin {
margin-top: 90px;
}
a {
font-weight: 500;
color: var(--link-color);
text-decoration: inherit;
}
a:hover {
color: var(--link-color-hover);
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: var('--link-color-hover');
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: rgba(255, 255, 255, 0.87);
background-color: #1d1d1d;
}
a:hover {
color: var(--link-color-hover-light);
}
}

View File

@@ -1,30 +0,0 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { RootStyleRegistry } from './_components/ThemeRegistry/EmotionRootStyleRegistry';
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Sr Izan\'s corner of the net',
icons: { icon: '/pfp.webp' },
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<script async src="https://analytics.srizan.dev/ua.js" data-website-id="54ccb44c-b03c-4790-8262-3e1a82241a24" />
</head>
<body className={inter.className}>
<RootStyleRegistry>
{children}
</RootStyleRegistry>
</body>
</html>
)
}

View File

@@ -1,32 +1,32 @@
[
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Dom_Luis_I_Bridge_Porto_dsdodq.jpg",
"author": "me",
"description": "Dom Luis I Bridge (Porto)",
"location": "https://www.openstreetmap.org/way/98835981"
},
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Oviedo_Landscape_c8xva6.jpg",
"author": "me",
"description": "Oviedo Landscape",
"location": "https://www.openstreetmap.org/way/605000131#map=17/43.37598/-5.86843"
},
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Playa_de_Santa_Marina_ae389j.jpg",
"author": "me",
"description": "Playa de Santa Marina (Ribadesella)",
"location": "https://www.openstreetmap.org/way/822201400#map=19/43.46545/-5.06683"
},
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Hotel_Best_Benalmadena_olqc9x.jpg",
"author": "me",
"description": "Hotel Best (Benalmádena)",
"location": "https://www.openstreetmap.org/node/1253883928"
},
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Douro_River_Porto_cuga4l.jpg",
"author": "me",
"description": "Douro River (Porto)",
"location": "https://www.openstreetmap.org/#map=18/41.14065/-8.60647"
}
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Dom_Luis_I_Bridge_Porto_dsdodq.jpg",
"author": "me",
"description": "Dom Luis I Bridge (Porto)",
"location": "https://www.openstreetmap.org/way/98835981"
},
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Oviedo_Landscape_c8xva6.jpg",
"author": "me",
"description": "Oviedo Landscape",
"location": "https://www.openstreetmap.org/way/605000131#map=17/43.37598/-5.86843"
},
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Playa_de_Santa_Marina_ae389j.jpg",
"author": "me",
"description": "Playa de Santa Marina (Ribadesella)",
"location": "https://www.openstreetmap.org/way/822201400#map=19/43.46545/-5.06683"
},
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Hotel_Best_Benalmadena_olqc9x.jpg",
"author": "me",
"description": "Hotel Best (Benalmádena)",
"location": "https://www.openstreetmap.org/node/1253883928"
},
{
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Douro_River_Porto_cuga4l.jpg",
"author": "me",
"description": "Douro River (Porto)",
"location": "https://www.openstreetmap.org/#map=18/41.14065/-8.60647"
}
]

View File

@@ -1,68 +0,0 @@
---
title: How I made the install functionality on Pyramid Launcher
description: ""
date: 2024-02-02T19:13:07.791Z
preview: ""
draft: true
tags: []
categories: []
---
# Introduction
Hey everyone! Back here with a random post about how I added the minecraft installation functionality to Pyramid Launcher.
Pyramid Launcher is yet another electron launcher I've been working on with [my buddies](https://github.com/pyramidmc/people).
# The problem
For some reason `@xmcl/installer` hasn't worked for me. It'd just get stuck downloading stuff. So I wanted to make my own installer.
# Minecraft's files
## Assets
Minecraft's assets are stored in a folder called `assets`. Inside it, there are two folders: `indexes` and `objects`. The `indexes` folder contains a JSON file with a list of all the assets and their hashes and the `objects` folder contains all the assets.
This last folder has all game files, and its layout was a bit confusing at first. It's a bit like this:
```
.
├── 00
├── 01
├── 02
├── 03
├── 04
├── 05
├── 06
├── 07
├── 08
├── 09
├── 0a
├── 0b
├── 0c
├── 0d
├── 0f
(...)
```
It looks a bit confusing, but when you notice that each folder is a hash of the file, it makes sense. This is a way to avoid having a lot of files in a single folder, which would make it slow to read, but also I don't think that's good for the filesystem read and writes! ;D
## Libraries
This one is probably the easiest one. It's just a folder with a bunch of `.jar` files inside the normal IDs that java packages use.
It's also the easiest one to query, as inside the [https://piston-meta.mojang.com/v1/packages/d546f1707a3f2b7d034eece5ea2e311eda875787/1.8.9.json](version metadata JSON file), there's a list of all the libraries, paths and their hashes.
## Versions
Finally, the versions folder, which has the client files and the metadata JSON file which is the same one as the one we pull the libraries from.
# The package
After starting off a new Typescript package project using [my ts-lib-boilerplate github repo](https://github.com/SrIzan10/ts-lib-boilerplate), I started off by creawting a new class called `Installer`.
This class would be responsible for downloading and installing the game files.
```typescript
```

View File

@@ -1,46 +0,0 @@
---
id: 5
title: Releasing sern Automata v2
description: ""
date: 2024-05-08T18:46:01.405Z
preview: ""
draft: false
tags: []
categories: []
type: default
---
As once our lord and savior Terry A. Davis said, ["An idiot admires complexity, a genius admires simplicity."](https://www.goodreads.com/quotes/10480697-an-idiot-admires-complexity-a-genius-admires-simplicity-a-physicist)
And, welp, that's what I just did with my github bot Automata.
# v1
Automata v1 was a real mess. We were supposed to publish a next.js website, a jobs system to make our lives easier, and a large etc.
Now, I've been working on this for quite a while (about a year) and I felt like I wasn't going anywhere. So I just thought about it we could run the automation scripts on Github Actions, right?
# So I got to work
And in 4 days I got the whole thing working. I'm really proud of it.
We send API requests to Github whenever we want to trigger a workflow, on demand, by using the webhook system and handling everything from there.
As a bonus I also added a command, so we could merge on a more controlled way, specially on larger PRs.
## Unexpected bugs on prod just after the PR merge
There were some bugs on launch.
- I didn't have a start script nor a license [(commit)](https://github.com/sern-handler/automata/commit/05c6ac3b81740ab9dcf86155b16afc84e7c850c8)
- I forgot to change some environment variable names on index.ts [(commit)](https://github.com/sern-handler/automata/commit/6e5d21ce548db04e9926ef7dfc3c1a5945d61aed)
- A day later, forgot to listen on the `PORT` environment variable [(commit)](https://github.com/sern-handler/automata/commit/622d6e72ded4330e06e802432677855a5397cedc)
- Squash and merge by default (ended up regretting it, see below) [(commit)](https://github.com/sern-handler/automata/commit/65ccede477f509c2e2de27be40a78281b79cda18)
- I FORGOT TO CHECK IF THE COMMAND MENTIONED @SERNBOT [(commit)](https://github.com/sern-handler/automata/commit/a5e58a415423eace3a8b0d6fbc4fe43e050c0624)
That last one was a real pain, as I detected the bug in production, on the most important PR of the week, if not the month: the website's move to starlight docs.
![](/blog/img/sernAutomataV2/prMerge1.png)
![](/blog/img/sernAutomataV2/prMerge2.png)
# Conclusion
Wow, a project with good DevEx? SIGN ME UP!
PD: I created a next.js template with my preferred tech stack, make sure to check it out! [link](https://stack.srizan.dev)

View File

@@ -1,15 +0,0 @@
---
id: 1
title: Welcome to my new blog!
description: This post welcomes you to my new blog
date: 2023-20-08T00:00:00Z
---
# Hey!
This is probably the last time I'm going to make a blog. I've made a few in the past, but I've never really stuck to them. I'm hoping that this time will be different.
This one was made entirely from scratch using React and Markdown, initially trying to use MDX, but it was a pain to set up, and it didn't end up working in the end.
I'm hoping to post about my projects, and maybe some other stuff too. I'm not sure yet, but I'll figure it out as I go along.
Anyways, thank you for reading. I hope you enjoyed my UX/UI for this one!
PD: I need some help for making the blog text look good and readable, so hit me up on my Discord if you have any ideas.

View File

@@ -1,53 +0,0 @@
---
id: 3
title: How to change the user password in Arch if you forgot it
description: This post was made for a certain person who loves to lose passwords
date: 2023-11-23T00:00:00Z
---
Alright, let's do this. Fast.
Disclaimer: this only works when the /home directory is on the same partition, which is the default option if you don't specify.
# Step 1: Boot up a live environment.
For the sake of simplicity, I'll be using the Endeavour OS Galileo installation media, but [any linux distro should work](https://command-not-found.com/arch-chroot)
When you're in, open the terminal:
![](https://img.srizan.dev/vmware_zCwt9ac9KE.png)
# Step 2: Mounting the linux distro
Type in `lsblk`. This will show all mounted drives.
![](https://img.srizan.dev/vmware_LPBNlTo9BI.png)
Locate the drive and partition where your installation is.
It's usually the partition with the most space. The space is on the size row (duh)
If you have multiple drives with the same size and want more info about the volumes, type in `fdisk -l`.
In my case it's `/dev/sda1`.
So let's mount the partition to the `/mnt` directory with `sudo mount /dev/sda1 /mnt`.
# Step 3: Chrootin'
Chroot is a linux tool which basically changes the root directory to whatever directory you specify. This will be used to run the `passwd` command inside your installation's context.
Arch Linux has it's own chroot command which does some magic in the background to make it useable on this distro's environments.
```sh
sudo arch-chroot /mnt
```
should chroot into your installation and after a few seconds a shell will show up!
![](https://img.srizan.dev/vmware_nyyqOA9ELo.png)
And now one last command, the one that actually changes the password:
```sh
passwd yourusername
```
and boom! that's it! impressive, right? `exit` off the console and then reboot.
# The end
That was quick.

View File

@@ -1,150 +0,0 @@
---
id: 4
title: Install osu! on Endeavour OS
description: A guide on how to install osu! on Endeavour OS
date: 2023-12-17T00:00:00Z
---
Alright, so you want to install osu! on Endeavour OS. I just reinstalled my system. Two birds with one stone!
Based on https://wiki.archlinux.org/title/User:Katoumegumi
# Backstory
My Windows installation has been unstable since year 1 of my computer, and it even went to the point that when I open osu! sometimes it would just not work, black out my screen and play the last sound it was playing (see audio when a BSOD happens)
Yesterday I went to install [the latest update](https://osu.ppy.sh/home/changelog/stable40/20231217.1), click on restart, yadiyadiyada and unfortunately my computer CRASHED. I didn't feel nervous until I opened up the game to play a bit and find out that my skin was the default one.
Okay, strange, lemme open up the game...
It starts importing all beatmaps.
Okay then, a bit understandable because the update is all about difficulty calculator.
When it finishes, I go to my most recent plays.
Nothing.
Never played.
What?
I go into my collections.
They're still there.
Okay, but are the scores there?
NO!
Thus, after recovering my replays using a batch scripts that opens all `.osr` files in my `Data\r` folder, I just moved to linux, and this is a writeup on how I've been installing the game since my first time in April.
Enjoy!
# Installation
## Installing Wine
We first need to install wine and winetricks, to do so run:
```sh
sudo pacman -S wine
wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
chmod +x winetricks
sudo mv ./winetricks /usr/local/bin/winetricks
```
## Preparing wineprefix
We now need to install dependencies like fonts or dotnet to make osu! run correctly
```sh
WINEARCH=win32 WINEPREFIX=~/.wineosu winetricks dotnet40 dotnet45 cjkfonts gdiplus
```
**(update 12/19/2023)**: added `dotnet45` to make it work with [gosumemory](https://github.com/l3lackShark/gosumemory/issues/140#issuecomment-1179663744)
## Installing osu!
Skipping this part, but basically download the installer and run it.
I already have my own 3-year-old installation with all my everything on it, so I'm bind-mounting it.
Create a directory:
```sh
mkdir ~/.wineosu/osu
```
And then I added the following fstab line:
```sh
/mnt/HDD/osu! /home/srizan/.wineosu/osu none defaults,bind 0 0
```
## Trying to open it up
Running `WINEARCH=win32 WINEPREFIX=~/.wineosu wine ~/.wineosu/osu/osu\!.exe` twice leads us in the osu! installer with no network connection found.
This is a bit easy to fix, as Wine itself yields this error if you scroll up a bit:
```sh
01b0:err:winediag:process_attach Failed to load libgnutls, secure connections will not be available.
```
Looking the package up in the [Arch Linux repos](https://archlinux.org/packages/?q=libgnutls), a package called `lib32-gnutls` shows up which looks to be exactly what we want.
After installing it, the SSL connection worked and the game is going to sta- too bad!
![](/blog/img/osu-eOS/graphicsContext.png)
You thought that was gonna be IT!
It wants a GL context, which we don't have, so installing [`lib32-mesa`](https://archlinux.org/packages/multilib-testing/x86_64/lib32-mesa/) fixes it. Easy!
Now, the window is... dark? WHEN ARE WE DONE MAN?
Welp, when going to almost the top of the file, we can see this:
```sh
0024:err:winediag:create_gl_drawable XComposite is not available, using GLXPixmap hack.
```
"GLXPixmap hack"?
The game is, according to peppy, held with duct tape, so no hacks are really going to work.
Here we go, [yet ANOTHER lib32 package](https://archlinux.org/packages/multilib/x86_64/lib32-libxcomposite/) should fix it.
It opens up!
![](/blog/img/osu-eOS/noaudio.mp4)
Yeah. Nice. No audio.
Browsing through the Arch forums I found ![this post](https://bbs.archlinux.org/viewtopic.php?id=135032), and I installed the three `lib32-alsa-plugins lib32-libpulse lib32-openal` packages.
And it started up! (too lazy to screenshot)
## Setting up the start script
edit a file in `~/.wineosu/osu/start.sh` with the following contents:
```sh
#!/usr/bin/env bash
# props to Katoumegumi for the original script, this is exactly the same one and it works wonders.
#export PATH="$HOME/.wineosu/osuwine/bin:$PATH" #Use custom WINE version to run osu!
export WINEARCH=win32
export WINEPREFIX="$HOME/.wineosu"
#export WINEFSYNC=1
#VSync. For some reason, some people had been getting input latency issues and for some reason, the fix is to set VSync to off.
export vblank_mode=0 #For AMD, Intel and others
export __GL_SYNC_TO_VBLANK=0 #For NVIDIA proprietary and open source >=500
#export STAGING_AUDIO_PERIOD=10000
#start osu!
wine osu\!.exe
```
## Setting up the freedesktop entry
Download the osu! logo:
```sh
wget --output-document ~/.wineosu/osu/icon.png https://github.com/ppy/osu-wiki/raw/master/wiki/Brand_identity_guidelines/img/usage-full-colour.png
```
and finally edit a new file in the path `~/.local/share/applications/osu.desktop`
```
[Desktop Entry]
Type=Application
Comment=A free-to-play rhythm game inspired in Osu! Tataekae! Ouendan!
Icon=/home/<username>/.wineosu/osu/icon.png
Exec=/home/<username>/.wineosu/osu/start.sh
Path=/home/<username>/.wineosu/osu
GenericName=osu!
Name=osu!
StartupNotify=true
```
# that's it
Way harder than I thought. If you want the command with all dependencies, type in:
```sh
sudo pacman -S lib32-gnutls lib32-mesa lib32-alsa-plugins lib32-libpulse lib32-openal
```

View File

@@ -1,223 +0,0 @@
---
id: 2
title: My tales of MongoDB migration
description: Here I ramble about the last service migration I did, MongoDB, and all the difficulties that came with it.
date: 2023-11-12T00:00:00Z
---
## Introduction
So, the last few months I've been migrating services from my good old Raspberry Pi into my new HP server and the last service I migrated was MongoDB.
I've been using MongoDB for a while now and I've been using it for a few things, like my discord bots, [webhooks-ui](https://github.com/SrIzan10/webhooks-ui) and probably other projects I don't remember right now.
So, let's get started!
## Testing the plan
My database instance is on Docker with a replica set of 1 node (itself) so [Prisma](https://www.prisma.io/) works.
My idea is to add the HP server as a secondary replica and then promote it to be the primary one, but I don't know if that will work, so we need to test some stuff.
I first created 2 docker containers on my [main Ryzen machine](https://gist.github.com/SrIzan10/50bc2ba689a4cc43bcbac2799cc733c9)'s WSL Ubuntu instance.
I created a `docker-compose.yml` file with the following content:
```yml
version: "3.8"
services:
mongo1:
image: mongo:4.4.17-rc0-focal
container_name: mongo1
restart: always
ports:
- 27017:27017
volumes:
- ./mongo1:/data/db
command: mongod --replSet mongoset
networks:
- mongo
mongo2:
image: mongo:4.4.17-rc0-focal
container_name: mongo2
restart: always
ports:
- 27018:27017
volumes:
- ./mongo2:/data/db
command: mongod --replSet mongoset
networks:
- mongo
networks:
mongo:
```
and ran it with `docker compose up -d`.
I went to connect with MongoDB Compass and it didn't work for some reason. I asked GPT and nothing. It looks like it accepted the connection but it won't connect, so I installed `mongosh` and tried to connect with that.
```bash
$ mongosh mongodb://localhost:27017
```
...and it worked! That didn't make any sense, but okay, we can work with it.
I then connected to the `mongo1` instance and ran the following commands:
```bash
> rs.initiate()
```
and it worked, but only that same database connected. Before adding the second database to the replica, I went ahead and pinged it from the first container (just to check if the network configuration worked):
```bash
docker exec mongo1 sh -c "rm /bin/ping;apt update;apt install inetutils-ping -y;ping mongo2"
```
I removed /bin/ping because I tried to transfer the binary from WSL to the container but it still needed some libraries and I didn't want to bother, so I just installed the package.
It worked, so I went ahead and added the second database to the replica set:
```bash
> rs.add("mongo2")
```
After waiting for it, the second database connected and everything was working fine. Let's create a collection and some documents on the primary replica (mongo1):
```bash
> use test
> db.createCollection("test")
> db.test.insertOne({ name: "test" })
```
and then, let's check if it's on the second replica (mongo2):
```bash
$ mongosh mongodb://localhost:27017
```
```bash
> use test
> db.getMongo().setReadPref("secondaryPreferred")
> db.test.find()
```
and, yeah, that worked.
I don't really know if ORMs will read when connecting to the second replica, but for now it's fine as the main plan is on track.
So, to promote I connected to the primary replica (mongo1) and ran the following command:
```bash
> rs.stepDown()
```
And that worked! Woo! The second replica is now the primary one. We can now start *drum rolls please*:
## The migration
This is it. We're doing it.
I went ahead and created a new docker-compose file on my server with the following content:
```yml
version: "3.8"
services:
mongo:
image: mongo:4.4.17-rc0-focal
container_name: mongodb
restart: unless-stopped
ports:
- 27017:27017
volumes:
- ./mongo:/data/db
command: mongod --replSet rs0
```
After deploying the stack, I connected using mongosh to the primary db and ran the following command:
```bash
> rs.add("ip")
```
and after waiting for a while it looked like it worked. I then connected to the new database and ran the following command to check if the replica cloned fine:
```bash
> db.getMongo().setReadPref("secondaryPreferred")
```
and let's just let the results speak for themselves:
```bash
rs0 [direct: secondary] test> show dbs
# author's note: some dbs are redacted for privacy reasons
admin 80.00 KiB
api 80.00 KiB
ava 40.00 KiB
bask 168.00 KiB
config 144.00 KiB
local 348.00 KiB
vinci 428.00 KiB
rs0 [direct: secondary] test> use vinci
switched to db vinci
rs0 [direct: secondary] vinci> show tables
afk
birthdays
chatgpt
giveaways-enters
giveaways-message
padyama
suggestions
twitter
warns
youtube
rs0 [direct: secondary] vinci> db.afk.find()
[
{
_id: ObjectId("sadfsad fsadfsdf"),
id: 'redacted',
reason: 'redacted',
__v: 0
},
{
_id: ObjectId("asdfsadfadf"),
id: 'redacted',
reason: 'readacted',
__v: 0
}
]
rs0 [direct: secondary] vinci>
```
Nice. let's now try to write something to the database from Vinci:
![](https://img.srizan.dev/Discord_a2iXkWYxwn.png)
That just worked and we can see it on the secondary replica:
```bash
rs0 [direct: secondary] vinci> db.afk.find({ id: '703974042700611634' })
[
{
_id: ObjectId("6550eccc6154a8c9030fe76a"),
id: '703974042700611634',
reason: 'test',
__v: 0
}
]
```
Let's now edit all .envs and change the database url to the new secondary one. For this I checked all dbs that I have and then go from top to bottom editing the secrets.
After that was done I needed to deploy all changes. I went ahead and created too many tabs on my terminal and ran the all deployment commands on each tab. At the same time.
I really hope that doesn't make my server run out of ram, because I'm really short on that.
After executing all the commands I `rs.stepDown()`'ed the primary Raspberry Pi replica and, as expected, the HP Server took over.
The last command of the day:
```bash
> rs.remove("ip")
```
...SIKE! I needed to check the logs of the containers to see if everything was working fine. The `api` and `vinci` to be exact.
This is because `api` runs Prisma and `vinci` runs the now defunct in my stack, [mongoose](https://mongoosejs.com/).
Luckily enough, both were fine, so I was free. Yay!
## Conclusion
Welp, that was a lot of work. I'm glad it's over. I got my HP server on July and it's now November and I just finished migrating.
Could I have done it in less time? Yes.
Was I lazy? Also yes.
So that answers all your questions.
I hope you enjoyed this my first blog post, and thankfully it was a big one.
This took 3 hours in total, but at the end of the day, it was worth it.
I'll see you in the next one!

View File

@@ -1,67 +0,0 @@
import { glob } from 'glob'
import * as fs from 'node:fs'
import gm from 'gray-matter'
import { Feed } from 'feed'
import dayjs from 'dayjs'
import customParseFormat from "dayjs/plugin/customParseFormat.js";
import { marked } from "marked";
dayjs.extend(customParseFormat)
const feed = new Feed({
title: 'Sr Izan\'s Blog',
description: 'My little donowall place on the net',
id: 'https://srizan.dev/blog',
link: 'https://srizan.dev/blog',
language: 'en',
image: 'https://srizan.dev/pfp.png',
favicon: 'https://srizan.dev/pfp.png',
copyright: 'Copyleft 2023, Sr Izan',
feedLinks: {
json: 'https://srizan.dev/blog/feed.json',
atom: 'https://srizan.dev/blog/atom.xml',
rss: 'https://srizan.dev/blog/rss.xml'
},
author: {
link: 'https://srizan.dev',
name: 'Sr Izan',
email: 'izan@srizan.dev'
}
})
const data = []
await glob('./src/blog/**/*.md').then(async (files) => {
for (const file of files) {
const readFile = fs.readFileSync(file)
const dt = gm(readFile).data
const fileContent = gm(readFile).content
if (dt.draft) return
dt.fileContent = fileContent
dt.fileName = file.replace('src/blog/', '')
console.log(`File ${dt.fileName} read successfully`)
data.push(dt)
feed.addItem({
title: dt.title,
id: `https://srizan.dev/blog/${dt.id}`,
link: `https://srizan.dev/blog/${dt.id}`,
description: dt.description,
content: marked.parse(fileContent),
date: dayjs(dt.date).toDate(),
author: [
{
name: 'Sr Izan',
link: 'https://srizan.dev'
}
]
})
}
})
data.sort((a, b) => b.id - a.id);
fs.writeFileSync('./src/blogPosts.json', JSON.stringify(data))
fs.writeFileSync('./public/blog/feed.json', feed.json1())
fs.writeFileSync('./public/blog/rss.xml', feed.rss2())
fs.writeFileSync('./public/blog/atom.xml', feed.atom1())

View File

@@ -0,0 +1,46 @@
---
// 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-1.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} />
<!-- Font preloads -->
<link rel="preload" href="https://fonts.srizan.dev/satoshi/fonts/Satoshi-Medium.woff" as="font" type="font/woff" crossorigin />
<!-- 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,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,31 @@
---
import { SITE_TITLE } from '../consts';
import { ModeToggle } from '@/components/ui/modetoggle';
---
<script is:inline>
const getThemePreference = () => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
const isDark = getThemePreference() === 'dark';
document.documentElement.classList[isDark ? 'add' : 'remove']('dark');
if (typeof localStorage !== 'undefined') {
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
}
</script>
<div class="flex items-center fixed gap-4 p-2 bg-slate-300/35 dark:bg-slate-500/20 backdrop-blur-md w-full sm:w-auto">
<a href="/" class="font-bold text-lg">{ SITE_TITLE }</a>
<a href="/blog">Blog</a>
<a href="https://jp.srizan.dev">日本語</a>
<div class="flex-1" />
<ModeToggle client:load />
</div>

View File

@@ -0,0 +1,24 @@
---
import { FaExternalLinkAlt } from 'react-icons/fa';
import '@/styles/global.css'
type Props = {
url: string;
author: string;
description: string;
location: string;
}
const { url, author, description, location } = Astro.props;
---
<style>
p {
@apply mr-[0.2em] my-[0.1em] first:ml-[0.2em];
}
</style>
<div class='absolute bottom-0 right-0 text-[0.8em] text-white bg-black opacity-40 select-none flex transition-opacity duration-150 ease-in-out hover:opacity-100'>
<a href={location} target="_blank"><p>{description}</p></a>
<p>by {author}</p>
<a href={url} target="_blank"><FaExternalLinkAlt /></a>
</div>

View File

@@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,198 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View File

@@ -0,0 +1,52 @@
import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function ModeToggle() {
const [theme, setThemeState] = React.useState<
"theme-light" | "dark" | "system"
>("theme-light")
React.useEffect(() => {
const isDarkMode = document.documentElement.classList.contains("dark")
setThemeState(isDarkMode ? "dark" : "theme-light")
}, [])
React.useEffect(() => {
const isDark =
theme === "dark" ||
(theme === "system" &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
document.documentElement.classList[isDark ? "add" : "remove"]("dark")
}, [theme])
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setThemeState("theme-light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

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 = 'Sr Izan\'s website';
export const SITE_DESCRIPTION = 'Welcome to my website!';

6
src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
interface ImportMetaEnv {
readonly BLOG_CONTENT_KEY: string;
readonly BLOG_CONTENT_URL: string;
}

View File

@@ -0,0 +1,52 @@
---
import BaseHead from '../components/BaseHead.astro'
import Header from '../components/Header.astro'
import FormattedDate from '../components/FormattedDate.astro'
import type { PostOrPage } from '@tryghost/content-api'
import { Image } from 'astro:assets'
type Props = PostOrPage
const { title, excerpt: description, published_at, updated_at, feature_image, feature_image_caption, feature_image_alt } = Astro.props
---
<html lang="en">
<head>
<BaseHead title={title!} description={description || '(no description)'} />
</head>
<body>
<Header />
<main class="w-full max-w-full mx-0">
<article class="my-16 sm:my-0">
<div class="mb-4 pt-4 text-sm leading-none">
<h1 class="m-0 mb-2 text-center text-4xl font-bold">{title}</h1>
<hr />
</div>
<div class="flex flex-col items-center">
<div class="mb-4">
{feature_image && <Image class="shadow" width={720} height={200} src={feature_image} alt={feature_image_alt || ''} />}
</div>
{feature_image_caption && <span class="text-left text-sm italic ml-4" set:html={feature_image_caption} />}
</div>
<div class="prose dark:prose-invert prose-code:dark:text-black w-prose max-w-[calc(100%-2em)] mx-auto p-2">
<div class="mb-2 text-gray-500">
<FormattedDate date={new Date(published_at!)} />
{
updated_at && (
<div class="italic">
Last updated on <FormattedDate date={new Date(updated_at)} />
</div>
)
}
</div>
<slot />
</div>
</article>
</main>
</body>
</html>

8
src/lib/ghost.ts Normal file
View File

@@ -0,0 +1,8 @@
import GhostContentAPI from '@tryghost/content-api';
// Create API instance with site credentials
export const ghostClient = new GhostContentAPI({
url: import.meta.env.BLOG_URL, // This is the default URL if your site is running on a local environment
key: import.meta.env.BLOG_CONTENT_KEY,
version: 'v5.82',
});

6
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -0,0 +1,41 @@
---
/* import { type CollectionEntry, getCollection } from 'astro:content
export async function getStaticPaths() {
const posts = await getCollection('blog')
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}))
}
type Props = CollectionEntry<'blog'>
const rawHTMLString = "Hello <strong>World</strong>"
const post = Astro.props
const { Content } = await post.render() */
import BlogPost from '../../layouts/BlogPost.astro'
import { ghostClient } from '../../lib/ghost';
export async function getStaticPaths() {
const posts = await ghostClient.posts
.browse({
limit: 'all',
});
return posts.map((post) => {
return {
params: {
slug: post.slug,
},
props: {
post: post,
},
};
});
}
const { post } = Astro.props;
---
<BlogPost {...post}>
<Fragment set:html={post.html} />
</BlogPost>

View File

@@ -0,0 +1,46 @@
---
import BaseHead from '../../components/BaseHead.astro'
import Header from '../../components/Header.astro'
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts'
import FormattedDate from '../../components/FormattedDate.astro'
import { Image } from 'astro:assets'
import { ghostClient } from '@/lib/ghost'
const posts = await ghostClient.posts.browse({
limit: 'all',
})
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style is:inline>
[data-astro-code-mark] {
display: none;
}
</style>
</head>
<body>
<Header />
<main>
<section class="p-12">
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-6">
{
posts.map((post) => (
<li class="flex flex-col items-center sm:items-start">
<a href={`/blog/${post.slug}/`} class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors duration-200">
<Image class="rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200" width={720} height={360} src={post.feature_image || 'https://images.unsplash.com/photo-1519681393784-d120267933ba'} alt="" loading="lazy" />
<h4 class="text-xl font-bold mt-2">{post.title}</h4>
<p class="mt-1">
<FormattedDate date={new Date(post.published_at!)} />
</p>
</a>
</li>
))
}
</ul>
</section>
</main>
</body>
</html>

39
src/pages/index.astro Normal file
View File

@@ -0,0 +1,39 @@
---
import BaseHead from '../components/BaseHead.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
import Backgrounds from '../backgrounds.json';
import { Image } from 'astro:assets';
import ImageAuthor from '@/components/ImageAuthor.astro';
import { FaGithub, FaBlog, FaDiscord, FaMastodon, FaTwitter } from 'react-icons/fa';
import { SiOsu } from 'react-icons/si';
const randomBackground = Backgrounds[Math.floor(Math.random() * Backgrounds.length)];
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
svg {
@apply w-6 h-6;
}
</style>
</head>
<body class="relative">
<div class={`w-screen h-screen z-[-1] bg-fixed bg-center bg-no-repeat bg-cover rounded-none object-cover brightness-[30%]`} style={`background-image: url('${randomBackground.url}')`}/>
<ImageAuthor {...randomBackground} />
<div class="w-[480px] h-[360px] absolute -translate-x-2/4 -translate-y-2/4 text-center overflow-visible left-2/4 top-2/4 text-white flex justify-center items-center flex-col gap-5">
<Image src='/pfp.webp' alt={''} width='200' height='200' class='rounded-full' />
<p>Hobbyist developer & <a href={'https://sern.dev'}>sern</a> lead developer team member</p>
<div class='flex gap-5 justify-center'>
<a href='https://github.com/SrIzan10'><FaGithub /></a>
<a href='/blog'><FaBlog /></a>
<a href='https://discord.com/users/703974042700611634'><FaDiscord /></a>
<a href='https://social.kalico.moe/@srizan'><FaMastodon /></a>
<a href='https://twitter.com/itssrizan'><FaTwitter /></a>
<a href='https://osu.ppy.sh/users/25350735'><SiOsu /></a>
</div>
</div>
</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}/`,
})),
});
}

182
src/styles/global.css Normal file
View File

@@ -0,0 +1,182 @@
/*
The CSS in this style tag is based off of Bear Blog's default CSS.
https://github.com/HermanMartinus/bearblog/blob/297026a877bc2ab2b3bdfbd6b9f7961c350917dd/templates/styles/blog/default.css
License MIT: https://github.com/HermanMartinus/bearblog/blob/master/LICENSE.md
*/
@import url('https://fonts.srizan.dev/satoshi.css');
:root {
--accent: #2337ff;
--accent-dark: #000d8a;
--black: 15, 18, 25;
--gray: 96, 115, 159;
--gray-light: 229, 233, 240;
--gray-dark: 34, 41, 57;
--gray-gradient: rgba(var(--gray-light), 50%), #fff;
--box-shadow: 0 2px 6px rgba(var(--gray), 25%), 0 8px 24px rgba(var(--gray), 33%), 0 16px 32px rgba(var(--gray), 33%);
}
body {
font-family: 'Satoshi-Medium', sans-serif !important;
@apply text-left overflow-auto text-lg leading-relaxed bg-white dark:bg-gray-900 text-black dark:text-white;
}
main {
@apply mx-auto px-4 py-16 w-full max-w-7xl;
}
h1 {
@apply text-6xl mb-2 leading-tight;
}
h2 {
@apply text-5xl mb-2 leading-snug;
}
h3 {
@apply text-4xl mb-2 leading-normal;
}
h4 {
@apply text-3xl mb-2 leading-normal;
}
h5 {
@apply text-2xl mb-2 leading-normal;
}
h6 {
@apply text-xl mb-2 leading-normal;
}
strong,
b {
@apply font-bold;
}
a {
@apply !text-blue-500;
}
a:hover {
@apply !text-blue-700;
}
textarea {
@apply w-full text-base;
}
input {
@apply text-base;
}
table {
@apply w-full;
}
img {
@apply max-w-full h-auto rounded-lg;
}
code {
@apply px-1 py-0.5 bg-gray-200 rounded-sm;
}
pre {
@apply p-6 rounded-lg;
}
blockquote {
@apply border-l-4 border-blue-600 pl-5 text-xl;
}
hr {
@apply border-none border-t border-gray-200;
}
@media (max-width: 720px) {
body {
@apply text-base;
}
main {
@apply p-4;
}
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

75
tailwind.config.mjs Normal file
View File

@@ -0,0 +1,75 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ["class"],
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
width: {
'prose': '720px'
}
},
},
plugins: [require("tailwindcss-animate"), require('@tailwindcss/typography')],
}

View File

@@ -1,27 +1,14 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true,
"jsx": "react-jsx",
"jsxImportSource": "react",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
"@/*": [
"./src/*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
}

10401
yarn.lock

File diff suppressed because it is too large Load Diff