<?xml version="1.0" encoding="UTF-8"?><rss 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" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[ore wa pw]]></title><description><![CDATA[I'll post some techy stuff I think]]></description><link>https://blog.orewa.pw/</link><image><url>https://blog.orewa.pw/favicon.png</url><title>ore wa pw</title><link>https://blog.orewa.pw/</link></image><generator>Ghost 5.76</generator><lastBuildDate>Sat, 11 Apr 2026 04:14:01 GMT</lastBuildDate><atom:link href="https://blog.orewa.pw/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[The World's Worst Scraper]]></title><description><![CDATA[<p>So recently, my friend needed a webscraper to be able to check text on the page and see if it had changed. This is pretty common problem that also has a lot of roadblocks. This is my journey of creating the world&apos;s worst scraper (but it works!)</p><h2 id="part-0-specs">Part</h2>]]></description><link>https://blog.orewa.pw/worlds-worst-scraper/</link><guid isPermaLink="false">65e33c0856dc16000139ee73</guid><dc:creator><![CDATA[Pat W]]></dc:creator><pubDate>Sat, 02 Mar 2024 15:29:16 GMT</pubDate><content:encoded><![CDATA[<p>So recently, my friend needed a webscraper to be able to check text on the page and see if it had changed. This is pretty common problem that also has a lot of roadblocks. This is my journey of creating the world&apos;s worst scraper (but it works!)</p><h2 id="part-0-specs">Part 0: Specs</h2><p>As this was a fun personal project, it wasn&apos;t formal but the idea was:</p><ul><li>Scrape webpage/API for info</li><li>Send some kind of message in order to tell the user the info had changed.</li></ul><h2 id="part-1-what-approach">Part 1: What approach?</h2><p>When it comes to webscraping, I usually go with one of three things:</p><ul><li>A Python script using a combination of <a href="https://requests.readthedocs.io/en/latest/" rel="noreferrer">Requests</a> and <a href="https://beautiful-soup-4.readthedocs.io/en/latest/" rel="noreferrer">BeautifulSoup</a></li><li>Web test runner such as <a href="https://www.selenium.dev/" rel="noreferrer">Selenium</a> or <a href="https://playwright.dev/" rel="noreferrer">Playwright</a></li><li>Sketchy <a href="https://www.tampermonkey.net/" rel="noreferrer">Tampermonkey</a> with Javascript</li></ul><p>You can guess which we ended up using but I actually tried each one.</p><h2 id="part-2-what-is-actually-available">Part 2: What is actually available?</h2><p>I spent some time looking through the network logs to see if there were any accessible REST endpoints we could just query for JSON data. There were some that were either encrypted or inaccessible from clients that weren&apos;t the website itself.</p><p>We ended up deciding we&apos;d have to use webscraping. I was able to come up with a quick <code>document.querySelectorAll(...).forEach</code> hack to grab the data I wanted using the devtools console.</p><p>What does the <code>querySelectorAll</code> do? It allows for me to search through the page for an element with a custom attribute. I was able to find a custom attribute attached to a <code>&lt;tr&gt;</code> that contained all the data I needed. Though this was in Javascript, that selector would be useful for any other scraper/script I used.</p><h2 id="part-2-the-approaches-we-ended-up-not-using">Part 2: The approaches we ended up not using</h2><p>So first I tried to use Requests and BeautifulSoup. Long story short, it became a pain in the butt because since it wasn&apos;t a JSON endpoint but an actual page that was rendered using Javascript/React, the data wasn&apos;t available on page load so we couldn&apos;t get the data this way.</p><p>So from there I tried to use Playwright since it actually uses a headless browser and user actions to run tests/scripts. Unfortunately, I was too lazy to go this direction since I anticipated test framework detection (as it&apos;s really popular) and already had the Javascript snippet so I ended up ignoring this route.</p><h2 id="part-3-tampering-with-monkeys">Part 3: Tampering with monkeys</h2><p>I ended up using Tampermonkey to run the script on page load. To those unfamiliar with Tampermonkey, it&apos;s a browser extension where you can write custom Javascript code that runs on every page load. It&apos;s how people used to (and sometimes still) install custom themes/actions on sites like Reddit or Facebook without creating a whole extension for it.</p><p>Originally the code looked like this:</p><pre><code class="language-Javascript">function sleep(ms) {
    return new Promise(resolve =&gt; setTimeout(resolve, ms));
}

(async function() {
    &apos;use strict&apos;;
    await sleep(5*1000);

    document.querySelectorAll(&quot;tr[abc]&quot;).forEach(async (row) =&gt; {
        if(row.textContent.contains(&quot;what we&apos;re looking for&quot;)) {
	    console.log(&quot;found!&quot;);
        } else {
            console.log(&quot;not found yet&quot;);
        }
    });

    setTimeout(function(){ location.reload(); }, 30*1000);
})();</code></pre><p>Basically, we would sleep for 5s to wait for the page to load, iterate over all the <code>&lt;tr&gt;</code>s that fit our criteria, find out if we had what we were looking for, then refresh after another 30s to avoid <code>429 Too many requests</code> errors.</p><p>I didn&apos;t want to mess with trying to send a message to the user because that would require 3rd party APIs which would be pretty annoying to try to use within a simple Tampermonkey script. I used a &quot;beeping&quot; sound that I had found on <a href="https://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep" rel="noreferrer">StackOverflow</a> that would go off every time the <code>textContent</code> matched our criteria. It was actually pretty cool because you were able to alert a user audibly without any extra resources. It would just take the actual <code>wav</code> content that we provided as a string and play it.</p><p>But... my friend was adamant that he needed a way that could notify him on his phone so that he&apos;d know while being away from his computer.</p><h2 id="part-4-telephone-telegram">Part 4: Tele...phone? Tele...gram?</h2><p>My friend had a couple options for how he wanted to receive the message:</p><ul><li>Email</li><li>Text</li><li>Some messaging platform</li></ul><p>For me, email felt annoying since the options were to use an existing email to email things which usually require the use of a 3rd party library or have your own SMTP/script running on a server to then contact another API which I didn&apos;t want to write.</p><p>Text was under a similar boat. I&apos;ve sent texts using email before (I don&apos;t know if it still works but <a href="https://www.wikihow.com/Send-a-Text-from-Email" rel="noreferrer">this is how I used to</a>) but that would require emailing. Another alternative to send a text was creating a Twilio account or something which seemed annoying too.</p><p>So I landed on messaging platforms. Again, I didn&apos;t want to have to import a library because doing that in Scriptmonkey seemed like a giant pain.</p><p>I looked around online and realized that Telegram has its own REST API that you just needed your user token/chat id to send a request to (more information <a href="https://gist.github.com/dideler/85de4d64f66c1966788c1b2304b9caf1" rel="noreferrer">here</a>).</p><p>Luckily my friend already had a Telegram account so I had him register a bot to get an API token and chat ID. From there it was child&apos;s play.</p><p>To actually send the request, I used the <code>fetch</code> API. Here&apos;s the gist of the code:</p><pre><code class="language-Javascript">// we&apos;re in the for loop

