Mert Özen Her Satırda Daha İleri
Geliştirici Araçları Productivity ve Automation — #05

Yeni Makineyi Tek Komutla Kurmak: Brewfile, WinGet Configuration, mise ile Runtime Sürümleri ve Bootstrap

Mert Özen 15 Kas 2026 13 dk 7 görüntülenme
Yeni Makineyi Tek Komutla Kurmak: Brewfile, WinGet Configuration, mise ile Runtime Sürümleri ve Bootstrap

Yeni bir makineyi tek komutla kurmanın yolu işi katmanlara ayırmak: makine araçlarını Brewfile ya da WinGet Configuration kurar, proje sürümlerini global.json ve mise.toml ile mise sabitler, dotfiles'ı chezmoi uygular, projeyi just setup hazırlar. Bookstore'un tekrar çalıştırılabilir bootstrap script'lerini, WinGet dosyasının şema doğrulamasını, mise'nin trust sorusunu ve eksik araçları kendiliğinden kuran mise exec'i gösteriyoruz.

Sürüm notu: Bu yazıdaki mise ve just adımları .NET 10 (SDK 10.0.404), mise 2026.11.8, just 1.59.0 ve Node.js 24.22 ile çalıştırılmıştır. Brewfile ve WinGet Configuration dosyaları Homebrew ve Microsoft Learn belgelerine göre yazılmış, sözdizimleri ve şemaları çevrimdışı doğrulanmıştır.

Önce Tahmin Et

Bookstore'un (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) mise.toml dosyasında Node.js ve just var, .NET SDK sürümü de global.json'dan okunuyor. Temiz bir makinede yalnız Node.js'i kuruyorsun: mise install node. Sonra mise exec -- node --version çalıştırıyorsun.

Ekranda yalnız v24.22.3 mı görürsün, yoksa bir hata mı? Kurulu olmayan iki araç bu komutu nasıl etkiler? Cevap "Runtime Sürümleri" bölümünde.

Neyi Hangi Araç Kurar?

Serinin önceki yazılarında komutları bir justfile'da topladık ve kişisel ayarları dotfiles repo'suna koyduk. Yeni gelen bir geliştiricinin hâlâ yarım gün harcadığı yer, bunları çalıştıracak araçları kurmak. Bu işi tek araca yüklemek yerine dört katmana ayırmak bakımı kolaylaştırıyor:

  • Makine araçları: Git, mise, chezmoi, Docker Desktop, Visual Studio Code (VS Code). İşletim sisteminin paket yöneticisi kurar: macOS'ta Homebrew, Windows'ta WinGet, Linux'ta apt.
  • Proje sürümleri: .NET SDK, Node.js, just. Proje başına sabitlenir, mise kurar; sürüm repo'daki dosyalarda yazılıdır.
  • Kişisel ayarlar: shell, prompt, .gitconfig. Dotfiles repo'sundan chezmoi uygular.
  • Proje kurulumu: paketleri geri yüklemek, seed verisi. just setup yapar.

05-bootstrap-flow-tr.png

Ayrımın sebebi sürüm sahipliği. Homebrew ve WinGet her zaman güncel sürümü kurar ve eski sürümü tutmak için tasarlanmamıştır. Projenin derlendiği .NET SDK ve Node.js sürümü ise projenin kararıdır; makinenin değil.

macOS: Brewfile ve brew bundle

Brewfile, Homebrew'un Ruby tabanlı bir listesi:

# Bookstore'un macOS'taki makine araçları. Proje runtime'ları (.NET SDK, Node.js, just) mise'den gelir.
brew "git"
brew "mise"
brew "chezmoi"
cask "docker-desktop"
cask "visual-studio-code"

