Home server : auto-shutdown if no other computers are running

I wanted to shutdown my linux home media server if there is no running computer on my network. So I wrote this little programs which reads all known ips from DHCP configuration and lease files and send a ping to them. If the ping respond, one PC of my LAN is up…

To re-start the computer in the morning, I use the BIOS RTC alarm (the thing you have by pressing F1 or ESC on reboot). You could also add a script/a program on each of your computers to send the magic packet to your home server to wake it by lan (see “wake on lan” on google).

This script can take any command. But if you want to do shutdown like proposed in the title, you can use :

[code]sudo ./autoshut “poweroff” 10.0.0.1[/code]

Where 10.0.0.1 is your local IP adress. To compile the program, simply use (after saving the code as “autoshut.c”) : [code]gcc -o autoshut autoshut.c[/code]

In my case, I wanted to launch the command only after midnight, so I used cron. Cron will launch that command every 5 minutes from midnight to eight o’clock. So if I stay up late, my server won’t shutdown if my own computer is not down too. That’s the whole purpose.

The line in my crontab :

[code]0,10,20,30,40,50 1,2,3,4,5,6,7 * * * root /home/tom/autoshut/autoshut “poweroff” 10.0.0.1[/code]

Why do all that ? Energy consumption…

[code]#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
#include <string.h>

#define LEASE_FILE “/var/lib/dhcp/dhcpd.leases”
#define DHCP_CONFIG_FILE “/etc/dhcp/dhcpd.conf”

int in_array(char** ar, char* str) {
int i = 0;
while (ar[i] != NULL) {
if (strcmp(ar[i],str) == 0) return 1;
i++;
}
return 0;
}

char* extractIP (char* filename, char** list, int* listNum) {
FILE *pfile;

pfile = fopen(filename, “rb”);

if(pfile == NULL){
printf(“Sorry, can’t open %s\n”, filename);
return ‘\0’;
}
regex_t reg;
int err = regcomp (&reg, “(10\\.[0-1]\\.0\\.([1-9]|[0-9]{2,3}))”, REG_EXTENDED);
if (err != 0)
{
printf(“ERREUR\n”);
return ‘\0’;
}
char ligne[255];

while(!feof(pfile)) {
fgets(ligne, 254 ,pfile);
int match;
size_t nmatch = 0;
regmatch_t *pmatch = NULL;

nmatch = reg.re_nsub;
pmatch = malloc (sizeof (*pmatch) * nmatch);
match = regexec (&reg, ligne, nmatch, pmatch, 0);
if (match == 0)
{
char *ip = NULL;
int start = pmatch->rm_so;
int end = pmatch->rm_eo;
size_t size = end – start;

char* str = malloc(sizeof(char) * 15);
strncpy (str, &ligne[start], size);
str[size] = ‘\0’;

if (!in_array(list, str)) {
list[*listNum] = str;
printf(“%s\n”, str);
(*listNum)++;
}

}

}

fclose(pfile);
regfree(&reg);
}

int ping(char* address) {

char cmd[30] = “ping -W 1 -q -c 1 “;
strcat(cmd,address);
int ret=system(cmd);
printf(“\nResultat : %d\n\n”,ret);
return !ret;
}
/*
TODO : maybe an option?
int checkCableStatus(const char* interface) {
/sys/class/net/eth0/carrier
}*/

int main(int argc, char** argv) {

if (argc <= 2) {
printf(“Usage : %s Command Local-IP [Local-IP-2]\n\tCommand : a command to execute if ping does not work\n\tLocal-IP : Ip to ignore\n\tLocal-IP-2 : Optional second ip to ignore”,argv[0]);
return -1;
}
int listNum = 0;
char** list = malloc(sizeof(char*) * 255);
extractIP(DHCP_CONFIG_FILE, list, &listNum);
extractIP(LEASE_FILE, list, &listNum);
int i = 0;
while (list[i] != NULL) {
if (strcmp(list[i],argv[2])!=0 && (argc==3 ||strcmp(list[i],argv[3])!=0)) {
if (ping (list[i])) {
printf(“%s responded. Command aborted !\n”,list[i]);
return EXIT_SUCCESS;
}
}
i++;
}
system(argv[1]);
return EXIT_SUCCESS;
}[/code]

Ugly things : Autologin of console at startup

→ Ugly but okay in a development virtual machine…

Add “– – autologin root” at the end of /etc/init/tty1.conf

