<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Cyber</title>
    <description>The latest articles on Forem by Cyber (@cyberdev_).</description>
    <link>https://forem.com/cyberdev_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1428945%2Fb266b83f-eebc-42a8-adf5-64c6eadb8690.jpg</url>
      <title>Forem: Cyber</title>
      <link>https://forem.com/cyberdev_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/cyberdev_"/>
    <language>en</language>
    <item>
      <title>My Top 5 Self-Hosted Tools Running on My Home Server via Docker (Part 2)</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Sat, 21 Feb 2026 03:47:57 +0000</pubDate>
      <link>https://forem.com/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-part-2-4aln</link>
      <guid>https://forem.com/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-part-2-4aln</guid>
      <description>&lt;p&gt;After sharing my first list of self-hosted tools, I realized something interesting:&lt;br&gt;
my home server isn't just replacing SaaS apps anymore, it’s replacing &lt;em&gt;habits&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Instead of doom-scrolling feeds, losing track of articles, or fighting with resume builders, I now rely on a second set of self-hosted tools that handle &lt;strong&gt;content consumption, learning, and utility tasks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are five more services I run daily, all self-hosted using Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/FreshRSS/FreshRSS" rel="noopener noreferrer"&gt;FreshRSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/booklore-app/booklore" rel="noopener noreferrer"&gt;Booklore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/advplyr/audiobookshelf" rel="noopener noreferrer"&gt;Audiobookshelf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CorentinTh/it-tools" rel="noopener noreferrer"&gt;IT-Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/amruthpillai/reactive-resume" rel="noopener noreferrer"&gt;Reactive Resume&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I Keep Expanding My Self-Hosted Stack
&lt;/h2&gt;

&lt;p&gt;At this point, self-hosting is less about saving money and more about &lt;strong&gt;intentional software&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What I care about now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No algorithms deciding what I read&lt;/li&gt;
&lt;li&gt;Offline-friendly content&lt;/li&gt;
&lt;li&gt;Tools that do &lt;em&gt;one thing well&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Full control over my data&lt;/li&gt;
&lt;li&gt;Apps that don’t disappear behind a paywall&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These five tools fit that philosophy perfectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. FreshRSS – Taking Back Control of My Reading
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2g5mdqoz74yj8l5shpz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2g5mdqoz74yj8l5shpz.jpg" alt=" " width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Feedly, Inoreader, Twitter/X, Google News, etc&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; News, blogs, YouTube channels, Reddit, updates&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Browser, Android (via FeedMe)&lt;/p&gt;

&lt;p&gt;FreshRSS is the backbone of how I consume information.&lt;/p&gt;

&lt;p&gt;Instead of relying on social media or algorithm-driven feeds, I subscribe directly to blogs, YouTube channels, and news sources I actually care about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why FreshRSS Is a Game Changer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chronological, distraction-free reading&lt;/li&gt;
&lt;li&gt;Tags, filters, and categories&lt;/li&gt;
&lt;li&gt;Supports thousands of feeds&lt;/li&gt;
&lt;li&gt;Extremely lightweight&lt;/li&gt;
&lt;li&gt;Works with almost any RSS client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use it to follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tech blogs&lt;/li&gt;
&lt;li&gt;Open-source projects&lt;/li&gt;
&lt;li&gt;DevOps updates&lt;/li&gt;
&lt;li&gt;Personal blogs&lt;/li&gt;
&lt;li&gt;And more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No rage-bait. No engagement tricks. Just content - on my terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Booklore - My Personal Digital Library
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpaqaibzg52hl6icvzwbv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpaqaibzg52hl6icvzwbv.jpg" alt=" " width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Goodreads, Kindle notes, random folders&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Tracking and organizing books&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Browser &lt;/p&gt;

&lt;p&gt;Booklore is one of those tools that feels small - until you start using it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I Love It&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean and simple UI&lt;/li&gt;
&lt;li&gt;Self-hosted book tracking&lt;/li&gt;
&lt;li&gt;No ads, no social pressure&lt;/li&gt;
&lt;li&gt;Full control over metadata&lt;/li&gt;
&lt;li&gt;Easy backups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I plan to use Booklore to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track reading progress (just 2 books for now haha)&lt;/li&gt;
&lt;li&gt;Plan future reading&lt;/li&gt;
&lt;li&gt;Keep notes on technical books&lt;/li&gt;
&lt;li&gt;Maintain a long-term reading archive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's calm, focused, and does exactly what I need.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Audiobookshelf - Your Own Audible, Minus the Lock-In
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3blikwl272hdcyyszfi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3blikwl272hdcyyszfi.jpg" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Audible, cloud-based audiobook apps&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Audiobooks and podcasts&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, iOS, Browser&lt;/p&gt;

&lt;p&gt;It turns your audiobook collection into a polished streaming platform, complete with progress syncing and mobile apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It’s Amazing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports audiobooks &lt;em&gt;and&lt;/em&gt; podcasts&lt;/li&gt;
&lt;li&gt;Beautiful UI&lt;/li&gt;
&lt;li&gt;Remembers listening position&lt;/li&gt;
&lt;li&gt;Mobile apps + web player&lt;/li&gt;
&lt;li&gt;Metadata fetching and organization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I plan to use it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tech and productivity audiobooks&lt;/li&gt;
&lt;li&gt;Long-form learning&lt;/li&gt;
&lt;li&gt;Podcasts I want to archive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No DRM. No subscriptions. Just my library, everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. IT-Tools – The Swiss Army Knife for Developers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fui7dkctmkvn1wokf3lwu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fui7dkctmkvn1wokf3lwu.jpg" alt=" " width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Random online tools, shady websites&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Dev utilities and quick conversions&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Browser&lt;/p&gt;

&lt;p&gt;IT-Tools is one of those things you don’t realize you need… until you have it.&lt;/p&gt;