brew komut satırı araçlarını, cask uygulamaları kurar; listede tap, mas (Mac App Store) ve vscode (VS Code extension'ları) satırları da olabilir. Dosya Ruby olduğu için koşul yazılabiliyor, ama bunu basit tut. Sözdizimini Homebrew'u çalıştırmadan Ruby'nin kendisiyle kontrol edebilirsin:

$ ruby -c Brewfile
Syntax OK

brew bundle listede olup kurulu olmayanları kurar, kurulu olanları atlar; bu yüzden tekrar tekrar çalıştırılabilir. Belgeye göre varsayılan olarak eski sürümleri de günceller; bootstrap sırasında sürpriz güncelleme istemiyorsan --no-upgrade ekle. brew bundle check bir şey yapılacak mı diye bakar, brew bundle dump kurulu olanlardan Brewfile üretir. brew bundle cleanup --force ise listede olmayan paketleri kaldırır; kişisel makinede bunu ekip Brewfile'ıyla çalıştırmak, ekibin listesinde olmayan her şeyi silmek demek.

Belgenin açıkça yazdığı bir sınır var: Brewfile'ın lock dosyası yok ve olmayacak. Homebrew rolling release (sürekli güncel sürüm) çalışır, keyfi bir eski sürümü kurmayı desteklemez. pin: true bir paketi yalnız güncellemekten korur. Sürümü önemli olan her şeyi bu yüzden mise'ye bırakıyoruz.

Bu bölümde resmî belgelerden yararlanılmıştır.

Windows: WinGet Configuration

Windows'taki karşılık, winget configure ile çalıştırılan YAML (girintiye dayalı configuration formatı) tabanlı bir configuration dosyası. Belgedeki adlandırma kuralına göre Git repo'larında varsayılan yer .config/configuration.winget:

# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
# Bookstore'un Windows'taki makine araçları. Proje runtime'ları (.NET SDK, Node.js, just) mise'den gelir.
properties:
  configurationVersion: 0.2.0
  assertions:
    - resource: Microsoft.Windows.Developer/OsVersion
      directives:
        description: Verify minimum OS version
        allowPrerelease: true
      settings:
        MinVersion: '10.0.22000'
  resources:
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: git
      directives:
        description: Install Git
      settings:
        id: Git.Git
        source: winget
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: pwsh
      directives:
        description: Install PowerShell 7
      settings:
        id: Microsoft.PowerShell
        source: winget
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: mise
      directives:
        description: Install mise
      settings:
        id: jdx.mise
        source: winget
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: chezmoi
      directives:
        description: Install chezmoi
      settings:
        id: twpayne.chezmoi
        source: winget
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: docker
      directives:
        description: Install Docker Desktop
        securityContext: elevated
      settings:
        id: Docker.DockerDesktop
        source: winget
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: vscode
      directives:
        description: Install Visual Studio Code
      settings:
        id: Microsoft.VisualStudioCode
        source: winget

Dosya bir komut listesi değil, istenen durumun tarifi. Arkada PowerShell DSC (Desired State Configuration; makinenin olması gereken durumunu tarif edip uygulayan altyapı) resource'ları çalışıyor. assertions ön koşullar, resources istenen durum; dependsOn ile bir resource'u başka birine bağlayabilirsin, gerisinin sırası önemli değil ve bazıları paralel çalışabilir. Bir assertion başarısız olursa ona bağlı resource'lar atlanıyor, diğerleri devam ediyor. securityContext: elevated yönetici yetkisi isteyen resource'ları işaretliyor; WinGet başta bir kez UAC (Windows'un yönetici onayı penceresi) onayı istiyor. Paket kimliklerinin her birini winget-pkgs repo'sunda kontrol ettim; Casey.Just de orada, ama just'ı mise'ye bıraktık.

Komutlar: winget configure show -f <dosya> içeriği gösterir, winget configure validate dosyayı doğrular, winget configure test makinenin istenen durumda olup olmadığını söyler, winget configure -f <dosya> uygular. Uygulamadan önce bir uyarı onayı istenir; --accept-configuration-agreements bunu atlar. Bunu yalnız dosyayı okuduğun ve repo'nun sana ait olduğu durumda kullan.

Windows makinem olmadığı için dosyayı Microsoft'un yayımladığı JSON Schema'ya karşı doğruladım. Ne kadar kolay hata yapılabildiğini görmek için iki küçük değişiklikle bir kopya hazırladım:

$ python validate_winget.py schema/0.2.json .config/configuration.winget
6 resources, 0 schema errors
$ python validate_winget.py schema/0.2.json bad.winget
ERROR ['properties', 'configurationVersion'] 0.2 is not of type 'string'
ERROR ['properties', 'resources', 4, 'directives', 'securityContext'] 'admin' is not one of ['current', 'restricted', 'elevated']
6 resources, 2 schema errors

İlk hata YAML'ın klasik tuzağı: tırnaksız 0.2 string değil sayı olarak okunuyor (JSON, YAML ve Configuration Formatları serisinde ayrıntısıyla). VS Code'da Red Hat'in YAML extension'ı ilk satırdaki yaml-language-server yorumunu okuyup aynı kontrolü yazarken yapıyor.

Bu bölümde resmî belgelerden yararlanılmıştır.

Linux ve WSL: apt Listesi

Linux'ta, WSL (Windows Subsystem for Linux; Windows içinde Linux çalıştıran katman) dahil, makine araçlarını dağıtımın paket yöneticisine bırakmak en sade yol. Bookstore'da scripts/apt-packages.txt her satırda bir paket adı tutuyor ve bootstrap script'i xargs -a scripts/apt-packages.txt sudo apt-get install -y ile kuruyor. Script mise ve chezmoi'yi kurmaya çalışmıyor; ikisinin belgesinde de Linux için birden çok kurulum yolu var ve ekip README'de birini seçiyor. Araç yoksa script bunu söyleyip duruyor. Homebrew Linux'ta da çalışıyor, istersen aynı Brewfile'ı orada da kullanabilirsin; cask satırları Linux'ta atlanır.

Runtime Sürümleri: global.json ve mise

.NET SDK sürümü zaten global.json'da yazılı (ASP.NET Core Web API serisinin ikinci yazısında anlatıldı). Node.js sürümünü ve just'ı da repo'da sabitlemek için mise.toml:

# Bookstore'un runtime sürümleri. .NET SDK sürümü global.json'dan okunur.
[tools]
node = "24"
just = "1.59.0"

[settings]
idiomatic_version_file_enable_tools = ["dotnet"]

idiomatic_version_file_enable_tools mise'ye global.json'u okumasını söylüyor; SDK sürümü tek yerde kalıyor. mise registry'sinde dotnet bir core backend; node da öyle. node = "24" 24 serisinin en yeni sürümüne çözülüyor. Angular 22'nin desteklediği Node aralığına bakınca 24 serisinin 24.15'ten sonrası uygun:

$ npm view @angular/core@22.2.6 engines.node
^22.22.3 || ^24.15.0 || >=26.0.0

Repo'yu klonlayıp ilk kez mise ls çalıştırınca mise durdu:

$ mise ls
mise ERROR error parsing config file: ~/src/bookstore/mise.toml
mise ERROR Config files in ~/src/bookstore/mise.toml are not trusted.
Trust them with `mise trust`. See https://mise.jdx.dev/cli/trust.html for more information.

Bu iyi bir varsayılan: mise.toml ortam değişkeni tanımlayabilir ve task çalıştırabilir, yani internetten gelen bir repo'nun dosyası kod gibi davranabilir. Dosyayı okuduktan sonra:

$ mise trust
mise trusted ~/src/bookstore
$ mise ls
dotnet  10.0.404 (missing)  ~/src/bookstore/global.json  10.0.404
just    1.59.0 (missing)    ~/src/bookstore/mise.toml    1.59.0
node    24.22.3 (missing)   ~/src/bookstore/mise.toml    24
$ mise install node
...
mise ✓ node@24.22.3  5.0s  node-v24.22.3-darwin-arm64.tar.gz
mise ████████████████ 1/1 · installed 1 tool in 5.0s

mise Node.js arşivini nodejs.org'un SHASUMS256.txt dosyasına ve onun imzasına göre doğruluyor. Önce Tahmin Et'in cevabı:

$ mise exec -- node --version
mise by @jdx – installing 2 tools
mise dotnet@10.0.404 dotnet-install: .NET Core SDK with version '10.0.404' is already installed.
mise ✓ dotnet@10.0.404  2.0s
mise ✓ just@1.59.0      2.0s  just-1.59.0-aarch64-apple-darwin.tar.gz
mise ████████████████ 2/2 · installed 2 tools in 2.0s
v24.22.3

mise exec eksik araçları kendisi kurdu, sonra komutu çalıştırdı. .NET SDK bu makinede zaten vardı; mise DOTNET_ROOT ortam değişkenindeki klasörü paylaşılan kök olarak kullandı ve yeniden indirmedi. Belgeye göre bu varsayılan "shared" mod; her SDK'yı ayrı klasöre koymak için dotnet.isolated ayarı var. Bootstrap bu davranıştan yararlanıyor, ama açık olmak için yine de önce mise install çalıştırıyor.

mise'yi shell'ine activate ile bağlarsan klasöre girdiğinde sürümler kendiliğinden değişir. Ben bağlamadım ve her yerde mise exec -- kullandım; script ve CI için bu daha öngörülebilir. Alternatifler: nvm yalnız Node.js'i yönetir ve Windows sürümü ayrı bir projedir, fnm iki platformda da çalışır ama yine yalnız Node.js içindir. .NET SDK ile Node.js'i aynı dosyadan yönetmek istiyorsan mise tek araç olarak işini görüyor.

Bootstrap Script'i

Bootstrap'ın iki kuralı var: tek giriş noktası olmalı ve kaç kez çalıştırılırsa çalıştırılsın aynı sonuca varmalı. Kurulu olanı atlayan araçlar (brew bundle, winget configure, mise install, chezmoi) bu işin çoğunu zaten yapıyor; script yalnız sırayı ve platform farkını yönetiyor. macOS, Linux ve WSL için scripts/bootstrap.sh:

#!/usr/bin/env bash
# Bookstore için macOS, Linux ya da WSL makineyi hazırlar. Tekrar çalıştırmak güvenlidir.
set -euo pipefail

cd "$(dirname "$0")/.."

case "$(uname -s)" in
  Darwin)
    if ! command -v brew >/dev/null 2>&1; then
      echo "Homebrew is missing. Install it from https://brew.sh and run this script again." >&2
      exit 1
    fi
    echo "==> Machine tools (Brewfile)"
    brew bundle --file=Brewfile --no-upgrade
    ;;
  Linux)
    echo "==> Machine tools (scripts/apt-packages.txt)"
    sudo apt-get update
    xargs -a scripts/apt-packages.txt sudo apt-get install -y
    for tool in mise chezmoi; do
      if ! command -v "$tool" >/dev/null 2>&1; then
        echo "$tool is missing. See the install steps in README.md." >&2
        exit 1
      fi
    done
    ;;
  *)
    echo "Unsupported OS: $(uname -s). On Windows use scripts/bootstrap.ps1." >&2
    exit 1
    ;;
