Installing SilverBullet v1 (0.x) on Windows 10 with a PowerShell script

This installation is for a local network (e.g., at home or in the office).
I recently wrote a script for myself and thought it might be useful to others. What it does:

  • Identifies the current user and local IPv4
  • Installs Deno
  • Installs SilverBullet via deno install
  • Downloads and installs Caddy and configures the NSSM service for Caddy
  • Creates a Caddyfile with HTTPS on the detected IP address → reverse proxy server on localhost:Port
  • Configures SilverBullet to start automatically
  • Copies the Caddy root certificate authority and installs it in LocalMachine\Root
  • Starts SilverBullet and opens Edge at the IP address
    You must run the script as an administrator for full control (this is especially important when installing the certificate authority).
    Don’t forget to install PowerShell if you don’t have it.
    Create a folder, for example /tmp, and place the script in it.

Run the script:
cd /tmp
./install-silverbullet.ps1

Below is the text of the script. Copy it into Notepad and save it as install-silverbullet.ps1 in your /tmp folder.

#Start the script
#What it does in one go:
#Detects current user + local IPv4
#Installs Deno
#Installs SilverBullet via deno install
#Downloads & installs Caddy + sets up NSSM service for Caddy
#Creates Caddyfile with HTTPS on your detected IP → reverse proxy to localhost:$Port
#Sets up SilverBullet autostart (per-user, via Startup shortcut + silverbullet.ps1)
#Copies Caddy root CA and installs it into LocalMachine\Root (if script run as admin and CA exists)
#Starts SilverBullet via shortcut and opens Edge to https://$Ip
#You should run this as the user who will use SilverBullet,
#ideally “Run as administrator” for full behavior (esp. CA install).


# install-silverbullet-all.ps1
# One-shot installer for:
#   - Deno
#   - SilverBullet (via deno install)
#   - Caddy (reverse proxy with HTTPS) + NSSM Windows service
#   - SilverBullet autostart (per-user)
#   - Caddy local CA installation into trusted root store (Windows)

param(
    [int]$Port = 3000
)

$ErrorActionPreference = "Stop"
$ProgressPreference    = "SilentlyContinue"

Write-Host "==============================================="
Write-Host " Installing Deno + SilverBullet + Caddy + CA"
Write-Host "==============================================="
Write-Host ""

# --- 0. Detect user and local IPv4 -------------------------------------------------

Write-Host "[0] Detecting current user and local IPv4 address..."
$User = $env:UserName

$Ip = (Get-NetIPAddress -AddressFamily IPv4 |
    Where-Object {
        $_.IPAddress -ne "127.0.0.1" -and
        $_.IPAddress -notlike "169.254.*"
    } |
    Select-Object -ExpandProperty IPAddress -First 1)

if (-not $Ip) {
    throw "Failed to determine local IPv4 address. Check network connectivity."
}

Write-Host "  Install user : $User"
Write-Host "  SB access IP : $Ip"
Write-Host ""

$SilverBulletDir = "C:\Users\$User\SilverBullet"
$CaddyDir        = "C:\caddy"
$CaddyFilePath   = Join-Path $CaddyDir "Caddyfile"
$LogsDir         = "C:\caddy\logs"
$ToolsDir        = "C:\Tools"

# --- 1. Create directories ---------------------------------------------------------

Write-Host "[1] Creating directories..."
New-Item -ItemType Directory -Path $SilverBulletDir -Force | Out-Null
New-Item -ItemType Directory -Path $CaddyDir        -Force | Out-Null
New-Item -ItemType Directory -Path $LogsDir         -Force | Out-Null
New-Item -ItemType Directory -Path $ToolsDir        -Force | Out-Null
Write-Host "  SilverBulletDir : $SilverBulletDir"
Write-Host "  CaddyDir        : $CaddyDir"
Write-Host ""

# --- 2. Install Deno ---------------------------------------------------------------

Write-Host "[2] Installing Deno..."
$env:DENO_INSTALL = "$env:USERPROFILE\.deno"
[Environment]::SetEnvironmentVariable("DENO_INSTALL", $env:DENO_INSTALL, "User")

$denoInstallUrl = "https://deno.land/install.ps1"
Write-Host "  Downloading Deno install script: $denoInstallUrl"
$denoInstallScript = Invoke-WebRequest -Uri $denoInstallUrl -UseBasicParsing

if ($denoInstallScript.Content -is [byte[ ]]) {
    $denoInstallText = [System.Text.Encoding]::UTF8.GetString($denoInstallScript.Content)
} else {
    $denoInstallText = [string]$denoInstallScript.Content
}

Write-Host "  Running Deno installer..."
Invoke-Expression $denoInstallText

$denoBin = Join-Path $env:DENO_INSTALL "bin"
Write-Host "  Adding Deno bin to user PATH: $denoBin"
$pathUser = [Environment]::GetEnvironmentVariable("Path", "User")
if ($pathUser -notlike "*$denoBin*") {
    [Environment]::SetEnvironmentVariable("Path", "$denoBin;$pathUser", "User")
}
$env:Path = "$denoBin;$env:Path"