&lt;p&gt;It’s a collection of dozens of small utilities that developers constantly Google for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools I Use the Most&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON formatter&lt;/li&gt;
&lt;li&gt;Base64 encoder/decoder&lt;/li&gt;
&lt;li&gt;UUID generator&lt;/li&gt;
&lt;li&gt;Hash generators&lt;/li&gt;
&lt;li&gt;Date/time tools&lt;/li&gt;
&lt;li&gt;Regex helpers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why I Self-Host It&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works offline&lt;/li&gt;
&lt;li&gt;No tracking&lt;/li&gt;
&lt;li&gt;No ads&lt;/li&gt;
&lt;li&gt;Instant access on my network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s bookmarked on every device I own.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Reactive Resume - My Resume, Always Ready
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbvbue85wmmytvawtwul.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbvbue85wmmytvawtwul.jpg" alt=" " width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Online resume builders&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Resume creation and versioning&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Browser&lt;/p&gt;

&lt;p&gt;Reactive Resume solves a very specific problem:&lt;br&gt;
&lt;em&gt;I want a great resume, but I don’t want my career data locked behind a SaaS platform.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It's Worth Hosting&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean, modern resume templates&lt;/li&gt;
&lt;li&gt;JSON-based resume data&lt;/li&gt;
&lt;li&gt;Multiple versions for different roles&lt;/li&gt;
&lt;li&gt;Export to PDF&lt;/li&gt;
&lt;li&gt;Fully self-hosted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintain different resumes&lt;/li&gt;
&lt;li&gt;Quickly tailor applications&lt;/li&gt;
&lt;li&gt;Keep my career history backed up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No watermarks. No subscriptions. No surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Setup (Same Philosophy, Same Simplicity)
&lt;/h2&gt;

&lt;p&gt;Just like my first stack, everything here runs in Docker using Docker Compose.&lt;/p&gt;

&lt;p&gt;Each service has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own container&lt;/li&gt;
&lt;li&gt;Persistent volumes&lt;/li&gt;
&lt;li&gt;Reverse proxy routing&lt;/li&gt;
&lt;li&gt;HTTPS via Let's Encrypt (if needed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the base setup exists, adding services like these takes minutes - not hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Usage (Still Very Reasonable)
&lt;/h2&gt;

&lt;p&gt;Even with this expanded stack, resource usage stays low.&lt;/p&gt;

&lt;p&gt;Typical requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FreshRSS: extremely light&lt;/li&gt;
&lt;li&gt;IT-Tools: negligible&lt;/li&gt;
&lt;li&gt;Booklore: very light&lt;/li&gt;
&lt;li&gt;Audiobookshelf: moderate (storage-heavy, CPU-light)&lt;/li&gt;
&lt;li&gt;Reactive Resume: minimal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This entire setup runs comfortably on the same home server without breaking a sweat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Self-hosting isn't about running &lt;em&gt;everything&lt;/em&gt; yourself.&lt;/p&gt;

&lt;p&gt;It's about choosing the right things to own.&lt;/p&gt;

&lt;p&gt;With these five tools, I’ve taken control of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What I read&lt;/li&gt;
&lt;li&gt;What I listen to&lt;/li&gt;
&lt;li&gt;How I manage my knowledge&lt;/li&gt;
&lt;li&gt;How I apply for jobs&lt;/li&gt;
&lt;li&gt;How I work day-to-day as a developer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Combined with &lt;a href="https://dev.to/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-52c5"&gt;my first list&lt;/a&gt;, my home server now replaces:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notes / journaling → &lt;strong&gt;Anynote&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Password managers → &lt;strong&gt;Vaultwarden&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Knowledge bases / documentation → &lt;strong&gt;AFFiNE&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Bookmarks / link management → &lt;strong&gt;Linkwarden&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Task management / Kanban → &lt;strong&gt;Vikunja&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;RSS readers / feed aggregators → &lt;strong&gt;FreshRSS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Book tracking / library management → &lt;strong&gt;Booklore&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Audiobooks / podcasts → &lt;strong&gt;Audiobookshelf&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Dev utilities / quick tools → &lt;strong&gt;IT-Tools&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Resume builders / CV tools → &lt;strong&gt;Reactive Resume&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And honestly?&lt;br&gt;
I wouldn't go back.&lt;/p&gt;

&lt;p&gt;If you're building your own self-hosted stack, this is a &lt;em&gt;fantastic&lt;/em&gt; second wave of services to try. &lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>docker</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My Top 5 Self-Hosted Tools Running on My Home Server via Docker (Part 1)</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Wed, 28 Jan 2026 16:17:39 +0000</pubDate>
      <link>https://forem.com/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-52c5</link>
      <guid>https://forem.com/cyberdev_/my-top-5-self-hosted-tools-running-on-my-home-server-via-docker-52c5</guid>
      <description>&lt;p&gt;Running your own home server is one of the most rewarding things you can do if you care about privacy, control, and learning real-world infrastructure skills. Over the past year, I've been slowly replacing SaaS tools with self-hosted alternatives - all running neatly inside Docker containers.&lt;/p&gt;

&lt;p&gt;Today I want to share my top 5 self-hosted apps that I use daily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/ychisbest/AnyNote" rel="noopener noreferrer"&gt;Anynote&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/dani-garcia/vaultwarden" rel="noopener noreferrer"&gt;Vaultwarden&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/toeverything/AFFiNE" rel="noopener noreferrer"&gt;AFFiNE&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/linkwarden/linkwarden" rel="noopener noreferrer"&gt;Linkwarden&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/go-vikunja/vikunja" rel="noopener noreferrer"&gt;Vikunja&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools cover notes, passwords, project management, and knowledge organization - basically my entire productivity stack.&lt;/p&gt;

&lt;p&gt;Let's dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Self-Host Everything
&lt;/h2&gt;

