diff --git a/control/README.md b/control/README.md
deleted file mode 100644
index 7c400e641aa3a2b440dfb00d4bb4723005458f3f..0000000000000000000000000000000000000000
--- a/control/README.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# control
-
-A simple control plane for the obfsvpn node.
-It's part of the [obfsvpn-server](/images/obfsvpn/Dockerfile) docker image.
-
-Control is intended to be used for communication between different services, such as the gateway and bridge distribution service  [Menshen](https://0xacab.org/leap/menshen).
-
-It exposes the bridge configuration and offers to flag a node as down for maintenance.
-
-The endpoints listening to GET requests are:
-* `/bridge`: returns bridge config as IP, port, certificate, location and maintenance state
-* `/enable`: flags the bridge as working
-* `/disable`: flags the bridge as being in maintenance
-
-A couple of environment variables need to be set to run control:
-
-`CONTROL_PORT`: port control is listening to
-`OBFSVPN_STATE`: directory of private and public key, certifcate, bridge-line file
-`OBFSVPN_LOCATION`: key representing a location of the OpenVPN gateway the bridge is pointing to. It will be used by [Menshen](https://0xacab.org/leap/menshen) to filter requests to the Menshen API for a bridge location 
-`OBFS4_IP`: public IP of the bridge, exposed in the Menshen API
-`OBFS4_PORT`: port of the bridge, exposed in the Menshen API
-
-
diff --git a/control/go.mod b/control/go.mod
deleted file mode 100644
index 8103f810361ad489a6bfcf1073e55a913dfbcc9c..0000000000000000000000000000000000000000
--- a/control/go.mod
+++ /dev/null
@@ -1,20 +0,0 @@
-module 0xacab.org/leap/obfsvpn/control
-
-go 1.20
-
-require (
-	github.com/labstack/echo/v4 v4.11.1
-	github.com/sirupsen/logrus v1.9.3
-)
-
-require (
-	github.com/labstack/gommon v0.4.0 // indirect
-	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-isatty v0.0.19 // indirect
-	github.com/valyala/bytebufferpool v1.0.0 // indirect
-	github.com/valyala/fasttemplate v1.2.2 // indirect
-	golang.org/x/crypto v0.11.0 // indirect
-	golang.org/x/net v0.12.0 // indirect
-	golang.org/x/sys v0.10.0 // indirect
-	golang.org/x/text v0.11.0 // indirect
-)
diff --git a/control/go.sum b/control/go.sum
deleted file mode 100644
index 5f10381eb114c8f1ed4b3be6fa8d9c48fb67678a..0000000000000000000000000000000000000000
--- a/control/go.sum
+++ /dev/null
@@ -1,44 +0,0 @@
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
-github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
-github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
-github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
-github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
-github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
-github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
-github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
-golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
-golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
-golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
-golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/control/main.go b/control/main.go
deleted file mode 100644
index 124483beb9707386a0a914c935c6eebbf6a32899..0000000000000000000000000000000000000000
--- a/control/main.go
+++ /dev/null
@@ -1,137 +0,0 @@
-// A simple control plane for the openvpn node
-package main
-
-import (
-	"bufio"
-	"fmt"
-	"net/http"
-	"os"
-	"path/filepath"
-	"strconv"
-	"strings"
-
-	"github.com/labstack/echo/v4"
-	log "github.com/sirupsen/logrus"
-)
-
-const (
-	CONTROL  = "CONTROL_PORT"
-	STATE    = "OBFSVPN_STATE"
-	LOCATION = "OBFSVPN_LOCATION"
-	IP       = "OBFS4_IP"
-	PORT     = "OBFS4_PORT"
-)
-
-type bridgeInfo struct {
-	IPAddr     string `json:"ip_addr"`
-	BridgeType string `json:"type"`
-	Healthy    bool   `json:"healthy"`
-	Location   string `json:"location"`
-	Options    struct {
-		Cert    string `json:"cert"`
-		IatMode int    `json:"iat-mode"`
-	} `json:"options"`
-	Port int `json:"port"`
-}
-
-var (
-	StateDir = os.Getenv(STATE)
-	Healthy  = true
-)
-
-func main() {
-	controlPort := os.Getenv(CONTROL)
-	if controlPort == "" {
-		fmt.Println(CONTROL, "not set")
-		return
-	}
-	if StateDir == "" {
-		StateDir = "/etc/obfsvpn/"
-	}
-	log.SetFormatter(&log.TextFormatter{})
-
-	e := echo.New()
-	e.GET("/bridge", getBridgeInfo)
-	e.GET("/disable", disableBridge)
-	e.GET("/enable", enableBridge)
-	e.Logger.Fatal(e.Start(":" + controlPort))
-}
-
-func getLocation() string {
-	l := os.Getenv(LOCATION)
-	if l == "" {
-		l = "unknown"
-	}
-	return l
-}
-
-type cmdResult struct {
-	OK bool `json:"ok"`
-}
-
-func disableBridge(c echo.Context) error {
-	Healthy = false
-	return c.JSON(http.StatusOK, &cmdResult{true})
-}
-
-func enableBridge(c echo.Context) error {
-	Healthy = true
-	return c.JSON(http.StatusOK, &cmdResult{true})
-}
-
-func getBridgeInfo(c echo.Context) error {
-	port, err := strconv.Atoi(os.Getenv(PORT))
-	if err != nil {
-		return fmt.Errorf("error parsing port: %v", err)
-	}
-	b := &bridgeInfo{
-		// TODO get kcp, hop type here
-		BridgeType: "obfs4",
-		Location:   getLocation(),
-		IPAddr:     os.Getenv(IP),
-		Port:       port,
-		Healthy:    Healthy,
-	}
-
-	f, err := os.Open(filepath.Join(StateDir, "obfs4_bridgeline.txt"))
-	if err != nil {
-		log.Warning(err)
-		return echo.NewHTTPError(http.StatusInternalServerError, "could not open bridgeline file")
-	}
-	defer f.Close()
-
-	scanner := bufio.NewScanner(f)
-
-	var cert string
-	var iatMode int
-
-	// The line we're targeting has the following format:
-	// Bridge obfs4 <IP ADDRESS>:<PORT> <FINGERPRINT> cert=87+zEbuoIIAWAPNPJ5Ci30hiFSroMjwESD1QBJGAyc9HTkCpKKJkF/Gh8kzK0Io9iI9Zbw iat-mode=1
-
-	for scanner.Scan() {
-		line := string(scanner.Text())
-		if !strings.HasPrefix(line, "Bridge") {
-			continue
-		}
-		for _, chunk := range strings.Split(line, " ") {
-			if strings.HasPrefix(chunk, "cert=") {
-				cert = chunk[len("cert="):]
-				b.Options.Cert = cert
-			}
-			if strings.HasPrefix(chunk, "iat-mode=") {
-				iatStr := chunk[len("iat-mode="):]
-				iatMode, err = strconv.Atoi(iatStr)
-				if err != nil {
-					return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("error reading bridgeline file: %v", err))
-				}
-				b.Options.IatMode = iatMode
-			}
-		}
-	}
-	if err := scanner.Err(); err != nil {
-		log.Warning(err)
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("error reading bridgeline file: %v", err))
-	}
-
-	return c.JSON(http.StatusOK, b)
-}
diff --git a/docker-compose.yml b/docker-compose.yml
index be0ad3d63283780d7fd67f3964d88b63999e7150..f6df35eb5ff8e554467b0e4e743414b6ceb5bd75 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -13,6 +13,26 @@ services:
       LIVE_TEST: "$LIVE_TEST"
     command: "/opt/openvpn-server/start.sh"
 
