installer: call reconciler on app install/update/remove
diff --git a/core/installer/cmd/app_manager.go b/core/installer/cmd/app_manager.go
index cfb69a4..14d6ee0 100644
--- a/core/installer/cmd/app_manager.go
+++ b/core/installer/cmd/app_manager.go
@@ -8,6 +8,7 @@
 
 	"github.com/giolekva/pcloud/core/installer"
 	"github.com/giolekva/pcloud/core/installer/soft"
+	"github.com/giolekva/pcloud/core/installer/tasks"
 	"github.com/giolekva/pcloud/core/installer/welcome"
 
 	"github.com/go-git/go-billy/v5/memfs"
@@ -71,14 +72,17 @@
 		return err
 	}
 	log.Println("Cloned repository")
+	repoIO := installer.NewRepoIO(repo, signer)
+	config, err := repoIO.ReadConfig()
+	if err != nil {
+		return err
+	}
+	log.Println("Read config")
 	kube, err := newNSCreator()
 	if err != nil {
 		return err
 	}
-	m, err := installer.NewAppManager(
-		installer.NewRepoIO(repo, signer),
-		kube,
-	)
+	m, err := installer.NewAppManager(repoIO, kube)
 	if err != nil {
 		return err
 	}
@@ -101,6 +105,10 @@
 		appManagerFlags.port,
 		m,
 		r,
+		tasks.NewFluxcdReconciler( // TODO(gio): make reconciler address a flag
+			"http://fluxcd-reconciler.dodo-fluxcd-reconciler.svc.cluster.local",
+			config.Values.Id,
+		),
 	)
 	return s.Start()
 }
diff --git a/core/installer/tasks/env.go b/core/installer/tasks/env.go
index f5cc0c7..f5978b3 100644
--- a/core/installer/tasks/env.go
+++ b/core/installer/tasks/env.go
@@ -1,10 +1,9 @@
 package tasks
 
 import (
+	"context"
 	"fmt"
 	"net"
-	"net/http"
-	"time"
 
 	"github.com/charmbracelet/keygen"
 
@@ -61,25 +60,19 @@
 			SetupInfra(env, &st)...,
 		)...,
 	)
-	done := make(chan struct{})
+	rctx, done := context.WithCancel(context.Background())
 	t.OnDone(func(_ error) {
-		close(done)
+		done()
 	})
-	go reconcile(fmt.Sprintf("%s-flux", env.PCloudEnvName), done)
-	go reconcile(env.Name, done)
+	pr := NewFluxcdReconciler( // TODO(gio): make reconciler address a flag
+		"http://fluxcd-reconciler.dodo-fluxcd-reconciler.svc.cluster.local",
+		fmt.Sprintf("%s-flux", env.PCloudEnvName),
+	)
+	er := NewFluxcdReconciler(
+		"http://fluxcd-reconciler.dodo-fluxcd-reconciler.svc.cluster.local",
+		env.Name,
+	)
+	go pr.Reconcile(rctx)
+	go er.Reconcile(rctx)
 	return t, DNSZoneRef{"dns-zone", env.Name}
 }
-
-func reconcile(name string, quit chan struct{}) {
-	git := fmt.Sprintf("http://fluxcd-reconciler.dodo-fluxcd-reconciler.svc.cluster.local/source/git/%s/%s/reconcile", name, name)
-	kust := fmt.Sprintf("http://fluxcd-reconciler.dodo-fluxcd-reconciler.svc.cluster.local/kustomization/%s/%s/reconcile", name, name)
-	for {
-		select {
-		case <-time.After(30 * time.Second):
-			http.Get(git)
-			http.Get(kust)
-		case <-quit:
-			return
-		}
-	}
-}
diff --git a/core/installer/tasks/reconciler.go b/core/installer/tasks/reconciler.go
new file mode 100644
index 0000000..461ffc8
--- /dev/null
+++ b/core/installer/tasks/reconciler.go
@@ -0,0 +1,38 @@
+package tasks
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"time"
+)
+
+type Reconciler interface {
+	Reconcile(ctx context.Context)
+}
+
+type fluxcdReconciler struct {
+	resources []string
+}
+
+func NewFluxcdReconciler(addr, name string) Reconciler {
+	return fluxcdReconciler{
+		resources: []string{
+			fmt.Sprintf("%s/source/git/%s/%s/reconcile", addr, name, name),
+			fmt.Sprintf("%s/kustomization/%s/%s/reconcile", addr, name, name),
+		},
+	}
+}
+
+func (r fluxcdReconciler) Reconcile(ctx context.Context) {
+	for {
+		select {
+		case <-time.After(30 * time.Second):
+			for _, res := range r.resources {
+				http.Get(res)
+			}
+		case <-ctx.Done():
+			return
+		}
+	}
+}
diff --git a/core/installer/welcome/appmanager.go b/core/installer/welcome/appmanager.go
index 033e1db..be077e0 100644
--- a/core/installer/welcome/appmanager.go
+++ b/core/installer/welcome/appmanager.go
@@ -2,6 +2,7 @@
 
 import (
 	"bytes"
+	"context"
 	"embed"
 	"encoding/json"
 	"fmt"
@@ -9,13 +10,13 @@
 	"io/ioutil"
 	"log"
 	"net/http"
-	// "net/http/httputil"
-	// "net/url"
+	"time"
 
 	"github.com/Masterminds/sprig/v3"
 	"github.com/labstack/echo/v4"
 
 	"github.com/giolekva/pcloud/core/installer"
+	"github.com/giolekva/pcloud/core/installer/tasks"
 )
 
 //go:embed appmanager-tmpl
@@ -28,20 +29,23 @@
 var appHtmlTmpl string
 
 type AppManagerServer struct {
-	port int
-	m    *installer.AppManager
-	r    installer.AppRepository[installer.StoreApp]
+	port       int
+	m          *installer.AppManager
+	r          installer.AppRepository[installer.StoreApp]
+	reconciler tasks.Reconciler
 }
 
 func NewAppManagerServer(
 	port int,
 	m *installer.AppManager,
 	r installer.AppRepository[installer.StoreApp],
+	reconciler tasks.Reconciler,
 ) *AppManagerServer {
 	return &AppManagerServer{
 		port,
 		m,
 		r,
+		reconciler,
 	}
 }
 
@@ -237,6 +241,8 @@
 		log.Printf("%s\n", err.Error())
 		return err
 	}
+	ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
+	go s.reconciler.Reconcile(ctx)
 	return c.String(http.StatusOK, "Installed")
 }
 
@@ -261,6 +267,8 @@
 	if err := s.m.Update(a.App, slug, values); err != nil {
 		return err
 	}
+	ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
+	go s.reconciler.Reconcile(ctx)
 	return c.String(http.StatusOK, "Installed")
 }
 
@@ -269,6 +277,8 @@
 	if err := s.m.Remove(slug); err != nil {
 		return err
 	}
+	ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
+	go s.reconciler.Reconcile(ctx)
 	return c.String(http.StatusOK, "Installed")
 }