<?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>DEV Community: Jurijs Ivolga</title>
    <description>The latest articles on DEV Community by Jurijs Ivolga (@jurijs_iv).</description>
    <link>https://dev.to/jurijs_iv</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%2F2928857%2Fd933ee53-5cb9-47fc-924b-201a3417a4b3.jpg</url>
      <title>DEV Community: Jurijs Ivolga</title>
      <link>https://dev.to/jurijs_iv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jurijs_iv"/>
    <language>en</language>
    <item>
      <title>How to manage Let's Encrypt certificate on EC2 instance</title>
      <dc:creator>Jurijs Ivolga</dc:creator>
      <pubDate>Tue, 18 Nov 2025 12:21:10 +0000</pubDate>
      <link>https://dev.to/jurijs_iv/how-to-manage-lets-encrypt-certificate-on-ec2-instance-46f4</link>
      <guid>https://dev.to/jurijs_iv/how-to-manage-lets-encrypt-certificate-on-ec2-instance-46f4</guid>
      <description>&lt;p&gt;In this guide, I’ll provide a short manual on how to create and manage Let’s Encrypt certificates on your EC2 instance using &lt;a href="https://go-acme.github.io/lego/" rel="noopener noreferrer"&gt;Lego&lt;/a&gt; (a Let's Encrypt/ACME client and library written in Go). We’ll use the DNS-01 challenge, and the instance will have an appropriate IAM role so only the instance itself can manage the &lt;code&gt;_acme-challenge&lt;/code&gt; TXT record for the domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why use Let's Encrypt on EC2?
&lt;/h3&gt;

&lt;p&gt;Why even bother using Let’s Encrypt in AWS? First of all — simpler setup. You don’t need an ALB or CloudFront, which also means lower cost. Sometimes, your application isn’t something you can easily put behind a load balancer — for example, any SIP proxy. So in some cases, you’ll want Let’s Encrypt certs directly on the EC2 instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why DNS-01 challenge?
&lt;/h3&gt;

&lt;p&gt;If you’re running a SIP application, why open port 80 at all if you don’t need to? Or maybe port 80 is already being used by another service. Personally, I just prefer the DNS-01 challenge — fewer firewall holes to worry about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;We’ll create an instance profile and assign it to our EC2 instance so the instance itself can have permissions to add or remove DNS records, which is required for the DNS-01 challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;I assume you already have an EC2 instance up and running. Here's an example Terraform configuration to spin up a test instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {  
  required_providers {  
    aws = {  
      source = "hashicorp/aws"  
    }  
  }  
}  

provider "aws" {  
  region  = "us-east-1"  
  profile = "dev"  
}  

resource "aws_key_pair" "jurijs" {  
  key_name   = "jurijs-key"  
  public_key = "ssh-rsa AAAAB3N..."  
}  

resource "aws_eip" "dev" {  
  instance = aws_instance.dev.id  
  domain   = "vpc"  
}  

output "aws_eip" {  
  value = aws_eip.dev.public_ip  
}  

output "aws_private" {  
  value = aws_instance.dev.private_ip  
}  

resource "aws_instance" "dev" {  
  ami                    = "ami-0facb4427ff2f68d4"  
  instance_type          = "t2.micro"  
  key_name               = aws_key_pair.jurijs.key_name  
  vpc_security_group_ids = [aws_security_group.dev.id]  
}  

resource "aws_security_group" "dev" {  
  name_prefix = "dev"  
}  

resource "aws_security_group_rule" "outbound" {  
  security_group_id = aws_security_group.dev.id  
  description       = "all-outbound-allowed"  
  from_port         = 0  
  to_port           = 0  
  protocol          = "-1"  
  type              = "egress"  
  cidr_blocks       = ["0.0.0.0/0"]  
}  

resource "aws_security_group_rule" "SSH" {  
  security_group_id = aws_security_group.dev.id  
  description       = "Allow SSH from everywhere"  
  from_port         = 22  
  to_port           = 22  
  protocol          = "tcp"  
  type              = "ingress"  
  cidr_blocks       = ["0.0.0.0/0"]  
}  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create IAM profile for Lego
&lt;/h2&gt;

&lt;p&gt;For the instance profile, you can use my Terraform module from GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/os11k/terraform-iam-lego" rel="noopener noreferrer"&gt;https://github.com/os11k/terraform-iam-lego&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module "lego-iam" {  
  source     = "../modules/lego-iam"  
  hostname   = ["my-domain.com", "www.my-domain.com"]  
  hostedzone = "7AZKFFF"  
}  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And make sure the EC2 instance has the IAM instance profile assigned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_instance" "instance-with-letsencrypt" {  
  ...  
  iam_instance_profile = module.lego-iam.instance-profile-name  
  ...  
}  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Lego and Issue the Certificate
&lt;/h2&gt;

&lt;p&gt;Install the necessary packages and issue the certificate with the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -s https://api.github.com/repos/go-acme/lego/releases/latest | grep "browser_download_url" | grep "linux_amd64.tar.gz" | cut -d '"' -f 4 | xargs curl -LO -#  
tar xzvf lego_v*.tar.gz  
install lego /usr/local/bin/  

export AWS_REGION=us-east-1  

/usr/local/bin/lego --accept-tos --dns route53 --email="my-gmail@gmail.com" --domains="my-domain.com" --domains="www.my-domain.com" --path="/etc/lego" run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Validate the Certificate
&lt;/h2&gt;

&lt;p&gt;To verify the certificate, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat /etc/lego/certificates/my-domain.com.crt  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-----BEGIN CERTIFICATE-----  
MIIDxDCCA0mgAwIBAgISBrctmxC+XMJfDQGtEo89F6aaMAoGCCqGSM49BAMDMDIx  
...  
-----END CERTIFICATE-----  