+  menshen:
+    build: ./images/menshen
+    ports:
+      - "8443:8443/tcp"
+    # enable agent registration and set last-seen-cutoff
+    command: menshen --agent-shared-key test-agent-shared-key --last-seen-cutoff-millis 10000
+    environment:
+      - MENSHEN_FROM_EIP_FILE=/etc/leap/test/eip-service.json
+      - MENSHEN_FROM_PROVIDER_JSON_FILE=/etc/leap/test/provider.json
+      - MENSHEN_CA_FILE=/etc/leap/test/ca.crt
+      - MENSHEN_DB_FILE=/tmp/sqlite.db
+      - MENSHEN_OVPN_CA_CRT=/etc/leap/test/ovpn_client_ca.crt
+      - MENSHEN_OVPN_CA_KEY=/etc/leap/test/ovpn_client_ca.key
+      - MENSHEN_PORT=8443
+      - MENSHEN_ALLOW_GATEWAY_LIST=true
+      - MENSHEN_ALLOW_BRIDGE_LIST=true
+      - MENSHEN_AUTO_TLS=false
+      - MENSHEN_VERBOSE=true
+      - ENABLE_CERT_V3=true
+
   obfsvpn-1:
     build:
       dockerfile: ./images/obfsvpn/Dockerfile