Write-Host "  Deno version:"
deno --version
Write-Host ""

# --- 3. Install SilverBullet via deno install -------------------------------------

Write-Host "[3] Installing SilverBullet via deno install..."
deno install -f --global --name silverbullet `
    --unstable-kv --unstable-worker-options -A `
    https://get.silverbullet.md

Write-Host "  Checking 'silverbullet' command..."
$sbCmd = Get-Command silverbullet -ErrorAction Stop
Write-Host "  SilverBullet installed at: $($sbCmd.Source)"
Write-Host ""

# --- 4. Download and configure Caddy ----------------------------------------------

Write-Host "[4] Downloading and configuring Caddy..."

$githubApi = "https://api.github.com/repos/caddyserver/caddy/releases/latest"
$headers   = @{ "User-Agent" = "PowerShell" }

Write-Host "  Querying latest Caddy release: $githubApi"
$release = Invoke-RestMethod -Uri $githubApi -Headers $headers

Write-Host "  Searching for *windows_amd64.zip / *windows-amd64.zip asset..."
$asset = $release.assets |
    Where-Object {
        $_.name -like "*windows_amd64.zip" -or
        $_.name -like "*windows-amd64.zip"
    } |
    Select-Object -First 1

if (-not $asset) {
    Write-Host "  Available assets:"
    $release.assets | ForEach-Object { Write-Host "    - $($_.name)" }
    throw "Could not find a Caddy Windows amd64 archive in the latest release."
}

$caddyZip = Join-Path $env:TEMP $asset.name
Write-Host "  Downloading Caddy: $($asset.browser_download_url)"
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $caddyZip -UseBasicParsing

Write-Host "  Extracting Caddy to $CaddyDir..."
Expand-Archive -Path $caddyZip -DestinationPath $CaddyDir -Force

Write-Host "  Looking for caddy.exe..."
$caddyExe = Get-ChildItem -Path $CaddyDir -Filter "caddy.exe" -Recurse | Select-Object -First 1
if (-not $caddyExe) {
    throw "caddy.exe not found after extraction."
}
Write-Host "  Found Caddy: $($caddyExe.FullName)"
Write-Host ""

Write-Host "  Creating Caddyfile: $CaddyFilePath"
@"
https://$Ip {
    reverse_proxy localhost:$Port {
        header_up Host $Ip
    }
}
"@ | Set-Content -Path $CaddyFilePath -Encoding UTF8

Write-Host "  Caddyfile content:"
Write-Host (Get-Content $CaddyFilePath | Out-String)
Write-Host ""

# --- 5. Install Caddy as a Windows service using NSSM -----------------------------

Write-Host "[5] Installing Caddy as a Windows service via NSSM..."

# 5.1 Find or install NSSM
$nssmExe = Get-ChildItem -Path $ToolsDir -Filter "nssm.exe" -Recurse -ErrorAction SilentlyContinue |
           Select-Object -First 1

if (-not $nssmExe) {
    Write-Host "  NSSM not found. Downloading NSSM 2.24..."
    $zipPath = Join-Path $env:TEMP "nssm-2.24.zip"
    Invoke-WebRequest "https://nssm.cc/release/nssm-2.24.zip" -OutFile $zipPath

    Write-Host "  Extracting NSSM to $ToolsDir..."
    Expand-Archive $zipPath -DestinationPath $ToolsDir -Force

    $nssmExe = Get-ChildItem -Path $ToolsDir -Filter "nssm.exe" -Recurse -ErrorAction SilentlyContinue |
               Select-Object -First 1
}

if (-not $nssmExe) {
    throw "NSSM not found in $ToolsDir after extraction."
}

$nssmPath = $nssmExe.FullName
Write-Host "  NSSM found: $nssmPath"

# 5.2 Remove existing Caddy service if present
Write-Host "  Removing existing Caddy service (if any)..."
& $nssmPath stop   Caddy 2>$null | Out-Null
& $nssmPath remove Caddy confirm 2>$null | Out-Null

# 5.3 Create Caddy service
Write-Host "  Creating Caddy service..."
& $nssmPath install Caddy $caddyExe.FullName "run" "--config" "Caddyfile"

Write-Host "  Configuring Caddy service parameters..."
& $nssmPath set Caddy AppDirectory $CaddyDir
& $nssmPath set Caddy AppStdout "$LogsDir\caddy-output.log"
& $nssmPath set Caddy AppStderr "$LogsDir\caddy-error.log"
& $nssmPath set Caddy AppRotateFiles 1
& $nssmPath set Caddy AppRotateOnline 1
& $nssmPath set Caddy AppRotateBytes 10485760
& $nssmPath set Caddy AppExit Default Restart
& $nssmPath set Caddy AppRestartDelay 5000
& sc.exe config Caddy start= auto | Out-Null

Write-Host "  Starting Caddy service..."
& $nssmPath start Caddy
Write-Host "  ✅ Caddy service is running." -ForegroundColor Green
Write-Host ""

# --- 6. Configure SilverBullet autostart via Startup shortcut --------------------

