This commit is contained in:
XenGi 2025-08-23 18:36:47 +02:00
parent 7180ddcfbc
commit fd4eb5b3e8
Signed by: xengi
SSH key fingerprint: SHA256:jxWM2RTHvxxcncXycwwWkP7HCWb4VREN05UGJTbIPZg
18 changed files with 195 additions and 272 deletions

View file

@ -1,53 +0,0 @@
# Maintainer: Ricardo Band <email@ricardo.band>
pkgname=sanic
pkgver=0.0.1
pkgrel=1
pkgdesc="chaos music control inspired by relaxx player"
arch=('any')
url=https://git.berlin.ccc.de/cccb/sanic
license=('custom:MIT')
makedepends=('go')
source=("$pkgname.service"
"$pkgname.sysusers"
"$pkgname.tmpfiles"
"$url/archive/v$pkgver.tar.gz")
sha256sums=("1337deadbeef"
"1337deadbeef"
"1337deadbeef"
"1337deadbeef")
prepare() {
cd "$pkgname-$pkgver"
mkdir -p build/
}
build() {
cd "$pkgname-$pkgver"
export CGO_CPPFLAGS="$CPPFLAGS"
export CGO_CFLAGS="$CFLAGS"
export CGO_CXXFLAGS="$CXXFLAGS"
export CGO_LDFLAGS="$LDFLAGS"
export GOFLAGS="-buildmode=pie -trimpath -ldflags=-linkmode=external -mod=readonly -modcacherw"
go build -o build/ .
}
check() {
cd "$pkgname-$pkgver"
go test ./...
}
package() {
cd "$pkgname-$pkgver"
install -Dm644 "LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm755 build/$pkgname "$pkgdir"/usr/bin/$pkgname
install -Dm644 "../$pkgname.service" "$pkgdir/usr/lib/systemd/system/$pkgname.service"
install -Dm644 "../$pkgname.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgname.conf"
install -Dm644 "../$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf"
}

View file

@ -1,28 +0,0 @@
[Unit]
Description=chaos music control
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=sanic
Group=sanic
ExecStart=/usr/bin/sanic
Restart=always
# security
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=yes
StateDirectory=sanic
StateDirectoryMode=0750
ConfigurationDirectory=sanic
ConfigurationDirectoryMode=0750
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
[Install]
WantedBy=multi-user.target

View file

@ -1,3 +0,0 @@
u sanic - "chaos music control" /run/sanic /usr/bin/nologin
g sanic - -

View file

@ -1,3 +0,0 @@
d /etc/sanic 0750 sanic sanic
d /run/sanic 0750 sanic sanic

114
flake.nix
View file

@ -37,119 +37,7 @@
];
};
packages.default = sanic;
nixosModules.default = { config, lib, pkgs, options, ... }:
let
cfg = config.services.sanic;
configFile = pkgs.writeText "config.ini" (pkgs.lib.generators.toINI {} cfg);
execCommand = "${cfg.package}/bin/sanic -c '${configFile}'";
in
{
options.services.sanic = {
enable = lib.mkEnableOption "Enables the sanic systemd service.";
package = lib.mkOption {
description = "Package to use.";
type = lib.types.package;
default = sanic;
};
ui = lib.mkOption {
description = "Setting for HTTP(S) UI.";
example = lib.literalExpression ''
{
host = "[::1]";
port = 8443;
tls = true;
certificate = "${config.security.acme.certs."sanic.example.com".directory}/fullchain.pem";
key = "${config.security.acme.certs."sanic.example.com".directory}/key.pem";
}
'';
default = {
host = "[::1]";
port = 80;
tls = false;
};
type = lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
default = "[::1]";
description = "Host to bind to.";
};
port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = "Port to listen on.";
};
tls = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enables HTTPS.";
};
certificate = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Path to TLS certificate for HTTPS.";
};
key = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Path to TLS key for HTTPS.";
};
};
};
};
backend = lib.mkOption {
description = "Configure MPD backend.";
example = lib.literalExpression ''
{
host = "localhost";
port = 6600;
}
'';
default = {
host = "localhost";
port = 6600;
};
type = lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
default = "localhost";
description = "Hostname or IP of MPD instance.";
};
port = lib.mkOption {
type = lib.types.port;
default = 6600;
description = "Port of MPD instance.";
};
};
};
};
};
config = lib.mkIf cfg.enable {
systemd.services."sanic" = {
description = "sanic - chaos music control";
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
serviceConfig = {
Restart = "always";
RestartSec = 30;
ExecStart = execCommand;
User = "sanic";
Group = "sanic";
AmbientCapabilities = lib.mkIf (cfg.ui.port < 1000) [ "CAP_NET_BIND_SERVICE" ];
CapabilityBoundingSet = lib.mkIf (cfg.ui.port < 1000) [ "CAP_NET_BIND_SERVICE" ];
NoNewPrivileges = true;
};
wantedBy = [ "multi-user.target" ];
};
};
#meta = {
# maintainers = with lib.maintainers; [ xengi ];
# doc = ./default.xml;
#};
};
nixosModules.default = import ./option.nix;
}
);
}

