Comment puis-je démoniser un script arbitraire dans unix?

j'aimerais un daemoniseur qui peut transformer un script ou une commande arbitraire et générique en un daemon .

il y a deux cas courants que j'aimerais traiter:

  1. j'ai un script qui devrait tourner pour toujours. Si elle meurt jamais (ou redémarrage), le redémarrer. Ne laissez jamais deux copies s'exécuter en même temps (détectez si une copie est déjà en cours d'exécution et ne la lancez pas dans ce cas).

  2. j'ai un script simple ou une commande en ligne de commande que je voudrais continuer à exécuter à répétition pour toujours (avec une courte pause entre les passages). Encore une fois, ne permettez pas que deux copies du script soient lancées en même temps.

bien sûr, il est trivial d'écrire une boucle "while (true)" autour du script dans le cas 2 et ensuite appliquer une solution pour le cas 1, mais une solution plus générale sera juste résoudre le cas 2 directement car cela s'applique à la script dans le cas 1 aussi bien (vous pouvez juste vouloir une pause plus courte ou pas si le script n'est pas destiné à mourir un jour (bien sûr si le script vraiment fait jamais mourir alors la pause n'a pas réellement d'importance)).

notez que la solution ne devrait pas impliquer, par exemple, l'ajout d'un code de verrouillage de fichier ou d'un enregistrement PID aux scripts existants.

plus précisément, je voudrais un programme "daemonize" que je peux exécuter comme

% daemonize myscript arg1 arg2

ou, par exemple,

% daemonize 'echo `date` >> /tmp/times.txt'

qui conserverait une liste croissante de dates annexées aux horaires.txt. (Notez que si l'argument(S) à daemonize est un script qui tourne pour toujours comme dans le cas 1 ci-dessus, alors daemonize fera quand même la bonne chose, la redémarrant si nécessaire.), J'ai pu alors mettre une commande comme ci-dessus dans mon .login et / ou cron il Toutes les heures ou minutieusement (en fonction de la façon dont je m'inquiétais de mourir inopinément).

NB: Le daemonize script devra se souvenir de la chaîne de commande qu'il est en train de démonter de sorte que si la même chaîne de commande est à nouveau démonisée, il ne lance pas de deuxième copie.

en outre, la solution devrait idéalement fonctionner à la fois sur OS X et linux, mais les solutions pour l'un ou l'autre sont les bienvenues.

EDIT: c'est très bien si vous devez l'invoquer avec sudo daemonize myscript myargs .

(si je pense à tout cela mal ou il ya des solutions partielles rapide-et-sale, j'aimerais d'entendre que trop.)


PS: Dans le cas où c'est utile, voici une question similaire, spécifique à python.

Et ce réponse à une question similaire a ce qui semble être un utile idiome pour un rapide et sale de diabolisation de l'arbitraire d'un script:

89
demandé sur Community 2009-02-08 09:11:08

12 réponses

vous pouvez démoniser n'importe quel exécutable sous Unix en utilisant nohup et l'opérateur&:

nohup yourScript.sh script args&

la commande nohup vous permet de fermer votre session shell sans qu'elle ne tue votre script, alors que le & place votre script en arrière-plan pour que vous obteniez une invite shell pour continuer votre session. Le seul problème mineur avec cela est standard out et l'erreur standard tous les deux sont envoyés à .- nohup.donc si vous démarrez plusieurs scripts dans ce manoir, leur sortie sera entrelacer. Une meilleure commande serait:

nohup yourScript.sh script args >script.out 2>script.error&

cela enverra standard out au fichier de votre choix et standard error à un fichier différent de votre choix. Si vous voulez utiliser un seul fichier pour standard out et standard error, vous pouvez utiliser ceci:

nohup yourScript.sh script args >script.out 2>&1 &

2>&1 indique au shell pour rediriger l'erreur standard (descripteur de fichier 2) pour le même fichier standard (descripteur de fichier 1).

Pour exécuter une commande une seule fois et redémarrez s'il meurt vous pouvez utiliser ce script:

#!/bin/bash

if [[ $# < 1 ]]; then
    echo "Name of pid file not given."
    exit
fi

# Get the pid file's name.
PIDFILE=
shift

if [[ $# < 1 ]]; then
    echo "No command given."
    exit
fi

echo "Checking pid in file $PIDFILE."

#Check to see if process running.
PID=$(cat $PIDFILE 2>/dev/null)
if [[ $? = 0 ]]; then
    ps -p $PID >/dev/null 2>&1
    if [[ $? = 0 ]]; then
        echo "Command  already running."
        exit
    fi
fi

# Write our pid to file.
echo $$ >$PIDFILE

# Get command.
COMMAND=
shift

# Run command until we're killed.
while true; do
    $COMMAND "$@"
    sleep 10 # if command dies immediately, don't go into un-ctrl-c-able loop
done

Le premier argument est le nom du fichier pid à utiliser. Le deuxième argument est la commande. Et tous les autres arguments sont les arguments de la commande.

si vous appelez ce script restart.sh c'est comme ça qu'on l'appelle:

nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 &
85
répondu Robert Menteer 2012-05-02 15:53:54

Je m'excuse pour la longue réponse (s'il vous plaît voir les commentaires sur la façon dont ma réponse cloue la spécification). J'essaie d'être compréhensive, pour que tu aies une longueur d'avance. :- )

Si vous êtes en mesure d'installer des programmes ("root"), et sont prêts à faire un temps de travail sur le terrain pour configurer votre script pour le démon de l'exécution (c'est à dire, plus que de simplement en spécifiant les arguments de ligne de commande à exécuter sur la ligne de commande, mais seulement besoin d'être fait une fois par service), J'ai une façon plus robuste.

Il s'agit d'utiliser daemontools . Le reste du post décrit comment mettre en place des services à l'aide de daemontools.

installation initiale

  1. suivre les instructions dans comment installer daemontools . Certaines distributions (par exemple, Debian, Ubuntu) ont déjà des paquets pour cela, utilisez-les simplement.
  2. créer un répertoire appelé /service . L'installateur aurait déjà dû le faire, mais il suffit de vérifier, ou si l'installation est manuelle. Si vous n'aimez pas cet emplacement, vous pouvez le changer dans votre script svscanboot , bien que la plupart des utilisateurs de daemontools soient habitués à utiliser /service et seront confus si vous ne l'utilisez pas.
  3. si vous utilisez Ubuntu ou une autre distribution qui n'utilise pas la norme init (i.e., n'utilise pas /etc/inittab ), vous devrez utiliser le inittab préinstallé comme un base pour l'arrangement svscanboot à appeler par init . Ce n'est pas difficile, mais vous devez savoir comment configurer le init que votre système d'exploitation utilise. svscanboot est un script qui appelle svscan , qui fait le travail principal de la recherche de services; il est appelé de init so init s'arrangera pour le redémarrer s'il meurt pour une raison quelconque.

Par-service d'installation de

  1. chaque service a besoin d'un service directory , qui stocke des informations administratives sur le service. Vous pouvez également faire un emplacement pour héberger ces annuaires de service afin qu'ils soient tous en un seul endroit; habituellement , j'utilise /var/lib/svscan , mais tout nouvel emplacement sera parfait.
  2. j'utilise habituellement un script pour configurer le répertoire de service, pour sauver beaucoup de travail manuel répétitif. par exemple,

    sudo mkservice -d /var/lib/svscan/some-service-name -l -u user -L loguser "command line here"
    

    some-service-name est le nom vous voulez donner votre service, user est à l'utilisateur d'exécuter ce service aussi, et loguser est à l'utilisateur d'exécuter le journal comme. (L'enregistrement est expliqué en juste un peu.)

  3. votre service doit exécuter au premier plan . Si votre arrière-plan de programme par défaut, mais a une option pour désactiver cela, alors faites le. Si votre arrière-plan de programme sans un moyen de le désactiver, lire sur fghack , bien que cela arrive à un compromis: vous ne pouvez plus contrôler le programme en utilisant svc .
  4. éditez le script run pour vous assurer qu'il fait ce que vous voulez. Vous devrez peut-être placer un appel sleep en haut, si vous vous attendez à ce que votre service se termine fréquemment.
  5. lorsque tout est réglé correctement, créez un lien symbolique dans /service pointant vers votre répertoire de service. (Ne mettez pas les annuaires de service directement dans /service ; it il est donc plus difficile de retirer le service de la montre svscan .)

Journalisation

  1. la méthode de journalisation daemontools consiste à faire écrire les messages de journalisation par le service à la sortie standard (ou erreur standard, si vous utilisez des scripts générés par mkservice ); svscan s'occupe d'envoyer les messages de journalisation au service de journalisation.
  2. le service de journalisation prend les messages de journal de l'entrée standard. Le le script de service de journalisation généré par mkservice va créer des fichiers de log auto-tournés et horodatés dans le répertoire log/main . Le fichier journal actuel s'appelle current .
  3. Le service de journalisation peut être démarré et arrêté indépendamment du service.
  4. Tuyauterie les fichiers journaux à travers tai64nlocal sera de traduire les horodateurs dans un format lisible par l'homme. (TAI64N est un timestamp atomique 64 bits avec un compte de nanosecondes.)

services de contrôle

  1. utilisez svstat pour obtenir le statut d'un service. Notez que le service de journalisation est indépendant et a son propre statut.
  2. vous contrôlez votre service (démarrage, arrêt, redémarrage, etc.) en utilisant svc . Par exemple, pour redémarrer votre service, utilisez svc -t /service/some-service-name ; -t signifie " envoyer SIGTERM ".
  3. autres signaux disponibles: -h ( SIGHUP ), -a ( SIGALRM ), -1 ( SIGUSR1 ), -2 ( SIGUSR2 ), et -k ( SIGKILL ).
  4. pour supprimer le service, utilisez -d . Vous pouvez également empêcher un service de démarrer automatiquement au démarrage en créant un fichier nommé down dans le répertoire service.
  5. pour démarrer le service, utilisez -u . Ce n'est pas nécessaire, sauf si vous l'avez déjà descendu (ou configuré pour ne pas démarrer automatiquement).
  6. pour demander au superviseur de quitter, utilisez -x ; habituellement utilisé avec -d pour mettre fin au service aussi. C'est la façon habituelle de permettre la suppression d'un service, mais vous devez débloquer le service de /service d'abord, sinon svscan redémarrera le superviseur. De plus, si vous avez créé votre service avec un service de journalisation ( mkservice -l ), n'oubliez pas de quitter également le superviseur de journalisation (par exemple, svc -dx /var/lib/svscan/some-service-name/log ) avant de supprimer le répertoire de service.

résumé

Pour:

  1. daemontools fournit un moyen à l'épreuve des balles pour créer et gérer des services. Je l'utilise pour mes serveurs, et je le recommande fortement.
  2. son système de journalisation est très robuste, tout comme le service Auto-restart facility.
  3. Parce qu'il commence les services avec un script shell que vous écrivez/accordez, vous pouvez adapter votre service comme vous voulez.
  4. puissants outils de contrôle de service: vous pouvez envoyer la plupart des signaux à un service, et peut élever des services de façon fiable.
  5. vos services sont garantis un environnement d'exécution propre: ils seront exécutés avec le même environnement, les limites de processus, etc., comme ce que init fournit.

Cons:

  1. Chaque service prend un peu de configuration. Heureusement, cela n'a besoin d'être fait qu'une fois par service.
  2. Services doit être configuré pour s'exécuter en arrière-plan. En outre, pour de meilleurs résultats, ils devraient être configurés pour se connecter à la sortie standard/erreur standard, plutôt que syslog ou d'autres fichiers.
  3. courbe D'apprentissage raide si vous êtes nouveau à la façon de faire les choses daemontools. Vous devez redémarrer les services en utilisant svc , et ne peut pas exécutez les scripts directement (puisqu'ils ne seraient alors pas sous le contrôle du superviseur).
  4. Beaucoup de ménage fichiers, et beaucoup de ménage processus. Chaque service a besoin de son propre répertoire de services, et chaque service utilise un processus de supervision pour redémarrer automatiquement le service s'il meurt. (Si vous avez beaucoup de services, vous verrez lots de supervise processus dans votre table de processus.)

In l'équilibre, je pense que daemontools est un excellent système pour vos besoins. Je suis ouvert à toute question sur la façon de le mettre en place et de le maintenir.

31
répondu Chris Jester-Young 2010-03-18 04:57:20

vous devriez jeter un oeil à daemonize . Il permet de détecter la seconde copie (mais il utilise un mécanisme de verrouillage des fichiers). Il fonctionne également sur différentes distributions UNIX et Linux.

si vous devez démarrer automatiquement votre application en tant que démon, alors vous devez créer le script init approprié.

vous pouvez utiliser le modèle suivant:

#!/bin/sh
#
# mydaemon     This shell script takes care of starting and stopping
#               the <mydaemon>
#

# Source function library
. /etc/rc.d/init.d/functions


# Do preliminary checks here, if any
#### START of preliminary checks #########


##### END of preliminary checks #######


# Handle manual control parameters like start, stop, status, restart, etc.

case "" in
  start)
    # Start daemons.

    echo -n $"Starting <mydaemon> daemon: "
    echo
    daemon <mydaemon>
    echo
    ;;

  stop)
    # Stop daemons.
    echo -n $"Shutting down <mydaemon>: "
    killproc <mydaemon>
    echo

    # Do clean-up works here like removing pid files from /var/run, etc.
    ;;
  status)
    status <mydaemon>

    ;;
  restart)
    "151900920" stop
    "151900920" start
    ;;

  *)
    echo $"Usage: "151900920" {start|stop|status|restart}"
    exit 1
esac

exit 0
12
répondu uthark 2010-03-17 02:52:10

je pense que vous pouvez essayer start-stop-daemon(8) . Consultez les scripts dans /etc/init.d dans N'importe quelle distribution Linux pour des exemples. Il peut trouver des processus lancés par ligne de commande invoquée ou fichier PID, donc il correspond à toutes vos exigences sauf être un chien de garde pour votre script. Mais vous pouvez toujours lancer un autre script daemon watchdog qui redémarre simplement votre script si nécessaire.

11
répondu Alex B 2009-02-09 01:25:44

comme alternative à la commande déjà mentionnée daemonize et daemontools , il y a la commande démon du paquet libslack.

daemon est tout à fait configurable et ne se soucie de tous les trucs de démon fastidieux tels que le redémarrage automatique, la journalisation ou la manipulation de pidfile.

7
répondu jefz 2012-06-09 14:46:06

si vous utilisez OS X spécifiquement, je vous suggère de jeter un oeil au fonctionnement de launchd. Il vérifiera automatiquement que votre script est en cours d'exécution et le relancera si nécessaire. Il comprend également toutes sortes de fonctionnalités de planification, etc. Elle doit satisfaire à la fois aux exigences 1 et 2.

pour vous assurer qu'une seule copie de votre script peut être exécutée, vous devez utiliser un fichier PID. Généralement j'écris un fichier /var/run/.pid qui contient un PID de l'instance courante. si le fichier existe lorsque le programme tourne, il vérifie si le PID dans le fichier est réellement en cours d'exécution (le programme peut avoir crashé ou oublié de supprimer le fichier PID). Si elle l'est, abandonner. Si ce n'est pas le cas, exécutez et écrasez le fichier PID.

5
répondu Kamil Kisiel 2009-02-08 08:33:25

Daemontools ( http://cr.yp.to/daemontools.html ) est un ensemble d'Utilités assez hard-core utilisé pour faire cela, écrit par DJ bernstein. J'ai utilisé cela avec un certain succès. L'ennuyeux, c'est que aucun des scripts de retour les résultats visibles lorsque vous les exécutez - juste invisible codes de retour. Mais une fois lancé, il est à l'épreuve des balles.

5
répondu fastmultiplication 2010-03-17 05:11:50

de la Première à obtenir createDaemon() à partir de http://code.activestate.com/recipes/278731/

puis le code principal:

import subprocess
import time

createDaemon()

while True:
    subprocess.call(" ".join(sys.argv[1:]),shell=True)
    time.sleep(10)
3
répondu Douglas Leeder 2009-02-08 15:37:41

il S'agit D'une version de travail complète avec un exemple que vous pouvez copier dans un répertoire vide et essayer (après avoir installé les dépendances CPAN, qui sont Getopt::Long , fichier::Spec , fichier::: Pid , et IPC:: système:: Simple -- tous assez standard et sont fortement recommandés pour tout hacker: vous pouvez les installer tous en même temps avec cpan <modulename> <modulename> ... ).


keepAlive.pl:

#!/usr/bin/perl

# Usage:
# 1. put this in your crontab, to run every minute:
#     keepAlive.pl --pidfile=<pidfile> --command=<executable> <arguments>
# 2. put this code somewhere near the beginning of your script,
#    where $pidfile is the same value as used in the cron job above:
#     use File::Pid;
#     File::Pid->new({file => $pidfile})->write;

# if you want to stop your program from restarting, you must first disable the
# cron job, then manually stop your script. There is no need to clean up the
# pidfile; it will be cleaned up automatically when you next call
# keepAlive.pl.

use strict;
use warnings;

use Getopt::Long;
use File::Spec;
use File::Pid;
use IPC::System::Simple qw(system);

my ($pid_file, $command);
GetOptions("pidfile=s"   => $pid_file,
           "command=s"   => $command)
    or print "Usage: "151900920" --pidfile=<pidfile> --command=<executable> <arguments>\n", exit;

my @arguments = @ARGV;

# check if process is still running
my $pid_obj = File::Pid->new({file => $pid_file});

if ($pid_obj->running())
{
    # process is still running; nothing to do!
    exit 0;
}

# no? restart it
print "Pid " . $pid_obj->pid . " no longer running; restarting $command @arguments\n";

system($command, @arguments);

example.pl:

#!/usr/bin/perl

use strict;
use warnings;

use File::Pid;
File::Pid->new({file => "pidfile"})->write;

print ""151910920" got arguments: @ARGV\n";

Maintenant, vous pouvez invoquer l'exemple ci-dessus avec: ./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3 et le fichier pidfile sera créé, et vous verrez le résultat:

Pid <random number here> no longer running; restarting ./example.pl 1 2 3
./example.pl got arguments: 1 2 3
1
répondu Ether 2013-01-08 08:34:06

, Vous pouvez aussi essayer de Monit . Monit est un service qui surveille les autres services et en rend compte. Bien qu'il soit principalement utilisé comme un moyen de notifier (par courriel et sms) les problèmes d'exécution, il peut également faire ce que la plupart des autres suggestions ici ont préconisé. Il peut (re)démarrer et arrêter des programmes, envoyer des e-mails, lancer d'Autres scripts, et maintenir un journal de sortie que vous pouvez ramasser. De plus, j'ai trouvé que c'est facile à installer et à entretenir puisqu'il y a des documentation.

1
répondu Adestin 2017-04-13 13:51:55

, Vous pouvez essayer de immortel C'est un *nix croix-plate-forme (système d'exploitation agnostique) superviseur.

pour un essai rapide sur macOS:

brew install immortal

dans le cas où vous utilisez FreeBSD depuis les ports ou en utilisant pkg:

pkg install immortal

pour Linux en téléchargeant les binaires précompilés ou à partir de la source: https://immortal.exécuter/source/

vous pouvez l'utiliser comme ceci:

immortal -l /var/log/date.log date

ou par un fichier de configuration YAML qui vous donne plus d'options, par exemple:

cmd: date
log:
    file: /var/log/date.log
    age: 86400 # seconds
    num: 7     # int
    size: 1    # MegaBytes
    timestamp: true # will add timesamp to log

si vous souhaitez conserver aussi la sortie d'erreur standard dans un fichier séparé, vous pouvez utiliser quelque chose comme:

cmd: date
log:
    file: /var/log/date.log
    age: 86400 # seconds
    num: 7     # int
    size: 1    # MegaBytes
stderr:
    file: /var/log/date-error.log
    age: 86400 # seconds
    num: 7     # int
    size: 1    # MegaBytes
    timestamp: true # will add timesamp to log
1
répondu nbari 2017-09-07 19:07:14

j'ai apporté une série d'améliorations à la autre réponse .

  1. stdout hors de ce script est purement constitué de stdout provenant de son enfant à moins qu'il ne sorte en raison de la détection que la commande est déjà en cours d'exécution
  2. nettoie après son pidfile lorsque terminé
  3. option configurable délai d'attente (Accepte aucun argument numérique, envoie sleep )
  4. indication d'utilisation sur -h
  5. exécution de commande arbitraire, plutôt que l'exécution d'une seule commande. Le dernier arg ou args restant (si plus d'un dernier arg) sont envoyés à eval , de sorte que vous pouvez construire n'importe quelle sorte de script shell comme une chaîne de caractères pour envoyer à ce script comme dernier arg (ou args arrière) pour qu'il daemonize
  6. argument count comparaison faite avec -lt au lieu de <

voici le script:

#!/bin/sh

# this script builds a mini-daemon, which isn't a real daemon because it
# should die when the owning terminal dies, but what makes it useful is
# that it will restart the command given to it when it completes, with a
# configurable timeout period elapsing before doing so.

if [ "" = '-h' ]; then
    echo "timeout defaults to 1 sec.\nUsage: $(basename ""151900920"") sentinel-pidfile [timeout] command [command arg [more command args...]]"
    exit
fi

if [ $# -lt 2 ]; then
    echo "No command given."
    exit
fi

PIDFILE=
shift

TIMEOUT=1
if [[  =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
        TIMEOUT=
        [ $# -lt 2 ] && echo "No command given (timeout was given)." && exit
        shift
fi

echo "Checking pid in file ${PIDFILE}." >&2

#Check to see if process running.
if [ -f "$PIDFILE" ]; then
    PID=$(< $PIDFILE)
    if [ $? = 0 ]; then
        ps -p $PID >/dev/null 2>&1
        if [ $? = 0 ]; then
            echo "This script is (probably) already running as PID ${PID}."
            exit
        fi
    fi
fi

# Write our pid to file.
echo $$ >$PIDFILE

cleanup() {
        rm $PIDFILE
}
trap cleanup EXIT

# Run command until we're killed.
while true; do
    eval "$@"
    echo "I am $$ and my child has exited; restart in ${TIMEOUT}s" >&2
    sleep $TIMEOUT
done

Utilisation:

$ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/'
Checking pid in file pidfilefortesting.
azzzcd
I am 79281 and my child has exited; restart in 0.5s
azzzcd
I am 79281 and my child has exited; restart in 0.5s
azzzcd
I am 79281 and my child has exited; restart in 0.5s
^C

$ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/' 2>/dev/null
azzzcd
azzzcd
azzzcd
^C

attention, si vous exécutez ce script à partir de répertoires différents, il peut utiliser des fichiers pidfiles différents et ne pas détecter d'instances existantes. Puisqu'il est conçu pour exécuter et redémarrer des commandes éphémères fournies par un argument, il n'y a aucun moyen de savoir si quelque chose a déjà commencé, car qui peut dire si c'est le même commande ou pas? Pour améliorer cette application de n'exécuter qu'une seule instance de quelque chose, une solution spécifique de la situation est nécessaire.

Aussi, pour qu'il fonctionne comme un véritable démon, vous devez utiliser (au minimum) nohup que l'autre réponse mentionne. Je n'ai fait aucun effort pour fournir une résistance aux signaux que le processus pourrait recevoir.

un autre point à noter est que tuer ce script (s'il a été appelé de encore un autre script qui est tué, ou avec un signal) peut ne pas réussir à tuer l'enfant, surtout si l'enfant est encore une autre "1519320920 de script". Je ne sais pas pourquoi, mais il semble que cela soit lié à la façon dont eval fonctionne, ce qui est mystérieux pour moi. Ainsi, il peut être prudent de remplacer cette ligne avec quelque chose qui n'accepte qu'une seule commande comme dans l'autre réponse.

0
répondu Steven Lu 2017-05-23 11:54:38