<?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: Muhammad Dhiyaul Atha</title>
    <description>The latest articles on DEV Community by Muhammad Dhiyaul Atha (@bangkah).</description>
    <link>https://dev.to/bangkah</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3665512%2Fdc6c5db5-91c9-4887-a5c6-33c8db391e07.jpeg</url>
      <title>DEV Community: Muhammad Dhiyaul Atha</title>
      <link>https://dev.to/bangkah</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bangkah"/>
    <language>en</language>
    <item>
      <title>EcoID: An Offline Plant Identifier That Gets You Outside</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Fri, 09 Oct 2026 20:55:09 +0000</pubDate>
      <link>https://dev.to/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e</link>
      <guid>https://dev.to/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;EcoID is an offline-first plant identification tool for field observations.&lt;/p&gt;

&lt;p&gt;The idea is simple: take a laptop or phone outside, photograph a real plant, and use a local AI model to get identification candidates. Then look at the plant yourself and verify the result.&lt;/p&gt;

&lt;p&gt;EcoID is designed for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;students learning about local plants&lt;/li&gt;
&lt;li&gt;field researchers and citizen scientists&lt;/li&gt;
&lt;li&gt;gardeners and small-scale farmers&lt;/li&gt;
&lt;li&gt;people who want to explore nature without sending their photos to a cloud service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current model supports five plant classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mango — &lt;code&gt;Mangifera indica&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Coconut — &lt;code&gt;Cocos nucifera&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Banana — &lt;code&gt;Musa acuminata&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Papaya — &lt;code&gt;Carica papaya&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cassava — &lt;code&gt;Manihot esculenta&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI does not present its output as a fact. It returns ranked identification candidates with confidence scores. The user then chooses whether the result is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verified&lt;/li&gt;
&lt;li&gt;Rejected&lt;/li&gt;
&lt;li&gt;Uncertain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This human-in-the-loop flow matters because a model prediction is only a suggestion until somebody looks at the actual plant.&lt;/p&gt;

&lt;p&gt;EcoID stores field observations locally, including the original photo, model prediction, alternative predictions, confidence score, verification status, notes, optional GPS coordinates, capture time from EXIF metadata, and user corrections.&lt;/p&gt;

&lt;p&gt;The project is designed to make the computer useful for a short moment, then get the person back to observing the real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;EcoID runs locally on a laptop or a phone-accessible local network. There is no public hosted demo because local execution is part of the project's privacy and offline design.&lt;/p&gt;

&lt;p&gt;The trained model and downloaded dataset are not committed to the repository. Model weights and image data are kept outside Git because of their size and individual dataset licensing terms.&lt;/p&gt;

&lt;p&gt;After training or obtaining the ONNX model locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; app &lt;span class="nt"&gt;--model&lt;/span&gt; models/ecoid-global-20261010.onnx &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app opens at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:8765
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Capture or upload a photo
        ↓
Run local inference
        ↓
Review possible identifications
        ↓
Verify, reject, or mark uncertain
        ↓
Save the field observation
        ↓
Review observations in history and on the local map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The source code is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Bangkah/EcoID/tree/main" rel="noopener noreferrer"&gt;github.com/Bangkah/EcoID/tree/main&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The source code is released under the &lt;a href="https://github.com/Bangkah/EcoID/blob/main/LICENSE" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;. Dataset images retain their original licenses and attribution requirements.&lt;/p&gt;

&lt;p&gt;The main parts of the project are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app/ai/&lt;/code&gt; — preprocessing, inference, model contracts, and result post-processing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/observation/&lt;/code&gt; — drafts, verification, EXIF metadata, maps, statistics, and exports&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/storage/&lt;/code&gt; — local SQLite persistence and image storage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/ui/&lt;/code&gt; — the local HTTP server and browser interface&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts/&lt;/code&gt; — dataset preparation, training, export, evaluation, and benchmarking&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/&lt;/code&gt; — unit, integration, offline, and browser tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F37wse1xbiqofnzi08nh7.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F37wse1xbiqofnzi08nh7.png" alt=" " width="563" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Local image preprocessing
&lt;/h3&gt;

&lt;p&gt;Every image follows the same preprocessing path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decode the image&lt;/li&gt;
&lt;li&gt;Apply EXIF orientation&lt;/li&gt;
&lt;li&gt;Convert to RGB&lt;/li&gt;
&lt;li&gt;Resize to &lt;code&gt;224x224&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Normalize with ImageNet mean and standard deviation&lt;/li&gt;
&lt;li&gt;Convert to a &lt;code&gt;float32&lt;/code&gt; tensor with shape &lt;code&gt;(1, 3, 224, 224)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping preprocessing in one shared module helps avoid train/serve skew between the training pipeline and the production inference path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open-source AI and local inference
&lt;/h3&gt;

&lt;p&gt;EcoID uses a MobileNetV3-Small image classifier exported to ONNX and executed with ONNX Runtime on the CPU.&lt;/p&gt;

&lt;p&gt;The UI and storage layers depend only on a small model contract, so the inference backend can be replaced without rewriting the rest of the application.&lt;/p&gt;

&lt;p&gt;The output is converted into a Top-3 list of candidates. If the confidence score is below the configured threshold, the result is marked as &lt;code&gt;LOW_CONFIDENCE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The initial threshold was calibrated using validation data. The current configuration is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.73&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the current validation set, this produced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;53.7% coverage&lt;/li&gt;
&lt;li&gt;97.5% selective accuracy&lt;/li&gt;
&lt;li&gt;80.0% negative rejection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These numbers are provisional and should be recalibrated when the field dataset grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Training results
&lt;/h3&gt;

&lt;p&gt;The first real dataset download produced 2,447 usable licensed images, approximately 370 MB in total. The dataset was collected from iNaturalist using CC0, CC BY, and CC BY-SA images, with attribution metadata preserved.&lt;/p&gt;

&lt;p&gt;The validated dataset contains approximately 2,000 training images across the five target classes, 300 validation images, and 149 negative samples across lookalike, non-plant, and low-quality groups.&lt;/p&gt;

&lt;p&gt;The dataset pipeline includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;license metadata&lt;/li&gt;
&lt;li&gt;author attribution&lt;/li&gt;
&lt;li&gt;photographer-based split separation&lt;/li&gt;
&lt;li&gt;duplicate and near-duplicate checks&lt;/li&gt;
&lt;li&gt;validation and evaluation splits&lt;/li&gt;
&lt;li&gt;negative examples&lt;/li&gt;
&lt;li&gt;dataset structure validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The initial MobileNetV3-Small training run achieved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;best validation accuracy: &lt;strong&gt;80.3%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;five target plant classes&lt;/li&gt;
&lt;li&gt;local CPU inference after ONNX export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a separate 50-image evaluation set (10 images per class), the model achieved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top-1 accuracy: &lt;strong&gt;86.0% (43/50)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Top-3 accuracy: &lt;strong&gt;94.0% (47/50)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;negative rejection: &lt;strong&gt;77.1% (84/109)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;selective accuracy at threshold &lt;code&gt;0.73&lt;/code&gt;: &lt;strong&gt;94.1%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;coverage: &lt;strong&gt;68.0%&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This evaluation set was collected from licensed iNaturalist images and is useful as a reproducible benchmark. It is not a substitute for a field evaluation with locally captured phone photos.&lt;/p&gt;

&lt;p&gt;The ONNX export was verified against the PyTorch model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maximum logit difference: approximately &lt;code&gt;1.9e-6&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Top-1 agreement: &lt;code&gt;20/20&lt;/code&gt; samples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository also generates contact sheets for mandatory manual label review. Automated dataset validation passed with zero errors, but a complete human review of all contact sheets remains a dataset-quality task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Human verification
&lt;/h3&gt;

&lt;p&gt;A prediction is first stored as a temporary draft. It becomes a permanent observation only after the user makes a verification decision.&lt;/p&gt;

&lt;p&gt;This prevents an abandoned or uncertain prediction from silently becoming part of the observation history.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;photo → identification draft → human verification → saved observation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Draft photos that are abandoned are automatically removed after 24 hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offline storage
&lt;/h3&gt;

&lt;p&gt;The application stores everything locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.ecoid/
├── ecoid.db
├── images/
├── thumbs/
└── drafts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application does not require a cloud API for inference. The browser UI also uses a strict Content Security Policy and does not load external scripts, fonts, or images.&lt;/p&gt;

&lt;p&gt;GPS is opt-in. A user can attach a photo's EXIF location, use device geolocation, or enter coordinates manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;The model was benchmarked on a Windows laptop using CPU inference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Average&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decode and resize&lt;/td&gt;
&lt;td&gt;6.3 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONNX inference&lt;/td&gt;
&lt;td&gt;5.8 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Softmax and Top-K&lt;/td&gt;
&lt;td&gt;0.4 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;12.4 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The measured p95 total latency was &lt;strong&gt;33.8 ms per image&lt;/strong&gt;, below the project's target of 10 seconds per image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;The project includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.11 and 3.12 test coverage&lt;/li&gt;
&lt;li&gt;Ruff linting&lt;/li&gt;
&lt;li&gt;byte-compilation checks&lt;/li&gt;
&lt;li&gt;tests with non-UTF-8 default encoding&lt;/li&gt;
&lt;li&gt;browser tests with real headless Chromium&lt;/li&gt;
&lt;li&gt;offline tests that block non-loopback sockets and DNS lookups&lt;/li&gt;
&lt;li&gt;UI tests for capture, verification, history, map, statistics, export, and deletion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current local test result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;222 tests passed
25 browser tests skipped unless browser testing is enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CI workflow is defined in &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Plant identification is a good example of why open innovation matters.&lt;/p&gt;

&lt;p&gt;A closed cloud API could return a label quickly, but it would also introduce several limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;photos would leave the device&lt;/li&gt;
&lt;li&gt;the application would depend on an internet connection&lt;/li&gt;
&lt;li&gt;the model's behavior would be difficult to inspect&lt;/li&gt;
&lt;li&gt;users would have less control over their field data&lt;/li&gt;
&lt;li&gt;the tool might not work in remote areas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using open-source tools and local inference makes a different design possible. The complete pipeline can be inspected and adapted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;image preprocessing is visible&lt;/li&gt;
&lt;li&gt;model inputs and outputs are explicit&lt;/li&gt;
&lt;li&gt;confidence thresholds can be calibrated&lt;/li&gt;
&lt;li&gt;observations remain on the user's device&lt;/li&gt;
&lt;li&gt;the backend can be replaced&lt;/li&gt;
&lt;li&gt;the application can run without a reliable internet connection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open innovation also makes the project easier to learn from. Someone can inspect the training scripts, run the synthetic smoke test, replace the model, add new plant classes, or adapt the observation workflow for another field research problem.&lt;/p&gt;