&lt;p&gt;Before getting into the tools, here's why I host my own services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data ownership&lt;/strong&gt; - My data stays on my hardware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No subscriptions&lt;/strong&gt; - One-time setup beats monthly fees&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customization&lt;/strong&gt; - I control updates, storage, and access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning experience&lt;/strong&gt; - Docker, networking, reverse proxies, backups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything below runs using Docker and Docker Compose on my home server, which makes upgrades, backups, and migrations painless.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Anynote - Simple, Fast Personal Notes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqv2bvzl91hak7cl5rki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqv2bvzl91hak7cl5rki.png" alt=" " width="515" height="885"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Google Keep / Apple Notes&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Lightweight note-taking and quick ideas&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Desktop (Windows) app&lt;/p&gt;

&lt;p&gt;Anynote is a minimal and fast note-taking app that I use for quick thoughts, daily logs, shopping lists, and scratch notes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I Like It
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clean UI with no distractions&lt;/li&gt;
&lt;li&gt;Lightweight and fast even on low-power servers&lt;/li&gt;
&lt;li&gt;Markdown support&lt;/li&gt;
&lt;li&gt;Easy backups via mounted volumes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It loads instantly and doesn't try to become a "second brain system." Sometimes simple is exactly what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Vaultwarden (Bitwarden) - Password Manager That Just Works
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9b5x0d7xv65uzxjt8jao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9b5x0d7xv65uzxjt8jao.png" alt=" " width="800" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Bitwarden Cloud (or any password manager)&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Secure password management&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Desktop (Windows) App + Browser&lt;/p&gt;

&lt;p&gt;Vaultwarden is hands down one of the best self-hosted services you can run.&lt;/p&gt;

&lt;p&gt;It's a lightweight Rust implementation of Bitwarden's server and works perfectly with official Bitwarden clients on desktop and mobile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It's Essential
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;End-to-end encryption&lt;/li&gt;
&lt;li&gt;Works with browser extensions and mobile apps&lt;/li&gt;
&lt;li&gt;Extremely low resource usage&lt;/li&gt;
&lt;li&gt;Easy to secure behind HTTPS and 2FA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the first service I ever self-hosted - and I've never looked back.&lt;br&gt;
If you host only one thing at home, make it this.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. AFFiNE - My Notion Replacement
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pyj3m7tsgi7s9n9l5zo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pyj3m7tsgi7s9n9l5zo.png" alt=" " width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Notion&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Knowledge bases, documentation, personal wikis&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Desktop (Windows) App + Browser&lt;/p&gt;

&lt;p&gt;AFFiNE is an open-source workspace tool that combines documents, whiteboards, and databases.&lt;br&gt;
Think Notion + Obsidian + Miro - but self-hosted.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Makes AFFiNE Awesome
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Block-based editing&lt;/li&gt;
&lt;li&gt;Whiteboard mode&lt;/li&gt;
&lt;li&gt;Local-first with sync&lt;/li&gt;
&lt;li&gt;Clean modern UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use AFFiNE for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project documentation&lt;/li&gt;
&lt;li&gt;Learning notes&lt;/li&gt;
&lt;li&gt;Long-term knowledge storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's actively developed and improving rapidly, which makes it exciting to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Linkwarden - Bookmark Manager but better
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmc12wrgcuwy7f22wkc19.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmc12wrgcuwy7f22wkc19.jpg" alt=" " width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Browser bookmarks, Pocket, Raindrop&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Saving articles and resources&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Browser&lt;/p&gt;

&lt;p&gt;Linkwarden solved a problem I didn't realize I had: bookmark chaos.&lt;/p&gt;

&lt;p&gt;Instead of losing links across browsers and devices, I now have a centralized searchable archive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I Use It Daily
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tagging and collections&lt;/li&gt;
&lt;li&gt;Full-page snapshots&lt;/li&gt;
&lt;li&gt;Searchable content&lt;/li&gt;
&lt;li&gt;Beautiful UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development resources&lt;/li&gt;
&lt;li&gt;Tutorials&lt;/li&gt;
&lt;li&gt;Articles I want to revisit&lt;/li&gt;
&lt;li&gt;Reference material&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also integrates nicely with browser extensions for one-click saving.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Vikunja - Open-Source Task Management
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrb5a4o5b65lxlvz4hcj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrb5a4o5b65lxlvz4hcj.jpg" alt=" " width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it replaces:&lt;/strong&gt; Todoist / Trello&lt;br&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; Tasks, projects, kanban boards&lt;br&gt;
&lt;strong&gt;Where:&lt;/strong&gt; Android, Browser&lt;/p&gt;

&lt;p&gt;Vikunja is my go-to task manager. It's flexible enough to replace both simple to-do apps and full kanban boards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features I Love
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Lists, projects, and namespaces&lt;/li&gt;
&lt;li&gt;Kanban board view&lt;/li&gt;
&lt;li&gt;Due dates and reminders&lt;/li&gt;
&lt;li&gt;API and mobile app support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use it to manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal tasks&lt;/li&gt;
&lt;li&gt;Home server maintenance&lt;/li&gt;
&lt;li&gt;Learning goals&lt;/li&gt;
&lt;li&gt;Long-term projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's fast, reliable, and easy to self-host.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Docker Setup (High Level)
&lt;/h2&gt;

&lt;p&gt;All of these services run using Docker Compose.&lt;/p&gt;

&lt;p&gt;Each service has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own container&lt;/li&gt;
&lt;li&gt;Mounted volumes for persistent data&lt;/li&gt;
&lt;li&gt;Reverse proxy routing (Nginx/Traefik)&lt;/li&gt;
&lt;li&gt;Automatic HTTPS via Let's Encrypt x&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup gives me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy backups&lt;/li&gt;
&lt;li&gt;Simple upgrades&lt;/li&gt;
&lt;li&gt;Isolation between services&lt;/li&gt;
&lt;li&gt;Minimal downtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you learn Docker basics, adding new services becomes trivial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Usage (Surprisingly Low)
&lt;/h2&gt;

&lt;p&gt;One of the biggest myths is that self-hosting needs powerful hardware.&lt;/p&gt;