if(row.textContent.contains(&quot;what we&apos;re looking for&quot;)) {
    const regex = /(custom.regex)/;
    const match = row.textContent.match(regex);

    const data = {
        &quot;chat_id&quot;: &quot;1234567890&quot;,
        &quot;text&quot;: match[0] + &quot; &quot; + match[1]
    };

    await fetch(&quot;https://api.telegram.org/bot&lt;BOT_TOKEN&gt;/sendMessage&quot;, {
        headers: {
            &quot;content-type&quot;: &quot;application/json&quot;
        },
        method: &apos;POST&apos;,
        body: JSON.stringify(data)
    });
} else {
    console.log(&quot;not found yet&quot;);
}</code></pre><p>Pretty simple right? All I needed to do was <code>POST</code> an endpoint with the token in the URI and send a stringified object with the <code>chat_id</code> and my parsed message! Super easy and super fast.</p><h2 id="part-5-did-it-work">Part 5: Did it work?</h2><p>Yeah it worked.</p><p>We&apos;re still getting rate-limited though so we might have to tweak the sleep time.</p><p>Could we have made it smarter with backoffs or used an actual API? Sure. But this was fast, easy, and hacky. Which is my favorite part of software engineering.</p><p>Hopefully this is helpful for someone who just needs a quick bot to help with notifying you of website changes!</p><p>Good luck hacking!</p>]]></content:encoded></item><item><title><![CDATA[MCBOT]]></title><description><![CDATA[<p>Hi TEALS class, I&apos;m writing this post for YOU.</p><p>I&apos;m not a huge Java guy but I wanted to write something cool to show how to setup a Java environment with dependencies.</p><p>What did I come up with to connect with you? I created another Discord</p>]]></description><link>https://blog.orewa.pw/mcbot/</link><guid isPermaLink="false">65c707da56dc16000139ec5d</guid><dc:creator><![CDATA[Pat W]]></dc:creator><pubDate>Thu, 22 Feb 2024 15:23:05 GMT</pubDate><content:encoded><![CDATA[<p>Hi TEALS class, I&apos;m writing this post for YOU.</p><p>I&apos;m not a huge Java guy but I wanted to write something cool to show how to setup a Java environment with dependencies.</p><p>What did I come up with to connect with you? I created another Discord Minecraft bot. yay...</p><p>I&apos;ll go through both thought-processes for the project and also technical aspects to give a rundown of how I approached an idea and turned it into reality.</p><p>Before we get to that, please download this zip file: <br><a href="https://files.orewa.pw/src.zip">https://files.orewa.pw/src.zip<br><br></a>It contains all my Java source folder with all the code we go over today.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.orewa.pw/content/images/2024/02/image-1.png" class="kg-image" alt loading="lazy" width="855" height="258" srcset="https://blog.orewa.pw/content/images/size/w600/2024/02/image-1.png 600w, https://blog.orewa.pw/content/images/2024/02/image-1.png 855w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">psych.</span></figcaption></figure><h2 id="step-0-what-should-iti-do">Step 0: What should it/I do?</h2><p>I start every software  project by considering what I specifically want it to do.</p><p>Some of y&apos;all talk pretty loud and always about Minecraft and Discord (or at least some of you) so it was a pretty easy launch point.</p><p>Originally, my idea was I wanted users to be able to see a log of all actions taken in Minecraft easily from Discord.</p><h3 id="step-%C2%BD-is-this-even-possible">Step &#xBD;: Is this even possible?</h3><p>This comes with experience/knowing what you&apos;re working on. I run a Minecraft server and just starting digging through logs. In another application, we might go through a website&apos;s API (such as <a href="https://rapidapi.com/apidojo/api/yahoo-finance1/" rel="noreferrer">Yahoo! Finance</a>) but here we can just force our bot to dig through logs.</p><p>I couldn&apos;t actually find a log of all user actions. It looks like there are plugins for it but I don&apos;t have it installed. What I <em>could</em> find was the user connect/disconnect logs. When Minecraft runs, it automatically puts out logs that have sections that look like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-logfile">[2024-01-26 15:38:26:863 INFO] Player connected: playerA, xuid: 1234567890123456
[2024-01-26 15:38:52:103 INFO] Player disconnected: playerA, xuid: 1234567890123456, pfid: ef03492acfbb3a21
[2024-01-26 15:41:50:593 INFO] Player connected: playerA, xuid: 1234567890123456
[2024-01-26 15:42:08:686 INFO] Player disconnected: playerA, xuid: 1234567890123456, pfid: ef03492acfbb3a21
[2024-01-26 15:46:59:125 INFO] Player connected: playerA, xuid: 1234567890123456
[2024-01-26 15:51:24:630 INFO] Player disconnected: playerA, xuid: 1234567890123456, pfid: ef03492acfbb3a21
[2024-01-26 15:56:33:223 INFO] Player connected: playerA, xuid: 1234567890123456
[2024-01-26 15:57:10:434 INFO] Player disconnected: playerA, xuid: 1234567890123456, pfid: ef03492acfbb3a21
[2024-01-26 16:00:25:129 INFO] Player connected: playerA, xuid: 1234567890123456
[2024-01-26 16:03:51:543 INFO] Player disconnected: playerA, xuid: 1234567890123456, pfid: ef03492asscfbb3a21
[2024-01-30 23:08:22:835 INFO] Player connected: player B, xuid: 1234567890123457
[2024-01-30 23:08:40:168 INFO] Player disconnected: player B, xuid: 1234567890123457, pfid: a67b4794acb35809
[2024-01-30 23:11:22:968 INFO] Player connected: player B, xuid: 1234567890123457
[2024-01-30 23:11:34:061 INFO] Player disconnected: player B, xuid: 1234567890123457, pfid: a67b4794acb35809
[2024-01-31 01:06:37:799 INFO] Player connected: playerA, xuid: 1234567890123456
[2024-01-31 01:07:13:208 INFO] Player connected: player B, xuid: 1234567890123457
[2024-01-31 01:07:21:413 INFO] Player connected: Player C, xuid: 1234567890123458
[2024-01-31 01:08:44:975 INFO] Player connected: Player D, xuid: 1234567890123459
[2024-01-31 02:28:38:116 INFO] Player disconnected: playerA, xuid: 1234567890123456, pfid: ef03492acfbb3a21
[2024-01-31 02:28:38:254 INFO] Player disconnected: Player D, xuid: 1234567890123459, pfid: 4b6be430457f31d
[2024-01-31 02:28:58:691 INFO] Player disconnected: Player C, xuid: 1234567890123458, pfid: b3d2cb5372fe5cfd
[2024-01-31 02:28:59:476 INFO] Player disconnected: player B, xuid: 1234567890123457, pfid: a67b4794acb35809</code></pre><figcaption><p><span style="white-space: pre-wrap;">Player connection logs</span></p></figcaption></figure><p>Player names and xuids (Xbox user IDs) have been modified for privacy&apos;s sake. They&apos;re also formatted differently so we can take care of edge cases such as whitespace in the logs. I manually grabbed these logs but I could probably quickly put these logs in an updating file for my bot to see. But for building the bot, I just put this in an <code>input.txt</code> file to test with.</p><p>From here I decided to write a bot that took a username or xuid and returned the amount of time they&apos;ve spent playing on my server.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.orewa.pw/content/images/2024/02/image-2.png" class="kg-image" alt loading="lazy" width="283" height="323"><figcaption><span style="white-space: pre-wrap;">proof im a gamer</span></figcaption></figure><h2 id="step-1-setup-%F0%9F%AA%A6">Step 1: Setup &#x1FAA6;</h2><p>This took me forever. I hate this. Let me explain what I have as my dev setup.<br>The underlined title of the bullet is a link for installation on Windows. I use Debian Linux but if you need help installing, let me know.</p><ul><li><a href="https://code.visualstudio.com/" rel="noreferrer">Visual Studio Code</a>: This is one of the defacto text editors today with many extensions to make it very powerful. I do all my coding here. For this project I also installed the Java extension just for some nice-to-have quality of life stuff like better autocomplete.</li><li><a href="https://www.digitalocean.com/community/tutorials/java-windows-10-download-install" rel="noreferrer">JDK (Java Development Kit)</a>: I know this part has been confusing to some people so I&apos;ll clarify the different downloads a bit (I&apos;m slightly summarizing <a href="https://www.educative.io/answers/what-is-the-difference-between-jvm-and-jre" rel="noreferrer">this article</a>):<ul><li>JVM - Java Virtual Machine: A program that&apos;s able to translate bytecode into machine code. We don&apos;t need to download/run this at all individually</li><li>JRE - Java Runtime Environment: This is an environment that you can download to run Java programs. JVM is a part of the JRE. If you download this, you can run <code>java</code> commands in the command line to run compiled Java programs.</li><li>JDK- Java Development Kit: This is what we actually want to install and comes with a compilers, tools, etc. It also comes with JRE so you&apos;ll be able to run what you make.</li><li><a href="https://openjdk.org/install/" rel="noreferrer">OpenJDK</a>: OpenJDK is a community/open-source version of JDK. I use this personally because I think I found a tutorial to install it for Linux but should work exactly the same as Oracle&apos;s JDK.</li></ul></li><li><a href="https://maven.apache.org/install.html" rel="noreferrer">Maven</a>: This is a build tool that <em>also</em> helps you manage dependencies. If you don&apos;t have dependencies on outside libraries/packages, you probably don&apos;t need this. Since we&apos;re going to be using an external library to package our program into a <code>.jar</code> (Java ARchive file/executable) and use a Java-based Discord package, it will be helpful in this instance. There are other alternatives that can do similar things such as <a href="https://ant.apache.org/" rel="noreferrer">Ant</a> or <a href="https://gradle.org/" rel="noreferrer">Gradle</a> but I remember hearing someone talk about Maven and I didn&apos;t feel like learning the other two so here we are.</li><li><a href="https://github.com/discord-jda/JDA" rel="noreferrer">JDA (Java Discord API)</a>: This is the library I use for communicating/building my Java bot. They have a couple good examples I based my code off of, especially the <a href="https://github.com/discord-jda/JDA/blob/master/src/examples/java/SlashBotExample.java" rel="noreferrer">SlashBotExample</a>. They also have a <a href="https://jda.wiki/introduction/jda/" rel="noreferrer">decent wiki</a> with helpful visual examples. It&apos;s an open-source library so you can see how they implemented it but my quick overview is that it&apos;s a wrapper library that basically <em>abstracts</em> API calls to Discord servers so we can easily use pretty objects and methods.</li></ul><p>NOTE about <code>PATH</code>/environment variables: So in operating systems when you go into a command line and try to execute something, there is a set amount of places it checks for an executable that&apos;s getting called. If you don&apos;t put your executable in the right place or add the executable&apos;s location to your <code>PATH</code>, when you run <code>java</code> in the command line, it won&apos;t do anything.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.orewa.pw/content/images/2024/02/image-3.png" class="kg-image" alt loading="lazy" width="564" height="576"><figcaption><span style="white-space: pre-wrap;">Just kidding. Kindof.</span></figcaption></figure><h3 id="step-1%C2%BD-sanity-checking">Step 1&#xBD;: Sanity checking</h3><p>After installing, we should make sure that everything works.</p><ul><li>JDK: If running <code>java --version</code> or <code>javac --version</code> gives you something, we&apos;re in business.</li><li>Maven: If running <code>mvn --version</code> gives you something, we&apos;re good.</li></ul><p>Don&apos;t worry about JDA for now. VSCode should be a normal application so I trust you can find it.</p><h2 id="step-2-creating-the-projectpomxml">Step 2: Creating the project/pom.xml</h2><p>Not gonna lie, it took me a solid hour to get it installed even to this point. Luckily for you, I took many hours getting dependencies and builds to work but now you get to shortcut it while I explain it. But I will force you to read about all the mistakes I made so you can work through my thought process.</p><p>First I just tried to create a &quot;hello world&quot; project. I was able to do that pretty easily and compile it with <code>javac</code> and run it with <code>java</code> (<a href="https://www.geeksforgeeks.org/java-hello-world-program/" rel="noreferrer">and some code I took from the internet</a>).</p><p>I just created a &quot;hello world&quot; class and function, ran <code>javac hello.java</code> and then <code>java HelloWorld</code> to run the program.</p><figure class="kg-card kg-code-card"><pre><code class="language-Java">class HelloWorld { 
    // Your program begins with a call to main(). 
    // Prints &quot;Hello, World&quot; to the terminal window. 
    public static void main(String args[]) 
    { 
        System.out.println(&quot;Hello, World&quot;); 
    } 
}</code></pre><figcaption><p><span style="white-space: pre-wrap;">Yet another hello, world. Literal copypasta.</span></p></figcaption></figure><p>At this point I realized I had no idea how dependency management/importing libraries worked for Java. That&apos;s when I finally realized that was what Maven was for.</p><p>I tried some dumb things like trying to create my own <code>pom.xml</code> and using it with Maven. What is <code>pom.xml</code>? I&apos;m glad you asked, I have such a great segue.</p><p><code>pom.xml</code> is a file that tells Maven what to build, what dependencies we have, and some other metadata (random info).</p><p>I then found this handy-dandy <a href="https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html" rel="noreferrer">Maven in 5 Minutes</a> article by the Maven creators themselves. By using deductive reasoning, I ended up modifying their command to something I wanted:</p><pre><code class="language-Command Line">mvn archetype:generate -DgroupId=com.patrick.app -DartifactId=mcbot -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false</code></pre><p>Realistically, all I changed was the <code>groupId</code> and <code>artifactId</code> to be less generic. Everything else was default.</p><p>In the end it gave me the folder structure as shown in the article. Now I had a project! I ran <code>mvn package</code> and <code>java -cp target/mcbot-1.0-SNAPSHOT.jar com.patrick.app.App</code> and I saw hello world! Very cool. At this point, I was still just doing tutorial stuff.</p><figure class="kg-card kg-image-card"><img src="https://blog.orewa.pw/content/images/2024/02/image-4.png" class="kg-image" alt loading="lazy" width="768" height="768" srcset="https://blog.orewa.pw/content/images/size/w600/2024/02/image-4.png 600w, https://blog.orewa.pw/content/images/2024/02/image-4.png 768w" sizes="(min-width: 720px) 720px"></figure><h2 id="step-3-writing-a-bunch-of-code-without-knowing-if-it-would-work">Step 3: Writing a bunch of code without knowing if it would work</h2><p>NOTE: This is where I go through most of the code in the aforementioned zip file. It should be in the <code>src</code> folder. Follow along with the files to read more notes on decision making and to look at the actual code/logic.</p><p>I didn&apos;t know how to get dependencies to work and I was starting to get annoyed trying random stuff so I just started writing code. I&apos;m at the point where I can mostly sus out my code from my initial idea and debug it later. I just used a bunch of the example code as a jumping off point. With a lot of Googling and decision making, I was able to come up with the following files:</p><ul><li><code>App.java</code>: this is just the main file. Most of this is based off JDA&apos;s <code>SlashBotExample</code> I linked above. I also use a <code>FileReader</code> object here to read my input text file.</li><li><code>LogParser.java</code>: This holds the <code>LogParser</code> class which has a <code>static</code> function that can parse a single line of my log into a <code>ConnectionLog</code> object.</li><li><code>ConnectionLog.java</code>: This is the actual object class for <code>ConnectionLog</code>. It&apos;s pretty basic and stores the following members:<ul><li><code>public enum ConnectionStatus</code>: Enums are basically just ways to have some special variables map to specific values, usually numbers starting from 0. <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html" rel="noreferrer">Here&apos;s an article</a> about them. Hilariously, I don&apos;t even use this right now with the code but could be used later.</li><li>A constructor that takes in and stores:<ul><li><code>String name</code>: The user&apos;s IGN.</li><li><code>String xuid</code>: The user&apos;s Xbox ID. This is part of the log and I wanted to give the option to lookup by this since it&apos;s unique, unlike usernames.</li><li><code>ConnectionStatus status</code>: Whether the log was for <code>CONNECTED</code> or <code>DISCONNECTED</code>. (Ends up being unused).</li><li><code>LocalDateTime time</code>: The timestamp for the log.</li></ul></li><li>All the members are set to <code>private</code> with <code>public</code> get methods and no set methods. I wanted the constructor to be the only way to set the values and only cared about getting the values later.</li></ul></li><li><code>CollectionLogAggregator.java</code>: This file might sound really verbose but Java is notorious for having a data structure for everything. I found out an <code>Aggregator</code> is a class that has functions that act on a <code>Collection</code> (or in my case a <code>List</code>). It has:<ul><li>A constructor that takes in a <code>List&lt;ConnectionLog&gt;</code> where the list is all the <code>ConnectionLog</code>s we parsed from the file.</li><li><code>public getTimePlayedByName/Xuid</code>: These are 2 functions that basically do the same thing.  They get their matched <code>ConnectionLog</code>s and pass them into a function that gives back the <code>Duration</code> based on that.</li></ul></li><ul><li><code>private getConnectionLogsByName/Xuid</code>: These 2 functions filter through all the logs and find the specific logs that are associated with the name/xuid.</li><li><code>private getTimePlayedFromLogs</code>: Takes in any <code>List&lt;ConnectionLog&gt;</code> and gives you a <code>Duration</code> object sum of all the differences between <code>CONNECTED</code> and <code>DISCONNECTED</code>.</li></ul><li>I originally also had a <code>CollectionLogBuilder</code> class that would be able to give you an object based on if you had the username or xuid but I realized later that every log literally had both so I would never have the chance to use this. So I deleted the file &#x1F626;</li></ul><p>I have left extensive comments in the code so you can see my thought process/understand what the code does.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.orewa.pw/content/images/2024/02/image-7.png" class="kg-image" alt loading="lazy" width="1440" height="1403" srcset="https://blog.orewa.pw/content/images/size/w600/2024/02/image-7.png 600w, https://blog.orewa.pw/content/images/size/w1000/2024/02/image-7.png 1000w, https://blog.orewa.pw/content/images/2024/02/image-7.png 1440w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">I was this close to being in the last row and this blog post almost just stayed as an idea lol</span></figcaption></figure><h2 id="step-4-now-i-have-this-code-now-what">Step 4: Now I have this code... now what?</h2><p>This is where a LOT of Googling happened. I know how to write code but I didn&apos;t know how to compile it or run it. I also hadn&apos;t installed the dependency.</p><p>I found some random stuff on Google that told me to add this code to my <code>pom.xml</code> to pull in/install the JDA library during the packaging process:</p><figure class="kg-card kg-code-card"><pre><code class="language-pom.xml">  &lt;repositories&gt;
    &lt;repository&gt;
      &lt;id&gt;jitpack.io&lt;/id&gt;
      &lt;url&gt;https://jitpack.io&lt;/url&gt;
    &lt;/repository&gt;
  &lt;/repositories&gt;

