Media build part 2 (unreleased): drive wiring in every app (R-1024/R-1037), old-install repair (R-1047), drive events notice + real restart now/later + stream check, file browser lets go of dead/removed drives and recreates on return (R-1038), safe removal keeps the drive (R-1044), Plex claim on the running server + probe accept (R-1028/R-1045)
gates / gates (push) Successful in 45s
gates / gates (push) Successful in 45s
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
1 parent
78c894234c
commit
f580d88a14
46 files changed
+3700
-8
No files matched your search
@@ -0,0 +1,86 @@
|
||||
package mediawire
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
||||
)
|
||||
|
||||
// DockerBox is the production Box: the docker CLI on this guest.
|
||||
type DockerBox struct{}
|
||||
|
||||
func (DockerBox) Running(c string) bool {
|
||||
out, err := dockerexec.Command("docker", "inspect", "-f", "{{.State.Running}}", c).Output()
|
||||
return err == nil && strings.TrimSpace(string(out)) == "true"
|
||||
}
|
||||
|
||||
func (DockerBox) Env(c, name string) (string, error) {
|
||||
out, err := dockerexec.Command("docker", "exec", c, "printenv", name).Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s: reading %s: %w", c, name, err)
|
||||
}
|
||||
return strings.TrimRight(string(out), "\n"), nil
|
||||
}
|
||||
|
||||
func (DockerBox) ReadFile(c, path string) ([]byte, error) {
|
||||
out, err := dockerexec.Command("docker", "exec", c, "cat", path).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: reading %s: %w", c, path, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (DockerBox) EnsureDir(p string) error { return appbackup.EnsureUserdataDir(p) }
|
||||
|
||||
func (DockerBox) Mounts(c string) map[string]string {
|
||||
out, err := dockerexec.Command("docker", "inspect", "-f", "{{json .Mounts}}", c).Output()
|
||||
res := map[string]string{}
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
var ms []struct {
|
||||
Source string `json:"Source"`
|
||||
Destination string `json:"Destination"`
|
||||
}
|
||||
if json.Unmarshal(out, &ms) == nil {
|
||||
for _, m := range ms {
|
||||
res[filepath.Clean(m.Destination)] = m.Source
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// JellyfinKey copies Jellyfin's database (with its -wal/-shm, R-286's lesson) into a 0700 temp dir, reads the API key
|
||||
// with this name read-only, and removes the copy at once. The key is returned to the caller only.
|
||||
func (DockerBox) JellyfinKey(name string) (string, error) {
|
||||
dir, err := os.MkdirTemp("", "felhom-jf-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
for _, f := range []string{"jellyfin.db", "jellyfin.db-wal", "jellyfin.db-shm"} {
|
||||
err := dockerexec.Command("docker", "cp", "jellyfin:/config/data/"+f, filepath.Join(dir, f)).Run()
|
||||
if err != nil && f == "jellyfin.db" {
|
||||
return "", fmt.Errorf("copying Jellyfin's database: %w", err)
|
||||
}
|
||||
}
|
||||
db, err := sql.Open("sqlite", "file:"+filepath.Join(dir, "jellyfin.db"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
var key string
|
||||
err = db.QueryRow(`SELECT AccessToken FROM ApiKeys WHERE Name = ? ORDER BY DateCreated DESC LIMIT 1`, name).Scan(&key)
|
||||
if err == sql.ErrNoRows {
|
||||
return "", nil
|
||||
}
|
||||
return key, err
|
||||
}
|
||||
Reference in new issue
Block a user