&lt;p&gt;My stack runs comfortably on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12GB RAM&lt;/li&gt;
&lt;li&gt;Low-power CPU&lt;/li&gt;
&lt;li&gt;SSD storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vaultwarden and Anynote barely consume resources, while AFFiNE and Linkwarden are the heavier ones - but still very manageable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Self-hosting changed how I think about software.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Which app should I subscribe to?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I now ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Which service should I host next?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These five tools cover almost everything I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notes&lt;/li&gt;
&lt;li&gt;Passwords&lt;/li&gt;
&lt;li&gt;Knowledge base&lt;/li&gt;
&lt;li&gt;Bookmarks&lt;/li&gt;
&lt;li&gt;Tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're just starting out, I recommend this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Vaultwarden&lt;/li&gt;
&lt;li&gt;Vikunja&lt;/li&gt;
&lt;li&gt;Linkwarden&lt;/li&gt;
&lt;li&gt;AFFiNE&lt;/li&gt;
&lt;li&gt;Anynote&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you get comfortable, your home server quickly becomes your personal cloud.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>🏠☠️ Home Alone: Exposing My Home Server to the Internet (and Judgment) with FRP + Jenkins + Bash</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Tue, 08 Jul 2025 11:42:11 +0000</pubDate>
      <link>https://forem.com/cyberdev_/home-alone-exposing-my-home-server-to-the-internet-and-judgment-with-frp-jenkins-bash-1lo0</link>
      <guid>https://forem.com/cyberdev_/home-alone-exposing-my-home-server-to-the-internet-and-judgment-with-frp-jenkins-bash-1lo0</guid>
      <description>&lt;p&gt;TL;DR:&lt;/p&gt;

&lt;p&gt;I turned my home server into a publicly available chaos machine by automating FRP tunnels from Jenkins… running on a remote VPS… like some kind of over-caffeinated Bond villain.&lt;/p&gt;

&lt;p&gt;After all, who doesn't want to broadcast their cat cam to friends?&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 The Setup (a.k.a. the Overkill Stack)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🧠 Home Server (Worker Jenkins Node) on private network - Raspberry Pi, old laptop, toaster with Linux - whatever you have.&lt;/li&gt;
&lt;li&gt;🌍 VPS (Main Jenkins Node) - Runs Jenkins jobs to control FRP clients.&lt;/li&gt;
&lt;li&gt;🚇 FRP (Fast Reverse Proxy) - An open-source alternative to Ngrok.&lt;/li&gt;
&lt;li&gt;🤖 Jenkins - Because we like suffering, but reproducible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🌟 Goal
&lt;/h2&gt;

&lt;p&gt;From Jenkins (already connected to the home server via a build agent), we want to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Spin up secure FRP tunnels that expose ports from the home server.&lt;/li&gt;
&lt;li&gt;Use Jenkins jobs like a dashboard - "Expose internal port 3000 as port 1000 externally on VPS", "Shut it down", etc.&lt;/li&gt;
&lt;li&gt;Avoid manual interaction unless something literally catches fire.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🛠️ Use Cases (Excuses to Build This)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;💻 Show off local dev apps to clients/friends&lt;/li&gt;
&lt;li&gt;📦 Share self-hosted dashboards (Grafana, Prometheus, whatever)&lt;/li&gt;
&lt;li&gt;🎥 Let your dog cam stream to the world&lt;/li&gt;
&lt;li&gt;⛔ Temporarily expose private tools during demos&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;FRP server runs on your VPS&lt;/li&gt;
&lt;li&gt;FRP client lives on your home server&lt;/li&gt;
&lt;li&gt;Jenkins jobs (run on a connected build agent) generate config files + launch tunnels&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📦 FRP Server Setup on Jenkins VPS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Steps to Create a Freestyle Job in Jenkins&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Jenkins Dashboard

&lt;ul&gt;
&lt;li&gt;Go to your Jenkins instance in the browser (e.g. &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Click on "New Item"

&lt;ul&gt;
&lt;li&gt;(Top-left of the dashboard)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Enter a Job Name&lt;/li&gt;

&lt;li&gt;Select "Freestyle Project"

&lt;ul&gt;
&lt;li&gt;Then click OK.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Configure the Job

&lt;ul&gt;
&lt;li&gt;Copy and paste the script provided below.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Click "Save"&lt;/li&gt;

&lt;li&gt;Run the Job

&lt;ul&gt;
&lt;li&gt;Click "Build Now" on the job page.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅ Here's a minimal script:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# --- SETTINGS ---
FRP_VERSION=0.63.0
FRP_PLATFORM=linux_amd64
FRP_DIR="frp_${FRP_VERSION}_${FRP_PLATFORM}"
FRP_DOWNLOAD_URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_DIR}.tar.gz"
FRP_INSTALL_DIR="/opt/frp"
FRPS_BINARY="${FRP_INSTALL_DIR}/frps"

# --- ENSURE INSTALL DIR EXISTS ---
mkdir -p "$FRP_INSTALL_DIR"

# --- DOWNLOAD + INSTALL IF NEEDED ---
if [[ ! -f "$FRPS_BINARY" ]]; then
  echo "📦 Downloading FRP server to $FRP_INSTALL_DIR..."
  wget "$FRP_DOWNLOAD_URL" -O frp.tar.gz || {
    echo "❌ Download failed. Check URL or network."
    exit 1
  }

  tar -xzf frp.tar.gz
  mv "${FRP_DIR}/frps" "$FRPS_BINARY"
  chmod +x "$FRPS_BINARY"
  rm -rf "$FRP_DIR" frp.tar.gz
fi

# --- CREATE CONFIG IF NEEDED ---
cat &amp;gt; "$FRP_INSTALL_DIR/frps.ini" &amp;lt;&amp;lt;EOF
[common]
bind_port = 1000
dashboard_port = 1500
dashboard_user = admin
dashboard_pwd = admin
EOF

