musare.sh 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. #!/bin/bash
  2. export PATH=/usr/local/bin:/usr/bin:/bin
  3. CYAN='\033[33;36m';
  4. RED='\033[0;31m'
  5. YELLOW='\033[0;93m'
  6. GREEN='\033[0;32m'
  7. NC='\033[0m'
  8. scriptLocation=$(dirname -- "$(readlink -fn -- "$0"; echo x)")
  9. cd "${scriptLocation%x}" || exit 1
  10. if [[ -f .env ]]; then
  11. # shellcheck disable=SC1091
  12. source .env
  13. else
  14. echo -e "${RED}Error: .env does not exist${NC}"
  15. exit 2
  16. fi
  17. if [[ -z ${DOCKER_COMMAND} ]]; then
  18. DOCKER_COMMAND="docker"
  19. elif [[ ${DOCKER_COMMAND} != "docker" && ${DOCKER_COMMAND} != "podman" ]]; then
  20. echo -e "${RED}Error: Invalid DOCKER_COMMAND${NC}"
  21. exit 1
  22. fi
  23. docker="${DOCKER_COMMAND}"
  24. dockerCompose="${DOCKER_COMMAND}-compose"
  25. if [[ ! -x "$(command -v ${docker})" || ! -x "$(command -v ${dockerCompose})" ]]; then
  26. if [[ -x "$(command -v ${docker})" && ! -x "$(command -v ${dockerCompose})" ]]; then
  27. echo -e "${RED}Error: ${dockerCompose} not installed.${NC}"
  28. elif [[ ! -x "$(command -v ${docker})" && -x "$(command -v ${dockerCompose})" ]]; then
  29. echo -e "${RED}Error: ${docker} not installed.${NC}"
  30. else
  31. echo -e "${RED}Error: ${docker} and ${dockerCompose} not installed.${NC}"
  32. fi
  33. exit 1
  34. fi
  35. handleServices()
  36. {
  37. validServices=(backend frontend mongo redis)
  38. servicesArray=()
  39. invalidServices=false
  40. for x in "$@"; do
  41. if [[ ${validServices[*]} =~ (^|[[:space:]])"$x"($|[[:space:]]) ]]; then
  42. if ! [[ ${servicesArray[*]} =~ (^|[[:space:]])"$x"($|[[:space:]]) ]]; then
  43. servicesArray+=("${x}")
  44. fi
  45. else
  46. if [[ $invalidServices == false ]]; then
  47. invalidServices="${x}"
  48. else
  49. invalidServices="${invalidServices} ${x}"
  50. fi
  51. fi
  52. done
  53. if [[ $invalidServices == false && ${#servicesArray[@]} -gt 0 ]]; then
  54. echo "1|${servicesArray[*]}"
  55. elif [[ $invalidServices == false ]]; then
  56. echo "1|all"
  57. else
  58. echo "0|Invalid Service(s): ${invalidServices}"
  59. fi
  60. }
  61. runDockerCommand()
  62. {
  63. validCommands=(start stop restart pull build ps logs)
  64. if [[ ${validCommands[*]} =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; then
  65. servicesString=$(handleServices "${@:3}")
  66. if [[ ${servicesString:0:1} == 1 ]]; then
  67. if [[ ${servicesString:2:4} == "all" ]]; then
  68. servicesString=""
  69. else
  70. servicesString=${servicesString:2}
  71. fi
  72. if [[ ${CONTAINER_MODE} == "dev" ]]; then
  73. composeFiles="-f docker-compose.yml -f docker-compose.dev.yml"
  74. else
  75. composeFiles="-f docker-compose.yml"
  76. fi
  77. if [[ ${2} == "stop" || ${2} == "restart" ]]; then
  78. # shellcheck disable=SC2086
  79. ${dockerCompose} ${composeFiles} stop ${servicesString}
  80. fi
  81. if [[ ${2} == "start" || ${2} == "restart" ]]; then
  82. # shellcheck disable=SC2086
  83. ${dockerCompose} ${composeFiles} up -d ${servicesString}
  84. fi
  85. if [[ ${2} == "pull" || ${2} == "build" || ${2} == "ps" || ${2} == "logs" ]]; then
  86. # shellcheck disable=SC2086
  87. ${dockerCompose} ${composeFiles} "${2}" ${servicesString}
  88. fi
  89. exitValue=$?
  90. if [[ ${exitValue} -gt 0 ]]; then
  91. exit ${exitValue}
  92. fi
  93. else
  94. echo -e "${RED}${servicesString:2}\n${YELLOW}Usage: ${1} restart [backend, frontend, mongo, redis]${NC}"
  95. exit 1
  96. fi
  97. else
  98. echo -e "${RED}Error: Invalid runDockerCommand input${NC}"
  99. exit 1
  100. fi
  101. }
  102. case $1 in
  103. start)
  104. echo -e "${CYAN}Musare | Start Services${NC}"
  105. # shellcheck disable=SC2068
  106. runDockerCommand "$(basename "$0")" start ${@:2}
  107. ;;
  108. stop)
  109. echo -e "${CYAN}Musare | Stop Services${NC}"
  110. # shellcheck disable=SC2068
  111. runDockerCommand "$(basename "$0")" stop ${@:2}
  112. ;;
  113. restart)
  114. echo -e "${CYAN}Musare | Restart Services${NC}"
  115. # shellcheck disable=SC2068
  116. runDockerCommand "$(basename "$0")" restart ${@:2}
  117. ;;
  118. build)
  119. echo -e "${CYAN}Musare | Build Services${NC}"
  120. # shellcheck disable=SC2068
  121. runDockerCommand "$(basename "$0")" pull ${@:2}
  122. # shellcheck disable=SC2068
  123. runDockerCommand "$(basename "$0")" build ${@:2}
  124. ;;
  125. status)
  126. echo -e "${CYAN}Musare | Service Status${NC}"
  127. # shellcheck disable=SC2068
  128. runDockerCommand "$(basename "$0")" ps ${@:2}
  129. ;;
  130. reset)
  131. echo -e "${CYAN}Musare | Reset Services${NC}"
  132. servicesString=$(handleServices "${@:2}")
  133. if [[ ${servicesString:0:1} == 1 && ${servicesString:2:4} == "all" ]]; then
  134. echo -e "${RED}Resetting will remove the ${REDIS_DATA_LOCATION} and ${MONGO_DATA_LOCATION} directories.${NC}"
  135. echo -e "${GREEN}Are you sure you want to reset all data? ${YELLOW}[y,n]: ${NC}"
  136. read -r confirm
  137. if [[ "${confirm}" == y* ]]; then
  138. runDockerCommand "$(basename "$0")" stop
  139. ${dockerCompose} rm -v --force
  140. if [[ -d $REDIS_DATA_LOCATION ]]; then
  141. rm -rf "${REDIS_DATA_LOCATION}"
  142. fi
  143. if [[ -d $MONGO_DATA_LOCATION ]]; then
  144. rm -rf "${MONGO_DATA_LOCATION}"
  145. fi
  146. else
  147. echo -e "${RED}Cancelled reset${NC}"
  148. fi
  149. elif [[ ${servicesString:0:1} == 1 ]]; then
  150. if [[ "${servicesString:2}" == *redis* && "${servicesString:2}" == *mongo* ]]; then
  151. echo -e "${RED}Resetting will remove the ${REDIS_DATA_LOCATION} and ${MONGO_DATA_LOCATION} directories.${NC}"
  152. elif [[ "${servicesString:2}" == *redis* ]]; then
  153. echo -e "${RED}Resetting will remove the ${REDIS_DATA_LOCATION} directory.${NC}"
  154. elif [[ "${servicesString:2}" == *mongo* ]]; then
  155. echo -e "${RED}Resetting will remove the ${MONGO_DATA_LOCATION} directory.${NC}"
  156. fi
  157. echo -e "${GREEN}Are you sure you want to reset all data for $(echo "${servicesString:2}" | tr ' ' ',')? ${YELLOW}[y,n]: ${NC}"
  158. read -r confirm
  159. if [[ "${confirm}" == y* ]]; then
  160. # shellcheck disable=SC2086
  161. runDockerCommand "$(basename "$0")" stop ${servicesString:2}
  162. # shellcheck disable=SC2086
  163. ${dockerCompose} rm -v --force ${servicesString:2}
  164. if [[ "${servicesString:2}" == *redis* && -d $REDIS_DATA_LOCATION ]]; then
  165. rm -rf "${REDIS_DATA_LOCATION}"
  166. fi
  167. if [[ "${servicesString:2}" == *mongo* && -d $MONGO_DATA_LOCATION ]]; then
  168. rm -rf "${MONGO_DATA_LOCATION}"
  169. fi
  170. else
  171. echo -e "${RED}Cancelled reset${NC}"
  172. fi
  173. else
  174. echo -e "${RED}${servicesString:2}\n${YELLOW}Usage: $(basename "$0") build [backend, frontend, mongo, redis]${NC}"
  175. exit 1
  176. fi
  177. ;;
  178. attach)
  179. echo -e "${CYAN}Musare | Attach${NC}"
  180. if [[ $2 == "backend" ]]; then
  181. containerId=$(${dockerCompose} ps -q backend)
  182. if [[ -z $containerId ]]; then
  183. echo -e "${RED}Error: Backend offline, please start to attach.${NC}"
  184. exit 1
  185. else
  186. echo -e "${YELLOW}Detach with CTRL+P+Q${NC}"
  187. ${docker} attach "$containerId"
  188. fi
  189. elif [[ $2 == "mongo" ]]; then
  190. MONGO_VERSION_INT=${MONGO_VERSION:0:1}
  191. if [[ -z $(${dockerCompose} ps -q mongo) ]]; then
  192. echo -e "${RED}Error: Mongo offline, please start to attach.${NC}"
  193. exit 1
  194. else
  195. echo -e "${YELLOW}Detach with CTRL+D${NC}"
  196. if [[ $MONGO_VERSION_INT -ge 5 ]]; then
  197. ${dockerCompose} exec mongo mongosh musare -u "${MONGO_USER_USERNAME}" -p "${MONGO_USER_PASSWORD}" --eval "disableTelemetry()" --shell
  198. else
  199. ${dockerCompose} exec mongo mongo musare -u "${MONGO_USER_USERNAME}" -p "${MONGO_USER_PASSWORD}"
  200. fi
  201. fi
  202. elif [[ $2 == "redis" ]]; then
  203. if [[ -z $(${dockerCompose} ps -q redis) ]]; then
  204. echo -e "${RED}Error: Redis offline, please start to attach.${NC}"
  205. exit 1
  206. else
  207. echo -e "${YELLOW}Detach with CTRL+C${NC}"
  208. ${dockerCompose} exec redis redis-cli -a "${REDIS_PASSWORD}"
  209. fi
  210. else
  211. echo -e "${RED}Invalid service $2\n${YELLOW}Usage: $(basename "$0") attach [backend,mongo,redis]${NC}"
  212. exit 1
  213. fi
  214. ;;
  215. eslint)
  216. echo -e "${CYAN}Musare | ESLint${NC}"
  217. fix=""
  218. if [[ $2 == "fix" || $3 == "fix" || $2 == "--fix" || $3 == "--fix" ]]; then
  219. fix="--fix"
  220. echo -e "${GREEN}Auto-fix enabled${NC}"
  221. fi
  222. case $2 in
  223. frontend)
  224. ${dockerCompose} exec frontend npx eslint src --ext .js,.vue $fix
  225. exitValue=$?
  226. ;;
  227. backend)
  228. ${dockerCompose} exec backend npx eslint logic $fix
  229. exitValue=$?
  230. ;;
  231. ""|fix|--fix)
  232. ${dockerCompose} exec frontend npx eslint src --ext .js,.vue $fix
  233. frontendExitValue=$?
  234. ${dockerCompose} exec backend npx eslint logic $fix
  235. backendExitValue=$?
  236. if [[ ${frontendExitValue} -gt 0 || ${backendExitValue} -gt 0 ]]; then
  237. exitValue=1
  238. else
  239. exitValue=0
  240. fi
  241. ;;
  242. *)
  243. echo -e "${RED}Invalid service $2\n${YELLOW}Usage: $(basename "$0") eslint [backend, frontend] [fix]${NC}"
  244. exitValue=1
  245. ;;
  246. esac
  247. if [[ ${exitValue} -gt 0 ]]; then
  248. exit ${exitValue}
  249. fi
  250. ;;
  251. update)
  252. echo -e "${CYAN}Musare | Update${NC}"
  253. git fetch
  254. exitValue=$?
  255. if [[ ${exitValue} -gt 0 ]]; then
  256. exit ${exitValue}
  257. fi
  258. if [[ $(git rev-parse HEAD) == $(git rev-parse @\{u\}) ]]; then
  259. echo -e "${GREEN}Already up to date${NC}"
  260. else
  261. dbChange=$(git log --name-only --oneline HEAD..origin/"$(git rev-parse --abbrev-ref HEAD)" | grep "backend/logic/db/schemas")
  262. fcChange=$(git log --name-only --oneline HEAD..origin/"$(git rev-parse --abbrev-ref HEAD)" | grep "frontend/dist/config/template.json")
  263. bcChange=$(git log --name-only --oneline HEAD..origin/"$(git rev-parse --abbrev-ref HEAD)" | grep "backend/config/template.json")
  264. if [[ ( $2 == "auto" && -z $dbChange && -z $fcChange && -z $bcChange ) || -z $2 ]]; then
  265. echo -e "${CYAN}Updating...${NC}"
  266. git pull
  267. exitValue=$?
  268. if [[ ${exitValue} -gt 0 ]]; then
  269. exit ${exitValue}
  270. fi
  271. runDockerCommand "$(basename "$0")" build
  272. runDockerCommand "$(basename "$0")" restart
  273. echo -e "${GREEN}Updated!${NC}"
  274. if [[ -n $dbChange ]]; then
  275. echo -e "${RED}Database schema has changed, please run migration!${NC}"
  276. fi
  277. if [[ -n $fcChange ]]; then
  278. echo -e "${RED}Frontend config has changed, please update!${NC}"
  279. fi
  280. if [[ -n $bcChange ]]; then
  281. echo -e "${RED}Backend config has changed, please update!${NC}"
  282. fi
  283. elif [[ $2 == "auto" ]]; then
  284. echo -e "${RED}Auto Update Failed! Database and/or config has changed!${NC}"
  285. exit 1
  286. fi
  287. fi
  288. ;;
  289. logs)
  290. echo -e "${CYAN}Musare | Logs${NC}"
  291. # shellcheck disable=SC2068
  292. runDockerCommand "$(basename "$0")" logs ${@:2}
  293. ;;
  294. backup)
  295. echo -e "${CYAN}Musare | Backup${NC}"
  296. if [[ -z "${BACKUP_LOCATION}" ]]; then
  297. backupLocation="${scriptLocation%x}/backups"
  298. else
  299. backupLocation="${BACKUP_LOCATION%/}"
  300. fi
  301. if [[ ! -d "${backupLocation}" ]]; then
  302. echo -e "${YELLOW}Creating backup directory at ${backupLocation}${NC}"
  303. mkdir "${backupLocation}"
  304. fi
  305. if [[ -z "${BACKUP_NAME}" ]]; then
  306. backupLocation="${backupLocation}/musare-$(date +"%Y-%m-%d-%s").dump"
  307. else
  308. backupLocation="${backupLocation}/${BACKUP_NAME}"
  309. fi
  310. echo -e "${YELLOW}Creating backup at ${backupLocation}${NC}"
  311. ${dockerCompose} exec -T mongo sh -c "mongodump --authenticationDatabase musare -u ${MONGO_USER_USERNAME} -p ${MONGO_USER_PASSWORD} -d musare --archive" > "${backupLocation}"
  312. ;;
  313. restore)
  314. echo -e "${CYAN}Musare | Restore${NC}"
  315. if [[ -z $2 ]]; then
  316. echo -e "${GREEN}Please enter the full path of the dump you wish to restore: ${NC}"
  317. read -r restoreFile
  318. else
  319. restoreFile=$2
  320. fi
  321. if [[ -z ${restoreFile} ]]; then
  322. echo -e "${RED}Error: no restore path given, cancelled restoration.${NC}"
  323. exit 1
  324. elif [[ -d ${restoreFile} ]]; then
  325. echo -e "${RED}Error: restore path given is a directory, cancelled restoration.${NC}"
  326. exit 1
  327. elif [[ ! -f ${restoreFile} ]]; then
  328. echo -e "${RED}Error: no file at restore path given, cancelled restoration.${NC}"
  329. exit 1
  330. else
  331. ${dockerCompose} exec -T mongo sh -c "mongorestore --authenticationDatabase musare -u ${MONGO_USER_USERNAME} -p ${MONGO_USER_PASSWORD} --archive" < "${restoreFile}"
  332. fi
  333. ;;
  334. admin)
  335. echo -e "${CYAN}Musare | Add Admin${NC}"
  336. MONGO_VERSION_INT=${MONGO_VERSION:0:1}
  337. if [[ $2 == "add" ]]; then
  338. if [[ -z $3 ]]; then
  339. echo -e "${GREEN}Please enter the username of the user you wish to make an admin: ${NC}"
  340. read -r adminUser
  341. else
  342. adminUser=$3
  343. fi
  344. if [[ -z $adminUser ]]; then
  345. echo -e "${RED}Error: Username for new admin not provided.${NC}"
  346. exit 1
  347. else
  348. if [[ $MONGO_VERSION_INT -ge 5 ]]; then
  349. ${dockerCompose} exec mongo mongosh musare -u "${MONGO_USER_USERNAME}" -p "${MONGO_USER_PASSWORD}" --eval "disableTelemetry(); db.users.updateOne({username: '${adminUser}'}, {\$set: {role: 'admin'}})"
  350. else
  351. ${dockerCompose} exec mongo mongo musare -u "${MONGO_USER_USERNAME}" -p "${MONGO_USER_PASSWORD}" --eval "db.users.updateOne({username: '${adminUser}'}, {\$set: {role: 'admin'}})"
  352. fi
  353. fi
  354. elif [[ $2 == "remove" ]]; then
  355. if [[ -z $3 ]]; then
  356. echo -e "${GREEN}Please enter the username of the user you wish to remove as admin: ${NC}"
  357. read -r adminUser
  358. else
  359. adminUser=$3
  360. fi
  361. if [[ -z $adminUser ]]; then
  362. echo -e "${RED}Error: Username for new admin not provided.${NC}"
  363. exit 1
  364. else
  365. if [[ $MONGO_VERSION_INT -ge 5 ]]; then
  366. ${dockerCompose} exec mongo mongosh musare -u "${MONGO_USER_USERNAME}" -p "${MONGO_USER_PASSWORD}" --eval "disableTelemetry(); db.users.updateOne({username: '${adminUser}'}, {\$set: {role: 'default'}})"
  367. else
  368. ${dockerCompose} exec mongo mongo musare -u "${MONGO_USER_USERNAME}" -p "${MONGO_USER_PASSWORD}" --eval "db.users.updateOne({username: '${adminUser}'}, {\$set: {role: 'default'}})"
  369. fi
  370. fi
  371. else
  372. echo -e "${RED}Invalid command $2\n${YELLOW}Usage: $(basename "$0") admin [add,remove] username${NC}"
  373. exit 1
  374. fi
  375. ;;
  376. "")
  377. echo -e "${CYAN}Musare | Available Commands${NC}"
  378. echo -e "${YELLOW}start - Start services${NC}"
  379. echo -e "${YELLOW}stop - Stop services${NC}"
  380. echo -e "${YELLOW}restart - Restart services${NC}"
  381. echo -e "${YELLOW}status - Service status${NC}"
  382. echo -e "${YELLOW}logs - View logs for services${NC}"
  383. echo -e "${YELLOW}update - Update Musare${NC}"
  384. echo -e "${YELLOW}attach [backend,mongo,redis] - Attach to backend service, mongo or redis shell${NC}"
  385. echo -e "${YELLOW}build - Build services${NC}"
  386. echo -e "${YELLOW}eslint - Run eslint on frontend and/or backend${NC}"
  387. echo -e "${YELLOW}backup - Backup database data to file${NC}"
  388. echo -e "${YELLOW}restore - Restore database data from backup file${NC}"
  389. echo -e "${YELLOW}reset - Reset service data${NC}"
  390. echo -e "${YELLOW}admin [add,remove] - Assign/unassign admin role to/from a user${NC}"
  391. ;;
  392. *)
  393. echo -e "${CYAN}Musare${NC}"
  394. echo -e "${RED}Error: Invalid Command $1${NC}"
  395. echo -e "${CYAN}Available Commands:${NC}"
  396. echo -e "${YELLOW}start - Start services${NC}"
  397. echo -e "${YELLOW}stop - Stop services${NC}"
  398. echo -e "${YELLOW}restart - Restart services${NC}"
  399. echo -e "${YELLOW}status - Service status${NC}"
  400. echo -e "${YELLOW}logs - View logs for services${NC}"
  401. echo -e "${YELLOW}update - Update Musare${NC}"
  402. echo -e "${YELLOW}attach [backend,mongo,redis] - Attach to backend service, mongo or redis shell${NC}"
  403. echo -e "${YELLOW}build - Build services${NC}"
  404. echo -e "${YELLOW}eslint - Run eslint on frontend and/or backend${NC}"
  405. echo -e "${YELLOW}backup - Backup database data to file${NC}"
  406. echo -e "${YELLOW}restore - Restore database data from backup file${NC}"
  407. echo -e "${YELLOW}reset - Reset service data${NC}"
  408. echo -e "${YELLOW}admin [add,remove] - Assign/unassign admin role to/from a user${NC}"
  409. exit 1
  410. ;;
  411. esac