This commit is contained in:
2026-08-29 10:29:48 +05:00
parent eaa9a7bef2
commit 069b51a84c
16 changed files with 949 additions and 471 deletions
+105
View File
@@ -0,0 +1,105 @@
# Инструкция: настроить CI-деплой проекта на боксе (для агента-исполнителя)
## Твоя задача
Сделать так, чтобы push в ветку `main` репозитория в Gitea автоматически:
1) собирал фронт (pnpm + vite), 2) собирал Go-бинарь, 3) раскладывал их на боксе и перезапускал сервис.
Раннер и инструменты УЖЕ стоят и работают. Ты только добавляешь workflow-файл в репо,
регистрируешь юнит сервиса и проверяешь деплой.
---
## Факты окружения (проверять не нужно — верны)
- **Раннер:** `box-runner` (Gitea Runner v3.3.1), user-юнит `act-runner.service`, метка `ubuntu-latest` = host-режим.
- **Джобы CI выполняются на хосте бокса от пользователя `homelab`** (не в docker!). Имеют доступ к `~/apps`, `systemctl --user`, локальному docker.
- **PATH внутри джоб:** `~/.local/bin` (node 22, npm, pnpm 11) + `~/apps/go/bin` (go 1.27) + стандартный.
- **Цели деплоя:**
- статика SPA → `~/apps/hub/dist/` (Caddy `hub.alexey-homelab.duckdns.org` отдаёт её по `/`, SPA-fallback на `/index.html`)
- Go-бинарь → `~/apps/hub/hub-api`, должен слушать **`127.0.0.1:8484`** (Caddy проксирует `/api/*` туда)
- **Юнит сервиса:** файл `~/srv/hub-api.service` уже создан, но НЕ зарегистрирован (бинарника ещё нет).
---
## Шаги
### Шаг 1. Узнай структуру проекта
Выясни у пользователя (или посмотри в репо):
- где `package.json` (фронт) — ожидается `frontend/`
- где `main.go` (бэк) — ожидается `api/`
Если структура другая — поправь пути в шаблоне workflow (шаг 2).
### Шаг 2. Добавь workflow в репо
Скопируй блок ниже в репо как `.gitea/workflows/deploy.yml`:
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build frontend (pnpm + vite)
run: |
cd frontend
pnpm install --frozen-lockfile
pnpm build
- name: Build API (Go)
run: |
cd api
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o hub-api .
- name: Deploy to box
run: |
mkdir -p ~/apps/hub/dist
cp -r frontend/dist/* ~/apps/hub/dist/
cp api/hub-api ~/apps/hub/hub-api
chmod +x ~/apps/hub/hub-api
systemctl --user restart hub-api
```
ВАЖНО:
- `pnpm install --frozen-lockfile` требует `pnpm-lock.yaml` в репо — убедись, что он закоммичен.
- Если юнит ещё не зарегистрирован (шаг 3 не сделан), последняя строка упадёт — добавь `|| true` временно, пока не сделаешь шаг 3.
### Шаг 3. Зарегистрируй юнит сервиса (один раз, на боксе)
```bash
ln -sf /home/homelab/srv/hub-api.service ~/.config/systemd/user/hub-api.service
systemctl --user daemon-reload
systemctl --user enable hub-api
```
НЕ запускай (`start`) — бинарника ещё нет, юнит упадёт. Он поднимется сам после первого деплоя
(`systemctl --user restart hub-api` в workflow).
### Шаг 4. Проверь джобу
- Сделай push в `main` → открой Gitea UI → репо → вкладка **Actions** → смотри лог джобы.
- Джоба должна пройти зелёной: build → deploy.
### Шаг 5. Проверь деплой
```bash
ls -la ~/apps/hub/ # dist/ + hub-api на месте
curl -s http://127.0.0.1:8484/api/health # ответ API (если бинарник жив)
curl -s -o /dev/null -w '%{http_code}\n' https://hub.alexey-homelab.duckdns.org/ \
--resolve hub.alexey-homelab.duckdns.org:443:127.0.0.1 # 200 = статика отдаётся
```
Если бинарник падает: `journalctl --user -u hub-api -n 30` — смотри ошибку (чаще всего: не тот порт/адрес, отсутствие флага).
---
## Питфоллы
1. **Джобы без docker** (host-метка). Не используй `docker:`-шаги в CI. Если понадобится docker —
менять метку в `~/apps/act_runner/config.yaml` (`ubuntu-latest:docker://...`) + `systemctl --user restart act-runner` — но это отдельное решение.
2. **Go-бинарь должен слушать `127.0.0.1:8484`** — проверь дефолт/флаг в коде (`:8484`), Caddy уже смотрит туда.
3. **`actions/checkout@v4` скачивается с GitHub** — с бокса работает (не менять на самописный клон без нужды).
4. **pnpm store** кэшируется в `~/.local/share/pnpm` — холодная сборка только первый раз.
5. **Не удаляй и не двигай** `~/apps/act_runner/` (там `.runner` — регистрация раннера) и `~/srv/act-runner.service`.
6. **`systemctl --user restart hub-api` в CI** — юнит должен существовать (шаг 3), иначе джоба упадёт на деплое.
+9 -6
View File
@@ -9,7 +9,7 @@ proto: ## Regenerate Go + TS code from proto
--es_out=web/src/gen --es_opt=target=ts \ --es_out=web/src/gen --es_opt=target=ts \
proto/homelab/v1/homelab.proto proto/homelab/v1/homelab.proto
web: ## Build frontend into web/dist web: ## Build frontend into web/dist (tsc + vite)
cd web && pnpm install --frozen-lockfile && pnpm build cd web && pnpm install --frozen-lockfile && pnpm build
server: ## Build Go binary server: ## Build Go binary
@@ -18,12 +18,15 @@ server: ## Build Go binary
dev: ## Dev: Go server on :8080 + Vite on :5173 (use http://localhost:5173) dev: ## Dev: Go server on :8080 + Vite on :5173 (use http://localhost:5173)
go run ./server & cd web && pnpm dev go run ./server & cd web && pnpm dev
dev-srv:
go run ./server
dev-web:
cd web && pnpm dev
test: ## Smoke-test the RPC endpoints test: ## Smoke-test the RPC endpoints
@go build -o bin/server ./server @go build -o bin/server ./server
@./bin/server & sleep 1; \ @./bin/server & sleep 1; \
for i in 1 2; do \ curl -sf -X POST localhost:8080/homelab.v1.HomelabService/ListSections -H 'Content-Type: application/json' -d '{}' | grep -q '"cpu"' || exit 1; \
curl -sf -X POST localhost:8080/homelab.v1.HomelabService/IncrementCounter -H 'Content-Type: application/json' -d '{}' | grep -q "\"value\":\"$$i\"" || exit 1; \ curl -sf -X POST localhost:8080/homelab.v1.HomelabService/RunScript -H 'Content-Type: application/json' -d '{"name":"greet","args":["pony","42"]}' | grep -q 'arg1: pony' || exit 1; \
done; \
curl -sf -X POST localhost:8080/homelab.v1.HomelabService/SayHello -H 'Content-Type: application/json' -d '{"name":"Pony"}' | grep -q 'Hello, Pony!' || exit 1; \
curl -sf -X POST localhost:8080/homelab.v1.HomelabService/GetLuckyNumber -H 'Content-Type: application/json' -d '{}' | grep -q '"number"' || exit 1; \
kill %1; echo OK kill %1; echo OK
+14
View File
@@ -0,0 +1,14 @@
sections:
- name: Overview
icon: gauge
items:
- name: cpu
type: monitor
script: scripts/cpu.sh
refresh: 5
data: percentage
- name: greet
type: response
script: scripts/greet.sh
args: 2
arg_types: [text, number]
+293 -213
View File
@@ -21,26 +21,32 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
) )
type GetLuckyNumberRequest struct { type Item struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` // monitor | response
Data string `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` // monitor: text | percentage
Value string `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"` // monitor: last output
Args int32 `protobuf:"varint,5,opt,name=args,proto3" json:"args,omitempty"` // response: arg count
ArgTypes []string `protobuf:"bytes,6,rep,name=arg_types,json=argTypes,proto3" json:"arg_types,omitempty"` // response: text | number per arg
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
func (x *GetLuckyNumberRequest) Reset() { func (x *Item) Reset() {
*x = GetLuckyNumberRequest{} *x = Item{}
mi := &file_homelab_v1_homelab_proto_msgTypes[0] mi := &file_homelab_v1_homelab_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
func (x *GetLuckyNumberRequest) String() string { func (x *Item) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
} }
func (*GetLuckyNumberRequest) ProtoMessage() {} func (*Item) ProtoMessage() {}
func (x *GetLuckyNumberRequest) ProtoReflect() protoreflect.Message { func (x *Item) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[0] mi := &file_homelab_v1_homelab_proto_msgTypes[0]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -52,200 +58,266 @@ func (x *GetLuckyNumberRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x) return mi.MessageOf(x)
} }
// Deprecated: Use GetLuckyNumberRequest.ProtoReflect.Descriptor instead. // Deprecated: Use Item.ProtoReflect.Descriptor instead.
func (*GetLuckyNumberRequest) Descriptor() ([]byte, []int) { func (*Item) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{0} return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{0}
} }
type GetLuckyNumberResponse struct { func (x *Item) GetName() string {
state protoimpl.MessageState `protogen:"open.v1"`
Number int64 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetLuckyNumberResponse) Reset() {
*x = GetLuckyNumberResponse{}
mi := &file_homelab_v1_homelab_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetLuckyNumberResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetLuckyNumberResponse) ProtoMessage() {}
func (x *GetLuckyNumberResponse) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetLuckyNumberResponse.ProtoReflect.Descriptor instead.
func (*GetLuckyNumberResponse) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{1}
}
func (x *GetLuckyNumberResponse) GetNumber() int64 {
if x != nil {
return x.Number
}
return 0
}
type IncrementCounterRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *IncrementCounterRequest) Reset() {
*x = IncrementCounterRequest{}
mi := &file_homelab_v1_homelab_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *IncrementCounterRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IncrementCounterRequest) ProtoMessage() {}
func (x *IncrementCounterRequest) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use IncrementCounterRequest.ProtoReflect.Descriptor instead.
func (*IncrementCounterRequest) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{2}
}
type IncrementCounterResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *IncrementCounterResponse) Reset() {
*x = IncrementCounterResponse{}
mi := &file_homelab_v1_homelab_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *IncrementCounterResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IncrementCounterResponse) ProtoMessage() {}
func (x *IncrementCounterResponse) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use IncrementCounterResponse.ProtoReflect.Descriptor instead.
func (*IncrementCounterResponse) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{3}
}
func (x *IncrementCounterResponse) GetValue() int64 {
if x != nil {
return x.Value
}
return 0
}
type SayHelloRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SayHelloRequest) Reset() {
*x = SayHelloRequest{}
mi := &file_homelab_v1_homelab_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SayHelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SayHelloRequest) ProtoMessage() {}
func (x *SayHelloRequest) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SayHelloRequest.ProtoReflect.Descriptor instead.
func (*SayHelloRequest) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{4}
}
func (x *SayHelloRequest) GetName() string {
if x != nil { if x != nil {
return x.Name return x.Name
} }
return "" return ""
} }
type SayHelloResponse struct { func (x *Item) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *Item) GetData() string {
if x != nil {
return x.Data
}
return ""
}
func (x *Item) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
func (x *Item) GetArgs() int32 {
if x != nil {
return x.Args
}
return 0
}
func (x *Item) GetArgTypes() []string {
if x != nil {
return x.ArgTypes
}
return nil
}
type Section struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Greeting string `protobuf:"bytes,1,opt,name=greeting,proto3" json:"greeting,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Icon string `protobuf:"bytes,2,opt,name=icon,proto3" json:"icon,omitempty"`
Items []*Item `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
func (x *SayHelloResponse) Reset() { func (x *Section) Reset() {
*x = SayHelloResponse{} *x = Section{}
mi := &file_homelab_v1_homelab_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Section) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Section) ProtoMessage() {}
func (x *Section) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Section.ProtoReflect.Descriptor instead.
func (*Section) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{1}
}
func (x *Section) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Section) GetIcon() string {
if x != nil {
return x.Icon
}
return ""
}
func (x *Section) GetItems() []*Item {
if x != nil {
return x.Items
}
return nil
}
type ListSectionsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListSectionsRequest) Reset() {
*x = ListSectionsRequest{}
mi := &file_homelab_v1_homelab_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListSectionsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListSectionsRequest) ProtoMessage() {}
func (x *ListSectionsRequest) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListSectionsRequest.ProtoReflect.Descriptor instead.
func (*ListSectionsRequest) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{2}
}
type ListSectionsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Sections []*Section `protobuf:"bytes,1,rep,name=sections,proto3" json:"sections,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListSectionsResponse) Reset() {
*x = ListSectionsResponse{}
mi := &file_homelab_v1_homelab_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListSectionsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListSectionsResponse) ProtoMessage() {}
func (x *ListSectionsResponse) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListSectionsResponse.ProtoReflect.Descriptor instead.
func (*ListSectionsResponse) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{3}
}
func (x *ListSectionsResponse) GetSections() []*Section {
if x != nil {
return x.Sections
}
return nil
}
type RunScriptRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Args []string `protobuf:"bytes,2,rep,name=args,proto3" json:"args,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RunScriptRequest) Reset() {
*x = RunScriptRequest{}
mi := &file_homelab_v1_homelab_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RunScriptRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RunScriptRequest) ProtoMessage() {}
func (x *RunScriptRequest) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RunScriptRequest.ProtoReflect.Descriptor instead.
func (*RunScriptRequest) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{4}
}
func (x *RunScriptRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *RunScriptRequest) GetArgs() []string {
if x != nil {
return x.Args
}
return nil
}
type RunScriptResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Output string `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RunScriptResponse) Reset() {
*x = RunScriptResponse{}
mi := &file_homelab_v1_homelab_proto_msgTypes[5] mi := &file_homelab_v1_homelab_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
func (x *SayHelloResponse) String() string { func (x *RunScriptResponse) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
} }
func (*SayHelloResponse) ProtoMessage() {} func (*RunScriptResponse) ProtoMessage() {}
func (x *SayHelloResponse) ProtoReflect() protoreflect.Message { func (x *RunScriptResponse) ProtoReflect() protoreflect.Message {
mi := &file_homelab_v1_homelab_proto_msgTypes[5] mi := &file_homelab_v1_homelab_proto_msgTypes[5]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -257,14 +329,14 @@ func (x *SayHelloResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x) return mi.MessageOf(x)
} }
// Deprecated: Use SayHelloResponse.ProtoReflect.Descriptor instead. // Deprecated: Use RunScriptResponse.ProtoReflect.Descriptor instead.
func (*SayHelloResponse) Descriptor() ([]byte, []int) { func (*RunScriptResponse) Descriptor() ([]byte, []int) {
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{5} return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{5}
} }
func (x *SayHelloResponse) GetGreeting() string { func (x *RunScriptResponse) GetOutput() string {
if x != nil { if x != nil {
return x.Greeting return x.Output
} }
return "" return ""
} }
@@ -274,21 +346,29 @@ var File_homelab_v1_homelab_proto protoreflect.FileDescriptor
const file_homelab_v1_homelab_proto_rawDesc = "" + const file_homelab_v1_homelab_proto_rawDesc = "" +
"\n" + "\n" +
"\x18homelab/v1/homelab.proto\x12\n" + "\x18homelab/v1/homelab.proto\x12\n" +
"homelab.v1\"\x17\n" + "homelab.v1\"\x89\x01\n" +
"\x15GetLuckyNumberRequest\"0\n" + "\x04Item\x12\x12\n" +
"\x16GetLuckyNumberResponse\x12\x16\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
"\x06number\x18\x01 \x01(\x03R\x06number\"\x19\n" + "\x04type\x18\x02 \x01(\tR\x04type\x12\x12\n" +
"\x17IncrementCounterRequest\"0\n" + "\x04data\x18\x03 \x01(\tR\x04data\x12\x14\n" +
"\x18IncrementCounterResponse\x12\x14\n" + "\x05value\x18\x04 \x01(\tR\x05value\x12\x12\n" +
"\x05value\x18\x01 \x01(\x03R\x05value\"%\n" + "\x04args\x18\x05 \x01(\x05R\x04args\x12\x1b\n" +
"\x0fSayHelloRequest\x12\x12\n" + "\targ_types\x18\x06 \x03(\tR\bargTypes\"Y\n" +
"\x04name\x18\x01 \x01(\tR\x04name\".\n" + "\aSection\x12\x12\n" +
"\x10SayHelloResponse\x12\x1a\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
"\bgreeting\x18\x01 \x01(\tR\bgreeting2\x8f\x02\n" + "\x04icon\x18\x02 \x01(\tR\x04icon\x12&\n" +
"\x0eHomelabService\x12W\n" + "\x05items\x18\x03 \x03(\v2\x10.homelab.v1.ItemR\x05items\"\x15\n" +
"\x0eGetLuckyNumber\x12!.homelab.v1.GetLuckyNumberRequest\x1a\".homelab.v1.GetLuckyNumberResponse\x12]\n" + "\x13ListSectionsRequest\"G\n" +
"\x10IncrementCounter\x12#.homelab.v1.IncrementCounterRequest\x1a$.homelab.v1.IncrementCounterResponse\x12E\n" + "\x14ListSectionsResponse\x12/\n" +
"\bSayHello\x12\x1b.homelab.v1.SayHelloRequest\x1a\x1c.homelab.v1.SayHelloResponseB*Z(pwa-homelab-mon/gen/homelab/v1;homelabv1b\x06proto3" "\bsections\x18\x01 \x03(\v2\x13.homelab.v1.SectionR\bsections\":\n" +
"\x10RunScriptRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
"\x04args\x18\x02 \x03(\tR\x04args\"+\n" +
"\x11RunScriptResponse\x12\x16\n" +
"\x06output\x18\x01 \x01(\tR\x06output2\xad\x01\n" +
"\x0eHomelabService\x12Q\n" +
"\fListSections\x12\x1f.homelab.v1.ListSectionsRequest\x1a .homelab.v1.ListSectionsResponse\x12H\n" +
"\tRunScript\x12\x1c.homelab.v1.RunScriptRequest\x1a\x1d.homelab.v1.RunScriptResponseB*Z(pwa-homelab-mon/gen/homelab/v1;homelabv1b\x06proto3"
var ( var (
file_homelab_v1_homelab_proto_rawDescOnce sync.Once file_homelab_v1_homelab_proto_rawDescOnce sync.Once
@@ -304,25 +384,25 @@ func file_homelab_v1_homelab_proto_rawDescGZIP() []byte {
var file_homelab_v1_homelab_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_homelab_v1_homelab_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_homelab_v1_homelab_proto_goTypes = []any{ var file_homelab_v1_homelab_proto_goTypes = []any{
(*GetLuckyNumberRequest)(nil), // 0: homelab.v1.GetLuckyNumberRequest (*Item)(nil), // 0: homelab.v1.Item
(*GetLuckyNumberResponse)(nil), // 1: homelab.v1.GetLuckyNumberResponse (*Section)(nil), // 1: homelab.v1.Section
(*IncrementCounterRequest)(nil), // 2: homelab.v1.IncrementCounterRequest (*ListSectionsRequest)(nil), // 2: homelab.v1.ListSectionsRequest
(*IncrementCounterResponse)(nil), // 3: homelab.v1.IncrementCounterResponse (*ListSectionsResponse)(nil), // 3: homelab.v1.ListSectionsResponse
(*SayHelloRequest)(nil), // 4: homelab.v1.SayHelloRequest (*RunScriptRequest)(nil), // 4: homelab.v1.RunScriptRequest
(*SayHelloResponse)(nil), // 5: homelab.v1.SayHelloResponse (*RunScriptResponse)(nil), // 5: homelab.v1.RunScriptResponse
} }
var file_homelab_v1_homelab_proto_depIdxs = []int32{ var file_homelab_v1_homelab_proto_depIdxs = []int32{
0, // 0: homelab.v1.HomelabService.GetLuckyNumber:input_type -> homelab.v1.GetLuckyNumberRequest 0, // 0: homelab.v1.Section.items:type_name -> homelab.v1.Item
2, // 1: homelab.v1.HomelabService.IncrementCounter:input_type -> homelab.v1.IncrementCounterRequest 1, // 1: homelab.v1.ListSectionsResponse.sections:type_name -> homelab.v1.Section
4, // 2: homelab.v1.HomelabService.SayHello:input_type -> homelab.v1.SayHelloRequest 2, // 2: homelab.v1.HomelabService.ListSections:input_type -> homelab.v1.ListSectionsRequest
1, // 3: homelab.v1.HomelabService.GetLuckyNumber:output_type -> homelab.v1.GetLuckyNumberResponse 4, // 3: homelab.v1.HomelabService.RunScript:input_type -> homelab.v1.RunScriptRequest
3, // 4: homelab.v1.HomelabService.IncrementCounter:output_type -> homelab.v1.IncrementCounterResponse 3, // 4: homelab.v1.HomelabService.ListSections:output_type -> homelab.v1.ListSectionsResponse
5, // 5: homelab.v1.HomelabService.SayHello:output_type -> homelab.v1.SayHelloResponse 5, // 5: homelab.v1.HomelabService.RunScript:output_type -> homelab.v1.RunScriptResponse
3, // [3:6] is the sub-list for method output_type 4, // [4:6] is the sub-list for method output_type
0, // [0:3] is the sub-list for method input_type 2, // [2:4] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee 2, // [2:2] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name 0, // [0:2] is the sub-list for field type_name
} }
func init() { file_homelab_v1_homelab_proto_init() } func init() { file_homelab_v1_homelab_proto_init() }
@@ -33,21 +33,18 @@ const (
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a // reflection-formatted method names, remove the leading slash and convert the remaining slash to a
// period. // period.
const ( const (
// HomelabServiceGetLuckyNumberProcedure is the fully-qualified name of the HomelabService's // HomelabServiceListSectionsProcedure is the fully-qualified name of the HomelabService's
// GetLuckyNumber RPC. // ListSections RPC.
HomelabServiceGetLuckyNumberProcedure = "/homelab.v1.HomelabService/GetLuckyNumber" HomelabServiceListSectionsProcedure = "/homelab.v1.HomelabService/ListSections"
// HomelabServiceIncrementCounterProcedure is the fully-qualified name of the HomelabService's // HomelabServiceRunScriptProcedure is the fully-qualified name of the HomelabService's RunScript
// IncrementCounter RPC. // RPC.
HomelabServiceIncrementCounterProcedure = "/homelab.v1.HomelabService/IncrementCounter" HomelabServiceRunScriptProcedure = "/homelab.v1.HomelabService/RunScript"
// HomelabServiceSayHelloProcedure is the fully-qualified name of the HomelabService's SayHello RPC.
HomelabServiceSayHelloProcedure = "/homelab.v1.HomelabService/SayHello"
) )
// HomelabServiceClient is a client for the homelab.v1.HomelabService service. // HomelabServiceClient is a client for the homelab.v1.HomelabService service.
type HomelabServiceClient interface { type HomelabServiceClient interface {
GetLuckyNumber(context.Context, *connect.Request[v1.GetLuckyNumberRequest]) (*connect.Response[v1.GetLuckyNumberResponse], error) ListSections(context.Context, *connect.Request[v1.ListSectionsRequest]) (*connect.Response[v1.ListSectionsResponse], error)
IncrementCounter(context.Context, *connect.Request[v1.IncrementCounterRequest]) (*connect.Response[v1.IncrementCounterResponse], error) RunScript(context.Context, *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error)
SayHello(context.Context, *connect.Request[v1.SayHelloRequest]) (*connect.Response[v1.SayHelloResponse], error)
} }
// NewHomelabServiceClient constructs a client for the homelab.v1.HomelabService service. By // NewHomelabServiceClient constructs a client for the homelab.v1.HomelabService service. By
@@ -61,22 +58,16 @@ func NewHomelabServiceClient(httpClient connect.HTTPClient, baseURL string, opts
baseURL = strings.TrimRight(baseURL, "/") baseURL = strings.TrimRight(baseURL, "/")
homelabServiceMethods := v1.File_homelab_v1_homelab_proto.Services().ByName("HomelabService").Methods() homelabServiceMethods := v1.File_homelab_v1_homelab_proto.Services().ByName("HomelabService").Methods()
return &homelabServiceClient{ return &homelabServiceClient{
getLuckyNumber: connect.NewClient[v1.GetLuckyNumberRequest, v1.GetLuckyNumberResponse]( listSections: connect.NewClient[v1.ListSectionsRequest, v1.ListSectionsResponse](
httpClient, httpClient,
baseURL+HomelabServiceGetLuckyNumberProcedure, baseURL+HomelabServiceListSectionsProcedure,
connect.WithSchema(homelabServiceMethods.ByName("GetLuckyNumber")), connect.WithSchema(homelabServiceMethods.ByName("ListSections")),
connect.WithClientOptions(opts...), connect.WithClientOptions(opts...),
), ),
incrementCounter: connect.NewClient[v1.IncrementCounterRequest, v1.IncrementCounterResponse]( runScript: connect.NewClient[v1.RunScriptRequest, v1.RunScriptResponse](
httpClient, httpClient,
baseURL+HomelabServiceIncrementCounterProcedure, baseURL+HomelabServiceRunScriptProcedure,
connect.WithSchema(homelabServiceMethods.ByName("IncrementCounter")), connect.WithSchema(homelabServiceMethods.ByName("RunScript")),
connect.WithClientOptions(opts...),
),
sayHello: connect.NewClient[v1.SayHelloRequest, v1.SayHelloResponse](
httpClient,
baseURL+HomelabServiceSayHelloProcedure,
connect.WithSchema(homelabServiceMethods.ByName("SayHello")),
connect.WithClientOptions(opts...), connect.WithClientOptions(opts...),
), ),
} }
@@ -84,31 +75,24 @@ func NewHomelabServiceClient(httpClient connect.HTTPClient, baseURL string, opts
// homelabServiceClient implements HomelabServiceClient. // homelabServiceClient implements HomelabServiceClient.
type homelabServiceClient struct { type homelabServiceClient struct {
getLuckyNumber *connect.Client[v1.GetLuckyNumberRequest, v1.GetLuckyNumberResponse] listSections *connect.Client[v1.ListSectionsRequest, v1.ListSectionsResponse]
incrementCounter *connect.Client[v1.IncrementCounterRequest, v1.IncrementCounterResponse] runScript *connect.Client[v1.RunScriptRequest, v1.RunScriptResponse]
sayHello *connect.Client[v1.SayHelloRequest, v1.SayHelloResponse]
} }
// GetLuckyNumber calls homelab.v1.HomelabService.GetLuckyNumber. // ListSections calls homelab.v1.HomelabService.ListSections.
func (c *homelabServiceClient) GetLuckyNumber(ctx context.Context, req *connect.Request[v1.GetLuckyNumberRequest]) (*connect.Response[v1.GetLuckyNumberResponse], error) { func (c *homelabServiceClient) ListSections(ctx context.Context, req *connect.Request[v1.ListSectionsRequest]) (*connect.Response[v1.ListSectionsResponse], error) {
return c.getLuckyNumber.CallUnary(ctx, req) return c.listSections.CallUnary(ctx, req)
} }
// IncrementCounter calls homelab.v1.HomelabService.IncrementCounter. // RunScript calls homelab.v1.HomelabService.RunScript.
func (c *homelabServiceClient) IncrementCounter(ctx context.Context, req *connect.Request[v1.IncrementCounterRequest]) (*connect.Response[v1.IncrementCounterResponse], error) { func (c *homelabServiceClient) RunScript(ctx context.Context, req *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error) {
return c.incrementCounter.CallUnary(ctx, req) return c.runScript.CallUnary(ctx, req)
}
// SayHello calls homelab.v1.HomelabService.SayHello.
func (c *homelabServiceClient) SayHello(ctx context.Context, req *connect.Request[v1.SayHelloRequest]) (*connect.Response[v1.SayHelloResponse], error) {
return c.sayHello.CallUnary(ctx, req)
} }
// HomelabServiceHandler is an implementation of the homelab.v1.HomelabService service. // HomelabServiceHandler is an implementation of the homelab.v1.HomelabService service.
type HomelabServiceHandler interface { type HomelabServiceHandler interface {
GetLuckyNumber(context.Context, *connect.Request[v1.GetLuckyNumberRequest]) (*connect.Response[v1.GetLuckyNumberResponse], error) ListSections(context.Context, *connect.Request[v1.ListSectionsRequest]) (*connect.Response[v1.ListSectionsResponse], error)
IncrementCounter(context.Context, *connect.Request[v1.IncrementCounterRequest]) (*connect.Response[v1.IncrementCounterResponse], error) RunScript(context.Context, *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error)
SayHello(context.Context, *connect.Request[v1.SayHelloRequest]) (*connect.Response[v1.SayHelloResponse], error)
} }
// NewHomelabServiceHandler builds an HTTP handler from the service implementation. It returns the // NewHomelabServiceHandler builds an HTTP handler from the service implementation. It returns the
@@ -118,32 +102,24 @@ type HomelabServiceHandler interface {
// and JSON codecs. They also support gzip compression. // and JSON codecs. They also support gzip compression.
func NewHomelabServiceHandler(svc HomelabServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { func NewHomelabServiceHandler(svc HomelabServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
homelabServiceMethods := v1.File_homelab_v1_homelab_proto.Services().ByName("HomelabService").Methods() homelabServiceMethods := v1.File_homelab_v1_homelab_proto.Services().ByName("HomelabService").Methods()
homelabServiceGetLuckyNumberHandler := connect.NewUnaryHandler( homelabServiceListSectionsHandler := connect.NewUnaryHandler(
HomelabServiceGetLuckyNumberProcedure, HomelabServiceListSectionsProcedure,
svc.GetLuckyNumber, svc.ListSections,
connect.WithSchema(homelabServiceMethods.ByName("GetLuckyNumber")), connect.WithSchema(homelabServiceMethods.ByName("ListSections")),
connect.WithHandlerOptions(opts...), connect.WithHandlerOptions(opts...),
) )
homelabServiceIncrementCounterHandler := connect.NewUnaryHandler( homelabServiceRunScriptHandler := connect.NewUnaryHandler(
HomelabServiceIncrementCounterProcedure, HomelabServiceRunScriptProcedure,
svc.IncrementCounter, svc.RunScript,
connect.WithSchema(homelabServiceMethods.ByName("IncrementCounter")), connect.WithSchema(homelabServiceMethods.ByName("RunScript")),
connect.WithHandlerOptions(opts...),
)
homelabServiceSayHelloHandler := connect.NewUnaryHandler(
HomelabServiceSayHelloProcedure,
svc.SayHello,
connect.WithSchema(homelabServiceMethods.ByName("SayHello")),
connect.WithHandlerOptions(opts...), connect.WithHandlerOptions(opts...),
) )
return "/homelab.v1.HomelabService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return "/homelab.v1.HomelabService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path { switch r.URL.Path {
case HomelabServiceGetLuckyNumberProcedure: case HomelabServiceListSectionsProcedure:
homelabServiceGetLuckyNumberHandler.ServeHTTP(w, r) homelabServiceListSectionsHandler.ServeHTTP(w, r)
case HomelabServiceIncrementCounterProcedure: case HomelabServiceRunScriptProcedure:
homelabServiceIncrementCounterHandler.ServeHTTP(w, r) homelabServiceRunScriptHandler.ServeHTTP(w, r)
case HomelabServiceSayHelloProcedure:
homelabServiceSayHelloHandler.ServeHTTP(w, r)
default: default:
http.NotFound(w, r) http.NotFound(w, r)
} }
@@ -153,14 +129,10 @@ func NewHomelabServiceHandler(svc HomelabServiceHandler, opts ...connect.Handler
// UnimplementedHomelabServiceHandler returns CodeUnimplemented from all methods. // UnimplementedHomelabServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedHomelabServiceHandler struct{} type UnimplementedHomelabServiceHandler struct{}
func (UnimplementedHomelabServiceHandler) GetLuckyNumber(context.Context, *connect.Request[v1.GetLuckyNumberRequest]) (*connect.Response[v1.GetLuckyNumberResponse], error) { func (UnimplementedHomelabServiceHandler) ListSections(context.Context, *connect.Request[v1.ListSectionsRequest]) (*connect.Response[v1.ListSectionsResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.GetLuckyNumber is not implemented")) return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.ListSections is not implemented"))
} }
func (UnimplementedHomelabServiceHandler) IncrementCounter(context.Context, *connect.Request[v1.IncrementCounterRequest]) (*connect.Response[v1.IncrementCounterResponse], error) { func (UnimplementedHomelabServiceHandler) RunScript(context.Context, *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.IncrementCounter is not implemented")) return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.RunScript is not implemented"))
}
func (UnimplementedHomelabServiceHandler) SayHello(context.Context, *connect.Request[v1.SayHelloRequest]) (*connect.Response[v1.SayHelloResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.SayHello is not implemented"))
} }
+1
View File
@@ -19,4 +19,5 @@ require (
golang.org/x/sys v0.46.0 // indirect golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.38.0 // indirect golang.org/x/text v0.38.0 // indirect
golang.org/x/time v0.15.0 // indirect golang.org/x/time v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+1
View File
@@ -32,5 +32,6 @@ golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+26 -16
View File
@@ -4,25 +4,35 @@ package homelab.v1;
option go_package = "pwa-homelab-mon/gen/homelab/v1;homelabv1"; option go_package = "pwa-homelab-mon/gen/homelab/v1;homelabv1";
message GetLuckyNumberRequest {} message Item {
message GetLuckyNumberResponse {
int64 number = 1;
}
message IncrementCounterRequest {}
message IncrementCounterResponse {
int64 value = 1;
}
message SayHelloRequest {
string name = 1; string name = 1;
string type = 2; // monitor | response
string data = 3; // monitor: text | percentage
string value = 4; // monitor: last output
int32 args = 5; // response: arg count
repeated string arg_types = 6; // response: text | number per arg
} }
message SayHelloResponse {
string greeting = 1; message Section {
string name = 1;
string icon = 2;
repeated Item items = 3;
}
message ListSectionsRequest {}
message ListSectionsResponse {
repeated Section sections = 1;
}
message RunScriptRequest {
string name = 1;
repeated string args = 2;
}
message RunScriptResponse {
string output = 1;
} }
service HomelabService { service HomelabService {
rpc GetLuckyNumber(GetLuckyNumberRequest) returns (GetLuckyNumberResponse); rpc ListSections(ListSectionsRequest) returns (ListSectionsResponse);
rpc IncrementCounter(IncrementCounterRequest) returns (IncrementCounterResponse); rpc RunScript(RunScriptRequest) returns (RunScriptResponse);
rpc SayHello(SayHelloRequest) returns (SayHelloResponse);
} }
Executable
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# Суммарная загрузка CPU в процентах 0-100 (mac + linux).
case "$(uname)" in
Darwin) cores=$(sysctl -n hw.ncpu) ;;
*) cores=$(nproc) ;;
esac
ps -A -o %cpu | awk -v c="$cores" '{s+=$1} END {printf "%d", s/c}'
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
echo "arg1: $1"
echo "arg2: $2"
+171 -14
View File
@@ -2,44 +2,201 @@ package main
import ( import (
"context" "context"
"math/rand/v2" "fmt"
"log"
"net/http" "net/http"
"os" "os"
"sync/atomic" "os/exec"
"strings"
"sync"
"time"
"connectrpc.com/connect" "connectrpc.com/connect"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
"gopkg.in/yaml.v3"
homelabv1 "pwa-homelab-mon/gen/homelab/v1" homelabv1 "pwa-homelab-mon/gen/homelab/v1"
"pwa-homelab-mon/gen/homelab/v1/homelabv1connect" "pwa-homelab-mon/gen/homelab/v1/homelabv1connect"
) )
// --- config ---
type Item struct {
Name string `yaml:"name"`
Type string `yaml:"type"` // monitor | response
Script string `yaml:"script"`
Refresh int `yaml:"refresh"` // monitor: seconds
Data string `yaml:"data"` // monitor: text | percentage
Args int `yaml:"args"` // response: arg count
ArgTypes []string `yaml:"arg_types"` // response: text | number per arg
}
// Section is a full view on the web page: a list of items plus sidebar metadata.
type Section struct {
Name string `yaml:"name"`
Icon string `yaml:"icon"`
Items []Item `yaml:"items"`
}
type Config struct {
Sections []Section `yaml:"sections"`
}
func loadConfig(path string) (*Config, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var cfg Config
dec := yaml.NewDecoder(f)
dec.KnownFields(true)
if err := dec.Decode(&cfg); err != nil {
return nil, fmt.Errorf("parse %s: %w", path, err)
}
for _, sec := range cfg.Sections {
for _, it := range sec.Items {
switch it.Type {
case "monitor":
if it.Refresh <= 0 {
return nil, fmt.Errorf("item %q: refresh must be > 0", it.Name)
}
if it.Data != "text" && it.Data != "percentage" {
return nil, fmt.Errorf("item %q: data must be text|percentage", it.Name)
}
case "response":
if len(it.ArgTypes) != it.Args {
return nil, fmt.Errorf("item %q: arg_types must have args entries", it.Name)
}
for _, t := range it.ArgTypes {
if t != "text" && t != "number" {
return nil, fmt.Errorf("item %q: arg type must be text|number", it.Name)
}
}
default:
return nil, fmt.Errorf("item %q: unknown type %q", it.Name, it.Type)
}
}
}
return &cfg, nil
}
// --- service ---
type server struct { type server struct {
lucky int64 cfg *Config
counter atomic.Int64 scripts map[string]*Item // by item name, read-only after start
monVals sync.Map // item name -> string (last monitor output)
} }
func (s *server) GetLuckyNumber(context.Context, *connect.Request[homelabv1.GetLuckyNumberRequest]) (*connect.Response[homelabv1.GetLuckyNumberResponse], error) { const execTimeout = 10 * time.Second
return connect.NewResponse(&homelabv1.GetLuckyNumberResponse{Number: s.lucky}), nil
func runScript(s *Item, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), execTimeout)
defer cancel()
out, err := exec.CommandContext(ctx, s.Script, args...).CombinedOutput()
if err != nil {
return "", fmt.Errorf("%v: %s", err, out)
}
return string(out), nil
} }
func (s *server) IncrementCounter(context.Context, *connect.Request[homelabv1.IncrementCounterRequest]) (*connect.Response[homelabv1.IncrementCounterResponse], error) { func (s *server) startMonitors() {
return connect.NewResponse(&homelabv1.IncrementCounterResponse{Value: s.counter.Add(1)}), nil for _, sec := range s.cfg.Sections {
for _, it := range sec.Items {
if it.Type != "monitor" {
continue
}
sc := it
log.Printf("monitor started: %s (%s) every %ds", sc.Name, sc.Script, sc.Refresh)
go func(sc *Item) {
for {
v, err := runScript(sc)
if err != nil {
log.Printf("monitor %s: %v", sc.Name, err)
v = "error: " + err.Error()
} else {
log.Printf("monitor %s -> %s", sc.Name, strings.TrimSpace(v))
}
s.monVals.Store(sc.Name, v)
time.Sleep(time.Duration(sc.Refresh) * time.Second)
}
}(&sc)
}
}
} }
func (s *server) SayHello(_ context.Context, req *connect.Request[homelabv1.SayHelloRequest]) (*connect.Response[homelabv1.SayHelloResponse], error) { func toProto(it *Item, val string) *homelabv1.Item {
name := req.Msg.GetName() p := &homelabv1.Item{Name: it.Name, Type: it.Type, Data: it.Data, Args: int32(it.Args), ArgTypes: it.ArgTypes}
if name == "" { if it.Type == "monitor" {
return nil, connect.NewError(connect.CodeInvalidArgument, os.ErrInvalid) p.Value = val
} }
return connect.NewResponse(&homelabv1.SayHelloResponse{Greeting: "Hello, " + name + "!"}), nil return p
} }
func (s *server) ListSections(context.Context, *connect.Request[homelabv1.ListSectionsRequest]) (*connect.Response[homelabv1.ListSectionsResponse], error) {
resp := &homelabv1.ListSectionsResponse{}
for _, sec := range s.cfg.Sections {
ps := &homelabv1.Section{Name: sec.Name, Icon: sec.Icon}
for _, it := range sec.Items {
val, _ := s.monVals.Load(it.Name)
v, _ := val.(string)
ps.Items = append(ps.Items, toProto(&it, v))
}
resp.Sections = append(resp.Sections, ps)
}
return connect.NewResponse(resp), nil
}
func (s *server) RunScript(_ context.Context, req *connect.Request[homelabv1.RunScriptRequest]) (*connect.Response[homelabv1.RunScriptResponse], error) {
it, ok := s.scripts[req.Msg.GetName()]
if !ok || it.Type != "response" {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("no response script %q", req.Msg.GetName()))
}
if len(req.Msg.GetArgs()) != it.Args {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("%q takes %d args, got %d", it.Name, it.Args, len(req.Msg.GetArgs())))
}
out, err := runScript(it, req.Msg.GetArgs()...)
if err != nil {
log.Printf("script %s args=%v: ERROR %v", it.Name, req.Msg.GetArgs(), err)
return nil, connect.NewError(connect.CodeInternal, err)
}
log.Printf("script %s args=%v -> %s", it.Name, req.Msg.GetArgs(), strings.TrimSpace(out))
return connect.NewResponse(&homelabv1.RunScriptResponse{Output: out}), nil
}
// --- main ---
func main() { func main() {
svc := &server{lucky: rand.Int64()} cfgPath := os.Getenv("CONFIG")
if cfgPath == "" {
cfgPath = "config.yaml"
}
cfg, err := loadConfig(cfgPath)
if err != nil {
fmt.Fprintln(os.Stderr, "config:", err)
os.Exit(1)
}
svc := &server{cfg: cfg, scripts: map[string]*Item{}}
for _, sec := range cfg.Sections {
for i := range sec.Items {
svc.scripts[sec.Items[i].Name] = &sec.Items[i]
}
}
svc.startMonitors()
log.Printf("config %s: %d sections, %d items", cfgPath, len(cfg.Sections), len(svc.scripts))
e := echo.New() e := echo.New()
e.HideBanner = true e.HideBanner = true
// HTTP request log, except the ListSections heartbeat (every 3s per client — pure noise).
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Skipper: func(c echo.Context) bool {
return c.Request().URL.Path == "/homelab.v1.HomelabService/ListSections"
},
}))
// ConnectRPC handler on the mux (matches /homelab.v1.HomelabService/<Method>). // ConnectRPC handler on the mux (matches /homelab.v1.HomelabService/<Method>).
mux := http.NewServeMux() mux := http.NewServeMux()
+1 -1
View File
@@ -4,7 +4,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "tsc --noEmit && vite build",
"gen": "protoc --proto_path=../proto --plugin=protoc-gen-es=./node_modules/.bin/protoc-gen-es --es_out=src/gen --es_opt=target=ts ../proto/homelab/v1/homelab.proto" "gen": "protoc --proto_path=../proto --plugin=protoc-gen-es=./node_modules/.bin/protoc-gen-es --es_out=src/gen --es_opt=target=ts ../proto/homelab/v1/homelab.proto"
}, },
"packageManager": "pnpm@10.6.5", "packageManager": "pnpm@10.6.5",
+41 -59
View File
@@ -1,85 +1,67 @@
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { createClient } from "@connectrpc/connect"; import { createClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web"; import { createConnectTransport } from "@connectrpc/connect-web";
import { Dices, Hash, Send } from "lucide-preact"; import { Gauge, FlaskConical } from "lucide-preact";
import type { Section } from "./gen/homelab/v1/homelab_pb";
import { HomelabService } from "./gen/homelab/v1/homelab_pb"; import { HomelabService } from "./gen/homelab/v1/homelab_pb";
import { ScriptCard } from "./components/ScriptCard";
import { MonitorCard } from "./components/MonitorCard";
const client = createClient(HomelabService, createConnectTransport({ baseUrl: "" })); const client = createClient(HomelabService, createConnectTransport({ baseUrl: "" }));
// Иконки секций: строка из конфига → компонент. Новый icon в yaml = добавить сюда.
const ICONS: Record<string, preact.JSX.ElementType> = { gauge: Gauge, "flask-conical": FlaskConical };
function DynIcon({ name, size = 20 }: { name: string; size?: number }) {
const Cmp = ICONS[name] ?? Gauge;
return <Cmp size={size} />;
}
export function App() { export function App() {
const [lucky, setLucky] = useState<number | null>(null); const [sections, setSections] = useState<Section[]>([]);
const [count, setCount] = useState(0); const [active, setActive] = useState(0);
const [name, setName] = useState("");
const [greeting, setGreeting] = useState("");
const [error, setError] = useState("");
useEffect(() => { useEffect(() => {
client.getLuckyNumber({}).then((r) => setLucky(r.number)); const poll = () => client.listSections({}).then((r) => setSections(r.sections));
poll();
const id = setInterval(poll, 3000);
return () => clearInterval(id);
}, []); }, []);
const run = async (fn: () => Promise<unknown>) => { const sec = sections[active];
try {
await fn();
setError("");
} catch (e) {
setError(String(e));
}
};
return ( return (
<div class="mx-auto max-w-md px-4 pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)]"> <div class="flex min-h-screen">
{/* Sidebar */}
<nav class="bg-base-200 w-20 flex flex-col items-center gap-1 py-4 pt-[calc(env(safe-area-inset-top)+1rem)]">
{sections.map((s, i) => (
<button
class={`btn btn-square btn-ghost ${i === active ? "btn-primary" : ""}`}
title={s.name}
onClick={() => setActive(i)}
>
<DynIcon name={s.icon} />
</button>
))}
</nav>
{/* Active section view */}
<div class="flex-1 max-w-md mx-auto px-4 pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)]">
<div class="navbar px-0"> <div class="navbar px-0">
<div class="navbar-start" /> <div class="navbar-start" />
<div class="navbar-center"> <div class="navbar-center">
<span class="text-lg font-bold">Homelab</span> <span class="text-lg font-bold">{sec?.name ?? "Homelab"}</span>
</div> </div>
<div class="navbar-end" /> <div class="navbar-end" />
</div> </div>
{error && <div role="alert" class="alert alert-error text-sm">{error}</div>} {sec && (
<div class="flex flex-col gap-4 pb-6">
<div class="stats w-full shadow bg-base-200 mt-4"> {sec.items.filter((it) => it.type === "monitor").map((it) => <MonitorCard s={it} />)}
<div class="stat"> {sec.items.filter((it) => it.type === "response").map((it) => <ScriptCard s={it} />)}
<div class="stat-figure text-primary"><Dices size={28} /></div>
<div class="stat-title">Lucky number</div>
<div class="stat-value text-primary">{lucky ?? "…"}</div>
<div class="stat-desc">set at server start</div>
</div> </div>
<div class="stat"> )}
<div class="stat-figure text-secondary"><Hash size={28} /></div>
<div class="stat-title">Counter</div>
<div class="stat-value text-secondary">{count}</div>
<div class="stat-actions">
<button class="btn btn-sm btn-secondary" onClick={() => run(async () => setCount((await client.incrementCounter({})).value))}>
+1
</button>
</div> </div>
</div> </div>
</div>
<form
class="card bg-base-200 shadow mt-4"
onSubmit={(e) => {
e.preventDefault();
run(async () => setGreeting((await client.sayHello({ name })).greeting));
}}
>
<div class="card-body">
<h2 class="card-title text-base">Hello</h2>
<div class="flex gap-2">
<input
class="input input-bordered flex-1"
value={name}
onInput={(e) => setName((e.target as HTMLInputElement).value)}
placeholder="NAME"
/>
<button type="submit" class="btn btn-primary">
<Send size={16} /> Send
</button>
</div>
{greeting && <p class="text-xl font-semibold">{greeting}</p>}
</div>
</form>
</div>
); );
} }
+25
View File
@@ -0,0 +1,25 @@
import type { Item } from "../gen/homelab/v1/homelab_pb";
/** Карточка монитора: percentage → radial-progress, иначе текст. */
export function MonitorCard({ s }: { s: Item }) {
const pct = Number.parseInt(s.value, 10);
const isPct = s.data === "percentage" && !Number.isNaN(pct);
return (
<div class="card bg-base-200 shadow">
<div class="card-body p-4">
<h2 class="card-title text-sm font-medium text-base-content/60">{s.name}</h2>
{isPct ? (
<div
class="radial-progress text-primary"
style={{ "--value": pct } as preact.JSX.CSSProperties}
role="progressbar"
>
{pct}%
</div>
) : (
<p class="font-mono text-lg break-all">{s.value || "…"}</p>
)}
</div>
</div>
);
}
+72
View File
@@ -0,0 +1,72 @@
import { useState } from "preact/hooks";
import { createClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { X, Play } from "lucide-preact";
import type { Item } from "../gen/homelab/v1/homelab_pb";
import { HomelabService } from "../gen/homelab/v1/homelab_pb";
const client = createClient(HomelabService, createConnectTransport({ baseUrl: "" }));
/** Карточка response-скрипта: форма по количеству аргументов; результат — в модалке. */
export function ScriptCard({ s }: { s: Item }) {
const [args, setArgs] = useState<string[]>(Array(s.args).fill(""));
const [output, setOutput] = useState<string | null>(null);
const [error, setError] = useState("");
const [running, setRunning] = useState(false);
const run = async () => {
setRunning(true);
try {
setOutput((await client.runScript({ name: s.name, args })).output);
setError("");
} catch (e) {
setError(String(e));
} finally {
setRunning(false);
}
};
return (
<div class="card bg-base-200 shadow">
<div class="card-body p-4">
<h2 class="card-title text-sm font-medium text-base-content/60">
<Play size={14} /> {s.name}
</h2>
{error && <div role="alert" class="alert alert-error text-sm">{error}</div>}
<div class="flex flex-wrap gap-2 items-center">
{s.argTypes.map((t, i) => (
<input
class="input input-bordered w-full sm:w-32"
type={t === "number" ? "number" : "text"}
placeholder={t}
value={args[i]}
onInput={(e) =>
setArgs((a) => {
const next = [...a];
next[i] = (e.target as HTMLInputElement).value;
return next;
})
}
/>
))}
<button class="btn btn-primary btn-sm" disabled={running} onClick={run}>
<Play size={14} /> Run
</button>
</div>
</div>
{output != null && (
<div class="modal modal-open">
<div class="modal-box">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => setOutput(null)}>
<X size={16} />
</button>
<h3 class="font-bold text-lg">{s.name}</h3>
<pre class="bg-base-300 rounded-box p-3 mt-3 text-sm whitespace-pre-wrap">{output}</pre>
</div>
<div class="modal-backdrop" onClick={() => setOutput(null)} />
</div>
)}
</div>
);
}
+135 -89
View File
@@ -10,100 +10,154 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file homelab/v1/homelab.proto. * Describes the file homelab/v1/homelab.proto.
*/ */
export const file_homelab_v1_homelab: GenFile = /*@__PURE__*/ export const file_homelab_v1_homelab: GenFile = /*@__PURE__*/
fileDesc("Chhob21lbGFiL3YxL2hvbWVsYWIucHJvdG8SCmhvbWVsYWIudjEiFwoVR2V0THVja3lOdW1iZXJSZXF1ZXN0IigKFkdldEx1Y2t5TnVtYmVyUmVzcG9uc2USDgoGbnVtYmVyGAEgASgDIhkKF0luY3JlbWVudENvdW50ZXJSZXF1ZXN0IikKGEluY3JlbWVudENvdW50ZXJSZXNwb25zZRINCgV2YWx1ZRgBIAEoAyIfCg9TYXlIZWxsb1JlcXVlc3QSDAoEbmFtZRgBIAEoCSIkChBTYXlIZWxsb1Jlc3BvbnNlEhAKCGdyZWV0aW5nGAEgASgJMo8CCg5Ib21lbGFiU2VydmljZRJXCg5HZXRMdWNreU51bWJlchIhLmhvbWVsYWIudjEuR2V0THVja3lOdW1iZXJSZXF1ZXN0GiIuaG9tZWxhYi52MS5HZXRMdWNreU51bWJlclJlc3BvbnNlEl0KEEluY3JlbWVudENvdW50ZXISIy5ob21lbGFiLnYxLkluY3JlbWVudENvdW50ZXJSZXF1ZXN0GiQuaG9tZWxhYi52MS5JbmNyZW1lbnRDb3VudGVyUmVzcG9uc2USRQoIU2F5SGVsbG8SGy5ob21lbGFiLnYxLlNheUhlbGxvUmVxdWVzdBocLmhvbWVsYWIudjEuU2F5SGVsbG9SZXNwb25zZUIqWihwd2EtaG9tZWxhYi1tb24vZ2VuL2hvbWVsYWIvdjE7aG9tZWxhYnYxYgZwcm90bzM"); fileDesc("Chhob21lbGFiL3YxL2hvbWVsYWIucHJvdG8SCmhvbWVsYWIudjEiYAoESXRlbRIMCgRuYW1lGAEgASgJEgwKBHR5cGUYAiABKAkSDAoEZGF0YRgDIAEoCRINCgV2YWx1ZRgEIAEoCRIMCgRhcmdzGAUgASgFEhEKCWFyZ190eXBlcxgGIAMoCSJGCgdTZWN0aW9uEgwKBG5hbWUYASABKAkSDAoEaWNvbhgCIAEoCRIfCgVpdGVtcxgDIAMoCzIQLmhvbWVsYWIudjEuSXRlbSIVChNMaXN0U2VjdGlvbnNSZXF1ZXN0Ij0KFExpc3RTZWN0aW9uc1Jlc3BvbnNlEiUKCHNlY3Rpb25zGAEgAygLMhMuaG9tZWxhYi52MS5TZWN0aW9uIi4KEFJ1blNjcmlwdFJlcXVlc3QSDAoEbmFtZRgBIAEoCRIMCgRhcmdzGAIgAygJIiMKEVJ1blNjcmlwdFJlc3BvbnNlEg4KBm91dHB1dBgBIAEoCTKtAQoOSG9tZWxhYlNlcnZpY2USUQoMTGlzdFNlY3Rpb25zEh8uaG9tZWxhYi52MS5MaXN0U2VjdGlvbnNSZXF1ZXN0GiAuaG9tZWxhYi52MS5MaXN0U2VjdGlvbnNSZXNwb25zZRJICglSdW5TY3JpcHQSHC5ob21lbGFiLnYxLlJ1blNjcmlwdFJlcXVlc3QaHS5ob21lbGFiLnYxLlJ1blNjcmlwdFJlc3BvbnNlQipaKHB3YS1ob21lbGFiLW1vbi9nZW4vaG9tZWxhYi92MTtob21lbGFidjFiBnByb3RvMw");
/** /**
* @generated from message homelab.v1.GetLuckyNumberRequest * @generated from message homelab.v1.Item
*/ */
export type GetLuckyNumberRequest = Message<"homelab.v1.GetLuckyNumberRequest"> & { export type Item = Message<"homelab.v1.Item"> & {
};
/**
* Describes the message homelab.v1.GetLuckyNumberRequest.
* Use `create(GetLuckyNumberRequestSchema)` to create a new message.
*/
export const GetLuckyNumberRequestSchema: GenMessage<GetLuckyNumberRequest> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 0);
/**
* @generated from message homelab.v1.GetLuckyNumberResponse
*/
export type GetLuckyNumberResponse = Message<"homelab.v1.GetLuckyNumberResponse"> & {
/**
* @generated from field: int64 number = 1;
*/
number: bigint;
};
/**
* Describes the message homelab.v1.GetLuckyNumberResponse.
* Use `create(GetLuckyNumberResponseSchema)` to create a new message.
*/
export const GetLuckyNumberResponseSchema: GenMessage<GetLuckyNumberResponse> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 1);
/**
* @generated from message homelab.v1.IncrementCounterRequest
*/
export type IncrementCounterRequest = Message<"homelab.v1.IncrementCounterRequest"> & {
};
/**
* Describes the message homelab.v1.IncrementCounterRequest.
* Use `create(IncrementCounterRequestSchema)` to create a new message.
*/
export const IncrementCounterRequestSchema: GenMessage<IncrementCounterRequest> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 2);
/**
* @generated from message homelab.v1.IncrementCounterResponse
*/
export type IncrementCounterResponse = Message<"homelab.v1.IncrementCounterResponse"> & {
/**
* @generated from field: int64 value = 1;
*/
value: bigint;
};
/**
* Describes the message homelab.v1.IncrementCounterResponse.
* Use `create(IncrementCounterResponseSchema)` to create a new message.
*/
export const IncrementCounterResponseSchema: GenMessage<IncrementCounterResponse> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 3);
/**
* @generated from message homelab.v1.SayHelloRequest
*/
export type SayHelloRequest = Message<"homelab.v1.SayHelloRequest"> & {
/** /**
* @generated from field: string name = 1; * @generated from field: string name = 1;
*/ */
name: string; name: string;
/**
* monitor | response
*
* @generated from field: string type = 2;
*/
type: string;
/**
* monitor: text | percentage
*
* @generated from field: string data = 3;
*/
data: string;
/**
* monitor: last output
*
* @generated from field: string value = 4;
*/
value: string;
/**
* response: arg count
*
* @generated from field: int32 args = 5;
*/
args: number;
/**
* response: text | number per arg
*
* @generated from field: repeated string arg_types = 6;
*/
argTypes: string[];
}; };
/** /**
* Describes the message homelab.v1.SayHelloRequest. * Describes the message homelab.v1.Item.
* Use `create(SayHelloRequestSchema)` to create a new message. * Use `create(ItemSchema)` to create a new message.
*/ */
export const SayHelloRequestSchema: GenMessage<SayHelloRequest> = /*@__PURE__*/ export const ItemSchema: GenMessage<Item> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 0);
/**
* @generated from message homelab.v1.Section
*/
export type Section = Message<"homelab.v1.Section"> & {
/**
* @generated from field: string name = 1;
*/
name: string;
/**
* @generated from field: string icon = 2;
*/
icon: string;
/**
* @generated from field: repeated homelab.v1.Item items = 3;
*/
items: Item[];
};
/**
* Describes the message homelab.v1.Section.
* Use `create(SectionSchema)` to create a new message.
*/
export const SectionSchema: GenMessage<Section> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 1);
/**
* @generated from message homelab.v1.ListSectionsRequest
*/
export type ListSectionsRequest = Message<"homelab.v1.ListSectionsRequest"> & {
};
/**
* Describes the message homelab.v1.ListSectionsRequest.
* Use `create(ListSectionsRequestSchema)` to create a new message.
*/
export const ListSectionsRequestSchema: GenMessage<ListSectionsRequest> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 2);
/**
* @generated from message homelab.v1.ListSectionsResponse
*/
export type ListSectionsResponse = Message<"homelab.v1.ListSectionsResponse"> & {
/**
* @generated from field: repeated homelab.v1.Section sections = 1;
*/
sections: Section[];
};
/**
* Describes the message homelab.v1.ListSectionsResponse.
* Use `create(ListSectionsResponseSchema)` to create a new message.
*/
export const ListSectionsResponseSchema: GenMessage<ListSectionsResponse> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 3);
/**
* @generated from message homelab.v1.RunScriptRequest
*/
export type RunScriptRequest = Message<"homelab.v1.RunScriptRequest"> & {
/**
* @generated from field: string name = 1;
*/
name: string;
/**
* @generated from field: repeated string args = 2;
*/
args: string[];
};
/**
* Describes the message homelab.v1.RunScriptRequest.
* Use `create(RunScriptRequestSchema)` to create a new message.
*/
export const RunScriptRequestSchema: GenMessage<RunScriptRequest> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 4); messageDesc(file_homelab_v1_homelab, 4);
/** /**
* @generated from message homelab.v1.SayHelloResponse * @generated from message homelab.v1.RunScriptResponse
*/ */
export type SayHelloResponse = Message<"homelab.v1.SayHelloResponse"> & { export type RunScriptResponse = Message<"homelab.v1.RunScriptResponse"> & {
/** /**
* @generated from field: string greeting = 1; * @generated from field: string output = 1;
*/ */
greeting: string; output: string;
}; };
/** /**
* Describes the message homelab.v1.SayHelloResponse. * Describes the message homelab.v1.RunScriptResponse.
* Use `create(SayHelloResponseSchema)` to create a new message. * Use `create(RunScriptResponseSchema)` to create a new message.
*/ */
export const SayHelloResponseSchema: GenMessage<SayHelloResponse> = /*@__PURE__*/ export const RunScriptResponseSchema: GenMessage<RunScriptResponse> = /*@__PURE__*/
messageDesc(file_homelab_v1_homelab, 5); messageDesc(file_homelab_v1_homelab, 5);
/** /**
@@ -111,28 +165,20 @@ export const SayHelloResponseSchema: GenMessage<SayHelloResponse> = /*@__PURE__*
*/ */
export const HomelabService: GenService<{ export const HomelabService: GenService<{
/** /**
* @generated from rpc homelab.v1.HomelabService.GetLuckyNumber * @generated from rpc homelab.v1.HomelabService.ListSections
*/ */
getLuckyNumber: { listSections: {
methodKind: "unary"; methodKind: "unary";
input: typeof GetLuckyNumberRequestSchema; input: typeof ListSectionsRequestSchema;
output: typeof GetLuckyNumberResponseSchema; output: typeof ListSectionsResponseSchema;
}, },
/** /**
* @generated from rpc homelab.v1.HomelabService.IncrementCounter * @generated from rpc homelab.v1.HomelabService.RunScript
*/ */
incrementCounter: { runScript: {
methodKind: "unary"; methodKind: "unary";
input: typeof IncrementCounterRequestSchema; input: typeof RunScriptRequestSchema;
output: typeof IncrementCounterResponseSchema; output: typeof RunScriptResponseSchema;
},
/**
* @generated from rpc homelab.v1.HomelabService.SayHello
*/
sayHello: {
methodKind: "unary";
input: typeof SayHelloRequestSchema;
output: typeof SayHelloResponseSchema;
}, },
}> = /*@__PURE__*/ }> = /*@__PURE__*/
serviceDesc(file_homelab_v1_homelab, 0); serviceDesc(file_homelab_v1_homelab, 0);