master
bel 2021-09-14 06:44:17 -06:00
commit c5fa6baecd
22 changed files with 828 additions and 0 deletions

8
ampache/build_and_run.sh Executable file
View File

@ -0,0 +1,8 @@
#! /bin/bash
docker run --rm -it \
--name=ampache \
-v $PWD/mnt:/media:ro \
-p 8080:80 \
ampache/ampache

15
booksonic/build_and_run.sh Executable file
View File

@ -0,0 +1,15 @@
#! /bin/bash
docker run --rm -it \
--name=booksonic \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-e TZ=Europe/London \
-e CONTEXT_PATH=localhost \
-p 4041:4040 \
-v $PWD/mnt/config:/config \
-v $PWD/mnt/audiobooks:/audiobooks \
-v $PWD/mnt/podcasts:/podcasts \
-v $PWD/mnt/othermedia:/othermedia \
linuxserver/booksonic

23
koel/build_and_run.sh Executable file
View File

@ -0,0 +1,23 @@
#! /bin/bash
cd "$(dirname "${BASH_SOURCE[0]}")"
mkdir -p ./mnt/{media,config,storage}
chmod -R 777 ./mnt
echo 'The default username for the web ui is "admin@example.com", password is "admin"'
echo 'Run `chmod -R 777 /opt/koel/storage` when it starts nginx'
docker run --rm -it \
-p 8050:8050 \
-p 8060:8060 \
--name=koel \
-v $PWD/mnt/media:/media \
-e PHP_MEMORY_LIMIT=2048 \
-e FASTCGI_READ_TIMEOUT=6000 \
-e UMASK=000 \
-e PUID=0 \
-e PGID=0 \
binhex/arch-koel
#-v $PWD/mnt/storage:/opt/koel/storage \
#-v $PWD/mnt/config:/config \

3
koel/compose_build_and_run.sh Executable file
View File

@ -0,0 +1,3 @@
#! /bin/bash
docker-compose up

38
koel/docker-compose.yaml Executable file
View File

@ -0,0 +1,38 @@
version: '3.4'
services:
koel:
image: 0xcaff/koel
depends_on:
- database
ports:
- 8080:80
environment:
DB_CONNECTION: mysql
DB_HOST: database
DB_USERNAME: koel
DB_PASSWORD: koel
DB_DATABASE: koel
volumes:
- music:/music
- covers:/var/www/html/public/img/covers
database:
image: mysql/mysql-server:5.7
volumes:
- db:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: koel
MYSQL_USER: koel
MYSQL_PASSWORD: koel
volumes:
db:
driver: local
music:
driver: local
covers:
driver: local

2
koel/review Executable file
View File

@ -0,0 +1,2 @@
FUCK its nice but doesnt save position

7
mpd-ympd-proxy-pause/.gitignore vendored Executable file
View File

@ -0,0 +1,7 @@
*.swo
*.swp
mnt
goprox/goprox
goproxystate
libmpc
mpc

37
mpd-ympd-proxy-pause/Dockerfile Executable file
View File

@ -0,0 +1,37 @@
FROM frolvlad/alpine-glibc:alpine-3.9_glibc-2.28
#FROM vimagick/mpd:latest
RUN apk add --no-cache \
gcc libc-dev \
curl \
bash \
mpd \
mpc \
fcron \
busybox-suid \
ympd
RUN crond
WORKDIR /opt
RUN addgroup -S user && adduser -S -G user user
RUN mkdir -p \
/mnt/media \
/mnt/playlists \
/mnt/database \
/var/log/mpd/mpd.log \
&& chown -R user:user /mnt /var/log/mpd /opt
USER user
COPY ./pause.c ./pause.c
RUN gcc ./pause.c -o ./pause
COPY ./goprox/goprox ./goprox
COPY ./mpd.conf /etc/mpd.conf
COPY ./entrypoint.sh ./entrypoint.sh
COPY ./stop.sh ./stop.sh
CMD []
ENTRYPOINT ["bash", "./entrypoint.sh"]

View File