# --- START FRP SERVER ---
cd "$FRP_INSTALL_DIR"
export BUILD_ID=dontKillMe
nohup "$FRPS_BINARY" -c frps.ini &amp;gt; frps.log 2&amp;gt;&amp;amp;1 &amp;amp;

echo "✅ FRP server started on port 1000, dashboard at :1500"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;After that you have:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frps running on port 1000&lt;/li&gt;
&lt;li&gt;A dashboard on &lt;a href="http://vps-ip:1500" rel="noopener noreferrer"&gt;http://vps-ip:1500&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Config in /opt/frp/frps.ini&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ How To Kill FTP Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I set up a different freestyle job - just like in the thrilling saga above, that shuts down the server when I don't need it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
FRP_PID=$(pgrep -f frps)

if [ -n "$FRP_PID" ]; then
  echo "Killing frps with PID $FRP_PID"
  kill "$FRP_PID"
else
  echo "No frps process found"
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📦 FRP Client Setup on Home Server via Jenkins
&lt;/h2&gt;

&lt;p&gt;The next step is to create a third freestyle job in Jenkins, which will set up the FRP client on the worker node of Jenkins running on my home server. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Keep in mind, you'll need to use the "Build with Parameters" option, where the parameters are $LOCAL_PORT (the port on your private server) and $REMOTE_PORT (the port on your public VPS server).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Here's another bash script:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

if [[ -z "$LOCAL_PORT" || -z "$REMOTE_PORT" ]]; then
  echo "❌ Missing required parameters."
  echo "Please provide: LOCAL_PORT, REMOTE_PORT"
  exit 1
fi

FRP_SERVER=&amp;lt;VPS IP&amp;gt;
FRP_SERVER_PORT=1000
FRP_VERSION=0.63.0
FRP_PLATFORM=linux_amd64
FRP_DIR="frp_${FRP_VERSION}_${FRP_PLATFORM}"

WORKDIR="$WORKSPACE/frpc_clients"
mkdir -p "$WORKDIR"

FRPC_BINARY="$WORKDIR/frpc"
FRPC_CONFIG="$WORKDIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.ini"
FRPC_LOG="$WORKDIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.log"
FRPC_PID="$WORKDIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.pid"

# Download frpc if missing
if [[ ! -f "$FRPC_BINARY" ]]; then
  echo "🔽 Downloading frpc v${FRP_VERSION}..."
  wget "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_DIR}.tar.gz" -O /tmp/frp.tar.gz || {
    echo "❌ Download failed. Please check FRP version or network."
    exit 1
  }

  tar -xzf /tmp/frp.tar.gz -C /tmp
  mv /tmp/${FRP_DIR}/frpc "$FRPC_BINARY"
  chmod +x "$FRPC_BINARY"
  rm -rf /tmp/${FRP_DIR} /tmp/frp.tar.gz
fi

echo "⚙️ Writing frpc config for local:$LOCAL_PORT → remote:$REMOTE_PORT"
cat &amp;gt; "$FRPC_CONFIG" &amp;lt;&amp;lt;EOL
[common]
server_addr = $FRP_SERVER
server_port = $FRP_SERVER_PORT

[tunnel_${LOCAL_PORT}]
type = tcp
local_port = $LOCAL_PORT
remote_port = $REMOTE_PORT
EOL

# Check if already running
if [[ -f "$FRPC_PID" ]]; then
  PID=$(cat "$FRPC_PID")
  if kill -0 "$PID" &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then
    echo "⚠️ Tunnel already running with PID $PID"
    exit 0
  else
    echo "⚠️ Stale PID file found, removing"
    rm -f "$FRPC_PID"
  fi
fi

echo "🚀 Starting FRP tunnel..."
export BUILD_ID=dontKillMe
nohup "$FRPC_BINARY" -c "$FRPC_CONFIG" &amp;gt; "$FRPC_LOG" 2&amp;gt;&amp;amp;1 &amp;amp;
echo $! &amp;gt; "$FRPC_PID"

echo "✅ Tunnel running at $FRP_SERVER:$REMOTE_PORT (PID $(cat $FRPC_PID))"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Your janky React app is now live at &lt;a href="http://vps-ip:30001" rel="noopener noreferrer"&gt;http://vps-ip:30001&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ How To Kill A Tunnel To FRP Client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create another freestyle job using the Bash script below.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As mentioned earlier, make sure to use the "Build with Parameters" option, setting $LOCAL_PORT and $REMOTE_PORT appropriately.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

LOCAL_PORT="$LOCAL_PORT"
REMOTE_PORT="$REMOTE_PORT"
FRPC_DIR="$FRP_DIR/frpc_clients"
PID_FILE="$FRPC_DIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.pid"

if [ -f "$PID_FILE" ]; then
  PID=$(cat "$PID_FILE")
  echo "Stopping tunnel: local $LOCAL_PORT → remote $REMOTE_PORT (PID: $PID)"
  kill "$PID"
  rm -f "$PID_FILE"
  echo "Tunnel stopped successfully."
else
  echo "No PID file found at $PID_FILE"
  exit 1
fi

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧠 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;They said, "Don't expose your local ports to the internet". They didn't say, "Don't automate it with Jenkins like a Bond villain".&lt;/p&gt;

&lt;p&gt;Because even in a zero-trust, cloud-native world…sometimes you just want to YOLO a port and livestream your cat feeder from Jenkins.&lt;/p&gt;

&lt;p&gt;You can find the full code for this madness &lt;a href="https://github.com/ppaczk0wsk1/frp-jenkins-tunnelkit" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jenkins</category>
      <category>frp</category>
      <category>bash</category>
      <category>programming</category>
    </item>
    <item>
      <title>💾 Go Sheet DB: I Built a REST API in Go Using Google Sheets as a Database, Then Dockerized It Like I Hate Myself</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Mon, 23 Jun 2025 11:06:48 +0000</pubDate>
      <link>https://forem.com/cyberdev_/go-sheet-db-i-built-a-rest-api-in-go-using-google-sheets-as-a-database-then-dockerized-it-like-1nfe</link>
      <guid>https://forem.com/cyberdev_/go-sheet-db-i-built-a-rest-api-in-go-using-google-sheets-as-a-database-then-dockerized-it-like-1nfe</guid>
      <description>&lt;p&gt;Welcome to Go Sheet DB (API) ™ - the only REST API held together by sheer willpower, a Google Sheet, and my irrational desire to do something in Go.&lt;/p&gt;

