Sunday, August 18, 2013

Synology Diskstation Disk Checking Python Script

After purchasing a Synology Diskstation, I thought it would be a good idea to familiarize myself with the inner workings and customizations.

One feature that is useful for power users is the command line interface that is accessible through ssh.

There's a few tutorials on how to bootstrap your Diskstation and gain root ssh access, but I'm not going to be covering it in this post.

There's a bunch of tutorials on how to run a proper disk check on your system, but I thought I would write a script to do it for me.

WARNING: I wrote this for my system specifically. I did write it as generally as possible, taking user input, so it should work on almost all systems.

note: I was also unaware of the python argparse module, so it's not as elegant as it could be. I may update it in my repository, but I likely won't update this post.

At this point, I would also like to say that the Synology Diskstation is the most spectacular NAS unit on the market. It costs a little more, but it is well worth it! 


The Script

#! /usr/bin/env python2

import subprocess as sp
from datetime import datetime as dt
from os import path, geteuid, getcwd
from sys import argv, exit



def run(test):
 """
 Run the functions that comprise the disk checking routine.
 """
 rootCheck()
 continueCheck()
 print 'Volume Checking For /volume1'
 mountPoint, optMount, fileSystem = getFileSystem()
 logfile = setLogFile(test, fileSystem)
 initLog(logfile)
 logFileSystem(logfile, mountPoint, optMount, fileSystem)
 stopRunningProcesses(test, logfile)
 umountVolumes(test, logfile, optMount)
 flags = getFlags(test, logfile)
 runCheck(test, logfile, fileSystem, mountPoint, flags)
 rebootSystem(test, logfile)



def getTime():
 """
 Get the current time
 """
 now = dt.now().strftime
 time = now('%H:%M:%S')
 return time



def getDate():
 """
 Get the current date
 """
 now = dt.now().strftime
 date = now('%d/%m/%y')
 return date



def writeLog(logfile, data):
 """
 Open the specified log file, get the current time,
 print it to the screen and append it to the log.
 Close the log file.
 """
 with open(logfile, 'a') as outFile:
  time = getTime()
  print '%s ->  %s' % (time, data)
  outFile.write('%s ->  %s\n' % (time, data))
  outFile.close()



def initLog(logfile):
 """
 Open the specified log file, get the current time/date,
 print it to the screen and append it to the log file with a
 --- START LOG --- opening tag. Close the file.
 """
 with open(logfile, 'a') as outFile:
  time = getTime()
  date = getDate()
  dateTime = '%s  %s' %(date, time)
  print dateTime
  print 'Starting Log: %s' % logfile
  startLog = '\n\n%s\n--- START LOG ---\n\n' %dateTime
  outFile.write(startLog)
  outFile.close()



def closeLog(logfile):
 """
 Open the specified log file, get the current time
 print it to the screen and append it to the log with a
 --- END OF LOG --- tag. Close the file.
 """
 with open(logfile, 'a') as outFile:
  time = getTime()
  print 'End Log: %s' % logfile
  endLog = '\n\n%s\n--- END OF LOG ---\n' %time
  outFile.write(endLog)
  outFile.close()



def exitError(logfile, err):
 """
 Write to the specified log file the given error (err) message.
 Close the file, and exit the script.
 """
 writeLog(logfile, err)
 closeLog(logfile)
 exit(err)



def rootCheck():
 """
 If the current user id is not root, exit.
 If the current working directory is not /, exit.
 """
 if geteuid() != 0:
  err = 'This script must be run as the root user!\nExiting Now.'
  exit(err)
 if getcwd() != '/':
  err = 'This script must be run from \'/\'!\nExiting Now.'
  exit(err)