esac

echo "==> Project runtimes (mise.toml, global.json)"
mise install

if [[ -n "${DOTFILES_REPO:-}" ]]; then
  echo "==> Dotfiles"
  if [[ -d "${XDG_DATA_HOME:-$HOME/.local/share}/chezmoi" ]]; then
    chezmoi update
  else
    chezmoi init --apply "$DOTFILES_REPO"
  fi
fi

echo "==> Project setup"
mise exec -- just setup

echo "Done. Start Bookstore with: mise exec -- just dev"

Homebrew eksikse script onu internetten indirip çalıştırmıyor, adresi gösterip duruyor; makineye neyin kurulacağına geliştirici karar veriyor. Dotfiles kişisel olduğu için isteğe bağlı: DOTFILES_REPO verilmezse adım atlanıyor. Akışı, brew, mise ve chezmoi yerine yalnız aldığı argümanları yazan küçük stub script'leri koyarak denedim:

$ DOTFILES_REPO=https://git.example.com/ada/dotfiles.git ./scripts/bootstrap.sh
==> Machine tools (Brewfile)
[stub] brew bundle --file=Brewfile --no-upgrade
==> Project runtimes (mise.toml, global.json)
[stub] mise install
==> Dotfiles
[stub] chezmoi init --apply https://git.example.com/ada/dotfiles.git
==> Project setup
[stub] mise exec -- just setup
Done. Start Bookstore with: mise exec -- just dev