&lt;!-- AND --&gt;

    &lt;dependency&gt;
      &lt;groupId&gt;com.github.discord-jda&lt;/groupId&gt;
      &lt;artifactId&gt;JDA&lt;/artifactId&gt;
      &lt;version&gt;v5.0.0-beta.20&lt;/version&gt;
      &lt;exclusions&gt;
          &lt;exclusion&gt;
              &lt;groupId&gt;club.minnced&lt;/groupId&gt;
              &lt;artifactId&gt;opus-java&lt;/artifactId&gt;
          &lt;/exclusion&gt;
      &lt;/exclusions&gt;
    &lt;/dependency&gt;</code></pre><figcaption><p><span style="white-space: pre-wrap;">Some random code I found on the internet</span></p></figcaption></figure><p>Then I tried ye old faithful <code>mvn package</code> then ran <code>java -jar &lt;filename&gt;.jar</code> and lo and behold I got a bunch of errors that basically amounted to &quot;You don&apos;t have JDA installed&quot;. At this point I just banged my head against a wall a bunch of times, tried compiling it and rerunning to no avail.</p><p>Then I found an article that said that I needed to use some <a href="https://stackoverflow.com/questions/13620281/what-is-the-maven-shade-plugin-used-for-and-why-would-you-want-to-relocate-java" rel="noreferrer">random &quot;shade&quot; plugin</a>. I haven&apos;t really needed to use something like this in other projects but apparently this was a thing in Java. It adds the dependency library&apos;s code into the <code>jar</code> I had so I can run just one <code>jar</code> file with <code>java</code> simply. So I <s>stole</s> borrowed some code snippets and ended up with this snippet in my <code>pom.xml</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-pom.xml">        &lt;plugin&gt;
            &lt;artifactId&gt;maven-shade-plugin&lt;/artifactId&gt;
            &lt;version&gt;3.2.1&lt;/version&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;phase&gt;package&lt;/phase&gt;
                        &lt;goals&gt;
                            &lt;goal&gt;shade&lt;/goal&gt;
                        &lt;/goals&gt;
                        &lt;configuration&gt;
                &lt;transformers&gt;
                    &lt;transformer implementation=&quot;org.apache.maven.plugins.shade.resource.ManifestResourceTransformer&quot;&gt;
                        &lt;mainClass&gt;com.patrick.app.App&lt;/mainClass&gt; &lt;!-- You have to replace this with a path to your main class like my.path.Main --&gt;
                    &lt;/transformer&gt;
                &lt;/transformers&gt;
                &lt;createDependencyReducedPom&gt;false&lt;/createDependencyReducedPom&gt;
            &lt;/configuration&gt;
                &lt;/execution&gt;
            &lt;/executions&gt;
        &lt;/plugin&gt;</code></pre><figcaption><p><span style="white-space: pre-wrap;">Some more random code. The only thing I changed was my &quot;main class&quot;. I don&apos;t totally know how this works.</span></p></figcaption></figure><p>You may be getting the gist that I have no idea what I&apos;m doing. This is true. I was cobbling a bunch of code together. But in software engineering you learn by trying things out, reading other people&apos;s code, and other people telling you your way sucks.</p><p>I&apos;m sure that my code and configurations suck in someone else&apos;s eyes but as one engineer to another they&apos;ll at least somewhat respect that it works. Try to remember this when you worry about having perfect code. I&apos;ll dive more into my philosophical take on being a software engineer later.</p><p>But after these changes I was finally able to package it with <code>mvn package</code> and run it using <code>java -jar &lt;my-api-key&gt;</code>!</p><h2 id="step-5-man-it-didnt-work-%F0%9F%98%A6">Step 5: Man, it didn&apos;t work &#x1F626;</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.orewa.pw/content/images/2024/02/image-5.png" class="kg-image" alt loading="lazy" width="541" height="679"><figcaption><span style="white-space: pre-wrap;">when the code don&apos;t work</span></figcaption></figure><p>Ah yes, we were able to package it and it actually connected to Discord!<br>But whenever I ran the command, it broke or didn&apos;t give me the right answer. And I would fix something and it would break again.</p><p>So I started debugging. I can go further into detail but this is the list of things I had to fix that I can remember:</p><ul><li>My regex wasn&apos;t working for lines that ended with the <code>pfid</code> (which also wasn&apos;t used) so I was getting 106 hours of gameplay since it would skip a couple logs and calculate the time between the 5 days of me logging out and logging back in.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.orewa.pw/content/images/2024/02/Screenshot-2024-02-10-at-2.40.34-AM.png" class="kg-image" alt loading="lazy" width="694" height="174" srcset="https://blog.orewa.pw/content/images/size/w600/2024/02/Screenshot-2024-02-10-at-2.40.34-AM.png 600w, https://blog.orewa.pw/content/images/2024/02/Screenshot-2024-02-10-at-2.40.34-AM.png 694w"><figcaption><span style="white-space: pre-wrap;">It&apos;s not a lot of hours I&apos;m sure but hey it was definitely wrong.</span></figcaption></figure><ul><li>I also got 0 hours because I accidentally put the <code>name</code> and <code>xuid</code> options on the wrong slash command so it wasn&apos;t actually getting anything as an input.</li><li>I got the filtering function wrong so it was also giving me 0 hours after I fixed the previous bug.</li><li>I didn&apos;t find out that my timestamp from the Minecraft wasn&apos;t in the format needed by the <code>LocalDateTime.parse()</code> function so I had to reformat it.</li></ul><p>You may be asking, &quot;Hey, how the heck did you figure out what was wrong and how to fix it?&quot; My answer is it is the combination of:</p><ul><li>I&apos;ve seen similar-ish errors.</li><li>I had a lot of <code>System.out.println</code> statements to help me figure out what was happening at different parts of the application/if I was even reaching certain places.</li><li>A lot of Googling.</li></ul><p>But in the end, after tons of debugging...</p><h2 id="step-6-profit">Step 6: Profit!</h2><p>Here&apos;s a screenshot of it working:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.orewa.pw/content/images/2024/02/image.png" class="kg-image" alt loading="lazy" width="1040" height="526" srcset="https://blog.orewa.pw/content/images/size/w600/2024/02/image.png 600w, https://blog.orewa.pw/content/images/size/w1000/2024/02/image.png 1000w, https://blog.orewa.pw/content/images/2024/02/image.png 1040w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Unfortunately it doesn&apos;t tell you your options but I swear these are right.</span></figcaption></figure><h2 id="step-7-you-thought-i-was-done">Step 7: You thought I was done?</h2><p>Now that I was done, I&apos;m done right?<br>... not really.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.orewa.pw/content/images/2024/02/image-8.png" class="kg-image" alt loading="lazy" width="291" height="274"><figcaption><span style="white-space: pre-wrap;">everything is ok i swear. i love java.</span></figcaption></figure><p><s>So that previous screenshot was kind of a lie. I figured out how to get it to work if I DM&apos;d the bot directly but I haven&apos;t figured out how to do it so I can call it from any server it&apos;s invited to and from any random channel.</s></p><p>So the previous paragraph was also kind of a lie. After eating dinner and coming back to my computer, it turns out the channel was finally able to pick up the commands and work on a server channel and not just through DMs! Success!</p><p>But that functionality aside, I also wanted to <em>refactor</em> the code. Refactoring is the act of taking your code and cleaning it up. It can be renaming variables, making duplicate code into functions, etc.</p><p>There&apos;s no such thing as perfect code but it&apos;s a good learning opportunity of looking through your first (or fiftieth) draft and trying to improve it to a &quot;final&quot; draft.</p><p>A couple examples of the refactors I did:</p><ul><li>I removed a bunch of unused code lol</li><li>In <code>ConnectionLogAggregator</code>, the <code>getTimePlayedByXuid</code> and <code>getTimePlayedByName</code> were both kind of big and had a lot of duplicate code. I decided to move the duplicate code out into its own function which ended up being <code>getTimePlayedFromLogs</code></li></ul><p>This isn&apos;t a huge project and I&apos;m not doing it professionally but there&apos;s definitely more refactors I could do and cleaner code I could have wrote but this was my approach.</p><h2 id="conclusion">Conclusion</h2><p>There we go! That was my adventure learning how to set up Java, Maven, and an external library! It was hard but I figured it out and hopefully you got a glimpse of how a software engineer might think. But we all think differently so I guess you only saw how I think.</p><p>But I want you to hopefully have the following takeaways (if I did a bad job explaining, I will try to make edits here or explain to you directly):</p><ul><li>How to set up Java locally (and Maven if needed) so you can run stuff at home.</li><li>No one knows everything and have to Google a LOT of stuff. With repetition and pattern recognition, you might be able to not Google as much but sometimes I still need to remember how to get the length of a list/string.</li><li>There is no one or perfect solution to any software problem. There are better and worse ones but I can guarantee you&apos;ll be hard pressed to do it the first time too. Write something that works and then you can work on making it better.</li><li>Speaking of making things better, learn early how to write code that works and refactor it. Getting it working first is important for learning and then try to clean it up later.</li><li>Going into a project with some kind of high-level idea of how you want it laid out will help you save time. I spent some time thinking about what my application needed to do and was able to glue them all together rather than just randomly typing. Learning how to lay things out comes with experience.</li><li>Making mistakes and writing extra stuff that is unnecessary is natural. I wasted so much time writing random code and trying random stuff that didn&apos;t actually do anything. I&apos;ve learned from many mistakes and detours and make less of those mistakes and waste less time since I can see further out that something isn&apos;t going to work. But I still waste time because I&apos;m not perfect &#x1F920;</li><li>Software engineering is built on the shoulders of giants and they&apos;re so giant we can&apos;t always see everything they saw. What I mean is for me, one of the beautiful parts of software engineering is I can do awesome things without knowing exactly what everything does. This can be dangerous but also can help you do a lot of things quickly. As long as you trust where the code comes from, that&apos;s the best we can ask for.</li></ul><p>This is over 3,400 words. I haven&apos;t written this much since college. Hope you enjoyed &#x1F603;</p><figure class="kg-card kg-image-card"><img src="https://blog.orewa.pw/content/images/2024/02/image-9.png" class="kg-image" alt loading="lazy" width="550" height="422"></figure><p>Good luck hacking!</p><p></p><p><em>Editors note: Big thanks to TH for editing/reading over this. Couldn&apos;t have done this without your help, bud!</em></p>]]></content:encoded></item><item><title><![CDATA[Apple Vision Pro?!]]></title><description><![CDATA[<p>Yes that&apos;s right, within 2 posts I&apos;ve become a sellout tech journalist.</p><p>Just kidding.</p><p>But I will be writing about my thoughts on the Apple Vision Pro. There will be spoilers about the experience so if you care about that, I guess don&apos;t read</p>]]></description><link>https://blog.orewa.pw/apple-vision-pro/</link><guid isPermaLink="false">65c7b72156dc16000139edba</guid><dc:creator><![CDATA[Pat W]]></dc:creator><pubDate>Sat, 10 Feb 2024 19:03:34 GMT</pubDate><content:encoded><![CDATA[<p>Yes that&apos;s right, within 2 posts I&apos;ve become a sellout tech journalist.</p><p>Just kidding.</p><p>But I will be writing about my thoughts on the Apple Vision Pro. There will be spoilers about the experience so if you care about that, I guess don&apos;t read this.</p><h2 id="part-1-signup">Part 1: Signup</h2><p>I went to the Apple store on whim since I saw they were advertising the Apple Vision Pro. I thought it was a walk-in and try type deal but you actually have to sign up for a guided tour.</p><p>I used a in-store QR code to sign up for a time that was only 30 minutes later, lucky me. I think <a href="https://www.apple.com/apple-vision-pro/guided-tour/" rel="noreferrer">this link</a> and the &quot;Book a demo&quot; up top will help you do that if you wanna try.</p><h2 id="part-2-i-dont-wear-my-glasses">Part 2: I don&apos;t wear my glasses?</h2><p>Apparently they partnered with Zeiss lenses to build custom lenses for the Apple Vision Pro. They took my glasses and gave me close to my prescription lenses based of a reading. It was pretty close. 85-90% clarity. They said they have 1000 lenses to match most prescriptions. </p><p>When you buy an Apple Vision Pro, you can buy a pair for $200 I think is what they said.</p><h2 id="part-3-setup">Part 3: Setup</h2><p>Setup was pretty easy. A quick hand scan then you do eye-tracking scanning by looking at 6 dots in a circle 3 times in different lighting while tapping your fingers to &quot;click&quot;.</p><h2 id="part-4-first-look">Part 4: First look</h2><p>Now we get to the actual &quot;demo-y&quot; part. After greeting you with floating text around your area, it drops you into a home screen with &quot;low immersion&quot;.</p><p>The &quot;immersion&quot; is how much you get rid of your background. At lowest, I saw everything in the Apple store and at highest I saw a background of Mt. Hood.</p><p>The screens are like 95% of the way there. The fidelity was close enough to almost trick you it was real but just blurry/off-colored for you to realize it isn&apos;t real. This comes up with some of their demos and my overall experience.</p><h2 id="part-5-demo-1-photos">Part 5: Demo 1, Photos</h2><p>First demo was the photos app. You could zoom in and out, scroll, and go to different albums. I jumped around and didn&apos;t listen to instructions but the gist of the guided tour was:</p><ul><li>First photo: a normal photo where you can just zoom in, pan, etc.</li><li>Second photo: immersive photo that looks pretty 3D.</li><li>Third was a video recorded on Apple Vision Pro of a birthday cake getting blown out by some kids and an adult. It was pretty wild how realistic it was. The depth perception of the video really takes it from &quot;I&apos;m watching a video recording&quot; to &quot;I&apos;m in the video recording&quot;. The slightest blur and how weird my hand looked when I held it up to the video brought me back to reality.</li><li>Fourth was a video recorded on an iPhone 15 Pro Max. It was cool but not as good as the Apple Vision Pro video.</li></ul><p>I asked about how large the video files were and the Apple Genius said &quot;large&quot; alluding to the fact iPhone 15 Pro Max and Apple Vision Pro minimum storage was 256gb.</p><p>I also learned about moving apps around (there&apos;s a iPhone home-bar-esque bar that you can select to drag around) and close apps with a small x.</p><p>It was cool to move apps around and just leave them there to do other things. I&apos;ve seen tech demos of this but I wasn&apos;t allowed to do too much and push it.</p><h2 id="part-6-demo-2-videos">Part 6: Demo 2, Videos</h2><p>I was then guided to the Apple TV app to watch some movie trailers. It was cool! You could choose your background or go into a &quot;cinema&quot; view. It was somewhat similar to other VR headsets that I&apos;ve used (albeit clearer with tech that&apos;s 5 years newer). It had some cool options I hadn&apos;t seen before. You were able to choose &quot;where&quot; in the theater you wanted to sit. Up front, middle, back and balcony were options. I liked it.</p><p>Then I got to watch their &quot;Immersive Experience&quot; video which was obviously tailored to show off the max 3D ability of the Apple Vision Pro and I must say it did a great job. The videos were super immersive and at times the animals shown made me back up from how close they felt they got. There were some sports scenes that were shown where it really did feel like I was behind the soccer goal or next to first base.</p><p>Though as a techy nerd, I know that it&apos;s just not feasible live (I think) with the sheer quantity of data and resolution needed to do it for every sporting event but we&apos;re getting pretty darned close.</p><h2 id="part-7-messing-around-while-theyre-trying-to-sell-me-the-vision-pro">Part 7: Messing around while they&apos;re trying to sell me the Vision Pro</h2><p>I then tried to mess around while the Apple Genius wasn&apos;t paying attention/busy typing on her iPad.</p><p>I opened up the Notes App and tried to have multiple instances open but couldn&apos;t figure it out.</p><p>Typing on the Apple Vision Pro is definitely clunky. I&apos;d just look at different letters and click one at a time. I&apos;d love to either have a portable keyboard OR for touch typers to calibrate how we&apos;d type and make a best guess at what we&apos;re typing based on where I put my fingers. That&apos;s the dream.</p><p>Then I had to end the demo &#x1F626;</p><h2 id="conclusion">Conclusion</h2><p>I think the videos and demos are great but it&apos;s very tech demo-y. It&apos;s 180deg 8k video footage which is HUGE and most people and media won&apos;t have access to. I think it&apos;s a great window into what tech might look like and what consumer tech can accomplish in the next 3, 5, or 10 years. So I <strong><em>DON&apos;T</em></strong> think I&apos;d buy one at $3,500 right now but maybe cheaper and better in the future &#x1F600;</p><p>Some things I wanted to try but couldn&apos;t:</p><ul><li>Connecting it to a Macbook to use it as extended monitors and use an actual keyboard with it.</li><li>Seeing what apps exist and if there are any cool IDEs.</li></ul><p>The coolest things I liked about it:</p><ul><li>I love the eye-tracking. With better calibration, it&apos;d be awesome and super intuitive.</li><li>The hand-tracking is also pretty cool and felt really natural. A lot of other VR headsets make me hold up my hands and I&apos;m lazy and get tired. So this is a really cool change.</li><li>The feel of the headset. Fitting and not wearing glasses and the weight were all great and very comfy.</li></ul><p>Some other cons I&apos;ve heard/experienced:</p><ul><li>Bad-ish battery life esp if you&apos;re consuming a lot of content</li><li>The battery is a bit unwieldy still even though it&apos;s a significant improvement over a lot of older VR sets.</li><li>The resolution is ALMOST there. Still just a wee bit blurry. Cameras/data transfer speeds will only continue to get better.</li></ul><p>I feel like for now the Meta Quest 3 or something would scratch the VR itch until we all get there later.</p>]]></content:encoded></item><item><title><![CDATA[VPS + DNS + Docker + Portainer + Caddy + Ghost = Here we are!]]></title><description><![CDATA[<p>After over 3 hours of me banging my head against the wall, I&apos;ve finally figured out how to get all this junk working and now I have a blog!</p><p>Unfortunately, I&apos;m still learning a lot so parts of these instructions may be extraneous and I welcome</p>]]></description><link>https://blog.orewa.pw/vps-docker-portainer-caddy-ghost-here-we-are/</link><guid isPermaLink="false">65b4adec56dc16000139ebcf</guid><dc:creator><![CDATA[Pat W]]></dc:creator><pubDate>Sat, 10 Feb 2024 05:21:20 GMT</pubDate><content:encoded><![CDATA[<p>After over 3 hours of me banging my head against the wall, I&apos;ve finally figured out how to get all this junk working and now I have a blog!</p><p>Unfortunately, I&apos;m still learning a lot so parts of these instructions may be extraneous and I welcome everyone smarter than me to help me learn!</p><p>Here&apos;s how I got it from ground zero:</p><h2 id="step-1-get-a-vps-and-a-domain">Step 1: Get a VPS and a domain</h2><p>I used <a href="vultr.com" rel="noreferrer">Vultr</a> as my VPS but you could use DigitalOcean, etc. I personally have grown to like Debian as my Linux distro of choice so I&apos;ll be using that.<br>Blogs use very little resources so if you&apos;re only doing this, you could get away with the lowest amount of stuff.</p><p>I used <a href="namecheap.com" rel="noreferrer">Namecheap</a> but you can use Google Domains, etc.</p><h2 id="step-2-point-your-domain-at-your-vps">Step 2: Point your domain at your VPS</h2><p>Namecheap makes it pretty easy to add DNS records so you just have to add 2 <code>A</code> records:</p><ul><li>One with a <code>host</code> of <code>*</code> to pick up all the subdomains</li><li>another with a <code>host</code> of <code>@</code> to pick up the <code>www</code> and <code>domain.tld</code> versions of your URL.</li></ul><p>They&apos;ll both use your VPS&apos;s IP address in the <code>value</code> section.</p><h2 id="step-3-installing-docker">Step 3:  Installing Docker</h2><p>I just used <a href="https://docs.docker.com/engine/install/debian/" rel="noreferrer">this set of instructions</a> from Docker to install it. The gist is:</p><ul><li><code>apt update</code></li><li>Add their repo</li><li>Install Docker and its dependencies</li></ul><p>Most likely, you&apos;ll be root in your VPS but if you made a user, feel free to add yourself to the <code>docker</code> group so you don&apos;t have to <code>sudo</code> every time.</p><p>The command would be <code>/usr/sbin/usermod -aG docker &lt;username&gt;</code>.</p><h2 id="step-4-installing-portainer">Step 4: Installing Portainer</h2><p>I personally really enjoy using Portainer since it simplifies doing a lot actions in Docker. To install it is pretty straightforward.</p><p>Following <a href="https://docs.portainer.io/start/install-ce/server/docker/linux" rel="noreferrer">these instructions</a>, just creating a volume and the container is enough. Once it&apos;s ready, we&apos;ll be doing stuff in Portainer.</p><p>Go to <code>https://&lt;your-ip&gt;:9443</code> and finish the initial setup.</p><p>To be safe, we <em>should</em> be setting up the VPS with a VPN and connecting through there but for speed just have a good username/password.</p><h2 id="step-5-installing-caddy">Step 5: Installing Caddy</h2><p>Once we have Portainer installed, the rest is pretty straightforward! I was pleasantly surprised about how fluid setting up environments and such are these days.</p><p>First, make sure that you have the <code>configs/caddy</code> folder set up. Then we can create a <code>Caddyfile</code> there already or else it will be angry and say you&apos;re trying to map a file to a directory. </p><p>Here&apos;s a sample one that I used:</p><figure class="kg-card kg-code-card"><pre><code class="language-Caddyfile">&lt;REDACTED&gt;.orewa.pw {
	reverse_proxy http://172.17.0.2:9000
}

