From bde958e45b9db3ac9db887beb3278afbca2009a6 Mon Sep 17 00:00:00 2001
From: Friedrich von Never <friedrich@fornever.me>
Date: Wed, 20 Dec 2023 21:53:18 +0100
Subject: [PATCH 1/6] ctor: add deployment skeleton

---
 .gitignore                  |  4 ++++
 Codingteam.Devops.sln       | 27 +++++++++++++++++++++++++++
 ctor/Codingteam.Ctor.fsproj | 12 ++++++++++++
 ctor/Program.fs             |  2 ++
 4 files changed, 45 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Codingteam.Devops.sln
 create mode 100644 ctor/Codingteam.Ctor.fsproj
 create mode 100644 ctor/Program.fs

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4467200
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/.idea/
+
+bin/
+obj/
diff --git a/Codingteam.Devops.sln b/Codingteam.Devops.sln
new file mode 100644
index 0000000..2c816d8
--- /dev/null
+++ b/Codingteam.Devops.sln
@@ -0,0 +1,27 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Codingteam.Ctor", "ctor\Codingteam.Ctor.fsproj", "{E08EC4D8-5FD7-45CB-B48A-689005546B10}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A37A2B6B-F09B-4105-8AA5-47BA5D74B68A}"
+	ProjectSection(SolutionItems) = preProject
+		.gitignore = .gitignore
+	EndProjectSection
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{E08EC4D8-5FD7-45CB-B48A-689005546B10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E08EC4D8-5FD7-45CB-B48A-689005546B10}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E08EC4D8-5FD7-45CB-B48A-689005546B10}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E08EC4D8-5FD7-45CB-B48A-689005546B10}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal
diff --git a/ctor/Codingteam.Ctor.fsproj b/ctor/Codingteam.Ctor.fsproj
new file mode 100644
index 0000000..5a3c697
--- /dev/null
+++ b/ctor/Codingteam.Ctor.fsproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="Program.fs" />
+  </ItemGroup>
+
+</Project>
diff --git a/ctor/Program.fs b/ctor/Program.fs
new file mode 100644
index 0000000..4d0a957
--- /dev/null
+++ b/ctor/Program.fs
@@ -0,0 +1,2 @@
+printfn "TODO: Deployment"
+exit 1

From fc166af27ed4c5b8d8df11578398c3db9da33056 Mon Sep 17 00:00:00 2001
From: Friedrich von Never <friedrich@fornever.me>
Date: Wed, 20 Dec 2023 21:56:38 +0100
Subject: [PATCH 2/6] Deployment: add a rudimentary test

---
 .editorconfig              |  3 +++
 .github/workflows/main.yml | 55 ++++++++++++++++++++++++++++++++++++++
 Codingteam.Devops.sln      | 11 ++++++++
 3 files changed, 69 insertions(+)
 create mode 100644 .github/workflows/main.yml

diff --git a/.editorconfig b/.editorconfig
index 2225fd9..e2139a6 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -6,3 +6,6 @@ indent_style = space
 indent_size = 4
 trim_trailing_whitespace = true
 insert_final_newline = true
