Untitled

Published on 18 June 2026 03:56 PM

PhantomBlogger Logo

PhantomBlogger

Automated LLM-driven blog infrastructure for offensive asset management from a single Discord interface.


OverviewArchitectureTemplatesQuick StartCommandsConfigurationLLM Models


Developed during my work at InTheCyber Group


Overview

PhantomBlogger is a red team infrastructure tool that leverages local LLMs to generate realistic, niche-specific blog content at scale. It automates the full pipeline, from content generation to deployment, making it possible to maintain multiple credible-looking, content-driven websites with minimal manual effort.

The project is built around a single key principle: asset categorization takes time. Automated scanners, crawlers, and reputation systems need to observe a site publishing consistent, topical content over weeks or months before assigning it a legitimate category. PhantomBlogger handles this background work automatically, so sites are ready well before an engagement begins.

[!NOTE] 📖 Companion Blog Post

Technical write-up and project rationale: https://medium.com/inthecyber-posts/phantomblogger-automating-red-team-infrastructure-with-local-llms-b4f895908509


Architecture

PhantomBlogger uses a centralized back-end / lightweight VPS model. All heavy lifting (LLM inference, image retrieval, SSH management) runs on a single Manager machine. Each site VPS only needs to rebuild the static site when new content arrives.

Manager (Back-End)

A team-server or local machine that:

  • Hosts the Discord bot — the primary operator interface
  • Runs a local LLM via Ollama for article generation
  • Fetches stock images from the Pixabay API
  • Connects to each site via SSH/SFTP to upload posts and trigger builds

[!IMPORTANT] The Manager must be capable of running the llama3 model with Ollama. For GPU-accelerated inference, refer to Ollama's hardware requirements.

Site (VPS)

A lightweight VPS that:

  • Hosts the static blog compiled by Eleventy (11ty)
  • Serves content via Caddy with automatic HTTPS/Let's Encrypt
  • Exposes a low-privilege site-manager SSH account for remote management

No persistent process or API is required on the VPS. Caddy serves the static output from /opt/Blog-Template/dist and handles certificate renewal automatically.

Interaction Flow

flowchart LR
    OP(["👤 Operator"])

    subgraph MGR ["Manager (Team Server)"]
        BOT["Discord Bot"]
        LLM["Ollama LLM"]
        PIX["Pixabay API"]
        STG["LLM-OUT/\n(staging)"]
        BOT --> LLM
        BOT --> PIX
        LLM --> STG
        PIX --> STG
    end

    subgraph VPS ["Site VPS"]
        SFTP["SFTP Upload"]
        BUILD["npm run build\n(Eleventy)"]
        DIST["/dist\n(static HTML)"]
        CADDY["Caddy\n(HTTPS)"]
        SFTP --> BUILD
        BUILD --> DIST
        CADDY -.- DIST
    end

    INET(["🌐 Internet"])

    OP -->|"/gen  /upload"| BOT
    STG -->|"SSH/SFTP"| SFTP
    INET -->|"HTTPS"| CADDY

Flow summary:

  1. Operator issues /gen with a target site and content prompt
  2. The bot reads site configuration (model, credentials, paths) from config.json
  3. The prompt is sent to Ollama — a structured Markdown article is returned
  4. A stock image is fetched from Pixabay and staged alongside the post
  5. On the next /upload or auto-uploader cycle, files are transferred via SFTP
  6. The Manager triggers npm run build remotely — Eleventy compiles Markdown to static HTML
  7. Caddy serves the updated site immediately from /opt/Blog-Template/dist

Templates

Three blog templates are included, all sharing a standardized provisioning interface. The setup script and Discord bot work with all of them without any modification.

TemplateStyleBest For
Base-TemplateClean, minimalGeneral-purpose baseline; suitable for any niche
Template-EmberWarm, editorialHealth, lifestyle, and finance niches
Template-MidnightDark, modernTech, security, and developer-focused content

All templates use an identical data file structure (site.json, author.json, menu.json) and the same Eleventy post pipeline (posts.11tydata.js). Building a new niche variant is as simple as customizing those data files and selecting a different GEN_MODEL.


Quick Start

1. Set Up the Manager

Install dependencies, configure Ollama with the appropriate Modelfiles, and start the Discord bot on your team-server or local machine.

➡️ Manager Installation Instructions

2. Provision a Site VPS

Spin up a lightweight VPS with a domain pointing at it and run vps_setup.sh. The script handles all system setup, template deployment, and prints the JSON configuration snippet for the Manager.

➡️ Site Provisioning Instructions

3. Add the Site to config.json

Paste the JSON block from the provisioning script into the sites array in DiscordBOT/config.json. Set "enabled": true and fill in GEN_MODEL with the Ollama model name for this site's niche.

4. Interact via Discord

With the bot running and a site configured, all operations are handled through Discord slash commands.


Commands

All operations are performed through Discord slash commands. site parameters support autocomplete — start typing a name and Discord suggests matching options.

CommandParametersDescription
/gensite, promptGenerate an article via LLM and fetch a stock image
/uploadsiteUpload staged posts/images to the VPS and trigger a rebuild
/auto_uploaderstate, poll_secondsStart or stop the background upload loop
/listsiteList all published articles on a remote site
/deletesite, articleDelete a remote article and its image(s), then rebuild
/test_sitessite (or all)Run local + SSH connectivity diagnostics
/logscount (optional)Retrieve the bot log file, newest entries first
/uptimeShow bot uptime, start time, and PID