&lt;p&gt;The goal is not to pretend that a small classifier can replace botanical expertise. The goal is to build a transparent tool that helps people notice, record, and learn from the plants around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and Next Steps
&lt;/h2&gt;

&lt;p&gt;This is an initial working model, not a finished botanical identification system.&lt;/p&gt;

&lt;p&gt;The most important remaining limitation is domain coverage. The 50-image independent evaluation set is sourced from licensed iNaturalist images, while a dedicated evaluation set of locally captured Indonesian field photos is still needed.&lt;/p&gt;

&lt;p&gt;The next steps are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add Indonesian field photos and compare them with the current 50-image iNaturalist benchmark.&lt;/li&gt;
&lt;li&gt;Perform the manual visual quality-control pass on all contact sheets.&lt;/li&gt;
&lt;li&gt;Remove or document near-duplicate training images.&lt;/li&gt;
&lt;li&gt;Re-run calibration using the expanded validation set.&lt;/li&gt;
&lt;li&gt;Re-run final evaluation on a fixed, independent test set.&lt;/li&gt;
&lt;li&gt;Record a physical field demonstration with the trained model.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;This project was developed with an AI coding assistant using Copilot SDK in VS Code.&lt;/p&gt;

&lt;p&gt;The agent helped inspect the repository, understand the architecture, connect the AI pipeline to the local observation workflow, run the dataset pipeline, train the initial model, verify ONNX parity, calibrate the confidence threshold, and validate the test suite.&lt;/p&gt;

&lt;p&gt;The agent session is optional for this submission and is not linked here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;I am entering the overall Hacktoberfest Open-Source AI Challenge.&lt;/p&gt;

&lt;p&gt;I am not entering a partner category because EcoID does not currently use a partner-specific technology.&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>UnvibeComplex Code to Real Workflows: Finding and Fixing a Secret-Handling Boundary Bug in UnvibeCode</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Thu, 08 Oct 2026 12:02:26 +0000</pubDate>
      <link>https://dev.to/bangkah/unvibecomplex-code-to-real-workflows-finding-and-fixing-a-secret-handling-boundary-bug-in-1mg</link>
      <guid>https://dev.to/bangkah/unvibecomplex-code-to-real-workflows-finding-and-fixing-a-secret-handling-boundary-bug-in-1mg</guid>
      <description>&lt;h1&gt;
  
  
  From CI/CD Secrets to External APIs: Finding a Boundary Bug in UnvibeCode
&lt;/h1&gt;

&lt;p&gt;When evaluating AI-powered developer tools, we often focus on the big picture: how well a repository is mapped, how useful the generated architecture diagrams are, or how accurate the code summaries can be.&lt;/p&gt;

&lt;p&gt;However, the reliability of an open-source project is also determined by small details that are easy to overlook—CI/CD scripts, environment variables, and requests to external APIs.&lt;/p&gt;

&lt;p&gt;While analyzing &lt;a href="https://github.com/FinanceFlash/unvibecode" rel="noopener noreferrer"&gt;UnvibeCode&lt;/a&gt;, I found an interesting example of a &lt;strong&gt;bug at the boundary between configuration and runtime behavior&lt;/strong&gt;: a secret was correctly passed by GitHub Actions and successfully read by the Python script, but it was never actually used in the outgoing HTTP request.&lt;/p&gt;

&lt;p&gt;This article explains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how the bug was discovered;&lt;/li&gt;
&lt;li&gt;why the existing test suite did not catch it;&lt;/li&gt;
&lt;li&gt;how the issue can be fixed;&lt;/li&gt;
&lt;li&gt;and what this teaches us about testing pipelines that rely on secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This analysis is based on source-code inspection and the repository's quality suite. No production credentials or tokens were accessed, displayed, or used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tracing the Secret Flow
&lt;/h2&gt;

&lt;p&gt;UnvibeCode includes a GitHub Actions workflow for archiving repository traffic statistics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.github/workflows/archive-github-traffic.yml
        |
        v
.github/scripts/archive_github_traffic.py
        |
        v
GitHub Traffic API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow requires a secret named &lt;code&gt;TRAFFIC_TOKEN&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Secret Is Passed by the Workflow
&lt;/h3&gt;

&lt;p&gt;The workflow maps the GitHub Actions secret to an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;TRAFFIC_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.TRAFFIC_TOKEN }}&lt;/span&gt;
  &lt;span class="na"&gt;GITHUB_REPOSITORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, everything appears correct. GitHub Actions makes the secret available to the environment in which the Python script runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Python Script Reads the Secret
&lt;/h3&gt;

&lt;p&gt;The script retrieves the environment variable and exits if it is missing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TRAFFIC_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TRAFFIC_TOKEN is not set. Add a repository secret named &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TRAFFIC_TOKEN with read access to repository traffic.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the flow appears correct: the secret is available and the application successfully reads it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Secret Is Lost When the Request Is Built
&lt;/h3&gt;

&lt;p&gt;The problem appears in the &lt;code&gt;api_get()&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/vnd.github+json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*****&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unvibecode-traffic-archiver&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;f"*****"&lt;/code&gt; value is not an f-string that incorporates the &lt;code&gt;TOKEN&lt;/code&gt; variable. It is simply a string literal.&lt;/p&gt;

&lt;p&gt;As a result, the &lt;code&gt;TOKEN&lt;/code&gt; value that was successfully read earlier never reaches the &lt;code&gt;Authorization&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;The request is then sent to several GitHub Traffic API endpoints, including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;traffic/views?per=day
traffic/clones?per=day
traffic/popular/referrers
traffic/popular/paths
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the API rejects the resulting authorization header, the workflow fails before the traffic data can be written to the CSV output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Is This Bug Easy to Miss?
&lt;/h2&gt;

&lt;p&gt;This finding highlights three distinct stages that should be considered when reviewing secrets in CI/CD pipelines:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Availability&lt;/strong&gt; — Is the secret available to the workflow?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readability&lt;/strong&gt; — Does the application read the correct environment variable?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage&lt;/strong&gt; — Does the actual runtime value reach the external API boundary?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this case, the first two stages appeared correct. The bug only became visible at the third stage.&lt;/p&gt;

&lt;p&gt;This is why simply reviewing configuration files is not enough. We need to trace the data flow all the way to the outgoing request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Didn't the Existing Tests Catch It?
&lt;/h2&gt;

&lt;p&gt;I ran the repository's quality suite, which completed successfully with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;906 passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test suite validates several important aspects of the repository, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;workflow-pack structure;&lt;/li&gt;
&lt;li&gt;scenario counts;&lt;/li&gt;
&lt;li&gt;skill metadata;&lt;/li&gt;
&lt;li&gt;local Markdown links;&lt;/li&gt;
&lt;li&gt;required automation files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, there was no test that invoked &lt;code&gt;api_get()&lt;/code&gt; with &lt;code&gt;urlopen&lt;/code&gt; mocked and then inspected the resulting request headers.&lt;/p&gt;

&lt;p&gt;In other words, the existing tests demonstrated that the repository structure and related metadata were valid, but they did not verify that the secret was actually used when the HTTP request was constructed.&lt;/p&gt;

&lt;p&gt;This is an important distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A test suite can provide strong structural coverage while still missing a critical runtime integration bug.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Recommended Fix
&lt;/h2&gt;

&lt;p&gt;The authorization header should use the runtime token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/vnd.github+json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unvibecode-traffic-archiver&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The change itself is small, but it should be accompanied by a regression test.&lt;/p&gt;

&lt;p&gt;A suitable test should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;provide a dummy token through the environment;&lt;/li&gt;
&lt;li&gt;mock &lt;code&gt;urlopen&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;api_get()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;inspect the generated request;&lt;/li&gt;
&lt;li&gt;verify that the &lt;code&gt;Authorization&lt;/code&gt; header contains the runtime token;&lt;/li&gt;
&lt;li&gt;ensure that the token is never printed to stdout or stderr;&lt;/li&gt;
&lt;li&gt;continue to verify that a missing token produces a clear error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The core assertion could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer test-token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual test should use a dummy value such as &lt;code&gt;test-token&lt;/code&gt;, never a real credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering Lessons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A Secret Being Available Does Not Mean It Is Being Used
&lt;/h3&gt;

&lt;p&gt;An environment variable can be correctly configured and successfully read without ever reaching the component that needs it.&lt;/p&gt;

&lt;p&gt;For external integrations, the final boundary—such as an HTTP header, SDK argument, or subprocess invocation—must be explicitly verified.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Test the External Boundary, Not Just Internal Logic
&lt;/h3&gt;

&lt;p&gt;For code that communicates with an API, mock the network client or network function at the point where the request leaves the application.&lt;/p&gt;

&lt;p&gt;Verify at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP method;&lt;/li&gt;
&lt;li&gt;URL;&lt;/li&gt;
&lt;li&gt;authorization headers;&lt;/li&gt;
&lt;li&gt;relevant request parameters;&lt;/li&gt;
&lt;li&gt;and, where appropriate, request payloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach avoids making real API calls while keeping the test fast, deterministic, and safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Never Print Secrets for Debugging
&lt;/h3&gt;

&lt;p&gt;When inspecting authentication behavior, assert against the request object or sanitized metadata.&lt;/p&gt;

&lt;p&gt;Do not print the raw token into CI logs—even temporarily.&lt;/p&gt;

&lt;p&gt;A debugging statement such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can turn a functional debugging session into a credential-exposure incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Analysis Tools Help Trace the Path, but They Do Not Replace Verification
&lt;/h3&gt;

&lt;p&gt;UnvibeCode can make it easier to connect workflows, files, and dependencies and identify the path a secret takes through a repository.&lt;/p&gt;

&lt;p&gt;However, the final conclusion still needs to be validated against the actual source code and, where possible, a test at the runtime boundary.&lt;/p&gt;

&lt;p&gt;Static analysis can show that a variable is present.&lt;/p&gt;

&lt;p&gt;A boundary test can show that its value is actually transmitted.&lt;/p&gt;

&lt;p&gt;Those are different guarantees.&lt;/p&gt;

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