&lt;p&gt;What started as "I should do something in Go" became a full-blown REST API running on Google Sheets as the backend database. No Postgres. No SQLite. Just... Sheets. The kind with rows and columns and sadness.&lt;/p&gt;

&lt;h2&gt;
  
  
  👶 Why Google Sheets?
&lt;/h2&gt;

&lt;p&gt;Because I didn't want to install a database. And I love bad ideas that technically work.&lt;/p&gt;

&lt;p&gt;✅ Free&lt;br&gt;
✅ Serverless (in the worst way possible)&lt;br&gt;
✅ Editable by non-devs (like your boss)&lt;br&gt;
✅ Feels illegal - but isn't&lt;/p&gt;
&lt;h2&gt;
  
  
  🧙 Step 1: Set Up Google Sheets API Like It's 2007
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;strong&gt;Google Cloud Console&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Create a new project&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;APIs &amp;amp; Services &amp;gt; Library&lt;/strong&gt;, search for "Google Sheets API", and enable it&lt;/li&gt;
&lt;li&gt;Then go to &lt;strong&gt;APIs &amp;amp; Services &amp;gt; Credentials &amp;gt; Create Credentials &amp;gt; Service Account&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Once your Service account is created, click it email, later go to &lt;strong&gt;"Keys" &amp;gt; Add key &amp;gt; Create new key&lt;/strong&gt; and download config.json&lt;/li&gt;
&lt;li&gt;Download the JSON key (put this in config.json)&lt;/li&gt;
&lt;li&gt;Share your Google Sheet with the service account email
(something like &lt;a href="mailto:my-api-thingy@your-project.iam.gserviceaccount.com"&gt;my-api-thingy@your-project.iam.gserviceaccount.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Breathe deeply. You're now legally a spreadsheet engineer.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  📄 Step 2: Create Your "Database"
&lt;/h2&gt;

&lt;p&gt;Create a new Google Sheet. Call it something like &lt;em&gt;users_db&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In the first sheet/tab, name the columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID | Name | Email | CreatedAt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's your schema. You're welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  💻 Step 3: Go Code That Shouldn't Exist But Does
&lt;/h2&gt;

&lt;p&gt;Let's write some Go that connects to this spreadsheet and makes it behave like a proper database.&lt;/p&gt;

&lt;p&gt;Install Dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go mod init gosheetdb 
go get google.golang.org/api/sheets/v4 
go get golang.org/x/oauth2/google 
go get github.com/gin-gonic/gin
go install github.com/swaggo/swag/cmd/swag@latest
go get github.com/swaggo/gin-swagger
go get github.com/swaggo/files

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;main.go&lt;/em&gt; &lt;strong&gt;(All in One Like a Champion)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// @title           Go Sheet DB API (aka Go Sh**t DB API)
// @version         1.0
// @description     Local API that stores users in Google Sheets
// @host            localhost:8081
// @BasePath        /

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/gin-gonic/gin"
    _ "gosheetdb/docs" // Swagger docs generated by swag init
    "github.com/swaggo/files"
    "github.com/swaggo/gin-swagger"

    "golang.org/x/oauth2/google"
    "google.golang.org/api/option"
    "google.golang.org/api/sheets/v4"
)

var (
    srv     *sheets.Service
    sheetID = "1n3Vt0_zLdMspL5VEzbD7SVWgK9186k-GRLv5xcjrFDc"
)

// User represents a user stored in Google Sheets
type User struct {
    ID        string `json:"id" example:"1234567890"`
    Name      string `json:"name" example:"Cyber"`
    Email     string `json:"email" example:"cyber@spreadsheet.go"`
    CreatedAt string `json:"createdAt" example:"2025-06-22T15:04:05Z"`
}

type ErrorResponse struct {
    Error string `json:"error" example:"some error message"`
}

func initSheets() {
    ctx := context.Background()
    b, err := os.ReadFile("config.json")
    if err != nil {
        log.Fatalf("Failed to read config.json: %v", err)
    }

    config, err := google.JWTConfigFromJSON(b, sheets.SpreadsheetsScope)
    if err != nil {
        log.Fatalf("Failed to parse config: %v", err)
    }

    client := config.Client(ctx)
    srv, err = sheets.NewService(ctx, option.WithHTTPClient(client))
    if err != nil {
        log.Fatalf("Failed to create Sheets client: %v", err)
    }
}

// getUsers godoc
// @Summary      Get all users
// @Description  Retrieve all users from Google Sheets
// @Tags         users
// @Produce      json
// @Success      200  {array}   User
// @Failure      500  {object}  ErrorResponse
// @Router       /users [get]
func getUsers(c *gin.Context) {
    resp, err := srv.Spreadsheets.Values.Get(sheetID, "Users!A2:D").Do()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    var users []User
    for _, row := range resp.Values {
    if len(row) &amp;lt; 4 {
            continue
        }
        user := User{
            ID:        fmt.Sprintf("%v", row[0]),
            Name:      fmt.Sprintf("%v", row[1]),
            Email:     fmt.Sprintf("%v", row[2]),
            CreatedAt: fmt.Sprintf("%v", row[3]),
        }
        users = append(users, user)
    }

    c.JSON(http.StatusOK, users)
}