-----BEGIN CERTIFICATE-----  
MIIEVzCCAj+gAwIBAgIRAIOPbGPOsTmMYgZigxXJ/d4wDQYJKoZIhvcNAQELBQAw  
...  
-----END CERTIFICATE-----  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first certificate is for &lt;code&gt;my-domain.com&lt;/code&gt; and &lt;code&gt;www.my-domain.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://certdecoder.com/" rel="noopener noreferrer"&gt;https://certdecoder.com/&lt;/a&gt; to verify the certificate. You should see that the Common Name is &lt;code&gt;my-domain.com&lt;/code&gt; and the Subject Alternative Names (SANs) include both &lt;code&gt;my-domain.com&lt;/code&gt; and &lt;code&gt;www.my-domain.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note: In the screenshot below, the example shows &lt;code&gt;certdecoder.com&lt;/code&gt; instead of &lt;code&gt;my-domain.com&lt;/code&gt;. The certificate is valid for 90 days.&lt;/p&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%2Fxd2cf4lczcnczkgbyov0.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%2Fxd2cf4lczcnczkgbyov0.png" alt="Diagram" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second certificate is Let’s Encrypt’s root certificate. You can inspect it if desired, though it is not essential for this guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Renewal via Cron
&lt;/h2&gt;

&lt;p&gt;Set up a cron job for automatic renewal with these entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_REGION=us-east-1  
12 1 * * 6 /usr/local/bin/lego --dns route53 --email="my-gmail@gmail.com" --domains="my-domain.com" --domains="www.my-domain.com" --path="/etc/lego" renew &amp;gt;&amp;gt; /var/log/lego.log 2&amp;gt;&amp;amp;1  
12 2 * * 6 systemctl reload nginx.service  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The first line ensures the AWS region is set.
&lt;/li&gt;
&lt;li&gt;The second line handles the renewal process.
&lt;/li&gt;
&lt;li&gt;The third line reloads nginx so that any new certificate is applied. (If you’re using another service like Apache or Kamailio, replace this command accordingly.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You now have Let’s Encrypt set up directly on your EC2 instance using the DNS-01 challenge with the proper IAM permissions. Certificates are issued and automatically renewed via cron.&lt;/p&gt;

&lt;p&gt;Keep an eye on &lt;code&gt;/var/log/lego.log&lt;/code&gt; — if renewal fails, you’ll find useful details there for troubleshooting.&lt;/p&gt;

</description>
      <category>ssl</category>
      <category>letsencrypt</category>
      <category>aws</category>
      <category>terraform</category>
    </item>
    <item>
      <title>🍌 I Built a Tool Directory Where Things Go Bananas (and You Can Too!)</title>
      <dc:creator>Jurijs Ivolga</dc:creator>
      <pubDate>Sat, 06 Sep 2025 15:06:55 +0000</pubDate>
      <link>https://dev.to/jurijs_iv/i-built-a-tool-directory-where-things-go-bananas-and-you-can-too-57en</link>
      <guid>https://dev.to/jurijs_iv/i-built-a-tool-directory-where-things-go-bananas-and-you-can-too-57en</guid>
      <description>&lt;h2&gt;
  
  
  Today's the day I finished my weird little tool directory:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://banana.dog" rel="noopener noreferrer"&gt;banana.dog&lt;/a&gt; 🎉&lt;/p&gt;

&lt;p&gt;It was quite the journey, let me tell you.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Over-Engineering Phase 🚀
&lt;/h3&gt;

&lt;p&gt;At the beginning, I had &lt;strong&gt;BIG PLANS™&lt;/strong&gt;. I was going to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Astro with all the bells and whistles&lt;/li&gt;
&lt;li&gt;AI to scrape websites and auto-generate descriptions&lt;/li&gt;
&lt;li&gt;Fancy animations everywhere&lt;/li&gt;
&lt;li&gt;A complex submission system&lt;/li&gt;
&lt;li&gt;Probably blockchain somehow (kidding... or am I?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But guess what? I got so overwhelmed by all that shiny tech, I never had the time or energy to actually &lt;em&gt;build the thing&lt;/em&gt;. Classic developer move, right? 🤦‍♂️&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Wait, Let's Actually Ship This" Phase 🍌
&lt;/h3&gt;

&lt;p&gt;So I went full banana and decided to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep it &lt;strong&gt;plain HTML&lt;/strong&gt; (gasp!)&lt;/li&gt;
&lt;li&gt;Build it using &lt;strong&gt;merge requests in GitLab&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add a validation bot that checks if submissions follow the rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the simplest solution is the best solution. Who knew?&lt;/p&gt;

&lt;h3&gt;
  
  
  The Plot Twist 📧
&lt;/h3&gt;

&lt;p&gt;Here's the funny part: I had quite a few email requests from people wanting their tools featured. Now that it's open source, let's see how many &lt;em&gt;actually&lt;/em&gt; open MRs. &lt;/p&gt;

&lt;p&gt;My prediction? About 3. Maybe 4 if we're being optimistic. 😄&lt;/p&gt;

&lt;h3&gt;
  
  
  Want Your Weird Tool Featured? 🛠️
&lt;/h3&gt;

&lt;p&gt;If you've built something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Useful, useless, or somewhere in between&lt;/li&gt;
&lt;li&gt;Weird, wonderful, or slightly unhinged&lt;/li&gt;
&lt;li&gt;That actually works (mostly)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just open a merge request here: &lt;a href="https://gitlab.com/jurijs.ivolga/banana-www" rel="noopener noreferrer"&gt;gitlab.com/jurijs.ivolga/banana-www&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The automated banana bot will check your submission, and if it passes, your tool joins the banana brigade!&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Learned 🎓
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Perfect is the enemy of done&lt;/strong&gt; - My simple HTML site is live, while my Astro masterpiece lives only in my imagination&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation bots are fun&lt;/strong&gt; - Mine posts banana emojis in MR comments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sometimes you just need to go bananas&lt;/strong&gt; 🍌&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://banana.dog" rel="noopener noreferrer"&gt;banana.dog&lt;/a&gt; and submit your weird tools. Or don't. I'm not your boss.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. - Yes, the domain name came first. No, I don't regret it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>sideprojects</category>
      <category>html</category>
    </item>
    <item>
      <title>How I sped up my WordPress from 800ms to 30ms for FREE!</title>
      <dc:creator>Jurijs Ivolga</dc:creator>
      <pubDate>Sun, 24 Aug 2025 17:12:28 +0000</pubDate>
      <link>https://dev.to/jurijs_iv/how-i-sped-up-my-wordpress-from-800ms-to-30ms-for-free-5gjh</link>
      <guid>https://dev.to/jurijs_iv/how-i-sped-up-my-wordpress-from-800ms-to-30ms-for-free-5gjh</guid>
      <description>&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%2Fwww.cyberpunk.tools%2Fassets%2Fwordpress-cloudflare%2F7.webp" 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%2Fwww.cyberpunk.tools%2Fassets%2Fwordpress-cloudflare%2F7.webp" alt="website response after I installed cache plugin" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was frustrated with my WordPress site's slow response times, until I discovered a WordPress Cache Plugin. And it change everything, at least for me!&lt;/p&gt;

&lt;p&gt;The Problem 🐌&lt;/p&gt;

&lt;p&gt;My WordPress site was struggling with response times:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mostly between 200-400ms (acceptable)&lt;/li&gt;
&lt;li&gt;Frequent spikes above 1 second&lt;/li&gt;
&lt;li&gt;Often hovering around 800ms (disappointing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Solution ⚡&lt;/p&gt;

&lt;p&gt;After one night of testing, I achieved incredible results using Cloudflare&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Super Page Cache WordPress plugin:

&lt;ul&gt;
&lt;li&gt;Before: 200-800ms+ response times&lt;/li&gt;
&lt;li&gt;After: Consistent 30-200ms response times&lt;/li&gt;
&lt;li&gt;Performance comparable to static websites hosted on S3!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Quick Setup Guide 🚀&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;WordPress website&lt;/li&gt;
&lt;li&gt;Free Cloudflare account (cloudflare.com)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 1: Add Domain to Cloudflare&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log into Cloudflare and add your domain&lt;/li&gt;
&lt;li&gt;Select the free plan&lt;/li&gt;
&lt;li&gt;Update your domain's nameservers at your registrar&lt;/li&gt;
&lt;li&gt;Wait for activation email&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2: Fix Common Issues&lt;/p&gt;

&lt;p&gt;If you encounter ERR_TOO_MANY_REDIRECTS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to SSL/TLS settings in Cloudflare&lt;/li&gt;
&lt;li&gt;Change encryption mode from "Flexible" to "Full" or "Full (Strict)"&lt;/li&gt;
&lt;li&gt;This prevents redirect loops between Cloudflare and your server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 3: Install Super Page Cache Plugin&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In WordPress admin → Plugins → Add New&lt;/li&gt;
&lt;li&gt;Search for "Super Page Cache"&lt;/li&gt;
&lt;li&gt;Install and activate&lt;/li&gt;
&lt;li&gt;Click "ENABLE PAGE CACHING NOW"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Important: Disable any other caching plugins first!&lt;/p&gt;

&lt;p&gt;Step 4: Connect to Cloudflare&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get your Cloudflare API key:

&lt;ul&gt;
&lt;li&gt;Profile → API Tokens → View Global API Key&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In Super Page Cache settings:

&lt;ul&gt;
&lt;li&gt;Go to "Cloudflare (CDN &amp;amp; Edge Caching)"&lt;/li&gt;
&lt;li&gt;Enter your Cloudflare email and API key&lt;/li&gt;
&lt;li&gt;Select your domain&lt;/li&gt;
&lt;li&gt;Click "Continue"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 5: Verify Setup&lt;/p&gt;

&lt;p&gt;Click "TEST CACHE" in the plugin settings. You should see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Cloudflare Page Caching is working properly&lt;/li&gt;
&lt;li&gt;✅ Disk Page Caching is functional&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Results 📊&lt;/p&gt;

&lt;p&gt;The transformation was immediate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployment at 1:00 AM&lt;/li&gt;
&lt;li&gt;All requests now between 30-200ms&lt;/li&gt;
&lt;li&gt;Performance matches static site hosting&lt;/li&gt;
&lt;li&gt;Zero cost - completely free solution!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key Takeaways 💡&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This isn't a silver bullet for all performance issues&lt;/li&gt;
&lt;li&gt;Completely free solution with enterprise-level performance gains&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wordpress</category>
      <category>cloudflare</category>
      <category>webperf</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Replace SSH with AWS Session Manager</title>
      <dc:creator>Jurijs Ivolga</dc:creator>
      <pubDate>Tue, 24 Jun 2025 14:57:51 +0000</pubDate>
      <link>https://dev.to/jurijs_iv/how-to-replace-ssh-with-aws-session-manager-4p3l</link>
      <guid>https://dev.to/jurijs_iv/how-to-replace-ssh-with-aws-session-manager-4p3l</guid>
      <description>&lt;p&gt;Ever wanted to simplify access management to your EC2 instances? AWS Session Manager might be the solution you're looking for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Ditch SSH?
&lt;/h2&gt;

&lt;p&gt;As your infrastructure grows, managing SSH access becomes a pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating/deleting users on multiple instances&lt;/li&gt;
&lt;li&gt;Managing SSH keys&lt;/li&gt;
&lt;li&gt;Dual access management (AWS + EC2)&lt;/li&gt;
&lt;li&gt;Complex offboarding processes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Session Manager Solution
&lt;/h2&gt;

&lt;p&gt;Session Manager combines AWS and EC2 access into one tool. Users only need AWS credentials to access instances. Remove AWS access = remove EC2 access. Simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Overview
&lt;/h2&gt;

&lt;p&gt;I followed &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/connect-to-an-amazon-ec2-instance-by-using-session-manager.html" rel="noopener noreferrer"&gt;AWS's official guide&lt;/a&gt; but simplified it for practical use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;No SSH required&lt;/strong&gt; - Remove port 22 from security groups&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;No SSH keys&lt;/strong&gt; - Zero key management&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;SSM Agent&lt;/strong&gt; - Pre-installed on most AMIs  &lt;/p&gt;
&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;I created a Terraform module after fixing issues in the &lt;a href="https://github.com/aws-samples/enable-session-manager-terraform" rel="noopener noreferrer"&gt;AWS sample&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Module:&lt;/strong&gt; &lt;a href="https://github.com/os11k/terraform-session-manager" rel="noopener noreferrer"&gt;terraform-session-manager&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Main Configuration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
  &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-profile-name"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ssm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../modules/ssm"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ssm_profile_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssm-profile-name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Region-Specific Setup
&lt;/h3&gt;

&lt;p&gt;Session Manager is region-specific, so you need this for each region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
  &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-profile-name"&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"useast1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssm_document"&lt;/span&gt; &lt;span class="s2"&gt;"session_manager_prefs_useast1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useast1&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SSM-SessionManagerRunShell"&lt;/span&gt;
  &lt;span class="nx"&gt;document_type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Session"&lt;/span&gt;
  &lt;span class="nx"&gt;document_format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"JSON"&lt;/span&gt;

  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;DOC&lt;/span&gt;&lt;span class="sh"&gt;
{
    "schemaVersion": "1.0",
    "description": "SSM document to house preferences for session manager",
    "sessionType": "Standard_Stream",
    "inputs": {
        "s3BucketName": "${module.ssm.ssm_s3_bucket_id}",
        "s3KeyPrefix": "AWSLogs/ssm_session_logs",
        "s3EncryptionEnabled": true,
        "cloudWatchLogGroupName": "",
        "runAsEnabled": true,
        "runAsDefaultUser": "${var.user}",
        "shellProfile": {
          "windows": "",
          "linux": "exec /bin/bash\ncd /home/${var.user}"
        },
        "idleSessionTimeout": "20"
    }
}
&lt;/span&gt;&lt;span class="no"&gt;DOC
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding More Regions
&lt;/h3&gt;

&lt;p&gt;For additional regions (like us-east-2), just add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-2"&lt;/span&gt;
  &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-profile-name"&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"useast2"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssm_document"&lt;/span&gt; &lt;span class="s2"&gt;"session_manager_prefs_useast2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useast2&lt;/span&gt;
  &lt;span class="c1"&gt;# ... same document config as above&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Benefits
&lt;/h2&gt;

&lt;p&gt;🔒 &lt;strong&gt;Security&lt;/strong&gt;: Encrypted sessions, centralized access control&lt;br&gt;&lt;br&gt;
⚡ &lt;strong&gt;Simplicity&lt;/strong&gt;: One place to manage all access&lt;br&gt;&lt;br&gt;
📊 &lt;strong&gt;Auditing&lt;/strong&gt;: All sessions logged to S3&lt;br&gt;&lt;br&gt;
🚀 &lt;strong&gt;Scalability&lt;/strong&gt;: Works across unlimited instances  &lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Internet access required&lt;/strong&gt; for private instances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region-specific&lt;/strong&gt; configuration needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSM Agent&lt;/strong&gt; must be running (usually is by default)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Useful Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-schema.html" rel="noopener noreferrer"&gt;Session document schema&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/os11k/terraform-session-manager" rel="noopener noreferrer"&gt;My Terraform module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/connect-to-an-amazon-ec2-instance-by-using-session-manager.html" rel="noopener noreferrer"&gt;AWS prescriptive guidance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Have you tried Session Manager? What's your preferred way to manage EC2 access? Let me know in the comments!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Found this helpful? I'd appreciate if you follow me for more AWS and infrastructure content! 🚀&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.cyberpunk.tools/jekyll/update/2025/01/07/aws-systems-manager-session-manager.html" rel="noopener noreferrer"&gt;Cyberpunk Tools Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>ssm</category>
      <category>opentofu</category>
    </item>
    <item>
      <title>Banana.dog back from the dead</title>
      <dc:creator>Jurijs Ivolga</dc:creator>
      <pubDate>Tue, 01 Apr 2025 13:58:32 +0000</pubDate>
      <link>https://dev.to/jurijs_iv/bananadog-back-from-the-dead-1cgo</link>
      <guid>https://dev.to/jurijs_iv/bananadog-back-from-the-dead-1cgo</guid>
      <description>&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%2F2j4s6nier0o2g56f8doh.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%2F2j4s6nier0o2g56f8doh.png" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;It’s April 1st — the perfect day for doing something a little foolish.&lt;br&gt;&lt;br&gt;
So here it is: I brought &lt;a href="https://banana.dog" rel="noopener noreferrer"&gt;banana.dog&lt;/a&gt; back from the dead.&lt;/p&gt;

&lt;p&gt;It used to be a Mastodon server.&lt;br&gt;&lt;br&gt;
Now? It’s being reborn as a weird, wonderful, slightly chaotic directory of tools — indie projects, small utilities, personal dev hacks, whatever deserves a little more spotlight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Because I built &lt;a href="https://certdecoder.com" rel="noopener noreferrer"&gt;certdecoder.com&lt;/a&gt; and couldn’t find a decent place to submit it.&lt;br&gt;&lt;br&gt;
So I said screw it — I’ll build my own. With blackjack and hookers.&lt;/p&gt;

&lt;p&gt;It’s not finished, and it’s not functional.&lt;br&gt;&lt;br&gt;
But it’s live. And sometimes, that’s enough.&lt;/p&gt;

&lt;p&gt;So let’s celebrate — the April 1st (re)birth of &lt;a href="https://banana.dog" rel="noopener noreferrer"&gt;banana.dog&lt;/a&gt; 🐶🍌&lt;/p&gt;

</description>
      <category>aprilfools</category>
      <category>webdev</category>
      <category>tooling</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Jekyll Blog with Terraform (OpenTofu) Using GitLab Pipelines</title>
      <dc:creator>Jurijs Ivolga</dc:creator>
      <pubDate>Sun, 16 Mar 2025 13:23:11 +0000</pubDate>
      <link>https://dev.to/jurijs_iv/jekyll-blog-with-terraform-opentofu-using-gitlab-pipelines-hja</link>
      <guid>https://dev.to/jurijs_iv/jekyll-blog-with-terraform-opentofu-using-gitlab-pipelines-hja</guid>
      <description>&lt;p&gt;In this guide, I will provide a short manual for deploying a static blog, in this case Jekyll (with minor changes, you should be able to make it work for any other static website), to Amazon S3 using GitLab pipelines. All infrastructure will be managed in Terraform (or OpenTofu, as in our case).&lt;/p&gt;

&lt;p&gt;This manual assumes that you are using Route 53 as your DNS provider, and all AWS infrastructure will be deployed to the US East (N. Virginia) region. If you want to use a different region, set the &lt;code&gt;aws_region&lt;/code&gt; variable accordingly in &lt;code&gt;blog.tfvars&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Credential Setup and Creating the S3 Bucket
&lt;/h2&gt;

&lt;p&gt;First, we need to set up AWS credentials and ensure that AWS CLI and Terraform (or OpenTofu) are already installed.&lt;/p&gt;

&lt;p&gt;I used this manual to install OpenTofu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opentofu.org/docs/intro/install/deb/" rel="noopener noreferrer"&gt;Installing OpenTofu on .deb-based Linux&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the installation code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Download the installer script:
curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh
# Alternatively: wget --secure-protocol=TLSv1_2 --https-only https://get.opentofu.org/install-opentofu.sh -O install-opentofu.sh

# Give it execution permissions:
chmod +x install-opentofu.sh

# Please inspect the downloaded script

# Run the installer:
./install-opentofu.sh --install-method deb

# Remove the installer:
rm -f install-opentofu.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install Terraform, use this guide:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli" rel="noopener noreferrer"&gt;Install Terraform&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg &amp;gt; /dev/null
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update
sudo apt-get install terraform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install AWS CLI, follow the instructions here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;Installing or updating to the latest version of the AWS CLI&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt install unzip
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the installation is complete, set up your AWS credentials using the profile &lt;code&gt;prod&lt;/code&gt; (you can choose any profile name, but &lt;code&gt;prod&lt;/code&gt; is used for this manual):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws configure --profile prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After setting up your credentials, verify them by running &lt;code&gt;cat ~/.aws/credentials&lt;/code&gt; — it should return something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[prod]
aws_access_key_id=AXXXXXXXXX
aws_secret_access_key=xxxxxxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the credentials with the following command to ensure they work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws sts get-caller-identity --profile prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "UserId": "AXXXXXXXXX",
    "Account": "8XXXXXXXXX",
    "Arn": "arn:aws:iam::8XXXXXXXXX:user/jurijsxxxxxxx"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, create the S3 bucket where the Terraform state file will be saved. Keep in mind that this name might already be taken, so use something unique. If you use a different name, make sure to update &lt;code&gt;main.tf&lt;/code&gt; accordingly.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws s3api create-bucket --bucket "myblog-tf-state" --region "us-east-1" --acl private --profile prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Location": "/myblog-tf-state"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Setup Terraform Code
&lt;/h2&gt;

&lt;p&gt;Most of the Terraform code was taken from the following repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pirxthepilot/terraform-aws-static-site" rel="noopener noreferrer"&gt;GitHub terraform-aws-static-site&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The author's blog post explains in detail what each part of the code does:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pirx.io/posts/2022-05-02-automated-static-site-deployment-in-aws-using-terraform/" rel="noopener noreferrer"&gt;pirx.io - Automated Static Site Deployment in AWS Using Terraform&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, I encountered a few issues with this code. First, it was missing the mandatory &lt;code&gt;aws_s3_bucket_ownership_controls&lt;/code&gt; resource. The solution was to add the &lt;code&gt;aws_s3_bucket_ownership_controls&lt;/code&gt; resource and update the &lt;code&gt;aws_s3_bucket_acl&lt;/code&gt; accordingly. Here is the full code snippet for the relevant part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_s3_bucket" "static_site" {
  bucket = var.domain
}

resource "aws_s3_bucket_ownership_controls" "static_site" {
  bucket = aws_s3_bucket.static_site.id
  rule {
    object_ownership = "BucketOwnerPreferred"
  }
}

resource "aws_s3_bucket_acl" "static_site" {
  depends_on = [aws_s3_bucket_ownership_controls.static_site]

  bucket = aws_s3_bucket.static_site.id
  acl    = "private"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, we need to create an IAM user for deploying the blog via the GitLab pipeline. I copied the relevant code from here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/brianmacdonald/terraform-aws-s3-static-site" rel="noopener noreferrer"&gt;GitHub terraform-aws-s3-static-site&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_iam_user" "deploy" {
  name = "${var.domain}-deploy"
  path = "/"
}

resource "aws_iam_access_key" "deploy" {
  user = aws_iam_user.deploy.name
}

resource "aws_iam_user_policy" "deploy" {
  name   = "deploy"
  user   = aws_iam_user.deploy.name
  policy = data.aws_iam_policy_document.deploy.json
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this code snippet to &lt;code&gt;outputs.tf&lt;/code&gt; to output the credentials needed for the pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "AWS_ACCESS_KEY_ID" {
  sensitive   = true
  description = "The AWS Access Key ID for the IAM deployment user."
  value       = aws_iam_access_key.deploy.id
}

output "AWS_SECRET_ACCESS_KEY" {
  sensitive   = true
  description = "The AWS Secret Key for the IAM deployment user."
  value       = aws_iam_access_key.deploy.secret
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make things easier, I’ve consolidated all the code into a single repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/os11k/jekyll-terraform-aws-s3" rel="noopener noreferrer"&gt;GitHub jekyll-terraform-aws-s3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I chose to use a monolithic approach instead of modules for simplicity. You just need to set at least the &lt;code&gt;domain&lt;/code&gt; and &lt;code&gt;route53_zone_id&lt;/code&gt; variables in &lt;code&gt;blog.tfvars&lt;/code&gt; and you're ready to deploy the infrastructure in AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tofu init
tofu plan -var-file="blog.tfvars"
tofu apply -var-file="blog.tfvars"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or using Terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform plan -var-file="blog.tfvars"
terraform apply -var-file="blog.tfvars"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once applied, the output will display variables to use later in the pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_ACCESS_KEY_ID = &amp;lt;sensitive&amp;gt;
AWS_SECRET_ACCESS_KEY = &amp;lt;sensitive&amp;gt;
CLOUDFRONT_DISTRIBUTION_ID = "EXXXXXX"
S3_BUCKET = "my-site.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To retrieve sensitive variables, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tofu output --json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or using Terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform output --json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Setup GitLab Pipeline
&lt;/h2&gt;

&lt;p&gt;The pipeline I use is directly copied from here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.schenk.tech/posts/jekyll-blog-in-aws-part2/" rel="noopener noreferrer"&gt;SchenkTech Blog - Hosting a Jekyll Site in S3 Part 2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the author didn’t provide a Git repository for the code, I created one myself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gitlab.com/jurijs.ivolga/deploy-jekyll-to-s3" rel="noopener noreferrer"&gt;GitLab deploy-jekyll-to-s3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you fork that repository or copy the files, you will need to set the following variables in GitLab CI/CD settings. Ensure they are masked and protected to safeguard sensitive information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLOUDFRONT_DISTRIBUTION_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;S3_BUCKET&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, add your actual Jekyll code to the repository, commit, and push it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~
git clone https://github.com/daattali/beautiful-jekyll.git
rm -Rf ./beautiful-jekyll/.git* ./beautiful-jekyll/README.md
cp -a ./beautiful-jekyll/* ./deploy-jekyll-to-s3/  # Assuming your code with the pipeline is in ~/deploy-jekyll-to-s3 directory
cd ~/deploy-jekyll-to-s3
git add .
git commit -m "added jekyll"
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you're done! Once the build completes, your website will be deployed, and you should see the Beautiful Jekyll template live.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.cyberpunk.tools/jekyll/update/2024/12/19/jekyll-terraform-gitlab-pipeline.html" rel="noopener noreferrer"&gt;Cyberpunk Tools Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>jekyll</category>
      <category>opentofu</category>
      <category>gitlab</category>
      <category>ci</category>
    </item>
    <item>
      <title>Using Grafana Loki as a Centralized Logging Solution</title>
      <dc:creator>Jurijs Ivolga</dc:creator>
      <pubDate>Wed, 12 Mar 2025 12:25:23 +0000</pubDate>
      <link>https://dev.to/jurijs_iv/using-grafana-loki-as-a-centralized-logging-solution-3hb6</link>
      <guid>https://dev.to/jurijs_iv/using-grafana-loki-as-a-centralized-logging-solution-3hb6</guid>
      <description>&lt;p&gt;Today, I'll explain how to use Grafana Loki as a centralized logging solution for all your Docker containers. As your infrastructure grows and more containers are added, troubleshooting via logs becomes increasingly important. You might need to diagnose issues like a database failure or an SSL certificate that didn’t renew. Personally, I used to check logs with the &lt;code&gt;docker logs&lt;/code&gt; command, but this approach isn’t efficient. Imagine trying to filter logs for a specific time window — like 12:00-12:05 UTC on May 5 — or investigating issues that span multiple containers, such as when a database failure causes an error on an Nginx container. Instead of manually piecing logs together from different machines, it’s more efficient to store all logs centrally, enabling simultaneous searches across all containers. With Loki, you can set up alerts, filter specific log entries using regex, and much more.&lt;/p&gt;

&lt;p&gt;In this post, I'll walk you through how I set up a centralized logging solution for all my Docker containers using Grafana Loki.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Loki
&lt;/h2&gt;

&lt;p&gt;I recommend installing Loki with Docker Compose. Here is Grafana's default &lt;code&gt;docker-compose.yaml&lt;/code&gt; file for Loki:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://grafana.com/docs/loki/latest/setup/install/docker/#install-with-docker-compose" rel="noopener noreferrer"&gt;Grafana Loki Docker Compose Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here is the code itself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/grafana/loki/v3.0.0/production/docker-compose.yaml" rel="noopener noreferrer"&gt;Grafana Loki Docker Compose YAML&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we don’t need Promtail (Loki's log collector), we can comment that part out. I'll also add volume configurations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3"

networks:
  loki:

services:
  loki:
    image: grafana/loki:2.9.2
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml
    volumes:
      - loki_data:/loki
    networks:
      - loki

#  promtail:
#    image: grafana/promtail:2.9.2
#    volumes:
#      - /var/log:/var/log
#    command: -config.file=/etc/promtail/config.yml
#    networks:
#      - loki

  grafana:
    environment:
      - GF_PATHS_PROVISIONING=/etc/grafana/provisioning
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
    entrypoint:
      - sh
      - -euc
      - |
        mkdir -p /etc/grafana/provisioning/datasources
        cat &amp;lt;&amp;lt;EOF &amp;gt; /etc/grafana/provisioning/datasources/ds.yaml
        apiVersion: 1
        datasources:
        - name: Loki
          type: loki
          access: proxy 
          orgId: 1
          url: http://loki:3100
          basicAuth: false
          isDefault: true
          version: 1
          editable: false
        EOF
        /run.sh
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - loki

volumes:
    grafana_data: {}
    loki_data: {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Configure Containers to Send Logs to Loki
&lt;/h2&gt;

&lt;p&gt;Once the Loki container is running, configure your containers to push logs to Loki. First, install the Docker plugin and restart the Docker engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that the plugin is installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker plugin ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your newly installed Docker plugin:&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          DESCRIPTION           ENABLED
ddd2367c8693   loki:latest   Loki Logging Driver   true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, configure each container to send logs to Loki by adding these lines in your &lt;code&gt;docker-compose&lt;/code&gt; file (replace &lt;code&gt;loki-ip&lt;/code&gt; with the actual IP of your Loki server):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;logging:
  driver: loki
  options:
    loki-url: http://loki-ip:3100/loki/api/v1/push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, configure Docker to send logs from all containers by creating an &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; file (again, replace &lt;code&gt;loki-ip&lt;/code&gt; with the actual IP of your Loki server):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "debug" : true,
    "log-driver": "loki",
    "log-opts": {
        "loki-url": "http://loki-ip:3100/loki/api/v1/push"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After making these changes, recreate your containers to start logging to Loki. With Docker Compose, run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose down
docker-compose up -d --build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you chose the &lt;code&gt;daemon.json&lt;/code&gt; approach, restart the Docker service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loki doesn’t pull logs; instead, Docker pushes logs to Loki. Ensure Docker can reach Loki on port 3100 (if using the default). Test connectivity with &lt;code&gt;telnet&lt;/code&gt; from the Docker host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;telnet loki-ip 3100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Viewing Logs in Grafana
&lt;/h2&gt;

&lt;p&gt;Now you should be able to see logs in Grafana. Go to the "Explore" section and make sure "Loki" is selected in the top-left dropdown menu.&lt;/p&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%2Fsxyba11mhpi82s1i58ax.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%2Fsxyba11mhpi82s1i58ax.png" alt="Diagram" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on "Label Browser" and select the appropriate label. In this example, it’s &lt;code&gt;compose_project =&amp;gt; random-logger&lt;/code&gt;. Then click "Show logs."&lt;/p&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%2Fhlj40fmdis92nny4xg8x.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%2Fhlj40fmdis92nny4xg8x.png" alt="Diagram" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After clicking "Show logs," you should see your logs:&lt;/p&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%2F1vvbyqruug2xdgohug4q.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%2F1vvbyqruug2xdgohug4q.png" alt="Diagram" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it! At this point, you've successfully set up Grafana with Loki, and your Docker containers should be sending logs to it.&lt;/p&gt;

&lt;p&gt;For the next steps, you might consider setting up data retention policies in Loki and creating custom dashboards — I’ll leave that as a homework exercise.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.cyberpunk.tools/jekyll/update/2024/11/10/grafana-loki.html" rel="noopener noreferrer"&gt;Cyberpunk Tools Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>grafana</category>
      <category>devops</category>
      <category>loki</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>How to Create a Widget for Meta Threads using Scriptable</title>
      <dc:creator>Jurijs Ivolga</dc:creator>
      <pubDate>Tue, 11 Mar 2025 09:03:56 +0000</pubDate>
      <link>https://dev.to/jurijs_iv/how-to-create-a-widget-for-meta-threads-using-scriptable-1298</link>
      <guid>https://dev.to/jurijs_iv/how-to-create-a-widget-for-meta-threads-using-scriptable-1298</guid>
      <description>&lt;p&gt;I recently started using Meta Threads and saw someone complaining that Meta should create a widget for certain stats. I replied, saying it should be possible using the Scriptable app. Long story short, I decided to dive into this rabbit hole and create some widgets for Meta Threads.&lt;/p&gt;

&lt;p&gt;I'll be creating two widgets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Main widget&lt;/strong&gt;: Follower counts and profile visitors (today and yesterday)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondary widget&lt;/strong&gt;: View counts for the latest post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll use the Scriptable app, which you can download free from the iOS App Store.&lt;/p&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%2Fr4ndus9958g2chs8nelb.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%2Fr4ndus9958g2chs8nelb.jpg" alt="Meta Threads Widget Example" width="800" height="933"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set Up the Meta App
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://developers.facebook.com/" rel="noopener noreferrer"&gt;Meta for Developers&lt;/a&gt; and create an app. The setup process is mostly straightforward ("next-next-next").&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;"I don’t want to connect a business portfolio yet."&lt;/strong&gt;&lt;/p&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%2Fomrg3mdqdbt08fgkih00.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%2Fomrg3mdqdbt08fgkih00.png" alt="Setup" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then choose &lt;strong&gt;"Access the Threads API"&lt;/strong&gt;.&lt;/p&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%2Fns074ptkpvgh68gq62kf.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%2Fns074ptkpvgh68gq62kf.png" alt="Threads API" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter your app name and email:&lt;/p&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%2F05y9ksyfk5m8fmtkxx1u.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%2F05y9ksyfk5m8fmtkxx1u.png" alt="App details" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;"Go to Dashboard"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In dashboard settings, continue with more straightforward steps.&lt;/p&gt;

&lt;p&gt;First, select permissions under &lt;strong&gt;"Access the Threads API"&lt;/strong&gt;—specifically &lt;code&gt;threads_basic&lt;/code&gt; and &lt;code&gt;threads_manage_insights&lt;/code&gt;:&lt;/p&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%2Fe9c8gb6wl8bar9jk0tol.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%2Fe9c8gb6wl8bar9jk0tol.png" alt="Permissions" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click &lt;strong&gt;"Test Use Cases"&lt;/strong&gt;, followed by &lt;strong&gt;"Finish Customization"&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add Roles
&lt;/h2&gt;

&lt;p&gt;Navigate to &lt;strong&gt;"App Roles" → "Roles"&lt;/strong&gt;.&lt;/p&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%2Fidgvpqdn8fi8i81mnqe9.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%2Fidgvpqdn8fi8i81mnqe9.png" alt="Roles" width="800" height="899"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;"Add People"&lt;/strong&gt;, select &lt;strong&gt;"Threads Tester"&lt;/strong&gt;, and add yourself:&lt;/p&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%2F42863looy7ybk1huvzkg.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%2F42863looy7ybk1huvzkg.png" alt="Add tester" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the Threads app with your user account, then navigate to &lt;strong&gt;Settings → Account → Website Permissions → Invites&lt;/strong&gt; and accept the invite:&lt;/p&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%2Fwnh4evznmrv7sdjpviwc.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%2Fwnh4evznmrv7sdjpviwc.png" alt="Accept invite" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Get an Access Token
&lt;/h2&gt;

&lt;p&gt;Visit &lt;a href="https://developers.facebook.com/tools/explorer/" rel="noopener noreferrer"&gt;Facebook Developer Explorer&lt;/a&gt; and select &lt;strong&gt;"threads.net"&lt;/strong&gt;, then click &lt;strong&gt;"Generate Threads Access Token"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;"Continue"&lt;/strong&gt; in the pop-up. Your token will appear in the &lt;strong&gt;Access Token&lt;/strong&gt; field.&lt;/p&gt;

&lt;p&gt;Validate your token with the &lt;a href="https://developers.facebook.com/tools/debug/accesstoken/" rel="noopener noreferrer"&gt;Access Token Debugger&lt;/a&gt; to ensure it works correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Exchange for a Long-Lived Token
&lt;/h2&gt;

&lt;p&gt;Exchange your short-lived token (expires in 60 minutes) for a long-lived token (valid for two months). Follow instructions from &lt;a href="https://developers.facebook.com/docs/threads/get-started/long-lived-tokens/" rel="noopener noreferrer"&gt;Meta Documentation on Long-Lived Tokens&lt;/a&gt; using this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -s -X GET "https://graph.threads.net/access_token?grant_type=th_exchange_token&amp;amp;client_secret=&amp;lt;THREADS_APP_SECRET&amp;gt;&amp;amp;access_token=&amp;lt;SHORT_LIVED_ACCESS_TOKEN&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To find your &lt;code&gt;THREADS_APP_SECRET&lt;/code&gt;, go to your app's settings (&lt;strong&gt;App Settings → Basic&lt;/strong&gt;) on &lt;a href="https://developers.facebook.com/apps/?show_reminder=true" rel="noopener noreferrer"&gt;Facebook Apps Dashboard&lt;/a&gt;:&lt;/p&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%2Fz87ohrseja4c4u30bmes.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%2Fz87ohrseja4c4u30bmes.png" alt="App secret" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use this secret in the curl command above. Recheck your new long-lived token with the Access Token Debugger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Save Your Long-Lived Token to iCloud
&lt;/h2&gt;

&lt;p&gt;Our scripts will need to access your token and refresh it too. First, we need to store your token in iCloud, so later our scripts can use it. Run this script just once with Scriptable, and later the main widget script will handle future token refreshes automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/os11k/501d7b2be09c6bba0e734485cce28365" rel="noopener noreferrer"&gt;Token Storage Script (Gist)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Building Your Widgets
&lt;/h2&gt;

&lt;p&gt;We'll build two widgets using Scriptable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A main widget displaying followers count and profile visitors (today/yesterday).&lt;/li&gt;
&lt;li&gt;A secondary widget showing view counts of your latest post.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Main Widget Endpoints:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Followers:
&lt;code&gt;https://graph.threads.net/v1.0/me/threads_insights?metric=followers_count&amp;amp;access_token=YOUR_TOKEN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Profile Visitors: &lt;code&gt;https://graph.threads.net/v1.0/me/threads_insights?metric=views&amp;amp;access_token=YOUR_TOKEN&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full code:&lt;br&gt;
&lt;a href="https://gist.github.com/os11k/1f109155706ce2bef8b68ea61a324126" rel="noopener noreferrer"&gt;Main Widget Code (Gist)&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Secondary Widget Endpoints:
&lt;/h3&gt;

&lt;p&gt;Fetch latest threads: &lt;code&gt;https://graph.threads.net/v1.0/me/threads?fields=id,text,views&amp;amp;access_token=YOUR_TOKEN&lt;/code&gt;&lt;br&gt;
Retrieve stats for latest thread:&lt;code&gt;https://graph.threads.net/v1.0/${postId}/insights?metric=likes,replies,views&amp;amp;access_token=YOUR_TOKEN&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Full code:&lt;br&gt;
&lt;a href="https://gist.github.com/os11k/583b8513b8abe1aa902c3d05f90ac8f7" rel="noopener noreferrer"&gt;Secondary Widget Code (Gist)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.cyberpunk.tools/jekyll/update/2024/10/26/how-to-create-threads-widgets.html" rel="noopener noreferrer"&gt;Cyberpunk Tools Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>scriptable</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>CertDecoder.com - A Free and Simple Online Certificate Decoder"</title>
      <dc:creator>Jurijs Ivolga</dc:creator>
      <pubDate>Mon, 10 Mar 2025 16:32:01 +0000</pubDate>
      <link>https://dev.to/jurijs_iv/certdecodercom-a-free-and-simple-online-certificate-decoder-3k58</link>
      <guid>https://dev.to/jurijs_iv/certdecodercom-a-free-and-simple-online-certificate-decoder-3k58</guid>
      <description>&lt;p&gt;Sometimes in my daily work, I need to handle certificates and double-check their details. Questions like, "Is this a new or old certificate? Does it have the SAN I need?" often come up.&lt;/p&gt;

&lt;p&gt;Recently, I decided to build my own online certificate decoder – &lt;strong&gt;CertDecoder.com&lt;/strong&gt;. I know there are dozens, if not hundreds, of certificate decoders available online, but I wanted something simple, fast, and reliable for my own use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why try Cert Decoder?
&lt;/h3&gt;

&lt;p&gt;If you're juggling certificates at midnight and struggling to keep your eyes open, you need a straightforward tool to quickly verify your certificate details without extra hassle.&lt;/p&gt;

&lt;p&gt;Just open &lt;a href="https://certdecoder.com" rel="noopener noreferrer"&gt;&lt;strong&gt;CertDecoder.com&lt;/strong&gt;&lt;/a&gt;, paste your PEM-formatted X.509 certificate, and instantly view its details.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy-first:&lt;/strong&gt; All processing happens entirely in your browser, ensuring no certificate data is ever leaked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retro and minimalist:&lt;/strong&gt; It's pure HTML—something you rarely see these days, offering a nostalgic, minimalist look.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're a web developer, sysadmin, cybersecurity enthusiast, or DevOps junkie, check it out here 👉 &lt;a href="https://certdecoder.com" rel="noopener noreferrer"&gt;&lt;strong&gt;CertDecoder.com&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd greatly appreciate your feedback! 🙏&lt;/p&gt;

&lt;p&gt;Feel free to share your suggestions or feature ideas to make it even better.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ssl</category>
      <category>cybersecurity</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