@ -0,0 +1,28 @@
#! /bin/bash
set -e
set -u
cd "$(dirname "$BASH_SOURCE")"
pushd ./goprox
GOOS=linux GOARCH=amd64 go build
popd
docker network create mpdnet || true
docker build -t dev:dev .
chmod -R 777 $PWD/mnt $PWD/goproxystate
docker images | grep dev.*dev
docker run --rm -it \
--name dev \
--network mpdnet \
-v $PWD/mnt:/mnt \
-v $PWD/mpd.conf:/etc/mpd.conf \
-v $PWD/goproxystate:/opt/goproxystate \
-p 4041:4041 \
-p 8000:8000 \
-p 7000:7000 \
dev:dev

View File

@ -0,0 +1,68 @@
#! /bin/bash
set -u
set -e
function main() {
start_mpd
init_mpd
start_ympd
start_goprox
init_cron
exec /opt/pause
}
function start_mpd() {
mpd --stdout --no-daemon &
disown %1
sleep 1
until curl localhost:6600 2> /dev/null; do
echo waiting for mpd...
sleep 1
done
}
function start_ympd() {
ympd -h localhost -p 6600 -w 4041 &
disown %1
}
function start_goprox() {
./goprox -port 7000 -httpd 8000 -mpd 6600 &
disown %1
}
function init_mpd() {
mpc update
mpc consume 0
mpc repeat 1
mpc random 1
mpc clear
if [ -n "$(mpc lsplaylists | grep RESTORE)" ]; then
mpc load RESTORE
fi
mpc play
mpc pause
}
function init_cron() {
echo '
#! /bin/sh
set -e
set -u
mpc rm RESTORE || true
mpc save RESTORE
echo "Done: $(date)" >> /opt/mpd.save.log
' > /opt/mpd.save.sh
chmod +x /opt/mpd.save.sh
echo '*/15 * * * * sh /opt/mpd.save.sh' > /opt/cron
crontab /opt/cron
}
if [ "$0" == "${BASH_SOURCE[0]}" ]; then
main "${@:-}"
fi

View File

@ -0,0 +1,2 @@
https://github.com/MusicPlayerDaemon/mpc/blob/master/src/command.c
^ get the commands raw

View File

