Last active
October 8, 2025 22:08
-
-
Save DurtyFree/5e01cd112811b5e8554074627be7cb4e to your computer and use it in GitHub Desktop.
Azure Meilisearch Infrastructure Deployment Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| param( | |
| [string]$SubscriptionId = "<SUBSCRIPTION-ID>", | |
| [string]$ResourceGroup = "rg-meilisearch", | |
| [string]$BaseName = "meilisearch-prod", | |
| [string]$Location = "westeurope", | |
| [string]$AdminUser = "azureuser", | |
| [string]$VmSize = "Standard_D4as_v5", | |
| [int] $MeiliPort = 7700, | |
| [string]$MeiliVersion = "v1.22.3", | |
| [string]$SshAllowedCidr = "<YOUR-IP>/16" | |
| ) | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = "Stop" | |
| function Ensure-Az { | |
| if (-not (az account show 1>$null 2>$null)) { az login | Out-Null } | |
| az account set --subscription $SubscriptionId | Out-Null | |
| } | |
| function Ensure-Rule { | |
| param( | |
| [string]$Rg, [string]$Nsg, [string]$Name, | |
| [int]$Priority, [string]$Dir, [string]$Access, [string]$Proto, | |
| [string]$Src, [string]$DstPorts | |
| ) | |
| $exists = az network nsg rule show -g $Rg --nsg-name $Nsg -n $Name 2>$null | |
| if ($exists) { | |
| az network nsg rule update -g $Rg --nsg-name $Nsg -n $Name ` | |
| --priority $Priority --direction $Dir --access $Access --protocol $Proto ` | |
| --source-address-prefixes $Src --destination-port-ranges $DstPorts 1>$null | |
| } | |
| else { | |
| az network nsg rule create -g $Rg --nsg-name $Nsg -n $Name ` | |
| --priority $Priority --direction $Dir --access $Access --protocol $Proto ` | |
| --source-address-prefixes $Src --destination-port-ranges $DstPorts 1>$null | |
| } | |
| } | |
| Ensure-Az | |
| # Names | |
| $Vnet = "vnet-$BaseName" | |
| $SubnetApps = "snet-apps" | |
| $Nsg = "nsg-$BaseName-apps" | |
| $VmName = "vm-$BaseName-01" | |
| $NicName = "nic-$VmName" | |
| $PublicIp = "pip-$BaseName-app" | |
| $DnsLabel = ("meili-" + [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()) | |
| Write-Host "Ensuring resource group $ResourceGroup" | |
| if (-not (az group exists -n $ResourceGroup | ConvertFrom-Json)) { | |
| az group create -n $ResourceGroup -l $Location 1>$null | |
| } | |
| Write-Host "Ensuring VNet and subnet" | |
| if (-not (az network vnet show -g $ResourceGroup -n $Vnet 2>$null)) { | |
| az network vnet create -g $ResourceGroup -n $Vnet --address-prefixes 10.50.0.0/16 ` | |
| --subnet-name $SubnetApps --subnet-prefixes 10.50.1.0/24 1>$null | |
| } | |
| Write-Host "Ensuring NSG and rules" | |
| if (-not (az network nsg show -g $ResourceGroup -n $Nsg 2>$null)) { | |
| az network nsg create -g $ResourceGroup -n $Nsg 1>$null | |
| } | |
| Ensure-Rule -Rg $ResourceGroup -Nsg $Nsg -Name "allow-ssh-from-cidr" ` | |
| -Priority 101 -Dir Inbound -Access Allow -Proto Tcp -Src $SshAllowedCidr -DstPorts "22" | |
| Ensure-Rule -Rg $ResourceGroup -Nsg $Nsg -Name "allow-meili" ` | |
| -Priority 110 -Dir Inbound -Access Allow -Proto Tcp -Src "Internet" -DstPorts "$MeiliPort" | |
| Ensure-Rule -Rg $ResourceGroup -Nsg $Nsg -Name "deny-all" ` | |
| -Priority 4096 -Dir Inbound -Access Deny -Proto "*" -Src "*" -DstPorts "*" | |
| # Attach NSG to subnet if not attached | |
| $appsSubnetNsg = az network vnet subnet show -g $ResourceGroup --vnet-name $Vnet -n $SubnetApps --query "networkSecurityGroup.id" -o tsv | |
| if ([string]::IsNullOrEmpty($appsSubnetNsg)) { | |
| az network vnet subnet update -g $ResourceGroup --vnet-name $Vnet -n $SubnetApps ` | |
| --network-security-group $Nsg 1>$null | |
| } | |
| Write-Host "Ensuring NIC and VM" | |
| if (-not (az network nic show -g $ResourceGroup -n $NicName 2>$null)) { | |
| az network nic create -g $ResourceGroup -n $NicName --vnet-name $Vnet --subnet $SubnetApps 1>$null | |
| } | |
| if (-not (az vm show -g $ResourceGroup -n $VmName 2>$null)) { | |
| az vm create -g $ResourceGroup -n $VmName --image "Ubuntu2204" --size $VmSize ` | |
| --nics $NicName --admin-username $AdminUser --generate-ssh-keys 1>$null | |
| } | |
| Write-Host "Attach Standard public IP with DNS to NIC" | |
| if (-not (az network public-ip show -g $ResourceGroup -n $PublicIp 2>$null)) { | |
| az network public-ip create -g $ResourceGroup -n $PublicIp --sku Standard --allocation-method Static 1>$null | |
| } | |
| az network public-ip update -g $ResourceGroup -n $PublicIp --dns-name $DnsLabel 1>$null | |
| $PipId = az network public-ip show -g $ResourceGroup -n $PublicIp --query id -o tsv | |
| $IpCfg = az network nic show -g $ResourceGroup -n $NicName --query "ipConfigurations[0].name" -o tsv | |
| az network nic ip-config update -g $ResourceGroup --nic-name $NicName -n $IpCfg --public-ip-address $PipId 1>$null | |
| $PublicFqdn = az network public-ip show -g $ResourceGroup -n $PublicIp --query "dnsSettings.fqdn" -o tsv | |
| Write-Host "Provision Meilisearch on the VM" | |
| $installScript = @' | |
| set -e | |
| apt-get update | |
| apt-get install -y curl ca-certificates | |
| curl -fL -o /usr/local/bin/meilisearch \ | |
| https://github.com/meilisearch/meilisearch/releases/download/__MEILI_VERSION__/meilisearch-linux-amd64 | |
| chmod +x /usr/local/bin/meilisearch | |
| /usr/local/bin/meilisearch --version | |
| id meili >/dev/null 2>&1 || useradd -r -s /usr/sbin/nologin meili | |
| install -d -o meili -g meili -m 0755 /var/lib/meilisearch | |
| install -d -o meili -g meili -m 0755 /var/lib/meilisearch/data.ms | |
| install -d -o meili -g meili -m 0755 /var/lib/meilisearch/dumps | |
| install -d -o meili -g meili -m 0755 /var/lib/meilisearch/snapshots | |
| KEY="$(grep -E "^MEILI_MASTER_KEY=" /etc/meili.env 2>/dev/null | cut -d= -f2)" | |
| if [ -z "$KEY" ] || [ "$KEY" = "null" ]; then | |
| KEY="$(tr -dc "A-Za-z0-9_.-" </dev/urandom | head -c 40)" | |
| fi | |
| cat >/etc/meili.env <<EOF | |
| MEILI_ENV=production | |
| MEILI_MASTER_KEY=$KEY | |
| MEILI_DB_PATH=/var/lib/meilisearch/data.ms | |
| MEILI_HTTP_ADDR=0.0.0.0:__MEILI_PORT__ | |
| EOF | |
| chown root:root /etc/meili.env | |
| chmod 600 /etc/meili.env | |
| cat >/etc/systemd/system/meilisearch.service <<'EOF' | |
| [Unit] | |
| Description=Meilisearch | |
| After=network-online.target | |
| Wants=network-online.target | |
| [Service] | |
| User=meili | |
| Group=meili | |
| Type=simple | |
| WorkingDirectory=/var/lib/meilisearch | |
| EnvironmentFile=/etc/meili.env | |
| ExecStart=/usr/local/bin/meilisearch \ | |
| --db-path /var/lib/meilisearch/data.ms \ | |
| --http-addr 0.0.0.0:__MEILI_PORT__ \ | |
| --env production \ | |
| --dump-dir /var/lib/meilisearch/dumps \ | |
| --snapshot-dir /var/lib/meilisearch/snapshots | |
| Restart=always | |
| RestartSec=2 | |
| LimitNOFILE=65535 | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| systemctl daemon-reload | |
| systemctl enable --now meilisearch | |
| ufw status | grep -iq active && ufw allow __MEILI_PORT__/tcp || true | |
| sleep 2 | |
| STATUS="$(systemctl is-active meilisearch || true)" | |
| HEALTH="$(curl -sS -i --max-time 5 http://127.0.0.1:__MEILI_PORT__/health 2>/dev/null | head -n 1 || echo none)" | |
| MS_KEY="$(grep '^MEILI_MASTER_KEY=' /etc/meili.env | cut -d= -f2)" | |
| echo "MS_STATUS=$STATUS" | |
| echo "MS_HEALTH=$HEALTH" | |
| echo "MS_KEY=$MS_KEY" | |
| '@ | |
| $installScript = $installScript.Replace("__MEILI_VERSION__", $MeiliVersion).Replace("__MEILI_PORT__", "$MeiliPort") | |
| $vmOut = az vm run-command invoke -g $ResourceGroup -n $VmName --command-id RunShellScript ` | |
| --scripts "$installScript" --query "value[0].message" -o tsv | |
| # Parse simple markers | |
| $msStatus = ($vmOut -split "`n" | Where-Object { $_ -match "^MS_STATUS=" }) -replace "^MS_STATUS=", "" | |
| $msHealth = ($vmOut -split "`n" | Where-Object { $_ -match "^MS_HEALTH=" }) -replace "^MS_HEALTH=", "" | |
| $msKey = ($vmOut -split "`n" | Where-Object { $_ -match "^MS_KEY=" }) -replace "^MS_KEY=", "" | |
| # External health | |
| Start-Sleep -Seconds 3 | |
| $extOk = $false | |
| try { | |
| $r = Invoke-WebRequest -Uri ("http://{0}:{1}/health" -f $PublicFqdn, $MeiliPort) -UseBasicParsing -TimeoutSec 8 | |
| if ($r.StatusCode -eq 200) { $extOk = $true } | |
| } | |
| catch { } | |
| Write-Host "" | |
| Write-Host "Public endpoint: http://$PublicFqdn:$MeiliPort" -ForegroundColor Cyan | |
| Write-Host "SSH: ssh $AdminUser@$PublicFqdn" -ForegroundColor DarkCyan | |
| Write-Host ("Master key: {0}" -f $(if ($msKey) { $msKey } else { "<unknown>" })) -ForegroundColor Yellow | |
| Write-Host ("Local service: {0}" -f $(if ($msStatus) { $msStatus } else { "<unknown>" })) -ForegroundColor Yellow | |
| Write-Host ("Local health: {0}" -f $(if ($msHealth) { $msHealth } else { "<unknown>" })) -ForegroundColor Yellow | |
| Write-Host ("External health: {0}" -f $(if ($extOk) { "OK" } else { "unreachable" })) -ForegroundColor Yellow | |
| Write-Host "" | |
| Write-Host "Try:" -ForegroundColor Gray | |
| Write-Host " curl -H 'Authorization: Bearer $msKey' http://$PublicFqdn:$MeiliPort/health" -ForegroundColor Gray |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment