Advertisement
d3phoenix

Minecraft Initscript

Apr 5th, 2011
325
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 30.66 KB | None | 0 0
  1. Minecraft Initscript
  2. Copyright (C) 2011 D3Phoenix
  3.  
  4. For /r/minecraft:
  5.  
  6. This is the script that I use to simplify my personal minecraft server
  7. administration. I am releasing it as open source to the /r/minecraft community
  8. in the hopes that someone will find it useful, and perhaps be able to develop
  9. it further. I am releasing this under the GNU GPLv3. (See code below for license)
  10.  
  11. This started as a project to teach myself Python 3. It probably shows in the
  12. code that I am not a professional programmer, but hey, it works :)
  13. I had originally planned on developing this much further before release, but
  14. unfortunately I have found that I simply no longer have the time to work on it.
  15.  
  16. As such, rather than let the project go to waste, I have decided to release
  17. what I have so far, in full, to the community just in case it might be of use
  18. to one of you.
  19.  
  20. Thank you,
  21.  
  22. -- D3Phoenix
  23.  
  24.  
  25. Current feature list:
  26. * Runs minecraft servers automatically on boot w/o login
  27. * Runs minecraft servers w/o root permissions, yet still as daemons.
  28. * LSB initscript / update-rc.d compatible
  29. * Still allows you to directly access the minecraft server console(s)
  30. even though they are running in the background.
  31. * Enables automation via simple cron jobs.
  32. * Makes it easier to run servers on an Xless box, freeing up resources
  33. for MOAR MINECRAFT!
  34. * Stop / start / restart instance(s)
  35. * Backup worldfiles for instance(s)
  36. * Update cartographer-like maps with c10t
  37. * Update google-like maps with minecraft overviewer
  38. * Broadcast a multi-line MOTD from a text file
  39. * Execute arbitrary commands in the server consoles
  40. * Connect your current terminal to a running server's console for direct control
  41.  
  42.  
  43. Stuff I should probably implement but have no time to do so right now:
  44. * Automatic updates for server.jar's and utility apps
  45. * Some way to automagically clean up all of the MOTD / backup spam in the server logs
  46. * Make the google map / cartographer parts optional so that they present an error if not configured
  47. or if the utility apps are missing, rather than preventing the script from running
  48.  
  49.  
  50. Prerequisites:
  51. * Good working knowledge of Linux
  52. * Debian-based linux OS - preferably as slim as possible
  53. (Developed on a debian 6 box w/ only the core packages)
  54. * Required additional packages:
  55. - GNU screen
  56. - pngcrush
  57. - python 3.x
  58. * Latest c10t build (optional)
  59. * Latest minecraft overviewer build (optional)
  60.  
  61.  
  62.  
  63. Installation:
  64. =============
  65. - install prereqs above
  66. - create a folder structure similar to below
  67. - install utilities / minecraft server instances in the appropriate places
  68. - create an /etc/minecraft.conf accordingly
  69. - check your permissions! make sure everything is at owned by and at least
  70. rw- for the user that you configured your servers to run as!
  71. - copy the script into /etc/init.d/minecraft
  72. - copy the config file to /etc/minecraft.conf
  73. - test! make sure everything is working (start/stop, backups, etc)
  74. - Once you're satisfied, add any automated tasks you like to /etc/crontab
  75. - Finally, insert it into your LSB startup scripts with update-rc.d if you
  76. want it to automagically start/stop minecraft servers with the machine.
  77.  
  78.  
  79.  
  80. Example File Structure:
  81. =======================
  82. (Through editing of the script and/or the config file, this is pretty flexible)
  83.  
  84. /etc
  85. crontab (Use cron jobs to set up automatic scheduled tasks)
  86. minecraft.conf (The master configuration file)
  87. /init.d
  88. minecraft (The python script itself)
  89. /home
  90. /minecraft
  91. /mcserver
  92. /util
  93. /c10t
  94. c10t
  95. /mco
  96. gmap.py
  97. (other minecraft overviewer files)
  98. /www
  99. index.html (with links to mco and c10t output)
  100. /creative
  101. /map
  102. (output from c10t -- day.png, night.png, & caves.png)
  103. /googlemap
  104. (output from minecraft overviewer)
  105. /survival
  106. /map
  107. (output from c10t -- day.png, night.png, & caves.png)
  108. /googlemap
  109. (output from minecraft overviewer)
  110. /survival
  111. minecraft_server.jar
  112. server.log
  113. server.properties
  114. /mapcache
  115. (this is where minecraft overviewer stores its cache)
  116. /<worldname>
  117. (your minecraft world files)
  118. /creative
  119. CRAFTBUKKIT_SNAPSHOT.jar
  120. minecraft_server.jar
  121. server.log
  122. server.properties
  123. /mapcache
  124. (this is where minecraft overviewer stores its cache)
  125. /<worldname>
  126. (your minecraft world files)
  127.  
  128.  
  129. Note: The "minecraft" user's home folder is being used because that is the user
  130. that the script is configured to "demote" each server to when it starts them.
  131.  
  132. You can move these places around (especially the www publishing directory, I can
  133. definitely see wanting to move that somewhere else), however it is CRITICAL that
  134. these locations have at least rw- permissions for the user that the servers are
  135. running under! Don't be fooled by the fact that you start the script as root.
  136.  
  137.  
  138.  
  139.  
  140. Example crontab:
  141. ================
  142.  
  143. SHELL=/bin/sh
  144. PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
  145.  
  146. # Minute Hour DayOfMonth Month DayOfWeek User Command
  147.  
  148.  
  149. # Take backup snapshot of minecraft world (Every hour on the hour)
  150. 0 * * * * root service minecraft backup
  151.  
  152.  
  153. # Update minecraft server cartography maps (Every hour on 15m)
  154. 15 * * * * root service minecraft update-cartograph
  155.  
  156.  
  157. # Update minecraft server google maps (Every hour on 30m)
  158. 30 * * * * root service minecraft update-googlemap
  159.  
  160.  
  161. # Broadcast minecraft server MOTD messages (Every hour on 45m)
  162. 45 * * * * root service minecraft motd
  163.  
  164.  
  165. # Daily server restart -- warning and action (Every Thursday at 4AM)
  166. 0 5 * * * root service minecraft exec-all 'say Warning: Daily server restart in 5 minutes. \nsay The server takes about 5 minutes to restart. \nsay Please be patient when trying to log back in. \nsay Thank you! '
  167. 4 5 * * * root service minecraft exec-all 'say Warning: Daily server restart in 1 minute. \nsay The server takes about 5 minutes to restart. \nsay Please be patient when trying to log back in. \nsay Thank you! '
  168. 5 5 * * * root service minecraft restart
  169.  
  170.  
  171.  
  172.  
  173.  
  174.  
  175. Example minecraft.conf:
  176. =======================
  177.  
  178. # This section provides all global configuration parameters
  179.  
  180. [global]
  181. backup_max_age: 30
  182. gmap_app_path: /home/minecraft/mcserver/util/mco/
  183. carto_app_path: /home/minecraft/mcserver/util/c10t/
  184.  
  185.  
  186. # each of the following sections is a named server instance and all of its configuration parameters
  187. # For now, all options are required
  188.  
  189. [creative]
  190. auto_start: 1
  191. server_path: /home/minecraft/mcserver/creative/
  192. server_jar: craftbukkit-0.0.1-SNAPSHOT.jar
  193. user_id: minecraft
  194. max_ram: 1024
  195. world_name: hydrogen.creative
  196. backup_target: /data/minecraft/backups/creative/
  197. googlemap_path: /home/minecraft/mcserver/www/creative/googlemap/
  198. cartograph_path: /home/minecraft/mcserver/www/creative/map/
  199. motd_path: /home/minecraft/mcserver/motd.txt
  200.  
  201.  
  202. [survival]
  203. auto_start: 1
  204. server_path: /home/minecraft/mcserver/survival/
  205. server_jar: minecraft_server.jar
  206. user_id: minecraft
  207. max_ram: 1536
  208. world_name: hydrogen.survival
  209. backup_target: /data/minecraft/backups/survival/
  210. googlemap_path: /home/minecraft/mcserver/www/survival/googlemap/
  211. cartograph_path: /home/minecraft/mcserver/www/survival/map/
  212. motd_path: /home/minecraft/mcserver/motd.txt
  213.  
  214.  
  215.  
  216.  
  217.  
  218. The script:
  219. ===========
  220. #!/usr/bin/python3.1
  221.  
  222. ### BEGIN INIT INFO
  223. # Provides: minecraft
  224. # Required-Start: $all
  225. # Required-Stop: $all
  226. # Default-Start: 2 3 4 5
  227. # Default-Stop: 0 1 6
  228. # Short-Description: Starts and controls a minecraft-server instance.
  229. ### END INIT INFO
  230.  
  231.  
  232. # Minecraft Initscript
  233. # Copyright (C) 2011 d3phoenix
  234. #
  235. # This program is free software: you can redistribute it and/or modify
  236. # it under the terms of the GNU General Public License as published by
  237. # the Free Software Foundation, either version 3 of the License, or
  238. # (at your option) any later version.
  239. #
  240. # This program is distributed in the hope that it will be useful,
  241. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  242. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  243. # GNU General Public License for more details.
  244. #
  245. # You should have received a copy of the GNU General Public License
  246. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  247.  
  248. import os
  249. import datetime
  250. import configparser
  251. from time import sleep
  252. from sys import argv, exit
  253. now = datetime.datetime.now()
  254. cp = configparser.RawConfigParser()
  255. scriptname = os.path.basename(argv[0])
  256.  
  257. DEBUG = 0
  258. CREEPER = 1
  259.  
  260. def main():
  261. # Only root is allowed to run this script, or else the way it manipulates screen won't work.
  262. if not os.getuid()==0:
  263. usage('root privileges are required to run this script.')
  264.  
  265. else:
  266. # Parse the configuration file
  267. result = cp.read('/etc/minecraft.conf')
  268. if len(result) == 0:
  269. fatal('missing or invalid /etc/minecraft.conf')
  270.  
  271. # Read in the global settings
  272. try:
  273. backup_max_age = cp.get('global','backup_max_age')
  274. gmap_app_path = cp.get('global','gmap_app_path')
  275. carto_app_path = cp.get('global','carto_app_path')
  276. except(NoSectionError, NoOptionError):
  277. fatal('missing or invalid /etc/minecraft.conf')
  278.  
  279. debug('read settings from config file: backup_max_age=' + backup_max_age + ' gmap_app_path=' + gmap_app_path + ' carto_app_path=' + carto_app_path)
  280.  
  281. # Get the list of instances (remove the 'global' section we already parsed)
  282. try:
  283. instance_list = cp.sections()
  284. instance_list.remove('global')
  285. except ValueError:
  286. fatal('missing or invalid /etc/minecraft.conf')
  287.  
  288. debug('retrieved instance list: ' + ' '.join(instance_list))
  289.  
  290. debug('retreived command line args: ' + ' '.join(argv))
  291.  
  292. # Begin processing the arguments; start with the first.
  293. if len(argv) >= 2:
  294. action = argv[1].lower()
  295.  
  296. if action in ['help', '--help', 'usage']:
  297. usage()
  298.  
  299. # There are two variants of the special 'exec' command:
  300. # minecraft exec <instance-name> <execute-string>
  301. # minecraft exec-all <execute-string>
  302.  
  303. # The first executes against a named instance,
  304. # The second executes sequentially against them all.
  305.  
  306. # Having them as distinct functions removes ambiguity in the parameters as far as whether you are specifying
  307. # an instance name or part of the command you want to execute.
  308.  
  309. elif action in ['exec-all']:
  310. debug('selected action: exec-all')
  311. if len(argv) >= 3:
  312. execute_string = ' '.join(argv[2:])
  313. for instance_name in instance_list:
  314. report(instance_name + ': executing "' + execute_string + '"...')
  315. send_to_screen(instance_name, execute_string)
  316. else:
  317. usage('missing required parameter')
  318.  
  319. elif action in ['exec']:
  320. debug('selected action: exec')
  321. if len(argv) >= 3:
  322. if argv[2].lower() in instance_list:
  323. # The second argument was a valid instance, continue...
  324. if len(argv) >= 4:
  325. instance_name = argv[2].lower()
  326. execute_string = ' '.join(argv[3:])
  327. report(instance_name + ': executing "' + execute_string + '"...')
  328. send_to_screen(instance_name, execute_string)
  329. else:
  330. usage('missing required parameter')
  331. else:
  332. usage('missing required parameter')
  333.  
  334. # The 'console' action is special in that it requires you to specify the instance name or it will not work.
  335. elif action in ['console']:
  336. debug('selected action: console')
  337. if len(argv) >= 3:
  338. instance_name = argv[2].lower()
  339. if not instance_name in instance_list:
  340. fatal('instance "' + instance_name + '" not configured. Please check /etc/minecraft.conf (World names are case-sensitive)')
  341. else:
  342. if is_instance_running(instance_name):
  343. send_to_screen(instance_name, '***** Connected to server console. Press CTRL+A, then D to leave. *****')
  344. os.system('screen -r ' + instance_name)
  345. else:
  346. usage('missing required parameter')
  347.  
  348. # The 'status' action is special in that it does not accept any arguments. It lists all known server instances regardless of the command line.
  349. elif action in ['status']:
  350. print()
  351. print("Server Instance: Status")
  352. print("=============================================")
  353. for instance in instance_list:
  354. if is_instance_running(instance):
  355. instance_status = "Running"
  356. else:
  357. instance_status = "Stopped"
  358. print(" " + instance + ": " + instance_status)
  359. print("=============================================")
  360. print()
  361.  
  362.  
  363. # All remaining valid actions accept either one or zero arguments based on whether the user wants it to run against a selected instance or all instances.
  364. else:
  365. debug('selected action: other (text:' + action + ')')
  366. # Determine whether we are running against a single instance or all of them, and return a list for the loop accordingly
  367. if len(argv) >= 3:
  368. if argv[2].lower() in instance_list:
  369. selected_instances = [argv[2].lower()]
  370. else:
  371. fatal('instance "' + argv[2].lower() + '" not configured in /etc/minecraft.conf')
  372. else:
  373. selected_instances = instance_list
  374.  
  375. debug('selected instances: ' + ' '.join(selected_instances))
  376.  
  377. for instance_name in selected_instances:
  378. debug('processing instance: ' + instance_name)
  379.  
  380. # Read in the instance-specific variables for each iteration of the loop
  381. auto_start = cp.get(instance_name, 'auto_start')
  382. server_path = cp.get(instance_name, 'server_path')
  383. server_jar = cp.get(instance_name, 'server_jar')
  384. user_id = cp.get(instance_name, 'user_id')
  385. max_ram = cp.get(instance_name, 'max_ram')
  386. world_name = cp.get(instance_name, 'world_name')
  387. backup_target = cp.get(instance_name, 'backup_target')
  388. googlemap_path = cp.get(instance_name, 'googlemap_path')
  389. cartograph_path = cp.get(instance_name, 'cartograph_path')
  390. motd_path = cp.get(instance_name, 'motd_path')
  391.  
  392. if action in ['start']:
  393. # Only start a server if 'auto_start' is true, or the server name was given explicitly
  394. if (auto_start.lower() in ['1', 'true', 'yes']) or (len(selected_instances) == 1):
  395. start_instance(instance_name, user_id, server_path, server_jar, max_ram)
  396.  
  397. elif action in ['stop']:
  398. stop_instance(instance_name)
  399.  
  400. elif action in ['stop-now', 'force-stop']:
  401. stop_instance(instance_name, True)
  402.  
  403. elif action in ['restart', 'try-restart', 'reload']:
  404. restart_instance(instance_name, server_path, server_jar, max_ram)
  405.  
  406. elif action in ['restart-now', 'force-restart', 'force-reload']:
  407. restart_instance(instance_name, server_path, server_jar, max_ram, True)
  408.  
  409. elif action in ['update-backup', 'backup']:
  410. update_backup(instance_name, server_path, world_name, backup_target, backup_max_age)
  411.  
  412. elif action in ['update-cartograph']:
  413. update_cartograph(user_id, instance_name, server_path, world_name, carto_app_path, cartograph_path)
  414.  
  415. elif action in ['update-googlemap']:
  416. update_googlemap(user_id, instance_name, server_path, world_name, gmap_app_path, googlemap_path)
  417.  
  418. elif action in ['motd']:
  419. broadcast_motd(instance_name, motd_path)
  420.  
  421. else:
  422. # Unrecognized command? Time to troll a bit :)
  423. if CREEPER:
  424. creeper()
  425. usage('("' + action + '" is not a supported action)')
  426.  
  427. else:
  428. usage('missing required parameter')
  429.  
  430.  
  431. def start_instance(instance_name, user_id, server_path, server_jar, max_ram):
  432. debug('start_instance called')
  433. if is_instance_running(instance_name):
  434. report(instance_name + ': already started.')
  435.  
  436. else:
  437. report(instance_name + ': spawning & configuring screen daemon...')
  438. # Spawn a new screen daemon containing a bash shell. We will manipulate this
  439. # to drop permissions and start the server. The screen itself runs as root.
  440. os.system('screen -dmS ' + instance_name + ' /bin/bash')
  441. # Wait for shell to initialize
  442. sleep(3)
  443. # Because this is an initscript, we have scary root permissions inside our spawned shell.
  444. # DO NOT WANT! The server shouldn't these perms, therefore, su - to a more appropriate account:
  445. send_to_screen(instance_name, 'su - ' + user_id)
  446. # This next line is an ugly hack to move the terminal to a new /dev/pts/* attachment. When we su -,
  447. # the terminal's /dev/pts/* owner is not updated, so screen will not have appropriate rights to interact with it properly.
  448. # Firing off the script command works around this, by forcing us into a new /dev/pts/* with correct ownership.
  449. send_to_screen(instance_name, 'script /dev/null')
  450. # Move to the correct working directory
  451. send_to_screen(instance_name, 'cd ' + server_path)
  452. # Finally, we can start the server!
  453. report(instance_name + ': starting server, please wait (30 sec)...')
  454. send_to_screen(instance_name, 'java -Xms' + max_ram + 'm -Xmx' + max_ram + 'M -jar ' + server_jar + ' nogui')
  455. # Wait a bit longer -- it takes the server a while to start
  456. sleep(30)
  457. # Report our status
  458. report(instance_name + ': startup complete.')
  459.  
  460.  
  461.  
  462. def stop_instance(instance_name, stop_now=False):
  463. debug('stop_instance called')
  464. if is_instance_running(instance_name):
  465. send_to_screen(instance_name, 'say *** SERVER SHUTDOWN REQUESTED FROM CONSOLE ***')
  466. if not stop_now:
  467. report(instance_name + ': informing users of shutdown. Please wait (30 sec)...')
  468. send_to_screen(instance_name, 'say Server will go offline in 30 seconds. Please log out.')
  469. sleep(12)
  470. report(instance_name + ': informing users of shutdown. Please wait (15 sec)...')
  471. send_to_screen(instance_name, 'say Server will go offline in 15 seconds. Please log out.')
  472. sleep(7)
  473. send_to_screen(instance_name, 'say Server will go offline in 5 seconds. Goodbye!')
  474. sleep(2)
  475. report(instance_name + ': stopping server...')
  476. send_to_screen(instance_name, 'stop')
  477. sleep(10)
  478. report(instance_name + ': cleaning up...')
  479. # Once the server has stopped, we will need to exit from our su - session...
  480. send_to_screen(instance_name, 'exit')
  481. # Close out of the script session...
  482. send_to_screen(instance_name, 'exit')
  483. # And finally, close out of the original shell
  484. send_to_screen(instance_name, 'exit')
  485. # At this point the screen should detach and die automatically.
  486. report(instance_name + ': stop complete.')
  487.  
  488.  
  489.  
  490. def restart_instance(instance_name, server_path, server_jar, max_ram, restart_now=False):
  491. debug('restart_instance called')
  492. if is_instance_running(instance_name):
  493. send_to_screen(instance_name, 'say *** SERVER RESTART REQUESTED FROM CONSOLE ***')
  494. if not restart_now:
  495. report(instance_name + ': informing users of restart. Please wait (30 sec)...')
  496. send_to_screen(instance_name, 'say Server will restart in 30 seconds. Please log out.')
  497. sleep(12)
  498. report(instance_name + ': informing users of restart. Please wait (15 sec)...')
  499. send_to_screen(instance_name, 'say Server will restart in 15 seconds. Please log out.')
  500. sleep(7)
  501. send_to_screen(instance_name, 'say Server will restart in 5 seconds. Goodbye!')
  502. sleep(2)
  503. send_to_screen(instance_name, 'say Server restarting...')
  504. report(instance_name + ': stopping server...')
  505. send_to_screen(instance_name, 'stop')
  506. sleep(10)
  507. report(instance_name + ': restarting server...')
  508. send_to_screen(instance_name, 'java -Xms' + max_ram + 'm -Xmx' + max_ram + 'M -jar ' + server_jar + ' nogui')
  509. sleep(10)
  510. report(instance_name + ': restart cycle complete.')
  511.  
  512.  
  513.  
  514. def update_backup(instance_name, server_path, world_name, backup_target, backup_max_age):
  515. debug('update_backup called')
  516. source = '"' + server_path + world_name + '/"'
  517. destination_path = '"' + backup_target + world_name + '.backup.'
  518. destination = destination_path + now.strftime('%Y.%m.%d-%H.%M') + '.tar.bz2"'
  519. if is_instance_running(instance_name):
  520. report(instance_name + ': informing users of update. Please wait (15 sec).')
  521. send_to_screen(instance_name, 'say World backup starting in 15 seconds...')
  522. sleep(12)
  523. send_to_screen(instance_name, 'say Starting backup...')
  524. send_to_screen(instance_name, 'save-off')
  525. send_to_screen(instance_name, 'save-all')
  526. sleep(5)
  527. report(instance_name + ': backing up world files to ' + destination + '; please wait...')
  528. os.system('mkdir -p "' + backup_target + '"')
  529. os.system('tar -cjf ' + destination + ' ' + source)
  530. os.system('find ' + destination_path + '"* -mtime +' + backup_max_age + ' -exec rm {} \;')
  531. if is_instance_running(instance_name):
  532. send_to_screen(instance_name, 'save-on')
  533. send_to_screen(instance_name, 'say Backup complete.')
  534. report(instance_name + ': backup complete.')
  535.  
  536.  
  537.  
  538. def update_googlemap(user_id, instance_name, server_path, world_name, gmap_app_path, googlemap_path):
  539. debug('update_googlemap called')
  540. report(instance_name + ': updating google map; please wait...')
  541. if is_instance_running(instance_name):
  542. send_to_screen(instance_name, 'say Updating google map...')
  543. user_exec(user_id, 'mkdir -p "' + googlemap_path + '"')
  544. user_exec(user_id, 'nice ' + gmap_app_path + 'gmap.py -v -p 1 --cachedir=' + server_path + 'mapcache/ ' + server_path + world_name + ' ' + googlemap_path)
  545. if is_instance_running(instance_name):
  546. send_to_screen(instance_name, 'say Google map update finished.')
  547. report(instance_name + ': google map update complete.')
  548.  
  549.  
  550.  
  551. def update_cartograph(user_id, instance_name, server_path, world_name, carto_app_path, cartograph_path):
  552. debug('update_cartograph called')
  553. print(instance_name + ': updating cartography maps; please wait...')
  554. if is_instance_running(instance_name):
  555. send_to_screen(instance_name, 'say Updating cartography maps...')
  556. user_exec(user_id, 'nice ' + carto_app_path + 'c10t -m 1 -w ' + server_path + world_name + ' -o ' + cartograph_path + 'day.pngcache')
  557. user_exec(user_id, 'nice ' + carto_app_path + 'c10t -n -m 1 -w ' + server_path + world_name + ' -o ' + cartograph_path + 'night.pngcache')
  558. user_exec(user_id, 'nice ' + carto_app_path + 'c10t -c -m 1 -w ' + server_path + world_name + ' -o ' + cartograph_path + 'caves.pngcache')
  559. user_exec(user_id, 'nice pngcrush ' + cartograph_path + 'day.pngcache ' + cartograph_path + 'day.png')
  560. user_exec(user_id, 'nice pngcrush ' + cartograph_path + 'night.pngcache ' + cartograph_path + 'night.png')
  561. user_exec(user_id, 'nice pngcrush ' + cartograph_path + 'caves.pngcache ' + cartograph_path + 'caves.png')
  562. user_exec(user_id, 'rm ' + cartograph_path + '*.pngcache')
  563. if is_instance_running(instance_name):
  564. send_to_screen(instance_name, 'say Cartography update finished.')
  565. report(instance_name + ': cartography update complete.')
  566.  
  567.  
  568. def broadcast_motd(instance_name, motd_path):
  569. debug('broadcast_motd called')
  570. if is_instance_running(instance_name):
  571. report(instance_name + ': broadcasting motd from file: ' + motd_path)
  572. debug('reading motd file')
  573. motdfile = open(motd_path, 'r')
  574. motdlines = motdfile.readlines()
  575. for m in motdlines:
  576. debug('parsing motd line: ' + m)
  577. if not m[0] in ['#', ';']:
  578. debug('line does not start with comment')
  579. send_to_screen(instance_name, 'say ' + m.strip())
  580. else:
  581. debug('line started with comment, skipping')
  582. report(instance_name + ': motd broadcast complete.')
  583.  
  584.  
  585. def send_to_screen(instance_name, command):
  586. debug('send_to_screen called; instance=' + instance_name + ', command=' + command)
  587. if is_instance_running(instance_name):
  588. # The nested quotes to get this working are ugly as the nether.
  589. # Made a function out of it so that I didn't go insane.
  590. execute_string = 'screen -S ' + instance_name + ' -p 0 -X stuff "`printf "' + command + '\\r"`"'
  591. os.system(execute_string)
  592. # Always wait a few seconds between commands, or things tend to glitch out on multicore systems (race condition? limitation of screen?)
  593. # Three seems to be the fastest setting that works reliably.
  594. sleep(3)
  595.  
  596.  
  597. def is_instance_running(instance_name):
  598. debug('is_instance_running called against: ' + instance_name)
  599. # Assume no screen session exists
  600. screen_exists = False
  601. # Search for a matching screen session
  602. for screen in os.listdir('/var/run/screen/S-root'):
  603. if instance_name in screen:
  604. # If we found a match, then the screen is running.
  605. screen_exists = True
  606. break
  607. # Return what we found
  608. return screen_exists
  609.  
  610.  
  611. def user_exec(user, execute_string):
  612. su_execute_string = "su " + user + " -l -c '" + execute_string + "'"
  613. debug('Executing: ' + su_execute_string)
  614. os.system(su_execute_string)
  615.  
  616.  
  617. def report(msg):
  618. print('[' + scriptname + ']: ' + msg)
  619.  
  620.  
  621. def debug(msg):
  622. if DEBUG:
  623. print('[' + scriptname + ']: ' + msg)
  624.  
  625.  
  626. def fatal(msg):
  627. print('[' + scriptname + ']: Fatal - ' + msg)
  628. print()
  629. exit(1)
  630.  
  631.  
  632. def usage(msg = ''):
  633. if len(msg):
  634. print('[' + scriptname + ']: Error - ' + msg)
  635. print()
  636. print('Usage: ' + scriptname + ' <action> [instance_name]')
  637. print()
  638. print('<action>')
  639. print(' start start instance(s)')
  640. print(' stop stop instance(s) with user warning period')
  641. print(' stop-now stop instance(s) immediately')
  642. print(' restart restart instance(s) with user warning period')
  643. print(' restart-now restart instance(s) immediately')
  644. print(' backup back up world files for the selected instance(s)')
  645. print(' update-cartograph update the cartography (run c10t)')
  646. print(' update-googlemap update the google map (run minecraft-overviewer)')
  647. print(' motd send the preconfigured motd text file to chat')
  648. print(" * exec <command> execute <command> in the selected instance")
  649. print(" * console <instance> connect to selected instance's console")
  650. print()
  651. print('[instance_name]')
  652. print(" Optional except for commandes noted with '*' above. ")
  653. print()
  654. print(' When specified, if it matches an instance configured in')
  655. print(' /etc/minecraft.conf, then the selected action will apply')
  656. print(' only to that instance. If there is no match, the script')
  657. print(' will exit with an error code.')
  658. print()
  659. print(' If [instance_name] is not specified, then the script will')
  660. print(' execute the command against each server in series.')
  661. print()
  662. exit(1)
  663.  
  664.  
  665.  
  666. def creeper():
  667. print()
  668. print('SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS')
  669. print('SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS')
  670. print('SSS SSSSSSSSSS SSS')
  671. print('SSS SSSSSSSSSS SSS')
  672. print('SSS SSSSSSSSSS SSS')
  673. print('SSS SSSSSSSSSS SSS')
  674. print('SSS SSSSSSSSSS SSS')
  675. print('SSSSSSSSSSSSS SSSSSSSSSSSSS')
  676. print('SSSSSSSSSSSSS SSSSSSSSSSSSS')
  677. print('SSSSSSSSSSSSS SSSSSSSSSSSSS')
  678. print('SSSSSSSS SSSSSSSS')
  679. print('SSSSSSSS SSSSSSSS')
  680. print('SSSSSSSS SSSSSSSS')
  681. print('SSSSSSSS SSSSSSSS')
  682. print('SSSSSSSS SSSSSSSS')
  683. print('SSSSSSSS SSSSSSSSSS SSSSSSSS')
  684. print('SSSSSSSS SSSSSSSSSS SSSSSSSS')
  685. print('SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS')
  686. print('SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS')
  687. print()
  688.  
  689.  
  690. ####################################
  691. if __name__ == "__main__":
  692. main()
  693. ####################################
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement