First commit.

This commit is contained in:
Théo Marchal 2023-03-15 11:18:37 +01:00
commit 5992d41d52
18 changed files with 2036 additions and 0 deletions

91
README.md Normal file
View File

@ -0,0 +1,91 @@
# media-center-setup
This is my setup for my media center. Its main purpose is for me to be able to replicate it, in case of need. However, with a few modifications, it can be a great way for you to have a Raspberry Pi with the following features:
- an mpd server to stream local music, with an equalizer and snapcast support to stream.
- an Airplay capable machine (for Apple Music for example).
- a place to stream videos through Samba and NFS.
## Prerequisites
* An SD card, with the latest 64-bit Raspberry Pi OS installed on it. Use [Raspberry Pi imager](https://www.raspberrypi.com/software/) for that purpose.
* A Raspberry Pi, where the firmware has been updated with the possibility of USB booting.
## First configuration
* Insert the SD card in the Pi, start up, and SSH to it.
* Be sure to be in the wanted distribution target. For instance, I regularly need to be in _testing_, for mpd to be up to date. This can be done my modifying _/etc/apt/sources.list_. I usually type in the explicit name of testing (currently _bookworm_) to be sure where I'm at.
* Run `sudo apt update && sudo apt dist-upgrade && sudo apt upgrade` to update the system to the last version we need.
* Run `sudo apt install mpd mpc libasound2-plugin-equal shairport-sync snapserver samba nfs-kernel-server vim` to install the needed packages.
## Copying the SD card data to the USB media
Please note that this step can be done after the other configurations are performed, as most of the things will be copied.
* Transfer the utilities folder to your Pi.
* Inside, there is a folder called usb-boot. It comes from RonR, a user from the Raspberry Pi forums. You can also [download it here](https://forums.raspberrypi.com/viewtopic.php?t=196778). The version included in this repository is from Feb 28, 2022.
* Run `usb-boot.sh`.
* You can now remove your SD card and boot the system from the USB media device.
## Configuring the audio
* I use a HifiBerry DAC. For that purpose, I need to do the following:
* update `/boot/config.txt`
* remove `dtparam=audio=on`
* add `dtoverlay=hifiberry-dacplusadc` (find what you need on the [documentation](https://www.hifiberry.com/docs/software/configuring-linux-3-18-x/))
* while you're on this file, you can also disable things you probably don't need like bluetooth (`dtoverlay=disable-bt`) or WiFi (`dtoverlay=disable-wifi`).
* Copy the `/etc/asound.conf` file.
* You can use the `equalizer.sh` script to configure the equalizer. A value I like is `74-66-64-66-72-72-66-64-66-74`.
### Troubleshooting audio
If you're having trouble with the Raspberry Pi audio, try one of the following things:
* `sudo raspi-config` => `1 System Options` => `S2 Audio`.
* list your devices with `aplay -l`.
* try `speaker-test -c 2`, it should output sound.
* you can also try `speaker-test -D hw:<card number>,<device number>`.
## Configuring mpd
My music is copied with rsync through my NAS. For that purpose I use a set of scripts, that you would have to modify to suit your needs.
* Fist, we create a folder where we'll mount to NAS: `mkdir /media/mantis-music`.
* Then a folder where to copy the music: `mkdir /home/pi/music`.
* Use to script to copy locally the music: `utilities/update-music.sh`.
* Copy the mpd configuration file in `/etc/mpd.conf`.
* Run `mpc update`, then `mpc ls` to try to play something with `mpc play`.
* Test right away, it should output sound if you've rightly configured your audio.
## Configuring the sharing
### Samba
I use Samba to be able to organize my videos through my Windows machine.
* Edit `/etc/samba/smb.conf` and add the following:
```
[VideoPi]
comment = Videos on Pi
path = /home/pi/video
read only = no
browsable = yes
```
* Then set up a password for the user pi: `sudo smbpasswd -a pi`
* You should be able to connect to it.
### NFS
NFS is used to communicate with my Linux machine reading videos through Kodi.
* Edit `/etc/exports` and add the following line to make it visible to the home network: `/home/pi/video 192.168.1.0/24(rw,all_squash,insecure)`.
* You should be able to connect to it.
## Utilities
* `equalizer.sh` allows you to modify the equalizer values of Alsamixer.
* `update-music.sh` calls the following two scripts to update your music:
* `update-music/mount-nfs.sh` mounts the NAS volume.
* `update-music/rsync-sync.sh` performs an rsync to copy the music.
* `temperature.sh` displays the current Raspberry Pi temperature.
* `add-cover.sh` extracts covers from the music files to make a `cover.jpg` file. Useful for old mpd that is not able to read directly into the file.
* `video-tools/video-infos.sh` extract infos from the videos to verify if they're readable as needed.
* `usb-boot` a set of script [documented here](https://forums.raspberrypi.com/viewtopic.php?t=196778).

19
etc/asound.conf Normal file
View File

@ -0,0 +1,19 @@
pcm.!default {
type plug
slave.pcm plugequal;
}
ctl.!default {
type hw card 0
}
ctl.equal {
type equal;
}
pcm.plugequal {
type equal;
slave.pcm "plughw:0,0";
}
pcm.equal {
type plug;
slave.pcm plugequal;
}

11
etc/exports Normal file
View File

@ -0,0 +1,11 @@
# /etc/exports: the access control list for filesystems which may be exported
# to NFS clients. See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#
/home/pi/video 192.168.1.0/24(rw,all_squash,insecure)

439
etc/mpd.conf Normal file
View File

@ -0,0 +1,439 @@
# An example configuration file for MPD.
# Read the user manual for documentation: http://www.musicpd.org/doc/user/
# or /usr/share/doc/mpd/html/user.html
# Files and directories #######################################################
#
# This setting controls the top directory which MPD will search to discover the
# available audio files and add them to the daemon's online database. This
# setting defaults to the XDG directory, otherwise the music directory will be
# be disabled and audio files will only be accepted over ipc socket (using
# file:// protocol) or streaming files over an accepted protocol.
#
music_directory "/home/pi/music/punk"
#
# This setting sets the MPD internal playlist directory. The purpose of this
# directory is storage for playlists created by MPD. The server will use
# playlist files not created by the server but only if they are in the MPD
# format. This setting defaults to playlist saving being disabled.
#
playlist_directory "/var/lib/mpd/playlists"
#
# This setting sets the location of the MPD database. This file is used to
# load the database at server start up and store the database while the
# server is not up. This setting defaults to disabled which will allow
# MPD to accept files over ipc socket (using file:// protocol) or streaming
# files over an accepted protocol.
#
db_file "/var/lib/mpd/tag_cache"
# These settings are the locations for the daemon log files for the daemon.
#
# The special value "syslog" makes MPD use the local syslog daemon. This
# setting defaults to logging to syslog.
#
# If you use systemd, do not configure a log_file. With systemd, MPD
# defaults to the systemd journal, which is fine.
#
#log_file "/var/log/mpd/mpd.log"
# This setting sets the location of the file which stores the process ID
# for use of mpd --kill and some init scripts. This setting is disabled by
# default and the pid file will not be stored.
#
# If you use systemd, do not configure a pid_file.
#
#pid_file "/run/mpd/pid"
# This setting sets the location of the file which contains information about
# most variables to get MPD back into the same general shape it was in before
# it was brought down. This setting is disabled by default and the server
# state will be reset on server start up.
#
state_file "/var/lib/mpd/state"
#
# The location of the sticker database. This is a database which
# manages dynamic information attached to songs.
#
sticker_file "/var/lib/mpd/sticker.sql"
#
###############################################################################
# General music daemon options ################################################
#
# This setting specifies the user that MPD will run as. MPD should never run as
# root and you may use this setting to make MPD change its user ID after
# initialization. This setting is disabled by default and MPD is run as the
# current user.
#
user "mpd"
#
# This setting specifies the group that MPD will run as. If not specified
# primary group of user specified with "user" setting will be used (if set).
# This is useful if MPD needs to be a member of group such as "audio" to
# have permission to use sound card.
#
#group "nogroup"
#
# This setting sets the address for the daemon to listen on. Careful attention
# should be paid if this is assigned to anything other than the default, any.
# This setting can deny access to control of the daemon. Not effective if
# systemd socket activation is in use.
#
# For network
bind_to_address "localhost"
#
# And for Unix Socket
#bind_to_address "/run/mpd/socket"
#
# This setting is the TCP port that is desired for the daemon to get assigned
# to.
#
port "6600"
#
# Suppress all messages below the given threshold. Use "verbose" for
# troubleshooting. Available setting arguments are "notice", "info", "verbose",
# "warning" and "error".
#
log_level "info"
#
# Setting "restore_paused" to "yes" puts MPD into pause mode instead
# of starting playback after startup.
#
#restore_paused "no"
#
# This setting enables MPD to create playlists in a format usable by other
# music players.
#
#save_absolute_paths_in_playlists "no"
#
# This setting defines a list of tag types that will be extracted during the
# audio file discovery process. The complete list of possible values can be
# found in the user manual.
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
#
# This example just enables the "comment" tag without disabling all
# the other supported tags:
#metadata_to_use "+comment"
#
# This setting enables automatic update of MPD's database when files in
# music_directory are changed.
#
auto_update "yes"
#
# Limit the depth of the directories being watched, 0 means only watch
# the music directory itself. There is no limit by default.
#
#auto_update_depth "3"
#
###############################################################################
# Symbolic link behavior ######################################################
#
# If this setting is set to "yes", MPD will discover audio files by following
# symbolic links outside of the configured music_directory.
#
#follow_outside_symlinks "yes"
#
# If this setting is set to "yes", MPD will discover audio files by following
# symbolic links inside of the configured music_directory.
#
#follow_inside_symlinks "yes"
#
###############################################################################
# Zeroconf / Avahi Service Discovery ##########################################
#
# If this setting is set to "yes", service information will be published with
# Zeroconf / Avahi.
#
#zeroconf_enabled "yes"
#
# The argument to this setting will be the Zeroconf / Avahi unique name for
# this MPD server on the network. %h will be replaced with the hostname.
#
#zeroconf_name "Music Player @ %h"
#
###############################################################################
# Permissions #################################################################
#
# If this setting is set, MPD will require password authorization. The password
# setting can be specified multiple times for different password profiles.
#
#password "password@read,add,control,admin"
#
# This setting specifies the permissions a user has who has not yet logged in.
#
#default_permissions "read,add,control,admin"
#
###############################################################################
# Database #######################################################################
#
# An example of a database section instead of the old 'db_file' setting.
# It enables mounting other storages into the music directory.
#
#database {
# plugin "simple"
# path "/var/lib/mpd/tag_cache"
# cache_directory "/var/lib/mpd/cache"
#}
#
# An example of database config for a satellite setup
#
#music_directory "nfs://fileserver.local/srv/mp3"
#database {
# plugin "proxy"
# host "other.mpd.host"
# port "6600"
#}
# Input #######################################################################
#
input {
plugin "curl"
# proxy "proxy.isp.com:8080"
# proxy_user "user"
# proxy_password "password"
}
# Decoder #####################################################################
#
decoder {
plugin "hybrid_dsd"
enabled "no"
# gapless "no"
}
decoder {
plugin "wildmidi"
enabled "no"
#config_file "/etc/timidity/timidity.cfg"
}
#
###############################################################################
# Audio Output ################################################################
#
# MPD supports various audio output types, as well as playing through multiple
# audio outputs at the same time, through multiple audio_output settings
# blocks. Setting this block is optional, though the server will only attempt
# autodetection for one sound card.
#
# An example of an ALSA output:
#
#audio_output {
# type "alsa"
# name "ALSA Base"
## device "hw:0,0" # optional
# mixer_type "software" # optional
## mixer_device "default" # optional
## mixer_control "PCM" # optional
## mixer_index "0" # optional
#}
audio_output {
type "alsa"
name "Alsa Equal"
device "plug:plugequal"
mixer_type "software"
}
audio_output {
type "fifo"
name "Snapcast"
path "/tmp/snapfifo"
format "48000:16:2"
mixer_type "software"
}
#
# An example of an OSS output:
#
#audio_output {
# type "oss"
# name "My OSS Device"
## device "/dev/dsp" # optional
## mixer_type "hardware" # optional
## mixer_device "/dev/mixer" # optional
## mixer_control "PCM" # optional
#}
#
# An example of a shout output (for streaming to Icecast):
#
#audio_output {
# type "shout"
# encoder "vorbis" # optional
# name "My Shout Stream"
# host "localhost"
# port "8000"
# mount "/mpd.ogg"
# password "hackme"
# quality "5.0"
# bitrate "128"
# format "44100:16:1"
## protocol "icecast2" # optional
## user "source" # optional
## description "My Stream Description" # optional
## url "http://example.com" # optional
## genre "jazz" # optional
## public "no" # optional
## timeout "2" # optional
## mixer_type "software" # optional
#}
#
# An example of a recorder output:
#
#audio_output {
# type "recorder"
# name "My recorder"
# encoder "vorbis" # optional, vorbis or lame
# path "/var/lib/mpd/recorder/mpd.ogg"
## quality "5.0" # do not define if bitrate is defined
# bitrate "128" # do not define if quality is defined
# format "44100:16:1"
#}
#
# An example of a httpd output (built-in HTTP streaming server):
#
#audio_output {
# type "httpd"
# name "My HTTP Stream"
# encoder "vorbis" # optional, vorbis or lame
# port "8000"
# bind_to_address "0.0.0.0" # optional, IPv4 or IPv6
## quality "5.0" # do not define if bitrate is defined
# bitrate "128" # do not define if quality is defined
# format "44100:16:1"
# max_clients "0" # optional 0=no limit
#}
#
# An example of a pulseaudio output (streaming to a remote pulseaudio server)
# Please see README.Debian if you want mpd to play through the pulseaudio
# daemon started as part of your graphical desktop session!
#
#audio_output {
# type "pulse"
# name "My Pulse Output"
## server "remote_server" # optional
## sink "remote_server_sink" # optional
## media_role "media_role" #optional
#}
#
# An example of a winmm output (Windows multimedia API).
#
#audio_output {
# type "winmm"
# name "My WinMM output"
## device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
# or
## device "0" # optional
## mixer_type "hardware" # optional
#}
#
# An example of an openal output.
#
#audio_output {
# type "openal"
# name "My OpenAL output"
## device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
#}
#
# An example of an sndio output.
#
#audio_output {
# type "sndio"
# name "sndio output"
# mixer_type "hardware"
#}
#
# An example of an OS X output:
#
#audio_output {
# type "osx"
# name "My OS X Device"
## device "Built-in Output" # optional
## channel_map "-1,-1,0,1" # optional
#}
#
## Example "pipe" output:
#
#audio_output {
# type "pipe"
# name "my pipe"
# command "aplay -f cd 2>/dev/null"
## Or if you're want to use AudioCompress
# command "AudioCompress -m | aplay -f cd 2>/dev/null"
## Or to send raw PCM stream through PCM:
# command "nc example.org 8765"
# format "44100:16:2"
#}
#
## An example of a null output (for no audio output):
#
#audio_output {
# type "null"
# name "My Null Output"
# mixer_type "none" # optional
#}
#
###############################################################################
# Normalization automatic volume adjustments ##################################
#
# This setting specifies the type of ReplayGain to use. This setting can have
# the argument "off", "album", "track" or "auto". "auto" is a special mode that
# chooses between "track" and "album" depending on the current state of
# random playback. If random playback is enabled then "track" mode is used.
# See <https://wiki.hydrogenaud.io/index.php?title=Replaygain> for
# more details about ReplayGain.
# This setting is off by default.
#
#replaygain "album"
#
# This setting sets the pre-amp used for files that have ReplayGain tags. By
# default this setting is disabled.
#
#replaygain_preamp "0"
#
# This setting sets the pre-amp used for files that do NOT have ReplayGain tags.
# By default this setting is disabled.
#
#replaygain_missing_preamp "0"
#
# This setting enables or disables ReplayGain limiting.
# MPD calculates actual amplification based on the ReplayGain tags
# and replaygain_preamp / replaygain_missing_preamp setting.
# If replaygain_limit is enabled MPD will never amplify audio signal
# above its original level. If replaygain_limit is disabled such amplification
# might occur. By default this setting is enabled.
#
#replaygain_limit "yes"
#
# This setting enables on-the-fly normalization volume adjustment. This will
# result in the volume of all playing audio to be adjusted so the output has
# equal "loudness". This setting is disabled by default.
#
volume_normalization "yes"
#
###############################################################################
# Character Encoding ##########################################################
#
# If file or directory names do not display correctly for your locale then you
# may need to modify this setting.
#
filesystem_charset "UTF-8"
#
###############################################################################
max_output_buffer_size "32768"

241
etc/samba/smb.conf Normal file
View File

@ -0,0 +1,241 @@
#
# Sample configuration file for the Samba suite for Debian GNU/Linux.
#
#
# This is the main Samba configuration file. You should read the
# smb.conf(5) manual page in order to understand the options listed
# here. Samba has a huge number of configurable options most of which
# are not shown in this example
#
# Some options that are often worth tuning have been included as
# commented-out examples in this file.
# - When such options are commented with ";", the proposed setting
# differs from the default Samba behaviour
# - When commented with "#", the proposed setting is the default
# behaviour of Samba but the option is considered important
# enough to be mentioned here
#
# NOTE: Whenever you modify this file you should run the command
# "testparm" to check that you have not made any basic syntactic
# errors.
#======================= Global Settings =======================
[global]
## Browsing/Identification ###
# Change this to the workgroup/NT-domain name your Samba server will part of
workgroup = WORKGROUP
#### Networking ####
# The specific set of interfaces / networks to bind to
# This can be either the interface name or an IP address/netmask;
# interface names are normally preferred
; interfaces = 127.0.0.0/8 eth0
# Only bind to the named interfaces and/or networks; you must use the
# 'interfaces' option above to use this.
# It is recommended that you enable this feature if your Samba machine is
# not protected by a firewall or is a firewall itself. However, this
# option cannot handle dynamic or non-broadcast interfaces correctly.
; bind interfaces only = yes
#### Debugging/Accounting ####
# This tells Samba to use a separate log file for each machine
# that connects
log file = /var/log/samba/log.%m
# Cap the size of the individual log files (in KiB).
max log size = 1000
# We want Samba to only log to /var/log/samba/log.{smbd,nmbd}.
# Append syslog@1 if you want important messages to be sent to syslog too.
logging = file
# Do something sensible when Samba crashes: mail the admin a backtrace
panic action = /usr/share/samba/panic-action %d
####### Authentication #######
# Server role. Defines in which mode Samba will operate. Possible
# values are "standalone server", "member server", "classic primary
# domain controller", "classic backup domain controller", "active
# directory domain controller".
#
# Most people will want "standalone server" or "member server".
# Running as "active directory domain controller" will require first
# running "samba-tool domain provision" to wipe databases and create a
# new domain.
server role = standalone server
obey pam restrictions = yes
# This boolean parameter controls whether Samba attempts to sync the Unix
# password with the SMB password when the encrypted SMB password in the
# passdb is changed.
unix password sync = yes
# For Unix password sync to work on a Debian GNU/Linux system, the following
# parameters must be set (thanks to Ian Kahan <<kahan@informatik.tu-muenchen.de> for
# sending the correct chat script for the passwd program in Debian Sarge).
passwd program = /usr/bin/passwd %u
passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
# This boolean controls whether PAM will be used for password changes
# when requested by an SMB client instead of the program listed in
# 'passwd program'. The default is 'no'.
pam password change = yes
# This option controls how unsuccessful authentication attempts are mapped
# to anonymous connections
map to guest = bad user
########## Domains ###########
#
# The following settings only takes effect if 'server role = classic
# primary domain controller', 'server role = classic backup domain controller'
# or 'domain logons' is set
#
# It specifies the location of the user's
# profile directory from the client point of view) The following
# required a [profiles] share to be setup on the samba server (see
# below)
; logon path = \\%N\profiles\%U
# Another common choice is storing the profile in the user's home directory
# (this is Samba's default)
# logon path = \\%N\%U\profile
# The following setting only takes effect if 'domain logons' is set
# It specifies the location of a user's home directory (from the client
# point of view)
; logon drive = H:
# logon home = \\%N\%U
# The following setting only takes effect if 'domain logons' is set
# It specifies the script to run during logon. The script must be stored
# in the [netlogon] share
# NOTE: Must be store in 'DOS' file format convention
; logon script = logon.cmd
# This allows Unix users to be created on the domain controller via the SAMR
# RPC pipe. The example command creates a user account with a disabled Unix
# password; please adapt to your needs
; add user script = /usr/sbin/useradd --create-home %u
# This allows machine accounts to be created on the domain controller via the
# SAMR RPC pipe.
# The following assumes a "machines" group exists on the system
; add machine script = /usr/sbin/useradd -g machines -c "%u machine account" -d /var/lib/samba -s /bin/false %u
# This allows Unix groups to be created on the domain controller via the SAMR
# RPC pipe.
; add group script = /usr/sbin/addgroup --force-badname %g
############ Misc ############
# Using the following line enables you to customise your configuration
# on a per machine basis. The %m gets replaced with the netbios name
# of the machine that is connecting
; include = /home/samba/etc/smb.conf.%m
# Some defaults for winbind (make sure you're not using the ranges
# for something else.)
; idmap config * : backend = tdb
; idmap config * : range = 3000-7999
; idmap config YOURDOMAINHERE : backend = tdb
; idmap config YOURDOMAINHERE : range = 100000-999999
; template shell = /bin/bash
# Setup usershare options to enable non-root users to share folders
# with the net usershare command.
# Maximum number of usershare. 0 means that usershare is disabled.
# usershare max shares = 100
# Allow users who've been granted usershare privileges to create
# public shares, not just authenticated ones
usershare allow guests = yes
#======================= Share Definitions =======================
[homes]
comment = Home Directories
browseable = no
# By default, the home directories are exported read-only. Change the
# next parameter to 'no' if you want to be able to write to them.
read only = yes
# File creation mask is set to 0700 for security reasons. If you want to
# create files with group=rw permissions, set next parameter to 0775.
create mask = 0700
# Directory creation mask is set to 0700 for security reasons. If you want to
# create dirs. with group=rw permissions, set next parameter to 0775.
directory mask = 0700
# By default, \\server\username shares can be connected to by anyone
# with access to the samba server.
# The following parameter makes sure that only "username" can connect
# to \\server\username
# This might need tweaking when using external authentication schemes
valid users = %S
# Un-comment the following and create the netlogon directory for Domain Logons
# (you need to configure Samba to act as a domain controller too.)
;[netlogon]
; comment = Network Logon Service
; path = /home/samba/netlogon
; guest ok = yes
; read only = yes
# Un-comment the following and create the profiles directory to store
# users profiles (see the "logon path" option above)
# (you need to configure Samba to act as a domain controller too.)
# The path below should be writable by all users so that their
# profile directory may be created the first time they log on
;[profiles]
; comment = Users profiles
; path = /home/samba/profiles
; guest ok = no
; browseable = no
; create mask = 0600
; directory mask = 0700
[printers]
comment = All Printers
browseable = no
path = /var/tmp
printable = yes
guest ok = no
read only = yes
create mask = 0700
# Windows clients look for this share name as a source of downloadable
# printer drivers
[print$]
comment = Printer Drivers
path = /var/lib/samba/printers
browseable = yes
read only = yes
guest ok = no
# Uncomment to allow remote administration of Windows print drivers.
# You may need to replace 'lpadmin' with the name of the group your
# admin users are members of.
# Please note that you also need to set appropriate Unix permissions
# to the drivers directory for these users to have write rights in it
; write list = root, @lpadmin
[VideoPi]
comment = Videos on Pi
path = /home/pi/video
read only = no
browsable = yes

53
utilities/cover.sh Normal file
View File

@ -0,0 +1,53 @@
#!/bin/bash
# Recursively gets into each folder, checks if there is an audio file
# and extracts the cover with ffmpeg
### Helpers
# Check all extensions available
# find . -type f | perl -ne 'print $1 if m/\.([^.\/]+)$/' | sort -u
### Configuration
export MUSIC_PATH="."
### Functions
file_exists()
{
files=( ./*$1 )
if [ ${#files[@]} -gt 0 ]; then
echo ${files[0]}
fi
}
check_extensions()
{
EXTENSIONS_SUPPORTED=("MP3" "Mp3" "mp3" "m4a")
for i in "${EXTENSIONS_SUPPORTED[@]}"
do
file_exists "$i"
done
}
extract_cover()
{
if [ ! -f "cover.jpg" ]
then
shopt -s nullglob # erase not matching result
val=$(check_extensions)
#echo "file found" $val "in" $(pwd)
ffmpeg -i "$val" cover.jpg
else
echo "cover.jpg exists in " $(pwd)
fi
}
### Program
find $MUSIC_PATH -type f -name ".*" -delete # remove hidden files
export -f extract_cover check_extensions file_exists
find $MUSIC_PATH \( -iname \*.MP3 -o -iname \*.Mp3 -o -iname \*.mp3 -o -iname \*.m4a \) -type f -execdir bash -c 'extract_cover "$0"' {} \;

1
utilities/equalizer.sh Normal file
View File

@ -0,0 +1 @@
sudo -H -u mpd alsamixer -D equal

1
utilities/temperature.sh Normal file
View File

@ -0,0 +1 @@
vcgencmd measure_temp

View File

@ -0,0 +1,4 @@
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source ${__dir}/update-music/mount-nfs.sh
sleep 5
source ${__dir}/update-music/rsync-sync.sh

View File

@ -0,0 +1 @@
sudo mount -t nfs4 -o proto=tcp,port=2049 192.168.1.30:/volume1/music /media/mantis-music

View File

@ -0,0 +1 @@
sudo rsync -av --exclude="@eaDir" --delete /media/mantis-music/punk /home/pi/music

View File

@ -0,0 +1,230 @@
#!/bin/bash
trap '{ stty sane; echo ""; errexit "Aborted"; }' SIGINT SIGTERM
MNTPATH="/tmp/boot-gpt-mnt"
mntpart()
{
MNTED=TRUE
if [ ! -d "${MNTPATH}/" ]; then
mkdir "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to make partition mount point"
fi
fi
mount "$1" "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to mount $2 partition"
fi
}
umntpart()
{
umount "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to unmount partition"
fi
rm -r "${MNTPATH}/"
MNTED=FALSE
}
errexit()
{
echo ""
echo "$1"
echo ""
if [ "${MNTED}" = "TRUE" ]; then
umount "${MNTPATH}/" &> /dev/null
rm -rf "${MNTPATH}/" &> /dev/null
fi
echo "Usage: $0 /dev/sdX"
echo ""
exit 1
}
MNTED=FALSE
if [ $(id -u) -ne 0 ]; then
errexit "$0 must be run as root user"
fi
PGMNAME="$(basename $0)"
for PID in $(pidof -x -o %PPID "${PGMNAME}"); do
if [ ${PID} -ne $$ ]; then
errexit "${PGMNAME} is already running"
fi
done
gdisk -l /dev/null &> /dev/null
if [ $? -eq 127 ]; then
echo ""
echo "gdisk not installed"
echo ""
echo -n "Ok to install gdisk (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
break
else
errexit "Aborted"
fi
fi
done
echo ""
echo "Installing gdisk"
echo ""
apt-get update
apt-get install gdisk
fi
DEVICE="$1"
if [ "${DEVICE}" = "" ]; then
errexit "No device specified"
fi
if [[ ! "${DEVICE}" =~ ^/dev/sd[a-z]$ || ! -b "${DEVICE}" ]]; then
errexit "Invalid DEVICE: ${DEVICE}"
fi
if [ $(mount | grep -c "^${DEVICE}") -ne 0 ]; then
errexit "${DEVICE} is in use (mounted)"
fi
PTTYPE="$(blkid ${DEVICE} | sed -n 's|^.*PTTYPE="\(\S\+\)".*|\1|p')"
PTTYPE="$(tr [a-z] [A-Z] <<< "${PTTYPE}")"
if [ "${PTTYPE}" = "DOS" ]; then
PTTYPE="MBR"
fi
if [[ "${PTTYPE}" != "MBR" && "${PTTYPE}" != "GPT" ]]; then
errexit "Unsupported partition table type"
fi
echo ""
echo "Partition table type is ${PTTYPE}"
if [ "${PTTYPE}" = "MBR" ]; then
echo ""
echo -n "Ok to convert ${DEVICE} to GPT (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
break
else
errexit "Aborted"
fi
fi
done
sgdisk -z "${DEVICE}" &> /dev/null
fi
INFO="$(gdisk -l ${DEVICE})"
LAST=$(sed -n 's|^.*last usable sector is \(\S\+\).*|\1|p' <<< "${INFO}")
START=$(sed -n 's|^\s\+2\s\+\(\S\+\).*|\1|p' <<< "${INFO}")
END=$(sed -n 's|^\s\+2\s\+\S\+\s\+\(\S\+\).*|\1|p' <<< "${INFO}")
SHRINK=FALSE
EXPAND=FALSE
SDBOOT=FALSE
if [ $(grep -c "Warning! Secondary partition table overlaps the last partition" <<< "${INFO}") -ne 0 ]; then
echo ""
echo "ROOT partition overlaps the Secondary partition table area"
echo ""
echo -n "Ok to resize ROOT partition (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
break
else
errexit "Aborted"
fi
fi
done
SHRINK=TRUE
elif [ ${END} -lt ${LAST} ]; then
echo ""
echo -n "Expand ROOT partition to use all available space (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
EXPAND=TRUE
fi
break
fi
done
fi
if [ -b /dev/mmcblk0 ]; then
echo ""
echo -n "Set SD card to boot ${DEVICE} (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
SDBOOT=TRUE
fi
break
fi
done
fi
if [[ "${PTTYPE}" = "MBR" || "${SHRINK}" = "TRUE" || "${EXPAND}" = "TRUE" || "${SDBOOT}" = "TRUE" ]]; then
echo ""
echo -n "Ok to process ${DEVICE} (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
break
else
errexit "Aborted"
fi
fi
done
if [ "${SHRINK}" = "TRUE" ]; then
echo ""
resize2fs -f -M "${DEVICE}2"
fi
if [[ "${SHRINK}" = "TRUE" || "${EXPAND}" = "TRUE" ]]; then
gdisk "${DEVICE}" <<EOF > /dev/null
d
2
n
2
${START}
w
y
EOF
echo ""
resize2fs -f "${DEVICE}2"
fi
gdisk "${DEVICE}" <<EOF > /dev/null
r
h
1
n
0c
n
n
w
y
EOF
PARTUUID_1="$(blkid ${DEVICE}1 | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
PARTUUID_2="$(blkid ${DEVICE}2 | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
mntpart "${DEVICE}1" "BOOT"
sed -i '/^[[:space:]]*#/!s|\(^.*rootwait\).*$|\1|' "${MNTPATH}/cmdline.txt"
sed -i "/^[[:space:]]*#/!s|^\(.*root=\)\S\+\(\s\+.*\)$|\1PARTUUID=${PARTUUID_2}\2|" "${MNTPATH}/cmdline.txt"
umntpart
mntpart "${DEVICE}2" "ROOT"
sed -i "/^[[:space:]]*#/!s|^\S\+\(\s\+/boot\s\+.*\)$|PARTUUID=${PARTUUID_1}\1|" "${MNTPATH}/etc/fstab"
sed -i "/^[[:space:]]*#/!s|^\S\+\(\s\+/\s\+.*\)$|PARTUUID=${PARTUUID_2}\1|" "${MNTPATH}/etc/fstab"
umntpart
if [ "${SDBOOT}" = "TRUE" ]; then
mntpart "/dev/mmcblk0p1" "BOOT"
sed -i "/^[[:space:]]*#/!s|^\(.*root=\)\S\+\(\s\+.*\)$|\1PARTUUID=${PARTUUID_2}\2|" "${MNTPATH}/cmdline.txt"
umntpart
mntpart "${DEVICE}2" "ROOT"
sed -i "/^[[:space:]]*#/!s|^\S\+\(\s\+/boot\s\+.*\)$|/dev/mmcblk0p1\1|" "${MNTPATH}/etc/fstab"
umntpart
fi
if [[ "${SHRINK}" = "FALSE" && "${EXPAND}" = "FALSE" ]]; then
echo ""
fi
else
echo ""
echo "Nothing to do!"
echo ""
fi

View File

@ -0,0 +1,145 @@
#!/bin/bash
trap '{ stty sane; echo ""; errexit "Aborted"; }' SIGINT SIGTERM
MNTPATH="/tmp/sdc-boot-mnt"
mntpart()
{
MNTED=TRUE
if [ ! -d "${MNTPATH}/" ]; then
mkdir "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to make partition mount point"
fi
fi
mount "$1" "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to mount $2 partition"
fi
}
umntpart()
{
umount "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to unmount partition"
fi
rm -r "${MNTPATH}/"
MNTED=FALSE
}
errexit()
{
echo ""
echo "$1"
echo ""
if [ "${MNTED}" = "TRUE" ]; then
umount "${MNTPATH}/" &> /dev/null
rm -rf "${MNTPATH}/" &> /dev/null
fi
echo "Usage: $0 [ /dev/sdX2 | /dev/mmcblk0p2 | hhhhhhhh-02 | hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh ]"
echo ""
exit 1
}
MNTED=FALSE
if [ $(id -u) -ne 0 ]; then
errexit "$0 must be run as root user"
fi
PGMNAME="$(basename $0)"
for PID in $(pidof -x -o %PPID "${PGMNAME}"); do
if [ ${PID} -ne $$ ]; then
errexit "${PGMNAME} is already running"
fi
done
if [ ! -b /dev/mmcblk0 ]; then
errexit "No SD card present"
fi
DEVICE="$1"
if [ "${DEVICE}" = "" ]; then
mntpart "/dev/mmcblk0p1" "SD card BOOT"
DEVICE="$(sed -n '/^[[:space:]]*#/!s|^.*root=\(\S\+\)\s\+.*$|\1|p' "${MNTPATH}/cmdline.txt")"
umntpart
if [[ "${DEVICE}" =~ ^PARTUUID=.*$ ]]; then
DEVICE="$(blkid -l -t "${DEVICE}" | sed -n 's|^\(/dev/.*\):.*|\1|p')"
fi
PARTUUID="$(blkid "${DEVICE}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
else
DEVICE="$(tr [A-Z] [a-z] <<< "${DEVICE}")"
if [[ "${DEVICE}" =~ ^[[:xdigit:]]{8}-02$ || "${DEVICE}" =~ ^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$ ]]; then
PARTUUID="${DEVICE}"
BLKID="$(blkid -l -t PARTUUID="${PARTUUID}")"
if [ $? -ne 0 ]; then
errexit "Invalid DEVICE: ${DEVICE}"
fi
DEVICE="$(sed -n 's|^\(/dev/.*\):.*$|\1|p' <<< "${BLKID}")"
fi
if [[ ("${DEVICE}" != "/dev/mmcblk0p2" && ! "${DEVICE}" =~ ^/dev/sd[a-z]2$) || ! -b "${DEVICE}" ]]; then
errexit "Invalid DEVICE: ${DEVICE}"
fi
PARTUUID="$(blkid "${DEVICE}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
echo ""
echo -n "Set SD card to boot to ${DEVICE} [PARTUUID=${PARTUUID}] (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
break
else
errexit "Aborted"
fi
fi
done
mntpart "/dev/mmcblk0p1" "SD card BOOT"
if [ "${DEVICE}" = "/dev/mmcblk0p2" ]; then
sed -i "/^[[:space:]]*#/!s|^\(.*root=\)\S\+\(\s\+.*\)$|\1/dev/mmcblk0p2\2|" "${MNTPATH}/cmdline.txt"
else
sed -i "/^[[:space:]]*#/!s|^\(.*root=\)\S\+\(\s\+.*\)$|\1PARTUUID=${PARTUUID}\2|" "${MNTPATH}/cmdline.txt"
fi
umntpart
if [ "${DEVICE}" != "/dev/mmcblk0p2" ]; then
mntpart "${DEVICE}" "device ROOT"
sed -i "/^[[:space:]]*#/!s|^\S\+\(\s\+/boot\s\+.*\)$|/dev/mmcblk0p1\1|" "${MNTPATH}/etc/fstab"
umntpart
fi
fi
echo ""
echo "SD card is set to boot to ${DEVICE} [PARTUUID=${PARTUUID}]"
if [[ "${DEVICE}" != "/dev/mmcblk0p2" && "$(blkid /dev/mmcblk0p2 | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" = "${PARTUUID}" ]]; then
echo ""
echo "WARNING : SD card (/dev/mmcblk0p2) and USB device (${DEVICE}) have the same PARTUUID (${PARTUUID}) : SD card will boot instead of USB device"
fi
DEV_LIST=()
DEV_LIST+=/dev/mmcblk0p2
DEV_LIST+=($(ls -l /dev/sd?2 2> /dev/null | sed -n 's|^.*\(/dev/.*\)|\1|p'))
if [ ${#DEV_LIST[@]} -gt 1 ]; then
for i in ${!DEV_LIST[@]}; do
if [ ${i} -lt $((${#DEV_LIST[@]} - 1)) ]; then
j=$((i + 1))
while [ ${j} -lt ${#DEV_LIST[@]} ]; do
if [ "$(blkid "${DEV_LIST[i]}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" = "$(blkid "${DEV_LIST[j]}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" ];then
if [[ "${DEV_LIST[i]}" != "/dev/mmcblk0p2" && ("${DEV_LIST[i]}" = "${DEVICE}" || "${DEV_LIST[j]}" = "${DEVICE}") ]]; then
echo ""
echo "WARNING : ${DEV_LIST[i]} and ${DEV_LIST[j]} have the same PARTUUID : $(blkid "${DEV_LIST[i]}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
fi
fi
((j += 1))
done
fi
done
fi
echo ""
if [ "$1" != "" ]; then
echo -n "Reboot now (y/n)? "
while read -r -n 1 -s answer; do
if [[ ${answer} = [yYnN] ]]; then
echo "${answer}"
if [[ ${answer} = [yY] ]]; then
shutdown -r now
fi
break
fi
done
echo ""
fi

View File

@ -0,0 +1,193 @@
#!/bin/bash
trap '{ stty sane; echo ""; errexit "Aborted"; }' SIGINT SIGTERM
MNTPATH="/tmp/set-partuuid-mnt"
mntpart()
{
MNTED=TRUE
if [ ! -d "${MNTPATH}/" ]; then
mkdir "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to make partition mount point"
fi
fi
mount "$1" "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to mount $2 partition"
fi
}
umntpart()
{
umount "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to unmount partition"
fi
rm -r "${MNTPATH}/"
MNTED=FALSE
}
errexit()
{
echo ""
echo "$1"
echo ""
if [ "${MNTED}" = "TRUE" ]; then
umount "${MNTPATH}/" &> /dev/null
rm -rf "${MNTPATH}/" &> /dev/null
fi
echo "Usage: $0 device [ hhhhhhhh-02 | hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh | random ]"
echo ""
exit 1
}
MNTED=FALSE
if [ $(id -u) -ne 0 ]; then
errexit "$0 must be run as root user"
fi
PGMNAME="$(basename $0)"
for PID in $(pidof -x -o %PPID "${PGMNAME}"); do
if [ ${PID} -ne $$ ]; then
errexit "${PGMNAME} is already running"
fi
done
gdisk -l "${DEVICE}" &> /dev/null
if [ $? -eq 127 ]; then
echo ""
echo "gdisk not installed"
echo ""
echo -n "Ok to install gdisk (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
break
else
errexit "Aborted"
fi
fi
done
echo ""
echo "Installing gdisk"
echo ""
apt-get update
apt-get install gdisk
fi
DEVICE="$1"
PARTUUID="$2"
if [[ (! "${DEVICE}" =~ ^/dev/sd[a-z]2$ && ! "${DEVICE}" = "/dev/mmcblk0p2") || ! -b "${DEVICE}" ]]; then
errexit "Invalid DEVICE: ${DEVICE}"
fi
ORIGUUID="$(blkid "${DEVICE}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
if [ "${PARTUUID}" = "" ]; then
PARTUUID="${ORIGUUID}"
else
DEVICE_P="$(sed 's/[0-9]\+$//' <<< "${DEVICE}")"
DEVICE_D="${DEVICE_P}"
if [ "${DEVICE_D}" = "/dev/mmcblk0p" ]; then
DEVICE_D="${DEVICE_D:0:(${#DEVICE_D} - 1)}"
fi
SDCBOOT=FALSE
if [ -b /dev/mmcblk0p1 ]; then
mntpart "/dev/mmcblk0p1" "BOOT"
CLRPART="$(sed -n '/^[[:space:]]*#/!s|^.*root=\(\S\+\)\s.*|\1|p' "${MNTPATH}/cmdline.txt")"
if [ "${CLRPART}" != "/dev/mmcblk0p2" ]; then
CLRUUID="$(sed -n 's|PARTUUID=\(\S\+\)|\1|p' <<< "${CLRPART}")"
if [[ "${CLRUUID}" != "$(blkid /dev/mmcblk0p2 | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" && "${CLRUUID}" = "${ORIGUUID}" ]]; then
SDCBOOT=TRUE
fi
fi
umntpart
fi
PTTYPE="$(blkid "${DEVICE_D}" | sed -n 's|^.*PTTYPE="\(\S\+\)".*|\1|p')"
if [[ "${PTTYPE}" != "dos" && "${PTTYPE}" != "gpt" ]]; then
errexit "Unsupported partition table type: ${PTTYPE}"
fi
PARTUUID="$(tr [A-Z] [a-z] <<< "${PARTUUID}")"
if [[ "${PARTUUID}" != "random" && (("${PTTYPE}" = "dos" && ! "${PARTUUID}" =~ ^[[:xdigit:]]{8}-02$) || \
("${PTTYPE}" = "gpt" && ! "${PARTUUID}" =~ ^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$)) ]]; then
errexit "Invalid PARTUUID: ${PARTUUID}"
fi
echo ""
echo -n "Set PARTUUID on ${DEVICE} to ${PARTUUID} (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
break
else
errexit "Aborted"
fi
fi
done
ORIGUUID_1="$(blkid "${DEVICE_P}1" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
ORIGUUID_2="$(blkid "${DEVICE_P}2" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
if [ "${PTTYPE}" = "dos" ]; then
if [ "${PARTUUID}" = "random" ]; then
PTUUID="$(hexdump -n 4 -e '"%08X"' /dev/random | tr [A-Z] [a-z])"
else
PTUUID="${PARTUUID:0:(${#PARTUUID} - 3)}"
fi
fdisk "${DEVICE_D}" <<EOF &> /dev/null
x
i
0x${PTUUID}
r
w
EOF
else
if [ "${PARTUUID}" = "random" ]; then
sgdisk -u 2:'R' "${DEVICE_D}" > /dev/null
else
sgdisk -u 2:${PARTUUID} "${DEVICE_D}" > /dev/null
fi
fi
partprobe
PARTUUID="$(blkid "${DEVICE}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
PARTUUID_1="$(blkid "${DEVICE_P}1" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
PARTUUID_2="$(blkid "${DEVICE_P}2" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
mntpart "${DEVICE_P}2" "ROOT"
sed -i "/^[[:space:]]*#/!s|^\(PARTUUID=\)${ORIGUUID_1}\(\s\+/boot\s\+.*\)$|\1${PARTUUID_1}\2|" "${MNTPATH}/etc/fstab"
sed -i "/^[[:space:]]*#/!s|^\(PARTUUID=\)${ORIGUUID_2}\(\s\+/\s\+.*\)$|\1${PARTUUID_2}\2|" "${MNTPATH}/etc/fstab"
umntpart
mntpart "${DEVICE_P}1" "BOOT"
sed -i "/^[[:space:]]*#/!s|^\(.*root=PARTUUID=\)${ORIGUUID_2}\(\s\+.*\)$|\1${PARTUUID_2}\2|" "${MNTPATH}/cmdline.txt"
umntpart
if [ "${SDCBOOT}" = "TRUE" ]; then
mntpart "/dev/mmcblk0p1" "SD card BOOT"
sed -i "/^[[:space:]]*#/!s|^\(.*root=PARTUUID=\)${ORIGUUID_2}\(\s\+.*\)$|\1${PARTUUID_2}\2|" "${MNTPATH}/cmdline.txt"
umntpart
fi
fi
echo ""
echo "PARTUUID on ${DEVICE} is set to ${PARTUUID}"
if [ -b /dev/mmcblk0 ]; then
if [[ "${DEVICE}" != "/dev/mmcblk0p2" && "$(blkid /dev/mmcblk0p2 | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" = "${PARTUUID}" ]]; then
echo ""
echo "WARNING : SD card (/dev/mmcblk0p2) and USB device (${DEVICE}) have the same PARTUUID (${PARTUUID}) : SD card will boot instead of USB device"
fi
fi
DEV_LIST=()
if [ -b /dev/mmcblk0 ]; then
DEV_LIST+=/dev/mmcblk0p2
fi
DEV_LIST+=($(ls -l /dev/sd?2 2> /dev/null | sed -n 's|^.*\(/dev/.*\)|\1|p'))
if [ ${#DEV_LIST[@]} -gt 1 ]; then
for i in ${!DEV_LIST[@]}; do
if [ ${i} -lt $((${#DEV_LIST[@]} - 1)) ]; then
j=$((i + 1))
while [ ${j} -lt ${#DEV_LIST[@]} ]; do
if [ "$(blkid "${DEV_LIST[i]}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" = "$(blkid "${DEV_LIST[j]}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" ];then
if [[ "${DEV_LIST[i]}" != "/dev/mmcblk0p2" && ("${DEV_LIST[i]}" = "${DEVICE}" || "${DEV_LIST[j]}" = "${DEVICE}") ]]; then
echo ""
echo "WARNING : ${DEV_LIST[i]} and ${DEV_LIST[j]} have the same PARTUUID : $(blkid "${DEV_LIST[i]}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
fi
fi
((j += 1))
done
fi
done
fi
echo ""

View File

@ -0,0 +1,403 @@
#!/bin/bash
trap '{ stty sane; echo ""; errexit "Aborted"; }' SIGINT SIGTERM
MNTPATH="/tmp/usb-boot-mnt"
mntdev()
{
MNTED=TRUE
if [ ! -d "${MNTPATH}/" ]; then
mkdir "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to make ROOT partition mount point"
fi
fi
mount "${1}2" "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to mount ROOT partition"
fi
if [ ! -d "${MNTPATH}/boot/" ]; then
mkdir -p "${MNTPATH}/boot/"
if [ $? -ne 0 ]; then
errexit "Unable to make BOOT partition mount point"
fi
fi
mount "${1}1" "${MNTPATH}/boot/"
if [ $? -ne 0 ]; then
errexit "Unable to mount BOOT partition"
fi
}
umntdev()
{
umount "${MNTPATH}/boot/"
if [ $? -ne 0 ]; then
errexit "Unable to unmount BOOT partition"
fi
umount "${MNTPATH}/"
if [ $? -ne 0 ]; then
errexit "Unable to unmount ROOT partition"
fi
rm -r "${MNTPATH}/"
MNTED=FALSE
}
errexit()
{
echo ""
echo "$1"
echo ""
if [ "${MNTED}" = "TRUE" ]; then
umount "${MNTPATH}/boot/" &> /dev/null
umount "${MNTPATH}/" &> /dev/null
rm -rf "${MNTPATH}/" &> /dev/null
fi
exit 1
}
MNTED=FALSE
if [ $(id -u) -ne 0 ]; then
errexit "Must be run as root user: sudo $0"
fi
PGMNAME="$(basename $0)"
for PID in $(pidof -x -o %PPID "${PGMNAME}"); do
if [ ${PID} -ne $$ ]; then
errexit "${PGMNAME} is already running"
fi
done
gdisk -l "${DEVICE}" &> /dev/null
if [ $? -eq 127 ]; then
echo ""
echo "gdisk not installed"
echo ""
echo -n "Ok to install gdisk (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
break
else
errexit "Aborted"
fi
fi
done
echo ""
echo "Installing gdisk"
echo ""
apt-get update
apt-get install gdisk
fi
rsync --version &> /dev/null
if [ $? -ne 0 ]; then
errexit "rsync not installed (run: apt-get install rsync)"
fi
ROOT_PART="$(mount | sed -n 's|^\(/dev/.*\) on / .*|\1|p')"
ROOT_DEV="$(sed 's/[0-9]\+$//' <<< "${ROOT_PART}")"
if [ "${ROOT_DEV}" = "/dev/mmcblk0p" ]; then
ROOT_DEV="${ROOT_DEV:0:(${#ROOT_DEV} - 1)}"
fi
ROOT_TYPE="$(blkid "${ROOT_PART}" | sed -n 's|^.*TYPE="\(\S\+\)".*|\1|p')"
if [ -b /dev/mmcblk0 ]; then
USESDC=TRUE
else
USESDC=FALSE
fi
if [[ $(cat /proc/cpuinfo | grep -c "^Revision\s\+:\s\+[abcd]0311[1245]$") -ne 0 || $(cat /proc/cpuinfo | grep -c "^Revision\s\+:\s\+c03130$") -ne 0 ]]; then
RPI_4=TRUE
else
RPI_4=FALSE
fi
if [ "$(vcgencmd otp_dump | grep 17:)" = "17:3020000a" ]; then
RPI_3=TRUE
else
RPI_3=FALSE
fi
if [[ "${USESDC}" = "TRUE" && "${RPI_4}" = "TRUE" ]]; then
whiptail --backtitle "USB Boot" --title "USB Boot Method" --yesno --defaultno "\nUse SD card to boot the USB device?" 12 40
YESNO=$?
if [ ${YESNO} -eq 255 ]; then
errexit "Aborted"
elif [ ${YESNO} -ne 0 ]; then
USESDC=FALSE
fi
elif [[ "${USESDC}" = "TRUE" && "${RPI_3}" = "TRUE" ]]; then
whiptail --backtitle "USB Boot" --title "USB Boot Method" --yesno "\nUse SD card to boot the USB device (recommended)?" 12 54
YESNO=$?
if [ ${YESNO} -eq 255 ]; then
errexit "Aborted"
elif [ ${YESNO} -ne 0 ]; then
USESDC=FALSE
fi
elif [[ "${USESDC}" = "FALSE" && "${RPI_4}" = "FALSE" && "${RPI_3}" = "FALSE" ]]; then
errexit "Not a Raspberry Pi 4, Raspberry Pi 3B+, or Raspberry Pi 3B with OTP set, and no SD card present or used"
fi
USBDEVS=($(ls -l /dev/sd? 2> /dev/null | sed -n 's|^.*\(/dev/.*\)|\1|p'))
if [ ${#USBDEVS[@]} -eq 0 ]; then
errexit "No available USB mass storage devices found"
fi
for i in ${!USBDEVS[@]}; do
USBDEVS[i]="${USBDEVS[i]} ${USBDEVS[i]} OFF"
done
USB_DEST="$(whiptail --backtitle "USB Boot" --title "USB Mass Storage Devices" --notags --radiolist \
"\nSelect the USB mass storage device to boot" 13 47 ${#USBDEVS[@]} ${USBDEVS[@]} 3>&1 1>&2 2>&3)"
if [[ $? -ne 0 || "${USB_DEST}" = "" ]]; then
errexit "Aborted"
fi
USB_BOOT="${USB_DEST}1"
USB_ROOT="${USB_DEST}2"
if [ "${ROOT_DEV}" != "${USB_DEST}" ]; then
whiptail --backtitle "USB Boot" --title "Replicate BOOT/ROOT Contents" --yesno "\nReplicate BOOT/ROOT contents from ${ROOT_DEV} to ${USB_DEST}?" 12 64
YESNO=$?
if [ ${YESNO} -eq 255 ]; then
errexit "Aborted"
elif [ ${YESNO} -eq 0 ]; then
if [ $(mount | grep -c "^${USB_DEST}") -ne 0 ]; then
errexit "USB mass storage device in use (mounted)"
fi
DEVSIZE=$(blockdev --getsz "${USB_DEST}")
PTTYPES=()
if [ ${DEVSIZE} -le 4294966784 ]; then
PTTYPES[0]="dos MBR ON"
PTTYPES[1]="gpt GPT OFF"
else
PTTYPES[0]="gpt GPT ON"
fi
PTTYPE="$(whiptail --backtitle "USB Boot" --title "Partition Table Type" --notags --radiolist \
"\nSelect the partition table type to use (MBR = 2TB Maximum)" 10 62 ${#PTTYPES[@]} ${PTTYPES[@]} 3>&1 1>&2 2>&3)"
if [[ $? -ne 0 || "${PTTYPE}" = "" ]]; then
errexit "Aborted"
fi
whiptail --backtitle "USB Boot" --title "WARNING" --yesno "\nWARNING\n\nAll existing data on USB device ${USB_DEST} will be destroyed!\n\nDo you wish to continue?" 14 64
YESNO=$?
if [ ${YESNO} -ne 0 ]; then
errexit "Aborted"
fi
echo ""
echo "Replicating BOOT/ROOT contents from ${ROOT_DEV} to ${USB_DEST} (this will take a while)"
if [ "${PTTYPE}" = "dos" ]; then
DEVSIZE=$(blockdev --getsz "${USB_DEST}")
dd bs=512 seek=0 count=34 if=/dev/zero of="${USB_DEST}" &> /dev/null
dd bs=512 seek=$((${DEVSIZE} - 33)) count=33 if=/dev/zero of="${USB_DEST}" &> /dev/null
partprobe
echo "label: dos" | sfdisk "${USB_DEST}" > /dev/null
sfdisk "${USB_DEST}" <<EOF &> /dev/null
,256MiB,c
,+,83
EOF
else
sgdisk -Z "${USB_DEST}" &> /dev/null
sgdisk -n 1:0:+256M "${USB_DEST}" &> /dev/null
sgdisk -t 1:0700 "${USB_DEST}" > /dev/null
sgdisk -n 2:0:0 "${USB_DEST}" &> /dev/null
sgdisk -t 2:8300 "${USB_DEST}" > /dev/null
fi
partprobe
mkfs.vfat -F 32 "${USB_BOOT}" > /dev/null
if [ $? -ne 0 ]; then
errexit "Unable to create BOOT filesystem"
fi
dosfsck "${USB_BOOT}" > /dev/null
if [ $? -ne 0 ]; then
errexit "BOOT filesystem appears corrupted"
fi
if [ "${ROOT_TYPE}" = "f2fs" ]; then
mkfs.f2fs -f "${USB_ROOT}" > /dev/null
else
mkfs.ext4 -F -q -b 4096 "${USB_ROOT}" > /dev/null
fi
if [ $? -ne 0 ]; then
errexit "Unable to create ROOT filesystem"
fi
mntdev "${USB_DEST}"
rsync -aDH --partial --numeric-ids --delete --force --exclude "${MNTPATH}" --exclude '/dev' --exclude '/lost+found' --exclude '/media' --exclude '/mnt' \
--exclude '/proc' --exclude '/run' --exclude '/sys' --exclude '/tmp' --exclude '/etc/udev/rules.d/70-persistent-net.rules' \
--exclude '/var/lib/asterisk/astdb.sqlite3-journal' "${OPTIONS[@]}" / "${MNTPATH}/"
if [[ $? -ne 0 && $? -ne 24 ]]; then
errexit "Unable to replicate BOOT/ROOT contents from ${ROOT_DEV} to ${USB_DEST}"
fi
mkdir -p "${MNTPATH}/dev/" "${MNTPATH}/lost+found/" "${MNTPATH}/media/" "${MNTPATH}/mnt/" "${MNTPATH}/proc/" "${MNTPATH}/run/" "${MNTPATH}/sys/" "${MNTPATH}/tmp/"
if [ $? -ne 0 ]; then
errexit "Unable to create directories"
fi
chmod a+rwxt "${MNTPATH}/tmp/"
umntdev
echo ""
echo "BOOT/ROOT contents replicated from ${ROOT_DEV} to ${USB_DEST}"
fi
fi
if [ "${USESDC}" = "TRUE" ]; then
if [ -b /dev/mmcblk0 ]; then
while [ "$(blkid /dev/mmcblk0p2 | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" = "$(blkid ${USB_DEST}2 | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" ]
do
echo ""
echo "WARNING : SD card (/dev/mmcblk0) and USB device (${USB_DEST}) have the same PARTUUID : SD card will boot instead of USB device"
echo ""
echo -n "Ok to change PARTUUID on USB device (${USB_DEST}) (y/n)? "
while read -r -n 1 -s answer; do
if [[ "${answer}" = [yYnN] ]]; then
echo "${answer}"
if [[ "${answer}" = [yY] ]]; then
break
else
break 2
fi
fi
done
echo ""
echo "Changing PARTUUID on USB device (${USB_DEST})"
PTTYPE="$(blkid "${USB_DEST}" | sed -n 's|^.*PTTYPE="\(\S\+\)".*|\1|p')"
if [ "${PTTYPE}" = "dos" ]; then
PTUUID="$(hexdump -n 4 -e '"%08X"' /dev/random | tr [A-Z] [a-z])"
fdisk "${USB_DEST}" <<EOF &> /dev/null
x
i
0x${PTUUID}
r
w
EOF
else
sgdisk -u 2:'R' "${USB_DEST}" &> /dev/null
fi
done
else
errexit "No SD card present"
fi
fi
if [ "${PTTYPE}" = "gpt" ]; then
gdisk "${USB_DEST}" <<EOF > /dev/null
r
h
1
n
0c
n
n
w
y
EOF
fi
partprobe &> /dev/null
PARTUUID_1="$(blkid "${USB_BOOT}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
PARTUUID_2="$(blkid "${USB_ROOT}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
if [ "${USESDC}" = "TRUE" ]; then
if [ -b /dev/mmcblk0 ]; then
mntdev "/dev/mmcblk0p"
sed -i "/^[[:space:]]*#/!s|^\(.*root=\)\S\+\(\s\+.*\)$|\1PARTUUID=${PARTUUID_2}\2|" "${MNTPATH}/boot/cmdline.txt"
umntdev
else
errexit "No SD card present"
fi
fi
mntdev "${USB_DEST}"
sed -i "/^[[:space:]]*#/!s|^\(.*root=\)\S\+\(\s\+.*\)$|\1PARTUUID=${PARTUUID_2}\2|" "${MNTPATH}/boot/cmdline.txt"
sed -i "/^[[:space:]]*#/!s|^\S\+\(\s\+/\s\+.*\)$|PARTUUID=${PARTUUID_2}\1|" "${MNTPATH}/etc/fstab"
if [ "${USESDC}" = "TRUE" ]; then
sed -i "/^[[:space:]]*#/!s|^\S\+\(\s\+/boot\s\+.*\)$|/dev/mmcblk0p1\1|" "${MNTPATH}/etc/fstab"
if [ $(grep -v '^[[:space:]]*#' "${MNTPATH}/etc/rc.local" | grep -c 'usb-boot-init') -eq 0 ]; then
cat <<\EOF1 > "${MNTPATH}/etc/usb-boot-init"
#!/bin/bash
MNTBOOT="/tmp/usb-boot-init-mnt"
REBOOT=FALSE
ROOT_PART="$(mount | sed -n 's|^\(/dev/.*\) on / .*|\1|p')"
ROOT_DEV="$(sed 's/[0-9]\+$//' <<< "${ROOT_PART}")"
if [ "${ROOT_DEV}" = "/dev/mmcblk0p" ]; then
ROOT_DEV="${ROOT_DEV:0:(${#ROOT_DEV} - 1)}"
fi
mkdir -p "${MNTBOOT}/"
mount "${ROOT_DEV}1" "${MNTBOOT}/"
if [ "$(find "${MNTBOOT}/" -type f -iregex "${MNTBOOT}/ssh\(\.txt\)?" -print -delete)" != "" ]; then
update-rc.d ssh enable && invoke-rc.d ssh start
fi
if [ -f "${MNTBOOT}/wpa_supplicant.conf" ]; then
mv "${MNTBOOT}/wpa_supplicant.conf" /etc/wpa_supplicant/wpa_supplicant.conf
REBOOT=TRUE
fi
if [[ $(grep -v '^[[:space:]]*#' "${MNTBOOT}/cmdline.txt" | grep -c 'init=/usr/lib/raspi-config/init_resize.sh') -ne 0 ]]; then
sed -i '/^[[:space:]]*#/!s| init=/usr/lib/raspi-config/init_resize\.sh||' "${MNTBOOT}/cmdline.txt"
START2=$(sfdisk -d "${ROOT_DEV}" | sed -n "s|^${ROOT_PART}\s\+:.*start=\s*\([0-9]\+\).*$|\1|p")
PTTYPE="$(blkid "${ROOT_DEV}" | sed -n 's|^.*PTTYPE="\(\S\+\)".*|\1|p')"
if [ "${PTTYPE}" = "dos" ]; then
sfdisk --delete "${ROOT_DEV}" 2 > /dev/null
echo "${START2},+" | sfdisk --no-reread --no-tell-kernel -N2 "${ROOT_DEV}" &> /dev/null
else
PARTUUID="$(blkid "${ROOT_PART}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
sgdisk -d 2 "${ROOT_DEV}" > /dev/null
sgdisk -n 2:${START2}:0 "${ROOT_DEV}" > /dev/null
sgdisk -u 2:${PARTUUID} "${ROOT_DEV}" > /dev/null
fi
cat <<\EOF2 > /etc/init.d/resize_root_fs
#!/bin/sh
### BEGIN INIT INFO
# Provides: resize_root_fs
# Required-Start:
# Required-Stop:
# Default-Start: 3
# Default-Stop:
# Short-Description: Resize root filesystem
# Description:
### END INIT INFO
case "$1" in
start)
resize2fs "$(mount | sed -n 's|^\(/dev/.*\) on / .*|\1|p')"
update-rc.d resize_root_fs remove
rm /etc/init.d/resize_root_fs
;;
*)
exit 3
;;
esac
EOF2
chmod +x /etc/init.d/resize_root_fs
update-rc.d resize_root_fs defaults
REBOOT=TRUE
fi
umount "${MNTBOOT}/"
rm -r "${MNTBOOT}/"
sed -i '/usb-boot-init/d' /etc/rc.local
rm /etc/usb-boot-init
if [ "${REBOOT}" = "TRUE" ]; then
shutdown --no-wall -r now
fi
EOF1
chmod +x "${MNTPATH}/etc/usb-boot-init"
sed -i 's|^exit 0$|/etc/usb-boot-init\nexit 0|' "${MNTPATH}/etc/rc.local"
fi
else
sed -i "/^[[:space:]]*#/!s|^\S\+\(\s\+/boot\s\+.*\)$|PARTUUID=${PARTUUID_1}\1|" "${MNTPATH}/etc/fstab"
fi
umntdev
DEV_LIST=()
if [ -b /dev/mmcblk0 ]; then
DEV_LIST+=/dev/mmcblk0p2
fi
DEV_LIST+=($(ls -l /dev/sd?2 2> /dev/null | sed -n 's|^.*\(/dev/.*\)|\1|p'))
if [ ${#DEV_LIST[@]} -gt 1 ]; then
for i in ${!DEV_LIST[@]}; do
if [ ${i} -lt $((${#DEV_LIST[@]} - 1)) ]; then
j=$((i + 1))
while [ ${j} -lt ${#DEV_LIST[@]} ]; do
if [ "$(blkid "${DEV_LIST[i]}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" = "$(blkid "${DEV_LIST[j]}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')" ]; then
if [[ "${DEV_LIST[i]}" != "/dev/mmcblk0p2" || "${DEV_LIST[j]}" != "${USB_DEST}2" ]]; then
echo ""
echo "WARNING : ${DEV_LIST[i]} and ${DEV_LIST[j]} have the same PARTUUID : $(blkid "${DEV_LIST[i]}" | sed -n 's|^.*PARTUUID="\(\S\+\)".*|\1|p')"
fi
fi
((j += 1))
done
fi
done
fi
if [ "${USESDC}" = "TRUE" ]; then
echo ""
echo "SD card must remain in place to boot the USB device"
else
if [ -b /dev/mmcblk0 ]; then
echo ""
echo "SD card must be removed to boot the USB device"
fi
fi
echo ""

View File

@ -0,0 +1,89 @@
Running Raspbian on USB Devices : Made Easy
A recurring topic of discussion is how to configure and reliably run Raspbian on a USB flash drive, USB hard drive, or USB SSD instead of an SD card.
A Raspberry Pi 3B+ has a native USB boot mode (this mode has to be manually enabled by setting an OTP bit on a Raspberry Pi 3B). This native USB boot mode has serious compatibility issues. A bootcode.bin file is available for older Raspberry Pi models. Unfortunately, both of these approaches have serious limitations and once working, can easily be broken by simply plugging in an additional USB storage device.
The easiest and most reliable way to run Raspbian on a USB device with any Raspberry Pi is to leave an SD card containing Raspbian in place, but use it only for starting Raspbian that is residing on a USB device. While setting up such a configuration is not rocket science, it can be confusing to a newcomer or someone unfamiliar with Linux internals. In an effort to simplify the task, I've created the attached script named 'usb-boot' to automate the process.
If usb-boot is running on a Raspberry Pi 4, usb-boot first prompts: 'Use SD card to boot the USB device?'
If 'No' is selected, the SD card will not be altered and the direct USB boot capability of the Raspberry Pi 4 will be used.
[NOTE: The direct USB boot capability of the Raspberry Pi 4 requires an updated bootloader and firmware: https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711_bootloader_config.md]
If usb-boot is running on a Raspberry Pi 3B+ or a Raspberry Pi 3B with its OTP bit set, usb-boot first prompts: 'Use SD card to boot the USB device (recommended)?'
If 'No' is selected, the SD card will not be altered, but booting the USB device may be limited and/or unreliable as described above.
usb-boot then presents a list of available USB mass storage devices and prompts: 'Select the USB mass storage device to boot'
Use the arrow keys on your keyboard to navigate to the desired device and press the spacebar to select it. Then use the tab key to navigate to the 'Ok' or 'Cancel' button and press the return key.
usb-boot will then prompt: 'Replicate BOOT/ROOT contents from /dev/mmcblk0 to /dev/sdX?'
/dev/mmcblk0 is the SD card and /dev/sdX is the USB device.
Select 'No' if the USB device already has Raspbian on it and you wish to use it (nothing will be copied).
Select 'Yes' if you want to copy the Raspbian on your SD card to the USB device (everything will be copied).
If you select 'Yes', usb-boot will then prompt: 'Select the partition table type to use (MBR = 2TB Maximum)'
usb-boot will then prompt: 'All existing data on USB device /dev/sdX will be destroyed!' and ask: 'Do you wish to continue?'
If you select 'Yes', the copy will begin. The time required for this process will depend on the amount of data on your SD card and the speed of your storage devices.
usb-boot will then complete the configuration process and warn you of any potential conflicts it detects.
When usb-boot has finished, you should be able to reboot and be running Raspbian on the USB device (first power off and remove the SD card if not using the SD card to boot the USB device).
=====
sdc-boot provides a convenient way to select which attached device will be booted.
Usage syntax is:
sdc-boot [ /dev/sdX2 | /dev/mmcblk0p2 | hhhhhhhh-02 | hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh ]
/dev/sdX2 is a USB device
/dev/mmcblk0p2 is the SD card
hhhhhhhh-02 | hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh is a device identified by its PARTUUID
If no device is specified, the currently selected boot device will be displayed.
=====
GPT partition tables are necessary for devices whose size is over 2TB.
mbr2gpt converts an MBR partition table on a USB device to a GPT partition table, as well as optionally expanding the ROOT partition and enabling booting via an SD card.
mbr2gpt converts any size USB device, including SD cards placed in a USB adapter.
!!! DO NOT PROCEED UNLESS YOU HAVE A RELIABLE BACKUP OF THE DEVICE BEING CONVERTED !!!
!!! INITIAL TESTING SHOULD BE PERFORMED ON A USB DEVICE CONTAINING EXPENDABLE DATA !!!
Usage syntax is:
mbr2gpt /dev/sdX
mbr2gpt will prompt for permission to perform the following optional functions:
1. Convert the USB device to use GPT partition tables
2. Expand the ROOT partition to use all available space
3. Set the SD card to boot the USB device
=====
set-partuuid displays or changes the ROOT partition PARTUUID on a device.
Usage syntax is:
set-partuuid device [ hhhhhhhh-02 | hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh | random ]
device may be /dev/sdX2 or /dev/mmcblk0p2
If no partuuid is specified, the current ROOT partition PARTUUID will be displayed.

View File

@ -0,0 +1,24 @@
# video-tools
## Use case
I bought a machine named Vero 4K+, running OSMC, to read my video library on my TV. It works fine on a number of files, but was struggling on some others, when no hardware decoder was available. I [found this thread](https://discourse.osmc.tv/t/ff-h264-dropping-and-skipping-frames/37459) that gave a first hint. I then decided to made a quick list of all the format of my files, to be able to test them. That's how this little project started.
## Set up
* On a Linux machine with apt: `sudo apt install -y ffmpeg mediainfo`
* If you are on Windows, you can:
* [install WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10)
* install Debian/Ubuntu/etc and type in: `sudo apt install -y ffmpeg mediainfo`
* If you access a networked drive, you can access it like this for example:
* `sudo mkdir /mnt/multimedia`
* `sudo mount -t drvfs //Vero/Multimedia /mnt/multimedia/` (this need to be done at each reboot)
## video-info.sh
video-info.sh generates a CSV file with the format, the format profile, and the codec ID. It can be opened with any spreadsheet software. If it is interrupted, an option to resume the operation is available, so that it is possible to launch it multiple time on huge video libraries.
It generates a file named `videoinfo-date_hour.csv` in the folder where the videos are located.
By default, only the following file extensions are taken into account: `mkv`, `mp4`, and `avi`. You can easily add more by modifying the script. You can know which file extensions you have in your library by typing the following command (more [on this thread](https://stackoverflow.com/questions/1842254/how-can-i-find-all-of-the-distinct-file-extensions-in-a-folder-hierarchy)):
`find . -type f | sed -e 's/.*\.//' | sed -e 's/.*\///' | sort | uniq -c | sort -rn`

View File

@ -0,0 +1,90 @@
#/bin/sh
CURRENT_DATE=$(date +%Y%m%d_%H%M)
bold=$(tput bold)
normal=$(tput sgr0)
# $1 = video file / $2 = result file
get_mediainfo() {
echo "Processing $1..."
echo -ne "\"$1\", " >> $2
mediainfo --Inform="Video;%Format%, %Format_Profile%, %CodecID%" "$1" >> $2
}
gather_data() {
echo "Gathering data..."
RESULT_PATH=./$1/videoinfo-"$CURRENT_DATE".csv
PWD=$(pwd $1)/$1
echo $PWD >> $RESULT_PATH
echo 'File, Format, Format Profile, Codec ID' >> $RESULT_PATH
find $1 -type f \( -iname \*.mkv -o -iname \*.mp4 -o -iname \*.avi \) | while read file;
do
get_mediainfo "$(pwd $1)/$file" $RESULT_PATH
done
echo "Finished! Result file is located at ${bold}$RESULT_PATH${normal}."
}
gather_data_continue() {
echo "Continue gather data of file ${bold}$1${normal}..."
LAST_LINE=$(tail -n 1 $1)
LAST_FILE=$(echo $LAST_LINE | sed 's/^\"\(.*\)\".*$/\1/g')
# where to find the files...
FIRST_LINE=$(head -n 1 $1)
# remove last line of file (might print error on WSL, hence the error suppression)
sed -i '$d' $1 2>/dev/null
FOUND_FILE=0
find $FIRST_LINE -type f \( -iname \*.mkv -o -iname \*.mp4 -o -iname \*.avi \) | while read file;
do
if [[ $file == $LAST_FILE ]]; then
FOUND_FILE=1
fi
if [[ $FOUND_FILE == 1 ]]; then
get_mediainfo "$file" $1
fi
done
echo "Finished! Result file is located at ${bold}$1${normal}."
}
continue_gather() {
if [[ $1 == "" ]]; then
echo "No input file. Please input CSV file generated by this program."
elif [[ -f $1 ]]; then
gather_data_continue $1
else
echo "File ${bold}$1${normal} does not exist."
fi
}
show_usage() {
echo -e "This tool generates a CSV file with useful video data\n"
echo -e "Options:"
echo -e "./video-info.sh [-h, --help]"
echo -e "\t\tDisplays help and usage.\n"
echo -e "./video-info.sh DIRECTORY"
echo -e "\t\tCreates a CSV file from all the video files from the directory.\n"
echo -e "./video-info.sh [-c, --continue] FILE"
echo -e "\t\tIf the gathering was interrupted, use this option to resume."
echo -e "\t\tTakes the incompletely generated CSV file as a parameter.\n"
echo -e "There are some limitations as this is a pretty simple tool. If you have any issue,"
echo -e "please read the README file."
}
if [[ $1 == "" ]] || [[ $1 == "--help" ]] || [[ $1 == "-h" ]]; then
show_usage
exit
elif [[ $1 == "-c" ]] || [[ $1 == "--continue" ]]; then
continue_gather $2
else
if [[ -d $1 ]]; then
gather_data $1
else
echo -e "Directory ${bold}$1${normal} does not exist. Showing usage...\n"
show_usage
fi
fi