+
+[*.yml]
+indent_size = 2
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..2f675b3
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,55 @@
+name: Main
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+  schedule:
+    - cron: '0 0 * * 6'
+
+  workflow_dispatch:
+
+jobs:
+  main:
+    strategy:
+      fail-fast: false
+      matrix:
+        config:
+          - name: 'macos'
+            image: 'macos-12'
+          - name: 'linux'
+            image: 'ubuntu-22.04'
+          - name: 'windows'
+            image: 'windows-2022'
+
+    name: main.${{ matrix.config.name }}
+    runs-on: ${{ matrix.config.image }}
+
+    # noinspection SpellCheckingInspection
+    env:
+      DOTNET_NOLOGO: 1
+      DOTNET_CLI_TELEMETRY_OPTOUT: 1
+      NUGET_PACKAGES: ${{ github.workspace }}/.github/nuget-packages
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: NuGet cache
+        uses: actions/cache@v3
+        with:
+          path: ${{ env.NUGET_PACKAGES }}
+          key: ${{ runner.os }}.nuget.${{ hashFiles('**/*.fsproj') }}
+
+      - name: Set up .NET SDK
+        uses: actions/setup-dotnet@v3
+        with:
+          dotnet-version: '8.0.x'
+
+      - name: Build
+        run: dotnet build
+
+      - name: Test
+        run: dotnet run --project ctor -- verify
diff --git a/Codingteam.Devops.sln b/Codingteam.Devops.sln
index 2c816d8..ac65655 100644
--- a/Codingteam.Devops.sln
+++ b/Codingteam.Devops.sln
@@ -8,6 +8,14 @@ EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A37A2B6B-F09B-4105-8AA5-47BA5D74B68A}"
 	ProjectSection(SolutionItems) = preProject
 		.gitignore = .gitignore
+		.editorconfig = .editorconfig
+	EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{C4672A21-FB21-4BBF-A0A1-512AC1B66C9A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{67164D20-A669-4BC3-A40E-751D3045BA89}"
+	ProjectSection(SolutionItems) = preProject
+		.github\workflows\main.yml = .github\workflows\main.yml
 	EndProjectSection
 EndProject
 Global
@@ -24,4 +32,7 @@ Global
 		{E08EC4D8-5FD7-45CB-B48A-689005546B10}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{E08EC4D8-5FD7-45CB-B48A-689005546B10}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{67164D20-A669-4BC3-A40E-751D3045BA89} = {C4672A21-FB21-4BBF-A0A1-512AC1B66C9A}
+	EndGlobalSection
 EndGlobal

From 19db3aee0b7ca486111674980ce8ea45e8af0d93 Mon Sep 17 00:00:00 2001
From: Friedrich von Never <friedrich@fornever.me>
Date: Wed, 20 Dec 2023 22:00:09 +0100
Subject: [PATCH 3/6] Deployment: add a Fabricator dependency (temporarily as a
 submodule)

---
 .gitmodules           |  3 +++
 Codingteam.Devops.sln | 16 ++++++++++++++++
 Fabricator            |  1 +
 3 files changed, 20 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 Fabricator

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..83afccf
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "Fabricator"]
+	path = Fabricator
+	url = git@github.com:ForNeVeR/Fabricator.git
diff --git a/Codingteam.Devops.sln b/Codingteam.Devops.sln
index ac65655..d275ce7 100644
--- a/Codingteam.Devops.sln
+++ b/Codingteam.Devops.sln
@@ -18,6 +18,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
 		.github\workflows\main.yml = .github\workflows\main.yml
 	EndProjectSection
 EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabricator.Console", "Fabricator\Fabricator.Console\Fabricator.Console.fsproj", "{0157906C-006C-45F8-A79A-0D55F16B16B2}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Fabricator", "Fabricator", "{853E359A-18C5-4472-A712-D5C80EE99D6B}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabricator.Core", "Fabricator\Fabricator.Core\Fabricator.Core.fsproj", "{C57439E9-20AF-4EEB-8C91-5D74D346374B}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -31,8 +37,18 @@ Global
 		{E08EC4D8-5FD7-45CB-B48A-689005546B10}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E08EC4D8-5FD7-45CB-B48A-689005546B10}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{E08EC4D8-5FD7-45CB-B48A-689005546B10}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0157906C-006C-45F8-A79A-0D55F16B16B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0157906C-006C-45F8-A79A-0D55F16B16B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0157906C-006C-45F8-A79A-0D55F16B16B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0157906C-006C-45F8-A79A-0D55F16B16B2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C57439E9-20AF-4EEB-8C91-5D74D346374B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C57439E9-20AF-4EEB-8C91-5D74D346374B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C57439E9-20AF-4EEB-8C91-5D74D346374B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C57439E9-20AF-4EEB-8C91-5D74D346374B}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(NestedProjects) = preSolution
 		{67164D20-A669-4BC3-A40E-751D3045BA89} = {C4672A21-FB21-4BBF-A0A1-512AC1B66C9A}