@@ -23,10 +43,11 @@ services:
       - "ovpn_data:/opt/openvpn/data"
     restart: always
     environment:
-      CONTROL_PORT: "9090"
       OBFSVPN_LOCATION: "unknown"
       OBFS4_IP: "BRIDGE'S PUBLIC HOST IP"
       OBFS4_HOST: "0.0.0.0"
+      MENSHEN_PORT: "8443"
+      MENSHEN_SHARED_KEY: "test-agent-shared-key"
       OPENVPN_HOST: "$OPENVPN_HOST"
       OPENVPN_PORT: "$OPENVPN_PORT"
       LIVE_TEST: "$LIVE_TEST"
@@ -54,10 +75,11 @@ services:
     volumes:
       - "ovpn_data:/opt/openvpn/data"
     environment:
-      CONTROL_PORT: "9090"
       OBFSVPN_LOCATION: "unknown"
       OBFS4_IP: "BRIDGE'S PUBLIC HOST IP"
       OBFS4_HOST: "0.0.0.0"
+      MENSHEN_PORT: "8443"
+      MENSHEN_SHARED_KEY: "test-agent-shared-key"
       OPENVPN_HOST: "openvpn-server"
       OPENVPN_PORT: "5540"
       HOP_PT: "$HOP_PT"
@@ -106,20 +128,5 @@ services:
       LIVE_TEST: "$LIVE_TEST"
     command: "/usr/bin/start.sh"
 
-  control-client:
-    build:
-      dockerfile: ./images/control-client/Dockerfile
-      context: ./
-    cap_add:
-      - NET_ADMIN
-    restart: always
-    volumes:
-      - "ovpn_data:/control_client"
-    environment:
-      OBFSVPN_SERVER_HOST: "obfsvpn-1"
-      LIVE_TEST: "$LIVE_TEST"
-    command: "/usr/bin/start.sh"
-
-
 volumes:
   ovpn_data:
