I built pulstack (pulumi+stack), a CLI tool that lets you deploy static websites either to:
βοΈ AWS S3 + CloudFront
π GitHub Pages
All powered by Pulumi Automation API, GitHub, and AWS.π
With one command, you go from a folder of static files to a live website in less than one minuteπ. No need of writing any code or No clicking around GitHub. 100% Automated π
How It Works
Itβs a 2-step flow, depending on what youβre targeting:
For GitHub Pages:
node index.js init --github# prompts for GitHub token and repo info
node index.js deploy --target github-pages --dir ./public
# creates repo, pushes gh-pages branch, enables github Pages
For AWS (S3 + CloudFront):
node index.js init
# prompts for project name, stack name, and AWS region
node index.js deploy --target aws --dir ./public
# provisions an S3 bucket, uploads files, sets up CloudFront distro
It uses Pulumiβs Automation API under the hood to manage stacks, config, resources, and output all in code β no YAML.
Pulstack β CLI tool for Instant Static Site Deployment with Pulumi
β‘ Pulstack β Instant Static Site Deployment with Pulumi
pulstack is a developer-friendly tool that lets you deploy static websites to AWS (S3 + CloudFront) or GitHub Pages with zero configuraton. It uses Pulumi under the hood to treat infrastructure as code, so your deployments are fully automated and version-controlled.
π§βπ» Who Is This For?
Pulstack is perfect if you:
Have a static website (HTML/CSS/JS or React/Vite/Next.js build)
Want to deploy to AWS (S3+CloudFront) or GitHub Pages in 1 command
Donβt want to write YAML, CloudFormation, or Terraform
Like simple CLI workflows with guided and simple prompts
β¨ Features
π Deploy static sites to AWS S3 with CloudFront CDN
π Automatically create Repo and publish to GitHub Pages
π Secure AWS deployments using best practices (no public buckets!)
π‘ Clean CLI prompts to guide you through setup
𧨠One-command destroy of your whole stack when you're done
This I started as a fun weekend challenge to explore the Pulumi Automation API. Initially, I just wanted to automate AWS S3 site deploys. But then I thought β why stop there? Letβs do GitHub Pages too.
Why Pulstack?π€
When I ran pulumi new aws-javascript, it generated the default Pulumi YAML files and a basic index.js where I had to manually define the S3 bucket, policy, CloudFront distribution, etc.
So I decided to build Pulstack, a zero-config CLI where you answer a few prompts and boom: it handles stack creation, configuration, deployment, and even GitHub repo setup (if youβre going the Pages route).
Hereβs how it went:π
π οΈ The Setup Before the Fun
Before I could dive into coding Pulstack, I had to make sure all the right tools were in place. If youβve ever worked with Pulumi or AWS before, you know there's a bit of prep involved β but once it's done, the automation possibilities are worth it.
This gave me access to Pulumi's commands locally, and I logged in using:
pulumi login
π AWS Setup
Since I was deploying to AWS S3 and CloudFront, I needed the AWS CLI and credentials with the right permissions.
I created a dedicated IAM user with access to S3, CloudFront, and IAM read permissions. That way, Pulumi could provision everything cleanly during stack deployment. Provide AdministratorAccess for the IAM user.
Then configured the AccessKey ID, Secret and Default origin.
π GitHub Token (For Pages Deployments)
When I added support for GitHub Pages deploys, I realized Iβd need to interact with the GitHub API β so a Personal Access Token was essential.
I generated one with repo and delete_repo scope from my GitHub account settings and stored it safely. Pulstack later prompts for this when initializing a GitHub-based stack.
At this point, I had all the things ready: Pulumi CLI installed, AWS CLI configured, GitHub token on hand. Time to start building.
π·ββοΈ Building Pulstack
π index.js
I started off by setting up the CLI with commander.js. Its simple, lightweight, and does exactly what I need.
#!/usr/bin/env node
const{Command}=require("commander");const{deploy}=require("./deploy");const{destroy}=require("./destroy");const{initProject}=require("./init");const{deployGithub}=require("./deployGithub");constprogram=newCommand();program.name("pulstack").description("Deploy static site to AWS S3 or GitHub using Pulumi instantly").version("0.1.0");program.command("deploy").description("Deploy static site to AWS or GitHub Pages").requiredOption("-d, --dir <path>","Path to static site files").option("-e, --env <name>","Environment/stack name","dev").option("-t, --target <provider>","Target platform: aws | github-pages","aws").action(async (opts)=>{consttarget=opts.target;if (target==="github-pages"){awaitdeployGithub(opts.dir);}elseif (target==="aws"){awaitdeploy(opts.dir,opts.env);}else{console.error(`β Unsupported target: ${target}`);process.exit(1);}});program.command("init").description("Initialize project and config").option("--github","Initialize for GitHub Pages").action(async (opts)=>{awaitinitProject({github:opts.github});});program.command("destroy").description("Destroy project").action(async ()=>{awaitdestroy();});program.parse();
The CLI has three main commands:
init β sets up the Pulumi project config
deploy β handles deployments to AWS or GitHub Pages
destroy β To destroy the stack you created
Depending on the target platform passed via --target, it routes to either AWS (deploy.js) or GitHub Pages (deployGithub.js). I also made the static folder path a required option so users donβt forget it.
init.js
Before anything gets deployed, we need to gather some info β and thatβs what init.js handles. It sets up the project depending on whether you want to deploy to AWS or GitHub Pages.
constfs=require("fs");constpath=require("path");constprompts=require("prompts");const{LocalWorkspace}=require("@pulumi/pulumi/automation");const{execSync}=require("child_process");functioncheckCLI(command,name){try{execSync(command,{stdio:"ignore"});console.log(`β ${name} CLI is installed`);returntrue;}catch{console.error(`β ${name} CLI is not installed. Please install it first.`);returnfalse;}}functioncheckPulumiLogin(){try{constuser=execSync("pulumi whoami",{stdio:"pipe"}).toString().trim();console.log(`π Logged in as ${user}`);returntrue;}catch{console.error("β οΈ Pulumi CLI is not logged in. Run `pulumi login` and try again.");returnfalse;}}functioncheckAwsConfigured(){try{constidentity=execSync("aws sts get-caller-identity",{stdio:"pipe"}).toString();constparsed=JSON.parse(identity);console.log(`π§Ύ AWS Configured for Account: ${parsed.Account}, ARN: ${parsed.Arn}`);returntrue;}catch{console.error("β AWS CLI is not configured. Run `aws configure` with your IAM credentials first.");returnfalse;}}asyncfunctioninitProject(options={}){constuseGitHub=options.github||false;console.log("π Checking environment...");constPulumiCheck=checkCLI("pulumi version","Pulumi");if (!PulumiCheck)process.exit(1);if (useGitHub){const{repoName,description,deployDir,stackName,githubToken}=awaitprompts([{type:"text",name:"repoName",message:"GitHub repo name:",initial:path.basename(process.cwd()),},{type:"text",name:"description",message:"Repo description:",},{type:"text",name:"deployDir",message:"Directory to deploy (e.g., ./build):",initial:"./build",},{type:"text",name:"stackName",message:"Stack name:",initial:"github-pages",},{type:"password",name:"githubToken",message:"Enter your github token",},]);constgithubConfig={projectName:repoName,description,deployDir,stackName,githubToken,target:"github",};fs.writeFileSync("config.json",JSON.stringify(githubConfig,null,2));console.log("β GitHub Pages project initialized and saved to config.json");return;}// For AWS S3 setupconsthasAws=checkCLI("aws --version","AWS");constisPulumiLoggedIn=checkPulumiLogin();constisAwsConfigured=checkAwsConfigured();if (!hasAws||!isPulumiLoggedIn||!isAwsConfigured){process.exit(1);}constresponse=awaitprompts([{type:"text",name:"projectName",message:"Project name:",initial:"Pulumi",},{type:"text",name:"stackName",message:"Stack name:",initial:"dev",},{type:"text",name:"projectDescription",message:"Project Description:",initial:"This is a cool project",},{type:"text",name:"region",message:"AWS region:",initial:"us-east-1",},{type:"confirm",name:"generateSite",message:"Create a sample index.html?",initial:true,},]);constconfig={projectName:response.projectName,stackName:response.stackName,projectDescription:response.projectDescription,region:response.region,target:"aws",};fs.writeFileSync("config.json",JSON.stringify(config,null,2));console.log("π¦ Saved all config β config.json");// Create sample static siteconstpublicDir=path.join(process.cwd(),"public");if (response.generateSite&&!fs.existsSync(publicDir)){fs.mkdirSync(publicDir);fs.writeFileSync(path.join(publicDir,"index.html"),`<html><body><h1>Pulumi is awesome broo!π₯</h1></body></html>`);console.log("π Created sample static site in ./public/");}// Initialize Pulumi stack for AWS onlyconststack=awaitLocalWorkspace.createOrSelectStack({stackName:response.stackName,projectName:response.projectName,program:async ()=>{},});awaitstack.setConfig("aws:region",{value:response.region});console.log("β Pulumi stack initialized!");}module.exports={initProject};
Once you run:
node index.js init
# or
node index.js init --github
It does the following:
β Checks for required CLIs (Pulumi, AWS CLI)
π§ Validates Pulumi login and AWS credentials (for AWS mode)
π£οΈ Prompts you for config, like project name, stack name, region, and target(GitHub Access Token if you want to deploy on GitHub)
π Saves everything to config.json β so you donβt have to answer again
π (Optional) Creates a sample index.html in a public/ folder, so you can test deployments instantly
Make sure that the IAM user has necessary permissions and also GitHub token has the repo and delete permissions. Visit my GitHub repo to see all the required permissions.
π pulumiProgram.js β Infra as Code
Here I am defining all the AWS infra as code.
// pulumiProgram.js"use strict";constaws=require("@pulumi/aws");constpulumi=require("@pulumi/pulumi");//const mime = require("mime");constfs=require("fs");constpath=require("path");functioncreatePulumiProgram(staticDir){returnasync ()=>{// Create a bucket and expose a website index documentconstconfig=JSON.parse(fs.readFileSync("config.json","utf-8"));constbucketName=config.projectName;letsiteBucket=newaws.s3.BucketV2(bucketName,{});letsiteBucketWebsiteConfig=newaws.s3.BucketWebsiteConfigurationV2("s3-website-bucket-config",{bucket:siteBucket.id,indexDocument:{suffix:"index.html",},});newaws.s3.BucketPublicAccessBlock("public-access-block",{bucket:siteBucket.id,blockPublicAcls:true,blockPublicPolicy:true,ignorePublicAcls:true,restrictPublicBuckets:true,});// Create CloudFront Origin Access Identityconstoai=newaws.cloudfront.OriginAccessIdentity("pulumi-oai",{comment:`Access Identity for ${bucketName}`,});// Upload files from the staticDirconstfiles=fs.readdirSync(staticDir);for (constfileoffiles){constfilePath=path.join(staticDir,file);constcontentType=getMimeType(file);newaws.s3.BucketObject(file,{bucket:siteBucket,source:newpulumi.asset.FileAsset(filePath),contentType,});}constaddFolderContents=(staticDir,prefix)=>{for (letitemoffs.readdirSync(staticDir)){letfilePath=path.join(staticDir,item);letisDir=fs.lstatSync(filePath).isDirectory();// This handles adding subfolders and their contentif (isDir){constnewPrefix=prefix?path.join(prefix,item):item;addFolderContents(filePath,newPrefix);continue;}letitemPath=prefix?path.join(prefix,item):item;itemPath=itemPath.replace(/\\/g,'/');// convert Windows paths to something S3 will recognizeletobject=newaws.s3.BucketObject(itemPath,{bucket:siteBucket.id,source:newpulumi.asset.FileAsset(filePath),// use FileAsset to point to a filecontentType:getMimeType(filePath),// set the MIME type of the file});}}// Attach bucket policy for OAInewaws.s3.BucketPolicy("pulumi-bucket-policy",{bucket:siteBucket.bucket,policy:pulumi.all([siteBucket.bucket,oai.iamArn]).apply(([bucket,iamArn])=>JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{AWS:iamArn},Action:"s3:GetObject",Resource:`arn:aws:s3:::${bucket}/*`,},],})),});// Upload static filesconstuploadFiles=(dir,prefix="")=>{for (constitemoffs.readdirSync(dir)){constfilePath=path.join(dir,item);conststat=fs.statSync(filePath);if (stat.isDirectory()){uploadFiles(filePath,path.join(prefix,item));}else{constrelativePath=path.join(prefix,item).replace(/\\/g,"/");newaws.s3.BucketObject(relativePath,{bucket:siteBucket.id,source:newpulumi.asset.FileAsset(filePath),contentType:getMimeType(filePath),});}}};uploadFiles(staticDir);// CloudFront Distributionconstdistribution=newaws.cloudfront.Distribution("pulumi-cdn",{enabled:true,defaultRootObject:"index.html",origins:[{originId:siteBucket.arn,domainName:siteBucket.bucketRegionalDomainName,s3OriginConfig:{originAccessIdentity:oai.cloudfrontAccessIdentityPath,},},],defaultCacheBehavior:{targetOriginId:siteBucket.arn,viewerProtocolPolicy:"redirect-to-https",allowedMethods:["GET","HEAD"],cachedMethods:["GET","HEAD"],forwardedValues:{queryString:false,cookies:{forward:"none"},},compress:true,},priceClass:"PriceClass_100",restrictions:{geoRestriction:{restrictionType:"none",},},viewerCertificate:{cloudfrontDefaultCertificate:true,},});return{bucketName:siteBucket.bucket,cloudfrontUrl:distribution.domainName.apply((domain)=>`https://${domain}`),};};}// Simple mime type guesserfunctiongetMimeType(file){if (file.endsWith(".html"))return"text/html";if (file.endsWith(".css"))return"text/css";if (file.endsWith(".js"))return"application/javascript";if (file.endsWith(".json"))return"application/json";if (file.endsWith(".png"))return"image/png";if (file.endsWith(".jpg")||file.endsWith(".jpeg"))return"image/jpeg";return"text/plain";}module.exports={createPulumiProgram};
πͺ£ S3 Bucket Creation: First, we create an S3 bucket to host the static files.
π« Blocking Public Access(For Security): To keep it private, we block all public access by default.
π΅οΈ CloudFront OAI (Origin Access Identity): Instead of making the bucket public, we use a CloudFront OAI to access the bucket securely. That means only CloudFront can fetch objects from S3.
π Upload Static Files: Then it recursively uploads everything from the provided --dir into the S3 bucket, preserving folder structure and setting proper MIME types. I wrote a custom uploadFiles() function for this.
π deploy.js β Deployment to AWS with Just One Command
This file is what gets executed when the user runs:
node index.js deploy --target aws --dir ./public
NOTE: I'm using ./publicdir here but you can pass any directory.
e.g If you have built a react app, you should pass ./builddir here.
// deploy.jsconst{LocalWorkspace}=require("@pulumi/pulumi/automation");constpath=require("path");constfs=require("fs");const{createPulumiProgram}=require("./pulumiProgram");asyncfunctiondeploy(staticDir){if (!fs.existsSync(staticDir)){console.error(`Directory "${staticDir}" does not exist.`);process.exit(1);}constconfigPath=path.resolve("config.json");if (!fs.existsSync(configPath)){console.error("β Missing config.json β have you run `init`?");process.exit(1);}constconfig=JSON.parse(fs.readFileSync(configPath,"utf8"));//const token = process.env.PULUMI_ACCESS_TOKEN || config.pulumiAccessToken;conststackName=config.stackName;constprojectName=config.projectName;console.log("β³ Initializing Pulumi stack...");conststack=awaitLocalWorkspace.createOrSelectStack({stackName,projectName,program:createPulumiProgram(staticDir),});console.log("β Stack initialized");awaitstack.setConfig("aws:region",{value:config.region||"us-east-1"});console.log("π Deploying to AWS...");constupRes=awaitstack.up();console.log("\nβ Deployment complete!");console.log(`π¦ Bucket Name: ${upRes.outputs.bucketName.value}`);console.log(`π Site URL: ${upRes.outputs.cloudfrontUrl.value}`);}module.exports={deploy};
TL;DR
β Reads static site and config
π οΈ Provisions infra via Pulumi Automation
π‘ Uploads all files to s3 bucket
π Returns live site URL β all in one command
π deployGithub.js β Deploy to GitHub Pages in One Shot
This function automates the full lifecycle of a GitHub Pages deployment:
NOTE: I'm using ./publicdir here but you can pass any directory.
e.g If you have built a react app, you should pass ./builddir here.
constfs=require("fs");constpath=require("path");const{LocalWorkspace}=require("@pulumi/pulumi/automation");constsimpleGit=require("simple-git");require("dotenv").config();asyncfunctiondeployGithub(){constconfigPath=path.resolve("config.json");if (!fs.existsSync(configPath)){console.error("β Missing config.json β please run `init` first.");process.exit(1);}constconfig=JSON.parse(fs.readFileSync(configPath,"utf8"));const{projectName,description,deployDir,stackName}=config;letenablePages=false;letfullName="";letrepoUrl="";if (!fs.existsSync(deployDir)){console.error(`β Deploy directory "${deployDir}" does not exist.`);process.exit(1);}consttoken=process.env.GITHUB_TOKEN||config.githubToken;if (!token){console.error("β GitHub token not found. Please set GITHUB_TOKEN as an env variable.");process.exit(1);}constprogram=async ()=>{constgithub=require("@pulumi/github");constrepo=newgithub.Repository(projectName,{name:projectName,description,visibility:"public",...(enablePages&&{pages:{source:{branch:"gh-pages",path:"/",},},}),});return{repoUrl:repo.htmlUrl,fullName:repo.fullName,};};conststack=awaitLocalWorkspace.createOrSelectStack({stackName,projectName,program,});awaitstack.setAllConfig({"github:token":{value:token,secret:true},});console.log("π¦ Creating GitHub repo...");constresult=awaitstack.up();fullName=result.outputs.fullName.value;repoUrl=result.outputs.repoUrl.value;console.log("β GitHub repo created:",repoUrl);// Step 2: Push static site to gh-pagesconsole.log("π€ Pushing site content to `gh-pages` branch...");constgit=simpleGit(deployDir);awaitgit.init();awaitgit.checkoutLocalBranch("gh-pages");// β Create gh-pages branchawaitgit.add(".");awaitgit.commit("Deploy to GitHub Pages from statik");constremotes=awaitgit.getRemotes(true);if (remotes.find(r=>r.name==="origin")){awaitgit.removeRemote("origin");}awaitgit.addRemote("origin",`https://github.com/${fullName}`).catch(()=>{});awaitgit.push("origin","gh-pages",["--force"]);// Step 3: Enable GitHub Pagesconsole.log("π Enabling GitHub Pages...");enablePages=true;constupdatedStack=awaitLocalWorkspace.createOrSelectStack({stackName,projectName,program,});awaitupdatedStack.setAllConfig({"github:token":{value:token,secret:true},});awaitupdatedStack.up();// β re-run with updated programconst[owner,repoName]=fullName.split("/");constsiteUrl=`https://${owner.toLowerCase()}.github.io/${repoName}/`;console.log(`π GitHub Pages deployed at: ${siteUrl}`);}module.exports={deployGithub};
β Creates a repo via Pulumi
β Pushes static content to gh-pages (used simple-git to manage git pushes programmatically.)
β Enables GitHub Pages via Pulumi
β Outputs a live site URL
I followed the two-step process to enable GitHub Pages:
First, create the repo without pages set
Push the static content to the gh-pages branch
Then re-run the Pulumi program with pages enabled
Why? Because GitHub Pages requires the branch to exist first before Pulumi can activate it
π₯ destroy.js β Destroys the stacks
This function will destroy the stack which is present in the config file.
constfs=require("fs");constpath=require("path");const{LocalWorkspace}=require("@pulumi/pulumi/automation");asyncfunctiondestroy(){constconfigPath=path.resolve("config.json");if (!fs.existsSync(configPath)){console.error("β Missing config.json β have you run ` init`?");process.exit(1);}constconfig=JSON.parse(fs.readFileSync(configPath,"utf8"));//const token = process.env.PULUMI_ACCESS_TOKEN || config.pulumiAccessToken;conststackName=config.stackName;constprojectName=config.projectName;console.log(`𧨠Destroying stack "${stackName}" from project "${projectName}"...`);conststack=awaitLocalWorkspace.selectStack({stackName,projectName,program:async ()=>{},// noop});awaitstack.destroy({onOutput:console.log});console.log(`β Stack "${stackName}" destroyed successfully.`);}module.exports={destroy};
By running:
node index.js destroy
The stack name and project name will be fetched from the config.json file.
Challenges Faced
Biggest challenge? GitHub Pages needs the gh-pages branch to exist before you enable it. That was super annoying. I ended up creating the repo first, pushing the site content, and then updating the repo again to enable Pages.
GitHub Access Token Permission for deleting the Repo when you run destroy command
Getting CloudFront to work with private S3 buckets required setting up a (OAI) and properly configuring the S3 bucket policy to allow access via that identity. So, I reviewed the AWS documentation and carefully constructed a Pulumi-based BucketPolicy that grants s3:GetObject permission specifically to the OAI. Once configured properly, it worked correctly..
What I Learned
Pulumi is powerful tool - being able to define infra as code in JavaScript (supports many languages) and deploy it programmatically made automation feel seamless.
To be honest, I never defined infra as code before. I always used direct aws GUI but after using pulumi I learned it.
Also never used simple-git and made commits and push programatically.
I started with a simple idea of automating but ended up with lot of learnings and handy CLI tool π
Currently it supports only AWS cloud but I will be adding Azure and GCP as well so that user can choose on which cloud service they want to deploy.
Using Pulumi with GitHub
In this project, I used Pulumi to automate the creation and management of cloud(AWS) and GitHub infrastructureβmaking it easy to go from local code to a live site with just a single command.
π What I Used Pulumi For?
AWS Deployment:
Pulumi provisions an S3 bucket (with static website hosting), sets up a CloudFront distribution, and securely connects them using an Origin Access Identity (OAI).
GitHub Repository Management:
Using the Pulumi GitHub provider, I automated:
Creating a public repo from code
Pushing content to the gh-pages branch
Enabling GitHub Pages for instant static hosting
Used Pulumi inlinePrograms for stacks.
Pulumi Copilot
I used the Debug with Copilot feature in Pulumi dashboard whenever stack updates failed. It analyzed the problem and provided me the cause of stack failure π
Great project. Integration with the gh cli tool would be incredible. You seem to rely on the classic tokens, since fine-grained token will be defined by a repo, so no instant repo unless you using the gh tool. You should update your repo README based on the tutorial. Your instructions in your README in our repo for installing pullstack seem to assume you're running aws, and that pullstack is already installed? Of course would be nice to be able to simply npm i pullstack --template? I guess node install etc will have to do for now. Ps: I also put up a gh-pages pun project this weekend (inspired by the zx posting on Dev) was not aware I could do this for every repo!
Since this is not a npm package you need to clone the repo & install the dependencies..
If you are going with (Pulumi+AWS) or (Pulumi+Github) in both the cases Pulumi needs to be installed and aws cli only if you want to host on aws s3...
I designed in such way that even for (Pulumi+AWS) or (Pulumi+Github) you just needs to pass you build files(copy-paste your build folder to the cloned repo) so it will upload all your build files and host instantly.
I Totally agree that an easier npm pulstack experience would make it way more approachable..
definitely going to iterate on this!
πππππππ BlackHat_Nexus π ππ πππ ππππ ππ πππππππ πππππππ ππππ πππππππ
Fast, Available and Reliable for any of the following services
π€³ Recovery of lost funds
π€³ Facebook Hack
π€³ WhatsApp Hack
π€³ Instagram Hack
π€³ Spying
π€³ Windows Hacking
π€³ Recover lost wallet
π€³ Credit score trick
π€³ Recover Password
π€³ Gmail Hack
π€³ SnapChat Hacking
π€³ Cellphone Monitoring
π€³ Tik Tok Hack
π€³ Twitter Hack
π€³ Lost Phone Tracking
π€³ Lost IaptopTracking
π€³ Lost Car Tracking
π€³ Cloning WhatsApp
π€³ Cryptocurrency Wallet
π€³ Hacking
π€³ Iphone unlock
π€³ Got banned
π€³ Private Number available
π€³ Telegram hacking
π€³ Websites hacking
π€³ Hack University
π€³ IOS and Android hack
π€³ Wifi Hacking
π€³ CCTV hacking
π€³ Hack Bot Game
π€³ Free fire hack
π€³ Changing of school grades
π€³ Cards π³hacking
πππππππ BlackHat_Nexus π ππ πππ ππππ ππ πππππππ πππππππ ππππ πππππππ
Fast, Available and Reliable for any of the following services
π€³ Recovery of lost funds
π€³ Facebook Hack
π€³ WhatsApp Hack
π€³ Instagram Hack
π€³ Spying
π€³ Windows Hacking
π€³ Recover lost wallet
π€³ Credit score trick
π€³ Recover Password
π€³ Gmail Hack
π€³ SnapChat Hacking
π€³ Cellphone Monitoring
π€³ Tik Tok Hack
π€³ Twitter Hack
π€³ Lost Phone Tracking
π€³ Lost IaptopTracking
π€³ Lost Car Tracking
π€³ Cloning WhatsApp
π€³ Cryptocurrency Wallet
π€³ Hacking
π€³ Iphone unlock
π€³ Got banned
π€³ Private Number available
π€³ Telegram hacking
π€³ Websites hacking
π€³ Hack University
π€³ IOS and Android hack
π€³ Wifi Hacking
π€³ CCTV hacking
π€³ Hack Bot Game
π€³ Free fire hack
π€³ Changing of school grades
π€³ Cards π³hacking
πππππππ BlackHat_Nexus π ππ πππ ππππ ππ πππππππ πππππππ ππππ πππππππ
Fast, Available and Reliable for any of the following services
π€³ Recovery of lost funds
π€³ Facebook Hack
π€³ WhatsApp Hack
π€³ Instagram Hack
π€³ Spying
π€³ Windows Hacking
π€³ Recover lost wallet
π€³ Credit score trick
π€³ Recover Password
π€³ Gmail Hack
π€³ SnapChat Hacking
π€³ Cellphone Monitoring
π€³ Tik Tok Hack
π€³ Twitter Hack
π€³ Lost Phone Tracking
π€³ Lost IaptopTracking
π€³ Lost Car Tracking
π€³ Cloning WhatsApp
π€³ Cryptocurrency Wallet
π€³ Hacking
π€³ Iphone unlock
π€³ Got banned
π€³ Private Number available
π€³ Telegram hacking
π€³ Websites hacking
π€³ Hack University
π€³ IOS and Android hack
π€³ Wifi Hacking
π€³ CCTV hacking
π€³ Hack Bot Game
π€³ Free fire hack
π€³ Changing of school grades
π€³ Cards π³hacking
Awesome Kiranπ₯