&lt;p&gt;This is not an example of a secret being leaked into the repository. The problem is almost the opposite: &lt;strong&gt;the configured secret was never actually used.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The secret was not exposed.&lt;/li&gt;
&lt;li&gt;The API request could nevertheless fail.&lt;/li&gt;
&lt;li&gt;The workflow appeared to be correctly configured.&lt;/li&gt;
&lt;li&gt;The existing tests could still pass because they did not inspect the outgoing request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When auditing CI/CD pipelines, don't stop at the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Is the secret configured?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ask the more important question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Does the secret actually reach the external boundary where it is required?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That boundary is often where a seemingly correct configuration turns into a runtime failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/FinanceFlash/unvibecode" rel="noopener noreferrer"&gt;UnvibeCode on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hamznabil/secure-api-key-handling-in-python-projects-1kg7"&gt;Secure API Key Handling in Python Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/gitguardian/how-to-handle-secrets-in-python-c0n"&gt;How to Handle Secrets in Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; This article was written with AI assistance for language editing and presentation. The technical finding, repository inspection, and final conclusions are based on analysis of the source code and the repository's test results.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>cicd</category>
      <category>debugging</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Sun, 04 Oct 2026 00:47:19 +0000</pubDate>
      <link>https://dev.to/bangkah/-1hip</link>
      <guid>https://dev.to/bangkah/-1hip</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bangkah/building-a-privacy-first-ai-crm-assistant-for-my-friends-business-58i7" class="crayons-story__hidden-navigation-link"&gt;Building a Privacy-First AI CRM Assistant for My Friend's Business&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bangkah" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3665512%2Fdc6c5db5-91c9-4887-a5c6-33c8db391e07.jpeg" alt="bangkah profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bangkah" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Muhammad Dhiyaul Atha
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Muhammad Dhiyaul Atha
                
                
              
              &lt;div id="story-author-preview-content-4793577" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bangkah" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3665512%2Fdc6c5db5-91c9-4887-a5c6-33c8db391e07.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Muhammad Dhiyaul Atha&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bangkah/building-a-privacy-first-ai-crm-assistant-for-my-friends-business-58i7" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 3&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bangkah/building-a-privacy-first-ai-crm-assistant-for-my-friends-business-58i7" id="article-link-4793577"&gt;
          Building a Privacy-First AI CRM Assistant for My Friend's Business
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/weekendchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;weekendchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hf26challenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hf26challenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hacktoberfest"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hacktoberfest&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/bangkah/building-a-privacy-first-ai-crm-assistant-for-my-friends-business-58i7" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/bangkah/building-a-privacy-first-ai-crm-assistant-for-my-friends-business-58i7#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>privacy</category>
      <category>startup</category>
    </item>
    <item>
      <title>Building a Privacy-First AI CRM Assistant for My Friend's Business</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Sat, 03 Oct 2026 21:20:23 +0000</pubDate>
      <link>https://dev.to/bangkah/building-a-privacy-first-ai-crm-assistant-for-my-friends-business-58i7</link>
      <guid>https://dev.to/bangkah/building-a-privacy-first-ai-crm-assistant-for-my-friends-business-58i7</guid>
      <description>&lt;p&gt;Matt runs a growing business, but his daily intake operation was becoming a bottleneck. &lt;/p&gt;

&lt;p&gt;His team receives business inquiries through email, website forms, and messaging channels. The incoming information is highly inconsistent. Some inquiries are valuable sales opportunities, some are support questions, some are junk, and others simply lack enough information to make a decision.&lt;/p&gt;

&lt;p&gt;He needed a system to ingest these inquiries, identify what they are, extract useful structured information, and draft the next response. However, sending sensitive client business data to closed-source cloud LLMs was a major privacy concern.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/strong&gt;, I decided to build him a secure, locally hosted &lt;strong&gt;AI CRM Intake Automation System&lt;/strong&gt; written in Go.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I designed a practical backend system that ingests incoming messages, uses local open-source AI to classify them, extracts structured data, and prepares the next steps for a human to review. &lt;/p&gt;

&lt;p&gt;Here is what the system does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion &amp;amp; Classification:&lt;/strong&gt; Determines if a message is a &lt;code&gt;Sales Lead&lt;/code&gt;, &lt;code&gt;Support Ticket&lt;/code&gt;, &lt;code&gt;Junk&lt;/code&gt;, or &lt;code&gt;Incomplete&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraction:&lt;/strong&gt; Pulls out names, contact info, and business needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drafting:&lt;/strong&gt; Prepares a response based on the classification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-in-the-Loop:&lt;/strong&gt; Alerts Matt's team to review and approve the action before any CRM record is updated or any email is sent.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The prototype can process an inquiry locally and produce structured output for the next stage of the workflow.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu757rijt0kvvlvrdy0pa.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu757rijt0kvvlvrdy0pa.png" alt="AI CRM automation workflow demo" width="457" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;You can check out the project and its code structure here:&lt;br&gt;
&lt;a href="https://github.com/Bangkah/CRM-Automation-System" rel="noopener noreferrer"&gt;GitHub: CRM Automation System&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I built the workflow engine in Go around a simple principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The AI analyzes the data, but deterministic code executes the actions.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The LLM is responsible for classification, extraction, and drafting. It does not directly send emails, modify CRM records, or execute arbitrary actions. The deterministic Go backend handles validation, duplicate protection, idempotency, database updates, and the audit trail.&lt;/p&gt;
&lt;h3&gt;
  
  
  Human-in-the-Loop
&lt;/h3&gt;

&lt;p&gt;Consequential actions require human approval. Before an email is sent or a CRM record is updated, the workflow pauses and creates a pending approval for the operator to review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// internal/workflow/approval_service.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ActionService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ApproveAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;actionID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;approverID&lt;/span&gt; &lt;span class="kt"&gt;string&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="n"&gt;ActionRecord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actionID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionRecord&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;ActionStatePendingApproval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionRecord&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"invalid approval transition from %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&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="n"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
    &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ActionStateApproved&lt;/span&gt;
    &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApprovedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;approverID&lt;/span&gt;
    &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdatedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionRecord&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;audit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;createAuditEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InquiryID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"approval.granted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;DecisionAllow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"approval"&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="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionRecord&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;For this project, using open-source AI locally through Ollama wasn't just a technical choice; it was a business requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Privacy
&lt;/h3&gt;

&lt;p&gt;Business inquiries can contain sensitive information such as budgets and personal contact details. By keeping AI inference local, the system does not need to send that data to a third-party LLM provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Control
&lt;/h3&gt;

&lt;p&gt;Running an open-weight model locally avoids per-token API costs for every inquiry, including messages that eventually turn out to be spam or incomplete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Control and Predictability
&lt;/h3&gt;

&lt;p&gt;Running the model locally gives me more control over the model, prompting, and output format. The Go backend validates the structured JSON response before processing it.&lt;/p&gt;




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

&lt;p&gt;This project reinforced my interest in building AI systems where models are useful for analysis and drafting, while deterministic software and human approval remain responsible for consequential actions.&lt;/p&gt;

&lt;p&gt;Happy hacking! 🌟&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>My Hacktoberfest 2026 Journey: Contributing to DevOps Daily</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Thu, 01 Oct 2026 20:03:26 +0000</pubDate>
      <link>https://dev.to/bangkah/diving-into-hacktoberfest-2026-my-open-source-contribution-journey-on-devops-daily-6go</link>
      <guid>https://dev.to/bangkah/diving-into-hacktoberfest-2026-my-open-source-contribution-journey-on-devops-daily-6go</guid>
      <description>&lt;p&gt;Hacktoberfest is back, and this year I decided to join the 7-Day DevOps Contribution Challenge hosted by &lt;a class="mentioned-user" href="https://dev.to/devopsdaily"&gt;@devopsdaily&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;I’m still learning my way around open-source contribution workflows, so I’m using this challenge as an opportunity to practice working with real repositories, following existing project conventions, and getting more comfortable with the Git workflow.&lt;/p&gt;

&lt;p&gt;This article documents my progress so far, including how I tested my changes locally and the pull requests I submitted along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Day 1: Registering as an Expert on DevOps Daily
&lt;/h2&gt;

&lt;p&gt;On Day 1, the challenge focused on adding a personal expert profile to the platform's repository. Here’s what I did:&lt;/p&gt;

&lt;h5&gt;
  
  
  1. &lt;strong&gt;Creating the JSON Profile&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I created a data file at &lt;code&gt;content/experts/muhammad-dhiyaul-atha.json&lt;/code&gt; containing my professional background, social links, and core specialties in backend development, containerization, and automated deployment infrastructure.&lt;/p&gt;

&lt;h5&gt;
  
  
  2. &lt;strong&gt;Preparing the Avatar&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I uploaded my avatar image in &lt;code&gt;.png&lt;/code&gt; format to &lt;code&gt;public/images/experts/muhammad-dhiyaul-atha.png&lt;/code&gt; to ensure the profile renders correctly.&lt;/p&gt;

&lt;h5&gt;
  
  
  3. &lt;strong&gt;Local Testing&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I ran the local development server using &lt;code&gt;pnpm dev&lt;/code&gt; to verify that the system successfully read the files.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1urirt50pugsa32g6xhp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1urirt50pugsa32g6xhp.png" alt="Local development testing" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  4. &lt;strong&gt;Opening the Pull Request (PR)&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;After confirming everything was clean, I created a dedicated branch, committed, and pushed the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hacktoberfest/add-muhammad-dhiyaul-atha&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content/experts/muhammad-dhiyaul-atha.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;public/images/experts/muhammad-dhiyaul-atha.png&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add Muhammad Dhiyaul Atha to experts directory"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hacktoberfest/add-muhammad-dhiyaul-atha&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then opened &lt;strong&gt;PR #1790&lt;/strong&gt; on &lt;a href="https://github.com/The-DevOps-Daily/devops-daily/pull/1790" rel="noopener noreferrer"&gt;@thedevopsdaily&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fobj761f698jo66jq8jap.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fobj761f698jo66jq8jap.png" alt="Pull Request #1790" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Day 2: Contributing a Favorite DevOps Tool (Lazydocker)
&lt;/h2&gt;

&lt;p&gt;For Day 2, the challenge was to modify a TypeScript component and add a new tool to the curated &lt;strong&gt;Toolbox&lt;/strong&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  1. &lt;strong&gt;Choosing the Tool&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I chose Lazydocker, a Go-based terminal user interface (TUI) that makes managing Docker containers and Docker Compose easier from the command line.&lt;/p&gt;

&lt;h5&gt;
  
  
  2. &lt;strong&gt;Modifying &lt;code&gt;app/toolbox/page.tsx&lt;/code&gt;&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I added a new configuration object to the &lt;code&gt;tools&lt;/code&gt; array under the &lt;code&gt;containers&lt;/code&gt; category:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lazydocker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A simple terminal UI for both docker and docker-compose, written in Go.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://github.com/jesseduffield/lazydocker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;containers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Terminal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;badges&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="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Docker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Open Source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secondary&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  3. &lt;strong&gt;Submitting the Second PR&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;Following the same Git workflow, I created a new branch, pushed the changes, and opened &lt;strong&gt;PR #1807&lt;/strong&gt; on &lt;a href="https://github.com/The-DevOps-Daily/devops-daily/pull/1807" rel="noopener noreferrer"&gt;@thedevopsdaily&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6m48eqz0npxv05mo8v89.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6m48eqz0npxv05mo8v89.png" alt="Pull Request #1807" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Day 3: Adding a Quiz Question to the Linux Quiz
&lt;/h2&gt;

&lt;p&gt;On Day 3, the task was to contribute a multiple-choice question to an existing quiz file on the platform. Here’s what I did:&lt;/p&gt;