+		{0157906C-006C-45F8-A79A-0D55F16B16B2} = {853E359A-18C5-4472-A712-D5C80EE99D6B}
+		{C57439E9-20AF-4EEB-8C91-5D74D346374B} = {853E359A-18C5-4472-A712-D5C80EE99D6B}
 	EndGlobalSection
 EndGlobal
diff --git a/Fabricator b/Fabricator
new file mode 160000
index 0000000..429cd9a
--- /dev/null
+++ b/Fabricator
@@ -0,0 +1 @@
+Subproject commit 429cd9a8c882ece78e43c1108e094a1d5b598576

From 373effdab9057f80244480e9fd31f9f3d5f1bc8c Mon Sep 17 00:00:00 2001
From: Friedrich von Never <friedrich@fornever.me>
Date: Wed, 20 Dec 2023 23:36:03 +0100
Subject: [PATCH 4/6] Deployment: preliminary GreenCaptchaBot schema

---
 .gitignore                         |  2 ++
 Codingteam.Devops.sln              |  7 +++++++
 Codingteam.Devops.sln.DotSettings  |  2 ++
 ctor/Codingteam.Ctor.fsproj        |  7 +++++++
 ctor/GreenCaptchaBot.fs            | 21 +++++++++++++++++++++
 ctor/GreenCaptchaBot.template.json |  6 ++++++
 ctor/Program.fs                    | 20 ++++++++++++++++++--
 7 files changed, 63 insertions(+), 2 deletions(-)
 create mode 100644 Codingteam.Devops.sln.DotSettings
 create mode 100644 ctor/GreenCaptchaBot.fs
 create mode 100644 ctor/GreenCaptchaBot.template.json

diff --git a/.gitignore b/.gitignore
index 4467200..90e413f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
 
 bin/
 obj/
