diff --git a/.abacus.donotdelete b/.abacus.donotdelete new file mode 100644 index 0000000..8369c20 --- /dev/null +++ b/.abacus.donotdelete @@ -0,0 +1 @@ +gAAAAABpdkLcuZ-miUqWjdB0Cd3dpyc1YEmQ8RIAGa-n_3eNbVkstw2BD1X53OMGz8gtf0FxtiCFoIcHfOQ5nT8jAV_c78JdPQ7TgTrfC2RhHeFhq5KBcmeY1CDhHwM8Y8Hn-VVzQyxAFfD89ch5n8zPcsMIVdmBH5y-r5LYVF7MvHrYVV6PfcBD0NNhn6rNJRHQThtYrhFm3Ln94HF7fvlhWwNogMHQ_6-BE7cqgjGQGMf2hzJIBl2Ljuw2ViSDHmIXixr4bMD4CRvbEr2oSAlsaDcIFNdw2sy5Jena9YW_t7qe4Qhy2J84fIF9oewlTuW-jYw3xiVvYUxKRfQNhYUU0mHqP0t14g6gPgow7gZjIBOD9ZhvZsf_Swc4XqUdRfRESziHnWh9na_mlBFeMmpFzUy9bzNkz-H_v9RmkGusKBqNPDtJkupRdX95ZEjSnh7zmmoNECCz6f2NVBq3Z47fvPA4C1QbImQuHbZkFFkrRUXr6XGRaEh0e-K23itcC1AtOd1V0MmDEecTfkw0HeYtQ3x-cWQmkaZp62l2A9l338P48lhCSSDloh_xOT851SDpkf3CBionDgKO7YSiMykTASUYJs9-s8KR2bJx_bq-Mkofa-nCqdVdivG3gocd0jShpu1Pkt7AplciUFAKIResUHzGBhpk6FhxVfzAy9mIQWBsiXrKI1sDBdKXxbE7FOLwVl2p9XIZFscL9R_v4QJVK4ANih_ysgTUIQDnM561asQcLMCRL5_k5rhfzVdB5uDibKqlM5TjMo0oI7Wj5N6qbpW3GdOKhItgYz_uOvg-2D3ix_IIYmQ62pfl5RcTEyZvKxdsbfvK5XZW5oFvgmjjoYazPYPjq2N2meEOXmU63ftGgv79g1IgjqxWXotBgeAZ9rh78J3JYvH_-9G2fBxWcHbzHfQZ4YTUrKdra4SV4RR9HBs3pPBQKHSWre9GcZz3bp7MtOg_gOeE91tftTKXcanILJmUNFyX18M3c3rZK6nWBhcdn7IRExi2wOmaVNBUSFCD_cd_PNOziO1IVsHrZPrPPRA2Jyp-LAeAjRYoa3FVibqhMWdQ9Yu-C0Q_Re5JFiMfavfZShhbAFkDsffXkUVfBxJILLS-wFt3SLyhatxfbOih-01-Q38d5XFREOTQg0ZLLEO-liGKrAs_bK6N9LvTGpAId7AI7vN9GCTq3F6GtWXfHDx9a3zPRPXdkh22oBFAwakamgr5EbCB6mGKypGiENpbfGF10KKqNKE7iDslJ7Ucacc_fSwceWub2DHPQPOU5f6YGPaPgD0sFLCQKsR7QYZRfbBLZyAirnMWfcNDAQHB8HPSB-P5EVGi7djhrvJaIGSN_JK05t4_ZhOg4NA7zXkfWtGLtu6VQrEj_Vza9UbEEO50MN3JdPB5IbJOjBVqpgdYfCuvY_NxPWkCAeQXZyVrGCqJ4TrCwW6PpSZRcrnfxyvw2GkNwR2zpq9DbXR4lXXsTL0CUzi_iE9-zX8VnRBbKNpnt_WOTjMmOig_wvTPnk43LQiWnUGGX8bJl87L4lTR6LsXLtwE-YD51aDydfpE-jbe3xNj9ZO4B0x31-GDt8WJsD-ZBhXN0QWnf2QA2pm1i6nDQkSU51FOD0NexU2vMKpmkQO1ClhjqrcBBaKbtI9V-o3bTRokqYbnGvH_LnegFJ-Yp94hfd-T-X6U2Hv1qL8AzKauHs0Sr7niFVR_-mgkZLoeT93OtXB7t61QE4v_1POBFJweMq_Nc1-4HSX7lVHqvEznpkgkg09_giV1EC8AN8w0MxiDOLVmEMRdg1Y1ybJJ3SV93g05lxSpbMAa7TW5pp6Kne8fCu9lwDFBp3kVoxouqaUOdAvp6lawkKvDvEvee1b9nbHsApvO4WzPcMAdnUMHyBSfpHRpQrXybKY8nb8yzB6QUyvtkYtgZtaJu6Vh-V2RkCsqIxBBTjUO-JE07Ix7R4M7az0q60zjKKMehijvqNsKkXlOX3IDQjk6QC6AwhwOJeaej4YlooxZFPl3pQxxhleY7-ivruJbT-mjav-vz7nunYfeRbjz7tWajvWOL-3svt2g7HSIuIhiiG9MKgJTeEKThGfXznYdvtN3fLQ_ef7B2lIxaB8ZJIqQuFNh5QY0g4rIL30B2FMbOqB7lIJeFtc-Y2enyu4oJxWm5I1bkolzPdoDGK0QWcrHyv8oS7RUhirTOESsH2bca4ynFd9I9DZfKwfCb8gl66uXDv_dmSBYVz9pQeteDrjUECO7aV2VSoqqj7zIA-1E2XTwQjwqhdXIU9ne3h3WZzhArJJstRNp6B2raJX_AGzEe2E2PC-GdT1fAMo2FB894dp2KDlJ3wr50ESzZunnSUNPIeRQez92SM9A6JufDs_JrPgrK7F3PlsYGCkZ7PsP3GFn3Dv7VE9JDRfQU3tBFBqfKGFEx6-rIeyw9CQROBNnmNYSbNJHM8Pb0Ci1dn10fy_Ais6N5O9B-9Ly0hdvADkUD1U7EpEHrWmGgtfn8V8keWRv-q8m72NnVbmvw90z5TeHc-tULIPJbLHBRbnumAOdK7XiMtYqyOauI5ZVq8QokfsfD0g_tuZOsxY4HYSIDPdpak-dJzCXbS5kY1NMYR9M9bGg7v60r7gGaJUl_vZn71p80NeKiU_08H_V589N1PLVyzT3UAb2wvPLvayz4eUEmsjqduPEGdVV1-fG6bHC9plJHpA5c9ppNrXJWav_HpramfcCekj9IXXMIitP-4Q4OBQdY8Vq7uQM9QK4mRv7hw6qCfEEaOoFDYkW7wB75y16s9KLzFQWvmRUnDrNy1uPz_r1ojWvwwq-iK6ik-YLLEhzpIrS5w2eR2OFuQ14qKdeafkt7QyYBo7W8PK7PaDt7mtU8-r6HSdAQkS1pL7LO39z5WO6PNLVTIButdLPp3DmL1j4TMV8_yZOKlft3aYrvJxrQw9IqQ0_LxY2fEASdtltR-0fKr2QRSwOPHA0PXJPaJSYIVzFiVEXOeio3FbDnq4iP2CeyIqNJlC4iik_O4Yy4ZXaemOfuiW8lG6IrSBS1iy6xLVNXc65bZAR6Yq4PbWC1iMXK3FgWL0F9ft2ajE7Ncs46XOIE_55yJPusJQi7dujB7l0KxYqYAd44QrenIFFdAUFCbp_yd8dMfaBF15fCvIBA0i78aZa1IZzPZXHlEto1g-lBwoKmBHzB7nSaAVErnz1YKW8EEMOjb_KnOOKEwFVHH8vMCxm4IazIfehwO8ZuPSluQ9a-NLVzp1yqDh8f33EaWWiGmysAcnGm3Kaj3GebcyjHaGU6slg40ZJxgoGNGV7vP7N49EYtaXMpmuVlP7fMOMNVRFUD0v7strBeVmp9n8Y5V_i91pYJj_n1zo9WUBliPkDbLmMwg5TFrPY6z6obFbvoS7XOZ5996P9G7-doCdQcP9xjOpV2oJIdMUgu_GljC_orQZZXuT9Y6egxtq1w7NytaRbDbcrPy-_zIpKcEF6NN5iVbBukar5O6a2k5YhsHV0hq-SMYDpyR4t_jl3zdkJ8UHvCxRypOrTxhxzhmTRDe7MShyJNe6W0yFCGepJp_ljnBN7zdFLS5KjRfTqcU8tTOs3WDPp21ZKG3aTDgKLcj6TDMsUBagY4btp5gXATT6SM2jI1V3x1mspe9W4pC3hY4lqtN7hpn8Fz3v_nk-kQamI3FJNnW8c5BBbctFXK5mnxMq07Npws_QBzG5mgjwub4w0aXy96yCbx8o2-4t1amiGSKtKL7sTRUDK0dOlXMjB7wHcP27HSItg8RRs_Y8_DEUCt_v4ss3N4lbtz4QjWPhMsi4dhMn8CRtbtzCsFrSfZ3PEoMIpPVfBuCNBIgd75aimV1CpLTy9Oi3cFaUgE30vuX_ItVncIUI0ORRbgoxlRAW553LF3QOMRPebnfkV_HyMRnGFwuWyVW13CTm_PbrvwuMQUJqyyjcl1I651ClblJm1vxq9duUtAMHTyfKZApyjOEUWF-rGMdbYERXHbILg21c8q05WYwFR1OV79VBw4nZNAVXP5-55IvcA1agAwHhFs0fvRPdMYaKO6iMoYXAb5YcYFlzZMCcIq3dRf8pOwiyWo11D-iUHh6RxZPAPlmVI4osqO3-1Q5LDGPV1mT0Gi4VLpjHvKO6Jpn3SZMEq9mw6OJcpXDOPLpV3WvI4RshV6OQWRYrevU8GTRs6tH-k7Dm1J_cWVboeBb5FU_r1ueF9hYXUgzs30riHc-JePWELqJs19eI5gZ4_NMabkVHErjb4Q8nHFn5uwkVb4GoKrptDdkxdJXrZ_d1iW_DIilKvd0_Ye47HD9GaOkO9ib2jlySfIIpnckpHeH-2D2lRRxX7j1fxIBQBbcTLq8UjITdojMP-WTPZGieZU2bbdgP0ZbFk59v7Fkbxl2mOAKTCDIAZDH4pRtkHQ6EaxwzyvtLVxvGYXXYVPVupfwYO5KLj74YHKrysjlQHbamfyAEq7hGovGgvFeBHMdynOw62TMMC0ot0xJOsio7nmDOvzWRKmI9xBuzgMaeTFnw5RaTRPMoQ66BnPfjP74qWp4yLYdEgU7EnVDlISlRAiHPc2oJ8IRC84Ez9dg38R_hE2gWPgWVQhmdSc18WIZkK7Un87TNgRFB1h6EB3tnMxXtZJ9pZ-heQCPz1fWZVjaUXkMeepCr6VqPFM9zIkyGgvvG11tthOUtiTjhxH7JMh7idsisdSSexlz4vHg7mz0t-wV4q3PRUXcZcij08SNAcwfC-lbnZ5S9jLpowUVy43dkRE32ML6ca8sV8mwNWeLE01VV0mRnt6ooIOcQXDlaDWFwCWKKmRyqYdQ0xyHdFHyLVHlnp9ElFxFkEYf5x8ysuY1_HWUZJ7oBfmX6MNNgGiJhY3VOZMhDsWEvAlF9aFeVvizKr2OldDvByS9XsoI3v0-vsrhGsuetTBf9cZt4zTeimZpnko34bPpAMN9BWg7OU3RXvk-XX4yY5jsieXbK1Q5CvA8ld92BI4DfzJv_ho3jPpN-Kpyu_wIoQz_C1E2T4fdoMHuqIwWh0Kr6j3Q-Y0loofvT0zCmn19FZ6c-o3qiKM_T72LUAMFjRy8TqPIjwCLlqd9IPTGdSkCm-f9qmNB5i3FY7kejd4I-r410N9G4_ZlLNhaElTYETFbtq0EOjVI1ORHOkS9gN2KGx6AU_PvQ4_EVFLnaRMMV-uF2ZCIkUfQsw9hDFE0Y14d4kFFo7uT9k4CaN-ic6k9emn9ga0p_qaPslu7C1B2jR4qI6hi6Xi7QiwoYatKDSq9lJyS3qzmaQllV1NIKOEVHVYNwBMlcGE9sdDz8RAr82wOcqkXCF58D740Ij74NYz2Z05Lwi4tAXpLK4IhEX8Ach9SXJdq3IEmIGFTjMMFWfVKiIB0Ode2014Prk2XmGYAKk58wO9DmIVwfCYZHz8Uno1uCFBIPbt2cK0zaE5rdxaE6FGaIcaqe2zP5VqDfO-9bu-X3x5pS2VTSU4DtaX2i4aBgdNwPxySeXecqRW_fh1aFaBzbhh-pMYcF3X-ZdjRqxCg93cC1BXnDWl5MaJDI4Kf6j5CHs2mYPLoLXrJzfg3u0ZgkbgpMSSZIhbwsINFEEUtiLtCdqpfADOv8iTtFpkNZymO9FhJBjq_BUryqx9PU6cYeTfFDI218XImJcdaPuTg2pqwHeaOL4SreyElVTfLUqUUNyDO22QSiWNkin3qLZM3wnTfiVIig-gWXd9W55FDObWv6kw0voSlncpFmKQ4NIQQLfCFXZuSrFL34x0D6pcisRvd1JKic0hGwwCgt_nJLqGABLgq1F6HChD5Mm-m5DEUYPVV4nwoFh411OzIcHdILcTTzdY8xmSViqZ2gYImsMN3ZwMDY4W0sZWo3EQSGmDIV2t6QK_IH90ovLQR6SkGL3YZ_yPU9F1mnAAhNqZ4dqfx1I9x5SFlbw4ZtrCufmuf2poeyGHz9EiHfXJzbe62kcLrB8uZs-6GQS3VIk_CfLMjbzbiDVumRgQafGX_hJkpD0o1S9DLern4FuT4K7GzRCG9UQ06APmWJy6SXXqpdBOLr632j9KE5AttwKm2Xuf1I8ebjnQvm9EwfOeGE5YVTcI5VxTpFAw9LaY2rBMxORjGkqct-9kjVSRpoKGXC4N-dhBPcxko599svijefu_Zri2fiMeTPSe-Ol6aKYPnVcI86shuD4M_F0nS2KL-t8pQEUJxXggmBkQYRJW0GvZYXO1Z40VrFQYi8k6pxySbx9gbBlpE2Mx3Zq7j0M4pXmB93Fyh0Rk1zuMdKp93ic5pZ2JKT_cPtuEUP2uMz4jC_nVA9rU2ZzvuDSRlCJl0OGUKbItqm43H3P4S81jCd_hFfSUc7f4FrIp9KJcwIbryUM66764innMpT1xHRVEAWDpvt4uDni5rNzsVf_7Wlu3eGsd_ZcMbpNOI1dSD-O7dIXb64KYNSPbTPLfYA9IGVWw8-IfyqZaVaC7hcEq2_ww2OiZIT8E_HIehC2ErKk3YFOvxek0D4IR7Y2OWmzML5Ju_inMs63LvSsQzlp3HjZ9cmNufxToguyOobn8kQEP1EQ3sxYVNmLRAxyfpstRFyMs2_SXquIdz0eo0MmLEZ4U0qqPlkM3D6aSHAv1kdzfJvF-ILhO-D-PV4HPt7fAJCtJhORX2bTREK1o4liT0Z_Lx9--WmzlcRbLuQ6aF94BztdqQLYLgtzh9URoKtligISbQuj4tGEaqCKJoJS2LKstNJxrGAHzprbfdLssdRBtC5x16T-1kaJwJVPLEjFiq0vZusBDa0a9_DS1zKbJoktpUuve1ncryuWZP08Ml0BDOWt0sAP6Nqiy5EGP_G9mC79mrc3FKzfqdb-RJ_I_qap9SjuW2O9iBLoo735eCM8_7GinEEq4Q1YWgS_cPQG-ckioL8BZGIvHnAEIhMDTmDB9GCBfQmegqym0AIokKPhFv_0xSB_cKYpxiGlUtAg1YfTtisB7N4MEBEMVebHRryoeTuA71hKaDbFBvz-28c0XdueWm2XhU9yHM_kNpBFV961bhGcLAB-pkmV2DnMPQ6-tmVrr1-9KJpxYTrkBH3MsC6Rp4W2oUhh_2J_KYGGLZIDTbV4C7TM0nroksIJZC0xoO5clnwDy0A_QsHGRsi2yfv_QT03DdQg4ZxbYZA4zyHxMP8v0lpYVeuoCPlnvb2cKB5iVMZghpXh8BMkjx6uFQGH7zyn7h0so2ZxnP7NLM5Y4fQ_nxC1cbuJk9aRB2A0ABFF7t9CJ8Gw6Tu4Iu99vZ1JCq6vdv5VrYpHA7rwFOQDQ606QPHFJpaaVTy6ROVscVdo7KnRTraJadR4x21KyHlWtDKZ1GGoCX7CxPyT4Zy56_-qvJTBro4zffafVe5DTxL4IypErNp7Q0Acnh6HyFzDui-3KhmF-rUclkqPHA4-0B7CXnbIO91dYmQVsJJPUCDz8PFP7so1J564vrDp5tq6YM8pCmfa7IhIU9rOr0bRGxTiB9VGHYzDJ3WV15FEgV0V2pX16pYr9MLRGBioJ1CdzC5YCFOZRlLyBNbvNxs5ve6b9kEJpwcBtfMYfF2iW56qOOpJ3NcPI9StPrRmXTPVYl6AnnLnysLcw5qch-v5-XEtZxn9ZEm1SuCwvOdvlLrBnJGT27MGcSrY488V4mFabNLV3uJRMz_MwD9yu3u2Ptgx2hWy-MUjeIeiNhIZ0USmbIiHfCTV1zDbc0__6EVbKZtZlbZUO4z2w9ZUeO1yemViq1cxLDTKFYcbVOeixHg-slGBZvFiXybA1yxVkGI7ioTbVZb6JAPGGSsn3Kr5319166kNK0j-XVxpbpdXKY2-VcCFcqVXgUrzMMN_uj5XUqwQ4rdLFE5xpz_Y-zFNE4bPumyiCep059BHDeU6D3rm1v4Uwzip3djB2wiRN_jLc4z_DXBQP2ZWxc_6Y7QgWw9ZiNDevLn6utn-69CZnltxR70SLgZew2Pok9MC8mLtMqdOSSU9LOtrqBV1a-CmI5aYqOB3iKBrDCYoFBMyjmueCntzGO0BrOIeBOM0sUtqsc4gkkeFlAO8GgiHdAFVr5Pk4CdClhAWUA7jFIvn7_eMlxHHlZfkMPOXW4pWEmiqDMX5DSMbifN9KoXkxOa2gGB8sNfeVurOy8b9JLBSaYUXUz-5VJMxQ7_YZqFsY8ADEVZor-pnb0xmeoNl7ZsesfVAJ33igXFep28g9pd410AgptePtTGrIFFa52HHkANEX-PFFKW1ZFvZW9bhtcTb4sDiAoEjUH3Ev2MOYdBF1WxrdTywwnL4MaCDK7eSJFXxDcQVaAUJxStey9guiR8atJHRopi_we6--Mpq2AuHdaEq0knQ6i5iOHCyYakNLHUqZX5suFXZmoOqTL4ULNHf4xxNGFeALAluwUEWJPcKLWbfClbI_1qXEE2CZ2u4Cn3WZ3loW03_idvi2waQjYa2mAji_lcwStzeoJAs-xUwMyWdmyuApbKFykHSmQCeA47aQYVG7Oc0I4PA1EJLgtw8C8PdDBBQC2RlhxwLe50YTdNu1UeB1USLHYNrL0XnCG93wUbabdO7bnQC0-nAOgEwUjlnLseBWWVcqh6FyhFpcZiqcD7iylZnNB8rKE8eoDB6vtUWEo3GB2blgto9xDKiuwsZ1Wa_5O_WP2sttEhYdDvfh9m3FC2pwlxJK8gpeADWJ5sJc8B-QAYVtCiM-8DaM3GeONu6b5gqnn_yINq63bxHfc1P638NDz27TACZ0oporSxIi481k_XEMiO1v0h2agHIOSAXGqnR-w0_ZgmzOBZ6csaX2B3dDFOOonGaKDpH1l9HtcK6Bur_gVB0XxVmVf671WQXyGUzAwJNGrSEyHaRPxka4N36OqLSff8M063e9KC_-sQnM3Z9zIlVuhX09pejC1FvCUwHNE2VK6kLZWzx4A1Hovr3P8WAta6SDZQvgnGYmvtls9gKJgRLqfrsj07I2HSPfSN0ZKq5M05RaMwDTdMj5uC0ZjEX161wiG9PNSLfnpt9il8zI9m9KTuK369plzgtJ0s01m9J-L3766usr_qjWLuKuuRZCOzSv47yJnL2dKruU3_0I0dibICXaVL5qQamidRbq_f-wspuB6C5oGM3H-iz0pzQP9dNAvGKKfhsiIrPDdNmwcx0DyUlFfOIgVicJ1HM_4PPZJAW9_v1J3av2h0s0fLowKWSqJ5UAwgbyHPqYBxxmYmzSmRGrmvzRKswL6GFtQxE-SxY3R5t8DlD5X30nVMRpnOQbs-0AKuL1m_-Lq5XfiON0zUpGKQD5etU_LA8PMS3Qesfrdlo2VtmIAw65srdiZcMOl_u3k-GEdeFyyqO-FRl-b7IsvgeNMFRITkfHZ4gm0Hyb8wGHB9BYy0iWPup6I5QAMp-SNv_wcwUz2R-QGTYsOCpcjxxwkf3GH0nmSMMJKlX3dDqabywyni4wj20mK9QOQqZ33FBgA4Xb2LwH90je_A15KOEmbMrP-ChnazyQX_tV2dF3_lkabmyG7UQJCA1qO2_QszIf4kI4FbfEKXkBkH9uvLeVscfsKEq7YRBkU0moNT3gt9TbHjOfmSW0EYOEBjXGyTAxb19RFT5R0rTQpG1XynzLOdh5FrQWBCaoTRRAJhRaTwm915RG5xxbn9e4_PYKwdwSxGFzvjIkW9URpl2Ru8qiu62Gw0mgfsqFh7SaJlve6m6zXswfD0QStkVSM-G82duLxRsjub-RLaHGgIAjYfC5LZOCIeNsXBYJOOdi7EhPJ4X4p3yg5FK7Qz68N5Crs5bNVm9akINfHymWVQBuWbv_zBk0THdIwxAucxsOf0LeeBfEJCRG0hoBUGzJVsU4A_6rMn6TChS5n8efdrgUV1zNM1_-ofZOao0VzGY9PwCX7Wwg3UTzJKAeHtvpulXYXZs8o-6X3wUhnX602ACQaYAGFJI2TejTKU0NINQSNjj2DV8pxGz0rVoYDyxdlG3TIfPXax3wQ0x6b67gKRb-i_uz-5wTqYCRTq1LLHZaX3pbsmNR81DkWKxdNylvDS9pIuAWrut7ENj_OvFGhIo7yGYbujE1OJki3CSpqP2wMCa0lJuAIlt7SREHDGKm8T8dt0y6zGd1V87pmlVNkmdV9slnAz4Lg24IP_uWyobSDhAtwkDzoSFZxguAj2rkMK66lTD167pYL-dxHsjlfWss4DB_JXdVh8dDfxwtwjkv4dxCqWyoRTq_LqnAERW4GGX2xPWwvQDbw8PTKQNkUG-8RydKAbk0P3O63gzkuhRlAm9AkJLb1nlKGPxw6uz_tBwPlr-ZvoRBD3uosg-rejj0LWUmIy_6OPvELmOhSok-vMBWNYm5EEQOqWDpPIPxKB3FpQKj5HMlDzOvZ54pSh_ojn6YPXdFZHi8kvuj38Tg7yUm9mK2F183gss368r9os-AYORqaWaOVaiLiKaTF_Epld8RW-nik36TweTaf0vMft3rirGk2ZMsgL6B_wWXIoq6zYxzqSEzKlKYolaFRXJUKr4QYmZUrZWIuZkdiMycvumWuPwkRUMJuI0kuFWqZMEYYKKT2cvHTVih90_XuV9lnQHRz6wDsrks6aFPVWtt1sV1rMBarifY4RBsOgFX-yD_sg03okhCeU8_bCG9VgLvWz8G86r1WWVfgh5yxqMh8S-TrE8p74wxUQI3XJol0ELQSB-UaJ2vlfoat6yMGk69EllN0gs22Yjm69xffyJwJTn2S8ifh871mNzYqVEDH_rpfD_7zZF-JZnTnJZwyyMkL70EZjeb55P6Rf5XCVlOiR_hYy6jqM11ksYUoc13oZf95NGy7382khIOisKzGpQISfcB3Wpi4mmQGoMy_Dyo5r1b14lT86MLTVT5RlThTvLEMpiVwv2PnCqcwmkVkq-RVlBG3v5T4Q4Ykl5DuN_Jj8ERiRWtaHdJOqBHIzaUp1PluJW5RFsdK47kI_ehe5XFMV-8LNzQAQRFyTKQI-QaJfCafzSKJzzjTFgsrvRSZ16WpP0srm0xyJ8xLFuF2MAEBq7UMWRdjcrj7lqxuqRWz70p3aZ7ZWCxRgPBU-nXS1FsfDffkUY58cTm6wbchEgYCVqs_yh8NOvu56_ncVc_3V3PAHRr9tlZmM0guR2Bp9jrVScZP9in_GZBDXbyUZkXl2q12LDdWUF7oDUi9R2URfHFeX48hTbwUE87cA7uAvn-y3o7GmQ4xdjnoa6UDRGpShHCFbBGHaFk7rWnqfZvhQZGJI7NXlMfcZsqziPYg0LTgUEu40GSvFsVFs5-I7vfDiiOVwIGbfB6kI3DUsVBXetHT2JkdrIB1-8N4gJ8NdgU7CZ7Rq37-YVC_zcE_rcmqWvuc36yvmCG-JzA_CeGzhvoP21f1NllqbNcgf4qsrR8MpXDOJX9UIWcxj58Y-xmaww-jwwYeCJDf28Opg06FrIUijHy9qY_c2jQkGriEOehltyJZjljdKf3yX3i_0IDtCEKuakzMo-dJGuqZ5J0gcsxX9sdNdas3wRfZrUwBCP_jS5eh2rVjPwSZbZQ94Iej-3c6TK-yzX2Dt1DTY5W6aXIKTh7P7g2b6JASEMe5au-uWiMaYrK1Sd2dLVyqpG6STTvOerWI-VeboLNIhWSQ6zP2ysu1k6AtMgxnaUIl2Ld6ZIlZPdkKASAPu8RETsXfKIxGkeVYIXFgbR-muCh5oehRvCRf_YXbv1yoFp5zCqhxs_QpOlu-sB_GJK7OwxDV44_QY0lNvYqNb81YbKN1SjZ8gn-5NoZGov8uU8NlO9B46Vh03oSRrjXEaSh5Rg2euNCRoboaGKfPh_ayC3KHQoateEc25g9iIDHTkZPNx6pxdz8qgoSKQMgHAdvDMaNFGeSTiCc4shr9aw_6vVyTLoXo-5k_mxrFJ33PN6y2LeNKc1USjaI1TFb3mgnquCfDVxrMhHH6W24IWmXIXZX9TQddfjYS4ttZ3Kck0DsucyOVHMNmckU64YGiS8cT_RxomVJvXv0DGFaqFtbCa8iXigAXCA6XAeeMakvyzboCSu5SAlhuTIeu3MXjhG1-UjPKfC7D4owTACXVBhDXqrGgIuZwZqjn43OI6gZYp0JNaCfikZCrw2pz1pTI8Ud5FRNMDvcd_QaHjGr_g0DEgNjYrBmnlJ47QsEdSHk5_8SKx6HqoDJIEBXsyJ0b7YdGfqrfuY_j7mA4uO7oEPvi6WXAUUIYFvzWXY8HmKUTt7cnVG5KeczDcP5yJXwcNgRHxAcSa85-0sxgy1HhwtsJryR70HeQGoU3ecdG3f63qVZpmupgEHtwJ_2Vu6P6sgHR2N_4kOtYZWcUImBo6snuLBizdoz-r94nPY53JXU5zxaUXvmqso_wyEIwRQ0X4iPC9WIj-LUxfPpZtStS1Ogpwool2vYK-F669nbw0-s6YJErCrOV9UGzpulaeDiUK6BC1TYgMF2KCnHN2dRl6HK7xMg6K-EX5QZqkUYnnpEPte-tfpka8xtoRsjMvyTkWzozJ1LGxhNeArmXqTY5hCiBRu9e6wMlgz9RYenmCrPxaOKBvDxIkGeavjKYqMRpk4hljoFbFb-DEfYUnhjWzPNRe1ZbvAgHby2eokZEeX9IwyKxTIhjJFQe_ufaN5-irunhE7QAAqMQ6cuQSP_QAj9CdYUXIz7cqiURAjQXkllpTNFG7V7WNXrNlO3vf6deQCJDkiCnJ70O6RNo36h8MlqUj0AFmc7nirC_CWEPP-PPbph5FzSD94ejGHljvRUSjRXtggu9BMv17fq2jDPsCF-xDnb0_e92cd-QnFB9O-IYEIRsFMDS58DnBf4pmQv-1l6WBv_f2rVOaD5nvE-k8vTsTfAP6dci53nJLkOYZCyCxGzixfwASTlorDdBoBqE_bUH4ECDEoZKKhsEJfc_7NXr_b8FQim4oyOCXQfGlfdeFqKoL83SAdraDWh468nlfPF6rDYX25l6z61hPAYx9l4r-6sm0RiwGKd7MjtttdbuTpf06OoWZR5B9LgOFLDb3pwed_jJDtf7XWWo5cslYs3Q1M2rkCpgMrudaBcH1aIpCWUnDLbkAmKwnpvj6bpfRYGxOqATtFf1xpcoPS_4JB19bLjdjbXMnDcoe1wgrQZ0bWxzBy6bviRMyFp6C701hJ0NRHAcGiIGlSk5z5uBxOXjQen-OUM-nDlpplFO_bv6WWep8sCHW_VPSzEbRNlSAYuRI3oTBMmzIF7m-QI6eO_r4NaAH-L5KJth4lK9dfOluKZGFI1tFuXK0ZAUmXUz3oT75wtt-_0KQxJhkQkGdoQgO3otlFLPVW_p1aMWFzn9Iif1-Bt1WCbIfv44eujh2AN0JUnrsJSdE-3Kpg54SKwKYghmT4nAOewvetpPfe7BuVAEsKJ85zVoctzNqg7Ap-AUQF5h3yskwyCq39eAVhNkQ2mcfRsEjBbtlethMW99OYlVwsOxah7lQJSJIBxBxJcwCTtC3ivENfsGG8pmZGqma9Wv7bogGQVpZyRbW9ilAljPBBrCIU6glbhUNxfJmjGNM1X6gbVpC7I7n8kgYp7Lqih3UCqnTQMVaRtI9jxM5fLtN3ARRvRLL-hPisniopKwumy0pRGIB0_lupkCTLit-83p7KJxggpjE55lVab-KghlaOb_sKbKdaGqEI3oWpMmo-8CmOoXpo6Ob402iZsp6Bx49eJ56YpPjHhHtWieiwj3bBL44q5g7gW14HMx7IYZRHUEUvIc_7gPI6OrjkEiAVdhdM8S5tyINnGhzgKa8_YIdMbEYaU2I8ape7s_RchcPF1_umcHy_ifWWr9EflI7yS8bwCj75pcYKL5NIb7cnxz3pNAWH8IUoayCkyDowQIxF0Qs26QB82LdJIiRLEBO8Z4YV7bevWMJg4vzjBAxaA-_hHqoT64du3l9AIaipsYAUAK8UIY6CRTD8gm46-tSPhUZJaBdDRricf3qSj28W-435y-zad8mqjO4EM2cgCFoyLwVpHqErUTKunInfpjZXCQ62nU0-boT2teOJVYckBvVajInAFdUnknUCrX9HhjPrLuW9vH1aRy4OeihYDkgOFziKw8J5v9QpvtxMfG6cGfr__SdILuhY-TIaE4eIRIYNHPkeroU34Xir8xqFIIR1EaG3p-Foi_vnuH9g51M8difQTyqFUdYd4qVS1NkhF50yswSzFhYSOpUkiSxDYx9f1s4DBacaLJn-nRCalFKxxnVMwI0L8lkhU3nzGiphUHG07eNsSd94Z6d85kX37HmDsSBmb4liIDXa6NOvnrUCVRL3F_OFgqPt1O-lfSgJD1yCeemyLChNKe8g4--rr9AHEy5cjUnZ-Yd774z8eok1xnFeJiIsYRwMkJgqUhid9RUBSFGqqANoLYN4RHpLQwfgj2-eJP3Wp_MSjk3uYCpoWyCYiAfU66VbiIJyP7J6062wCdZg4HH0ebRjvbnHpYkrAtKAhR6u2qIRT5rWNlUFpuo1HjoCbNlsLl3aBIPlXK3zPEoJPtSW_gQQ7Pv4gbC0QjjB0o3I3XM7NQMhl4aLFF4zTDcG0bGbb9A9P2Vq8SLELDgF2n2jKN-ue9BoHy-QSXOeM3pOEtlQtHCgn4BosgPwf-TVDkvvI3qA7-hJC-ZbPhGwkRj7XfmhyXhnod6zCTe8xr9m_itXbMEgMvHIlXnE6dWDEOKNbAwp-MV8rh3eThyj6PGKoqDSVmTqFlAzJoTMq2ubxktdJ-7jMX9Uxba2R7aT92Ijz-sgt6W6OggTUoeIA4Wwc9tUN0D40kOneGhVo4t8lPS778WG6ZOtepuT2tNwmKrJ8FbPuW25fcCmOBMFMB98Dx2CQzVuaMABy5VnUPGAx6cbG49MoTxtoD9oH4MfCsaIfZqsZ4epwEzHTjW-mJTk0DDziICXI1ljxIro6175e7NGsLBDkELU97g55ouOGiKrmju0T7HWURVwpHHnrOCvnrb-iJv0MX1BvxFrMkxgE1e8SQqbwsW3giM8coXeN0h1j291vOUc9yGHO0CzkQ_nnW_fCqqH0PfNXuQzktH7Au0uH2HNUucUS9FdLRhVK862b744mhKtd_K8BprWHWatKRA9-CMMTTs7QShEbVowO8S45GPNrYA7v2tNIq9r_Mh5gr0yqjpwnvoavt0apyl3vipq6t57cLGz8ShlOH9Qy2Br93hi5nn1jsICMB3Olx-PFsHutC4woDk519bZKG36DfaRZsMAXK4FlpEM_Y-TCBeUV1w3LZakbpC7SJSOo2D0Rq-sGdl-OhxFonUWcb0CqwIu13KF64FD-GTYloX1PbsBI4ro3x1dKLqoQ2IsiwyDPDGHCSrRHSqqxgexAaFLBq-lKeUm0nRmcldct3HrJ-agNRJnYqOMD7fLu02wU9EQ3Di6YvAEfbsFNF_fnPVeq7CbIQxBxMqBwGY-tmZ7V3_lcuNr5KARTNsCC21rXn7G8X-lr-9zAGbUelHCQ0ifvCzcMHO7c-2LvGnkQ6L1lmufUdbBTY4zHjpQACim06moCqjfR__gepEJoVvAvo_B6RrpSNL0nFNctjZ30GA2k-gUJlkdMHYSbiCTiMtcuKnhqeya8MARHVtfwrkR_OiKLE2-0Ci6vyxIxYbxOW-ap8W5Bfk_8ygbc6UhAyaMCDawXQkBgjXHGe3TEbbqWgsQjCkT_MrUTtndiQv7yRAm2FFdNOjG4b4MyE1Hklo5FFrFxTleDKXDC4GZ939-vTHbK4aSUEw16kqvyu3DsBcvNZRPWAkzh5FAz-56-fRSRAim-xTwyFHIFBjYNlcssSO9btj4BagWi-lh_BMHN04o9-zLFtB-CG6tL_yk931XLiHICYVmY30AQNYsJnLheSuzpFszs-LWhAqxhiWLdpPCJQmmeAUga4hHjciN2HEteXCdaQ2L9c5KMSu6ue37Den4QzcySPzOZ7v8YiBpDtVpAd2cZOIlYWZ4f5LDVQnyWJ_W2WcU3h7U3ofYa4rl8G0z1jAyzjh5Ly-poKhF6rM6Wtj9SZdFG6GKzJyPmgI3EnT5SU3C_bqhJx0ah9TGnz5hhdGoIqQwmFu63XmOwY-sezMk9AX6-t-vdsZ_uw8a7GAwZHKRvfee-Vjx-QqbZbU1w7ZCWy2zw1CScw9vbsN8mnFz-F7ne1rLgZYdjTfdoMCT9ucnu6cR-1LxikY3VUpR_pWClWWLJFdJwTysr-ybowevDweIpigvwLEMkt1yNwIj9EHUDlXE4B7jjzGfhA3IXxPg6Eclo--WdFjosC_8qn5LydaKPSf37yKYWDE0iVAVJEc9odozGr50ZGgEfVFn6IjfpgJgmklMTNYXWKLck_CFsio_Eezd_dVFqrY_3xqqBlVXLi_tDLLFCF26DDR05P3XUdVzgeToYnVd1PRLNUjnNdqq0s6LTBdeTX3XoG4QPwqMiMGu4Wk3wOlwhtvpQINzd3V7FofMZo21semW9RRKavUWzhgF4Z0PuE87BG7wREWUrICqTkeHdxxIyaaLwnQ_7u5ry5gaz3EOdme1tZnpxpfmVUcAtqaVWfGx3iSiBLB_mv8dxAbEpmjL2CIE-0AO3QJikdlw7uhuoJtTbwFyUiaUw_RfuD7wWNlmamlEwrHsBClAYZnaK28DrjmrdRkxgOJgzX5tI60ZVnJJEsCdfWOB9gA8nSURlozjGimUdN0mxRFz2Jxk3HpD1wun3ZW4vcp-fYUPXh-ClIWeRuoK0Q3_vV_qNMttAxCSNo0M3pNR5lwpE-XqgbLqzj1iDwH0gmT68m9aae8fsLxkjvOPRhFHooT8y4YXqBV_H13F7RyIy0iRX4l-qoSs8Hoj4odF-G3PWPPEGkfQqLykcmFPlsQMAbvIzF8ovhlgGwSqs6aBtf4y2g4J8QL5_jKBEy9zDxAbIDViWA0yxDtiIUiVBPtGoLWI8q9j8Hg9-dbfRXAzkKBkMFgaWqprN__Itc-uPMyVmyPDycJkKpEgKxwUmo-zuVpnwjMsh2ZHCugptrkvw0_MPOixVOLlveIuz3oqUns9Ve26QyS09NXuiDUCVndLFHzXlbs3omkgQa-FbBZL03xEMD7z-mtQYtB8A-cQZl0Hs-kbiBPB4H_T_4wyIQLPCWQke76ZdoZEe96Ncs0_hsS1L-aMxN7W3GyE6Ije2BQiCcYJkBoYDcm2AaaDel5VU5bFcxe-wrcFPbSIcOjWY_L3G3_XQ3KBM42jSOlIuYUHJ6DNpCXexOrHw9K3ESuvqoVJm2fnfEKuigfB7dPOHxXCijiDqxElvnBl10uK2Z269FdwjKXu0Hn6tHY4Tfy_xkOgn9nrB2m6RE2W-y4rHwGBh0HohTo7R_EtjaXC-6-D5VlIjTFbgZpC6q_vEVyiNhlkjpVY_CYX0-ZG3Abr8HvMMP40cG63GvQLDSFiW3aol340zkN44-oLJaWKHiA93ce8gzMXPRULcj6YCn0eBMa8Wg1Xqt4sbM1PRXVs8-hofr04dvLS4asHs9N06S9yoj41cCFOnAvTICzmRtXmCcwHKrIEmai2jyKnbNExwenDxAtYvE9E3sbbJvw9lx3kXxgXccnXz9berMIuE6heNLdFyuEMo42VM17OCMw_n9xnes-yIW2IVUXv4Pf2gBoMNiIbWrdUC8nta4yLyQRap_OQ2D7O6wnqdg7J3igxwWM8spmea5zyqeHph9g4rpMUGqKShSGryelE4RvgFwRJeQSq6ccgixsf252_tZqIjYqllHO6gDddYPf5-6-ykmLdagw7Jkw87BhE3r_Gfx_m9dhk4D7V7pbPT9fUnO9jrUbUqgp-Q5Wm7FRVhD0SvwQX3bgOJRgcqI_YAxHoPg22CX1zSKr_nWNfPwbhIPGYanOVoePT1N3zhygLCYvsXvAtyHBjgIM2LyXDbwMa2VNUBnfILtntuToZYh7zagT8Bnh61qAc8glqBvVK-7SUhU0yfNoeFlGW6SCCBoktbZ5NwxYZx14YWcdQ9biWYcAKa6lqCuz9SzMReMTB-pmt_2ESRifYTuU-ccJFjshG47tF5Wf6jTWzt4BHMeIn8wSQS96SYj4KyldPeT45umMIAtfIIBl7cn80mhieXjunlDd1HTcbxZRaAg8dF4aS6ClxSg4jik9tzCGV5_yrIiXdxiT8TimiKh1jyEoaahSq-9bTt6nIAdvzwNpC1gdS7lp0kT83u9AX13L-8HdInua4r1PCdvATILbBlReBs-X2m1w_ilIRbs8OiCVC6nuiJxDzfD5TTrdR9S6rOIT33CT-gZ_eY-rU4_oUcbvO2ECF53D_op0iGXOqHuGeHsda_QZksM_2LKpFSVK2xKf_GS2VvZ5BICHblwnGOm2N6huKum1bfktwNBdBQE2_m-YUKVSU2PQNQnuCRmQdcw8aL6SiF0TgDIwXF8nUVig-g0GCbwe1Of9W3vpI5PGsbQge8fLTLtXw3y4LK041-xLbFmuseP0AIuKwXl5REzEB_u38dsgBo38PBMmEjL3nmKvwx32jBtw0XIiVyVBBni6j2umU03s60gm40GvTk0Tsbj4wWGRr2_ir36sR4a-1JQcMwLxXcp2bOUOOwDCL26O_D2LWEgMsMZOzD3IguYnotTLN-JdqtIGeaZvhA7c9PGgLQV7XNtMA6ySpF3hc_1vJnrhjhp1QE7JOJD8dgPoEgWEFv2xJjwwD2SYU_YZL7rFx6wRINasosQU-6Qcmks2fbCS53KQWAFS-VyrSnVc0pk-uTTURY7HnkNV5DHq2qQOUMR7kKtmerFNOOmLx5nWasCoYU6f3tvsMGdK4CPzy60moDM_FgfZzQ5QU0aMjCjdnFkYurSSZ8ddEDUR3zYrOfpMJNFUcZGlJy3XMdKmBZdlnR9jjzbCrSSqFv_CDaDDDj3XemueL9FymAanKhrRMb2_41ESQp_eYTjNVhf9xo69UapCext5Zc2tVpfxJEsMlQ37Yr9Rc-jYamjW8abvYAFfa36NMsHlPH9el7-XVEP7TQFc5mSBZF1zmS-B7hntP4syldSVGLfsDPoWE_HSv5mICUEzmQAD5mZG6AvOvXtZtlwaNS7TWejh4Q2ZIYi0NFrIjYIGrU9KZqVyYPEqCCXs8NGyI5VbhsogyTl4rbWgpAFJjMCPACDRNCQpH1hY9ynTkz6vKkvJUaSNABlRaGRK9J8S9pfHOZ4PZwo022pQcSSA_Xo_qwzrJPl3emTwYb76xP3T7toW1PXBhi5L1M_STEq2lNpwY0IpMInw6RikVvCx50sQEMR6YlxE5WAfPNkJ81fwcIwbgmgOS0UZv3Rr5sQCJBIkeD6FwcLDr3cF1AQmfFPk51O6_hlbwoLtLIQO8T6sX7uvTqBJuodEOIcMdSguOzFFHzrnlIkQavgA5-X2IUWMZ1tsf6HbIOcMx2Po61rGqiypWwxQXyDwoR4vZ-s7JxeM7_TSnTOniyQjWIp8pRfyYLevmu1HCc4bk13YrtuXYEVTWalF0wUb00AN5UjrGLWyBzrVVE5Xfqt8RKHFinRHJoTw1HxyHoG3IP14RhgwmtG4tBDhJ3moqjYSMXDBhPJ3OhU-lRTZNcjPHfRX5vSKcE2KDje_LXIS37mH2o7Bg2qFSeVAuaCO4bxstJZurDhG9-ggavLExpMCUlKndblXQzGgzEAuzHfhT0VpfiCCMTyQJN0rOTZthvSb45i6b7Mrto1ZsYdSphXlDlz54g6e0QP4iR-JFg63_Say0_4EN7tKAxbUON3_zdFORuync1J_0XIx_tbhueLOCIkyOdejhUe1sq7opWd-92dxZc5b-UgEjhBlz50oO6vCpwH8ck0LvbkRsB6Poryu9o0fEm8Eex4jFcZwpLJGnFtavaPymYahJOF2oWffJ6WfmSJbgdGmA97bmr7sfefP2wJ1f6wlQ4CzcVpBjrJ6yt19XPL0zwQj9jyqcAd7KXe2VK-sfvGfLn8A_73ExGBFr_LEncMTK4aJRxeToWdNEYMYamsyychWqgxrSXulKJKYR7K237xBTXV5Vf0R2FyYY-soAoIbBm-Hu_dvOAdZ3O1ZzvwFW0QgAWyzPoi15OIBW1ZmWNKoA7aAPYDPFzZ0x1KzaiclaPdq4906SA-F4Xzbo9q4DK3F0uDvHJgI3dW3ONOHZHaMZo1_EpNA6wQ8h0j1ZYdf3doDuN1xHcskMvgfR38_U0vL_c1FB2b-oouuz70pNfV9aZ804jYDE5-9FAtfJz8S_9gZ7GqqegOqTANuJvcnVk8SBaEQjgSf80Sw7ccfeEuUa15gp0RiAg90dsdNTmHVSjMZo2U4eLUmjbKs4I-MgT46J6HW2nVYOki_7eGJ0RjXjcHPgvlQlW4RFb22TyY2xpjk1UvYOoi94XUtGU3pPWp0CLX6oMaThpTL6MGjuIPZpT5j5UCAA1TU36N3RtF6X83PGTuoz4p3aCb1WjfVFu1gX0c3JGqlL075dnPoynJ89BnB_1Pj0N9BVbjvI6_SGW8ewnAFIKWUsm3fFbzRzkGAnrGyBuBZ8PRwYPlx-ovnaJThwQP15l88zOf4EmY2saCk12QJba4ySBAyaWfv_8em8CvXG89EumPllFBy7HvqR5W9l2v6YOLJb7AHa265tDgrSk2sBqqxSwQYOR3VsVq6Mc9RuPHyI2OXad6xwwYf7S2HrbT1nxeVcevz-gC6Q-_FtqrxKIcNWPJuflx5lNHVCwf7umbiFZCqa5j8iDYCi6O3C87P-CtMbt7e_-HFrpH7t6UeAqpebR-DrCE0G0yISs-gms2EzamUXi7MgPVpanaCoyt65HdqX369WansuUt1Loxex-ohJNE0pd7ZKX0Xzr4JLU_OEou-uD_p6vhg4VEFcZAcxZnuj6zHBSCYJvM6eJWKum93o0eQqUfg4gcs08FBjW1U6cwB4c18cee-DfAy_Zexb0eRWPKz258o_s--HmIhNb8GIR4lilwcoq_aKWbUP0Pk1KRLyUpLoSKgO6oCaEhAyWf0XUlW0NphFqwi1ZzRZfcR2vSpSHAcjleApAwHzfjpurwqRrsdQnVWsP4yDGpysaCVrJupfyEZPTM3zSMHDLLR_AeWHJpyK6ipRZVJtByRXi0hjSgbiElfndb8mSUeIYFJyFRkMBbe59RtokoytcYHqeLzzvMX1y9GrxrgCpYxZJ8JGEe4ZGP8CveiU7ZVmlWpc0bTBYr2-E_Yy5IzBKFakWF8b9K77mOo-aEGVcjkbSRNXpumM1HoSvUg5lZ2vaYcyBzf9T2bRNZfrkmkvzz7OmOIkRkChHchwUeCKcnH46P4sVtRa_MxLv3g_rvPEaGOk-n1V6if9r7T4nJ1KGGV65NrUO7NMEvc4NyrPmC4gE5zIvvmk9NzFZ1BkO88SqRDtEZ1a-rjoR5N_wiS8z6__fZLljxL21gKhuQ5nWBap0LjbFWrEl1xF2LjtOi-c0UnE2y75yNctNPvsfkyXuEF3wzDijnlPPV7r9Qn6riXCqH4NNfb6f3lunLvmJr2nirNU8BdwDA5I1-as7UgGXvvFwZivmaxGCUCOt4FMio6GaioxsYhYFfFSRcfmJajANtuVmBAICHhGjTVmhzpD4ic0lTmeBv2wY0hAzQ75Ss1RLatRo9gKw21scUgjhEssXhb2p7cfqxObixssFRFnUA2EIOnKUBHkRmf7YUZhF2EdzqTEnJC6_NqMLOegHpXt9z1l-lL4KFnm8G5TJyv9zAN0hEZrB81MZ1gE1e7nLMV9D4EXYr98eyijEKuZtrfpUU8FuP9ub2ubMBBuuEyjh_nMGq_0iC-hcz3Tnh4ynzqcfuCwUsb9V2I9mLninsmImrz8Lr5-bM0mDm-4Eb1z-xaRTrYvvtlpE6C6xafHL9lV0cE572MxtFW8OrfgCyGbdJS-s3aRDY-a4yvARgG6OFuod7zOHmbLZX4-reWeuOxZ40qjHnAne6Nx2xL8dPDoO7S03sYMPfA3XrT3EETo2g4ZzIcHnwFv1Tp9xWI7Sv0IDUYPNXVOzoFX8Wdr3Z6-YVUpJTaXgS4oaFBJJYQwu09DoUR00Q7H8F_SsRRCMaECTd_OH1Xn9tDu_XJ6X-1ajn74W-OFSmf1wqoQAtH3CwYLOvRok6H-_NDgAliUt9heb8hcLR5Arg7BAA0kBcpnq0xufy2dLZ3GQhf7EhR3QprszOGArchNCn3hFn_-fHEnk6FQnIX_jCOycXDitVi9vPGUgIViMw5D3uaCbd6lBNFty1PquRGTq1wgJ9pCDRv1GdO2cougJbR5H77i_MEoji_wQRQfL4vXUJjWKOm3r-eaJVsvyx9K6LBHhJOA85l8noSH3USYpZNjXNmM4PhdG0zY3NeYZseTcM8AJR2veVpVD-2c1ZfNL3kmRMqPvKc-_RqkOG9B8_jJ14J47cC5036SPbpdNSNuX3JqDAL4XjJ_OTSJhNCakaR3tZLOWvf1zmL7XqOgLfo0QHRUboRgjmMTMexszSSUC_B9mYHgG1jq-eijRhtBVxaWHMhmYipi59qGRdq0wNjm9IaGjIuWbNKZsQnIqVC6pwCLTQIUyXfJjj8q4sd5uuOcuPwX0ANV6lJl7nTTtM-npLuTqrB0D_H453HLl5dqmIDssgqAceLLhRl-Zs_PWvE-Sr87oAXeZc3RDSZkYpK9DMK0hWJuDjjQoV9iOg8FxY3Nq6HBPqylUkPv7Vd8vZM838O9xkc72mW-uvJwEbGaSRw9g-PaW6lvwKTSX-oJL_PggmUSnKBANZp_JPcQfHVhqhtUydkCYrAy3Li3pp0h9jPUomY7K9VgzUnjP4e3Kss5cRHtzy4450xXLZcBZWrimUOga5vUQXlqslg2wmD04QPEgHQ-7kD1pxeF9RzkoAL9IEzBTi2vJMgzTpCg1qrHprEZsfnnCKsEeeS9Tzxd2j28QUqrEW__1ICsFLoLckRZXa7-QwmsOtH1FotceS7h4gM7cnxwZg-HbhwXUcrVwfXCdDMo8rerBagpuERsHFlKxe3TKQoKWZaT-cox75K5H56-UoUBe3NCN3hf9NzEK2h180WT63vIGuVWz2n5ZH44Tfuoa_VupdN-UZY2fFOGzBjsnO54dBb2T12ZudrfaMlTSaoHX-6gCscxEhJFDBQ4JwUWLQABEqAt06m9yuP8f-ltogEcXPD2Iic1hdJeNFeLwslIvrll_TxxOQgc0kcX0feHFOZXpEiHNJW6M5r6P5JESnBgHoNvDcvojwCgD2Po2zprJ0XRAQqepyTGcY4EEFS1B8pb1TxXz45wBlJ0YIbkxT-XkIWFeCxc8yvo7RWsOcCUfKPVcEs2D9vr_OgQ7ASCllwZdOaCDsXayIRmmOfzkzf1ssmrLWhEDLsXn3prVXZtfZPmuOu4Y07EakPQH5XqBEgnrQT7TggS6p1J-dxebzOhVcUSC5sxzqQ96tLtnFrPhnm_UwWgSvnoTbagoOV-mArej5rFJggzcOMQpUJtKn-Z8O_Kr_Unc7AmzzJ8BA9hfv1ksUdQsc9bO9paasOxci7CJr-TlmsBQeXB_gIq8E_l-AI3Nz4keTZgAdM6kGeSK1ggaenGcdJni-nQodP6dLwT78DFsshyW1MS4Fl5dRdk4caBp8FZ9nhPrWgI4kAz6eVsOXGHar84PtBtfZIB52ex7oUtpca1nHMo-M_alGf7W1i0e_iApOlSvjJ__TANb2FY0gmbgg8JYcGVigMw5E-zJ4WIOdVhp26rgAzyEGFCQ5VcW0aK3lPtA7iGBVNHd4gFaWpUnbXTh0_6MBR4vQ5w9xfEEyDd2bXGMj9eQWRuhHX_aN-uln9yTBWa0N9cMtT2ucP2YSnyoOyV_JV5WQRTq9QxDFL6hxE5_sg3Z90sy8Fwbxi6jAoyGC4Vm7_ajSXBup4s1qpUabHkXsR1UizyxkslshvoptvVIRZIy6BrOEBoGSvK5iv-dU-In1hwMwBk9vaxkpAmd2hypCDET4IhllNLNKgH5GEDg9pL9rG5WJIiPIaemX-pROSnO_9tEb0cz7606VRDQzPdjerbYWjLoiOH_cudws9Oa47Frbuk2uARX0zLGgtX5N5myy_tewcx6ji4YVLESqKGNwDa5oS6JIXbruji7MmXROr16SJuLO4kZ5U4EOZYDLrcXgGwf6lZCWBMmHCKyuEpFgA2jf69Fjx0uZ3CV6QY1Yo_J3r7HAZklcFSmWw3EPMg_y09zBzqFhPHy0aFyKe43XuO26j-uouRra0pR6wXTJL8wX4xio7rvw08judqy52zdyLfROQXIdlFetFVOD3XlpC_JVx1-6Ef5aYJ9VdCLN9Kl4agGHs2V2NSPNPcT8iZakMJXA5MLpPc4HU3EyFl9dia9juAfgmn7WOitehGql-WqeU-MhuqLGh0NR8EpOvctTwz9LjDNvL672dnK5p-k2aTUo0rVA8QnI15Q_cWA2S1-a5hj3sF92yhgqirr6HAqkSLjzksdzkThOJT7e1feJAE13BpBXV3GpHxZ2LljhaM_zTrDLNNaRtIf-jncJcHMCOMiiASSj0Y5otF2JM93ai5SzWCZzwWo7ifjq5bDymQrTaZx2IYPNMPC8-NSd-yNtnxIL86VF_PB0aoR1P7a8p1GAi29PN3bPxADyfm2ltOv0WUQclMo2RSsz4sCLe1bJ9HJPjVjPCBPOk1eyAvTWDBb8gQJhazA6aK0UORHCRPwTK56HxCTRdgAfvqZHTZlv1xWa3necdPA30cG7YdgLRR3eKWZK6uhtRuiav3GgaT1Kj1z1SEzEKT7M7IqzbAKRi5n3eP4P21Uadahi7HzLftseUqv0e4ZHoerorTgxh9mPImStJ-Rkfu3vA9H1Kzr9jl8HE4BhlNAf5WrhQhurlDV0nj92nt4FbEUwRz3n899EodWxF45nHL2c17QEwCuvH9SbIc24H6xe0BN5DdCVrAkQZz6_TWrrjWImSiavaTv1EWkpG28tik4ROmVtPaeFpd3dzxpbfZwjiBPbun0kjOUk6lOGcWkFEH5A3GH1ZmfK5XiB0IQnj3L3QPa60zZT73l2aj1Z45LB5_iU55w6YEXh_3EmGblnoUbtF-9ixFQBBpJqI38aZQFgFAA_6nKZSQ_6b_kGPmUk8pLsmk_xwGKuZJrwB3XU1rAfXnF5bhPypEKEYaGE4T60NYafISIxEGEGsex--BrOGyjvn_hRFSBf_3UsLVFcng2nD_ZrwD2QMoL9MuITqCbaaiqnpqhHJzW4rr6BYZ6r2zH1SPPH_m6UxvZObBhEYRvzpAcFwmug6oFNxLl19qEatw3c5aL8bQvPJd1CT0JDB4QbZ4NZAEdPyM2MbwUt9Ka8eYooh4hn8mn_Y5PdXQQmeCi22Kt3XgulIm7LMKFj0YkYttJvWkzoMzt12PYQU3kDetTfmrPu0ldzVJh8tI4hUeU2b11cywqOowD3pxZFC0FjeUZcprS9m3699NRi6ioCTR2q_d4f7JmWHuEGkg-sGNhnezsIX5KLG2v4pCv2FKXKeETYSG_4jDeyL-qlqB7nhKY2XQ3ByJ5iexhyi0kDPKGXNx15Y3abNahl_08GWSN6uwr44FDftHhUVQrId9yt17PgzTTY6UyWkFx7-i2RZ9F1EaYvNE5bKbWWG9sU1m3NEQMJWMkHoCk922vOT213aXD9MKWtHbmIFApcVbnWvwXyr9XWkG7-0bZp_ZQzWUe-YA8c6ZANrXdGitilsuPlrA-ropRkA78FO1Yw3yIutpgajshFtjYb9a5vsWWBfSri2KiZJlezT-VbKev3mhlcSPnwIL5vHD8Yf-wxkca_OBaCoe7-93LviwS4jVoqLEYEUe9-aatDRPe7Dk6dE7nO5cadaXO-seyfREDoUJeH3GrjDDt8dcOlvDHY_XDyRaDnPADuO0P-WSrDFgEO7qTC1fIueY8MkfuWH1OYpVRsT6zQX_e-fCej51tAaRRqoLsw-k-yI51BjFzU1mGx3PmiJCzc0L9eRp5ZN92F9oL1TPAutDiHFTNGZVrbkQFZkTBbxX0kfmU7ISgN0QuuET0dckk3Go6ADmwYWEF-fvfSiRsbUtnQVOd4QNjQA8Ua76Lcda8I_z0rz3ZTlEyQLOuz325pwT4bmilqE0n0hCH8UTZNYyajq1H26_NwtyVPh1VfHOsSrvX6GMH_rp8eONPrJboajJX6iFz1STw2lwTtQbYxM6NqWzh7fR4pWTdmM2llBZWk7InUy3lxsk9zeuhLKo7A73MT_SQG6cn50juMtR1Zbv7ydcOrTsWZSJ_MU2jJkDLwJ1ut5kVChGus84Ba5rNN2MxPFIpHy9F8xJBWcWo4AqDNLsgq3MqqKZrTuVHHVIS4fEX-4L-ThjfDMDGH--PAVF-d23tnecA5BhpRVc0Dl4ho_BJmTmlsP4RunSvbWbyVEEKCVb4Acr4NSWJY0r4RUJ0yagdiO1jPVCeS_Px0UAB78zWNlSW6vAZIMn45lrBntweiIJr0CMAuscXDoXgHy-TBq2AbakN_U0XHMccBhlXLIvi6hwjtAhEqbrJks2KPzraPEvRPOJ6FzzFFkEIc6Hf4k5aMFf0z4hPgvQsVwrEncKu1H-IXAB3wKe2NYT_meGkV8qyyMT-b9BhW6QN8RskA2BqlU4kmgsqIJVk_rCYIVPnMqxpBf2YquHAbBURnQWubnYfDYlCCFlVZ4bUji5SoVaDdYoDO_Fxqn9amEV86dhUOiz7IhkgoJygrfQ5ohBT2uuXgU5PdgdEaOwjGIQC6lChI66Z_P-4ae9YC2HYnv4xgfcFP5u3q4US2FL-cZAeJNNaEaNmQpbZy6TiLaCESS5HwZ0qYMpDjCXJJzZxx_Y9Dg03dU17RvQ76HjMmjT5lsxfK40g8IiJTZPq5IqdNbUI6NFYdyGTKKWILHb57K4sarOcWBFbvoSPvo2SpWB4kn_1yL7-cwmFzdAign6VE2oKsjWJZYblf-VntMBNHiEfD06vyLUlKmgwz2Jc4IfCffZGVILcOZ2XaUpdCr0N-863VKc_TT3LA3obCGgrS8hmZoenXADDTNflm6-HpHfP5boHQUhEGcDUD5i1OvNCywPB_FLqZnK33gf_YV2fT8trAnVYAh6d377z6UThxhB9o3Sd8854pnAw80uI-lTZ9DZbvubDvxC2YqUpocZUUN9wcl_GY4uHupknrN4NKDn1FCogvX1TCWXYTFuIduOJ8LJkrnF5xQqhlX-NDFLN_zgJvxxJU_8egbY0fEsR5v6ybPgfnEte_yeVbNVfRCcQqOlwaD8vjkYbu0nuLgQPchMVS4AQZ-MytarToGPwPMXvXkGGL6hHMlhf5JzbWawa068dtLh9KQmBCnaX0kUneRqcM3G9PiRcl2-rzOAY3x33eYYiEQXHTBJ3o_vy5_9YV7l2DMlogv1UWnZaVOjBKJLAN4yNCzmWiWMWkxH_FfWoiIlk0AsV50Dtg41q_Q2mwXSyeCa2ZPsIQIoN8DWmlq3VSrI2wd5bIIFTcT-t3ZbST01ysemyGJX3rq7OlRbWMzHhJ81KUGiFNoOc1Xgyrs2Foj3aHycxtYTLsqtvfcnpau00YvQgE2Nhy5jDYfj0ZZjAciW3kGA_FzznheVGOomaM7sH6D6jHsjB0bjSl4pU7doulMAhPPXq36C_i4GN5nq-z9vjUkwyLIvAYMN0SRJlliPP5zZ72ElpZnxsDkGHKC5e6IRceHv_isXAllPY2eqlPzQtAKGEzBB8bUnVAE_agGq8U_XYwLKgJ46YpLF3Pte0GonSpN0qKeJEcGJlfmIEbxo-h8sca-f9d5H7XfXTPHk9CBrxglR3GhmPS6iEtNRZrCOl97BdChIiDWRuJb7scaDOVmbapeCBBw24F8rfp2p5RwgnVBOqSmntD6XIyfUrk1SIbr7-vexGZbtdD_mBAHmxYoUHez9uM_H64kRr1AWuXbr4omtLAQrHsSQsxujQoJkjxd6aG10CLHxfqejTNmmiEQe4sut9Oeo0fKPOzTDAuM47WTNBqc79PZCBvw8uk4hTaO6apttMtOT2TCrc3WgWPmYxelWb6LkcRyzKjqMCHDTkJXJAYgjBQe7nyrFzvchtsT0AtZgV8Ro_U8aa7AvSkbd3k2_HGbP3chg5n8jgzDzaPSbReO5Jt33h0PeXMr1OrA5NxC_kEwAUQd5buKXfPE8fMyTToyXAd1GDVlD1-yuuX3pja8WzUPBJv62fGsvGWWXrQijnuz8PUID1CAoO2ZLwls9jT4xqFfJvLSvwQwdG8R8w-htXsi-h1wS1tirmp54U0qWbaSVS2v514Hm9X7GN8hVW7vAEjiwuo_iVFN5HIOqo1C8BtGut5AVhHHdakmdbmOscC1McLwe7WnoAl9D7di8GToMNCSjtd9EgVh1Pd77e1vbQplB0wMQ5iXTuBTxdrL69Vn6e3sa8WLcaQGP3rE5mwtKcW9pDRx3dcefwxVjsduqaJWyTJnLNbTJG7DYdWXHgnHmsCkFlMXEQJ0sRTdqALZ2UPGgprDJqY00r2fD3jHXhoyGDpZGF5tu-ZJUwjOrG0YoeCl_6amTyhNL7s53LuJKQBnuJr35-qNXBy-QBZ4WTBDuqUo-8jxDRn7O3rpcmEpq0qCNRPvMEP2VMjAe4mwpEJB2XC8A8lUuiYZsQ2N22f8T5nqWDsaR1tt_ol99Qtg3BL2MwREPHgYRi_QIrcIKS52tjIAj4AOZq6QViblmRQXfx0g5BUTQNjoWNCFf7aDdKt67S1cFGRlM-ekS3sM90cNKKgEpWqB3PcbDMuWsGD8kZ9zq0ZW86GOUYq2b1KcljH8dwalJQw7-IygkLvefjPbXXtrTYGOCJBB7PORVrxpQ7espp0VPspQ9xF8kGJkC4Fuu5PF-mTkRqFuTlmRSX94kHphJjcHMgmhXmtY05bRHTAptujncyby9Rl5SI1qiyjH_5bM1GgPPxzDGeF7xBCNqEqWfxJyl_nvoBLHTGR0FRvOoQZ_rCPgE2U9s86AgJhZ2SN7ru4zlFhZh9TxoUF3fS9xicQGmoFyOhkOupPxy-Nt6qwUsDNhk61iIsx2dvASFbfVvu0AJgMft2TR59fnifoDXX3c9iZ3zVLqirYP4xB_rr8XcxiYWUvwyK3kTFgvFaT8d8VfhT31NWStK2WXzuah32Ek02Hn2sWISBqBtSxgfY0lWBVulUHBXqmbdDCgptjM-n85Nxp3-iXlAIk4_gBp4xluUf_Q0hkNuj0obur8unS-ZR60baWk3ESUCbpjmv2-eqr7LV2RSfjgcTt_2FTyMQTfn-y5aYhfbYQ0LQI45ZjJhOm4s35eJAtU8nD_2VtVdZ-IArCQF7LR1F4FVskXB8EJOB6Vaud9pt-bjUpvNBadf8k0jmfXZAogTP0I8YW8AfnspYE9anICD3p-n5_P4SPqCqkkl71veWVgLAhVeTT2wIHIjckVG92p6VUPiV8McRNycSK77r1Swr9Syw08ECkwCd39eG17Habod1iY6rRMXgnaBt9D8TY466oMkGPm1KsCJyuoOKaSuBOZ8rdYHd8QLcaaemuIePezQsKt1-uCk7PPT-eiuxi9wNhWl_lOS0aa_erEYgsI0S73nWuRRnj3TjmPp4MsTpF94QB_nP9JzlLeknAYcd2j8PLxELYjurcYrDHmGaSE3GOclWIGwRjw6f6SKhSucoQtDbdTBpAdpA17WckioCRrma4SzuOJIa7YD6q9LtfBDhPpVtIEcFX8eEd9xgx9sulU6wa9IyiF4Y3LALBerlS9nPE8G4H-uetKBDO1nO_ep2ZszviH8cUjXL4IJwfeq5EBY4Aq6l8m78b2xVkl_1ePeXMRnXFPpgyfzr_139xxqTwGBch9dYFgNRueZClDcCxVTrYhFhzQjQh_AjQNowb119VWF1bf-sQDQvZgMNrGR5lD9U9Of6cl3GJcdAL6CTLFbQDeqUivFJmqYm0OjUVIZKWqfuca1tf-EzLY5TZXhXeVFHZlswl0iIfIevCgimg5tVacq4ZLpX2pK3DskT7Nnr965Gs6joZ3_4NftRn0z85vLPUrrSSjt4RysMCSz3RN3RDi8mCPdCsVTR04q3Fs90Kb8uA0PFIKyCkwDsEKIFitDVaMz063jGJ82lYPTworlBeLBzyu9YigEaPgCrHjowf7cGRrABgyPwW6x3hVakhxfFuuGtm3Cmpne4nZzm9JOCsBk9QgmDZZjQ1Y6B0Q7JD5zKJgYSSbpI3vfUngcZ0iZXy4KgB9cT5-A9LxPJLMaK_abkMWBdcCNMrtIsXWXSmhKP7lebdNHQENPnCVQQeKQjj9pDh0IrAnav-JPNFA7wT5jkdg31-kzV_ur-JEMkbmJy8gOVbqwJRZg9otZqZZVoxf5wXOm9zzI7hbhBj37q73YsUc-xOd0q5F42UJoXcCO81uBTspLd_n-jLU_X3Uov38mIyHeaDT1OoIYswXrE7Ms-2HFo22_TD5TTc52GMDVTbt7EFckAThgaO_OW5hCQU7B2nXr7_KqkocultJUQY9p1MuN6YJAX1hmBtJ4s933uSz2ATj1dovgPrLDdntyDB5oFdmaDDQpuozQTweG3BFQXUIjzZX9Wn_PU1MX_5_A6rpCjrSyMS-CvUpSGhQ3IfbnQfrlpm4SqHyqpcPDFssGIcMWQm8sF5_4LDbTHNmEI3-S6JAZeio89mEYyuZ_msUweNLFZ_hEWi-t21r1i6w2ew2PAuaYohu3B7d0zbSVMYDGglmAbVTEQvlgje1zXYH6wTqNrQxCHwz28gj-mFTXP8bBBdnnh-BpOeRcn9IyD4hUVU6KzzkQDuapEPfi3EJgymUkE-RlceXB5ZShzRW7GmYHvXX03k0gyGaKF8ibycZN8brCRaefUMdtjp1WthunWhSqGyn9v8Pts-fULclgIFiMrkBU1A_nvsQA8_V2kJhtTMBgDEQLk6FBjeXnMb8x0Vaf1LMmxwkjugnsUaCppw5geMWAs_aIGQ_G3_3rHQDNFI1zFJmM_ZVEZ12_o-d070pONOoe201KqP5a5iot_OTTtb9yfTkzu5VSM3SQOVezprs-sVvf-lY7Rr7EmVC-JXTjh0KI6W7WGJP21OPTYeFRqvYzjY_lBMvPXdskqUor_M-nXVWGsZ8OU3gJu-Tftl2OxjlXeKcr3qTjGGRcjYpWhC84nDhcxvxXkGQJuzpL7045R1oCXw7ta8C5uZcrOTCZzb4suOCs5uHHtgkqd5SO9hhue2otqZQm-fHBVOpzN7kMlQ8EEpJfmzradyA7OAs1rQqm7__f5OYoJ4J0zE93KWR1MAEK42AhGVQv4fUGZ8vbA5dPgzGrDvvrXElXKwnzjgVx20BLdzX9ezRdHBrCkEn1t76WvY88I0JWM7Ucqxd32u33Oza9BFyzcrp6Js_ESrqAbJOC5jMAXYF7sVaS-i3R6CEy-hwDzzzHmybMOQmw0yD14tbP9_jdGRjKnq2Vi5tdpsXnTiaN18bHqaVlNWQs7QxUENcr_eE967qOz0xok9t00HApEKUroIyq0HBjL7u4FfdOUTgxJCwROBvrbFhuYN9FTQ-YCk22HShQML7Mj3tCRsS2GWb2c2GdwUl5-oMYnyAc25-PywB39uHZHFVYLpesYpkegn5cAQ8HLYIEp8Ocysk6rJO9wbJuK5UE-1BHKy6xUhd896xaKHsdv-cmB3bwesJA-Tq4R2Kf6SxqdzB2r96-DXLkyYo17iAScOK3kKLDcuuaiDBzD7EKepxLdvUTb50FOolu2trzdXzymcPwqvUKnEBk75T62juzwu2F5bI4uKeCGhZ2ycuPxmfDpmtzpWdrUWAQTAgsotMV3Lg4wx0hmDU7GxGDMXRHcchecuNzo8sgzDScQ8pAj4NeBGj6ctiH3cJRvAw012fbKOiZvn_MqAzWgYmr15a5MEXCn4YKGSk-EBG-2kVVPWszgyXwAHP311uYNuQ86Ts7oF_KuQT3PPN4hNHCgLE-p1prA8jPZMBzItDcD2CZf0exOy1DJSSx8Zd96elyB0AP7RGSpYeoEPxydylZje8WCEIESH-EU_28XwWr0q_kP6q6bxlN911_Dxn9o2V6Vxci3jgiEzuGGDflvyHO6fgGx60yw_lRUFW4Rr0rLwBd-NqQxy7rqZOISiH_VM6vq_bR_Gz1VyFqMMbqNXxwTfTtagnQACqa7bM0-AG9kz5J0hY8pbB2XK_cTvFvT-AG6VN0RazlgV7rpAAVD0N1TGCVN-v9mrVxGuqrmr_8fnQbA2vYkxXD0e0TWsBYvRU-YwgC6qlC65dlFXMR87F3L1Y_p5G7tIYObg-sENUty8bkVawKFg-JALC9VqYXgqtXQonZX_wABA7jfT7PSHiw4qivAEIXmWy_nolO3LxkcGcGQuG8bV_-EFk3KEgju9hKnNs4wbTmehvKPGvyMlnSO95G0KOhKE7j8AFB-QRAukSabh8o9QuI_NFGwM7qDJKcLDcKnVCgUwjOEF5MBUtx5-mN36_w_0r0UZWKndGIPvAesMwTV64EV1mStkF5i2RXaInrqdBcyLrb7ne-4apYfD1_UFsORA3bM65UVPWY8fEPo400pRr-vXmZP6MHLvd1VmwxqCIFHkwmeZvQK-iIlzTaj8PzsuMJKa2CrmoxqDN6q5cst1AfI9CyzkhprFAPeWIxgZaDlcOtjLdGwlDNWRPN-7Qu5VHL8NdefPDaRaUvXvnzkklENtJTaU0xczZiqSMEqiXm34AOodl9gzru6AzGGWYRuG40Jx338sgyV34fTRKtnqEZ8pBOepc6978y-EpjJPGn4r6uYk714nSSNqGG6pT-rmSlZ3MVLzxJUidJSTPXC3lB3muKc7jr982ojvmhZsZ1T7cj-kvzgrmL4XXmiVs_3JMQ_VxvRlJkCr22asNzWxWqapO_iZmost2yaO8M9RuG9lxlvKd_VGXBQEWx7dRqhr-cjsFIU54OJf1UIl9vppk_eYEnQVPX9VAgFLy0MVT-3JE45Xsf_tHdWQaWqAGfSaseA_-aY49hbuvDxrdnfBiv4jBwJU88EIPEYVg2O7whC9YiK94vHVaqXMhOZ0hq9XCc7vaLiVxmrS0DBc3PpW0goLBI_IEipl5ZNz6EBbHfFlJpt6mEzzjeliivvWLvHPSu3bH2kLqnHNXlr7_phTqWsBEKrA2V7F8C_8T6HSqKblyBBHLNUxSGvFfNnQD1TyBdB6trkemWRBJWHCxX70u0MzhKeCzH-ac1AXGiyMYuTUTB5cFNx6t03GrJIBynovLwoJlX2T4PAqNDJ9fgzxwwT8l8B60hNoWQuUGXWyBbR5b5SG1qpltSk6s-VaVuVYPitctyl-p71Zkgy1gmxWh14AmtuYapJ1Etzhsu5154XmI6EWPocbh31diczEm-hLtPNrHO6mfxxHcIVYp-a_s5k5czXc09RAhaA_HOeE_ocqQXOm-k-BFfStojWzfhSROIapKJJ6vGjMa27VPB_zj8Qgrs8MI7kIEEnmTtBY6QJbnEQsfySU-ex7skYbpZT5YPV2Ch3B4T4ob4VhhElxdQC7Rn1uIPObfAcFkQQbt4500IkOix-o9SLqjAUTiOb20Ps1Qw3Tlt7DT1OMDOWokLBASnDvHLWfT_IiU1eHA2nR4ltTTOjdQsMJeuzInJinGe9iFO6PHUIfcIAbqnJlRX7xysakcxgkA0RZ9MRgayYbBmjhah25AbGZ99WhiN_djH5MP4QN0Re-Yycoahf0lt_XGal6_Yb9UuWuw5N7UuJmsHXYlADsQwcg6i07vHJ4UJ6BU0ckFxCbu5By8zErtbTWD8lfYZiURHc2zOSpY7oDV4e0dqVC-VspqiETDJcfqDHp_2NbHgN3bs38bNutPR7l05W8RmCyCTAAqhzs4pFEMFOyHbIMAymGinylGAdt97qiy0957HXLAgMBrViB7WqUPXrqpjhx_Jar_krywxxgfs1aev9hq0g1Y_bf89l_P5PqRSAVfcCX1N94mDOuLpA4hke9RIeTaSpHV3t9uDQq45zogYSSsCQpDhmlbC9ZytoqnzkCQgXfe22WQSpYYKQdpLgdD_k6n0f2-QMR8284JPkFf3Pr6oPNlzor0ib9yNW1T9-vf4NMjLWBC6ajR7Uk02iGjeHclvbUc1TtU2vLJqu1NKSefUS7K0FAJPl8FwLJaseykDy7BpivjceWdEeP9NfxleN5wfHWPR066MZjlz9JWipxmO8oFA-N4KP3YTob9kacBo8lxSj_J2QIiv6OpIv6YEHPzyNFNjrsTgmmQ7k3r9neO3zP8FLiKFWsQiZ4DAJWpx4zXkJcfIinzBLNjHT6fWElhGZgGVrCJzzkUXVzT7xgMwzKCxK_dIUBcxWRqMf1-mfOAvZ3Ru1RdRmkcehsgtDLdvXTKGlMbTsYSUw37CN4PZHd_V9oBth2CVRuYSCCD2eCOCPoTO93kNlLObx_FepNCqLAckNfyQGYfA6u7eKUSkS1LBrlLc9DhOmqOU4-PGd_6tI_Yql2fUJgCTGmpG2q6sizQIRB93n9yz8Ae11sGUtDNS1iFOibht123W7QhlIx9oypFUQ0OdEpHhDtBwGclgpK2VaxJa1o-vxxYf1GwvAkZbH32d2weebF6BQEzuyqJXKX6I-2Txk-YCrAD-PsZHRwcZJRwXi4XEOlV9Do1BeIoTKWeaMiWdLqjnbJq6oRlTvWMM7WRA7Ebt5JhboVZmLMdoppnIKnhhBILbFqmqTLPWeYqNKNTVGfl5wkFGj2oIozExq4ru5-Y5Eua0yLQ7XhTx3Kvz1AcLtdWd84g1GNCJzNI8P_sEaWeQvMCUIxu60CGPjkUD4p0oT9gW_ADdII_TRnD9UncIH0neajQ3K6nmExZzOWSmYp1kcqI3LNseC0ssbMitTidZkwMN_FLHB0HA1G-OjPPjqCzzE_3JCBcLxJKc05RYfI9hUXFlk-LbrkeNIE-sD7UJELY41_vQSxI4yTnAtNgcCbKszYmSLk45ja7_tThFXoq5838fDvcKDf9D95Em4Wfk3KT1tZk0XMYLmfeWsB8KtCXzuDf3xNkU8GTiMomHuzKurtevGFkel2c0vcHEoWGMx3YofWTDMd5nIOZxTt0PnEuxptgb_PTGzDSlfks86V_xIdNANeaevYKnR8OrDi9TUgH34T6QOaxD8Cr35flpHmBbdBWXO0Oa5rPjFFVzf-J-RuAXxRkZHKWgNL5kybAtaaYykR8pgXj5ew9pla4H2pjdD02RpxnlmibKWUuU1uSGnvLxlij64ajc2cehXC1L_I3MkFrNtyrI_voM4gV-fqn5eG4ru6-GKr0w4XciwcJbFWuUGHQeHDvqUIFTDLfhdyQMrFC3RPRVMzai_FI3OzIpJrwRKSi2_TUSjcWGR9YsOE2Myb_pyDsUW_P4Oh6KsaUQip1RjsbzArGrSR6reuHlH1teKbBVOCLkERsVXC6RAm-AY5v21UFvimEtwfxlQ3V-l27u_FuNNpz3HzpbB2j0kkPiRYK6xPW6CHSLdkdm25f9pHrdEOGr4LiHUAI9Wl2JzvrqMKxVyToQJ54Z7QA5MAllz7xkJvl-JQ_GpeejvMBELQNXkXznW6wL2avdLL1VmNewKVYJjb9Gl021FGi1_pwV-EY53PU-rnzWOLIH0n-RWZcqTEuDgMB39uY1IvgsvGKP4JVLU1xZcywKsIoXDEEf23prlckSSlU6m63y5zd_p-KeAO-Sv5BPrhBMtzJmc8GAjusUqdYw95Fyq0jSFNptp1YU_QoB8weYggSYAyv23nupGFZDS6q6t3keyfeUVtKKcVNn4EojxrS54eZ31u0t03LqTWcQwJUq5ehR9FtUuN_cPjIzsujkEiNUkNcNSuPoSNyEbRFpjEsUfVE_5dmRl7O4tCiTmlNT55B8Y2CsjPfdOxbORZNwjLW6_ai1z3-YqNuowlUEEqbK1ksTVkoLuA7hNfrxLVnWK3xEITw0e855MxkiW6sRaVoWv2bq-aXmXjcY4nDiRKxh49inn1dN_i78tS3ZaxSRMcdsDYCUyllPS1HZLb-pRZwkfXrMQfazG4fCQixSMsbeOy1Jfzk3Djmi03EfPO9wyDVVhGgWq7iWey1JlFY8sUprp6T_cYybo4CXIKvbjhXft-l_FwCtb-8EOSebeXFgHjIadktaclMROqKqL1yE_ZSLVsdqd4M8tmS9ArwIX59tKhhn0lIAB-FfOiXinzmQnKoGRaJ2fsY87mabNb3flIw2rgQuXOeLA8mQZf5jY0e-S-dpqp9hhBV9aQsTrh5S_QYpTCcSLPvCTAr0hvmCWLjGWCpVvYUonKKOjpEAi90FSXvevYjmE9t9wJQKO8hXz2LtwO_jElrKvl9o0h7Jjti5Q_uUO5vokLD8JJHH6EiIQucFE6AQVN7ixwXIY_mGe--i3xY3Tv5ca5vGJgUwPxGG4_1cFtXGVxRiwRSKNIDWzQJoALA7aKRLEgpg14f2nSXYd2uQ5Z-F9C38_yFUzVUOdcd5oCJb2xRtLY3FYtfVXzOHf6I9oAyA7-t0224mGAAUx08R-E5H0hURdQCZu6TkHuWStaeF8iecpuC7BqxFb6iGDdwSRAPWFiUzjUA0b40Hl7MrJ1-ODFzZZYJtV4d7r4WUSkEiRzcrYf1GBRrHEB-XjnMCA_DX74tYXsEewp-rAJ0qFrTQJXLTHih_efu41g6so50XPv4hV2sL8NOR4CM62QC7vlEy-prlDt5EnkPjgkTKv_VYOODsemIk8lowAxmoCHxBqY9Z-h7SsXD7tOZoUqSDaEyz6VCnLVy6xJCjIuRtVXWoLOBqpP6E1Ob6l0o6lo4pyonvniS4XXljKC2nnAp71eW0ca-rkK-_emxB_IcdUWgRgfbC297wWYKIeWg_AjaHfdnwrEaBXbbFIcDpzdKuLDfbC2jiEmLiU887hs2rqx1C438ZyfoD-Yj-jjGzXLBNZ8yt10by8BwpzWXG3tMgURQer3qFUs5t4aQnyO5jtB9QovJL3EKyZ6oiNcPJj77RX5RQCzmjxi5Ol148p4lqdhl86RRtgoZbtWNI4EfzYuR3-vGzhopjQK9TgumGoUxoNuToZedlUYJDH1xalprD-lQC3GgEerUBtXlrATUj-DCOjuSY4n_Z3phNuFdKL3kcjpe2eiLlHFkey2fujfM9jRP-Vh4cW0cW9ByLcMoa8n7FRMymaZjlyKslSM-K8fs8ZR8-XxMaOM334581gOH7ymAUgQuaEu5ZHyXAJWBGjD0tklFrarymXpiTKAcduwvy_pZtph-c7U_wRdgFZ-YxczaheliRlGHmf-N8XhGEfiWH8uLgdRd1RFGaVFe8y-7i9lrNSy1F3G8DEPr8B6CJaEHACOF2SGKBQb06SjKfdUmp4OP5nGSV49JdmXtLw5EVLmb94uVHfTn2-DL3_8YW_BCfHQK4xpCHMQb9ct1wvI8SocWc3eCz8M2aOnazPkELr46aWqPNiEd8GD_R2_mYFXbWVHk-l6-Hzdik_MukjgsJZHPhCmQpxOptIHHtdtmaZCupDJDVJ3bqtGTUpfzz92YStA0S0FlJUZBnGby4ZZSG5RHu3C-YcT-n4R7VFmDyhn-9ovHpg6PZ7OWvXxMinV0HReoKq_1faES-vuVt2lrOiK-L05v0eOh-xz5yDAx9pYyc5KnOldfeiAX8nKBEhMji-jDShiZOKWh_4_H-Xm48tHbstCwAls1dw3fcRLHoWxi-uBSvdZDXbWBhMH5to6hIxyTXqz69AnpTrPNCBTZIFBvasHHSORRl1XtfrkK-beqVQDuxfpv6jPnmrygvtfDbNBS4G2dtx0N1sCuAUqTCKXJdGbXFxqCYcxzRpnWsrtr0VeiMlAL0tqGnPn2ru2eAwGUlI3tLZwRV4AdizZEXfH2dSWbIQ9eIvTcTvvPhm4-gYjuLof5IxuPh3VsDNGVU1TPJzKbLPfZOWzqZ5fjcAIqXTIXbVzHhWo_qPuP9zrC0OktoOGY3jvELUtBgqb0G7C8HugxlbKnb8YFEgZgNJWGn3C6yD89QLmfjYNQE_T-gtVvII2UBnn_KXrSwg1b72nEg6dj2pNxZ14z31kqrb1HG3F6yQ2nVhSeOg-AuMpBzRar37uIpopMypJRfvQywdfAm6jERBjzzJWWk-fGZNWBD_lATcov6N6POQFtZuitfqI6SYdlJkIMc22ZNshXGZEKEbs7d9POixzdCqNJI_JyjQJvspayKf4HKiFuvNY7P_UXG_Lhx5YrC9eYzSx_7fpIl0Ns-VE-urIZWU3ZKzZFlagelDBmnxdPBksyieaBPDjTqVEuQsQq2V7-ZEQGkkA54P3p14WSziyb-ZI_JslydezujnQJnwY_sXx5ZMl3Q6sxtCveLecWezWHQ6i2PfzusjlEWgINS0s1vS1C6bmA9tyswDNEgKC3L29caZmFByAYSaq9WomUPSUhLdH7WgxgXbFGw7QSkgRds2y7YlpWDV-uNxx2aC4wGfJldTbB-RLz8n4mJpYoByrxfW9e71HRo6pHRk3lu6n7LnnHcWRctPiZKt-InuWLw6GCAejRD2z1EiZJrrQrrMerU1DfzSeESBzmKRuRKTqEUDwrQcgO7t_1i1cB3OfpJuMpSzgWojbl1gCTO5WtTYi3o_Mq_Zy4C-u7px18XdM9dkGvOatdNNgG61gMkPFVex85m6ufYFgJY3UggoQfbj7YR6KMfmmvsRPZOZZ2Z7YEhElQEbnPbFbJ64ZpWF6pLKbMCM0tPKIms56spUBcgO6GsWd1VbvdkvTe_-vjLL1xM24L802jAbXmBuKWYRn-v4TgTIyOJuDJ_OvEixj90TV4MRt-FO5MBxS6op2a6a1jnN_6bT2ixKt3ITTTmLU_ZeCu7mCGw8XDLYA_X9qoRhOQ_w1gXD9cK5sSyAxy7lbt0CIjzdEJML-3K9UW_A2b91leWwfqcvMC7NXUStf_I0xDcFMbzvL1GVMipAJ3ig3UziBr8WXd1POyhtFHXP2o4DRUsyA-0-6ILJRXgq3dM3I1QswMbT_9w9D3OFcJKL-RorXhCd-clRH3ZylErJxm1-eaRaHe6vS6sjZTSzShPZU5PM9wccbHAj83CpeI5Kz9g6w0SzMvFtks4nu8zWKwhNlaZZwTJg92QU-6oS4hGxQdHYKuniBCdHrAybdXb8mEEEctWTjRMZOM87Lenbtd70wkzvM1tsawtWPeIbnRKgkJHoKeRbZuhUM8dqDDoNPPBuYTiTtrLWPmtyrjJUraylgokiwkTauQHD5HaA3CiAZFRxDUHdzFBew56QPY69Lp1NSSzHEMXrtHZiS0ix3ky87ddugu48KnRK82f4NgByhGVgp6fbXjItX50cbAPXXKooyv0JSnulW2db_33AvFiICefRIt9zeG5VyjQ2K9k8Cx4FjweAX7PqfMKyyjdSfzFb-IIomPb6lVIEdP7UO_OdixkSFKaGgz1IuP89tlc3ZEHBsQlBDzqBccNHRN2-bozCGfBs7nPizn3vMH53uMDHybWl2KwavzmKu3pcy0BjFfJtS_bryQK2dm0BhdHgBNHXXXGSqjegTNmpLFadbzUHFfcIgB8MBKObxp-iIKFn6PVgt73f3alItSrqIfHqSDNxeaKFLPAgjyX9fbN6F0jam-90vzjJvf8WXc5l7TU4FkshCUCN0g9HzS9RuIQPPmf0DO5TIScrw1FHpqprRHTAIxXqDqqV71Veg628GEkbH5LdHrKpy6Jp4S-O5yMZECwWed3KdYqoCAZA81Ntny-7UK1e-s33D45UpgBf5URKuJZNXxgRb2-w6eByTB-izpKcNmZ8KfoYd36sYLYNhXrg7eWQ9IciFCkQf8v1WeTkJ3cvI-f-57G6Zr6WcboFVWq7n9-QdEoxRc6YpAPPNpuZlddBuOG8cBQpZ4aDjrUkJgfi8e5dv0UbsJyXfrLqsp0TXnWOs41x2Ug3fRPaduyvvCWhAWXJa_RB_J04KXpI0Z0TyDyrLqQkSy53U2mHF_38oQuS-t47Na3mjJqLRAry9FKwhqEnOh33sKlYmigoDlZV-vynKksFrcJ2AZf5E6W4WywcunnRrMaA_VWcpdx3Wk9114G0GdriKD05Uuw1Im9FnE-ovL7TMqccodFVneqO-qRMcGufwNzx8jhYXqxcZFyUK42K9FoGbBSfFaSf7BcBom816VnbrzDdLNbT7aDu7nRS3eXLhmCBzSNnOfvIXXtVxCa0KNPPw-k0q9veups_znSuXIupIstolwcIgBA_C2R9SyJN5scGjso1ns6W184FijugKZud_SN2GwiPj7k0SLgx8E1cB7-UcNvJXPk2Wl5KDJBqo2EIWgSkeXPKpW0E2D0R01Xlxrpy1I_1Ln7T3pFXxjkMEjGKBcTZEY-4Fyg3lN7nTUy6h-ulUQ8ueO48Ql5Fi2VnmKS6YfN56-8AkDATkcTcWt_VQZ3PTL1S8DXyyAc6IYBasPCRU7-RzwNYWCDHziOUCpMUF6BZqmlf99wBdHS5GRBSBl0iTptFlAMyjzIDETDe4wDCbOSeWXnTRlwiKvIfT3PwUAo8K9BMG-eBtDmMZhoZMqFASeZbNxDo8YBhOh1yH7QBewjvF6xcPwQXJFL6Sk6Yt7CC63m1g_yQAZv8PQQOJzM1bMhpSdQyAUC2Cc-dc2sWUkQ9zd_PclYFFIRnFFFuywNPb3jd8_wtIZySdxPgJ3l-g9_nhgzDTjbeBKWqySpabnu8If-XLqlqi6OE1HHutR1UlWdblloQKu6SDxbLDfJT0siLhmZPVjCcXmIo05fiuD1pNqQdxDBQoJn9dA3RaOVdaao1D0xVA5lFL_NucYiRFNhQu52ROEE1uM7yac1pWMNdyav6xqrtIa4cGD9vwCA6Dj2UCay7vU1rcY6NIehORL0ToVi3ImsVeCg9_1dW1OqzQoukbDJJSN61gkyEkOvGwqYFlb7CxEMmaiLC2rYI6hZFXaVBm3icl8-Ygd3ZrIJOLuzwZY1Kjj4zkhY1CCg5fN0Vn8doA68BkrJmo0s3TBjod-zSxnOiUdQJtncMEnh1AmgN3sFEJLIcx--3wtxMDsbdSjnJq0skHtGNZw8q3FY6V9UJ8aJ5y_x5fkJRpJKeBbw45qhasb_GZBsqUzwMTiuZddkyMcjdtHZvzWRfbLltv0JGFoiBtcP1p6NYQJfV9ese9fL5mx6YscpnfG8aMMZaFkMTPbm-V4OBi_mwtbUvgfpfCAKSHp7xZ9faKopZ6W3vU-mzusC8cGnbqNJoLGbbbCYvGb9WDt9GHq9_o2FwLfSm6PF0PpbT4iB5epys1uvYwDuYy3PyCsxwofMT1ffFagxDVahT0Iealrncb0mzFbegBC66UDwgc9yB22DsNGs1nsE17tFdqE7U7E18cW-o3p8OIVICFxN6GuoPYXJ_bCOrW6awLD3ilufLUTzJgwhaai4D_bZOb57ZWzMcdMnvXWHS4kqq3kSTkMYgBcb_HGc44tLcr43HhLGYqcisRtdhszaCxOatAKBezTl1KNqduq5ry3jfbj210sgoWgK12_UvggRdkFrstbmlqpiROYNKQWtcLli5-8zZWexAdtzV_XYEwIpt-daqmEMHvh81pZt59N1iA68eH2-bx-RhQLbDb1b3Ca8XitnEuXFAmeEw8jngrTJn1OtrX8JW3OFLAYwNjGRCQ3cIo0Bd460WhkAgfcpbDqXqislOAoo2EMEmJO5DvEtAymWQNR5Ssx_u_IDfbCf2cdKMvCC_ZEI4A94RDnxMFYXtlQXDXocGsFwg1NpE2d0frhmfyR6dchBfn-OYlhlN9Y43u6EXlapp8ZY5nIm9C0aARHRuT1IX3Qrx_B73aOWL3T8fhC_teYPnskInKvA2qNNh22ndIQWo-sQWbcK6xJJUcc8wzsDVNENj2ZgiN9vRACi9GamX5mth1CmjdtqNQ4hF54m0pAVC8OKgOxmM86ZgjXLmDj-xyVUMMwlzUbFR5jaiL7BzbXzCnLLTt_4kTc1QLo8jj-xyYhlGAQj_HUQJQZX81o3-X_HgEuJ26XalLfxt3qp9wnMsQf9do8eUQGH4ywBTlk3dH2mloXhLSAnWQARPOQ1yscGArq8FiUc8OglXz4E04nwnXfUVpjyxdQTfaEOic2USTRENshxJvD6ikNE6zzyPE0Pki9ZxTPy9oPoCg5IaSrQ7lF3bHdUS8i7vtRX9W5JyOXRuh1EaFhbRWRU7efkZVuQ_BmTR6Lo1OOhN5xuVw9lDUQ8m-EkWwPzgbQtttnLH9yYMd8rtN8FzVba-gXE1gLfGOsAD9QiyWyAki6MfSYhPnNkg-65R3Lz-tKgkf-LXQG01WDZXida2oNW8P7DEvPLglRVvqS2JtSCfOhNvJiLgkEnuvcbcXkXQtTgqyYRCAS2I3ux7PCB2nxoKYlFlsZBb3KnuGCneKm4OYYMhv-fkbJkjTgieBZkVrtidJby8TQnv09wIUGn-UVfEeK8LkE9vs0vUag1cEqNz-yA1HPBUh6WQtBdCtSwMZsnBV6KQSyeuybb0uXlTdE0IyQmDiMKwbf3tQpkjmKEA2YzWgAQjNto0GekyvMUTDt4_G5mu2msYf_ytgROilU_3L5mRMpxkRf7B2cZjRcNVzUkJrZySjcQ_K4lpuMt_vxGefFt8vPPFeEUPeZaEWaywSCHBMhuvXTPvvrx0cXRmXhKL-x-LgfkY2HDnclDh524JzOmtjjSqTRvo0xCvLClFmDAxByrODCUl6Ff8hlrYYgrVcL0TnOrMcFt2PHHj4HAnxik_B1Wv5IfvaT-XOhpfDAmRLWueHO3wavY8ZYRs_rc36Ujc_feDa1rech12jmzwByME2CGC1quDoI3YRSXQNd6OpGzlVViNuXH_sf9BO0co56ssGS0M5uQjpvp6uAqCrsz33nVyeN5JpILHn2geFPc8jcwdCYLkRmr5upDaMtyudGY81NxyJ1WNLOrWXth9ofmKaYFkuK9OBGNXSOYqA1uA7rcRDXONMfeyWE-OCsuqkufBJeetQBeOVG8cXJH-e5OAEh00RvpgpVbsTCCxkUqC76kemudnv6RtM2BUfugc6z9xpKFYO__IUPzT56p7cfSCCfuKNzVQIr2tXkSh0upIqeOshwwnyrRf-BIFNomTwtvTYpmkk5WsyoKAoa44TvT6BY0Ozr_PkoQl1hswL8RYUxXSb1XFl4z7CuWitz1cWp8YP7XfedRV2mrsQAgXVkBaI5JzSD8GW5GQuRns1z_UxBHsSLBfK4l0VJBKSthp3kCrzlGNj_LUWQEyW1DYc9M0RBYBRkTinMn8VTrjhn-_7uTRn1wUjMivRO41J6wGN_RlowP8qFSjnqc40E4l3rXJjdL8dL8TIWhDxj01e5GlHTIBh3zGBwiPHiXqMtj1SmURa3WMVNiGSi2Kb-6FYZUuKkN1lrKf7v7tyfi_nQkLJDdOEnpRWPRcrKF5pTKXZSXMjj6zJW8bnj6UCMEAt6q0s7Xyvcnh-NRo4cZsKkOO5fFs-JAHZ3FIvBriHgHdpGEceCQUqvbCm50ErB3F0yImIhOiffFT4moiFBvUj4PNB5VircZcaSKzu6QdCmYg4Anm8I0IaUyfq6CQEQMTEHrrk-v7E_h7fkeYIy1XiJLp4-eWKs8rQh56y9IulQ6cipoYJaBcRQFvb4l57MgA0IVa6NU1Lh1Z6iMLKIm6rrIMIcnsd88fi1oRHRk4mjq4jMSP_tw5PYJy2P_DpQM1UDubWCVyjKeZxtFXLBC8DON2tP8FciTV6safKYead_UZBLCytd8w1TLG9DCmBI_68cz3952dUI10kNwrEObHLPu-3r9PTwD4gP27qPS-9ndX5VmWXRoD7ftTsL21tUHK72bQ7eh1ggQ1NRQyCQgNOks_eyEN9MjMX6f7A8kioDIyDAc9wpE43tEl7oboMfQx1BtxrzcozvVWa0YIiKYyYULHX2ZBxqIge6-WQkzNCzN7cBZrWoPv6CK8isM8AUkeuMAOav1UwHc9LtpN_e3Tcw72pKPh2UqfUFuDDMJk5v8Z4_TT-zO2TaysSRxmtHHdHeCILBAWBnyosTujwF01vyqc-eMGBKkogvBKhJP3ecALAO7L2gjNYI0jHQPkHsc9WkHVNPF30UF0o2qCd_T6950dZ2zJ8VX69gw5MnEoXTopI0WzlNAG0xEWH_YqUBLjJpdrOSwv_Ick0MlT_KuprlsbOjpjSh0uE2Jn8F5tCEZU5jmBzZS5Z5-QuyC-Ip9OPFLWqE8o8D7yyKb0vKo66XqJ2TrQhVopCrVhdkAfbngaulLGRrZnydIwjBQqLTYdsWPW-Ui2mza_KIdcSMn0sQ7RR_M9cozpnLLA-Yv3rk4RqxEAuA8RVSfNzEAnVxyZJFyWk0-Yir98JVZ6zZO0H1Oo6NQtrM6zf7bJsAK46zkc7Q8vIo72NJyBqIns8anNtNOMmGo4PsDx6rlbK0_gKJ_XbB_dwfOyi3Uf0__o_wFLiIP9iXinYI65kbRJJVmaDorJC61X0Hcj15hk6Ac_20FF3I7Z6cWRBeAf2bXBHFkSuLN3XeXDDntD_1V32PwtJ7E5xTV_7xSMMUvn4KFJkvjvtynPJEXd1z-4b7Y2IHgGRZzyjOzfT6Ts47QyFc5nnWnzWAbsxsD65gFcUgcyGZd2xz5iIYrwNDFUpZYkh9Hh9cQ8VYZm13ChH44C_nv1itgvS_uVTck4DIZq7CTXaSpcj549NxXln387Ftie0HE_qV_ALVAQdGq0xx_EvYtli1KNgoj5LN0luHT_YyvVg9gqwLjRnUCEX9E4qH5naSMJl7i8QiBivk7lgINwQ114rSctrv2cUglIxWY5VGG-WqNaf12znrOTRw2zXjI0167jH_0XFZchQMrU8cCwEM1BkNAX-oO-zTx-vdFDfMmd6lWrceiJ23_u_ATVu_RiauAhivfCT-pie-lNFPxCwvcG79MXv3H55pFgy6BGyr6L9eTF5PpqM72TUq-YIi4gxBybZFL_8Kr4sUuvZKJ5XSLpVkBgUJDczW7uL4HIc7P-2K-gOXjyMdTKP-tz582F5bfsJlBFulDHR24b7ACmILg-7NpCpE1SFlm_nYqjNF1EUVX-jTEZrP-nULcWcBqi8S0NIuDhh27gx2wSfxa7t6vVU-unmMHUhGSkbN3HiycQUU0bU2rjcxJ6JoVkqMDxUoTL0xQg9FUItAR51zPZipaSY5lC-O58f7fXT92lfI6lb4LspJohgmy5VJNeFNDO4VWvtuCJo3_jcgMLGD_8Hr9Kqi69NmUyDsGmCNKJXVYmxjT43wWOVVtmXgW-VZiSpcZcdLwZHrxy-1xPP-6-qovoQDiScrqHaCMzbgOaXB_KQ20pReLAwUqmnD1NNy43F64c1MGib6dm79WjwOWyVbKbAfUJZGNUF0V3Ga_s5YUTVtb8yyCLBX1JUKrEAE162VQoaL5G_iEwUPsGe98ympskGCsk3WC9oU62ITD8nTZ8sA4ymNNXvSwb8WCJ5zNry0nyN5fRXd17m_ZZ_zYrlLlfn1MsuxYu5RaYW6wnupgTiTWU55ig85Bhnn-koDdO6po52L6JWFOvZoJYbXu-i-xwvkQnoar3rvva6iFrPEssGNKVRIEqpy7_LeErfDl0dQTdVno5sAeZFI_D2NZq1bpnO_DRWJ-SWzfkBhCOgypWv9iIGPH6y7WMC4Z7idoM4pXVUjTw9YJYAwVO3QZ7yiZjuouXEtOyJlqLUPQvUsWDhH6Fi1L2XMGiqYnMFHWzBoy1ffzkksbrQbaoAH620yGVmNi514nJmx48sIm8SNRp19t8brBdGaosluOLTCzXtSKSIIRuzRp54gm_6hWw4y0FgmRc9yZtcJTHHacj5oW4cwzQgvejxaItavdTEcbcekaHoOSGWaWGZCIgO1nWnR1EvicgAI1FESK3ZYrqGI5gOHqRgBZMTN0VNuY5eydjQZEiOgRWdVvCDV-0hKntgwR_E6SZylYKjtcPBsLIaGOmIX_TbrOJ3dpVT-671soPSe5ieBaT5afd3OjLZQg1AeATwpEsEenQmWnjsxnhmZS_RwqvmEEQ58VepqVYu3kOPPGgycZI3DlyUYYqAGkEUJ5dyWscc-5AGcqw1XFf0m6qI0fg3wa1NuwHPBUzBzcGFn7kvQNuit2qD3Y344D8gtrsziYr3H2HtbOlagB98p84ri1ogkdMGVcbGAZxK8KCwb-GxQC-afIaSYGmvRlGbSEXNn0bRo7eEWBP1vHf_LvjEzEbC4PasLjSHzLoRh4cEL6CuROlIUzghkNl0viGINoKd0S0dRw7bixRzvMCVz5QTrnb5YEFRGFPv68Hmtqp39e7cWfEMa6pEzbQz52h3NSvVnKA2gjfB8g8BikHz9bd_akC-areKrWxvV9OHh_9_pdKBghcOOCE1kFhwmVGt0HzYh4zacV-soVTDxKMtJRjMZpHWpnRcjj9B1oU5xgNSXqgHdiX3bbqWbssUpUXZbzojmyqRJB8SKHg_hi3nQZja1Vzan_JQw3cgDL_fpSFncJ3l5CB4UocEftjQrW6PaonJk0nyGktOT48TNpJ41Q1Yq9vjdXohT0o3yXSkUCkZEXwx422vuSY6qB8bbV_0kAgzyQKUrLANsJQL4kzylP2jBxL0qlF-GjCs-YUIWtx2uhofq0nLP4CqajVJZcxrcOg874-l2fILsTRjVcqIdG4I4QHneiDFHfNVRFR7k3g6rW9Gme8eZKMBLuASEG7a_jIrZFz8KjNxI0EC-nhtO8QClIYhDXdVcP1KLOlgZ4bgysbkiqdJT94QPsnfWPxuYWW5doWSalHa9xBEs6OU_NTkQ3T-XF1XpfoJsYGhe9T2MnAp4SY2WmcnAd7PIuIY7QpRmbOYpi4L4YxlO_8uDPYFMZ5_kZBq2cGdLlR4IzjXmI9fz27uc96SJPu1JqB_gyPgPlbPiedtGKK6AsFdo8xPfXDNbfecymO0F11YF0UVTo72Xbu4tmJcQ2Ek6Hd0EfYvhLnCIBGINeETuoOPcdGmcDt0P_zNIUEMEkkGldvzJ5j9IPqZ7koeyH4qDcLPkAM-rewnqL-vZeC1JAOi5XYR450kIORhbLY_T29LW-d7yAd7_UjSs0SFT6KgEjUmmKerLnRIvGSQhgynCfBO2Lxu9lMpkuOQycB9dg7UnUrtNQ_M-aKdmEDmFKbd2-SmngNMm2fdQSSMxDyZLd6GPuK-FtpWcIgenNLAJpZ6ttPf4xTP7lTjvWN-QjtCBZZgI2r4a00WAitEVcSdbR1sC85wnBmNJdBgqlAezCNb4VZZvQWNrd6RAvjgDhG7mfAborXQ_2D5_QcwC90pYcOP2eHQJzWjfcMahAr2DvKgg7-TuM_sNQXH6K9wmncIQHygITUdx7bQnEp12yoklTsozDrrsbWUgbxH-sqAW2nnsDbNS6bSyRzKCpTQKVWTsIeJu_hrOg9ZD9yrwZGUdTb43pGOdssF4B1toPbEBBsU3aVMxUhlLeCVz04qEQKlC4SjFj0lr_SCNkHbDMBXFRlIeUUs_Fxzb5ekp9OsYrXogKid0HOasgvgGSomRSyChlDkrPlrKM9TRPjGrD8Ihb5_OnZZoZJ_WMdR-uu5ZIbNU85PCZrb6LVHV7l39PoMLonbJZjfn9QjdMv8fJc0TxcJ2Dh42giIOEu2Q2gT42k0kbq7_AGLukogKd8QPl7845OAfF2YoPeRSMI07mbefIJyL4L1r1SITOMOSsqNPHd-W2AS1ZPThxQWfHgULH-PAzL8mubzUsSeJTsDL9COOK420ds8GaKaoL9BDYGd8KlQ5Pn5US_aFG_5wNbjDvj3quxIpbHD6F6QSOMaWy3ymKbDpn3bm3LMNjO7myBqRh3pOZSxnJZW7o2PfNmxcNnIDRyItYWtHXUAPy7v-qb25hS0c_H1yWdTrFJ1I6F38AZRYQigvmyddHxhtpsjlXtCvbQiJuz3Gs5tWlgo21m7F6_fd9S6oPmkUM4k05Nqh0NY5HMElBIBtocM334q-JDuWAQPnUv0CGWyN8hmh6u2BSi-wYI8Vun7LKMJyHONfkhUTOa_4ZDY0c_3kOnSA8aPlObVZONt2swM1HPrJIlFO1KOhio3GMp2MDI1pUYYdQbygH-YtlMMH2Q7Hq-jaRSUjMEmpT17itWBmWI29f81kj3jc3aCq0ra7iHULF3gWl5EZ7OOoAFFJF2L6irJkSc4Sq4S3Rqq4v9dVy4sjoxLc9ILlJRAL51AEz16uvZStnHagnc8-k6PplDAUu2r1ueEN2f0-Ks1kbi39a-Tm7zAtRhyJyRPd3VDxlcPeWpVvQpFDNPOnqD2Zz1_n2ozHYfkXSZgn1bk3ZchxoLVwU1_mt1wA_2sbFguBogJNNlHZ_czhZwh_B0tHgTnlQEQhkFs0vHIrkaIQ4P51eFVW_PtROnplptqbcOQhgJ3n5iS1NYwDOGWIbkWyorSWk6SRQcJKSy5-tbiiegZDKuVtKmI9Jgxaaw7oZoOeJfr8l88X2PutP1YsebbZOaRtKX1fR3_Dz61BAyd8cJaw8YZ869U3X7NbYfQA-Q5ZakgGmkdqDCReOngU1XHIvFunqFVEF2N976ovS6x4Tghmy6QOri7nI5rxlc9_bFLTiRR8VO-b4e42Av1x_ZWABkzjMkVFYX4ocVrfOkqo6vSUDWeY3XM4vyAIceZN6kuZ9t5qFb4vdKZEtUnrM0uevRq8IQajGTyWKmczXrw_6M7F6NvoXX_XopE4495__X7lfFs5TzESnUROhReivfWEUGSsHq3YRKcyIKkfUffH-Its37TYrb34Av1g5_9q-qIigs1a6xHLIB8XUUqJllY7Tvd0rY-InhEo-AirX7sS-SMpQHYA96vGFi3jWv_IOhGte70OT8MamIsmHY5vL-ixysEtV4pCjboUO-y2jPLshXUpfbwy3tydtwiPiHuC1PYkPeLXHZmFHhZkhMdPqc2glMB2-Bt66T34QXuJpzBhQw8g0IcaDfg3B5YvQeqTHUziK77o052DkDa0vi0fgKcM_JQ4FtA9Ek3mStsEQ0tlK9cXBJiVzzlyQqLrHA-IKoiU-UzojqKNCV2hQZN-soU4qF-3boabZ6Wq1pSKURPLRvy3cCuS7iCuIya2XmS7DUQhXgnaI73WsaOS14TthMCVefBTAY3P76p5YTyqJNUrKzFOBS6VFLIMkttPPjRo1HQGaDxtJ22rgPY1mA35FFIxuCLK_d3-tWk6cutVoYwwEgy3tmbCDtlebbwsEH8tZSh6Q2EeecWfM_bcyHD0aQrCqsrlQnCYeIEdDQB_F49NDWHN8W9S3TqPouTeZ2K-gKGuK6jiMF4gwJu0KPKO1MS9nZ7SJud8QBoC5xwC4kPOwEokfbVqxitUriK678KZgKBSaDVQWDekY21zktepLRGS0n8YzEPPEhuN_2Ygwf1KxZ9DmCrzEQnsnYlX0EjqPvbs2OrR3Dg9paVU8Cm--iQa_CDiyUo2LWwS7OV2ez0hCGbAV1AM9TrekrcCit8Fl9JY_jMzo-LstHuBXOshNK3P9y011S8UuU20shcaUmlS2R6vtagfCIiICAZJF9LKDBLTQWTeeeyyMQJ14ZM_BbLG5-ymcNeLx6KFn7hLefajaKAKCMQHbQ31f-YpnyxayR6rsKkK5ckCQl0NwnPeHwaYYVJ5ln2VwpqOcht8LymiDs5xR9JbqqrZVEjqtXiBYvmkyDxUJuAGggQ-OnjDBBgRQSRGb8cFZqeArtxItpGqJnZpJr_85BHwcFeaZIHPbJ9IjNqDkLUxMvehiB0eqjWmF2B5v8VBfE5_tl23M-sBqhSDwkc_GQ1WJG9wzSsJQqH_H1Gp0aZ8-9wBQ2mT-AoPvzHX2jDWb02y4Rd-0T-cZ5ofJDMapq_c_crNiC_j7ISJ2bA9aZ2NoRE1UcV89H4x0F4kVFrDYcY_dJmsSG0-YaRo3ti8UmPLMtp_TPDHViZxwYiHd0TlyxcbdSxi36vISxcwNwhMf8ZVMM0JuLYReSQT8LUgB8JHjX_R_7n1wMxlySDLBLNUrYnu_oOnkdUmWG4aTbF_SXloG5-9IqwYy2GLsqForEXkW9Txxc0OC4spKE4MN8d0Kuhkv4OIwEPIjajEfkdMG4z0fs9CD3M5wEPA1TumOCOXxxraue_uuPkaPEznqtWhZHLTXgOfoEB4v4JH4QULPgFfn1xkkYz6nqHZZ0T8SPrJR3uexUi1rJ053lk8FW7-Ipd0MPkWyGta3DykqTSwCE0U3HgdAEdW9HI6nfwZjbTsxLCaIYz4oMoFGKCzar7SEn9RF1RIYGayG5T1Dn0XIeRjENg1F7By2944YIZYa2hEfT-JyEOhNBF4YzwTr5t_5ml71DKSQdI6ZZBi-qcIFYkxG6hc22-lX83jkvlcDX0dx4rVRsPh8lB-wIzqMJ4qL-E2Gu_VD_OGCegI7TQR0hmplUhMWOSJkkPuDKbEcas2e7vf_SiurDEm3-l6OvixrGWXMQ5zK97vDX1VTnRovI6QGiCIPAN0gkB2-eOqpwUyzH7ZvnUP704vZooltGIlusQRQWo-CK_ldv4py--ZyuMm37j1EhzAhsY2poF8z_gou3ogPxDBEysvmnSuc8K4p3pmqdJYq0Kg6RYyNKK9RKDwgnkPf69Lfj3Rje3Jee2PIJ1jyB3bVYfYXqQ813v58vFxkUF0pWudOlftL2FowEPDXGZGzM2cVHa6M7CTEE0F_llzyY7Zzh4Orpf7WD4Qsrl-EW5Isr8kmw7nLEkq3DEzEh7eHPxFfdJnzD8VaiYSiNtPIoseaxBKBra6v6PdIjQOtWazjj2fsk4tVHxWuQtboLV0t0C53PdXFkmSY_VStdtpb7df72CkXotAPzZIp3cSjNMt7SHFK1brOfA5Rn1Thh_PXcsty8Fngj4fwF5QqrmcYfLNNV53dhG2_w2rXVvUrX2qIHkf1vGInQX1eBpSs1lAMX0z_rDlR6yuPvXil0wA1MKFuoX26gw_yf4LFrn4aJ4sp54Xgeccpem2GMUGBtZXKA7CRpc48IvYcPYsnYGV8ZeHbonq3cBmOTJNvcR74Fk4D9sVvhtjeNg7B6HefApjDffi3M4f86MOOZn5goVsDm-AdMTN0Xr_hpscROEoLdXrV3BxwcnC7F1GIEPfP-Mc3HAcn5djcQTjltUaYvKy86ShgT_DdOZkFdyJ5FErE_X_zX8QtRRKCcrdk9h6Kon7uJyWVtXaVAxhaYLi2PtJvGhl8w_LFtSUQVeOy_vyXh7TCdAFYeCHq6dyWfRY2dj1ZY3zYbEYv4XMdlz9TAKjfVkayREorwry3aelri__CMNiO8mJH5_OUX7AxnQCLoOhf-S1ZC2sQoTAIv9lYlichhqHxQx6ue3cR6YA6OdCQamEzeVApHPnnq_74UC8pVV0ULvqsQBbE_tdAU_8yzNU0AJJQhHqlFJjg_Rjxw6S-dK5XRDrKkS47asXnBXCK6lAJ_E_j6u7W7dkvxItW2s-HxtjRSxcXVMAq6vylPLttw0osoFWbQi7W4CxRT_YMdXBftGEBpA65kGFvpeT7DjI4JUi_KmOgl679rnhsj5Lk848ysbHVvl0bIPJ4UNC9IKHmAuFhb7m9yJnw2j112T3TGCYhNPTB8-rplYUdJACa2pBJfGhqGrxXVgIv-RPas4EwfP49D-SyATSWFZnVwRCjb2SLScrJLl7NAdd5uiRjzXTBLLRKkfCYvr8Rd_N97YUxTxC407_JYZt4v0PfBSUpbXcXSb_OdxUpvrqCII-fi8Wt1bBW0ZiSqIOBzciw_Q9YDGPGj5oO49a31CPK7ltoigpAqzjpM-wMEHihGWy5rjYNkQgM3s_Q3P3WVI2iLFWAG8ZQJTplovIqH1efr-hqXfMsca-1Z9hLQZlwWgrEPXkLZbkmfaThcLkiPjNqln7_nxR5byC0K2FY5ZJQYeF59mFknD4c_PurZ6jD6MERIXZ_alUHyMVTYRumkZ8qRJCwD6isJzVG20ZUiHOHe--T72YyUVvI181iWJRVGU4uYtGyvMIjI8Bdpai0rH7fzduHWCcIQJrLmdua5YW-_V0Ti2qNzdNMBgRAvGWd_iItsrTcy3BEkmB-XE25uyvzGEbcTRi2SE_JMylgHo5kqLotLQkUxo7XktNWiykiziK19OTk9lZcHAP-X5ATCHWhPxcqgRO2SlSde4qecPecdgf4LUs_WjeZx1zpjL8yr1bETv5zfHlQQwTqfVxOcEser-DUl7q3AMExd6WksBxLPoB6FAvJ-mGZHya5-HpUEwcX9R6kO4yOk2oFxJhI792g1RefJg9PRWQ1kCSnfYJ5E04manc1JPbNm71GAY5YVv-3y5FNsuHZ8AJHXJFyOC0D2mZwnrMlsZydjp3BNp0QTO-ZYik7r_gmlX0-AuiMgUajoTmFn4kYB04SqcBUeX_mZGIvJgwoXPWR9NNRuJ-rwCFE6PcWKAwh9lQwvc02DE5BN35_aIUX1FAmxrA7eXfuAaBSNsClfFCVyueZnysCvWXgy90z76lscUlAjB52GscgoxPlrWzcw0cF5wh77s2AifHqy-shkPS05F-4S-49P2WbHB9Yx87S426LXr0hwN9EDC8w0nSynBmDR59zZCuHTw_e_w837GvgLLsK0-4mpPpIoREDTWn65MO5WyqeRfKYD-3ssJ21YFMNJWPgoZM9Mzo6JtYfKAKq_GYgC1-pNnrKHq0redXiu3B8kYMSgbpUqpTSiPKgf44dE59UCFnYun3ZGT6QYtCfTad3DyXFuO8twnbyL5_s1WXPYQHuUO6k6CvFJg9UplQXc3v5PNgciD26rWIHImBrmtkTLgrM5MqrKAk3ssZTKbQfS_2CzHsuhmfyaHdUI3pFkRp3nUUW8IOUr99rhGgwHgyfKLeE0doZ3f9waULqgRRL1W-3-Ax4s0NouOngOsJrLHJV6Jj2K6RUYY45vfFeOPphJ_VFb_fFZ6C5yaO_IR-WXxwYBEqbx9-TC48kLuscE44RdIcR1SEXxeZgrtm1FKblFqPt4WI0WVwUw2T3C8kI5jW9trAVeQ5tEnGA3y0tKoqPiNWyGnmQfmUS5RAr9iaWDL6vXKQ-pIPCOPm6zWSKv0Uq67jAUfB5XmrMT130KehzHdeXh2IzzdBHKMHD70jWxxjCG0g7MFsCk1NGd6Vj6G1TwAb_UBxoI8cssIFWlSTFlj56KTdM9MtZ1DWaAD9EUyr6xWjtTSWXgaA_h8PdejxEl7ZKQaMqBbV8_cYtmameNb3ZEt8wp6FKFoRVfJEqwiCtKZZlOyGfnVai7_aXXGiUqK00FCTZ_0n5Qxa8SosIJ8Dx7WLV_QjR-T_RqaUs6BDIm3oB-k-yfyFbkCw2lNGDTe9rjuBCu63VKUEnRmpukH0m2pW3ilkYfB-wDb6OoGS1Go-dfRW-eXaVGNZtnFlv9MzPn1oeQWcyWvUP5_OSvV9-6oprmkC6yFJjMnFjegwzAtdIHKwpzbsyH83BRxQut3pXdY_Z8YIOnoNDOTv4PbWYCYYlZcD8CErmW_-S-brZmfZWvQiF427zLfi-4Qcm4pd630ae3L4WPheJBofSkZoQ3TKcBNFp_moIrpdX0W_g0tMvwrswrIlPAbwzhq-Nde_zpNjYHemUf2O1fDrCMq1byyxDCwzaNJNonKxE2C2AzDumxFZZPqxTwkeqjHKkm8Oxot2TZtyxwYkmQDhORWTn1hIKdv0EfWEA7Ap3bw9Tktzq_-BCxC8CRZPkCSFr-U-Yzd0Uo3zQObAirLIBvTmcij2xC9uiacNo2YGhkR3ds59QLZw4-_ffoA-MmTAPntmgdN7FSgWrcM61WDrK8Z8qwpMlUA4Xa2VErCrV3Mze-JxWrmZjNwUNQ8tEB5uaFBJ4KnWpHTkIDhIFRQohiwyjU7j3Gr9dugTCmn17bwkfJSAbffVulsrxx91zbNsvBkNhSSRxMM_6_G8Ck7p-U8HtrDjzKgCAQkSy6XDFVGVV51oUsDQvV3LGrC4Zg8RHkqcpXyLr2pVAA-kFP1hQ4g4m_CTfHhEZkBGUbg2_YaWk95mbUdrTsfPyUO_7Psh5or79ui7jvAgj5HRynbQwSmaVn4iAVR_wVdVQM_O0b3YDeOCq-DqJfTFrxCBPYpXqaWQJPt5JgLU_SmK6www3mBPUndqktZVbunGF9cvcGqzBKth7ksRCPYg3Uer1DGbNmY9ZnX97l0J1cAeNDrWZ-AzYYB_EEi-uJN-Lpp8y-f6GWuav5_xSDlQdvF7nBuTTfpANjA2AVva_ChNT8h88lvLczTmzXVY-NoQp77coPlly2rsfDrXy8jIKIbJ3ieYNZ-h37DloRboUd75Nv_yI5RTDQnH7cLEegi08EGBhXV6fvW89UNfAeZHsaOy-x7sBHyJmou_BuNn327IDUEWABt91pF_U9afT6ubdajyUyiMjLoFCE0GyOMTEALDtea9KVKk_hhoYkvuUHf62kUmA54Qz7RR05p3mtLoo50NUZ4pzyhsDMIqfO1m7OyHBDcX3O4ECbuIi34lE6r3lYH33Y2OexHhqnzZfkKIWlMrORhdI2TpEDPsI2cGhWsQ-5gZTcnx8SzGm6JzFxGUbILJVrdN9lObA4gZcFQqaeJCKdjgnps7Gk0ykg9X8LZXyt3A1h7xC9APhC8ByiY-RTMUPUpBRfxXoHOmKdzERU-4kreemHZCMo9Hk47gIaQqKHzzXXOE1dmTmC4pZSXAlsKJQY3rlNI0L_BU2EQ02oNJZAaKYQKdX23Uc5hmTSCBmGF15UOVd86Z-jJxzza9lS6hPGVR_uKwWBJ8GC-nZhfwUe6N_MFuKoio9KmFuUAOd9a-8fo-PtFus8p8TZbJoakMChJmdd3mvSbFSxXqR1W32wZdymM-qaGaog4z96_j_6L14pyFbK-mc51b7nfJxcXPYtLEyiY-dqZCRACGjrKgbD-pvaf_A8av1N0v2rZUJMwbwqDIgmNoBvTwnb4HVM-z6VHOsrMqoZMkHYOmx2e9RNI3daxsjX6d5V_mlSoIMIjOa75mYbjLkHnKcYA748C47K4CB7Eu5LjM9E17NPp184tegLmhnN66m9c_5mc3L-CGAsyCfzEXBCdW0-rJVupoBB4j-5AoYaQKM2UVK9QLEztlSXCSnRhuxJOs6XJBoHbpn2ds1mdmqgXk7qqXJUC53LQ884ak566YcfwtykKvsLyv63YaGz-ZCMxnbTVdylmgSJXqr_nh5Stpd_AztDjCGHHSvL20-t0EnKqKiZFuOvF9MEqoD0CJC5L5ydblDxcZGK0mL2CNx6Uv07bvYnmtC7x8Clu7udT8UkMKXPXtGseZdYhkIQjiY3Be7gQDIOFvUGyxKccbi-p9hqviJ6x3cNkgyFmHPEfR_-tzHZvVEvnVmCduDCOiVBfA7DhrGFTuRFzDXHvyDXh7Ie15QFAjKk55StwYYbp6j3JXJB5D9hSnUR3tRAf9y8_PhsUmXhYizJR7pEqCi1ko0zXInl2VIUg2ACxcaQyAaugbqAFbMBokYCuGyUskPiOZ4Yp7HA2cEbqelspJpLG5jnsfzzN3XVPXqGA1teYEPHxIbjS8opofut_7hVqAAv2qQ4wkg9E30HKvWcxSdgdf3oFPaeTKpFpr_exVWlSRb_7Z1MMu5jHqpzNOACCsjkPJToPxwJ_5oEgfvHB0BysyoIhLhWNiyykBBR8TTto3nmRr4NxSAbrkmjo8pxsR1U_d99mPqwg3k2KZo60AXDgpOu5Gsqy_KsiG0On8KX7f4S0q4JrGw9xT5cetYa6ZijdH2iwnZaGz5WTxYVC_6QTIcizoAuUsapJAGtKgPH83gT9lzfvYOytdyMVw4OcfujG28dJTT3kuTMnCO5SYet3AJ7C6QTWoqavFaKm0G4DJKQU7fWiboYTZK2_O5vJX80b2-TM5P_q2eF84l0B5r0fcLgOhRNW3APh5VikGuh0UwYWQsuDzOM6xT-CTPlJ4VsnYTKacoPEYn3iBlzfTcAyI1oG6CrL4vBlpPQDNjXSuNCRLDZT-oDE4qhK3f0GxyMwH9fPtru2AbIgjxzf5V1KPDJedfIOug0u_H-rG7SLblWbNookOuKZMaDX4W7eMnl9htdcecnvhYgBdqVQ0m3cBV85gmyBz_X6mEi2nzvEK-m3NEV9tITnCg45EOofmuMCR74WSdX37F3DZSwDzuDusFqlQOu19pBc205Omp6AJd27gJUMQIX8oPlvSZrGiRS-6z0qX0ZHC1ohBMau8edOw09oP1AOtiixfq-NSqxS3or-qOu1kF644iSVWiDV_2RIv65wy8cGdMTf2getLLX04FVqzpwNov70OqH_CWyMab3nLbJzhhiGhTe_qHLFQLoTWxjkkTYR6V9Os2oRMo9Sk8lfDUbT7UrZAaYCcG07Kr7V9q0K3-D7EMaf6gpdkIUTrHeO9DK7Ws9x7dZLtC_S1xQP8f5x0B5iUWksPS3ETv1qglU2RQ4uE0RAdkQjCf4fzsk5uU3SjBIfmZsSu_gGwvELsNsBZYju96lzsHx5oZv47uVOqcvelQUUWRddFHu0gnCIY9ueTm_tv031C1TWIsqkciEp4IDuIxfOVY7ZkY5DFMLley_p-5o5DCYpLinPiplUGYNI8uZ1mHzMGV-OJv26-L59qtDr6Ptf7JNAZ7NLrXsKWCNm-nw2eAs5rfjZddlaaAHwy3wn6UaMFXAvbW2Jb-gdNDtJIv8MKsOTvjKYxkrBgjtFoUzdHcLP-ryBT7Y3TjK6BECe5rRIbFC1o6B6RoXDqKGu7DSDtdbnikwhxoHQjFCPU8YN648sWlAqhlTQ9cy3LgUA65XBiD1Yss39aJrSNrF-h2N_vpUm3eA69lVOgOqx7UeHmQOhMe6AgfIFeS7MPciUXvgTvpHedfEhlNtTJsTxGr1Eu0Fp9eDbSsAGYZXgG6wQ4RXNkO5USShBSXfMAdPSE5R6GRvDd7QayqDItBI0SoOzrUHNmZrv2k0MFixGZXgO619I872nfuUvd4TEA0944PfcXbKCBEotRnDPpPCSiZRg8F2nQCn2zK9oLtCftokAAbC8L4PJouji2nsKvwC-OHP8RhXgfVm9hqe-Cx2mYJxaBiE1PcM8gxA8lZGJEX869FzZ4rUZYdHItDs8tRly6RIbtL8ArSqpIsFepW5sDPLwqDZiA7mCfmbMQaNsR_VODbowvvbE7bCLNr0tmX_H7953JM0jB0ZHXev1PmTrw4CO-qxpB7MrsoYrBGpKwvBjAIEY1OL8YFhZg0WH-DXBURfAHUXGWPMqhCeuORU2_mm0sJbXjmUY2imzgQIi6sxvl2u37fD7mfU_r7DcM2hJz65Yp9P9FAhT1dN7-lgUeVaicxM68fHzl5nygq66XW1ohe8JqCml1pntl8gvgSVsc-aS4_yqPYOn7vH_Rdq1M2kr6FPauhXsyVx7a3Pjrll0jIoLs4C0Knklr0rENrTTbvk-16FU7S7EK6Qh8AqWPwOE4avnkcN97ENcy6m1iFbMEEBGJEKAHL4-dqFu1FcYoEl9K8Z8y3XHvaHzAYwDA4nGngz9-TV9EPb6t1uGc4g0vsbJxAHM4heBOMCsoVVfNQAsOHM9DMvx46m5LMVqrFKt3s_cLaAyOX-MiEuKOCXfrkJ0KIgUWQtSLel-PRsQ8ou4lFUD1EHe2SuZ9-6NkMf4taDf5QTDI6mglbJLf6Mz6c7x8eMQOM0VT-5UwOYZF6mqdUX2-bHBSXv6PXRrTsxRKzo66XaU2Uu0-T-QNfEhweGleLbR46BNXk6MhC2IlWeDvmySU1NlU6FKoaycDBkK41s1rhCKpzcRzHQjIyXiAW7ynVv_QglVhWDXhZl1ROdan4uvy9lt0eXB-iwavGuyOcT6kODfyp198TEBoGnsBuuiHMibPhLxYETJHkcZi-sFbTbdsG2tw3m65RgNnmzD8V3ZbfaI-64odOVgQ6ADDYsj3VIattyxg4ni2OWpgxSEyPz7hfKANFQ3CMmdX1FwfjXuWZpoI1PHQcLU9B8-lT-9O4wftprhVwm32ESJS8RsC4L8rp5HdDmo054ypiwgkhNBYK4eohE3Yd07elgmrcveMUVpOI3_ftUZOagX6UEGbyt3RymJZ71OEwtSVekrkCY1iqbyR8M4_4Zonz9m5j09SBe99NLxqJrIMfzAjThfidoD_2DzyTE8FeAZYCyg9-xVjAcvQf4dBcHXejCCGHQ7LD_kQG8poRZWurwS3Epq43gfUi_blxl0G1MONmUsEBOyUI-8Q8HJtx0HaNnM8UKnI8GyvwsvkcP1WYqKNa-XDD8Yvdb_PU76UjDOHNT0sAYX3F8TnDtcngjcgLujoaCcyyvvycm2ACnIHgr4qkL0fkX8-aLTxvN33aVVvULeOfB-fX4tk3WAoxPpPAQu7S7GKtpFE4vrJf6xDAywt1kmmlxUcaF0WwOypZ44saRS0siEWnxwPYWImVTMAcirV6CZHh_NiLRphadg7QBMIWMMxnLT7CMTjxo-ptaqR9vcyzynl1TntDSeNBkoUM-0ZEVDvVPrphuc6Pcpg4G3kxmc7Tw_kVWsSIQJR9dbJZTYO9cdfntSAnxt-H1c-rshhNIi-e5FGNcKQIxb_lFwIKiSeDsNv_xr7YebGGOn_uxSxgAZwxCuVd6iFu8KrBbwDJ_oktH2CVl8ykrFWxUWsmjfLW-T3nhHu4Dk5SCAhcaUDR-8O5SxdRssk-zJNzcSTd1JJUOEcaba9fW3OaxPtgWbbBnhMzj80w3qT083IXAiHFW-I87kSvcljo0bld_jkGkcvivQly4z-w2u-N6kWofkbEzG8wCrDKOUItAiUUB-9yNvcCVtBRZ0XaaxBExulGQhwsPETa6LG23Tm4gbPRiQgvvHA8mW5Ra8dKQ-6lAhCB_mLtrcw4OioQLOlMkLNPOQJQKkab67fcq2iDLJL7mMKDUDpEKBOdYlDWa7-NCIC6g55f3H6iuR4jQ9NHHbBAVjRn-060kQ2RBXIkGSGXSv8VcwXcOWL15LWEdNR25Zv3Uj3kf-3eVEiYdGOO2WDXxtKmBAlTzTOKgljWH0sUnkdB2sfC_ZGMbqxOFWrXAXUN4iUnFdJyvp9hlMNIb-cokwIMZh1glqvrDhvwlVOUL-3NaprZ_qCYSiRgZyKJjcLOZ_nukr-9PMRirjdRuQMD9qLjE4DKSSj2Ie2Wc-H3xGrLJDArnKt_b_2ozZtL_pQ0GRh-HosGJX8gZ6pulUsmrSN6CZrjFQpN_UUAzpPJBQziRML-6KoU1iZpaIH4oCp3mFlt-o2qxsV6uAeOR230UX9HScGkRtzgbIdY6Dc3RZkKKzzjfpL4A7sQPPm2sxr1lkwoWypoFv3NYl-pArVfrxCiqohcRN98q528P09j1ug_s8zYYbYZIwpNuvJA3XFARL6I9gf507JCNZzSvvCNpNGX4IUwPfgg2hds_9sT5MZbIH96be5wFAgDs1H7Kk-oBLtRU1MLkeKJ_dGpkjAnBJtwfJcKgl-OboUr7E74ukOsIl0nmCFLs9eZX8BBGRYSVnkNbnxFpwJYNOOE3HuaZ8Q76FE7_c0xQYxk7SDK8s2aD4iWCkH_gz2dHNyCXtlL-Le4kZ4YczODhqml1-o9ccj6qnIiMZPdzuxotk7bofDnMKLoSPZNtzha4_LmZ8UF68NZRwWO-VDaIEnPZxQYRRc2adUEUAG0KhcGb1LaL-_RwaLtFhcA5K7VzgDFFL4lV9_gNo2iZ0em49Gu9Wy8Q9U7r2wa4EZpt3k9PEl7CqvJOwqZwfa3UebXjYxJStCFrAZNUq9j4BB8a3ICiYwTwMwW1jfUpjo9q1jTYD2XW-LOZgg0WK07rZbAELjKWWE1rV4n0BIvUpwUq7c5YhhQ3BjbTROgcEZaf69AyJoeYyT17YcFmdj87LzsQ0vFswcxiez4B2EhAXXppmiyL6WZm1Epep-oTenLGG_t21hV4XjuZzdzNBmKyoVZUp9QXP2d-1u5MIqCWPsfiAHdqMD8zz5RbPIOq2rX2-JkfkZBEGWQT1PPa_rIVu9Ngqy_YF1z8qzfsc3KWUaohUGS4KC1aD-fRM2NXZf7aIQL1-waGXS-gO2l20-XFNZLO3r78L3myqPGa4vxgMoMbMIL0tP7IsPj6w5L6eoPbgLGxsjILqqQe33153qM79IQ9l48WtVHNQfRtW87NEdqkSO9dgO6fDSfF-vSHoQcwFl2U8Z0Mk7ZKb-HgRH-aFmeHrpa2qM-J7k_Q5ew9Z402J6WF7InJaufHX7DfZB2IYVgQQ7hKuHmx5Wj5GRnqxnIMACPtW8GvrgD9ARq3J_xJ8aohpv7MsH6pFtsgxPYhA2wokaW77A72NNVnxtj3C1W_bW5ewLXYGbZfamwXSNidox8BK7pY3LNiVfBJ3iioE2B3V68V2MZ8B5GFMP0ggwM9OfVQP5b97cYpoyxSBAPA-wnqRtftcxNBMJvGzEfa6OT7ZpyL6aMJkEzjcaWBgZiITqcr27dQsz8fbPcVgEsfBeYcOphodf_VndEWmxpEv-c_7WmH4pEC1Iw0QaNlOKwx7Adzq5NzDbLplxlxjB0uG28jRhVPu239tENv_s-2LHqVfyzMiGLtCRv9H1Rw8GJjiNfXeVZrKsQ1CpCxL6znaRx1GR3raf2bSO4Z85ot3CEGR_MP4s86q7PZAMv5us7Q6QFdT06EzDA4wyFG3ok_olaN0ksxJLVKbphBVHRf5Ci6ebe3fZrifJcb06Q4lTO4Aqje-mL6q2Pzg8-R5h3geKIXEfzra5dszM52uYf2mFS-UU9u_sKG_OgJpCP2pz6JLEiuyMfpy384HZMW4uTnrW3XZ3Lr8lSwwcqEI5piuofXZTZgL5ZTsFjH8gVHgNXCDlxT7i6UGEsEumbRbM-xhJvltUKRuGvj57l0rRLbH0qd1DAr2st_0PMEz6LQ6kYGOlOFkkZvRaM3wAoSkgprwHsVGfaAfE8AjejRV1Rcgaa5YFYN23xuOBkgpKslnHHwPYiwCihKxPlxDX9ZnLnRY5bQZJIJiXZfp_bZVBXdXNjzkvEkGltsa8P0DicHKOSYzybGn1Ut0qu9avoHWX6b9CPuMgr-nxrvwcqRF0sG-uTMZ7n_nE-dB2EGJSIZwutyWDkTtDqPphupLJeOvVVXAwx_3wVVn8wI2y_8lg3WnnPNY2VGtnu8A3dDfRmojRygEUD7rl84M7iwh2MIYIJkNpmhUAejd8kkfZB4EVBITdGyagl_XdEoEcHD6KwFKBWn3dsNrvkJaFfLJYJ9dI1PAp251Wbkp9JZ64pTzD9OwRT7m3Y0Ws2N2EVUUL-5yDv6enFqLHYSRzn0zDOaf9NoBFg3s-ZwRpqDXtXqksv5xz7zXSNm7zPoturwelvdpi0bdGuwRv4gY1kTeaGt_TfCn7HdgmYi4n-1zqwL7YhdbwqtxQAPAvY3exd4xyPeJD4DnlXdtNNgW9FnTxNDXmCYxth5A0tan4IVVHbfJShX6PeY7EQDy9wGm2qpabtzsZW5cFw2z9BFXlC1nAupktJVO5yu50nEuejtiDIRAEeBIWTpOynFCnodPZV3lKyQ4EzaE5Z7_z_IB90bYpiGbEONRqs0h5xK5gJ-SJlzvU4Cc98zXUaUvlVN773uEbFmf2yCN92lfA-KV7cfXpYMTD3uwgdc7lhApmO2lzdglpAASGltrXbW758WJY6tMKi56D2iv9lx8WzehHOq7Bv2rv7ksYWQbUr2GqaLv_-0s5YBW7_F4xpEDQv2Qel82Biyl8sZIVC-_cxaMRwMqmX2NuXZDlo_r2tf6h3nIY6N1xvl7rwvO5DbeaDeq9w9yPihhuheXe86Ge8G6CUPz256IVSIH6cvMrRYFRtZGCG3EHNrRYZ7yb4NTf-pob5juGRnyWQ71Z0mQSmiHO17ZoJ_vCqbcOqOsBCRpnvIT_WWffft-Ax-B-wo5H6I3Ifxl-peOyqye4We3_QvTH5kC9N4XRLA_Uff79UP4RTLc9XFsO-Lpo1x_qAK1ryrB7WSRnPF1yMMwM_79NNr2J4qCF3d2_5IpI47XImcHjIjII8JVf5ebyQA6Vl4TlgBlUE_CX4Qx5C5XmoJfDza-u4aXixrCJYMvquPc6MfVjiccmb9r04U4SnRq0fWnZ8wjPKApGK1cugJi-JW-bI3c9E9oXE4Hsrd_BKUu1oTP86cR5RZmsEm3roN1L_80lw2ISW_IcSGDIDqWF4s0Hxk0wwweKXNsvltMQKmnvT4svXbcndhnD-dEhrXYkCPV89GkO8dUC6-ehsX4PknC9Ae9XArkxbgJXxiKyq1mlkM2rQfgtjLgP5iDWjq4fQlXJ8og_BYoJIucv8pyOiaAVV16Lfk11GdegL1yue1601w45DW1zyuFhN-ncAw-d-cPhNaid1xkb1WiTQAfWIREFZzPBO8HGIWHiPCNXzTall3CIgwGTbIOV40B2Cq4vqHHTZPcmvK1MblmFfWp7xDUodJQ5G_IIFVCRxwFs8h1z3WNJzBQafqSs47Vqx583gfCzaIaxS6DbS23IomfNr2ZdsvFEsd3L2Jg-GC9-YOP30QUAWsEkpc6L_HC4R-_yI5-bhN9bkBVCpXRL-CRdCOoBrYISoEdme5x_CG1U5ez2En4AUAkq6Foed6h5OWIoXZ_yxTW_VyYueYkTXK07gloU_kFZpLwDTE89xiY-Q0hjYlvRu9q9KLWshD835iKpv-8sw2yrE2w4= \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..5860d70 --- /dev/null +++ b/.npmrc @@ -0,0 +1,16 @@ +# npm Configuration für LibreBooking n8n Node +# Diese Datei konfiguriert npm für dieses Projekt + +# Audit-Warnungen deaktivieren +# Die Vulnerabilities kommen von n8n-workflow Dependencies und sind +# in diesem Kontext nicht kritisch (siehe SECURITY.md) +audit=false + +# Fund-Nachrichten deaktivieren +fund=false + +# Optional: Legacy Peer Dependencies (für ältere n8n Versionen) +# legacy-peer-deps=true + +# Optional: Engine-Strict deaktivieren +# engine-strict=false diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f1293b..e651f0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,109 +1,35 @@ # Changelog -Alle wichtigen Änderungen an diesem Projekt werden in dieser Datei dokumentiert. +Alle wichtigen Änderungen werden hier dokumentiert. -Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.0.0/), -und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). +## [1.1.0] - 2026-01-25 -## [Unreleased] - -### Geplant -- Webhook-basierter Trigger (falls von LibreBooking unterstützt) -- Batch-Operationen für mehrere Reservierungen -- Erweiterte Filteroptionen - -## [1.0.0] - 2026-01-25 +### Geändert +- ⭐ **Vereinfachte Installation**: Fokus auf "auf dem Host bauen" +- Aktualisierte Dokumentation mit funktionierender Methode +- Neue npm scripts: `docker:deploy`, `docker:copy`, `docker:restart` ### Hinzugefügt +- `quick-install.sh` - Ultra-einfache Installation +- `update-node.sh` - Für Updates +- `git-commit.sh` - Git Commit Helper +- `git-cleanup.sh` - Cleanup alter Dateien +- `create-release.sh` - Release-Erstellung +- `GIT-COMMANDS.md` - Git-Befehlsreferenz -#### LibreBooking Node -- **Reservierung (Reservation)** - - Alle Reservierungen abrufen (GetAll) - - Reservierung nach Referenznummer abrufen (Get) - - Neue Reservierung erstellen (Create) - - Reservierung aktualisieren (Update) - - Reservierung löschen (Delete) - - Reservierung genehmigen (Approve) - - Check-In durchführen (CheckIn) - - Check-Out durchführen (CheckOut) +### Behoben +- TypeScript Installation Problem gelöst ("tsc not found") +- Read-only Volume Problem dokumentiert und gelöst +- npm audit Vulnerabilities dokumentiert -- **Ressource (Resource)** - - Alle Ressourcen abrufen (GetAll) - - Ressource nach ID abrufen (Get) - - Verfügbarkeit prüfen (GetAvailability) - - Status abrufen (GetStatus) - - Neue Ressource erstellen (Create) - - Ressource aktualisieren (Update) - - Ressource löschen (Delete) +## [1.0.0] - 2026-01-24 -- **Zeitplan (Schedule)** - - Alle Zeitpläne abrufen (GetAll) - - Zeitplan nach ID abrufen (Get) - - Slots abrufen (GetSlots) - -- **Benutzer (User)** - - Alle Benutzer abrufen (GetAll) - - Benutzer nach ID abrufen (Get) - - Neuen Benutzer erstellen (Create) - - Benutzer aktualisieren (Update) - - Benutzer löschen (Delete) - -- **Konto (Account)** - - Eigenes Konto abrufen (Get) - - Konto aktualisieren (Update) - - Passwort ändern (ChangePassword) - -- **Gruppe (Group)** - - Alle Gruppen abrufen (GetAll) - - Gruppe nach ID abrufen (Get) - - Neue Gruppe erstellen (Create) - - Gruppe aktualisieren (Update) - - Gruppe löschen (Delete) - -- **Zubehör (Accessory)** - - Alles Zubehör abrufen (GetAll) - - Zubehör nach ID abrufen (Get) - - Neues Zubehör erstellen (Create) - - Zubehör aktualisieren (Update) - - Zubehör löschen (Delete) - -- **Attribut (Attribute)** - - Attributkategorien abrufen (GetCategories) - - Attribute nach Kategorie abrufen (GetByCategory) - -#### LibreBooking Trigger Node -- Polling-basierter Trigger für Reservierungs-Events -- Event-Typen: - - Neue Reservierung - - Geänderte Reservierung - - Alle Reservierungen -- Filter nach Ressource, Zeitplan und Benutzer -- Konfigurierbares Zeitfenster (7-90 Tage) -- Deduplizierung von Events - -#### Credentials -- LibreBooking API Credentials mit Session-basierter Authentifizierung -- Automatische Token-Verwaltung -- Verbindungstest integriert - -#### Dokumentation -- Vollständige README.md auf Deutsch -- Detaillierte INSTALLATION.md +### Hinzugefügt +- Vollständige LibreBooking API Integration +- 8 Ressourcen: Reservierung, Ressource, Zeitplan, Benutzer, Konto, Gruppe, Zubehör, Attribut +- Trigger Node für neue/geänderte Reservierungen +- Docker Support mit docker-compose.yml +- Automatische Installationsskripte +- Umfangreiche Dokumentation auf Deutsch - Beispiel-Workflows -- API-Dokumentation mit allen Operationen - -#### Entwickler-Tools -- Docker-Support mit Dockerfile und docker-compose -- Installations-Skripte für Linux/Mac und Windows -- Test-Suite für API-Verbindung -- ESLint und Prettier Konfiguration - -### Sicherheit -- Keine Speicherung von Passwörtern im Klartext -- Session-basierte Authentifizierung -- Automatisches Sign-Out nach Operationen - ---- - -[Unreleased]: https://github.com/DEIN-REPO/n8n-nodes-librebooking/compare/v1.0.0...HEAD -[1.0.0]: https://github.com/DEIN-REPO/n8n-nodes-librebooking/releases/tag/v1.0.0 +- Test-Skripte diff --git a/DOCKER-INTEGRATION.md b/DOCKER-INTEGRATION.md index 390d0dc..0bb0681 100644 --- a/DOCKER-INTEGRATION.md +++ b/DOCKER-INTEGRATION.md @@ -1,168 +1,41 @@ -# Docker-Integration für LibreBooking n8n Node +# Docker Integration -Diese Anleitung beschreibt die Integration des LibreBooking Nodes in eine **bestehende n8n Docker-Installation**. +## Empfohlene Methode: Auf dem Host bauen -## Inhaltsverzeichnis +Die zuverlässigste Methode für Docker-Installationen. -- [Voraussetzungen](#voraussetzungen) -- [Methode 1: Automatische Integration mit Skript](#methode-1-automatische-integration-mit-skript) -- [Methode 2: Manuelle Integration](#methode-2-manuelle-integration) -- [Methode 3: Integration in bestehende docker-compose.yml](#methode-3-integration-in-bestehende-docker-composeyml) -- [Methode 4: Dockerfile erweitern](#methode-4-dockerfile-erweitern) -- [Verifizierung der Installation](#verifizierung-der-installation) -- [Troubleshooting](#troubleshooting) -- [Updates und Wartung](#updates-und-wartung) - ---- - -## Voraussetzungen - -### System-Anforderungen - -- **Docker** Version 20.10 oder höher -- **Docker Compose** v2.0+ (Plugin) oder docker-compose v1.29+ -- **Laufende n8n Docker-Installation** -- **Zugriff auf das Dateisystem** des Docker-Hosts - -### Prüfen der Voraussetzungen +### Schritt-für-Schritt ```bash -# Docker Version prüfen -docker --version -# Docker version 24.0.x, build xxxxx - -# Docker Compose Version prüfen -docker compose version -# Docker Compose version v2.x.x - -# Oder für ältere Versionen: -docker-compose --version -# docker-compose version 1.29.x, build xxxxx - -# n8n Container Status prüfen -docker ps | grep n8n -``` - ---- - -## Methode 1: Automatische Integration mit Skript - -Die einfachste Methode für die Integration in eine bestehende Installation. - -### Schritt 1: Skript ausführbar machen - -```bash -chmod +x install-docker.sh -``` - -### Schritt 2: Skript ausführen - -```bash -# Im aktuellen Verzeichnis (wenn dort n8n läuft) -./install-docker.sh - -# Oder mit Pfad zur n8n Installation -./install-docker.sh -p /pfad/zu/n8n - -# Mit Überschreiben bestehender Dateien -./install-docker.sh -f -p /pfad/zu/n8n -``` - -### Skript-Optionen - -| Option | Beschreibung | -|--------|--------------| -| `-p, --path PATH` | Pfad zur n8n Docker-Installation | -| `-b, --build` | Node im Container bauen | -| `-f, --force` | Bestehende Installation überschreiben | -| `-h, --help` | Hilfe anzeigen | - -### Was das Skript tut - -1. Prüft Docker und Docker Compose Installation -2. Prüft ob n8n Container läuft -3. Kopiert `custom-nodes/` Verzeichnis -4. Erstellt/aktualisiert `docker-compose.override.yml` -5. Setzt korrekte Berechtigungen (UID 1000) -6. Startet Container bei Bedarf neu - ---- - -## Methode 2: Manuelle Integration - -Für mehr Kontrolle oder spezielle Setups. - -### Schritt 1: Custom Nodes Verzeichnis kopieren - -```bash -# Zum n8n Verzeichnis wechseln -cd /pfad/zu/ihrer/n8n/installation - -# Custom Nodes kopieren -cp -r /pfad/zu/librebooking_n8n_node/custom-nodes ./custom-nodes -``` - -### Schritt 2: Dependencies installieren und bauen - -```bash -cd custom-nodes - -# Node.js Dependencies installieren +# 1. Auf dem Host +cd /pfad/zu/n8n-nodes-librebooking npm install - -# TypeScript kompilieren npm run build + +# 2. In Container kopieren +docker cp dist n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp package.json n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp node_modules n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ + +# 3. Neustarten +docker restart n8n ``` -### Schritt 3: docker-compose.override.yml erstellen - -Erstellen Sie eine `docker-compose.override.yml` Datei: - -```yaml -version: '3.8' - -services: - n8n: - volumes: - # LibreBooking Custom Node einbinden - - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking:ro - - environment: - # Custom Nodes Pfad - - N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom - # Community Nodes aktivieren - - N8N_COMMUNITY_NODES_ENABLED=true -``` - -### Schritt 4: Berechtigungen setzen +### Mit Skript ```bash -# n8n läuft als "node" User mit UID 1000 -sudo chown -R 1000:1000 custom-nodes/ +./quick-install.sh n8n ``` -### Schritt 5: Container neustarten +### Mit npm ```bash -# Mit docker-compose -docker-compose restart n8n - -# Oder mit Docker Compose Plugin -docker compose restart n8n - -# Bei Problemen: Container komplett neu erstellen -docker-compose down && docker-compose up -d +npm run docker:deploy ``` --- -## Methode 3: Integration in bestehende docker-compose.yml - -Wenn Sie keine Override-Datei verwenden möchten. - -### Bestehende docker-compose.yml erweitern - -Fügen Sie folgende Einträge zu Ihrem n8n Service hinzu: +## docker-compose.yml Beispiel ```yaml version: '3.8' @@ -170,423 +43,50 @@ version: '3.8' services: n8n: image: n8nio/n8n:latest - # ... Ihre bestehende Konfiguration ... - - volumes: - # Bestehende Volumes beibehalten - - n8n_data:/home/node/.n8n - - # LibreBooking Node hinzufügen - - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking:ro - + container_name: n8n + restart: unless-stopped + ports: + - "5678:5678" environment: - # Bestehende Umgebungsvariablen beibehalten - - N8N_HOST=0.0.0.0 - - N8N_PORT=5678 - - # Custom Nodes Konfiguration hinzufügen - N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom - N8N_COMMUNITY_NODES_ENABLED=true + - TZ=Europe/Berlin + volumes: + - n8n_data:/home/node/.n8n + # Optional: Custom Nodes Verzeichnis + # - ./custom-nodes:/home/node/.n8n/custom + +volumes: + n8n_data: ``` -### Vollständiges Beispiel - -Siehe `docker-compose.example.yml` für eine vollständige Konfiguration mit: -- PostgreSQL Datenbank (optional) -- Redis Queue (optional) -- Health Checks -- Alle Umgebungsvariablen - --- -## Methode 4: Dockerfile erweitern - -Für produktive Deployments oder wenn Sie ein eigenes Image benötigen. - -### Einfaches Dockerfile - -```dockerfile -# Dockerfile.custom-nodes -ARG N8N_VERSION=latest -FROM n8nio/n8n:${N8N_VERSION} - -USER root - -# Custom Node Verzeichnis erstellen -RUN mkdir -p /home/node/.n8n/custom/n8n-nodes-librebooking && \ - chown -R node:node /home/node/.n8n/custom - -WORKDIR /home/node/.n8n/custom/n8n-nodes-librebooking - -# Dateien kopieren -COPY --chown=node:node custom-nodes/ ./ - -# Dependencies installieren und bauen -RUN npm install && npm run build - -USER node -WORKDIR /home/node - -ENV N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom -ENV N8N_COMMUNITY_NODES_ENABLED=true - -EXPOSE 5678 -CMD ["n8n", "start"] -``` - -### Image bauen und verwenden +## Für Updates ```bash -# Image bauen -docker build -f Dockerfile.custom-nodes -t n8n-librebooking . +./update-node.sh n8n -# Container starten -docker run -d \ - --name n8n \ - -p 5678:5678 \ - -v n8n_data:/home/node/.n8n \ - n8n-librebooking -``` - -### Mit docker-compose - -```yaml -version: '3.8' - -services: - n8n: - build: - context: . - dockerfile: Dockerfile.custom-nodes - args: - N8N_VERSION: latest - # ... weitere Konfiguration -``` - -### Multi-Stage Build (Optimiert) - -```dockerfile -# Build Stage -FROM node:18-alpine AS builder -WORKDIR /build -COPY custom-nodes/ ./ -RUN npm install && npm run build - -# Production Stage -ARG N8N_VERSION=latest -FROM n8nio/n8n:${N8N_VERSION} - -USER root -RUN mkdir -p /home/node/.n8n/custom/n8n-nodes-librebooking && \ - chown -R node:node /home/node/.n8n/custom - -# Nur gebaute Dateien kopieren -COPY --from=builder --chown=node:node /build/dist /home/node/.n8n/custom/n8n-nodes-librebooking/dist -COPY --from=builder --chown=node:node /build/package.json /home/node/.n8n/custom/n8n-nodes-librebooking/ - -USER node - -ENV N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom -ENV N8N_COMMUNITY_NODES_ENABLED=true - -CMD ["n8n", "start"] +# Oder +npm run docker:deploy ``` --- -## Verifizierung der Installation - -### 1. Container-Logs prüfen +## Verifikation ```bash -# Logs anzeigen -docker logs n8n 2>&1 | grep -i "librebooking\|custom\|node" +# Dateien prüfen +docker exec n8n ls -la /home/node/.n8n/custom/n8n-nodes-librebooking/ -# Live Logs verfolgen -docker logs -f n8n -``` - -### 2. In n8n prüfen - -1. Öffnen Sie n8n im Browser (z.B. `http://localhost:5678`) -2. Erstellen Sie einen neuen Workflow -3. Fügen Sie einen neuen Node hinzu -4. Suchen Sie nach "LibreBooking" - zwei Nodes sollten erscheinen: - - **LibreBooking** - Hauptnode für alle Operationen - - **LibreBooking Trigger** - Trigger für neue Reservierungen - -### 3. Node-Verzeichnis im Container prüfen - -```bash -# In Container einloggen -docker exec -it n8n /bin/sh - -# Custom Nodes Verzeichnis prüfen -ls -la /home/node/.n8n/custom/ - -# LibreBooking Node prüfen -ls -la /home/node/.n8n/custom/n8n-nodes-librebooking/dist/ -``` - -### 4. Umgebungsvariablen prüfen - -```bash -docker exec n8n env | grep N8N_CUSTOM -# N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom - -docker exec n8n env | grep COMMUNITY -# N8N_COMMUNITY_NODES_ENABLED=true +# Sollte zeigen: +# dist/ +# package.json +# node_modules/ ``` --- -## Troubleshooting +## Probleme? -### Problem: Node wird nicht erkannt - -**Symptom:** LibreBooking Node erscheint nicht in der Node-Suche - -**Lösungen:** - -1. **Pfad prüfen:** - ```bash - docker exec n8n ls -la /home/node/.n8n/custom/n8n-nodes-librebooking/ - ``` - -2. **Umgebungsvariablen prüfen:** - ```bash - docker exec n8n env | grep -E "N8N_CUSTOM|COMMUNITY" - ``` - -3. **Container komplett neu starten:** - ```bash - docker-compose down - docker-compose up -d - ``` - -4. **Logs auf Fehler prüfen:** - ```bash - docker logs n8n 2>&1 | grep -i error - ``` - -### Problem: Permissions-Fehler - -**Symptom:** `EACCES: permission denied` oder ähnliche Fehler - -**Lösungen:** - -1. **Berechtigungen setzen:** - ```bash - sudo chown -R 1000:1000 custom-nodes/ - sudo chmod -R 755 custom-nodes/ - ``` - -2. **Volume mit korrektem User mounten:** - ```yaml - services: - n8n: - user: "1000:1000" - ``` - -3. **SELinux Context (falls aktiviert):** - ```bash - sudo chcon -R -t svirt_sandbox_file_t custom-nodes/ - ``` - -### Problem: Container startet nicht - -**Symptom:** Container crashed oder startet nicht - -**Lösungen:** - -1. **Logs prüfen:** - ```bash - docker logs n8n - ``` - -2. **Ohne Override starten (zum Testen):** - ```bash - mv docker-compose.override.yml docker-compose.override.yml.bak - docker-compose up -d - ``` - -3. **Volume-Konflikte prüfen:** - ```bash - docker volume ls - docker volume inspect n8n_data - ``` - -### Problem: Node wird geladen aber Fehler bei Ausführung - -**Symptom:** Node ist sichtbar aber Ausführung schlägt fehl - -**Lösungen:** - -1. **Build prüfen:** - ```bash - docker exec n8n ls -la /home/node/.n8n/custom/n8n-nodes-librebooking/dist/ - ``` - -2. **Neu bauen:** - ```bash - cd custom-nodes - npm run rebuild - docker-compose restart n8n - ``` - -3. **Dependencies prüfen:** - ```bash - docker exec -it n8n /bin/sh - cd /home/node/.n8n/custom/n8n-nodes-librebooking - npm ls - ``` - -### Problem: TypeScript Build schlägt fehl - -**Symptom:** `tsc` Fehler beim Bauen - -**Lösungen:** - -1. **Node.js Version prüfen:** - ```bash - node --version # Sollte 18+ sein - npm --version - ``` - -2. **Clean Build:** - ```bash - cd custom-nodes - rm -rf node_modules dist package-lock.json - npm install - npm run build - ``` - -### Problem: Webhook URL nicht erreichbar - -**Symptom:** LibreBooking kann Webhooks nicht senden - -**Lösungen:** - -1. **WEBHOOK_URL Umgebungsvariable setzen:** - ```yaml - environment: - - WEBHOOK_URL=https://ihre-domain.com/ - ``` - -2. **Netzwerk-Konfiguration prüfen:** - ```bash - docker network inspect $(docker network ls -q) - ``` - ---- - -## Updates und Wartung - -### Node aktualisieren - -```bash -# Neue Version herunterladen -cd /pfad/zu/neuem/librebooking_n8n_node - -# Custom Nodes ersetzen -rm -rf /pfad/zu/n8n/custom-nodes -cp -r custom-nodes /pfad/zu/n8n/custom-nodes - -# Neu bauen -cd /pfad/zu/n8n/custom-nodes -npm install -npm run build - -# Container neustarten -cd /pfad/zu/n8n -docker-compose restart n8n -``` - -### n8n Version aktualisieren - -```bash -# Image aktualisieren -docker pull n8nio/n8n:latest - -# Container neu erstellen -docker-compose down -docker-compose up -d -``` - -### Backup erstellen - -```bash -# Docker Volumes sichern -docker run --rm \ - -v n8n_data:/source:ro \ - -v $(pwd)/backup:/backup \ - alpine tar cvzf /backup/n8n_data_backup.tar.gz -C /source . - -# Custom Nodes sichern -tar cvzf custom-nodes-backup.tar.gz custom-nodes/ -``` - -### Logs rotieren - -```yaml -services: - n8n: - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "3" -``` - ---- - -## Erweiterte Konfiguration - -### Kubernetes Deployment - -Für Kubernetes-Umgebungen kann ein ConfigMap oder PersistentVolume verwendet werden: - -```yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: librebooking-node -data: - # Node-Dateien als ConfigMap ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: n8n -spec: - template: - spec: - containers: - - name: n8n - volumeMounts: - - name: custom-nodes - mountPath: /home/node/.n8n/custom/n8n-nodes-librebooking - volumes: - - name: custom-nodes - configMap: - name: librebooking-node -``` - -### Mit Reverse Proxy (Nginx) - -Siehe `nginx.conf` für eine vollständige Nginx-Konfiguration mit: -- SSL/TLS Termination -- WebSocket Support -- Optimierte Timeouts für Webhooks - ---- - -## Hilfe und Support - -- **GitHub Issues:** [Repository Issues](https://github.com/ihr-repo/librebooking-n8n-node/issues) -- **n8n Community:** [community.n8n.io](https://community.n8n.io) -- **LibreBooking Dokumentation:** [LibreBooking Wiki](https://github.com/effgarces/BookedScheduler/wiki) - ---- - -*Letzte Aktualisierung: Januar 2026* +Siehe [TROUBLESHOOTING.md](TROUBLESHOOTING.md) diff --git a/GIT-COMMANDS.md b/GIT-COMMANDS.md new file mode 100644 index 0000000..d5599be --- /dev/null +++ b/GIT-COMMANDS.md @@ -0,0 +1,65 @@ +# Git-Befehle für LibreBooking n8n Node + +## Schnellbefehle + +```bash +# Alle Änderungen committen +git add . +git commit -m "fix: Vereinfachte Installation" + +# Push +git push origin main +``` + +## Release erstellen + +```bash +# 1. Version in package.json anpassen (z.B. 1.1.0) + +# 2. Alte Archive löschen +rm ../n8n-nodes-librebooking*.tar.gz +rm ../n8n-nodes-librebooking*.zip + +# 3. Neue Archive erstellen +git archive --format=tar.gz --prefix=n8n-nodes-librebooking/ --output=../n8n-nodes-librebooking-v1.1.0.tar.gz HEAD +git archive --format=zip --prefix=n8n-nodes-librebooking/ --output=../n8n-nodes-librebooking-v1.1.0.zip HEAD + +# 4. Tag erstellen +git tag -a v1.1.0 -m "Version 1.1.0 - Vereinfachte Installation" + +# 5. Push +git push origin main +git push origin v1.1.0 +``` + +## Mit Skripten + +```bash +# Commit +./git-commit.sh "fix: Beschreibung der Änderung" + +# Cleanup +./git-cleanup.sh + +# Release +./create-release.sh +``` + +## Nützliche Befehle + +```bash +# Status anzeigen +git status + +# Änderungen anzeigen +git diff + +# Log anzeigen +git log --oneline -10 + +# Tags anzeigen +git tag -l + +# Remote anzeigen +git remote -v +``` diff --git a/INSTALLATION.md b/INSTALLATION.md index ba46884..e225a3c 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -1,553 +1,101 @@ -# Installationsanleitung - LibreBooking n8n Node +# Installation -Diese Anleitung beschreibt alle verfügbaren Methoden zur Installation des LibreBooking n8n Nodes. +## Methode 1: Auf dem Host bauen (EMPFOHLEN) ⭐ -## Inhaltsverzeichnis +Die zuverlässigste Methode für Docker-Installationen. -- [Voraussetzungen](#voraussetzungen) -- [Installation aus Git-Archiv](#installation-aus-git-archiv) -- [Methode 1: Automatische Installation mit Skript](#methode-1-automatische-installation-mit-skript) -- [Methode 2: Manuelle Installation mit npm](#methode-2-manuelle-installation-mit-npm) -- [Methode 3: Installation aus npm Registry](#methode-3-installation-aus-npm-registry) -- [Methode 4: Docker Installation](#methode-4-docker-installation) -- [Methode 5: n8n Community Nodes](#methode-5-n8n-community-nodes) -- [Verifizierung der Installation](#verifizierung-der-installation) -- [Troubleshooting](#troubleshooting) -- [Deinstallation](#deinstallation) +### Voraussetzungen ---- +- Node.js 18+ +- npm +- Docker -## Voraussetzungen - -### Systemanforderungen - -| Komponente | Mindestversion | Empfohlen | -|------------|---------------|-----------| -| Node.js | 18.x | 20.x LTS | -| npm | 8.x | 10.x | -| n8n | 1.0.0 | Neueste | - -### Node.js installieren - -**Linux (Ubuntu/Debian):** -```bash -# Mit NodeSource Repository (empfohlen) -curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - -sudo apt-get install -y nodejs - -# Version prüfen -node --version -npm --version -``` - -**macOS:** -```bash -# Mit Homebrew -brew install node@20 - -# Version prüfen -node --version -npm --version -``` - -**Windows:** -1. Lade Node.js von https://nodejs.org/ herunter -2. Wähle die LTS-Version (20.x) -3. Führe den Installer aus -4. Öffne PowerShell und prüfe: `node --version` - -### n8n installieren +### Installation ```bash -# Global installieren -npm install -g n8n - -# Installation prüfen -n8n --version -``` - ---- - -## Installation aus Git-Archiv - -### Archiv herunterladen - -1. **GitHub Release herunterladen:** - ```bash - # .tar.gz für Linux/Mac - wget https://github.com/DEIN-REPO/n8n-nodes-librebooking/releases/latest/download/n8n-nodes-librebooking.tar.gz - - # .zip für Windows - # Über Browser herunterladen - ``` - -2. **Oder direkt von Git:** - ```bash - git clone https://github.com/DEIN-REPO/n8n-nodes-librebooking.git - cd n8n-nodes-librebooking - ``` - -### Archiv entpacken - -**Linux/macOS:** -```bash -# .tar.gz entpacken -tar -xzf n8n-nodes-librebooking.tar.gz -cd n8n-nodes-librebooking -``` - -**Windows (PowerShell):** -```powershell -# .zip entpacken -Expand-Archive -Path n8n-nodes-librebooking.zip -DestinationPath . -cd n8n-nodes-librebooking -``` - ---- - -## Methode 1: Automatische Installation mit Skript - -Die einfachste Methode für die meisten Benutzer. - -### Linux/macOS - -```bash -# In das Verzeichnis wechseln +# 1. Klonen +git clone https://github.com/your-org/n8n-nodes-librebooking.git cd n8n-nodes-librebooking -# Skript ausführbar machen (falls nötig) -chmod +x install.sh - -# Installation starten -./install.sh -``` - -**Optionen:** -```bash -./install.sh # Standard-Installation mit npm link -./install.sh --no-link # Nur Build, ohne npm link -./install.sh --global # Globale Installation -./install.sh --help # Hilfe anzeigen -``` - -### Windows (PowerShell) - -```powershell -# In das Verzeichnis wechseln -cd n8n-nodes-librebooking - -# Skript ausführen (evtl. Ausführungsrichtlinie anpassen) -Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -.\install.ps1 -``` - -**Optionen:** -```powershell -.\install.ps1 # Standard-Installation mit npm link -.\install.ps1 -NoLink # Nur Build, ohne npm link -.\install.ps1 -Global # Globale Installation -.\install.ps1 -Help # Hilfe anzeigen -``` - ---- - -## Methode 2: Manuelle Installation mit npm - -Für Benutzer, die mehr Kontrolle über den Installationsprozess möchten. - -### Schritt 1: Dependencies installieren - -```bash -cd n8n-nodes-librebooking +# 2. Dependencies installieren npm install -``` -### Schritt 2: TypeScript kompilieren - -```bash +# 3. Bauen npm run build + +# 4. In Container kopieren +docker cp dist n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp package.json n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp node_modules n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ + +# 5. Container neustarten +docker restart n8n ``` -### Schritt 3: Node verlinken +### Mit Skript ```bash -# Node global verfügbar machen +./quick-install.sh n8n +``` + +### Mit npm scripts + +```bash +npm install +npm run docker:deploy +``` + +--- + +## Methode 2: Docker mit vorgebautem dist/ + +Für Read-only Volumes. + +```bash +# 1. Bauen +npm install +npm run build + +# 2. Kopieren +cp -r dist package.json node_modules /pfad/zu/custom-nodes/n8n-nodes-librebooking/ + +# 3. docker-compose.yml +volumes: + - ./custom-nodes:/home/node/.n8n/custom:ro # Read-only möglich! +``` + +--- + +## Methode 3: Native Installation (ohne Docker) + +```bash +# 1. Installieren +npm install +npm run build npm link -# Mit n8n verlinken (optional, falls n8n global installiert ist) -cd $(npm root -g)/n8n -npm link n8n-nodes-librebooking -``` - -### Schritt 4: n8n neu starten - -```bash -# n8n stoppen (falls läuft) -# Ctrl+C oder: -pkill -f n8n - -# n8n starten +# 2. n8n starten n8n start ``` --- -## Methode 3: Installation aus npm Registry +## Verifikation -> **Hinweis:** Diese Methode ist für eine zukünftige Veröffentlichung auf npm vorgesehen. +Nach der Installation: -```bash -# Global installieren -npm install -g n8n-nodes-librebooking - -# Oder lokal in einem Projekt -npm install n8n-nodes-librebooking -``` - -Nach der Veröffentlichung auf npm wird diese Methode die einfachste sein. - ---- - -## Methode 4: Docker Installation - -### Voraussetzungen für Docker - -- Docker 20.x oder höher -- Docker Compose v2.x (empfohlen) - -**Docker installieren:** -- Linux: https://docs.docker.com/engine/install/ -- macOS: https://docs.docker.com/desktop/mac/install/ -- Windows: https://docs.docker.com/desktop/windows/install/ - -### Mit docker-compose (empfohlen) - -```bash -cd n8n-nodes-librebooking - -# Umgebungsvariablen konfigurieren (optional) -cp .env.example .env -# .env Datei bearbeiten - -# Container bauen und starten -docker-compose up -d - -# Logs anzeigen -docker-compose logs -f - -# Status prüfen -docker-compose ps -``` - -**Umgebungsvariablen (`.env`):** -```env -# n8n Authentifizierung -N8N_BASIC_AUTH_USER=admin -N8N_BASIC_AUTH_PASSWORD=sicheres-passwort-hier - -# Webhook-URL (für Produktion) -WEBHOOK_URL=https://n8n.deine-domain.de/ - -# Zeitzone -TZ=Europe/Berlin - -# Log-Level (debug, info, warn, error) -N8N_LOG_LEVEL=info -``` - -**Nützliche docker-compose Befehle:** -```bash -# Stoppen -docker-compose down - -# Neu bauen (nach Änderungen) -docker-compose build --no-cache - -# Neustart -docker-compose restart - -# Logs eines bestimmten Services -docker-compose logs -f n8n - -# In Container Shell -docker-compose exec n8n sh -``` - -### Mit Docker direkt - -```bash -cd n8n-nodes-librebooking - -# Image bauen -docker build -t n8n-librebooking . - -# Container starten -docker run -d \ - --name n8n-librebooking \ - -p 5678:5678 \ - -e N8N_BASIC_AUTH_ACTIVE=true \ - -e N8N_BASIC_AUTH_USER=admin \ - -e N8N_BASIC_AUTH_PASSWORD=changeme \ - -e GENERIC_TIMEZONE=Europe/Berlin \ - -v n8n_data:/home/node/.n8n \ - n8n-librebooking -``` - -**Container verwalten:** -```bash -# Logs anzeigen -docker logs -f n8n-librebooking - -# Stoppen -docker stop n8n-librebooking - -# Starten -docker start n8n-librebooking - -# Entfernen -docker rm -f n8n-librebooking - -# Image entfernen -docker rmi n8n-librebooking -``` - -### Volumes und Konfiguration - -**Wichtige Volumes:** - -| Volume/Pfad | Beschreibung | -|-------------|--------------| -| `/home/node/.n8n` | n8n Datenverzeichnis (Workflows, Credentials) | -| `/home/node/.n8n/custom` | Custom Nodes | -| `/home/node/workflows` | Beispiel-Workflows (read-only) | - -**Daten sichern:** -```bash -# Mit docker-compose -docker-compose exec n8n tar -czf /tmp/backup.tar.gz /home/node/.n8n -docker cp n8n-librebooking:/tmp/backup.tar.gz ./backup.tar.gz - -# Ohne docker-compose -docker cp n8n-librebooking:/home/node/.n8n ./n8n-backup -``` - -**Daten wiederherstellen:** -```bash -docker cp ./n8n-backup/. n8n-librebooking:/home/node/.n8n/ -docker-compose restart -``` - ---- - -## Methode 5: n8n Community Nodes - -> **Hinweis:** Diese Methode wird verfügbar sein, sobald der Node im n8n Community Node Repository veröffentlicht ist. - -1. Öffne n8n im Browser -2. Gehe zu **Settings** → **Community Nodes** -3. Klicke auf **Install a community node** -4. Gib ein: `n8n-nodes-librebooking` -5. Klicke auf **Install** -6. Starte n8n neu - ---- - -## Verifizierung der Installation - -### 1. n8n starten - -```bash -# Lokal -n8n start - -# Mit Docker -docker-compose up -d -``` - -### 2. Browser öffnen - -Öffne http://localhost:5678 im Browser. - -### 3. Node suchen - -1. Erstelle einen neuen Workflow -2. Klicke auf das **+** Symbol +1. Öffne n8n: http://localhost:5678 +2. Erstelle neuen Workflow 3. Suche nach "LibreBooking" -4. Du solltest zwei Nodes sehen: - - **LibreBooking** (für API-Operationen) - - **LibreBooking Trigger** (für Events) - -### 4. Credentials einrichten - -1. Klicke auf einen LibreBooking Node -2. Unter "Credentials" klicke auf **Create New** -3. Wähle **LibreBooking API** -4. Fülle aus: - - **URL:** Deine LibreBooking URL (z.B. `https://booking.example.com/Web/Services`) - - **Username:** Dein Admin-Benutzername - - **Password:** Dein Passwort -5. Klicke auf **Save** -6. Teste die Verbindung - ---- - -## Troubleshooting - -### Häufige Probleme - -#### Node wird nicht angezeigt - -**Problem:** Der LibreBooking Node erscheint nicht in n8n. - -**Lösungen:** -```bash -# 1. Prüfe, ob der Build erfolgreich war -ls -la dist/ - -# 2. Prüfe npm link Status -npm ls -g --depth=0 | grep librebooking - -# 3. n8n Custom Extensions Pfad prüfen -echo $N8N_CUSTOM_EXTENSIONS - -# 4. n8n komplett neu starten -pkill -f n8n -n8n start -``` - -#### Build-Fehler - -**Problem:** `npm run build` schlägt fehl. - -**Lösungen:** -```bash -# Node.js Version prüfen -node --version # Sollte >= 18 sein - -# node_modules löschen und neu installieren -rm -rf node_modules -npm install - -# TypeScript-Fehler anzeigen -npx tsc --noEmit -``` - -#### npm link Probleme - -**Problem:** `npm link` funktioniert nicht. - -**Lösungen:** -```bash -# Als Admin/Root ausführen (Linux/Mac) -sudo npm link - -# Windows: PowerShell als Administrator starten - -# Alternativer Pfad für Custom Nodes -export N8N_CUSTOM_EXTENSIONS=$(pwd) -n8n start -``` - -#### Docker-Probleme - -**Problem:** Container startet nicht. - -**Lösungen:** -```bash -# Logs prüfen -docker-compose logs n8n - -# Container-Status prüfen -docker-compose ps - -# Neu bauen -docker-compose build --no-cache -docker-compose up -d -``` - -#### Credential-Fehler - -**Problem:** "Authentication failed" bei Verbindung. - -**Lösungen:** -1. Prüfe LibreBooking URL (inkl. `/Web/Services`) -2. Prüfe Benutzername und Passwort -3. Prüfe ob API in LibreBooking aktiviert ist: - - Admin → Konfiguration → API aktivieren -4. Teste API manuell: - ```bash - curl -X POST "https://dein-server/Web/Services/Authentication/Authenticate" \ - -H "Content-Type: application/json" \ - -d '{"username":"admin","password":"password"}' - ``` - -### Logs und Debugging - -**n8n Logs aktivieren:** -```bash -# Umgebungsvariable setzen -export N8N_LOG_LEVEL=debug -n8n start - -# Mit Docker -docker-compose exec n8n sh -c "N8N_LOG_LEVEL=debug n8n start" -``` - -**Node-spezifische Logs:** -In n8n Workflow-Ausführungen werden Details angezeigt unter "Execution Data". - ---- +4. Wenn der Node erscheint → ✅ Installation erfolgreich ## Deinstallation -### npm link entfernen - ```bash -# Im Projektverzeichnis -npm unlink +# Docker +docker exec n8n rm -rf /home/node/.n8n/custom/n8n-nodes-librebooking +docker restart n8n -# Global entfernen +# Native npm unlink -g n8n-nodes-librebooking ``` - -### Global installiertes Paket entfernen - -```bash -npm uninstall -g n8n-nodes-librebooking -``` - -### Docker entfernen - -```bash -# Container und Volumes entfernen -docker-compose down -v - -# Images entfernen -docker rmi n8n-librebooking -docker rmi n8nio/n8n -``` - -### Projektverzeichnis löschen - -```bash -# Verzeichnis löschen -rm -rf n8n-nodes-librebooking - -# Archiv löschen -rm n8n-nodes-librebooking.tar.gz -rm n8n-nodes-librebooking.zip -``` - ---- - -## Support - -Bei Fragen oder Problemen: - -1. **GitHub Issues:** [Hier Issues erstellen](https://github.com/DEIN-REPO/n8n-nodes-librebooking/issues) -2. **Dokumentation:** Siehe [README.md](README.md) -3. **LibreBooking API:** https://www.bookedscheduler.com/help/api/ - ---- - -*Letzte Aktualisierung: Januar 2026* diff --git a/MANUELLE-INSTALLATION-CONTAINER.md b/MANUELLE-INSTALLATION-CONTAINER.md new file mode 100644 index 0000000..559b94e --- /dev/null +++ b/MANUELLE-INSTALLATION-CONTAINER.md @@ -0,0 +1,328 @@ +# Manuelle Installation im Docker Container + +Diese Anleitung beschreibt, wie Sie den LibreBooking Node manuell im Docker Container installieren, wenn die Dateien bereits kopiert wurden, aber der Node nicht in n8n erscheint. + +--- + +## Situation + +Sie haben die Dateien in den Container kopiert (z.B. nach `/opt/n8n/custom-nodes` oder `/home/node/.n8n/custom/n8n-nodes-librebooking`), aber: + +- Der LibreBooking Node erscheint nicht in der n8n Node-Suche +- Die TypeScript-Dateien wurden nicht kompiliert +- Das `dist/` Verzeichnis fehlt oder ist leer + +--- + +## Diagnose + +### Schnell-Check mit einem Befehl + +```bash +# Prüft Dateien und Status im Container +docker exec n8n sh -c "ls -la /home/node/.n8n/custom/*/dist/ 2>/dev/null || echo 'dist/ nicht gefunden'" +``` + +### Ausführlicher Check mit Skript + +```bash +# Check-Skript in Container kopieren und ausführen +docker cp check-installation.sh n8n:/tmp/ +docker exec n8n sh /tmp/check-installation.sh +``` + +### Manuell im Container prüfen + +```bash +# In den Container einloggen +docker exec -it n8n sh + +# Prüfen, was vorhanden ist +ls -la /home/node/.n8n/custom/ +ls -la /home/node/.n8n/custom/n8n-nodes-librebooking/ + +# Umgebungsvariablen prüfen +env | grep N8N + +# Container verlassen +exit +``` + +--- + +## Lösung 1: Automatisch mit Skript (empfohlen) + +Das einfachste ist das All-in-One-Skript: + +```bash +# Auf dem Host ausführen +./fix-node-installation.sh +``` + +Oder mit spezifischem Container-Namen: + +```bash +./fix-node-installation.sh -c mein-n8n-container +``` + +### Alternative: Nur das Installations-Skript + +```bash +# Skript in Container kopieren +docker cp install-in-container.sh n8n:/tmp/ + +# Skript im Container ausführen +docker exec -it n8n sh /tmp/install-in-container.sh + +# Container neustarten +docker restart n8n +``` + +--- + +## Lösung 2: Manuelle Schritte im Container + +### Schritt 1: In den Container einloggen + +```bash +docker exec -it n8n sh +``` + +### Schritt 2: Zum Custom-Node-Verzeichnis wechseln + +```bash +# Standard-Pfad (offizielles n8n Image) +cd /home/node/.n8n/custom/n8n-nodes-librebooking + +# Oder falls an anderem Ort: +cd /opt/n8n/custom-nodes +# oder +cd /data/custom-nodes +``` + +### Schritt 3: Prüfen ob Dateien vorhanden sind + +```bash +ls -la +# Sollte zeigen: package.json, tsconfig.json, nodes/, credentials/ +``` + +### Schritt 4: Dependencies installieren + +```bash +npm install +``` + +Erwartete Ausgabe: +``` +added 50 packages in 10s +``` + +### Schritt 5: Node kompilieren + +```bash +npm run build +``` + +Erwartete Ausgabe: +``` +> n8n-nodes-librebooking@1.0.0 build +> tsc && npm run copy:icons +``` + +### Schritt 6: Prüfen ob Build erfolgreich war + +```bash +ls -la dist/ +ls -la dist/nodes/LibreBooking/ +``` + +Sollte zeigen: +- `dist/nodes/LibreBooking/LibreBooking.node.js` +- `dist/nodes/LibreBookingTrigger/LibreBookingTrigger.node.js` +- `dist/credentials/LibreBookingApi.credentials.js` + +### Schritt 7: Container verlassen und neustarten + +```bash +# Container verlassen +exit + +# Container neustarten (auf dem Host) +docker restart n8n +``` + +--- + +## Lösung 3: Direkt mit docker exec (Ein-Befehl-Lösung) + +Wenn Sie schnell zum Ziel kommen wollen: + +```bash +# Alles in einem Befehl +docker exec n8n sh -c "cd /home/node/.n8n/custom/n8n-nodes-librebooking && npm install && npm run build" + +# Container neustarten +docker restart n8n +``` + +Für anderen Pfad: + +```bash +docker exec n8n sh -c "cd /opt/n8n/custom-nodes && npm install && npm run build" +docker restart n8n +``` + +--- + +## Verifizierung + +### 1. Logs prüfen + +```bash +# Nach LibreBooking in den Logs suchen +docker logs n8n 2>&1 | grep -i "librebooking\|custom\|node" +``` + +### 2. In n8n prüfen + +1. Öffnen Sie n8n im Browser (z.B. `http://localhost:5678`) +2. Erstellen Sie einen neuen Workflow oder öffnen Sie einen bestehenden +3. Klicken Sie auf `+` um einen neuen Node hinzuzufügen +4. Suchen Sie nach "LibreBooking" +5. Es sollten zwei Nodes erscheinen: + - **LibreBooking** - Hauptnode für alle Operationen + - **LibreBooking Trigger** - Trigger für Reservierungen + +### 3. Node-Dateien im Container prüfen + +```bash +# Prüfe ob .node.js Dateien existieren +docker exec n8n find /home/node/.n8n/custom -name "*.node.js" 2>/dev/null +``` + +--- + +## Troubleshooting + +### Problem: npm nicht gefunden + +**Symptom:** +``` +sh: npm: not found +``` + +**Lösung:** +Das verwendete Docker-Image enthält kein npm. Verwenden Sie ein Image mit Node.js: + +```bash +# Prüfen Sie das Image +docker inspect n8n --format='{{.Config.Image}}' + +# Das offizielle n8n Image (n8nio/n8n) enthält npm +# Falls Sie ein minimales Image verwenden, wechseln Sie zu n8nio/n8n +``` + +### Problem: Permission denied + +**Symptom:** +``` +EACCES: permission denied +npm ERR! could not create a lockfile +``` + +**Lösung:** +n8n läuft als User `node` (UID 1000). Setzen Sie die Berechtigungen: + +```bash +# Auf dem Host (vor dem Kopieren) +sudo chown -R 1000:1000 custom-nodes/ + +# Oder im Container als root +docker exec -u root n8n chown -R node:node /home/node/.n8n/custom/ +``` + +### Problem: Build schlägt fehl + +**Symptom:** +``` +error TS2307: Cannot find module 'n8n-workflow' +``` + +**Lösung:** +```bash +# Im Container +cd /home/node/.n8n/custom/n8n-nodes-librebooking +rm -rf node_modules package-lock.json +npm install +npm run build +``` + +### Problem: Node erscheint nach Neustart immer noch nicht + +**Mögliche Ursachen:** + +1. **Falscher Pfad**: N8N_CUSTOM_EXTENSIONS zeigt nicht auf das richtige Verzeichnis + ```bash + docker exec n8n env | grep N8N_CUSTOM + # Sollte zeigen: N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom + ``` + +2. **package.json n8n-Sektion fehlt**: Die `n8n` Konfiguration in package.json muss korrekt sein + ```bash + docker exec n8n cat /home/node/.n8n/custom/n8n-nodes-librebooking/package.json | grep -A10 '"n8n"' + ``` + +3. **Container-Cache**: Container komplett neu erstellen + ```bash + docker compose down + docker compose up -d + ``` + +4. **n8n Version zu alt**: Der Node benötigt n8n >= 1.0.0 + ```bash + docker exec n8n n8n --version + ``` + +### Problem: Fehler bei npm install (Netzwerk) + +**Symptom:** +``` +npm ERR! network request failed +``` + +**Lösung:** +```bash +# DNS prüfen +docker exec n8n cat /etc/resolv.conf + +# npm Registry prüfen +docker exec n8n npm config get registry + +# Ggf. Registry setzen +docker exec n8n npm config set registry https://registry.npmjs.org/ +``` + +--- + +## Schnellreferenz + +| Aktion | Befehl | +|--------|--------| +| Status prüfen | `docker exec n8n sh /tmp/check-installation.sh` | +| Installieren | `docker exec n8n sh -c "cd /home/node/.n8n/custom/n8n-nodes-librebooking && npm install && npm run build"` | +| Neustarten | `docker restart n8n` | +| Logs prüfen | `docker logs n8n 2>&1 \| grep -i libre` | +| Im Container | `docker exec -it n8n sh` | + +--- + +## Weiterführende Dokumentation + +- [DOCKER-INTEGRATION.md](DOCKER-INTEGRATION.md) - Vollständige Docker-Anleitung +- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Alle Troubleshooting-Themen +- [SCHNELLSTART-DOCKER.md](SCHNELLSTART-DOCKER.md) - Docker-Schnellstart + +--- + +*Letzte Aktualisierung: Januar 2026* diff --git a/README.md b/README.md index ec38613..ef4abac 100644 --- a/README.md +++ b/README.md @@ -1,618 +1,102 @@ -# n8n-nodes-librebooking +# LibreBooking n8n Node -Ein vollständiger n8n Node für die Integration mit [LibreBooking](https://librebooking.org/) - einer Open-Source Ressourcen- und Raumbuchungslösung. +Integration von LibreBooking in n8n für automatisierte Reservierungs- und Ressourcenverwaltung. - -[![npm version](https://img.shields.io/npm/v/n8n-nodes-librebooking.svg?style=flat-square)](https://www.npmjs.com/package/n8n-nodes-librebooking) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) -[![n8n Community Node](https://img.shields.io/badge/n8n-Community%20Node-orange?style=flat-square)](https://n8n.io) -[![LibreBooking](https://img.shields.io/badge/LibreBooking-Integration-blue?style=flat-square)](https://librebooking.org) -[![Node.js Version](https://img.shields.io/badge/node-%3E%3D18-brightgreen?style=flat-square)](https://nodejs.org) -[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue?style=flat-square)](https://www.typescriptlang.org) +## ⚡ Schnellstart (EMPFOHLEN) ---- - -## ⚡ Schnellstart - -### Option 1: Mit Installations-Skript (empfohlen) +**Die einfachste Methode: Auf dem Host bauen, in Docker kopieren** ```bash -# Archiv entpacken -tar -xzf n8n-nodes-librebooking.tar.gz -cd n8n-nodes-librebooking - -# Installieren (Linux/Mac) -./install.sh - -# n8n starten -n8n start -``` - -### Option 2: Mit Docker - -```bash -# Archiv entpacken und starten -tar -xzf n8n-nodes-librebooking.tar.gz -cd n8n-nodes-librebooking -docker-compose up -d - -# Browser öffnen: http://localhost:5678 -``` - -### Option 3: Manuell mit npm - -```bash -cd n8n-nodes-librebooking -npm install && npm run build && npm link -n8n start -``` - -📖 **Detaillierte Anleitung:** Siehe [INSTALLATION.md](INSTALLATION.md) - ---- - -## 📋 Inhaltsverzeichnis - -- [Schnellstart](#-schnellstart) -- [Funktionen](#-funktionen) -- [Installation](#-installation) -- [Konfiguration](#️-konfiguration) -- [Operationen](#-operationen) -- [Beispiele](#-beispiele) -- [Trigger Node](#-trigger-node) -- [Troubleshooting](#-troubleshooting) -- [Entwicklung](#-entwicklung) -- [Lizenz](#-lizenz) - ---- - -## 🚀 Funktionen - -### Regular Node (LibreBooking) -- **Reservierungen**: Erstellen, Abrufen, Aktualisieren, Löschen, Genehmigen, Check-In/Check-Out -- **Ressourcen**: Verwalten von Räumen, Equipment und anderen buchbaren Ressourcen -- **Zeitpläne**: Abrufen von Zeitplänen und verfügbaren Slots -- **Benutzer**: Vollständige Benutzerverwaltung (Admin-Rechte erforderlich) -- **Konten**: Eigenes Konto verwalten -- **Gruppen**: Benutzergruppen mit Rollen und Berechtigungen verwalten -- **Zubehör**: Zubehörteile abrufen -- **Attribute**: Benutzerdefinierte Felder verwalten - -### Trigger Node (LibreBooking Trigger) -- Polling-basierter Trigger für Reservierungs-Events -- Erkennung neuer Reservierungen -- Erkennung geänderter Reservierungen -- Konfigurierbare Filter (Ressource, Zeitplan, Benutzer) -- Deduplizierung von Events - ---- - -## 📦 Installation - -### Über npm (empfohlen) - -```bash -npm install n8n-nodes-librebooking -``` - -### Manuelle Installation - -1. Laden Sie das Paket herunter oder klonen Sie das Repository: - ```bash - git clone https://github.com/your-org/n8n-nodes-librebooking.git - ``` - -2. Wechseln Sie ins Verzeichnis und installieren Sie die Abhängigkeiten: - ```bash - cd n8n-nodes-librebooking - npm install - ``` - -3. Kompilieren Sie das Projekt: - ```bash - npm run build - ``` - -4. Verlinken Sie das Paket für lokale Entwicklung: - ```bash - npm link - ``` - -5. Verlinken Sie es in Ihrem n8n Custom-Nodes-Verzeichnis: - ```bash - cd ~/.n8n/custom - npm link n8n-nodes-librebooking - ``` - -6. Starten Sie n8n neu: - ```bash - n8n start - ``` - -### Docker-Installation - -Fügen Sie in Ihrem Docker-Compose oder Dockerfile hinzu: - -```dockerfile -RUN npm install -g n8n-nodes-librebooking -``` - -Oder über Umgebungsvariable: -```yaml -environment: - - N8N_CUSTOM_EXTENSIONS=n8n-nodes-librebooking -``` - -### Integration in bestehende Docker-Installation - -Für bestehende n8n Docker-Installationen gibt es mehrere Integrationsmethoden: - -#### Schnellste Methode: Automatisches Skript - -```bash -# Ins n8n Verzeichnis wechseln -cd /pfad/zu/ihrer/n8n/installation - -# Skript ausführen -./install-docker.sh -``` - -#### Manuelle Methode - -```bash -# 1. Custom Nodes kopieren -cp -r custom-nodes /pfad/zu/n8n/ -cd /pfad/zu/n8n/custom-nodes && npm install && npm run build - -# 2. docker-compose.override.yml erstellen -cat > docker-compose.override.yml << 'EOF' -version: '3.8' -services: - n8n: - volumes: - - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking:ro - environment: - - N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom - - N8N_COMMUNITY_NODES_ENABLED=true -EOF - -# 3. Container neustarten -docker-compose restart n8n -``` - -#### Eigenes Docker-Image bauen - -```bash -docker build -f Dockerfile.custom-nodes -t n8n-librebooking . -docker run -d -p 5678:5678 n8n-librebooking -``` - -📖 **Ausführliche Docker-Anleitung:** Siehe [DOCKER-INTEGRATION.md](DOCKER-INTEGRATION.md) - -🚀 **Docker-Schnellstart:** Siehe [SCHNELLSTART-DOCKER.md](SCHNELLSTART-DOCKER.md) - ---- - -## ⚙️ Konfiguration - -### Credentials einrichten - -1. Öffnen Sie n8n und gehen Sie zu **Settings** → **Credentials** -2. Klicken Sie auf **Add Credential** und wählen Sie **LibreBooking API** -3. Füllen Sie die folgenden Felder aus: - -| Feld | Beschreibung | Beispiel | -|------|-------------|----------| -| **LibreBooking URL** | Die Basis-URL Ihrer LibreBooking-Installation | `https://booking.example.com` | -| **Benutzername** | Ihr LibreBooking-Login (E-Mail oder Benutzername) | `admin@example.com` | -| **Passwort** | Ihr LibreBooking-Passwort | `•••••••••` | - -> ⚠️ **Wichtig**: Die URL sollte **ohne** `/Web/Services` angegeben werden! - -### API aktivieren - -Stellen Sie sicher, dass die API in Ihrer LibreBooking-Installation aktiviert ist: - -1. Öffnen Sie die `config.php` Ihrer LibreBooking-Installation -2. Suchen Sie nach `$conf['settings']['api']['enabled']` -3. Setzen Sie den Wert auf `true` - -```php -$conf['settings']['api']['enabled'] = 'true'; -``` - -### Credential-Test - -Nach dem Speichern der Credentials können Sie diese testen: -- Klicken Sie auf **Test** um die Verbindung zu überprüfen -- Bei erfolgreicher Verbindung wird eine Bestätigung angezeigt - ---- - -## 📖 Operationen - -### Reservierungen - -| Operation | Beschreibung | Admin-Rechte | -|-----------|-------------|--------------| -| **Alle Abrufen** | Liste aller Reservierungen mit optionalen Filtern | ❌ | -| **Abrufen** | Einzelne Reservierung per Referenznummer | ❌ | -| **Erstellen** | Neue Reservierung anlegen | ❌ | -| **Aktualisieren** | Bestehende Reservierung ändern | ❌ | -| **Löschen** | Reservierung entfernen | ❌ | -| **Genehmigen** | Ausstehende Reservierung genehmigen | ❌* | -| **Check-In** | In Reservierung einchecken | ❌ | -| **Check-Out** | Aus Reservierung auschecken | ❌ | - -*Erfordert Genehmigungsrechte - -#### Filter für "Alle Abrufen" -- `userId` - Nach Benutzer filtern -- `resourceId` - Nach Ressource filtern -- `scheduleId` - Nach Zeitplan filtern -- `startDateTime` - Startzeit (ISO 8601) -- `endDateTime` - Endzeit (ISO 8601) - -#### Zusätzliche Felder für "Erstellen/Aktualisieren" -- `description` - Beschreibung -- `participants` - Teilnehmer (Benutzer-IDs) -- `invitees` - Eingeladene (Benutzer-IDs) -- `resources` - Zusätzliche Ressourcen -- `allowParticipation` - Teilnahme erlauben - -### Ressourcen - -| Operation | Beschreibung | Admin-Rechte | -|-----------|-------------|--------------| -| **Alle Abrufen** | Liste aller Ressourcen | ❌ | -| **Abrufen** | Einzelne Ressource per ID | ❌ | -| **Verfügbarkeit Prüfen** | Verfügbarkeit einer/aller Ressourcen | ❌ | -| **Gruppen Abrufen** | Ressourcen-Gruppenstruktur | ❌ | -| **Typen Abrufen** | Verfügbare Ressourcen-Typen | ❌ | -| **Status Abrufen** | Verfügbare Status-Werte | ❌ | -| **Erstellen** | Neue Ressource anlegen | ✅ | -| **Aktualisieren** | Ressource ändern | ✅ | -| **Löschen** | Ressource entfernen | ✅ | - -#### Ressourcen-Optionen -- `location` - Standort -- `contact` - Kontaktinformation -- `description` - Beschreibung -- `maxParticipants` - Maximale Teilnehmerzahl -- `requiresApproval` - Genehmigung erforderlich -- `allowMultiday` - Mehrtägige Buchungen -- `requiresCheckIn` - Check-In erforderlich -- `autoReleaseMinutes` - Auto-Release nach X Minuten -- `color` - Anzeigefarbe (Hex) -- `statusId` - Status (0=Versteckt, 1=Verfügbar, 2=Nicht verfügbar) - -### Zeitpläne - -| Operation | Beschreibung | Admin-Rechte | -|-----------|-------------|--------------| -| **Alle Abrufen** | Liste aller Zeitpläne | ❌ | -| **Abrufen** | Einzelner Zeitplan mit Perioden | ❌ | -| **Slots Abrufen** | Verfügbare Slots eines Zeitplans | ❌ | - -### Benutzer (Admin) - -| Operation | Beschreibung | Admin-Rechte | -|-----------|-------------|--------------| -| **Alle Abrufen** | Liste aller Benutzer | ❌ | -| **Abrufen** | Einzelner Benutzer per ID | ❌ | -| **Erstellen** | Neuen Benutzer anlegen | ✅ | -| **Aktualisieren** | Benutzer ändern | ✅ | -| **Passwort Ändern** | Benutzer-Passwort setzen | ✅ | -| **Löschen** | Benutzer entfernen | ✅ | - -### Konten - -| Operation | Beschreibung | Admin-Rechte | -|-----------|-------------|--------------| -| **Abrufen** | Eigene Kontoinformationen | ❌ | -| **Erstellen** | Neues Konto (Registrierung) | ❌ | -| **Aktualisieren** | Eigenes Konto ändern | ❌ | -| **Passwort Ändern** | Eigenes Passwort ändern | ❌ | - -### Gruppen - -| Operation | Beschreibung | Admin-Rechte | -|-----------|-------------|--------------| -| **Alle Abrufen** | Liste aller Gruppen | ❌ | -| **Abrufen** | Einzelne Gruppe | ❌ | -| **Erstellen** | Neue Gruppe anlegen | ✅ | -| **Aktualisieren** | Gruppe ändern | ✅ | -| **Löschen** | Gruppe entfernen | ✅ | -| **Rollen Ändern** | Gruppenrollen setzen | ✅ | -| **Berechtigungen Ändern** | Ressourcen-Berechtigungen | ✅ | -| **Benutzer Ändern** | Gruppenmitglieder | ✅ | - -#### Rollen-IDs -- `1` - Gruppenadministrator -- `2` - Anwendungsadministrator -- `3` - Ressourcenadministrator -- `4` - Zeitplanadministrator - -### Zubehör - -| Operation | Beschreibung | Admin-Rechte | -|-----------|-------------|--------------| -| **Alle Abrufen** | Liste aller Zubehörteile | ❌ | -| **Abrufen** | Einzelnes Zubehörteil | ❌ | - -### Attribute - -| Operation | Beschreibung | Admin-Rechte | -|-----------|-------------|--------------| -| **Abrufen** | Einzelnes Attribut | ❌ | -| **Nach Kategorie Abrufen** | Alle Attribute einer Kategorie | ❌ | -| **Erstellen** | Neues Attribut anlegen | ✅ | -| **Aktualisieren** | Attribut ändern | ✅ | -| **Löschen** | Attribut entfernen | ✅ | - -#### Attribut-Kategorien -- `1` - Reservierung -- `2` - Benutzer -- `4` - Ressource -- `5` - Ressourcen-Typ - -#### Attribut-Typen -- `1` - Einzeilig (Text) -- `2` - Mehrzeilig (Textarea) -- `3` - Auswahlliste -- `4` - Checkbox -- `5` - Datum/Zeit - ---- - -## 💡 Beispiele - -### Beispiel 1: Alle Reservierungen der nächsten Woche abrufen - -```json -{ - "nodes": [ - { - "parameters": { - "resource": "reservation", - "operation": "getAll", - "filters": { - "startDateTime": "={{ $now.toISO() }}", - "endDateTime": "={{ $now.plus({days: 7}).toISO() }}" - } - }, - "name": "LibreBooking", - "type": "n8n-nodes-librebooking.libreBooking", - "typeVersion": 1, - "position": [250, 300], - "credentials": { - "libreBookingApi": { - "id": "1", - "name": "LibreBooking" - } - } - } - ] -} -``` - -### Beispiel 2: Neue Reservierung erstellen - -```json -{ - "parameters": { - "resource": "reservation", - "operation": "create", - "resourceId": 1, - "startDateTime": "2026-01-26T10:00:00", - "endDateTime": "2026-01-26T11:00:00", - "title": "Team Meeting", - "additionalFields": { - "description": "Wöchentliches Team-Meeting", - "participants": "2,3,4" - } - } -} -``` - -### Beispiel 3: Verfügbarkeit prüfen - -```json -{ - "parameters": { - "resource": "resource", - "operation": "getAvailability", - "resourceIdOptional": 1, - "availabilityDateTime": "2026-01-26T14:00:00" - } -} -``` - -### Beispiel 4: Benutzer mit Filter suchen - -```json -{ - "parameters": { - "resource": "user", - "operation": "getAll", - "userFilters": { - "organization": "Marketing", - "lastName": "Müller" - } - } -} -``` - ---- - -## ⚡ Trigger Node - -Der **LibreBooking Trigger** ist ein Polling-basierter Trigger, der auf neue oder geänderte Reservierungen reagiert. - -### Konfiguration - -| Parameter | Beschreibung | -|-----------|-------------| -| **Event** | Art des Events (Neue/Geänderte/Alle Reservierungen) | -| **Filter** | Optional: Ressource, Zeitplan, Benutzer | -| **Zeitfenster** | Überwachungszeitraum (7/14/30/90 Tage) | -| **Detaillierte Daten** | Vollständige Reservierungsdetails abrufen | - -### Event-Typen - -- **Neue Reservierung**: Wird nur bei neuen Reservierungen ausgelöst -- **Geänderte Reservierung**: Wird bei Änderungen an bestehenden Reservierungen ausgelöst -- **Alle Reservierungen**: Wird bei neuen und geänderten Reservierungen ausgelöst - -### Beispiel-Workflow: Benachrichtigung bei neuer Reservierung - -``` -[LibreBooking Trigger] → [IF: Ressource = 1] → [Slack: Nachricht senden] -``` - -### Deduplizierung - -Der Trigger speichert Informationen über bereits verarbeitete Reservierungen und verhindert so doppelte Ausführungen. Bei Änderungen (Titel, Zeit, etc.) wird eine Reservierung als "geändert" erkannt. - ---- - -## 🔧 Troubleshooting - -### Häufige Fehler - -#### "Authentifizierung fehlgeschlagen" -- Überprüfen Sie die LibreBooking-URL (ohne `/Web/Services`) -- Stellen Sie sicher, dass Benutzername und Passwort korrekt sind -- Prüfen Sie, ob die API in LibreBooking aktiviert ist - -#### "Zugriff verweigert" (403) -- Die Operation erfordert Administrator-Rechte -- Verwenden Sie einen Admin-Account oder wählen Sie eine andere Operation - -#### "Nicht gefunden" (404) -- Die angegebene ID (Ressource, Benutzer, etc.) existiert nicht -- Überprüfen Sie die Referenznummer bei Reservierungen - -#### "Session abgelaufen" -- Der Session-Token ist abgelaufen -- Führen Sie den Workflow erneut aus - -### API-Limitierungen - -- LibreBooking hat keine dokumentierten Rate-Limits -- Bei vielen Anfragen empfehlen wir Pausen zwischen den Operationen -- Der Node authentifiziert sich bei jeder Ausführung neu und meldet sich am Ende ab - -### Debug-Tipps - -1. Aktivieren Sie die n8n-Logs für detaillierte Fehlermeldungen: - ```bash - export N8N_LOG_LEVEL=debug - n8n start - ``` - -2. Testen Sie die API direkt im Browser: - ``` - https://your-librebooking.com/Web/Services/index.php - ``` - -3. Überprüfen Sie die Zeitzonen-Einstellungen in LibreBooking und n8n - ---- - -## 🛠 Entwicklung - -### Voraussetzungen - -- Node.js 18.17.0 oder höher -- npm 9.x oder höher -- n8n (für Tests) - -### Setup - -```bash -# Repository klonen +# 1. Repository klonen git clone https://github.com/your-org/n8n-nodes-librebooking.git cd n8n-nodes-librebooking -# Abhängigkeiten installieren +# 2. Bauen und installieren +./quick-install.sh n8n + +# Fertig! ✔ +``` + +**Oder manuell:** + +```bash +# Dependencies & Build npm install - -# TypeScript kompilieren npm run build -# Für Entwicklung (Watch-Modus) -npm run dev +# In Container kopieren +docker cp dist n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp package.json n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp node_modules n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ + +# Container neustarten +docker restart n8n ``` -### Projektstruktur - -``` -n8n-nodes-librebooking/ -├── credentials/ -│ └── LibreBookingApi.credentials.ts -├── nodes/ -│ ├── LibreBooking/ -│ │ ├── LibreBooking.node.ts -│ │ └── librebooking.svg -│ └── LibreBookingTrigger/ -│ ├── LibreBookingTrigger.node.ts -│ └── librebooking.svg -├── workflows/ -│ └── example-workflows.json -├── test/ -│ └── test-api.ts -├── package.json -├── tsconfig.json -└── README.md -``` - -### Tests ausführen +## 🛠️ npm Scripts ```bash -# API-Test mit echten Credentials -npm test +npm run build # Baut den Node +npm run docker:deploy # Baut, kopiert & startet Container neu +npm run docker:copy # Kopiert in Container +npm run docker:restart # Startet Container neu ``` -### Linting +## 📚 Dokumentation + +- **[INSTALLATION.md](INSTALLATION.md)** - Alle Installationsmethoden +- **[SCHNELLSTART.md](SCHNELLSTART.md)** - Ultra-kurze Anleitung +- **[TROUBLESHOOTING.md](TROUBLESHOOTING.md)** - Problemlösung +- **[DOCKER-INTEGRATION.md](DOCKER-INTEGRATION.md)** - Docker-spezifische Anleitung + +## 🔑 Credentials einrichten + +1. Öffne n8n: http://localhost:5678 +2. Gehe zu: **Einstellungen** → **Credentials** → **Add Credential** +3. Suche: **LibreBooking API** +4. Eingabe: + - **URL**: `https://deine-librebooking-url.de` + - **Benutzername**: Admin-Benutzer + - **Passwort**: Passwort + +## 🌟 Features + +### LibreBooking Node +- Reservierungen erstellen, bearbeiten, löschen +- Ressourcen und Verfügbarkeit verwalten +- Benutzer und Gruppen administrieren +- Zeitpläne und Zubehör konfigurieren + +### LibreBooking Trigger Node +- Neue Reservierungen überwachen +- Geänderte Reservierungen erfassen +- Filter nach Ressource/Zeitplan/Benutzer + +## 🔄 Updates ```bash -npm run lint -npm run lint:fix +# Nach Änderungen oder git pull +./update-node.sh n8n + +# Oder mit npm +npm run docker:deploy ``` -### Build für Produktion +## ❓ Problemlösung -```bash -npm run build -``` +### tsc not found? +→ **Lösung**: Auf dem Host bauen (siehe Schnellstart) ---- +### Read-only Volume? +→ **Lösung**: dist/ in Container kopieren statt npm im Container + +### npm audit Vulnerabilities? +→ Sind non-critical Dependencies von n8n-workflow. Siehe [SECURITY.md](SECURITY.md) ## 📄 Lizenz -MIT License - siehe [LICENSE](LICENSE) Datei - ---- +MIT - Siehe [LICENSE](LICENSE) ## 🤝 Beitragen -Beiträge sind willkommen! Bitte öffnen Sie einen Issue oder Pull Request. - -1. Fork des Repositories -2. Feature-Branch erstellen (`git checkout -b feature/AmazingFeature`) -3. Änderungen committen (`git commit -m 'Add AmazingFeature'`) -4. Branch pushen (`git push origin feature/AmazingFeature`) -5. Pull Request öffnen - ---- - -## 📞 Support - -- **Issues**: [GitHub Issues](https://github.com/your-org/n8n-nodes-librebooking/issues) -- **LibreBooking Dokumentation**: [https://librebooking.org/docs](https://librebooking.org/docs) -- **n8n Community**: [https://community.n8n.io](https://community.n8n.io) - ---- - -**Erstellt mit ❤️ für die n8n und LibreBooking Community** +Beiträge sind willkommen! Siehe [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/SCHNELLSTART-DOCKER.md b/SCHNELLSTART-DOCKER.md index a36c6d8..7092986 100644 --- a/SCHNELLSTART-DOCKER.md +++ b/SCHNELLSTART-DOCKER.md @@ -1,37 +1,33 @@ -# Schnellstart: Docker-Integration +# Docker Schnellstart - LibreBooking n8n Node -Ultra-kurze Anleitung für erfahrene Docker-Nutzer. +Schnelle Befehle für erfahrene Docker-Benutzer. --- -## Automatische Installation (Empfohlen) +## Neue Installation + +### Option A: Mit docker-compose (empfohlen) ```bash -# Ins n8n Verzeichnis wechseln -cd /pfad/zu/deiner/n8n/installation +# 1. Starten +cd /pfad/zu/librebooking_n8n_node +docker compose up -d -# Skript ausführen -./install-docker.sh - -# Oder mit Pfad -./install-docker.sh -p /opt/n8n +# 2. Browser öffnen +open http://localhost:5678 ``` ---- - -## Manuelle Installation (3 Schritte) - -### 1. Custom Nodes kopieren +### Option B: In bestehende n8n Installation integrieren ```bash +# 1. Custom Nodes kopieren cp -r custom-nodes /pfad/zu/n8n/ + +# 2. Bauen cd /pfad/zu/n8n/custom-nodes npm install && npm run build -``` -### 2. Override-Datei erstellen - -```bash +# 3. docker-compose.override.yml erstellen cat > docker-compose.override.yml << 'EOF' version: '3.8' services: @@ -42,62 +38,110 @@ services: - N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom - N8N_COMMUNITY_NODES_ENABLED=true EOF -``` -### 3. Neustarten - -```bash -docker-compose restart n8n +# 4. Neustarten +docker compose restart n8n ``` --- -## Neues Setup mit Docker +## Node im Container bauen/reparieren + +### Quick-Fix (Ein Befehl) ```bash -# Beispiel-Konfiguration verwenden -cp docker-compose.example.yml docker-compose.yml -cp .env.docker .env +docker exec n8n sh -c "cd /home/node/.n8n/custom/n8n-nodes-librebooking && npm install && npm run build" && docker restart n8n +``` -# .env anpassen, dann starten -docker-compose up -d +### Mit Auto-Fix Skript + +```bash +./fix-node-installation.sh +``` + +### Status prüfen + +```bash +# Check-Skript ausführen +docker cp check-installation.sh n8n:/tmp/ +docker exec n8n sh /tmp/check-installation.sh ``` --- -## Eigenes Image bauen +## Häufige Befehle -```bash -docker build -f Dockerfile.custom-nodes -t n8n-librebooking . -docker run -d -p 5678:5678 n8n-librebooking +| Aktion | Befehl | +|--------|--------| +| Container starten | `docker compose up -d` | +| Container stoppen | `docker compose down` | +| Container neustarten | `docker restart n8n` | +| Logs anzeigen | `docker logs -f n8n` | +| In Container einloggen | `docker exec -it n8n sh` | +| Node bauen | `docker exec n8n sh -c "cd /home/node/.n8n/custom/n8n-nodes-librebooking && npm install && npm run build"` | +| Status prüfen | `docker exec n8n sh /tmp/check-installation.sh` | + +--- + +## Pfade im Container + +| Beschreibung | Pfad | +|--------------|------| +| n8n Home | `/home/node/.n8n` | +| Custom Nodes | `/home/node/.n8n/custom` | +| LibreBooking Node | `/home/node/.n8n/custom/n8n-nodes-librebooking` | +| Daten-Verzeichnis | `/home/node/.n8n` | + +--- + +## Umgebungsvariablen + +```yaml +environment: + - N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom + - N8N_COMMUNITY_NODES_ENABLED=true + - N8N_LOG_LEVEL=info ``` --- -## Verifizierung +## Troubleshooting + +### Node erscheint nicht ```bash -# Node prüfen +# 1. Build prüfen docker exec n8n ls /home/node/.n8n/custom/n8n-nodes-librebooking/dist/ -# In n8n: Nach "LibreBooking" suchen +# 2. Falls leer - neu bauen +docker exec n8n sh -c "cd /home/node/.n8n/custom/n8n-nodes-librebooking && npm install && npm run build" + +# 3. Neustarten +docker restart n8n ``` ---- - -## Bei Problemen +### Permission denied ```bash -# Berechtigungen sudo chown -R 1000:1000 custom-nodes/ +``` -# Logs -docker logs n8n | grep -i error +### docker-compose: distutils Fehler (Python 3.12) -# Neustart -docker-compose down && docker-compose up -d +```bash +# Lösung: Docker Compose v2 verwenden +sudo apt-get install docker-compose-plugin +docker compose up -d # Beachte: ohne Bindestrich ``` --- -📖 **Ausführliche Anleitung:** [DOCKER-INTEGRATION.md](DOCKER-INTEGRATION.md) +## Detaillierte Anleitungen + +- [DOCKER-INTEGRATION.md](DOCKER-INTEGRATION.md) - Vollständige Docker-Dokumentation +- [MANUELLE-INSTALLATION-CONTAINER.md](MANUELLE-INSTALLATION-CONTAINER.md) - Manuelle Installation +- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Alle Probleme und Lösungen + +--- + +*Schnellstart-Guide für Docker-Profis* diff --git a/SCHNELLSTART.md b/SCHNELLSTART.md index a30325f..28b3f17 100644 --- a/SCHNELLSTART.md +++ b/SCHNELLSTART.md @@ -1,60 +1,40 @@ -# Schnellstart - LibreBooking n8n Node +# Schnellstart -Diese Anleitung ist für erfahrene Benutzer, die schnell loslegen möchten. - -## Option A: Mit Skript (empfohlen) +## ⚡ Installation in 4 Zeilen ```bash -# Archiv entpacken -tar -xzf n8n-nodes-librebooking.tar.gz -cd n8n-nodes-librebooking - -# Installieren -./install.sh - -# n8n starten -n8n start -``` - -## Option B: Mit Docker - -```bash -# Archiv entpacken -tar -xzf n8n-nodes-librebooking.tar.gz -cd n8n-nodes-librebooking - -# Container starten -docker-compose up -d - -# Browser öffnen -open http://localhost:5678 -``` - -## Option C: Manuell - -```bash -tar -xzf n8n-nodes-librebooking.tar.gz -cd n8n-nodes-librebooking +cd /pfad/zu/n8n-nodes-librebooking npm install npm run build -npm link -n8n start +./quick-install.sh n8n ``` -## Credentials einrichten +**Oder mit npm scripts:** -1. Öffne http://localhost:5678 -2. Erstelle neuen Workflow -3. Füge "LibreBooking" Node hinzu -4. Erstelle neue Credentials: - - **URL:** `https://dein-server/Web/Services` - - **Username:** Admin-Benutzer - - **Password:** Passwort +```bash +npm install +npm run docker:deploy +``` -## Fertig! +## 🔑 Credentials -Der LibreBooking Node ist jetzt verfügbar. +n8n → Einstellungen → Credentials → Add → "LibreBooking API" ---- +| Feld | Wert | +|------|------| +| URL | `https://deine-librebooking-url.de` | +| Benutzername | Admin-Benutzer | +| Passwort | Passwort | -Für detaillierte Anleitungen siehe [INSTALLATION.md](INSTALLATION.md) +## 🔄 Updates + +```bash +git pull +./update-node.sh n8n +``` + +## ❓ Probleme? + +- **tsc not found**: Auf dem Host bauen (npm install && npm run build) +- **Read-only Volume**: `docker cp` verwenden +- Mehr: [TROUBLESHOOTING.md](TROUBLESHOOTING.md) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..8b1cbbd --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,234 @@ +# Sicherheitshinweise - LibreBooking n8n Node + +Dieses Dokument erklärt die npm audit Vulnerabilities und wie man damit umgeht. + +## Inhaltsverzeichnis + +- [Übersicht der Vulnerabilities](#übersicht-der-vulnerabilities) +- [Warum diese Vulnerabilities existieren](#warum-diese-vulnerabilities-existieren) +- [Risikoeinschätzung](#risikoeinschätzung) +- [Empfehlungen](#empfehlungen) +- [Wie man sie beheben kann](#wie-man-sie-beheben-kann) +- [Produktionsumgebungen](#produktionsumgebungen) + +--- + +## Übersicht der Vulnerabilities + +Beim Ausführen von `npm audit` werden möglicherweise folgende Vulnerabilities angezeigt: + +### Critical: form-data + +``` +form-data <4.0.1 +Severity: critical +Prototype Pollution in form-data +https://github.com/advisories/GHSA-xxx +``` + +### Moderate: lodash + +``` +lodash <4.17.21 +Severity: moderate +Prototype Pollution in lodash +https://github.com/advisories/GHSA-xxx +``` + +--- + +## Warum diese Vulnerabilities existieren + +Diese Vulnerabilities kommen **nicht direkt aus diesem Projekt**, sondern sind **transitive Dependencies** von `n8n-workflow` und `n8n-core`. + +### Dependency-Kette: + +``` +n8n-nodes-librebooking + └── n8n-workflow (devDependency für Typen) + └── axios + └── form-data (vulnerable version) + └── lodash (vulnerable version) +``` + +### Wichtig zu verstehen: + +1. **n8n-workflow** ist nur als `devDependency` und `peerDependency` deklariert +2. In Produktion verwendet n8n seine **eigene** n8n-workflow Version +3. Die vulnerable Dependencies werden nur beim **Entwickeln** installiert +4. Diese Package werden **nicht** in das finale dist/ Verzeichnis gebündelt + +--- + +## Risikoeinschätzung + +### Für dieses Projekt: **NIEDRIGES RISIKO** + +| Aspekt | Risiko | Begründung | +|--------|--------|------------| +| Entwicklung | Niedrig | form-data/lodash werden nicht direkt verwendet | +| Produktion | Sehr niedrig | Keine transtiven Dependencies werden deployed | +| n8n Runtime | Abhängig von n8n | n8n selbst muss die Vulnerabilities beheben | + +### Warum niedriges Risiko: + +1. **form-data Vulnerability:** + - Betrifft nur das Parsen von multipart/form-data + - Dieser Node verwendet keine File-Uploads über form-data + - Die LibreBooking API verwendet JSON für alle Requests + +2. **lodash Vulnerability:** + - Betrifft `_.set()` und `_.setWith()` Funktionen + - Dieser Node verwendet keine direkten lodash Aufrufe + - Die Vulnerability erfordert Angreifer-kontrollierten Input + +--- + +## Empfehlungen + +### Für Entwickler: + +1. **Warnungen ignorieren** (wenn nicht kritisch): + ```bash + npm install --ignore-scripts + ``` + +2. **Audit bei npm install deaktivieren:** + ```bash + # Einmalig: + npm install --no-audit + + # Permanent via .npmrc: + echo "audit=false" >> .npmrc + ``` + +3. **Overrides verwenden** (in package.json): + ```json + "overrides": { + "form-data": "^4.0.1", + "lodash": "^4.17.21" + } + ``` + +### Für Produktionsumgebungen: + +1. **n8n aktuell halten:** Die n8n-Entwickler aktualisieren regelmäßig ihre Dependencies +2. **Nur vertrauenswürdige Inputs:** Keine ungeprüften Daten an die Nodes übergeben +3. **Network Isolation:** n8n Container im isolierten Netzwerk betreiben + +--- + +## Wie man sie beheben kann + +### Option 1: Overrides in package.json (empfohlen) + +Die package.json enthält bereits Overrides für bekannte Vulnerabilities: + +```json +"overrides": { + "form-data": "^4.0.1", + "lodash": "^4.17.21" +} +``` + +### Option 2: npm audit fix (begrenzt) + +```bash +# Automatische Fixes (nur kompatible Updates) +npm audit fix + +# Force Fixes (VORSICHT: kann Breaking Changes einführen) +npm audit fix --force +``` + +**Hinweis:** `npm audit fix` kann transitive Dependencies nur begrenzt beheben. + +### Option 3: Update-Skript verwenden + +```bash +./update-dependencies.sh +``` + +Das Skript: +- Führt `npm audit fix` aus +- Aktualisiert alle Dependencies +- Testet ob alles funktioniert +- Gibt einen Report + +### Option 4: Resolutions (für yarn/pnpm) + +Wenn Sie yarn statt npm verwenden: + +```json +"resolutions": { + "form-data": "^4.0.1", + "lodash": "^4.17.21" +} +``` + +--- + +## Produktionsumgebungen + +### Best Practices: + +1. **Docker Image aktuell halten:** + ```bash + docker pull n8nio/n8n:latest + ``` + +2. **Regelmäßige Updates:** + ```bash + docker compose pull + docker compose up -d + ``` + +3. **Security Scanning:** + ```bash + # Image auf Vulnerabilities prüfen + docker scan n8nio/n8n:latest + # Oder mit Trivy: + trivy image n8nio/n8n:latest + ``` + +4. **Netzwerk-Isolation:** + - n8n nicht direkt im Internet exponieren + - Reverse Proxy mit TLS verwenden + - Firewall-Regeln setzen + +5. **Zugriffskontrollen:** + - Starke Passwörter verwenden + - Basic Auth oder OAuth aktivieren + - API-Keys für LibreBooking sicher speichern + +### Sicherheits-Checkliste: + +- [ ] n8n Version aktuell? +- [ ] Docker Image aktuell? +- [ ] TLS/HTTPS aktiviert? +- [ ] Starke Passwörter? +- [ ] Netzwerk isoliert? +- [ ] Regelmäßige Backups? + +--- + +## Weiterführende Links + +- [n8n Security Best Practices](https://docs.n8n.io/hosting/security/) +- [npm audit Documentation](https://docs.npmjs.com/cli/v8/commands/npm-audit) +- [OWASP Dependency Check](https://owasp.org/www-project-dependency-check/) +- [Snyk Vulnerability Database](https://snyk.io/vuln/) + +--- + +## Meldung von Sicherheitsproblemen + +Wenn Sie eine Sicherheitslücke **direkt in diesem Projekt** (nicht in Dependencies) finden: + +1. **Nicht öffentlich melden** (kein GitHub Issue) +2. Kontaktieren Sie uns direkt per E-Mail +3. Geben Sie Zeit für einen Fix bevor öffentliche Disclosure + +--- + +*Letzte Aktualisierung: Januar 2026* diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..c8ad009 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,141 @@ +# Troubleshooting + +## Inhaltsverzeichnis + +1. [tsc not found](#tsc-not-found) +2. [Read-only Volume](#read-only-volume) +3. [npm audit Vulnerabilities](#npm-audit-vulnerabilities) +4. [Node nicht sichtbar](#node-nicht-sichtbar) +5. [Authentifizierung fehlgeschlagen](#authentifizierung-fehlgeschlagen) + +--- + +## tsc not found + +**Symptom:** +``` +sh: 1: tsc: not found +npm error code 127 +``` + +**Ursache:** TypeScript ist nicht installiert (im Container oft nicht verfügbar). + +**Lösung:** Auf dem Host bauen! + +```bash +# Auf dem Host (nicht im Container) +npm install +npm run build + +# Dann in Container kopieren +docker cp dist n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp package.json n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp node_modules n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker restart n8n +``` + +**Oder mit Skript:** +```bash +./quick-install.sh n8n +``` + +--- + +## Read-only Volume + +**Symptom:** +``` +EROFS: read-only file system +npm error EACCES: permission denied +``` + +**Ursache:** Volume ist mit `:ro` gemountet. + +**Lösung 1:** Volume ohne `:ro` mounten + +```yaml +# docker-compose.yml +volumes: + - ./custom-nodes:/home/node/.n8n/custom # Ohne :ro +``` + +**Lösung 2:** Auf dem Host bauen und kopieren + +```bash +npm install && npm run build +docker cp dist n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ +``` + +--- + +## npm audit Vulnerabilities + +**Symptom:** +``` +found 2 vulnerabilities (1 moderate, 1 critical) +``` + +**Erklärung:** Diese kommen von `n8n-workflow` und sind für dieses Projekt nicht kritisch. + +**Lösung:** Ignorieren oder `.npmrc` verwenden: + +```bash +# .npmrc bereits konfiguriert +audit=false +``` + +Siehe [SECURITY.md](SECURITY.md) für Details. + +--- + +## Node nicht sichtbar + +**Lösung:** + +1. Container neustarten: + ```bash + docker restart n8n + ``` + +2. Dateien prüfen: + ```bash + docker exec n8n ls -la /home/node/.n8n/custom/n8n-nodes-librebooking/ + ``` + +3. Environment prüfen: + ```yaml + environment: + - N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom + ``` + +4. Logs prüfen: + ```bash + docker logs n8n | grep -i librebooking + ``` + +--- + +## Authentifizierung fehlgeschlagen + +**Symptom:** "401 Unauthorized" oder "Invalid credentials" + +**Lösung:** + +1. URL prüfen (ohne `/Web/` am Ende) +2. Benutzer muss Admin sein +3. API muss in LibreBooking aktiviert sein + +Test: +```bash +curl -X POST https://librebooking.example.com/Web/Services/Authentication/Authenticate \ + -H 'Content-Type: application/json' \ + -d '{"username": "admin", "password": "pass"}' +``` + +--- + +## Weitere Hilfe + +- [README.md](README.md) - Übersicht +- [INSTALLATION.md](INSTALLATION.md) - Installationsanleitung +- [DOCKER-INTEGRATION.md](DOCKER-INTEGRATION.md) - Docker Details diff --git a/build-on-host.sh b/build-on-host.sh new file mode 100755 index 0000000..7fa0e3c --- /dev/null +++ b/build-on-host.sh @@ -0,0 +1,183 @@ +#!/bin/bash +# ============================================================================ +# build-on-host.sh - Baut den Node auf dem Host (außerhalb des Containers) +# +# Verwendung: Wenn das Volume als read-only gemountet werden soll +# +# Dieses Skript: +# 1. Installiert Dependencies auf dem Host +# 2. Baut den Node auf dem Host +# 3. Kopiert nur die fertigen Dateien in den Container +# 4. Das Volume kann dann read-only gemountet werden +# ============================================================================ + +set -e + +# Farben +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo -e "${BLUE}============================================${NC}" +echo -e "${BLUE} LibreBooking Node - Build auf Host${NC}" +echo -e "${BLUE}============================================${NC}" +echo "" + +# Hilfe anzeigen +show_help() { + echo "Verwendung: $0 [OPTIONEN]" + echo "" + echo "Dieses Skript baut den Node auf dem Host, sodass er als" + echo "read-only Volume gemountet werden kann." + echo "" + echo "Optionen:" + echo " -o, --output DIR Ausgabeverzeichnis (Standard: ./dist-for-docker)" + echo " -c, --copy-to PATH Kopiert direkt zu einem Pfad (z.B. custom-nodes/)" + echo " -h, --help Diese Hilfe anzeigen" + echo "" + echo "Beispiele:" + echo " $0 # Baut in ./dist-for-docker" + echo " $0 -c ../n8n/custom-nodes/n8n-nodes-librebooking" + echo "" + echo "Danach in docker-compose.yml:" + echo " volumes:" + echo " - ./dist-for-docker:/home/node/.n8n/custom/n8n-nodes-librebooking:ro" + exit 0 +} + +OUTPUT_DIR="$SCRIPT_DIR/dist-for-docker" +COPY_TO="" + +# Parameter parsen +while [[ $# -gt 0 ]]; do + case $1 in + -o|--output) + OUTPUT_DIR="$2" + shift 2 + ;; + -c|--copy-to) + COPY_TO="$2" + shift 2 + ;; + -h|--help) + show_help + ;; + *) + echo -e "${RED}Unbekannte Option: $1${NC}" + show_help + ;; + esac +done + +log() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" + exit 1 +} + +# Schritt 1: Prüfe Voraussetzungen +log "Prüfe Voraussetzungen..." + +if ! command -v node &>/dev/null; then + error "Node.js ist nicht installiert!\n\n Installieren Sie Node.js 18+: https://nodejs.org/" +fi + +NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1) +if [ "$NODE_VERSION" -lt 18 ]; then + error "Node.js Version $NODE_VERSION ist zu alt. Mindestens v18 erforderlich." +fi + +if ! command -v npm &>/dev/null; then + error "npm ist nicht installiert!" +fi + +log "Node.js: $(node -v)" +log "npm: $(npm -v)" + +# Schritt 2: Dependencies installieren +log "Installiere Dependencies..." +cd "$SCRIPT_DIR" + +if npm install 2>&1 | tail -5; then + log "Dependencies installiert ✓" +else + error "npm install fehlgeschlagen!" +fi + +# Schritt 3: Bauen +log "Baue den Node..." + +if npm run build 2>&1 | tail -10; then + log "Build erfolgreich ✓" +else + error "npm run build fehlgeschlagen!" +fi + +# Schritt 4: Prüfe Build +if [ ! -d "$SCRIPT_DIR/dist" ]; then + error "dist/ Verzeichnis wurde nicht erstellt!" +fi + +NODE_COUNT=$(find "$SCRIPT_DIR/dist" -name "*.node.js" | wc -l) +if [ "$NODE_COUNT" -eq 0 ]; then + error "Keine .node.js Dateien im dist/ Verzeichnis!" +fi + +log "Gefunden: $NODE_COUNT Node-Datei(en)" + +# Schritt 5: Erstelle Ausgabeverzeichnis +log "Erstelle Ausgabeverzeichnis: $OUTPUT_DIR" + +mkdir -p "$OUTPUT_DIR" + +# Kopiere alle notwendigen Dateien +cp -r "$SCRIPT_DIR/dist" "$OUTPUT_DIR/" +cp "$SCRIPT_DIR/package.json" "$OUTPUT_DIR/" +cp -r "$SCRIPT_DIR/node_modules" "$OUTPUT_DIR/" 2>/dev/null || true + +# Optional: Nur essentielle Dateien für minimales Image +log "Räume auf..." +rm -rf "$OUTPUT_DIR/node_modules/.cache" 2>/dev/null || true + +# Schritt 6: Optional kopieren +if [ -n "$COPY_TO" ]; then + log "Kopiere zu: $COPY_TO" + mkdir -p "$COPY_TO" + cp -r "$OUTPUT_DIR/"* "$COPY_TO/" + log "Kopiert ✓" +fi + +echo "" +echo -e "${GREEN}============================================${NC}" +echo -e "${GREEN} Build abgeschlossen!${NC}" +echo -e "${GREEN}============================================${NC}" +echo "" +log "Ausgabe in: $OUTPUT_DIR" +echo "" +echo "Nächste Schritte:" +echo "" +echo "1. Kopieren Sie das Verzeichnis zu Ihrer n8n Installation:" +echo " cp -r $OUTPUT_DIR /pfad/zu/n8n/custom-nodes/n8n-nodes-librebooking" +echo "" +echo "2. Oder verwenden Sie es direkt in docker-compose.yml:" +echo "" +echo " services:" +echo " n8n:" +echo " volumes:" +echo " # Read-only möglich, da bereits gebaut!" +echo " - $OUTPUT_DIR:/home/node/.n8n/custom/n8n-nodes-librebooking:ro" +echo "" +echo "3. Starten Sie n8n neu:" +echo " docker compose restart n8n" +echo "" diff --git a/check-installation.sh b/check-installation.sh new file mode 100755 index 0000000..97f751c --- /dev/null +++ b/check-installation.sh @@ -0,0 +1,281 @@ +#!/bin/sh +# ============================================================================ +# check-installation.sh - Debug-Skript für LibreBooking Node Installation +# Kann sowohl im Container als auch auf dem Host ausgeführt werden +# ============================================================================ + +echo "============================================" +echo " LibreBooking Node - Installation Check" +echo "============================================" +echo "" +echo "Ausgeführt: $(date)" +echo "Hostname: $(hostname)" +echo "" + +# Status-Zähler +OK_COUNT=0 +WARN_COUNT=0 +ERROR_COUNT=0 + +ok() { + echo "[✓] $1" + OK_COUNT=$((OK_COUNT + 1)) +} + +warn() { + echo "[!] $1" + WARN_COUNT=$((WARN_COUNT + 1)) +} + +error() { + echo "[✗] $1" + ERROR_COUNT=$((ERROR_COUNT + 1)) +} + +info() { + echo "[i] $1" +} + +echo "============================================" +echo " 1. UMGEBUNG" +echo "============================================" + +# Node.js/npm prüfen +if command -v node >/dev/null 2>&1; then + ok "Node.js installiert: $(node --version)" +else + error "Node.js nicht gefunden" +fi + +if command -v npm >/dev/null 2>&1; then + ok "npm installiert: $(npm --version)" +else + error "npm nicht gefunden" +fi + +# Aktueller Benutzer +info "Aktueller Benutzer: $(whoami) (UID: $(id -u))" + +echo "" +echo "============================================" +echo " 2. UMGEBUNGSVARIABLEN" +echo "============================================" + +if [ -n "$N8N_CUSTOM_EXTENSIONS" ]; then + ok "N8N_CUSTOM_EXTENSIONS: $N8N_CUSTOM_EXTENSIONS" +else + warn "N8N_CUSTOM_EXTENSIONS nicht gesetzt" + info " Empfehlung: N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom" +fi + +if [ "$N8N_COMMUNITY_NODES_ENABLED" = "true" ]; then + ok "N8N_COMMUNITY_NODES_ENABLED: true" +else + warn "N8N_COMMUNITY_NODES_ENABLED ist nicht 'true'" + info " Aktueller Wert: ${N8N_COMMUNITY_NODES_ENABLED:-}" +fi + +# Weitere n8n Variablen +if [ -n "$N8N_LOG_LEVEL" ]; then + info "N8N_LOG_LEVEL: $N8N_LOG_LEVEL" +fi + +echo "" +echo "============================================" +echo " 3. VERZEICHNISSE PRÜFEN" +echo "============================================" + +# Mögliche Pfade +POSSIBLE_PATHS=" +/home/node/.n8n/custom/n8n-nodes-librebooking +/home/node/.n8n/custom +/opt/n8n/custom-nodes +/data/custom-nodes +" + +FOUND_PATH="" + +for path in $POSSIBLE_PATHS; do + if [ -d "$path" ]; then + if [ -f "$path/package.json" ]; then + ok "Custom Node Verzeichnis: $path" + FOUND_PATH="$path" + break + elif [ -f "$path/n8n-nodes-librebooking/package.json" ]; then + ok "Custom Node Verzeichnis: $path/n8n-nodes-librebooking" + FOUND_PATH="$path/n8n-nodes-librebooking" + break + else + info "Verzeichnis existiert (ohne package.json): $path" + fi + fi +done + +if [ -z "$FOUND_PATH" ]; then + error "Kein Custom Node Verzeichnis mit package.json gefunden!" + info " Geprüfte Pfade:" + for path in $POSSIBLE_PATHS; do + info " - $path" + done +fi + +echo "" +echo "============================================" +echo " 4. NODE-DATEIEN PRÜFEN" +echo "============================================" + +if [ -n "$FOUND_PATH" ]; then + cd "$FOUND_PATH" 2>/dev/null || true + + # package.json + if [ -f "package.json" ]; then + ok "package.json vorhanden" + info " Name: $(grep '"name":' package.json | head -1 | cut -d'"' -f4)" + info " Version: $(grep '"version":' package.json | head -1 | cut -d'"' -f4)" + else + error "package.json fehlt" + fi + + # node_modules + if [ -d "node_modules" ]; then + MODULE_COUNT=$(ls -1 node_modules 2>/dev/null | wc -l) + ok "node_modules vorhanden ($MODULE_COUNT Pakete)" + else + error "node_modules fehlt - führen Sie 'npm install' aus" + fi + + # dist Verzeichnis + if [ -d "dist" ]; then + ok "dist/ Verzeichnis vorhanden" + + # .node.js Dateien suchen + echo "" + info "Gefundene Node-Dateien:" + NODE_FILES=$(find dist -name "*.node.js" 2>/dev/null) + if [ -n "$NODE_FILES" ]; then + echo "$NODE_FILES" | while read -r f; do + if [ -f "$f" ]; then + ok " $f ($(ls -lh "$f" | awk '{print $5}'))" + fi + done + else + error " Keine .node.js Dateien gefunden!" + fi + + # Credentials + echo "" + info "Gefundene Credential-Dateien:" + CRED_FILES=$(find dist -name "*.credentials.js" 2>/dev/null) + if [ -n "$CRED_FILES" ]; then + echo "$CRED_FILES" | while read -r f; do + if [ -f "$f" ]; then + ok " $f" + fi + done + else + error " Keine .credentials.js Dateien gefunden!" + fi + else + error "dist/ Verzeichnis fehlt - führen Sie 'npm run build' aus" + fi + + # Icons + echo "" + info "Icon-Dateien:" + find . -name "*.svg" 2>/dev/null | while read -r f; do + info " $f" + done +fi + +echo "" +echo "============================================" +echo " 5. BERECHTIGUNGEN" +echo "============================================" + +# Funktion: Prüft ob ein Verzeichnis read-only ist +check_readonly() { + local dir="$1" + local test_file="$dir/.write_test_$$" + + # Versuche eine Test-Datei zu erstellen + if touch "$test_file" 2>/dev/null; then + rm -f "$test_file" 2>/dev/null + return 0 # Schreibbar + else + return 1 # Read-only + fi +} + +if [ -n "$FOUND_PATH" ]; then + # Prüfe Schreibrechte (inkl. read-only Volume Check) + if check_readonly "$FOUND_PATH"; then + ok "Schreibrechte auf $FOUND_PATH" + else + error "KEINE Schreibrechte auf $FOUND_PATH (Read-only Volume?)" + echo "" + info " PROBLEM: Das Verzeichnis ist möglicherweise als :ro gemountet." + info " LÖSUNG 1: Volume ohne :ro mounten:" + info " - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking" + info " LÖSUNG 2: Auf dem Host bauen mit ./build-on-host.sh" + info " Siehe: TROUBLESHOOTING.md" + echo "" + fi + + # Besitzer prüfen + OWNER=$(ls -ld "$FOUND_PATH" | awk '{print $3}') + GROUP=$(ls -ld "$FOUND_PATH" | awk '{print $4}') + info "Besitzer: $OWNER:$GROUP" + + # n8n läuft als node (UID 1000) + if [ "$(id -u)" = "1000" ]; then + ok "Läuft als UID 1000 (Standard für n8n)" + else + warn "Läuft nicht als UID 1000 (aktuell: $(id -u))" + fi +fi + +echo "" +echo "============================================" +echo " ZUSAMMENFASSUNG" +echo "============================================" +echo "" +echo "Ergebnisse:" +echo " ✓ OK: $OK_COUNT" +echo " ! Warnung: $WARN_COUNT" +echo " ✗ Fehler: $ERROR_COUNT" +echo "" + +if [ "$ERROR_COUNT" -gt 0 ]; then + echo "============================================" + echo " EMPFOHLENE AKTIONEN" + echo "============================================" + echo "" + + if [ ! -d "node_modules" ] 2>/dev/null; then + echo "1. Dependencies installieren:" + echo " cd $FOUND_PATH && npm install" + echo "" + fi + + if [ ! -d "dist" ] 2>/dev/null; then + echo "2. Node bauen:" + echo " cd $FOUND_PATH && npm run build" + echo "" + fi + + echo "3. Container neustarten:" + echo " docker restart n8n" + echo "" + + exit 1 +elif [ "$WARN_COUNT" -gt 0 ]; then + echo "Status: TEILWEISE OK (Warnungen beachten)" + exit 0 +else + echo "Status: ALLES OK ✓" + echo "" + echo "Falls der Node trotzdem nicht erscheint:" + echo " 1. Starten Sie n8n neu: docker restart n8n" + echo " 2. Prüfen Sie die Logs: docker logs n8n | grep -i libre" + exit 0 +fi diff --git a/create-release.sh b/create-release.sh new file mode 100755 index 0000000..af08448 --- /dev/null +++ b/create-release.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# ============================================================================ +# create-release.sh - Erstellt neue Archive und Git Tag +# ============================================================================ + +set -e + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PARENT_DIR="$(dirname "$SCRIPT_DIR")" + +# Version aus package.json lesen +VERSION=$(grep '"version"' "$SCRIPT_DIR/package.json" | head -1 | sed 's/.*"version": "\([^"]*\)".*/\1/') + +echo -e "${GREEN}=== Release erstellen v$VERSION ===${NC}\n" + +cd "$SCRIPT_DIR" + +# 1. Prüfe, ob alles committet ist +if [ -n "$(git status --porcelain)" ]; then + echo -e "${YELLOW}Warnung: Es gibt uncommittete Änderungen!${NC}" + git status --short + echo "" + read -p "Trotzdem fortfahren? (j/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Jj]$ ]]; then + exit 1 + fi +fi + +# 2. Alte Archive löschen +echo "[1/4] Lösche alte Archive..." +./git-cleanup.sh 2>/dev/null || true + +# 3. Build prüfen +echo "" +echo "[2/4] Prüfe Build..." +if [ ! -d "dist" ] || [ -z "$(find dist -name '*.node.js' 2>/dev/null)" ]; then + echo "Baue Node..." + npm install --silent + npm run build --silent +fi +echo " ✓ Build OK" + +# 4. Archive erstellen +echo "" +echo "[3/4] Erstelle Archive..." + +TAR_FILE="$PARENT_DIR/n8n-nodes-librebooking-v${VERSION}.tar.gz" +ZIP_FILE="$PARENT_DIR/n8n-nodes-librebooking-v${VERSION}.zip" + +git archive --format=tar.gz --prefix=n8n-nodes-librebooking/ --output="$TAR_FILE" HEAD +echo " ✓ $TAR_FILE" + +git archive --format=zip --prefix=n8n-nodes-librebooking/ --output="$ZIP_FILE" HEAD +echo " ✓ $ZIP_FILE" + +# 5. Git Tag (optional) +echo "" +echo "[4/4] Git Tag..." + +if git tag | grep -q "v$VERSION"; then + echo -e " ${YELLOW}Tag v$VERSION existiert bereits${NC}" +else + read -p "Git Tag v$VERSION erstellen? (j/N) " -n 1 -r + echo + if [[ $REPLY =~ ^[Jj]$ ]]; then + git tag -a "v$VERSION" -m "Version $VERSION - Vereinfachte Installation" + echo " ✓ Tag v$VERSION erstellt" + fi +fi + +echo "" +echo -e "${GREEN}============================================${NC}" +echo -e "${GREEN} Release v$VERSION erstellt!${NC}" +echo -e "${GREEN}============================================${NC}" +echo "" +echo "Archive:" +echo " $TAR_FILE" +echo " $ZIP_FILE" +echo "" +echo "Zum Pushen:" +echo " git push origin main" +echo " git push origin v$VERSION" +echo "" diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 1c915ec..4a4a01e 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,31 +1,21 @@ -# Docker Compose Override für bestehende n8n Installationen -# Diese Datei erweitert eine bestehende docker-compose.yml um den LibreBooking Node +# Docker Compose Override für LibreBooking n8n Node +# +# WICHTIG: custom-nodes Volume NICHT als read-only (:ro) mounten! +# Der Node benötigt Schreibrechte für npm install/build. # -# Verwendung: -# 1. Diese Datei in das Verzeichnis mit der bestehenden docker-compose.yml kopieren -# 2. custom-nodes Verzeichnis in das gleiche Verzeichnis kopieren -# 3. docker-compose up -d ausführen -# -# HINWEIS: Diese Datei wird automatisch mit der bestehenden docker-compose.yml zusammengeführt +# Für read-only Volumes siehe: docker-compose.readonly.yml +# und führen Sie vorher build-on-host.sh aus. version: '3.8' services: n8n: - # Zusätzliche Volumes für Custom Nodes volumes: - # LibreBooking Custom Node - Variante 1: Vorgebauter Node - - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking:ro - - # Alternative: Nur das dist-Verzeichnis (wenn bereits gebaut) - # - ./custom-nodes/dist:/home/node/.n8n/custom/n8n-nodes-librebooking/dist:ro - # - ./custom-nodes/package.json:/home/node/.n8n/custom/n8n-nodes-librebooking/package.json:ro + # LibreBooking Custom Node + # OHNE :ro - Der Node muss beim Start gebaut werden können! + - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking - # Zusätzliche Umgebungsvariablen environment: - # Pfad für Custom Nodes (normalerweise bereits gesetzt) + # Custom Nodes aktivieren - N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom - # Aktiviert erweiterte Node-Typen - N8N_COMMUNITY_NODES_ENABLED=true - # Optional: Debug-Logging für Node-Entwicklung - # - N8N_LOG_LEVEL=debug diff --git a/docker-compose.readonly.yml b/docker-compose.readonly.yml new file mode 100644 index 0000000..dab572c --- /dev/null +++ b/docker-compose.readonly.yml @@ -0,0 +1,25 @@ +# Docker Compose Override für READ-ONLY Volume Mount +# +# VORAUSSETZUNG: Node wurde auf dem Host gebaut! +# Führen Sie zuerst aus: ./build-on-host.sh +# +# Verwendung: +# docker compose -f docker-compose.yml -f docker-compose.readonly.yml up -d +# +# Oder setzen Sie in Ihrer Umgebung: +# export COMPOSE_FILE=docker-compose.yml:docker-compose.readonly.yml + +version: '3.8' + +services: + n8n: + volumes: + # LibreBooking Custom Node - READ-ONLY + # Das dist-for-docker Verzeichnis wurde mit build-on-host.sh erstellt + # und enthält bereits alle kompilierten Dateien. + - ./dist-for-docker:/home/node/.n8n/custom/n8n-nodes-librebooking:ro + + environment: + # Custom Nodes aktivieren + - N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom + - N8N_COMMUNITY_NODES_ENABLED=true diff --git a/fix-node-installation.sh b/fix-node-installation.sh new file mode 100755 index 0000000..af8e266 --- /dev/null +++ b/fix-node-installation.sh @@ -0,0 +1,268 @@ +#!/bin/bash +# ============================================================================ +# fix-node-installation.sh - All-in-One Lösung für Node-Installation im Container +# Führt alle Schritte automatisch aus (auf dem HOST ausführen!) +# ============================================================================ + +set -e + +# Farben +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Standard-Werte +CONTAINER_NAME="n8n" +CONTAINER_PATH="/home/node/.n8n/custom/n8n-nodes-librebooking" +SKIP_RESTART=false +VERBOSE=false + +echo -e "${BLUE}============================================${NC}" +echo -e "${BLUE} LibreBooking Node - Auto-Fix Installation${NC}" +echo -e "${BLUE}============================================${NC}" +echo "" + +# Hilfe anzeigen +show_help() { + echo "Verwendung: $0 [OPTIONEN]" + echo "" + echo "Dieses Skript installiert den LibreBooking Node automatisch im Docker Container." + echo "" + echo "Optionen:" + echo " -c, --container NAME Container-Name (Standard: n8n)" + echo " -p, --path PATH Pfad im Container (Standard: /home/node/.n8n/custom/n8n-nodes-librebooking)" + echo " -n, --no-restart Container nicht neustarten" + echo " -v, --verbose Ausführliche Ausgabe" + echo " -h, --help Diese Hilfe anzeigen" + echo "" + echo "Beispiele:" + echo " $0 # Standard-Installation" + echo " $0 -c mein-n8n # Anderer Container-Name" + echo " $0 -p /opt/n8n/custom-nodes # Anderer Pfad" + echo " $0 -n # Ohne Neustart" + exit 0 +} + +# Parameter parsen +while [[ $# -gt 0 ]]; do + case $1 in + -c|--container) + CONTAINER_NAME="$2" + shift 2 + ;; + -p|--path) + CONTAINER_PATH="$2" + shift 2 + ;; + -n|--no-restart) + SKIP_RESTART=true + shift + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -h|--help) + show_help + ;; + *) + echo -e "${RED}Unbekannte Option: $1${NC}" + show_help + ;; + esac +done + +log() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" + exit 1 +} + +# Funktion: Prüft ob ein Verzeichnis im Container read-only ist +check_readonly_container() { + local container="$1" + local dir="$2" + local test_file="$dir/.write_test_$$" + + # Versuche eine Test-Datei im Container zu erstellen + if docker exec "$container" touch "$test_file" 2>/dev/null; then + docker exec "$container" rm -f "$test_file" 2>/dev/null + return 0 # Schreibbar + else + return 1 # Read-only + fi +} + +print_readonly_solution() { + echo "" + echo -e "${RED}============================================${NC}" + echo -e "${RED} PROBLEM: Read-only Volume erkannt!${NC}" + echo -e "${RED}============================================${NC}" + echo "" + echo "Das custom-nodes Verzeichnis ist als read-only gemountet." + echo "npm install und npm run build benötigen Schreibrechte." + echo "" + echo "LÖSUNGEN:" + echo "" + echo "1. Volume OHNE :ro mounten (empfohlen):" + echo " In docker-compose.yml oder docker-compose.override.yml:" + echo "" + echo " volumes:" + echo " - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking" + echo " # ENTFERNEN: :ro am Ende!" + echo "" + echo "2. Auf dem Host bauen (für read-only Volumes):" + echo " ./build-on-host.sh" + echo " # Dann docker-compose.readonly.yml verwenden" + echo "" + echo "3. docker-compose.override.yml anpassen:" + echo " cp docker-compose.override.yml docker-compose.override.yml.bak" + echo " # Entfernen Sie ':ro' aus der Volume-Definition" + echo "" + echo "Siehe: TROUBLESHOOTING.md und DOCKER-INTEGRATION.md" +} + +# Schritt 1: Docker prüfen +log "Prüfe Docker..." +if ! command -v docker &>/dev/null; then + error "Docker ist nicht installiert!" +fi + +# Schritt 2: Container prüfen +log "Prüfe Container '$CONTAINER_NAME'..." +if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + # Prüfe ob Container existiert aber nicht läuft + if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + warn "Container '$CONTAINER_NAME' existiert aber läuft nicht." + log "Starte Container..." + docker start "$CONTAINER_NAME" + sleep 3 + else + error "Container '$CONTAINER_NAME' nicht gefunden! Prüfen Sie den Namen mit: docker ps -a" + fi +fi +log "Container '$CONTAINER_NAME' läuft ✓" + +# Schritt 3: Prüfe ob Pfad im Container existiert +log "Prüfe Pfad im Container: $CONTAINER_PATH" +if ! docker exec "$CONTAINER_NAME" test -d "$CONTAINER_PATH"; then + # Versuche alternative Pfade + ALTERNATIVE_PATHS=( + "/home/node/.n8n/custom/n8n-nodes-librebooking" + "/home/node/.n8n/custom" + "/opt/n8n/custom-nodes" + "/data/custom-nodes" + ) + + FOUND_PATH="" + for alt_path in "${ALTERNATIVE_PATHS[@]}"; do + if docker exec "$CONTAINER_NAME" test -f "$alt_path/package.json" 2>/dev/null; then + FOUND_PATH="$alt_path" + break + fi + done + + if [ -n "$FOUND_PATH" ]; then + warn "Angegebener Pfad nicht gefunden, verwende: $FOUND_PATH" + CONTAINER_PATH="$FOUND_PATH" + else + error "Kein Custom-Node-Verzeichnis mit package.json gefunden!\n\n Bitte stellen Sie sicher, dass die Dateien korrekt kopiert wurden.\n Beispiel: docker cp custom-nodes/. $CONTAINER_NAME:/home/node/.n8n/custom/n8n-nodes-librebooking/" + fi +fi +log "Pfad gefunden: $CONTAINER_PATH ✓" + +# Schritt 3.5: Prüfe ob Verzeichnis schreibbar ist (read-only Volume?) +log "Prüfe Schreibrechte im Container..." +if ! check_readonly_container "$CONTAINER_NAME" "$CONTAINER_PATH"; then + print_readonly_solution + exit 1 +fi +log "Schreibrechte vorhanden ✓" + +# Schritt 4: Skript in Container kopieren +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/install-in-container.sh" ]; then + log "Kopiere install-in-container.sh in Container..." + docker cp "$SCRIPT_DIR/install-in-container.sh" "$CONTAINER_NAME:/tmp/" +else + warn "install-in-container.sh nicht gefunden, erstelle inline..." +fi + +# Schritt 5: npm install und build im Container +log "Führe npm install aus..." +if $VERBOSE; then + docker exec -w "$CONTAINER_PATH" "$CONTAINER_NAME" npm install +else + if ! docker exec -w "$CONTAINER_PATH" "$CONTAINER_NAME" npm install 2>&1 | tail -5; then + error "npm install fehlgeschlagen!" + fi +fi +log "Dependencies installiert ✓" + +log "Führe npm run build aus..." +if $VERBOSE; then + docker exec -w "$CONTAINER_PATH" "$CONTAINER_NAME" npm run build +else + if ! docker exec -w "$CONTAINER_PATH" "$CONTAINER_NAME" npm run build 2>&1 | tail -10; then + error "npm run build fehlgeschlagen!" + fi +fi +log "Build erfolgreich ✓" + +# Schritt 6: Prüfe Ergebnis +log "Prüfe Build-Ergebnis..." +NODE_COUNT=$(docker exec "$CONTAINER_NAME" find "$CONTAINER_PATH/dist" -name "*.node.js" 2>/dev/null | wc -l) +if [ "$NODE_COUNT" -gt 0 ]; then + log "$NODE_COUNT Node-Datei(en) gefunden ✓" +else + error "Keine Node-Dateien nach Build gefunden!" +fi + +# Schritt 7: Container neustarten +if $SKIP_RESTART; then + warn "Container-Neustart übersprungen (-n Option)" + echo "" + echo -e "${YELLOW}Bitte starten Sie den Container manuell neu:${NC}" + echo " docker restart $CONTAINER_NAME" +else + log "Starte Container neu..." + docker restart "$CONTAINER_NAME" + log "Container neugestartet ✓" + + # Warte kurz auf Start + sleep 5 +fi + +# Schritt 8: Abschluss-Check (optional) +echo "" +echo -e "${GREEN}============================================${NC}" +echo -e "${GREEN} Installation abgeschlossen!${NC}" +echo -e "${GREEN}============================================${NC}" +echo "" +log "Der LibreBooking Node sollte jetzt in n8n verfügbar sein." +echo "" +echo "Nächste Schritte:" +echo " 1. Öffnen Sie n8n im Browser" +echo " 2. Erstellen Sie einen neuen Workflow" +echo " 3. Suchen Sie nach 'LibreBooking'" +echo "" +echo "Falls der Node nicht erscheint:" +echo " - Prüfen Sie die Logs: docker logs $CONTAINER_NAME 2>&1 | grep -i libre" +echo " - Führen Sie das Check-Skript aus: docker exec $CONTAINER_NAME sh /tmp/check-installation.sh" +echo "" + +# Optional: Check-Skript kopieren und ausführen +if [ -f "$SCRIPT_DIR/check-installation.sh" ]; then + docker cp "$SCRIPT_DIR/check-installation.sh" "$CONTAINER_NAME:/tmp/" + echo "Tipp: Führen Sie für einen detaillierten Status aus:" + echo " docker exec $CONTAINER_NAME sh /tmp/check-installation.sh" +fi diff --git a/git-cleanup.sh b/git-cleanup.sh new file mode 100755 index 0000000..f7184b2 --- /dev/null +++ b/git-cleanup.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# ============================================================================ +# git-cleanup.sh - Löscht alte Archive und temporäre Dateien +# ============================================================================ + +set -e + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PARENT_DIR="$(dirname "$SCRIPT_DIR")" + +echo -e "${GREEN}=== Git Cleanup ===${NC}\n" + +# Alte Archive im übergeordneten Verzeichnis löschen +echo "Lösche alte Archive in $PARENT_DIR..." +find "$PARENT_DIR" -maxdepth 1 -name "n8n-nodes-librebooking*.tar.gz" -delete 2>/dev/null && echo " ✓ .tar.gz gelöscht" || echo " - Keine .tar.gz gefunden" +find "$PARENT_DIR" -maxdepth 1 -name "n8n-nodes-librebooking*.zip" -delete 2>/dev/null && echo " ✓ .zip gelöscht" || echo " - Keine .zip gefunden" + +# Temporäre Dateien im Projekt löschen +echo "" +echo "Lösche temporäre Dateien..." +rm -rf "$SCRIPT_DIR/dist-for-docker" 2>/dev/null && echo " ✓ dist-for-docker/ gelöscht" || true +rm -rf "$SCRIPT_DIR/.tsbuildinfo" 2>/dev/null && echo " ✓ .tsbuildinfo gelöscht" || true +find "$SCRIPT_DIR" -name "*.log" -delete 2>/dev/null && echo " ✓ .log Dateien gelöscht" || true +find "$SCRIPT_DIR" -name ".DS_Store" -delete 2>/dev/null || true + +echo "" +echo -e "${GREEN}✓ Cleanup abgeschlossen${NC}" +echo "" diff --git a/git-commit.sh b/git-commit.sh new file mode 100755 index 0000000..7f5abec --- /dev/null +++ b/git-commit.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# ============================================================================ +# git-commit.sh - Committet alle Änderungen +# ============================================================================ + +set -e + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +MESSAGE="${1:-fix: Vereinfachte Installation - auf dem Host bauen}" + +echo -e "${GREEN}=== Git Commit ===${NC}\n" + +# Status anzeigen +echo "Geänderte Dateien:" +git status --short +echo "" + +# Alles hinzufügen +git add . + +# Commit +echo "Commit mit Nachricht: $MESSAGE" +git commit -m "$MESSAGE" + +echo "" +echo -e "${GREEN}✓ Commit erfolgreich${NC}" +echo "" +echo "Zum Pushen:" +echo " git push origin main" +echo "" diff --git a/install-docker-manual.sh b/install-docker-manual.sh new file mode 100755 index 0000000..9b57de4 --- /dev/null +++ b/install-docker-manual.sh @@ -0,0 +1,238 @@ +#!/bin/bash + +# ============================================================================= +# LibreBooking n8n Node - Manuelle Docker Installation (ohne docker-compose) +# ============================================================================= +# Diese Alternative verwendet nur "docker" Befehle für maximale Kompatibilität. +# Nutzen Sie dieses Skript wenn docker-compose Probleme macht. +# +# Verwendung: ./install-docker-manual.sh [OPTIONS] +# +# Optionen: +# -p, --port PORT n8n Port (Standard: 5678) +# -n, --name NAME Container Name (Standard: n8n-librebooking) +# -f, --force Bestehenden Container ersetzen +# -h, --help Diese Hilfe anzeigen +# +# ============================================================================= + +set -e + +# Farben für Ausgabe +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Standardwerte +N8N_PORT=5678 +CONTAINER_NAME="n8n-librebooking" +FORCE=false +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Hilfsfunktionen +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[✓]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[!]${NC} $1" +} + +print_error() { + echo -e "${RED}[FEHLER]${NC} $1" +} + +show_help() { + echo "LibreBooking n8n Node - Manuelle Docker Installation" + echo "" + echo "Verwendung: $0 [OPTIONS]" + echo "" + echo "Optionen:" + echo " -p, --port PORT n8n Port (Standard: 5678)" + echo " -n, --name NAME Container Name (Standard: n8n-librebooking)" + echo " -f, --force Bestehenden Container ersetzen" + echo " -h, --help Diese Hilfe anzeigen" + echo "" + echo "Beispiele:" + echo " $0 # Standard-Installation" + echo " $0 -p 8080 # Anderer Port" + echo " $0 -n mein-n8n -p 9000 # Eigener Name und Port" + exit 0 +} + +# Argumente parsen +while [[ $# -gt 0 ]]; do + case $1 in + -p|--port) + N8N_PORT="$2" + shift 2 + ;; + -n|--name) + CONTAINER_NAME="$2" + shift 2 + ;; + -f|--force) + FORCE=true + shift + ;; + -h|--help) + show_help + ;; + *) + print_error "Unbekannte Option: $1" + show_help + ;; + esac +done + +echo "" +echo "=============================================" +echo " LibreBooking n8n Node - Manuelle Installation" +echo " (ohne docker-compose)" +echo "=============================================" +echo "" + +# ============================================================================= +# Voraussetzungen prüfen +# ============================================================================= + +print_info "Prüfe Voraussetzungen..." + +# Docker prüfen +if ! command -v docker &> /dev/null; then + print_error "Docker ist nicht installiert!" + echo " Bitte installieren Sie Docker: https://docs.docker.com/get-docker/" + exit 1 +fi +print_success "Docker gefunden: $(docker --version)" + +# Prüfen ob Container bereits existiert +if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + if [ "$FORCE" = true ]; then + print_warning "Bestehender Container '$CONTAINER_NAME' wird entfernt..." + docker stop "$CONTAINER_NAME" 2>/dev/null || true + docker rm "$CONTAINER_NAME" 2>/dev/null || true + else + print_error "Container '$CONTAINER_NAME' existiert bereits!" + echo " Nutzen Sie -f um ihn zu ersetzen, oder -n für einen anderen Namen." + exit 1 + fi +fi + +# ============================================================================= +# Volume und Verzeichnisse vorbereiten +# ============================================================================= + +print_info "Bereite Volumes vor..." + +# Docker Volume für n8n Daten erstellen +VOLUME_NAME="${CONTAINER_NAME}_data" +if ! docker volume ls --format '{{.Name}}' | grep -q "^${VOLUME_NAME}$"; then + docker volume create "$VOLUME_NAME" + print_success "Volume '$VOLUME_NAME' erstellt" +else + print_info "Volume '$VOLUME_NAME' existiert bereits" +fi + +# Custom Nodes Verzeichnis vorbereiten +CUSTOM_NODES_DIR="$SCRIPT_DIR/custom-nodes" +if [ ! -d "$CUSTOM_NODES_DIR" ]; then + print_error "custom-nodes Verzeichnis nicht gefunden: $CUSTOM_NODES_DIR" + exit 1 +fi + +# ============================================================================= +# Node bauen (wenn nicht bereits gebaut) +# ============================================================================= + +if [ ! -d "$CUSTOM_NODES_DIR/dist" ]; then + print_info "Baue LibreBooking Node..." + + if command -v npm &> /dev/null; then + cd "$CUSTOM_NODES_DIR" + npm install 2>/dev/null || print_warning "npm install hatte Warnungen" + npm run build 2>/dev/null || { + print_warning "Build fehlgeschlagen, versuche alternativen Ansatz..." + npx tsc 2>/dev/null || true + mkdir -p dist/nodes/LibreBooking dist/nodes/LibreBookingTrigger + cp nodes/LibreBooking/*.svg dist/nodes/LibreBooking/ 2>/dev/null || true + cp nodes/LibreBookingTrigger/*.svg dist/nodes/LibreBookingTrigger/ 2>/dev/null || true + } + cd "$SCRIPT_DIR" + print_success "Node erfolgreich gebaut" + else + print_warning "npm nicht gefunden - Node wird im Container gebaut" + fi +fi + +# ============================================================================= +# n8n Container starten +# ============================================================================= + +print_info "Starte n8n Container..." + +# Berechtigungen setzen +if [ "$(id -u)" = "0" ]; then + chown -R 1000:1000 "$CUSTOM_NODES_DIR" 2>/dev/null || true +else + sudo chown -R 1000:1000 "$CUSTOM_NODES_DIR" 2>/dev/null || print_warning "Konnte Berechtigungen nicht setzen" +fi + +# Container starten +docker run -d \ + --name "$CONTAINER_NAME" \ + --restart unless-stopped \ + -p "${N8N_PORT}:5678" \ + -v "${VOLUME_NAME}:/home/node/.n8n" \ + -v "${CUSTOM_NODES_DIR}:/home/node/.n8n/custom/n8n-nodes-librebooking:ro" \ + -e N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom \ + -e N8N_COMMUNITY_NODES_ENABLED=true \ + -e TZ=Europe/Berlin \ + -e GENERIC_TIMEZONE=Europe/Berlin \ + n8nio/n8n:latest + +if [ $? -eq 0 ]; then + print_success "Container gestartet!" +else + print_error "Container konnte nicht gestartet werden!" + exit 1 +fi + +# Warten auf Start +print_info "Warte auf n8n Start..." +sleep 5 + +# Status prüfen +if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + print_success "n8n Container läuft!" +else + print_error "Container läuft nicht - prüfen Sie: docker logs $CONTAINER_NAME" + exit 1 +fi + +# ============================================================================= +# Abschluss +# ============================================================================= + +echo "" +echo "=============================================" +print_success "Installation abgeschlossen!" +echo "=============================================" +echo "" +echo "n8n ist erreichbar unter: http://localhost:${N8N_PORT}" +echo "" +echo "Nützliche Befehle:" +echo " docker logs $CONTAINER_NAME # Logs anzeigen" +echo " docker stop $CONTAINER_NAME # Container stoppen" +echo " docker start $CONTAINER_NAME # Container starten" +echo " docker restart $CONTAINER_NAME # Container neustarten" +echo " docker rm -f $CONTAINER_NAME # Container löschen" +echo "" +echo "Bei Problemen siehe: TROUBLESHOOTING.md" +echo "" diff --git a/install-docker.sh b/install-docker.sh index 705fe6c..fd81d55 100755 --- a/install-docker.sh +++ b/install-docker.sh @@ -110,19 +110,112 @@ if ! command -v docker &> /dev/null; then fi print_success "Docker gefunden: $(docker --version)" -# Docker Compose prüfen -if command -v docker-compose &> /dev/null; then - COMPOSE_CMD="docker-compose" - print_success "docker-compose gefunden: $(docker-compose --version)" -elif docker compose version &> /dev/null; then - COMPOSE_CMD="docker compose" - print_success "docker compose gefunden: $(docker compose version)" +# Docker Compose prüfen - v2 bevorzugen +detect_compose_command() { + # Zuerst docker compose (v2/Plugin) prüfen - bevorzugt + if docker compose version &> /dev/null 2>&1; then + COMPOSE_CMD="docker compose" + COMPOSE_VERSION="v2" + return 0 + fi + + # Dann docker-compose (v1) prüfen + if command -v docker-compose &> /dev/null; then + # Prüfen ob es tatsächlich funktioniert (distutils Problem bei Python 3.12) + if docker-compose --version &> /dev/null 2>&1; then + COMPOSE_CMD="docker-compose" + COMPOSE_VERSION="v1" + return 0 + else + # docker-compose existiert aber funktioniert nicht + print_warning "docker-compose (v1) ist installiert, funktioniert aber nicht!" + print_warning "Dies liegt wahrscheinlich am fehlenden 'distutils' Modul (Python 3.12+)" + echo "" + echo " Mögliche Lösungen:" + echo " 1. Docker Compose v2 installieren (empfohlen):" + echo " sudo apt-get update && sudo apt-get install docker-compose-plugin" + echo "" + echo " 2. distutils für Python installieren (Workaround):" + echo " sudo apt-get install python3-distutils" + echo " # Oder für neuere Systeme:" + echo " pip3 install setuptools" + echo "" + echo " Siehe TROUBLESHOOTING.md für weitere Details." + echo "" + return 1 + fi + fi + + return 1 +} + +# Compose Command ermitteln +if detect_compose_command; then + if [ "$COMPOSE_VERSION" = "v2" ]; then + print_success "Docker Compose v2 (Plugin) gefunden: $(docker compose version --short 2>/dev/null || docker compose version)" + else + print_success "docker-compose v1 gefunden: $(docker-compose --version)" + print_warning "Empfehlung: Upgrade zu Docker Compose v2 für bessere Kompatibilität" + fi else - print_error "Docker Compose ist nicht installiert!" - echo " Bitte installieren Sie Docker Compose" + print_error "Docker Compose ist nicht installiert oder funktioniert nicht!" + echo "" + echo " Installation von Docker Compose v2 (empfohlen):" + echo " sudo apt-get update && sudo apt-get install docker-compose-plugin" + echo "" + echo " Oder siehe: https://docs.docker.com/compose/install/" + echo "" + echo " Alternativ: Verwenden Sie install-docker-manual.sh für Installation ohne docker-compose" exit 1 fi +# Hilfsfunktion für Compose-Befehle +run_compose() { + $COMPOSE_CMD "$@" +} + +# ============================================================================= +# Funktion: Prüft ob ein Verzeichnis read-only ist +# ============================================================================= +check_readonly() { + local dir="$1" + local test_file="$dir/.write_test_$$" + + # Versuche eine Test-Datei zu erstellen + if touch "$test_file" 2>/dev/null; then + rm -f "$test_file" 2>/dev/null + return 0 # Schreibbar + else + return 1 # Read-only + fi +} + +print_readonly_warning() { + local dir="$1" + print_error "Das Verzeichnis '$dir' ist read-only!" + echo "" + echo " Das custom-nodes Verzeichnis benötigt Schreibrechte für:" + echo " - npm install (Dependencies)" + echo " - npm run build (Kompilierung)" + echo "" + echo " LÖSUNGEN:" + echo "" + echo " 1. Volume OHNE :ro mounten (empfohlen):" + echo " volumes:" + echo " - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking" + echo " # NICHT: - ./custom-nodes:/...:ro" + echo "" + echo " 2. Auf dem Host bauen (für read-only Volumes):" + echo " ./build-on-host.sh" + echo " # Dann docker-compose.readonly.yml verwenden" + echo "" + echo " 3. Berechtigungen prüfen:" + echo " ls -la $dir" + echo " sudo chown -R 1000:1000 $dir" + echo "" + echo " Siehe: TROUBLESHOOTING.md und SECURITY.md" +} + # Zielpfad prüfen if [ ! -d "$N8N_PATH" ]; then print_error "Verzeichnis existiert nicht: $N8N_PATH" @@ -195,6 +288,18 @@ if [ ! -d "$CUSTOM_NODES_DIR" ]; then print_success "Custom Nodes kopiert nach: $CUSTOM_NODES_DIR" fi +# Prüfe ob Verzeichnis schreibbar ist +if ! check_readonly "$CUSTOM_NODES_DIR"; then + print_readonly_warning "$CUSTOM_NODES_DIR" + echo "" + read -p "Trotzdem fortfahren? (j/n): " CONTINUE_RO + if [[ ! "$CONTINUE_RO" =~ ^[jJyY]$ ]]; then + print_error "Abbruch wegen read-only Verzeichnis" + exit 1 + fi + print_warning "Fortsetzen trotz read-only - npm install wird fehlschlagen!" +fi + # ============================================================================= # Node bauen (wenn nicht bereits gebaut) # ============================================================================= diff --git a/install-in-container.sh b/install-in-container.sh new file mode 100755 index 0000000..ff87675 --- /dev/null +++ b/install-in-container.sh @@ -0,0 +1,191 @@ +#!/bin/sh +# ============================================================================ +# install-in-container.sh - Installiert den LibreBooking Node IM Docker Container +# Dieses Skript wird INNERHALB des Containers ausgeführt! +# ============================================================================ + +set -e + +echo "============================================" +echo " LibreBooking Node - Container Installation" +echo "============================================" +echo "" + +# Funktion: Prüft ob ein Verzeichnis read-only ist +check_readonly() { + local dir="$1" + local test_file="$dir/.write_test_$$" + + # Versuche eine Test-Datei zu erstellen + if touch "$test_file" 2>/dev/null; then + rm -f "$test_file" 2>/dev/null + return 0 # Schreibbar (exit code 0 = true) + else + return 1 # Read-only (exit code 1 = false) + fi +} + +print_readonly_error() { + local dir="$1" + echo "" + echo "============================================" + echo " FEHLER: Read-only Volume!" + echo "============================================" + echo "" + echo "Das Verzeichnis '$dir' ist read-only gemountet." + echo "npm install und npm run build benötigen Schreibrechte." + echo "" + echo "LÖSUNGEN:" + echo "" + echo "1. Volume OHNE :ro mounten:" + echo " In docker-compose.yml oder docker-compose.override.yml:" + echo "" + echo " volumes:" + echo " - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking" + echo " # NICHT:" + echo " - ./custom-nodes:/home/node/.n8n/custom/n8n-nodes-librebooking:ro" + echo "" + echo "2. Auf dem Host bauen:" + echo " Führen Sie auf dem HOST aus: ./build-on-host.sh" + echo " Dann verwenden Sie: docker-compose.readonly.yml" + echo "" + echo "Siehe: TROUBLESHOOTING.md" + exit 1 +} + +# Mögliche Pfade für custom-nodes +POSSIBLE_PATHS=" +/home/node/.n8n/custom/n8n-nodes-librebooking +/home/node/.n8n/custom +/opt/n8n/custom-nodes +/data/custom-nodes +" + +CUSTOM_NODE_PATH="" + +# Finde den custom-nodes Pfad +echo "[1/5] Suche custom-nodes Verzeichnis..." +for path in $POSSIBLE_PATHS; do + if [ -d "$path" ]; then + # Prüfe ob package.json vorhanden ist + if [ -f "$path/package.json" ]; then + CUSTOM_NODE_PATH="$path" + echo " ✓ Gefunden: $path" + break + elif [ -f "$path/n8n-nodes-librebooking/package.json" ]; then + CUSTOM_NODE_PATH="$path/n8n-nodes-librebooking" + echo " ✓ Gefunden: $CUSTOM_NODE_PATH" + break + fi + fi +done + +if [ -z "$CUSTOM_NODE_PATH" ]; then + echo " ✗ Kein custom-nodes Verzeichnis mit package.json gefunden!" + echo "" + echo " Geprüfte Pfade:" + for path in $POSSIBLE_PATHS; do + if [ -d "$path" ]; then + echo " - $path (existiert, aber keine package.json)" + else + echo " - $path (existiert nicht)" + fi + done + echo "" + echo " Bitte stellen Sie sicher, dass die Dateien korrekt kopiert wurden." + exit 1 +fi + +# Wechsle ins Verzeichnis +echo "[2/5] Wechsle ins Verzeichnis: $CUSTOM_NODE_PATH" +cd "$CUSTOM_NODE_PATH" +echo " ✓ Aktuelles Verzeichnis: $(pwd)" + +# Prüfe ob npm verfügbar ist +echo "[3/5] Prüfe npm..." +if ! command -v npm >/dev/null 2>&1; then + echo " ✗ npm ist nicht installiert!" + echo "" + echo " Im n8n Docker-Image sollte npm vorhanden sein." + echo " Falls nicht, verwenden Sie ein Image mit Node.js." + exit 1 +fi +echo " ✓ npm Version: $(npm --version)" +echo " ✓ node Version: $(node --version)" + +# Prüfe Schreibrechte (read-only Volume?) +echo "[3.5/5] Prüfe Schreibrechte..." +if ! check_readonly "$CUSTOM_NODE_PATH"; then + print_readonly_error "$CUSTOM_NODE_PATH" +fi +echo " ✓ Schreibrechte vorhanden" + +# Dependencies installieren +echo "[4/5] Installiere Dependencies..." +echo " Führe 'npm install' aus..." +if npm install 2>&1; then + echo " ✓ Dependencies installiert" +else + echo " ✗ npm install fehlgeschlagen!" + echo "" + echo " Mögliche Lösungen:" + echo " - Prüfen Sie die Berechtigungen" + echo " - Prüfen Sie die Internetverbindung" + echo " - Führen Sie 'npm cache clean --force' aus" + exit 1 +fi + +# Build ausführen +echo "[5/5] Baue den Node..." +echo " Führe 'npm run build' aus..." +if npm run build 2>&1; then + echo " ✓ Build erfolgreich" +else + echo " ✗ Build fehlgeschlagen!" + echo "" + echo " Prüfen Sie die TypeScript-Fehler in der Ausgabe oben." + exit 1 +fi + +# Prüfe Ergebnis +echo "" +echo "============================================" +echo " Prüfe Installation..." +echo "============================================" + +if [ -d "$CUSTOM_NODE_PATH/dist" ]; then + echo "✓ dist/ Verzeichnis existiert" + + # Prüfe auf .node.js Dateien + NODE_FILES=$(find "$CUSTOM_NODE_PATH/dist" -name "*.node.js" 2>/dev/null | wc -l) + if [ "$NODE_FILES" -gt 0 ]; then + echo "✓ $NODE_FILES Node-Datei(en) gefunden:" + find "$CUSTOM_NODE_PATH/dist" -name "*.node.js" -exec echo " - {}" \; + else + echo "✗ Keine .node.js Dateien im dist/ Verzeichnis gefunden!" + fi + + # Prüfe credentials + CRED_FILES=$(find "$CUSTOM_NODE_PATH/dist" -name "*.credentials.js" 2>/dev/null | wc -l) + if [ "$CRED_FILES" -gt 0 ]; then + echo "✓ $CRED_FILES Credential-Datei(en) gefunden" + fi +else + echo "✗ dist/ Verzeichnis wurde nicht erstellt!" + exit 1 +fi + +echo "" +echo "============================================" +echo " Installation abgeschlossen!" +echo "============================================" +echo "" +echo "Nächste Schritte:" +echo " 1. Verlassen Sie den Container: exit" +echo " 2. Starten Sie n8n neu: docker restart n8n" +echo " 3. Öffnen Sie n8n im Browser und suchen Sie nach 'LibreBooking'" +echo "" +echo "Falls der Node nicht erscheint:" +echo " - Prüfen Sie die Umgebungsvariable N8N_CUSTOM_EXTENSIONS" +echo " - Führen Sie ./check-installation.sh aus" +echo "" diff --git a/n8n-nodes-librebooking-v1.0.0.tar.gz b/n8n-nodes-librebooking-v1.0.0.tar.gz new file mode 100644 index 0000000..e6c52bc Binary files /dev/null and b/n8n-nodes-librebooking-v1.0.0.tar.gz differ diff --git a/n8n-nodes-librebooking-v1.0.0.zip b/n8n-nodes-librebooking-v1.0.0.zip new file mode 100644 index 0000000..38b91aa Binary files /dev/null and b/n8n-nodes-librebooking-v1.0.0.zip differ diff --git a/package.json b/package.json index 498b5e3..67bb1ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-librebooking", - "version": "1.0.0", + "version": "1.1.0", "description": "n8n Node für LibreBooking - Ressourcen- und Reservierungsverwaltung", "keywords": [ "n8n-community-node-package", @@ -46,7 +46,11 @@ "postinstall": "echo 'Installation abgeschlossen. Führe npm run build aus.'", "test": "ts-node test/test-api.ts", "link:n8n": "npm link && echo 'Node verlinkt. Starte n8n neu mit: n8n start'", - "unlink": "npm unlink -g n8n-nodes-librebooking" + "unlink": "npm unlink -g n8n-nodes-librebooking", + "install-in-docker": "npm install && npm run build && echo '\\n✓ Installation abgeschlossen. Bitte n8n Container neustarten: docker restart n8n'", + "docker:copy": "docker cp dist n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ && docker cp package.json n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/ && docker cp node_modules n8n:/home/node/.n8n/custom/n8n-nodes-librebooking/", + "docker:restart": "docker restart n8n", + "docker:deploy": "npm run build && npm run docker:copy && npm run docker:restart" }, "files": [ "dist", @@ -78,5 +82,9 @@ }, "engines": { "node": ">=18.17.0" + }, + "overrides": { + "form-data": "^4.0.1", + "lodash": "^4.17.21" } } diff --git a/quick-install.sh b/quick-install.sh new file mode 100755 index 0000000..da1c634 --- /dev/null +++ b/quick-install.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# ============================================================================ +# quick-install.sh - Schnellste Installation des LibreBooking n8n Nodes +# +# EMPFOHLENE METHODE: Auf dem Host bauen, in Container kopieren +# ============================================================================ + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +CONTAINER_NAME="${1:-n8n}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo -e "${GREEN}=== LibreBooking Quick Install ===${NC}\n" + +# Prüfe Voraussetzungen +if ! command -v node &>/dev/null; then + echo -e "${RED}Fehler: Node.js nicht installiert!${NC}" + echo "Installieren: https://nodejs.org/ (v18+)" + exit 1 +fi + +if ! command -v docker &>/dev/null; then + echo -e "${RED}Fehler: Docker nicht installiert!${NC}" + exit 1 +fi + +if ! docker ps | grep -q "$CONTAINER_NAME"; then + echo -e "${YELLOW}Warnung: Container '$CONTAINER_NAME' nicht gefunden oder läuft nicht.${NC}" + echo "Verfügbare Container:" + docker ps --format " {{.Names}}" + exit 1 +fi + +cd "$SCRIPT_DIR" + +# 1. Dependencies installieren +echo "[1/4] Installiere Dependencies..." +npm install --silent + +# 2. Bauen +echo "[2/4] Baue Node..." +npm run build --silent + +# 3. In Container kopieren +echo "[3/4] Kopiere in Container '$CONTAINER_NAME'..." + +# Erstelle Zielverzeichnis falls nötig +docker exec "$CONTAINER_NAME" mkdir -p /home/node/.n8n/custom/n8n-nodes-librebooking 2>/dev/null || true + +# Kopiere Dateien +docker cp dist "$CONTAINER_NAME":/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp package.json "$CONTAINER_NAME":/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp node_modules "$CONTAINER_NAME":/home/node/.n8n/custom/n8n-nodes-librebooking/ + +# 4. Container neustarten +echo "[4/4] Starte Container neu..." +docker restart "$CONTAINER_NAME" + +echo "" +echo -e "${GREEN}✓ Installation abgeschlossen!${NC}" +echo "" +echo "Nächste Schritte:" +echo " 1. Öffne n8n: http://localhost:5678" +echo " 2. Gehe zu: Einstellungen → Credentials → Add Credential" +echo " 3. Suche: 'LibreBooking API'" +echo "" diff --git a/update-dependencies.sh b/update-dependencies.sh new file mode 100755 index 0000000..da4318e --- /dev/null +++ b/update-dependencies.sh @@ -0,0 +1,112 @@ +#!/bin/bash +# ============================================================================ +# update-dependencies.sh - Aktualisiert Dependencies und behebt Vulnerabilities +# ============================================================================ + +set -e + +# Farben +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo -e "${BLUE}============================================${NC}" +echo -e "${BLUE} LibreBooking Node - Dependency Update${NC}" +echo -e "${BLUE}============================================${NC}" +echo "" + +log() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +cd "$SCRIPT_DIR" + +# Schritt 1: Aktuelle Vulnerabilities anzeigen +log "Prüfe aktuelle Vulnerabilities..." +echo "" +echo "=== VOR dem Update ===" +npm audit 2>/dev/null || true +echo "" + +# Schritt 2: npm audit fix +log "Führe npm audit fix aus..." +if npm audit fix 2>&1; then + log "npm audit fix erfolgreich ✓" +else + warn "npm audit fix hatte Probleme (evtl. normale Warnungen)" +fi + +# Schritt 3: npm update +log "Führe npm update aus..." +if npm update 2>&1; then + log "npm update erfolgreich ✓" +else + warn "npm update hatte Probleme" +fi + +# Schritt 4: Outdated Packages prüfen +log "Prüfe veraltete Packages..." +echo "" +npm outdated 2>/dev/null || log "Alle Packages sind aktuell ✓" +echo "" + +# Schritt 5: Build testen +log "Teste Build..." +if npm run build 2>&1 | tail -5; then + log "Build erfolgreich ✓" +else + error "Build fehlgeschlagen!" + echo "" + echo "Versuchen Sie:" + echo " 1. rm -rf node_modules package-lock.json" + echo " 2. npm install" + echo " 3. npm run build" + exit 1 +fi + +# Schritt 6: Finale Vulnerability-Prüfung +log "Finale Vulnerability-Prüfung..." +echo "" +echo "=== NACH dem Update ===" +npm audit 2>/dev/null || true +echo "" + +# Schritt 7: Report +echo -e "${GREEN}============================================${NC}" +echo -e "${GREEN} Update abgeschlossen!${NC}" +echo -e "${GREEN}============================================${NC}" +echo "" + +# Verbleibende Vulnerabilities zählen +AUDIT_OUTPUT=$(npm audit 2>/dev/null || true) +if echo "$AUDIT_OUTPUT" | grep -q "found 0 vulnerabilities"; then + log "Keine Vulnerabilities mehr! ✓" +else + VULN_COUNT=$(echo "$AUDIT_OUTPUT" | grep -oP '\d+ vulnerabilities' | head -1 || echo "Einige") + warn "Verbleibende Vulnerabilities: $VULN_COUNT" + echo "" + echo "Diese kommen wahrscheinlich von n8n-workflow Dependencies." + echo "Siehe SECURITY.md für weitere Informationen." +fi + +echo "" +echo "Nächste Schritte:" +echo " 1. Testen Sie die Änderungen lokal" +echo " 2. Committen Sie die Änderungen:" +echo " git add package.json package-lock.json" +echo " git commit -m 'chore: update dependencies'" +echo " 3. Bauen Sie das Docker-Image neu:" +echo " docker compose build --no-cache" +echo "" diff --git a/update-node.sh b/update-node.sh new file mode 100755 index 0000000..44c940b --- /dev/null +++ b/update-node.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# ============================================================================ +# update-node.sh - Aktualisiert den LibreBooking n8n Node +# +# Verwendung nach git pull oder Änderungen am Code +# ============================================================================ + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +CONTAINER_NAME="${1:-n8n}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo -e "${GREEN}=== LibreBooking Node Update ===${NC}\n" + +cd "$SCRIPT_DIR" + +# Git Pull (optional) +if [ -d ".git" ]; then + echo "[1/5] Hole neueste Änderungen..." + git pull 2>/dev/null || echo -e "${YELLOW}Git pull übersprungen${NC}" +else + echo "[1/5] Kein Git Repository - übersprungen" +fi + +# Dependencies aktualisieren +echo "[2/5] Aktualisiere Dependencies..." +npm install --silent + +# Bauen +echo "[3/5] Baue Node..." +npm run build --silent + +# Prüfe Container +if ! docker ps | grep -q "$CONTAINER_NAME"; then + echo -e "${YELLOW}Container '$CONTAINER_NAME' nicht gefunden.${NC}" + echo "Verfügbare Container:" + docker ps --format " {{.Names}}" + echo "" + echo "Manuell kopieren:" + echo " docker cp dist :/home/node/.n8n/custom/n8n-nodes-librebooking/" + exit 0 +fi + +# In Container kopieren +echo "[4/5] Kopiere in Container '$CONTAINER_NAME'..." +docker cp dist "$CONTAINER_NAME":/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp package.json "$CONTAINER_NAME":/home/node/.n8n/custom/n8n-nodes-librebooking/ +docker cp node_modules "$CONTAINER_NAME":/home/node/.n8n/custom/n8n-nodes-librebooking/ + +# Container neustarten +echo "[5/5] Starte Container neu..." +docker restart "$CONTAINER_NAME" + +echo "" +echo -e "${GREEN}✓ Update abgeschlossen!${NC}" +echo ""