&lt;h5&gt;
  
  
  1. &lt;strong&gt;Selecting the Quiz File&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I chose &lt;code&gt;content/quizzes/linux-quiz.json&lt;/code&gt; and drafted an intermediate-level question focused on modern network troubleshooting in Linux using the &lt;code&gt;ss&lt;/code&gt; command.&lt;/p&gt;

&lt;h5&gt;
  
  
  2. &lt;strong&gt;Drafting the Question &amp;amp; Updating Metadata&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I added a new question object to the &lt;code&gt;questions&lt;/code&gt; array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux-check-listening-ports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Checking Listening Ports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Which command-line tool is commonly used on modern Linux distributions to inspect socket statistics and display active listening ports along with their corresponding process names?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"netstat -an"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"ss -tulpn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"lsof -port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"ps aux --ports"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"correctAnswer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"explanation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ss (socket statistics) is the modern, faster replacement for netstat. The flags -tulpn display TCP/UDP connections, listening sockets, and the numeric PIDs/program names holding them."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"difficulty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"intermediate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;I also updated the &lt;code&gt;totalPoints&lt;/code&gt; (increased to &lt;code&gt;202&lt;/code&gt;) and incremented the &lt;code&gt;intermediate&lt;/code&gt; difficulty level count in the file's metadata.&lt;/p&gt;

&lt;h5&gt;
  
  
  3. &lt;strong&gt;Local Testing &amp;amp; Validation&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;Before committing, I ran the repository's test and validation commands to verify the quiz schema and metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;test &lt;/span&gt;tests/quiz-validation.test.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft96hk6jjkhyvfkb4nqcs.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft96hk6jjkhyvfkb4nqcs.png" alt="Quiz validation test result" width="630" height="195"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm quiz:validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fanvig2qg0b613ulz0gft.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fanvig2qg0b613ulz0gft.png" alt="Quiz validation" width="755" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  4. &lt;strong&gt;Opening the Pull Request (PR)&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;After confirming everything passed successfully, I created a dedicated branch, committed, and pushed the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; hacktoberfest/add-quiz-question
git add content/quizzes/linux-quiz.json
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add quiz question on checking listening ports to linux-quiz"&lt;/span&gt;
git push origin hacktoberfest/add-quiz-question

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

&lt;/div&gt;



&lt;p&gt;I then opened the Pull Request on &lt;a href="https://github.com/The-DevOps-Daily/devops-daily/pull/1814" rel="noopener noreferrer"&gt;@thedevopsdaily&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx074x8wh8t01dyzob8g9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx074x8wh8t01dyzob8g9.png" alt=" " width="800" height="797"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Day 4: Adding Flashcards to Linux Fundamentals
&lt;/h2&gt;

&lt;p&gt;On Day 4, the objective was to contribute one or two study flashcards to an existing set to help others learn DevOps and system administration concepts. Here’s what I did:&lt;/p&gt;

&lt;h5&gt;
  
  
  1. &lt;strong&gt;Selecting the Flashcard Set&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I chose &lt;code&gt;content/flashcards/linux-fundamentals.json&lt;/code&gt; since it covers essential Linux topics and fits well with my background in Linux system administration.&lt;/p&gt;

&lt;h5&gt;
  
  
  2. &lt;strong&gt;Drafting and Adding the Flashcards&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I added two new flashcard objects to the end of the cards array, covering network troubleshooting with ss and systemd logs with journalctl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux-check-listening-ports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"front"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"How do you inspect active listening ports and their processes on modern Linux?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"back"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Use the ss command with flags -tulpn (ss -tulpn). It is the modern, faster replacement for netstat, displaying TCP/UDP connections, listening sockets, and the numeric PIDs/program names holding them."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Networking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"networking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"troubleshooting"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"journalctl-systemd-logs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"front"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"How do you query and follow systemd logs using journalctl?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"back"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"journalctl -u service-name views logs for a specific service. Add -f to follow live output (like tail -f), -n 50 for the last 50 lines, and --since today for recent logs. It reads binary journal logs maintained by systemd."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Monitoring"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"journalctl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"systemd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"logs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"troubleshooting"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;I also updated the &lt;code&gt;cardCount&lt;/code&gt; metadata at the top of the file, increasing it from &lt;code&gt;20&lt;/code&gt; to &lt;code&gt;22&lt;/code&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  3. &lt;strong&gt;Local Testing &amp;amp; Preview&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;I started the local development server to test the flashcards page and ensure the new cards rendered correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4m5avzi0u4abr2z2fd7e.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4m5avzi0u4abr2z2fd7e.png" alt=" " width="800" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  4. &lt;strong&gt;Opening the Pull Request (PR)&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;After confirming everything passed successfully, I created a dedicated branch, committed, and pushed the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; hacktoberfest/add-flashcard
git add content/flashcards/linux-fundamentals.json
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add flashcard for checking listening ports using ss to linux-fundamentals"&lt;/span&gt;
git push origin hacktoberfest/add-flashcard

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

&lt;/div&gt;



&lt;p&gt;I then opened the Pull Request on &lt;a href="https://github.com/The-DevOps-Daily/devops-daily/pull/1828" rel="noopener noreferrer"&gt;@thedevopsdaily&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4k13vpcjwwv0zbuv0f1a.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4k13vpcjwwv0zbuv0f1a.png" alt=" " width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Day 5: Sharing a Practical DevOps Gotcha on CI/CD Security
&lt;/h2&gt;

&lt;p&gt;On Day 5, the objective shifted from adding static data (like quizzes or flashcards) to sharing a practical tip, "gotcha," or lesson learned the hard way by improving an existing guide on the platform.&lt;/p&gt;

&lt;h5&gt;
  
  
  1. Choosing the Right Article to Improve
&lt;/h5&gt;

&lt;p&gt;I browsed through &lt;code&gt;content/guides/introduction-to-cicd/&lt;/code&gt; and selected &lt;code&gt;09-security-best-practices-and-production-patterns.md&lt;/code&gt;. Having dealt firsthand with workflow permission issues while setting up automated tasks and repository stats updaters, I knew exactly what kind of pitfall often catches developers off guard.&lt;/p&gt;

&lt;h5&gt;
  
  
  2. Drafting the Gotcha Section
&lt;/h5&gt;

&lt;p&gt;Inside the section covering &lt;em&gt;Least Privilege Access&lt;/em&gt;, I inserted a concise and technically accurate warning regarding GitHub Actions default token permissions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; If your GitHub Actions workflow fails with a "403 Forbidden" error when trying to push commits or update repository files, check the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; permissions. Repositories can use read-only defaults for the token. If the workflow needs to write repository contents, explicitly grant the required permission, such as &lt;code&gt;contents: write&lt;/code&gt;, in the workflow.&lt;/p&gt;
&lt;/blockquote&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxkegtd0kxqovudrsk4ts.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxkegtd0kxqovudrsk4ts.png" alt=" " width="631" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  3. Verifying and Isolating the Branch
&lt;/h5&gt;

&lt;p&gt;To keep my contribution clean and prevent stray build artifacts or previous commits from leaking into this pull request, I made sure to check out a dedicated branch from a clean state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout main
git pull origin main
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; hacktoberfest/add-tip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  4. Committing and Opening the Pull Request
&lt;/h5&gt;

&lt;p&gt;After verifying the changes locally, I staged only the modified guide file, committed with a clear message, and pushed to my fork:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add content/guides/introduction-to-cicd/09-security-best-practices-and-production-patterns.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"docs: add github token permission gotcha"&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin hacktoberfest/add-tip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then opened the Pull Request on &lt;a href="https://github.com/The-DevOps-Daily/devops-daily/pull/1835" rel="noopener noreferrer"&gt;@thedevopsdaily&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqnjk5gapc71zo0gm1zik.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqnjk5gapc71zo0gm1zik.png" alt=" " width="799" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;So far, this challenge has been a useful way to practice the basics of contributing to an existing open-source project.&lt;/p&gt;

&lt;p&gt;The work itself has been relatively small, but there have still been things to learn: understanding an unfamiliar codebase, following its existing structure, testing changes locally, creating focused branches, and preparing pull requests.&lt;/p&gt;

&lt;p&gt;I’m still in the middle of the challenge, so there is a lot more to learn and contribute. I’ll continue updating this article as I work through the remaining days of the challenge.&lt;/p&gt;

&lt;p&gt;If you're also participating in Hacktoberfest this year, I'd be interested to hear what you've been working on.&lt;/p&gt;

&lt;p&gt;Happy hacking! 🌟&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>devops</category>
      <category>opensource</category>
      <category>devopsdaily</category>
    </item>
    <item>
      <title>My Experience Deploying Projects Smoothly with Tencent EdgeOne Makers</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Sun, 13 Sep 2026 11:23:07 +0000</pubDate>
      <link>https://dev.to/bangkah/my-experience-deploying-projects-smoothly-with-tencent-edgeone-makers-epm</link>
      <guid>https://dev.to/bangkah/my-experience-deploying-projects-smoothly-with-tencent-edgeone-makers-epm</guid>
      <description>&lt;p&gt;Exploring modern cloud infrastructure, edge computing, and streamlined deployment platforms has always been an exciting journey for developers. Recently, as part of the DevHandal 2026 Batch 2 program, I had the opportunity to dive deep into Tencent EdgeOne Makers, and it has genuinely changed how I approach shipping web projects efficiently.&lt;/p&gt;

&lt;p&gt;In this article, I want to share my honest review, technical impressions, and why this platform is becoming an exceptional choice for developers looking for fast, secure, and hassle-free website deployment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Tencent EdgeOne Makers Stands Out
&lt;/h4&gt;

&lt;p&gt;When working on modern web applications, setting up Content Delivery Networks (CDNs), security layers like Web Application Firewalls (WAF), DDoS mitigation, and global edge routing usually takes a considerable amount of configuration overhead. Traditional setups often require juggling multiple services across different cloud providers.&lt;/p&gt;

&lt;p&gt;EdgeOne Makers simplifies all of that by consolidating these essential features into a unified, developer-friendly ecosystem. Some of the standout features that caught my attention during my exploration include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global Edge Performance:&lt;/strong&gt; Static and dynamic assets are cached and served closer to users worldwide. This architectural approach results in significantly lower latency, faster Time to First Byte (TTFB), and an overall instant page load experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in Security Protection:&lt;/strong&gt; Robust security measures come right out of the box. It keeps applications safe from common web vulnerabilities, malicious traffic, and DDoS attacks without requiring complex external plugins or expensive enterprise firewalls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streamlined Developer Workflow:&lt;/strong&gt; The integration pipeline allows developers to spin up, configure, and deploy applications rapidly, making it ideal for both rapid prototyping and production-grade applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Getting Started and Deployment Experience
&lt;/h4&gt;

