diff --git a/install.sh b/install.sh index 4c058beb9..d173dae5f 100644 --- a/install.sh +++ b/install.sh @@ -2,8 +2,12 @@ alias=r1 -# 20221005 -[[ -e /srv/http/data/system/hddspindown ]] && mv /srv/http/data/system/{hddspindown,apm} +. /srv/http/bash/addons.sh + +# 20221007 +grep -q hard,intr /etc/fstab && sed -i '/hard,intr/soft/' /etc/fstab + +[[ -e $dirsystem/hddspindown ]] && mv $dirsystem/{hddspindown,apm} if [[ ! -e /boot/kernel.img ]]; then dir=/etc/systemd/system @@ -43,11 +47,10 @@ fi systemctl daemon-reload # 20220916 -dirmpd=/srv/http/data/mpd if (( $( cat $dirmpd/counts | wc -l ) == 1 )); then echo '{ - "playlists" : '$( ls -1 /srv/http/data/playlists | wc -l )' -, "webradio" : '$( find -L /srv/http/data/webradio -type f ! -path '*/img/*' | wc -l )' + "playlists" : '$( ls -1 $dirplaylists | wc -l )' +, "webradio" : '$( find -L $dirwebradio -type f ! -path '*/img/*' | wc -l )' }' > $dirmpd/counts fi @@ -58,15 +61,16 @@ fi rm /srv/http/bash/{camilladsp*,features*,networks*,player*,relays*,system*} &> /dev/null #------------------------------------------------------------------------------- -. /srv/http/bash/addons.sh - installstart "$1" getinstallzip -chmod +x $dirbash/cmd.sh -$dirbash/cmd.sh dirpermissions +chmod +x $dirbash/settings/system.sh +$dirbash/settings/system.sh dirpermissions [[ -e $dirsystem/color ]] && $dirbash/cmd.sh color installfinish #------------------------------------------------------------------------------- + +# 20221010 +[[ -e /srv/http/shareddata ]] && echo -e "$info Shared Data must be disabled and setup again." diff --git a/srv/http/assets/css/common.css b/srv/http/assets/css/common.css index 1c46ae13b..883d05d8b 100644 --- a/srv/http/assets/css/common.css +++ b/srv/http/assets/css/common.css @@ -1,6 +1,6 @@ @font-face { font-family: rern; - src: url( '/assets/fonts/rern.2022100300.woff2' ) format( 'woff2' ); + src: url( '/assets/fonts/rern.2022101100.woff2' ) format( 'woff2' ); font-display: block; font-style: normal; font-weight: normal; diff --git a/srv/http/assets/fonts/rern.woff2 b/srv/http/assets/fonts/rern.woff2 index d48a8fb3f..8af781cb9 100644 Binary files a/srv/http/assets/fonts/rern.woff2 and b/srv/http/assets/fonts/rern.woff2 differ diff --git a/srv/http/assets/js/context.js b/srv/http/assets/js/context.js index 4a014458c..800920307 100644 --- a/srv/http/assets/js/context.js +++ b/srv/http/assets/js/context.js @@ -1,9 +1,8 @@ function addReplace( cmd, command, title, msg ) { - if ( cmd === 'addplay' || cmd === 'replaceplay' || cmd === 'replace' ) $( '#stop' ).click(); + var play = cmd === 'addplay' || cmd === 'replaceplay'; + if ( play || cmd === 'replace' ) $( '#stop' ).click(); bash( command, function() { - if ( !G.display.playbackswitch ) return - - if ( cmd === 'addplay' || cmd === 'replaceplay' ) $( '#playback' ).click(); + if ( G.display.playbackswitch && play ) $( '#playback' ).click(); } ); banner( title, msg, 'playlist' ); } @@ -780,6 +779,7 @@ $( '.contextmenu a, .contextmenu .submenu' ).click( function() { } if ( !mpccmd ) mpccmd = []; var sleep = G.mode.slice( -5 ) === 'radio' ? 1 : 0.2; + if ( G.status.state === 'play' && G.status.webradio ) sleep += 1; var contextCommand = { add : mpccmd , playnext : mpccmd diff --git a/srv/http/assets/js/features.js b/srv/http/assets/js/features.js index 8dc7fda13..c49a1e4b9 100644 --- a/srv/http/assets/js/features.js +++ b/srv/http/assets/js/features.js @@ -390,6 +390,7 @@ $( '#nfsserver' ).click( function() { , title : 'Server rAudio' , message : $this.prev().html() } ); + $this.prop( 'checked', G.nfsserver ); return } diff --git a/srv/http/assets/js/function.js b/srv/http/assets/js/function.js index a2b5c016d..7432af567 100644 --- a/srv/http/assets/js/function.js +++ b/srv/http/assets/js/function.js @@ -573,8 +573,8 @@ function getPlaybackStatus( withdisplay ) { if ( G.playback ) { displayPlayback(); } else if ( G.library ) { - if ( !$( '#lib-search-close' ).text() && !G.librarylist ) { - $.post( 'index-body.php', { modelist: 1 }, function( html ) { + if ( !G.librarylist ) { + $.post( 'mpdlibrary.php', { query: 'home' }, function( html ) { $( '#lib-mode-list' ).html( html ); renderLibrary(); } ); @@ -1248,9 +1248,7 @@ function renderPlaylist( data ) { setPlaylistScroll(); } else { G.htmlplaylist = data.html; - var timestamp = Math.floor( Date.now() / 1000 ); - var html = data.html.replaceAll( 'thumb.jpg', 'thumb.'+ timestamp +'.jpg' ); - $( '#pl-list' ).html( html +'

' ).promise().done( function() { + $( '#pl-list' ).html( data.html +'