Write-Host "[6] Setting up SilverBullet autostart (user Startup)..."

# 6.1 Set user environment variables for SilverBullet
[Environment]::SetEnvironmentVariable("SB_HOSTNAME",   "0.0.0.0",        "User")
[Environment]::SetEnvironmentVariable("SB_INDEX_PAGE", "Home",           "User")
[Environment]::SetEnvironmentVariable("SB_FOLDER",     $SilverBulletDir, "User")

$env:SB_HOSTNAME   = "0.0.0.0"
$env:SB_INDEX_PAGE = "Home"
$env:SB_FOLDER     = $SilverBulletDir

# 6.2 Generate silverbullet.ps1 in the space folder
$silverbulletPs1 = Join-Path $SilverBulletDir "silverbullet.ps1"

$content = @"
# Autostart script for SilverBullet (generated by install-silverbullet-all.ps1)
# Uses the 'silverbullet' command installed via deno install.

Try {
    Stop-Process -Name "silverbullet" -Force -ErrorAction SilentlyContinue
} Catch {
    Write-Host "No running SilverBullet instance found."
}

`$space = "`$env:SB_FOLDER"
if (-not (Test-Path `$space)) {
    Write-Host "Space folder not found: `$space. Creating..."
    New-Item -ItemType Directory -Path `$space -Force | Out-Null
}

Write-Host "Starting SilverBullet in: `$space (hostname=0.0.0.0, port=$Port)" -ForegroundColor Cyan

Start-Process -FilePath "$($sbCmd.Source)" `
    -ArgumentList "`"`$space`" --hostname 0.0.0.0 --port $Port" `
    -WorkingDirectory "`$space" `
    -WindowStyle Hidden
"@

$content | Set-Content -Path $silverbulletPs1 -Encoding UTF8
Write-Host "  Created SilverBullet autostart script: $silverbulletPs1"

# 6.3 Create a shortcut in the user's Startup folder
$startupPath  = [Environment]::GetFolderPath("Startup")
$shortcutPath = Join-Path $startupPath "SilverBullet.lnk"
$psExe        = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$psArgs       = "-ExecutionPolicy Bypass -File `"$silverbulletPs1`""

$shell    = New-Object -ComObject WScript.Shell
$shortcut = $shell.CreateShortcut($shortcutPath)
$shortcut.TargetPath       = $psExe
$shortcut.Arguments        = $psArgs
$shortcut.WorkingDirectory = $SilverBulletDir
$shortcut.WindowStyle      = 7
$shortcut.IconLocation     = $psExe
$shortcut.Save()

Write-Host "  Created Startup shortcut: $shortcutPath"
Write-Host ""

# --- 7. Install Caddy local root CA into Windows trusted root store --------------

Write-Host "[7] Installing Caddy local root CA into Trusted Root store (if possible)..."

$principal = New-Object Security.Principal.WindowsPrincipal(
    [Security.Principal.WindowsIdentity]::GetCurrent()
)

if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Host "  ⚠ Script is NOT running as Administrator - skipping CA installation." -ForegroundColor Yellow
    Write-Host "    You can later run a separate CA-install script as admin if needed."
} else {
    $caddyCaDir = "C:\Windows\System32\config\systemprofile\AppData\Roaming\Caddy\pki\authorities\local"
    $srcCa      = Join-Path $caddyCaDir "root.crt"
    $dstCa      = "C:\caddy\local-root-ca.crt"

    if (Test-Path $srcCa) {
        Copy-Item $srcCa $dstCa -Force
        Write-Host "  Copied Caddy root CA: $srcCa -> $dstCa"

        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($dstCa)
        Write-Host "  CA Subject: $($cert.Subject)"
        Write-Host "  CA Issuer : $($cert.Issuer)"

        $store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
            "Root",
            [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
        )
        $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
        $store.Add($cert)
        $store.Close()

        Write-Host "  ✅ Caddy CA installed into LocalMachine\\Root." -ForegroundColor Green
    } else {
        Write-Host "  ⚠ Caddy root CA not found at: $srcCa" -ForegroundColor Yellow
        Write-Host "    Make sure Caddy has started at least once so it can generate its local CA."
    }
}
Write-Host ""

# --- 8. Test SilverBullet autostart and open Edge --------------------------------

Write-Host "[8] Test-starting SilverBullet via Startup shortcut..."
Start-Process -FilePath $shortcutPath

$sbUrl = "https://$Ip"
Write-Host "Opening Microsoft Edge: $sbUrl ..."
Start-Process "microsoft-edge:$sbUrl"

Write-Host ""
Write-Host "==============================================="
Write-Host " Done."
Write-Host "  SilverBullet space  : $SilverBulletDir"
Write-Host "  Caddy directory     : $CaddyDir"
Write-Host "  Access URL          : $sbUrl"
Write-Host " At next user logon, SilverBullet will autostart."
Write-Host "==============================================="
# End the script

I think you’ll have to reformat this a bit to be usable. Also I updated the title to include that this is for SB v1, it won’t work for v2.

1 Like