def continueCheck():
 """
 Prompt the user with a warning, ask for confirmation before continuing.
 If 'n', exit the script. If 'y', continue.
 """
 proceed = 'x'
 warning = "\n\
 WARNING: THIS PROGRAM STOPS FILESYSTEM PROCESSES AND UNMOUNTS VOLUMES!\n\
          PLEASE MAKE SURE YOU'VE STOPPED ALL RUNNING PROCESSES\n\
          AND DISCONNECTED FROM NETWORK SHARES BEFORE YOU CONTINUE.\n\
          THE SYSTEM WILL REBOOT TO COMPLETE THE PROCESS.\n\
  (it's also recommended that you disable your system beep temporarily)\n\
 "
 print warning
 while proceed not in ['Y','y','N','n']:
  proceed = raw_input('Are you sure you want to continue? (y/n): ')
 if proceed == 'n' or 'N':
  exit('Maybe next time. Take it easy.')
 if proceed == 'y' or 'Y':
  print 'Continuing...\n\n'



def getFileSystem():
 """
 Get the mounted volume information from the system.
 Check for /volume1 and /opt. If they don't exist, exit.
 Return the mountPoint, optMount, fileSystem of /volume1.
 """
 system = sp.check_output('mount').split('\n')
 optMount = 0
 for mount in system:
  try:
   if '/volume1' in mount:
    if not '/opt' in mount:
     volumeInfoList = mount.split(' ')
     mountPoint = volumeInfoList[0]
     fileSystem = volumeInfoList[4]
    if '/opt' in mount:
     optMount = 1
  except:
   err = 'Can\'t find volume1!!' 
   exit(err)
 return (mountPoint, optMount, fileSystem)



def setLogFile(test, fileSystem):
 """
 With the specified fileSystem and test value, check for/create the
 /log directory and a log file of either fsck.ext4.log, e2fsck.log,
 or test.log.
 Print the results to the screen and return logfile.
 """
 logdir = '/log'
 if not path.exists(logdir):
  print 'Log directory %s does not exist. Creating it now.' % logdir
  try:
   sp.check_output(['mkdir', logdir])
   print 'Directory successfully created.'
  except:
   exit('Something went wrong.')
 if test:
  logfile = '%s/test.log' % logdir
 else:
  if fileSystem == 'ext4':
   logfile = '%s/fsck.ext4.log' % logdir
  if fileSystem == 'ext2':
   logfile = '%s/fsck.ext2.log' % logdir
 if not path.exists(logfile):
  print 'Log file does not exist. Creating it now.'
  try:
   sp.check_output(['touch', logfile])
  except:
   exit('Something went wrong.')
 print 'Log directory: %s' % logdir
 print 'Log file: %s' % logfile
 return logfile



def logFileSystem(logfile, mountPoint, optMount, fileSystem):
 """
 Write the system information to the given log file.
 """
 if not optMount:
  writeLog(logfile, '/opt Mount: No')
 writeLog(logfile, '/opt Mount: Yes')
 writeLog(logfile, 'Mount Point: %s' % mountPoint)
 writeLog(logfile, 'File System: %s' % fileSystem)



def stopRunningProcesses(test, logfile):
 """
 Stop the file services specified in the list.
 Log the service as it's stopped.
 If it's a test, log the command but do not execute.
 """
 fileServiceList = ['S25download.sh', 'S20pgsql.sh', 'S80samba.sh', 'S83nfsd.sh', 'S81atalk.sh']
 serviceDir = '/usr/syno/etc/rc.d/'
 cmd = 'stop'
 writeLog(logfile, 'Running Commands:') 
 for fileService in fileServiceList:
  if path.exists('%s%s' %(serviceDir,fileService)):
   service = '%s%s' %(serviceDir, fileService)
   stopService = '%s %s' %(service, cmd)
   if test:
    writeLog(logfile, stopService)
   else:
    try:
     writeLog(logfile, stopService)
     sp.check_output([service,cmd])
    except:
     err = 'Error Stopping Processes!\nSomething went wrong...'
     exitError(logfile, err)