4
go.mod
View file

@ -6,6 +6,7 @@ require (
github.com/fhs/gompd/v2 v2.3.0
github.com/labstack/echo-contrib v0.17.1
github.com/labstack/echo/v4 v4.12.0
github.com/tdewolff/minify/v2 v2.24.0
gopkg.in/ini.v1 v1.67.0
)
@ -21,11 +22,12 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/tdewolff/parse/v2 v2.8.3 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.6.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect

10
go.sum
View file

@ -35,6 +35,12 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tdewolff/minify/v2 v2.24.0 h1:m6j8VXvgUtmkavubzHbaNTXi9tw3hjIMZbdc57SRdvI=
github.com/tdewolff/minify/v2 v2.24.0/go.mod h1:uqtSu3w0+anqk4ofcsuLPZ8tV8yAZL1r/ILWYYl2j3c=
github.com/tdewolff/parse/v2 v2.8.3 h1:5VbvtJ83cfb289A1HzRA9sf02iT8YyUwN84ezjkdY1I=
github.com/tdewolff/parse/v2 v2.8.3/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
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.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
@ -45,8 +51,8 @@ golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
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.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=

13
mpd.go
View file

@ -1,12 +1,13 @@
package main
import (
"fmt"
"github.com/fhs/gompd/v2/mpd"
"github.com/labstack/echo/v4"
"net/http"
"strconv"
"time"
"fmt"
"net/http"
"strconv"
"time"
"github.com/fhs/gompd/v2/mpd"
"github.com/labstack/echo/v4"
)
// MPD API calls

114
option.nix Normal file
View file

@ -0,0 +1,114 @@
{ config, lib, pkgs, options, ... }:
let
cfg = config.services.sanic;
configFile = pkgs.writeText "config.ini" (pkgs.lib.generators.toINI {} cfg);
execCommand = "${cfg.package}/bin/sanic -c '${configFile}'";
in
{
options.services.sanic = {
enable = lib.mkEnableOption "Enables the sanic systemd service.";
package = lib.mkOption {
description = "Package to use.";
type = lib.types.package;
default = sanic;
};
ui = lib.mkOption {
description = "Setting for HTTP(S) UI.";
example = lib.literalExpression ''
{
host = "[::1]";
port = 8443;
tls = true;
certificate = "${config.security.acme.certs."sanic.example.com".directory}/fullchain.pem";
key = "${config.security.acme.certs."sanic.example.com".directory}/key.pem";
}
'';
default = {
host = "[::1]";
port = 80;
tls = false;
};
type = lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
default = "[::1]";
description = "Host to bind to.";
};
port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = "Port to listen on.";
};
tls = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enables HTTPS.";
};
certificate = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Path to TLS certificate for HTTPS.";
};
key = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Path to TLS key for HTTPS.";
};
};
};
};
backend = lib.mkOption {
description = "Configure MPD backend.";
example = lib.literalExpression ''
{
host = "localhost";
port = 6600;
}
'';
default = {
host = "localhost";
port = 6600;
};
type = lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
default = "localhost";
description = "Hostname or IP of MPD instance.";
};
port = lib.mkOption {
type = lib.types.port;
default = 6600;
description = "Port of MPD instance.";
};
};
};
};
};
config = lib.mkIf cfg.enable {
systemd.services."sanic" = {
description = "sanic - chaos music control";
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
serviceConfig = {
Restart = "always";
RestartSec = 30;
ExecStart = execCommand;
User = "sanic";
Group = "sanic";
AmbientCapabilities = lib.mkIf (cfg.ui.port < 1000) [ "CAP_NET_BIND_SERVICE" ];
CapabilityBoundingSet = lib.mkIf (cfg.ui.port < 1000) [ "CAP_NET_BIND_SERVICE" ];
NoNewPrivileges = true;
};
wantedBy = [ "multi-user.target" ];
};
};
#meta = {
# maintainers = with lib.maintainers; [ xengi ];
# doc = ./default.xml;
#};
}

View file

@ -26,4 +26,3 @@ TimeoutStartSec=900
[Install]
WantedBy=multi-user.target default.target

View file