Son adımı ise gerçek araçlarla çalıştırdım. justfile'a eklenen setup recipe'si yalnız iki bağımlılıktan oluşuyor: setup: restore seed.

$ mise exec -- just setup
dotnet tool restore
Tool 'dotnet-ef' (version '10.0.15') was restored. Available commands: dotnet-ef
Restore was successful.
dotnet restore
  Determining projects to restore...
  All projects are up-to-date for restore.
npm --prefix web/bookstore-web ci
added 294 packages, and audited 296 packages in 3s
...
dotnet run tools/seed.cs -- --count 10
Wrote 10 books to data/books.json

Windows için scripts/bootstrap.ps1 aynı adımları izliyor:

# Bookstore için Windows makineyi hazırlar. Tekrar çalıştırmak güvenlidir.
# Windows PowerShell 5.1'de çalışır, çünkü PowerShell 7'yi bu script kuruyor.
[CmdletBinding()]
param(
    [switch] $AcceptConfigurationAgreements
)

$ErrorActionPreference = 'Stop'
Set-Location (Join-Path $PSScriptRoot '..')

if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
    throw 'winget is missing. Install or update App Installer from the Microsoft Store.'
}

Write-Host '==> Machine tools (.config/configuration.winget)'
$wingetArgs = @('configure', '--file', '.config/configuration.winget', '--disable-interactivity')
if ($AcceptConfigurationAgreements) {
    $wingetArgs += '--accept-configuration-agreements'
}
winget @wingetArgs
if ($LASTEXITCODE -ne 0) {
    throw "winget configure failed with exit code $LASTEXITCODE"
}