+
+*.private.json
diff --git a/Codingteam.Devops.sln b/Codingteam.Devops.sln
index d275ce7..324521f 100644
--- a/Codingteam.Devops.sln
+++ b/Codingteam.Devops.sln
@@ -24,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Fabricator", "Fabricator",
 EndProject
 Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabricator.Core", "Fabricator\Fabricator.Core\Fabricator.Core.fsproj", "{C57439E9-20AF-4EEB-8C91-5D74D346374B}"
 EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabricator.Resources", "Fabricator\Fabricator.Resources\Fabricator.Resources.fsproj", "{414C5063-3201-43AC-9DA1-B18A18802C0D}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -45,10 +47,15 @@ Global
 		{C57439E9-20AF-4EEB-8C91-5D74D346374B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{C57439E9-20AF-4EEB-8C91-5D74D346374B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{C57439E9-20AF-4EEB-8C91-5D74D346374B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{414C5063-3201-43AC-9DA1-B18A18802C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{414C5063-3201-43AC-9DA1-B18A18802C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{414C5063-3201-43AC-9DA1-B18A18802C0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{414C5063-3201-43AC-9DA1-B18A18802C0D}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(NestedProjects) = preSolution
 		{67164D20-A669-4BC3-A40E-751D3045BA89} = {C4672A21-FB21-4BBF-A0A1-512AC1B66C9A}
 		{0157906C-006C-45F8-A79A-0D55F16B16B2} = {853E359A-18C5-4472-A712-D5C80EE99D6B}
 		{C57439E9-20AF-4EEB-8C91-5D74D346374B} = {853E359A-18C5-4472-A712-D5C80EE99D6B}
+		{414C5063-3201-43AC-9DA1-B18A18802C0D} = {853E359A-18C5-4472-A712-D5C80EE99D6B}
 	EndGlobalSection
 EndGlobal
diff --git a/Codingteam.Devops.sln.DotSettings b/Codingteam.Devops.sln.DotSettings
new file mode 100644
index 0000000..2f3a410
--- /dev/null
+++ b/Codingteam.Devops.sln.DotSettings
@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/ctor/Codingteam.Ctor.fsproj b/ctor/Codingteam.Ctor.fsproj
index 5a3c697..6ab6cae 100644
--- a/ctor/Codingteam.Ctor.fsproj
+++ b/ctor/Codingteam.Ctor.fsproj
@@ -6,7 +6,14 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <Compile Include="GreenCaptchaBot.fs" />
     <Compile Include="Program.fs" />
+    <Content Include="GreenCaptchaBot.template.json" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Fabricator\Fabricator.Console\Fabricator.Console.fsproj" />
+    <ProjectReference Include="..\Fabricator\Fabricator.Resources\Fabricator.Resources.fsproj" />
   </ItemGroup>
 
 </Project>
diff --git a/ctor/GreenCaptchaBot.fs b/ctor/GreenCaptchaBot.fs
new file mode 100644
index 0000000..de55876
--- /dev/null
+++ b/ctor/GreenCaptchaBot.fs
@@ -0,0 +1,21 @@
+module internal Codingteam.Ctor.GreenCaptchaBot
+
+open Fabricator.Core
+open Fabricator.Resources.Files
+
+let private hostConfigDirectory = "/opt/green-captcha-bot/"
+let private docker = docker {
+    let version = "v1.15.1"
+    fromSources <| sources {
+        gitRepository "https://github.com/ImoutoChan/GreenCaptchaBot.git"
+        reference $"tags/{version}"
+    }
+    withDockerfile "CaptchaBot/Dockerfile"
+    withTag version
+    withName "green-captcha-bot"
+    withVolume(hostPath = hostConfigDirectory, containerPath = "/app/Configuration")
+}
+
+let private configFile = FileResource(templatedFile "GreenCaptchaBot.template.json", $"{hostConfigDirectory}/appsettings.json")
+
+let resources: IResource[] = [| configFile; docker |]
diff --git a/ctor/GreenCaptchaBot.template.json b/ctor/GreenCaptchaBot.template.json
new file mode 100644
index 0000000..b42143b
--- /dev/null
+++ b/ctor/GreenCaptchaBot.template.json
@@ -0,0 +1,6 @@
+{
+    "Configuration": {
+        "BotToken": "$BOT_TOKEN",
+        "DeleteJoinMessages": "Unsuccessful"
+    }
+}
diff --git a/ctor/Program.fs b/ctor/Program.fs
index 4d0a957..2359df6 100644
--- a/ctor/Program.fs
+++ b/ctor/Program.fs
@@ -1,2 +1,18 @@
-printfn "TODO: Deployment"
-exit 1
+open Codingteam.Ctor
+open Fabricator.Console
+open Fabricator.Core
+
+let private cluster mode =
+    let connectionsFileName = if mode = RunMode.Verify then "connections.stub.json" else "connections.private.json"
+    [|
+        {
+            Name = "ctor"
+            Designator = Designators.fromConnectionsFile connectionsFileName "ctor"
+            Resources = GreenCaptchaBot.resources
+            Type = MachineType.Linux
+        }
+    |]
+
+let main (args: string[]): int =
+    let cluster =
+    EntryPoint.main args cluster

From aea03c5443686954888ae41f82cc68a553557859 Mon Sep 17 00:00:00 2001
From: Friedrich von Never <friedrich@fornever.me>
Date: Sat, 23 Dec 2023 00:09:27 +0100
Subject: [PATCH 5/6] Deployment: improve the docker specifications

---
 ctor/GreenCaptchaBot.fs | 22 +++++++++++++---------
 ctor/Program.fs         |  1 -
 2 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/ctor/GreenCaptchaBot.fs b/ctor/GreenCaptchaBot.fs
index de55876..cb0d67a 100644
--- a/ctor/GreenCaptchaBot.fs
+++ b/ctor/GreenCaptchaBot.fs
@@ -2,19 +2,23 @@
 
 open Fabricator.Core
 open Fabricator.Resources.Files
+open Fabricator.Resources.Docker
 
 let private hostConfigDirectory = "/opt/green-captcha-bot/"
-let private docker = docker {
+let private docker =
     let version = "v1.15.1"
-    fromSources <| sources {
-        gitRepository "https://github.com/ImoutoChan/GreenCaptchaBot.git"
-        reference $"tags/{version}"
+    dockerContainer {
+        Sources = {
+            GitRepository = "https://github.com/ImoutoChan/GreenCaptchaBot.git"
+            GitReference = $"tags/{version}"
+        }
+        DockerfilePath = "CaptchaBot/Dockerfile"
+        Tag = version
+        Name = "green-captcha-bot"
+        Options = [|
+            Volume(hostPath = hostConfigDirectory, containerPath = "/app/Configuration")
+        |]
     }
-    withDockerfile "CaptchaBot/Dockerfile"
-    withTag version
-    withName "green-captcha-bot"
-    withVolume(hostPath = hostConfigDirectory, containerPath = "/app/Configuration")
-}
 
 let private configFile = FileResource(templatedFile "GreenCaptchaBot.template.json", $"{hostConfigDirectory}/appsettings.json")
 
diff --git a/ctor/Program.fs b/ctor/Program.fs
index 2359df6..5dbc4ae 100644
--- a/ctor/Program.fs
+++ b/ctor/Program.fs
@@ -14,5 +14,4 @@ let private cluster mode =
     |]
 
 let main (args: string[]): int =
-    let cluster =
     EntryPoint.main args cluster

From 9968ebb6328fed9356edc51d220b91dc34259128 Mon Sep 17 00:00:00 2001
From: Friedrich von Never <friedrich@fornever.me>
Date: Sat, 23 Dec 2023 20:54:56 +0100
Subject: [PATCH 6/6] Deployment: initial templated version

---
 Codingteam.Devops.sln       |  7 +++++++
 Fabricator                  |  2 +-
 ctor/Codingteam.Ctor.fsproj |  1 +
 ctor/GreenCaptchaBot.fs     |  8 ++++++--
 ctor/Program.fs             | 18 +++++++++++-------
 5 files changed, 26 insertions(+), 10 deletions(-)

diff --git a/Codingteam.Devops.sln b/Codingteam.Devops.sln
index 324521f..e1a92c3 100644
--- a/Codingteam.Devops.sln
+++ b/Codingteam.Devops.sln
@@ -26,6 +26,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabricator.Core", "Fabricat
 EndProject
 Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabricator.Resources", "Fabricator\Fabricator.Resources\Fabricator.Resources.fsproj", "{414C5063-3201-43AC-9DA1-B18A18802C0D}"
 EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabricator.Templates", "Fabricator\Fabricator.Templates\Fabricator.Templates.fsproj", "{E4F7858D-B0F7-4B42-928F-02688E843566}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -51,11 +53,16 @@ Global
 		{414C5063-3201-43AC-9DA1-B18A18802C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{414C5063-3201-43AC-9DA1-B18A18802C0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{414C5063-3201-43AC-9DA1-B18A18802C0D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E4F7858D-B0F7-4B42-928F-02688E843566}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E4F7858D-B0F7-4B42-928F-02688E843566}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E4F7858D-B0F7-4B42-928F-02688E843566}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E4F7858D-B0F7-4B42-928F-02688E843566}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(NestedProjects) = preSolution
 		{67164D20-A669-4BC3-A40E-751D3045BA89} = {C4672A21-FB21-4BBF-A0A1-512AC1B66C9A}
 		{0157906C-006C-45F8-A79A-0D55F16B16B2} = {853E359A-18C5-4472-A712-D5C80EE99D6B}
 		{C57439E9-20AF-4EEB-8C91-5D74D346374B} = {853E359A-18C5-4472-A712-D5C80EE99D6B}
 		{414C5063-3201-43AC-9DA1-B18A18802C0D} = {853E359A-18C5-4472-A712-D5C80EE99D6B}
+		{E4F7858D-B0F7-4B42-928F-02688E843566} = {853E359A-18C5-4472-A712-D5C80EE99D6B}
 	EndGlobalSection
 EndGlobal
diff --git a/Fabricator b/Fabricator
index 429cd9a..b5b64d1 160000
--- a/Fabricator
+++ b/Fabricator
@@ -1 +1 @@
-Subproject commit 429cd9a8c882ece78e43c1108e094a1d5b598576
+Subproject commit b5b64d10005b1fd45f4cbe2172e17c1f91e21f9d
diff --git a/ctor/Codingteam.Ctor.fsproj b/ctor/Codingteam.Ctor.fsproj
index 6ab6cae..58c3f3a 100644
--- a/ctor/Codingteam.Ctor.fsproj
+++ b/ctor/Codingteam.Ctor.fsproj
@@ -14,6 +14,7 @@
   <ItemGroup>
     <ProjectReference Include="..\Fabricator\Fabricator.Console\Fabricator.Console.fsproj" />
     <ProjectReference Include="..\Fabricator\Fabricator.Resources\Fabricator.Resources.fsproj" />
+    <ProjectReference Include="..\Fabricator\Fabricator.Templates\Fabricator.Templates.fsproj" />
   </ItemGroup>
 
 </Project>
diff --git a/ctor/GreenCaptchaBot.fs b/ctor/GreenCaptchaBot.fs
index cb0d67a..a706483 100644
--- a/ctor/GreenCaptchaBot.fs
+++ b/ctor/GreenCaptchaBot.fs
@@ -3,6 +3,7 @@
 open Fabricator.Core
 open Fabricator.Resources.Files
 open Fabricator.Resources.Docker
+open Fabricator.Templates.FileTemplates
 
 let private hostConfigDirectory = "/opt/green-captcha-bot/"
 let private docker =
@@ -20,6 +21,9 @@ let private docker =
         |]
     }
 
-let private configFile = FileResource(templatedFile "GreenCaptchaBot.template.json", $"{hostConfigDirectory}/appsettings.json")
+let private configFile parameters = FileResource(
+    templatedFile "GreenCaptchaBot.template.json" parameters,
+    $"{hostConfigDirectory}/appsettings.json"
+)
 
-let resources: IResource[] = [| configFile; docker |]
+let resources(parameters: Map<string, string>): IResource[] = [| configFile parameters; docker |]
diff --git a/ctor/Program.fs b/ctor/Program.fs
index 5dbc4ae..78dee2a 100644
--- a/ctor/Program.fs
+++ b/ctor/Program.fs
@@ -1,17 +1,21 @@
-open Codingteam.Ctor
-open Fabricator.Console
+open Fabricator.Console
 open Fabricator.Core
+open Fabricator.Templates.FileTemplates
 
-let private cluster mode =
-    let connectionsFileName = if mode = RunMode.Verify then "connections.stub.json" else "connections.private.json"
-    [|
+open Codingteam.Ctor
+
+let private cluster mode = task {
+    let connectionsFileName = if mode = EntryPoint.RunMode.Verify then "connections.stub.json" else "connections.private.json"
+    let! parameters = readParameterFile "parameters.json"
+    return [|
         {
             Name = "ctor"
             Designator = Designators.fromConnectionsFile connectionsFileName "ctor"
-            Resources = GreenCaptchaBot.resources
+            Resources = GreenCaptchaBot.resources parameters
             Type = MachineType.Linux
         }
     |]
+}
 
 let main (args: string[]): int =
-    EntryPoint.main args cluster
+    EntryPoint.main args (fun m -> (cluster m).GetAwaiter().GetResult())