// addUser godoc
// @Summary      Add a new user
// @Description  Append a new user to Google Sheets
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        user  body      User  true  "User to add"
// @Success      201   {object}  User
// @Failure      400   {object}  ErrorResponse
// @Failure      500   {object}  ErrorResponse
// @Router       /users [post]
func addUser(c *gin.Context) {
    var u User
    if err := c.BindJSON(&amp;amp;u); 
    err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Bad JSON"})
        return
    }

    u.ID = fmt.Sprintf("%d", time.Now().UnixNano())
    u.CreatedAt = time.Now().Format(time.RFC3339)

    v := &amp;amp;sheets.ValueRange{
        Values: [][]interface{}{
            {u.ID, u.Name, u.Email, u.CreatedAt},
        },
    }

    _, err := srv.Spreadsheets.Values.Append(sheetID, "Users!A:D", v).ValueInputOption("RAW").Do()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, u)
}

func main() {
    initSheets()

    r := gin.Default()

    // Swagger UI at /swagger/index.html
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

    // API routes
    r.GET("/users", getUsers)
    r.POST("/users", addUser)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8081"
    }

    r.Run(":" + port)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'export PATH="$HOME/go/bin:$PATH"' &amp;gt;&amp;gt; ~/.bashrc
source ~/.bashrc
swag init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📦 How to Run It
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add your real Google Sheet ID where it says &lt;em&gt;sheetID = "...&lt;/em&gt;"&lt;/li&gt;
&lt;li&gt;Put your &lt;em&gt;config.json&lt;/em&gt; in the root directory&lt;/li&gt;
&lt;li&gt;Run it:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go run main.go

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test it:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST http://localhost:8081/users -H "Content-Type: application/json" -d '{"name": "Cyber", "email": "cyber@spreadsheet.go"}'

