├── .autorc ├── .dockerignore ├── .gitattributes ├── .github ├── .kodiak.toml ├── CODEOWNERS ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── .kodiak.toml ├── .prettierignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── LICENSE.md ├── Makefile ├── README.md ├── assets ├── foundryvtt-click-timed-url.png ├── foundryvtt-click-username.png └── upload.png ├── docker-compose.yml ├── docs └── kube.md ├── renovate.json ├── server ├── Cargo.toml ├── src │ ├── config.rs │ ├── downloader.rs │ ├── events.rs │ ├── extractor.rs │ ├── handlers.rs │ ├── initialization.rs │ ├── launch.rs │ ├── main.rs │ ├── server.rs │ └── utils.rs └── static │ ├── index.css │ ├── index.html │ └── index.js └── yarn.lock /.autorc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "git-tag", 4 | ["docker", { "registry": "docker.io/mbround18/foundryvtt-docker", "tagLatest": true }] 5 | ], 6 | "owner": "mbround18", 7 | "repo": "foundryvtt-docker", 8 | "name": "mbround18", 9 | "email": "12646562+mbround18@users.noreply.github.com", 10 | "onlyPublishWithReleaseLabel": true 11 | } 12 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Repo Specific 2 | dist/backend 3 | assets/ 4 | client/ 5 | docs/ 6 | 7 | # IDEs 8 | .idea 9 | .vscode/ 10 | 11 | 12 | # Other 13 | tmp/ 14 | .env 15 | .github/ 16 | *.log* 17 | 18 | node_modules/ 19 | target/ 20 | 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.txt text 3 | *.vcproj text eol=crlf 4 | *.sh text eol=lf 5 | *.jpg -text 6 | *.png -textgh -------------------------------------------------------------------------------- /.github/.kodiak.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [update] 4 | always = true # default: false 5 | require_automerge_label = false # default: true 6 | 7 | [approve] 8 | auto_approve_usernames = ["dependabot", "renovate", "mbround18"] 9 | 10 | [merge.automerge_dependencies] 11 | usernames = ["dependabot", "renovate"] 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @mbround18 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [mbround18] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: mbround18 # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://paypal.me/MichaelBruno"] 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v* 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | docker-release: 15 | name: Release Train 16 | uses: mbround18/gh-reusable/.github/workflows/docker-release.yaml@main 17 | with: 18 | image: "mbround18/foundryvtt-docker" 19 | platforms: linux/amd64 20 | secrets: inherit 21 | 22 | git-release: 23 | if: ${{ github.ref_name == 'main' }} 24 | needs: docker-release 25 | uses: mbround18/gh-reusable/.github/workflows/tagger.yaml@main 26 | secrets: inherit 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bak 2 | .env* 3 | tmp/ 4 | *.log* 5 | node_modules 6 | dist/ 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/releases 10 | !.yarn/plugins 11 | !.yarn/sdks 12 | !.yarn/versions 13 | .pnp.* 14 | .parcel-cache/ 15 | 16 | # Created by https://www.toptal.com/developers/gitignore/api/nuxt,nuxtjs,visualstudiocode,intellij+all 17 | # Edit at https://www.toptal.com/developers/gitignore?templates=nuxt,nuxtjs,visualstudiocode,intellij+all 18 | 19 | ### Intellij+all ### 20 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 21 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 22 | 23 | # User-specific stuff 24 | .idea/**/workspace.xml 25 | .idea/**/tasks.xml 26 | .idea/**/usage.statistics.xml 27 | .idea/**/dictionaries 28 | .idea/**/shelf 29 | 30 | # Generated files 31 | .idea/**/contentModel.xml 32 | 33 | # Sensitive or high-churn files 34 | .idea/**/dataSources/ 35 | .idea/**/dataSources.ids 36 | .idea/**/dataSources.local.xml 37 | .idea/**/sqlDataSources.xml 38 | .idea/**/dynamic.xml 39 | .idea/**/uiDesigner.xml 40 | .idea/**/dbnavigator.xml 41 | 42 | # Gradle 43 | .idea/**/gradle.xml 44 | .idea/**/libraries 45 | 46 | # Gradle and Maven with auto-import 47 | # When using Gradle or Maven with auto-import, you should exclude module files, 48 | # since they will be recreated, and may cause churn. Uncomment if using 49 | # auto-import. 50 | # .idea/artifacts 51 | # .idea/compiler.xml 52 | # .idea/jarRepositories.xml 53 | # .idea/modules.xml 54 | # .idea/*.iml 55 | # .idea/modules 56 | # *.iml 57 | # *.ipr 58 | 59 | # CMake 60 | cmake-build-*/ 61 | 62 | # Mongo Explorer plugin 63 | .idea/**/mongoSettings.xml 64 | 65 | # File-based project format 66 | *.iws 67 | 68 | # IntelliJ 69 | out/ 70 | 71 | # mpeltonen/sbt-idea plugin 72 | .idea_modules/ 73 | 74 | # JIRA plugin 75 | atlassian-ide-plugin.xml 76 | 77 | # Cursive Clojure plugin 78 | .idea/replstate.xml 79 | 80 | # Crashlytics plugin (for Android Studio and IntelliJ) 81 | com_crashlytics_export_strings.xml 82 | crashlytics.properties 83 | crashlytics-build.properties 84 | fabric.properties 85 | 86 | # Editor-based Rest Client 87 | .idea/httpRequests 88 | 89 | # Android studio 3.1+ serialized cache file 90 | .idea/caches/build_file_checksums.ser 91 | 92 | ### Intellij+all Patch ### 93 | # Ignores the whole .idea folder and all .iml files 94 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 95 | 96 | .idea/ 97 | 98 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 99 | 100 | *.iml 101 | modules.xml 102 | .idea/misc.xml 103 | *.ipr 104 | 105 | # Sonarlint plugin 106 | .idea/sonarlint 107 | 108 | ### Nuxt ### 109 | # gitignore template for Nuxt.js projects 110 | # 111 | # Recommended template: Node.gitignore 112 | 113 | # Nuxt build 114 | .nuxt 115 | 116 | # Nuxt generate 117 | dist 118 | 119 | ### Nuxtjs ### 120 | # dependencies 121 | node_modules 122 | 123 | # logs 124 | npm-debug.log 125 | 126 | # Nuxt build 127 | 128 | # Nuxt generate 129 | 130 | ### VisualStudioCode ### 131 | .vscode/* 132 | !.vscode/settings.json 133 | !.vscode/tasks.json 134 | !.vscode/launch.json 135 | !.vscode/extensions.json 136 | *.code-workspace 137 | 138 | ### VisualStudioCode Patch ### 139 | # Ignore all local history of files 140 | .history 141 | .ionide 142 | 143 | target 144 | 145 | # End of https://www.toptal.com/developers/gitignore/api/nuxt,nuxtjs,visualstudiocode,intellij+all 146 | -------------------------------------------------------------------------------- /.kodiak.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [merge.automerge_dependencies] 4 | # only auto merge "minor" and "patch" version upgrades. 5 | # do not automerge "major" version upgrades. 6 | versions = ["minor", "patch"] 7 | usernames = ["dependabot", "renovate"] 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .yarn 4 | .parcel-cache 5 | tmp 6 | target -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.1.1 (Tue Apr 26 2022) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - Deps [#345](https://github.com/mbround18/foundryvtt-docker/pull/345) ([@mbround18](https://github.com/mbround18)) 6 | - Update dependency sass to v1.51.0 [#344](https://github.com/mbround18/foundryvtt-docker/pull/344) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 7 | - Update dependency axios to v0.27.0 [#343](https://github.com/mbround18/foundryvtt-docker/pull/343) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 8 | - Update dependency autoprefixer to v10.4.5 [#342](https://github.com/mbround18/foundryvtt-docker/pull/342) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 9 | - Update parcel monorepo to v2.5.0 [#341](https://github.com/mbround18/foundryvtt-docker/pull/341) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 10 | - Update dependency sass to v1.50.1 [#340](https://github.com/mbround18/foundryvtt-docker/pull/340) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 11 | - Update dependency @types/lodash to v4.14.182 [#339](https://github.com/mbround18/foundryvtt-docker/pull/339) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 12 | - Update dependency h3 to v0.7.4 [#338](https://github.com/mbround18/foundryvtt-docker/pull/338) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 13 | - Update dependency tailwindcss to v3.0.24 [#337](https://github.com/mbround18/foundryvtt-docker/pull/337) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 14 | - Update dependency h3 to v0.7.3 [#336](https://github.com/mbround18/foundryvtt-docker/pull/336) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 15 | - Update dependency h3 to v0.7.2 [#335](https://github.com/mbround18/foundryvtt-docker/pull/335) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 16 | - Update dependency h3 to v0.7.1 [#334](https://github.com/mbround18/foundryvtt-docker/pull/334) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 17 | - Update dependency sass to v1.50.0 [#333](https://github.com/mbround18/foundryvtt-docker/pull/333) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 18 | - Update dependency h3 to v0.6.0 [#332](https://github.com/mbround18/foundryvtt-docker/pull/332) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 19 | - Update dependency @babel/runtime to v7.17.9 [#331](https://github.com/mbround18/foundryvtt-docker/pull/331) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 20 | - Update dependency h3 to v0.5.5 [#330](https://github.com/mbround18/foundryvtt-docker/pull/330) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 21 | - Update dependency concurrently to v7.1.0 [#329](https://github.com/mbround18/foundryvtt-docker/pull/329) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 22 | - Update dependency sass to v1.49.11 [#328](https://github.com/mbround18/foundryvtt-docker/pull/328) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 23 | - Update dependency h3 to v0.5.4 [#327](https://github.com/mbround18/foundryvtt-docker/pull/327) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 24 | - Update dependency h3 to v0.5.3 [#326](https://github.com/mbround18/foundryvtt-docker/pull/326) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 25 | - Update parcel monorepo to v2.4.1 [#325](https://github.com/mbround18/foundryvtt-docker/pull/325) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 26 | - Update dependency h3 to v0.5.2 [#324](https://github.com/mbround18/foundryvtt-docker/pull/324) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 27 | - Update dependency sass to v1.49.10 [#323](https://github.com/mbround18/foundryvtt-docker/pull/323) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 28 | - Update dependency h3 to v0.5.1 [#322](https://github.com/mbround18/foundryvtt-docker/pull/322) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 29 | - Update dependency @types/lodash to v4.14.181 [#321](https://github.com/mbround18/foundryvtt-docker/pull/321) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 30 | - Update dependency serve-static to v1.15.0 [#320](https://github.com/mbround18/foundryvtt-docker/pull/320) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 31 | - Update parcel monorepo to v2.4.0 [#319](https://github.com/mbround18/foundryvtt-docker/pull/319) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 32 | - Update actions/cache action to v3 [#318](https://github.com/mbround18/foundryvtt-docker/pull/318) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 33 | - Update dependency @auto-it/core to v10.36.5 [#316](https://github.com/mbround18/foundryvtt-docker/pull/316) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 34 | - Update dependency auto to v10.36.5 [#317](https://github.com/mbround18/foundryvtt-docker/pull/317) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 35 | - Update dependency auto to v10.36.4 [#315](https://github.com/mbround18/foundryvtt-docker/pull/315) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 36 | - Update dependency @auto-it/core to v10.36.4 [#314](https://github.com/mbround18/foundryvtt-docker/pull/314) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 37 | - Update dependency auto to v10.36.2 [#313](https://github.com/mbround18/foundryvtt-docker/pull/313) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 38 | - Update dependency @auto-it/core to v10.36.2 [#312](https://github.com/mbround18/foundryvtt-docker/pull/312) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 39 | - Update dependency @auto-it/core to v10.36.0 [#310](https://github.com/mbround18/foundryvtt-docker/pull/310) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 40 | - Update dependency auto to v10.36.0 [#311](https://github.com/mbround18/foundryvtt-docker/pull/311) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 41 | - Update dependency @babel/runtime to v7.17.8 [#309](https://github.com/mbround18/foundryvtt-docker/pull/309) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 42 | - Update dependency auto to v10.34.2 [#308](https://github.com/mbround18/foundryvtt-docker/pull/308) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 43 | - Update dependency @auto-it/core to v10.34.2 [#307](https://github.com/mbround18/foundryvtt-docker/pull/307) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 44 | - Update dependency h3 to v0.4.2 [#306](https://github.com/mbround18/foundryvtt-docker/pull/306) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 45 | - Update dependency postcss to v8.4.12 [#305](https://github.com/mbround18/foundryvtt-docker/pull/305) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 46 | - Update dependency autoprefixer to v10.4.4 [#304](https://github.com/mbround18/foundryvtt-docker/pull/304) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 47 | - Update dependency postcss to v8.4.11 [#303](https://github.com/mbround18/foundryvtt-docker/pull/303) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 48 | - Update dependency autoprefixer to v10.4.3 [#302](https://github.com/mbround18/foundryvtt-docker/pull/302) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 49 | - Update dependency postcss to v8.4.9 [#301](https://github.com/mbround18/foundryvtt-docker/pull/301) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 50 | - Update dependency @types/lodash to v4.14.180 [#300](https://github.com/mbround18/foundryvtt-docker/pull/300) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 51 | - Update dependency @babel/runtime to v7.17.7 [#299](https://github.com/mbround18/foundryvtt-docker/pull/299) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 52 | - Update dependency h3 to v0.4.1 [#298](https://github.com/mbround18/foundryvtt-docker/pull/298) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 53 | - Update dependency h3 to v0.4.0 [#297](https://github.com/mbround18/foundryvtt-docker/pull/297) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 54 | - Update dependency axios to v0.26.1 [#296](https://github.com/mbround18/foundryvtt-docker/pull/296) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 55 | - Update dependency postcss to v8.4.8 [#295](https://github.com/mbround18/foundryvtt-docker/pull/295) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 56 | - Update dependency auto to v10.34.1 [#294](https://github.com/mbround18/foundryvtt-docker/pull/294) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 57 | - Update dependency @auto-it/core to v10.34.1 [#293](https://github.com/mbround18/foundryvtt-docker/pull/293) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 58 | - Update dependency auto to v10.34.0 [#292](https://github.com/mbround18/foundryvtt-docker/pull/292) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 59 | - Update dependency @auto-it/core to v10.34.0 [#291](https://github.com/mbround18/foundryvtt-docker/pull/291) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 60 | - Update dependency auto to v10.33.1 [#290](https://github.com/mbround18/foundryvtt-docker/pull/290) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 61 | - Update dependency @auto-it/core to v10.33.1 [#289](https://github.com/mbround18/foundryvtt-docker/pull/289) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 62 | - Update dependency auto to v10.33.0 [#288](https://github.com/mbround18/foundryvtt-docker/pull/288) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 63 | - Update dependency @auto-it/core to v10.33.0 [#287](https://github.com/mbround18/foundryvtt-docker/pull/287) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 64 | - Update actions/checkout action to v3 [#286](https://github.com/mbround18/foundryvtt-docker/pull/286) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 65 | - Update dependency @types/lodash to v4.14.179 [#285](https://github.com/mbround18/foundryvtt-docker/pull/285) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 66 | - Update actions/setup-node action to v3 [#284](https://github.com/mbround18/foundryvtt-docker/pull/284) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 67 | - Update dependency postcss to v8.4.7 [#283](https://github.com/mbround18/foundryvtt-docker/pull/283) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 68 | - Update dependency sass to v1.49.9 [#282](https://github.com/mbround18/foundryvtt-docker/pull/282) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 69 | - Update dependency @yarnpkg/pnpify to v3.1.1 [#281](https://github.com/mbround18/foundryvtt-docker/pull/281) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 70 | - Update parcel monorepo to v2.3.2 [#280](https://github.com/mbround18/foundryvtt-docker/pull/280) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 71 | - Update dependency follow-redirects to v1.14.9 [#279](https://github.com/mbround18/foundryvtt-docker/pull/279) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 72 | - Update dependency sass to v1.49.8 [#278](https://github.com/mbround18/foundryvtt-docker/pull/278) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 73 | - Update dependency tailwindcss to v3.0.23 [#277](https://github.com/mbround18/foundryvtt-docker/pull/277) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 74 | - Update dependency sass-loader to v12.6.0 [#276](https://github.com/mbround18/foundryvtt-docker/pull/276) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 75 | - Update dependency sass-loader to v12.5.0 [#275](https://github.com/mbround18/foundryvtt-docker/pull/275) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 76 | 77 | #### Authors: 4 78 | 79 | - [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) 80 | - [@renovate[bot]](https://github.com/renovate[bot]) 81 | - Michael ([@mbround18](https://github.com/mbround18)) 82 | - WhiteSource Renovate ([@renovate-bot](https://github.com/renovate-bot)) 83 | 84 | --- 85 | 86 | # v1.1.0 (Mon Feb 14 2022) 87 | 88 | #### 🚀 Enhancement 89 | 90 | - Update README.md [#274](https://github.com/mbround18/foundryvtt-docker/pull/274) ([@mbround18](https://github.com/mbround18)) 91 | - Simplify project. [#272](https://github.com/mbround18/foundryvtt-docker/pull/272) ([@mbround18](https://github.com/mbround18)) 92 | 93 | #### 🐛 Bug Fix 94 | 95 | - Pin dependencies [#273](https://github.com/mbround18/foundryvtt-docker/pull/273) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 96 | - Update dependency axios to v0.26.0 [#271](https://github.com/mbround18/foundryvtt-docker/pull/271) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 97 | - Update dependency express-rate-limit to v6.2.1 [#270](https://github.com/mbround18/foundryvtt-docker/pull/270) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 98 | - Update babel monorepo to v7.17.2 [#269](https://github.com/mbround18/foundryvtt-docker/pull/269) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 99 | - Update dependency follow-redirects to v1.14.8 [#268](https://github.com/mbround18/foundryvtt-docker/pull/268) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 100 | - Update babel monorepo to v7.17.0 [#266](https://github.com/mbround18/foundryvtt-docker/pull/266) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 101 | - Update dependency express-fileupload to v1.3.1 [#265](https://github.com/mbround18/foundryvtt-docker/pull/265) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 102 | - Update dependency webpack-dev-server to v4.7.4 [#264](https://github.com/mbround18/foundryvtt-docker/pull/264) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 103 | - Update dependency sass to v1.49.7 [#263](https://github.com/mbround18/foundryvtt-docker/pull/263) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 104 | - Update dependency sass to v1.49.6 [#262](https://github.com/mbround18/foundryvtt-docker/pull/262) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 105 | - Update dependency sass to v1.49.5 [#261](https://github.com/mbround18/foundryvtt-docker/pull/261) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 106 | - Update dependency sass to v1.49.4 [#260](https://github.com/mbround18/foundryvtt-docker/pull/260) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 107 | - Update dependency sass to v1.49.3 [#259](https://github.com/mbround18/foundryvtt-docker/pull/259) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 108 | - Update dependency sass to v1.49.0 [#257](https://github.com/mbround18/foundryvtt-docker/pull/257) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 109 | - Update dependency express-rate-limit to v6.2.0 [#256](https://github.com/mbround18/foundryvtt-docker/pull/256) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 110 | - Update dependency axios to v0.25.0 [#255](https://github.com/mbround18/foundryvtt-docker/pull/255) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 111 | - Update dependency webpack-dev-server to v4.7.3 [#254](https://github.com/mbround18/foundryvtt-docker/pull/254) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 112 | - Update dependency webpack-cli to v4.9.2 [#253](https://github.com/mbround18/foundryvtt-docker/pull/253) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 113 | - Update dependency vuetify to v2.6.3 [#252](https://github.com/mbround18/foundryvtt-docker/pull/252) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 114 | - Update dependency sass-loader to v10.2.1 [#251](https://github.com/mbround18/foundryvtt-docker/pull/251) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 115 | - Update dependency auto to v10.32.6 [#249](https://github.com/mbround18/foundryvtt-docker/pull/249) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 116 | - Update dependency @auto-it/core to v10.32.6 [#247](https://github.com/mbround18/foundryvtt-docker/pull/247) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 117 | - Update dependency follow-redirects to v1.14.7 [SECURITY] - autoclosed [#248](https://github.com/mbround18/foundryvtt-docker/pull/248) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 118 | - Update babel monorepo [#246](https://github.com/mbround18/foundryvtt-docker/pull/246) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 119 | - Update dependency html-loader to v3.1.0 [#244](https://github.com/mbround18/foundryvtt-docker/pull/244) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 120 | - Update dependency sass to v1.47.0 [#243](https://github.com/mbround18/foundryvtt-docker/pull/243) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 121 | - Update dependency shelljs to v0.8.5 [#242](https://github.com/mbround18/foundryvtt-docker/pull/242) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 122 | - Update dependency express-rate-limit to v6.0.5 [#241](https://github.com/mbround18/foundryvtt-docker/pull/241) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 123 | - Update dependency sass to v1.46.0 [#240](https://github.com/mbround18/foundryvtt-docker/pull/240) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 124 | - Update dependency express-rate-limit to v6.0.4 [#239](https://github.com/mbround18/foundryvtt-docker/pull/239) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 125 | - Update babel monorepo to v7.16.7 [#237](https://github.com/mbround18/foundryvtt-docker/pull/237) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 126 | - Update dependency sass to v1.45.2 [#238](https://github.com/mbround18/foundryvtt-docker/pull/238) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 127 | - Update dependency express-rate-limit to v6.0.3 [#236](https://github.com/mbround18/foundryvtt-docker/pull/236) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 128 | - Update dependency express-rate-limit to v6.0.2 [#235](https://github.com/mbround18/foundryvtt-docker/pull/235) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 129 | - Update dependency webpack-dev-server to v4.7.2 [#234](https://github.com/mbround18/foundryvtt-docker/pull/234) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 130 | - Update dependency vuetify to v2.6.2 [#233](https://github.com/mbround18/foundryvtt-docker/pull/233) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 131 | - Update dependency express-rate-limit to v6.0.1 [#232](https://github.com/mbround18/foundryvtt-docker/pull/232) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 132 | - Update dependency express-rate-limit to v6 [#231](https://github.com/mbround18/foundryvtt-docker/pull/231) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 133 | - Update dependency webpack-dev-server to v4.7.1 [#230](https://github.com/mbround18/foundryvtt-docker/pull/230) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 134 | - Update dependency webpack-dev-server to v4.7.0 [#229](https://github.com/mbround18/foundryvtt-docker/pull/229) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 135 | - Update dependency sass to v1.45.1 [#228](https://github.com/mbround18/foundryvtt-docker/pull/228) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 136 | - Update dependency express to v4.17.2 [#227](https://github.com/mbround18/foundryvtt-docker/pull/227) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 137 | - Update babel monorepo to v7.16.5 [#226](https://github.com/mbround18/foundryvtt-docker/pull/226) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 138 | - Update dependency @nuxtjs/vuetify to v1.12.3 [#225](https://github.com/mbround18/foundryvtt-docker/pull/225) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 139 | - Update dependency auto to v10.32.5 [#224](https://github.com/mbround18/foundryvtt-docker/pull/224) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 140 | - Update dependency @auto-it/core to v10.32.5 [#223](https://github.com/mbround18/foundryvtt-docker/pull/223) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 141 | - Update dependency sass to v1.45.0 [#222](https://github.com/mbround18/foundryvtt-docker/pull/222) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 142 | - Update dependency auto to v10.32.4 [#221](https://github.com/mbround18/foundryvtt-docker/pull/221) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 143 | - Update dependency @auto-it/core to v10.32.4 [#220](https://github.com/mbround18/foundryvtt-docker/pull/220) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 144 | - Update dependency follow-redirects to v1.14.6 [#219](https://github.com/mbround18/foundryvtt-docker/pull/219) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 145 | - Update dependency webpack to v5.65.0 [#218](https://github.com/mbround18/foundryvtt-docker/pull/218) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 146 | - Update dependency workbox-webpack-plugin to v6.4.2 [#217](https://github.com/mbround18/foundryvtt-docker/pull/217) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 147 | - Update dependency vuetify to v2.6.1 [#216](https://github.com/mbround18/foundryvtt-docker/pull/216) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 148 | - Update dependency sass to v1.44.0 [#215](https://github.com/mbround18/foundryvtt-docker/pull/215) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 149 | - Update dependency @nuxtjs/vuetify to v1.12.2 [#214](https://github.com/mbround18/foundryvtt-docker/pull/214) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 150 | - Update dependency debug to v4.3.3 [#213](https://github.com/mbround18/foundryvtt-docker/pull/213) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 151 | - Update dependency supports-color to v9.2.1 [#212](https://github.com/mbround18/foundryvtt-docker/pull/212) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 152 | - Update dependency webpack-dev-server to v4.6.0 [#211](https://github.com/mbround18/foundryvtt-docker/pull/211) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 153 | - Update dependency webpack to v5.64.4 [#210](https://github.com/mbround18/foundryvtt-docker/pull/210) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 154 | - Update dependency sass to v1.43.5 [#209](https://github.com/mbround18/foundryvtt-docker/pull/209) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 155 | - Update dependency webpack to v5.64.3 [#208](https://github.com/mbround18/foundryvtt-docker/pull/208) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 156 | - Update dependency auto to v10.32.3 [#207](https://github.com/mbround18/foundryvtt-docker/pull/207) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 157 | - Update dependency @auto-it/core to v10.32.3 [#206](https://github.com/mbround18/foundryvtt-docker/pull/206) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 158 | - Update dependency webpack to v5.64.2 [#205](https://github.com/mbround18/foundryvtt-docker/pull/205) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 159 | - Update dependency supports-color to v9.1.0 [#204](https://github.com/mbround18/foundryvtt-docker/pull/204) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 160 | - Update babel monorepo to v7.16.4 [#203](https://github.com/mbround18/foundryvtt-docker/pull/203) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 161 | - Update dependency workbox-webpack-plugin to v6.4.1 [#202](https://github.com/mbround18/foundryvtt-docker/pull/202) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 162 | - Update dependency webpack to v5.64.1 [#201](https://github.com/mbround18/foundryvtt-docker/pull/201) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 163 | - Update dependency vuetify to v2.6.0 [#200](https://github.com/mbround18/foundryvtt-docker/pull/200) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 164 | - Update dependency webpack-dev-server to v4.5.0 [#199](https://github.com/mbround18/foundryvtt-docker/pull/199) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 165 | - Update dependency webpack to v5.64.0 [#198](https://github.com/mbround18/foundryvtt-docker/pull/198) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 166 | - Update dependency webpack to v5.63.0 [#197](https://github.com/mbround18/foundryvtt-docker/pull/197) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 167 | - Update dependency @babel/runtime to v7.16.3 [#196](https://github.com/mbround18/foundryvtt-docker/pull/196) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 168 | - Update dependency nodemon to v2.0.15 [#195](https://github.com/mbround18/foundryvtt-docker/pull/195) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 169 | - Update dependency vuetify to v2.5.14 [#194](https://github.com/mbround18/foundryvtt-docker/pull/194) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 170 | - Update dependency webpack to v5.62.2 [#193](https://github.com/mbround18/foundryvtt-docker/pull/193) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 171 | - Update dependency vuetify to v2.5.13 [#192](https://github.com/mbround18/foundryvtt-docker/pull/192) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 172 | - Update dependency express-rate-limit to v5.5.1 [#191](https://github.com/mbround18/foundryvtt-docker/pull/191) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 173 | - Update dependency webpack to v5.62.1 [#190](https://github.com/mbround18/foundryvtt-docker/pull/190) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 174 | - Update dependency webpack to v5.62.0 [#189](https://github.com/mbround18/foundryvtt-docker/pull/189) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 175 | - Update dependency vuetify to v2.5.12 [#188](https://github.com/mbround18/foundryvtt-docker/pull/188) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 176 | - Update dependency vuetify to v2.5.11 [#187](https://github.com/mbround18/foundryvtt-docker/pull/187) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 177 | - Update dependency html-loader to v3.0.1 [#186](https://github.com/mbround18/foundryvtt-docker/pull/186) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 178 | - Update dependency follow-redirects to v1.14.5 [#185](https://github.com/mbround18/foundryvtt-docker/pull/185) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 179 | - Update babel monorepo to v7.16.0 [#184](https://github.com/mbround18/foundryvtt-docker/pull/184) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 180 | - Update dependency webpack to v5.61.0 [#183](https://github.com/mbround18/foundryvtt-docker/pull/183) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 181 | - Update dependency webpack-dev-server to v4.4.0 [#182](https://github.com/mbround18/foundryvtt-docker/pull/182) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 182 | - Update dependency sass to v1.43.4 [#181](https://github.com/mbround18/foundryvtt-docker/pull/181) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 183 | - Update dependency auto to v10.32.2 [#180](https://github.com/mbround18/foundryvtt-docker/pull/180) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 184 | - Update dependency @auto-it/core to v10.32.2 [#179](https://github.com/mbround18/foundryvtt-docker/pull/179) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 185 | - Update dependency axios to v0.24.0 [#178](https://github.com/mbround18/foundryvtt-docker/pull/178) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 186 | - Update dependency webpack to v5.60.0 [#177](https://github.com/mbround18/foundryvtt-docker/pull/177) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 187 | - Update dependency @yarnpkg/pnpify to v3.1.0 [#176](https://github.com/mbround18/foundryvtt-docker/pull/176) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 188 | - Update dependency html-webpack-plugin to v5.5.0 [#175](https://github.com/mbround18/foundryvtt-docker/pull/175) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 189 | - Update dependency sass to v1.43.3 [#174](https://github.com/mbround18/foundryvtt-docker/pull/174) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 190 | - Update dependency html-loader to v3 [#173](https://github.com/mbround18/foundryvtt-docker/pull/173) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 191 | - Update dependency webpack to v5.59.1 [#172](https://github.com/mbround18/foundryvtt-docker/pull/172) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 192 | - Update dependency babel-loader to v8.2.3 [#171](https://github.com/mbround18/foundryvtt-docker/pull/171) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 193 | - Update dependency webpack to v5.59.0 [#170](https://github.com/mbround18/foundryvtt-docker/pull/170) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 194 | - Update dependency nodemon to v2.0.14 [#169](https://github.com/mbround18/foundryvtt-docker/pull/169) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 195 | - Update dependency webpack-cli to v4.9.1 [#168](https://github.com/mbround18/foundryvtt-docker/pull/168) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 196 | - Update dependency html-webpack-plugin to v5.4.0 [#167](https://github.com/mbround18/foundryvtt-docker/pull/167) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 197 | - Update dependency sass to v1.43.2 [#166](https://github.com/mbround18/foundryvtt-docker/pull/166) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 198 | - Update dependency webpack to v5.58.2 [#165](https://github.com/mbround18/foundryvtt-docker/pull/165) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 199 | - Update dependency express-rate-limit to v5.5.0 [#164](https://github.com/mbround18/foundryvtt-docker/pull/164) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 200 | - Update dependency axios to v0.23.0 [#163](https://github.com/mbround18/foundryvtt-docker/pull/163) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 201 | - Update dependency vuetify to v2.5.10 [#162](https://github.com/mbround18/foundryvtt-docker/pull/162) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 202 | - Update dependency webpack to v5.58.1 [#161](https://github.com/mbround18/foundryvtt-docker/pull/161) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 203 | - Update dependency webpack to v5.58.0 [#160](https://github.com/mbround18/foundryvtt-docker/pull/160) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 204 | - Update dependency webpack-cli to v4.9.0 [#159](https://github.com/mbround18/foundryvtt-docker/pull/159) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 205 | - Update babel monorepo to v7.15.8 [#158](https://github.com/mbround18/foundryvtt-docker/pull/158) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 206 | - Update dependency express-rate-limit to v5.4.1 [#157](https://github.com/mbround18/foundryvtt-docker/pull/157) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 207 | - Update dependency webpack to v5.57.1 [#156](https://github.com/mbround18/foundryvtt-docker/pull/156) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 208 | - Update dependency webpack to v5.57.0 [#155](https://github.com/mbround18/foundryvtt-docker/pull/155) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 209 | - Update dependency webpack-dev-server to v4.3.1 [#154](https://github.com/mbround18/foundryvtt-docker/pull/154) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 210 | - Update dependency webpack to v5.56.1 [#153](https://github.com/mbround18/foundryvtt-docker/pull/153) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 211 | - Update dependency express-rate-limit to v5.4.0 [#152](https://github.com/mbround18/foundryvtt-docker/pull/152) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 212 | - Update dependency webpack to v5.56.0 [#151](https://github.com/mbround18/foundryvtt-docker/pull/151) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 213 | - Update dependency axios to v0.22.0 [#150](https://github.com/mbround18/foundryvtt-docker/pull/150) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 214 | - Update dependency auto to v10.32.1 [#149](https://github.com/mbround18/foundryvtt-docker/pull/149) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 215 | - Update dependency @auto-it/core to v10.32.1 [#148](https://github.com/mbround18/foundryvtt-docker/pull/148) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 216 | - Update dependency webpack to v5.55.1 [#147](https://github.com/mbround18/foundryvtt-docker/pull/147) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 217 | - Update dependency vuetify to v2.5.9 [#146](https://github.com/mbround18/foundryvtt-docker/pull/146) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 218 | - Update dependency webpack to v5.55.0 [#145](https://github.com/mbround18/foundryvtt-docker/pull/145) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 219 | - Update dependency webpack-dev-server to v4.3.0 [#144](https://github.com/mbround18/foundryvtt-docker/pull/144) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 220 | - Update dependency webpack to v5.54.0 [#143](https://github.com/mbround18/foundryvtt-docker/pull/143) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 221 | - Update dependency nodemon to v2.0.13 [#142](https://github.com/mbround18/foundryvtt-docker/pull/142) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 222 | - Update dependency sass to v1.42.1 [#141](https://github.com/mbround18/foundryvtt-docker/pull/141) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 223 | - Update dependency sass to v1.42.0 [#140](https://github.com/mbround18/foundryvtt-docker/pull/140) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 224 | - Update dependency sass to v1.41.1 [#139](https://github.com/mbround18/foundryvtt-docker/pull/139) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 225 | - Update dependency webpack to v5.53.0 [#138](https://github.com/mbround18/foundryvtt-docker/pull/138) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 226 | - Update dependency auto to v10.32.0 [#137](https://github.com/mbround18/foundryvtt-docker/pull/137) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 227 | - Update dependency @auto-it/core to v10.32.0 [#136](https://github.com/mbround18/foundryvtt-docker/pull/136) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 228 | - Update dependency sass to v1.41.0 [#135](https://github.com/mbround18/foundryvtt-docker/pull/135) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 229 | - Update dependency follow-redirects to v1.14.4 [#134](https://github.com/mbround18/foundryvtt-docker/pull/134) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 230 | - Update dependency sass to v1.40.1 [#133](https://github.com/mbround18/foundryvtt-docker/pull/133) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 231 | - Update dependency sass to v1.40.0 [#132](https://github.com/mbround18/foundryvtt-docker/pull/132) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 232 | - Update dependency webpack-dev-server to v4.2.1 [#131](https://github.com/mbround18/foundryvtt-docker/pull/131) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 233 | - Update dependency webpack to v5.52.1 [#130](https://github.com/mbround18/foundryvtt-docker/pull/130) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 234 | - Update dependency sass to v1.39.2 [#129](https://github.com/mbround18/foundryvtt-docker/pull/129) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 235 | - Update dependency sass to v1.39.1 [#128](https://github.com/mbround18/foundryvtt-docker/pull/128) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 236 | - Update dependency webpack-dev-server to v4.2.0 [#127](https://github.com/mbround18/foundryvtt-docker/pull/127) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 237 | - Update dependency @babel/preset-env to v7.15.6 [#126](https://github.com/mbround18/foundryvtt-docker/pull/126) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 238 | - Update dependency workbox-webpack-plugin to v6.3.0 [#125](https://github.com/mbround18/foundryvtt-docker/pull/125) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 239 | - Update dependency axios to v0.21.4 [#123](https://github.com/mbround18/foundryvtt-docker/pull/123) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 240 | - Update dependency webpack-dev-server to v4.1.1 [#124](https://github.com/mbround18/foundryvtt-docker/pull/124) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 241 | - Update dependency axios to v0.21.3 [#122](https://github.com/mbround18/foundryvtt-docker/pull/122) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 242 | - Update dependency axios to v0.21.2 [#121](https://github.com/mbround18/foundryvtt-docker/pull/121) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 243 | - Update dependency @babel/core to v7.15.5 [#120](https://github.com/mbround18/foundryvtt-docker/pull/120) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 244 | - Update dependency webpack to v5.52.0 [#119](https://github.com/mbround18/foundryvtt-docker/pull/119) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 245 | - Update babel monorepo to v7.15.4 [#118](https://github.com/mbround18/foundryvtt-docker/pull/118) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 246 | - Update dependency webpack to v5.51.2 [#117](https://github.com/mbround18/foundryvtt-docker/pull/117) ([@renovate-bot](https://github.com/renovate-bot) [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) [@renovate[bot]](https://github.com/renovate[bot])) 247 | - Update dependency follow-redirects to v1.14.3 [#116](https://github.com/mbround18/foundryvtt-docker/pull/116) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 248 | - Update dependency sass to v1.39.0 [#115](https://github.com/mbround18/foundryvtt-docker/pull/115) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 249 | - Update dependency webpack-dev-server to v4.1.0 [#114](https://github.com/mbround18/foundryvtt-docker/pull/114) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 250 | - Update dependency sass to v1.38.2 [#112](https://github.com/mbround18/foundryvtt-docker/pull/112) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 251 | - Update dependency webpack to v5.51.1 [#110](https://github.com/mbround18/foundryvtt-docker/pull/110) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 252 | - Update dependency sass to v1.38.1 [#111](https://github.com/mbround18/foundryvtt-docker/pull/111) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 253 | - Update dependency webpack to v5.51.0 [#109](https://github.com/mbround18/foundryvtt-docker/pull/109) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 254 | - Update dependency webpack-dev-server to v4 [#108](https://github.com/mbround18/foundryvtt-docker/pull/108) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 255 | - Update dependency follow-redirects to v1.14.2 [#107](https://github.com/mbround18/foundryvtt-docker/pull/107) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 256 | - Update dependency sass to v1.38.0 [#106](https://github.com/mbround18/foundryvtt-docker/pull/106) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 257 | - Update dependency webpack-cli to v4.8.0 [#105](https://github.com/mbround18/foundryvtt-docker/pull/105) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 258 | - Update dependency auto to v10.31.0 [#104](https://github.com/mbround18/foundryvtt-docker/pull/104) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 259 | - Update dependency @auto-it/core to v10.31.0 [#103](https://github.com/mbround18/foundryvtt-docker/pull/103) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 260 | - Update dependency @yarnpkg/pnpify to v3.0.1 [#102](https://github.com/mbround18/foundryvtt-docker/pull/102) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 261 | - Update dependency workbox-webpack-plugin to v6.2.4 [#100](https://github.com/mbround18/foundryvtt-docker/pull/100) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 262 | - Update dependency @babel/runtime to v7.15.3 [#99](https://github.com/mbround18/foundryvtt-docker/pull/99) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 263 | - Update dependency webpack to v5.50.0 [#98](https://github.com/mbround18/foundryvtt-docker/pull/98) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 264 | - Update dependency workbox-webpack-plugin to v6.2.3 [#97](https://github.com/mbround18/foundryvtt-docker/pull/97) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 265 | - Update dependency workbox-webpack-plugin to v6.2.2 [#96](https://github.com/mbround18/foundryvtt-docker/pull/96) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 266 | - Update dependency webpack to v5.49.0 [#95](https://github.com/mbround18/foundryvtt-docker/pull/95) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 267 | - Update dependency workbox-webpack-plugin to v6.2.0 [#94](https://github.com/mbround18/foundryvtt-docker/pull/94) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 268 | - Update babel monorepo to v7.15.0 [#93](https://github.com/mbround18/foundryvtt-docker/pull/93) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 269 | - Update dependency sass to v1.37.5 [#92](https://github.com/mbround18/foundryvtt-docker/pull/92) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 270 | - Update dependency sass to v1.37.4 [#91](https://github.com/mbround18/foundryvtt-docker/pull/91) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 271 | - Update dependency sass to v1.37.3 [#90](https://github.com/mbround18/foundryvtt-docker/pull/90) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 272 | - Update dependency sass to v1.37.2 [#89](https://github.com/mbround18/foundryvtt-docker/pull/89) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 273 | - Update dependency sass to v1.37.1 [#88](https://github.com/mbround18/foundryvtt-docker/pull/88) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 274 | - Update dependency webpack to v5.48.0 [#87](https://github.com/mbround18/foundryvtt-docker/pull/87) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 275 | - Update dependency @babel/preset-env to v7.14.9 [#86](https://github.com/mbround18/foundryvtt-docker/pull/86) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 276 | - Update dependency sass to v1.37.0 [#85](https://github.com/mbround18/foundryvtt-docker/pull/85) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 277 | - Update dependency vuetify to v2.5.8 [#84](https://github.com/mbround18/foundryvtt-docker/pull/84) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 278 | - Update dependency webpack to v5.47.1 [#83](https://github.com/mbround18/foundryvtt-docker/pull/83) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 279 | - Update dependency webpack to v5.47.0 [#82](https://github.com/mbround18/foundryvtt-docker/pull/82) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 280 | - Update dependency @yarnpkg/pnpify to v3.0.0 [#81](https://github.com/mbround18/foundryvtt-docker/pull/81) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 281 | - Update dependency @yarnpkg/pnpify to v3.0.0-rc.14 [#80](https://github.com/mbround18/foundryvtt-docker/pull/80) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 282 | - Update dependency sass to v1.36.0 [#79](https://github.com/mbround18/foundryvtt-docker/pull/79) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 283 | - Update dependency auto to v10.30.0 [#78](https://github.com/mbround18/foundryvtt-docker/pull/78) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 284 | - Update dependency @auto-it/core to v10.30.0 [#77](https://github.com/mbround18/foundryvtt-docker/pull/77) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 285 | - Update dependency webpack to v5.46.0 [#76](https://github.com/mbround18/foundryvtt-docker/pull/76) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 286 | - Update babel monorepo to v7.14.8 [#75](https://github.com/mbround18/foundryvtt-docker/pull/75) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 287 | - Update dependency vuetify to v2.5.7 [#74](https://github.com/mbround18/foundryvtt-docker/pull/74) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 288 | - Update dependency @yarnpkg/pnpify to v3.0.0-rc.13 [#73](https://github.com/mbround18/foundryvtt-docker/pull/73) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 289 | - Update dependency @yarnpkg/pnpify to v3.0.0-rc.12 [#72](https://github.com/mbround18/foundryvtt-docker/pull/72) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 290 | - Update dependency @yarnpkg/pnpify to v3.0.0-rc.11 [#71](https://github.com/mbround18/foundryvtt-docker/pull/71) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 291 | - Update dependency supports-color to v9.0.2 [#70](https://github.com/mbround18/foundryvtt-docker/pull/70) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 292 | - Update dependency webpack to v5.45.1 [#69](https://github.com/mbround18/foundryvtt-docker/pull/69) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 293 | - Update dependency webpack to v5.45.0 [#68](https://github.com/mbround18/foundryvtt-docker/pull/68) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 294 | - Update dependency webpack to v5.44.0 [#65](https://github.com/mbround18/foundryvtt-docker/pull/65) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 295 | - Update dependency nodemon to v2.0.12 [#67](https://github.com/mbround18/foundryvtt-docker/pull/67) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 296 | - Update dependency nodemon to v2.0.11 [#66](https://github.com/mbround18/foundryvtt-docker/pull/66) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 297 | - Update dependency nodemon to v2.0.10 [#64](https://github.com/mbround18/foundryvtt-docker/pull/64) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 298 | - Update dependency webpack to v5.43.0 [#62](https://github.com/mbround18/foundryvtt-docker/pull/62) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 299 | - Update dependency sass to v1.35.2 [#63](https://github.com/mbround18/foundryvtt-docker/pull/63) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 300 | - Update dependency express-rate-limit to v5.3.0 [#59](https://github.com/mbround18/foundryvtt-docker/pull/59) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 301 | - Update dependency webpack to v5.42.1 [#60](https://github.com/mbround18/foundryvtt-docker/pull/60) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 302 | - Update dependency @yarnpkg/pnpify to v3.0.0-rc.10 [#61](https://github.com/mbround18/foundryvtt-docker/pull/61) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 303 | - Update dependency nodemon to v2.0.9 [#58](https://github.com/mbround18/foundryvtt-docker/pull/58) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 304 | - Update dependency nodemon to v2.0.8 [#55](https://github.com/mbround18/foundryvtt-docker/pull/55) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 305 | - Update dependency vuetify to v2.5.6 [#57](https://github.com/mbround18/foundryvtt-docker/pull/57) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 306 | - Update dependency webpack to v5.41.1 [#56](https://github.com/mbround18/foundryvtt-docker/pull/56) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 307 | - Update dependency webpack to v5.41.0 [#54](https://github.com/mbround18/foundryvtt-docker/pull/54) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 308 | - Update dependency html-webpack-plugin to v5.3.2 [#51](https://github.com/mbround18/foundryvtt-docker/pull/51) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 309 | - Update dependency @yarnpkg/pnpify to v3.0.0-rc.7 [#52](https://github.com/mbround18/foundryvtt-docker/pull/52) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 310 | - Update dependency vuetify to v2.5.5 [#53](https://github.com/mbround18/foundryvtt-docker/pull/53) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 311 | - Update dependency webpack to v5.40.0 [#49](https://github.com/mbround18/foundryvtt-docker/pull/49) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 312 | - Update dependency @babel/preset-env to v7.14.7 [#50](https://github.com/mbround18/foundryvtt-docker/pull/50) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 313 | - Update dependency vuetify to v2.5.4 [#47](https://github.com/mbround18/foundryvtt-docker/pull/47) ([@renovate-bot](https://github.com/renovate-bot) [@mbround18](https://github.com/mbround18) [@renovate[bot]](https://github.com/renovate[bot])) 314 | - Update dependency sass to v1.35.1 [#48](https://github.com/mbround18/foundryvtt-docker/pull/48) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 315 | - Update dependency nuxt to v2.15.7 [#43](https://github.com/mbround18/foundryvtt-docker/pull/43) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 316 | - Update babel monorepo to v7.14.6 [#44](https://github.com/mbround18/foundryvtt-docker/pull/44) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 317 | - Update dependency webpack to v5.39.0 [#45](https://github.com/mbround18/foundryvtt-docker/pull/45) ([@renovate-bot](https://github.com/renovate-bot) [@mbround18](https://github.com/mbround18) [@renovate[bot]](https://github.com/renovate[bot])) 318 | - Update dependency sass to v1.35.0 [#46](https://github.com/mbround18/foundryvtt-docker/pull/46) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 319 | - Update dependency @nuxtjs/vuetify to v1.12.1 [#42](https://github.com/mbround18/foundryvtt-docker/pull/42) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 320 | - Update babel monorepo to v7.14.5 [#41](https://github.com/mbround18/foundryvtt-docker/pull/41) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 321 | - Update dependency @auto-it/core to v10.29.3 [#39](https://github.com/mbround18/foundryvtt-docker/pull/39) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 322 | - Update dependency auto to v10.29.3 [#40](https://github.com/mbround18/foundryvtt-docker/pull/40) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 323 | - Update dependency webpack-cli to v4.7.2 [#38](https://github.com/mbround18/foundryvtt-docker/pull/38) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 324 | - Update dependency webpack-cli to v4.7.1 [#37](https://github.com/mbround18/foundryvtt-docker/pull/37) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 325 | - Update dependency @yarnpkg/pnpify to v3.0.0-rc.6 [#35](https://github.com/mbround18/foundryvtt-docker/pull/35) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 326 | - Update dependency vuetify to v2.5.3 [#33](https://github.com/mbround18/foundryvtt-docker/pull/33) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 327 | - Update dependency vuetify to v2.5.2 [#31](https://github.com/mbround18/foundryvtt-docker/pull/31) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 328 | - Update dependency supports-color to v9.0.1 [#29](https://github.com/mbround18/foundryvtt-docker/pull/29) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 329 | - Update dependency @babel/preset-env to v7.14.4 [#28](https://github.com/mbround18/foundryvtt-docker/pull/28) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 330 | - Update dependency webpack to v5.38.1 [#27](https://github.com/mbround18/foundryvtt-docker/pull/27) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 331 | - Update dependency webpack to v5.38.0 [#26](https://github.com/mbround18/foundryvtt-docker/pull/26) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 332 | - Update dependency webpack to v5.37.1 [#19](https://github.com/mbround18/foundryvtt-docker/pull/19) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 333 | 334 | #### ⚠️ Pushed to `main` 335 | 336 | - Create LICENSE.md ([@mbround18](https://github.com/mbround18)) 337 | - Create FUNDING.yml ([@mbround18](https://github.com/mbround18)) 338 | - Create .kodiak.toml ([@mbround18](https://github.com/mbround18)) 339 | 340 | #### 📝 Documentation 341 | 342 | - Update dependency sass to v1.34.1 [#34](https://github.com/mbround18/foundryvtt-docker/pull/34) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 343 | - Update README.md [#24](https://github.com/mbround18/foundryvtt-docker/pull/24) ([@mbround18](https://github.com/mbround18)) 344 | - [ImgBot] Optimize images [#21](https://github.com/mbround18/foundryvtt-docker/pull/21) ([@ImgBotApp](https://github.com/ImgBotApp) [@imgbot[bot]](https://github.com/imgbot[bot])) 345 | 346 | #### 🔩 Dependency Updates 347 | 348 | - Pin dependencies [#22](https://github.com/mbround18/foundryvtt-docker/pull/22) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 349 | 350 | #### Authors: 6 351 | 352 | - [@imgbot[bot]](https://github.com/imgbot[bot]) 353 | - [@kodiakhq[bot]](https://github.com/kodiakhq[bot]) 354 | - [@renovate[bot]](https://github.com/renovate[bot]) 355 | - Imgbot ([@ImgBotApp](https://github.com/ImgBotApp)) 356 | - Michael ([@mbround18](https://github.com/mbround18)) 357 | - WhiteSource Renovate ([@renovate-bot](https://github.com/renovate-bot)) 358 | 359 | --- 360 | 361 | # v1.0.0 (Thu May 27 2021) 362 | 363 | #### 💥 Breaking Change 364 | 365 | - Mbround18/release ready [#20](https://github.com/mbround18/foundryvtt-docker/pull/20) ([@mbround18](https://github.com/mbround18)) 366 | 367 | #### 🐛 Bug Fix 368 | 369 | - Update babel monorepo to v7.14.3 [#18](https://github.com/mbround18/foundryvtt-docker/pull/18) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 370 | - Update babel monorepo to v7.14.2 [#17](https://github.com/mbround18/foundryvtt-docker/pull/17) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 371 | - Update dependency webpack to v5.37.0 [#16](https://github.com/mbround18/foundryvtt-docker/pull/16) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 372 | - Update dependency webpack-cli to v4.7.0 [#14](https://github.com/mbround18/foundryvtt-docker/pull/14) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 373 | - Update dependency webpack to v5.36.2 [#13](https://github.com/mbround18/foundryvtt-docker/pull/13) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 374 | - Update dependency html-webpack-plugin to v5 [#15](https://github.com/mbround18/foundryvtt-docker/pull/15) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 375 | - Update babel monorepo [#12](https://github.com/mbround18/foundryvtt-docker/pull/12) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 376 | - Pin dependencies [#11](https://github.com/mbround18/foundryvtt-docker/pull/11) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 377 | - Configure Renovate [#10](https://github.com/mbround18/foundryvtt-docker/pull/10) ([@renovate-bot](https://github.com/renovate-bot) [@renovate[bot]](https://github.com/renovate[bot])) 378 | - Removed the uploading zip and simplified uploading process [#6](https://github.com/mbround18/foundryvtt-docker/pull/6) (no-reply@bruno.fyi [@mbround18](https://github.com/mbround18)) 379 | - Updated readme, scripts, deps [#2](https://github.com/mbround18/foundryvtt-docker/pull/2) (no-reply@bruno.fyi [@mbround18](https://github.com/mbround18)) 380 | 381 | #### ⚠️ Pushed to `main` 382 | 383 | - reduced docker image size ([@mbround18](https://github.com/mbround18)) 384 | - Updated deps ([@mbround18](https://github.com/mbround18)) 385 | - Merge branch 'master' of github.com:mbround18/foundryvtt-docker ([@mbround18](https://github.com/mbround18)) 386 | - Create CODEOWNERS ([@mbround18](https://github.com/mbround18)) 387 | - Update README.md ([@mbround18](https://github.com/mbround18)) 388 | - Replaced line endings ([@mbround18](https://github.com/mbround18)) 389 | - Updated to support wget method (no-reply@bruno.fyi) 390 | - remove development modules (no-reply@bruno.fyi) 391 | - Build web app in the container (no-reply@bruno.fyi) 392 | - Removed githubs workflows' (no-reply@bruno.fyi) 393 | - Cleaned up readme (no-reply@bruno.fyi) 394 | - Uploader :) (no-reply@bruno.fyi) 395 | - Create dockerimage.yml ([@mbround18](https://github.com/mbround18)) 396 | - added a new line (no-reply@bruno.fyi) 397 | - Readme update (no-reply@bruno.fyi) 398 | - dockerfile and shell script (no-reply@bruno.fyi) 399 | - Initial commit (no-reply@bruno.fyi) 400 | 401 | #### 🔩 Dependency Updates 402 | 403 | - Bump axios from 0.19.2 to 0.21.1 [#8](https://github.com/mbround18/foundryvtt-docker/pull/8) ([@dependabot[bot]](https://github.com/dependabot[bot])) 404 | - Bump ini from 1.3.5 to 1.3.8 [#7](https://github.com/mbround18/foundryvtt-docker/pull/7) ([@dependabot[bot]](https://github.com/dependabot[bot])) 405 | - Bump elliptic from 6.5.2 to 6.5.3 [#4](https://github.com/mbround18/foundryvtt-docker/pull/4) ([@dependabot[bot]](https://github.com/dependabot[bot])) 406 | - Bump lodash from 4.17.15 to 4.17.19 [#3](https://github.com/mbround18/foundryvtt-docker/pull/3) ([@dependabot[bot]](https://github.com/dependabot[bot])) 407 | 408 | #### Authors: 5 409 | 410 | - [@dependabot[bot]](https://github.com/dependabot[bot]) 411 | - [@renovate[bot]](https://github.com/renovate[bot]) 412 | - Michael ([@mbround18](https://github.com/mbround18)) 413 | - Michael Bruno (no-reply@bruno.fyi) 414 | - WhiteSource Renovate ([@renovate-bot](https://github.com/renovate-bot)) 415 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "server", 5 | ] 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG RUST_VERSION=1.86 2 | ARG NODE_VERSION=22 3 | FROM rust:${RUST_VERSION}-alpine AS builder 4 | 5 | WORKDIR /application 6 | 7 | COPY Cargo.toml Cargo.lock ./ 8 | COPY server/Cargo.toml server/ 9 | 10 | RUN apk add --no-cache \ 11 | musl-dev \ 12 | build-base \ 13 | openssl-dev \ 14 | pkgconfig \ 15 | curl \ 16 | bash \ 17 | cmake 18 | 19 | 20 | RUN --mount=type=cache,target=/usr/local/cargo/registry \ 21 | cargo fetch 22 | 23 | COPY . . 24 | 25 | RUN --mount=type=cache,target=/usr/local/cargo/registry \ 26 | rustup target add x86_64-unknown-linux-musl && \ 27 | cargo build --release --target x86_64-unknown-linux-musl \ 28 | && mv target/x86_64-unknown-linux-musl/release/foundry-watcher target/release/foundry-watcher 29 | 30 | FROM node:${NODE_VERSION}-alpine AS runtime 31 | 32 | ARG CROC_VERSION=10.2.2 33 | RUN apk add --no-cache \ 34 | curl \ 35 | iproute2 \ 36 | net-tools \ 37 | shadow \ 38 | sudo \ 39 | bash \ 40 | && curl -L https://github.com/schollz/croc/releases/download/v${CROC_VERSION}/croc_v${CROC_VERSION}_Linux-64bit.tar.gz \ 41 | | tar -xz -C /usr/local/bin/ \ 42 | && groupdel $(getent group 1000 | cut -d: -f1) 2>/dev/null || true \ 43 | && userdel -f $(getent passwd 1000 | cut -d: -f1) 2>/dev/null || true \ 44 | && groupadd -g 1001 sudo \ 45 | && groupadd -g 1000 node \ 46 | && useradd -m -u 1000 -g 1000 -s /bin/bash node \ 47 | && usermod -aG sudo node \ 48 | && echo "node ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 49 | 50 | # Copy the binary ensuring it exists and is executable 51 | COPY --from=builder --chmod=0755 /application/target/release/foundry-watcher /usr/local/bin/foundry-watcher 52 | # Add better verification that the file exists and is executable 53 | RUN ls -la /usr/local/bin/foundry-watcher && \ 54 | chmod +x /usr/local/bin/foundry-watcher && \ 55 | chown node:node /usr/local/bin/foundry-watcher 56 | 57 | ENV APPLICATION_HOST="foundry.vtt" \ 58 | APPLICATION_PORT="4444" \ 59 | SSL_PROXY="true" \ 60 | APPLICATION_DIR="/foundryvtt" \ 61 | DATA_DIR="/foundrydata" \ 62 | STATIC_FILES_DIR="/foundry-watcher/frontend" \ 63 | SERVER_PORT="4444" \ 64 | SERVER_HOST="0.0.0.0" \ 65 | TARGET_DIR="/foundryvtt" 66 | 67 | EXPOSE ${APPLICATION_PORT} 68 | 69 | WORKDIR ${DATA_DIR} 70 | COPY ./server/static /foundry-watcher/frontend 71 | RUN mkdir -p /foundryvtt /foundrydata /foundry-watcher/frontend \ 72 | && chown -R node:node /foundry-watcher/frontend \ 73 | && chmod -R 755 /foundry-watcher/frontend \ 74 | && chown -R node:node /foundryvtt \ 75 | && chmod -R 755 /foundryvtt \ 76 | && chown -R node:node /foundrydata \ 77 | && chmod -R 755 /foundrydata \ 78 | && npm install -g npm 79 | 80 | USER node 81 | 82 | RUN ls -la /usr/local/bin/foundry-watcher && \ 83 | chmod +x /usr/local/bin/foundry-watcher && \ 84 | chown node:node /usr/local/bin/foundry-watcher 85 | 86 | 87 | # Set the entrypoint to run the Rust application directly 88 | ENTRYPOINT ["/usr/local/bin/foundry-watcher"] 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | 3 | Copyright (c) 2024, MBRound18 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Michael Bruno 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build lint 2 | 3 | lint: 4 | @npx -y prettier --write . 5 | @cargo fmt 6 | @cargo clippy 7 | 8 | build: lint 9 | @cargo build 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # FoundryVTT Docker 4 | 5 |
6 | Docker Pulls 7 | License 8 | GitHub Stars 9 |
10 | 11 | ## Overview 12 | 13 | **⚠️ This docker container requires an active Foundry VTT license. [Purchase one here](https://foundryvtt.com/purchase/).** 14 | 15 | A streamlined Docker container for Foundry Virtual Tabletop with an Actix-powered web uploader. This container was designed with simplicity in mind - no credentials to supply, no web driver configurations, and no web automation required. 16 | 17 | ### Key Features 18 | 19 | - 🚀 **Simple Installation** - Easy-to-use web interface for installation 20 | - 🔒 **Secure** - No credentials stored in the container 21 | - 🔄 **Persistent Storage** - Mount volumes for data and application 22 | - 🌐 **Flexible Networking** - Configurable hostname and SSL options 23 | 24 | ## Quick Start 25 | 26 | ### Running with Docker 27 | 28 | ```sh 29 | docker run --rm -it \ 30 | -p 4444:4444 \ 31 | -e HOSTNAME="127.0.0.1" \ 32 | -e SSL_PROXY="false" \ 33 | -v ${PWD}/foundry/data:/foundrydata \ 34 | -v ${PWD}/foundry/app:/foundryvtt \ 35 | mbround18/foundryvtt-docker:latest 36 | ``` 37 | 38 | ### Running with Docker Compose 39 | 40 | Create a `docker-compose.yml` file: 41 | 42 | ```yaml 43 | version: "3" 44 | services: 45 | foundry: 46 | image: mbround18/foundryvtt-docker:latest 47 | ports: 48 | - "4444:4444" 49 | environment: 50 | - HOSTNAME=127.0.0.1 51 | - SSL_PROXY=false 52 | volumes: 53 | - ./foundry/data:/foundrydata 54 | - ./foundry/app:/foundryvtt 55 | restart: unless-stopped 56 | ``` 57 | 58 | Then run: 59 | 60 | ```sh 61 | docker-compose up -d 62 | ``` 63 | 64 | ## Installation Process 65 | 66 | 1. Launch the container using one of the methods above 67 | 2. Navigate to your installation URL: [http://localhost:4444](http://localhost:4444/) 68 | 3. In another tab, open your Purchased Licenses page on [foundryvtt.com](https://foundryvtt.com/) 69 | 4. Click the link icon to generate a timed download link 70 | 5. Return to [http://localhost:4444](http://localhost:4444/) and paste the timed URL 71 | 6. Click the submit button and monitor the logs 72 | 7. When complete, navigate to [http://localhost:4444/](http://localhost:4444/) to access the Foundry VTT setup screen 73 | 74 | ## Environment Variables 75 | 76 | | Variable | Description | Default | 77 | | --------------------- | --------------------------------------- | --------- | 78 | | `HOSTNAME` | The hostname for the server | `0.0.0.0` | 79 | | `SSL_PROXY` | Whether SSL is being handled by a proxy | `false` | 80 | | `APPLICATION_PORT` | The port the application runs on | `4444` | 81 | | `ADMIN_KEY` | Admin password for Foundry | _(empty)_ | 82 | | `MINIFY_STATIC_FILES` | Whether to minify static files | `true` | 83 | 84 | ## Volumes 85 | 86 | | Path | Description | 87 | | -------------- | -------------------------------------- | 88 | | `/foundrydata` | Foundry user data, worlds, and modules | 89 | | `/foundryvtt` | Foundry application files | 90 | 91 | ## Troubleshooting 92 | 93 | ### Common Issues 94 | 95 | - **Port already in use**: Change the port mapping in your docker run command (e.g., `-p 8080:4444`) 96 | - **Permissions errors**: Ensure your mounted volumes have the correct permissions 97 | - **Download failures**: Verify your Foundry license and that the timed URL is still valid 98 | 99 | ## Contributing 100 | 101 | Contributions are welcome! Feel free to open issues or submit pull requests. 102 | 103 | ## License 104 | 105 | This project is licensed under the [BSD 3-Clause License](LICENSE.md). 106 | -------------------------------------------------------------------------------- /assets/foundryvtt-click-timed-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbround18/foundryvtt-docker/35c4914b9b992159050d2ba98e606f91411e6ccf/assets/foundryvtt-click-timed-url.png -------------------------------------------------------------------------------- /assets/foundryvtt-click-username.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbround18/foundryvtt-docker/35c4914b9b992159050d2ba98e606f91411e6ccf/assets/foundryvtt-click-username.png -------------------------------------------------------------------------------- /assets/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbround18/foundryvtt-docker/35c4914b9b992159050d2ba98e606f91411e6ccf/assets/upload.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | foundry: 3 | container_name: foundryvtt 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | image: mbround18/foundryvtt-docker:latest 8 | user: "1000:1000" 9 | ports: 10 | - "4444:4444" 11 | - "3000:3000" 12 | volumes: 13 | - data:/foundrydata 14 | - app:/foundryvtt 15 | environment: 16 | APPLICATION_DIR: /foundryvtt 17 | DATA_DIR: /foundrydata 18 | APPLICATION_HOST: foundry.vtt 19 | SSL_PROXY: "true" 20 | RUST_LOG: "debug" 21 | EMPTY_APP_DIR_ON_START: "true" 22 | restart: no 23 | healthcheck: 24 | test: ["CMD", "curl", "-f", "http://localhost:4444"] 25 | interval: 30s 26 | timeout: 10s 27 | retries: 5 28 | 29 | # Define named volumes for persistence 30 | volumes: 31 | data: 32 | driver: local 33 | driver_opts: 34 | type: none 35 | o: bind 36 | device: ${FOUNDRY_DATA_PATH:-./tmp/data} 37 | app: 38 | driver: local 39 | driver_opts: 40 | type: none 41 | o: bind 42 | device: ${FOUNDRY_APP_PATH:-./tmp/app} 43 | -------------------------------------------------------------------------------- /docs/kube.md: -------------------------------------------------------------------------------- 1 | # Installation on Kubernetes 2 | 3 | > I built my setup on a k3s stack. Please take a look at [k3s.io](https://k3s.io) for details on setting up k3s. Otherwise you may have to tweak your configuration a bit. 4 | 5 | ### Installation Steps 6 | 7 | 1. Add a namespace 8 | 9 | > \${PWD}/00-namespace.yml 10 | 11 | ```yaml 12 | --- 13 | apiVersion: v1 14 | kind: Namespace 15 | metadata: 16 | name: foundryvtt 17 | labels: 18 | name: foundryvtt 19 | ``` 20 | 21 | 2. Set up a persisted volume claim. 22 | Note: You must already have a persisted volume storage configuration setup. In this example I am using [k3s.io](https://k3s.io) which already has a local storage persisted volume claim setup. 23 | 24 | > \${PWD}/01-persisted-volume-claim.yml 25 | 26 | ```yaml 27 | --- 28 | apiVersion: v1 29 | kind: PersistentVolumeClaim 30 | metadata: 31 | name: foundryvtt-data-pv-claim 32 | namespace: foundryvtt 33 | spec: 34 | storageClassName: local-path 35 | accessModes: 36 | - ReadWriteOnce 37 | resources: 38 | requests: 39 | storage: 40G 40 | ``` 41 | 42 | 3. Setup the deployment, in this I have configured an SFTP server along side my foundry setup for file system access. 43 | Note 1: The known issue here is, after you first launch the SFTP server and log into it, you might have to format the permissions to allow reading and writting. I am currenly working on a solution for this. 44 | Note 2: With the SFTP server you will have to have a dedicated IP address on a node in its current configuration... (if you know a way to put it behind ingress and a domain name please DM me on discord. 45 | 46 | > \${PWD}/02-deployment.yml 47 | 48 | ```yaml 49 | --- 50 | apiVersion: apps/v1 51 | kind: Deployment 52 | metadata: 53 | namespace: foundryvtt 54 | name: foundryvtt 55 | labels: 56 | app: foundryvtt 57 | 58 | spec: 59 | replicas: 1 60 | selector: 61 | matchLabels: 62 | app: foundryvtt 63 | template: 64 | metadata: 65 | labels: 66 | app: foundryvtt 67 | spec: 68 | containers: 69 | - name: foundryvtt-web 70 | image: mbround18/foundryvtt-docker:latest 71 | env: 72 | - name: APPLICATION_HOST 73 | valueFrom: 74 | secretKeyRef: 75 | name: foundryvtt-env 76 | key: APPLICATION_HOST 77 | - name: APPLICATION_PORT 78 | value: "4444" 79 | - name: SSL_PROXY 80 | value: "true" 81 | ports: 82 | - name: web 83 | containerPort: 4444 84 | volumeMounts: 85 | - name: foundryvtt-data-persistent-storage 86 | mountPath: /foundrydata 87 | - name: foundryvtt-ftp 88 | image: atmoz/sftp 89 | env: 90 | - name: FTP_USER 91 | value: gm 92 | - name: FTP_PASS 93 | valueFrom: 94 | secretKeyRef: 95 | name: foundryvtt-env 96 | key: FTP_PASS 97 | command: [] 98 | args: ["$(FTP_USER):$(FTP_PASS):1001:95687:foundryvtt-data"] 99 | ports: 100 | - name: sftp 101 | containerPort: 22 102 | volumeMounts: 103 | - name: foundryvtt-data-persistent-storage 104 | mountPath: /home/gm/foundryvtt-data 105 | volumes: 106 | - name: foundryvtt-data-persistent-storage 107 | persistentVolumeClaim: 108 | claimName: foundryvtt-data-pv-claim 109 | ``` 110 | 111 | Note: In a folder called secrets I have placed the env file: 112 | 113 | > \${PWD}/secrets/env.txt 114 | 115 | ```txt 116 | APPLICATION_HOST=your.domain 117 | FTP_PASS=changeme 118 | ``` 119 | 120 | You will have to update these accordingly to your setup. 121 | 122 | 4. Create the service file 123 | 124 | > \${PWD}/03-web-service.yml 125 | 126 | ```yaml 127 | --- 128 | apiVersion: v1 129 | kind: Service 130 | metadata: 131 | name: foundryvtt-web 132 | labels: 133 | name: foundryvtt-web 134 | namespace: foundryvtt 135 | spec: 136 | selector: 137 | app: foundryvtt 138 | ports: 139 | - name: web 140 | port: 80 141 | targetPort: 4444 142 | --- 143 | apiVersion: v1 144 | kind: Service 145 | metadata: 146 | name: foundryvtt-ftp 147 | labels: 148 | name: foundryvtt-ftp 149 | namespace: foundryvtt 150 | spec: 151 | type: NodePort 152 | selector: 153 | app: foundryvtt 154 | externalIPs: 155 | - xx.xx.xx.xx 156 | ports: 157 | - port: 2222 158 | targetPort: 22 159 | ``` 160 | 161 | 5. Configure the Ingress for the web client 162 | Note: As a reminder, I am using [k3s.io](https://k3s.io) for my deployment. So when it comes to ingress, it ships with traefik and you might have to alter the configuration to match your setup. 163 | 164 | > \${PWD}/04-ingress.yml 165 | 166 | ```yaml 167 | --- 168 | apiVersion: extensions/v1beta1 169 | kind: Ingress 170 | metadata: 171 | name: foundryvtt 172 | namespace: foundryvtt 173 | annotations: 174 | kubernetes.io/ingress.class: traefik 175 | spec: 176 | rules: 177 | - host: your.domain 178 | http: 179 | paths: 180 | - backend: 181 | serviceName: foundryvtt-web 182 | servicePort: web 183 | tls: 184 | - secretName: foundryvtt-web-tls-cert 185 | ``` 186 | 187 | 6. Lets link all this together with a `kustomization.yml` 188 | 189 | > \${PWD}/kustomization.yml 190 | 191 | ```yml 192 | --- 193 | apiVersion: kustomize.config.k8s.io/v1beta1 194 | kind: Kustomization 195 | namespace: foundryvtt 196 | 197 | commonLabels: 198 | app: foundryvtt 199 | 200 | resources: 201 | - 00-namespace.yml 202 | - 01-persisted-volume-claim.yml 203 | - 02-deployment.yml 204 | - 03-web-service.yml 205 | - 04-ingress.yml 206 | 207 | secretGenerator: 208 | - name: foundryvtt-env 209 | # env is a path to a file to read lines of key=val 210 | # you can only specify one env file per secret. 211 | env: secrets/env.txt 212 | type: Opaque 213 | - name: foundryvtt-web-tls-cert 214 | files: 215 | - secrets/tls.crt 216 | - secrets/tls.key 217 | type: "kubernetes.io/tls" 218 | ``` 219 | 220 | 7. Before we execute, we need to generate TLS certificates. In my case I have a ruby script that generates certificates before deployment but for this example, you can generate your TLS secrets with the following command: 221 | 222 | ```sh 223 | openssl req –new –newkey rsa:4096 –nodes –keyout secrets/tls.key –out secrets/tls.crt 224 | ``` 225 | 226 | Follow the prompts and if you have questions [Click Here for more info](https://www.digicert.com/kb/csr-ssl-installation/ubuntu-server-with-apache2-openssl.htm). Once completed this should create the following files: 227 | 228 | > ${PWD}/secrets/tls.key 229 | > ${PWD}/secrets/tls.crt 230 | 231 | 8. Open up CloudFlare or what ever you are using to configure your DNS Provider and setup a CNAME to point to your root domain. Or set up an A record to point to the IP address of your master node. In my case I have my base domain pointing to my master node, so that andy CNAME only has to point to the root domain. If this step is confusing, please [Click Here for more info](https://www.cloudflare.com/learning/dns/dns-records/dns-cname-record/). 232 | 233 | 9. Now its time to stand up the instance! :) Just run: 234 | 235 | ```sh 236 | kubectl apply -k . 237 | ``` 238 | 239 | 10. Give it a couple minute, and your instance should be online :) 240 | 11. Follow post installation steps as needed. 241 | 242 | ### Post Installation (Kube) 243 | 244 | Much like the post installation of a local setup, you will still have to upload the initial package. This can be accomplished by the following steps. 245 | 246 | 1. Navigate to `https://your.domain/uploader` 247 | 2. Open your profile on FoundryVTT 248 | 3. Navigate to your Purchased Licenses page 249 | 4. Click the link icon to get a timed link. 250 | 5. Paste that link on the uploader screen. 251 | 6. CLick the submit button. 252 | 7. If you get a Completed!! message, navigate to `https://your.domain/` and setup foundry as your normally would. 253 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "labels": ["dependencies", "automerge"] 4 | } 5 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server" 3 | version = "0.2.0" 4 | edition = "2024" 5 | 6 | [[bin]] 7 | name = "foundry-watcher" 8 | path = "src/main.rs" 9 | 10 | [dependencies] 11 | actix-web = "4" 12 | actix-files = "0.6" 13 | reqwest = { version = "0.12", default-features = false, features = ["json", "blocking", "stream", "rustls-tls"] } 14 | tokio = { version = "1", features = ["full"] } 15 | zip = "4" 16 | serde = { version = "1", features = ["derive"] } 17 | tracing = "0.1" 18 | tracing-actix-web = "0.7" 19 | tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } 20 | chrono = "0.4.40" 21 | anyhow = "1.0.97" 22 | lazy_static = "1.5.0" 23 | serde_json = "1" 24 | bytes = "1" 25 | futures-util = "0.3" 26 | actix-multipart = "0" 27 | -------------------------------------------------------------------------------- /server/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::paths; 2 | use std::env; 3 | 4 | pub struct AppConfig { 5 | pub static_files_dir: String, 6 | pub server_port: u16, 7 | pub server_host: String, 8 | pub target_dir: String, 9 | pub foundry_args: Vec, 10 | pub foundry_script: String, 11 | } 12 | 13 | impl AppConfig { 14 | pub fn from_env() -> Self { 15 | let static_files_dir = 16 | env::var("STATIC_FILES_DIR").unwrap_or_else(|_| "static".to_string()); 17 | 18 | let server_port = env::var("SERVER_PORT") 19 | .or_else(|_| env::var("APPLICATION_PORT")) 20 | .unwrap_or_else(|_| "4444".to_string()) 21 | .parse::() 22 | .unwrap_or(4444); 23 | 24 | let server_host = env::var("SERVER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string()); 25 | 26 | let target_dir = get_target_directory(); 27 | 28 | let foundry_host = 29 | env::var("APPLICATION_HOST").unwrap_or("foundry.vtt".to_string()); 30 | 31 | let foundry_args = vec![ 32 | format!("--dataPath={}", *paths::DATA_DIR), 33 | format!("--port={}", server_port), 34 | format!("--hostname={}", foundry_host), 35 | "--noupnp".to_string(), 36 | "--proxySSL".to_string(), 37 | ]; 38 | 39 | let foundry_script = paths::FOUNDRY_SCRIPT_PATH.to_string_lossy().to_string(); 40 | 41 | Self { 42 | static_files_dir, 43 | server_port, 44 | server_host, 45 | target_dir, 46 | foundry_args, 47 | foundry_script, 48 | } 49 | } 50 | } 51 | 52 | pub(crate) fn get_target_directory() -> String { 53 | // Check for TARGET_DIR first, then APPLICATION_DIR, then fallback 54 | env::var("TARGET_DIR").unwrap_or_else(|_| { 55 | env::var("APPLICATION_DIR").unwrap_or_else(|_| { 56 | let mut dir = env::current_dir().expect("Failed to get current directory"); 57 | dir.push("tmp"); 58 | // Make sure the directory exists 59 | std::fs::create_dir_all(&dir).expect("Failed to create target directory"); 60 | dir.to_str().unwrap().to_string() 61 | }) 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /server/src/downloader.rs: -------------------------------------------------------------------------------- 1 | use crate::events::ProgressEvent; 2 | use reqwest::Client; 3 | use tokio::fs; 4 | use tokio::sync::broadcast; 5 | use tracing::{debug, error, info}; 6 | 7 | pub struct DownloadService; 8 | 9 | impl DownloadService { 10 | /// Download file from `url` and write it to `save_path`, streaming the response. 11 | pub async fn download_file_from_url( 12 | url: &str, 13 | save_path: &str, 14 | event_tx: broadcast::Sender, 15 | ) -> Result<(), actix_web::Error> { 16 | info!("Starting download from URL: {}", url); 17 | 18 | let client = Client::new(); 19 | let mut resp = client.get(url).send().await.map_err(|e| { 20 | error!("Request error: {}", e); 21 | actix_web::error::ErrorInternalServerError(format!("Failed to send request: {}", e)) 22 | })?; 23 | 24 | // Check the response status 25 | if !resp.status().is_success() { 26 | let status = resp.status(); 27 | error!("Download request failed with status: {}", status); 28 | return Err(actix_web::error::ErrorInternalServerError(format!( 29 | "Download failed with status: {}", 30 | status 31 | ))); 32 | } 33 | 34 | // Get content length if available for progress calculation 35 | let content_length = resp.content_length().unwrap_or(0); 36 | if content_length > 0 { 37 | debug!("Content length: {} bytes", content_length); 38 | let _ = event_tx.send(ProgressEvent::new( 39 | "downloading", 40 | &format!("Download size: {} MB", content_length / (1024 * 1024)), 41 | Some(15.0), 42 | )); 43 | } else { 44 | let _ = event_tx.send(ProgressEvent::new( 45 | "downloading", 46 | "Download size unknown", 47 | Some(15.0), 48 | )); 49 | } 50 | 51 | let mut out = fs::File::create(save_path).await.map_err(|e| { 52 | error!("Failed to create file: {}", e); 53 | actix_web::error::ErrorInternalServerError(format!("Failed to create file: {}", e)) 54 | })?; 55 | info!("Saving downloaded file to: {}", save_path); 56 | 57 | // Use a buffer to track download progress 58 | let mut downloaded: u64 = 0; 59 | while let Some(chunk) = resp.chunk().await.map_err(|e| { 60 | error!("Failed reading download stream: {}", e); 61 | actix_web::error::ErrorInternalServerError(format!( 62 | "Failed reading download stream: {}", 63 | e 64 | )) 65 | })? { 66 | use tokio::io::AsyncWriteExt; 67 | out.write_all(&chunk).await.map_err(|e| { 68 | error!("Failed to write file: {}", e); 69 | actix_web::error::ErrorInternalServerError(format!("Failed to write file: {}", e)) 70 | })?; 71 | 72 | downloaded += chunk.len() as u64; 73 | 74 | // Calculate progress between 15-50% for download phase 75 | if content_length > 0 { 76 | let progress_percent = (downloaded as f64 / content_length as f64) * 100.0; 77 | let normalized_progress = 15.0 + (progress_percent * 0.35); 78 | 79 | // Log progress every 5MB or 10% progress 80 | if downloaded % (5 * 1024 * 1024) < chunk.len() as u64 81 | || progress_percent % 10.0 82 | < (chunk.len() as f64 / content_length as f64 * 100.0) 83 | { 84 | debug!( 85 | "Downloaded: {} MB ({:.1}%)", 86 | downloaded / (1024 * 1024), 87 | progress_percent 88 | ); 89 | let _ = event_tx.send(ProgressEvent::new( 90 | "downloading", 91 | &format!( 92 | "Downloaded: {:.1} MB ({:.0}%)", 93 | downloaded as f32 / (1024.0 * 1024.0), 94 | progress_percent 95 | ), 96 | Some(normalized_progress as f32), 97 | )); 98 | } 99 | } else if downloaded % (5 * 1024 * 1024) < chunk.len() as u64 { 100 | // If content length is unknown, just show downloaded amount 101 | debug!("Downloaded: {} MB", downloaded / (1024 * 1024)); 102 | let _ = event_tx.send(ProgressEvent::new( 103 | "downloading", 104 | &format!( 105 | "Downloaded: {:.1} MB", 106 | downloaded as f32 / (1024.0 * 1024.0) 107 | ), 108 | Some(30.0), 109 | )); 110 | } 111 | } 112 | 113 | info!("Download completed successfully: {} bytes", downloaded); 114 | Ok(()) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /server/src/events.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{Error, HttpResponse, web}; 2 | use bytes::Bytes; 3 | use futures_util::stream::{self}; 4 | use serde::Serialize; 5 | use std::time::Duration; 6 | use tokio::time::interval; 7 | use tracing::{debug, error}; 8 | 9 | use crate::server::AppState; 10 | 11 | #[derive(Debug, Serialize, Clone)] 12 | pub struct ProgressEvent { 13 | pub event_type: String, 14 | pub message: String, 15 | pub progress: Option, 16 | } 17 | 18 | impl ProgressEvent { 19 | pub fn new(event_type: &str, message: &str, progress: Option) -> Self { 20 | Self { 21 | event_type: event_type.to_string(), 22 | message: message.to_string(), 23 | progress, 24 | } 25 | } 26 | 27 | pub fn to_sse_format(&self) -> String { 28 | let json = serde_json::to_string(self).unwrap_or_else(|e| { 29 | error!("Failed to serialize event: {}", e); 30 | "{}".to_string() 31 | }); 32 | format!("data: {}\n\n", json) 33 | } 34 | } 35 | 36 | pub async fn sse_events(data: web::Data) -> HttpResponse { 37 | debug!("Client connected to SSE events endpoint"); 38 | let rx = data.event_channel.subscribe(); 39 | 40 | // Create a stream that combines events with keepalive pings 41 | // We need to wrap the state in Arc> to avoid ownership issues 42 | let stream = stream::unfold( 43 | (rx, interval(Duration::from_secs(15))), 44 | |(mut rx, mut keepalive)| async move { 45 | tokio::select! { 46 | _ = keepalive.tick() => { 47 | debug!("Sending SSE keepalive"); 48 | Some((Ok::<_, Error>(Bytes::from(":\n\n")), (rx, keepalive))) 49 | } 50 | event = rx.recv() => { 51 | match event { 52 | Ok(progress_event) => { 53 | debug!("Sending event: {:?}", progress_event); 54 | let bytes = Bytes::from(progress_event.to_sse_format()); 55 | Some((Ok::<_, Error>(bytes), (rx, keepalive))) 56 | } 57 | Err(e) => { 58 | error!("SSE channel error: {}", e); 59 | None 60 | } 61 | } 62 | } 63 | } 64 | }, 65 | ); 66 | 67 | HttpResponse::Ok() 68 | .insert_header(("Content-Type", "text/event-stream")) 69 | .insert_header(("Cache-Control", "no-cache")) 70 | .insert_header(("Connection", "keep-alive")) 71 | .streaming(stream) 72 | } 73 | -------------------------------------------------------------------------------- /server/src/extractor.rs: -------------------------------------------------------------------------------- 1 | use crate::events::ProgressEvent; 2 | use std::fs::File; 3 | use std::path::Path; 4 | use std::sync::Arc; 5 | use tokio::fs; 6 | use tokio::sync::broadcast; 7 | use tokio::task; 8 | use tracing::{debug, error, info, warn}; 9 | use zip::read::ZipArchive; 10 | 11 | pub struct ExtractorService; 12 | 13 | impl ExtractorService { 14 | /// Extract ZIP at `archive_path` into `target_directory` using a blocking task. 15 | pub async fn extract_zip( 16 | archive_path: String, 17 | target_directory: String, 18 | event_tx: broadcast::Sender, 19 | ) -> Result<(), std::io::Error> { 20 | info!("Starting extraction of archive: {}", archive_path); 21 | 22 | // Verify file exists before attempting extraction 23 | if !Path::new(&archive_path).exists() { 24 | let err_msg = format!("Archive file not found at path: {}", archive_path); 25 | error!("{}", err_msg); 26 | let _ = event_tx.send(ProgressEvent::new("error", &err_msg, None)); 27 | return Err(std::io::Error::new(std::io::ErrorKind::NotFound, err_msg)); 28 | } 29 | 30 | // Ensure target directory exists 31 | if !Path::new(&target_directory).exists() { 32 | info!("Creating target directory: {}", &target_directory); 33 | fs::create_dir_all(&target_directory).await?; 34 | } 35 | 36 | // Get file size for logging 37 | let _file_size = match fs::metadata(&archive_path).await { 38 | Ok(metadata) => { 39 | let size_mb = metadata.len() as f64 / 1_048_576.0; 40 | debug!("Archive file size: {:.2} MB", size_mb); 41 | Some(size_mb) 42 | } 43 | Err(e) => { 44 | warn!("Could not get archive file size: {}", e); 45 | None 46 | } 47 | }; 48 | 49 | // Clone the target_directory for use after the spawn_blocking closure 50 | let target_directory_clone = target_directory.clone(); 51 | let event_tx_clone = event_tx.clone(); 52 | let event_tx_for_task = Arc::new(event_tx); 53 | 54 | task::spawn_blocking(move || { 55 | // Print normalized paths for debugging 56 | let archive_path_obj = Path::new(&archive_path); 57 | let target_dir_obj = Path::new(&target_directory); 58 | 59 | debug!( 60 | "Extracting from canonical path: {}", 61 | archive_path_obj.display() 62 | ); 63 | debug!("Extracting to canonical path: {}", target_dir_obj.display()); 64 | 65 | // Open and extract the file 66 | let file = match File::open(&archive_path) { 67 | Ok(f) => f, 68 | Err(e) => { 69 | error!("Failed to open archive file: {}", e); 70 | let _ = event_tx_for_task.send(ProgressEvent::new( 71 | "error", 72 | &format!("Failed to open archive file: {}", e), 73 | None, 74 | )); 75 | return Err(e); 76 | } 77 | }; 78 | 79 | let mut archive = match ZipArchive::new(file) { 80 | Ok(a) => { 81 | let file_count = a.len(); 82 | debug!("Successfully opened ZIP archive with {} files", file_count); 83 | let _ = event_tx_for_task.send(ProgressEvent::new( 84 | "extracting", 85 | &format!("Extracting {} files...", file_count), 86 | Some(65.0), 87 | )); 88 | a 89 | } 90 | Err(e) => { 91 | error!("Failed to read ZIP archive: {}", e); 92 | let _ = event_tx_for_task.send(ProgressEvent::new( 93 | "error", 94 | &format!("Invalid ZIP file: {}", e), 95 | None, 96 | )); 97 | return Err(std::io::Error::new( 98 | std::io::ErrorKind::InvalidData, 99 | format!("Invalid ZIP file: {}", e), 100 | )); 101 | } 102 | }; 103 | 104 | // Extract with detailed error information 105 | match archive.extract(&target_directory) { 106 | Ok(()) => { 107 | debug!("ZIP extraction completed successfully"); 108 | let _ = event_tx_for_task.send(ProgressEvent::new( 109 | "extracted", 110 | "ZIP extraction completed successfully", 111 | Some(85.0), 112 | )); 113 | Ok(()) 114 | } 115 | Err(e) => { 116 | error!("Failed to extract archive: {}", e); 117 | let _ = event_tx_for_task.send(ProgressEvent::new( 118 | "error", 119 | &format!("Extraction failed: {}", e), 120 | None, 121 | )); 122 | // Convert ZipError to std::io::Error 123 | Err(std::io::Error::new( 124 | std::io::ErrorKind::Other, 125 | format!("Extraction failed: {}", e), 126 | )) 127 | } 128 | } 129 | }) 130 | .await 131 | .unwrap_or_else(|e| { 132 | error!("Blocking task panicked during extraction: {}", e); 133 | let _ = event_tx_clone.send(ProgressEvent::new( 134 | "error", 135 | &format!("Extraction thread panicked: {}", e), 136 | None, 137 | )); 138 | Err(std::io::Error::new( 139 | std::io::ErrorKind::Other, 140 | format!("Extraction thread panicked: {}", e), 141 | )) 142 | })?; 143 | 144 | info!( 145 | "Extraction completed successfully to {}", 146 | target_directory_clone 147 | ); 148 | Ok(()) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /server/src/handlers.rs: -------------------------------------------------------------------------------- 1 | use crate::downloader::DownloadService; 2 | use crate::events::ProgressEvent; 3 | use crate::extractor::ExtractorService; 4 | use crate::server::AppState; 5 | use actix_multipart::Multipart; 6 | use actix_web::{HttpResponse, Responder, web}; 7 | use futures_util::StreamExt; 8 | use serde::{Deserialize, Serialize}; 9 | use std::path::Path; 10 | use tokio::fs; 11 | use tokio::io::AsyncWriteExt; 12 | use tokio::sync::broadcast; 13 | use tracing::{debug, error, info, warn}; 14 | 15 | #[derive(Deserialize)] 16 | pub struct UrlPayload { 17 | url: String, 18 | } 19 | 20 | #[derive(Serialize)] 21 | pub struct SuccessResponse { 22 | message: String, 23 | } 24 | 25 | #[derive(Serialize)] 26 | pub struct ErrorResponse { 27 | error: String, 28 | } 29 | 30 | pub async fn info() -> impl Responder { 31 | // This endpoint can be used to check the server status or provide information 32 | HttpResponse::Ok().json(SuccessResponse { 33 | message: "Server is running".to_string(), 34 | }) 35 | } 36 | 37 | // Helper functions to reduce code duplication 38 | 39 | /// Ensures the target directory exists and returns its path 40 | async fn ensure_target_directory( 41 | event_tx: &broadcast::Sender, 42 | ) -> Result { 43 | let target_directory = crate::config::get_target_directory(); 44 | debug!("Target directory for extraction: {}", target_directory); 45 | 46 | // Ensure target directory exists 47 | if !Path::new(&target_directory).exists() { 48 | if let Err(e) = fs::create_dir_all(&target_directory).await { 49 | error!("Failed to create target directory: {}", e); 50 | let _ = event_tx.send(ProgressEvent::new( 51 | "error", 52 | &format!("Failed to create target directory: {}", e), 53 | None, 54 | )); 55 | return Err(HttpResponse::InternalServerError().json(ErrorResponse { 56 | error: format!("Failed to create target directory: {}", e), 57 | })); 58 | } 59 | info!("Created target directory: {}", target_directory); 60 | } 61 | 62 | Ok(target_directory) 63 | } 64 | 65 | /// Extracts a ZIP archive and cleans up the source file 66 | async fn extract_and_cleanup( 67 | archive_path: String, 68 | target_directory: String, 69 | event_tx: broadcast::Sender, 70 | ) -> Result<(), HttpResponse> { 71 | // Send extraction started event 72 | let _ = event_tx.send(ProgressEvent::new( 73 | "extracting", 74 | "Extracting archive...", 75 | Some(60.0), 76 | )); 77 | 78 | // Extract the archive 79 | if let Err(e) = ExtractorService::extract_zip( 80 | archive_path.clone(), 81 | target_directory.clone(), 82 | event_tx.clone(), 83 | ) 84 | .await 85 | { 86 | error!("Extraction error: {}", e); 87 | let _ = event_tx.send(ProgressEvent::new( 88 | "error", 89 | &format!("Failed to extract file: {}", e), 90 | None, 91 | )); 92 | return Err(HttpResponse::InternalServerError().json(ErrorResponse { 93 | error: format!("Failed to extract file: {}", e), 94 | })); 95 | } 96 | 97 | // Cleanup just the zip file, not other content 98 | let _ = event_tx.send(ProgressEvent::new( 99 | "cleanup", 100 | "Cleaning up temporary files...", 101 | Some(90.0), 102 | )); 103 | 104 | if let Err(e) = fs::remove_file(&archive_path).await { 105 | error!("Failed to delete temporary zip file: {}", e); 106 | // Continue despite cleanup failure 107 | } else { 108 | info!("Deleted temporary zip file: {}", archive_path); 109 | } 110 | 111 | Ok(()) 112 | } 113 | 114 | /// Completes the installation process by checking for Foundry and shutting down the installer 115 | async fn complete_installation( 116 | event_tx: broadcast::Sender, 117 | app_state: web::Data, 118 | success_message: &str, 119 | ) -> HttpResponse { 120 | // Send completion event 121 | let _ = event_tx.send(ProgressEvent::new( 122 | "complete", 123 | "Download and extraction complete!", 124 | Some(100.0), 125 | )); 126 | 127 | // Check for foundry script existence 128 | let foundry_script_path = Path::new("/foundryvtt/resources/app/main.js"); 129 | if foundry_script_path.exists() { 130 | info!( 131 | "Foundry main.js detected after extraction: {}", 132 | foundry_script_path.display() 133 | ); 134 | } else { 135 | warn!( 136 | "Foundry main.js not found at expected path after extraction: {}", 137 | foundry_script_path.display() 138 | ); 139 | } 140 | 141 | // Signal the server to shut down 142 | if let Some(tx) = app_state.shutdown_sender.lock().unwrap().take() { 143 | let _ = tx.send(()); 144 | info!("Sent shutdown signal to Actix server"); 145 | let _ = event_tx.send(ProgressEvent::new( 146 | "transition", 147 | "Transitioning to Foundry VTT...", 148 | Some(100.0), 149 | )); 150 | } else { 151 | warn!("Shutdown channel was already used or unavailable"); 152 | } 153 | 154 | HttpResponse::Ok().json(SuccessResponse { 155 | message: success_message.to_string(), 156 | }) 157 | } 158 | 159 | pub async fn download_and_extract( 160 | url_payload: web::Json, 161 | app_state: web::Data, 162 | ) -> impl Responder { 163 | let url = url_payload.url.clone(); 164 | let event_tx = app_state.event_channel.clone(); 165 | 166 | info!("Received request to download and extract from URL: {}", url); 167 | 168 | // Send initial progress event 169 | let _ = event_tx.send(ProgressEvent::new( 170 | "start", 171 | &format!("Starting download from {}", url), 172 | Some(0.0), 173 | )); 174 | 175 | // Ensure target directory exists 176 | let target_directory = match ensure_target_directory(&event_tx).await { 177 | Ok(dir) => dir, 178 | Err(response) => return response, 179 | }; 180 | 181 | let archive_path = format!("{}/archive.zip", target_directory); 182 | debug!("Archive will be saved to: {}", archive_path); 183 | 184 | // Send download started event 185 | let _ = event_tx.send(ProgressEvent::new( 186 | "downloading", 187 | "Downloading archive...", 188 | Some(10.0), 189 | )); 190 | 191 | // Download the archive 192 | if let Err(e) = 193 | DownloadService::download_file_from_url(&url, &archive_path, event_tx.clone()).await 194 | { 195 | error!("Download error: {}", e); 196 | let _ = event_tx.send(ProgressEvent::new( 197 | "error", 198 | &format!("Failed to download file: {}", e), 199 | None, 200 | )); 201 | return HttpResponse::InternalServerError().json(ErrorResponse { 202 | error: format!("Failed to download file: {}", e), 203 | }); 204 | } 205 | 206 | // Verify file exists and has content before extraction 207 | match fs::metadata(&archive_path).await { 208 | Ok(metadata) => { 209 | let size_bytes = metadata.len(); 210 | if size_bytes == 0 { 211 | error!("Downloaded file is empty (0 bytes)"); 212 | let _ = event_tx.send(ProgressEvent::new( 213 | "error", 214 | "Downloaded file is empty", 215 | None, 216 | )); 217 | return HttpResponse::InternalServerError().json(ErrorResponse { 218 | error: "Downloaded file is empty".to_string(), 219 | }); 220 | } 221 | info!("Downloaded file size: {} bytes", size_bytes); 222 | let _ = event_tx.send(ProgressEvent::new( 223 | "downloaded", 224 | &format!("Download complete: {} bytes", size_bytes), 225 | Some(50.0), 226 | )); 227 | } 228 | Err(e) => { 229 | error!("Failed to check downloaded file: {}", e); 230 | let _ = event_tx.send(ProgressEvent::new( 231 | "error", 232 | &format!("Failed to verify downloaded file: {}", e), 233 | None, 234 | )); 235 | return HttpResponse::InternalServerError().json(ErrorResponse { 236 | error: format!("Failed to verify downloaded file: {}", e), 237 | }); 238 | } 239 | } 240 | 241 | // Extract and cleanup 242 | if let Err(response) = 243 | extract_and_cleanup(archive_path, target_directory, event_tx.clone()).await 244 | { 245 | return response; 246 | } 247 | 248 | info!( 249 | "Successfully downloaded and extracted content from: {}", 250 | url 251 | ); 252 | 253 | // Complete installation 254 | complete_installation( 255 | event_tx, 256 | app_state, 257 | &format!("Downloaded and extracted content from: {}", url), 258 | ) 259 | .await 260 | } 261 | 262 | pub async fn upload_and_extract( 263 | mut payload: Multipart, 264 | app_state: web::Data, 265 | ) -> impl Responder { 266 | let event_tx = app_state.event_channel.clone(); 267 | 268 | info!("Received file upload request"); 269 | 270 | // Send initial progress event 271 | let _ = event_tx.send(ProgressEvent::new( 272 | "start", 273 | "Starting file upload process", 274 | Some(0.0), 275 | )); 276 | 277 | // Ensure target directory exists 278 | let target_directory = match ensure_target_directory(&event_tx).await { 279 | Ok(dir) => dir, 280 | Err(response) => return response, 281 | }; 282 | 283 | let archive_path = format!("{}/archive.zip", target_directory); 284 | debug!("Archive will be saved to: {}", archive_path); 285 | 286 | // Process the uploaded file 287 | let mut file = match fs::File::create(&archive_path).await { 288 | Ok(file) => file, 289 | Err(e) => { 290 | error!("Failed to create file: {}", e); 291 | let _ = event_tx.send(ProgressEvent::new( 292 | "error", 293 | &format!("Failed to create file: {}", e), 294 | None, 295 | )); 296 | return HttpResponse::InternalServerError().json(ErrorResponse { 297 | error: format!("Failed to create file: {}", e), 298 | }); 299 | } 300 | }; 301 | 302 | let mut total_bytes = 0; 303 | let mut field_name = String::new(); 304 | 305 | // Process uploaded file chunks 306 | while let Some(field_result) = payload.next().await { 307 | let mut field = match field_result { 308 | Ok(field) => field, 309 | Err(e) => { 310 | error!("Error getting multipart field: {}", e); 311 | let _ = event_tx.send(ProgressEvent::new( 312 | "error", 313 | &format!("Upload error: {}", e), 314 | None, 315 | )); 316 | return HttpResponse::InternalServerError().json(ErrorResponse { 317 | error: format!("Upload error: {}", e), 318 | }); 319 | } 320 | }; 321 | 322 | field_name = field.name().unwrap_or("unknown").to_string(); 323 | let _ = event_tx.send(ProgressEvent::new( 324 | "uploading", 325 | "Uploading file...", 326 | Some(10.0), 327 | )); 328 | 329 | // Process uploaded chunks 330 | let mut field_bytes = 0; 331 | while let Some(chunk) = field.next().await { 332 | let data = match chunk { 333 | Ok(data) => data, 334 | Err(e) => { 335 | error!("Error getting next chunk: {}", e); 336 | let _ = event_tx.send(ProgressEvent::new( 337 | "error", 338 | &format!("Upload error: {}", e), 339 | None, 340 | )); 341 | return HttpResponse::InternalServerError().json(ErrorResponse { 342 | error: format!("Upload error: {}", e), 343 | }); 344 | } 345 | }; 346 | 347 | field_bytes += data.len() as u64; 348 | total_bytes += data.len() as u64; 349 | 350 | // Write chunk to file 351 | if let Err(e) = file.write_all(&data).await { 352 | error!("Failed to write to file: {}", e); 353 | let _ = event_tx.send(ProgressEvent::new( 354 | "error", 355 | &format!("Error writing to file: {}", e), 356 | None, 357 | )); 358 | return HttpResponse::InternalServerError().json(ErrorResponse { 359 | error: format!("Error writing to file: {}", e), 360 | }); 361 | } 362 | 363 | // Update progress (scaling between 10-50%) 364 | if field_bytes % (512 * 1024) == 0 { 365 | // Update every 512KB 366 | let progress = 10.0 + (field_bytes as f32 / 1024.0 / 1024.0); // Rough estimate 367 | let scaled_progress = if progress > 50.0 { 50.0 } else { progress }; 368 | let _ = event_tx.send(ProgressEvent::new( 369 | "uploading", 370 | &format!("Uploaded: {:.1} MB", field_bytes as f32 / 1024.0 / 1024.0), 371 | Some(scaled_progress), 372 | )); 373 | } 374 | } 375 | } 376 | 377 | if total_bytes == 0 { 378 | error!("Uploaded file is empty"); 379 | let _ = event_tx.send(ProgressEvent::new("error", "Uploaded file is empty", None)); 380 | return HttpResponse::BadRequest().json(ErrorResponse { 381 | error: "Uploaded file is empty".to_string(), 382 | }); 383 | } 384 | 385 | info!( 386 | "Upload complete: {} bytes in field '{}'", 387 | total_bytes, field_name 388 | ); 389 | let _ = event_tx.send(ProgressEvent::new( 390 | "uploaded", 391 | &format!( 392 | "Upload complete: {:.2} MB", 393 | total_bytes as f32 / 1024.0 / 1024.0 394 | ), 395 | Some(50.0), 396 | )); 397 | 398 | // Verify file exists and has content before extraction 399 | match fs::metadata(&archive_path).await { 400 | Ok(metadata) => { 401 | let size_bytes = metadata.len(); 402 | if size_bytes == 0 { 403 | error!("Uploaded file is empty (0 bytes)"); 404 | let _ = event_tx.send(ProgressEvent::new("error", "Uploaded file is empty", None)); 405 | return HttpResponse::InternalServerError().json(ErrorResponse { 406 | error: "Uploaded file is empty".to_string(), 407 | }); 408 | } 409 | info!("Uploaded file size: {} bytes", size_bytes); 410 | } 411 | Err(e) => { 412 | error!("Failed to check uploaded file: {}", e); 413 | let _ = event_tx.send(ProgressEvent::new( 414 | "error", 415 | &format!("Failed to verify uploaded file: {}", e), 416 | None, 417 | )); 418 | return HttpResponse::InternalServerError().json(ErrorResponse { 419 | error: format!("Failed to verify uploaded file: {}", e), 420 | }); 421 | } 422 | } 423 | 424 | // Extract and cleanup 425 | if let Err(response) = 426 | extract_and_cleanup(archive_path, target_directory, event_tx.clone()).await 427 | { 428 | return response; 429 | } 430 | 431 | info!("Successfully extracted uploaded content"); 432 | 433 | // Complete installation 434 | complete_installation( 435 | event_tx, 436 | app_state, 437 | "Successfully uploaded and extracted content", 438 | ) 439 | .await 440 | } 441 | -------------------------------------------------------------------------------- /server/src/initialization.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result, anyhow}; 2 | use std::env; 3 | use std::fs; 4 | use std::os::unix::fs::PermissionsExt; 5 | use std::path::Path; 6 | use std::process::Command; 7 | use tracing::{debug, error, info, warn}; 8 | 9 | use crate::config::AppConfig; 10 | use crate::utils::{paths, run_command}; 11 | 12 | pub fn initialize(app_config: &AppConfig) -> Result<()> { 13 | print_banner()?; 14 | print_system_info()?; 15 | check_required_env()?; 16 | validate_env()?; 17 | ensure_directories()?; 18 | 19 | info!("Configuration Summary:"); 20 | info!(" - Application directory: {}", app_config.target_dir); 21 | info!(" - Data directory: {}", *paths::DATA_DIR); 22 | info!( 23 | " - Host: {}", 24 | env::var("APPLICATION_HOST").unwrap_or_else(|_| "foundry.vtt".to_string()) 25 | ); 26 | info!( 27 | " - SSL Proxy: {}", 28 | env::var("SSL_PROXY").unwrap_or_else(|_| "false".to_string()) 29 | ); 30 | info!( 31 | " - Port: {}", 32 | env::var("APPLICATION_PORT").unwrap_or_else(|_| "4444".to_string()) 33 | ); 34 | info!( 35 | " - Empty App Dir On Start: {}", 36 | env::var("EMPTY_APP_DIR_ON_START").unwrap_or_else(|_| "false".to_string()) 37 | ); 38 | 39 | info!("──────────────────────────────────────────────────────────"); 40 | Ok(()) 41 | } 42 | 43 | fn print_banner() -> Result<()> { 44 | info!("──────────────────────────────────────────────────────────"); 45 | info!( 46 | "🎲 FoundryVTT - {}", 47 | chrono::Local::now().format("%Y-%m-%d %H:%M:%S") 48 | ); 49 | info!("──────────────────────────────────────────────────────────"); 50 | Ok(()) 51 | } 52 | 53 | fn print_system_info() -> Result<()> { 54 | // Collect system information in a more compact format 55 | let hostname = run_command("hostname", &[])?.trim().to_string(); 56 | let kernel = run_command("uname", &["-r"])?.trim().to_string(); 57 | let os = run_command( 58 | "sh", 59 | &[ 60 | "-c", 61 | "grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '\"'", 62 | ], 63 | )? 64 | .trim() 65 | .to_string(); 66 | let cpu = run_command( 67 | "sh", 68 | &[ 69 | "-c", 70 | "lscpu | grep 'Model name' | cut -d: -f2 | sed 's/^ *//'", 71 | ], 72 | )? 73 | .trim() 74 | .to_string(); 75 | let memory = run_command("sh", &["-c", "free -h | awk '/^Mem:/ {print $2}'"])? 76 | .trim() 77 | .to_string(); 78 | let disk = run_command("sh", &["-c", "df -h / | awk 'NR==2 {print $4}'"])? 79 | .trim() 80 | .to_string(); 81 | let node_version = run_command("node", &["--version"])?.trim().to_string(); 82 | let npm_version = run_command("npm", &["--version"])?.trim().to_string(); 83 | 84 | info!("System Information:"); 85 | info!(" - Hostname: {}", hostname); 86 | info!(" - OS: {} (Kernel: {})", os, kernel); 87 | info!( 88 | " - Resources: CPU: {}, Memory: {}, Free Disk: {}", 89 | if cpu.is_empty() { "Unknown" } else { &cpu }, 90 | memory, 91 | disk 92 | ); 93 | info!(" - Node: v{}, NPM: v{}", node_version, npm_version); 94 | 95 | debug!("Detailed System Information:"); 96 | debug!(" - Hostname: {}", hostname); 97 | debug!(" - Kernel: {}", kernel); 98 | debug!(" - OS: {}", os); 99 | debug!(" - CPU: {}", if cpu.is_empty() { "Unknown" } else { &cpu }); 100 | debug!(" - Memory: {}", memory); 101 | debug!(" - Disk Space: {}", disk); 102 | debug!(" - Node Version: {}", node_version); 103 | debug!(" - NPM Version: {}", npm_version); 104 | 105 | Ok(()) 106 | } 107 | 108 | fn check_required_env() -> Result<()> { 109 | let required_vars = ["APPLICATION_DIR", "DATA_DIR", "APPLICATION_HOST"]; 110 | let mut missing = false; 111 | 112 | info!("Checking environment variables"); 113 | for var in required_vars { 114 | match env::var(var) { 115 | Ok(value) => debug!("{} = {}", var, value), 116 | Err(_) => { 117 | error!("{} is required but not set!", var); 118 | missing = true; 119 | } 120 | } 121 | } 122 | 123 | if missing { 124 | return Err(anyhow!("Missing required environment variables")); 125 | } 126 | 127 | Ok(()) 128 | } 129 | 130 | fn validate_env() -> Result<()> { 131 | let app_dir = &*paths::APPLICATION_DIR; 132 | let data_dir = &*paths::DATA_DIR; 133 | 134 | if app_dir == data_dir { 135 | error!("APPLICATION_DIR and DATA_DIR cannot be the same!"); 136 | error!(" Application: {}", app_dir); 137 | error!(" Data: {}", data_dir); 138 | return Err(anyhow!("APPLICATION_DIR and DATA_DIR cannot be the same")); 139 | } 140 | 141 | let app_port = env::var("APPLICATION_PORT").unwrap_or_else(|_| "4444".to_string()); 142 | if app_port.parse::().is_err() { 143 | error!("APPLICATION_PORT must be a number: {}", app_port); 144 | return Err(anyhow!("Invalid APPLICATION_PORT")); 145 | } 146 | 147 | Ok(()) 148 | } 149 | 150 | fn ensure_directories() -> Result<()> { 151 | info!("Validating directories"); 152 | 153 | let app_dir = &*paths::APPLICATION_DIR; 154 | let data_dir = &*paths::DATA_DIR; 155 | 156 | // Check if we should empty the application directory 157 | let empty_app_dir = env::var("EMPTY_APP_DIR_ON_START") 158 | .unwrap_or_else(|_| "false".to_string()) 159 | .to_lowercase() 160 | == "true"; 161 | 162 | if empty_app_dir { 163 | let path = Path::new(app_dir); 164 | if path.exists() { 165 | info!("EMPTY_APP_DIR_ON_START is set to true, emptying application directory"); 166 | for entry in fs::read_dir(path)? { 167 | let entry = entry?; 168 | let path = entry.path(); 169 | 170 | if path.is_dir() { 171 | debug!("Removing directory: {:?}", path); 172 | fs::remove_dir_all(&path)?; 173 | } else { 174 | debug!("Removing file: {:?}", path); 175 | fs::remove_file(&path)?; 176 | } 177 | } 178 | info!("Application directory emptied successfully"); 179 | } 180 | } 181 | 182 | for dir in &[app_dir, data_dir] { 183 | let path = Path::new(dir); 184 | 185 | if !path.exists() { 186 | info!("Creating directory: {} (missing)", dir); 187 | fs::create_dir_all(path).with_context(|| format!("Failed to create {}", dir))?; 188 | } 189 | 190 | // Check if directory is writable 191 | let metadata = fs::metadata(path)?; 192 | let permissions = metadata.permissions(); 193 | let is_writable = permissions.mode() & 0o200 != 0; 194 | 195 | if !is_writable { 196 | warn!("Directory not writable: {}. This might cause issues.", dir); 197 | } 198 | 199 | // Print detailed directory info only at debug level 200 | debug!("Directory details for {}:", dir); 201 | debug!( 202 | " - Status: {}", 203 | if path.exists() { 204 | "Exists" 205 | } else { 206 | "Does not exist" 207 | } 208 | ); 209 | debug!(" - Writable: {}", if is_writable { "Yes" } else { "No" }); 210 | 211 | if path.is_dir() { 212 | let file_count = fs::read_dir(path)? 213 | .filter(|entry| entry.as_ref().map(|e| e.path().is_file()).unwrap_or(false)) 214 | .count(); 215 | 216 | let dir_count = fs::read_dir(path)? 217 | .filter(|entry| entry.as_ref().map(|e| e.path().is_dir()).unwrap_or(false)) 218 | .count(); 219 | 220 | debug!( 221 | " - Contents: {} files, {} directories", 222 | file_count, dir_count 223 | ); 224 | } 225 | } 226 | 227 | // Network configuration - simplified for INFO level 228 | let user_info = run_command("id", &["-u"])?.trim().to_string(); 229 | info!("Running as UID: {}", user_info); 230 | 231 | // Network configuration at debug level 232 | debug!("Network configuration:"); 233 | if Command::new("ip").arg("addr").arg("show").status().is_ok() { 234 | let network_info = run_command("ip", &["addr", "show"])?; 235 | // Just log that we have this info, full details in debug 236 | debug!("{}", network_info); 237 | } 238 | 239 | if Command::new("netstat").arg("-tulpn").status().is_ok() { 240 | let ports_info = run_command("netstat", &["-tulpn"])?; 241 | debug!("{}", ports_info); 242 | } else if Command::new("ss").arg("-tulpn").status().is_ok() { 243 | let ports_info = run_command("ss", &["-tulpn"])?; 244 | debug!("{}", ports_info); 245 | } 246 | 247 | Ok(()) 248 | } 249 | -------------------------------------------------------------------------------- /server/src/launch.rs: -------------------------------------------------------------------------------- 1 | use crate::config::AppConfig; 2 | use std::path::Path; 3 | use std::process::Stdio; 4 | use tokio::process::Command; 5 | use tokio::sync::oneshot; 6 | use tokio::time::{Duration, sleep}; 7 | use tracing::{debug, error, info, warn}; 8 | 9 | pub async fn launch_foundry_process( 10 | shutdown_rx: Option>, 11 | config: &AppConfig, 12 | ) { 13 | // Convert string args to &str for the launch_foundry function 14 | let args: Vec<&str> = config.foundry_args.iter().map(|s| s.as_str()).collect(); 15 | 16 | // Launch Foundry in the same task, passing the shutdown channel 17 | launch_foundry(&args, &config.foundry_script, shutdown_rx).await; 18 | } 19 | 20 | pub async fn launch_foundry( 21 | args: &[&str], 22 | script_path: &str, 23 | shutdown_rx: Option>, 24 | ) { 25 | let script_path_owned = script_path.to_string(); 26 | 27 | // Take ownership of the shutdown_rx outside the loop 28 | let mut shutdown_rx_option = shutdown_rx; 29 | 30 | loop { 31 | // Wait until the script file is present 32 | if !Path::new(&script_path_owned).exists() { 33 | warn!("⚠️ Script not found at {}, waiting...", script_path_owned); 34 | sleep(Duration::from_secs(10)).await; 35 | continue; 36 | } 37 | 38 | info!("🚀 Launching FoundryVTT with script: {}", script_path_owned); 39 | debug!( 40 | "Launch command: npx --yes node {} with args: {:?}", 41 | script_path_owned, args 42 | ); 43 | 44 | let mut cmd = Command::new("npx"); 45 | cmd.arg("--yes") 46 | .arg("node") 47 | .arg(&script_path_owned) 48 | .args(args) 49 | .stdout(Stdio::inherit()) 50 | .stderr(Stdio::inherit()); 51 | 52 | debug!("Full command: {:?}", cmd); 53 | 54 | let mut child = match cmd.spawn() { 55 | Ok(child) => child, 56 | Err(e) => { 57 | error!("❌ Failed to spawn FoundryVTT: {}", e); 58 | sleep(Duration::from_secs(5)).await; 59 | continue; 60 | } 61 | }; 62 | 63 | info!("FoundryVTT process started"); 64 | 65 | // Handle shutdown signal if provided 66 | if let Some(shutdown_rx) = shutdown_rx_option.take() { 67 | let child_id = child.id(); 68 | tokio::select! { 69 | exit_status = child.wait() => { 70 | match exit_status { 71 | Ok(exit) => { 72 | warn!("⚠️ FoundryVTT exited with: {}", exit); 73 | } 74 | Err(e) => { 75 | error!("❌ Failed to wait for FoundryVTT: {}", e); 76 | } 77 | } 78 | }, 79 | _ = shutdown_rx => { 80 | info!("Received shutdown signal, terminating FoundryVTT process"); 81 | if let Some(pid) = child_id { 82 | info!("Sending SIGTERM to FoundryVTT process (PID: {})", pid); 83 | if let Err(e) = child.kill().await { 84 | error!("Failed to kill FoundryVTT process: {}", e); 85 | } 86 | } 87 | // Wait for child process to exit after kill signal 88 | if let Err(e) = child.wait().await { 89 | error!("Error waiting for FoundryVTT to exit: {}", e); 90 | } 91 | info!("FoundryVTT process terminated"); 92 | return; // Exit the function, don't restart 93 | } 94 | } 95 | } else { 96 | // Without shutdown channel, just wait for the process 97 | match child.wait().await { 98 | Ok(exit) => { 99 | warn!("⚠️ FoundryVTT exited with: {}", exit); 100 | } 101 | Err(e) => { 102 | error!("❌ Failed to wait for FoundryVTT: {}", e); 103 | } 104 | } 105 | } 106 | 107 | // Retry after 5 seconds if the script or process exits (only if we didn't get a shutdown signal) 108 | sleep(Duration::from_secs(5)).await; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod downloader; 3 | mod events; 4 | mod extractor; 5 | mod handlers; 6 | mod initialization; 7 | mod launch; 8 | mod server; 9 | mod utils; 10 | 11 | use crate::utils::paths; 12 | use tokio::sync::oneshot; 13 | use tracing::{Level, error, info}; 14 | 15 | #[actix_web::main] 16 | async fn main() -> std::io::Result<()> { 17 | // Initialize tracing with a more verbose default level 18 | tracing_subscriber::fmt() 19 | .with_max_level(Level::DEBUG) 20 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 21 | .with_writer(std::io::stdout) 22 | .init(); 23 | 24 | info!("Logging initialized at DEBUG level"); 25 | 26 | // Load application configuration 27 | let app_config = config::AppConfig::from_env(); 28 | 29 | // Run initialization checks and setup from the old run.sh 30 | if let Err(e) = initialization::initialize(&app_config) { 31 | error!("Initialization failed: {}", e); 32 | return Err(std::io::Error::new( 33 | std::io::ErrorKind::Other, 34 | e.to_string(), 35 | )); 36 | } 37 | 38 | // Check if we should directly launch Foundry 39 | if paths::FOUNDRY_SCRIPT_PATH.exists() { 40 | info!("Foundry main.js detected, skipping Actix server and launching Foundry directly"); 41 | launch::launch_foundry_process(None, &app_config).await; 42 | return Ok(()); 43 | } 44 | 45 | // Log configuration settings 46 | info!("Serving static files from: {}", app_config.static_files_dir); 47 | info!("Downloading files to: {}", app_config.target_dir); 48 | 49 | // Create a channel for shutting down Foundry when needed 50 | let (_foundry_tx, foundry_rx) = oneshot::channel::<()>(); 51 | 52 | // Start the HTTP server 53 | let server_handle = server::start_server(&app_config).await?; 54 | 55 | // Wait for the server to complete (after receiving shutdown signal) 56 | // Fix: Explicitly acknowledge the Result with let _ 57 | let _ = server_handle.await?; 58 | info!("Actix server has terminated, launching Foundry VTT"); 59 | 60 | // After server stops, launch Foundry directly with the shutdown channel 61 | launch::launch_foundry_process(Some(foundry_rx), &app_config).await; 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /server/src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::config::AppConfig; 2 | use crate::events::{self, ProgressEvent}; 3 | use crate::handlers; 4 | use actix_files::Files; 5 | use actix_web::dev::ServiceResponse; 6 | use actix_web::http::{StatusCode, header}; 7 | use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers}; 8 | use actix_web::{App, HttpResponse, HttpServer, Result, web}; 9 | use std::sync::{Arc, Mutex}; 10 | use tokio::sync::{broadcast, oneshot}; 11 | use tokio::task::JoinHandle; 12 | use tracing::{debug, info}; 13 | use tracing_actix_web::TracingLogger; 14 | 15 | pub struct AppState { 16 | pub shutdown_sender: Arc>>>, 17 | pub event_channel: broadcast::Sender, 18 | } 19 | 20 | pub async fn start_server(config: &AppConfig) -> std::io::Result>> { 21 | // Create a channel for shutting down the server 22 | let (tx, rx) = oneshot::channel::<()>(); 23 | 24 | // Wrap the Sender in Arc>> so it can be shared and taken 25 | let shared_tx = Arc::new(Mutex::new(Some(tx))); 26 | 27 | // Create a broadcast channel for SSE events 28 | let (event_tx, _) = broadcast::channel::(100); 29 | 30 | let app_state = web::Data::new(AppState { 31 | shutdown_sender: Arc::clone(&shared_tx), 32 | event_channel: event_tx, 33 | }); 34 | 35 | info!( 36 | "Server is running on {}:{}", 37 | config.server_host, config.server_port 38 | ); 39 | debug!("Debug logging is enabled"); 40 | 41 | // Clone the values we need inside the closure to avoid lifetime issues 42 | let static_files_dir = config.static_files_dir.clone(); 43 | let server_host = config.server_host.clone(); 44 | let server_port = config.server_port; 45 | 46 | // Start the server 47 | let server = HttpServer::new(move || { 48 | App::new() 49 | // Logging for Actix with more details 50 | .wrap(TracingLogger::default()) 51 | // Add error handlers for 404 Not Found errors to redirect to root 52 | .wrap(ErrorHandlers::new().handler(StatusCode::NOT_FOUND, redirect_to_root)) 53 | // Store the app state 54 | .app_data(app_state.clone()) 55 | // Serve the download endpoint and static files 56 | .route("/download", web::post().to(handlers::download_and_extract)) 57 | .route("/upload", web::post().to(handlers::upload_and_extract)) 58 | .route("/events", web::get().to(events::sse_events)) 59 | .route("/dev-info", web::get().to(handlers::info)) 60 | .service(Files::new("/", &static_files_dir).index_file("index.html")) 61 | }) 62 | .bind((server_host, server_port))? 63 | .run(); 64 | 65 | let server_handle = server.handle(); 66 | 67 | // Spawn a task to wait for the shutdown signal 68 | tokio::spawn(async move { 69 | // If we receive the shutdown signal, stop the server gracefully 70 | if rx.await.is_ok() { 71 | info!("Received shutdown signal, stopping Actix server"); 72 | server_handle.stop(true).await; 73 | info!("Actix server stopped, transitioning to process management mode"); 74 | } 75 | }); 76 | 77 | // Setup signal handlers for SIGTERM and SIGINT 78 | setup_signal_handlers(); 79 | 80 | Ok(tokio::spawn(server)) 81 | } 82 | 83 | // Function to redirect 404 responses to the root path 84 | fn redirect_to_root(res: ServiceResponse) -> Result> { 85 | let response = HttpResponse::Found() 86 | .insert_header((header::LOCATION, "/")) 87 | .finish() 88 | .map_into_right_body(); 89 | 90 | Ok(ErrorHandlerResponse::Response(res.into_response(response))) 91 | } 92 | 93 | fn setup_signal_handlers() { 94 | #[cfg(unix)] 95 | { 96 | use tokio::signal::unix::{SignalKind, signal}; 97 | use tracing::info; 98 | 99 | // Handle SIGTERM 100 | tokio::spawn(async move { 101 | let mut sigterm = signal(SignalKind::terminate()).unwrap(); 102 | sigterm.recv().await; 103 | info!("Received SIGTERM, initiating shutdown"); 104 | std::process::exit(0); 105 | }); 106 | 107 | // Handle SIGINT 108 | tokio::spawn(async move { 109 | let mut sigint = signal(SignalKind::interrupt()).unwrap(); 110 | sigint.recv().await; 111 | info!("Received SIGINT, initiating shutdown"); 112 | std::process::exit(0); 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /server/src/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use lazy_static::lazy_static; 3 | use std::env; 4 | use std::path::PathBuf; 5 | use std::process::{Command, Output}; 6 | use tracing::debug; 7 | 8 | pub mod paths { 9 | use super::*; 10 | 11 | lazy_static! { 12 | /// Base application directory where the Foundry VTT application is installed 13 | pub static ref APPLICATION_DIR: String = env::var("APPLICATION_DIR") 14 | .unwrap_or_else(|_| "/foundryvtt".to_string()); 15 | 16 | /// Data directory for user data 17 | pub static ref DATA_DIR: String = env::var("DATA_DIR") 18 | .unwrap_or_else(|_| "/foundrydata".to_string()); 19 | 20 | /// Path to the main Foundry script 21 | pub static ref FOUNDRY_SCRIPT_PATH: PathBuf = { 22 | let mut path = PathBuf::from(&*APPLICATION_DIR); 23 | path.push("resources"); 24 | path.push("app"); 25 | path.push("main.js"); 26 | path 27 | }; 28 | } 29 | } 30 | 31 | /// Run a system command and return its output 32 | pub fn run_command(command: &str, args: &[&str]) -> Result { 33 | debug!("Running command: {} {:?}", command, args); 34 | 35 | let output: Output = Command::new(command) 36 | .args(args) 37 | .output() 38 | .with_context(|| format!("Failed to execute command: {} {:?}", command, args))?; 39 | 40 | if !output.status.success() { 41 | let stderr = String::from_utf8_lossy(&output.stderr); 42 | debug!( 43 | "Command failed with status code {:?}: {}", 44 | output.status.code(), 45 | stderr.trim() 46 | ); 47 | } 48 | 49 | // Return stdout as string 50 | let stdout = String::from_utf8_lossy(&output.stdout).to_string(); 51 | Ok(stdout) 52 | } 53 | -------------------------------------------------------------------------------- /server/static/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Roboto", sans-serif; 3 | line-height: 1.6; 4 | margin: 0; 5 | padding: 0; 6 | background-color: #f5f5f5; 7 | color: #333; 8 | } 9 | 10 | .header { 11 | background-color: #1a1a1a; 12 | color: white; 13 | padding: 1rem; 14 | text-align: center; 15 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 16 | } 17 | 18 | .container { 19 | max-width: 800px; 20 | margin: 20px auto; 21 | padding: 20px; 22 | background-color: #fff; 23 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 24 | border-radius: 10px; 25 | overflow: hidden; 26 | } 27 | 28 | h1 { 29 | text-align: center; 30 | color: #fff; 31 | margin: 0; 32 | } 33 | 34 | .instructions { 35 | background-color: #fff; 36 | padding: 20px; 37 | border-radius: 5px; 38 | margin-bottom: 20px; 39 | transition: all 0.3s ease; 40 | } 41 | 42 | .instructions h5 { 43 | color: #555; 44 | font-weight: 500; 45 | margin-top: 0; 46 | } 47 | 48 | .instructions ol { 49 | padding-left: 20px; 50 | color: #666; 51 | } 52 | 53 | .instructions ol li { 54 | margin-bottom: 10px; 55 | } 56 | 57 | .method-tabs { 58 | display: flex; 59 | margin-bottom: 0; 60 | background-color: #f8f8f8; 61 | border-radius: 8px 8px 0 0; 62 | overflow: hidden; 63 | border: 1px solid #ddd; 64 | border-bottom: none; 65 | transition: all 0.3s ease; 66 | } 67 | 68 | .tab-button { 69 | flex: 1; 70 | padding: 15px 20px; 71 | background: #f8f8f8; 72 | border: none; 73 | border-bottom: 1px solid #ddd; 74 | cursor: pointer; 75 | font-size: 16px; 76 | font-weight: 500; 77 | color: #666; 78 | transition: all 0.3s ease; 79 | position: relative; 80 | text-align: center; 81 | } 82 | 83 | .tab-button:first-child { 84 | border-right: 1px solid #ddd; 85 | } 86 | 87 | .tab-button.active { 88 | color: #4285f4; 89 | background-color: #fff; 90 | border-bottom-color: transparent; 91 | } 92 | 93 | .tab-button.active::after { 94 | content: ""; 95 | position: absolute; 96 | bottom: 0; 97 | left: 0; 98 | width: 100%; 99 | height: 3px; 100 | background-color: #4285f4; 101 | } 102 | 103 | .tab-button:hover:not(.active) { 104 | background-color: #f0f0f0; 105 | } 106 | 107 | .method-content { 108 | padding: 25px; 109 | border: 1px solid #ddd; 110 | border-top: none; 111 | border-radius: 0 0 8px 8px; 112 | background-color: #fff; 113 | margin-bottom: 20px; 114 | transition: all 0.3s ease; 115 | } 116 | 117 | .input-field { 118 | margin-bottom: 20px; 119 | } 120 | 121 | .input-field label { 122 | display: block; 123 | margin-bottom: 5px; 124 | font-weight: 500; 125 | color: #333; 126 | } 127 | 128 | .input-field input { 129 | width: 100%; 130 | padding: 12px 15px; 131 | border: 1px solid #ddd; 132 | border-radius: 6px; 133 | box-sizing: border-box; 134 | font-size: 16px; 135 | transition: 136 | border-color 0.3s ease, 137 | box-shadow 0.3s ease; 138 | } 139 | 140 | .input-field input:focus { 141 | border-color: #4285f4; 142 | outline: none; 143 | box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2); 144 | } 145 | 146 | .btn { 147 | display: inline-block; 148 | width: 100%; 149 | padding: 12px; 150 | color: #fff; 151 | background-color: #4285f4; 152 | border: none; 153 | border-radius: 6px; 154 | cursor: pointer; 155 | font-size: 16px; 156 | font-weight: 500; 157 | text-align: center; 158 | text-decoration: none; 159 | transition: 160 | background-color 0.3s ease, 161 | transform 0.2s ease; 162 | } 163 | 164 | .btn:hover { 165 | background-color: #3367d6; 166 | transform: scale(1.02); 167 | } 168 | 169 | .btn:disabled { 170 | background-color: #a4a4a4; 171 | cursor: not-allowed; 172 | } 173 | 174 | .drop-area { 175 | border: 2px dashed #ddd; 176 | border-radius: 8px; 177 | padding: 40px 30px; 178 | text-align: center; 179 | margin-bottom: 20px; 180 | transition: all 0.3s ease; 181 | background-color: #f9f9f9; 182 | } 183 | 184 | .drop-area.highlight { 185 | border-color: #4285f4; 186 | background-color: #e8f0fe; 187 | } 188 | 189 | .drop-area.has-file { 190 | border-color: #34a853; 191 | background-color: #e6f4ea; 192 | } 193 | 194 | .file-name { 195 | margin-top: 10px; 196 | font-weight: 500; 197 | word-break: break-all; 198 | } 199 | 200 | .toast { 201 | visibility: hidden; 202 | min-width: 300px; 203 | margin-left: -150px; 204 | background-color: #333; 205 | color: #fff; 206 | text-align: center; 207 | border-radius: 6px; 208 | padding: 16px; 209 | position: fixed; 210 | z-index: 1; 211 | left: 50%; 212 | bottom: 30px; 213 | font-size: 16px; 214 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); 215 | opacity: 0; 216 | transition: 217 | opacity 0.5s ease, 218 | bottom 0.5s ease; 219 | } 220 | 221 | .toast.show { 222 | visibility: visible; 223 | opacity: 1; 224 | animation: 225 | fadein 0.5s, 226 | fadeout 0.5s 2.5s; 227 | } 228 | 229 | .waiting-symbol { 230 | text-align: center; 231 | font-size: 18px; 232 | color: #555; 233 | margin-top: 20px; 234 | font-style: italic; 235 | } 236 | 237 | .progress-container { 238 | margin-top: 20px; 239 | padding: 15px; 240 | background-color: #f8f8f8; 241 | border-radius: 8px; 242 | border: 1px solid #ddd; 243 | } 244 | 245 | .progress-bar-wrapper { 246 | width: 100%; 247 | height: 20px; 248 | background-color: #e0e0e0; 249 | border-radius: 10px; 250 | overflow: hidden; 251 | margin-bottom: 10px; 252 | } 253 | 254 | .progress-bar { 255 | height: 100%; 256 | background-color: #4285f4; 257 | width: 0%; 258 | border-radius: 10px; 259 | transition: width 0.3s ease; 260 | } 261 | 262 | .progress-message { 263 | text-align: center; 264 | font-size: 16px; 265 | color: #555; 266 | } 267 | 268 | .redirect-status { 269 | margin-top: 10px; 270 | text-align: center; 271 | font-size: 14px; 272 | color: #4285f4; 273 | font-weight: 500; 274 | padding: 8px; 275 | background-color: #e8f0fe; 276 | border-radius: 5px; 277 | animation: pulse 2s infinite; 278 | } 279 | 280 | @keyframes pulse { 281 | 0% { 282 | opacity: 0.8; 283 | } 284 | 50% { 285 | opacity: 1; 286 | } 287 | 100% { 288 | opacity: 0.8; 289 | } 290 | } 291 | 292 | @keyframes fadein { 293 | from { 294 | bottom: 0; 295 | opacity: 0; 296 | } 297 | to { 298 | bottom: 30px; 299 | opacity: 1; 300 | } 301 | } 302 | 303 | @keyframes fadeout { 304 | from { 305 | bottom: 30px; 306 | opacity: 1; 307 | } 308 | to { 309 | bottom: 0; 310 | opacity: 0; 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /server/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Foundry VTT Installer 8 | 9 | 13 | 14 | 15 |
16 |