@ -1,24 +1,29 @@
package main
import (
"fmt"
"github.com/labstack/echo-contrib/echoprometheus"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"gopkg.in/ini.v1"
"net/http"
"os"
"os/exec"
"embed"
"fmt"
"net/http"
"os"
"os/exec"
"github.com/labstack/echo-contrib/echoprometheus"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"gopkg.in/ini.v1"
)
// Config holds the configuration for the mpd connection and for the web server.
//go:embed static/*
var staticFS embed.FS
// Config holds the configuration for the mpd backend connection and for the web server.
type Config struct {
MPD struct {
BACKEND struct {
Hostname string `ini:"hostname"`
Port int `ini:"port"`
Username string `ini:"username"`
Password string `ini:"password"`
} `ini:"mpd"`
} `ini:"backend"`
UI struct {
Hostname string `ini:"hostname"`
Port int `ini:"port"`

View file

@ -1,30 +0,0 @@
{ self, ...}: {config, lib, pkgs, ...}:
let
cfg = config.services.sanic;
format = pkgs.formats.ini { };
in
{
options.services.sanic = {
enable = mkEnableOption (lib.mdDoc "sanic");
settings = mkOption {
type = format.type;
default = { };
description = lib.mkDoc ''
'';
};
};
config = mkIf cfg.enable {
systemd.services.sanic = {
description = "chaos music control";
wantedBy = [ "multi-user.target" "default.target" ];
serviceConfig = {
DynamicUser = true;
ExecStart = "${self.packages.${pkgs.system}.default}/bin/sanic";
Restart = "on-failure";
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
};
};
};
}

15
sse.go
View file

@ -1,13 +1,14 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/fhs/gompd/v2/mpd"
"github.com/labstack/echo/v4"
"io"
"time"
"bytes"
"encoding/json"
"fmt"
"io"
"time"
"github.com/fhs/gompd/v2/mpd"
"github.com/labstack/echo/v4"
)
// Event represents Server-Sent Event.

View file

@ -1,11 +1,38 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<meta name="application-name" content="Sanic">
<!-- seo data -->
<meta name="description" lang="en" content="chaos music control inspired by relaxx player">
<meta name="keywords" lang="en" content="music, playlist, mpd, ui">
<meta name="author" content="XenGi">
<link rel="canonical" href="https://gitlab.com/XenGi/sanic">
<!-- social media -->
<meta property="og:title" content="Sanic">
<meta property="og:description" content="chaos music control inspired by relaxx player">
<meta property="og:url" content="https://gitlab.com/XenGi/sanic">
<meta property="og:image" content="https://gitlab.com/XenGi/sanic/-/raw/main/static/img/sanic-logo.webp?ref_type=heads&inline=false">
<meta property="og:image:alt" content="Crappy picture of sonic the hedgehog">
<meta name="twitter:title" content="Sanic">
<meta name="twitter:description" content="chaos music control inspired by relaxx player">
<meta name="twitter:url" content="https://gitlab.com/XenGi/sanic">
<meta name="twitter:image:src" content="https://gitlab.com/XenGi/sanic/-/raw/main/static/img/sanic-logo.webp?ref_type=heads&inline=false">
<meta name="twitter:image:alt" content="Crappy picture of sonic the hedgehog">
<!-- icons and color -->
<meta name="theme-color" content="#000000">
<meta name="theme-scheme" content="dark">
<link rel="shortcut icon" href="/favicon.ico">
<title>Sanic</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="treeview.css">
<link rel="icon" href="favicon.ico" sizes="16x16 32x32 48x48 64x64" type="image/png">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/treeview.css">
</head>
<body>
@ -17,7 +44,7 @@
<input type="text" id="control-playlist-name" name="playlist-name" autofocus>
<button>Save</button>
</form>
</dialog>
</dialog><!--/#save-playlist-->
<main>
<div id="nav">
@ -36,7 +63,7 @@
<div class="spaced">
<label for="control-progress"></label>
<input type="range" id="control-progress" name="progress" min="0" step="1" />
</div>
</div><!--/.spaced-->
</div>
<div>
<div class="spaced">
@ -63,20 +90,20 @@
<!--<input type="text" id="control-track" name="track" disabled="disabled" />-->
<div class="marquee" id="control-track" data-songid="">
<span></span>
</div>
</div><!--/#control-track /.marquee-->
</div>
<div>
<label for="control-time">Time:</label>
<input type="text" id="control-time" name="time" value="00:00:00/00:00:00" disabled="disabled" />
</div>
</div>
</div><!--/.wide-->
<div id="sanic-logo">
<div><!-- TODO: try to remove this div -->
<img alt="sanic logo" src="img/sanic-logo.webp" />
Sanic &copy; 2023
</div>
</div><!--/#sanic-logo-->
</div>
</div><!--/#nav-->
<div id="queue">
<table>
<thead>
@ -179,7 +206,7 @@
</li>
</ul>
</li>
</ul>
</ul><!--/#tree-->
</div>
<div>
actions

View file

@ -157,7 +157,6 @@ dialog_save_playlist_close.addEventListener("click", () => {
dialog_save_playlist.close()
});
// Add API calls to controls
control_search_submit.addEventListener("click", event => {
@ -215,8 +214,6 @@ control_delete_playlist.addEventListener("click", () => {
});
});
// Add API calls to controls
control_update_db.addEventListener("click", (event) => {
console.log("Issuing database update");
fetch(`${API_URL}/update_db`).then(async r => {