&lt;p&gt;Deploying a project on the platform is remarkably straightforward. Whether you choose to manage your configurations via their intuitive command-line interface (CLI) or through the web dashboard, the process is streamlined to let developers focus purely on writing application code rather than wrestling with server maintenance.&lt;/p&gt;

&lt;p&gt;When I tested deploying my project, the workflow felt natural. Here is a quick breakdown of the steps and best practices I followed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clean Project Initialization:&lt;/strong&gt; Structuring project files logically so that the edge runtime can route static assets and serverless functions seamlessly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth CLI Commands:&lt;/strong&gt; Leveraging fast local testing tools and straightforward deployment commands that push code straight to the edge network in a matter of seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generous Free Tier and Quotas:&lt;/strong&gt; Having access to a robust free tier makes it stress-free to experiment, build, and showcase projects without worrying about unexpected upfront server costs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My overall experience using Tencent EdgeOne Makers has been fantastic. It successfully removes the friction and complexity of traditional server management, enabling developers to ship secure, high-performance applications with absolute ease. If you haven't tried it yet, I highly recommend exploring EdgeOne Makers for your next web project. It is a powerful tool that elevates both developer productivity and end-user performance. Furthermore, the supportive ecosystem and continuous updates make it a reliable choice for long-term scalability, ensuring that hobbyists and professional developers alike can build resilient web solutions with minimal hassle. Additionally, participating in this developer program has opened up new insights into modern edge architectures.&lt;/p&gt;

</description>
      <category>tencentedgeone</category>
      <category>edgeonemakers</category>
      <category>codepolitan</category>
      <category>edgeone</category>
    </item>
    <item>
      <title>Three Bugs That Almost Killed My Honeypot Before It Ever Caught Anyone</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Thu, 10 Sep 2026 09:30:55 +0000</pubDate>
      <link>https://dev.to/bangkah/three-bugs-that-almost-killed-my-honeypot-before-it-ever-caught-anyone-5a2g</link>
      <guid>https://dev.to/bangkah/three-bugs-that-almost-killed-my-honeypot-before-it-ever-caught-anyone-5a2g</guid>
      <description>&lt;p&gt;I built Bangk-Shield, an edge-native honeypot that runs on Cloudflare Workers. The idea is simple: sit in front of a site, watch for SQLi/RCE/SSRF/LFI/XSS/recon probes, and instead of quietly blocking them, answer with a convincing fake payload and a locally-flavored roast — while logging the real attacker's fingerprint.&lt;/p&gt;

&lt;p&gt;The pitch is fun. The bug hunt getting it to actually work was not. Here are three failures that taught me more about edge runtimes and silent regressions than any tutorial would have.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug #1: The Worker That Attacked Itself
&lt;/h2&gt;

&lt;p&gt;First local test run. I hit two endpoints with &lt;code&gt;curl&lt;/code&gt; and watched the &lt;code&gt;wrangler dev&lt;/code&gt; log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[wrangler:info] GET /etc/passwd 500 Internal Server Error (13820ms)
[wrangler:info] GET /.env 500 Internal Server Error (13833ms)
[wrangler:info] GET /etc/passwd 500 Internal Server Error (13831ms)
[wrangler:info] GET /.env 500 Internal Server Error (13844ms)
...hundreds more lines...
[wrangler:info] GET /etc/passwd 500 Internal Server Error (20822ms)
⎔ Shutting down local server...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two &lt;code&gt;curl&lt;/code&gt; calls. Hundreds of log lines. Latency climbing from 13.8 seconds to 20.8 seconds before the dev server gave up and died. That climb is the tell — nothing about a single HTTP request naturally gets 7 seconds slower over its own lifetime. Something was calling itself.&lt;/p&gt;

&lt;p&gt;The culprit was in my "passthrough" logic — the code path that forwards legitimate traffic to the real origin server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The bug&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;passthrough&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ASSETS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ASSETS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- this&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In local dev, with no real origin configured, &lt;code&gt;fetch(request)&lt;/code&gt; re-fetches the &lt;em&gt;exact same URL&lt;/em&gt; — which, in &lt;code&gt;wrangler dev&lt;/code&gt;, is the Worker's own address. The Worker calls itself. That call also fails to resolve a real backend, hits the same fail-open catch block, and calls &lt;code&gt;fetch(request)&lt;/code&gt; again. Recursion, with no base case, burning CPU and stacking subrequests until the runtime hit a limit and returned 500.&lt;/p&gt;

&lt;p&gt;The scary part isn't that it happened in dev. It's that this same failure mode is a documented Cloudflare Workers gotcha in production too, if a Worker's outbound fetch happens to match one of its own routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt; had three parts, because I wanted defense in depth, not just a patch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;passthrough&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Loop guard — if we already forwarded this request once, stop.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-bangk-shield-forwarded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bangk-Shield: forwarding loop detected.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;508&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ASSETS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ASSETS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Explicit origin — never re-fetch the same host as this Worker.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ORIGIN_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ORIGIN_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forwardUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;forwardUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;forwardUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;forwardUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forwarded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forwardUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;forwarded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-bangk-shield-forwarded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forwarded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. No origin configured — say so, instead of guessing and looping.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bangk-Shield is active, but no ORIGIN_URL is configured for legitimate traffic.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lesson: a "safe default" that just re-fetches the incoming request isn't safe. It's an assumption that something &lt;em&gt;else&lt;/em&gt; out there is going to intercept and redirect the call before it loops. In a reverse-proxy pattern, that assumption needs to be an explicit contract (an origin URL, a binding), not implicit behavior you hope the runtime handles for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug #2: The Detector That Never Detected Path Attacks
&lt;/h2&gt;

&lt;p&gt;This one didn't crash anything. It just silently did nothing, which is worse.&lt;/p&gt;

&lt;p&gt;After a rewrite that moved detection rules into a scored, combo-bonus rule engine, I built the request context like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;safeDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks reasonable. Except: &lt;strong&gt;there's no &lt;code&gt;path&lt;/code&gt; field.&lt;/strong&gt; And the rule engine matches signals against &lt;code&gt;context[target]&lt;/code&gt;, where &lt;code&gt;target&lt;/code&gt; comes from each signal's &lt;code&gt;where&lt;/code&gt; array in the rules config — things like &lt;code&gt;"where": ["path", "query", "body"]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Every rule that targeted &lt;code&gt;path&lt;/code&gt; — &lt;code&gt;/etc/passwd&lt;/code&gt;, &lt;code&gt;/.env&lt;/code&gt;, &lt;code&gt;/wp-admin&lt;/code&gt;, all the classic recon and LFI probes that live in the URL path rather than the query string — evaluated &lt;code&gt;context['path']&lt;/code&gt;, got &lt;code&gt;undefined&lt;/code&gt;, and silently failed to match. No error. No warning. The honeypot just let path-based attacks walk straight through to the real origin, forever, while confidently reporting "no threats detected."&lt;/p&gt;

&lt;p&gt;This is the kind of bug that unit tests catch and manual clicking doesn't, because manually poking &lt;code&gt;/?id=1' OR 1=1&lt;/code&gt; in the query string "worked," so the feature "looked done."&lt;/p&gt;

&lt;p&gt;The fix was one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;safeDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;-- this was missing&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;safeDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the fix isn't really the interesting part — the &lt;em&gt;verification&lt;/em&gt; is. I wrote a small standalone test harness that fed known attack shapes straight into the scoring function and asserted whether they crossed the trigger threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;testCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/etc/passwd in path (must trigger alone)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/etc/passwd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;curl/8.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;testCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/.env alone (score too low, must pass through)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/.env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;curl/8.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running that harness against the &lt;em&gt;old&lt;/em&gt; context shape would have failed instantly and obviously — instead of failing silently in production traffic six months later. Lesson: for a security tool, "it compiles and the demo works" is not a test suite. If a detector can fail closed (i.e., fail into "everything looks safe"), you need an explicit assertion that proves the opposite case still fires.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug #3: The Watermark That Lied
&lt;/h2&gt;

&lt;p&gt;Every honeypot response includes a cryptographic watermark — a hash derived from the visitor's IP, User-Agent, date, and a rotating salt. The point is defensive: if someone screenshots a fake "leaked database" response and claims it's a real breach, the site owner can reproduce the hash and prove it's a decoy.&lt;/p&gt;