➡️ Full Command Reference with screenshots


Configuration

The Manager is configured through DiscordBOT/config.json. A fully annotated example is available at DiscordBOT/config.example.json.

{
  "generation_config": {
    "GLOBAL_GEN_MODEL": "llama3-11ty",
    "IMAGE_MODEL": "llama3-pixabay",
    "OLLAMA_API_URL": "http://localhost:11434/api/generate",
    "PIXABAY_API_KEY": "YOUR_PIXABAY_KEY",
    "BASE_SITE_PATH": "/opt/PhantomBlogger/LLM-OUT/",
    "HTTP_TIMEOUT_SEC": 300
  },
  "manager_config": {
    "DISCORD_BOT_TOKEN": "YOUR_DISCORD_TOKEN",
    "DISCORD_GUILD_ID": 123456789012345678,
    "DISCORD_CHANNEL_ID": 123456789012345678,
    "PYTHON_EXECUTABLE": "python3",
    "GEN_SCRIPT_PATH": "/opt/PhantomBlogger/DiscordBOT/utils/gen_article.py",
    "LOG_FILE": "/opt/PhantomBlogger/logs/discord-bot.log"
  },
  "sites": [
    {
      "name": "example.com",
      "enabled": true,
      "local_post_dir": "/opt/PhantomBlogger/LLM-OUT/example.com/posts/",
      "local_image_dir": "/opt/PhantomBlogger/LLM-OUT/example.com/_images/",
      "GEN_MODEL": "llama3-pharma-medical",
      "remote": {
        "host": "example.com",
        "user": "site-manager",
        "key": "BASE64_ENCODED_SSH_PRIVATE_KEY",
        "key_passphrase": "",
        "post_path": "/opt/Blog-Template/src/posts/",
        "image_path": "/opt/Blog-Template/src/posts/img/",
        "build_path": "/opt/Blog-Template/",
        "build_cmd": "npm run build",
        "static_site_location": "/opt/Blog-Template/dist"
      }
    }
  ]
}
Configuration field reference

generation_config

FieldDescription
GLOBAL_GEN_MODELDefault Ollama model used when a site has no GEN_MODEL override
IMAGE_MODELOllama model used to generate Pixabay image search terms
OLLAMA_API_URLOllama REST API endpoint
PIXABAY_API_KEYAPI key for stock image retrieval
BASE_SITE_PATHRoot directory for all local LLM output (LLM-OUT/)
HTTP_TIMEOUT_SECTimeout for Ollama HTTP requests, in seconds

manager_config

FieldDescription
DISCORD_BOT_TOKENDiscord bot token from the Developer Portal
DISCORD_GUILD_IDServer ID where slash commands are registered
DISCORD_CHANNEL_IDChannel ID where the bot posts generation/upload notifications
PYTHON_EXECUTABLEPython interpreter path (e.g. python3 or a venv path)
GEN_SCRIPT_PATHAbsolute path to gen_article.py
LOG_FILEAbsolute path for the bot log file

sites[] entry

FieldDescription
nameSite identifier used in Discord autocomplete
enabledSet false to disable without removing the entry
local_post_dirLocal staging directory for generated .md files
local_image_dirLocal staging directory for fetched images
GEN_MODELOllama model for this site's content niche (overrides GLOBAL_GEN_MODEL)
remote.hostVPS hostname
remote.userSSH user (always site-manager)
remote.keyBase64-encoded Ed25519 private key (generated by vps_setup.sh)
remote.key_passphraseKey passphrase, or empty string
remote.post_pathPath to the Eleventy posts directory on the VPS
remote.image_pathPath to the posts image directory on the VPS
remote.build_pathWorking directory for the remote build command
remote.build_cmdBuild command (npm run build)
remote.static_site_locationPath to the compiled static output served by Caddy

LLM Models

Article generation uses Ollama with custom Modelfiles in LLM-CONF/. Each Modelfile wraps llama3 with a niche-specific system prompt that defines topic, tone, and output format. One example Modelfile is provided

ModelLanguageNiche
llama3-pharma-medicalEnglishPharmaceuticals, medicine, healthcare, biotechnology

To add a model for a new niche, copy an existing Modelfile, update the SYSTEM prompt, and register it with Ollama:

ollama create my-niche-model -f /opt/PhantomBlogger/LLM-CONF/my-niche-model/Modelfile

Repository Structure

PhantomBlogger/
├── DiscordBOT/
│   ├── commands/           # Slash command modules
│   ├── utils/
│   │   └── gen_article.py  # Article generation pipeline (Ollama → SFTP)
│   ├── main.py             # Bot entry point
│   ├── requirements.txt
│   ├── config.example.json # Annotated configuration template
│   └── config.json         # Live configuration (gitignored)
├── LLM-CONF/               # Ollama Modelfiles, one directory per niche model
├── LLM-OUT/                # Local staging area for generated content (gitignored)
├── Site/
│   ├── vps_setup.sh        # Idempotent VPS provisioning script
│   ├── clean_template.sh   # Strips build artifacts before template transfer
│   ├── Base-Template/      # Default blog template (Eleventy + Tailwind CSS)
│   ├── Template-Ember/     # Warm editorial template variant
│   ├── Template-Midnight/  # Dark modern template variant
│   └── Caddy/Caddyfile     # Example Caddy configuration
├── logs/                   # Bot log output (gitignored)
└── README.md