Skip to content

Commit

Permalink
Add new module and configuration components
Browse files Browse the repository at this point in the history
  • Loading branch information
elikoga committed Feb 9, 2024
1 parent 8273a82 commit c477244
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 42 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

SD-Karten-Images bauen:

- `nix build .#nixosConfigurations.<hostname>.config.formats.sd-card-image`
15 changes: 10 additions & 5 deletions controller/app/models/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,17 @@ def write_nix(
filename = f"{self.type}.nix"

with open(path / filename, "w+") as f:
f.write("{ pkgs, lib, ... }:\n")
f.write("{ pkgs, lib, inputs, ... }:\n")
f.write("{\n")

for attr, value in module_settings.settings.items():
my_attr = getattr(self, attr)
assert isinstance(my_attr, models.Setting)
my_attr.write_nix(f, value, priority)
self.write_nix_settings(f, module_settings, priority)

f.write("}\n")

def write_nix_settings(
self, f, module_settings: models.ModuleSettings, priority: int
):
for attr, value in module_settings.settings.items():
my_attr = getattr(self, attr)
assert isinstance(my_attr, models.Setting)
my_attr.write_nix(f, value, priority)
2 changes: 2 additions & 0 deletions controller/app/models/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from .minio import Minio
from .node_red import NodeRed
from .thymis import ThymisController, ThymisDevice
from .whatever import WhateverModule

ALL_MODULES = [
Minio(),
NodeRed(),
ThymisController(),
ThymisDevice(),
WhateverModule(),
]
41 changes: 20 additions & 21 deletions controller/app/models/modules/thymis.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,32 +42,31 @@ class ThymisDevice(Module):
example="",
)

def write_nix(
self,
path: os.PathLike,
module_settings: models.ModuleSettings,
priority: int,
):
filename = f"{self.type}.nix"
wifi_ssid: Setting = Setting(
name="thymis.config.wifi-ssid",
type="string",
default="",
description="The wifi ssid of the thymis device.",
example="",
)

wifi_password: Setting = Setting(
name="thymis.config.wifi-password",
type="string",
default="",
description="The wifi password of the thymis device.",
example="",
)

def write_nix_settings(self, f, module_settings: ModuleSettings, priority: int):
device_type = (
module_settings.settings["device_type"].value
if "device_type" in module_settings.settings
else self.device_type.default
)

with open(path / filename, "w+") as f:
f.write("{ inputs, pkgs, lib, ... }:\n")
f.write("{\n")

f.write(f" imports = [\n")
# imports inputs.thymis.nixosModules.thymis-device-<device_type>
f.write(f" inputs.thymis.nixosModules.thymis-device-{device_type}\n")
f.write(f" ];\n")

for attr, value in module_settings.settings.items():
my_attr = getattr(self, attr)
assert isinstance(my_attr, models.Setting)
my_attr.write_nix(f, value, priority)
f.write(f" imports = [\n")
f.write(f" inputs.thymis.nixosModules.thymis-device-{device_type}\n")
f.write(f" ];\n")

f.write("}\n")
return super().write_nix_settings(f, module_settings, priority)
21 changes: 21 additions & 0 deletions controller/app/models/modules/whatever.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from app.models import Module, Setting
from app.models.setting import ModuleSettings


class WhateverModule(Module):
settings: Setting = Setting(
name="freeform.settings",
type="textarea",
default="",
description="The settings for the freeform module.",
example="",
)

def write_nix_settings(self, f, module_settings: ModuleSettings, priority: int):
settings = (
module_settings.settings["settings"].value
if "settings" in module_settings.settings
else self.settings.default
)

f.write(settings)
32 changes: 32 additions & 0 deletions controller/app/models/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from git import Repo
import pathlib
from app.models.modules import ALL_MODULES
import subprocess

REPO_PATH = os.getenv("REPO_PATH")

Expand Down Expand Up @@ -72,6 +73,8 @@ def write_nix(self, path: os.PathLike):
assert device.hostname, "hostname cannot be empty"
device_path = path / "hosts" / device.hostname
device_path.mkdir(exist_ok=True)
# create a empty .gitignore file
os.mknod(device_path / ".gitignore")
# write its modules
for module_settings in device.modules:
# module holds settings right now.
Expand All @@ -85,6 +88,8 @@ def write_nix(self, path: os.PathLike):
for tag in self.tags:
tag_path = path / "tags" / tag.name
tag_path.mkdir(exist_ok=True)
# create a empty .gitignore file
os.mknod(tag_path / ".gitignore")
# write its modules
for module_settings in tag.modules:
# module holds settings right now.
Expand All @@ -94,6 +99,9 @@ def write_nix(self, path: os.PathLike):
if m.type == module_settings.type
)
module.write_nix(tag_path, module_settings, tag.priority)
# run git add
repo = Repo.init(self.repo_dir())
repo.git.add(".")

def available_modules(self):
return ALL_MODULES
Expand Down Expand Up @@ -170,6 +178,30 @@ async def build_nix(self, q: List):
else:
self.update_status("success")

async def build_image_path(self, q: List, hostname: str):
await terminate_other_procs()
self.__q = q
self.__stdout = bytearray()
self.__stderr = bytearray()
self.update_status("started building")

cmd = f"nix build {self.repo_dir()}#nixosConfigurations.{hostname}.config.formats.sd-card-image --out-link /tmp/thymis-devices.{hostname}"

proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)

other_procs.append(proc)
asyncio.create_task(self.stream_reader(proc.stdout, self.__stdout))
asyncio.create_task(self.stream_reader(proc.stderr, self.__stderr))

r = await proc.wait()
if proc.returncode != 0:
raise Exception(f"failed to build image for {hostname}")
return f"/tmp/thymis-devices.{hostname}"

async def deploy(self, q: List):
await terminate_other_procs()
self.__q = q
Expand Down
23 changes: 21 additions & 2 deletions controller/app/routes/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from typing import List
from app.models.state import State
from fastapi import APIRouter, Depends, Request, BackgroundTasks, WebSocket
from fastapi.responses import FileResponse
from ..dependencies import get_or_init_state
from app import models
from app.crud import state

import subprocess

router = APIRouter()

Expand Down Expand Up @@ -56,11 +57,29 @@ async def build_status(websocket: WebSocket):

@router.post("/action/deploy")
def deploy(
summary: str, background_tasks: BackgroundTasks, state: State = Depends(get_or_init_state)
summary: str,
background_tasks: BackgroundTasks,
state: State = Depends(get_or_init_state),
):
state.commit(summary)

# runs a nix command to deploy the flake
background_tasks.add_task(state.deploy, last_build_status)

return {"message": "nix deploy started"}


@router.get("/action/build-download-image")
async def build_download_image(
hostname: str,
background_tasks: BackgroundTasks,
state: State = Depends(get_or_init_state),
):
image_path = await state.build_image_path(q=last_build_status, hostname=hostname)

# return the image bytes
return FileResponse(
image_path,
media_type="application/octet-stream",
filename=f"thymis-{hostname}.img",
)
2 changes: 1 addition & 1 deletion frontend/src/lib/buildstatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const startSocket = () => {
console.log('build_status socket closed');
setTimeout(startSocket, 1000);
};
}
};

if (browser) {
startSocket();
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/lib/config/ConfigTextarea.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
export let placeholder: string | null;
export let value: unknown;
export let change: (value: string) => void = () => {};
const changeInternal = (e: Event) => {
change((e.target as HTMLInputElement).value);
};
</script>

<textarea
class="w-full rounded-md bg-transparent border border-blue-gray-200 border-t-transparent outline outline-1 outline-secondary-400/70 p-1"
{placeholder}
on:change={changeInternal}>{value || ''}</textarea
>
4 changes: 4 additions & 0 deletions frontend/src/lib/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export type SettingTypes =
| {
type: 'path';
value: string;
}
| {
type: 'textarea';
value: string;
};

export type Setting = SettingTypes & {
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/routes/config/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { ListBox, ListBoxItem, type PopupSettings } from '@skeletonlabs/skeleton';
import ConfigBool from '$lib/config/ConfigBool.svelte';
import ConfigString from '$lib/config/ConfigString.svelte';
import ConfigTextarea from '$lib/config/ConfigTextarea.svelte';
import type { PageData } from './$types';
import { queryParam } from 'sveltekit-search-params';
import { saveState, type Module, type Tag, type Device } from '$lib/state';
Expand Down Expand Up @@ -214,6 +215,14 @@
if ($selected != null) setSetting(modules[$selected], settingKey, value);
}}
/>
{:else if modules[$selected][settingKey].type == 'textarea'}
<ConfigTextarea
value={setting}
placeholder={modules[$selected][settingKey].default}
change={(value) => {
if ($selected != null) setSetting(modules[$selected], settingKey, value);
}}
/>
{/if}
</div>
{#if effectingSettings && effectingSettings.length >= 1}
Expand Down
19 changes: 18 additions & 1 deletion frontend/src/routes/overview/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@
);
saveState(state);
}
const downloadUri = (uri: string) => {
const link = document.createElement('a');
link.href = uri;
link.download = 'image.img';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
const buildAndDownloadImage = (device: Device) => {
console.log('Building and downloading image');
downloadUri(`http://localhost:8000/action/build-download-image?hostname=${device.hostname}`);
};
</script>

<button class="btn variant-filled mb-8" on:click={() => openCreateDeviceModal()}>
Expand Down Expand Up @@ -88,7 +102,10 @@
</td>
<td class="border-t border-slate-200 p-2">
<a class="btn variant-filled" href="/config?device={device.hostname}">Edit</a>
<a href="." class="btn variant-filled">Download Image</a>
<!-- <a href="." class="btn variant-filled">Download Image</a> -->
<button class="btn variant-filled" on:click={() => buildAndDownloadImage(device)}>
Download Image
</button>
<button class="btn variant-filled" on:click={() => deleteDevice(device)}>
Delete
</button>
Expand Down
24 changes: 12 additions & 12 deletions frontend/tailwind.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ module.exports = {
theme: {
extend: {
height: {
"10v": "10vh",
"20v": "20vh",
"30v": "30vh",
"40v": "40vh",
"50v": "50vh",
"60v": "60vh",
"70v": "70vh",
"80v": "80vh",
"90v": "90vh",
"100v": "100vh",
},
},
'10v': '10vh',
'20v': '20vh',
'30v': '30vh',
'40v': '40vh',
'50v': '50vh',
'60v': '60vh',
'70v': '70vh',
'80v': '80vh',
'90v': '90vh',
'100v': '100vh'
}
}
},
plugins: [
forms,
Expand Down

0 comments on commit c477244

Please sign in to comment.