&lt;p&gt;The spec (written into the project's PRD) was explicit about the exact format, because a mismatch means the hash can never be reproduced later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Canonical: bangk-shield/v1|&amp;lt;ip&amp;gt;|&amp;lt;ua&amp;gt;|&amp;lt;yyyy-mm-dd&amp;gt;|&amp;lt;salt&amp;gt;
Output:    bs1-&amp;lt;first 16 hex chars of SHA-256(canonical)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shipped code did this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`bangk-shield|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dateStr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// missing /v1&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`bs-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hashHex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                              &lt;span class="c1"&gt;// missing the 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small diff. Big consequence: any CI test vector built against the spec format would fail against this code forever, and worse, nobody would notice until someone tried to reproduce a watermark from a real incident and got a hash that simply didn't match anything.&lt;/p&gt;

&lt;p&gt;The second half of this bug was sneakier. The spec said the salt should live in Workers KV as a &lt;strong&gt;rotatable object&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wm:salt:current&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"salt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a1b2c3..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"since"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-08T10:00:00Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the code read the KV value and used it &lt;em&gt;directly&lt;/em&gt; as the salt string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WATERMARK_SALT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default-dev-salt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// never even reads KV&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two problems stacked on top of each other: the salt wasn't actually coming from KV at all (so "rotate without redeploy" didn't work), and if I'd naively "fixed" that by just doing &lt;code&gt;await env.BANGK_KV.get('wm:salt:current')&lt;/code&gt;, the salt would have become the literal string &lt;code&gt;'{"salt":"a1b2c3...","since":"..."}'&lt;/code&gt; — every watermark computed with the &lt;em&gt;entire JSON blob&lt;/em&gt; as the salt, silently, with no error thrown anywhere.&lt;/p&gt;

&lt;p&gt;The fix parses defensively and stays backward-compatible with plain strings during migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractSaltValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// valid JSON, wrong shape — treat as a plain salt&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// not JSON at all — legacy plain-string salt&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lesson: any time two independent things (a spec doc and an implementation, or a producer and a consumer of the same data) both need to agree on a format, that agreement should be enforced somewhere machine-checkable — a schema, a fixed test vector, a shared constant — not just "well-documented." Documentation drifts. Code that has to pass a byte-for-byte comparison doesn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Changed My Process
&lt;/h2&gt;

&lt;p&gt;None of these three bugs were exotic. They were all boring, one-line-diff mistakes. What made them dangerous was that each one &lt;strong&gt;failed silently&lt;/strong&gt; — the Worker kept returning 200s, the demo kept looking fine, and the failure mode only showed up under conditions I hadn't manually tested (a request with no real origin behind it, an attack in the path instead of the query, a salt fetched from the "real" storage instead of the dev fallback).&lt;/p&gt;

&lt;p&gt;Three things I do differently now, on this project and since:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fail-closed the build, fail-open the runtime.&lt;/strong&gt; A honeypot must never take down the site it's protecting — so the request path stays fail-open (any internal error just forwards traffic). But the &lt;em&gt;build pipeline&lt;/em&gt; that compiles detection rules now fails hard on anything suspicious: invalid regex, patterns that can never match because of a later &lt;code&gt;.toLowerCase()&lt;/code&gt;, ReDoS-prone patterns. Silent failure in the runtime is safe; silent failure in the config is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the shape of the data, not just the happy path.&lt;/strong&gt; The path-detection bug and the salt-shape bug were both "the code runs, produces a 200, and does the wrong thing." A quick harness that asserts on expected trigger/no-trigger outcomes for known attack payloads catches this class of bug in seconds instead of after deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat spec-vs-code drift as a bug class of its own.&lt;/strong&gt; Once I had a real PRD with byte-exact format requirements, I started explicitly diffing "what the spec says" against "what the code does" as a review step — not just reading the code in isolation and asking "does this look reasonable?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is still in beta. But it's a beta that now has a test harness, a build-time linter that can fail on purpose, and a much shorter list of ways it can lie to me about whether it's actually working.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>debugging</category>
      <category>security</category>
    </item>
    <item>
      <title>Membangun Infrastruktur Enterprise: Part 2 - Sinkronisasi Waktu Server dengan NTP (Chrony)</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Thu, 11 Jun 2026 13:26:59 +0000</pubDate>
      <link>https://dev.to/bangkah/membangun-infrastruktur-enterprise-part-2-sinkronisasi-waktu-server-dengan-ntp-chrony-29no</link>
      <guid>https://dev.to/bangkah/membangun-infrastruktur-enterprise-part-2-sinkronisasi-waktu-server-dengan-ntp-chrony-29no</guid>
      <description>&lt;p&gt;Setelah berhasil melakukan konfigurasi dasar pada &lt;em&gt;Main Server&lt;/em&gt; di Part 1, langkah krusial selanjutnya sebelum mengonfigurasi Active Directory (ADDS) adalah memastikan waktu pada sistem server benar-benar akurat. Artikel ini akan membahas langkah demi langkah instalasi dan konfigurasi &lt;strong&gt;NTP (Network Time Protocol) menggunakan Chrony&lt;/strong&gt; di &lt;strong&gt;Ubuntu Server 26.04&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Teori Dasar: Mengapa NTP Sangat Krusial Sebelum ADDS?
&lt;/h3&gt;

&lt;p&gt;Banyak administrator jaringan pemula melewatkan konfigurasi NTP dan langsung melompat ke instalasi Active Directory. Hal ini sering kali berujung pada kegagalan sistem yang membingungkan.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protokol Kerberos:&lt;/strong&gt; Active Directory Domain Services (ADDS) menggunakan protokol &lt;strong&gt;Kerberos&lt;/strong&gt; untuk proses autentikasi keamanan. Kerberos menggunakan stempel waktu (&lt;em&gt;timestamp&lt;/em&gt;) untuk mencegah serangan siber jenis &lt;em&gt;replay attacks&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Toleransi Batas Waktu (Time Skew):&lt;/strong&gt; Secara bawaan, Kerberos membatasi perbedaan waktu maksimal antara Domain Controller, server, dan komputer &lt;em&gt;client&lt;/em&gt; sebesar &lt;strong&gt;5 menit&lt;/strong&gt;. Jika perbedaan waktu melebihi ambang batas ini, proses login user, replikasi &lt;em&gt;database&lt;/em&gt; domain, hingga proses &lt;em&gt;Join Domain&lt;/em&gt; pada komputer klien akan &lt;strong&gt;pasti ditolak dan gagal&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oleh karena itu, server harus bertindak sebagai sumber waktu yang andal (&lt;em&gt;NTP Server&lt;/em&gt;) bagi seluruh perangkat di dalam jaringan &lt;code&gt;a3n4.com&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Sintaks &amp;amp; Langkah Konfigurasi
&lt;/h3&gt;

&lt;p&gt;Pastikan Anda sudah berada di terminal Ubuntu Server 26.04 dengan hak akses &lt;em&gt;root&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;su

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Langkah A: Menonaktifkan NTP Bawaan Ubuntu
&lt;/h4&gt;

&lt;p&gt;Secara bawaan, Ubuntu menggunakan layanan &lt;code&gt;systemd-timesyncd&lt;/code&gt; untuk sinkronisasi waktu internal. Karena kita ingin server ini bertindak sebagai &lt;em&gt;NTP Server&lt;/em&gt; mandiri (melayani &lt;em&gt;request&lt;/em&gt; dari jaringan lokal), kita harus mematikan layanan bawaan tersebut.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timedatectl set-ntp off

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Langkah B: Instalasi Chrony
&lt;/h4&gt;

&lt;p&gt;Chrony adalah aplikasi NTP modern yang sangat direkomendasikan untuk Linux karena proses sinkronisasinya yang lebih cepat dan presisi dibanding &lt;em&gt;daemon&lt;/em&gt; NTP lama.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install &lt;/span&gt;chrony &lt;span class="nt"&gt;-y&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Langkah C: Konfigurasi Chrony Server
&lt;/h4&gt;

&lt;p&gt;Buka dan edit file konfigurasi utama Chrony menggunakan &lt;code&gt;nano&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /etc/chrony/chrony.conf

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

&lt;/div&gt;



&lt;p&gt;Tambahkan baris berikut di bagian bawah file untuk mengizinkan jaringan lokal &lt;code&gt;192.168.190.0/24&lt;/code&gt; menyalin waktu dari server ini, serta memaksa server tetap memberikan waktu meskipun koneksi internet terputus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allow 192.168.190.0/24
local stratum 10

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

&lt;/div&gt;



&lt;p&gt;Simpan file (&lt;code&gt;Ctrl + O&lt;/code&gt;, lalu &lt;code&gt;Enter&lt;/code&gt;) dan keluar (&lt;code&gt;Ctrl + X&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Langkah D: Restart &amp;amp; Aktivasi Layanan
&lt;/h4&gt;

&lt;p&gt;Terapkan konfigurasi baru dengan merestart layanan Chrony dan pastikan statusnya berjalan otomatis saat &lt;em&gt;booting&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;chrony
systemctl restart chrony
systemctl status chrony

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

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. Pengujian &amp;amp; Verifikasi
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Langkah A: Memeriksa Port NTP (123)
&lt;/h4&gt;

&lt;p&gt;NTP berjalan di atas protokol &lt;strong&gt;UDP port 123&lt;/strong&gt;. Pastikan socket port tersebut sudah terbuka dan didengarkan (&lt;em&gt;listen&lt;/em&gt;) oleh Chrony.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-unlp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;123

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Langkah B: Verifikasi Sumber Waktu &amp;amp; Pelacakan
&lt;/h4&gt;

&lt;p&gt;Pastikan Chrony sukses terhubung ke server waktu publik di internet untuk menyinkronkan jam internalnya.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chronyc sources
chronyc tracking

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

&lt;/div&gt;






&lt;h3&gt;
  
  
  Kesimpulan
&lt;/h3&gt;

&lt;p&gt;Dengan menyelesaikan konfigurasi NTP menggunakan Chrony, kita telah memastikan bahwa jam internal pada Ubuntu Server 26.04 berjalan dengan akurasi tinggi dan siap melayani permintaan sinkronisasi dari komputer &lt;em&gt;client&lt;/em&gt;. Sinkronisasi waktu ini meminimalkan risiko kegagalan otentikasi di masa mendatang.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apa Selanjutnya?
&lt;/h3&gt;

&lt;p&gt;Waktu server sudah akurat dan siap. Di artikel berikutnya (Part 3), kita akan masuk ke tahap paling krusial dari seluruh proyek ini: &lt;strong&gt;Membangun Active Directory Domain Services (ADDS)&lt;/strong&gt; menggunakan Samba untuk menginisiasi domain utama kita, &lt;strong&gt;&lt;code&gt;a3n4.com&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.google.com/search?q=%23" rel="noopener noreferrer"&gt;Baca Part 3: Membangun Active Directory Domain Controller (ADDS) dengan Samba ➡️&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>linux</category>
      <category>networking</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Membangun Infrastruktur Enterprise: Part 1 - Konfigurasi Main Server (Ubuntu Server 26.04)</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Thu, 11 Jun 2026 13:24:34 +0000</pubDate>
      <link>https://dev.to/bangkah/membangun-infrastruktur-enterprise-part-1-konfigurasi-main-server-ubuntu-server-2604-32c2</link>
      <guid>https://dev.to/bangkah/membangun-infrastruktur-enterprise-part-1-konfigurasi-main-server-ubuntu-server-2604-32c2</guid>
      <description>&lt;p&gt;Sebelum melangkah ke layanan kompleks seperti Active Directory atau VPN, kita wajib membangun pondasinya terlebih dahulu. Artikel ini akan membahas langkah demi langkah instalasi dasar, konfigurasi IP Statis, penyelarasan &lt;em&gt;hostname&lt;/em&gt;, dan &lt;em&gt;update&lt;/em&gt; sistem pada &lt;strong&gt;Ubuntu Server 26.04&lt;/strong&gt; di lingkungan &lt;strong&gt;VirtualBox&lt;/strong&gt; dengan target nama domain utama: &lt;strong&gt;&lt;code&gt;a3n4.com&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Teori Dasar: Mengapa "Main Server" Harus Benar?
&lt;/h3&gt;

&lt;p&gt;Sebuah &lt;em&gt;Main Server&lt;/em&gt; yang akan mengemban tugas sebagai Domain Controller (ADDS), DNS, dan DHCP tidak boleh memiliki konfigurasi yang berubah-ubah.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IP Statis (Static IP):&lt;/strong&gt; Jika server menggunakan DHCP (IP dinamis), ketika server &lt;em&gt;reboot&lt;/em&gt; dan IP-nya berubah, seluruh komputer &lt;em&gt;client&lt;/em&gt; di jaringan akan kehilangan koneksi ke DNS dan Domain Controller.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fully Qualified Domain Name (FQDN):&lt;/strong&gt; Identitas server harus jelas (contoh: &lt;code&gt;server.a3n4.com&lt;/code&gt;). Tanpa FQDN yang valid, layanan berbasis Kerberos seperti ADDS akan mengalami &lt;em&gt;error&lt;/em&gt; resolusi nama.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topologi VirtualBox:&lt;/strong&gt; Demi kelancaran lab ini, pastikan Pengaturan Jaringan (&lt;em&gt;Network Settings&lt;/em&gt;) pada VM Ubuntu Server dan Ubuntu Client Anda di VirtualBox diatur ke &lt;strong&gt;Internal Network&lt;/strong&gt; (atau &lt;strong&gt;Bridged Adapter&lt;/strong&gt; jika lab Anda membutuhkan akses internet langsung dari router fisik).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Sintaks &amp;amp; Langkah Konfigurasi
&lt;/h3&gt;

&lt;p&gt;Pastikan Anda sudah masuk ke terminal Ubuntu Server 26.04 sebagai &lt;em&gt;root&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;su

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Langkah A: Pembaruan Repositori &amp;amp; Sistem
&lt;/h4&gt;

&lt;p&gt;Sebelum menyentuh konfigurasi, pastikan semua paket sistem berada di versi terbaru dan paling stabil.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Langkah B: Konfigurasi IP Statis (Netplan)
&lt;/h4&gt;

&lt;p&gt;Ubuntu 26.04 menggunakan &lt;strong&gt;Netplan&lt;/strong&gt; untuk manajemen jaringan. Cari nama file konfigurasi Anda di direktori &lt;code&gt;/etc/netplan/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /etc/netplan/

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Biasanya bernama &lt;code&gt;50-cloud-init.yaml&lt;/code&gt; atau &lt;code&gt;01-netcfg.yaml&lt;/code&gt;)&lt;/em&gt;. Edit file tersebut menggunakan teks editor &lt;code&gt;nano&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /etc/netplan/50-cloud-init.yaml

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

&lt;/div&gt;



&lt;p&gt;Ubah atau sesuaikan isinya menjadi seperti ini. Pada lingkungan VirtualBox, nama interface umumnya terdeteksi sebagai &lt;code&gt;enp0s3&lt;/code&gt;. (Penting: &lt;strong&gt;Gunakan indentasi 2 atau 4 spasi, jangan gunakan tombol TAB&lt;/strong&gt; karena YAML sangat sensitif terhadap spasi):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;renderer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networkd&lt;/span&gt;
  &lt;span class="na"&gt;ethernets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enp0s3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Sesuaikan dengan nama interface VirtualBox Anda (cek via 'ip a')&lt;/span&gt;
      &lt;span class="na"&gt;dhcp4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
      &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.190.50/24&lt;/span&gt;
      &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
          &lt;span class="na"&gt;via&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.190.2&lt;/span&gt; &lt;span class="c1"&gt;# Sesuaikan dengan IP Gateway internal VirtualBox Anda&lt;/span&gt;
      &lt;span class="na"&gt;nameservers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8.8.8.8&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;1.1.1.1&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Simpan file (&lt;code&gt;Ctrl + O&lt;/code&gt;, lalu &lt;code&gt;Enter&lt;/code&gt;) dan keluar (&lt;code&gt;Ctrl + X&lt;/code&gt;). Terapkan konfigurasi baru tersebut:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netplan apply

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Langkah C: Konfigurasi Hostname &amp;amp; FQDN
&lt;/h4&gt;

&lt;p&gt;Sekarang kita ubah nama server kita menjadi &lt;code&gt;server&lt;/code&gt; dan memetakannya ke domain &lt;code&gt;a3n4.com&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hostnamectl set-hostname server
nano /etc/hosts

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

&lt;/div&gt;



&lt;p&gt;Tambahkan atau ubah baris di dalam file &lt;code&gt;/etc/hosts&lt;/code&gt; sehingga menjadi seperti ini:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1   localhost
192.168.190.50  server.a3n4.com  server

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

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. Pengujian &amp;amp; Verifikasi
&lt;/h3&gt;

&lt;p&gt;Untuk memastikan konfigurasi dasar ini tidak bermasalah, lakukan pengujian berikut:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cek IP Address:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip a s enp0s3

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Uji Koneksi Jaringan:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 3 192.168.190.2

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Ganti dengan IP gateway Anda, atau gunakan google.com jika menggunakan mode Bridged)&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cek FQDN (Resolusi Hostname):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;

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