' ).promise().done( function() { G.status.pllength = $( '#pl-list li' ).length; setPlaylistScroll(); imageLoad( 'pl-list' ); diff --git a/srv/http/assets/js/info.js b/srv/http/assets/js/info.js index 23a5d61b4..ca89abb63 100644 --- a/srv/http/assets/js/info.js +++ b/srv/http/assets/js/info.js @@ -163,34 +163,34 @@ info( { // default order : [ TYPE, ... ] // (sequence) (order of *** inputs) + values : [ 'VALUE', ... ] // (none) (default values - in layout order) + checkchanged : 1 // (none) (check values changed) + checkblank : 1 or [ i, ... ] // (none) (check values not blank / [ partial ] ) + checklength : { i: N, . } // (none) (required N characters in i) + checklength : { i: [ N, 'COND' ], ... } // (none) (required N: characters; COND: min, max; in i) + + beforeshow : FUNCTION // (none) (function after values set) + noreload : 1 // (none) (do not reset content - for update value) + filelabel : 'LABEL' // *** (browse button label) fileoklabel : 'LABEL' // 'OK' (upload button label) filetype : '.EXT, ...' // (none) (filter and verify filetype (with 'dot' - 'image/*' for all image types) - buttonlabel : [ 'LABEL', ... ] // *** (label array) + buttonlabel : [ 'LABEL', ... ] // *** (extra buttons - label array) button : [ FUNCTION, ... ] // (none) (function array) buttoncolor : [ 'COLOR', ... ] // 'var( --cm )' (color array) buttonfit : 1 // (none) (fit buttons width to label) - buttonnoreset : 1 // (none) (do not hide/reset content on button clicked) - - okno : 1 // (show) (no ok button) - oklabel : 'LABEL' // ('OK') (ok button label) - okcolor : 'COLOR' // var( --cm ) (ok button color) - ok : FUNCTION // (reset) (ok click function) + buttonnoreset : 1 // (none) (do not hide/reset content on button clicked) - player.js cancellabel : 'LABEL' // *** (cancel button label) cancelcolor : 'COLOR' // var( --cg ) (cancel button color) cancelshow : 1 // (hide) (show cancel button) cancel : FUNCTION // (reset) (cancel click function) - - values : [ 'VALUE', ... ] // (none) (default values - in layout order) - checkchanged : 1 // (none) (check values changed) - checkblank : 1 or [ i, ... ] // (none) (check values not blank / [ partial ] ) - checklength : { i: N, . } // (none) (required N characters in i) - checklength : { i: [ N, 'COND' ], ... } // (none) (required N: characters; COND: min, max; in i) - - beforeshow : FUNCTION // (none) (function after values set) - noreload : 1 // (none) (do not reset content - for update value) + + okno : 1 // (show) (no ok button) + oklabel : 'LABEL' // ('OK') (ok button label) + okcolor : 'COLOR' // var( --cm ) (ok button color) + ok : FUNCTION // (reset) (ok click function) } ); Get values: infoVal() @@ -201,12 +201,13 @@ Note: - Single value/function - no need to be array ` ); } -function infoReset() { +function infoReset( fn ) { if ( O.infoscroll ) $( 'html, body' ).scrollTop( O.infoscroll ); - $( '#infoOverlay' ) - .addClass( 'hide' ) - .empty(); - O = {} + if ( !O.buttonnoreset ) $( '#infoOverlay' ).addClass( 'hide' ); + setTimeout( function() { + if ( typeof fn === 'function' ) fn(); + $( '#infoOverlay' ).empty(); + }, 0 ); } O = {} @@ -222,15 +223,14 @@ function info( json ) { ` ); O.infoscroll = $( window ).scrollTop(); - // simple use as info( 'message' ) - setTimeout( function() { // allow consecutive infos - ////////////////////////////////////////////////////////////////////////// + /* $( '#infoOverlay' ).on( 'mousedown touchstart', function( e ) { if ( e.target.id === 'infoOverlay' ) $( '#infoX' ).click(); } );*/ + $( '#infoX' ).click( function() { - if ( O.cancel ) O.cancel(); - infoReset(); + delete O.buttonnoreset; + infoReset( O.cancel ); } ); if ( typeof O !== 'object' ) { $( '#infoIcon' ).addClass( 'fa fa-info-circle' ); @@ -286,18 +286,14 @@ function info( json ) { if ( O.button ) { if ( typeof O.button !== 'object' ) O.button = [ O.button ]; $( '#infoButtons' ).on( 'click', '.extrabtn', function() { - var fn = O.button[ $( this ).index( '.extrabtn' ) ]; - if ( fn ) fn(); - if ( !O.buttonnoreset ) infoReset(); + infoReset( O.button[ $( this ).index( '.extrabtn' ) ] ); } ); } $( '#infoCancel' ).one( 'click', function() { - if ( typeof O.cancel === 'function' ) O.cancel(); - infoReset(); + infoReset( O.cancel ); } ); $( '#infoOk' ).one( 'click', function() { - if ( typeof O.ok === 'function' ) O.ok(); - if ( !O.buttonnoreset ) infoReset(); + infoReset( O.ok ); } ); if ( O.fileoklabel ) { var htmlfile = '
' @@ -568,8 +564,6 @@ function info( json ) { O.nochange = O.values && O.checkchanged ? true : false; $( '#infoOk' ).toggleClass( 'disabled', O.blank || O.short || O.nochange ); // initial check infoCheckSet(); - ////////////////////////////////////////////////////////////////////////// - }, 0 ); } function checkBlank() { diff --git a/srv/http/assets/js/main.js b/srv/http/assets/js/main.js index 89cc042ba..4b758f83a 100644 --- a/srv/http/assets/js/main.js +++ b/srv/http/assets/js/main.js @@ -4,7 +4,6 @@ var G = { , apikeylastfm : 'd666cd06ec4fcf84c3b86279831a1c8e' , sharedsecret : '390372d3a1f60d4030e2a612260060e0' , bioartist : [] - , bookmarkedit : 0 , coverart : '/assets/img/coverart.svg' , coversave : 0 , covervu : '/assets/img/vu.svg' @@ -438,15 +437,18 @@ $( '#addons' ).click( function () { loader(); } ); $( '#library, #button-library' ).click( function() { - $( '#lib-path span' ).removeClass( 'hide' ); - if ( !$( '#lib-search-input' ).val() ) $( '#lib-search-close' ).empty(); - if ( G.library ) { - if ( G.librarylist ) G.scrolltop[ $( '#lib-path .lipath' ).text() ] = $( window ).scrollTop(); - renderLibrary(); - } else { - switchPage( 'library' ); - if ( G.status.updating_db ) banner( 'Library Database', 'Update ...', 'refresh-library blink' ); - } + $.post( 'mpdlibrary.php', { query: 'home' }, function( html ) { + $( '#lib-mode-list' ).html( html ); + $( '#lib-path span' ).removeClass( 'hide' ); + if ( !$( '#lib-search-input' ).val() ) $( '#lib-search-close' ).empty(); + if ( G.library ) { + if ( G.librarylist ) G.scrolltop[ $( '#lib-path .lipath' ).text() ] = $( window ).scrollTop(); + renderLibrary(); + } else { + switchPage( 'library' ); + if ( G.status.updating_db ) banner( 'Library Database', 'Update ...', 'refresh-library blink' ); + } + } ); } ); $( '#playback' ).click( function() { if ( G.playback && ( G.wH - $( '#coverart' )[ 0 ].getBoundingClientRect().bottom ) < 30 ) { @@ -1314,7 +1316,9 @@ $( '#button-lib-back' ).click( function() { } } } ); -$( '.mode' ).click( function() { +$( '#lib-mode-list' ).click( function( e ) { + if ( !G.press && $( '.bkedit' ).length && !$( e.target ).hasClass( 'bkedit' ) ) setBookmarkEdit(); +} ).on( 'click', '.mode', function() { var $this = $( this ); G.mode = $this.data( 'mode' ); $( '#lib-search-close' ).click(); @@ -1386,11 +1390,7 @@ $( '.mode' ).click( function() { query.path = G.mode.slice( -5 ) === 'radio' ? '' : path; query.modetitle = path; if ( query.query !== 'ls' ) G.query.push( query ); -} ); -$( '#lib-mode-list' ).click( function( e ) { - if ( !G.press && $( '.bkedit' ).length && !$( e.target ).hasClass( 'bkedit' ) ) setBookmarkEdit(); -} ); -$( '#lib-mode-list' ).on( 'click', '.mode-bookmark', function( e ) { // delegate - id changed on renamed +} ).on( 'click', '.mode-bookmark', function( e ) { // delegate - id changed on renamed $( '#lib-search-close' ).click(); if ( G.press || $( '.bkedit' ).length ) return @@ -1503,7 +1503,7 @@ $( '#lib-mode-list' ).on( 'click', '.mode-bookmark', function( e ) { // delegate } } ); } ) -$( '.mode-bookmark' ).press( setBookmarkEdit ); +$( '#lib-mode-list' ).press( '.mode-bookmark', setBookmarkEdit ); new Sortable( document.getElementById( 'lib-mode-list' ), { // onChoose > onClone > onStart > onMove > onChange > onUnchoose > onUpdate > onSort > onEnd ghostClass : 'lib-sortable-ghost' diff --git a/srv/http/assets/js/settings.js b/srv/http/assets/js/settings.js index dae81a6bf..791e0c0b8 100644 --- a/srv/http/assets/js/settings.js +++ b/srv/http/assets/js/settings.js @@ -2,9 +2,12 @@ function bash( command, callback, json ) { if ( typeof command === 'string' ) { var args = { cmd: 'bash', bash : command } } else { - if ( command[ 0 ] === 'cmd' ) { + var cmd0 = command[ 0 ]; + if ( cmd0 === 'cmd' ) { var filesh = 'cmd'; command.shift(); + } else if ( cmd0 === 'pkgstatus' || cmd0 === 'rebootlist' ) { + var filesh = 'settings/system'; } else { var filesh = 'settings/'+ page; } @@ -67,7 +70,7 @@ function currentStatus( id, refresh ) { notify( 'Get Data', id, page ); }, 1000 ); } - var command = services.includes( id ) ? [ 'cmd', 'pkgstatus', id ] : cmd[ id ]+' 2> /dev/null'; + var command = services.includes( id ) ? [ 'pkgstatus', id ] : cmd[ id ]+' 2> /dev/null'; bash( command, function( status ) { clearTimeout( timeoutGet ); $el.html( status ).promise().done( function() { @@ -413,32 +416,27 @@ $( '.container' ).on( 'click', '.status', function( e ) { if ( !$this.hasClass( 'single' ) ) currentStatus( $this.data( 'status' ) ); } ); $( '.close' ).click( function() { - if ( page === 'networks' ) { - clearTimeout( G.timeoutScan ); - bash( 'killall networks-scan.sh &> /dev/null' ); - } - bash( [ 'cmd', 'rebootlist' ], function( list ) { + bash( [ 'rebootlist' ], function( list ) { if ( !list ) { location.href = '/'; - } else { - var list = list.replace( /\^/s, '\n' ); - info( { - icon : page - , title : 'System Setting' - , message : `\ -Reboot required for: -${ list }` - , cancel : function() { - bash( 'rm -f /srv/http/data/shm/reboot /srv/http/data/tmp/backup.*' ); - location.href = '/'; - } - , okcolor : orange - , oklabel : 'Reboot' - , ok : function() { - bash( [ 'cmd', 'power', 'reboot' ] ); - } - } ); + return } + + info( { + icon : page + , title : 'System Setting' + , message : 'Reboot required for:

' + +'
'+ list +'
' + , cancel : function() { + bash( 'rm -f /srv/http/data/shm/reboot /srv/http/data/tmp/backup.*' ); + location.href = '/'; + } + , okcolor : orange + , oklabel : 'Reboot' + , ok : function() { + bash( [ 'cmd', 'power', 'reboot' ] ); + } + } ); } ); } ); $( '#button-data' ).click( function() { diff --git a/srv/http/assets/js/system.js b/srv/http/assets/js/system.js index 8940faa04..a6f7a3a7d 100644 --- a/srv/http/assets/js/system.js +++ b/srv/http/assets/js/system.js @@ -131,6 +131,15 @@ $( '#menu a' ).click( function() { var icon = 'usbdrive'; var title = 'Local Mount'; } + if ( G.shareddata && mountpoint === '/mnt/MPD/NAS/data' ) { + info( { + icon : 'networks' + , title : 'Network Storage' + , message : 'Shared Data is currently enabled.' + } ); + return + } + switch ( cmd ) { case 'forget': notify( title, 'Forget ...', icon ); @@ -512,9 +521,9 @@ $( '#setting-mpdoled' ).click( function() { , cancel : function() { $( '#mpdoled' ).prop( 'checked', G.mpdoled ); } - , buttonlabel : 'Logo' + , buttonlabel : !G.mpdoled ? '' : 'Logo' , button : !G.mpdoled ? '' : function() { - bash( '/srv/http/bash/cmd.sh mpdoledlogo' ); + bash( [ 'mpdoledlogo' ] ); } , ok : function() { notify( 'Spectrum OLED', G.mpdoled ? 'Change ...' : 'Enable ...', 'mpdoled' ); @@ -911,6 +920,7 @@ function infoMount( values ) { , title : shareddata ? 'Shared Data Server' : 'Add Network Storage' , content : htmlmount , values : values || [ 'cifs', '', ipsub, '', '', '', '', true ] + , checkblank : [ 0, 1, 2 ] , beforeshow : function() { $( '#infoContent td' ).eq( 0 ).css( 'width', 90 ); $( '#infoContent td' ).eq( 1 ).css( 'width', 230 ); @@ -952,7 +962,7 @@ function infoMount( values ) { } ); $share.on( 'keyup paste', function() { setTimeout( function() { - var slash = $( '#infoContent input[type=radio]:checked' ).val() === 'cifs' ? /[\/\\]/g : /\\//g; + var slash = $( '#infoContent input[type=radio]:checked' ).val() === 'cifs' ? /[\/\\]/g : /\\/g; $share.val( $share.val().replace( slash, '' ) ); }, 0 ); } ); @@ -1023,9 +1033,7 @@ function infoNFSconnect( ip ) { $( '#shareddata' ).prop( 'checked', false ); } , ok : function() { - setTimeout( function() { - infoNFSconnect( ip ); - },0 ); + infoNFSconnect( ip ); } } ); } diff --git a/srv/http/bash/cmd.sh b/srv/http/bash/cmd.sh index 8cfdf7a3a..f3e073689 100644 --- a/srv/http/bash/cmd.sh +++ b/srv/http/bash/cmd.sh @@ -114,11 +114,6 @@ jpgThumbnail() { esac pushstreamImage "$target" $type "$covername" } -mpdoledLogo() { - systemctl stop mpd_oled - type=$( grep mpd_oled /etc/systemd/system/mpd_oled.service | cut -d' ' -f3 ) - mpd_oled -o $type -L -} plAddPlay() { pushstreamPlaylist add if [[ ${1: -4} == play ]]; then @@ -232,6 +227,7 @@ stopRadio() { if [[ -e $dirshm/radio ]]; then systemctl stop radio dab rm -f $dirshm/{radio,status} + sleep 1 fi } urldecode() { # for webradio url to filename @@ -364,12 +360,6 @@ webRadioSampling() { case ${args[0]} in -addonsclose ) - script=${args[1]} - alias=${args[2]} - killall $script curl pacman &> /dev/null - rm -f /var/lib/pacman/db.lck /srv/http/*.zip $diraddons/$alias /usr/local/bin/uninstall_$alias.sh - ;; addonslist ) addonsListGet ${args[1]} bash=$( jq -r .push.bash $diraddons/addons-list.json ) # push bash @@ -522,9 +512,6 @@ s|(path.*hsl).*|\1(${hsg}75%);}| sed -i -E 's/\?v=.{10}/?v='$hash'/g' /srv/http/settings/camillagui/build/index.html pushstream reload 1 ;; -count ) - count - ;; coverartget ) path=${args[1]} coverartfile=$( ls -1X "$path"/coverart.* 2> /dev/null \ @@ -613,20 +600,6 @@ dabscan ) $dirbash/dab-scan.sh &> /dev/null & pushstream mpdupdate '{"type":"dabradio"}' ;; -dirpermissions ) - chmod 755 /srv /srv/http /srv/http/* /mnt /mnt/MPD /mnt/MPD/*/ - chown http:http /srv /srv/http /srv/http/* /mnt /mnt/MPD /mnt/MPD/*/ - chmod -R 755 /srv/http/{assets,bash,data,settings} - chown -R http:http /srv/http/{assets,bash,data,settings} - chown mpd:audio $dirmpd $dirmpd/mpd.db $dirplaylists 2> /dev/null - if [[ $( readlink $dirshareddata ) == $dirdata ]]; then - chmod 777 $filesharedip $dirshareddata/system/{display,order} - readarray -t dirs <<< $( showmount --no-headers -e localhost | awk 'NF{NF-=1};1' ) - for dir in "${dirs[@]}"; do - chmod 777 "$dir" - done - fi - ;; displaysave ) data=${args[1]} pushstream display "$data" @@ -873,12 +846,12 @@ mpcplayback ) command=play fi fi + stopRadio if [[ $command == play ]]; then mpc | grep -q '^\[paused\]' && pause=1 mpc -q $command $pos [[ $( mpc | head -c 4 ) == cdda && ! $pause ]] && pushstreamNotifyBlink 'Audio CD' 'Start play ...' audiocd else - stopRadio [[ -e $dirsystem/scrobble && $command == stop && $pos ]] && cp -f $dirshm/{status,scrobble} mpc -q $command killall cava &> /dev/null @@ -989,84 +962,11 @@ mpcupdate ) [[ $path == rescan ]] && mpc -q rescan || mpc -q update "$path" pushstream mpdupdate '{"type":"mpd"}' ;; -mpdoledlogo ) - mpdoledLogo - ;; ordersave ) data=$( jq <<< ${args[1]} ) pushstream order "$data" echo "$data" > $dirsystem/order ;; -partexpand ) - dev=$( mount | awk '/ on \/ / {printf $1}' | head -c -2 ) - if (( $( sfdisk -F $dev | head -1 | awk '{print $6}' ) != 0 )); then - echo -e "d\n\nn\n\n\n\n\nw" | fdisk $dev &>/dev/null - partprobe $dev - resize2fs ${dev}p2 - fi - ;; -pkgstatus ) - id=${args[1]} - pkg=$id - service=$id - case $id in - camilladsp ) - fileconf=$dircamilladsp/configs/camilladsp.yml - ;; - hostapd ) - conf="\ -# cat /etc/hostapd/hostapd.conf -$( cat /etc/hostapd/hostapd.conf ) - -# cat /etc/dnsmasq.conf" - ;; - localbrowser ) - pkg=chromium - fileconf=$dirsystem/localbrowser.conf - ;; - nfs-server ) - pkg=nfs-utils - systemctl -q is-active nfs-server && fileconf=/etc/exports - ;; - rtsp-simple-server ) - conf="\ -# rtl_test -t -$( script -c "timeout 1 rtl_test -t" | grep -v ^Script )" - ;; - smb ) - pkg=samba - fileconf=/etc/samba/smb.conf - ;; - snapclient|snapserver ) - pkg=snapcast - [[ $id == snapclient ]] && fileconf=/etc/default/snapclient - ;; - * ) - fileconf=/etc/$id.conf - ;; - esac - config="$( pacman -Q $pkg )" - if [[ $conf ]]; then - config+=" -$conf" - elif [[ -e $fileconf ]]; then - config+=" -# cat $fileconf -$( grep -v ^# $fileconf )" - fi - status=$( systemctl status $service \ - | sed -E '1 s|^.* (.*service) |\1|' \ - | sed -E '/^\s*Active:/ s|( active \(.*\))|\1|; s|( inactive \(.*\))|\1|; s|(failed)|\1|ig' ) - if [[ $pkg == chromium ]]; then - status=$( echo "$status" | grep -E -v 'Could not resolve keysym|Address family not supported by protocol|ERROR:chrome_browser_main_extra_parts_metrics' ) - elif [[ $pkg == nfs-utils ]]; then - status=$( echo "$status" | grep -v 'Protocol not supported' ) - fi - echo "\ -$config - -$status" - ;; playerstart ) player=${args[1]} [[ $player == bluetooth ]] && volumeGet save @@ -1174,15 +1074,6 @@ radiorestart ) sleep 1 rm $disshm/radiorestart ;; -rebootlist ) - [[ -e $dirshm/reboot ]] && cat $dirshm/reboot \ - | sort -u \ - | tr '\n' ^ \ - | head -c -1 - ;; -refreshbrowser ) - pushstream reload 1 - ;; relaystimerreset ) killall relays-timer.sh &> /dev/null $dirbash/settings/relays-timer.sh &> /dev/null & diff --git a/srv/http/bash/settings/features.sh b/srv/http/bash/settings/features.sh index 291f26f05..884f23e7a 100644 --- a/srv/http/bash/settings/features.sh +++ b/srv/http/bash/settings/features.sh @@ -278,6 +278,7 @@ EOF nfsserver ) active=${args[1]} readarray -t dirs <<< $( nfsShareList ) + mpc -q clear if [[ $active == true ]]; then ip=$( ipGet ) options="${ip%.*}.0/24(rw,sync,no_subtree_check)" @@ -298,15 +299,14 @@ nfsserver ) mv -f $dirmpd $dirbackup mv -f $dirbackup/mpdnfs $dirdata/mpd systemctl restart mpd - action=update else rm -f $dirmpd/{listing,updating} mkdir -p $dirbackup cp -r $dirmpd $dirbackup - action=rescan + systemctl restart mpd + $dirbash/cmd.sh mpcupdate$'\n'rescan fi systemctl enable --now nfs-server - $dirbash/cmd.sh mpcupdate$'\n'$action else systemctl disable --now nfs-server rm -f /mnt/MPD/.mpdignore \ @@ -324,7 +324,6 @@ nfsserver ) mv -f $dirbackup/mpd $dirdata mv -f $dirbackup/{display,order} $dirsystem systemctl restart mpd - $dirbash/cmd.sh mpcupdate$'\n'update fi pushRefresh pushstream refresh '{"page":"system","nfsserver":'$active'}' diff --git a/srv/http/bash/settings/system-datareset.sh b/srv/http/bash/settings/system-datareset.sh index 9c9d48422..e1db5ec26 100644 --- a/srv/http/bash/settings/system-datareset.sh +++ b/srv/http/bash/settings/system-datareset.sh @@ -146,7 +146,7 @@ sed -i -e -E '/^auto_update|^audio_buffer_size| #custom$/ d quality "very high"\ } ' /etc/mpd.conf -curl -L https://github.com/rern/rAudio-addons/raw/main/webradio/radioparadise.tar.xz | bsdtar xvf - -C / # webradio default +curl -L https://github.com/rern/rAudio-addons/raw/main/webradio/radioparadise.tar.xz | bsdtar xvf - -C $dirwebradio # webradio default if [[ ! -e $dirmpd/counts ]]; then echo '{ "playlists" : '$( ls -1 $dirplaylists | wc -l )' @@ -160,7 +160,7 @@ usermod -a -G root http # add user http to group root to allow /dev/gpiomem acce systemctl -q disable --now bluetooth hostapd shairport-sync smb snapserver spotifyd upmpdcli # set ownership and permissions -$dirbash/cmd.sh dirpermissions +$dirbash/settings/system.sh dirpermissions [[ $version ]] && exit diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh index ca12d61e2..c8bf2f327 100644 --- a/srv/http/bash/settings/system.sh +++ b/srv/http/bash/settings/system.sh @@ -7,6 +7,20 @@ filemodule=/etc/modules-load.d/raspberrypi.conf # convert each line to each args readarray -t args <<< "$1" +dirPermissions() { + chmod 755 /srv /srv/http /srv/http/* /mnt /mnt/MPD /mnt/MPD/*/ + chown http:http /srv /srv/http /srv/http/* /mnt /mnt/MPD /mnt/MPD/*/ + chmod -R 755 /srv/http/{assets,bash,data,settings} + chown -R http:http /srv/http/{assets,bash,data,settings} + chown mpd:audio $dirmpd $dirmpd/mpd.db $dirplaylists 2> /dev/null + if [[ $( readlink $dirshareddata ) == $dirdata ]]; then + chmod 777 $filesharedip $dirshareddata/system/{display,order} + readarray -t dirs <<< $( showmount --no-headers -e localhost | awk 'NF{NF-=1};1' ) + for dir in "${dirs[@]}"; do + chmod 777 "$dir" + done + fi +} pushReboot() { pushRefresh pushstreamNotify "${1//\"/\\\"}" 'Reboot required.' system 5000 @@ -77,7 +91,7 @@ sharedDataSet() { systemctl restart mpd sharedDataIPlist pushRefresh - pusrstream refresh '{"page":"features","shareddata":true}' + pushstream refresh '{"page":"features","shareddata":true}' } soundProfile() { if [[ $1 == reset ]]; then @@ -236,7 +250,7 @@ datarestore ) mv $dirdata/{webradiosimg,webradio/img} fi # temp 20220808 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - $dirbash/cmd.sh dirpermissions + dirPermissions [[ -e $dirsystem/color ]] && $dirbash/cmd.sh color uuid1=$( head -1 /etc/fstab | cut -d' ' -f1 ) uuid2=${uuid1:0:-1}2 @@ -278,6 +292,9 @@ datarestore ) grep -q /mnt/MPD/SD /etc/exports && $dirbash/settings/features.sh nfsserver$'\n'true $dirbash/cmd.sh power$'\n'reboot ;; +dirpermissions ) + dirPermissions + ;; hddinfo ) dev=${args[1]} echo -n "\ @@ -495,6 +512,7 @@ mirrorlist ) }' ;; mount ) + ['mount', 'cifs', 'data', '192.168.1.9', 'data', '', '', '', true] protocol=${args[1]} mountpoint="/mnt/MPD/NAS/${args[2]}" ip=${args[3]} @@ -502,17 +520,14 @@ mount ) user=${args[5]} password=${args[6]} extraoptions=${args[7]} - update=${args[8]} - shareddata=${args[9]} + shareddata=${args[8]} ! ping -c 1 -w 1 $ip &> /dev/null && echo "IP address not found: $ip" && exit - if [[ -e $mountpoint ]]; then - [[ $( ls "$mountpoint" ) ]] && echo "Mount name $mountpoint not empty." && exit - - else - mkdir "$mountpoint" - fi + [[ $( ls "$mountpoint" ) ]] && echo "Mount name $mountpoint not empty." && exit + + umount -ql "$mountpoint" + mkdir -p "$mountpoint" chown mpd:audio "$mountpoint" if [[ $protocol == cifs ]]; then source="//$ip/$directory" @@ -525,7 +540,7 @@ mount ) options+=,uid=$( id -u mpd ),gid=$( id -g mpd ),iocharset=utf8 else source="$ip:$directory" - options=defaults,noauto,bg,hard,intr,timeo=5 + options=defaults,noauto,bg,soft,timeo=5 fi [[ $extraoptions ]] && options+=,$extraoptions fstab="\ @@ -548,7 +563,7 @@ ${source// /\\040} ${mountpoint// /\\040} $protocol ${options// /\\040} 0 0 sleep 1 mount | grep -q "$mountpoint" && break done - [[ $shareddata ]] && sharedDataSet || pushRefresh + [[ $shareddata == true ]] && sharedDataSet || pushRefresh ;; mpdoleddisable ) rm $dirsystem/mpdoled @@ -556,6 +571,11 @@ mpdoleddisable ) $dirbash/settings/player-conf.sh pushRefresh ;; +mpdoledlogo ) + systemctl stop mpd_oled + type=$( grep mpd_oled /etc/systemd/system/mpd_oled.service | cut -d' ' -f3 ) + mpd_oled -o $type -L + ;; mpdoledset ) chip=${args[1]} baud=${args[2]} @@ -606,6 +626,68 @@ $description fi grep -B1 -A2 --no-group-separator "^${args[1],}" $filepackages ;; +pkgstatus ) + id=${args[1]} + pkg=$id + service=$id + case $id in + camilladsp ) + fileconf=$dircamilladsp/configs/camilladsp.yml + ;; + hostapd ) + conf="\ +# cat /etc/hostapd/hostapd.conf +$( cat /etc/hostapd/hostapd.conf ) + +# cat /etc/dnsmasq.conf" + ;; + localbrowser ) + pkg=chromium + fileconf=$dirsystem/localbrowser.conf + ;; + nfs-server ) + pkg=nfs-utils + systemctl -q is-active nfs-server && fileconf=/etc/exports + ;; + rtsp-simple-server ) + conf="\ +# rtl_test -t +$( script -c "timeout 1 rtl_test -t" | grep -v ^Script )" + ;; + smb ) + pkg=samba + fileconf=/etc/samba/smb.conf + ;; + snapclient|snapserver ) + pkg=snapcast + [[ $id == snapclient ]] && fileconf=/etc/default/snapclient + ;; + * ) + fileconf=/etc/$id.conf + ;; + esac + config="$( pacman -Q $pkg )" + if [[ $conf ]]; then + config+=" +$conf" + elif [[ -e $fileconf ]]; then + config+=" +# cat $fileconf +$( grep -v ^# $fileconf )" + fi + status=$( systemctl status $service \ + | sed -E '1 s|^.* (.*service) |\1|' \ + | sed -E '/^\s*Active:/ s|( active \(.*\))|\1|; s|( inactive \(.*\))|\1|; s|(failed)|\1|ig' ) + if [[ $pkg == chromium ]]; then + status=$( echo "$status" | grep -E -v 'Could not resolve keysym|Address family not supported by protocol|ERROR:chrome_browser_main_extra_parts_metrics' ) + elif [[ $pkg == nfs-utils ]]; then + status=$( echo "$status" | grep -v 'Protocol not supported' ) + fi + echo "\ +$config + +$status" + ;; powerbuttondisable ) if [[ -e $dirsystem/audiophonics ]]; then rm $dirsystem/audiophonics @@ -645,6 +727,10 @@ reserved=$reserved" > $dirsystem/powerbutton.conf [[ $reserved != $prevreserved ]] && pushReboot 'Power Button' fi ;; +rebootlist ) + killall networks-scan.sh &> /dev/null + [[ -e $dirshm/reboot ]] && cat $dirshm/reboot | sort -u + ;; relaysdisable ) rm -f $dirsystem/relays pushRefresh @@ -729,8 +815,9 @@ shareddataconnect ) dir="/mnt/MPD/NAS/$( basename "$path" )" [[ $( ls "$dir" ) ]] && echo "Directory not empty: $dir" && exit + umount -ql "$dir" done - options="nfs defaults,noauto,bg,hard,intr,timeo=5 0 0" + options="nfs defaults,noauto,bg,soft,timeo=5 0 0" fstab=$( cat /etc/fstab ) for path in "${paths[@]}"; do dir="/mnt/MPD/NAS/$( basename "$path" )" @@ -767,18 +854,23 @@ shareddatadisconnect ) rm -f $dirshareddata /mnt/MPD/NAS/.mpdignore sed -i "/$( ipGet )/ d" $filesharedip mpc -q clear - ipserver=$( grep $dirshareddata /etc/fstab | cut -d: -f1 ) - readarray -t dirs <<< $( awk '/^'$ipserver'/ {print $2}' /etc/fstab | sed 's/\\040/ /g' ) + if [[ $( readlink $dirshareddata ) == $dirdata ]]; then + ipserver=$( grep $dirshareddata /etc/fstab | cut -d: -f1 ) + fstab=$( grep -v ^$ipserver /etc/fstab ) + readarray -t dirs <<< $( awk '/^'$ipserver'/ {print $2}' /etc/fstab | sed 's/\\040/ /g' ) + else + fstab=$( grep -v $dirshareddata /etc/fstab ) + dirs=( $dirshareddata ) + fi for dir in "${dirs[@]}"; do umount -l "$dir" rmdir "$dir" &> /dev/null done - fstab=$( sed "/^$ipserver/ d" /etc/fstab ) echo "$fstab" | column -t > /etc/fstab systemctl daemon-reload systemctl restart mpd pushRefresh - pusrstream refresh '{"page":"features","shareddata":false}' + pushstream refresh '{"page":"features","shareddata":false}' if [[ ! $disable ]]; then echo $ipserver > $dirsystem/sharedipserver # for sshpass reconnect pushstreamNotify 'Server rAudio' 'Offline ...' rserver @@ -867,7 +959,10 @@ systemconfig ) $( cat /boot/cmdline.txt ) # cat /boot/config.txt -$( cat /boot/config.txt )" +$( cat /boot/config.txt ) + +# bootloader and firmware +$( pacman -Q firmware-raspberrypi linux-firmware raspberrypi-bootloader raspberrypi-firmware )" file=/etc/modules-load.d/raspberrypi.conf raspberrypiconf=$( cat $file ) if [[ $raspberrypiconf ]]; then diff --git a/srv/http/bash/startup.sh b/srv/http/bash/startup.sh index ad81c8043..9305d6c21 100644 --- a/srv/http/bash/startup.sh +++ b/srv/http/bash/startup.sh @@ -107,7 +107,7 @@ if [[ -e $dirsystem/lcdchar ]]; then $dirbash/lcdcharinit.py $dirbash/lcdchar.py logo fi -[[ -e $dirsystem/mpdoled ]] && $dirbash/cmd.sh mpdoledlogo +[[ -e $dirsystem/mpdoled ]] && $dirbash/settings/system.sh mpdoledlogo [[ -e $dirsystem/soundprofile ]] && $dirbash/settings/system.sh soundprofile diff --git a/srv/http/bash/status-push.sh b/srv/http/bash/status-push.sh index b697ee552..a8f57e2ac 100644 --- a/srv/http/bash/status-push.sh +++ b/srv/http/bash/status-push.sh @@ -91,6 +91,9 @@ fi [[ -e $dirsystem/librandom && $webradio == false ]] && $dirbash/cmd.sh mpcaddrandom +[[ $state == play ]] && playing=true || playing=false +pushstream refresh '{"page":"features","playing":'$playing'}' + [[ ! $scrobble ]] && exit # must be last for $statusprev - webradio and state . <( echo "$statusprev" ) diff --git a/srv/http/bash/status.sh b/srv/http/bash/status.sh index 60c8f8d5b..4de204ace 100644 --- a/srv/http/bash/status.sh +++ b/srv/http/bash/status.sh @@ -315,12 +315,15 @@ $radiosampling" > $dirshm/radio [[ ! $displaycover ]] && coverart= fi elif [[ $Title && $displaycover ]]; then - # split Artist - Title: Artist - Title (extra tag) or Artist: Title (extra tag) - readarray -t radioname <<< $( echo $Title | sed -E 's/ - |: /\n/' ) - Artist=${radioname[0]} - Title=${radioname[1]} + if [[ $Title == *" - "* ]]; then # split 'Artist - Title' or 'Artist: Title' (extra tag) + readarray -t radioname <<< $( echo $Title | sed -E 's/ - |: /\n/' ) + Artist=${radioname[0]} + Title=${radioname[1]} + else + Artist=$station + fi # fetched coverart - covername=$( echo "$Artist${Title/ (*}" | tr -d ' "`?/#&'"'" ) # remove ' (extra tag)' + covername=$( echo "$Artist${Title/ (*}" | tr -d ' "`?/#&'"'" ) # remove '... (extra tag)' coverfile=$( ls $dirshm/webradio/$covername.* 2> /dev/null | head -1 ) if [[ $coverfile ]]; then coverart=${coverfile:9} @@ -331,10 +334,17 @@ $radiosampling" > $dirshm/radio if [[ $displaycover ]]; then filenoext=/data/webradio/img/$urlname pathnoext=/srv/http$filenoext - if [[ -e $pathnoext.gif ]]; then - stationcover=$filenoext.$date.gif - elif [[ -e $pathnoext.jpg ]]; then - stationcover=$filenoext.$date.jpg + if [[ -e $pathnoext.jpg ]]; then + type=jpg + elif [[ -e $pathnoext.gif ]]; then + type=gif + fi + if [[ $type ]]; then + if [[ $urlname == *\?* ]]; then # cannot bust: url with ?param=... + stationcover=${filenoext//\?/%3F}.$type?v=$date + else + stationcover=$filenoext.$date.$type + fi fi fi status=$( grep -E -v '^, *"state"|^, *"webradio".*true|^, *"webradio".*false' <<< "$status" ) diff --git a/srv/http/index-body.php b/srv/http/index-body.php index 448a25483..617e08582 100644 --- a/srv/http/index-body.php +++ b/srv/http/index-body.php @@ -47,51 +47,6 @@ exit; } - -// library home blocks -$modes = [ 'Album', 'Artist', 'Album Artist', 'Composer', 'Conductor', 'Date', 'Genre', 'Latest', 'NAS', 'SD', 'USB', 'Playlists', 'Web Radio', 'DAB Radio' ]; -$htmlmode = ''; -foreach( $modes as $mode ) { - $lipath = str_replace( ' ', '', $mode ); - $modeLC = strtolower( $lipath ); - $htmlmode.= - '
' - .'
' - .''.$modeLC.'' - .'' - .'' - .''.$mode.'' - .'
' - .'
'; -} -// bookmarks -$dir = '/srv/http/data/bookmarks'; -$files = array_slice( scandir( $dir ), 2 ); // remove ., .. -if ( count( $files ) ) { - foreach( $files as $name ) { - $data = file( $dir.'/'.str_replace( '|', '/', $name ), FILE_IGNORE_NEW_LINES ); - $bkpath = $data[ 0 ]; - $coverart = $data[ 1 ] ?? ''; - if ( $coverart ) { - $coverart = substr( $coverart, 0, -3 ).time().substr( $coverart, -4 ); - $icon = ''; - } else { - $icon = ''.$name.''; - } - $htmlmode.= - '
' - .'
' - .''.$bkpath.'' - .$icon - .'
' - .'
'; - } -} -if ( $_POST[ 'modelist' ] ) { // for refresh on page visible - echo $htmlmode; - exit; -} - // context menus function menucommon( $add, $replace ) { $htmlcommon = 'Add'; @@ -365,7 +320,7 @@ function htmlmenu( $menulist, $mode ) {
-
+
diff --git a/srv/http/mpdlibrary.php b/srv/http/mpdlibrary.php index f314587f4..e6b9a8a49 100644 --- a/srv/http/mpdlibrary.php +++ b/srv/http/mpdlibrary.php @@ -88,6 +88,47 @@ htmlFind( $lists, $f ); } break; +case 'home': + $modes = [ 'Album', 'Artist', 'Album Artist', 'Composer', 'Conductor', 'Date', 'Genre', 'Latest', 'NAS', 'SD', 'USB', 'Playlists', 'Web Radio', 'DAB Radio' ]; + $htmlmode = ''; + foreach( $modes as $mode ) { + $lipath = str_replace( ' ', '', $mode ); + $modeLC = strtolower( $lipath ); + $htmlmode.= + '
' + .'
' + .''.$modeLC.'' + .'' + .'' + .''.$mode.'' + .'
' + .'
'; + } + // bookmarks + $dir = '/srv/http/data/bookmarks'; + $files = array_slice( scandir( $dir ), 2 ); // remove ., .. + if ( count( $files ) ) { + foreach( $files as $name ) { + $data = file( $dir.'/'.str_replace( '|', '/', $name ), FILE_IGNORE_NEW_LINES ); + $bkpath = $data[ 0 ]; + $coverart = $data[ 1 ] ?? ''; + if ( $coverart ) { + $coverart = substr( $coverart, 0, -3 ).time().substr( $coverart, -4 ); + $icon = ''; + } else { + $icon = ''.$name.''; + } + $htmlmode.= + '
' + .'
' + .''.$bkpath.'' + .$icon + .'
' + .'
'; + } + } + echo $htmlmode; + break; case 'list': $filemode = '/srv/http/data/mpd/'.$mode; if ( $mode === 'album' && exec( 'grep "albumbyartist.*true" /srv/http/data/system/display' ) ) $filemode.= 'byartist'; @@ -424,13 +465,15 @@ function htmlRadio( $subdirs, $files, $dir ) { usort( $array, function( $a, $b ) { return strnatcasecmp( $a->sort, $b->sort ); } ); + $time = time(); foreach( $array as $each ) { $index = strtoupper( mb_substr( $each->sort, 0, 1, 'UTF-8' ) ); $indexes[] = $index; $datacharset = $each->charset ? ' data-charset="'.$each->charset.'"' : ''; $url = $each->url; $urlname = str_replace( '/', '|', $url ); - $thumbsrc = rawurlencode( '/data/'.$gmode.'/img/'.$urlname.'-thumb.'.time().'.jpg' ); + $thumbsrc = '/data/'.$gmode.'/img/'.rawurlencode( $urlname ).'-thumb.'; + $thumbsrc.= strpos( $urlname, '?' ) ? 'jpg?v='.$time : $time.'.jpg'; $liname = $each->name; $name = $searchmode ? preg_replace( "/($string)/i", '$1', $liname ) : $liname; $html.= '
  • diff --git a/srv/http/mpdplaylist.php b/srv/http/mpdplaylist.php index d896639dc..ecaa73a90 100644 --- a/srv/http/mpdplaylist.php +++ b/srv/http/mpdplaylist.php @@ -35,7 +35,7 @@ htmlSavedPlaylist(); break; case 'load': // load saved playlist to current - if ( $_POST[ 'replace' ] ) exec( 'mpc clear' ); + if ( $_POST[ 'replace' ] ) exec( 'mpc -q clear' ); $name = $_POST[ 'name' ] ?? $argv[ 2 ]; // $argv - by import playlists exec( 'mpc -q load "'.$name.'"' ); @@ -181,7 +181,7 @@ function htmlTrack( $lists, $plname = '' ) { $class = 'file'; $discid = ''; $path = pathinfo( $file, PATHINFO_DIRNAME ); - $thumbsrc = '/mnt/MPD/'.rawurlencode( $path ).'/thumb.jpg' ; // replaced with icon on load error(faster than existing check) + $thumbsrc = '/mnt/MPD/'.rawurlencode( $path ).'/thumb.'.$time.'.jpg' ; // replaced with icon on load error(faster than existing check) $icon = 'music'; $htmlicon = ''; } else { @@ -228,7 +228,9 @@ function htmlTrack( $lists, $plname = '' ) { } if ( $stationname !== '' ) { $notsaved = 0; - $icon = ''; + $thumbsrc = '/data/'.$type.'/img/'.rawurlencode( $urlname ).'-thumb.'; + $thumbsrc.= strpos( $urlname, '?' ) ? 'jpg?v='.$time : $time.'.jpg'; + $icon = ''; } else { $notsaved = 1; $icon = ''; diff --git a/srv/http/settings/features.php b/srv/http/settings/features.php index 3a8bb661d..eb56eaca6 100644 --- a/srv/http/settings/features.php +++ b/srv/http/settings/features.php @@ -288,9 +288,11 @@ - Must be set to static IP address which should be set on router. - Existing list in | I^library^I | I^usb^I USB | will display in | I^networks^I NAS | once update finished. - On reboot / power off, Shared Data on clients will be temporarily disabled > re-enabled once the server is back online. + • rAudio Shared Data clients: - | I^system^I System | Settings and Data | Shared Data I^networks^I | • rAudio - Automatically setup: discover, connect shared files and data + • Windows NFS clients: - Windows Features > Services for NFS > Client for NFS - Enable - $fileexplorer diff --git a/srv/http/settings/system.php b/srv/http/settings/system.php index c3a2dad62..7cc91269e 100644 --- a/srv/http/settings/system.php +++ b/srv/http/settings/system.php @@ -92,15 +92,16 @@ ] ); ?> -
    Icon context menu: Unmount / Re-mount / Forget / Info / Share +
    Context menu: +| | Info +| | Unmount, Re-mount, Forget (Hidden if Shared Data is enabled) USB drives: • Will be found and mounted automatically. Network shares: • Must be manually configured. - • If mount failed, try in SSH terminal: - (replace YELLOW with actual values) + • If mount failed, try in SSH terminal: (replace YELLOW with actual values)
     mkdir -p "/mnt/MPD/NAS/NAME"
     # CIFS: (no user - username=guest, no password - password="")
    @@ -113,7 +114,7 @@
     
    
      'HDD Sleep'
    +	  'label'    => 'Hard Drive Sleep'
     	, 'id'       => 'hddsleep'
     	, 'icon'     => 'screenoff'
     	, 'disabled' => 'HDD not support sleep'
    @@ -124,7 +125,7 @@
     htmlSetting( [
     	  'label'    => 'Hotplug Update'
     	, 'id'       => 'usbautoupdate'
    -	, 'sublabel' => 'USB drives data'
    +	, 'sublabel' => 'data on USB'
     	, 'icon'     => 'refresh-library'
     	, 'setting'  => false
     	, 'disabled' => $disabledusbautoupdate
    @@ -203,7 +204,7 @@
     		, 'help'     => <<< HTML
     LCD module - display playback data
      • Support 16x2 and 20x4 LCD modules.
    -Iarning yl^I LCD with I²C backpack must be modified: 5V to 3.3V I²C and 5V LCD
    +I^warning yl^I LCD with I²C backpack must be modified: 5V to 3.3V I²C and 5V LCD
     HTML
     	]
     	, [
    @@ -295,7 +296,7 @@
     		, 'help'     => <<< HTML
     I^gear^I
      • NTP server: For time sync
    - • Package mirror server: For system upgrade pacman -Syu
    + • Package mirror server
     HTML
     	]
     	, [
    @@ -349,10 +350,12 @@
     	- Data - Audio CD, bookmarks, lyrics, saved playlists and Web Radio
     	- Show / hide items
     	- Display order of Library home
    - • rAudio as server:
    +	
    + • rAudio as server: (Alternative 1)
     	Server: | I^features^I Features | Server rAudio I^rserver^I |
     	Clients: | Shared Data I^networks^I | • rAudio |
    - • Other servers: 
    +	
    + • Other servers: (Alternative 2)
     	Server: Create a share for data with full permissions
     		- Linux: NFS 777, CIFS/SMB read only = no
     		- Windows: Everyone - Full Control (Sharing + Security)