NAS で iptv 録画をしてますが、cron 設定はかなり面倒です。
ローカルのシェルスクリプトで NAS の cron 設定をしてますが、手動で行っています。
これはちょっと面倒で、時々混乱しておかしな設定をしてしまうことがあります。
以前から時間があったら python などを使って自動化したいと思っていましたが、chatGPT が優秀なので依頼してみました。
cron はいわばタイムスケジューラで、とても便利なものです。
windows にもこの機能はありますが linux の方が遥かに細かい設定が可能です。
例えば、現在 NAS に実行させている cron は、
* 19 * * 0 moheno for i in `seq 0 3 59`;do (sleep ${i}; python /volume1/bak/iptv/python/moheno.py >> /volume1/bak/iptv/moheno/log/cron.log 2>&1 ) & done;こんな感じです。
これは、日曜日の 19 時台に、ある python プログラムを 3 秒おきに実行して、そのログをあるディレクトリに記録するものです。
このような内容のテキストファイルを作成して、NAS へアップロードして cron 設定していました。
アップするのはシェルスクリプトで、
#!/bin/sh
scp -O -P 12345 ./crontab_backup.txt moheno@192.168.0.20:/volume1/bak/iptv
# SSH接続ssh -p 12345 moheno@192.168.0.20 <<EOF cd /volume1/bak/iptv sudo sh ./editcron.shEOFこんな感じでした。
これは、ローカルで作成した crontab_backup.txt を NAS に送って、その情報を元にして NAS の cron 設定をローカルからおこなうものです。
editcron.sh は NAS 上にあり、
#!/bin/bash
cat /volume1/bak/iptv/crontab_backup.txt | sudo tee /etc/crontab > /dev/nullこれ、ほとんど理解できませんが、ローカルからこのシェルスクリプトを動かすと、NAS の cron 設定がおこなわれます。
でも、これらの処理を手動でおこなうのは面倒でもあり、また複雑なのでエラーが混入する可能性もあります。
そこで、これらの処理を python で自動化できないかと chatGPT に質問してみました。
chatGPT はとても優秀で、自分で作ると最低数日はかかるプログラムをあっという間に作ってくれます。
まずは、出来上がりのイメージから。
あるシェルスクリプトを実行すると、tkinter による入力フォームが表示されます。
チャンネルを選んで開始時刻と終了時刻を入力すると、cron_backup.txt の temporary 領域に cron 設定文が追記されます。
例えば、10 月 17 日の 10:00 から 13:30 まで設定すると、以下のような cron 設定文が追記されます。
# ----------------------------------- temporary# Italy0-59 10 17 10 * moheno for i in `seq 0 3 59`;do (sleep ${i}; python /volume1/bak/iptv/python/Italy.py >> /volume1/bak/iptv/Italy/log/cron.log 2>&1 ) & done;* 11 17 10 * moheno for i in `seq 0 3 59`;do (sleep ${i}; python /volume1/bak/iptv/python/Italy.py >> /volume1/bak/iptv/Italy/log/cron.log 2>&1 ) & done;* 12 17 10 * moheno for i in `seq 0 3 59`;do (sleep ${i}; python /volume1/bak/iptv/python/Italy.py >> /volume1/bak/iptv/Italy/log/cron.log 2>&1 ) & done;0-30 13 17 10 * moheno for i in `seq 0 3 59`;do (sleep ${i}; python /volume1/bak/iptv/python/Italy.py >> /volume1/bak/iptv/Italy/log/cron.log 2>&1 ) & done;#----------------------------------------------これは、これまでは以下のようなテキストを手動で書いていました。
# ----------------------------------- temporary# Italy* 10-12 17 10 * moheno for i in `seq 0 3 59`;do (sleep ${i}; python /volume1/bak/iptv/python/Italy.py >> /volume1/bak/iptv/Italy/log/cron.log 2>&1 ) &0-30 13 17 10 * moheno for i in `seq 0 3 59`;do (sleep ${i}; python /volume1/bak/iptv/python/Italy.py >> /volume1/bak/iptv/Italy/log/cron.log 2>&1 ) & done;#----------------------------------------------でも、最初の cron 設定は間違いではないのできちんと実行されます。
python を実行すると cron_backup.txt を編集して、NAS にアップロードして、NAS 上のシェルスクリプトを実行するまで完全自動化することができます。
いきなりですが、chatGPT が作ってくれた python です。
#!/usr/bin/env python
import tkinter as tkfrom tkinter import ttk, messageboxfrom tkcalendar import DateEntryfrom pathlib import Pathfrom datetime import datetime, timedeltaimport paramikofrom scp import SCPClientimport tkinter.font as tkFont
# ---------------- Cron管理 ----------------class CronFileManager: def __init__(self, base_file="/home/moheno/iptv/AAA/NAS720/crontab_backup.txt", interval=3, user="moheno"): self.base_file = Path(base_file) self.interval = interval self.user = user self.start_marker = "# ----------------------------------- temporary" self.end_marker = "#----------------------------------------------"
def generate_lines(self, channel_name, script_name, start_dt, end_dt): base_cmd = ( f"{self.user} for i in `seq 0 {self.interval} 59`;do (sleep ${{i}}; " f"python /volume1/bak/iptv/python/{script_name} >> " f"/volume1/bak/iptv/{channel_name}/log/cron.log 2>&1 ) & done;" ) lines = [f"# {channel_name}"] current_dt = start_dt
while True: if current_dt.date() == end_dt.date(): end_hour = end_dt.hour end_min = end_dt.minute else: end_hour = 23 end_min = 59
start_hour = current_dt.hour start_min = current_dt.minute day = current_dt.day month = current_dt.month
if start_hour == end_hour: lines.append(f"{start_min}-{end_min} {start_hour} {day} {month} * {base_cmd}") else: lines.append(f"{start_min}-59 {start_hour} {day} {month} * {base_cmd}") for hour in range(start_hour + 1, end_hour): lines.append(f"* {hour} {day} {month} * {base_cmd}") lines.append(f"0-{end_min} {end_hour} {day} {month} * {base_cmd}")
if current_dt.date() == end_dt.date(): break current_dt += timedelta(days=1) current_dt = current_dt.replace(hour=0, minute=0)
return lines
def append_to_temporary(self, channel_name, script_name, start_dt, end_dt): if not self.base_file.exists(): raise FileNotFoundError(f"ベースファイルが存在しません: {self.base_file}")
lines = self.base_file.read_text().splitlines() start_index = end_index = None temp_buffer = []
for idx, line in enumerate(lines): if self.start_marker in line: start_index = idx elif self.end_marker in line: end_index = idx if start_index is not None and end_index is None: temp_buffer.append(line)
if start_index is None or end_index is None: raise ValueError("temporary セクションがベースファイル内に見つかりません。")
temp_buffer.append("") temp_buffer += self.generate_lines(channel_name, script_name, start_dt, end_dt)
new_lines = lines[:start_index] + temp_buffer + lines[end_index:] self.base_file.write_text("\n".join(new_lines) + "\n") return self.base_file
# ---------------- NAS転送 & SSH ----------------class NASUploader: def __init__(self, host, port, username, password=None, key_filename=None): self.host = host self.port = port self.username = username self.password = password self.key_filename = key_filename
def upload_and_execute(self, local_file, remote_path, remote_command): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname=self.host, port=self.port, username=self.username, password=self.password, key_filename=self.key_filename) with SCPClient(ssh.get_transport()) as scp: scp.put(local_file, remote_path) stdin, stdout, stderr = ssh.exec_command(remote_command) out = stdout.read().decode() err = stderr.read().decode() ssh.close() return out, err
# ---------------- CSV読み込み(1カラム) ----------------def load_channels_from_csv(csv_path): channels = [] with open(csv_path, newline='', encoding='utf-8') as f: for line in f: name = line.strip() if name: channels.append((name, f"{name}.py")) return channels
# ---------------- GUI ----------------class CronGUI: def __init__(self, root): # フォント設定 default_font = tkFont.Font(family="Helvetica", size=14) root.option_add("*Font", default_font)
# CSVファイルパス固定 csv_path = "/home/moheno/iptv/AAA/channels.csv" self.channels = load_channels_from_csv(csv_path)
self.manager = CronFileManager() self.nas = NASUploader( host="192.168.0.20", port=12345, username="moheno", password="YOUR_PASSWORD" ) self.remote_path = "/volume1/bak/iptv/crontab_backup.txt" self.remote_command = "cd /volume1/bak/iptv && sudo sh ./editcron.sh"
self.root = root self.root.title("NAS720 Cron Updater + NAS転送") self.root.geometry("800x450")
# root全体を縦横中央寄せ self.root.grid_rowconfigure(0, weight=1) self.root.grid_columnconfigure(0, weight=1)
self.create_widgets()
def create_widgets(self): padx_label = 10 pady_label = 8 padx_entry = 5 pady_entry = 5 ipady_val = 3 # 垂直中央寄せ用の内部パディング
# メインフレームで全体中央に配置 main_frame = tk.Frame(self.root) main_frame.grid(row=0, column=0, sticky="nsew") main_frame.grid_columnconfigure(0, weight=1) for i in range(5): # 5行想定で均等に伸ばす main_frame.grid_rowconfigure(i, weight=1)
# チャンネル tk.Label(main_frame, text="チャンネル:").grid(row=0, column=0, padx=padx_label, pady=pady_label, sticky="e") self.channel_var = tk.StringVar() channel_names = [c[0] for c in self.channels] self.channel_box = ttk.Combobox(main_frame, textvariable=self.channel_var, values=channel_names, width=30, justify="center") self.channel_box.current(0) self.channel_box.grid(row=0, column=1, columnspan=4, padx=padx_entry, pady=pady_entry, ipady=ipady_val)
# 開始フレーム frame_start = tk.LabelFrame(main_frame, text="開始日時", padx=10, pady=5) frame_start.grid(row=1, column=0, columnspan=5, padx=10, pady=5, sticky="nsew") for i in range(5): frame_start.grid_columnconfigure(i, weight=1) tk.Label(frame_start, text="日付:").grid(row=0, column=0, padx=5, pady=5) self.start_date = DateEntry(frame_start, width=12, justify="center", background='darkblue', foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd') self.start_date.grid(row=0, column=1, padx=5, pady=5, ipady=ipady_val) tk.Label(frame_start, text="時刻:").grid(row=0, column=2, padx=5, pady=5) self.start_hour = ttk.Combobox(frame_start, values=[f"{i:02d}" for i in range(24)], width=5, justify="center") self.start_hour.set("10") self.start_hour.grid(row=0, column=3, padx=2, pady=5, ipady=ipady_val) self.start_min = ttk.Combobox(frame_start, values=[f"{i:02d}" for i in range(60)], width=5, justify="center") self.start_min.set("00") self.start_min.grid(row=0, column=4, padx=2, pady=5, ipady=ipady_val)
# 終了フレーム frame_end = tk.LabelFrame(main_frame, text="終了日時", padx=10, pady=5) frame_end.grid(row=2, column=0, columnspan=5, padx=10, pady=5, sticky="nsew") for i in range(5): frame_end.grid_columnconfigure(i, weight=1) tk.Label(frame_end, text="日付:").grid(row=0, column=0, padx=5, pady=5) self.end_date = DateEntry(frame_end, width=12, justify="center", background='darkblue', foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd') self.end_date.grid(row=0, column=1, padx=5, pady=5, ipady=ipady_val) tk.Label(frame_end, text="時刻:").grid(row=0, column=2, padx=5, pady=5) self.end_hour = ttk.Combobox(frame_end, values=[f"{i:02d}" for i in range(24)], width=5, justify="center") self.end_hour.set("11") self.end_hour.grid(row=0, column=3, padx=2, pady=5, ipady=ipady_val) self.end_min = ttk.Combobox(frame_end, values=[f"{i:02d}" for i in range(60)], width=5, justify="center") self.end_min.set("30") self.end_min.grid(row=0, column=4, padx=2, pady=5, ipady=ipady_val)
# 実行ボタン tk.Button( main_frame, text="temporaryに追記 + NAS転送実行", command=self.on_append_and_upload, bg="#4CAF50", fg="white", font=("Helvetica", 13, "bold") ).grid(row=3, column=0, columnspan=5, pady=20, padx=10, sticky="ew")
tk.Label(main_frame, text=f"ベースファイル: {self.manager.base_file}").grid( row=4, column=0, columnspan=5, pady=5, sticky="w" )
def on_append_and_upload(self): try: channel_name = self.channel_var.get() script_name = next(s[1] for s in self.channels if s[0] == channel_name)
start_date_val = self.start_date.get_date() end_date_val = self.end_date.get_date() start_hour = int(self.start_hour.get()) start_min = int(self.start_min.get()) end_hour = int(self.end_hour.get()) end_min = int(self.end_min.get())
start_dt = datetime(year=start_date_val.year, month=start_date_val.month, day=start_date_val.day, hour=start_hour, minute=start_min) end_dt = datetime(year=end_date_val.year, month=end_date_val.month, day=end_date_val.day, hour=end_hour, minute=end_min)
updated_path = self.manager.append_to_temporary(channel_name, script_name, start_dt, end_dt)
out, err = self.nas.upload_and_execute( local_file=str(updated_path), remote_path=self.remote_path, remote_command=self.remote_command )
messagebox.showinfo("完了", f"temporary部分に追記しました。\nベースファイル更新済み。\n\n" f"SCP + editcron.sh 実行完了\n\nSTDOUT:\n{out}\nSTDERR:\n{err}")
except Exception as e: messagebox.showerror("エラー", str(e))
if __name__ == "__main__": root = tk.Tk() app = CronGUI(root) root.mainloop()このプログラムは 360 行を超えるもので、これを自分で作成しようと思うと最低 1 週間はかかると思います。
いや、今となってはもうできないかもしれません。
chatGPT と一緒にいろいろとプログラムを作ってきましたが、挫折する場合とうまく行く場合の違いがやっと少しつかめてきたように思います。
今回、ほとんど大きな混乱もなく比較的短時間でかなり大きなプログラムができたのは、簡単なものから始めて少しずつ追加してきたためだと思います。
しかも、chatGPT に依頼する内容はかなり具体的にお願いしました。そのおかげで短時間で大きなプログラムをうまく作って貰えたと思います。
今回のプロセスは、
テーマを限定して具体的に依頼すると、とても迅速に答えを返してくれます。
素晴らしいプログラムを作ってもらいましたが、私がいろいろチャットしているのはフリーの chatGPT 5 です。
有料の方は chat GPT 5 plus。
chatGPT に依頼すると非常に便利なのですが、自分で作成していないので編集が少し面倒ではあります。
それと、やはり自分で考えることが少なくなってきたように思います。
効率はいいのですが、やはり自分で考えることは大切だと感じています。
それと、現在はフリーの chatGPT とチャットしてますが、あまりにも便利なのである日突然に有料化しますと言われたら断れないようにも思います。