होम ग्रेडल प्लगइन जो दो ओल्लामा प्रो इंस्टेंसेस को समानांतर में चलाता है
प्रकाशित: 08 May 2026
Docker कमांड्स को हाथ से क्यों टाइप करें जब आप उन्हें Gradle कार्यों में पूरी तरह से एंकैप्सुलेट कर सकते हैं? यहाँ मैं बताता हूँ कि मैंने अपना dual-Ollama Pro कॉन्फ़िगरेशन को शून्य बाहरी शेल स्क्रिप्ट के साथ कैसे औद्योगिक बनाया। स्टार्ट, स्टॉप, पहचान, साइन-इन, पुल — सब कुछ इससे गुजरता है।./gradlew.
परिचय
पिछले लेख में, मैंने दिखाया कि कैसे दो Ollama Pro खातों को एक ही मशीन पर coexist करवाया जा सकता है, दूसरे इंस्टेंस को एक Docker कंटेनर में अलग करके। सेटअप काम करता है, लेकिन दैनिक प्रबंधन जल्दी ही परेशान करने वाला बन जाता है: कंटेनर का नाम याद रखना, सही कमांड चलाना।docker exec, checker कौन सा पोर्ट सक्रिय है…
आप जानते हैं वह क्षण जब आपके पास 14 टर्मिनल खुले होते हैं और आप टाइप कर रहे होते हैं`docker exec ollama-instance-b ollama list`चौथी बार, और जब आप खुद से कहते हैं 'शायद कोई बेहतर तरीका होना चाहिए'?
यह तरीका, Gradle है
यह सिर्फ कोड बनाने के लिए नहीं है। Gradle स्थानीय बुनियादी ढाँचे का एक संचालक है। जो प्रावधान करते हैं, सत्यापित करते हैं, परीक्षण करते हैं और साफ़ करते हैं। सब कुछ एक में संस्करणित`build.gradle.kts`जो के पास रहता है`docker-compose.yml`.
@startuml
!theme plain
skinparam handwritten false
skinparam shadowing false
skinparam BoxPadding 8
skinparam DefaultFontSize 11
skinparam ArrowThickness 1.5
left to right direction
actor "डेवलपर" as Dev
package "build.gradle.kts" as GradleFile {
usecase "startOllamaB" as Start
usecase "stopOllamaB" as Stop
usecase "checkIdentity" as Check
usecase "signInB" as SignIn
usecase "pullModelB" as Pull
usecase "testOllamaB" as Test
usecase "statusOllamaAll" as Status
usecase "cleanOllamaB" as Clean
}
package "Docker डेमोन" as DockerDaemon {
node "ollama-instance-b" as ContainerB {
component ":11435" as Port
database "~/ollama-b-data" as Volume
}
}
package "Ollama नेतिव" as Native {
component ":11434" as NatPort
}
Dev --> Start
Dev --> SignIn
Dev --> Pull
Dev --> Test
Dev --> Status
Dev --> Check
Dev --> Stop
Dev --> Clean
Start ..> ContainerB : docker run -d
Stop ..> ContainerB : docker stop
Check ..> Volume : बिल्ली id_ed25519.pub
SignIn ..> ContainerB : ollama signin
Pull ..> ContainerB : ollama pull
Test ..> ContainerB : curl /v1/chat/completions
Status ..> ContainerB : docker ps
Status ..> Native : curl :11434
Clean ..> ContainerB : docker rm -f
note bottom of GradleFile
कोई बाहरी बश स्क्रिप्ट नहीं।
सभी चीज़ें build.gradle.kts में हैं,
प्रोजेक्ट के साथ संस्करणित।
end note
@enduml
इंफ़्रा के लिए Gradle क्यों?
प्रश्न वैध है। Gradle, यह एक बिल्ड टूल है। सामान्य रूप से, यह संकलित करता है, परीक्षण करता है, पैकेज करता है। यह डॉक्कर कंटेनर नहीं चलाता।
सिवाय इसके Gradle भी एक निर्भरता ग्राफ़ इंजन है — और यही वह जगह है जहाँ यह हमारे मामले के लिए दिलचस्प हो जाता है।
हम चाहते हैं :
-
साइन-इन से पहले कंटेनर को लॉन्च किया जाना चाहिए
-
पहचान की पुष्टि मॉडल पुल से पहले की जाए
-
यदि कंटेनर डाउन है तो एपीआई परीक्षण चुपचाप विफल हो जाता है।
शुद्ध निर्भरता ग्राफ़ से। Gradle इसे के लिए बनाया गया है। Kotlin DSL टास्क लिखने को सहज बनाता है, और`doLast`सिस्टम कमांड को एप्लिकेशन कोड की तरह चलाने की अनुमति देता है।
और सबसे बढ़कर: bashrc को बनाए रखने की ज़रूरत नहीं, कोई स्क्रिप्ट नहीं जो पड़ी हो`~/bin`, Makefile के बगल में docker-compose नहीं है। सब कुछ एक ही फ़ाइल में रहता है, प्रोजेक्ट के कोड के समान स्थान पर.
पूर्ण build.gradle.kts
यहाँ वह फ़ाइल है जो मैंने अंततः लिखी। यह सब कुछ करती है, वॉल्यूम प्रावधान से लेकर सत्यापन के लिए curl तक, और बातचीत करने वाले साइन-इन के बीच में।
import java.io.ByteArrayOutputStream
plugins {
base
}
// -------------------------------------------------------------------------
// Configuration
// -------------------------------------------------------------------------
val instanceName = "ollama-instance-b"
val instancePort = 11435
val instanceImage = "ollama/ollama:0.20.2"
val dataDir = file("${System.getProperty("user.home")}/ollama-b-data")
val nativePort = 11434
// Modèles à gérer — cloud Pro + un modèle local gratuit pour les tests
val proModels = listOf("deepseek-v4-pro:cloud", "gemma4:31b-cloud")
val freeModels = listOf("qwen3:0.6b")
// -------------------------------------------------------------------------
// Tâche préparatoire — création du volume si absent
// -------------------------------------------------------------------------
val prepareVolume by tasks.registering {
group = "ollama"
description = "Crée le répertoire de volume pour l'instance B si absent"
doLast {
if (!dataDir.exists()) {
dataDir.mkdirs()
println("Volume créé : ${dataDir.absolutePath}")
} else {
println("Volume existant : ${dataDir.absolutePath}")
}
}
}
// -------------------------------------------------------------------------
// Démarrage du conteneur Docker
// -------------------------------------------------------------------------
val startOllamaB by tasks.registering(Exec::class) {
group = "ollama"
description = "Lance le conteneur Docker pour l'instance Ollama B"
dependsOn(prepareVolume)
commandLine(
"docker", "run", "-d",
"--name", instanceName,
"-p", "$instancePort:11434",
"-v", "${dataDir.absolutePath}:/root/.ollama",
"-e", "OLLAMA_HOST=0.0.0.0",
"--restart", "always",
instanceImage
)
isIgnoreExitValue = true
doLast {
val alreadyRunning = executionResult.get().exitValue != 0
if (alreadyRunning) {
logger.lifecycle("Le conteneur '$instanceName' existe déjà — tentative de démarrage")
} else {
logger.lifecycle("Conteneur '$instanceName' lancé")
}
}
}
// -------------------------------------------------------------------------
// Arrêt du conteneur
// -------------------------------------------------------------------------
val stopOllamaB by tasks.registering(Exec::class) {
group = "ollama"
description = "Arrête le conteneur Docker de l'instance B"
commandLine("docker", "stop", instanceName)
isIgnoreExitValue = true
doLast {
logger.lifecycle("Conteneur '$instanceName' arrêté")
}
}
// -------------------------------------------------------------------------
// Affichage de la clé publique (Device Key)
// -------------------------------------------------------------------------
val checkIdentity by tasks.registering(Exec::class) {
group = "ollama"
description = "Affiche la clé publique SSH de l'instance B"
dependsOn(startOllamaB)
commandLine("docker", "exec", instanceName, "cat", "/root/.ollama/id_ed25519.pub")
standardOutput = ByteArrayOutputStream()
doLast {
val pubKey = standardOutput.toString().trim()
println("=".repeat(60))
println("Device Key (Instance B)")
println("=".repeat(60))
println(pubKey)
println("=".repeat(60))
println("→ Enregistre cette clé sur https://ollama.com/settings/keys")
println("→ Puis exécute : ./gradlew signInB")
println("=".repeat(60))
}
}
// -------------------------------------------------------------------------
// Sign-in interactif (Ouvrira le navigateur)
// -------------------------------------------------------------------------
val signInB by tasks.registering(Exec::class) {
group = "ollama"
description = "Lance l'authentification interactive Ollama pour le compte B"
dependsOn(startOllamaB)
standardInput = System.`in`
standardOutput = System.out
errorOutput = System.err
commandLine("docker", "exec", "-it", instanceName, "ollama", "signin")
doFirst {
logger.lifecycle("Authentification pour le Compte Pro B...")
logger.lifecycle("Ouvre ton navigateur et connecte-toi avec l'email du compte B")
}
}
// -------------------------------------------------------------------------
// Pull d'un modèle (paramétrable via propriété Gradle)
// -------------------------------------------------------------------------
val pullModelB by tasks.registering(Exec::class) {
group = "ollama"
description = "Pull un modèle sur l'instance B. Usage : ./gradlew pullModelB -Pmodel=nom:tag"
dependsOn(startOllamaB)
val modelName = project.findProperty("model") as? String ?: "qwen3:0.6b"
commandLine("docker", "exec", instanceName, "ollama", "pull", modelName)
doFirst {
logger.lifecycle("Pull du modèle '$modelName' sur l'instance B...")
}
doLast {
logger.lifecycle("Modèle '$modelName' pullé avec succès")
}
}
// -------------------------------------------------------------------------
// Pull de tous les modèles Pro (batch)
// -------------------------------------------------------------------------
val pullAllProModels by tasks.registering {
group = "ollama"
description = "Pull tous les modèles cloud Pro sur l'instance B"
dependsOn(startOllamaB)
doLast {
proModels.forEach { model ->
logger.lifecycle("→ Pull $model...")
val process = ProcessBuilder("docker", "exec", instanceName, "ollama", "pull", model)
.inheritIO()
.start()
process.waitFor()
}
logger.lifecycle("Tous les modèles Pro pullés")
}
}
// -------------------------------------------------------------------------
// Pull de tous les modèles gratuits (batch / première install)
// -------------------------------------------------------------------------
val pullAllFreeModels by tasks.registering {
group = "ollama"
description = "Pull tous les modèles locaux gratuits sur l'instance B"
dependsOn(startOllamaB)
doLast {
freeModels.forEach { model ->
logger.lifecycle("→ Pull $model...")
val process = ProcessBuilder("docker", "exec", instanceName, "ollama", "pull", model)
.inheritIO()
.start()
process.waitFor()
}
logger.lifecycle("Tous les modèles gratuits pullés")
}
}
// -------------------------------------------------------------------------
// Liste des modèles disponibles sur l'instance B
// -------------------------------------------------------------------------
val listModelsB by tasks.registering(Exec::class) {
group = "ollama"
description = "Liste les modèles disponibles sur l'instance B"
dependsOn(startOllamaB)
commandLine("docker", "exec", instanceName, "ollama", "list")
}
// -------------------------------------------------------------------------
// Health check — test API sur les deux instances
// -------------------------------------------------------------------------
val testOllamaB by tasks.registering {
group = "ollama"
description = "Teste la connectivité API de l'instance B avec un simple chat"
dependsOn(startOllamaB)
doLast {
val model = project.findProperty("model") as? String ?: "qwen3:0.6b"
logger.lifecycle("Test de l'instance B avec le modèle '$model'...")
val payload = """
{
"model": "$model",
"messages": [{"role": "user", "content": "Réponds uniquement par OK"}],
"max_tokens": 5
}
""".trimIndent()
val process = ProcessBuilder(
"curl", "-s", "http://localhost:$instancePort/v1/chat/completions",
"-H", "Content-Type: application/json",
"-d", payload
).start()
val response = process.inputStream.bufferedReader().readText()
process.waitFor()
if (response.contains("\"id\":\"chatcmpl") || response.contains("\"choices\"")) {
println("✓ Instance B OK — port $instancePort répond")
println(" Response: ${response.take(120)}...")
} else {
println("✗ Instance B INACTIVE — vérifie le conteneur avec './gradlew statusOllamaAll'")
println(" Raw: $response")
}
}
}
// -------------------------------------------------------------------------
// Health check sur les deux instances simultanément
// -------------------------------------------------------------------------
val statusOllamaAll by tasks.registering {
group = "ollama"
description = "Vérifie le statut des deux instances Ollama (native + Docker)"
doLast {
// Instance native
runCatching {
val process = ProcessBuilder(
"curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
"http://localhost:$nativePort/api/tags"
).start()
val code = process.inputStream.bufferedReader().readText().trim()
process.waitFor()
println(if (code == "200") "✓ Instance Native — port $nativePort OK (HTTP $code)"
else "✗ Instance Native — port $nativePort (HTTP $code)")
}.getOrElse {
println("✗ Instance Native — injoignable sur le port $nativePort")
}
// Instance Docker
runCatching {
val process = ProcessBuilder(
"curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
"http://localhost:$instancePort/api/tags"
).start()
val code = process.inputStream.bufferedReader().readText().trim()
process.waitFor()
println(if (code == "200") "✓ Instance Docker B — port $instancePort OK (HTTP $code)"
else "✗ Instance Docker B — port $instancePort (HTTP $code)")
}.getOrElse {
println("✗ Instance Docker B — injoignable sur le port $instancePort")
}
// Info conteneur
runCatching {
val process = ProcessBuilder("docker", "ps", "--filter", "name=$instanceName",
"--format", "table {{.Names}}\\t{{.Status}}\\t{{.Ports}}").start()
val status = process.inputStream.bufferedReader().readText()
process.waitFor()
println()
println("Conteneur Docker :")
println(status)
}
}
}
// -------------------------------------------------------------------------
// Nettoyage complet
// -------------------------------------------------------------------------
val cleanOllamaB by tasks.registering(Exec::class) {
group = "ollama"
description = "Arrête et supprime le conteneur Docker de l'instance B"
dependsOn(stopOllamaB)
commandLine("docker", "rm", instanceName)
isIgnoreExitValue = true
doLast {
logger.lifecycle("Conteneur '$instanceName' supprimé")
logger.lifecycle("Le volume ${dataDir.absolutePath} est conservé (identité persistante)")
}
}
// -------------------------------------------------------------------------
// Affichage des instructions
// -------------------------------------------------------------------------
val helpOllama by tasks.registering {
group = "ollama"
description = "Affiche l'aide des tâches Ollama disponibles"
doLast {
println("""
╔══════════════════════════════════════════════════════════════╗
║ Tâches Gradle — Pilotage Dual Ollama Pro ║
╠══════════════════════════════════════════════════════════════╣
║ ./gradlew startOllamaB Démarrer l'instance Docker B ║
║ ./gradlew stopOllamaB Arrêter l'instance Docker B ║
║ ./gradlew checkIdentity Afficher la clé SSH publique ║
║ ./gradlew signInB Sign-in interactif compte B ║
║ ./gradlew pullModelB Pull un modèle (-Pmodel=...) ║
║ ./gradlew pullAllProModels Pull tous les modèles Pro ║
║ ./gradlew pullAllFreeModels Pull tous les modèles gratuits ║
║ ./gradlew listModelsB Lister les modèles instance B ║
║ ./gradlew testOllamaB Test API avec un chat simple ║
║ ./gradlew statusOllamaAll Santé des 2 instances ║
║ ./gradlew cleanOllamaB Supprimer le conteneur ║
║ ./gradlew helpOllama Cette aide ║
╚══════════════════════════════════════════════════════════════╝
""".trimIndent())
}
}
यहाँ। बारह कार्य, एक एकल प्रवेश बिंदु, और कोई घर्षण नहीं।
मुख्य कार्यों की अनाटॉमी
startOllamaB` — लॉन्चर
कोई जादू नहीं: एक`docker run -d`सही सेटिंग्स के साथ. ट्रिक में है`isIgnoreExitValue = true`.
isIgnoreExitValue = true
यदि कंटेनर पहले से ही मौजूद है, कमांड`docker run`असफल हो जाता है (exit code ≠ 0)। इस संपत्ति के बिना, Gradle पूरे बिल्ड को त्रुटि में रोक देगा। यहाँ, विफलता चुपचाप है — और उस`doLast`यह जाँचता है कि क्या यह 'पहले से मौजूद' था या एक वास्तविक त्रुटि।
Le `doLast`यह बाद में कमांड के निष्पादन के बाद चलता है, जिससे निरीक्षण करना संभव होता है`executionResult.get().exitValue`और एक उपयुक्त संदेश लॉग करना
signInB` — इंटरैक्टिव
यह एकमात्र कार्य है जो मानव बातचीत की आवश्यकता रखता है। Gradle भेजता है`System.in`प्रक्रिया के लिए ताकि प्रॉम्प्ट`ollama signin`सामान्य रूप से प्रदर्शित हो सके :
standardInput = System.`in`
standardOutput = System.out
errorOutput = System.err
उसके बिना, साइन-इन ब्लॉक हो जाएगा क्योंकि stdin बंद हो जाएगा और आप कभी भी प्रॉम्प्ट नहीं देखेंगे।
pullModelB` — पैरामीट्रेबल
एक सामान्य कार्य जो एक पैरामीटर के माध्यम से स्वीकार करता है`-P`:
./gradlew pullModelB -Pmodel=deepseek-v4-pro:cloud
./gradlew pullModelB # fallback : qwen3:0.6b
फ़ॉलबैक महत्वपूर्ण है :`project.findProperty("model") as? String ?: "qwen3:0.6b"`यह सुनिश्चित करता है कि अगर हम पैरामीटर भूल जाएं तो काम बिगड़ नहीं जाता।
statusOllamaAll` — निदान
दो`curl`पोर्ट्स पर स्वास्थ्य`11434` et 11435, एक फ़ॉलबैक के साथ`runCatching`किसी भी इंस्टेंस के डाउन होने पर क्रैश न होने के लिए। आउटपुट संरचित है:
✓ Instance Native — port 11434 OK (HTTP 200)
✓ Instance Docker B — port 11435 OK (HTTP 200)
Conteneur Docker :
NAMES STATUS PORTS
ollama-instance-b Up 3 hours 0.0.0.0:11435->11434/tcp
एक नज़र में, तुम ठीक-ठीक जान जाते हो कि क्या घूम रहा है और क्या गड़बड़ है।
cleanOllamaB` — वह सफ़ाई जो पहचान को संरक्षित रखती है
commandLine("docker", "rm", instanceName)
isIgnoreExitValue = true
कंटेनर हटाया गया है, लेकिन वॉल्यूम`~/ollama-b-data`हैसंरक्षित. SSH पहचान सफाई के बाद भी जीवित रहती है। अगले`startOllamaB`, समान कुंजियाँ फिर से उपयोग की जाएँगी — साइन-इन करने की आवश्यकता नहीं है, और डिवाइस की को फिर से पंजीकृत करने की भी आवश्यकता नहीं है।
|
|
पूर्ण कार्यप्रवाह — शून्य से दो सत्रों तक
यह प्रोजेक्ट पर आए किसी भी व्यक्ति के लिए आदर्श अनुक्रम है, जो दस्तावेज़ खोलें बिना सब कुछ सेट अप करना चाहता है:
# 1. Premier contact — que faire ?
$ ./gradlew helpOllama
# 2. On démarre l'instance B
$ ./gradlew startOllamaB
# 3. On récupère la Device Key et on l'enregistre sur ollama.com
$ ./gradlew checkIdentity
# → copier-coller de la clé sur https://ollama.com/settings/keys
# 4. Authentification interactive
$ ./gradlew signInB
# 5. Pull d'un petit modèle gratuit pour tester la plomberie
$ ./gradlew pullModelB
# 6. Test API
$ ./gradlew testOllamaB
# 7. Si tout est vert, pull des modèles Pro
$ ./gradlew pullAllProModels
# 8. Vérification finale des deux instances
$ ./gradlew statusOllamaAll
कम से कम दो मिनट में, सब कुछ प्रावधानित है। कोई README पढ़ने की ज़रूरत नहीं, कोई पर्यावरण चर सेट करने की ज़रूरत नहीं। कार्य`helpOllama`एकीकृत दस्तावेज़ के रूप में कार्य करता है।
@startuml !theme plain skinparam DefaultFontSize 11 actor Développeur as Dev participant "Gradle\n(build.gradle.kts)" as Gradle participant "Docker\nडेमोन" as Docker participant "Ollama\nइंस्टेंस बी" as OllamaB participant "Ollama\nक्लाउड" as Cloud == Provisionnement == Dev -> Gradle : helpOllama Gradle --> Dev : सहायता और निर्देश Dev -> Gradle : startOllamaB Gradle -> Docker : docker run -d ollama/ollama:0.20.2 Docker -> OllamaB : जेनरेशन की SSH (id_ed25519) Docker --> Gradle : कंटेनर लांच किया गया Gradle --> Dev : OK Dev -> Gradle : checkIdentity Gradle -> Docker : बिल्ली id_ed25519.pub Gradle --> Dev : सार्वजनिक कुंजी SSH Dev -> Cloud : की रिकॉर्ड SSH\n(पर ollama.com/settings/keys) == Authentification == Dev -> Gradle : signInB Gradle -> Docker : ollama साइन इन (इंटरैक्टिव) Dev <-> Cloud : लॉग इन ब्राउज़र (खाता बी) Docker -> Cloud : Échange token ⇔ Device Key Gradle --> Dev : प्रमाणित == Vérification == Dev -> Gradle : pullModelB Gradle -> Docker : ollama pull qwen3:0.6b Gradle --> Dev : पुल किया गया मॉडल Dev -> Gradle : testOllamaB Gradle -> Docker : curl /v1/chat/completions Gradle --> Dev : ✓ उदाहरण बी ओके == Production == Dev -> Gradle : pullAllProModels Gradle -> Docker : ollama pull deepseek-v4-pro:cloud Gradle -> Docker : ollama pull gemma4:31b-cloud Gradle --> Dev : खींचे गए प्रो मॉडल्स Dev -> Gradle : statusOllamaAll Gradle --> Dev : ✓ Native ✓ Docker B @enduml
क्यों न इसे एक वास्तविक ग्रैडल प्लगइन बनाएं?
वैध प्रश्न। इसे निकालें`build.gradle.kts`एक स्वतंत्र Gradle प्लगिन के फायदे हैं: परियोजनाओं के बीच पुनः उपयोग, घोषणात्मक कॉन्फ़िगरेशन, DSL विस्तार
अभी के लिए, यह कोई अर्थ नहीं रखता। यह बिल्ड में कोई जटिल व्यावसायिक तर्क नहीं है — यह Docker और curl कमांडों के आसपास एक रैपर है। पूरी Gradle प्लगिन परत (एक्सटेंशन, यूनिट टेस्ट, प्रकाशन के साथ) 12 उपयोगितार्थ कार्यों के लिए अनुपातहीन होगी।
Le `build.gradle.kts`inline आवश्यक परियोजना में रहता है। इसे अन्य जगह कॉपी करना तुच्छ है। यदि एक दिन मेरे पास तीन परियोजनाएँ हैं जो इस पर निर्भर हैं, तो मैं इसे एक प्लगइन में बदल दूँगा। अभी के लिए, KISS.
|
वह दिन जब तेरे पास तीन हैं`build.gradle.kts`तीन परियोजनाओं में समान, यह संकेत है कि प्लगइन को निकाला जाना चाहिए। इससे पहले, डुप्लिकेट करना अमूर्तिकरण की तुलना में कम खर्चीला होता है। विकासकर्ता की समझदारी। |
सीखे गए सबक
-
Gradle केवल एक बिल्ड टूल नहीं है— निर्भरता ग्राफ़ इंजन इसे एक खतरनाक बुनियादी ढाँचे का ऑर्केस्ट्रेटर बनाता है।
doLast,isIgnoreExitValue, `dependsOn`ये तीन प्राइमिटिव्स डॉकर कंटेनर को चलाने के लिए पर्याप्त हैं, जिनकी विश्वसनीयता CI पाइपलाइन के समान है। -
isIgnoreExitValue` आपका मित्र है— एक आदर्श दुनिया में,
docker run`पहले से मौजूद कंटेनर पर एक चेतावनी लौटेगा, एक त्रुटि exit कोड के बजाय। वास्तविक दुनिया में, तुम रखते हो`isIgnoreExitValue = true`और आप निदान में प्रबंधन करते हैं`doLast. -
पैरामीटर करने योग्य कार्य अस्पष्टता को मारते हैं — Un
pullModelB`सामान्य के साथ-Pmodel=…`N विशेषज्ञ कार्यों को बदलता है। एक ही कार्य, एक ही दस्तावेज़, एक ही व्यवहार। -
runCatching` हेल्थ चेक्स के लिएजब आप N endpoints का परीक्षण करते हैं, तो पहले के क्रैश के कारण अन्य का परीक्षण बंद नहीं होना चाहिए`runCatching`+`getOrElse`प्रति एन्डपॉइंट, और निदान आंशिक विफलता के मामले में भी पूरा रहता है।
-
कार्य
helpOllamaनिष्पादन योग्य दस्तावेज़ के रूप में — Un `println()`अच्छे से स्वरूपित किया गया एक तीन‑पैराग्राफ़ README को बदल देता है। यह स्व‑डॉक्यूमेंटेड, हमेशा अप‑टू‑डेट और टर्मिनल छोड़े बिना पहुँचा जा सकता है।
निष्कर्ष
हम मैन्युअल रूप से टाइप की गई docker कमांडों से एक पूर्ण Gradle वर्कफ़्लो में स्थानांतरित हो गए, जो संस्करणबद्ध और पुन: उपयोग करने योग्य है। बारह कार्य पूरे जीवन चक्र को कवर करते हैं: प्रावधान, पहचान, साइन-इन, पुल, टेस्ट, निदान, सफाई।
docker-compose + build.gradle.kts + opencode.json का कॉम्बो एक स्वच्छ त्रिक बनाता है: Docker अलगाव के लिए, Gradle ऑर्केस्ट्रेशन के लिए, OpenCode उपभोग के लिए। प्रत्येक परत की अपनी जिम्मेदारी होती है, और कोई भी एक-दूसरे पर अतिक्रमण नहीं करता।
अगला चरण क्या है? CI में जीवन चक्र को स्वचालित करें ताकि Ollama इंस्टेंस अस्थायी विकास वातावरण में उपलब्ध हो सकें। लेकिन यह, एक और लेख है।