add: in production

This commit is contained in:
Charles Hathaway
2023-10-03 16:17:34 -07:00
parent dfb8584910
commit d16d1e0ac5
12 changed files with 339 additions and 57 deletions
+2
View File
@@ -0,0 +1,2 @@
server.crt
server.key
+32
View File
@@ -0,0 +1,32 @@
# syntax=docker/dockerfile:1
# Build the application from source
FROM golang:1.21 AS build-stage
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY cmd ./cmd
COPY internal ./internal
COPY pkg ./pkg
COPY gen ./gen
RUN CGO_ENABLED=0 GOOS=linux go build -o /signaler ./cmd/signaler
# Run the tests in the container
FROM build-stage AS run-test-stage
RUN go test -v ./...
# Deploy the application binary into a lean image
FROM golang:1.21 AS build-release-stage
WORKDIR /
COPY --from=build-stage /signaler /
COPY server.crt /server.crt
COPY server.key /server.key
EXPOSE 8080
ENTRYPOINT ["/signaler"]
+5 -13
View File
@@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"log" "log"
"net/http" "net/http"
@@ -10,8 +9,6 @@ import (
"github.com/chathaway-codes/home-sensors/v2/pkg/signaler" "github.com/chathaway-codes/home-sensors/v2/pkg/signaler"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/cors" "github.com/rs/cors"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
) )
func main() { func main() {
@@ -19,15 +16,8 @@ func main() {
reflector := grpcreflect.NewStaticReflector( reflector := grpcreflect.NewStaticReflector(
servicepb.SignalerServiceName, servicepb.SignalerServiceName,
) )
path, _ := grpcreflect.NewHandlerV1(reflector)
fmt.Printf("Got path %s\n", path)
mux.Handle(grpcreflect.NewHandlerV1(reflector)) mux.Handle(grpcreflect.NewHandlerV1(reflector))
path, _ = grpcreflect.NewHandlerV1Alpha(reflector)
fmt.Printf("Got path %s\n", path)
mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector))
path, _ = servicepb.NewSignalerServiceHandler(signaler.New())
fmt.Printf("Got path %s\n", path)
mux.Handle(servicepb.NewSignalerServiceHandler(signaler.New())) mux.Handle(servicepb.NewSignalerServiceHandler(signaler.New()))
corsHandler := cors.New(cors.Options{ corsHandler := cors.New(cors.Options{
@@ -35,7 +25,7 @@ func main() {
http.MethodGet, http.MethodGet,
http.MethodPost, http.MethodPost,
}, },
AllowedOrigins: []string{"example.com"}, AllowedOrigins: []string{"*"},
AllowedHeaders: []string{ AllowedHeaders: []string{
"Accept-Encoding", "Accept-Encoding",
"Authorization", "Authorization",
@@ -61,10 +51,12 @@ func main() {
server := &http.Server{ server := &http.Server{
Addr: "0.0.0.0:8080", Addr: "0.0.0.0:8080",
Handler: h2c.NewHandler(handler, &http2.Server{}), Handler: handler,
//Handler: h2c.NewHandler(handler, &http2.Server{}),
} }
if err := server.ListenAndServe(); err != nil { if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil {
log.Fatalf("Failed to listen for HTTP traffic: %v", err) log.Fatalf("Failed to listen for HTTP traffic: %v", err)
} }
} }
+41 -18
View File
@@ -8,7 +8,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"net/http" "net/http"
"os"
"time" "time"
"connectrpc.com/connect" "connectrpc.com/connect"
@@ -16,13 +15,17 @@ import (
servicepb "github.com/chathaway-codes/home-sensors/v2/gen/genconnect" servicepb "github.com/chathaway-codes/home-sensors/v2/gen/genconnect"
"github.com/chathaway-codes/home-sensors/v2/internal/sensors" "github.com/chathaway-codes/home-sensors/v2/internal/sensors"
"github.com/chathaway-codes/home-sensors/v2/internal/video" "github.com/chathaway-codes/home-sensors/v2/internal/video"
"github.com/chathaway-codes/home-sensors/v2/internal/watcher/config"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
"github.com/pion/webrtc/v3/pkg/media" "github.com/pion/webrtc/v3/pkg/media"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
var (
signalerServer = flag.String("signaler_address", "home.chathaway.codes", "address of the signaler")
)
func withAuth[T any](token string, v *T) *connect.Request[T] { func withAuth[T any](token string, v *T) *connect.Request[T] {
req := connect.NewRequest[T](v) req := connect.NewRequest[T](v)
req.Header().Add("Authorization", "Bearer "+token) req.Header().Add("Authorization", "Bearer "+token)
@@ -30,10 +33,32 @@ func withAuth[T any](token string, v *T) *connect.Request[T] {
} }
func main() { func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
flag.Parse() flag.Parse()
ctx := context.Background() ctx := context.Background()
cfg, err := config.Default.Get()
if err != nil {
log.Fatal().Err(err).Msg("failed to get config")
}
client := servicepb.NewSignalerServiceClient(
http.DefaultClient,
fmt.Sprintf("https://%s/", *signalerServer),
connect.WithGRPC(),
)
authToken, err := client.CreateAuthToken(ctx, connect.NewRequest(&pb.CreateAuthTokenRequest{
Home: cfg.HomeName,
Type: &pb.CreateAuthTokenRequest_Camera_{
Camera: &pb.CreateAuthTokenRequest_Camera{
Id: cfg.CameraName,
},
},
}))
if err != nil {
log.Fatal().Err(err).Msg("failed to get auth token")
}
token := authToken.Msg.GetToken()
vid, err := video.Default.Get() vid, err := video.Default.Get()
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("failed to get default video") log.Fatal().Err(err).Msg("failed to get default video")
@@ -43,19 +68,6 @@ func main() {
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("failed to get default sensor") log.Fatal().Err(err).Msg("failed to get default sensor")
} }
client := servicepb.NewSignalerServiceClient(http.DefaultClient, "http://192.168.0.65:8080/")
authToken, err := client.CreateAuthToken(ctx, connect.NewRequest(&pb.CreateAuthTokenRequest{
Home: "home1234",
Type: &pb.CreateAuthTokenRequest_Camera_{
Camera: &pb.CreateAuthTokenRequest_Camera{
Id: "movie",
},
},
}))
if err != nil {
log.Fatal().Err(err).Msg("failed to get auth token")
}
token := authToken.Msg.GetToken()
go vid.Run() go vid.Run()
defer vid.Done() defer vid.Done()
@@ -75,7 +87,8 @@ func main() {
// Wait for a session request // Wait for a session request
session, err := client.PopSession(ctx, withAuth(token, &pb.PopSessionRequest{})) session, err := client.PopSession(ctx, withAuth(token, &pb.PopSessionRequest{}))
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("error creating session") log.Error().Err(err).Msg("error creating session")
continue
} }
go handleSession(ctx, client, token, session, vid) go handleSession(ctx, client, token, session, vid)
} }
@@ -174,6 +187,7 @@ func handleSession(ctx context.Context, client servicepb.SignalerServiceClient,
// Set the handler for Peer connection state // Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected // This will notify you when the peer has connected/disconnected
exitCh := make(chan struct{})
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
log.Debug().Msgf("Peer Connection State has changed: %s\n", s.String()) log.Debug().Msgf("Peer Connection State has changed: %s\n", s.String())
@@ -181,9 +195,12 @@ func handleSession(ctx context.Context, client servicepb.SignalerServiceClient,
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected. // Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting") close(exitCh)
return return
} }
if s == webrtc.PeerConnectionStateDisconnected {
close(exitCh)
}
}) })
peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) { peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
@@ -223,6 +240,12 @@ func handleSession(ctx context.Context, client servicepb.SignalerServiceClient,
// Add ICE candidates from remote // Add ICE candidates from remote
for { for {
select {
case <-exitCh:
return
default:
// check for another message
}
msg, err := client.PopIceMessage(ctx, withAuth(token, &pb.PopIceMessageRequest{ msg, err := client.PopIceMessage(ctx, withAuth(token, &pb.PopIceMessageRequest{
SessionIdentifier: session.Msg.GetId(), SessionIdentifier: session.Msg.GetId(),
})) }))
+2
View File
@@ -17,6 +17,8 @@ type Cmd struct {
} }
type Config struct { type Config struct {
HomeName string `yaml:"home"`
CameraName string `yaml:"name"`
H264Cmd *Cmd `yaml:"h264"` H264Cmd *Cmd `yaml:"h264"`
IVFCmd *Cmd `yaml:"ivf"` IVFCmd *Cmd `yaml:"ivf"`
SensorCmd *Cmd `yaml:"sensor"` SensorCmd *Cmd `yaml:"sensor"`
+90
View File
@@ -0,0 +1,90 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: signaler-deployment
labels:
app: signaler
spec:
replicas: 1
selector:
matchLabels:
app: signaler
template:
metadata:
labels:
app: signaler
app.kubernetes.io/name: signaler-pods
spec:
containers:
- name: signaler
image: us-central1-docker.pkg.dev/home-sensors-400805/signaler/image:$VERSION
command:
- /signaler
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /metrics
port: 8080
scheme: HTTPS
initialDelaySeconds: 3
periodSeconds: 3
readinessProbe:
httpGet:
path: /metrics
port: 8080
scheme: HTTPS
initialDelaySeconds: 3
periodSeconds: 3
---
apiVersion: v1
kind: Service
metadata:
name: signaler-service
annotations:
cloud.google.com/app-protocols: '{"my-port":"HTTP2"}'
spec:
selector:
app.kubernetes.io/name: signaler-pods
ports:
- name: my-port
protocol: TCP
port: 8080
targetPort: 8080
appProtocol: HTTP2
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: signaler-ingress
annotations:
kubernetes.io/ingress.global-static-ip-name: signaler
networking.gke.io/managed-certificates: managed-cert
kubernetes.io/ingress.class: "gce"
spec:
rules:
- http:
paths:
- path: /signaler.SignalerService
pathType: Prefix
backend:
service:
name: signaler-service
port:
number: 8080
---
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: managed-cert
spec:
domains:
- home.chathaway.codes
- www.home.chathaway.codes
---
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
name: my-bsc-backendconfig
spec:
timeoutSec: 40
+83
View File
@@ -0,0 +1,83 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: signaler-deployment
labels:
app: signaler
spec:
replicas: 1
selector:
matchLabels:
app: signaler
template:
metadata:
labels:
app: signaler
app.kubernetes.io/name: signaler-pods
spec:
containers:
- name: signaler
image: us-central1-docker.pkg.dev/home-sensors-400805/signaler/image:20231003-0032
command:
- /signaler
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /metrics
port: 8080
scheme: HTTPS
initialDelaySeconds: 3
periodSeconds: 3
readinessProbe:
httpGet:
path: /metrics
port: 8080
scheme: HTTPS
initialDelaySeconds: 3
periodSeconds: 3
---
apiVersion: v1
kind: Service
metadata:
name: signaler-service
annotations:
cloud.google.com/app-protocols: '{"my-port":"HTTP2"}'
spec:
selector:
app.kubernetes.io/name: signaler-pods
ports:
- name: my-port
protocol: TCP
port: 8080
targetPort: 8080
appProtocol: HTTP2
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: signaler-ingress
annotations:
kubernetes.io/ingress.global-static-ip-name: signaler
networking.gke.io/managed-certificates: managed-cert
kubernetes.io/ingress.class: "gce"
spec:
rules:
- http:
paths:
- path: /signaler.SignalerService
pathType: Prefix
backend:
service:
name: signaler-service
port:
number: 8080
---
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: managed-cert
spec:
domains:
- home.chathaway.codes
- www.home.chathaway.codes
+2
View File
@@ -1,3 +1,5 @@
home: Sunnyvale
name: Office
h264: h264:
binary: "/usr/bin/libcamera-vid" binary: "/usr/bin/libcamera-vid"
arguments: arguments:
+2
View File
@@ -1,3 +1,5 @@
home: Sunnyvale
name: Movie
h264: h264:
binary: "/usr/bin/cat" binary: "/usr/bin/cat"
arguments: arguments:
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
ROOT=$(pwd)
export VERSION=$(date +%Y%m%d-%H%M)
IMAGE=us-central1-docker.pkg.dev/home-sensors-400805/signaler/image:$VERSION
docker build -f Dockerfile . -t $IMAGE
echo "Done building $IMAGE"
echo -n "Hit enter to deploy"
read
docker push $IMAGE
envsubst < kube/prod.yaml > prod.yaml
kubectl apply -f prod.yaml
+17
View File
@@ -0,0 +1,17 @@
import bme280
import smbus2
from time import sleep
port = 1
address = 0x77 # Adafruit BME280 address. Other BME280s may be different
bus = smbus2.SMBus(port)
bme280.load_calibration_params(bus,address)
while True:
bme280_data = bme280.sample(bus,address)
humidity = bme280_data.humidity
pressure = bme280_data.pressure
ambient_temperature = bme280_data.temperature
print(humidity, pressure, ambient_temperature)
sleep(1)
+24 -6
View File
@@ -10,9 +10,9 @@ void main() async {
Logger logger = Logger(); Logger logger = Logger();
logger.i("Establishing connection..."); logger.i("Establishing connection...");
final channel = ClientChannel( final channel = ClientChannel(
'192.168.0.65', 'home.chathaway.codes',
port: 8080, //port: 80,
options: const ChannelOptions(credentials: ChannelCredentials.insecure()), //options: const ChannelOptions(credentials: ChannelCredentials.insecure()),
channelShutdownHandler: () { channelShutdownHandler: () {
logger.e("Channel shutdown unexpectedly"); logger.e("Channel shutdown unexpectedly");
}, },
@@ -54,7 +54,7 @@ class MyApp extends StatelessWidget {
client, client,
sessionService, sessionService,
title: 'Home Sensors', title: 'Home Sensors',
home: "home1234", home: "Sunnyvale",
), ),
); );
} }
@@ -85,12 +85,14 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
String topMessage = "Creating session..."; String topMessage = "Creating session...";
List<Call> camerasToRender = []; List<Call> camerasToRender = [];
List<Widget> samples = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_getSession(); _getSession();
_listCameras(); _listCameras();
_listSensors();
} }
_getSession() async { _getSession() async {
@@ -99,6 +101,20 @@ class _MyHomePageState extends State<MyHomePage> {
setState(() {}); setState(() {});
} }
_listSensors() async {
var callOptions = CallOptions(metadata: {
'Authorization': await widget.sessionService.getAuthToken(widget.home)
});
var resp = await widget.client
.listSamples(ListSamplesRequest(), options: callOptions);
for (var sample in resp.samples) {
samples
.add(Text("${sample.type}: ${sample.reading} on ${sample.cameraId}"));
}
setState(() {});
}
_listCameras() async { _listCameras() async {
var callOptions = CallOptions(metadata: { var callOptions = CallOptions(metadata: {
'Authorization': await widget.sessionService.getAuthToken(widget.home) 'Authorization': await widget.sessionService.getAuthToken(widget.home)
@@ -135,15 +151,17 @@ class _MyHomePageState extends State<MyHomePage> {
// the App.build method, and use it to set our appbar title. // the App.build method, and use it to set our appbar title.
title: Text(widget.title), title: Text(widget.title),
), ),
body: Column( body: SingleChildScrollView(
child: Column(
// Center is a layout widget. It takes a single child and positions it // Center is a layout widget. It takes a single child and positions it
// in the middle of the parent. // in the middle of the parent.
children: <Widget>[ children: <Widget>[
Text(topMessage), Text(topMessage),
] + ] +
samples +
camerasToRender, camerasToRender,
), ),
); ));
} }
} }