diff --git a/.gitignore b/.gitignore index 1e353b75..afd73f11 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ server/images .env .env.dev +.env.bak.* # Local dev exclusion ffmpeg.exe @@ -57,6 +58,9 @@ downloads/* # Backup archive location backups/ +# Database migration safety backups +database.bind-mount-backup.*/ + # Superpowers brainstorming session artifacts (local-only, ephemeral mockups) .superpowers/ diff --git a/README.md b/README.md index 05a64605..a08a5ea8 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ You'll need Docker, Docker Compose, Git, and a Bash shell (Git Bash on Windows). > Want to try unreleased features? See [Using Development Builds](docs/DEVELOPMENT.md#using-development-builds) for the bleeding-edge `dev-latest` image. +> **Database note for Docker Desktop/ARM/NAS users:** Fresh installs started with `./start.sh` use Docker named-volume storage for MariaDB. Existing bind-mounted installs and plain `docker compose up -d` installs may use `./database`; on virtualized filesystems this can be risky for MariaDB schema migrations. If you see `Table ... doesn't exist in engine` or `Incorrect information in file` errors, or if you want to proactively migrate, see [Database Management](docs/DATABASE.md#migrating-from-bind-mount-to-named-volume). + ## Documentation ### Setup & Configuration diff --git a/client/package-lock.json b/client/package-lock.json index f91c3b81..3c532e8f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -149,7 +149,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -775,7 +774,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -799,7 +797,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2019,26 +2016,26 @@ } }, "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/balanced-match": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", - "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" } }, "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" } }, "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/glob": { @@ -2070,16 +2067,16 @@ } }, "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/minimatch": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", - "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3669,9 +3666,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", "cpu": [ "arm" ], @@ -3683,9 +3680,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", "cpu": [ "arm64" ], @@ -3697,9 +3694,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", "cpu": [ "arm64" ], @@ -3711,9 +3708,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", "cpu": [ "x64" ], @@ -3725,9 +3722,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", "cpu": [ "arm64" ], @@ -3739,9 +3736,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", "cpu": [ "x64" ], @@ -3753,9 +3750,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", "cpu": [ "arm" ], @@ -3767,9 +3764,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", "cpu": [ "arm" ], @@ -3781,9 +3778,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", "cpu": [ "arm64" ], @@ -3795,9 +3792,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", "cpu": [ "arm64" ], @@ -3809,9 +3806,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", "cpu": [ "loong64" ], @@ -3823,9 +3820,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", "cpu": [ "loong64" ], @@ -3837,9 +3834,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", "cpu": [ "ppc64" ], @@ -3851,9 +3848,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", "cpu": [ "ppc64" ], @@ -3865,9 +3862,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", "cpu": [ "riscv64" ], @@ -3879,9 +3876,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", "cpu": [ "riscv64" ], @@ -3893,9 +3890,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", "cpu": [ "s390x" ], @@ -3907,9 +3904,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", "cpu": [ "x64" ], @@ -3921,9 +3918,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", "cpu": [ "x64" ], @@ -3935,9 +3932,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", "cpu": [ "x64" ], @@ -3949,9 +3946,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", "cpu": [ "arm64" ], @@ -3963,9 +3960,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", "cpu": [ "arm64" ], @@ -3977,9 +3974,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", "cpu": [ "ia32" ], @@ -3991,9 +3988,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", "cpu": [ "x64" ], @@ -4005,9 +4002,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", "cpu": [ "x64" ], @@ -4236,7 +4233,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" @@ -4602,7 +4598,8 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4774,7 +4771,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz", "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4788,7 +4784,6 @@ "version": "18.2.6", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz", "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==", - "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4799,7 +4794,6 @@ "version": "18.2.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", - "peer": true, "dependencies": { "@types/react": "*" } @@ -5316,9 +5310,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -5443,12 +5437,12 @@ } }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } @@ -5592,9 +5586,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -5634,7 +5628,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6383,7 +6376,8 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -6526,7 +6520,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -6838,15 +6831,16 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -8588,7 +8582,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8618,7 +8611,6 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -8791,6 +8783,7 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -9457,9 +9450,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -9510,13 +9503,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -10035,9 +10028,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -10081,9 +10074,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { @@ -10100,7 +10093,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10358,7 +10350,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -10415,7 +10406,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -10626,9 +10616,9 @@ } }, "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -10782,12 +10772,11 @@ } }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -10799,31 +10788,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" } }, @@ -11020,7 +11009,6 @@ "integrity": "sha512-yueTpl5YJqLzQqs3CanxNdAAfFU23iP0j+JVJURE4ghfEtRmWfWoZWLGkVcyjmgum7UmjwAlqRuOjQDNvH89kw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", @@ -11422,9 +11410,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -11455,9 +11443,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -11641,7 +11629,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11739,7 +11726,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12058,7 +12044,6 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/docker-compose.arm.yml b/docker-compose.arm.yml index c5d156d2..c21c85fe 100644 --- a/docker-compose.arm.yml +++ b/docker-compose.arm.yml @@ -1,5 +1,7 @@ -# Override file for ARM-based systems (Apple Silicon, Raspberry Pi, etc.) -# This uses a named volume for MariaDB to work around virtiofs bugs on ARM +# Named-volume database override. +# The filename is historical: it was originally added for ARM systems, but it is +# also useful on Docker Desktop and NAS/virtualized filesystems where bind-mounted +# MariaDB data can corrupt during schema migrations. services: youtarr-db: @@ -8,4 +10,3 @@ services: volumes: youtarr-db-data: - diff --git a/docker-compose.yml b/docker-compose.yml index 1122b84b..a66b6af8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,10 +15,12 @@ # mkdir -p config jobs server/images /path/to/youtube_videos # sudo chown -R 1000:1000 config jobs server/images /path/to/youtube_videos # -# 2) Database setup notes for Synology and ARM-based systems (Apple Silicon, Raspberry Pi): -# There are known issues with using bind mounts for MariaDB data directories due to permission and virtiofs bugs. -# The start scripts automatically detect ARM architecture and use docker-compose.arm.yml override to switch to named volumes. -# For Synology or manual override, run: docker compose -f docker-compose.yml -f docker-compose.arm.yml up -d +# 2) Database setup notes: +# The default bundled MariaDB uses ./database as a bind mount for backwards compatibility. +# On Docker Desktop (Windows/macOS), ARM hosts, and some NAS/virtualized filesystems, +# bind-mounted MariaDB data can corrupt during schema migrations. For a safer named +# volume, use docker-compose.arm.yml or run ./scripts/migrate-to-named-volume.sh +# for an existing bind-mounted install. services: youtarr-db: @@ -96,6 +98,6 @@ networks: default: name: youtarr-network -# Named volume definition (used by docker-compose.arm.yml override for ARM systems) +# Named volume definition (used by docker-compose.arm.yml override) volumes: youtarr-db-data: diff --git a/docs/DATABASE.md b/docs/DATABASE.md index 6bdcdca3..3f288b2b 100644 --- a/docs/DATABASE.md +++ b/docs/DATABASE.md @@ -45,52 +45,102 @@ Youtarr uses MariaDB/MySQL for storing: ### Storage Options -#### Option 1: Bind Mount (Default) +#### Option 1: Bind Mount (legacy / pre-existing installs) ```yaml volumes: - ./database:/var/lib/mysql ``` - Data stored in `./database` directory on the host -- May have permission issues on Synology/QNAP and or virtiofs issues on Apple Silicon macOS, leading to database issues/corruption +- Kept for backwards compatibility with existing bind-mounted installs and plain `docker compose up -d` users +- Works well on native Linux Docker hosts +- Can have permission issues on Synology/QNAP +- Can corrupt during MariaDB schema migrations on Docker Desktop for Windows/macOS, ARM hosts, and some virtualized filesystems -#### Option 2: Named Volume (Recommended for Synology/Apple) +#### Option 2: Named Volume (Recommended for Docker Desktop/ARM/NAS) ```yaml volumes: - youtarr-db-data:/var/lib/mysql ``` - Better compatibility with Synology/QNAP -- Avoids permission issues -- Required for macOS Apple Silicon -- Not easily visible on host +- Avoids the virtualized-filesystem write semantics problem that can affect bind-mounted MariaDB +- Used automatically for fresh installs started with `./start.sh` on every platform (Linux included, since v1.69) +- Recommended for Docker Desktop on Windows/macOS, ARM systems, and NAS setups +- Not easily visible on host: data lives under `/var/lib/docker/volumes/_youtarr-db-data/_data` rather than `./database/`. `./scripts/backup.sh` dumps from the running MariaDB container when it is already up; when it has to start MariaDB for a backup, it detects whether this install uses the bind mount or named volume first. -### Switching to Named Volume +### Migrating from Bind Mount to Named Volume -If experiencing permission errors on Synology/QNAP or corruption on Apple Silicon: +If you already have Youtarr data in `./database/`, do **not** switch the compose mount by hand unless you intentionally want to start with an empty database. Use the migration helper instead: -**IMPORTANT**: Changing your DB volume mount will *not* migrate your existing database! If you are not experiencing problems, leave this setting alone! +```bash +./scripts/migrate-to-named-volume.sh +``` -1. Stop the stack: - `docker compose down` or `./stop.sh` +What the script does (in this order, so any failure leaves the simplest possible recovery state): +1. Runs a pre-flight permissions check so it fails fast (instead of stalling on an interactive `sudo` prompt) if it cannot write to the project directory. +2. Stops Youtarr. +3. Starts the existing bind-mounted MariaDB long enough to run `mysqldump` and to capture per-table row counts. +4. Renames `./database/` to `./database.bind-mount-backup./` so the original files are preserved. +5. Starts a fresh named-volume MariaDB and imports the dump. +6. Verifies that the table set matches the source **and** that every table has the same row count as the source. +7. **Only after verification succeeds**, snapshots `.env` to `./.env.bak.` and pins `COMPOSE_PATH_SEPARATOR=:` and `COMPOSE_FILE=docker-compose.yml:docker-compose.arm.yml` in `.env`. This means a failure during step 5 or 6 leaves `.env` untouched, and recovery is just `mv ./database.bind-mount-backup. ./database` plus removing the partial named volume. +8. Brings the full stack (app + database) back up so Youtarr is immediately usable. -2. Edit `docker-compose.yml`: - ```yaml - # Change from: - volumes: - - ./database:/var/lib/mysql +**What the migration does *not* copy**: `mysqldump` runs with `--single-transaction --routines --triggers --events`. Schema, data, stored routines, triggers, and events all migrate. MariaDB users and `GRANT` statements (anything in `mysql.user` / `mysql.db`) do **not**. The default Youtarr install only uses the bundled `root` user, so this is a no-op for almost everyone. If you have created additional database users on the bundled MariaDB, recreate them after the migration completes. - # To: - volumes: - - youtarr-db-data:/var/lib/mysql - ``` +**Password note**: for the bundled `root` database user, `DB_ROOT_PASSWORD` seeds the root password when a fresh MariaDB data directory is initialized, while Youtarr connects with `DB_PASSWORD`. The migration requires those two values to match before it creates the new named-volume database. -3. Add volume definition at bottom of file: - ```yaml - volumes: - youtarr-db-data: +After it completes, the stack is already running. Subsequent restarts can use any of: + +```bash +./start.sh # recommended +docker compose up -d # the script pins COMPOSE_FILE in .env +docker compose -f docker-compose.yml -f docker-compose.arm.yml up -d # explicit override +``` + +### Reverting to Bind Mount + +The migration is reversible: + +1. Stop the stack: + ```bash + ./stop.sh + ``` +2. Restore the `.env` snapshot: + ```bash + mv ./.env.bak. .env ``` +3. Remove the named volume for this install. The name is usually `_youtarr-db-data`: + ```bash + docker volume ls --format '{{.Name}}' | grep -E '(^|_)youtarr-db-data$' + docker volume rm + ``` +4. Restore the original bind-mounted database directory: + ```bash + mv ./database.bind-mount-backup. ./database + ``` +5. Start Youtarr: + ```bash + ./start.sh + ``` + +Changes made while running on the named volume are not present in the old bind-mounted backup. If you have used the named volume for a while and want to keep those newer changes, take a backup first with `./scripts/backup.sh`. + +### Fresh Installs with Named Volume + +For a new install with no data to preserve, you can start directly with the named-volume override: + +```bash +docker compose -f docker-compose.yml -f docker-compose.arm.yml up -d +``` + +Or pin the override in `.env` so plain `docker compose up -d` uses it: + +```env +COMPOSE_PATH_SEPARATOR=: +COMPOSE_FILE=docker-compose.yml:docker-compose.arm.yml +``` -4. Restart: - `docker compose up -d` or `./start.sh` +`COMPOSE_PATH_SEPARATOR=:` is important on Windows so Compose parses the file list consistently. ### Security Considerations @@ -214,11 +264,11 @@ npm run db:create-migration -- --name my-migration-name #### Common Causes 1. **Synology/QNAP NAS**: MariaDB runs as UID 999, which may not exist -2. **macOS Apple Silicon**: virtiofs incompatibility with MariaDB 10.3 +2. **Docker Desktop/ARM/NAS**: virtualized filesystem or permission issues with bind-mounted MariaDB data 3. **Wrong ownership**: Database files owned by incorrect user #### Solutions -1. **Switch to named volume** (see above) +1. **Migrate to named volume** (see above) 2. **Fix permissions**: ```bash # Check current ownership diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 58ee0cd6..849a5cd5 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -14,11 +14,11 @@ The `dev-latest` tag always points at the most recent dev build. Each commit als This pulls `dialmaster/youtarr:dev-latest` and starts the stack. On later runs, drop `--pull-latest` if you want to stay on the image you already have locally. -### Alternative: bypass `./start.sh` (safer for existing installs on ARM) +### Alternative: bypass `./start.sh` -`./start.sh` auto-detects ARM hosts (Apple Silicon, Raspberry Pi, ARM Linux) and layers `docker-compose.arm.yml` on top of the default compose file. That override switches MariaDB from the bind mount at `./database/` to a named volume. If you already have data in `./database/` and you run `./start.sh` on ARM for the first time, the container boots against an empty database and it looks like all your channels and videos disappeared. The data is fine, it's still sitting in `./database/`, but the running container isn't pointing at it. +`./start.sh` layers `docker-compose.arm.yml` on top of the default compose file for fresh installs so MariaDB uses a named volume. Existing installs with real MariaDB data in `./database/` keep using the bind mount and print a migration warning instead. ARM installs continue to use the named-volume override. -To pull the dev image without letting `./start.sh` touch your compose file selection, use docker directly: +To pull the dev image while managing compose file selection yourself, use docker directly: ```bash ./stop.sh diff --git a/docs/DOCKER.md b/docs/DOCKER.md index 32d80826..95f0f279 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -14,7 +14,7 @@ Youtarr ships four Compose files so each supported runtime can layer the right o |------|---------| | `docker-compose.yml` | Production defaults with the bundled MariaDB container. Used by `./start.sh`. | | `docker-compose.dev.yml` | Development mode: mounts `./server/` and migrations into the container, runs the backend with `node --watch` for hot reload, and uses a separate `youtarr-db-dev` database with its own named volume. Used by `./scripts/start-dev.sh`. See [DEVELOPMENT.md](DEVELOPMENT.md). | -| `docker-compose.arm.yml` | ARM override (Apple Silicon, Raspberry Pi) that switches the MariaDB data directory to a named volume to avoid virtiofs issues. Layered on top of `docker-compose.yml` via `-f`. | +| `docker-compose.arm.yml` | Named-volume database override. The filename is historical: it was originally added for ARM systems, but it is also useful on Docker Desktop and NAS/virtualized filesystems. Layered on top of `docker-compose.yml` via `-f`. | | `docker-compose.external-db.yml` | Runs Youtarr against an external MariaDB/MySQL instance instead of the bundled database. Used by `./start-with-external-db.sh`. | ## Container Details @@ -34,10 +34,10 @@ Youtarr ships four Compose files so each supported runtime can layer the right o - **Port**: 3321 (both host and container) - **Volumes**: - `./database:/var/lib/mysql` - Database persistence (default) - - `youtarr-db-data:/var/lib/mysql` - Named volume (required for ARM/Synology) + - `youtarr-db-data:/var/lib/mysql` - Named volume (recommended for Docker Desktop/ARM/NAS) - **Character Set**: utf8mb4 (full Unicode support) -> **ARM Users**: See [ARM Architecture Notes](#arm-architecture-apple-silicon-raspberry-pi) below. +> **Docker Desktop/ARM/NAS users**: See [Named-Volume Database Override](#named-volume-database-override) below. ## ⚠️ Important: Do Not Mount the Migrations Directory @@ -62,25 +62,30 @@ volumes: If your automation creates a migrations directory, remove it from both directory creation and volume mounts. -## ARM Architecture (Apple Silicon, Raspberry Pi) +## Named-Volume Database Override -ARM-based systems (Apple Silicon Macs, Raspberry Pi, etc.) have known issues with MariaDB bind mounts due to virtiofs bugs. The start scripts automatically detect ARM and apply the fix. +Docker Desktop on Windows/macOS, ARM hosts, Synology/QNAP, and some virtualized filesystems can have trouble with MariaDB data stored on a bind mount. The named-volume override avoids that class of issue. ### Using Start Scripts (Recommended) -The `./start.sh` script automatically detects ARM architecture and applies the correct configuration: +`./start.sh` automatically uses `docker-compose.arm.yml` for fresh installs on every platform. Existing installs with real MariaDB data in `./database/` keep using the bind mount and print a migration warning instead, including on ARM hosts. ```bash ./start.sh ``` +For an existing bind-mounted install, migrate with: +```bash +./scripts/migrate-to-named-volume.sh +``` + ### Using Docker Compose Directly -If you prefer running `docker compose` commands directly on ARM systems, use the override file: +For a fresh install, use the override file: ```bash docker compose -f docker-compose.yml -f docker-compose.arm.yml up -d ``` -This uses a named Docker volume instead of a bind mount for MariaDB data, avoiding the virtiofs issues. +This uses a named Docker volume instead of a bind mount for MariaDB data. ### Manual Configuration @@ -99,7 +104,7 @@ volumes: youtarr-db-data: ``` -See [Troubleshooting](TROUBLESHOOTING.md#apple-silicon--arm-incorrect-information-in-file-errors) for more details on the underlying issue. +See [Troubleshooting](TROUBLESHOOTING.md#docker-desktop--arm-incorrect-information-in-file-errors) for more details on the underlying issue. ## Configuration Setup - **Create a .env file** to configure environment variables: @@ -246,7 +251,7 @@ On most Linux hosts, Docker will auto-create `./database` on first run and Maria sudo chown -R 999:999 database ``` -If you hit `InnoDB: Operating system error number 13` at startup, you have hit this case - see [Switching to Named Volume](DATABASE.md#switching-to-named-volume) in the database docs for an alternative that sidesteps bind-mount permission issues entirely. +If you hit `InnoDB: Operating system error number 13` at startup, you have hit this case - see [Migrating from Bind Mount to Named Volume](DATABASE.md#migrating-from-bind-mount-to-named-volume) in the database docs for an alternative that sidesteps bind-mount permission issues entirely. #### 5. Set Permissions diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index be0b621a..959ca2ef 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -33,7 +33,7 @@ Choose your preferred installation method - `--pull-latest`: Pull latest code from Github and latest image from DockerHub - `--debug`: Set log level to debug - This automatically creates a `.env` file from the included `.env.example` and starts both the Youtarr application and MariaDB database containers. + This automatically creates a `.env` file from the included `.env.example` and starts both the Youtarr application and MariaDB database containers. On a fresh install, `./start.sh` uses Docker named-volume storage for MariaDB. If an existing `./database/` MariaDB directory is present, it preserves that bind-mounted database and prints a migration warning. 3. **Access the web interface**: Open your browser and navigate to `http://localhost:3087` @@ -81,11 +81,11 @@ If you prefer to use standard `docker compose up` commands: docker compose up -d ``` - > **ARM Users (Apple Silicon, Raspberry Pi)**: Use the ARM override to avoid MariaDB volume issues: + > **Docker Desktop/ARM/NAS users**: For a fresh install, use the named-volume database override to avoid MariaDB bind-mount issues on virtualized filesystems: > ```bash > docker compose -f docker-compose.yml -f docker-compose.arm.yml up -d > ``` - > See [Troubleshooting](TROUBLESHOOTING.md#apple-silicon--arm-incorrect-information-in-file-errors) for details. + > If you already have data in `./database/`, use `./scripts/migrate-to-named-volume.sh` instead. See [Database Management](DATABASE.md#migrating-from-bind-mount-to-named-volume) and [Troubleshooting](TROUBLESHOOTING.md#docker-desktop--arm-incorrect-information-in-file-errors) for details. 5. **Access the web interface**: - Navigate to `http://localhost:3087` @@ -95,7 +95,7 @@ If you prefer to use standard `docker compose up` commands: > **Important**: Ensure the path you assign to `YOUTUBE_OUTPUT_DIR` already exists on the host and is writable before starting the stack. Otherwise Docker will create it as root-owned and the container may not be able to write downloads. -This method is **functionally equivalent** to using the start.sh script, but gives you direct control over environment variables. It's the preferred approach for any Docker-native workflow. +This method gives you direct control over environment variables and compose files, but it is not identical to `./start.sh`: plain `docker compose up -d` uses the legacy bind-mounted database unless you include or pin `docker-compose.arm.yml`. ### Method 3: Manual Setup Without Git (Advanced Users Only) diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 43331842..8bbdc3cd 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -360,50 +360,42 @@ ERROR 1396 (HY000) at line 21: Operation CREATE USER failed for 'root'@'%' - Exit MySQL and restart the stack. 4. Once the stack is back online, verify the latest schema under **Configuration → System → Database Health**. -Tip: run with a named volume (see Apple Silicon/Synology sections) so filesystem corruption is less likely to recur. +Tip: run with a named volume (see Docker Desktop/ARM/Synology sections) so filesystem corruption is less likely to recur. -### Apple Silicon / ARM: `Incorrect information in file` errors +### Docker Desktop / ARM: `Incorrect information in file` errors -**Problem**: On Apple Silicon (M1/M2/M3/M4) or other ARM systems running Docker Desktop, MariaDB logs errors like: +**Problem**: MariaDB logs errors like: ``` ERROR 1033 (HY000): Incorrect information in file: './youtarr/videos.frm' ``` -This happens whenever MariaDB touches tables stored on a bind-mounted host directory (our default `./database:/var/lib/mysql`). Docker Desktop shares bind mounts over `virtiofs`, and MariaDB 10.3 cannot reliably reopen InnoDB tables on that filesystem ([MariaDB issue #447](https://github.com/MariaDB/mariadb-docker/issues/447), [#481](https://github.com/MariaDB/mariadb-docker/issues/481)). Linux and WSL users are unaffected. +or: +``` +errno 1932 - Table 'youtarr.videos' doesn't exist in engine +``` + +This can happen when MariaDB data lives on the bind-mounted host directory (`./database:/var/lib/mysql`) and Docker proxies that directory through a virtualized filesystem, most often Docker Desktop on Windows/macOS, ARM hosts, or some NAS setups. During DDL operations such as `CREATE TABLE` or `ALTER TABLE`, InnoDB can end up out of sync with MariaDB's table metadata. Native Linux Docker hosts are usually unaffected. -**Solution A: Use the start scripts (Recommended)** +**Existing install with data to preserve** -The `./start.sh` and `./start-dev.sh` scripts automatically detect ARM architecture and apply the fix: +Use the migration helper. It dumps the bind-mounted DB, preserves `./database/` as a timestamped backup directory, pins the named-volume override in `.env`, and imports the dump into a fresh named-volume MariaDB: ```bash -./start.sh +./scripts/migrate-to-named-volume.sh ``` -No manual configuration needed—the scripts use `docker-compose.arm.yml` as an override on ARM systems. -**Solution B: Manual docker compose (if not using start scripts)** +See [Database Management](DATABASE.md#migrating-from-bind-mount-to-named-volume) for the full migration and revert details. + +**Fresh install with no data to preserve** -If you run `docker compose up` directly, use the ARM override file: +Start with the named-volume override: ```bash docker compose -f docker-compose.yml -f docker-compose.arm.yml up -d ``` -Or manually edit `docker-compose.yml` to use a named volume: - -**NOTE:** Existing data will *not* be migrated! -1. Stop the stack: `docker compose down` -2. Edit `docker-compose.yml`: - ```yaml - services: - youtarr-db: - volumes: - # Comment out the bind mount: - # - ./database:/var/lib/mysql - # Use named volume instead: - - youtarr-db-data:/var/lib/mysql - - # Add at the bottom of the file: - volumes: - youtarr-db-data: - ``` -3. Start Youtarr: `docker compose up -d` +Or add this to `.env` before plain `docker compose up -d`: +```env +COMPOSE_PATH_SEPARATOR=: +COMPOSE_FILE=docker-compose.yml:docker-compose.arm.yml +``` **Alternatives**: - Point Youtarr at an external MariaDB/MySQL instance via `./start-with-external-db.sh`. diff --git a/package-lock.json b/package-lock.json index 13c48d3a..5705a6ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2793,12 +2793,12 @@ } }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } @@ -4503,12 +4503,12 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", - "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz", + "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==", "license": "MIT", "dependencies": { - "ip-address": "10.1.0" + "ip-address": "^10.2.0" }, "engines": { "node": ">= 16" @@ -4699,15 +4699,16 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -5363,9 +5364,9 @@ } }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" diff --git a/scripts/_env_helpers.sh b/scripts/_env_helpers.sh new file mode 100755 index 00000000..a116204e --- /dev/null +++ b/scripts/_env_helpers.sh @@ -0,0 +1,281 @@ +#!/bin/bash + +# Shared helpers for managing Youtarr's .env file and Docker Compose project state. +# Pure functions only; safe to source from any script without triggering side effects. + +# Read a key from an env file without sourcing it. This intentionally supports +# the simple KEY=value format used by Youtarr's .env files. +youtarr_get_env_file_value() { + local env_file="$1" + local key="$2" + local default="${3:-}" + local value="" + + if [[ -f "$env_file" ]]; then + value=$(grep -E "^[[:space:]]*${key}[[:space:]]*=" "$env_file" 2>/dev/null \ + | tail -n 1 \ + | sed -E "s/^[[:space:]]*${key}[[:space:]]*=[[:space:]]*//" \ + | sed -E 's/[[:space:]]+#.*$//' \ + | sed -E 's/[[:space:]]+$//' \ + | sed -E 's/^"(.*)"$/\1/' \ + | sed -E "s/^'(.*)'$/\1/" || true) + fi + + if [[ -n "$value" ]]; then + printf '%s' "$value" + else + printf '%s' "$default" + fi +} + +# Compute the Docker Compose project name for a checkout. Mirrors Compose's own +# normalization (lowercase basename of the project dir, with anything outside +# [a-z0-9_-] stripped). Honors COMPOSE_PROJECT_NAME (env, then .env) when set. +youtarr_get_compose_project_name() { + local project_dir="$1" + local project_name="${COMPOSE_PROJECT_NAME:-}" + + if [[ -z "$project_name" ]] && [[ -f "$project_dir/.env" ]]; then + project_name=$(youtarr_get_env_file_value "$project_dir/.env" "COMPOSE_PROJECT_NAME" "") + fi + + if [[ -z "$project_name" ]]; then + project_name=$(basename "$project_dir" | tr '[:upper:]' '[:lower:]' | tr -dc 'a-z0-9_-') + fi + + printf '%s' "$project_name" +} + +# Compute the expected MariaDB named-volume name for a checkout. +youtarr_expected_db_volume_name() { + local project_dir="$1" + printf '%s_youtarr-db-data' "$(youtarr_get_compose_project_name "$project_dir")" +} + +# Returns 0 if THIS install's named MariaDB volume exists, 1 otherwise. +# Scoped to the current Compose project; does not match volumes from other +# Youtarr checkouts on the same host. +youtarr_named_volume_exists() { + local project_dir="$1" + local volume_name + volume_name=$(youtarr_expected_db_volume_name "$project_dir") + docker volume inspect "$volume_name" >/dev/null 2>&1 +} + +youtarr_database_dir_has_content() { + local project_dir="$1" + [[ -f "$project_dir/database/ibdata1" || -d "$project_dir/database/mysql" ]] +} + +youtarr_is_arm_host() { + local arch + arch=$(uname -m) + [[ "$arch" == "arm64" || "$arch" == "aarch64" ]] +} + +youtarr_effective_compose_file_value() { + local project_dir="$1" + if [[ -n "${COMPOSE_FILE:-}" ]]; then + printf '%s' "$COMPOSE_FILE" + else + youtarr_get_env_file_value "$project_dir/.env" "COMPOSE_FILE" "" + fi +} + +youtarr_compose_file_has_named_volume_override() { + local project_dir="$1" + local value + value=$(youtarr_effective_compose_file_value "$project_dir") + [[ "$value" == *"docker-compose.arm.yml"* ]] +} + +# Returns one of: named-volume, bind-mount, ambiguous. +# Fresh bundled-DB installs default to named-volume storage. +youtarr_detect_bundled_db_storage_mode() { + local project_dir="$1" + local force_named="${2:-}" + local bind_has_content=false + local named_exists=false + + if youtarr_database_dir_has_content "$project_dir"; then + bind_has_content=true + fi + + if youtarr_named_volume_exists "$project_dir"; then + named_exists=true + fi + + if [[ "$bind_has_content" == "true" && "$named_exists" == "true" ]]; then + printf '%s' "ambiguous" + elif [[ "$force_named" == "--force-named" ]]; then + printf '%s' "named-volume" + elif youtarr_compose_file_has_named_volume_override "$project_dir"; then + printf '%s' "named-volume" + elif [[ "$named_exists" == "true" ]]; then + printf '%s' "named-volume" + elif [[ "$bind_has_content" == "true" ]]; then + printf '%s' "bind-mount" + elif youtarr_is_arm_host; then + printf '%s' "named-volume" + else + printf '%s' "named-volume" + fi +} + +youtarr_compose_args_for_storage_mode() { + local project_dir="$1" + local storage_mode="$2" + + case "$storage_mode" in + named-volume) + if youtarr_compose_file_has_named_volume_override "$project_dir"; then + # Let Compose honor the user's full COMPOSE_FILE list from the shell/.env. + printf '' + elif [[ -n "$(youtarr_effective_compose_file_value "$project_dir")" ]]; then + local compose_file_value + local compose_args="" + local compose_file + local -a compose_files + compose_file_value=$(youtarr_effective_compose_file_value "$project_dir") + IFS=':' read -r -a compose_files <<< "$compose_file_value" + for compose_file in "${compose_files[@]}"; do + [[ -z "$compose_file" ]] && continue + compose_args="$compose_args -f $compose_file" + done + printf '%s' "${compose_args# } -f $project_dir/docker-compose.arm.yml" + else + printf '%s' "-f $project_dir/docker-compose.yml -f $project_dir/docker-compose.arm.yml" + fi + ;; + bind-mount) + printf '%s' "-f $project_dir/docker-compose.yml" + ;; + *) + return 1 + ;; + esac +} + +# Pin the named-volume Compose override in .env so plain `docker compose up -d` +# (without -f flags) picks up docker-compose.arm.yml automatically. +# +# Safe by default: preserves an existing COMPOSE_FILE setting and appends the +# named-volume override if missing. +# Pass --force to strip and replace any existing COMPOSE_FILE / COMPOSE_PATH_SEPARATOR. +# Pass --use-sudo to run the final `mv` under `sudo -n` (for installs where the +# project directory is not directly writable by the current user). The flag may +# be passed in either positional slot. +# +# Return codes: +# 0 - pinned successfully +# 1 - error (env file missing, mktemp failed, or final mv failed; under +# --use-sudo this includes `sudo -n` being unavailable). The temp file +# is cleaned up on mv failure on both the sudo and non-sudo paths. +# 2 - skipped because COMPOSE_FILE already includes docker-compose.arm.yml +youtarr_pin_named_volume_in_env() { + local env_file="$1" + shift || true + + local force="" + local use_sudo="" + local arg + for arg in "$@"; do + case "$arg" in + --force) force="--force" ;; + --use-sudo) use_sudo="--use-sudo" ;; + esac + done + + if [[ ! -f "$env_file" ]]; then + return 1 + fi + + local existing + existing=$(grep -E '^[[:space:]]*COMPOSE_FILE[[:space:]]*=' "$env_file" 2>/dev/null | tail -n 1 || true) + + local existing_value="" + if [[ -n "$existing" ]]; then + existing_value=$(printf '%s' "$existing" \ + | sed -E 's/^[[:space:]]*COMPOSE_FILE[[:space:]]*=[[:space:]]*//' \ + | sed -E 's/[[:space:]]+#.*$//' \ + | sed -E 's/[[:space:]]+$//' \ + | sed -E 's/^"(.*)"$/\1/' \ + | sed -E "s/^'(.*)'$/\1/") + fi + + local existing_separator=":" + local configured_separator + configured_separator=$(youtarr_get_env_file_value "$env_file" "COMPOSE_PATH_SEPARATOR" "") + if [[ "$configured_separator" == ";" ]]; then + existing_separator=";" + elif [[ "$existing_value" == *";"* && "$existing_value" != *":"* ]]; then + existing_separator=";" + fi + + local normalized_existing_value="$existing_value" + if [[ -n "$normalized_existing_value" && "$existing_separator" == ";" ]]; then + normalized_existing_value=${normalized_existing_value//;/:} + fi + + local existing_has_named_volume_override=false + if [[ -n "$normalized_existing_value" && "$normalized_existing_value" == *"docker-compose.arm.yml"* ]]; then + existing_has_named_volume_override=true + fi + + local needs_separator_normalization=false + if [[ "$configured_separator" == ";" || "$normalized_existing_value" != "$existing_value" ]]; then + needs_separator_normalization=true + fi + + if [[ "$force" != "--force" && "$existing_has_named_volume_override" == "true" && "$needs_separator_normalization" != "true" ]]; then + return 2 + fi + + local tmp_file + tmp_file=$(mktemp) || return 1 + + grep -vE '^[[:space:]]*COMPOSE_FILE[[:space:]]*=' "$env_file" \ + | grep -vE '^[[:space:]]*COMPOSE_PATH_SEPARATOR[[:space:]]*=' \ + > "$tmp_file" || true + + if [[ -s "$tmp_file" ]] && [[ -n "$(tail -c1 "$tmp_file")" ]]; then + printf '\n' >> "$tmp_file" + fi + + local compose_file_value="docker-compose.yml:docker-compose.arm.yml" + if [[ "$force" != "--force" && "$existing_has_named_volume_override" == "true" ]]; then + compose_file_value="$normalized_existing_value" + elif [[ -n "$normalized_existing_value" && "$force" != "--force" ]]; then + compose_file_value="${normalized_existing_value}:docker-compose.arm.yml" + fi + + { + printf '# Use named-volume database storage (managed by Youtarr)\n' + printf 'COMPOSE_PATH_SEPARATOR=:\n' + printf 'COMPOSE_FILE=%s\n' "$compose_file_value" + } >> "$tmp_file" + + local mv_rc=0 + if [[ "$use_sudo" == "--use-sudo" ]]; then + sudo -n mv "$tmp_file" "$env_file" || mv_rc=$? + else + mv "$tmp_file" "$env_file" || mv_rc=$? + fi + + if [[ "$mv_rc" -ne 0 ]]; then + rm -f "$tmp_file" 2>/dev/null || true + return 1 + fi +} + +# Returns 0 if the COMPOSE_FILE value in .env contains docker-compose.arm.yml, +# 1 otherwise (including when COMPOSE_FILE is unset). Used to detect whether +# plain `docker compose up -d` would include the named-volume override. +youtarr_env_has_named_volume_pin() { + local env_file="$1" + [[ -f "$env_file" ]] || return 1 + local value + value=$(grep -E '^[[:space:]]*COMPOSE_FILE[[:space:]]*=' "$env_file" 2>/dev/null | tail -n 1 || true) + [[ -n "$value" ]] || return 1 + [[ "$value" == *"docker-compose.arm.yml"* ]] +} diff --git a/scripts/_shared_start_tasks.sh b/scripts/_shared_start_tasks.sh index 679c66a5..cdc7bc82 100755 --- a/scripts/_shared_start_tasks.sh +++ b/scripts/_shared_start_tasks.sh @@ -5,6 +5,8 @@ START_SCRIPT_NAME=$(basename "$0") # shellcheck source=scripts/_console_output.sh source "$SHARED_SCRIPT_DIR/_console_output.sh" +# shellcheck source=scripts/_env_helpers.sh +source "$SHARED_SCRIPT_DIR/_env_helpers.sh" print_usage() { cat <" + yt_detail " Keep the named volume: mv ./database ./database.unused.\$(date +%Y%m%d)" + yt_detail "" + yt_detail "If you are migrating from the bind mount to a named volume for the first time," + yt_detail "use the helper instead: ./scripts/migrate-to-named-volume.sh" + exit 1 + fi + + if [ "$USE_ARM" == "true" ]; then + if [ "$DATABASE_HAS_CONTENT" == "true" ]; then + # Existing bind-mounted installs keep using their current database even + # when --arm is passed. Silently switching would make their data appear + # to vanish behind an empty named volume. + unset COMPOSE_FILES + yt_warn "--arm requested, but bind-mounted MariaDB data was detected in ./database/." + yt_warn "Keeping the existing bind-mounted database for this run." + yt_detail "To move this install to named-volume storage, run:" + yt_detail " ./scripts/migrate-to-named-volume.sh" + else + # Explicit --arm uses the named-volume override when there is no bind data to preserve. + prepare_named_volume_compose_selection + fi + elif [ "$DETECTED_ARM" == "true" ]; then + if [ "$DATABASE_HAS_CONTENT" == "true" ]; then + # Existing bind-mounted installs keep using their current database on ARM. + unset COMPOSE_FILES + yt_warn "ARM host detected, but bind-mounted MariaDB data was detected in ./database/." + yt_warn "Keeping the existing bind-mounted database for this run." + yt_detail "To move this install to named-volume storage, run:" + yt_detail " ./scripts/migrate-to-named-volume.sh" + else + # ARM fresh installs use the named-volume override. + prepare_named_volume_compose_selection + yt_info "ARM host detected; using named-volume database storage." + fi + elif [ "$NAMED_VOLUME_EXISTS" == "true" ]; then + # An existing named volume with no bind-mount data: keep using the named volume. + # This is the common case after a successful migration, or after the user manually + # switched to the named-volume override on a previous run. + prepare_named_volume_compose_selection + yt_info "Existing named-volume database detected; using named-volume storage." + elif [ "$DATABASE_HAS_CONTENT" != "true" ]; then + # Fresh bundled-DB installs use the named-volume override on every platform. + prepare_named_volume_compose_selection + yt_info "Fresh install detected; using named-volume database storage." + else + # Default - use standard docker-compose.yml (existing bind-mounted install). + unset COMPOSE_FILES + yt_warn "Bind-mounted MariaDB data detected in ./database/." + yt_detail "Docker Desktop on Windows/Mac and some NAS/virtualized filesystems can corrupt bind-mounted MariaDB during schema migrations." + yt_detail "Linux native Docker hosts are usually unaffected. To migrate to a named volume, run:" + yt_detail " ./scripts/migrate-to-named-volume.sh" + fi fi yt_section "Docker" +# shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_FILES intentionally expand into command/flag words. $COMPOSE_CMD $COMPOSE_FILES down yt_success "Existing containers stopped." diff --git a/scripts/_start_template.sh b/scripts/_start_template.sh index b378c5ff..a718d361 100755 --- a/scripts/_start_template.sh +++ b/scripts/_start_template.sh @@ -45,15 +45,8 @@ fi # shellcheck source=scripts/_shared_start_tasks.sh source "$SCRIPT_DIR/_shared_start_tasks.sh" "$@" -# Detect ARM architecture (Apple Silicon, Raspberry Pi, etc.) -ARCH=$(uname -m) -IS_ARM=false -if [[ "$ARCH" == "arm64" || "$ARCH" == "aarch64" ]]; then - IS_ARM=true - yt_info "Detected ARM architecture ($ARCH) - using named volume for MariaDB" -fi - # Bring up the stack using the selected compose files (COMPOSE_FILES is set in _shared_start_tasks.sh) +# shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_FILES intentionally expand into command/flag words. $COMPOSE_CMD $COMPOSE_FILES up -d yt_section "Environment" diff --git a/scripts/backup.sh b/scripts/backup.sh index b9b2db5f..057b470c 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -6,6 +6,8 @@ PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" # shellcheck source=scripts/_console_output.sh source "$SCRIPT_DIR/_console_output.sh" +# shellcheck source=scripts/_env_helpers.sh +source "$SCRIPT_DIR/_env_helpers.sh" # Default configuration OUTPUT_DIR="$PROJECT_DIR/backups" @@ -112,7 +114,8 @@ fi COMPOSE_CMD=$(get_compose_command) yt_info "Docker compose command: $COMPOSE_CMD" -# Detect ARM architecture +# Detect ARM architecture for backup metadata only. Database storage selection +# is based on the configured/actual Youtarr storage mode below. ARCH=$(uname -m) IS_ARM=false if [[ "$ARCH" == "arm64" || "$ARCH" == "aarch64" ]]; then @@ -209,11 +212,27 @@ fi yt_section "Database Backup" mkdir -p "$BACKUP_DIR/database" -# Determine compose args based on architecture (needed for starting/stopping db) -if [[ "$IS_ARM" == "true" ]]; then - COMPOSE_ARGS="-f $PROJECT_DIR/docker-compose.yml -f $PROJECT_DIR/docker-compose.arm.yml" -else - COMPOSE_ARGS="-f $PROJECT_DIR/docker-compose.yml" +DB_STORAGE_MODE=$(youtarr_detect_bundled_db_storage_mode "$PROJECT_DIR") +if [[ "$DB_STORAGE_MODE" == "ambiguous" ]]; then + if [[ "$DB_RUNNING" == "true" ]]; then + yt_warn "Both ./database/ and the named MariaDB volume exist; dumping the currently running database container." + else + yt_error "Ambiguous database storage: both ./database/ and the Docker named volume exist." + yt_detail "Refusing to start a database for backup because that could dump the wrong storage." + yt_detail "Start Youtarr normally and re-run backup, or remove the unused storage first." + exit 1 + fi +fi + +if [[ "$DB_STORAGE_MODE" == "named-volume" ]]; then + yt_info "Backup storage mode: named-volume database." +elif [[ "$DB_STORAGE_MODE" == "bind-mount" ]]; then + yt_info "Backup storage mode: bind-mounted ./database/." +fi + +COMPOSE_ARGS="" +if [[ "$DB_STORAGE_MODE" != "ambiguous" ]]; then + COMPOSE_ARGS=$(youtarr_compose_args_for_storage_mode "$PROJECT_DIR" "$DB_STORAGE_MODE") fi DB_STARTED_FOR_BACKUP=false @@ -225,6 +244,7 @@ if [[ "$DB_RUNNING" == "false" ]]; then export YOUTUBE_OUTPUT_DIR="${YOUTUBE_OUTPUT_DIR:-/tmp}" # Start only the database container + # shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_ARGS intentionally expand into command/flag words. (cd "$PROJECT_DIR" && $COMPOSE_CMD $COMPOSE_ARGS up -d youtarr-db) DB_STARTED_FOR_BACKUP=true @@ -243,6 +263,7 @@ if [[ "$DB_RUNNING" == "false" ]]; then if [[ $WAITED -ge $MAX_WAIT ]]; then yt_error "Database failed to become ready within ${MAX_WAIT}s" # Clean up + # shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_ARGS intentionally expand into command/flag words. (cd "$PROJECT_DIR" && $COMPOSE_CMD $COMPOSE_ARGS down) rm -rf "$STAGING_DIR" exit 1 @@ -266,6 +287,7 @@ else yt_error "Database dump failed" # Clean up if we started the container if [[ "$DB_STARTED_FOR_BACKUP" == "true" ]]; then + # shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_ARGS intentionally expand into command/flag words. (cd "$PROJECT_DIR" && $COMPOSE_CMD $COMPOSE_ARGS down) fi rm -rf "$STAGING_DIR" @@ -275,6 +297,7 @@ fi # Stop database if we started it if [[ "$DB_STARTED_FOR_BACKUP" == "true" ]]; then yt_info "Stopping database container..." + # shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_ARGS intentionally expand into command/flag words. (cd "$PROJECT_DIR" && $COMPOSE_CMD $COMPOSE_ARGS down) yt_success "Database container stopped" fi diff --git a/scripts/migrate-to-named-volume.sh b/scripts/migrate-to-named-volume.sh new file mode 100755 index 00000000..1fabe6dc --- /dev/null +++ b/scripts/migrate-to-named-volume.sh @@ -0,0 +1,603 @@ +#!/usr/bin/env bash +set -eo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# shellcheck source=scripts/_console_output.sh +source "$SCRIPT_DIR/_console_output.sh" +# shellcheck source=scripts/_env_helpers.sh +source "$SCRIPT_DIR/_env_helpers.sh" + +FORCE=false +STAGING_DIR="" +COMPOSE_CMD="" +ENV_BACKUP_PATH="" +BACKUP_DIR_NAME="" + +print_usage() { + cat </ + - .env is snapshotted to ./.env.bak. + - .env is updated so future docker compose and ./start.sh runs use the + named-volume override + +Options: + --force Skip the confirmation prompt + --help Show this help message +EOF +} + +cleanup() { + if [[ -n "$STAGING_DIR" ]] && [[ -d "$STAGING_DIR" ]]; then + rm -rf "$STAGING_DIR" 2>/dev/null || true + fi +} +trap cleanup EXIT + +print_error_log() { + local log_file="$1" + if [[ -s "$log_file" ]]; then + yt_detail "Error output (last 40 lines):" + while IFS= read -r line; do + yt_detail " $line" + done < <(tail -n 40 "$log_file") + fi +} + +get_compose_command() { + if docker compose version &>/dev/null; then + echo "docker compose" + elif docker-compose version &>/dev/null; then + echo "docker-compose" + else + yt_error "Neither 'docker compose' nor 'docker-compose' command found." + exit 1 + fi +} + +get_env_value() { + local key="$1" + local default="${2:-}" + local value="${!key:-}" + + if [[ -n "$value" ]]; then + printf '%s' "$value" + return + fi + + if [[ -f "$PROJECT_DIR/.env" ]]; then + value=$(grep -E "^[[:space:]]*${key}[[:space:]]*=" "$PROJECT_DIR/.env" 2>/dev/null \ + | tail -n 1 \ + | sed -E "s/^[[:space:]]*${key}[[:space:]]*=[[:space:]]*//" \ + | sed -E 's/[[:space:]]+#.*$//' \ + | sed -E 's/[[:space:]]+$//' \ + | sed -E 's/^"(.*)"$/\1/' \ + | sed -E "s/^'(.*)'$/\1/" || true) + fi + + if [[ -n "$value" ]]; then + printf '%s' "$value" + else + printf '%s' "$default" + fi +} + +database_dir_has_real_content() { + [[ -f "$PROJECT_DIR/database/ibdata1" || -d "$PROJECT_DIR/database/mysql" ]] +} + +# Single, consistent message for any failure that happens after we have +# renamed ./database/ to the timestamped backup but before the migration is +# fully verified. .env has not been touched at this point, so the user can +# get back to a clean pre-migration state with a few commands. +print_post_rename_failure_help() { + yt_detail "" + yt_detail "Your .env file was NOT modified." + yt_detail "Your original database is preserved at ./$BACKUP_DIR_NAME/." + yt_detail "" + yt_detail "To return to the pre-migration state:" + yt_detail " 1. Restore the database directory:" + yt_detail " mv ./$BACKUP_DIR_NAME ./database" + yt_detail " 2. Remove the partially-populated named volume:" + yt_detail " docker volume ls --format '{{.Name}}' | grep youtarr-db-data" + yt_detail " docker volume rm " + yt_detail " 3. Start normally:" + yt_detail " ./start.sh" +} + +wait_for_db_ready() { + local db_user="$1" + local db_password="$2" + local db_port="$3" + local max_wait=180 + local waited=0 + + while [[ $waited -lt $max_wait ]]; do + if docker exec youtarr-db mysqladmin ping -h localhost -P "$db_port" -u "$db_user" -p"$db_password" &>/dev/null; then + if docker exec youtarr-db mysql -h 127.0.0.1 -P "$db_port" -u "$db_user" -p"$db_password" -e "SELECT 1;" &>/dev/null; then + return 0 + fi + fi + sleep 2 + waited=$((waited + 2)) + done + + return 1 +} + +# Distinguishes "MariaDB never came up" from "MariaDB is up but our credentials are wrong." +# Probes with a deliberately-bogus user; "Access denied" means the server is alive +# and answering, so the failure mode is auth, not startup. +db_running_but_auth_rejected() { + local db_port="$1" + local probe_output + probe_output=$(docker exec youtarr-db mysqladmin ping \ + -h 127.0.0.1 -P "$db_port" \ + -u __youtarr_migrate_probe -p__not_a_real_password 2>&1) || true + [[ "$probe_output" == *"Access denied"* ]] +} + +print_db_auth_failure_hint() { + yt_detail "MariaDB is running, but the credentials in .env did not authenticate." + yt_detail "The bundled compose seeds MYSQL_ROOT_PASSWORD from DB_ROOT_PASSWORD on the very first DB" + yt_detail "init only; subsequent edits to DB_ROOT_PASSWORD do not change an existing DB. The app connects with DB_USER" + yt_detail "and DB_PASSWORD, so DB_PASSWORD in .env must match the password that was actually seeded." + yt_detail "Verify which password Youtarr currently uses to connect, set DB_PASSWORD in .env to that value," + yt_detail "then re-run this script." +} + +stop_started_db() { + local compose_file_args="$1" + if [[ -n "$COMPOSE_CMD" ]]; then + # shellcheck disable=SC2086 # COMPOSE_CMD and compose_file_args intentionally expand into command/flag words. + (cd "$PROJECT_DIR" && $COMPOSE_CMD $compose_file_args down) >/dev/null 2>&1 || true + fi +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --force) + FORCE=true + shift + ;; + --help) + print_usage + exit 0 + ;; + *) + yt_error "Unknown option: $1" + print_usage + exit 1 + ;; + esac +done + +yt_banner "Youtarr DB Migration: bind mount to named volume" + +if [[ ! -f "$PROJECT_DIR/docker-compose.yml" || ! -f "$PROJECT_DIR/docker-compose.arm.yml" ]]; then + yt_error "docker-compose.yml and docker-compose.arm.yml must both exist." + exit 1 +fi + +if [[ ! -f "$PROJECT_DIR/.env" ]]; then + yt_error "No .env file found. Run ./start.sh once before migrating." + exit 1 +fi + +if ! docker info >/dev/null 2>&1; then + yt_error "Docker does not appear to be running." + exit 1 +fi + +if ! database_dir_has_real_content; then + yt_error "No bind-mounted MariaDB data found in ./database/." + yt_detail "This script only migrates existing bind-mounted bundled MariaDB installs." + exit 1 +fi + +COMPOSE_FILE_VALUE=$(get_env_value "COMPOSE_FILE" "") +if [[ "$COMPOSE_FILE_VALUE" == *"docker-compose.external-db.yml"* || "${COMPOSE_FILE:-}" == *"docker-compose.external-db.yml"* ]]; then + yt_error "External database configuration detected." + yt_detail "This script only migrates the bundled MariaDB container." + exit 1 +fi + +if [[ "$COMPOSE_FILE_VALUE" == *"docker-compose.arm.yml"* || "${COMPOSE_FILE:-}" == *"docker-compose.arm.yml"* ]]; then + yt_error "Named-volume override already appears to be configured." + yt_detail "Refusing to guess whether ./database/ is active or leftover data." + exit 1 +fi + +if [[ -n "${COMPOSE_FILE:-}" ]]; then + yt_error "COMPOSE_FILE is set in your shell environment: ${COMPOSE_FILE}" + yt_detail "Unset it before migrating so the .env named-volume pin will take effect." + yt_detail "Run: unset COMPOSE_FILE" + exit 1 +fi + +if [[ -n "${COMPOSE_PATH_SEPARATOR:-}" && "${COMPOSE_PATH_SEPARATOR}" != ":" ]]; then + yt_error "COMPOSE_PATH_SEPARATOR is set in your shell environment to: ${COMPOSE_PATH_SEPARATOR}" + yt_detail "Unset it or set it to ':' before migrating so Compose reads the new .env pin correctly." + exit 1 +fi + +COMPOSE_FILE_TRIMMED=$(printf '%s' "$COMPOSE_FILE_VALUE" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//') +if [[ -n "$COMPOSE_FILE_TRIMMED" && "$COMPOSE_FILE_TRIMMED" != "docker-compose.yml" ]]; then + yt_error "Custom COMPOSE_FILE detected: $COMPOSE_FILE_VALUE" + yt_detail "This migration only supports the default bundled MariaDB stack." + yt_detail "Restore custom compose behavior from .env after migration, or migrate manually." + exit 1 +fi + +COMPOSE_PATH_SEPARATOR_VALUE=$(get_env_value "COMPOSE_PATH_SEPARATOR" "") +if [[ -n "$COMPOSE_FILE_TRIMMED" || -n "$COMPOSE_PATH_SEPARATOR_VALUE" ]]; then + yt_warn "Existing COMPOSE_FILE/COMPOSE_PATH_SEPARATOR settings will be replaced in .env." + yt_detail "A full .env snapshot will be written before any change: ./.env.bak." +fi + +COMPOSE_CMD=$(get_compose_command) +yt_info "Docker compose command: $COMPOSE_CMD" + +EXPECTED_DB_VOLUME="$(youtarr_expected_db_volume_name "$PROJECT_DIR")" +if docker volume inspect "$EXPECTED_DB_VOLUME" >/dev/null 2>&1; then + yt_error "Named-volume database already exists: $EXPECTED_DB_VOLUME" + yt_detail "Remove it only if you are certain it is empty or disposable, then re-run this script." + exit 1 +fi + +# Pre-flight permissions check. We need to write a .env backup and rename +# ./database/ to ./database.bind-mount-backup./. If the user can't do +# either directly, we want to know now (before the dump) rather than stalling +# on an interactive sudo prompt mid-migration on a headless box. +USE_SUDO_FOR_FS=false +PRE_FLIGHT_TEST_PATH="$PROJECT_DIR/.youtarr-migrate-write-test.$$" +if touch "$PRE_FLIGHT_TEST_PATH" 2>/dev/null; then + rm -f "$PRE_FLIGHT_TEST_PATH" +else + if sudo -n true 2>/dev/null; then + USE_SUDO_FOR_FS=true + yt_warn "Cannot write to $PROJECT_DIR directly; will use passwordless sudo for filesystem operations." + else + yt_error "Cannot write to $PROJECT_DIR and passwordless sudo is not available." + yt_detail "This migration must rename ./database/ to ./database.bind-mount-backup./" + yt_detail "and write a .env backup, both in $PROJECT_DIR." + yt_detail "" + yt_detail "Resolve one of these and re-run:" + yt_detail " - Re-run the script as root: sudo $(basename "$0")" + yt_detail " - Configure passwordless sudo for the current user" + yt_detail " - Make $PROJECT_DIR writable by the current user (chown / chmod)" + exit 1 + fi +fi + +DB_USER=$(get_env_value "DB_USER" "root") +DB_PASSWORD=$(get_env_value "DB_PASSWORD" "123qweasd") +DB_ROOT_PASSWORD=$(get_env_value "DB_ROOT_PASSWORD" "123qweasd") +DB_NAME=$(get_env_value "DB_NAME" "youtarr") +DB_PORT=$(get_env_value "DB_PORT" "3321") +YOUTUBE_OUTPUT_DIR_VALUE=$(get_env_value "YOUTUBE_OUTPUT_DIR" "/tmp") +export YOUTUBE_OUTPUT_DIR="$YOUTUBE_OUTPUT_DIR_VALUE" + +if [[ "$DB_USER" == "root" && "$DB_ROOT_PASSWORD" != "$DB_PASSWORD" ]]; then + yt_error "DB_ROOT_PASSWORD and DB_PASSWORD differ in .env." + yt_detail "The fresh named-volume MariaDB initializes root from DB_ROOT_PASSWORD," + yt_detail "but this migration connects as root using DB_PASSWORD." + yt_detail "Set DB_ROOT_PASSWORD to the same value as DB_PASSWORD before migrating, then re-run this script." + exit 1 +fi + +TIMESTAMP=$(date +"%Y%m%d-%H%M%S") +BACKUP_DIR_NAME="database.bind-mount-backup.$TIMESTAMP" +ENV_BACKUP_PATH="$PROJECT_DIR/.env.bak.$TIMESTAMP" +STAGING_DIR=$(mktemp -d) +DUMP_PATH="$STAGING_DIR/youtarr-$TIMESTAMP.sql" +ERROR_LOG="$STAGING_DIR/error.log" + +yt_section "Plan" +yt_info "Bind-mount source: ./database/" +yt_info "Preserved as: ./$BACKUP_DIR_NAME/" +yt_info ".env snapshot: ./.env.bak.$TIMESTAMP" +yt_info "Target storage: Docker named volume from docker-compose.arm.yml" + +if [[ "$FORCE" != "true" ]]; then + echo "" + read -r -p "Type 'MIGRATE' to proceed or anything else to abort: " confirmation + if [[ "$confirmation" != "MIGRATE" ]]; then + yt_info "Aborted. Nothing changed." + exit 0 + fi +fi + +yt_section "Stopping Youtarr" +if [[ -x "$PROJECT_DIR/stop.sh" ]]; then + (cd "$PROJECT_DIR" && ./stop.sh) || yt_warn "stop.sh returned non-zero; continuing." +else + (cd "$PROJECT_DIR" && $COMPOSE_CMD down) || yt_warn "compose down returned non-zero; continuing." +fi + +yt_section "Dumping Bind-Mounted Database" +(cd "$PROJECT_DIR" && $COMPOSE_CMD -f docker-compose.yml up -d youtarr-db) + +if ! wait_for_db_ready "$DB_USER" "$DB_PASSWORD" "$DB_PORT"; then + yt_error "Bind-mounted database did not become ready." + if db_running_but_auth_rejected "$DB_PORT"; then + print_db_auth_failure_hint + fi + stop_started_db "-f docker-compose.yml" + exit 1 +fi + +: > "$ERROR_LOG" +if ! SOURCE_TABLE_COUNT=$(docker exec youtarr-db mysql -N -B -h 127.0.0.1 -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" \ + -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DB_NAME';" 2>"$ERROR_LOG"); then + yt_error "Could not inspect source database tables." + print_error_log "$ERROR_LOG" + stop_started_db "-f docker-compose.yml" + exit 1 +fi + +if [[ ! "$SOURCE_TABLE_COUNT" =~ ^[0-9]+$ ]]; then + yt_error "Unexpected source table count: $SOURCE_TABLE_COUNT" + stop_started_db "-f docker-compose.yml" + exit 1 +fi + +if [[ "$SOURCE_TABLE_COUNT" -lt 1 ]]; then + yt_warn "Source database has no tables; nothing needs to be migrated." + yt_detail "For a fresh named-volume install, stop the stack, move or remove ./database/, then run ./start.sh." + stop_started_db "-f docker-compose.yml" + exit 0 +fi + +# Capture the source database's default charset/collation so we can preserve it +# on the target. mysqldump emits per-table CHARSET/COLLATE, but any future +# migrations or ALTER TABLE ... CONVERT TO operations inherit the DB-level +# default; silently switching it can change sort/compare semantics. If the +# source default is not utf8mb4_*, fall back to utf8mb4_unicode_ci and warn. +TARGET_DB_CHARSET="utf8mb4" +TARGET_DB_COLLATION="utf8mb4_unicode_ci" + +: > "$ERROR_LOG" +SOURCE_DB_DEFAULTS=$(docker exec youtarr-db mysql -N -B -h 127.0.0.1 -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" \ + -e "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$DB_NAME';" 2>"$ERROR_LOG") || { + yt_error "Could not read source database default charset/collation." + print_error_log "$ERROR_LOG" + stop_started_db "-f docker-compose.yml" + exit 1 +} + +SOURCE_DB_CHARSET="" +SOURCE_DB_COLLATION="" +if [[ -n "$SOURCE_DB_DEFAULTS" ]]; then + read -r SOURCE_DB_CHARSET SOURCE_DB_COLLATION <<<"$SOURCE_DB_DEFAULTS" +fi + +if [[ "$SOURCE_DB_CHARSET" == "utf8mb4" && "$SOURCE_DB_COLLATION" == utf8mb4_* ]]; then + TARGET_DB_COLLATION="$SOURCE_DB_COLLATION" + yt_info "Source database default: $SOURCE_DB_CHARSET / $SOURCE_DB_COLLATION (will reuse on target)" +else + yt_warn "Source database default is '${SOURCE_DB_CHARSET:-unknown}' / '${SOURCE_DB_COLLATION:-unknown}'; target will use $TARGET_DB_CHARSET / $TARGET_DB_COLLATION." + yt_detail "Per-table CHARSET/COLLATE in the dump is preserved verbatim, so existing tables keep their column collations. This only affects the DB-level default used by future ALTERs or new tables." +fi + +: > "$ERROR_LOG" +if ! docker exec youtarr-db mysqldump \ + --single-transaction \ + --routines --triggers --events \ + -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" > "$DUMP_PATH" 2>"$ERROR_LOG"; then + yt_error "mysqldump failed. Your ./database/ data was not modified." + print_error_log "$ERROR_LOG" + stop_started_db "-f docker-compose.yml" + exit 1 +fi + +yt_success "Database dump created." + +# Capture per-table row counts from the source DB so we can verify the import is +# complete (not just structurally valid). A truncated dump (disk full, broken +# pipe) can produce a syntactically valid SQL file with all CREATE TABLEs but +# missing INSERTs; the table-count check alone wouldn't notice that. +SOURCE_COUNTS_FILE="$STAGING_DIR/source_counts.tsv" +: > "$SOURCE_COUNTS_FILE" +: > "$ERROR_LOG" +SOURCE_TABLE_LIST=$(docker exec youtarr-db mysql -N -B -h 127.0.0.1 -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" \ + -e "SELECT table_name FROM information_schema.tables WHERE table_schema='$DB_NAME' AND table_type='BASE TABLE';" 2>"$ERROR_LOG") || { + yt_error "Could not enumerate source tables for verification." + print_error_log "$ERROR_LOG" + stop_started_db "-f docker-compose.yml" + exit 1 +} + +while IFS= read -r table; do + [ -z "$table" ] && continue + count=$(docker exec youtarr-db mysql -N -B -h 127.0.0.1 -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" \ + -e "SELECT COUNT(*) FROM \`$table\`;" 2>"$ERROR_LOG") || { + yt_error "Could not count rows in source table: $table" + print_error_log "$ERROR_LOG" + stop_started_db "-f docker-compose.yml" + exit 1 + } + printf '%s\t%s\n' "$table" "$count" >> "$SOURCE_COUNTS_FILE" +done <<< "$SOURCE_TABLE_LIST" + +yt_success "Captured row counts for $(wc -l < "$SOURCE_COUNTS_FILE" | tr -d ' ') source tables." +stop_started_db "-f docker-compose.yml" + +yt_section "Preserving Bind-Mount Directory" +RENAME_RC=0 +if [ "$USE_SUDO_FOR_FS" = "true" ]; then + sudo -n mv "$PROJECT_DIR/database" "$PROJECT_DIR/$BACKUP_DIR_NAME" || RENAME_RC=$? +else + mv "$PROJECT_DIR/database" "$PROJECT_DIR/$BACKUP_DIR_NAME" || RENAME_RC=$? +fi +if [ "$RENAME_RC" -ne 0 ]; then + yt_error "Failed to rename ./database/. No configuration changes were made." + exit 1 +fi +if [ "$USE_SUDO_FOR_FS" = "true" ]; then + yt_success "Renamed ./database/ to ./$BACKUP_DIR_NAME/ (using sudo)" +else + yt_success "Renamed ./database/ to ./$BACKUP_DIR_NAME/" +fi + +# IMPORTANT: .env is NOT modified here. We hold off on rewriting .env until +# after the import is fully verified, so that any failure in this section +# leaves the user's .env intact and recovery is just a directory rename. +# The migration steps below all use explicit -f flags and do not depend on +# the .env pin to function. + +yt_section "Importing Into Named Volume" +(cd "$PROJECT_DIR" && $COMPOSE_CMD -f docker-compose.yml -f docker-compose.arm.yml up -d youtarr-db) + +if ! wait_for_db_ready "$DB_USER" "$DB_PASSWORD" "$DB_PORT"; then + yt_error "Named-volume database did not become ready." + if db_running_but_auth_rejected "$DB_PORT"; then + print_db_auth_failure_hint + fi + print_post_rename_failure_help + stop_started_db "-f docker-compose.yml -f docker-compose.arm.yml" + exit 1 +fi + +# Belt-and-suspenders: the up-front docker-volume-inspect check guesses the project +# name; if Compose ended up reusing a stale volume under a slightly different name, +# our DROP/CREATE below would wipe whatever was in it. Refuse instead. +TARGET_TABLE_COUNT=$(docker exec youtarr-db mysql -N -B -h 127.0.0.1 -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" \ + -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DB_NAME';" 2>/dev/null || echo "") +if [[ "$TARGET_TABLE_COUNT" =~ ^[0-9]+$ ]] && [[ "$TARGET_TABLE_COUNT" -gt 0 ]]; then + yt_error "Target named-volume database is not empty: $TARGET_TABLE_COUNT tables already in '$DB_NAME'." + yt_detail "Refusing to overwrite an existing volume." + print_post_rename_failure_help + stop_started_db "-f docker-compose.yml -f docker-compose.arm.yml" + exit 1 +fi + +: > "$ERROR_LOG" +if ! docker exec youtarr-db mysql -h 127.0.0.1 -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" \ + -e "DROP DATABASE IF EXISTS \`$DB_NAME\`; CREATE DATABASE \`$DB_NAME\` CHARACTER SET $TARGET_DB_CHARSET COLLATE $TARGET_DB_COLLATION;" 2>"$ERROR_LOG"; then + yt_error "Failed to prepare named-volume database for import." + print_error_log "$ERROR_LOG" + print_post_rename_failure_help + stop_started_db "-f docker-compose.yml -f docker-compose.arm.yml" + exit 1 +fi + +: > "$ERROR_LOG" +if ! docker exec -i youtarr-db mysql -h 127.0.0.1 -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" < "$DUMP_PATH" 2>"$ERROR_LOG"; then + yt_error "SQL import failed." + print_error_log "$ERROR_LOG" + print_post_rename_failure_help + stop_started_db "-f docker-compose.yml -f docker-compose.arm.yml" + exit 1 +fi + +TABLE_COUNT=$(docker exec youtarr-db mysql -N -B -h 127.0.0.1 -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" \ + -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DB_NAME';" 2>/dev/null || echo "0") +if [[ ! "$TABLE_COUNT" =~ ^[0-9]+$ ]] || [[ "$TABLE_COUNT" -lt 1 ]]; then + yt_error "Import verification failed: no tables found." + print_post_rename_failure_help + stop_started_db "-f docker-compose.yml -f docker-compose.arm.yml" + exit 1 +fi + +if [[ "$TABLE_COUNT" != "$SOURCE_TABLE_COUNT" ]]; then + yt_error "Import verification failed: source had $SOURCE_TABLE_COUNT tables, target has $TABLE_COUNT." + print_post_rename_failure_help + stop_started_db "-f docker-compose.yml -f docker-compose.arm.yml" + exit 1 +fi + +# Per-table row count verification. Catches truncated imports that produced all +# tables but only a partial set of rows. +MISMATCH_REPORT="$STAGING_DIR/mismatch.txt" +: > "$MISMATCH_REPORT" +MISMATCH_COUNT=0 +while IFS=$'\t' read -r table src_count; do + [ -z "$table" ] && continue + tgt_count=$(docker exec youtarr-db mysql -N -B -h 127.0.0.1 -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" \ + -e "SELECT COUNT(*) FROM \`$table\`;" 2>/dev/null) || tgt_count="MISSING" + if [[ "$tgt_count" != "$src_count" ]]; then + printf ' %s: source=%s target=%s\n' "$table" "$src_count" "$tgt_count" >> "$MISMATCH_REPORT" + MISMATCH_COUNT=$((MISMATCH_COUNT + 1)) + fi +done < "$SOURCE_COUNTS_FILE" + +if [ "$MISMATCH_COUNT" -gt 0 ]; then + yt_error "Per-table row count verification failed: $MISMATCH_COUNT table(s) differ." + while IFS= read -r line; do yt_detail "$line"; done < "$MISMATCH_REPORT" + print_post_rename_failure_help + stop_started_db "-f docker-compose.yml -f docker-compose.arm.yml" + exit 1 +fi + +yt_success "Import verified: $TABLE_COUNT tables, all per-table row counts match source." + +# Verification passed. Now (and only now) update .env so future plain +# `docker compose up -d` runs pick up the named-volume override. +yt_section "Updating .env" +SNAPSHOT_RC=0 +if [ "$USE_SUDO_FOR_FS" = "true" ]; then + sudo -n cp "$PROJECT_DIR/.env" "$ENV_BACKUP_PATH" || SNAPSHOT_RC=$? +else + cp "$PROJECT_DIR/.env" "$ENV_BACKUP_PATH" || SNAPSHOT_RC=$? +fi +if [ "$SNAPSHOT_RC" -ne 0 ]; then + yt_error "Failed to snapshot .env to $ENV_BACKUP_PATH." + yt_detail "Migration data is in place, but .env was not pinned. Either retry the snapshot manually" + yt_detail "or pin the override yourself by adding these two lines to .env:" + yt_detail " COMPOSE_PATH_SEPARATOR=:" + yt_detail " COMPOSE_FILE=docker-compose.yml:docker-compose.arm.yml" + stop_started_db "-f docker-compose.yml -f docker-compose.arm.yml" + exit 1 +fi + +PIN_ARGS=(--force) +if [ "$USE_SUDO_FOR_FS" = "true" ]; then + PIN_ARGS+=(--use-sudo) +fi +if ! youtarr_pin_named_volume_in_env "$PROJECT_DIR/.env" "${PIN_ARGS[@]}"; then + yt_error "Failed to update .env." + yt_detail "Restore .env with: mv ./.env.bak.$TIMESTAMP .env" + yt_detail "Then add these two lines to .env manually:" + yt_detail " COMPOSE_PATH_SEPARATOR=:" + yt_detail " COMPOSE_FILE=docker-compose.yml:docker-compose.arm.yml" + stop_started_db "-f docker-compose.yml -f docker-compose.arm.yml" + exit 1 +fi +yt_success "Pinned docker-compose.arm.yml named-volume override in .env." + +yt_section "Starting Full Stack" +if (cd "$PROJECT_DIR" && $COMPOSE_CMD -f docker-compose.yml -f docker-compose.arm.yml up -d); then + yt_success "Youtarr is now running on the named-volume database." +else + yt_warn "Could not auto-start the full Youtarr stack." + yt_detail "Bring it up manually with: ./start.sh" +fi + +yt_section "Migration Complete" +yt_success "Youtarr is now using the named-volume database override." +yt_detail "" +yt_detail "Going forward, any of these will start the stack against the named volume:" +yt_detail " Recommended: ./start.sh" +yt_detail " Plain docker compose: docker compose up -d" +yt_detail " (this script pinned COMPOSE_FILE in .env, so plain compose" +yt_detail " will pick up the named-volume override automatically)" +yt_detail " Without the .env pin: docker compose -f docker-compose.yml -f docker-compose.arm.yml up -d" +yt_detail "" +yt_detail "Backups (preserved for safety):" +yt_detail " Original DB: ./$BACKUP_DIR_NAME/" +yt_detail " Original .env: ./.env.bak.$TIMESTAMP" +yt_detail "" +yt_detail "To revert: restore the .env backup, remove the named volume, rename the database backup" +yt_detail "back to ./database, then run ./start.sh." +yt_detail "" +yt_detail "After confirming the install is healthy for a few days, you can reclaim space by removing" +yt_detail "the backups above. This script will not delete them automatically." diff --git a/scripts/restore.sh b/scripts/restore.sh index 6cab73e4..fcbe7b97 100755 --- a/scripts/restore.sh +++ b/scripts/restore.sh @@ -6,6 +6,8 @@ PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" # shellcheck source=scripts/_console_output.sh source "$SCRIPT_DIR/_console_output.sh" +# shellcheck source=scripts/_env_helpers.sh +source "$SCRIPT_DIR/_env_helpers.sh" # Default configuration FORCE=false @@ -136,9 +138,7 @@ yt_info "Docker compose command: $COMPOSE_CMD" # Detect ARM architecture ARCH=$(uname -m) -IS_ARM=false if [[ "$ARCH" == "arm64" || "$ARCH" == "aarch64" ]]; then - IS_ARM=true yt_info "Detected ARM architecture ($ARCH)" fi @@ -318,17 +318,37 @@ elif [[ -f "$BACKUP_DIR/database/youtarr.sql" ]]; then DB_PORT=$(get_env_value "$PROJECT_DIR/.env" "DB_PORT" "3321") # Need YOUTUBE_OUTPUT_DIR for compose to work - export YOUTUBE_OUTPUT_DIR=$(get_env_value "$PROJECT_DIR/.env" "YOUTUBE_OUTPUT_DIR" "/tmp") + YOUTUBE_OUTPUT_DIR=$(get_env_value "$PROJECT_DIR/.env" "YOUTUBE_OUTPUT_DIR" "/tmp") + export YOUTUBE_OUTPUT_DIR + + DB_STORAGE_MODE=$(youtarr_detect_bundled_db_storage_mode "$PROJECT_DIR") + if [[ "$DB_STORAGE_MODE" == "ambiguous" ]]; then + yt_error "Ambiguous database storage: both ./database/ and the Docker named volume exist." + yt_detail "Refusing to restore because that could overwrite the wrong database." + yt_detail "Remove the unused storage, then re-run restore." + rm -rf "$EXTRACT_DIR" + exit 1 + fi - # Determine compose args based on architecture - if [[ "$IS_ARM" == "true" ]]; then - COMPOSE_ARGS="-f $PROJECT_DIR/docker-compose.yml -f $PROJECT_DIR/docker-compose.arm.yml" + if [[ "$DB_STORAGE_MODE" == "named-volume" ]]; then + yt_info "Restore storage mode: named-volume database." + if youtarr_pin_named_volume_in_env "$PROJECT_DIR/.env"; then + yt_detail "Pinned named-volume override in restored .env." + else + PIN_RC=$? + if [[ "$PIN_RC" -ne 2 ]]; then + yt_warn "Could not pin named-volume override in restored .env." + yt_detail "Start future runs with ./start.sh, or add docker-compose.arm.yml to COMPOSE_FILE manually." + fi + fi else - COMPOSE_ARGS="-f $PROJECT_DIR/docker-compose.yml" + yt_info "Restore storage mode: bind-mounted ./database/." fi + COMPOSE_ARGS=$(youtarr_compose_args_for_storage_mode "$PROJECT_DIR" "$DB_STORAGE_MODE") + # Clear existing database directory for fresh import (only for bind mount, not named volume) - if [[ "$IS_ARM" != "true" ]] && [[ -d "$PROJECT_DIR/database" ]]; then + if [[ "$DB_STORAGE_MODE" == "bind-mount" ]] && [[ -d "$PROJECT_DIR/database" ]]; then yt_info "Clearing existing database directory..." sudo rm -rf "$PROJECT_DIR/database" mkdir -p "$PROJECT_DIR/database" @@ -336,6 +356,7 @@ elif [[ -f "$BACKUP_DIR/database/youtarr.sql" ]]; then # Start database container yt_info "Starting database container..." + # shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_ARGS intentionally expand into command/flag words. (cd "$PROJECT_DIR" && $COMPOSE_CMD $COMPOSE_ARGS up -d youtarr-db) # Wait for database to be healthy @@ -357,6 +378,7 @@ elif [[ -f "$BACKUP_DIR/database/youtarr.sql" ]]; then if [[ $WAITED -ge $MAX_WAIT ]]; then yt_error "Database failed to become ready within ${MAX_WAIT}s" yt_detail "Try starting Youtarr normally with ./start.sh" + # shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_ARGS intentionally expand into command/flag words. (cd "$PROJECT_DIR" && $COMPOSE_CMD $COMPOSE_ARGS down) rm -rf "$EXTRACT_DIR" exit 1 @@ -386,6 +408,7 @@ elif [[ -f "$BACKUP_DIR/database/youtarr.sql" ]]; then else yt_error "Database import failed" yt_detail "Error: $IMPORT_ERROR" + # shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_ARGS intentionally expand into command/flag words. (cd "$PROJECT_DIR" && $COMPOSE_CMD $COMPOSE_ARGS down) rm -rf "$EXTRACT_DIR" exit 1 @@ -393,6 +416,7 @@ elif [[ -f "$BACKUP_DIR/database/youtarr.sql" ]]; then # Stop containers - let user start manually yt_info "Stopping database container..." + # shellcheck disable=SC2086 # COMPOSE_CMD and COMPOSE_ARGS intentionally expand into command/flag words. (cd "$PROJECT_DIR" && $COMPOSE_CMD $COMPOSE_ARGS down) yt_success "Database container stopped" else