first commit
🚀 Deploy — JH Photomaton / 🔍 Vérification (push) Has been cancelled
🚀 Deploy — JH Photomaton / 🍓 Deploy sur le Pi (push) Has been cancelled

This commit is contained in:
2026-07-16 00:55:29 +02:00
commit 209f81aa3d
100 changed files with 17682 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
# =============================================================================
# Notes GPIO - Simplification du declenchement bouton
# =============================================================================
#
# SITUATION ACTUELLE:
# Bouton physique -> Relay 12V -> GPIO -> Node-RED -> API photobooth-app
#
# PROBLEME:
# - Node-RED consomme ~150-300 Mo de RAM (Node.js)
# - Un composant intermediaire de plus = un point de panne de plus
# - Latence supplementaire
#
# SOLUTION RECOMMANDEE:
# photobooth-app supporte NATIVEMENT le GPIO Raspberry Pi via gpiozero.
# Tu peux brancher le bouton directement sur un GPIO sans relay ni Node-RED.
#
# OPTION A: GPIO DIRECT (recommande - sans relay)
# - Brancher le bouton entre GND et GPIO 27 (pin physique 13)
# - photobooth-app active les resistances pull-up internes
# - Bouton normalement ouvert (NO)
# - Schema: GND ---[BOUTON]--- GPIO27
#
# OPTION B: GARDER LE RELAY 12V (si le bouton necessite du 12V)
# - Le relay sort sur un GPIO du Pi
# - Configurer ce GPIO dans photobooth-app directement
# - PAS BESOIN de Node-RED pour ca
#
# CONFIGURATION DANS PHOTOBOOTH-APP:
# 1. Ouvrir l'admin: http://localhost:8083/admin
# 2. Aller dans: CONFIGURATION -> hardwareinput
# 3. Activer "GPIO"
# 4. Dans Actions, configurer:
# - Picture trigger: GPIO 27 (ou le GPIO connecte a ton relay)
# - Collage trigger: GPIO 22 (optionnel)
# - Print trigger: GPIO 23 (optionnel)
# - Shutdown: GPIO 17 (hold 2 secondes)
#
# GPIO PAR DEFAUT DE PHOTOBOOTH-APP:
# | Fonction | GPIO | Pin physique | Type |
# |-------------|------|-------------|------------|
# | Photo | 27 | 13 | Appui |
# | Collage | 22 | 15 | Appui |
# | Animation | 24 | 18 | Appui |
# | Video | 25 | 22 | Appui |
# | Impression | 23 | 16 | Appui |
# | Shutdown | 17 | 11 | Maintien 2s|
# | Reboot | 18 | 12 | Maintien 2s|
#
# APRES MIGRATION:
# - Desactiver Node-RED: sudo systemctl disable nodered.service
# - Gain: ~150-300 Mo de RAM liberee !
#
# =============================================================================
@@ -0,0 +1,5 @@
[
{ "_comment": "Backup du flow Node-RED complet - voir le fichier original pour le contenu" },
{ "_comment": "Sauvegarde faite le 2026-06-24" },
{ "_comment": "Ce fichier est trop volumineux pour etre inclus ici - copier directement depuis le Pi" }
]
@@ -0,0 +1,690 @@
{
"common": {
"admin_password": "xxxxxxxxxxx",
"logging_level": "DEBUG",
"users_delete_to_recycle_dir": true
},
"actions": {
"image": [
{
"name": "HnB - normal",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": false,
"fill_background_enable": false,
"fill_background_color": "#ededed",
"img_background_enable": false,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/hopnbloc/frames/hnb cadre final.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Photo normal",
"icon": "Photo",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Hnb - Etoile",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": true,
"fill_background_color": "#ededed",
"img_background_enable": true,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/hopnbloc/frames/hnb cadre final.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Photo - voie lactée",
"icon": "star_shine",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Hnb - Cailloux",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": true,
"fill_background_color": "#f2f0f0",
"img_background_enable": true,
"img_background_file": "userdata/hopnbloc/backgrounds/Wall-with-large-and-small-stones.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/hopnbloc/frames/hnb cadre final.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Photo - Mur de pierre",
"icon": "brick",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Hnb - Light",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": true,
"fill_background_color": "#f2f0f0",
"img_background_enable": true,
"img_background_file": "userdata/hopnbloc/backgrounds/2149243965.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/hopnbloc/frames/calque photos final.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "HnB - Soirée branchée",
"icon": "nightlife",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "background stars",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": false,
"fill_background_color": "white",
"img_background_enable": true,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": false,
"img_frame_file": "userdata/demoassets/frames/frame_image_photobooth-app.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Star back",
"icon": "photo_camera",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Normal image",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": false,
"fill_background_enable": false,
"fill_background_color": "#2d68c4",
"img_background_enable": false,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": false,
"img_frame_file": "userdata/demoassets/frames/frame_image_photobooth-app.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Normal",
"icon": "photo_camera",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "LULU",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": false,
"fill_background_enable": false,
"fill_background_color": "#ededed",
"img_background_enable": false,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/LSDW/lulu-versaire2.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "LULU",
"icon": "star_shine",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "LULU - Etoile",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": true,
"fill_background_color": "#ededed",
"img_background_enable": true,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/LSDW/lulu-versaire2.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "LULU - Etoile",
"icon": "star_shine",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Hnb - Etoile - copy",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": true,
"fill_background_color": "#ededed",
"img_background_enable": true,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/hopnbloc/frames/calque photos final.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "HnB - nuitée étoilée new",
"icon": "star_shine",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
}
],
"collage": [
{
"name": "default action",
"jobcontrol": {
"countdown_capture": 2.0,
"countdown_capture_second_following": 1.0,
"ask_approval_each_capture": true,
"approve_autoconfirm_timeout": 15.0,
"show_individual_captures_in_gallery": true
},
"processing": {
"capture_remove_background": true,
"capture_fill_background_enable": true,
"capture_fill_background_color": "white",
"capture_img_background_enable": false,
"capture_img_background_file": null,
"canvas_width": 1920,
"canvas_height": 1280,
"merge_definition": [
{
"description": "left",
"pos_x": 160,
"pos_y": 220,
"pos_z": 0,
"width": 510,
"height": 725,
"rotate": 0,
"predefined_image": null,
"image_filter": "FilterPilgram2.earlybird"
},
{
"description": "middle predefined",
"pos_x": 705,
"pos_y": 66,
"pos_z": 0,
"width": 510,
"height": 725,
"rotate": 0,
"predefined_image": "userdata/demoassets/predefined_images/photobooth-collage-predefined-image.png",
"image_filter": "original"
},
{
"description": "right",
"pos_x": 1245,
"pos_y": 220,
"pos_z": 0,
"width": 510,
"height": 725,
"rotate": 0,
"predefined_image": null,
"image_filter": "FilterPilgram2.reyes"
}
],
"canvas_fill_background_enable": false,
"canvas_fill_background_color": "#2e6f40",
"canvas_img_background_enable": false,
"canvas_img_background_file": null,
"canvas_img_front_enable": true,
"canvas_img_front_file": "userdata/demoassets/frames/pixabay-poster-2871536_1920.png",
"canvas_texts_enable": true,
"canvas_texts": [
{
"text": "Have a nice day :)",
"pos_x": 200,
"pos_y": 1100,
"rotate": 1,
"font_size": 40,
"font": "userdata/demoassets/fonts/Roboto-Bold.ttf",
"color": "#333"
}
]
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Collage",
"icon": "auto_awesome_mosaic",
"use_custom_color": false,
"custom_color": "#196cb0"
},
"keyboard_trigger": {
"keycode": "c"
},
"gpio_trigger": {
"pin": "22",
"trigger_on": "pressed"
}
}
}
],
"animation": [],
"video": [],
"multicamera": []
},
"share": {
"sharing_enabled": true,
"actions": [
{
"name": "Demande d'impression",
"handles_images_only": true,
"processing": {
"share_command": "curl http://127.0.0.1:1880/api/photobooth-custom/ask_print?filename={filename}",
"ask_user_for_parameter_input": false,
"parameters_dialog_caption": "Make your choice!",
"parameters_dialog_action_icon": "print",
"parameters_dialog_action_label": "GO",
"parameters": [],
"share_blocked_time": 1,
"max_shares": 0
},
"trigger": {
"ui_trigger": {
"show_button": true,
"title": "Demander l'impression ?",
"icon": "photo_prints",
"use_custom_color": true,
"custom_color": "#ab0a78"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Impression",
"handles_images_only": true,
"processing": {
"share_command": "/home/pi/photobooth-data/script/script_print.sh \"{filename}\" \"{media_type}\" \"{action_config_name}\" \"{copies}\"",
"ask_user_for_parameter_input": false,
"parameters_dialog_caption": "Make your choice!",
"parameters_dialog_action_icon": "print",
"parameters_dialog_action_label": "GO",
"parameters": [
{
"key": "copies",
"label": "Copies",
"ui_type": "int",
"default": "1",
"valid_min": "1",
"valid_max": "2"
}
],
"share_blocked_time": 120,
"max_shares": 0
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Direct Print",
"icon": "print",
"use_custom_color": true,
"custom_color": "#b01992"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Printing copies",
"handles_images_only": true,
"processing": {
"share_command": "echo {filename} media_type={media_type} action_config_name={action_config_name} copies={copies}",
"ask_user_for_parameter_input": true,
"parameters_dialog_caption": "How many copies?",
"parameters_dialog_action_icon": "print",
"parameters_dialog_action_label": "GO",
"parameters": [
{
"key": "copies",
"label": "Copies",
"ui_type": "int",
"default": "1",
"valid_min": "1",
"valid_max": "3"
}
],
"share_blocked_time": 3,
"max_shares": 0
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Print Copies",
"icon": "print",
"use_custom_color": false,
"custom_color": "#196cb0"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Mailing action",
"handles_images_only": false,
"processing": {
"share_command": "echo {filename} media_type={media_type} action_config_name={action_config_name} to mail {mail}",
"ask_user_for_parameter_input": true,
"parameters_dialog_caption": "E-Mail your image...",
"parameters_dialog_action_icon": "mail",
"parameters_dialog_action_label": "Send",
"parameters": [
{
"key": "mail",
"label": "E-Mail address",
"ui_type": "input",
"default": "me@mgineer85.de",
"valid_min": "5",
"valid_max": "128"
}
],
"share_blocked_time": 3,
"max_shares": 0
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Send Mail",
"icon": "mail",
"use_custom_color": false,
"custom_color": "#196cb0"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Pouet",
"handles_images_only": true,
"processing": {
"share_command": "echo OK && exit 0",
"ask_user_for_parameter_input": false,
"parameters_dialog_caption": "Make your choice!",
"parameters_dialog_action_icon": "print",
"parameters_dialog_action_label": "GO",
"parameters": [],
"share_blocked_time": 0,
"max_shares": 0
},
"trigger": {
"ui_trigger": {
"show_button": true,
"title": "pouet",
"icon": "",
"use_custom_color": false,
"custom_color": "#196cb0"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
}
]
},
"qrshare": {
"enabled": false,
"shareservice_url": "https://photobooth-app.org/extras/shareservice-landing/",
"shareservice_apikey": "changedefault!",
"enabled_custom": true,
"share_custom_qr_url": "https://photomaton.lessapinsduweb.com/sharepage/#?url=https://photomaton.lessapinsduweb.com/media/full/{identifier}"
},
"filetransfer": {
"enabled": false,
"target_folder_name": "photobooth"
},
"mediaprocessing": {
"full_still_length": 4608,
"preview_still_length": 2304,
"thumbnail_still_length": 960,
"video_bitrate": 3000,
"video_compatibility_mode": true,
"remove_background_model": "modnet",
"fileformat_animations": "webp",
"fileformat_multicamera": "mp4"
},
"uisettings": {
"PRIMARY_COLOR": "#196cb0",
"SECONDARY_COLOR": "#4283b8",
"theme": "system",
"show_gallery_on_frontpage": true,
"show_admin_on_frontpage": true,
"admin_button_invisible": true,
"show_frontpage_timeout": 5,
"enable_automatic_slideshow": true,
"show_automatic_slideshow_timeout": 120,
"enable_livestream_when_idle": false,
"enable_livestream_when_active": true,
"livestream_mirror_effect": false,
"livestream_blurredbackground": false,
"livestream_blurredbackground_high_framerate": false,
"enable_livestream_frameoverlay": true,
"livestream_frameoverlay_image": "userdata/hopnbloc/frames/hnb cadre final.png",
"livestream_frameoverlay_mirror_effect": false,
"FRONTPAGE_TEXT": "<div> <div class=\"fixed-right text-white text-right\" style=\"margin-top: 45px; text-shadow: 4px 4px 4px #666;\"> <div class=\"text-h5\"> \tSi vous utilisez le photomaton, vous nous autorisez à utiliser les photos. <br> \tCe photomaton est proposé en libre-service par 'Les Sapins Du Web'. <br> \tLes photos peuvent être imprimées sur demande et sur dons libre. (2 Photos maximum par personne) <br> \tMerci de votre compréhension. \t</div> <div class=\"text-h3 text-weight-bold\">Compét Hop'N Bloc !!!!!</div> </div> <div class=\"q-page-sticky row flex-center fixed-bottom-right q-page-sticky--shrink q-mb-lg q-mr-lg\"> <img src=\"/userdata/LSDW/logo/logo.jpeg\" alt=\"LSDW icon\" style=\"width:160px;height:160px;\"> </div> </div>",
"TAKEPIC_MSG_TIME": 0.5,
"TAKEPIC_MSG_TEXT": "😃",
"AUTOCLOSE_NEW_ITEM_ARRIVED": 30,
"GALLERY_EMPTY_MSG": "<div class=\"fixed-center text-h2 text-weight-bold text-center text-white\" style=\"text-shadow: 4px 4px 4px #666;\">Empty, Zero, Nada! 🤷‍♂️<br>Let's take some pictures! <br>📷💕</div>",
"gallery_show_qrcode": true,
"qrcode_text_above": "⬇️ Télécharger la photo!",
"qrcode_text_below": "Scannez le QR code ci-dessus pour télécharger cette photo. ( Vous devez être connecté au Wifi Photomaton)",
"qrcode_link_codes": true,
"gallery_show_filter": false,
"gallery_show_download": false,
"gallery_show_delete": false,
"gallery_show_shareprint": false
},
"backends": {
"enable_livestream": true,
"retry_capture": 3,
"countdown_camera_capture_offset": 0.2,
"index_backend_stills": 0,
"index_backend_video": 0,
"index_backend_multicam": 0,
"group_backends": [
{
"enabled": true,
"description": "pi camera module V3",
"backend_config": {
"orientation": "1: 0°",
"backend_type": "Picamera2",
"camera_num": 0,
"CAPTURE_CAM_RESOLUTION_WIDTH": 4608,
"CAPTURE_CAM_RESOLUTION_HEIGHT": 3072,
"PREVIEW_CAM_RESOLUTION_WIDTH": 2304,
"PREVIEW_CAM_RESOLUTION_HEIGHT": 1536,
"LIVEVIEW_RESOLUTION_WIDTH": 750,
"LIVEVIEW_RESOLUTION_HEIGHT": 500,
"framerate_still_mode": 20,
"framerate_video_mode": 25,
"frame_skip_count": 4,
"optimized_lowlight_short_exposure": false,
"videostream_quality": "MEDIUM",
"original_still_quality": 100
}
}
]
},
"hardwareinputoutput": {
"keyboard_input_enabled": false,
"gpio_enabled": false,
"gpio_pin_shutdown": 17,
"gpio_pin_reboot": 18,
"gpio_pin_job_next": 27,
"gpio_pin_job_reject": 22,
"gpio_pin_job_abort": 20
},
"misc": {
"secret_key": "ThisIsTheDefaultSecret",
"cmd_shutdown": "shutdown now",
"cmd_reboot": "reboot"
}
}
@@ -0,0 +1,548 @@
{
"_comment": "Config optimisee pour RPi4 2Go - Photomaton Les Sapins Du Web",
"_comment2": "Changements vs config originale marques avec OPTI:",
"common": {
"admin_password": "xxxxxxxxxxx",
"logging_level": "WARNING",
"_OPTI_logging": "OPTI: DEBUG -> WARNING. DEBUG consomme CPU+RAM en continu. Passer en INFO si besoin de diagnostiquer.",
"users_delete_to_recycle_dir": true
},
"actions": {
"image": [
{
"_comment": "Action principale sans remove_background - la plus legere en RAM",
"name": "HnB - normal",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": false,
"fill_background_enable": false,
"fill_background_color": "#ededed",
"img_background_enable": false,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/hopnbloc/frames/hnb cadre final.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": true,
"_OPTI_show_button": "OPTI: false -> true. Au moins 1 action doit etre visible pour les utilisateurs.",
"title": "Photo",
"icon": "photo_camera",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "27",
"_OPTI_gpio": "OPTI: '' -> '27'. Declenchement direct via GPIO sans passer par Node-RED.",
"trigger_on": "pressed"
}
}
},
{
"_comment": "Avec remove_background - uniquement quand on change le fond",
"name": "Hnb - Etoile",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": true,
"fill_background_color": "#ededed",
"img_background_enable": true,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/hopnbloc/frames/hnb cadre final.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": true,
"title": "Voie lactee",
"icon": "star_shine",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Hnb - Cailloux",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": true,
"fill_background_color": "#f2f0f0",
"img_background_enable": true,
"img_background_file": "userdata/hopnbloc/backgrounds/Wall-with-large-and-small-stones.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/hopnbloc/frames/hnb cadre final.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Mur de pierre",
"icon": "brick",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "Hnb - Light",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": true,
"fill_background_color": "#f2f0f0",
"img_background_enable": true,
"img_background_file": "userdata/hopnbloc/backgrounds/2149243965.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/hopnbloc/frames/calque photos final.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Soiree branchee",
"icon": "nightlife",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"_comment": "OPTI: remove_background desactive car img_frame_enable est false et pas de fond custom utile",
"name": "Normal image",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": false,
"fill_background_enable": false,
"fill_background_color": "#2d68c4",
"img_background_enable": false,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": false,
"img_frame_file": "userdata/demoassets/frames/frame_image_photobooth-app.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Normal",
"icon": "photo_camera",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"_comment": "LULU - sans remove_background car juste un cadre",
"name": "LULU",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": false,
"fill_background_enable": false,
"fill_background_color": "#ededed",
"img_background_enable": false,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/LSDW/lulu-versaire2.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "LULU",
"icon": "star_shine",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"name": "LULU - Etoile",
"jobcontrol": {
"countdown_capture": 5.0
},
"processing": {
"remove_background": true,
"fill_background_enable": true,
"fill_background_color": "#ededed",
"img_background_enable": true,
"img_background_file": "userdata/demoassets/backgrounds/background.jpg",
"image_filter": "original",
"img_frame_enable": true,
"img_frame_file": "userdata/LSDW/lulu-versaire2.png",
"texts_enable": false,
"texts": []
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "LULU - Etoile",
"icon": "star_shine",
"use_custom_color": true,
"custom_color": "#016911"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
}
],
"_OPTI_removed_actions": "OPTI: Supprime 'background stars' (doublon de Hnb-Etoile) et 'Hnb-Etoile-copy' (copie). Garde 7 actions au lieu de 9.",
"collage": [
{
"name": "default action",
"jobcontrol": {
"countdown_capture": 2.0,
"countdown_capture_second_following": 1.0,
"ask_approval_each_capture": true,
"approve_autoconfirm_timeout": 15.0,
"show_individual_captures_in_gallery": true
},
"processing": {
"capture_remove_background": true,
"capture_fill_background_enable": true,
"capture_fill_background_color": "white",
"capture_img_background_enable": false,
"capture_img_background_file": null,
"canvas_width": 1920,
"canvas_height": 1280,
"merge_definition": [
{
"description": "left",
"pos_x": 160,
"pos_y": 220,
"pos_z": 0,
"width": 510,
"height": 725,
"rotate": 0,
"predefined_image": null,
"image_filter": "FilterPilgram2.earlybird"
},
{
"description": "middle predefined",
"pos_x": 705,
"pos_y": 66,
"pos_z": 0,
"width": 510,
"height": 725,
"rotate": 0,
"predefined_image": "userdata/demoassets/predefined_images/photobooth-collage-predefined-image.png",
"image_filter": "original"
},
{
"description": "right",
"pos_x": 1245,
"pos_y": 220,
"pos_z": 0,
"width": 510,
"height": 725,
"rotate": 0,
"predefined_image": null,
"image_filter": "FilterPilgram2.reyes"
}
],
"canvas_fill_background_enable": false,
"canvas_fill_background_color": "#2e6f40",
"canvas_img_background_enable": false,
"canvas_img_background_file": null,
"canvas_img_front_enable": true,
"canvas_img_front_file": "userdata/demoassets/frames/pixabay-poster-2871536_1920.png",
"canvas_texts_enable": true,
"canvas_texts": [
{
"text": "Have a nice day :)",
"pos_x": 200,
"pos_y": 1100,
"rotate": 1,
"font_size": 40,
"font": "userdata/demoassets/fonts/Roboto-Bold.ttf",
"color": "#333"
}
]
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Collage",
"icon": "auto_awesome_mosaic",
"use_custom_color": false,
"custom_color": "#196cb0"
},
"keyboard_trigger": {
"keycode": "c"
},
"gpio_trigger": {
"pin": "22",
"trigger_on": "pressed"
}
}
}
],
"animation": [],
"video": [],
"multicamera": []
},
"share": {
"sharing_enabled": true,
"actions": [
{
"_comment": "Action visible par les utilisateurs - envoie la demande a Node-RED pour la file d'attente SQLite + dashboard admin",
"name": "Demande d'impression",
"handles_images_only": true,
"processing": {
"share_command": "curl http://127.0.0.1:1880/api/photobooth-custom/ask_print?filename={filename}",
"ask_user_for_parameter_input": false,
"parameters_dialog_caption": "Make your choice!",
"parameters_dialog_action_icon": "print",
"parameters_dialog_action_label": "GO",
"parameters": [],
"share_blocked_time": 1,
"max_shares": 0
},
"trigger": {
"ui_trigger": {
"show_button": true,
"title": "Demander l'impression ?",
"icon": "photo_prints",
"use_custom_color": true,
"custom_color": "#ab0a78"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
},
{
"_comment": "Action cachee - appelee par Node-RED dashboard quand l'admin valide l'impression",
"name": "Impression directe",
"handles_images_only": true,
"processing": {
"share_command": "/home/pi/photobooth-data/script/script_print.sh \"{filename}\" \"{media_type}\" \"{action_config_name}\" \"{copies}\"",
"ask_user_for_parameter_input": false,
"parameters_dialog_caption": "Combien de copies ?",
"parameters_dialog_action_icon": "print",
"parameters_dialog_action_label": "Imprimer",
"parameters": [
{
"key": "copies",
"label": "Copies",
"ui_type": "int",
"default": "1",
"valid_min": "1",
"valid_max": "2"
}
],
"share_blocked_time": 60,
"_OPTI_blocked_time": "OPTI: 120 -> 60s de blocage.",
"max_shares": 0
},
"trigger": {
"ui_trigger": {
"show_button": false,
"title": "Impression directe",
"icon": "print",
"use_custom_color": true,
"custom_color": "#b01992"
},
"keyboard_trigger": {
"keycode": ""
},
"gpio_trigger": {
"pin": "",
"trigger_on": "pressed"
}
}
}
],
"_OPTI_removed_share_actions": "OPTI: Supprime 'Pouet' (test), 'Mailing action' (demo), 'Printing copies' (doublon). Garde 'Demande impression' (via Node-RED SQLite) + 'Impression directe' (via script_print.sh). 2 actions au lieu de 5."
},
"qrshare": {
"enabled": false,
"shareservice_url": "https://photobooth-app.org/extras/shareservice-landing/",
"shareservice_apikey": "changedefault!",
"enabled_custom": true,
"share_custom_qr_url": "https://photomaton.lessapinsduweb.com/sharepage/#?url=https://photomaton.lessapinsduweb.com/media/full/{identifier}"
},
"filetransfer": {
"enabled": false,
"target_folder_name": "photobooth"
},
"mediaprocessing": {
"full_still_length": 2304,
"_OPTI_full_still": "OPTI: 4608 -> 2304. La Selphy CP1300 imprime en 300dpi sur 4x6 pouces = 1200x1800px. 2304px est largement suffisant et divise la RAM par 4 pour le traitement d'image.",
"preview_still_length": 1200,
"_OPTI_preview": "OPTI: 2304 -> 1200. Pour l'affichage web/QR share, 1200px est largement suffisant.",
"thumbnail_still_length": 480,
"_OPTI_thumbnail": "OPTI: 960 -> 480. Les thumbnails de la galerie n'ont pas besoin d'etre si grands.",
"video_bitrate": 3000,
"video_compatibility_mode": true,
"remove_background_model": "modnet",
"fileformat_animations": "webp",
"fileformat_multicamera": "mp4"
},
"uisettings": {
"PRIMARY_COLOR": "#196cb0",
"SECONDARY_COLOR": "#4283b8",
"theme": "system",
"show_gallery_on_frontpage": true,
"show_admin_on_frontpage": true,
"admin_button_invisible": true,
"show_frontpage_timeout": 5,
"enable_automatic_slideshow": true,
"show_automatic_slideshow_timeout": 120,
"enable_livestream_when_idle": false,
"enable_livestream_when_active": true,
"livestream_mirror_effect": false,
"livestream_blurredbackground": false,
"livestream_blurredbackground_high_framerate": false,
"enable_livestream_frameoverlay": true,
"livestream_frameoverlay_image": "userdata/hopnbloc/frames/hnb cadre final.png",
"livestream_frameoverlay_mirror_effect": false,
"FRONTPAGE_TEXT": "<div> <div class=\"fixed-right text-white text-right\" style=\"margin-top: 45px; text-shadow: 4px 4px 4px #666;\"> <div class=\"text-h5\"> \tSi vous utilisez le photomaton, vous nous autorisez \u00e0 utiliser les photos. <br> \tCe photomaton est propos\u00e9 en libre-service par 'Les Sapins Du Web'. <br> \tLes photos peuvent \u00eatre imprim\u00e9es sur demande et sur dons libre. (2 Photos maximum par personne) <br> \tMerci de votre compr\u00e9hension. \t</div> <div class=\"text-h3 text-weight-bold\">Comp\u00e9t Hop'N Bloc !!!!!</div> </div> <div class=\"q-page-sticky row flex-center fixed-bottom-right q-page-sticky--shrink q-mb-lg q-mr-lg\"> <img src=\"/userdata/LSDW/logo/logo.jpeg\" alt=\"LSDW icon\" style=\"width:160px;height:160px;\"> </div> </div>",
"TAKEPIC_MSG_TIME": 0.5,
"TAKEPIC_MSG_TEXT": "\ud83d\ude03",
"AUTOCLOSE_NEW_ITEM_ARRIVED": 30,
"GALLERY_EMPTY_MSG": "<div class=\"fixed-center text-h2 text-weight-bold text-center text-white\" style=\"text-shadow: 4px 4px 4px #666;\">Pas encore de photos ! \ud83e\udd37\u200d\u2642\ufe0f<br>Appuyez sur le bouton ! <br>\ud83d\udcf7\ud83d\udc95</div>",
"_OPTI_gallery_empty": "OPTI: Traduit en francais",
"gallery_show_qrcode": true,
"qrcode_text_above": "\u2b07\ufe0f T\u00e9l\u00e9charger la photo!",
"qrcode_text_below": "Scannez le QR code ci-dessus pour t\u00e9l\u00e9charger cette photo. ( Vous devez \u00eatre connect\u00e9 au Wifi Photomaton)",
"qrcode_link_codes": true,
"gallery_show_filter": false,
"gallery_show_download": false,
"gallery_show_delete": false,
"gallery_show_shareprint": false
},
"backends": {
"enable_livestream": true,
"retry_capture": 3,
"countdown_camera_capture_offset": 0.2,
"index_backend_stills": 0,
"index_backend_video": 0,
"index_backend_multicam": 0,
"group_backends": [
{
"enabled": true,
"description": "pi camera module V3",
"backend_config": {
"orientation": "1: 0\u00b0",
"backend_type": "Picamera2",
"camera_num": 0,
"CAPTURE_CAM_RESOLUTION_WIDTH": 2304,
"CAPTURE_CAM_RESOLUTION_HEIGHT": 1536,
"_OPTI_capture_res": "OPTI: 4608x3072 -> 2304x1536. Divise la RAM par 4 pour chaque capture. Toujours suffisant pour impression Selphy 300dpi.",
"PREVIEW_CAM_RESOLUTION_WIDTH": 1152,
"PREVIEW_CAM_RESOLUTION_HEIGHT": 768,
"_OPTI_preview_res": "OPTI: 2304x1536 -> 1152x768. La preview n'a pas besoin d'etre aussi grande que la capture.",
"LIVEVIEW_RESOLUTION_WIDTH": 750,
"LIVEVIEW_RESOLUTION_HEIGHT": 500,
"framerate_still_mode": 20,
"framerate_video_mode": 25,
"frame_skip_count": 4,
"optimized_lowlight_short_exposure": false,
"videostream_quality": "MEDIUM",
"original_still_quality": 90,
"_OPTI_quality": "OPTI: 100 -> 90. Difference visuelle imperceptible, fichiers 30-40% plus petits = moins de RAM et I/O disque."
}
}
]
},
"hardwareinputoutput": {
"keyboard_input_enabled": false,
"gpio_enabled": true,
"_OPTI_gpio": "OPTI: false -> true. Active le GPIO natif pour le bouton, permet de se passer de Node-RED.",
"gpio_pin_shutdown": 17,
"gpio_pin_reboot": 18,
"gpio_pin_job_next": 27,
"gpio_pin_job_reject": 22,
"gpio_pin_job_abort": 20
},
"misc": {
"secret_key": "CHANGER_CETTE_CLE_SECRETE",
"_OPTI_secret": "OPTI: Changer la cle par defaut ! Generer avec: python3 -c \"import secrets; print(secrets.token_hex(32))\"",
"cmd_shutdown": "shutdown now",
"cmd_reboot": "reboot"
}
}
+80
View File
@@ -0,0 +1,80 @@
# =============================================================================
# Zoraxy - Configuration reverse proxy pour photomaton
# =============================================================================
# Installation: ~/zoraxy/
# Config: ~/zoraxy/conf/proxy/
# Admin panel: http://127.0.0.1:5487 (localhost only)
# =============================================================================
#
# ROLE DE ZORAXY:
# - Reverse proxy HTTPS devant tous les services
# - Gestion des certificats TLS
# - Necessaire pour le QR code (HTTPS requis sur mobile)
#
# PORTS ZORAXY:
# 80 : HTTP (redirect -> HTTPS)
# 443 : HTTPS (reverse proxy)
# 5487 : Admin panel (localhost only)
# 8001 : Admin panel (expose via sous-domaine)
#
# =============================================================================
# PROXIES CONFIGURES
# =============================================================================
#
# 1. photomaton.lessapinsduweb.com -> 127.0.0.1:8083 (photobooth-app)
# - WebSocket: OUI (EnableWebsocketCustomHeaders: true)
# - ChunkedTransfer: DESACTIVE (necessaire pour photobooth-app)
# - C'est le proxy principal pour les utilisateurs
#
# 2. photomaton-nodered.lessapinsduweb.com -> 127.0.0.1:1880 (Node-RED)
# - Dashboard admin impression
# - Acces: auth propre a Node-RED
#
# 3. photomaton-raspap.lessapinsduweb.com -> 127.0.0.1:8082 (RaspAP/lighttpd)
# - Config WiFi
# - Acces: auth propre a RaspAP
#
# 4. photomaton-zoraxy.lessapinsduweb.com -> 127.0.0.1:8001 (Zoraxy admin)
# - Admin du reverse proxy lui-meme
# - Acces: auth propre a Zoraxy
#
# =============================================================================
# ARCHITECTURE RESEAU COMPLETE
# =============================================================================
#
# [Telephone utilisateur]
# |
# | WiFi "Photomaton" (RaspAP, dnsmasq :53)
# v
# [Zoraxy :443 HTTPS]
# |
# +-- photomaton.lessapinsduweb.com ---------> photobooth-app :8083
# +-- photomaton-nodered.lessapinsduweb.com -> Node-RED :1880
# +-- photomaton-raspap.lessapinsduweb.com --> lighttpd/RaspAP :8082
# +-- photomaton-zoraxy.lessapinsduweb.com --> Zoraxy admin :8001
#
# Services locaux (non proxies):
# - CUPS :631 (impression, acces local)
# - uvicorn :8081 (API backend RaspAP, /etc/raspap/api/)
# - Zoraxy admin :5487 (localhost only)
# - SSH :22
# - rpcbind :111 (INUTILE - a desactiver)
#
# =============================================================================
# NOTES IMPORTANTES
# =============================================================================
#
# - DisableChunkedTransferEncoding: true sur photomaton.lessapinsduweb.com
# C'est necessaire pour que le livestream photobooth fonctionne correctement
#
# - SkipCertValidations: true sur photomaton.lessapinsduweb.com
# Parce que l'upstream est en HTTP, pas HTTPS
#
# - Tous les sous-domaines resolvent vers la meme IP (le Pi)
# via dnsmasq de RaspAP
#
# - rpcbind sur le port 111 est inutile et peut etre desactive:
# sudo systemctl disable rpcbind.service rpcbind.socket
# sudo systemctl stop rpcbind.service rpcbind.socket
#
# =============================================================================
+87
View File
@@ -0,0 +1,87 @@
# Analyse de la configuration photobooth-app
## Problemes identifies et corrections
### CRITIQUES (causent crashs / OOM)
| # | Probleme | Impact | Correction |
|---|----------|--------|------------|
| 1 | `logging_level: "DEBUG"` | CPU+RAM en continu, fichiers logs enormes | -> `WARNING` |
| 2 | `remove_background` actif sur 5/9 actions | MODNet charge ~300-400Mo RAM par traitement | Garder uniquement sur les actions qui changent le fond |
| 3 | `full_still_length: 4608` (14 Mpx) | Image de ~40Mo en RAM par capture | -> `2304` (suffisant pour Selphy 300dpi) |
| 4 | `CAPTURE_CAM_RESOLUTION: 4608x3072` | Le capteur charge l'image pleine res | -> `2304x1536` |
| 5 | `PREVIEW_CAM_RESOLUTION: 2304x1536` | Preview inutilement grande | -> `1152x768` |
| 6 | `original_still_quality: 100` | JPEG non compresse = fichiers 2x plus gros | -> `90` (visuellement identique) |
### Estimation gain RAM avec corrections
```
AVANT (config actuelle):
photobooth-app base ~300 Mo
MODNet charge en RAM ~300 Mo
Image 4608x3072 RGBA ~56 Mo/image
Preview 2304x1536 ~14 Mo
Node-RED (Node.js) ~200 Mo
Chromium ~300 Mo
TOTAL ~1170 Mo (sur 2048 Mo dispo)
-> Marge: ~878 Mo (insuffisant avec traitement)
APRES (config optimisee):
photobooth-app base ~300 Mo
MODNet (si active) ~300 Mo
Image 2304x1536 RGBA ~14 Mo/image (-75%)
Preview 1152x768 ~3 Mo (-78%)
Node-RED DESACTIVE 0 Mo (-200 Mo)
Chromium (optimise) ~200 Mo (-100 Mo)
TOTAL ~817 Mo
-> Marge: ~1231 Mo (suffisant meme avec remove_background)
```
### SECURITE
| # | Probleme | Correction |
|---|----------|------------|
| 7 | `secret_key: "ThisIsTheDefaultSecret"` | Generer une vraie cle: `python3 -c "import secrets; print(secrets.token_hex(32))"` |
| 8 | `admin_password` en clair dans la config | Normal pour photobooth-app, mais ne pas exposer le fichier |
### IMPRESSION
| # | Probleme | Correction |
|---|----------|------------|
| 9 | "Demande d'impression" passe par Node-RED (curl 127.0.0.1:1880) | Remplacer par appel `lp` direct |
| 10 | Script print.sh reference `/home/pi/` | Verifier que le chemin est correct |
| 11 | Actions "Pouet" et "Mailing" inutiles | Supprimees |
### INTERFACE
| # | Probleme | Correction |
|---|----------|------------|
| 12 | Toutes les actions ont `show_button: false` | Au moins 1 action visible (HnB-normal) |
| 13 | `gpio_enabled: false` | -> `true` pour utiliser le GPIO natif |
| 14 | `GALLERY_EMPTY_MSG` en anglais | Traduit en francais |
| 15 | `thumbnail_still_length: 960` | -> `480` (suffisant pour la galerie web) |
### ACTIONS SIMPLIFIEES
**Supprimees** (doublons):
- `background stars` : doublon de `Hnb - Etoile`
- `Hnb - Etoile - copy` : copie de `Hnb - Etoile` avec cadre different
**Actions share supprimees**:
- `Pouet` : action test (echo OK)
- `Mailing action` : email demo (me@mgineer85.de)
- `Printing copies` : doublon de "Impression"
### RESTE A FAIRE
- [ ] Recevoir et analyser `/home/pi/photobooth-data/script/script_print.sh`
- [ ] Remplacer l'appel Node-RED par un appel `lp` direct dans l'action "Demande d'impression"
- [ ] Tester la config optimisee sur le Pi
- [ ] Verifier que le QR share fonctionne avec l'URL `photomaton.lessapinsduweb.com`
- [ ] Generer une vraie `secret_key`
## Fichiers de reference
- Config originale: `config/photobooth-config-backup.json`
- Config optimisee: `config/photobooth-config-optimized.json`
- La config optimisee contient des commentaires `_OPTI_*` pour chaque changement
+222
View File
@@ -0,0 +1,222 @@
# Photomaton - Architecture du projet
Association: Les Sapins Du Web (LSDW)
Events: Hop'N Bloc, anniversaires, etc.
## Hardware
| Composant | Modele | Notes |
|-----------|--------|-------|
| SBC | Raspberry Pi 4 (2 Go RAM) | Contrainte memoire forte |
| Camera | Camera Module 3 officielle | Backend picamera2, ratio 3:2 natif |
| Ecran | HDMI (moniteur/TV) | Interface photobooth-app + Chromium kiosk |
| Bouton | Bouton lumineux 12V + relay | GPIO23 (input) + GPIO12 (relay ON/OFF) |
| LED Strip | NeoPixels 35 LEDs | GPIO18, feedback visuel pendant capture |
| Imprimante 1 | Canon Selphy CP1300 (blanche) | USB, CUPS: Canon_SELPHY_CP1300_usb_blanche |
| Imprimante 2 | Canon Selphy CP1300 (noire) | USB, CUPS: Canon_SELPHY_CP1300_usb_noire |
| WiFi | RaspAP (hotspot dedie) | Pour distribution photos via QR code |
## Software Stack
```
+----------------------------------------------------------+
| Navigateur Web |
| (Chromium kiosk mode, ecran HDMI) |
+----------------------------------------------------------+
| |
| photobooth-app (:8083) Node-RED (:1880) |
| - Capture camera - Bouton GPIO23 (multi-clic) |
| - Post-traitement - Relay bouton GPIO12 |
| - Galerie + QR share - NeoPixels GPIO18 |
| - Interface utilisateur - Dashboard admin impression |
| - File attente SQLite |
| - Gestion CUPS (enable/ |
| disable, cancel jobs) |
+----------------------------------------------------------+
| Zoraxy (reverse proxy + TLS :443) |
+----------------------------------------------------------+
| CUPS + Gutenprint | RaspAP (hotspot WiFi) |
| (impression Selphy x2) | (reseau dedie QR code) |
+----------------------------------------------------------+
| Raspberry Pi OS (Trixie) |
+----------------------------------------------------------+
```
## Flux utilisateur (prise de photo)
```
Bouton physique (GPIO23)
|
v
Node-RED (button-events: clic/double/triple/quadruple)
|
+--> Desactive relay bouton (GPIO12 = OFF)
+--> NeoPixels: vert (countdown)
+--> Appel API photobooth-app: GET /api/actions/image/{N}
|
v
photobooth-app
1. Countdown (5s) affiche sur ecran
2. NeoPixels: blanc (flash) <-- via webhook -> Node-RED
3. Capture Camera Module 3 (picamera2)
4. NeoPixels: violet (captured)
5. Post-traitement (cadre, remove_background si active)
6. Photo ajoutee a la galerie
7. NeoPixels: bleu (finished) puis vert apres 2s
8. Reactive relay bouton (GPIO12 = ON)
9. QR code affiche pour telecharger via WiFi
```
## Flux impression
```
Utilisateur: clic "Demander l'impression" dans galerie photobooth-app
|
v
photobooth-app: share_command = curl Node-RED /api/photobooth-custom/ask_print
|
v
Node-RED:
1. Recupere l'UID de la photo via API photobooth /api/mediacollection
2. Insere dans SQLite (photo-to-print.sqlite)
3. Affiche dans le dashboard admin (/dashboard)
|
v
Admin (sur telephone via WiFi):
- Voit la demande dans le tableau avec preview
- Clic "OK" = lance impression via script_print.sh (crop 3:2 + lp)
- Clic "x" = annule la demande
- Peut aussi cancel/clear les jobs CUPS, enable/disable imprimantes
```
## GPIO
| GPIO | Pin physique | Fonction | Controle par |
|------|-------------|----------|--------------|
| 12 | 32 | Relay bouton 12V (output) | Node-RED |
| 17 | 11 | Shutdown (hold 2s) | photobooth-app |
| 18 | 12 | NeoPixels 35 LEDs (output) | Node-RED (rpi-neopixels) |
| 20 | 38 | Job abort | photobooth-app |
| 22 | 15 | Job reject / Collage GPIO | photobooth-app |
| 23 | 16 | Bouton physique (input, pull-up) | Node-RED (button-events) |
| 27 | 13 | Job next | photobooth-app |
## Modes photo (bouton multi-clic)
| Clics | Mode | Action photobooth-app | remove_background |
|-------|------|----------------------|-------------------|
| 1 | HnB normal | /api/actions/image/0 | Non |
| 2 | HnB etoile | /api/actions/image/1 | Oui |
| 3 | HnB cailloux | /api/actions/image/2 | Oui |
| 4 | HnB light | /api/actions/image/3 | Oui |
| Long press | Demande impression | /api/share/actions/latest/0 | - |
## Ports reseau
| Service | Port | Process | Notes |
|---------|------|---------|-------|
| SSH | 22 | sshd | Acces distant |
| DNS | 53 | dnsmasq | RaspAP, resolution domaines locaux |
| HTTP | 80 | zoraxy | Redirect -> HTTPS |
| HTTPS | 443 | zoraxy | Reverse proxy principal |
| CUPS | 631 | cupsd | Administration imprimantes |
| Node-RED | 1880 | node-red | Dashboard admin + GPIO + NeoPixels |
| Zoraxy admin | 5487 | zoraxy | Panel admin (localhost only) |
| Zoraxy admin | 8001 | zoraxy | Panel admin (expose via sous-domaine) |
| RaspAP API | 8081 | uvicorn/python3 | API backend RaspAP (/etc/raspap/api/) |
| RaspAP | 8082 | lighttpd | Interface admin WiFi |
| photobooth-app | 8083 | python | Interface web principale |
| ~~rpcbind~~ | ~~111~~ | ~~rpcbind~~ | **INUTILE - a desactiver** |
## Domaines (Zoraxy reverse proxy)
| Sous-domaine | Upstream | Service |
|-------------|----------|---------|
| `photomaton.lessapinsduweb.com` | 127.0.0.1:8083 | photobooth-app (WebSocket ON) |
| `photomaton-nodered.lessapinsduweb.com` | 127.0.0.1:1880 | Node-RED dashboard |
| `photomaton-raspap.lessapinsduweb.com` | 127.0.0.1:8082 | RaspAP admin |
| `photomaton-zoraxy.lessapinsduweb.com` | 127.0.0.1:8001 | Zoraxy admin |
- Certificats TLS geres par Zoraxy
- QR share: `https://photomaton.lessapinsduweb.com/sharepage/#?url=...`
- Chaque service gere sa propre authentification
## Reseau WiFi (RaspAP)
| Element | Valeur |
|---------|--------|
| Interface | wlan0 |
| IP du Pi | 10.3.141.1 |
| Plage DHCP | 10.3.141.50 - 10.3.141.254 (~200 clients) |
| Masque | 255.255.255.0 |
| Bail DHCP | 12h |
| DNS | dnsmasq sur le Pi (port 53) |
| Internet | AUCUN (captive portal) |
### Resolution DNS (dnsmasq)
```
# /etc/dnsmasq.d/090_custom_local_hotspot.conf
address=/lessapinsduweb.com/10.3.141.1 # *.lessapinsduweb.com -> Pi
address=/photomaton.lessapinsduweb.com/10.3.141.1 # explicite
address=/#/127.0.0.1 # tout le reste -> null (pas d'internet)
```
Flux QR code :
1. Telephone se connecte au WiFi "Photomaton"
2. Recoit IP en 10.3.141.x via DHCP, DNS = 10.3.141.1
3. Scan QR code -> `https://photomaton.lessapinsduweb.com/sharepage/...`
4. dnsmasq resout vers 10.3.141.1 (le Pi)
5. Zoraxy (:443) sert la page via HTTPS
6. photobooth-app (:8083) fournit la photo
## Fichiers sur le Pi
```
/home/pi/
photobooth-data/
media/
processed_full/ # Photos traitees (pleine resolution)
script/
script_print.sh # Script d'impression avec load-balancing
log/ # Logs quotidiens
photo-to-print.sqlite # File d'attente d'impression (Node-RED)
userdata/
hopnbloc/
frames/ # Cadres PNG 2000x1333 (ratio 3:2)
backgrounds/ # Fonds pour remove_background
LSDW/
lulu-versaire2.png # Cadre anniversaire
logo/logo.jpeg # Logo LSDW
demoassets/
backgrounds/ # Fonds demo (etoiles, etc.)
frames/ # Cadres demo
```
## Fichiers du projet (ce repo)
```
photomaton/
scripts/
optimize-system.sh # Optimisation OS pour 2Go RAM
setup-printer.sh # Configuration CUPS + Selphy CP1300
setup-photobooth.sh # Installation complete
script_print.sh # Script d'impression optimise
health-check.sh # Surveillance sante du systeme
maintenance.sh # Nettoyage automatique quotidien
config/
photobooth-config-backup.json # Config originale (sauvegarde)
photobooth-config-optimized.json # Config optimisee
gpio-notes.conf # Documentation GPIO
zoraxy-notes.conf # Notes Zoraxy
nodered-flows-backup.json # Backup flows Node-RED
systemd/
photobooth-app.service # Service robuste avec auto-restart
photobooth-watchdog.service # Health check
photobooth-watchdog.timer # Toutes les 2 minutes
photobooth-maintenance.service # Nettoyage
photobooth-maintenance.timer # Quotidien a 4h
docs/
ARCHITECTURE.md # Ce fichier
ANALYSE-CONFIG.md # Analyse detaillee des problemes
```
+777
View File
@@ -0,0 +1,777 @@
# Photomaton LSDW - Guide complet
> Projet photomaton de l'association **Les Sapins Du Web** (LSDW)
> Events : Hop'N Bloc, anniversaires, etc.
> Derniere mise a jour : Juin 2026
---
## Table des matieres
1. [Hardware](#1-hardware)
2. [Software Stack](#2-software-stack)
3. [Architecture reseau](#3-architecture-reseau)
4. [Flux utilisateur](#4-flux-utilisateur)
5. [GPIO et bouton physique](#5-gpio-et-bouton-physique)
6. [Configuration photobooth-app](#6-configuration-photobooth-app)
7. [Impression (Canon Selphy CP1300)](#7-impression-canon-selphy-cp1300)
8. [Node-RED](#8-node-red)
9. [Analyse des problemes et optimisations](#9-analyse-des-problemes-et-optimisations)
10. [Guide de deploiement](#10-guide-de-deploiement)
11. [Fichiers du projet](#11-fichiers-du-projet)
12. [Maintenance et surveillance](#12-maintenance-et-surveillance)
13. [Questions en suspens](#13-questions-en-suspens)
---
## 1. Hardware
| Composant | Modele | Notes |
|-----------|--------|-------|
| SBC | Raspberry Pi 4 (**2 Go RAM**) | Contrainte memoire forte |
| OS | Raspberry Pi OS / Debian 13 Trixie | Reference 2025-12-04, pi-gen stage4 |
| Camera | Camera Module 3 officielle | Backend picamera2, ratio 3:2 natif (4608x3072) |
| Ecran | Moniteur/TV HDMI | Chromium kiosk mode (Wayland/labwc) |
| Bouton | Bouton lumineux 12V + relay | GPIO23 (input) + GPIO12 (relay ON/OFF) |
| LED Strip | NeoPixels WS2812 (35 LEDs) | GPIO18, feedback visuel pendant capture |
| Imprimante 1 | Canon Selphy CP1300 (blanche) | USB, CUPS: `Canon_SELPHY_CP1300_usb_blanche` |
| Imprimante 2 | Canon Selphy CP1300 (noire) | USB, CUPS: `Canon_SELPHY_CP1300_usb_noire` |
| WiFi | Chip WiFi interne RPi4 | Hotspot via RaspAP |
### Format d'impression
- Papier : **100 x 150 mm** (carte postale, 4x6 pouces)
- Ratio : **3:2** (identique au capteur Camera Module 3)
- Resolution minimum a 300 dpi : 1770 x 1182 px
- Cadres PNG : **2000 x 1333 px** (ratio 3:2)
---
## 2. Software Stack
### Schema general
```
+----------------------------------------------------------+
| Navigateur Web |
| Chromium kiosk (Wayland, labwc) |
| ~/Desktop/photobooth-app.desktop |
+----------------------------------------------------------+
| |
| photobooth-app (:8083) Node-RED (:1880) |
| via pipx venv via npm |
| - Capture camera - Bouton GPIO23 (multi-clic) |
| - Post-traitement - Relay bouton GPIO12 |
| - Galerie + QR share - NeoPixels GPIO18 |
| - Interface utilisateur - Dashboard admin impression |
| - File attente SQLite |
| - Gestion CUPS |
| |
+----------------------------------------------------------+
| Zoraxy (reverse proxy + TLS :443) |
| ~/zoraxy/ |
+----------------------------------------------------------+
| CUPS + Gutenprint | RaspAP (hotspot WiFi) |
| Canon Selphy CP1300 x2 | lighttpd :8082 |
+----------------------------------------------------------+
| uvicorn :8081 (API backend RaspAP) |
+----------------------------------------------------------+
| Raspberry Pi OS / Debian 13 Trixie |
+----------------------------------------------------------+
```
### Versions et chemins
| Composant | Chemin / Info |
|-----------|--------------|
| photobooth-app | `~/.local/share/pipx/venvs/photobooth-app/` |
| Donnees photobooth | `~/photobooth-data/` |
| Node-RED | `~/.node-red/` |
| Zoraxy | `~/zoraxy/` |
| Config Zoraxy | `~/zoraxy/conf/proxy/` |
| RaspAP API | `/etc/raspap/api/` (uvicorn :8081) |
| Chromium | `/usr/bin/chromium` (pas chromium-browser, Trixie) |
| Kiosk desktop | `~/Desktop/photobooth-app.desktop` |
---
## 3. Architecture reseau
### Ports en ecoute
| Service | Port | Process | Role |
|---------|------|---------|------|
| SSH | 22 | sshd | Acces distant |
| DNS | 53 | dnsmasq | RaspAP, resolution domaines locaux |
| HTTP | 80 | zoraxy | Redirect -> HTTPS |
| HTTPS | 443 | zoraxy | Reverse proxy principal |
| CUPS | 631 | cupsd | Administration imprimantes |
| Node-RED | 1880 | node-red | Dashboard admin + GPIO + NeoPixels |
| Zoraxy admin | 5487 | zoraxy | Panel admin (localhost only) |
| Zoraxy admin | 8001 | zoraxy | Panel admin (expose via sous-domaine) |
| RaspAP API | 8081 | uvicorn/python3 | API backend RaspAP (/etc/raspap/api/) |
| RaspAP | 8082 | lighttpd + php-fpm | Interface admin WiFi |
| photobooth-app | 8083 | python (pipx) | Interface web principale |
| ~~rpcbind~~ | ~~111~~ | ~~rpcbind~~ | **INUTILE - a desactiver** |
### Reverse proxy Zoraxy
Zoraxy ecoute sur les ports 80 (HTTP redirect) et 443 (HTTPS) et route vers les services locaux :
| Sous-domaine | Upstream | Service | Notes |
|-------------|----------|---------|-------|
| `photomaton.lessapinsduweb.com` | 127.0.0.1:8083 | photobooth-app | WebSocket ON, ChunkedTransfer OFF |
| `photomaton-nodered.lessapinsduweb.com` | 127.0.0.1:1880 | Node-RED | Dashboard impression |
| `photomaton-raspap.lessapinsduweb.com` | 127.0.0.1:8082 | RaspAP | Config WiFi |
| `photomaton-zoraxy.lessapinsduweb.com` | 127.0.0.1:8001 | Zoraxy admin | Self-management |
- Certificats TLS geres par Zoraxy
- Chaque service gere sa propre authentification
- `DisableChunkedTransferEncoding: true` sur le proxy photobooth (necessaire pour le livestream)
### WiFi (RaspAP)
| Element | Valeur |
|---------|--------|
| Interface | wlan0 |
| IP du Pi | 10.3.141.1 |
| Plage DHCP | 10.3.141.50 - 10.3.141.254 (~200 clients) |
| Masque | 255.255.255.0 |
| Bail DHCP | 12h |
| DNS | dnsmasq sur le Pi (port 53) |
| Internet | **AUCUN** (captive portal) |
### Resolution DNS (dnsmasq)
```
# /etc/dnsmasq.d/090_custom_local_hotspot.conf
address=/lessapinsduweb.com/10.3.141.1 # *.lessapinsduweb.com -> Pi
address=/photomaton.lessapinsduweb.com/10.3.141.1 # explicite
address=/#/127.0.0.1 # tout le reste -> null (pas d'internet)
```
Les telephones connectes au WiFi ne peuvent acceder qu'aux sous-domaines `*.lessapinsduweb.com`. Tout le reste est bloque (pas d'internet).
---
## 4. Flux utilisateur
### Prise de photo
```
Bouton physique (GPIO23)
|
v
Node-RED (button-events: clic/double/triple/quadruple)
|
+--> Desactive relay bouton (GPIO12 = OFF)
+--> NeoPixels: vert (countdown)
+--> Appel API photobooth-app: GET /api/actions/image/{N}
|
v
photobooth-app
1. Countdown (5s) affiche sur ecran
2. NeoPixels: blanc (flash) <-- webhook -> Node-RED
3. Capture Camera Module 3 (picamera2)
4. NeoPixels: violet (captured)
5. Post-traitement (cadre, remove_background si active)
6. Photo ajoutee a la galerie
7. NeoPixels: bleu (finished) puis vert apres 2s
8. Reactive relay bouton (GPIO12 = ON)
9. QR code affiche pour telecharger via WiFi
```
### Recuperation de la photo (QR code)
```
1. Telephone se connecte au WiFi "Photomaton"
2. Recoit IP en 10.3.141.x via DHCP, DNS = 10.3.141.1
3. Scan QR code -> https://photomaton.lessapinsduweb.com/sharepage/#?url=...
4. dnsmasq resout vers 10.3.141.1 (le Pi)
5. Zoraxy (:443) sert la page via HTTPS
6. photobooth-app (:8083) fournit la photo
```
### Impression
```
Utilisateur: clic "Demander l'impression" dans galerie photobooth-app
|
v
photobooth-app: share_command = curl Node-RED /api/photobooth-custom/ask_print
|
v
Node-RED:
1. Recupere l'UID de la photo via API photobooth /api/mediacollection
2. Insere dans SQLite (photo-to-print.sqlite)
3. Affiche dans le dashboard admin (/dashboard)
|
v
Admin (sur telephone via WiFi, https://photomaton-nodered.lessapinsduweb.com/dashboard):
- Voit la demande dans le tableau avec preview photo
- Clic "OK" = lance impression via script_print.sh
- Clic "x" = annule la demande
- Peut aussi cancel/clear les jobs CUPS, enable/disable imprimantes
|
v
script_print.sh:
1. Verifie le ratio de l'image (crop 3:2 si necessaire)
2. Choisit l'imprimante idle (blanche ou noire, load-balancing)
3. Envoie le job via lp -d PRINTER -o landscape -o fit-to-page
```
---
## 5. GPIO et bouton physique
### Mapping GPIO
| GPIO | Pin physique | Fonction | Direction | Controle par |
|------|-------------|----------|-----------|--------------|
| 12 | 32 | Relay bouton lumineux 12V | Output | Node-RED |
| 17 | 11 | Shutdown (maintien 2s) | Input | photobooth-app |
| 18 | 12 | NeoPixels 35 LEDs WS2812 | Output | Node-RED (neopix.py, sudo) |
| 20 | 38 | Job abort | Input | photobooth-app |
| 22 | 15 | Job reject / Collage trigger | Input | photobooth-app |
| 23 | 16 | Bouton physique principal | Input (pull-up) | Node-RED (button-events) |
| 27 | 13 | Job next | Input | photobooth-app |
### Modes photo (bouton multi-clic sur GPIO23)
| Clics | Mode | Action API | remove_background |
|-------|------|-----------|-------------------|
| 1 clic | HnB normal | `/api/actions/image/0` | Non |
| 2 clics | HnB etoile | `/api/actions/image/1` | Oui |
| 3 clics | HnB cailloux | `/api/actions/image/2` | Oui |
| 4 clics | HnB light | `/api/actions/image/3` | Oui |
| Appui long | Demande impression | `/api/share/actions/latest/0` | - |
Le multi-clic est gere par le node `button-events` de Node-RED avec :
- Intervalle clic : 500 ms
- Intervalle appui long : 2000 ms
- Debounce : 15 ms
### NeoPixels - Couleurs par evenement
| Evenement | Couleur | Brightness |
|-----------|---------|------------|
| Countdown (counting) | Vert `#008000` | 100 |
| Flash (capture) | Blanc `#FFFFFF` | 255 |
| Captured | Violet `#8A2BE2` | 100 |
| Finished | Bleu `#0000FF` | 100 |
| Idle (apres 2s) | Vert `#008000` | 100 |
---
## 6. Configuration photobooth-app
### Parametres critiques (config originale vs optimisee)
| Parametre | Avant (original) | Apres (optimise) | Impact |
|-----------|-----------------|------------------|--------|
| `logging_level` | `DEBUG` | `WARNING` | CPU+RAM en continu |
| `full_still_length` | 4608 | 2304 | /4 RAM par image |
| `CAPTURE_CAM_RESOLUTION` | 4608x3072 | 2304x1536 | /4 RAM capture |
| `PREVIEW_CAM_RESOLUTION` | 2304x1536 | 1152x768 | /4 RAM preview |
| `original_still_quality` | 100 | 90 | -35% taille fichiers |
| `thumbnail_still_length` | 960 | 480 | Moins de RAM galerie |
| `gpio_enabled` | false | true | GPIO natif photobooth-app |
| `secret_key` | `ThisIsTheDefaultSecret` | A changer ! | Securite |
### Justification des resolutions
- Camera Module 3 : capteur 4608x3072 (ratio 3:2)
- Cadres PNG : 2000x1333 (ratio 3:2)
- Impression Selphy 300dpi sur 100x150mm : besoin minimum 1770x1182 px
- **2304x1536 est suffisant** : au-dessus du cadre (2000px) et de l'impression (1770px)
- Ratio 3:2 conserve partout : pas de deformation ni de crop a l'impression
### Actions image (7 actions, config optimisee)
| # | Nom | Cadre | remove_background | Fond | show_button |
|---|-----|-------|-------------------|------|-------------|
| 0 | HnB normal | hnb cadre final.png | Non | Non | **Oui** |
| 1 | HnB etoile | hnb cadre final.png | Oui | background.jpg | Oui |
| 2 | HnB cailloux | hnb cadre final.png | Oui | Wall-with-large-and-small-stones.jpg | Non |
| 3 | HnB light | calque photos final.png | Oui | 2149243965.jpg | Non |
| 4 | Normal image | Aucun | Non | Non | Non |
| 5 | LULU | lulu-versaire2.png | Non | Non | Non |
| 6 | LULU etoile | lulu-versaire2.png | Oui | background.jpg | Non |
Actions supprimees (doublons) : `background stars`, `Hnb - Etoile - copy`
### Actions share (2 actions, config optimisee)
| # | Nom | Commande | Visible | Usage |
|---|-----|----------|---------|-------|
| 0 | Demande d'impression | `curl http://127.0.0.1:1880/api/photobooth-custom/ask_print?filename={filename}` | Oui | File d'attente SQLite via Node-RED |
| 1 | Impression directe | `/home/pi/photobooth-data/script/script_print.sh "{filename}" ...` | Non | Appelee par Node-RED quand l'admin valide |
Actions supprimees : `Pouet` (test), `Mailing action` (demo), `Printing copies` (doublon)
### QR Share
```json
{
"enabled": false,
"enabled_custom": true,
"share_custom_qr_url": "https://photomaton.lessapinsduweb.com/sharepage/#?url=https://photomaton.lessapinsduweb.com/media/full/{identifier}"
}
```
### Kiosk (Chromium)
Fichier : `~/Desktop/photobooth-app.desktop`
```ini
[Desktop Entry]
X-GNOME-Autostart-enabled=true
X-GNOME-Autostart-Delay=120
Terminal=false
Type=Application
Name=Photobooth-App
Exec=bash -c "sleep 60 && chromium --kiosk --accept-lang=fr-FR --incognito
--password-store=basic --disable-features=Translate,OverscrollHistoryNavigation
--noerrdialogs --disable-infobars --no-first-run --ozone-platform=wayland
--enable-features=OverlayScrollbar --start-maximized http://127.0.0.1:8083/"
StartupNotify=false
```
Points importants :
- `--ozone-platform=wayland` : Wayland natif (pas X11)
- Le binaire est `chromium` (pas `chromium-browser`) sous Trixie
- Le desktop file est dans `~/Desktop/`, pas dans `~/.config/autostart/`
---
## 7. Impression (Canon Selphy CP1300)
### Configuration CUPS
| Imprimante | Nom CUPS | Couleur |
|-----------|---------|---------|
| Selphy 1 | `Canon_SELPHY_CP1300_usb_blanche` | Blanche |
| Selphy 2 | `Canon_SELPHY_CP1300_usb_noire` | Noire |
Driver : Gutenprint (`printer-driver-gutenprint`)
### Script d'impression (`script_print.sh`)
Le script optimise fait :
1. **Verifie le ratio** de l'image. Si c'est deja du 3:2 (cas normal), pas de crop. Sinon, crop avec ImageMagick.
2. **Load-balancing** entre les 2 imprimantes :
- Si une est idle, utilise celle-la
- Si aucune n'est idle, choisit celle avec le moins de jobs en attente
- Fallback sur la blanche si les deux sont inaccessibles
3. **Envoie le job** via `lp -n COPIES -d PRINTER -o landscape -o fit-to-page`
4. **Nettoie** le fichier temporaire croppe apres 30 secondes
5. **Logs** via `logger` (visible avec `journalctl -t photomaton-print`)
### Commande d'impression directe (test)
```bash
lp -d Canon_SELPHY_CP1300_usb_blanche -o landscape -o fit-to-page /chemin/vers/photo.jpg
```
### Administration CUPS
- Interface web : `http://localhost:631`
- Via Node-RED dashboard : enable/disable imprimantes, cancel jobs
- Via script : `scripts/cups-cleanup.sh` (genere par `setup-printer.sh`)
---
## 8. Node-RED
### Role dans le systeme
Node-RED est **indispensable** dans le setup actuel. Il gere :
1. **Bouton physique** (GPIO23) : detection multi-clic via `button-events`, appel API photobooth-app
2. **Relay bouton** (GPIO12) : ON/OFF du bouton lumineux 12V pendant les captures
3. **NeoPixels** (GPIO18) : changement de couleur synchronise aux evenements photobooth
4. **Dashboard admin impression** : tableau des demandes, preview photos, boutons imprimer/annuler
5. **File d'attente SQLite** : stockage des demandes d'impression dans `photo-to-print.sqlite`
6. **Gestion CUPS** : etat imprimantes, enable/disable, cancel jobs
### Flows (onglets)
| Tab | Nom | Fonction |
|-----|-----|----------|
| 1 | Photomaton | GPIO bouton, relay, declenchement photo, selection mode |
| 2 | Flux 2 | Dashboard imprimantes (etat, enable/disable, jobs) |
| 3 | Flux 3 | Webhook events photobooth -> NeoPixels + relay bouton |
| 4 | Flux 4 | Controle NeoPixels direct (35 LEDs, GPIO18) |
| 5 | Sqlite | File d'attente impression (SQLite, dashboard, CRUD) |
### Endpoints HTTP (Node-RED)
| Endpoint | Methode | Usage |
|----------|---------|-------|
| `/api/photobooth/*` | GET | Webhook events photobooth (counting, capture, captured, finished) |
| `/api/photobooth-custom/ask_print` | GET | Demande d'impression (appelee par photobooth-app) |
| `/dashboard` | GET | Interface admin (impression, imprimantes, systeme) |
### Base SQLite
- Fichier : `/home/pi/photobooth-data/photo-to-print.sqlite`
- Table : `photoToPrint` (id, filename, uid, toPrint, printed, created_at)
- L'admin voit les photos en attente dans le dashboard avec preview et peut imprimer ou annuler
### Migration vers Python ?
**Non recommande.** Le gain serait de ~100 Mo de RAM, mais le cout de redeveloppement est eleve (dashboard, SQLite CRUD, GPIO multi-clic, NeoPixels sync, gestion CUPS). Les optimisations systeme et config liberent deja ~630 Mo sans rien recoder.
---
## 9. Analyse des problemes et optimisations
### Problemes rapportes
1. **Crashs frequents**
2. **Problemes d'impression**
3. **Memoire insuffisante (2 Go)**
### Consommation RAM mesuree (ps aux, au repos)
| Service | RAM (Mo) | % du total |
|---------|----------|-----------|
| **Chromium** (kiosk + processus fils) | **~580** | 40% |
| **Node-RED** | **171** | 12% |
| **photobooth-app** | **142** | 10% |
| **Zoraxy** | **99** | 7% |
| **Desktop inutile** (pcmanfm, wf-panel, portails, gvfsd...) | **~231** | 16% |
| Autres (labwc, pipewire, CUPS, dnsmasq, lighttpd, php-fpm...) | ~130 | 9% |
| Kernel + systeme | ~100 | 7% |
| **TOTAL** | **~1450** | **79%** |
RAM disponible au repos : **~725 Mo** -- mais ca chute fortement pendant les captures, surtout avec `remove_background`.
### Causes des crashs identifiees
| Cause | Impact | Correction |
|-------|--------|------------|
| `logging_level: DEBUG` | CPU+RAM constant, logs enormes | -> `WARNING` |
| Resolution capture 4608x3072 (14 Mpx) | ~56 Mo par image en RAM | -> 2304x1536 (~14 Mo) |
| `remove_background` (MODNet) sur 5/9 actions | +300-400 Mo RAM par traitement | Garder uniquement quand on change le fond |
| `original_still_quality: 100` | Fichiers JPEG 2x plus gros | -> 90 |
| Desktop inutile (pcmanfm, barre de taches, portails) | ~231 Mo gaspilles | Script `optimize-desktop.sh` |
| Swappiness a 60 | Le systeme swappe trop tot | -> 10 |
| Pas de swap fichier (seulement zram) | OOM kill si zram plein | Ajouter swapfile 1 Go |
### Plan d'optimisation
| Optimisation | Script | Gain RAM estime |
|-------------|--------|----------------|
| Desactiver desktop inutile | `optimize-desktop.sh` | **~231 Mo** |
| Reduire resolution capture | Config photobooth-app | **~42 Mo/capture** |
| Reduire resolution preview | Config photobooth-app | **~11 Mo** |
| `logging_level` DEBUG -> WARNING | Config photobooth-app | Variable (CPU + RAM) |
| JPEG quality 100 -> 90 | Config photobooth-app | -35% I/O disque |
| Desactiver services inutiles | `optimize-system.sh` | **~50 Mo** |
| Swappiness 60 -> 10 | `optimize-system.sh` | Moins de swap inutile |
| Swapfile 1 Go (filet de securite) | `optimize-system.sh` | Evite les OOM kills |
| Limiter Node-RED a 256 Mo (cgroup) | `optimize-system.sh` | Protection fuites memoire |
| Chromium optimise (process-per-site, JS heap) | Config kiosk | Reduction Chromium |
### Estimation apres optimisation
```
AVANT (mesure reelle):
Chromium ~580 Mo
Node-RED ~171 Mo
photobooth-app ~142 Mo
Zoraxy ~99 Mo
Desktop inutile ~231 Mo
Autres ~230 Mo
--------------------------------
TOTAL ~1453 Mo / 1843 Mo dispo
Marge disponible: ~390 Mo
Marge pendant capture: ~0 Mo (crash!)
APRES optimisation:
Chromium (optimise) ~400 Mo (options memoire)
Node-RED (cgroup 256 Mo) ~171 Mo (max 256 Mo)
photobooth-app ~142 Mo
Zoraxy ~99 Mo
Desktop inutile 0 Mo (desactive)
Autres (sans services inut) ~180 Mo
--------------------------------
TOTAL ~992 Mo / 1843 Mo dispo
Marge disponible: ~851 Mo
+ swapfile 1 Go en filet de securite
+ images 4x plus petites en RAM pendant capture
-> Suffisant meme avec remove_background (~300 Mo)
```
---
## 10. Guide de deploiement
### Prerequis
- Acces SSH au Raspberry Pi
- Copier le dossier `photomaton/` sur le Pi
### Etape 1 : Optimisation systeme
```bash
sudo bash scripts/optimize-system.sh
sudo reboot
```
Ce script :
- Ajoute un swapfile 1 Go (complement au zram existant)
- Baisse le swappiness de 60 a 10
- Configure gpu_mem a 128 Mo
- Desactive les services inutiles (bluetooth, ModemManager, rpcbind, apt timers...)
- Desactive le Bluetooth au niveau kernel
- Optimise les parametres memoire kernel (vfs_cache_pressure, dirty_ratio)
- Limite Node-RED a 256 Mo RAM (cgroup systemd)
- Optimise les parametres reseau
### Etape 2 : Optimisation desktop
```bash
sudo bash scripts/optimize-desktop.sh
sudo reboot
```
Ce script :
- Cree un autostart labwc minimal (que Chromium + pipewire)
- Desactive pcmanfm, wf-panel-pi, portails desktop, polkit, gvfsd
- Desactive ModemManager, accounts-daemon, colord, rpcbind, upower
- Reduit les workers php-fpm (RaspAP) a 2
**Reversible** : `sudo bash scripts/optimize-desktop.sh --restore`
### Etape 3 : Script d'impression
```bash
cp scripts/script_print.sh /home/pi/photobooth-data/script/script_print.sh
chmod +x /home/pi/photobooth-data/script/script_print.sh
```
### Etape 4 : Configuration photobooth-app
Deux options :
**Option A** : Appliquer via l'interface admin (`https://photomaton.lessapinsduweb.com/admin`)
- Modifier chaque parametre un par un en suivant le tableau de la section 6
**Option B** : Remplacer le fichier de config
- Copier `config/photobooth-config-optimized.json` dans le dossier de config de photobooth-app
- Supprimer les champs `_comment`, `_OPTI_*` (ce sont des annotations)
- Redemarrer photobooth-app
**Important** : Generer une vraie `secret_key` :
```bash
python3 -c "import secrets; print(secrets.token_hex(32))"
```
### Etape 5 : Services systemd (watchdog + maintenance)
```bash
# Service photobooth-app (utilisateur)
cp systemd/photobooth-app.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable photobooth-app
# Watchdog (systeme)
sudo cp systemd/photobooth-watchdog.service /etc/systemd/system/
sudo cp systemd/photobooth-watchdog.timer /etc/systemd/system/
# Editer les chemins dans les .service si necessaire
sudo systemctl daemon-reload
sudo systemctl enable photobooth-watchdog.timer
sudo systemctl start photobooth-watchdog.timer
# Maintenance quotidienne (systeme)
sudo cp systemd/photobooth-maintenance.service /etc/systemd/system/
sudo cp systemd/photobooth-maintenance.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable photobooth-maintenance.timer
sudo systemctl start photobooth-maintenance.timer
```
### Etape 6 : Verification
```bash
bash scripts/health-check.sh
```
### Quick wins immediats (sans script)
Si tu veux appliquer les corrections les plus impactantes maintenant sans lancer les scripts complets :
```bash
# 1. Baisser le swappiness (gain immediat)
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-photomaton-swap.conf
# 2. Desactiver rpcbind (inutile)
sudo systemctl disable rpcbind.service rpcbind.socket
sudo systemctl stop rpcbind.service rpcbind.socket
# 3. Desactiver ModemManager (inutile)
sudo systemctl disable ModemManager.service
sudo systemctl stop ModemManager.service
# 4. Changer le logging_level dans photobooth-app admin
# http://localhost:8083/admin -> common -> logging_level -> WARNING
```
---
## 11. Fichiers du projet
```
photomaton/
scripts/
optimize-system.sh # Optimisation OS (swap, kernel, services)
optimize-desktop.sh # Desactiver le desktop inutile (~231 Mo)
setup-printer.sh # Configuration CUPS + Selphy CP1300
setup-photobooth.sh # Installation complete (master script)
script_print.sh # Script d'impression optimise (load-balancing)
health-check.sh # Watchdog / surveillance sante
maintenance.sh # Nettoyage automatique quotidien
config/
photobooth-config-backup.json # Config photobooth-app originale
photobooth-config-optimized.json # Config optimisee (avec annotations _OPTI_)
gpio-notes.conf # Documentation GPIO complete
zoraxy-notes.conf # Cartographie reseau Zoraxy
nodered-flows-backup.json # Reference backup flows Node-RED
systemd/
photobooth-app.service # Service robuste (auto-restart, limites RAM)
photobooth-watchdog.service # Health check automatique
photobooth-watchdog.timer # Toutes les 2 minutes
photobooth-maintenance.service # Nettoyage quotidien
photobooth-maintenance.timer # A 4h du matin
docs/
GUIDE-COMPLET.md # Ce fichier
ARCHITECTURE.md # Schema d'architecture
ANALYSE-CONFIG.md # Analyse detaillee des problemes
```
### Fichiers sur le Pi
```
/home/pi/
photobooth-data/
media/
processed_full/ # Photos traitees (pleine resolution)
script/
script_print.sh # Script d'impression
log/ # Logs quotidiens photobooth-app
photo-to-print.sqlite # File d'attente impression (Node-RED)
userdata/
hopnbloc/
frames/ # Cadres PNG 2000x1333 (ratio 3:2)
hnb cadre final.png
calque photos final.png
backgrounds/ # Fonds pour remove_background
Wall-with-large-and-small-stones.jpg
2149243965.jpg
LSDW/
lulu-versaire2.png # Cadre anniversaire
logo/logo.jpeg # Logo LSDW
demoassets/
backgrounds/background.jpg # Fond etoiles
frames/ # Cadres demo
.node-red/ # Node-RED
flows.json # Flows (5 onglets)
node_modules/
node-red-node-pi-gpio/ # GPIO nodes
node-red-node-pi-neopixel/ # NeoPixels nodes
zoraxy/
zoraxy # Binaire
start.sh # Script de demarrage
conf/proxy/ # Configs reverse proxy
ws2812/ # Ancien test NeoPixels (inactif)
Desktop/
photobooth-app.desktop # Autostart Chromium kiosk
```
---
## 12. Maintenance et surveillance
### Watchdog (health-check.sh)
Execute toutes les 2 minutes par `photobooth-watchdog.timer`, verifie :
- **Memoire** : alerte si < 100 Mo dispo, vide les caches si critique
- **Temperature CPU** : alerte si > 70C, erreur si > 80C
- **photobooth-app** : verifie si le service tourne ET si l'interface web repond (HTTP 200)
- **Imprimantes** : verifie CUPS, reactive les imprimantes desactivees, nettoie les jobs bloques
- **Espace disque** : alerte si > 80%, nettoyage d'urgence si > 90%
- **Camera** : verifie que la camera est detectee par libcamera
Si photobooth-app ne repond pas, le watchdog le relance automatiquement (max 3 tentatives).
### Maintenance quotidienne (maintenance.sh)
Execute tous les jours a 4h par `photobooth-maintenance.timer` :
- Nettoyage des vieux logs photobooth (> 7 jours)
- Nettoyage des logs systeme comprimes
- Limitation journald a 50 Mo
- Nettoyage des jobs CUPS bloques
- Reactivation des imprimantes desactivees
- Nettoyage cache apt
- Rapport d'etat (espace disque, nombre de photos, uptime)
### Service systemd photobooth-app
Le service `photobooth-app.service` inclut :
- `Restart=always` avec 5s de delai
- `MemoryMax=1500M` : kill si depasse 1.5 Go (protection OOM)
- `MemoryHigh=1200M` : avertissement a 1.2 Go
- `WatchdogSec=120` : watchdog integre systemd
- `StartLimitBurst=5` / `StartLimitIntervalSec=300` : max 5 redemarrages en 5 min
### Commandes utiles
```bash
# Logs photobooth-app
journalctl --user --unit=photobooth-app -n 200 --no-pager
# Logs impression
journalctl -t photomaton-print --no-pager
# Logs watchdog
journalctl -t photomaton-watchdog --no-pager
# Etat des services
systemctl --user status photobooth-app
systemctl status photobooth-watchdog.timer
systemctl status photobooth-maintenance.timer
# Etat imprimantes
lpstat -p
# Etat memoire
free -h
zramctl
# Etat des processus par RAM
ps aux --sort=-%mem | head -20
# Health check manuel
bash scripts/health-check.sh
```
---
## 13. Questions en suspens
- [x] ~~**NeoPixels doublon**~~ : RESOLU. uvicorn (:8081) est l'API backend de RaspAP (`/etc/raspap/api/`), pas les NeoPixels. Les NeoPixels sont geres uniquement par `neopix.py` via Node-RED. Le dossier `~/ws2812/` est un ancien test inactif.
- [ ] **Tester la config optimisee** en conditions reelles lors d'un event.
- [ ] **Generer une vraie `secret_key`** pour photobooth-app (remplacer `ThisIsTheDefaultSecret`).
- [ ] **Verifier le captive portal** : `address=/#/127.0.0.1` redirige tout domaine inconnu vers localhost du telephone (pas vers le Pi). Les telephones ne detecteront pas automatiquement un portail captif. Si besoin d'une page d'accueil, rediriger vers 10.3.141.1 a la place.
- [ ] **Verifier les cadres PNG** des autres actions (LULU, calque photos final.png) pour confirmer qu'ils sont aussi en ratio 3:2 et en resolution ~2000px.
+872
View File
@@ -0,0 +1,872 @@
# Photomaton LSDW - Guide d'installation from scratch
> Ce guide permet de remonter le photomaton completement a partir d'un Raspberry Pi 4 (2 Go) neuf.
> Reconstitue a partir de l'historique bash du Pi original.
> Derniere mise a jour : Juin 2026
---
## Table des matieres
1. [Prerequis](#1-prerequis)
2. [Installation de l'OS](#2-installation-de-los)
3. [Configuration initiale du Pi](#3-configuration-initiale-du-pi)
4. [Installation de photobooth-app](#4-installation-de-photobooth-app)
5. [Installation de RaspAP (hotspot WiFi)](#5-installation-de-raspap-hotspot-wifi)
6. [Installation de Zoraxy (reverse proxy)](#6-installation-de-zoraxy-reverse-proxy)
7. [Certificat TLS (Let's Encrypt + OVH)](#7-certificat-tls-lets-encrypt--ovh)
8. [Configuration des imprimantes (CUPS + Selphy)](#8-configuration-des-imprimantes-cups--selphy)
9. [Installation de Node-RED](#9-installation-de-node-red)
10. [NeoPixels (WS2812)](#10-neopixels-ws2812)
11. [Configuration du mode kiosk (Chromium)](#11-configuration-du-mode-kiosk-chromium)
12. [Optimisation systeme](#12-optimisation-systeme)
13. [Services systemd](#13-services-systemd)
14. [Verification finale](#14-verification-finale)
15. [Post-installation : contenu a restaurer](#15-post-installation--contenu-a-restaurer)
---
## 1. Prerequis
### Materiel necessaire
- Raspberry Pi 4 (2 Go RAM)
- Carte microSD (32 Go minimum, classe A2 recommandee)
- Camera Module 3 officielle Raspberry Pi
- Ecran HDMI
- Bouton physique lumineux 12V avec relay
- NeoPixels WS2812 (35 LEDs, strip)
- Canon Selphy CP1300 x2 (blanche + noire) + cables USB
- Cable Ethernet (pour l'installation initiale et les certificats)
- Clavier + souris (pour l'installation initiale)
### Logiciels sur le PC
- Raspberry Pi Imager (pour flasher la carte SD)
- Un client SSH (terminal, PuTTY, etc.)
---
## 2. Installation de l'OS
### Flasher la carte SD
1. Telecharger et installer [Raspberry Pi Imager](https://www.raspberrypi.com/software/)
2. Choisir l'OS : **Raspberry Pi OS (64-bit)** base sur Debian 13 Trixie
- Version **Desktop** (pas Lite, on a besoin de l'environnement graphique pour Chromium)
- Architecture **arm64**
3. Dans les options avancees (engrenage) :
- Hostname : `PiPhotobooth`
- Activer SSH (authentification par mot de passe)
- Utilisateur : `pi` / mot de passe : `[choisir un mot de passe]`
- Locale : `fr_FR.UTF-8`, timezone : `Europe/Paris`
- WiFi : ne pas configurer (on utilisera RaspAP)
4. Flasher la carte SD
### Premier demarrage
1. Inserer la carte SD, connecter l'ecran HDMI, le clavier, et le cable Ethernet
2. Demarrer le Pi
3. Attendre la fin du premier boot (peut prendre 2-3 minutes)
4. Se connecter en SSH : `ssh pi@PiPhotobooth.local`
---
## 3. Configuration initiale du Pi
### Mise a jour systeme
```bash
sudo apt update
sudo apt upgrade -y
```
### Configuration avec raspi-config
```bash
sudo raspi-config
```
Configurer :
- **System Options > Boot / Auto Login** : Desktop Autologin (B4)
- **Interface Options > Camera** : Activer (si demande)
- **Localisation Options** : Locale fr_FR.UTF-8, Timezone Europe/Paris
- **Advanced Options > Expand Filesystem** (si pas fait automatiquement)
### Desactiver les services inutiles
```bash
sudo systemctl disable bluetooth.service
sudo systemctl disable wg-quick@wg0.service 2>/dev/null
```
### Desactiver cloud-init (si present)
```bash
sudo touch /etc/cloud/cloud-init.disabled
```
### Configurer /etc/hosts
```bash
sudo nano /etc/hosts
```
Ajouter :
```
127.0.0.1 localhost
127.0.1.1 PiPhotobooth
10.3.141.1 photomaton.lessapinsduweb.com
10.3.141.1 photomaton-nodered.lessapinsduweb.com
10.3.141.1 photomaton-raspap.lessapinsduweb.com
10.3.141.1 photomaton-zoraxy.lessapinsduweb.com
```
Si cloud-init ecrase `/etc/hosts`, editer aussi le template :
```bash
sudo nano /etc/cloud/templates/hosts.debian.tmpl
```
### Redemarrer
```bash
sudo reboot
```
---
## 4. Installation de photobooth-app
### Dependances
```bash
sudo apt -y install ffmpeg libturbojpeg0 libgl1 libgphoto2-dev fonts-noto-color-emoji
sudo apt -y install libexif12 libgphoto2-6 libgphoto2-port12 libltdl7
sudo apt -y install python3-dev
sudo apt -y install pipx
sudo apt -y install imagemagick
```
### Installation via pipx
```bash
pipx ensurepath
# Fermer et rouvrir le terminal (ou source ~/.bashrc)
exit
```
```bash
ssh pi@PiPhotobooth.local
pipx install --system-site-packages photobooth-app --pip-args='--prefer-binary'
```
### Creer le dossier de donnees
```bash
mkdir ~/photobooth-data
```
### Verifier que la camera fonctionne
```bash
rpicam-hello --list-cameras -v
```
### Premier lancement (test)
```bash
cd ~/photobooth-data
photobooth --port 8083
```
Verifier sur `http://PiPhotobooth.local:8083` que l'interface fonctionne. Arreter avec Ctrl+C.
### Configuration de photobooth-app
Le service systemd est installe automatiquement par photobooth-app dans :
`~/.local/share/systemd/user/photobooth-app.service`
Editer pour configurer le port et les dependances :
```bash
nano ~/.local/share/systemd/user/photobooth-app.service
```
Contenu recommande :
```ini
[Unit]
Description=photobooth-app
After=zoraxy.service raspapd.service
[Service]
Type=simple
Restart=always
WorkingDirectory=/home/pi/photobooth-data
ExecStart=/home/pi/.local/share/pipx/venvs/photobooth-app/bin/python -O -m photobooth --port 8083
[Install]
WantedBy=default.target
```
Points importants :
- `After=zoraxy.service raspapd.service` : attend que Zoraxy et RaspAP soient prets
- `--port 8083` : port d'ecoute
- `Restart=always` : relance en cas de crash
Activer et demarrer :
```bash
systemctl --user daemon-reload
systemctl --user enable photobooth-app
systemctl --user start photobooth-app
```
Activer le linger (pour que le service demarre au boot sans login) :
```bash
sudo loginctl enable-linger pi
```
Verifier :
```bash
systemctl --user status photobooth-app
journalctl --user --unit=photobooth-app -n 50 --no-pager
```
### Configuration dans l'interface admin
Acceder a `http://PiPhotobooth.local:8083/admin` et configurer :
- **common** : `logging_level` -> `WARNING` (pas DEBUG !)
- **backends** : Camera Module 3, backend Picamera2, resolution 2304x1536
- **hardwareinputoutput** : `gpio_enabled` -> `true`
- **mediaprocessing** : `full_still_length` -> 2304, `preview_still_length` -> 1200
- **misc** : Changer `secret_key` (generer avec `python3 -c "import secrets; print(secrets.token_hex(32))"`)
Voir `config/photobooth-config-optimized.json` pour la config complete recommandee.
---
## 5. Installation de RaspAP (hotspot WiFi)
### Installation automatique
```bash
curl -sL https://install.raspap.com | bash
```
Suivre les instructions. Repondre oui aux questions par defaut.
Apres l'installation, le Pi redemarrera avec un hotspot WiFi actif.
### Configuration du hotspot
Acceder a l'interface RaspAP : `http://10.3.141.1:8082` (ou `http://PiPhotobooth.local:8082` via Ethernet)
Login par defaut : `admin` / `secret` (changer le mot de passe !)
Configurer :
- **Hotspot > Basic** :
- SSID : `Photomaton` (ou le nom souhaite)
- Security : WPA2
- Mot de passe WiFi : `[choisir]`
- **Hotspot > Advanced** :
- Country code : FR
- Channel : choisir un canal peu encombre
### Configuration DNS (dnsmasq)
Creer le fichier de resolution locale :
```bash
sudo nano /etc/dnsmasq.d/090_custom_local_hotspot.conf
```
Contenu :
```
address=/lessapinsduweb.com/10.3.141.1
address=/photomaton.lessapinsduweb.com/10.3.141.1
address=/#/127.0.0.1
```
Redemarrer dnsmasq :
```bash
sudo systemctl restart dnsmasq.service
```
Verifier :
```bash
dig @127.0.0.1 photomaton.lessapinsduweb.com A
# Doit repondre 10.3.141.1
```
### Changer le port lighttpd (si conflit)
Si lighttpd est sur le port 80 (conflit avec Zoraxy), le changer :
```bash
sudo nano /etc/lighttpd/lighttpd.conf
```
Changer `server.port = 80` en `server.port = 8082` (devrait deja etre fait par RaspAP).
---
## 6. Installation de Zoraxy (reverse proxy)
### Telecharger et installer
```bash
mkdir ~/zoraxy
cd ~/zoraxy
# Telecharger la derniere version arm64
# Verifier la derniere release sur https://github.com/tobychui/zoraxy/releases
wget https://github.com/tobychui/zoraxy/releases/download/v3.3.2/zoraxy_linux_arm64
mv zoraxy_linux_arm64 zoraxy
chmod +x zoraxy
```
### Creer le script de demarrage
```bash
nano ~/zoraxy/start.sh
```
Contenu :
```bash
#!/bin/bash
cd /home/pi/zoraxy
./zoraxy -port=:8001
```
```bash
chmod +x ~/zoraxy/start.sh
```
### Creer le service systemd
```bash
sudo nano /etc/systemd/system/zoraxy.service
```
Contenu :
```ini
[Unit]
Description=Zoraxy Reverse Proxy
After=network.target
[Service]
Type=simple
ExecStart=/home/pi/zoraxy/start.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable zoraxy.service
sudo systemctl start zoraxy.service
```
Verifier :
```bash
sudo systemctl status zoraxy.service
sudo ss -ltnp | grep zoraxy
```
### Configurer les proxies
Acceder a l'admin Zoraxy : `http://127.0.0.1:8001` (ou `http://PiPhotobooth.local:8001` via Ethernet)
Creer les 4 proxies :
**1. photomaton.lessapinsduweb.com -> 127.0.0.1:8083**
- WebSocket Custom Headers : ON
- Disable Chunked Transfer Encoding : ON
- Skip Certificate Validations : ON
**2. photomaton-nodered.lessapinsduweb.com -> 127.0.0.1:1880**
- Options par defaut
**3. photomaton-raspap.lessapinsduweb.com -> 127.0.0.1:8082**
- Options par defaut
**4. photomaton-zoraxy.lessapinsduweb.com -> 127.0.0.1:8001**
- Options par defaut
---
## 7. Certificat TLS (Let's Encrypt + OVH)
> Le certificat est necessaire pour HTTPS. Comme le Pi n'a pas d'acces internet permanent
> (WiFi isole), on utilise le challenge DNS via l'API OVH.
> Cette etape necessite un cable Ethernet connecte a internet.
### Installer certbot avec le plugin OVH
```bash
sudo apt install certbot # ou via venv si le paquet n'existe pas :
# sudo python3 -m venv /opt/certbot/
# sudo /opt/certbot/bin/pip install --upgrade pip
# sudo /opt/certbot/bin/pip install certbot certbot-dns-ovh
# sudo ln -s /opt/certbot/bin/certbot /usr/local/bin/certbot
```
### Configurer les credentials OVH
Creer les credentials API OVH sur https://api.ovh.com/createToken/
```bash
nano ~/.ovhapi
```
Contenu :
```ini
dns_ovh_endpoint = ovh-eu
dns_ovh_application_key = VOTRE_APP_KEY
dns_ovh_application_secret = VOTRE_APP_SECRET
dns_ovh_consumer_key = VOTRE_CONSUMER_KEY
```
```bash
chmod 600 ~/.ovhapi
```
### Generer le certificat
**Important** : Le Pi doit etre connecte a internet via Ethernet pour cette etape.
```bash
sudo certbot certonly \
--dns-ovh \
--dns-ovh-credentials ~/.ovhapi \
-d photomaton.lessapinsduweb.com \
-d photomaton-nodered.lessapinsduweb.com \
-d photomaton-raspap.lessapinsduweb.com \
-d photomaton-zoraxy.lessapinsduweb.com
```
Les certificats sont generes dans `/etc/letsencrypt/live/photomaton.lessapinsduweb.com/`.
### Importer dans Zoraxy
Dans l'admin Zoraxy (`http://127.0.0.1:8001`) :
1. Aller dans TLS / SSL
2. Importer le certificat :
- Certificate : contenu de `/etc/letsencrypt/live/photomaton.lessapinsduweb.com/fullchain.pem`
- Private Key : contenu de `/etc/letsencrypt/live/photomaton.lessapinsduweb.com/privkey.pem`
### Renouvellement
Le certificat expire tous les 90 jours. Pour renouveler :
1. Connecter le Pi a internet via Ethernet
2. Lancer : `sudo certbot renew`
3. Reimporter dans Zoraxy si necessaire
> Note : Ce n'est pas automatise car le Pi n'a pas d'acces internet permanent.
> Penser a renouveler avant chaque saison d'events.
---
## 8. Configuration des imprimantes (CUPS + Selphy)
### Installer les paquets
```bash
sudo apt -y install cups printer-driver-gutenprint cups-client
```
### Configurer les permissions
```bash
sudo usermod -aG lpadmin pi
sudo usermod -aG lp pi
```
### Configurer CUPS pour l'acces distant
```bash
sudo cupsctl --remote-any
sudo systemctl restart cups
```
### Gestion du driver USB
La Selphy CP1300 peut utiliser ipp-usb ou usblp. Pour eviter les conflits :
```bash
# Verifier si ipp-usb est installe
sudo apt list --installed | grep ipp
# Si necessaire, blacklister usblp pour utiliser ipp-usb
# (ou l'inverse selon ce qui fonctionne)
sudo nano /etc/modprobe.d/blacklist-usblp.conf
```
Contenu (si on utilise ipp-usb) :
```
# blacklist usblp
```
### Ajouter les imprimantes
1. Brancher les 2 Canon Selphy CP1300 en USB
2. Aller sur `http://localhost:631` (ou `https://photomaton-raspap.lessapinsduweb.com` via WiFi)
3. Administration > Add Printer
4. Selectionner l'imprimante USB Canon
5. Nommer :
- Premiere (blanche) : `Canon_SELPHY_CP1300_usb_blanche`
- Deuxieme (noire) : `Canon_SELPHY_CP1300_usb_noire`
6. Selectionner le driver Gutenprint
Verification :
```bash
lpstat -v
lpstat -p
```
### Installer le script d'impression
```bash
mkdir -p ~/photobooth-data/script
cp scripts/script_print.sh ~/photobooth-data/script/
chmod +x ~/photobooth-data/script/script_print.sh
```
### Tester l'impression
```bash
# Prendre une photo de test via photobooth-app, puis :
cd ~/photobooth-data/media/processed_full/
lp -d Canon_SELPHY_CP1300_usb_blanche -o landscape -o fit-to-page [nom_fichier].jpg
```
---
## 9. Installation de Node-RED
### Installation via le script officiel
```bash
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
```
Repondre oui aux questions. Le script installe Node.js et Node-RED, et cree le service systemd.
### Activer le service
```bash
sudo systemctl enable nodered.service
sudo systemctl start nodered.service
```
### Installer les nodes necessaires
Acceder a Node-RED : `http://PiPhotobooth.local:1880`
Dans le menu hamburger > Manage palette > Install, installer :
- `node-red-node-pi-gpio` (GPIO input/output)
- `node-red-node-pi-neopixel` (NeoPixels WS2812)
- `node-red-node-sqlite` (base SQLite)
- `node-red-dashboard` ou `@flowfuse/node-red-dashboard` (Dashboard v2)
- `node-red-contrib-button-events` (multi-clic bouton)
### Importer les flows
1. Dans Node-RED, menu hamburger > Import
2. Coller le contenu du fichier de flows (backup dans `config/nodered-flows-backup.json`)
3. Deploy
### Creer la base SQLite
```bash
sudo apt install sqlite3
```
La base est creee automatiquement par Node-RED au premier usage, mais on peut la creer manuellement :
```bash
sqlite3 ~/photobooth-data/photo-to-print.sqlite
```
```sql
CREATE TABLE photoToPrint(
id INT PRIMARY KEY,
filename TEXT NOT NULL,
uid TEXT NOT NULL,
toPrint INT DEFAULT 1,
printed BOOLEAN DEFAULT 0,
created_at TEXT NOT NULL DEFAULT current_timestamp
);
.quit
```
---
## 10. NeoPixels (WS2812)
### Cablage
- Data : GPIO18
- Alimentation : 5V + GND
- 35 LEDs
### Via Node-RED (methode utilisee)
Le node `node-red-node-pi-neopixel` gere les NeoPixels directement. Il lance un script Python en `sudo` :
```
/home/pi/.node-red/node_modules/node-red-node-pi-neopixel/neopix.py 35 0 pixels 100 true 0 18
```
Pas d'installation supplementaire requise au-dela du node Node-RED.
### Note : uvicorn sur le port 8081
Le processus uvicorn sur le port 8081 est l'**API backend de RaspAP** (`/etc/raspap/api/main.py`), pas un service NeoPixels. Il est lance automatiquement par RaspAP et ne doit pas etre desactive.
Le dossier `~/ws2812/` est un ancien test NeoPixels inactif (rien ne le lance). Il peut etre supprime si souhaite.
---
## 11. Configuration du mode kiosk (Chromium)
### Fichier desktop
```bash
nano ~/Desktop/photobooth-app.desktop
```
Contenu :
```ini
[Desktop Entry]
X-GNOME-Autostart-enabled=true
X-GNOME-Autostart-Delay=120
Terminal=false
Type=Application
Name=Photobooth-App
Exec=bash -c "sleep 60 && chromium --kiosk --accept-lang=fr-FR --incognito --password-store=basic --disable-features=Translate,OverscrollHistoryNavigation --noerrdialogs --disable-infobars --no-first-run --ozone-platform=wayland --enable-features=OverlayScrollbar --start-maximized http://127.0.0.1:8083/"
StartupNotify=false
```
### Points importants
- Le binaire est `chromium` (pas `chromium-browser`) sous Debian 13 Trixie
- `--ozone-platform=wayland` : utilisation native de Wayland (le compositeur est labwc)
- Le fichier est dans `~/Desktop/`, pas dans `~/.config/autostart/`
- Le `sleep 60` + `X-GNOME-Autostart-Delay=120` laissent le temps a photobooth-app de demarrer
- `--incognito` : pas de cache persistant, pas de popups "restore session"
### Lien symbolique pour autostart
```bash
sudo ln -s /home/pi/Desktop/photobooth-app.desktop /etc/xdg/autostart/photobooth.desktop
```
---
## 12. Optimisation systeme
### Scripts d'optimisation
Copier le dossier `photomaton/` sur le Pi, puis :
```bash
# Optimisation systeme (swap, kernel, services)
sudo bash scripts/optimize-system.sh
sudo reboot
# Optimisation desktop (desactiver composants inutiles, ~231 Mo)
sudo bash scripts/optimize-desktop.sh
sudo reboot
```
### Optimisations manuelles rapides
Si on ne veut pas lancer les scripts complets :
```bash
# Baisser le swappiness (60 -> 10)
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-photomaton-swap.conf
# Desactiver rpcbind (inutile)
sudo systemctl disable rpcbind.service rpcbind.socket
sudo systemctl stop rpcbind.service rpcbind.socket
# Desactiver ModemManager (inutile)
sudo systemctl disable ModemManager.service
sudo systemctl stop ModemManager.service
# Desactiver bluetooth
sudo systemctl disable bluetooth.service
sudo systemctl stop bluetooth.service
# Ajouter un swapfile de securite (en complement de zram)
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon -p 10 /swapfile
echo '/swapfile none swap sw,pri=10 0 0' | sudo tee -a /etc/fstab
```
---
## 13. Services systemd
### Liste des services du photomaton
| Service | Type | Fichier | Gere par |
|---------|------|---------|----------|
| photobooth-app | User | `~/.local/share/systemd/user/photobooth-app.service` | pipx (auto-installe), After=zoraxy+raspapd |
| nodered | System | `/etc/systemd/system/nodered.service` | Script officiel Node-RED |
| zoraxy | System | `/etc/systemd/system/zoraxy.service` | Manuel (voir section 6) |
| cups | System | (pre-installe) | apt |
| hostapd | System | (pre-installe) | RaspAP |
| dnsmasq | System | (pre-installe) | RaspAP |
| lighttpd | System | (pre-installe) | RaspAP |
| dhcpcd | System | (pre-installe) | RaspAP |
### Services optionnels (surveillance)
```bash
# Watchdog (health check toutes les 2 minutes)
sudo cp systemd/photobooth-watchdog.service /etc/systemd/system/
sudo cp systemd/photobooth-watchdog.timer /etc/systemd/system/
# Editer ExecStart pour pointer vers le bon chemin de health-check.sh
sudo systemctl daemon-reload
sudo systemctl enable photobooth-watchdog.timer
sudo systemctl start photobooth-watchdog.timer
# Maintenance quotidienne (4h du matin)
sudo cp systemd/photobooth-maintenance.service /etc/systemd/system/
sudo cp systemd/photobooth-maintenance.timer /etc/systemd/system/
# Editer ExecStart pour pointer vers le bon chemin de maintenance.sh
sudo systemctl daemon-reload
sudo systemctl enable photobooth-maintenance.timer
sudo systemctl start photobooth-maintenance.timer
```
### Verification des services
```bash
# Tous les services actifs
sudo systemctl list-unit-files --type=service --state=enabled
# Etat de chaque service
systemctl --user status photobooth-app
sudo systemctl status nodered
sudo systemctl status zoraxy
sudo systemctl status cups
sudo systemctl status hostapd
sudo systemctl status dnsmasq
```
---
## 14. Verification finale
### Checklist
```bash
# 1. Tous les services tournent ?
systemctl --user status photobooth-app # active
sudo systemctl status nodered # active
sudo systemctl status zoraxy # active
sudo systemctl status cups # active
sudo systemctl status hostapd # active
sudo systemctl status dnsmasq # active
# 2. Tous les ports ecoutent ?
sudo ss -ltnp
# Attendu : 22, 53, 80, 443, 631, 1880, 5487, 8001, 8082, 8083
# 3. Camera detectee ?
rpicam-hello --list-cameras
# 4. Imprimantes detectees ?
lpstat -v
lpstat -p
# 5. DNS fonctionne ?
dig @127.0.0.1 photomaton.lessapinsduweb.com A
# Doit repondre 10.3.141.1
# 6. HTTPS fonctionne ?
curl -k https://photomaton.lessapinsduweb.com/
# (depuis le Pi lui-meme, ou depuis un telephone connecte au WiFi)
# 7. Memoire ?
free -h
# Verifier que Swap est present (zram + swapfile)
# 8. Health check complet
bash scripts/health-check.sh
```
### Test fonctionnel complet
1. Connecter un telephone au WiFi "Photomaton"
2. Appuyer sur le bouton physique (1 clic)
3. Verifier le countdown sur l'ecran
4. Verifier que la photo est prise et apparait dans la galerie
5. Scanner le QR code avec le telephone
6. Verifier que la photo se telecharge
7. Cliquer "Demander l'impression" dans la galerie
8. Sur le dashboard Node-RED (telephone) : valider l'impression
9. Verifier que la Selphy imprime correctement
---
## 15. Post-installation : contenu a restaurer
### Fichiers a copier depuis une sauvegarde
```bash
# Cadres et fonds
~/photobooth-data/userdata/hopnbloc/
~/photobooth-data/userdata/LSDW/
~/photobooth-data/userdata/demoassets/
# Script d'impression
~/photobooth-data/script/script_print.sh
# Configuration photobooth-app
~/photobooth-data/config/config.json
# Flows Node-RED
~/.node-red/flows.json
# Config Zoraxy (proxies)
~/zoraxy/conf/proxy/*.config
# Config dnsmasq custom
/etc/dnsmasq.d/090_custom_local_hotspot.conf
# Credentials OVH (pour certbot)
~/.ovhapi
```
### Sauvegarder le systeme
Pour eviter de devoir tout refaire, creer une image de la carte SD :
```bash
# Sur le PC (pas sur le Pi), avec la carte SD inseree :
sudo dd if=/dev/sdX of=photomaton-backup-$(date +%Y%m%d).img bs=4M status=progress
```
Ou utiliser `rpi-clone` pour cloner sur une deuxieme carte SD.
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Affiche les actions image photobooth-app en detail.
Usage: echo JSON | python3 display_actions.py [--file backup.json]
"""
import json, sys
def display_actions(actions):
defaults_proc = {
"remove_background": False,
"fill_background_enable": False,
"fill_background_color": "#ededed",
"img_background_enable": False,
"img_background_file": None,
"image_filter": "original",
"img_frame_enable": False,
"img_frame_file": None,
"texts_enable": False,
}
defaults_ui = {
"show_button": False,
"title": "",
"icon": "photo_camera",
"use_custom_color": False,
"custom_color": "#196cb0",
}
for i, a in enumerate(actions):
name = a["name"]
proc = a.get("processing", {})
ui = a.get("trigger", {}).get("ui_trigger", {})
print(f" [{i}] {name}")
proc_diffs = []
for key, default in defaults_proc.items():
val = proc.get(key, default)
if val != default and val is not None:
if key.endswith("_file") and val:
val = val.split("/")[-1]
proc_diffs.append(f"{key}: {val}")
if proc_diffs:
print(f" processing: {', '.join(proc_diffs)}")
show_button = ui.get("show_button", False)
if show_button:
ui_diffs = []
for key, default in defaults_ui.items():
val = ui.get(key, default)
if val != default:
ui_diffs.append(f"{key}: {val}")
if ui_diffs:
print(f" ui_trigger: {', '.join(ui_diffs)}")
print()
print(f" Total: {len(actions)} action(s)")
if __name__ == "__main__":
if "--file" in sys.argv:
filepath = sys.argv[sys.argv.index("--file") + 1]
with open(filepath) as f:
data = json.load(f)
display_actions(data["image"])
else:
data = json.load(sys.stdin)
if "actions" in data:
display_actions(data["actions"]["image"])
elif "image" in data:
display_actions(data["image"])
+242
View File
@@ -0,0 +1,242 @@
#!/bin/bash
# =============================================================================
# health-check.sh - Surveillance de sante du photomaton
# =============================================================================
# Usage: bash health-check.sh
# Appele regulierement par le timer systemd photobooth-watchdog.timer
# =============================================================================
set -uo pipefail
LOG_TAG="photomaton-watchdog"
PHOTOBOOTH_URL="http://localhost:8083"
MAX_RESTART_ATTEMPTS=3
RESTART_COUNT_FILE="/tmp/photomaton-restart-count"
log_info() { logger -t "$LOG_TAG" "[INFO] $1"; echo "[INFO] $1"; }
log_warn() { logger -t "$LOG_TAG" "[WARN] $1"; echo "[WARN] $1"; }
log_error() { logger -t "$LOG_TAG" "[ERROR] $1"; echo "[ERROR] $1"; }
# Initialiser le compteur de redemarrage
if [ ! -f "$RESTART_COUNT_FILE" ]; then
echo "0" > "$RESTART_COUNT_FILE"
fi
restart_service() {
local service="$1"
local count
count=$(cat "$RESTART_COUNT_FILE" 2>/dev/null || echo 0)
if [ "$count" -ge "$MAX_RESTART_ATTEMPTS" ]; then
log_error "Service $service: trop de redemarrages ($count). Intervention manuelle requise."
return 1
fi
log_warn "Redemarrage de $service (tentative $((count + 1))/$MAX_RESTART_ATTEMPTS)..."
echo "$((count + 1))" > "$RESTART_COUNT_FILE"
systemctl --user restart "$service" 2>/dev/null || \
systemctl restart "$service" 2>/dev/null || true
sleep 5
}
# Reset le compteur si l'app tourne bien depuis plus de 10 minutes
reset_restart_counter() {
local uptime_check
uptime_check=$(systemctl --user show photobooth-app --property=ActiveEnterTimestamp 2>/dev/null | cut -d= -f2 || true)
if [ -n "$uptime_check" ]; then
local now
now=$(date +%s)
local started
started=$(date -d "$uptime_check" +%s 2>/dev/null || echo "$now")
local diff=$((now - started))
if [ "$diff" -gt 600 ]; then
echo "0" > "$RESTART_COUNT_FILE"
fi
fi
}
# -------------------------------------------------------------------
# 1. VERIFIER LA MEMOIRE
# -------------------------------------------------------------------
check_memory() {
local mem_available
mem_available=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
local mem_total
mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
# Alerte si moins de 100 Mo disponibles
if [ "$mem_available" -lt 102400 ]; then
log_warn "Memoire faible: ${mem_available}kB disponible sur ${mem_total}kB total"
# Liberer les caches
sync
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
log_info "Caches memoire vides"
# Re-verifier
mem_available=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
if [ "$mem_available" -lt 51200 ]; then
log_error "Memoire critique: ${mem_available}kB - intervention necessaire"
return 1
fi
fi
log_info "Memoire OK: ${mem_available}kB disponible"
return 0
}
# -------------------------------------------------------------------
# 2. VERIFIER LA TEMPERATURE CPU
# -------------------------------------------------------------------
check_temperature() {
local temp_file="/sys/class/thermal/thermal_zone0/temp"
if [ -f "$temp_file" ]; then
local temp
temp=$(cat "$temp_file")
local temp_c=$((temp / 1000))
if [ "$temp_c" -ge 80 ]; then
log_error "Temperature CPU critique: ${temp_c}C - throttling probable!"
return 1
elif [ "$temp_c" -ge 70 ]; then
log_warn "Temperature CPU elevee: ${temp_c}C"
else
log_info "Temperature CPU OK: ${temp_c}C"
fi
fi
return 0
}
# -------------------------------------------------------------------
# 3. VERIFIER PHOTOBOOTH-APP
# -------------------------------------------------------------------
check_photobooth() {
# Verifier si le service tourne
if ! systemctl --user is-active photobooth-app &>/dev/null; then
log_error "photobooth-app n'est pas actif!"
restart_service "photobooth-app"
return 1
fi
# Verifier si l'interface web repond
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$PHOTOBOOTH_URL" 2>/dev/null || echo "000")
if [ "$http_code" = "000" ] || [ "$http_code" = "502" ] || [ "$http_code" = "503" ]; then
log_error "photobooth-app ne repond pas (HTTP $http_code)"
restart_service "photobooth-app"
return 1
fi
log_info "photobooth-app OK (HTTP $http_code)"
reset_restart_counter
return 0
}
# -------------------------------------------------------------------
# 4. VERIFIER CUPS / IMPRIMANTES
# -------------------------------------------------------------------
check_printers() {
if ! systemctl is-active cups &>/dev/null; then
log_error "CUPS n'est pas actif!"
systemctl restart cups 2>/dev/null || true
return 1
fi
# Verifier les imprimantes
local disabled_printers
disabled_printers=$(lpstat -p 2>/dev/null | grep -c "disabled" || echo "0")
if [ "$disabled_printers" -gt 0 ]; then
log_warn "$disabled_printers imprimante(s) desactivee(s), reactivation..."
# Reactiver les imprimantes
for printer in $(lpstat -p 2>/dev/null | grep "disabled" | awk '{print $2}'); do
cupsenable "$printer" 2>/dev/null || true
log_info "Imprimante reactivee: $printer"
done
fi
# Nettoyer les jobs bloques (plus de 10 minutes)
local stuck_jobs
stuck_jobs=$(lpstat -o 2>/dev/null | wc -l || echo "0")
if [ "$stuck_jobs" -gt 5 ]; then
log_warn "$stuck_jobs jobs d'impression en attente, nettoyage..."
cancel -a 2>/dev/null || true
fi
log_info "Imprimantes OK"
return 0
}
# -------------------------------------------------------------------
# 5. VERIFIER L'ESPACE DISQUE
# -------------------------------------------------------------------
check_disk() {
local usage
usage=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$usage" -ge 90 ]; then
log_error "Espace disque critique: ${usage}% utilise"
# Nettoyage d'urgence
# Supprimer les anciens logs
find /var/log -name "*.gz" -mtime +1 -delete 2>/dev/null || true
find /var/log -name "*.[0-9]" -mtime +1 -delete 2>/dev/null || true
# Vider le cache apt
apt-get clean 2>/dev/null || true
log_info "Nettoyage d'urgence effectue"
return 1
elif [ "$usage" -ge 80 ]; then
log_warn "Espace disque: ${usage}% utilise"
else
log_info "Espace disque OK: ${usage}% utilise"
fi
return 0
}
# -------------------------------------------------------------------
# 6. VERIFIER LA CAMERA
# -------------------------------------------------------------------
check_camera() {
# Verifier que la camera est detectee
if command -v libcamera-hello &>/dev/null; then
# Test rapide sans afficher d'image
if ! libcamera-hello --list-cameras 2>/dev/null | grep -q "Available cameras"; then
log_warn "Camera non detectee par libcamera"
return 1
fi
elif [ -e /dev/video0 ]; then
log_info "Camera detectee: /dev/video0"
else
log_warn "Aucun peripherique camera detecte"
return 1
fi
log_info "Camera OK"
return 0
}
# -------------------------------------------------------------------
# EXECUTION
# -------------------------------------------------------------------
echo "=== Health Check Photomaton - $(date) ==="
ERRORS=0
check_memory || ((ERRORS++))
check_temperature || ((ERRORS++))
check_photobooth || ((ERRORS++))
check_printers || ((ERRORS++))
check_disk || ((ERRORS++))
check_camera || ((ERRORS++))
echo ""
if [ "$ERRORS" -eq 0 ]; then
log_info "Tous les checks OK"
else
log_warn "$ERRORS probleme(s) detecte(s)"
fi
exit "$ERRORS"
+143
View File
@@ -0,0 +1,143 @@
#!/bin/bash
# =============================================================================
# maintenance.sh - Maintenance automatique du photomaton
# =============================================================================
# Usage: bash maintenance.sh
# Appele quotidiennement par photobooth-maintenance.timer
# =============================================================================
set -uo pipefail
LOG_TAG="photomaton-maintenance"
PHOTOBOOTH_DATA="${HOME}/photobooth-data"
PHOTOS_DIR="${PHOTOBOOTH_DATA}/media"
LOG_DIR="${PHOTOBOOTH_DATA}/log"
# Nombre de jours de retention des photos (0 = garder indefiniment)
PHOTO_RETENTION_DAYS=${PHOTO_RETENTION_DAYS:-0}
# Nombre de jours de retention des logs
LOG_RETENTION_DAYS=7
# Taille max du dossier photos en Mo (0 = pas de limite)
MAX_PHOTOS_SIZE_MB=${MAX_PHOTOS_SIZE_MB:-0}
log_info() { logger -t "$LOG_TAG" "[INFO] $1"; echo "[INFO] $1"; }
log_warn() { logger -t "$LOG_TAG" "[WARN] $1"; echo "[WARN] $1"; }
echo "=== Maintenance Photomaton - $(date) ==="
# -------------------------------------------------------------------
# 1. NETTOYAGE DES LOGS
# -------------------------------------------------------------------
log_info "Nettoyage des logs..."
# Logs photobooth-app
if [ -d "$LOG_DIR" ]; then
deleted=$(find "$LOG_DIR" -name "*.log" -mtime +${LOG_RETENTION_DAYS} -delete -print 2>/dev/null | wc -l)
log_info " $deleted ancien(s) log(s) photobooth supprime(s)"
fi
# Logs systeme comprimes
find /var/log -name "*.gz" -mtime +3 -delete 2>/dev/null || true
find /var/log -name "*.[0-9]" -mtime +3 -delete 2>/dev/null || true
# Journald - garder seulement 50 Mo
journalctl --vacuum-size=50M 2>/dev/null || true
log_info "Logs nettoyes"
# -------------------------------------------------------------------
# 2. NETTOYAGE DES PHOTOS (si retention configuree)
# -------------------------------------------------------------------
if [ "$PHOTO_RETENTION_DAYS" -gt 0 ] && [ -d "$PHOTOS_DIR" ]; then
log_info "Nettoyage des photos de plus de ${PHOTO_RETENTION_DAYS} jours..."
deleted=$(find "$PHOTOS_DIR" -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.gif" \) \
-mtime +${PHOTO_RETENTION_DAYS} -delete -print 2>/dev/null | wc -l)
log_info " $deleted photo(s) supprimee(s)"
fi
# Limite de taille
if [ "$MAX_PHOTOS_SIZE_MB" -gt 0 ] && [ -d "$PHOTOS_DIR" ]; then
current_size=$(du -sm "$PHOTOS_DIR" 2>/dev/null | awk '{print $1}')
if [ "${current_size:-0}" -gt "$MAX_PHOTOS_SIZE_MB" ]; then
log_warn "Dossier photos: ${current_size}Mo > limite ${MAX_PHOTOS_SIZE_MB}Mo"
log_warn "Suppression des photos les plus anciennes..."
# Supprimer les plus vieux fichiers jusqu'a etre sous la limite
while [ "$(du -sm "$PHOTOS_DIR" | awk '{print $1}')" -gt "$MAX_PHOTOS_SIZE_MB" ]; do
oldest=$(find "$PHOTOS_DIR" -type f \( -name "*.jpg" -o -name "*.png" \) -printf '%T+ %p\n' 2>/dev/null | sort | head -1 | awk '{print $2}')
if [ -n "$oldest" ]; then
rm -f "$oldest"
else
break
fi
done
fi
fi
# -------------------------------------------------------------------
# 3. NETTOYAGE CUPS
# -------------------------------------------------------------------
log_info "Nettoyage des jobs d'impression..."
# Supprimer les jobs termines/en erreur
cancel -a 2>/dev/null || true
# Reactiver les imprimantes si necessaire
for printer in $(lpstat -p 2>/dev/null | grep "disabled" | awk '{print $2}'); do
cupsenable "$printer" 2>/dev/null || true
log_info " Imprimante reactivee: $printer"
done
log_info "Jobs d'impression nettoyes"
# -------------------------------------------------------------------
# 4. NETTOYAGE CACHE SYSTEME
# -------------------------------------------------------------------
log_info "Nettoyage du cache systeme..."
# Cache apt
apt-get clean 2>/dev/null || true
apt-get autoclean 2>/dev/null || true
# Cache thumbnails
rm -rf ~/.cache/thumbnails/* 2>/dev/null || true
# Fichiers temporaires
find /tmp -type f -mtime +1 -delete 2>/dev/null || true
log_info "Cache systeme nettoye"
# -------------------------------------------------------------------
# 5. VERIFICATION DE L'INTEGRITE
# -------------------------------------------------------------------
log_info "Verification de l'integrite du systeme..."
# Verifier le filesystem (sans corriger, juste signaler)
disk_errors=$(dmesg | grep -c "EXT4-fs error" 2>/dev/null || echo "0")
if [ "$disk_errors" -gt 0 ]; then
log_warn " $disk_errors erreur(s) filesystem detectee(s) dans dmesg!"
fi
# Espace disque
disk_usage=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
log_info " Espace disque utilise: ${disk_usage}%"
# Uptime
uptime_info=$(uptime -p)
log_info " Uptime: $uptime_info"
# -------------------------------------------------------------------
# 6. RAPPORT
# -------------------------------------------------------------------
echo ""
echo "=== Rapport de maintenance ==="
echo " Date: $(date)"
echo " Espace disque: ${disk_usage}%"
if [ -d "$PHOTOS_DIR" ]; then
photo_count=$(find "$PHOTOS_DIR" -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.gif" \) 2>/dev/null | wc -l)
photo_size=$(du -sh "$PHOTOS_DIR" 2>/dev/null | awk '{print $1}')
echo " Photos: ${photo_count} fichiers (${photo_size})"
fi
echo " Uptime: $uptime_info"
echo ""
log_info "Maintenance terminee"
+252
View File
@@ -0,0 +1,252 @@
#!/bin/bash
# =============================================================================
# optimize-desktop.sh - Desactiver les composants desktop inutiles en mode kiosk
# =============================================================================
# Usage: bash optimize-desktop.sh
# Gain estime: ~200 Mo de RAM
#
# Ce script configure labwc pour ne lancer QUE Chromium en mode kiosk,
# sans barre de taches, gestionnaire de fichiers, portails desktop, etc.
#
# REVERSIBLE: un fichier de backup est cree, et on peut restaurer avec:
# bash optimize-desktop.sh --restore
# =============================================================================
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || whoami)}"
USER_HOME=$(eval echo "~$REAL_USER")
# -------------------------------------------------------------------
# Mode restauration
# -------------------------------------------------------------------
if [ "${1:-}" = "--restore" ]; then
log_info "Restauration du desktop original..."
if [ -f "$USER_HOME/.config/labwc/autostart.backup" ]; then
cp "$USER_HOME/.config/labwc/autostart.backup" "$USER_HOME/.config/labwc/autostart"
log_info "autostart restaure"
fi
# Reactiver les services
XDG_RUNTIME_DIR="/run/user/$(id -u "$REAL_USER")"
DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus"
for service in xdg-desktop-portal xdg-desktop-portal-gtk xdg-desktop-portal-wlr; do
runuser -u "$REAL_USER" -- env \
XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \
DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
systemctl --user unmask "$service" 2>/dev/null || true
done
log_info "Restauration terminee. Redemarrer pour appliquer."
exit 0
fi
echo "============================================="
echo " Optimisation desktop pour mode kiosk"
echo " Gain estime: ~200 Mo de RAM"
echo "============================================="
echo ""
# -------------------------------------------------------------------
# 1. LABWC AUTOSTART - Ne lancer que Chromium
# -------------------------------------------------------------------
log_info "Configuration de labwc autostart..."
LABWC_DIR="$USER_HOME/.config/labwc"
mkdir -p "$LABWC_DIR"
# Backup de l'autostart original
if [ -f "$LABWC_DIR/autostart" ] && [ ! -f "$LABWC_DIR/autostart.backup" ]; then
cp "$LABWC_DIR/autostart" "$LABWC_DIR/autostart.backup"
log_info "Backup cree: $LABWC_DIR/autostart.backup"
fi
# Creer un autostart minimal pour le mode kiosk
cat > "$LABWC_DIR/autostart" << 'EOF'
#!/bin/bash
# =============================================================================
# labwc autostart - Mode kiosk photomaton
# =============================================================================
# Ne lance que le strict necessaire:
# - pipewire (audio, requis par certains composants)
# - Chromium en mode kiosk
#
# Composants DESACTIVES (gain ~200 Mo RAM):
# - pcmanfm (gestionnaire fichiers desktop)
# - wf-panel-pi (barre de taches)
# - xdg-desktop-portal* (portails desktop)
# - polkit-mate-authentication-agent
# - gvfsd (systeme fichiers virtuels)
# =============================================================================
# Desactiver le screen blanking / DPMS via wlr-randr si disponible
if command -v wlr-randr &>/dev/null; then
wlr-randr --output HDMI-A-1 --on 2>/dev/null || true
fi
# Demarrer pipewire (necessaire pour certains composants systeme)
/usr/bin/pipewire &
sleep 1
/usr/bin/pipewire -c filter-chain.conf &
/usr/bin/wireplumber &
/usr/bin/pipewire-pulse &
# Attendre que photobooth-app soit pret
MAX_WAIT=60
WAITED=0
while ! curl -s -o /dev/null --max-time 2 http://127.0.0.1:8083/ 2>/dev/null; do
sleep 2
WAITED=$((WAITED + 2))
if [ "$WAITED" -ge "$MAX_WAIT" ]; then
break
fi
done
# Lancer Chromium en mode kiosk
chromium \
--kiosk \
--accept-lang=fr-FR \
--incognito \
--password-store=basic \
--ozone-platform=wayland \
--enable-features=OverlayScrollbar \
--disable-features=Translate,OverscrollHistoryNavigation \
--noerrdialogs \
--disable-infobars \
--disable-session-crashed-bubble \
--disable-component-update \
--disable-background-networking \
--disable-sync \
--disable-extensions \
--disable-dev-shm-usage \
--no-first-run \
--start-maximized \
--process-per-site \
--js-flags="--max-old-space-size=128" \
--renderer-process-limit=1 \
"http://127.0.0.1:8083/" &
EOF
chmod +x "$LABWC_DIR/autostart"
chown "$REAL_USER:$REAL_USER" "$LABWC_DIR/autostart"
log_info "labwc autostart kiosk cree"
# -------------------------------------------------------------------
# 2. DESACTIVER LES PORTAILS DESKTOP (services utilisateur)
# -------------------------------------------------------------------
log_info "Desactivation des portails desktop..."
# Ces services sont lances automatiquement par D-Bus
# On les masque pour qu'ils ne demarrent plus
# Utiliser machinectl ou runuser pour eviter les demandes de mot de passe
# quand le script est lance avec sudo
XDG_RUNTIME_DIR="/run/user/$(id -u "$REAL_USER")"
DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus"
for portal_svc in xdg-desktop-portal.service xdg-desktop-portal-gtk.service xdg-desktop-portal-wlr.service; do
runuser -u "$REAL_USER" -- env \
XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \
DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
systemctl --user mask "$portal_svc" 2>/dev/null || true
runuser -u "$REAL_USER" -- env \
XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \
DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
systemctl --user stop "$portal_svc" 2>/dev/null || true
done
log_info "Portails desktop desactives"
# -------------------------------------------------------------------
# 3. DESACTIVER LES SERVICES SYSTEME INUTILES
# -------------------------------------------------------------------
log_info "Desactivation des services systeme inutiles..."
SERVICES_TO_DISABLE=(
# ModemManager - pas de modem
"ModemManager.service"
# accounts-daemon - gestion comptes, inutile en kiosk
"accounts-daemon.service"
# colord - gestion couleurs, inutile
"colord.service"
# rpcbind - NFS/RPC, inutile
"rpcbind.service"
"rpcbind.socket"
# udisks2 - gestion disques amovibles, inutile en kiosk
# "udisks2.service" # Garder si on branche des cles USB
# upower - gestion alimentation, inutile sur Pi fixe
"upower.service"
)
for service in "${SERVICES_TO_DISABLE[@]}"; do
if systemctl is-enabled "$service" &>/dev/null 2>&1; then
systemctl disable "$service" 2>/dev/null || true
systemctl stop "$service" 2>/dev/null || true
log_info " Desactive: $service"
elif systemctl is-active "$service" &>/dev/null 2>&1; then
systemctl stop "$service" 2>/dev/null || true
log_info " Arrete: $service"
fi
done
# blkmapd - NFS block mapping, inutile
systemctl disable blkmapd.service 2>/dev/null || true
systemctl stop blkmapd.service 2>/dev/null || true
log_info "Services systeme inutiles desactives"
# -------------------------------------------------------------------
# 4. DESACTIVER PHP-FPM SI RASPAP N'EN A PAS BESOIN IMMEDIAT
# -------------------------------------------------------------------
# Note: RaspAP utilise php-fpm via lighttpd
# On le garde car RaspAP en a besoin pour l'interface admin
# Mais on peut reduire les workers
if [ -f /etc/php/8.4/fpm/pool.d/www.conf ]; then
log_info "Optimisation php-fpm (reduction des workers)..."
sed -i 's/^pm.max_children = .*/pm.max_children = 2/' /etc/php/8.4/fpm/pool.d/www.conf 2>/dev/null || true
sed -i 's/^pm.start_servers = .*/pm.start_servers = 1/' /etc/php/8.4/fpm/pool.d/www.conf 2>/dev/null || true
sed -i 's/^pm.min_spare_servers = .*/pm.min_spare_servers = 1/' /etc/php/8.4/fpm/pool.d/www.conf 2>/dev/null || true
sed -i 's/^pm.max_spare_servers = .*/pm.max_spare_servers = 1/' /etc/php/8.4/fpm/pool.d/www.conf 2>/dev/null || true
systemctl restart php8.4-fpm 2>/dev/null || true
log_info "php-fpm optimise (max 2 workers)"
fi
# -------------------------------------------------------------------
# RESUME
# -------------------------------------------------------------------
echo ""
echo "============================================="
echo " Optimisation desktop terminee !"
echo "============================================="
echo ""
echo "Composants desactives:"
echo " - pcmanfm (gestionnaire fichiers) ~38 Mo"
echo " - wf-panel-pi (barre de taches) ~37 Mo"
echo " - xdg-desktop-portal* (portails) ~77 Mo"
echo " - polkit-mate-auth-agent ~13 Mo"
echo " - gvfsd + volume monitors ~30 Mo"
echo " - ModemManager ~6 Mo"
echo " - accounts-daemon ~7 Mo"
echo " - colord ~10 Mo"
echo " - rpcbind + blkmapd ~4 Mo"
echo " - upower ~9 Mo"
echo " ----------------------------------------"
echo " Total estime: ~231 Mo"
echo ""
echo -e "${YELLOW}IMPORTANT: Redemarrer pour appliquer les changements${NC}"
echo " sudo reboot"
echo ""
echo "Pour restaurer le desktop original:"
echo " bash $0 --restore"
echo " sudo reboot"
echo ""
+374
View File
@@ -0,0 +1,374 @@
#!/bin/bash
# =============================================================================
# optimize-system.sh - Optimisation Raspberry Pi 4 (2 Go RAM) pour photomaton
# =============================================================================
# Usage: sudo bash optimize-system.sh
# A executer UNE SEULE FOIS lors du setup initial, puis reboot
# =============================================================================
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
if [ "$EUID" -ne 0 ]; then
log_error "Ce script doit etre execute en root (sudo)"
exit 1
fi
echo "============================================="
echo " Optimisation systeme pour Photomaton"
echo " Raspberry Pi 4 - 2 Go RAM"
echo " Raspberry Pi OS / Debian 13 Trixie"
echo "============================================="
echo ""
# -------------------------------------------------------------------
# 0. DETECTION DE L'ENVIRONNEMENT
# -------------------------------------------------------------------
log_info "Detection de l'environnement..."
# Config.txt : priorite a /boot/firmware/config.txt (Trixie)
CONFIG_FILE=""
if [ -f /boot/firmware/config.txt ]; then
CONFIG_FILE="/boot/firmware/config.txt"
elif [ -f /boot/config.txt ]; then
CONFIG_FILE="/boot/config.txt"
fi
log_info " config.txt: ${CONFIG_FILE:-'non trouve'}"
# Chromium : s'appelle 'chromium' sous Trixie (pas 'chromium-browser')
CHROMIUM_BIN=""
if command -v chromium &>/dev/null; then
CHROMIUM_BIN="chromium"
elif command -v chromium-browser &>/dev/null; then
CHROMIUM_BIN="chromium-browser"
fi
log_info " Chromium: ${CHROMIUM_BIN:-'non trouve'}"
# OS
OS_PRETTY=$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d'"' -f2 || echo "inconnu")
log_info " OS: $OS_PRETTY"
echo ""
# -------------------------------------------------------------------
# 1. SWAP - Augmenter pour compenser les 2 Go de RAM
# -------------------------------------------------------------------
log_info "Configuration du swap..."
# Verifier si zram est deja actif
if zramctl 2>/dev/null | grep -q "zram"; then
log_info "zram deja actif, on le conserve"
else
# Installer zram si pas present (voir section 5)
log_info "zram non detecte, sera installe plus bas"
fi
# Ajouter un swap fichier sur disque en COMPLEMENT de zram
# C'est un filet de securite: si zram est plein, le systeme utilise le disque
# au lieu de faire un OOM kill (crash)
if ! swapon --show | grep -q "/swapfile"; then
if [ ! -f /swapfile ]; then
log_info "Creation d'un swapfile de 1 Go (filet de securite en complement de zram)..."
fallocate -l 1G /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count=1024
chmod 600 /swapfile
mkswap /swapfile
log_info "Swapfile 1 Go cree"
fi
swapon -p 10 /swapfile 2>/dev/null || true
# Ajouter au fstab si pas deja present
if ! grep -q '/swapfile' /etc/fstab; then
echo '/swapfile none swap sw,pri=10 0 0' >> /etc/fstab
fi
log_info "Swapfile active avec priorite 10 (zram priorite 100 = utilise en premier)"
else
log_info "Swapfile deja actif"
fi
# Reduire le swappiness (60 par defaut est trop eleve pour 2 Go)
# 10 = le systeme utilise la RAM au maximum avant de swapper
echo 'vm.swappiness=10' > /etc/sysctl.d/99-photomaton-swap.conf
sysctl -p /etc/sysctl.d/99-photomaton-swap.conf 2>/dev/null || true
log_info "Swappiness regle a 10 (etait probablement a 60)"
# -------------------------------------------------------------------
# 2. GPU MEMORY - Allouer assez pour la camera sans gaspiller
# -------------------------------------------------------------------
log_info "Configuration memoire GPU..."
if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
# 128 Mo pour la camera Module 3 (minimum recommande)
# Ne pas mettre plus, chaque Mo compte avec 2 Go de RAM
if grep -q '^gpu_mem=' "$CONFIG_FILE"; then
sed -i 's/^gpu_mem=.*/gpu_mem=128/' "$CONFIG_FILE"
else
echo 'gpu_mem=128' >> "$CONFIG_FILE"
fi
# S'assurer que la camera est activee (pour libcamera)
# Sur Bookworm+, camera_auto_detect est par defaut
if ! grep -q 'camera_auto_detect' "$CONFIG_FILE"; then
echo 'camera_auto_detect=1' >> "$CONFIG_FILE"
fi
log_info "GPU memory = 128 Mo, camera auto-detect active"
else
log_warn "Fichier config.txt non trouve, configuration GPU manuelle requise"
fi
# -------------------------------------------------------------------
# 3. DESACTIVER LES SERVICES INUTILES pour liberer la RAM
# -------------------------------------------------------------------
log_info "Desactivation des services inutiles..."
# Liste des services a desactiver (adapter selon tes besoins)
SERVICES_TO_DISABLE=(
# Bluetooth (inutile pour le photomaton)
"bluetooth.service"
"hciuart.service"
# ModemManager (pas de modem)
"ModemManager.service"
# Triggerhappy (raccourcis clavier inutiles)
"triggerhappy.service"
# Avahi (decouverte reseau, peut interrerer)
# "avahi-daemon.service" # Garder si CUPS en a besoin
# Apt services automatiques (consomment RAM/CPU en arriere-plan)
"apt-daily.timer"
"apt-daily-upgrade.timer"
"apt-daily.service"
"apt-daily-upgrade.service"
# Man-db auto update
"man-db.timer"
# Packagekit (si installe)
"packagekit.service"
# RPC bind (NFS/RPC, totalement inutile pour un photomaton)
"rpcbind.service"
"rpcbind.socket"
)
for service in "${SERVICES_TO_DISABLE[@]}"; do
if systemctl is-enabled "$service" &>/dev/null; then
systemctl disable "$service" 2>/dev/null || true
systemctl stop "$service" 2>/dev/null || true
log_info " Desactive: $service"
fi
done
# Desactiver le Bluetooth au niveau kernel aussi
if [ -f "$CONFIG_FILE" ]; then
if ! grep -q 'dtoverlay=disable-bt' "$CONFIG_FILE"; then
echo 'dtoverlay=disable-bt' >> "$CONFIG_FILE"
log_info " Bluetooth desactive au niveau kernel"
fi
fi
# -------------------------------------------------------------------
# 4. OPTIMISATION MEMOIRE - Parametres kernel
# -------------------------------------------------------------------
log_info "Optimisation parametres memoire kernel..."
cat > /etc/sysctl.d/99-photomaton-memory.conf << 'EOF'
# Reduire la pression memoire cache
vm.vfs_cache_pressure=200
# Ecriture sur disque plus frequente (eviter accumulation en RAM)
vm.dirty_ratio=10
vm.dirty_background_ratio=5
# Limiter la reserve memoire minimale
vm.min_free_kbytes=16384
# OOM killer plus agressif sur les gros processus
vm.oom_kill_allocating_task=1
EOF
sysctl -p /etc/sysctl.d/99-photomaton-memory.conf 2>/dev/null || true
log_info "Parametres memoire appliques"
# -------------------------------------------------------------------
# 5. ZRAM - Compression memoire (doublement effectif de la RAM)
# -------------------------------------------------------------------
log_info "Verification de zram..."
if zramctl 2>/dev/null | grep -q "zram"; then
log_info "zram deja actif et fonctionnel ($(zramctl --output NAME,ALGORITHM,DISKSIZE --noheadings))"
log_info "Aucune modification necessaire"
else
log_info "zram non detecte, installation..."
if ! dpkg -l | grep -q zram-tools; then
apt-get update -qq
apt-get install -y -qq zram-tools
fi
cat > /etc/default/zramswap << 'EOF'
# Utiliser 50% de la RAM pour zram (compresse ~2x, donc ~2Go supplementaires)
ALGO=zstd
PERCENT=50
PRIORITY=100
EOF
fi
systemctl enable zramswap 2>/dev/null || true
systemctl restart zramswap 2>/dev/null || true
log_info "zram configure (compression memoire active)"
# -------------------------------------------------------------------
# 6. TMPFS - Reduire les ecritures disque et accelerer
# -------------------------------------------------------------------
log_info "Configuration tmpfs..."
# Monter /tmp en RAM (accelere les fichiers temporaires)
if ! grep -q 'tmpfs.*/tmp' /etc/fstab; then
echo 'tmpfs /tmp tmpfs defaults,noatime,nosuid,nodev,size=256M 0 0' >> /etc/fstab
log_info " /tmp monte en tmpfs (256 Mo)"
fi
# Logs en RAM pour reduire les ecritures SD (optionnel mais recommande)
if ! grep -q 'tmpfs.*/var/log' /etc/fstab; then
echo 'tmpfs /var/log tmpfs defaults,noatime,nosuid,nodev,size=64M 0 0' >> /etc/fstab
log_info " /var/log monte en tmpfs (64 Mo) - ATTENTION: logs perdus au reboot"
fi
# -------------------------------------------------------------------
# 7. CHROMIUM / NAVIGATEUR - Optimisation kiosk mode
# -------------------------------------------------------------------
log_info "Creation du script de lancement Chromium kiosk..."
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
KIOSK_SCRIPT="${SCRIPT_DIR}/../config/chromium-kiosk.sh"
cat > "$KIOSK_SCRIPT" << 'KIOSK_EOF'
#!/bin/bash
# Lancement de Chromium en mode kiosk pour photomaton
# Raspberry Pi OS Trixie (Wayland)
# A utiliser dans un fichier .desktop dans ~/Desktop/
# Attendre que photobooth-app soit pret
MAX_WAIT=60
WAITED=0
while ! curl -s -o /dev/null --max-time 2 http://127.0.0.1:8083/ 2>/dev/null; do
sleep 2
WAITED=$((WAITED + 2))
if [ "$WAITED" -ge "$MAX_WAIT" ]; then
echo "photobooth-app non pret apres ${MAX_WAIT}s, lancement quand meme"
break
fi
done
# Detecter le binaire Chromium (chromium sous Trixie, chromium-browser avant)
CHROMIUM=$(command -v chromium || command -v chromium-browser || echo "chromium")
# Lancer Chromium en mode kiosk
# - Wayland natif (ozone-platform=wayland)
# - Optimisations memoire pour Pi 4 2Go
"$CHROMIUM" \
--kiosk \
--accept-lang=fr-FR \
--incognito \
--password-store=basic \
--ozone-platform=wayland \
--enable-features=OverlayScrollbar \
--disable-features=Translate,OverscrollHistoryNavigation \
--noerrdialogs \
--disable-infobars \
--disable-session-crashed-bubble \
--disable-component-update \
--disable-background-networking \
--disable-sync \
--disable-extensions \
--disable-dev-shm-usage \
--no-first-run \
--start-maximized \
--process-per-site \
--js-flags="--max-old-space-size=128" \
--renderer-process-limit=1 \
"http://127.0.0.1:8083/"
KIOSK_EOF
chmod +x "$KIOSK_SCRIPT"
log_info "Script kiosk Chromium cree"
# -------------------------------------------------------------------
# 8. OVERCLOCK LEGER (optionnel, stable)
# -------------------------------------------------------------------
log_info "Configuration overclock leger..."
if [ -f "$CONFIG_FILE" ]; then
# Overclock leger et stable pour le Pi 4
# over_voltage=2 est tres conservateur
if ! grep -q '^arm_freq=' "$CONFIG_FILE"; then
cat >> "$CONFIG_FILE" << 'EOF'
# Overclock leger pour photomaton (stable)
# Decommenter pour activer:
#arm_freq=1800
#over_voltage=2
EOF
log_info "Overclock leger prepare (desactive par defaut, decommenter si souhaite)"
fi
fi
# -------------------------------------------------------------------
# 9. CGROUPS - Limiter la memoire de Node-RED si encore utilise
# -------------------------------------------------------------------
log_info "Configuration des limites memoire..."
# Si Node-RED est installe, creer un override pour limiter sa RAM
if systemctl is-enabled nodered.service &>/dev/null 2>&1; then
mkdir -p /etc/systemd/system/nodered.service.d/
cat > /etc/systemd/system/nodered.service.d/memory-limit.conf << 'EOF'
[Service]
MemoryMax=256M
MemorySwapMax=128M
EOF
systemctl daemon-reload
log_info "Node-RED limite a 256 Mo RAM max"
fi
# -------------------------------------------------------------------
# 10. RESEAU - Optimiser pour le WiFi hotspot
# -------------------------------------------------------------------
log_info "Optimisation reseau..."
cat > /etc/sysctl.d/99-photomaton-network.conf << 'EOF'
# Optimiser pour servir les photos via WiFi
net.core.somaxconn=256
net.ipv4.tcp_fastopen=3
net.core.netdev_max_backlog=1000
EOF
sysctl -p /etc/sysctl.d/99-photomaton-network.conf 2>/dev/null || true
log_info "Parametres reseau optimises"
# -------------------------------------------------------------------
# RESUME
# -------------------------------------------------------------------
echo ""
echo "============================================="
echo " Optimisation terminee !"
echo "============================================="
echo ""
echo "Environnement: $OS_PRETTY"
echo "config.txt: ${CONFIG_FILE:-'non trouve'}"
echo ""
echo "Modifications appliquees:"
echo " - Swapfile 1 Go ajoute (filet de securite en complement de zram)"
echo " - Swappiness reduit a 10"
echo " - GPU memory = 128 Mo (si config.txt present)"
echo " - Services inutiles desactives"
echo " - Bluetooth desactive"
echo " - zram conserve/installe (compression memoire)"
echo " - tmpfs pour /tmp et /var/log"
echo " - Parametres memoire kernel optimises"
echo " - Script kiosk Chromium cree (${CHROMIUM_BIN:-chromium})"
echo " - Limites memoire Node-RED (si present)"
echo ""
echo -e "${YELLOW}IMPORTANT: Redemarrer le Raspberry Pi pour appliquer tous les changements${NC}"
echo " sudo reboot"
echo ""
echo -e "${YELLOW}NOTE: /var/log est en tmpfs - les logs seront perdus au reboot.${NC}"
echo " Si vous voulez conserver les logs, commentez la ligne tmpfs /var/log dans /etc/fstab"
echo ""
+761
View File
@@ -0,0 +1,761 @@
#!/bin/bash
# =============================================================================
# photobooth-config.sh - Gestion rapide de la config photobooth-app
# =============================================================================
# Usage:
# bash photobooth-config.sh quality high|low|status
# bash photobooth-config.sh livestream on|off|status
# bash photobooth-config.sh frontpage manuel|direct|noprint|status
# bash photobooth-config.sh restart
# =============================================================================
set -uo pipefail
API_URL="http://127.0.0.1:8083"
API_USER="admin"
API_PASS="PhotoBooth2026!"
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'
# --- Auth ---
get_token() {
curl -s -X POST "${API_URL}/api/admin/auth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=${API_USER}&password=${API_PASS}" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])'
}
get_config() {
local token="$1"
curl -s "${API_URL}/api/admin/config/app" \
-H "Authorization: Bearer ${token}"
}
set_config() {
local token="$1"
local config="$2"
curl -s -X PATCH "${API_URL}/api/admin/config/app" \
-H "Authorization: Bearer ${token}" \
-H "Content-Type: application/json" \
-d "${config}" > /dev/null
}
# =============================================================================
# QUALITE
# =============================================================================
quality_status() {
local token
token=$(get_token)
local config
config=$(get_config "$token")
local capture_w quality
capture_w=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["backends"]["group_backends"][0]["backend_config"]["CAPTURE_CAM_RESOLUTION_WIDTH"])')
quality=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["backends"]["group_backends"][0]["backend_config"]["original_still_quality"])')
if [ "$capture_w" -ge 4000 ]; then
echo -e "Qualite: ${GREEN}HAUTE${NC} (${capture_w}px, quality ${quality})"
else
echo -e "Qualite: ${YELLOW}BASSE${NC} (${capture_w}px, quality ${quality})"
fi
}
quality_high() {
local token
token=$(get_token)
set_config "$token" '{
"backends": {
"group_backends": [{
"enabled": true,
"description": "pi camera module V3",
"backend_config": {
"orientation": "1: 0°",
"backend_type": "Picamera2",
"camera_num": 0,
"CAPTURE_CAM_RESOLUTION_WIDTH": 4608,
"CAPTURE_CAM_RESOLUTION_HEIGHT": 3072,
"PREVIEW_CAM_RESOLUTION_WIDTH": 2304,
"PREVIEW_CAM_RESOLUTION_HEIGHT": 1536,
"LIVEVIEW_RESOLUTION_WIDTH": 750,
"LIVEVIEW_RESOLUTION_HEIGHT": 500,
"framerate_still_mode": 20,
"framerate_video_mode": 25,
"frame_skip_count": 4,
"optimized_lowlight_short_exposure": false,
"videostream_quality": "MEDIUM",
"original_still_quality": 100
}
}]
},
"mediaprocessing": {
"full_still_length": 4608,
"preview_still_length": 2304,
"thumbnail_still_length": 960
}
}'
echo -e "${GREEN}Qualite HAUTE activee${NC} (4608x3072, quality 100)"
echo " -> systemctl --user restart photobooth-app"
}
quality_low() {
local token
token=$(get_token)
set_config "$token" '{
"backends": {
"group_backends": [{
"enabled": true,
"description": "pi camera module V3",
"backend_config": {
"orientation": "1: 0°",
"backend_type": "Picamera2",
"camera_num": 0,
"CAPTURE_CAM_RESOLUTION_WIDTH": 2304,
"CAPTURE_CAM_RESOLUTION_HEIGHT": 1536,
"PREVIEW_CAM_RESOLUTION_WIDTH": 1152,
"PREVIEW_CAM_RESOLUTION_HEIGHT": 768,
"LIVEVIEW_RESOLUTION_WIDTH": 750,
"LIVEVIEW_RESOLUTION_HEIGHT": 500,
"framerate_still_mode": 20,
"framerate_video_mode": 25,
"frame_skip_count": 4,
"optimized_lowlight_short_exposure": false,
"videostream_quality": "MEDIUM",
"original_still_quality": 90
}
}]
},
"mediaprocessing": {
"full_still_length": 2304,
"preview_still_length": 1200,
"thumbnail_still_length": 480
}
}'
echo -e "${YELLOW}Qualite BASSE activee${NC} (2304x1536, quality 90)"
echo " -> systemctl --user restart photobooth-app"
}
# =============================================================================
# LIVESTREAM
# =============================================================================
livestream_status() {
local token
token=$(get_token)
local config
config=$(get_config "$token")
local idle_stream
idle_stream=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["uisettings"]["enable_livestream_when_idle"])')
if [ "$idle_stream" = "True" ]; then
echo -e "Livestream idle: ${GREEN}ACTIVE${NC}"
else
echo -e "Livestream idle: ${RED}DESACTIVE${NC}"
fi
}
livestream_on() {
local token
token=$(get_token)
set_config "$token" '{
"uisettings": {
"enable_livestream_when_idle": true
}
}'
echo -e "${GREEN}Livestream idle ACTIVE${NC}"
echo " Les visiteurs voient la camera en direct sur l'ecran d'accueil"
}
livestream_off() {
local token
token=$(get_token)
set_config "$token" '{
"uisettings": {
"enable_livestream_when_idle": false
}
}'
echo -e "${RED}Livestream idle DESACTIVE${NC}"
echo " L'ecran d'accueil affiche le slideshow"
}
# =============================================================================
# FRONTPAGE TEXT
# =============================================================================
frontpage_status() {
local token
token=$(get_token)
local config
config=$(get_config "$token")
local text
text=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);t=c["uisettings"]["FRONTPAGE_TEXT"];print("impression" if "Impression" in t else "noprint" if "Appui long" not in t else "unknown")')
echo -e "Frontpage: ${CYAN}${text}${NC}"
}
# --- HTML templates ---
FRONTPAGE_MANUEL='<div> <div class="fixed-top-right q-mt-lg q-mr-lg" style="pointer-events: none; max-width: 400px;"> <div class="q-pa-md" style="background: rgba(226,235,242,0.85); border-radius: 12px; backdrop-filter: blur(4px); border: 2px solid #89a9c2;"> <div class="text-h3 text-weight-bold" style="color: #335e7e;">Bienvenue !</div> <div style="font-size: 1.3rem; line-height: 1.6; color: #335e7e; margin-top: 10px;"> <div>Appui court = Photo</div> <div>Appui long (3s) = Demande d'\''impression</div> </div> </div> </div> <div class="fixed-bottom-left q-mb-sm q-ml-lg" style="pointer-events: none;"> <div class="q-pa-sm" style="background: rgba(226,235,242,0.75); border-radius: 8px; border: 1px solid #89a9c2; white-space: nowrap;"> <span style="font-size: 0.85rem; color: #335e7e;"> Photomaton propos\u00e9 par Les Sapins Du Web. En l'\''utilisant, vous acceptez que les photos puissent etre utilis\u00e9es par Les Sapins Du Web et ses partenaires. </span> </div> </div> <div class="fixed-bottom-right q-mb-lg q-mr-lg" style="pointer-events: none;"> <img src="/userdata/LSDW/logo/logo.jpeg" alt="Les Sapins Du Web" style="width:180px; height:180px; border-radius: 12px; box-shadow: 2px 2px 10px rgba(0,0,0,0.5);"> </div></div>'
FRONTPAGE_DIRECT='<div> <div class="fixed-top-right q-mt-lg q-mr-lg" style="pointer-events: none; max-width: 400px;"> <div class="q-pa-md" style="background: rgba(226,235,242,0.85); border-radius: 12px; backdrop-filter: blur(4px); border: 2px solid #89a9c2;"> <div class="text-h3 text-weight-bold" style="color: #335e7e;">Bienvenue !</div> <div style="font-size: 1.3rem; line-height: 1.6; color: #335e7e; margin-top: 10px;"> <div>Appui court = Photo</div> <div>Appui long (3s) = Impression</div> </div> </div> </div> <div class="fixed-bottom-left q-mb-sm q-ml-lg" style="pointer-events: none;"> <div class="q-pa-sm" style="background: rgba(226,235,242,0.75); border-radius: 8px; border: 1px solid #89a9c2; white-space: nowrap;"> <span style="font-size: 0.85rem; color: #335e7e;"> Photomaton propos\u00e9 par Les Sapins Du Web. En l'\''utilisant, vous acceptez que les photos puissent etre utilis\u00e9es par Les Sapins Du Web et ses partenaires. </span> </div> </div> <div class="fixed-bottom-right q-mb-lg q-mr-lg" style="pointer-events: none;"> <img src="/userdata/LSDW/logo/logo.jpeg" alt="Les Sapins Du Web" style="width:180px; height:180px; border-radius: 12px; box-shadow: 2px 2px 10px rgba(0,0,0,0.5);"> </div></div>'
FRONTPAGE_NOPRINT='<div> <div class="fixed-top-right q-mt-lg q-mr-lg" style="pointer-events: none; max-width: 400px;"> <div class="q-pa-md" style="background: rgba(226,235,242,0.85); border-radius: 12px; backdrop-filter: blur(4px); border: 2px solid #89a9c2;"> <div class="text-h3 text-weight-bold" style="color: #335e7e;">Bienvenue !</div> <div style="font-size: 1.3rem; line-height: 1.6; color: #335e7e; margin-top: 10px;"> <div>Appuyez sur le bouton pour prendre une photo</div> </div> </div> </div> <div class="fixed-bottom-left q-mb-sm q-ml-lg" style="pointer-events: none;"> <div class="q-pa-sm" style="background: rgba(226,235,242,0.75); border-radius: 8px; border: 1px solid #89a9c2; white-space: nowrap;"> <span style="font-size: 0.85rem; color: #335e7e;"> Photomaton propos\u00e9 par Les Sapins Du Web. En l'\''utilisant, vous acceptez que les photos puissent etre utilis\u00e9es par Les Sapins Du Web et ses partenaires. </span> </div> </div> <div class="fixed-bottom-right q-mb-lg q-mr-lg" style="pointer-events: none;"> <img src="/userdata/LSDW/logo/logo.jpeg" alt="Les Sapins Du Web" style="width:180px; height:180px; border-radius: 12px; box-shadow: 2px 2px 10px rgba(0,0,0,0.5);"> </div></div>'
frontpage_set() {
local mode="$1"
local token
token=$(get_token)
local html=""
case "$mode" in
manuel)
html="$FRONTPAGE_MANUEL"
;;
direct)
html="$FRONTPAGE_DIRECT"
;;
noprint)
html="$FRONTPAGE_NOPRINT"
;;
esac
# Utiliser python3 pour construire le JSON proprement (gestion des quotes)
python3 -c "
import json, urllib.request
data = json.dumps({'uisettings': {'FRONTPAGE_TEXT': '''${html}'''}}).encode()
req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH')
req.add_header('Authorization', 'Bearer ${token}')
req.add_header('Content-Type', 'application/json')
urllib.request.urlopen(req)
"
case "$mode" in
manuel)
echo -e "${CYAN}Frontpage: MODE MANUEL${NC}"
echo " Appui court = Photo / Appui long = Demande d'impression"
;;
direct)
echo -e "${CYAN}Frontpage: MODE DIRECT${NC}"
echo " Appui court = Photo / Appui long = Impression"
;;
noprint)
echo -e "${CYAN}Frontpage: SANS IMPRESSION${NC}"
echo " Appuyez sur le bouton pour prendre une photo"
;;
esac
}
# =============================================================================
# ACTIONS IMAGE
# =============================================================================
ACTIONS_DIR="${HOME}/photobooth-data/actions-backups"
actions_init() {
mkdir -p "$ACTIONS_DIR"
}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DISPLAY_ACTIONS_PY="${SCRIPT_DIR}/display_actions.py"
actions_list() {
local token
token=$(get_token)
local config
config=$(get_config "$token")
echo "=== Actions image actuelles ==="
echo "$config" | python3 "$DISPLAY_ACTIONS_PY"
}
actions_backup() {
local name="$1"
actions_init
local token
token=$(get_token)
local config
config=$(get_config "$token")
local filepath="${ACTIONS_DIR}/${name}.json"
echo "$config" | python3 -c "
import sys, json
c = json.load(sys.stdin)
with open('${filepath}', 'w') as f:
json.dump(c['actions'], f, indent=2, ensure_ascii=False)
"
local count
count=$(python3 -c "import json;print(len(json.load(open('${filepath}'))['image']))")
echo -e "${GREEN}Backup sauvegarde: ${filepath}${NC}"
echo " ${count} action(s) image"
}
actions_restore() {
local name="$1"
actions_init
local filepath="${ACTIONS_DIR}/${name}.json"
if [ ! -f "$filepath" ]; then
echo -e "${RED}Backup introuvable: ${filepath}${NC}"
echo "Backups disponibles:"
actions_list_backups
return 1
fi
local token
token=$(get_token)
python3 -c "
import json, urllib.request
with open('${filepath}') as f:
actions = json.load(f)
data = json.dumps({'actions': actions}).encode()
req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH')
req.add_header('Authorization', 'Bearer ${token}')
req.add_header('Content-Type', 'application/json')
urllib.request.urlopen(req)
"
local count
count=$(python3 -c "import json;print(len(json.load(open('${filepath}'))['image']))")
echo -e "${GREEN}Backup restaure: ${name}${NC}"
echo " ${count} action(s) image chargees"
echo " -> systemctl --user restart photobooth-app"
}
actions_list_backups() {
actions_init
echo "=== Backups disponibles ==="
local found=false
for f in "${ACTIONS_DIR}"/*.json; do
if [ -f "$f" ]; then
found=true
local basename
basename=$(basename "$f" .json)
local count
count=$(python3 -c "import json;print(len(json.load(open('${f}'))['image']))" 2>/dev/null || echo "?")
local date
date=$(stat -c '%y' "$f" 2>/dev/null | cut -d. -f1)
echo " ${basename} (${count} actions, ${date})"
fi
done
if ! $found; then
echo " (aucun)"
fi
}
actions_show() {
local name="$1"
actions_init
local filepath="${ACTIONS_DIR}/${name}.json"
if [ ! -f "$filepath" ]; then
echo -e "${RED}Backup introuvable: ${filepath}${NC}"
return 1
fi
echo "=== Backup: ${name} ==="
python3 "$DISPLAY_ACTIONS_PY" --file "$filepath"
}
actions_move() {
local from="$1"
local to="$2"
local token
token=$(get_token)
local tmpfile
tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json)
get_config "$token" > "$tmpfile"
python3 -c "
import json, urllib.request, sys
with open('${tmpfile}') as f:
config = json.load(f)
actions = config['actions']['image']
count = len(actions)
f, t = int('${from}'), int('${to}')
if f < 0 or f >= count or t < 0 or t >= count:
print(f'Erreur: index invalide (0-{count-1})')
sys.exit(1)
item = actions.pop(f)
actions.insert(t, item)
config['actions']['image'] = actions
data = json.dumps({'actions': config['actions']}).encode()
req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH')
req.add_header('Authorization', 'Bearer ${token}')
req.add_header('Content-Type', 'application/json')
urllib.request.urlopen(req)
print(f'Action [{f}] deplacee vers [{t}]')
for i, a in enumerate(actions):
marker = ' <--' if i == t else ''
print(f' [{i}] {a[\"name\"]}{marker}')
"
rm -f "$tmpfile"
}
actions_enable() {
local index="$1"
local token
token=$(get_token)
local tmpfile
tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json)
get_config "$token" > "$tmpfile"
python3 -c "
import json, urllib.request, sys
with open('${tmpfile}') as f:
config = json.load(f)
actions = config['actions']['image']
idx = int('${index}')
if idx < 0 or idx >= len(actions):
print(f'Erreur: index invalide (0-{len(actions)-1})')
sys.exit(1)
actions[idx]['trigger']['ui_trigger']['show_button'] = True
data = json.dumps({'actions': config['actions']}).encode()
req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH')
req.add_header('Authorization', 'Bearer ${token}')
req.add_header('Content-Type', 'application/json')
urllib.request.urlopen(req)
print(f'Action [{idx}] {actions[idx][\"name\"]} : bouton VISIBLE')
"
rm -f "$tmpfile"
}
actions_disable() {
local index="$1"
local token
token=$(get_token)
local tmpfile
tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json)
get_config "$token" > "$tmpfile"
python3 -c "
import json, urllib.request, sys
with open('${tmpfile}') as f:
config = json.load(f)
actions = config['actions']['image']
idx = int('${index}')
if idx < 0 or idx >= len(actions):
print(f'Erreur: index invalide (0-{len(actions)-1})')
sys.exit(1)
actions[idx]['trigger']['ui_trigger']['show_button'] = False
data = json.dumps({'actions': config['actions']}).encode()
req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH')
req.add_header('Authorization', 'Bearer ${token}')
req.add_header('Content-Type', 'application/json')
urllib.request.urlopen(req)
print(f'Action [{idx}] {actions[idx][\"name\"]} : bouton CACHE')
"
rm -f "$tmpfile"
}
actions_duplicate() {
local index="$1"
local token
token=$(get_token)
local tmpfile
tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json)
get_config "$token" > "$tmpfile"
python3 -c "
import json, urllib.request, copy, sys
with open('${tmpfile}') as f:
config = json.load(f)
actions = config['actions']['image']
idx = int('${index}')
if idx < 0 or idx >= len(actions):
print(f'Erreur: index invalide (0-{len(actions)-1})')
sys.exit(1)
new_action = copy.deepcopy(actions[idx])
new_action['name'] = new_action['name'] + ' (copie)'
new_action['trigger']['ui_trigger']['show_button'] = False
actions.insert(idx + 1, new_action)
data = json.dumps({'actions': config['actions']}).encode()
req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH')
req.add_header('Authorization', 'Bearer ${token}')
req.add_header('Content-Type', 'application/json')
urllib.request.urlopen(req)
print(f'Action [{idx}] dupliquee vers [{idx+1}]')
for i, a in enumerate(actions):
marker = ' <-- copie' if i == idx + 1 else ''
print(f' [{i}] {a[\"name\"]}{marker}')
"
rm -f "$tmpfile"
}
actions_remove() {
local indices="$@"
local token
token=$(get_token)
local tmpfile
tmpfile=$(mktemp /tmp/photobooth-config-XXXXXX.json)
get_config "$token" > "$tmpfile"
python3 -c "
import json, urllib.request, sys
with open('${tmpfile}') as f:
config = json.load(f)
actions = config['actions']['image']
indices = sorted([int(x) for x in '${indices}'.split()], reverse=True)
for idx in indices:
if idx < 0 or idx >= len(actions):
print(f'Erreur: index {idx} invalide (0-{len(actions)-1})')
sys.exit(1)
removed = []
for idx in indices:
removed.append(f'[{idx}] {actions[idx][\"name\"]}')
actions.pop(idx)
data = json.dumps({'actions': config['actions']}).encode()
req = urllib.request.Request('${API_URL}/api/admin/config/app', data=data, method='PATCH')
req.add_header('Authorization', 'Bearer ${token}')
req.add_header('Content-Type', 'application/json')
urllib.request.urlopen(req)
print('Actions supprimees:')
for r in removed:
print(f' - {r}')
print()
print('Actions restantes:')
for i, a in enumerate(actions):
print(f' [{i}] {a[\"name\"]}')
print(f' Total: {len(actions)} action(s)')
"
rm -f "$tmpfile"
}
# =============================================================================
# RESTART
# =============================================================================
do_restart() {
echo "Redemarrage de photobooth-app..."
systemctl --user restart photobooth-app
sleep 3
if systemctl --user is-active photobooth-app &>/dev/null; then
echo -e "${GREEN}photobooth-app redemarre avec succes${NC}"
else
echo -e "${RED}ERREUR: photobooth-app n'a pas redemarre${NC}"
systemctl --user status photobooth-app
fi
}
# =============================================================================
# AIDE
# =============================================================================
show_help() {
cat << 'HELP'
===============================================================================
photobooth-config.sh - Gestion rapide de la config photobooth-app
===============================================================================
USAGE: photobooth-config.sh <commande> [arguments]
COMMANDES GENERALES:
status, s Afficher la config actuelle (qualite, livestream, frontpage)
restart, r Redemarrer photobooth-app
help, -h, --help Afficher cette aide
QUALITE D'IMAGE:
quality status Voir la qualite actuelle
quality high Haute qualite (4608x3072, JPEG 100%)
Meilleure qualite d'impression mais consomme plus de RAM
quality low Basse qualite (2304x1536, JPEG 90%)
Economise la RAM, suffisant pour impression Selphy
Note: necessite un restart apres changement
LIVESTREAM:
livestream status Voir si le livestream idle est actif
livestream on Activer le livestream sur l'ecran d'accueil
Les visiteurs voient la camera en direct
livestream off Desactiver le livestream idle
L'ecran d'accueil affiche le slideshow
TEXTE D'ACCUEIL (FRONTPAGE):
frontpage status Voir le mode actuel
frontpage manuel "Appui long (3s) = Demande d'impression"
Pour les events avec validation admin
frontpage direct "Appui long (3s) = Impression"
Pour les events avec impression automatique
frontpage noprint "Appuyez sur le bouton pour prendre une photo"
Pas de mention d'impression
ACTIONS IMAGE:
actions list Lister toutes les actions avec leurs parametres
L'index [0] = simple clic, [1] = double clic, etc.
actions backup <nom> Sauvegarder les actions actuelles
Ex: actions backup hopnbloc
actions restore <nom> Restaurer un backup d'actions
Ex: actions restore hopnbloc
Note: necessite un restart apres
actions backups Lister les backups disponibles
actions show <nom> Voir le contenu d'un backup
actions move <de> <vers> Deplacer une action dans la liste
Ex: actions move 3 0 (met l'action 3 en premier)
Important: l'ordre determine le bouton physique
[0] = simple clic
[1] = double clic
[2] = triple clic
[3] = quadruple clic
actions enable <index> Rendre le bouton visible sur l'ecran tactile
actions disable <index> Cacher le bouton de l'ecran tactile
actions dup <index> Dupliquer une action (copie inseree apres)
actions remove <i> [i...] Supprimer une ou plusieurs actions
Ex: actions remove 5 (supprime l'action 5)
Ex: actions remove 3 5 7 (supprime les actions 3, 5 et 7)
EXEMPLES:
# Preparer un event Hop'N Bloc
photobooth-config.sh actions backup config-avant
photobooth-config.sh actions restore hopnbloc
photobooth-config.sh frontpage manuel
photobooth-config.sh livestream on
photobooth-config.sh quality low
photobooth-config.sh restart
# Passer en haute qualite pour un event special
photobooth-config.sh quality high
photobooth-config.sh restart
# Apres l'event, tout remettre
photobooth-config.sh actions restore config-avant
photobooth-config.sh restart
===============================================================================
HELP
}
# =============================================================================
# MAIN
# =============================================================================
CMD="${1:-help}"
ARG="${2:-status}"
case "$CMD" in
quality|q)
case "$ARG" in
high|haute|hq) quality_high ;;
low|basse|lq) quality_low ;;
status|info) quality_status ;;
*) echo "Usage: $0 quality {high|low|status}" ;;
esac
;;
livestream|ls)
case "$ARG" in
on|true|1) livestream_on ;;
off|false|0) livestream_off ;;
status|info) livestream_status ;;
*) echo "Usage: $0 livestream {on|off|status}" ;;
esac
;;
frontpage|fp)
case "$ARG" in
manuel|manual) frontpage_set manuel ;;
direct) frontpage_set direct ;;
noprint|sans|off) frontpage_set noprint ;;
status|info) frontpage_status ;;
*) echo "Usage: $0 frontpage {manuel|direct|noprint|status}" ;;
esac
;;
actions|a)
case "$ARG" in
list|ls) actions_list ;;
backup|save)
if [ -z "${3:-}" ]; then
echo "Usage: $0 actions backup <nom>"
echo " Exemple: $0 actions backup hopnbloc"
exit 1
fi
actions_backup "$3"
;;
restore|load)
if [ -z "${3:-}" ]; then
echo "Usage: $0 actions restore <nom>"
actions_list_backups
exit 1
fi
actions_restore "$3"
;;
backups) actions_list_backups ;;
show)
if [ -z "${3:-}" ]; then
echo "Usage: $0 actions show <nom>"
actions_list_backups
exit 1
fi
actions_show "$3"
;;
move|mv)
if [ -z "${3:-}" ] || [ -z "${4:-}" ]; then
echo "Usage: $0 actions move <from> <to>"
echo " Exemple: $0 actions move 3 0 (deplace l'action 3 en position 0)"
exit 1
fi
actions_move "$3" "$4"
;;
enable|on)
if [ -z "${3:-}" ]; then
echo "Usage: $0 actions enable <index>"
actions_list
exit 1
fi
actions_enable "$3"
;;
disable|off)
if [ -z "${3:-}" ]; then
echo "Usage: $0 actions disable <index>"
actions_list
exit 1
fi
actions_disable "$3"
;;
dup|duplicate|copy)
if [ -z "${3:-}" ]; then
echo "Usage: $0 actions dup <index>"
actions_list
exit 1
fi
actions_duplicate "$3"
;;
remove|rm|del|delete)
if [ -z "${3:-}" ]; then
echo "Usage: $0 actions remove <index> [index...]"
echo " Exemple: $0 actions remove 5"
echo " Exemple: $0 actions remove 3 5 7"
actions_list
exit 1
fi
shift 2
actions_remove "$@"
;;
*) echo "Usage: $0 actions {list|backup|restore|backups|show|move|enable|disable|dup|remove}" ;;
esac
;;
restart|r)
do_restart
;;
status|s)
echo "=== Photobooth Config ==="
quality_status
livestream_status
frontpage_status
;;
help|-h|--help)
show_help
;;
*)
show_help
;;
esac
+68
View File
@@ -0,0 +1,68 @@
#!/bin/bash
# =============================================================================
# ram-watchdog.sh - Watchdog memoire pour le photomaton
# =============================================================================
# Usage: Lancer via cron toutes les minutes :
# * * * * * /home/pi/opencode/scripts/ram-watchdog.sh
#
# Actions par seuil :
# - Swap > 70% : vider les caches
# - Swap > 85% : redemarrer photobooth-app
# - Swap > 95% : reboot du Pi
# =============================================================================
LOG_TAG="ram-watchdog"
log_info() { logger -t "$LOG_TAG" "[INFO] $1"; }
log_warn() { logger -t "$LOG_TAG" "[WARN] $1"; }
log_error() { logger -t "$LOG_TAG" "[ERROR] $1"; }
# --- Lire les valeurs memoire ---
SWAP_TOTAL=$(awk '/SwapTotal/ {print $2}' /proc/meminfo)
SWAP_FREE=$(awk '/SwapFree/ {print $2}' /proc/meminfo)
MEM_AVAILABLE=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
# Eviter division par zero
if [ "$SWAP_TOTAL" -eq 0 ]; then
exit 0
fi
SWAP_USED=$((SWAP_TOTAL - SWAP_FREE))
SWAP_PERCENT=$((SWAP_USED * 100 / SWAP_TOTAL))
MEM_AVAILABLE_MB=$((MEM_AVAILABLE / 1024))
# --- Seuil 1 : Swap > 70% -> vider les caches ---
if [ "$SWAP_PERCENT" -ge 70 ] && [ "$SWAP_PERCENT" -lt 85 ]; then
log_warn "Swap a ${SWAP_PERCENT}% (RAM dispo: ${MEM_AVAILABLE_MB}Mo) - Vidage des caches"
sync
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
fi
# --- Seuil 2 : Swap > 85% -> redemarrer photobooth-app ---
if [ "$SWAP_PERCENT" -ge 85 ] && [ "$SWAP_PERCENT" -lt 95 ]; then
log_error "Swap a ${SWAP_PERCENT}% (RAM dispo: ${MEM_AVAILABLE_MB}Mo) - Redemarrage photobooth-app"
# Redemarrer photobooth-app en tant que l'utilisateur pi
REAL_USER="pi"
XDG_RUNTIME_DIR="/run/user/$(id -u "$REAL_USER")"
DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus"
runuser -u "$REAL_USER" -- env \
XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \
DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
systemctl --user restart photobooth-app 2>/dev/null || true
log_warn "photobooth-app redemarre"
# Attendre et vider les caches
sleep 5
sync
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
fi
# --- Seuil 3 : Swap > 95% -> reboot ---
if [ "$SWAP_PERCENT" -ge 95 ]; then
log_error "CRITIQUE: Swap a ${SWAP_PERCENT}% (RAM dispo: ${MEM_AVAILABLE_MB}Mo) - REBOOT"
sync
sleep 2
reboot
fi
+138
View File
@@ -0,0 +1,138 @@
#!/bin/bash
# =============================================================================
# script_print.sh - Impression photo pour photomaton
# =============================================================================
# Usage: script_print.sh "<filename>" "<media_type>" "<action_config_name>" "<copies>"
# Appele par photobooth-app via l'action Share "Impression"
#
# Imprimantes: Canon Selphy CP1300 x2 via WiFi (blanche + noire)
# Load balancing: utilise l'imprimante idle, sinon file d'attente sur la 1ere
#
# Sortie stdout parsable par Node-RED:
# PRINTED:<printer_name>:<copies> en cas de succes
# PRINT_ERROR:<printer_name> en cas d'erreur
# =============================================================================
set -uo pipefail
# --- Parametres ---
filename="${1:-}"
media_type="${2:-}"
action_config_name="${3:-}"
copies="${4:-1}"
# --- Noms des imprimantes CUPS (WiFi) ---
PRINTER_1="Selphy_Blanche_WiFi"
PRINTER_2="Selphy_Noire_WiFi"
# --- Options d'impression Selphy ---
# Mode raw : envoie le JPEG directement a la Selphy
# Postcard.Fullbleed : impression sans bordures
PRINT_OPTIONS="-o media=Postcard.Fullbleed -o raw"
# --- Calibrage bordures pour impression Fullbleed ---
# La Selphy deborde de quelques mm en mode Fullbleed
# On ajoute des marges noires pour compenser et eviter de couper la photo
# Valeurs calibrees pour Canon Selphy CP1300 via WiFi
PRINT_EXTENT="2114x1418"
PRINT_ROLL="-12+8"
# --- Logging ---
LOG_TAG="photomaton-print"
log_info() { logger -t "$LOG_TAG" "[INFO] $1"; echo "[INFO] $1"; }
log_error() { logger -t "$LOG_TAG" "[ERROR] $1"; echo "[ERROR] $1"; }
# --- Validation ---
if [ -z "$filename" ]; then
log_error "Aucun fichier specifie"
exit 1
fi
if [ ! -f "$filename" ]; then
log_error "Fichier introuvable: $filename"
exit 1
fi
if [ "$copies" -lt 1 ] || [ "$copies" -gt 3 ]; then
log_error "Nombre de copies invalide: $copies (doit etre entre 1 et 3)"
exit 1
fi
# --- Preparation de l'image pour impression ---
# Resize a 2000x1333 (ratio 3:2), ajout de marges noires pour compenser
# le debordement Fullbleed, puis decalage pour centrer correctement
filename_no_ext="${filename%.jpg}"
filename_print="${filename_no_ext}_print.jpg"
if command -v convert &>/dev/null; then
log_info "Preparation de l'image pour impression Fullbleed..."
if convert "$filename" \
-resize 2000x1333 \
-background black \
-gravity center \
-extent ${PRINT_EXTENT} \
-roll ${PRINT_ROLL} \
-quality 95 \
"$filename_print" 2>/dev/null; then
FILE_TO_PRINT="$filename_print"
log_info "Image preparee: $filename_print"
else
log_error "Echec de la preparation, impression de l'original"
FILE_TO_PRINT="$filename"
fi
else
log_error "ImageMagick (convert) non installe, impression de l'original"
FILE_TO_PRINT="$filename"
fi
# --- Selection de l'imprimante (load balancing) ---
printer_1_status=$(lpstat -p "$PRINTER_1" 2>/dev/null || echo "not found")
printer_2_status=$(lpstat -p "$PRINTER_2" 2>/dev/null || echo "not found")
SELECTED_PRINTER=""
if [[ "$printer_1_status" == *"idle"* ]]; then
SELECTED_PRINTER="$PRINTER_1"
log_info "Imprimante selectionnee: $PRINTER_1 (idle)"
elif [[ "$printer_2_status" == *"idle"* ]]; then
SELECTED_PRINTER="$PRINTER_2"
log_info "Imprimante selectionnee: $PRINTER_2 (idle)"
else
# Aucune idle : choisir celle avec le moins de jobs en attente
jobs_1=$(lpstat -o "$PRINTER_1" 2>/dev/null | wc -l)
jobs_2=$(lpstat -o "$PRINTER_2" 2>/dev/null | wc -l)
if [ "$jobs_1" -le "$jobs_2" ] 2>/dev/null; then
SELECTED_PRINTER="$PRINTER_1"
else
SELECTED_PRINTER="$PRINTER_2"
fi
log_info "Aucune imprimante idle, file d'attente sur: $SELECTED_PRINTER (jobs: P1=$jobs_1, P2=$jobs_2)"
fi
# --- Impression ---
log_info "Impression x${copies} sur $SELECTED_PRINTER: $FILE_TO_PRINT"
if lp -n "$copies" -d "$SELECTED_PRINTER" $PRINT_OPTIONS "$FILE_TO_PRINT"; then
log_info "Job d'impression envoye avec succes"
# Sortie parsable par Node-RED pour les compteurs
echo "PRINTED:${SELECTED_PRINTER}:${copies}"
# Nettoyage du fichier temporaire (apres que CUPS l'ait lu)
if [ "$FILE_TO_PRINT" = "$filename_print" ] && [ -f "$filename_print" ]; then
(sleep 30 && rm -f "$filename_print" 2>/dev/null) &
fi
exit 0
else
log_error "Echec de l'envoi du job d'impression"
echo "PRINT_ERROR:${SELECTED_PRINTER}"
# Nettoyage meme en cas d'erreur
if [ -f "$filename_print" ]; then
rm -f "$filename_print" 2>/dev/null
fi
exit 1
fi
+276
View File
@@ -0,0 +1,276 @@
#!/bin/bash
# =============================================================================
# setup-photobooth.sh - Installation et deploiement complet du photomaton
# =============================================================================
# Usage: bash setup-photobooth.sh
# A executer sur un Raspberry Pi 4 fraichement installe
# =============================================================================
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "\n${CYAN}=== $1 ===${NC}\n"; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || whoami)}"
USER_HOME=$(eval echo "~$REAL_USER")
echo "============================================="
echo " Installation complete du Photomaton"
echo " Raspberry Pi 4 - 2 Go RAM"
echo "============================================="
echo ""
echo " Utilisateur: $REAL_USER"
echo " Home: $USER_HOME"
echo " Projet: $PROJECT_DIR"
echo ""
# Verifier qu'on est root pour certaines operations
NEED_SUDO=false
if [ "$EUID" -ne 0 ]; then
log_warn "Certaines etapes necessitent sudo. Vous serez peut-etre invite a entrer le mot de passe."
NEED_SUDO=true
fi
# ===================================================================
# ETAPE 1: Optimisation systeme
# ===================================================================
log_step "1/7 - Optimisation systeme"
if [ "$EUID" -eq 0 ]; then
bash "$SCRIPT_DIR/optimize-system.sh"
else
log_warn "Lancez optimize-system.sh separement avec sudo:"
echo " sudo bash $SCRIPT_DIR/optimize-system.sh"
fi
# ===================================================================
# ETAPE 2: Installation des dependances
# ===================================================================
log_step "2/7 - Installation des dependances"
sudo apt-get update -qq
sudo apt-get install -y -qq \
python3 \
python3-pip \
python3-venv \
python3-picamera2 \
libcamera-apps \
libcap-dev \
curl \
unclutter \
chromium \
git \
cups \
printer-driver-gutenprint \
cups-client
log_info "Dependances installees"
# ===================================================================
# ETAPE 3: Installation de photobooth-app
# ===================================================================
log_step "3/7 - Installation de photobooth-app"
# Creer le dossier de donnees
su - "$REAL_USER" -c "mkdir -p $USER_HOME/photobooth-data"
# Installer photobooth-app via pip (mode utilisateur)
su - "$REAL_USER" -c "pip install --user --upgrade photobooth-app" || {
log_warn "pip install --user a echoue, essai avec pipx..."
su - "$REAL_USER" -c "pip install --user pipx && pipx install photobooth-app" || {
log_warn "pipx a echoue, essai avec venv..."
su - "$REAL_USER" -c "
python3 -m venv $USER_HOME/photobooth-venv
$USER_HOME/photobooth-venv/bin/pip install photobooth-app
"
log_info "photobooth-app installe dans un venv: $USER_HOME/photobooth-venv"
log_warn "Adapter le chemin dans le service systemd (ExecStart)"
}
}
log_info "photobooth-app installe"
# ===================================================================
# ETAPE 4: Configuration du service systemd
# ===================================================================
log_step "4/7 - Configuration des services systemd"
# Service photobooth-app (service utilisateur)
su - "$REAL_USER" -c "mkdir -p $USER_HOME/.config/systemd/user/"
# Copier le service utilisateur
cp "$PROJECT_DIR/systemd/photobooth-app.service" \
"$USER_HOME/.config/systemd/user/photobooth-app.service"
chown "$REAL_USER:$REAL_USER" "$USER_HOME/.config/systemd/user/photobooth-app.service"
# Activer le linger pour que les services utilisateur demarrent au boot
loginctl enable-linger "$REAL_USER" 2>/dev/null || true
# Activer le service
su - "$REAL_USER" -c "
systemctl --user daemon-reload
systemctl --user enable photobooth-app
"
log_info "Service photobooth-app configure"
# Services systeme (watchdog + maintenance)
# Adapter les chemins dans les services
SCRIPTS_PATH="$PROJECT_DIR/scripts"
sed "s|/home/pi/photomaton/scripts|$SCRIPTS_PATH|g" \
"$PROJECT_DIR/systemd/photobooth-watchdog.service" \
> /etc/systemd/system/photobooth-watchdog.service
cp "$PROJECT_DIR/systemd/photobooth-watchdog.timer" \
/etc/systemd/system/photobooth-watchdog.timer
sed "s|/home/pi/photomaton/scripts|$SCRIPTS_PATH|g" \
"$PROJECT_DIR/systemd/photobooth-maintenance.service" \
> /etc/systemd/system/photobooth-maintenance.service
cp "$PROJECT_DIR/systemd/photobooth-maintenance.timer" \
/etc/systemd/system/photobooth-maintenance.timer
# Rendre les scripts executables
chmod +x "$SCRIPTS_PATH"/*.sh
systemctl daemon-reload
systemctl enable photobooth-watchdog.timer
systemctl enable photobooth-maintenance.timer
log_info "Watchdog et maintenance configures"
# ===================================================================
# ETAPE 5: Configuration de l'imprimante
# ===================================================================
log_step "5/7 - Configuration imprimante"
if [ "$EUID" -eq 0 ]; then
bash "$SCRIPT_DIR/setup-printer.sh"
else
log_warn "Lancez setup-printer.sh separement avec sudo:"
echo " sudo bash $SCRIPT_DIR/setup-printer.sh"
fi
# ===================================================================
# ETAPE 6: Configuration de l'auto-login et kiosk
# ===================================================================
log_step "6/7 - Configuration auto-login et mode kiosk"
# Activer l'auto-login en mode graphique
# (depand de la config actuelle, peut necessiter raspi-config)
if command -v raspi-config &>/dev/null; then
# Mode desktop auto-login
raspi-config nonint do_boot_behaviour B4 2>/dev/null || true
log_info "Auto-login graphique configure via raspi-config"
fi
# Creer le fichier .desktop pour le kiosk Chromium
# Sur RPi OS Trixie (Wayland), le fichier va dans ~/Desktop/
DESKTOP_DIR="$USER_HOME/Desktop"
su - "$REAL_USER" -c "mkdir -p $DESKTOP_DIR"
# Detecter chromium
CHROMIUM_BIN=$(command -v chromium || command -v chromium-browser || echo "chromium")
cat > "$DESKTOP_DIR/photobooth-app.desktop" << EOF
[Desktop Entry]
X-GNOME-Autostart-enabled=true
X-GNOME-Autostart-Delay=120
Terminal=false
Type=Application
Name=Photobooth-App
Exec=bash -c "sleep 60 && $CHROMIUM_BIN --kiosk --accept-lang=fr-FR --incognito --password-store=basic --disable-features=Translate,OverscrollHistoryNavigation --noerrdialogs --disable-infobars --no-first-run --ozone-platform=wayland --enable-features=OverlayScrollbar --start-maximized --process-per-site --js-flags='--max-old-space-size=128' --renderer-process-limit=1 --disable-dev-shm-usage http://127.0.0.1:8083/"
StartupNotify=false
EOF
chown "$REAL_USER:$REAL_USER" "$DESKTOP_DIR/photobooth-app.desktop"
log_info "Mode kiosk configure (Wayland, ~/Desktop/photobooth-app.desktop)"
# ===================================================================
# ETAPE 7: Configuration finale et verification
# ===================================================================
log_step "7/7 - Verification finale"
echo ""
echo "=== Verification des composants ==="
echo ""
# Verifier photobooth-app
if su - "$REAL_USER" -c "which photobooth" &>/dev/null || \
[ -f "$USER_HOME/photobooth-venv/bin/photobooth" ]; then
echo -e " photobooth-app: ${GREEN}INSTALLE${NC}"
else
echo -e " photobooth-app: ${RED}NON TROUVE${NC}"
fi
# Verifier CUPS
if systemctl is-active cups &>/dev/null; then
echo -e " CUPS: ${GREEN}ACTIF${NC}"
else
echo -e " CUPS: ${YELLOW}INACTIF${NC}"
fi
# Verifier les imprimantes
printers=$(lpstat -p 2>/dev/null | grep -c "printer" || echo "0")
echo -e " Imprimantes: ${printers} detectee(s)"
# Verifier la camera
if libcamera-hello --list-cameras 2>/dev/null | grep -q "Available"; then
echo -e " Camera: ${GREEN}DETECTEE${NC}"
elif [ -e /dev/video0 ]; then
echo -e " Camera: ${GREEN}/dev/video0${NC}"
else
echo -e " Camera: ${YELLOW}NON DETECTEE (verifier apres reboot)${NC}"
fi
# Verifier la memoire
mem_total=$(awk '/MemTotal/ {printf "%.0f", $2/1024}' /proc/meminfo)
mem_available=$(awk '/MemAvailable/ {printf "%.0f", $2/1024}' /proc/meminfo)
echo -e " RAM: ${mem_available}Mo disponible / ${mem_total}Mo total"
# Verifier le swap
swap_total=$(awk '/SwapTotal/ {printf "%.0f", $2/1024}' /proc/meminfo)
echo -e " Swap: ${swap_total}Mo"
echo ""
echo "============================================="
echo " Installation terminee !"
echo "============================================="
echo ""
echo "Prochaines etapes:"
echo ""
echo " 1. REBOOTER le Raspberry Pi:"
echo " sudo reboot"
echo ""
echo " 2. Apres reboot, acceder a l'admin photobooth:"
echo " http://localhost:8083/admin"
echo ""
echo " 3. Configurer dans l'admin:"
echo " - Camera: Backends -> picamera2"
echo " - GPIO: hardwareinput -> activer GPIO"
echo " - Photo trigger: GPIO 27 (ou votre GPIO relay)"
echo " - Impression: Share & Print -> ajouter une action Print"
echo " Commande: /home/pi/photobooth-data/script/script_print.sh \"{filename}\" \"{media_type}\" \"{action_config_name}\" \"{copies}\""
echo " - QR Share: configurer le service QR"
echo ""
echo " 4. Si vous utilisez encore Node-RED pour le bouton:"
echo " -> Envisagez de passer au GPIO natif de photobooth-app"
echo " -> Gain: ~200 Mo de RAM en desactivant Node-RED"
echo " -> sudo systemctl disable nodered.service"
echo ""
echo " 5. Verifier que tout fonctionne:"
echo " bash $SCRIPT_DIR/health-check.sh"
echo ""
+321
View File
@@ -0,0 +1,321 @@
#!/bin/bash
# =============================================================================
# setup-printer.sh - Configuration CUPS + Canon Selphy CP1300
# =============================================================================
# Usage: sudo bash setup-printer.sh
# Prerequis: imprimante branchee en USB et allumee
# =============================================================================
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
if [ "$EUID" -ne 0 ]; then
log_error "Ce script doit etre execute en root (sudo)"
exit 1
fi
echo "============================================="
echo " Configuration imprimante Canon Selphy CP1300"
echo "============================================="
echo ""
# -------------------------------------------------------------------
# 1. INSTALLATION DES PAQUETS
# -------------------------------------------------------------------
log_info "Installation des paquets necessaires..."
apt-get update -qq
apt-get install -y -qq \
cups \
printer-driver-gutenprint \
cups-client \
system-config-printer \
2>/dev/null || apt-get install -y -qq cups printer-driver-gutenprint cups-client
# Ajouter l'utilisateur au groupe lpadmin pour administrer CUPS
REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || echo pi)}"
usermod -aG lpadmin "$REAL_USER" 2>/dev/null || true
log_info "Utilisateur $REAL_USER ajoute au groupe lpadmin"
# -------------------------------------------------------------------
# 2. CONFIGURATION CUPS
# -------------------------------------------------------------------
log_info "Configuration de CUPS..."
# Backup de la config originale
cp /etc/cups/cupsd.conf /etc/cups/cupsd.conf.backup.$(date +%Y%m%d) 2>/dev/null || true
# Autoriser l'acces local au panneau CUPS
# (utile pour debug mais pas necessaire en production)
cat > /etc/cups/cupsd.conf << 'EOF'
# Configuration CUPS optimisee pour photomaton
LogLevel warn
MaxLogSize 0
PageLogFormat
# Ecouter sur localhost et le reseau local
Listen localhost:631
Listen /run/cups/cups.sock
# Partage d'imprimante desactive (pas besoin)
Browsing Off
BrowseLocalProtocols none
DefaultAuthType Basic
WebInterface Yes
# Politique par defaut
<Location />
Order allow,deny
Allow localhost
Allow 10.0.0.*
Allow 192.168.*
</Location>
<Location /admin>
Order allow,deny
Allow localhost
</Location>
<Location /admin/conf>
AuthType Default
Require user @SYSTEM
Order allow,deny
Allow localhost
</Location>
<Policy default>
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
<Limit Create-Job Print-Job Print-URI Validate-Job>
Order deny,allow
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default CUPS-Get-Devices>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
<Limit All>
Order deny,allow
</Limit>
</Policy>
EOF
systemctl restart cups
systemctl enable cups
log_info "CUPS configure et redemarre"
# -------------------------------------------------------------------
# 3. DETECTION DE L'IMPRIMANTE
# -------------------------------------------------------------------
log_info "Recherche des imprimantes Canon Selphy..."
sleep 3 # Laisser CUPS detecter les USB
# Chercher les imprimantes USB Canon
USB_PRINTERS=$(lpinfo -v 2>/dev/null | grep -i "canon\|selphy\|usb" || true)
if [ -z "$USB_PRINTERS" ]; then
log_warn "Aucune imprimante Canon detectee via USB."
log_warn "Verifiez que l'imprimante est branchee, allumee, et prete."
echo ""
echo "Imprimantes detectees:"
lpinfo -v 2>/dev/null || true
echo ""
echo "Vous pouvez relancer ce script une fois l'imprimante connectee,"
echo "ou ajouter manuellement via: http://localhost:631"
echo ""
else
echo ""
echo -e "${CYAN}Imprimantes detectees:${NC}"
echo "$USB_PRINTERS"
echo ""
fi
# Chercher le driver Gutenprint pour Canon Selphy
log_info "Recherche du driver Canon Selphy CP1300..."
DRIVER=$(lpinfo -m 2>/dev/null | grep -i "selphy\|cp1300\|CP1300" | head -1 || true)
if [ -z "$DRIVER" ]; then
# Essayer avec un pattern plus large
DRIVER=$(lpinfo -m 2>/dev/null | grep -i "canon.*cp" | head -1 || true)
fi
if [ -n "$DRIVER" ]; then
DRIVER_URI=$(echo "$DRIVER" | awk '{print $1}')
log_info "Driver trouve: $DRIVER"
else
log_warn "Driver Selphy CP1300 non trouve dans Gutenprint."
log_warn "Essayez: sudo apt install printer-driver-gutenprint && sudo systemctl restart cups"
DRIVER_URI=""
fi
# -------------------------------------------------------------------
# 4. AJOUT AUTOMATIQUE DE L'IMPRIMANTE (si detectee)
# -------------------------------------------------------------------
USB_URI=$(echo "$USB_PRINTERS" | grep "usb://" | head -1 | awk '{print $2}' || true)
if [ -n "$USB_URI" ] && [ -n "$DRIVER_URI" ]; then
log_info "Ajout automatique de l'imprimante..."
# Supprimer les anciennes configs si elles existent
lpadmin -x Canon_SELPHY_CP1300_usb_blanche 2>/dev/null || true
lpadmin -x Canon_SELPHY_CP1300_usb_noire 2>/dev/null || true
# Ajouter la premiere imprimante (blanche)
lpadmin -p Canon_SELPHY_CP1300_usb_blanche \
-v "$USB_URI" \
-m "$DRIVER_URI" \
-L "Photomaton" \
-D "Canon Selphy CP1300 Blanche" \
-E
# Configurer comme imprimante par defaut
lpadmin -d Canon_SELPHY_CP1300_usb_blanche
log_info "Imprimante 'Canon_SELPHY_CP1300_usb_blanche' ajoutee et definie par defaut"
# Detecter une deuxieme imprimante si presente
USB_URI_2=$(echo "$USB_PRINTERS" | grep "usb://" | sed -n '2p' | awk '{print $2}' || true)
if [ -n "$USB_URI_2" ]; then
lpadmin -p Canon_SELPHY_CP1300_usb_noire \
-v "$USB_URI_2" \
-m "$DRIVER_URI" \
-L "Photomaton" \
-D "Canon Selphy CP1300 Noire" \
-E
log_info "Deuxieme imprimante 'Canon_SELPHY_CP1300_usb_noire' ajoutee"
fi
else
log_warn "Ajout automatique impossible. Ajoutez manuellement via http://localhost:631"
fi
# -------------------------------------------------------------------
# 5. OPTIONS D'IMPRESSION OPTIMALES POUR SELPHY CP1300
# -------------------------------------------------------------------
log_info "Configuration des options d'impression..."
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cat > "${SCRIPT_DIR}/../config/cups-selphy.conf" << 'EOF'
# =============================================================================
# Options d'impression pour Canon Selphy CP1300
# =============================================================================
# Format: lpoptions -p PRINTER_NAME -o OPTION=VALUE
#
# La Selphy CP1300 supporte:
# - Format carte postale : 100x148mm (4x6 pouces)
# - Format carte : 54x86mm
# - Format carre : 89x89mm (si media disponible)
#
# Noms CUPS des imprimantes:
# Canon_SELPHY_CP1300_usb_blanche
# Canon_SELPHY_CP1300_usb_noire
#
# Commande d'impression pour photobooth-app:
# lp -d Canon_SELPHY_CP1300_usb_blanche -o landscape -o fit-to-page {filename}
#
# Options utilisees:
# - fit-to-page : Ajuster l'image a la page sans crop
# - landscape : Orientation paysage
# - -n X : Nombre de copies
#
# Pour le load balancing entre 2 imprimantes:
# Utiliser script_print.sh (gere automatiquement)
# =============================================================================
EOF
# Appliquer les options par defaut si les imprimantes sont configurees
if lpstat -p Canon_SELPHY_CP1300_usb_blanche &>/dev/null; then
lpoptions -p Canon_SELPHY_CP1300_usb_blanche -o fit-to-page -o landscape 2>/dev/null || true
log_info "Options par defaut appliquees a Canon_SELPHY_CP1300_usb_blanche"
fi
if lpstat -p Canon_SELPHY_CP1300_usb_noire &>/dev/null; then
lpoptions -p Canon_SELPHY_CP1300_usb_noire -o fit-to-page -o landscape 2>/dev/null || true
log_info "Options par defaut appliquees a Canon_SELPHY_CP1300_usb_noire"
fi
# -------------------------------------------------------------------
# 6. COPIER LE SCRIPT D'IMPRESSION OPTIMISE
# -------------------------------------------------------------------
log_info "Verification du script d'impression..."
if [ -f "${SCRIPT_DIR}/script_print.sh" ]; then
log_info "script_print.sh present dans le projet"
log_info "Pour deployer: cp ${SCRIPT_DIR}/script_print.sh /home/pi/photobooth-data/script/"
else
log_warn "script_print.sh non trouve dans ${SCRIPT_DIR}/"
fi
# -------------------------------------------------------------------
# 7. SCRIPT DE NETTOYAGE AUTOMATIQUE DES JOBS BLOQUES
# -------------------------------------------------------------------
log_info "Creation du script de nettoyage jobs CUPS..."
cat > "${SCRIPT_DIR}/cups-cleanup.sh" << 'CLEANUP_EOF'
#!/bin/bash
# Nettoyer les jobs d'impression bloques/en erreur
# A appeler periodiquement via cron ou le watchdog
# Annuler tous les jobs en erreur
cancel -a 2>/dev/null || true
# Reactiver les imprimantes si elles sont en pause/erreur
for printer in $(lpstat -p 2>/dev/null | grep -E "disabled|stopped" | awk '{print $2}'); do
cupsenable "$printer" 2>/dev/null || true
cupsdisable --release "$printer" 2>/dev/null || true
cupsenable "$printer" 2>/dev/null || true
echo "Imprimante reactivee: $printer"
done
CLEANUP_EOF
chmod +x "${SCRIPT_DIR}/cups-cleanup.sh"
log_info "Script cups-cleanup.sh cree"
# -------------------------------------------------------------------
# RESUME
# -------------------------------------------------------------------
echo ""
echo "============================================="
echo " Configuration imprimante terminee !"
echo "============================================="
echo ""
echo "Imprimantes configurees:"
lpstat -p 2>/dev/null || echo " (aucune)"
echo ""
echo "Commande d'impression pour photobooth-app:"
echo " script_print.sh \"{filename}\" \"{media_type}\" \"{action_config_name}\" \"{copies}\""
echo ""
echo "Imprimantes CUPS:"
echo " Canon_SELPHY_CP1300_usb_blanche"
echo " Canon_SELPHY_CP1300_usb_noire"
echo ""
echo "Administration CUPS: http://localhost:631"
echo ""
echo "Pour tester l'impression:"
echo " lp -d Canon_SELPHY_CP1300_usb_blanche -o landscape -o fit-to-page /chemin/vers/une/photo.jpg"
echo ""
+187
View File
@@ -0,0 +1,187 @@
#!/bin/bash
# =============================================================================
# switch-quality.sh - Basculer la qualite d'image de photobooth-app
# =============================================================================
# Usage:
# bash switch-quality.sh high # Qualite haute (4608x3072, quality 100)
# bash switch-quality.sh low # Qualite basse (2304x1536, quality 90)
# bash switch-quality.sh status # Afficher la config actuelle
# =============================================================================
set -uo pipefail
API_URL="http://127.0.0.1:8083"
API_USER="admin"
API_PASS="PhotoBooth2026!"
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# --- Obtenir le token ---
get_token() {
curl -s -X POST "${API_URL}/api/admin/auth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=${API_USER}&password=${API_PASS}" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])'
}
# --- Lire la config actuelle ---
get_config() {
local token="$1"
curl -s "${API_URL}/api/admin/config/app" \
-H "Authorization: Bearer ${token}"
}
# --- Appliquer une config ---
set_config() {
local token="$1"
local config="$2"
curl -s -X PATCH "${API_URL}/api/admin/config/app" \
-H "Authorization: Bearer ${token}" \
-H "Content-Type: application/json" \
-d "${config}"
}
# --- Afficher le statut ---
show_status() {
local token
token=$(get_token)
local config
config=$(get_config "$token")
local capture_w capture_h preview_w preview_h full_still preview_still quality
capture_w=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["backends"]["group_backends"][0]["backend_config"]["CAPTURE_CAM_RESOLUTION_WIDTH"])')
capture_h=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["backends"]["group_backends"][0]["backend_config"]["CAPTURE_CAM_RESOLUTION_HEIGHT"])')
preview_w=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["backends"]["group_backends"][0]["backend_config"]["PREVIEW_CAM_RESOLUTION_WIDTH"])')
preview_h=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["backends"]["group_backends"][0]["backend_config"]["PREVIEW_CAM_RESOLUTION_HEIGHT"])')
full_still=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["mediaprocessing"]["full_still_length"])')
preview_still=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["mediaprocessing"]["preview_still_length"])')
quality=$(echo "$config" | python3 -c 'import sys,json;c=json.load(sys.stdin);print(c["backends"]["group_backends"][0]["backend_config"]["original_still_quality"])')
echo "=== Configuration actuelle ==="
echo " Capture: ${capture_w}x${capture_h}"
echo " Preview: ${preview_w}x${preview_h}"
echo " Full still: ${full_still}"
echo " Preview still: ${preview_still}"
echo " JPEG quality: ${quality}"
if [ "$capture_w" -ge 4000 ]; then
echo -e " Mode: ${GREEN}HAUTE QUALITE${NC}"
else
echo -e " Mode: ${YELLOW}BASSE QUALITE (economie RAM)${NC}"
fi
}
# --- Appliquer haute qualite ---
set_high() {
local token
token=$(get_token)
echo "Passage en HAUTE QUALITE..."
# Config backends (camera)
set_config "$token" '{
"backends": {
"group_backends": [{
"enabled": true,
"description": "pi camera module V3",
"backend_config": {
"orientation": "1: 0°",
"backend_type": "Picamera2",
"camera_num": 0,
"CAPTURE_CAM_RESOLUTION_WIDTH": 4608,
"CAPTURE_CAM_RESOLUTION_HEIGHT": 3072,
"PREVIEW_CAM_RESOLUTION_WIDTH": 2304,
"PREVIEW_CAM_RESOLUTION_HEIGHT": 1536,
"LIVEVIEW_RESOLUTION_WIDTH": 750,
"LIVEVIEW_RESOLUTION_HEIGHT": 500,
"framerate_still_mode": 20,
"framerate_video_mode": 25,
"frame_skip_count": 4,
"optimized_lowlight_short_exposure": false,
"videostream_quality": "MEDIUM",
"original_still_quality": 100
}
}]
},
"mediaprocessing": {
"full_still_length": 4608,
"preview_still_length": 2304,
"thumbnail_still_length": 960
}
}' > /dev/null
echo -e "${GREEN}HAUTE QUALITE activee${NC}"
echo " Capture: 4608x3072, Quality: 100"
echo " ATTENTION: consomme plus de RAM"
echo ""
echo "Redemarrage de photobooth-app necessaire :"
echo " systemctl --user restart photobooth-app"
}
# --- Appliquer basse qualite ---
set_low() {
local token
token=$(get_token)
echo "Passage en BASSE QUALITE (economie RAM)..."
set_config "$token" '{
"backends": {
"group_backends": [{
"enabled": true,
"description": "pi camera module V3",
"backend_config": {
"orientation": "1: 0°",
"backend_type": "Picamera2",
"camera_num": 0,
"CAPTURE_CAM_RESOLUTION_WIDTH": 2304,
"CAPTURE_CAM_RESOLUTION_HEIGHT": 1536,
"PREVIEW_CAM_RESOLUTION_WIDTH": 1152,
"PREVIEW_CAM_RESOLUTION_HEIGHT": 768,
"LIVEVIEW_RESOLUTION_WIDTH": 750,
"LIVEVIEW_RESOLUTION_HEIGHT": 500,
"framerate_still_mode": 20,
"framerate_video_mode": 25,
"frame_skip_count": 4,
"optimized_lowlight_short_exposure": false,
"videostream_quality": "MEDIUM",
"original_still_quality": 90
}
}]
},
"mediaprocessing": {
"full_still_length": 2304,
"preview_still_length": 1200,
"thumbnail_still_length": 480
}
}' > /dev/null
echo -e "${YELLOW}BASSE QUALITE activee${NC}"
echo " Capture: 2304x1536, Quality: 90"
echo " Economie RAM optimale"
echo ""
echo "Redemarrage de photobooth-app necessaire :"
echo " systemctl --user restart photobooth-app"
}
# --- Main ---
case "${1:-status}" in
high|haute|hq)
set_high
;;
low|basse|lq)
set_low
;;
status|info)
show_status
;;
*)
echo "Usage: $0 {high|low|status}"
echo " high - Haute qualite (4608x3072, quality 100)"
echo " low - Basse qualite (2304x1536, quality 90)"
echo " status - Afficher la configuration actuelle"
exit 1
;;
esac
+63
View File
@@ -0,0 +1,63 @@
# =============================================================================
# photobooth-app.service - Service systemd pour photobooth-app
# =============================================================================
# Installation:
# cp photobooth-app.service ~/.config/systemd/user/
# systemctl --user daemon-reload
# systemctl --user enable photobooth-app
# systemctl --user start photobooth-app
#
# Logs:
# journalctl --user --unit=photobooth-app -f
# =============================================================================
[Unit]
Description=Photobooth App - Photomaton
After=network.target cups.service
Wants=cups.service
[Service]
Type=simple
WorkingDirectory=%h/photobooth-data
ExecStart=%h/.local/bin/photobooth
# Alternative si installe dans un venv:
# ExecStart=%h/photobooth-venv/bin/photobooth
# --- Redemarrage automatique robuste ---
Restart=always
RestartSec=5
# Nombre max de redemarrages en 60 secondes
StartLimitIntervalSec=300
StartLimitBurst=5
# --- Limites memoire (protection contre les fuites) ---
# Limite douce: avertissement a 1.2 Go
MemoryHigh=1200M
# Limite dure: kill a 1.5 Go (laisser de la RAM pour le systeme)
MemoryMax=1500M
# Limite swap
MemorySwapMax=512M
# --- Watchdog integre ---
WatchdogSec=120
# Si le watchdog ne recoit pas de signal, redemarrer
WatchdogSignal=SIGKILL
# --- Environnement ---
Environment=PYTHONUNBUFFERED=1
Environment=HOME=%h
# --- Securite ---
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=%h/photobooth-data
ReadWritePaths=%h/.config
ReadWritePaths=/tmp
# --- Nettoyage propre ---
ExecStopPost=/bin/bash -c 'sleep 2'
KillMode=mixed
TimeoutStopSec=30
[Install]
WantedBy=default.target
@@ -0,0 +1,14 @@
# =============================================================================
# photobooth-maintenance.service - Maintenance quotidienne du photomaton
# =============================================================================
[Unit]
Description=Photomaton Daily Maintenance
[Service]
Type=oneshot
ExecStart=/home/pi/photomaton/scripts/maintenance.sh
# Adapter le chemin ci-dessus selon votre installation
# ExecStart=/home/VOTRE_USER/opencode/perso/photomaton/scripts/maintenance.sh
TimeoutStartSec=300
@@ -0,0 +1,16 @@
# =============================================================================
# photobooth-maintenance.timer - Maintenance quotidienne a 4h du matin
# =============================================================================
[Unit]
Description=Photomaton Daily Maintenance Timer
[Timer]
# Tous les jours a 4h du matin
OnCalendar=*-*-* 04:00:00
# Tolerence de 1 heure (pas critique)
AccuracySec=1h
Persistent=true
[Install]
WantedBy=timers.target
@@ -0,0 +1,25 @@
# =============================================================================
# photobooth-watchdog.service - Surveillance de sante du photomaton
# =============================================================================
# Installation:
# sudo cp photobooth-watchdog.service /etc/systemd/system/
# sudo cp photobooth-watchdog.timer /etc/systemd/system/
# sudo systemctl daemon-reload
# sudo systemctl enable photobooth-watchdog.timer
# sudo systemctl start photobooth-watchdog.timer
# =============================================================================
[Unit]
Description=Photomaton Health Check Watchdog
[Service]
Type=oneshot
ExecStart=/home/pi/photomaton/scripts/health-check.sh
# Adapter le chemin ci-dessus selon votre installation
# ExecStart=/home/VOTRE_USER/opencode/perso/photomaton/scripts/health-check.sh
# Timeout: le check ne devrait pas prendre plus de 60 secondes
TimeoutStartSec=60
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,17 @@
# =============================================================================
# photobooth-watchdog.timer - Timer pour le watchdog (toutes les 2 minutes)
# =============================================================================
[Unit]
Description=Photomaton Health Check Timer
[Timer]
# Lancer 1 minute apres le boot
OnBootSec=60
# Puis toutes les 2 minutes
OnUnitActiveSec=120
# Tolerence de 30 secondes (economie CPU)
AccuracySec=30
[Install]
WantedBy=timers.target