@ -0,0 +1,277 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/textproto"
"os"
"os/exec"
"os/signal"
"strings"
"sync/atomic"
"syscall"
"time"
)
type mpd struct {
conns int32
port string
httpd string
mpd string
stateChange chan string
stateFilePath string
}
func main() {
listenPort := flag.String("port", "7000", "port to listen on")
httpdPort := flag.String("httpd", "8000", "port to forward to")
mpdPort := flag.String("mpd", "6600", "port of mpd")
flag.Parse()
mpd := &mpd{
conns: int32(0),
port: ":" + strings.TrimPrefix(*listenPort, ":"),
httpd: ":" + strings.TrimPrefix(*httpdPort, ":"),
mpd: ":" + strings.TrimPrefix(*mpdPort, ":"),
stateChange: make(chan string, 6),
stateFilePath: "/mnt/goproxystate",
}
go mpd.proxy()
go mpd.periodic()
catchSignal()
mpd.close()
}
func (mpd *mpd) proxy() {
go mpd.queueStateChange()
mpd.setState("pause")
mpd.loadState()
log.Printf("xferring %v -> %v", mpd.port, mpd.httpd)
listen, err := net.Listen("tcp", ":"+strings.TrimPrefix(mpd.port, ":"))
if err != nil {
panic(err)
}
for {
conn, err := listen.Accept()
if err != nil {
log.Printf("accept err: %v", err)
} else {
remote, err := net.Dial("tcp", mpd.httpd)
if err != nil {
log.Printf("dial err: %v", err)
} else {
log.Printf("xferring r:%v <-> c:%v", remote.RemoteAddr(), conn.RemoteAddr())
go mpd.xfer(conn, remote)
go mpd.xfer(remote, conn)
}
}
}
}
func (mpd *mpd) periodic() {
for {
time.Sleep(time.Hour)
if err := mpd.saveState(); err != nil {
log.Printf("MPD failed to save state: %v", err)
}
}
}
func (mpd *mpd) queueStateChange() {
minSilence := time.Second * 1
for {
last := <-mpd.stateChange
complete := false
for !complete {
select {
case last = <-mpd.stateChange:
case <-time.After(minSilence):
complete = true
}
}
mpd.setState(last)
}
}
func (mpd *mpd) queueState(state string) {
log.Printf("queue state %q", state)
mpd.stateChange <- state
}
func (mpd *mpd) setState(state string) {
log.Printf("Set state: %v", state)
defer log.Printf("/Set state: %v", state)
was, err := mpd.getStatus()
if err != nil {
log.Printf("cannot get state: %v", err)
return
}
log.Printf("WAS: %v", was)
if was["state"] == state {
log.Printf("state is already %q: %q", state, was["state"])
return
}
if _, err := mpd.mpdCommand(state); err != nil {
log.Printf("MPD %q returned err on cmd: %v", state, err)
return
}
if state != "pause" {
return
}
if _, err := mpd.mpdCommand("seekcur -15"); err != nil {
log.Printf("MPD seek -15s returned err: %v", err)
return
} else if err := mpd.saveState(); err != nil {
log.Printf("MPD failed to save state: %v", err)
return
}
}
func (mpd *mpd) loadState() error {
log.Printf("LOAD STATE")
var m map[string]string
if b, err := ioutil.ReadFile(mpd.stateFilePath); err != nil {
log.Printf("1 %v", err)
return err
} else if err := json.Unmarshal(b, &m); err != nil {
log.Printf("2 %v", err)
return err
} else if _, err := mpd.mpdCommand("play " + m["song"]); err != nil {
log.Printf("3 %v", err)
return err
} else if _, err := mpd.mpdCommand("pause"); err != nil {
log.Printf("4 %v", err)
return err
} else if _, err := mpd.mpdCommand("seekcur " + m["elapsed"]); err != nil {
log.Printf("5 %v", err)
return err
}
return nil
}
func (mpd *mpd) saveState() error {
if is, err := mpd.getStatus(); err != nil {
return err
} else if b, err := json.Marshal(is); err != nil {
return err
} else if err := ioutil.WriteFile(mpd.stateFilePath, b, os.ModePerm); err != nil {
return err
}
shPath, err := exec.LookPath("sh")
if err != nil {
return err
}
cmd := exec.Command(shPath, "/opt/mpd.save.sh")
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (mpd *mpd) mpdCommand(cmd string) ([]string, error) {
mpc, err := textproto.Dial("tcp", mpd.mpd)
if err != nil {
return nil, err
}
defer mpc.Close()
if ok, err := mpc.ReadLine(); err != nil || !strings.HasPrefix(ok, "OK MPD") {
return nil, err
}
id, err := mpc.Cmd(cmd)
if err != nil {
return nil, err
}
mpc.StartResponse(id)
defer mpc.EndResponse(id)
b := []string{}
line, err := mpc.ReadLine()
for err == nil && line != "OK" {
b = append(b, line)
line, err = mpc.ReadLine()
}
return b, err
}
func (mpd *mpd) getStatus() (map[string]string, error) {
lines, err := mpd.mpdCommand("status")
if err != nil {
return nil, err
}
m := make(map[string]string)
for _, line := range lines {
splits := strings.Split(line, ": ")
if len(splits) != 2 {
continue
}
m[splits[0]] = splits[1]
}
return m, nil
}
func (mpd *mpd) close() {
log.Printf("CLOSE")
}
func (mpd *mpd) inc() {
if atomic.AddInt32(&mpd.conns, 1) == 1 {
mpd.queueState("play")
}
mpd.blockUntilPlaying()
}
func (mpd *mpd) blockUntilPlaying() {
for {
time.Sleep(time.Second)
resp, err := http.Get(fmt.Sprintf("http://localhost%s", mpd.httpd))
if err != nil {
continue
}
defer resp.Body.Close()
b := make([]byte, 1024)
if _, err := resp.Body.Read(b); err != nil {
continue
}
return
}
}
func (mpd *mpd) dec() {
if atomic.AddInt32(&mpd.conns, -1) == 0 {
mpd.queueState("pause")
}
}
func (mpd *mpd) xfer(dst io.WriteCloser, src io.ReadCloser) {
mpd.inc()
defer mpd.dec()
defer dst.Close()
defer src.Close()
io.Copy(dst, src)
log.Printf("xfer %v -> %v ended", dst, src)
}
func catchSignal() {
log.Printf("listening for signal")
sigc := make(chan os.Signal)
signal.Notify(sigc,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
)
<-sigc
}

21
mpd-ympd-proxy-pause/mpd.conf Executable file
View File

@ -0,0 +1,21 @@
db_file "/mnt/database/database"
music_directory "/mnt/media"
playlist_directory "/mnt/playlists"
state_file "/opt/state"
auto_update "yes"
bind_to_address "0.0.0.0"
audio_output {
type "httpd"
name "Mpd_Lame"
port "8000"
max_clients "0"
encoder "flac"
format "44100:16:1"
#encoder "lame"
#bitrate "96"
}

19
mpd-ympd-proxy-pause/pause.c Executable file
View File

@ -0,0 +1,19 @@
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
int main() {
int stat;
int sig;
sigset_t set;
sigemptyset(&set);
printf("add signal SIGINT: %i\n", sigaddset(&set, SIGINT));
printf("add signal SIGKILL: %i\n", sigaddset(&set, SIGKILL));
printf("add signal SIGTERM: %i\n", sigaddset(&set, SIGTERM));
sigprocmask(SIG_BLOCK, &set, NULL);
printf("Waiting...\n");
stat = sigwait(&set, &sig);
printf("Wait complete: %i (%i)\n", sig, stat);
printf("`sh /opt/stop.sh`: %i\n", system("sh /opt/stop.sh"));
return 0;
}

View File

@ -0,0 +1,92 @@
#! /bin/bash
set -u
set -e
name="$(basename ${BASH_SOURCE})"
dir="$(dirname $(realpath ${BASH_SOURCE}))"
mntme="$dir/$name-mnt"
mkdir -p $mntme
chmod -R 777 $mntme || true
docker rm -f \
mpd \
ympd \
rompr \
icecast \
|| true
# network
docker network create mpdnet || true
# MPD // server
mkdir -p $mntme/mpd/{playlists,db}
touch $mntme/mpd/{conf,state}
if [ ! -e $mntme/mpd/media ]; then
ln -s /volume1/video/Bel/Podcast $mntme/mpd/media
fi
sudo docker run -d \
--restart=unless-stopped \
--name=mpd \
--network=mpdnet \
--net-alias mpd \
--net-alias mpd.mpd \
-v $mntme/mpd/conf:/etc/mpd.conf \
-v $mntme/mpd/state:/state \
-v $mntme/mpd/playlists:/playlists \
-v $mntme/mpd/media:/media \
-v $mntme/mpd/db:/db \
-p 6600:6600 \
-p 6501-6510:6501-6510 \
-p 8000:8000 \
vimagick/mpd
sleep 2
for cmd in update "consume 1" clear "load RESTORE" play pause; do
docker exec -i mpd mpc $cmd
done
# YMPD // remote
sudo docker run -d \
--restart=unless-stopped \
--name=ympd \
--network=mpdnet \
--net-alias ympd \
-p 4041:8080 \
vitiman/alpine-ympd
# rompr // remote
#sudo docker run -d \
# --restart=unless-stopped \
# --name=rompr \
# --network=mpdnet \
# --net-alias rompr \
# -p 4042:80 \
# rawdlite/rompr
# icecast // remote+player
#sudo docker run -d \
# --restart=unless-stopped \
# --name=icecast \
# --network=mpdnet \
# --net-alias icecast \
# -p 8000:8000 \
# vitiman/alpine-icecast
# rompr // remote
docker exec -i mpd apk add --no-cache fcron
docker exec -i mpd mkdir -p /etc/periodic/15min || true
echo '
#! /bin/sh
mpc rm RESTORE
mpc save RESTORE
echo "Done: $(date)" >> /var/log/mpd_save
' | docker exec -i mpd sh -c 'cat > /mpd_save.sh'
echo '
*/25 * * * * sh /mpd_save.sh
' | docker exec -i mpd sh -c 'cat > /save_cron'
docker exec -i mpd chmod +x /mpd_save.sh
docker exec -i mpd crond
docker exec -i mpd crontab /save_cron
echo DONE

View File

@ -0,0 +1,52 @@
db_file "/db/database"
music_directory "/media"
playlist_directory "/playlists"
state_file "/state"
auto_update "yes"
#audio_output {
# type "httpd"
# name "Mpd_Ogg"
# encoder "vorbis" # optional, vorbis or lame
# port "6501"
# #quality "5.0" # do not define if bitrate is defined
# bitrate "128"
# max_clients "0" # optional 0=no limit
#}
audio_output {
type "httpd"
name "Mpd_Lame"
encoder "lame"
port "8000"
bitrate "128"
format "44100:16:1"
max_clients "0"
bind_to_address "0.0.0.0"
}
#
#audio_output {
# type "httpd"
# name "Mpd_Wav"
# encoder "wave"
# port "6502"
#}
#
#audio_output {
# type "httpd"
# name "Mpd_Flac"
# encoder "flac"
# port "6503"
# format "44100:16:2"
#}
#audio_output {
# type "httpd"
# name "Mpd_Lame"
# encoder "lame"
# port "6504"
# bitrate "128"
#}

View File

@ -0,0 +1,27 @@
### PAUSE
FROM frolvlad/alpine-glibc:alpine-3.9_glibc-2.28
RUN apk add --no-cache \
gcc \
libc-dev
COPY ./pause.c /pause.c
RUN gcc /pause.c -o /pause
### RUNNER
FROM frolvlad/alpine-glibc:alpine-3.9_glibc-2.28
RUN apk add --no-cache \
mpd \
mpc \
fcron \
ympd
RUN mkdir -p \
/mnt/{media,playlists,database} \
/var/log/mpd/mpd.log
COPY --from=0 /pause /pause
COPY ./entrypoint.sh /entrypoint.sh
COPY ./stop.sh /stop.sh
CMD []
ENTRYPOINT ["sh", "/entrypoint.sh"]

13
mpd-ympd-proxy-pause/stop.sh Executable file
View File

@ -0,0 +1,13 @@
#! /bin/sh
set -e
set -u
ps aux | grep goprox | grep -v grep
kill $(ps aux | grep goprox | grep -v grep | awk '{print $1}') || true
mpd
until curl localhost:6600 2> /dev/null; do
sleep 1
done
bash "$(crontab -l | grep "\*" | awk '{print $NF}')"

57
rompr/build_and_run.sh Executable file
View File

@ -0,0 +1,57 @@
#! /bin/bash
cd "$(dirname "${BASH_SOURCE[0]}")"
set -e
set -u
function clean() {
docker rm -f \
mpd \
ympd
}
trap clean EXIT
# network
docker network create podcastnet || true
# mpd // server
mkdir -p mpd-mnt/{media,playlists,db}
mkdir -p mpd-mnt/playlists
touch mpd-mnt/conf
docker run --rm -d \
--name mpd \
-p 6501-6510:6501-6510 \
-v $PWD/mpd-mnt/conf:/etc/mpd.conf \
-v $PWD/mpd-mnt/playlists:/playlists \
-v $PWD/mpd-mnt/media:/media \
-v $PWD/mpd-mnt/db:/db \
--network podcastnet \
--net-alias mpd \
vimagick/mpd
# --device /dev/snd
#-v $PWD/mpd-mnt/media:/var/lib/mpd/music \
#-v $PWD/mpd-mnt/playlists:/var/lib/mpd/playlists \
for cmd in "update" "repeat on" "random on" "clear" "play"; do
docker exec -i mpd mpc $cmd
done
## rompr // remote1
#docker run --rm -d \
# --name rompr \
# -p 8801:80 \
# --network podcastnet \
# --net-alias rompr.rompr \
# rawdlite/rompr
# ympd // remoteA
docker run --rm -d \
--name ympd \
-p 4041:8080 \
--network podcastnet \
--net-alias ympd \
vitiman/alpine-ympd
read -p "Enter when done..." asf

37
rompr/mpd-mnt/conf Executable file
View File

@ -0,0 +1,37 @@
db_file "/db/database"
music_directory "/media"
playlist_directory "/playlists"
auto_update "yes"
audio_output {
type "httpd"
name "Mpd_Ogg"
encoder "vorbis" # optional, vorbis or lame
port "6501"
quality "5.0" # do not define if bitrate is defined
max_clients "0" # optional 0=no limit
}
audio_output {
type "httpd"
name "Mpd_Wav"
encoder "wave"
port "6502"
}
audio_output {
type "httpd"
name "Mpd_Flac"
encoder "flac"
port "6503"
format "44100:16:2"
}
audio_output {
type "httpd"
name "Mpd_Lame"
encoder "lame"
port "6504"
bitrate "128"
}

BIN
rompr/mpd-mnt/db/database Executable file

Binary file not shown.

2
rompr/mpd-mnt/playlists/a.m3u Executable file
View File

@ -0,0 +1,2 @@
03 - 1985.mp3
03 - 1985.mp3