&lt;/div&gt;






&lt;h3&gt;
  
  
  Kesimpulan
&lt;/h3&gt;

&lt;p&gt;Dengan menyelesaikan konfigurasi &lt;em&gt;Main Server&lt;/em&gt; ini, kita telah berhasil mengamankan alamat IP statis &lt;code&gt;192.168.190.50&lt;/code&gt; dan memberikan identitas FQDN &lt;code&gt;server.a3n4.com&lt;/code&gt; di dalam lingkungan VirtualBox. Server kini dalam kondisi prima dan siap dipasangi layanan tingkat lanjut tanpa khawatir terjadi konflik jaringan atau perubahan IP dinamis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apa Selanjutnya?
&lt;/h3&gt;

&lt;p&gt;Sebuah server &lt;em&gt;enterprise&lt;/em&gt; tidak akan bisa berjalan jika waktunya melenceng, karena sistem keamanan domain (terutama protokol Kerberos pada ADDS) sangat bergantung pada sinkronisasi waktu. Di artikel berikutnya (Part 2), kita akan membahas bagaimana mengonfigurasi &lt;strong&gt;NTP (Network Time Protocol) menggunakan Chrony&lt;/strong&gt; serta melakukan pengujian langsung menggunakan &lt;strong&gt;Ubuntu Client&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/bangkah/membangun-infrastruktur-enterprise-part-2-sinkronisasi-waktu-server-dengan-ntp-chrony-29no"&gt;Baca Part 2: Sinkronisasi Waktu Server dengan NTP (Chrony) ➡️&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>linux</category>
      <category>networking</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Step-by-Step Deployment Active Directory (AD DS) pada Windows Server 2025 via PowerShel</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Fri, 15 May 2026 15:20:57 +0000</pubDate>
      <link>https://dev.to/bangkah/step-by-step-deployment-active-directory-ad-ds-pada-windows-server-2025-via-powershel-2ibb</link>
      <guid>https://dev.to/bangkah/step-by-step-deployment-active-directory-ad-ds-pada-windows-server-2025-via-powershel-2ibb</guid>
      <description>&lt;p&gt;Banyak orang masih menggunakan GUI untuk mengonfigurasi Windows Server. Padahal, menggunakan PowerShell jauh lebih cepat, ringan, dan minim &lt;em&gt;error&lt;/em&gt; navigasi. Di artikel ini, saya akan membagikan alur praktikum saya dalam membangun Forest baru &lt;code&gt;imsak.com&lt;/code&gt; di Windows Server 2025 sepenuhnya menggunakan terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment Praktikum
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Platform:&lt;/strong&gt; VirtualBox (Internal Network)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS:&lt;/strong&gt; Windows Server 2025&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target Domain:&lt;/strong&gt; &lt;code&gt;imsak.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static IP:&lt;/strong&gt; &lt;code&gt;172.20.80.10&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tahapan Konfigurasi
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Identitas Server (Hostname)
&lt;/h3&gt;

&lt;p&gt;Langkah pertama adalah mengubah nama komputer agar lebih mudah dikenali dalam jaringan forest. Di sini saya mengubahnya menjadi &lt;code&gt;A3N4&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Rename-Computer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NewName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A3N4"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Restart&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&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%2F2gc1o8gxb9slu2pddxfw.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%2F2gc1o8gxb9slu2pddxfw.png" alt=" " width="781" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Networking: Static IP &amp;amp; DNS Loopback
&lt;/h3&gt;

&lt;p&gt;Domain Controller wajib memiliki IP statis. Selain itu, DNS harus diarahkan ke &lt;code&gt;127.0.0.1&lt;/code&gt; (loopback) karena server ini sendiri yang akan mengelola record DNS domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Mengatur IP Statis&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-NetIPAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-InterfaceIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-IPAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;172.20.80.10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-PrefixLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DefaultGateway&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;172.20.80.1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Mengatur DNS ke Loopback&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-DnsClientServerAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-InterfaceIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ServerAddresses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&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%2Fn4wn20ipi6cghtqdw71u.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%2Fn4wn20ipi6cghtqdw71u.png" alt=" " width="781" height="261"&gt;&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%2Fb7d7jgj8idaws7h6ei28.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%2Fb7d7jgj8idaws7h6ei28.png" alt=" " width="781" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Instalasi Fitur AD DS
&lt;/h3&gt;

&lt;p&gt;Sebelum bisa mempromosikan server menjadi DC, kita perlu memasang binary dan modul manajemen Active Directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-WindowsFeature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AD-Domain-Services&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-IncludeManagementTools&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&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%2Fv3h9fwpcjlep8c9qycrz.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%2Fv3h9fwpcjlep8c9qycrz.png" alt=" " width="760" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Membangun Forest Baru (Promosi Domain)
&lt;/h3&gt;

&lt;p&gt;Ini adalah inti dari praktikum. Kita akan membuat Forest baru bernama &lt;code&gt;imsak.com&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-ADDSForest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DomainName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"imsak.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DomainNetbiosName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IMSAK"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-InstallDns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&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%2Fqp83a992a4c3syyymqp2.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%2Fqp83a992a4c3syyymqp2.png" alt=" " width="777" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Setelah perintah dijalankan, masukkan **SafeModeAdministratorPassword&lt;/em&gt;* yang kuat. Sistem akan otomatis melakukan restart setelah proses selesai.*&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifikasi &amp;amp; Pengujian
&lt;/h2&gt;

&lt;p&gt;Setelah server aktif kembali, kita perlu memastikan semuanya berjalan normal.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Cek Login Otoritas
&lt;/h3&gt;

&lt;p&gt;Pastikan user yang masuk adalah user domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;whoami&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Output: imsak\administrator&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Resolusi Nama DNS
&lt;/h3&gt;

&lt;p&gt;Tes apakah DNS sudah mengenali domain baru kita.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Resolve-DnsName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;imsak.com&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&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%2Fksyj5um5uhdudfi20h48.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%2Fksyj5um5uhdudfi20h48.png" alt=" " width="683" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Status Layanan (Health Check)
&lt;/h3&gt;

&lt;p&gt;Memastikan layanan utama AD DS (NTDS, DNS, ADWS) sudah berstatus &lt;code&gt;Running&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;adws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;dns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;ntds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&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%2Fl9bzusi0tsxsoy86jjno.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%2Fl9bzusi0tsxsoy86jjno.png" alt=" " width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Kesimpulan
&lt;/h2&gt;

&lt;p&gt;Deploy Active Directory di Windows Server 2025 via PowerShell memberikan pengalaman manajemen server yang lebih profesional. Dengan mengikuti alur yang terstruktur, kita bisa meminimalisir kesalahan konfigurasi dan mempercepat proses &lt;em&gt;deployment&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apakah kamu punya kendala saat mencoba Windows Server 2025? Yuk diskusi di kolom komentar!&lt;/strong&gt; &lt;/p&gt;