blog.orewa.pw {
	reverse_proxy ghost:2368
	header {
		header_up Strict-Transport-Security max-age=31536000; includeSubDomains; preload
		header_up X-Forwarded-Proto {scheme}
		header_up X-Forwarded-For {remote}
		header_up X-Real-IP {remote}
	}
}</code></pre><figcaption><p dir="ltr"><span style="white-space: pre-wrap;">Caddyfile</span></p></figcaption></figure><p>The first entry is for the Portainer instance. I think (?) I could probably use the container name instead of the Docker IP but it works... This way I don&apos;t have to go to a port, just a nice ole subdomain.</p><p>The second entry has some settings that I saw help with reverse proxying for this Ghost blog.</p><p>I love <code>docker compose</code> (especially coming from typing hundreds of <code>docker</code> commands), it <a href="https://docs.docker.com/compose/" rel="noreferrer">simplifies and speeds up the process</a> for me.</p><p>In Portainer, they&apos;re called <code>stacks</code>. Here&apos;s the one I used:</p><figure class="kg-card kg-code-card"><pre><code class="language-YAML">version: &quot;3.9&quot;

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - &quot;80:80&quot;
      - &quot;443:443&quot;
      - &quot;443:443/udp&quot;
    volumes:
      - /root/configs/caddy/Caddyfile:/etc/caddy/Caddyfile
      - /root/configs/caddy/site:/srv
      - caddy_data:/data
      - caddy_config:/config
    network_mode: bridge