curl http://localhost:8081/users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉 You just built an API powered by a spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  📦 Step 4. Dockerfile From the Abyss
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM golang:1.23 
WORKDIR /app 
COPY go.mod go.sum ./ 
RUN go mod download 
COPY . . 
RUN go build -o gosheetdb . 
CMD ["./gosheetdb"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fast. Irresponsible. Beautiful.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧼 .dockerignore - Hide the Evidence
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config.json .git *.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't ship your secrets or your entire git history.&lt;br&gt;
You're building a meme, not a monolith.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧪 Run It Locally (For Science)
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t gosheetdb .

docker run -p 8081:8081 -v $(pwd)/config.json:/app/config.json gosheetdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And just like that, your spreadsheet is now a cloud-native database.&lt;br&gt;
Somewhere, a DBA just felt a cold chill.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ud44sH3RwUA"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/wEYVB4Idimo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Real-Life Uses for Go Sheet DB (Weirdly Viable)
&lt;/h2&gt;

&lt;p&gt;Despite being built on pure tech blasphemy, this API can work for small, chaotic, and oddly practical use cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;💬 Event RSVP Tracking (Without a Real Backend)&lt;/li&gt;
&lt;li&gt;📋 Internal Tools for Non-Devs&lt;/li&gt;
&lt;li&gt;🧪 Prototyping APIs Before You Commit to a Real DB&lt;/li&gt;
&lt;li&gt;🎓 Teaching &amp;amp; Workshops&lt;/li&gt;
&lt;li&gt;📈 Collecting Feedback, Bug Reports, or Feature Votes&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  🤔 Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Sure, it was a wacky little experiment, but hey, now you know what a REST API in Go might look like, how to sweet-talk the Google Sheets API, and how to shove the whole thing into a Docker container like a tech-savvy magician.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I regret everything&lt;/li&gt;
&lt;li&gt;I also regret nothing&lt;/li&gt;
&lt;li&gt;It works&lt;/li&gt;
&lt;li&gt;And that’s the real problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to witness the beautiful chaos for yourself? The full code lives here (handle with care): &lt;a href="https://github.com/ppaczk0wsk1/go-sheet-api" rel="noopener noreferrer"&gt;https://github.com/ppaczk0wsk1/go-sheet-api&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>docker</category>
      <category>restapi</category>
      <category>googlesheets</category>
    </item>
    <item>
      <title>Automating My Minecraft Bedrock Server with Bash, Jenkins, and Chicken Noises 🐔</title>
      <dc:creator>Cyber</dc:creator>
      <pubDate>Sat, 14 Jun 2025 08:39:12 +0000</pubDate>
      <link>https://forem.com/cyberdev_/automating-my-minecraft-server-with-bash-jenkins-and-chicken-noises-5gi0</link>
      <guid>https://forem.com/cyberdev_/automating-my-minecraft-server-with-bash-jenkins-and-chicken-noises-5gi0</guid>
      <description>&lt;p&gt;&lt;em&gt;And yes, the Lava Chicken makes an appearance at the end.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Keeping your Minecraft Bedrock server up to date is important, unless you enjoy surprise crashes, player complaints, and corrupt worlds. Updating it manually? That’s a whole saga: stop the server, back everything up, grab the latest release, restore configs, restart it all. Miss a step? Say goodbye to your Nether castle.&lt;/p&gt;

&lt;p&gt;Naturally, I decided to &lt;strong&gt;automate the whole thing with a Bash script and Jenkins&lt;/strong&gt;, so now my server updates itself while I’m off building pixel art and eating a pickle pizza.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Why Jenkins?
&lt;/h2&gt;

&lt;p&gt;Jenkins is like that overachieving robot butler you wish you had in real life. It’s open-source, endlessly configurable, and perfect for automating server-side rituals like this one.&lt;/p&gt;

&lt;p&gt;With Jenkins, I get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic updates&lt;/strong&gt; on a schedule (e.g., 3 AM, when no one's online except Endermen).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs and job status&lt;/strong&gt; in a web UI (goodbye, mystery bugs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure notifications&lt;/strong&gt; if something breaks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-click manual runs&lt;/strong&gt;, for when I’m feeling fancy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔄 How Jenkins Runs the Update Script
&lt;/h2&gt;

&lt;p&gt;I set up my Bash script as a Jenkins freestyle job (pipeline works too):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stops the server (gently)&lt;/li&gt;
&lt;li&gt;Backs up all important files (just in case Creepers learn how to hack)&lt;/li&gt;
&lt;li&gt;Downloads the latest Bedrock release&lt;/li&gt;
&lt;li&gt;Restores worlds and configs&lt;/li&gt;
&lt;li&gt;Restarts the server&lt;/li&gt;
&lt;li&gt;Logs everything like it’s testifying before a redstone tribunal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If something fails? Jenkins sends me a passive-aggressive email like, “Hey, your server faceplanted, bozo!”&lt;/p&gt;

&lt;h2&gt;
  
  
  🧰 Jenkins Setup TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Install Jenkins somewhere with access to your server files. I have it on my VPS which is connected to my home server.&lt;/li&gt;
&lt;li&gt;Make sure the Jenkins user can stop/start the Bedrock server and write to the backup directory&lt;/li&gt;
&lt;li&gt;Create a Jenkins job with a build step that runs the Bash script&lt;/li&gt;
&lt;li&gt;Use cron syntax in Build Triggers to schedule it or trigger it manually.&lt;/li&gt;
&lt;li&gt;Peek at “Console Output” in Jenkins if things get weird&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📜 &lt;em&gt;The Bash Script That Runs the Show&lt;br&gt;
Pro tip: Put on headphones before running it. There’s a 1% chance the Lava Chicken song plays.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# Variables
BEDROCK_DIR=&amp;lt;BEDROCK_DIR&amp;gt;
BEDROCK_LOG_FILE="$BEDROCK_DIR/bedrock_server.log"
BACKUP_DIR=&amp;lt;BACKUP_DIR&amp;gt;
TEMP_DIR=&amp;lt;TEMP_DIR&amp;gt;
WORLD_DIR="$BEDROCK_DIR/worlds"
CONFIG_FILES=("$BEDROCK_DIR/server.properties" "$BEDROCK_DIR/permissions.json" "$BEDROCK_DIR/whitelist.json")
LOG_FILE=&amp;lt;LOG_FILE&amp;gt;
BACKUP_FILE="$BACKUP_DIR/bedrock_backup_latest.tar.gz"

# Logging
exec &amp;gt; &amp;gt;(tee -i $LOG_FILE)
exec 2&amp;gt;&amp;amp;1

echo "Starting Bedrock Server update process..."

# Stop the server
echo "Stopping Bedrock server..."
pkill -f "bedrock_server" || echo "Bedrock server not running."

# Backup files
echo "Backing up world and configuration files..."
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
tar -czf "$BACKUP_FILE" -C "$BEDROCK_DIR" .

# Save world/configs
echo "Saving world and configuration files..."
mkdir -p "$TEMP_DIR/config_backup"
cp -r "$WORLD_DIR" "$TEMP_DIR/config_backup"
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
  [ -f "$CONFIG_FILE" ] &amp;amp;&amp;amp; cp "$CONFIG_FILE" "$TEMP_DIR/config_backup/"
done

# Download latest version
echo "Downloading the latest Bedrock server version..."
mkdir -p "$TEMP_DIR"
LATEST_URL=$(curl -Ls -A "Mozilla/5.0" "https://www.minecraft.net/en-us/download/server/bedrock/" | grep -o 'https://www.minecraft.net/bedrockdedicatedserver/bin-linux/[^"]*')
[ -z "$LATEST_URL" ] &amp;amp;&amp;amp; echo "Error: Could not find latest Bedrock URL." &amp;amp;&amp;amp; exit 1
wget --user-agent="Mozilla/5.0" "$LATEST_URL" -O "$TEMP_DIR/bedrock-server.zip" || { echo "Error: Download failed."; exit 1; }

# Update files
echo "Updating server files..."
unzip -o "$TEMP_DIR/bedrock-server.zip" -d "$BEDROCK_DIR"

# Restore world/configs
echo "Restoring world and configuration files..."
cp -r "$TEMP_DIR/config_backup/worlds" "$BEDROCK_DIR"
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
  RESTORED="$TEMP_DIR/config_backup/$(basename "$CONFIG_FILE")"
  [ -f "$RESTORED" ] &amp;amp;&amp;amp; cp "$RESTORED" "$BEDROCK_DIR"
done

# Cleanup
rm -rf "$TEMP_DIR"
echo "Update complete. Cleaning up temporary files."

# Restart server
echo "Starting the Bedrock server..."
export BUILD_ID=dontKillMe
cd "$BEDROCK_DIR"
nohup bash -c "LD_LIBRARY_PATH=. ./bedrock_server" &amp;gt; "$BEDROCK_LOG_FILE" 2&amp;gt;&amp;amp;1 &amp;amp;
echo "Server restarted successfully. Log: $BEDROCK_LOG_FILE"
echo "https://youtu.be/41O_MydqxTU"

exit 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  🧠 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Thanks to this setup, my Minecraft Bedrock server updates itself while I’m AFK. Safely, reliably, and without any “oops I overwrote my Nether base” moments.&lt;/p&gt;

&lt;p&gt;If you're running a personal or community server, automating your updates with Jenkins + Bash is a power move. Add backups, clean logging, and a sprinkle of Lava Chicken magic, and you're golden.&lt;/p&gt;

&lt;p&gt;Bonus: Yes, I actually listen to the Lava Chicken song every time this script runs. It’s tradition now.&lt;br&gt;
La-la-la-Lava! Ch-ch-ch-chicken! 🐔🔥&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://tenor.com/pl/view/a-minecraft-movie-jack-black-gif-10393245520183570317" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia1.tenor.com%2Fm%2FkDw5vB32L40AAAAC%2Fa-minecraft.gif" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://tenor.com/pl/view/a-minecraft-movie-jack-black-gif-10393245520183570317" rel="noopener noreferrer" class="c-link"&gt;
            A Minecraft GIF – A Minecraft Movie – odkrywaj i udostępniaj GIF-y
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Click to view the GIF
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftenor.com%2Fassets%2Fimg%2Ffavicon%2Ffavicon-16x16.png"&gt;
          tenor.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>jenkins</category>
      <category>bash</category>
      <category>minecraft</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