</description>
      <category>windowsserver2025</category>
      <category>powershell</category>
      <category>activedirectory</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Implementasi Kea DHCPv4 pada RHEL 10: Solusi Modern Pengelolaan Jaringan</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Thu, 30 Apr 2026 11:31:39 +0000</pubDate>
      <link>https://dev.to/bangkah/implementasi-kea-dhcpv4-pada-rhel-10-solusi-modern-pengelolaan-jaringan-23o7</link>
      <guid>https://dev.to/bangkah/implementasi-kea-dhcpv4-pada-rhel-10-solusi-modern-pengelolaan-jaringan-23o7</guid>
      <description>&lt;h3&gt;
  
  
  1. Pendahuluan
&lt;/h3&gt;

&lt;p&gt;Kea mulai diadopsi sebagai pengganti ISC DHCP dengan arsitektur yang lebih modern. Kea menawarkan performa lebih tinggi dan struktur konfigurasi berbasis JSON yang lebih modular. Artikel ini mendokumentasikan langkah-langkah konfigurasi Kea di lingkungan virtual (VirtualBox) dengan client Windows 10.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Topologi &amp;amp; Skenario Jaringan
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Server (RHEL 10)&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  Adapter 1: NAT (Akses Internet)&lt;/li&gt;
&lt;li&gt;  Adapter 2: Internal Network (&lt;code&gt;enp0s8&lt;/code&gt;) - IP Statis: &lt;code&gt;10.0.2.1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Client (Windows 10)&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  Adapter 1: Internal Network - DHCP Client&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Konfigurasi Interface Server
&lt;/h3&gt;

&lt;p&gt;Sebelum menjalankan Kea, interface yang mengarah ke client harus memiliki IP statis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Menambahkan IP statis ke interface internal&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip addr add 10.0.2.1/24 dev enp0s8
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip &lt;span class="nb"&gt;link set &lt;/span&gt;enp0s8 up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fp6r4joipaqmj42y9aewx.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%2Fp6r4joipaqmj42y9aewx.png" alt=" " width="799" height="341"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Konfigurasi Kea DHCPv4
&lt;/h3&gt;

&lt;p&gt;Edit file &lt;code&gt;/etc/kea/kea-dhcp4.conf&lt;/code&gt;. Fokus utama adalah mengarahkan &lt;code&gt;interfaces&lt;/code&gt; ke &lt;code&gt;enp0s8&lt;/code&gt; dan menentukan &lt;code&gt;pools&lt;/code&gt; alamat IP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"Dhcp4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"interfaces-config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"interfaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"enp0s8"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lease-database"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"memfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/lib/kea/kea-leases4.csv"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"subnet4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"subnet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"pools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10.0.2.100 - 10.0.2.200"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"option-data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"routers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10.0.2.1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"domain-name-servers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8.8.8.8, 8.8.4.4"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&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%2Fu7fke9pub51ygaiemt8m.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%2Fu7fke9pub51ygaiemt8m.png" alt=" " width="399" height="397"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5. Keamanan &amp;amp; Aktivasi Service
&lt;/h3&gt;

&lt;p&gt;Membuka port DHCP pada firewall dan merestart service Kea.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Konfigurasi Firewall&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--add-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dhcp &lt;span class="nt"&gt;--permanent&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--reload&lt;/span&gt;

&lt;span class="c"&gt;# Restart Service&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart kea-dhcp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&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%2Ffqemfvoa4yphodur24pg.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%2Ffqemfvoa4yphodur24pg.png" alt=" " width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  6. Pengujian pada Client
&lt;/h3&gt;

&lt;p&gt;Pada client Windows 10, lakukan pembaruan IP melalui Command Prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ipconfig /release
ipconfig /renew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&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%2Fml0ykos897ksoz2h82rh.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%2Fml0ykos897ksoz2h82rh.png" alt=" " width="552" height="224"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  7. Analisis Log &amp;amp; Database Lease
&lt;/h3&gt;

&lt;p&gt;Verifikasi terakhir adalah memeriksa apakah server mencatat penyewaan IP tersebut dalam database lokal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Melihat log aktivitas DORA secara real-time&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; kea-dhcp4 &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Melihat database penyewaan IP&lt;/span&gt;
&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /var/lib/kea/kea-leases4.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&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%2Fg03f0b41kssesisz80ae.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%2Fg03f0b41kssesisz80ae.png" alt=" " width="800" height="69"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Kesimpulan
&lt;/h3&gt;

&lt;p&gt;Keberhasilan implementasi Kea DHCP sangat bergantung pada ketepatan penentuan interface fisik dan pengaturan IP statis pada sisi server. Dengan berpindah ke Kea, administrator jaringan mendapatkan kontrol lebih baik melalui struktur data JSON yang rapi.&lt;/p&gt;

</description>
      <category>rhel</category>
      <category>dhcp</category>
      <category>linux</category>
      <category>networking</category>
    </item>
    <item>
      <title>Setup Identity Management (FreeIPA) di RHEL 10: Fondasi Infrastruktur Server Terpusat</title>
      <dc:creator>Muhammad Dhiyaul Atha</dc:creator>
      <pubDate>Wed, 29 Apr 2026 15:21:59 +0000</pubDate>
      <link>https://dev.to/bangkah/setup-ad-ds-di-rhel-10-membangun-fondasi-infrastruktur-server-terpusat-598m</link>
      <guid>https://dev.to/bangkah/setup-ad-ds-di-rhel-10-membangun-fondasi-infrastruktur-server-terpusat-598m</guid>
      <description>&lt;p&gt;Di lingkungan enterprise, manajemen identitas terpusat adalah komponen penting untuk menjaga keamanan dan skalabilitas sistem. Jika di ekosistem Windows kita mengenal Active Directory Domain Services (AD DS), maka di dunia Linux tersedia solusi setara yaitu Identity Management (IdM) berbasis FreeIPA.&lt;/p&gt;

&lt;p&gt;Pada artikel ini, saya akan membagikan pengalaman melakukan deployment FreeIPA di RHEL 10, lengkap dengan troubleshooting yang saya temui.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Environment Setup
&lt;/h3&gt;

&lt;p&gt;Untuk tutorial ini, saya menggunakan spesifikasi sistem berikut agar performa &lt;em&gt;Identity Management&lt;/em&gt; tetap stabil:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;OS:&lt;/strong&gt; Red Hat Enterprise Linux 10.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Hardware:&lt;/strong&gt; Laptop dengan Intel i7-14650HX dan RTX 4050.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Network:&lt;/strong&gt; Pastikan sistem menggunakan IP statis.&lt;/li&gt;
&lt;/ul&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%2F8ti5theh6glkzc8wk2w4.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%2F8ti5theh6glkzc8wk2w4.png" alt=" " width="773" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The FQDN Rule: Don't Get Stuck!
&lt;/h3&gt;

&lt;p&gt;Salah satu syarat wajib untuk setup "AD DS" versi Linux ini adalah penggunaan &lt;strong&gt;Fully Qualified Domain Name (FQDN)&lt;/strong&gt;. Installer tidak akan menerima nama tunggal karena sistem identitas seperti Kerberos membutuhkan domain yang lengkap.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Masalah:&lt;/strong&gt; Menggunakan nama seperti &lt;code&gt;atha&lt;/code&gt; akan memicu error &lt;code&gt;must be fully-qualified&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Solusi:&lt;/strong&gt; Set hostname dengan format &lt;code&gt;&amp;lt;hostname&amp;gt;.&amp;lt;domain&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;hostnamectl set-hostname server.atha.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&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%2Fjfq7saxtq9agxmj8813t.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%2Fjfq7saxtq9agxmj8813t.png" alt=" " width="600" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Step-by-Step Installation
&lt;/h3&gt;

&lt;p&gt;Gunakan perintah berikut untuk memulai instalasi server beserta DNS-nya. Saya sangat menyarankan menentukan &lt;em&gt;forwarder&lt;/em&gt; secara eksplisit untuk menghindari masalah resolusi.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ipa-server-install &lt;span class="nt"&gt;--setup-dns&lt;/span&gt; &lt;span class="nt"&gt;--forwarder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.1.1.1 &lt;span class="nt"&gt;--forwarder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F9wrt4jmqpc7377zs217s.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%2F9wrt4jmqpc7377zs217s.png" alt=" " width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tips Tambahan:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;NetBIOS:&lt;/strong&gt; Default-nya adalah &lt;code&gt;ATHA&lt;/code&gt;, ini berguna untuk integrasi dengan sistem Windows.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;NTP:&lt;/strong&gt; Biarkan menggunakan pool bawaan sistem kecuali Anda memiliki server waktu khusus.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Troubleshooting: Belajar dari Kesalahan
&lt;/h3&gt;

&lt;p&gt;Selama proses &lt;em&gt;sharing&lt;/em&gt; ini, ada beberapa kendala yang sempat saya alami dan bisa menjadi pelajaran buat teman-teman:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;DNS Timeout:&lt;/strong&gt; Jika installer gagal saat validasi &lt;em&gt;forwarder&lt;/em&gt;, biasanya ada masalah pada kueri IPv6 di jaringan Anda. Menggunakan flag &lt;code&gt;--forwarder&lt;/code&gt; manual adalah solusinya.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The "yes" Confirmation:&lt;/strong&gt; Jangan terkecoh di akhir ringkasan konfigurasi. Sistem menunggu input &lt;strong&gt;&lt;code&gt;yes&lt;/code&gt;&lt;/strong&gt; secara eksplisit. Jika hanya ditekan Enter, instalasi akan otomatis batal (&lt;em&gt;Aborted&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&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%2Fz4glebmtkpxtopoylg5c.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%2Fz4glebmtkpxtopoylg5c.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Final Steps &amp;amp; Verification
&lt;/h3&gt;

&lt;p&gt;Setelah instalasi selesai, buka akses layanan pada firewall agar sistem berjalan lancar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--add-service&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;freeipa-ldap,freeipa-ldaps,dns,ntp,http,https,kerberos&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--permanent&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gunakan perintah &lt;code&gt;kinit admin&lt;/code&gt; untuk memverifikasi tiket Kerberos Anda.&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%2F0clry2t01retc7x0yg82.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%2F0clry2t01retc7x0yg82.png" alt=" " width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Membangun sistem identitas terpusat dengan FreeIPA memberikan kontrol penuh atas keamanan jaringan. Sebagai mahasiswa Teknik Informatika yang fokus pada &lt;em&gt;Backend&lt;/em&gt; dan &lt;em&gt;Security&lt;/em&gt;, pemahaman ini sangat membantu saya dalam mengelola infrastruktur server yang lebih profesional.&lt;/p&gt;

&lt;p&gt;Semoga tutorial ini bermanfaat bagi teman-teman yang juga sedang mengulik ekosistem RHEL!&lt;/p&gt;

</description>
      <category>linux</category>
      <category>rhel</category>
      <category>devops</category>
      <category>freeipa</category>
    </item>
  </channel>
</rss>