volumes:
  caddy_data:
  caddy_config:</code></pre><figcaption><p dir="ltr"><span style="white-space: pre-wrap;">Portainer Caddy stack</span></p></figcaption></figure><p>Once you run the  stack, Caddy should be up and running!</p><h2 id="step-6-setting-up-ghost">Step 6: Setting up Ghost</h2><p>Now that we&apos;re more acclimated with Portainer stacks, here&apos;s the one for Ghost! It sets up the Ghost and the MySQL containers. You just have to set the root password as the same thing for both and your URL.  </p><figure class="kg-card kg-code-card"><pre><code class="language-YAML">version: &apos;3.1&apos;

services:
  ghost:
    container_name: ghost
    image: ghost:5-alpine
    restart: always
    ports:
      - 2368:2368
    environment:
      database__client: mysql
      database__connection__host: db
      database__connection__user: root
      database__connection__password: &lt;PASSWORD&gt;
      database__connection__database: ghost
      url: https://&lt;your-url&gt;
      NODE_ENV: development
    volumes:
      - ghost:/var/lib/ghost/content

  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: &lt;PASSWORD&gt;
    volumes:
      - db:/var/lib/mysql

volumes:
  ghost:
  db:</code></pre><figcaption><p dir="ltr"><span style="white-space: pre-wrap;">Portainer Ghost stack</span></p></figcaption></figure><p>Badabing badaboom, your Ghost stack is up.</p><p>Once it&apos;s up, you can go to <code>https://&lt;YOUR-URL&gt;/ghost</code> and set up your blog! You don&apos;t have to use a real email if you don&apos;t want.</p><h2 id="conclusion">Conclusion</h2><p>Now you have Portainer and a blog running! The cool part is you can extrapolate from the Caddyfile and Portainer stacks to spin up as many containers you want! Very exciting stuff.</p><p>Good luck hacking!</p>]]></content:encoded></item></channel></rss>