Foundry VTT Installer

17 |
18 |
19 |
20 |
Instructions
21 |

Option 1: Download using URL

22 |
    23 |
  1. 24 | Log into Foundry VTT at 25 | https://foundryvtt.com/ 28 |
  2. 29 |
  3. Click your profile in the upper right hand corner
  4. 30 |
  5. Click purchased content
  6. 31 |
  7. Change "Download Version" to desired version
  8. 32 |
  9. 33 | Change "Operating System" to 34 | linux/nodejs 35 |
  10. 36 |
  11. Click time URL
  12. 37 |
  13. Paste it into the input field below
  14. 38 |
39 |

Option 2: Upload ZIP file directly

40 |
    41 |
  1. Select the Foundry VTT ZIP file from your device
  2. 42 |
  3. Or drag and drop the file into the upload area
  4. 43 |
44 |
45 | 46 |
47 | 51 | 52 |
53 |
54 | 55 | 60 |
61 | 62 |
63 | 64 | 79 |
80 | 81 |
82 | 92 |
93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /server/static/index.js: -------------------------------------------------------------------------------- 1 | // Improved version of index.js - Scoped in an IIFE to avoid global namespace pollution. 2 | (() => { 3 | "use strict"; 4 | 5 | // Track global state 6 | const state = { 7 | eventSourceConnected: false, 8 | processingComplete: false, 9 | serverShuttingDown: false, 10 | }; 11 | 12 | /** 13 | * Disables or enables UI elements. 14 | * @param {boolean} disabled - Whether to disable the UI. 15 | */ 16 | const disableUI = (disabled) => { 17 | const elements = document.querySelectorAll( 18 | "button, input, select, textarea, fieldset", 19 | ); 20 | elements.forEach((el) => (el.disabled = disabled)); 21 | }; 22 | 23 | /** 24 | * Initializes tab switching functionality. 25 | */ 26 | const initTabs = () => { 27 | const tabs = ["url", "file"]; 28 | tabs.forEach((tab) => { 29 | const tabElement = document.getElementById(`${tab}-tab`); 30 | if (tabElement) { 31 | tabElement.addEventListener("click", () => { 32 | if (!tabElement.classList.contains("active")) { 33 | switchTab(tab); 34 | } 35 | }); 36 | } 37 | }); 38 | }; 39 | 40 | /** 41 | * Switches active tab and displays corresponding content. 42 | * @param {string} tabName - The name of the tab to activate. 43 | */ 44 | const switchTab = (tabName) => { 45 | document 46 | .querySelectorAll(".tab-button") 47 | .forEach((tab) => tab.classList.remove("active")); 48 | document 49 | .querySelectorAll(".method-content") 50 | .forEach((content) => (content.style.display = "none")); 51 | 52 | const activeTab = document.getElementById(`${tabName}-tab`); 53 | const activeContent = document.getElementById(`${tabName}-method`); 54 | if (activeTab && activeContent) { 55 | activeTab.classList.add("active"); 56 | activeContent.style.display = "block"; 57 | } 58 | }; 59 | 60 | /** 61 | * Processes the given request endpoint with the provided data. 62 | * @param {string} endpoint - The endpoint URL. 63 | * @param {FormData|object} data - The data to be sent. 64 | */ 65 | const processRequest = async (endpoint, data) => { 66 | showProcessingUI(true); 67 | try { 68 | const options = { 69 | method: "POST", 70 | ...(data instanceof FormData 71 | ? { body: data } 72 | : { 73 | headers: { "Content-Type": "application/json" }, 74 | body: JSON.stringify(data), 75 | }), 76 | }; 77 | const response = await fetch(endpoint, options); 78 | if (!response.ok) { 79 | throw new Error(`Server responded with status ${response.status}`); 80 | } 81 | showToast( 82 | "Process initiated. The server will exit once the operation is complete.", 83 | "green", 84 | ); 85 | } catch (error) { 86 | console.error(`Error with ${endpoint}:`, error); 87 | showToast("Failed to process request. Please try again.", "red"); 88 | showProcessingUI(false); 89 | } 90 | }; 91 | 92 | /** 93 | * Initializes URL download functionality. 94 | */ 95 | const initUrlDownload = () => { 96 | const downloadButton = document.getElementById("download-button"); 97 | if (downloadButton) { 98 | downloadButton.addEventListener("click", async () => { 99 | const urlInput = document.getElementById("url-input"); 100 | const url = urlInput ? urlInput.value : ""; 101 | if (url) { 102 | await processRequest("/download", { url }); 103 | } else { 104 | showToast("Please enter a URL.", "red"); 105 | } 106 | }); 107 | } 108 | }; 109 | 110 | /** 111 | * Initializes file upload functionality with drag and drop support. 112 | */ 113 | const initFileUpload = () => { 114 | let selectedFile = null; 115 | const fileInput = document.getElementById("file-input"); 116 | const fileNameEl = document.getElementById("file-name"); 117 | const uploadButton = document.getElementById("upload-button"); 118 | const dropArea = document.getElementById("drop-area"); 119 | 120 | if (!fileInput || !fileNameEl || !uploadButton || !dropArea) { 121 | console.warn("Some file upload elements are missing in the DOM."); 122 | return; 123 | } 124 | 125 | // Update file selection display 126 | const updateFileSelection = (file) => { 127 | fileNameEl.textContent = file.name; 128 | uploadButton.disabled = false; 129 | }; 130 | 131 | fileInput.addEventListener("change", (e) => { 132 | if (e.target.files && e.target.files.length > 0) { 133 | selectedFile = e.target.files[0]; 134 | updateFileSelection(selectedFile); 135 | } 136 | }); 137 | 138 | const fileSelectButton = document.getElementById("file-select-button"); 139 | if (fileSelectButton) { 140 | fileSelectButton.addEventListener("click", () => fileInput.click()); 141 | } 142 | 143 | uploadButton.addEventListener("click", async () => { 144 | if (selectedFile) { 145 | const formData = new FormData(); 146 | formData.append("file", selectedFile); 147 | await processRequest("/upload", formData); 148 | } else { 149 | showToast("Please select a file to upload.", "red"); 150 | } 151 | }); 152 | 153 | // Initialize drag and drop functionality 154 | initDragAndDrop(dropArea, (files) => { 155 | if (files && files.length > 0) { 156 | selectedFile = files[0]; 157 | updateFileSelection(selectedFile); 158 | } 159 | }); 160 | }; 161 | 162 | /** 163 | * Initializes drag and drop events on a given drop area. 164 | * @param {HTMLElement} dropArea - The drop area element. 165 | * @param {function(FileList): void} onDropCallback - Callback for handling dropped files. 166 | */ 167 | const initDragAndDrop = (dropArea, onDropCallback) => { 168 | const preventDefaults = (e) => { 169 | e.preventDefault(); 170 | e.stopPropagation(); 171 | }; 172 | 173 | const highlight = () => dropArea.classList.add("highlight"); 174 | const unhighlight = () => dropArea.classList.remove("highlight"); 175 | 176 | const events = { 177 | dragenter: preventDefaults, 178 | dragover: (e) => { 179 | preventDefaults(e); 180 | highlight(); 181 | }, 182 | dragleave: (e) => { 183 | preventDefaults(e); 184 | unhighlight(); 185 | }, 186 | drop: (e) => { 187 | preventDefaults(e); 188 | unhighlight(); 189 | onDropCallback(e.dataTransfer.files); 190 | }, 191 | }; 192 | 193 | Object.entries(events).forEach(([event, handler]) => { 194 | dropArea.addEventListener(event, handler, false); 195 | }); 196 | }; 197 | 198 | /** 199 | * Displays a toast message with a given color. 200 | * @param {string} message - The message to display. 201 | * @param {string} color - The toast color (e.g., "green", "red"). 202 | */ 203 | const showToast = (message, color) => { 204 | const toast = document.getElementById("toast"); 205 | if (toast) { 206 | toast.textContent = message; 207 | toast.className = "toast"; 208 | toast.classList.add(color); 209 | toast.style.display = "block"; 210 | setTimeout(() => { 211 | toast.style.display = "none"; 212 | }, 5000); 213 | } 214 | }; 215 | 216 | /** 217 | * Updates the UI to show processing status. 218 | * @param {boolean} processing - Whether processing is active. 219 | */ 220 | const showProcessingUI = (processing) => { 221 | disableUI(processing); 222 | const progressContainer = document.getElementById("progress-container"); 223 | if (progressContainer) { 224 | progressContainer.style.display = processing ? "block" : "none"; 225 | } 226 | const instructions = document.querySelector(".instructions"); 227 | const methodTabs = document.querySelector(".method-tabs"); 228 | if (instructions) 229 | instructions.style.display = processing ? "none" : "block"; 230 | if (methodTabs) methodTabs.style.display = processing ? "none" : "flex"; 231 | 232 | document.querySelectorAll(".method-content").forEach((el) => { 233 | if (processing) { 234 | el.style.display = "none"; 235 | } else { 236 | // Ensure only the active tab's content is visible 237 | const activeTab = document.querySelector(".tab-button.active"); 238 | if (activeTab && el.id === activeTab.id.replace("-tab", "-method")) { 239 | el.style.display = "block"; 240 | } 241 | } 242 | }); 243 | 244 | if (processing && !state.eventSourceConnected) { 245 | connectToEventSource(); 246 | } 247 | }; 248 | 249 | /** 250 | * Establishes connection to the server via Server-Sent Events (SSE) for progress updates. 251 | */ 252 | const connectToEventSource = () => { 253 | // Only connect once 254 | if (state.eventSourceConnected) return; 255 | 256 | state.eventSourceConnected = true; 257 | const eventSource = new EventSource("/events"); 258 | const progressBar = document.getElementById("progress-bar"); 259 | const progressMessage = document.getElementById("progress-message"); 260 | 261 | eventSource.onopen = () => { 262 | console.log("EventSource connection established"); 263 | }; 264 | 265 | eventSource.onmessage = (event) => { 266 | try { 267 | const data = JSON.parse(event.data); 268 | console.log("Event received:", data); 269 | 270 | if (data.progress !== undefined && progressBar) { 271 | progressBar.style.width = `${data.progress}%`; 272 | } 273 | if (data.message && progressMessage) { 274 | progressMessage.textContent = data.message; 275 | } 276 | 277 | const isComplete = 278 | data.type === "complete" || data.event_type === "complete"; 279 | const isError = data.type === "error" || data.event_type === "error"; 280 | 281 | if (isComplete || isError) { 282 | console.log( 283 | `Closing EventSource connection: ${isComplete ? "complete" : "error"} event received`, 284 | ); 285 | state.eventSourceConnected = false; 286 | eventSource.close(); 287 | 288 | if (isError) { 289 | showToast( 290 | "An error occurred: " + (data.message || "Unknown error"), 291 | "red", 292 | ); 293 | showProcessingUI(false); 294 | } else if (isComplete) { 295 | state.processingComplete = true; 296 | if (progressMessage) { 297 | progressMessage.textContent = 298 | "Installation complete! Waiting 30 seconds for server to shut down..."; 299 | } 300 | // Hard 20-second wait after complete event 301 | setTimeout(waitForServerShutdown, 30000); 302 | } 303 | } 304 | } catch (error) { 305 | console.error("Error processing event:", error, event.data); 306 | state.eventSourceConnected = false; 307 | eventSource.close(); 308 | showToast("Error processing server event. Please try again.", "red"); 309 | showProcessingUI(false); 310 | } 311 | }; 312 | 313 | eventSource.onerror = (error) => { 314 | console.error("EventSource connection error", error); 315 | 316 | // If we were expecting this due to server shutdown, don't treat as error 317 | if (state.processingComplete && !state.serverShuttingDown) { 318 | state.serverShuttingDown = true; 319 | console.log( 320 | "EventSource disconnected after completion - likely server shutdown", 321 | ); 322 | setTimeout(waitForServerShutdown, 10000); 323 | } else if (!state.processingComplete) { 324 | showToast("Lost connection to server. Please try again.", "red"); 325 | showProcessingUI(false); 326 | } 327 | 328 | state.eventSourceConnected = false; 329 | eventSource.close(); 330 | }; 331 | 332 | // Set a timeout to handle cases where the server never responds 333 | setTimeout(() => { 334 | if (state.eventSourceConnected && !state.processingComplete) { 335 | console.warn("EventSource timeout - no activity detected"); 336 | state.eventSourceConnected = false; 337 | eventSource.close(); 338 | showToast("Server did not respond in time. Please try again.", "red"); 339 | showProcessingUI(false); 340 | } 341 | }, 60000); // 1 minute timeout 342 | }; 343 | 344 | /** 345 | * Attempts to redirect the user to the appropriate URL after process completion. 346 | */ 347 | const attemptRedirect = () => { 348 | const progressContainer = document.getElementById("progress-container"); 349 | let redirectStatus = document.getElementById("redirect-status"); 350 | if (!redirectStatus && progressContainer) { 351 | redirectStatus = document.createElement("div"); 352 | redirectStatus.id = "redirect-status"; 353 | redirectStatus.className = "redirect-status"; 354 | progressContainer.appendChild(redirectStatus); 355 | } 356 | 357 | let attempts = 0; 358 | const maxAttempts = 30; 359 | const delayBetweenAttempts = 3000; // 3 seconds 360 | 361 | const tryRedirect = async () => { 362 | attempts++; 363 | if (redirectStatus) { 364 | redirectStatus.textContent = `Checking for Foundry VTT (${attempts}/${maxAttempts})...`; 365 | } 366 | 367 | try { 368 | // Use Promise.allSettled to handle both successful and failed requests 369 | const [rootResult, licenseResult] = await Promise.allSettled([ 370 | fetch("/", { method: "HEAD" }), 371 | fetch("/license", { method: "HEAD" }), 372 | ]); 373 | 374 | const rootResponse = 375 | rootResult.status === "fulfilled" 376 | ? rootResult.value 377 | : { status: 404 }; 378 | const licenseResponse = 379 | licenseResult.status === "fulfilled" 380 | ? licenseResult.value 381 | : { status: 404 }; 382 | 383 | if (rootResponse.status === 200) { 384 | if (redirectStatus) { 385 | redirectStatus.textContent = "Foundry VTT is ready! Redirecting..."; 386 | } 387 | setTimeout(() => (window.location.href = "/"), 1000); 388 | } else if (licenseResponse.status === 200) { 389 | if (redirectStatus) { 390 | redirectStatus.textContent = 391 | "Foundry VTT license page is ready! Redirecting..."; 392 | } 393 | setTimeout(() => (window.location.href = "/license"), 1000); 394 | } else if (attempts < maxAttempts) { 395 | if (redirectStatus) { 396 | redirectStatus.textContent = `Foundry VTT not ready yet. Next check in ${delayBetweenAttempts / 1000} seconds...`; 397 | } 398 | setTimeout(tryRedirect, delayBetweenAttempts); 399 | } else { 400 | if (redirectStatus) { 401 | redirectStatus.textContent = 402 | "Max redirect attempts reached. Please try manually refreshing the page."; 403 | } 404 | showToast( 405 | "Foundry VTT might need more time to start. Try refreshing this page in a moment.", 406 | "orange", 407 | ); 408 | } 409 | } catch (error) { 410 | console.warn("Redirect check failed:", error); 411 | if (attempts < maxAttempts) { 412 | if (redirectStatus) { 413 | redirectStatus.textContent = `Connection attempt failed. Retrying in ${delayBetweenAttempts / 1000} seconds...`; 414 | } 415 | setTimeout(tryRedirect, delayBetweenAttempts); 416 | } else { 417 | if (redirectStatus) { 418 | redirectStatus.textContent = 419 | "Unable to connect to Foundry VTT after multiple attempts. Please refresh manually."; 420 | } 421 | showToast( 422 | "Couldn't connect to Foundry VTT. Try refreshing this page in a moment.", 423 | "red", 424 | ); 425 | } 426 | } 427 | }; 428 | 429 | tryRedirect(); 430 | }; 431 | 432 | /** 433 | * Waits for the server to shut down before attempting redirection. 434 | */ 435 | const waitForServerShutdown = () => { 436 | if (state.serverShuttingDown) { 437 | console.log( 438 | "Already monitoring server shutdown, skipping duplicate call", 439 | ); 440 | return; 441 | } 442 | 443 | state.serverShuttingDown = true; 444 | const progressMessage = document.getElementById("progress-message"); 445 | const progressContainer = document.getElementById("progress-container"); 446 | 447 | let shutdownStatus = document.getElementById("shutdown-status"); 448 | if (!shutdownStatus && progressContainer) { 449 | shutdownStatus = document.createElement("div"); 450 | shutdownStatus.id = "shutdown-status"; 451 | shutdownStatus.className = "redirect-status"; 452 | progressContainer.appendChild(shutdownStatus); 453 | } 454 | 455 | if (progressMessage) { 456 | progressMessage.textContent = 457 | "Server shutdown period complete. Starting redirect attempts..."; 458 | } 459 | if (shutdownStatus) { 460 | shutdownStatus.textContent = "Beginning redirect attempts..."; 461 | } 462 | 463 | // Start redirect attempts immediately after the 20-second timeout 464 | attemptRedirect(); 465 | }; 466 | 467 | // Initialize the application once the DOM is ready 468 | document.addEventListener("DOMContentLoaded", () => { 469 | initTabs(); 470 | initUrlDownload(); 471 | initFileUpload(); 472 | switchTab("url"); // Set initial tab to URL tab 473 | }); 474 | })(); 475 | --------------------------------------------------------------------------------