# Yukarıda kurulan araçlar yalnız yeni süreçlerin PATH'inde; bu oturum için yeniden yükle.
$env:Path = [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' +
    [Environment]::GetEnvironmentVariable('Path', 'User')

Write-Host '==> Project runtimes (mise.toml, global.json)'
mise install

if ($env:DOTFILES_REPO) {
    Write-Host '==> Dotfiles'
    if (Test-Path (Join-Path $HOME '.local/share/chezmoi')) {
        chezmoi update
    } else {
        chezmoi init --apply $env:DOTFILES_REPO
    }
}

Write-Host '==> Project setup'
mise exec -- just setup

Write-Host 'Done. Start Bookstore with: mise exec -- just dev'

İki Windows'a özgü ayrıntı var. Boş bir Windows'ta yalnız Windows PowerShell 5.1 bulunur; PowerShell 7'yi bu script kurduğu için başına #Requires -Version 7 koymak ilk çalıştırmayı imkânsız kılar. İkincisi, WinGet'in kurduğu araçlar o anda açık olan oturumun PATH (komutların arandığı klasör listesi) değişkenine girmez; script PATH'i makine ve kullanıcı değerlerinden yeniden okuyor. Script'i PowerShell 7.7'nin parser'ıyla ayrıştırdım, hata çıkmadı; $args gibi otomatik değişken adlarını da bu yüzden kullanmadım ($wingetArgs).

Boş Makineden just dev'e

Boş bir Windows makinesinde Bookstore'u çalıştırmak için gereken adımlar:

  • winget install --id Git.Git --exact ile Git'i kur; repo'yu klonlamak için ondan başka bir şeye gerek yok.
  • Repo'yu klonla ve .config/configuration.winget dosyasını oku.
  • Windows PowerShell'de .\scripts\bootstrap.ps1 çalıştır ve configuration uyarısını onayla.
  • Yeni bir terminal aç ve mise exec -- just dev ile API'yi ve Angular'ı başlat.

macOS'ta ilk adım Xcode Command Line Tools (xcode-select --install, Git'i getirir) ve Homebrew; sonrası ./scripts/bootstrap.sh ve aynı just dev. İlk adımlar bilerek elle: makineye paket yöneticisi kurmak geliştiricinin bilerek verdiği bir karar olmalı. Bootstrap da dev recipe'sini başlatmıyor, yalnız hazır hâle getiriyor.