It will auto-log you as root for the first console. For the others, do the same with /etc/init/tty[1-5].conf

You can autolog as “student” by replacing root by student. It’s a little safer … but still ugly.

Detecting if another user is connected on UNIX systems

How to detect if another user is connected to your machine or your server? You can use the command “users” to check yourself is someone is connected. But to do it automatically, you’ll have to use some pipe :

[code lang=”bash”]expr length “`users | sed -e “s/\($USER\|\[ \]*\)//ig”`”[/code]

The first thing executed by the shell will be the thing under french apostrophe ( ` ). Theses are for evaluation a command, and replace it by what it outputs (normally print on the screen). The command users print the list of connected users into a pipe, to sed which evaluate its command as a regular expression (-e parameter). The command is “s” for substitute, and the rest tells him to find “$USER” (replaced by the currently connected user, you) and spaces and replace them by … nothing. So this part will be an empty string if there is no other users connected than “$USER” and something not empty if there is.

The “expr length” return the length of a string. So this commands print 0 if there is no other user connected, and >0 if there is some !

In a shell script to do something if yes or no…
[code lang=”bash”]#!/bin/sh
usersstripped=`users | sed -e “s/\(tom\|\[ \]*\)//ig”`
connected=`expr length “$usersstripped”`
if [ “$connected” -eq “0” ] ; then
echo “No other user is connected”
else
echo “Other user connected !”
fi[/code]

It is essentially the same command but done in two times, as in a shell script this command would not be evaluated correctly.

And if you want to put that in a cron to eg. send you a mail, you just have to type “crontab -e” and put a line like :

[code]* * * * * /home/tom/connected[/code]

To launch it every minutes. But if you do that you’d better do something like detecting a connection…

My .ZSHRC

To gain a lot of time, you can replace the old “bash” by “zsh”, which is a very more powerful shell with autocompletion, but not only… It has a configuration file called “.zshrc” that you have to put in your home. You can try zsh by installing it and typing “zsh”, and if you’re convinced, keeping it by typing chsh and choosing /bin/zsh.

You have my entire .zshrc but I choose 3 snippets that I prefer to show you the usefullness :

As I use Fedora, Debian, and Ubuntu, I made this to type “i program” to install profram on any system, and “u” to update the system. 


[code lang=”bash”]
#i for install, u for update

if [ -e /bin/yum ]; then
alias i=”sudo yum install”
alias u=”sudo yum update”
else
alias i=”sudo apt-get install”
alias u=”sudo apt-get update && sudo apt-get dist-upgrade”
fi
[/code]

Force sudo for commands that anyway need it
[code lang=”bash”]
alias yum=”sudo yum”
alias apt-get=”sudo apt-get”
alias service=”sudo service”
[/code]

 

Setting vi as default editor :
[code lang=”bash”]
export EDITOR=”vi”
[/code]

 

The multiples ssh shorcuts
[code lang=”bash”]alias sshd=”ssh mappam.dyndns.org -X”
alias sshc=”ssh itstudents.be -X -L 3129:localhost:3129″[/code]

I use my zshrc on multiple system and multiple environment, hence you have some tricks and multiplications like different variables for the same program in the path variable.

[code lang=”bash”]

#Using color schemes
autoload -U colors && colors

#Adding ADB to path variable
export PATH=${PATH}:/usr/src/android-sdk-linux/platform-tools/:/usr/src/android-sdk-linux/tools/:/opt/android/platform-tols/

##################
# Aliases
##################
#SSH shorcuts
alias sshd=”ssh mappam.dyndns.org -X”
alias sshc=”ssh itstudents.be -X -L 3129:localhost:3129″
alias ssha=”ssh asbss.be -X”
alias sshu=”ssh barbette@ms806.montefiore.ulg.ac.be -X”
alias sshq=”ssh barbette@queen.run.montefiore.ulg.ac.be -X”
alias scpi=”scp -i /home/tom/.ssh/id_rsa”
alias scp3=”scp -P 3690 -i /home/tom/.ssh/id_rsa”
alias sam=”ssh-add /home/tom/.ssh/id_rsa.montefiore”

#I have all my usefull scripts on my server itstudents, this alias update all scripts on a client, including this .zshrc
alias us=”mkdir -p /home/tom/.scripts && scpi tom@itstudents.be:/home/tom/.ssh/authorized_keys /home/tom/.ssh/authorized_keys && scpi tom@itstudents.be:/home/tom/.zshrc /home/tom/.zshrc && scpi -r tom@itstudents.be:/home/tom/.scripts/ /home/tom/ && source /home/tom/.zshrc”

#Alias for these scripts…
#Archiver pack a folder in tar.gz
alias archiver=”/home/tom/.scripts/archiver”
#Archiver7 pack a folder in a tar.7z
alias archiver7=”/home/tom/.scripts/archiver7″
#Update and reset permissions of an svn
alias svnup=”/home/tom/.scripts/svnup”
#Push an rsa key to the list of authorized keys
alias pushrsa=”ssh tom@itstudents.be \”cat – >> /home/tom/.ssh/authorized_keys\” < ”

#Mount some local shares
alias mh=”sudo mount -t nfs debserver:/home/tom /mnt/debserver-home”
alias mp=”sudo mount -t nfs debserver:/pub /mnt/debserver-pub”

#Force sudo for some sudo-only commands like installers
alias yum=”sudo yum”
alias apt-get=”sudo apt-get”
alias service=”sudo service”

#Some copy-pasted shorcut from elsewhere
alias k=’tree’
alias ltr=’ls -ltr’
alias r=’screen -D -R’
alias ls=’ls –color’
alias l=’ls -lh’
alias ll=’ls -la’
#i for install, u for update
if [ -e /bin/yum ]; then
alias i=”sudo yum install”
alias u=”sudo yum update”
else
alias i=”sudo apt-get install”
alias u=”sudo apt-get update && sudo apt-get dist-upgrade”
fi

#Some links to launch programs on my android devices
alias adbf=”ard && adb forward tcp:8999 tcp:8999 && google-chrome http://localhost:8999 &”
alias agmail=”adb shell am start -a android.intent.action.MAIN -n com.google.android.gm/.ConversationListActivityGmail”
alias amail=”adb shell am start -a android.intent.action.MAIN -n com.google.android.email/com.android.email.activity.EmailActivity”
alias ard=”adb shell am start -a android.intent.action.MAIN -n net.xdevelop.rm/.RemoteMobile”
alias rr=”sudo route del default && sudo route add default gw 10.0.0.1″

#Wake on lan shorcuts
alias wdebian=”sudo etherwake 8C:89:A5:C1:D2:8A”

 

 

# Meta-u to chdir to the parent directory
bindkey -s ‘\eu’ ‘^Ucd ..; ls^M’

bindkey ‘\e[1~’ beginning-of-line
bindkey ‘\e[4~’ end-of-line
bindkey ‘\e[7~’ beginning-of-line
bindkey ‘\e[8~’ end-of-line
bindkey ‘\eOH’ beginning-of-line
bindkey ‘\eOF’ end-of-line
bindkey ‘\e[H’ beginning-of-line
bindkey ‘\e[F’ end-of-line
bindkey ‘\e[5~’ beginning-of-history
bindkey ‘\e[6~’ end-of-history
bindkey ‘\e[3~’ delete-char

#Enable auto correct for commands
setopt correct

# Pipe the current command through less
bindkey -s “\el” ” 2>&1|less^M”
zstyle ‘:completion:*:(all-|)files’ ignored-patterns ‘(|*/)CVS’
zstyle ‘:completion:*:cd:*’ ignored-patterns ‘(*/)#CVS’

#Mode verbose pour cp, rm, chmod, chown et rename
for c in cp rm chmod chown rename; do
alias $c=”$c -v”
done

#Pendunt une complétion affiche les points
expand-or-complete-with-dots() {
echo -n “\e[31m……\e[0m”
zle expand-or-complete
zle redisplay
}
zle -N expand-or-complete-with-dots
bindkey “^I” expand-or-complete-with-dots

setopt EXTENDED_GLOB
setopt NO_BEEP
export EDITOR=”vi”
setopt ZLE
setopt AUTO_CD

##################
# Completion Stuff
##################
bindkey -M viins ‘\C-i’ complete-word

# Faster! (?)
zstyle ‘:completion::complete:*’ use-cache 1
# generate descriptions with magic.
zstyle ‘:completion:*’ auto-description ‘specify: %d’

# Don’t prompt for a huge list, page it!
zstyle ‘:completion:*:default’ list-prompt ‘%S%M matches%s’

# Don’t prompt for a huge list, menu it!
zstyle ‘:completion:*:default’ menu ‘select=0’

# Have the newer files last so I see them first
zstyle ‘:completion:*’ file-sort modification reverse

# color code completion!!!! Wohoo!
zstyle ‘:completion:*’ list-colors “=(#b) #([0-9]#)*=36=31″

unsetopt LIST_AMBIGUOUS
setopt COMPLETE_IN_WORD

# Separate man page sections. Neat.
zstyle ‘:completion:*:manuals’ separate-sections true

# Egomaniac!
zstyle ‘:completion:*’ list-separator ‘fREW’

# complete with a menu for xwindow ids
zstyle ‘:completion:*:windows’ menu on=0
zstyle ‘:completion:*:expand:*’ tag-order all-expansions

# more errors allowed for large words and fewer for small words
zstyle ‘:completion:*:approximate:*’ max-errors ‘reply=( $(( ($#PREFIX+$#SUFFIX)/3 )) )’

# Errors format
zstyle ‘:completion:*:corrections’ format ‘%B%d (errors %e)%b’

# Don’t complete stuff already on the line
zstyle ‘:completion::*:(rm|vi):*’ ignore-line true

# Don’t complete directory we are already in (../here)
zstyle ‘:completion:*’ ignore-parents parent pwd

zstyle ‘:completion::approximate*:*’ prefix-needed false

#}}}

export GREP_COLOR=31
alias grep=’grep –color=auto’

#{{{ Prompt!
colors
host_color=cyan
history_color=yellow
user_color=green
root_color=red
directory_color=magenta
error_color=red
jobs_color=green

host_prompt=”%{$fg_bold[$host_color]%}%m%{$reset_color%}”
jobs_prompt1=”%{$fg_bold[$jobs_color]%}(%{$reset_color%}”
jobs_prompt2=”%{$fg[$jobs_color]%}%j%{$reset_color%}”
jobs_prompt3=”%{$fg_bold[$jobs_color]%})%{$reset_color%}”
jobs_total=”%(1j.${jobs_prompt1}${jobs_prompt2}${jobs_prompt3} .)”
history_prompt1=”%{$fg_bold[$history_color]%}[%{$reset_color%}”
history_prompt2=”%{$fg[$history_color]%}%h%{$reset_color%}”
history_prompt3=”%{$fg_bold[$history_color]%}]%{$reset_color%}”
history_total=”${history_prompt1}${history_prompt2}${history_prompt3}”
error_prompt1=”%{$fg_bold[$error_color]%}<%{$reset_color%}”
error_prompt2=”%{$fg[$error_color]%}%?%{$reset_color%}”
error_prompt3=”%{$fg_bold[$error_color]%}>%{$reset_color%}”
error_total=”%(?..${error_prompt1}${error_prompt2}${error_prompt3} )”

case “$TERM” in
(screen)
function precmd() { print -Pn “\033]0;S $TTY:t{%100<…<%~%<<}\007″ }
;;
(xterm)
directory_prompt=”%{$fg[$directory_color]%}%~%{$reset_color%} ”
;;
(*)
directory_prompt=”%{$fg[$directory_color]%}%~%{$reset_color%} ”
;;
esac

if [[ $USER == root ]]; then
post_prompt=”%{$fg_bold[$root_color]%}%#%{$reset_color%}”
else
post_prompt=”%{$fg_bold[$user_color]%}%#%{$reset_color%}”
fi
fg_light_gray=$’%{\e[0;34m%}’
PS1=”${host_prompt} ${jobs_total}${history_total} ${error_total}${post_prompt} ”
RPROMPT=”%{$fg_bold[$user_color]%}<%{$reset_color%} ${directory_prompt}${fg_light_gray}[%*]%{$reset_color%}”
#}}}
#Type f to flush the console to history file
alias f=”fc -W”

HISTSIZE=10000
SAVEHIST=10000
HISTFILE=~/.history

setopt LIST_PACKED

#Append to history file instead of re-writing
setopt APPEND_HISTORY

#To share history between terminal, not what I want as I always have multiple terminals like one for generating packet, the other to receive them
#setopt SHARE_HISTORY

#Remove blanks
setopt hist_reduce_blanks
#Remove duplicates
setopt hist_ignore_all_dups
#Do not store commands in history starting with white space
setopt hist_ignore_space

#Init
autoload -U compinit promptinit zcalc zsh-mime-setup
compinit
promptinit
zsh-mime-setup[/code]