diff --git a/images/control-client/Dockerfile b/images/control-client/Dockerfile
deleted file mode 100644
index d0cfe6f8a3bee8c4a8012b68d1c33bf4530d8f9c..0000000000000000000000000000000000000000
--- a/images/control-client/Dockerfile
+++ /dev/null
@@ -1,12 +0,0 @@
-FROM alpine:3.14.1
-
-RUN apk --no-cache --no-progress upgrade && \
-    apk --no-cache --no-progress add bash curl dumb-init jq && \
-    rm -rf /tmp/*
-
-COPY images/control-client/start.sh /usr/bin/
-
-VOLUME ["/control_client"]
-
-ENTRYPOINT ["dumb-init"]
-
diff --git a/images/control-client/start.sh b/images/control-client/start.sh
deleted file mode 100755
index 803f955b12ee554499b4934a251aa66b4f67d8a8..0000000000000000000000000000000000000000
--- a/images/control-client/start.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-if [[ "$LIVE_TEST" == "1" ]]; then exit 0; fi
-curl -s $OBFSVPN_SERVER_HOST:9090/bridge | jq > bridge.json
-cat bridge.json
-if [[ `jq .type bridge.json` == "\"obfs4\"" ]]; then echo "successfully fetched bridge info"; else echo "parsing failure"; exit 1; fi;
\ No newline at end of file
diff --git a/images/menshen/Dockerfile b/images/menshen/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..22639833242b2a1e7a0236bdae4cd901b3c01264
--- /dev/null
+++ b/images/menshen/Dockerfile
@@ -0,0 +1,3 @@
+FROM registry.0xacab.org/leap/menshen:1-6-0
+
+COPY ./* /etc/leap/test/
diff --git a/images/menshen/ca.crt b/images/menshen/ca.crt
new file mode 100644
index 0000000000000000000000000000000000000000..c505e18afbf91515bb16523d8578f77fe11c04e1
--- /dev/null
+++ b/images/menshen/ca.crt
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBhDCCASqgAwIBAgIRAPYDVEAmiYsTQOiqOtngWWAwCgYIKoZIzj0EAwIwOTE3
+MDUGA1UEAwwuUHJvdmlkZXItWCBSb290IENBIChjbGllbnQgY2VydGlmaWNhdGVz
+IG9ubHkhKTAeFw0yNDA0MTgwNjMxMzdaFw0yNDA1MjMwNjMxMzdaMBQxEjAQBgNV
+BAMTCVVOTElNSVRFRDAqMAUGAytlcAMhABvfZyR9f5vc+S6OYFdSdEZzJQZpd3+b
+5OsNv1aywXSwo2cwZTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH
+AwIwHQYDVR0OBBYEFHVE6GGU/uE6fcvQT7UwthpSoyFuMB8GA1UdIwQYMBaAFN2L
+7f8wCjlBf3LPaU5hINMld7beMAoGCCqGSM49BAMCA0gAMEUCIAhX1H/0vtVYPal7
+Qc1c1bzsWdO5kFlpX65TagBoqXdIAiEAvFPd8FVtY5N9Ix/2D06r9lm8qmzbc0BT
+xfUbJSYMENY=
+-----END CERTIFICATE-----
diff --git a/images/menshen/eip-service.json b/images/menshen/eip-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..a197819d5c8e2a56caeea6be83aa13d3cdfac300
--- /dev/null
+++ b/images/menshen/eip-service.json
@@ -0,0 +1 @@
+{"gateways": [], "locations": {"Seattle": {"country_code": "US", "hemisphere": "N", "name": "Seattle", "timezone": "-7"}}, "openvpn_configuration": {"auth": "SHA512", "cipher": "AES-256-GCM", "data-ciphers": "AES-256-GCM", "dev": "tun", "float": "", "keepalive": "10 30", "key-direction": "1", "nobind": true, "persist-key": true, "rcvbuf": "0", "sndbuf": "0", "tls-cipher": "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", "tls-version-min": "1.2", "verb": "3"}, "serial": 3, "version": 3}
diff --git a/images/menshen/ovpn_client_ca.crt b/images/menshen/ovpn_client_ca.crt
new file mode 100644
index 0000000000000000000000000000000000000000..478f204ae366e2a65ae8dd1947c5170a255ccae2
--- /dev/null
+++ b/images/menshen/ovpn_client_ca.crt
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBpjCCAUygAwIBAgIBATAKBggqhkjOPQQDAjA5MTcwNQYDVQQDDC5Qcm92aWRl
+ci1YIFJvb3QgQ0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTI0MDQy
+NTA1NTcwN1oXDTI5MDQyNTA2MDIwN1owOTE3MDUGA1UEAwwuUHJvdmlkZXItWCBS
+b290IENBIChjbGllbnQgY2VydGlmaWNhdGVzIG9ubHkhKTBZMBMGByqGSM49AgEG
+CCqGSM49AwEHA0IABFxrESaqiSWAHSp12Pp32bvsjDnKIUvXLLZ5MD3nU/69kHX/
+FgHQPSMZqDVlkyueclPrFU32P3RksCVT/JaCvfCjRTBDMA4GA1UdDwEB/wQEAwIC
+pDASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTdi+3/MAo5QX9yz2lOYSDT
+JXe23jAKBggqhkjOPQQDAgNIADBFAiBji6IIK5dX4aBpMxWG6gfMrE7KCIZ376Q3
+1TB9KoX9uQIhAOT9E5DDdXwANLfUdRn3IE/tNTkLPXMK5JCzUsFDQaH3
+-----END CERTIFICATE-----
diff --git a/images/menshen/ovpn_client_ca.key b/images/menshen/ovpn_client_ca.key
new file mode 100644
index 0000000000000000000000000000000000000000..279be17f86cbf7eba31d45086c43944a805a7955
--- /dev/null
+++ b/images/menshen/ovpn_client_ca.key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJ2/NbndB5gluHMnaoB5Wqtapce7eOMqaKbleWSyGMMYoAoGCCqGSM49
+AwEHoUQDQgAEXGsRJqqJJYAdKnXY+nfZu+yMOcohS9cstnkwPedT/r2Qdf8WAdA9
+IxmoNWWTK55yU+sVTfY/dGSwJVP8loK98A==
+-----END EC PRIVATE KEY-----
diff --git a/images/menshen/provider.crt b/images/menshen/provider.crt
new file mode 100644
index 0000000000000000000000000000000000000000..077871e60dfb08d331a29efb1514c3fc008527f7
--- /dev/null
+++ b/images/menshen/provider.crt
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBaTCCARCgAwIBAgIBATAKBggqhkjOPQQDAjAbMRkwFwYDVQQDExBQcm92aWRl
+ciBSb290IENBMB4XDTI0MTAyMzEyMDQyNFoXDTI5MTAyMzEyMDkyNFowGzEZMBcG
+A1UEAxMQUHJvdmlkZXIgUm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
+BOub3PgGo0HQ4LvEur9nJw1CjLDRNPPCR+ICP8TKvxcgAtQaYZ4QpIFYOaMWjO21
+1i09mlfxssEebMWCxWcMmq+jRTBDMA4GA1UdDwEB/wQEAwICpDASBgNVHRMBAf8E
+CDAGAQH/AgEBMB0GA1UdDgQWBBQOqRGo/9bx+kx43QeIM2SKiilUojAKBggqhkjO
+PQQDAgNHADBEAiBbkdxl2y2rkeJnbZiLzavvGcx/wg149vpMO/HQi8F8fAIgHI+u
+xQeeJMb8hl0yBa8O8X+m5ikGyJ1KNICJ6RduGNE=
+-----END CERTIFICATE-----
diff --git a/images/menshen/provider.json b/images/menshen/provider.json
new file mode 100644
index 0000000000000000000000000000000000000000..f1efe1c03924ef71ae37cccaae5d958f6ee5e80e
--- /dev/null
+++ b/images/menshen/provider.json
@@ -0,0 +1,30 @@
+{
+    "api_uri":"https://api.example.org:4430",
+    "api_version":"3",
+    "ask_for_donations":true,
+    "ca_cert_fingerprint":"SHA256: 40ed9d9c13872c1fcba25abdcf26a7b1bdeded433d74fbe1ccb58fbaaab58e23",
+    "ca_cert_uri":"https://example.org/ca.crt",
+    "default_language":"en",
+    "description":{
+       "en":"non-prod testing provider"
+    },
+    "domain":"example.org",
+    "donate_url":"https://leap.se/donate",
+    "donate_period":"7",
+    "info_url":"https://leap.se",
+    "languages":[
+       "en"
+    ],
+    "motd_url":"",
+    "name":{
+       "en":"non-prod testing provider"
+    },
+    "service":{
+       "allow_anonymous":true,
+       "allow_registration":true
+    },
+    "services":[
+       "openvpn"
+    ],
+    "tos_url":""
+ }
diff --git a/images/obfsvpn/Dockerfile b/images/obfsvpn/Dockerfile
index 03984a1222ca942ffe9d0aed2927d02054f1ab28..e47882334ffc770d8841bdb6ebc72dc62951ef08 100644
--- a/images/obfsvpn/Dockerfile
+++ b/images/obfsvpn/Dockerfile
@@ -4,7 +4,6 @@ ENV SOURCE_PATH ${GOPATH}/src/0xacab.org/leap/obfsvpn
 COPY . ${SOURCE_PATH}/
 WORKDIR ${SOURCE_PATH}
 RUN cd cmd/server && CGO_ENABLED=0 go build && cp server /obfsvpn-server && cd ../..
-RUN cd control && CGO_ENABLED=0 go build && cp control /control && cd ..
 
 
 FROM alpine:3
@@ -15,7 +14,6 @@ ENV OBFS4_DATA_DIR ${APP_INSTALL_PATH}/data
 
 ENV OBFS4_HOST 0.0.0.0
 ENV OBFS4_PORT 4430
-ENV CONTROL_PORT 9090
 
 ENV OBFSVPN_STATE ${OBFS4_DATA_DIR}
 ENV OBFS4_KEY_FILE ${OBFS4_DATA_DIR}/obfs4.json
@@ -26,7 +24,6 @@ WORKDIR ${APP_INSTALL_PATH}
 RUN apk add --no-cache dumb-init iptables iproute2 tcpdump netcat-openbsd bind-tools bash coreutils
 
 COPY --from=build /obfsvpn-server .
-COPY --from=build /control .
 
 COPY images/obfsvpn/scripts .
 COPY server/test_data ${OBFS4_DATA_DIR}
diff --git a/images/obfsvpn/scripts/start_obfsvpn.sh b/images/obfsvpn/scripts/start_obfsvpn.sh
index 1e07a8308ef269be3746aa402579c9f52cd96f2e..3d86f54a95718a971a5b606be3a71c7e7c43c342 100755
--- a/images/obfsvpn/scripts/start_obfsvpn.sh
+++ b/images/obfsvpn/scripts/start_obfsvpn.sh
@@ -2,21 +2,10 @@
 
 set -e
 
-kill_foreground() {
-    echo "Control plane exited. Killing server."
-    ps -C "obfsvpn-server" -o "%p"| grep -v PID | xargs kill;
-}
-
-kill_background() {
-    echo "Server exited. Killing background process."
-    kill 0;
-}
-
 cd "${APP_INSTALL_PATH}"
 
 # check that all environment variables are set
 requiredVars=(
-  CONTROL_PORT
   OBFSVPN_STATE
   OBFSVPN_LOCATION
   OBFS4_IP
@@ -39,17 +28,6 @@ if [[ ${QUIT} ]]; then
     exit 1;
 fi
 
-# ensure the control plane is stopped once we leave the script
-trap "exit" INT TERM
-trap "kill_background" EXIT
-
-# start the control plane in background
-./control &
-export PID1=$!
-
-# track existance of the background process, kill server if it's gone
-( tail -f --pid=$PID1 /dev/null; kill_foreground ) &
-
 echo ">>>> LIVE_TEST: ${LIVE_TEST}"
 echo ">>>> starting obfsvpn server - pointing to gateway ${OPENVPN_HOST}:${OPENVPN_PORT}"
 
@@ -63,6 +41,11 @@ if [[ "$HOP_PT" == "1" ]]; then
           --state "$OBFS4_DATA_DIR" \
           --config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \
           --persist="${PERSIST_BRIDGE_STATE:-false}" \
+          --location="${OBFSVPN_LOCATION}" \
+          --hostname="$(hostname)" \
+          --menshen-agent-enabled="true" \
+          --menshen-url="http://menshen:${MENSHEN_PORT}" \
+          --menshen-agent-key="${MENSHEN_SHARED_KEY}" \
           -v
     elif [[ "$QUIC" == "1" ]]; then
       # start the obfsvpn server in hopping + quic mode
@@ -75,6 +58,11 @@ if [[ "$HOP_PT" == "1" ]]; then
           --state "$OBFS4_DATA_DIR" \
           --config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \
           --persist="${PERSIST_BRIDGE_STATE:-false}" \
+          --location="${OBFSVPN_LOCATION}" \
+          --hostname="$(hostname)" \
+          --menshen-agent-enabled="true" \
+          --menshen-url="http://menshen:${MENSHEN_PORT}" \
+          --menshen-agent-key="${MENSHEN_SHARED_KEY}" \
           -v
     fi
     # start the obfsvpn server in udp hopping mode
@@ -84,6 +72,11 @@ if [[ "$HOP_PT" == "1" ]]; then
         --state "$OBFS4_DATA_DIR" \
         --config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \
         --persist="${PERSIST_BRIDGE_STATE:-false}" \
+        --location="${OBFSVPN_LOCATION}" \
+        --hostname="$(hostname)" \
+        --menshen-agent-enabled="true" \
+        --menshen-url="http://menshen:${MENSHEN_PORT}" \
+        --menshen-agent-key="${MENSHEN_SHARED_KEY}" \
         -v
 elif [[ "$KCP" == "1" ]]; then
     # start the obfsvpn server in obfs4 kcp mode
@@ -96,6 +89,11 @@ elif [[ "$KCP" == "1" ]]; then
         --state "$OBFS4_DATA_DIR" \
         --config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \
         --persist="${PERSIST_BRIDGE_STATE:-false}" \
+        --location="${OBFSVPN_LOCATION}" \
+        --hostname="$(hostname)" \
+        --menshen-agent-enabled="true" \
+        --menshen-url="http://menshen:${MENSHEN_PORT}" \
+        --menshen-agent-key="${MENSHEN_SHARED_KEY}" \
         -v
 
 elif [[ "$QUIC" == "1" ]]; then
@@ -111,6 +109,11 @@ elif [[ "$QUIC" == "1" ]]; then
         --state "$OBFS4_DATA_DIR" \
         --config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \
         --persist="${PERSIST_BRIDGE_STATE:-false}" \
+        --location="${OBFSVPN_LOCATION}" \
+        --hostname="$(hostname)" \
+        --menshen-agent-enabled="true" \
+        --menshen-url="http://menshen:${MENSHEN_PORT}" \
+        --menshen-agent-key="${MENSHEN_SHARED_KEY}" \
         -v
 
 elif [[ "$TCP" == "1" ]]; then
@@ -122,6 +125,11 @@ elif [[ "$TCP" == "1" ]]; then
         --state "$OBFS4_DATA_DIR" \
         --config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \
         --persist="${PERSIST_BRIDGE_STATE:-false}" \
+        --location="${OBFSVPN_LOCATION}" \
+        --hostname="$(hostname)" \
+        --menshen-agent-enabled="true" \
+        --menshen-url="http://menshen:${MENSHEN_PORT}" \
+        --menshen-agent-key="${MENSHEN_SHARED_KEY}" \
         -v
 else
      # start the obfsvpn server in regular obfs4 mode
@@ -133,5 +141,10 @@ else
         --state "$OBFS4_DATA_DIR" \
         --config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \
         --persist="${PERSIST_BRIDGE_STATE:-false}" \
+        --location="${OBFSVPN_LOCATION}" \
+        --hostname="$(hostname)" \
+        --menshen-agent-enabled="true" \
+        --menshen-url="http://menshen:${MENSHEN_PORT}" \
+        --menshen-agent-key="${MENSHEN_SHARED_KEY}" \
         -v
 fi
diff --git a/scripts/integration-test.sh b/scripts/integration-test.sh
index 7d7001678b4cbb8d8f7f614e83ecfae7b9ce0418..9e8636b801c9e272d226388d8b56b1097d6113ae 100755
--- a/scripts/integration-test.sh
+++ b/scripts/integration-test.sh
@@ -45,6 +45,8 @@ else
   exit 1
 fi
 
+source $env_file
+
 docker compose -p "$mode" down --rmi all -v
 docker compose -p "$mode" build --parallel --no-cache
 docker compose -p "$mode" --env-file $env_file up -d --build
@@ -66,7 +68,7 @@ counter=0
 max_reconnect_retry=10
 
 if [[ "$LIVE_TEST" == "0" ]]; then
-  echo "Testing reconnect"
+  echo "Testing reconnect" | tee -a $logfile
 
   # We can't just restart the obfsvpn servers because of some very odd way that key material is being generated so we go down and up instead 🤷
   docker compose -p "$mode" --env-file $env_file down obfsvpn-1 obfsvpn-2
@@ -84,13 +86,15 @@ fi
 
 docker compose -p "$mode" --env-file $env_file exec client ndt7-client -quiet | tee -a $logfile
 
-# Testing bridge control panel
+# Testing menshen-agent registration
 if [[ "$LIVE_TEST" == "0" ]]; then
-  if [[ $(docker compose -p "$mode" logs | grep control-client | tail -n1 | cut -d "|" -f2 | xargs) == "parsing failure" ]]
+  echo "Testing menshen-agent registration" | tee -a $logfile
+
+  if docker compose -p "$mode" exec obfsvpn-1 wget -qO- http://menshen:8443/api/5/bridges | jq ".[0]" | grep -q  '"healthy": true'
   then
-    echo "failed to parse from control panel" | tee -a $logfile
-    exit 1
+    echo "menshen-agent registration successful" | tee -a $logfile
   else
-    echo "control panel parsing succeeded" | tee -a $logfile
+    echo "menshen-agent registration failed" | tee -a $logfile
+    exit 1
   fi
-fi
\ No newline at end of file
+fi