Üretimde Dikkat

CI ile geliştirici makinesi aynı sürümleri kullanmalı. Serinin üçüncü yazısındaki workflow .NET SDK'yı global.json'dan okuyordu, ama Node.js sürümünü (24) ve just sürümünü (1.59.0) ayrıca yazıyordu; bu, mise.toml ile workflow'un ayrışabileceği iki yer demek. Ya workflow'da mise'nin kendi GitHub Action'ını kullanıp aynı dosyayı okut ya da en azından iki dosyayı aynı pull request'te değiştirmeyi kural yap.

Bootstrap'ı da test et. Brewfile ve configuration dosyası nadiren değişir, değiştiğinde de kimse yeni makine kurmadığı için hata aylarca fark edilmez. En az, her değişiklikte sözdizimi ve şema kontrolünü CI'da çalıştır (ruby -c Brewfile, configuration'ın şema doğrulaması). Belge, configuration dosyalarını gerçek makinede çalıştırmadan önce izole bir ortamda denemeyi öneriyor; Windows Sandbox bunun için uygun.

Sık Düşülen Tuzaklar

Birincisi "latest". Her şey en güncel sürümle kurulursa iki hafta arayla kurulan iki makine farklı SDK'larla derler ve "bende çalışıyor" konuşması başlar. Makine araçları için bu kabul edilebilir; proje runtime'ları için değil. Sürümler global.json ve mise.toml'da, repo'da ve review'dan geçerek değişmeli.

İkincisi internetten indirilen configuration'ı okumadan çalıştırmak. WinGet Configuration dosyası PowerShell Gallery'den modül indirip yönetici yetkisiyle çalıştırabilir; curl ... | sh ile çalıştırılan bir kurulum script'i de aynı riski taşır. Dosyayı oku, kaynağını kontrol et, mümkünse önce izole bir ortamda dene. mise'nin trust sorusu ve WinGet'in onay uyarısı bunun için var; alışkanlıkla otomatik onaylamak korumayı kapatır.

Üçüncüsü bootstrap'ı tek seferlik saymak. Script yalnız ilk gün çalışıyorsa bir sonraki yeni gelende bozuk çıkar. Tekrar çalıştırılabilir yazıldığı için onu bir proje güncellemesinden sonra da çalıştırabilirsin; yeni bir araç ya da sürüm eklendiğinde en kolay yol bu. Dördüncüsü kişisel ve ekip ayarlarını karıştırmak: ekibin Brewfile'ına kişisel uygulamalar, dotfiles'a ekibin araçları girmemeli.

Küçük Bir Deneme

★☆☆ Kendi projende "neyi hangi araç kurar" ayrımını dört satırlık bir tabloyla yaz. Hangi aracın sürümü proje için önemli, hangisininki değil?

★★☆ Bu yazıdaki bootstrap.sh'i stub script'lerle kendi klasöründe çalıştır, sonra bir stub'ın hata döndürmesini sağla (exit 1). set -euo pipefail sayesinde script'in nerede durduğunu ve hangi mesajın göründüğünü kontrol et.

★★★ Windows Sandbox'ta (ya da bir sanal makinede) Bookstore'u boş bir Windows'tan mise exec -- just dev aşamasına getir. Ne kadar sürdüğünü ve elle müdahale ettiğin her adımı not al; her biri script'e eklenecek bir aday.

Bu yazıyla Productivity ve Automation serisi bitiyor. Snippet'lerle tek dosyadaki kalıbı, dotnet new template'iyle yeni servisin iskeletini, justfile ile proje komutlarını, dotfiles ile kişisel ayarları ve bootstrap ile makinenin kendisini otomatikleştirdik. Ortak fikir hep aynıydı: tekrar eden işi repo'ya, versiyonlanan ve review edilen bir dosyaya taşımak. Bu dosyaların içeriğini geliştirmek için Terminal ve Command Line serisi shell tarafını, Bash Scripting serisi script yazmayı, AI Destekli Geliştirme serisi ise kod üretmenin başka bir yolunu anlatıyor.