def umountVolumes(test, logfile, optMount):
 """
 Unmount the volumes /volume1 and /opt (if available).
 Append to the log. If it's a test, log the command but do not execute.
 """
 writeLog (logfile, 'Unmounting volumes...')
 if test:
  writeLog (logfile, 'umount /volume1')
  if optMount:
   writeLog (logfile, 'umount /opt')
 else:
  writeLog (logfile, 'umount /volume1')
  sp.check_output(['umount','/volume1'])
  if optMount:
   writeLog (logfile, 'umount /opt')
   sp.check_output(['umount','/opt'])



def getFlags(test, logfile):
 """
 Set the fsck flags to p (auto-fix), v (verbose), f (force).
 If it's a test, change p to n (no action).
 If not the default flags, enter the desired sequence.
 Log and print the flags.
 """
 if not test:
  useDefault = 'x'
  while useDefault not in ['y','Y','n','N']:
   useDefault = raw_input('Use the default -pvf flags for the disk check? (y/n): ')
  if useDefault in ['y','Y']:
   flags = '-pvf'
  else:
   sp.check_output(['fsck.ext4'],['--help'])
   flags = raw_input('Please specify the flags you would like to use.\n\
   (example: -nvf): ')
 else:
  flags = '-nvf'
 writeLog(logfile, 'Using flags: %s' % flags)
 return flags



def runCheck(test, logfile, fileSystem, mountPoint, flags):
 """
 Choose the correct checking system given the file system provided.
 ext2 and ext4 are supported. Other systems will exit the script.
 Log the running command, run the command and
 write the output to the logfile.
 """
 if fileSystem == 'ext4':
  checkCommand = 'fsck.ext4'
 elif fileSystem == 'ext2':
  checkCommand = 'e2fsck'
 else:
  err = 'Unrecognized file system!\nSomething went wrong!' 
  exitError(logfile, err)
 running = 'Running Command: %s %s %s' % (checkCommand, flags, mountPoint)
 writeLog(logfile, running)
 if test:
  writeLog(logfile, 'sp.check_output([checkCommand,flags,mountPoint])')
 else:
  writeLog(logfile, sp.check_output([checkCommand,flags,mountPoint]))

  

def rebootSystem(test, logfile):
 """
 Log the reboot step and close the logfile.
 If it's not a test, reboot the system.
 """
 writeLog(logfile, '* Rebooting System *')
 if test:
  closeLog(logfile)
 else:
  closeLog(logfile)
  sp.check_output('reboot')



if __name__=="__main__":
 test = 0
 help = "\n\
 SYNOCHK HELP!\n\
 Usage: [python] synochk [-h, -t]\n\
 \n\
         You will be prompted later for flags relating to the disk checking process.\n\
 \n\
         Synochk must be run as the root administrator from the root directory '/'.\n\
 \n\
 [Invalid Flags]  Bring up this help dialog\n\
 -t, -test        Run synochk in 'test' mode. Your file system will not be modified.\n\
                  Test mode will create a test.log file and log the results of the test.\n\
 -d               Display the disclaimer\n\n\
 "

 disclaimer = 'DISCLAIMER:\n\
 The author of this script is not responsible for any damage\n\
 caused during it\'s execution. There are many safeguards in place\n\
 to prevent harm, and let\'s be honest, if you\'re using this script\n\
 in an ssh shell, you probably know what you\'re doing.\n\
 Synology does not support any tampering in the terminal.\n\
 Neither does the author.\n\
 Play safe.\n\n\
 Peace in the middle east,\n\t\t\t- The Author\n\n'

 testMode = '\n\nTEST MODE ENABLED!\nYOUR SYSTEM WILL NOT BE MODIFIED.\n' 

 if len(argv)>2:
  exit(help)
 if argv[1] not in ['-t','-T','-test','-TEST']:
  exit(help)
 if argv[1] in ['-d','-D']:
  exit(disclaimer)
 if argv[1] in ['-t','-test']:
  print testMode
  print disclaimer
  test = 1

 test = 1
 run(test)

No comments:

Post a Comment