Compare commits

...

62 commits

Author SHA1 Message Date
allcontributors[bot]
c962f030e3 docs: update .all-contributorsrc [skip ci] 2023-12-07 08:15:30 +01:00
allcontributors[bot]
1f5701120f docs: update README.md [skip ci] 2023-12-07 08:15:30 +01:00
dependabot[bot]
249103f0a6 chore(deps-dev): bump @types/jest from 29.5.7 to 29.5.11
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.5.7 to 29.5.11.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-06 21:04:50 +01:00
dependabot[bot]
bb1f343ac9 chore(deps): bump react-tooltip from 5.22.0 to 5.25.0
Bumps [react-tooltip](https://github.com/ReactTooltip/react-tooltip) from 5.22.0 to 5.25.0.
- [Release notes](https://github.com/ReactTooltip/react-tooltip/releases)
- [Changelog](https://github.com/ReactTooltip/react-tooltip/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ReactTooltip/react-tooltip/compare/v5.22.0...v5.25.0)

---
updated-dependencies:
- dependency-name: react-tooltip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-06 20:50:02 +01:00
Stavros
6b371de137 refactor(ui): change font to geist 2023-12-06 20:44:51 +01:00
Nicolas Meienberger
7133a2a2dc Merge branch 'master' into develop 2023-12-01 09:12:31 +01:00
Nicolas Meienberger
44d40b4a34 Merge branch 'hotfix/restart-failure' 2023-12-01 09:07:26 +01:00
Nicolas Meienberger
03e2604ca0 hotfix(docker): make postgres restart policy "unless-stopped" instead of "on-failure" 2023-12-01 08:44:53 +01:00
Nicolas Meienberger
ba3d860176 hotfix(install.sh): ask to re-run install script after docker is installed 2023-11-29 18:34:50 +01:00
Nicolas Meienberger
250e78450f chore: bump node to 20 in dockerfiles and actions 2023-11-29 08:41:35 +01:00
dependabot[bot]
d11299eeb8 chore(deps-dev): bump @types/cli-progress from 3.11.4 to 3.11.5
Bumps [@types/cli-progress](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/cli-progress) from 3.11.4 to 3.11.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/cli-progress)

---
updated-dependencies:
- dependency-name: "@types/cli-progress"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-29 08:22:40 +01:00
dependabot[bot]
6cd7ca1a4e chore(deps): bump @tabler/icons-react from 2.40.0 to 2.42.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 2.40.0 to 2.42.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v2.42.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-29 08:22:22 +01:00
dependabot[bot]
bb9f26b2b1 chore(deps-dev): bump @types/react from 18.2.34 to 18.2.39
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.34 to 18.2.39.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-29 08:22:09 +01:00
dependabot[bot]
30890bedd5 chore(deps-dev): bump eslint-config-next from 14.0.1 to 14.0.3
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.0.1 to 14.0.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v14.0.3/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-29 08:21:58 +01:00
dependabot[bot]
3876e8ee55 chore(deps-dev): bump @typescript-eslint/eslint-plugin
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 6.9.1 to 6.13.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.13.1/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-29 08:21:44 +01:00
Nicolas Meienberger
92d5a7b6a3 ci(release): make publish dependant on all builds 2023-11-28 22:31:44 +01:00
Nicolas Meienberger
ca3dc64fd4 ci(release): fix dependency on first job 2023-11-28 22:30:30 +01:00
Nicolas Meienberger
59dbe672c8
Merge pull request #960 from runtipi/release/2.2.0
Release 2.2.0
2023-11-28 22:29:01 +01:00
Nicolas Meienberger
42349c5a27 fix(worker): no need to mount host 2023-11-28 22:02:52 +01:00
Nicolas Meienberger
8104a9f3f7 ci(e2e): update upgrade before running install script 2023-11-28 19:12:40 +01:00
Nicolas Meienberger
83e96cfd31 ci(releases): refactor to use non-outdated actions 2023-11-28 19:12:35 +01:00
Nicolas Meienberger
4d69fc4cff ci(e2e): update upgrade before running install script 2023-11-28 18:49:47 +01:00
Nicolas Meienberger
37c551da1f ci(releases): refactor to use non-outdated actions 2023-11-28 13:29:02 +01:00
Nicolas Meienberger
852128f551 fix(worker): default to disk 0 if /host/root not found
In order to avoid displaying 0 disk space on some systems, we default to the first disk found
2023-11-28 08:26:43 +01:00
Nicolas Meienberger
bffa31c0b7 fix(worker): default to disk 0 if /host/root not found
In order to avoid displaying 0 disk space on some systems, we default to the first disk found
2023-11-28 08:18:48 +01:00
Nicolas Meienberger
0f129a7809 Merge branch 'steveiliop56-develop' into develop 2023-11-27 21:07:51 +01:00
Nicolas Meienberger
79b448adf3 fix(worker): apply file permissions on start 2023-11-27 21:06:14 +01:00
Nicolas Meienberger
dffa3ed670 ci(beta): build arm64 images 2023-11-27 21:06:14 +01:00
allcontributors[bot]
ef1ac3633b docs: update .all-contributorsrc [skip ci] 2023-11-27 21:06:14 +01:00
allcontributors[bot]
e520afdeff docs: update README.md [skip ci] 2023-11-27 21:06:14 +01:00
Olivier Garcia
52499cb0bd feat(support-repoURL-with-branch-syntax): If a appstore repo URL contains a branch, checkout that branch 2023-11-27 21:06:14 +01:00
Nicolas Meienberger
cfeb9d4e19 fix(app-status): rely on server status after update 2023-11-27 21:06:14 +01:00
Nicolas Meienberger
a1515ac7b8 ci(beta): adapt workflow with new worker build 2023-11-27 21:06:14 +01:00
Nicolas Meienberger
396d08dde0 feat(ThemeProvider): add some magic 2023-11-27 21:06:14 +01:00
Nicolas Meienberger
3c7dcfeb5e ci(release): replace butlerlogic/action-autotag with Klemensas/action-autotag 2023-11-27 21:06:14 +01:00
dependabot[bot]
9b5bcc7147 chore(deps-dev): bump @types/web-push from 3.6.2 to 3.6.3
Bumps [@types/web-push](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/web-push) from 3.6.2 to 3.6.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/web-push)

---
updated-dependencies:
- dependency-name: "@types/web-push"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 21:05:46 +01:00
cchalopin
5c276bd235 feat(update app): fix tests 2023-11-27 21:05:34 +01:00
cchalopin
c1723bb36b feat(update app): change pnpm 2023-11-27 21:05:34 +01:00
cchalopin
31c3de78b8 feat(update app): back to prev status before update 2023-11-27 21:05:12 +01:00
Nicolas Meienberger
197f6e3998 fix(worker): ensure state folder is rwx for non-root users 2023-11-27 21:04:48 +01:00
Nicolas Meienberger
abff9a4d5a fix(worker): apply file permissions on start 2023-11-27 09:41:42 +01:00
Nicolas Meienberger
91a361add1 ci(beta): build arm64 images 2023-11-27 09:41:37 +01:00
allcontributors[bot]
68b3e4e8bd docs: update .all-contributorsrc [skip ci] 2023-11-27 08:21:57 +01:00
allcontributors[bot]
f893cf482d docs: update README.md [skip ci] 2023-11-27 08:21:57 +01:00
Olivier Garcia
88878fccda feat(support-repoURL-with-branch-syntax): If a appstore repo URL contains a branch, checkout that branch 2023-11-27 08:19:22 +01:00
Nicolas Meienberger
64325150d5 fix(app-status): rely on server status after update 2023-11-27 08:10:29 +01:00
Nicolas Meienberger
203db0160a Merge branch 'cchalop1-feat/restart-app-after-update' into develop 2023-11-27 07:42:43 +01:00
Nicolas Meienberger
0261cf577c fix(worker): ensure state folder is rwx for non-root users 2023-11-27 07:42:36 +01:00
Nicolas Meienberger
166d9b49f2 ci(beta): adapt workflow with new worker build 2023-11-27 07:42:36 +01:00
Nicolas Meienberger
213b9ed482 feat(ThemeProvider): add some magic 2023-11-27 07:42:36 +01:00
Nicolas Meienberger
de3141e1f8 ci(release): replace butlerlogic/action-autotag with Klemensas/action-autotag 2023-11-27 07:42:36 +01:00
dependabot[bot]
6ec79ac1e8 chore(deps-dev): bump @types/web-push from 3.6.2 to 3.6.3
Bumps [@types/web-push](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/web-push) from 3.6.2 to 3.6.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/web-push)

---
updated-dependencies:
- dependency-name: "@types/web-push"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 07:42:36 +01:00
Nicolas Meienberger
5dbc996b92 refactor(docker): use non-root user in dashboard image 2023-11-27 07:42:36 +01:00
Nicolas Meienberger
f76af65212 fix(worker): ensure state folder is rwx for non-root users 2023-11-27 07:42:16 +01:00
cchalopin
1357efa0e3 feat(update app): fix tests 2023-11-26 15:17:27 +01:00
cchalopin
4da6193ae9 feat(update app): change pnpm 2023-11-26 15:12:09 +01:00
cchalopin
f61e6b2dae feat(update app): back to prev status before update 2023-11-26 15:03:21 +01:00
Stavros
230ae0a412 fix(worker): remount / to /host/root 2023-11-26 11:17:11 +02:00
Nicolas Meienberger
7c967271d7 ci(beta): adapt workflow with new worker build 2023-11-26 09:30:19 +01:00
Nicolas Meienberger
3d85051915 feat(ThemeProvider): add some magic 2023-11-26 09:13:06 +01:00
Nicolas Meienberger
467fc070d9 ci(release): replace butlerlogic/action-autotag with Klemensas/action-autotag 2023-11-25 11:43:18 +01:00
Nicolas Meienberger
df59d21ce7
Merge pull request #879 from runtipi/release/2.1.0
Release 2.1.0
2023-11-07 22:14:38 +01:00
43 changed files with 661 additions and 490 deletions

View file

@ -382,6 +382,24 @@
"contributions": [
"code"
]
},
{
"login": "0livier",
"name": "Olivier Garcia",
"avatar_url": "https://avatars.githubusercontent.com/u/10607?v=4",
"profile": "https://github.com/0livier",
"contributions": [
"code"
]
},
{
"login": "qcoudeyr",
"name": "qcoudeyr",
"avatar_url": "https://avatars.githubusercontent.com/u/124463277?v=4",
"profile": "https://github.com/qcoudeyr",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View file

@ -11,20 +11,20 @@ jobs:
create-tag:
runs-on: ubuntu-latest
outputs:
tagname: ${{ steps.create_tag.outputs.tagname }}
tagname: ${{ steps.get_tag.outputs.tagname }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create Tag
id: create_tag
uses: butlerlogic/action-autotag@stable
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
- name: Get tag from package.json
id: get_tag
run: |
VERSION=$(npm run version --silent)
echo "tagname=v${VERSION}-alpha.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
- uses: rickstaa/action-create-tag@v1
with:
tag_prefix: 'v'
tag_suffix: '-alpha.${{ github.event.inputs.tag }}'
tag: ${{ steps.get_tag.outputs.tagname }}
build-worker:
runs-on: ubuntu-latest
@ -60,7 +60,6 @@ jobs:
build-images:
runs-on: ubuntu-latest
needs: create-tag
steps:
- name: Checkout code
uses: actions/checkout@v4
@ -91,7 +90,6 @@ jobs:
build-cli:
runs-on: ubuntu-latest
needs: create-tag
steps:
- name: Checkout code
uses: actions/checkout@v4
@ -99,7 +97,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v2.4.0
name: Install pnpm
@ -147,25 +145,21 @@ jobs:
name: cli
path: cli
- name: Rename CLI
run: |
mv cli/bin/cli-x64 ./runtipi-cli-linux-x64
- name: Create alpha release
id: create_release
uses: actions/create-release@v1
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
body: |
**${{ needs.create-tag.outputs.tagname }}**
tag_name: ${{ needs.create-tag.outputs.tagname }}
release_name: ${{ needs.create-tag.outputs.tagname }}
name: ${{ needs.create-tag.outputs.tagname }}
draft: false
prerelease: true
- name: Upload X64 Linux CLI binary to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: cli/bin/cli-x64
asset_name: runtipi-cli-linux-x64
asset_content_type: application/octet-stream
files: |
runtipi-cli-linux-x64

View file

@ -8,27 +8,57 @@ on:
required: true
jobs:
get-tag:
create-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.get_tag.outputs.tag }}
tagname: ${{ steps.get_tag.outputs.tagname }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
- name: Get tag from VERSION file
- name: Get tag from package.json
id: get_tag
run: |
VERSION=$(npm run version --silent)
echo "tag=v${VERSION}-beta.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
echo "tagname=v${VERSION}-beta.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
- uses: rickstaa/action-create-tag@v1
with:
tag: ${{ steps.get_tag.outputs.tagname }}
build-worker:
runs-on: ubuntu-latest
needs: create-tag
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push images
uses: docker/build-push-action@v5
with:
context: .
file: ./packages/worker/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository_owner }}/worker:${{ needs.create-tag.outputs.tagname }}
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/worker:buildcache
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/worker:buildcache,mode=max
build-images:
needs: get-tag
needs: create-tag
runs-on: ubuntu-latest
steps:
- name: Checkout code
@ -53,13 +83,13 @@ jobs:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository_owner }}/runtipi:${{ needs.get-tag.outputs.tag }}
tags: ghcr.io/${{ github.repository_owner }}/runtipi:${{ needs.create-tag.outputs.tagname }}
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/runtipi:buildcache
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/runtipi:buildcache,mode=max
build-cli:
runs-on: ubuntu-latest
needs: get-tag
needs: create-tag
steps:
- name: Checkout code
uses: actions/checkout@v4
@ -67,7 +97,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v2.4.0
name: Install pnpm
@ -93,7 +123,7 @@ jobs:
run: pnpm install
- name: Set version
run: pnpm -r --filter cli set-version ${{ needs.get-tag.outputs.tag }}
run: pnpm -r --filter cli set-version ${{ needs.create-tag.outputs.tagname }}
- name: Build CLI
run: pnpm -r --filter cli package
@ -104,28 +134,9 @@ jobs:
name: cli
path: packages/cli/dist
create-tag:
needs: [build-images, build-cli]
runs-on: ubuntu-latest
outputs:
tagname: ${{ steps.create_tag.outputs.tagname }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create Tag
id: create_tag
uses: butlerlogic/action-autotag@stable
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
with:
tag_prefix: 'v'
tag_suffix: '-beta.${{ github.event.inputs.tag }}'
publish-release:
runs-on: ubuntu-latest
needs: [create-tag, build-images, build-cli]
needs: [create-tag, build-images, build-cli, build-worker]
outputs:
id: ${{ steps.create_release.outputs.id }}
steps:
@ -135,38 +146,26 @@ jobs:
name: cli
path: cli
- name: Rename CLI
run: |
mv cli/bin/cli-x64 ./runtipi-cli-linux-x64
mv cli/bin/cli-arm64 ./runtipi-cli-linux-arm64
- name: Create beta release
id: create_release
uses: actions/create-release@v1
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
body: |
**${{ needs.create-tag.outputs.tagname }}**
tag_name: ${{ needs.create-tag.outputs.tagname }}
release_name: ${{ needs.create-tag.outputs.tagname }}
name: ${{ needs.create-tag.outputs.tagname }}
draft: false
prerelease: true
- name: Upload X64 Linux CLI binary to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: cli/bin/cli-x64
asset_name: runtipi-cli-linux-x64
asset_content_type: application/octet-stream
- name: Upload ARM64 Linux CLI binary to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: cli/bin/cli-arm64
asset_name: runtipi-cli-linux-arm64
asset_content_type: application/octet-stream
files: |
runtipi-cli-linux-x64
runtipi-cli-linux-arm64
e2e-tests:
needs: [create-tag, publish-release]

View file

@ -43,7 +43,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v2.4.0
name: Install pnpm
@ -106,7 +106,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v2.4.0
name: Install pnpm

View file

@ -72,9 +72,6 @@ jobs:
run: |
while ! ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa root@${{ steps.get-droplet-ip.outputs.droplet_ip }} "echo 'SSH is ready'"; do sleep 5; done
- name: Wait 1 minute for Droplet to be ready
run: sleep 60
- name: Create docker group on Droplet
uses: fifsky/ssh-action@master
with:
@ -85,6 +82,9 @@ jobs:
user: root
key: ${{ secrets.SSH_KEY }}
- name: Wait 90 seconds for Docker to be ready on Droplet
run: sleep 90
- name: Deploy app to Droplet
uses: fifsky/ssh-action@master
with:
@ -134,7 +134,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Create .env.e2e file with Droplet IP
run: |

View file

@ -3,28 +3,28 @@ on:
workflow_dispatch:
jobs:
get-tag:
create-tag:
runs-on: ubuntu-latest
needs: [build-images, build-cli]
outputs:
tag: ${{ steps.get_tag.outputs.tag }}
tagname: ${{ steps.get_tag.outputs.tagname }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
- name: Get tag from VERSION file
- name: Get tag from package.json
id: get_tag
run: |
VERSION=$(npm run version --silent)
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
echo "tagname=v${VERSION}" >> $GITHUB_OUTPUT
- uses: rickstaa/action-create-tag@v1
with:
tag: ${{ steps.get_tag.outputs.tagname }}
build-images:
if: github.repository == 'runtipi/runtipi'
needs: get-tag
needs: create-tag
runs-on: ubuntu-latest
steps:
- name: Checkout
@ -49,14 +49,45 @@ jobs:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository_owner }}/runtipi:${{ needs.get-tag.outputs.tag }},ghcr.io/${{ github.repository_owner }}/runtipi:latest
tags: ghcr.io/${{ github.repository_owner }}/runtipi:${{ needs.create-tag.outputs.tagname }},ghcr.io/${{ github.repository_owner }}/runtipi:latest
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/runtipi:buildcache
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/runtipi:buildcache,mode=max
build-worker:
runs-on: ubuntu-latest
needs: create-tag
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push images
uses: docker/build-push-action@v5
with:
context: .
file: ./packages/worker/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository_owner }}/worker:${{ needs.create-tag.outputs.tagname }},ghcr.io/${{ github.repository_owner }}/worker:latest
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/worker:buildcache
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/worker:buildcache,mode=max
build-cli:
runs-on: ubuntu-latest
timeout-minutes: 10
needs: get-tag
needs: create-tag
steps:
- name: Checkout code
uses: actions/checkout@v4
@ -64,7 +95,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v2.4.0
name: Install pnpm
@ -90,7 +121,7 @@ jobs:
run: pnpm install
- name: Set version
run: pnpm -r --filter cli set-version ${{ needs.get-tag.outputs.tag }}
run: pnpm -r --filter cli set-version ${{ needs.create-tag.outputs.tagname }}
- name: Build CLI
run: pnpm -r --filter cli package
@ -101,26 +132,9 @@ jobs:
name: cli
path: packages/cli/dist
create-tag:
runs-on: ubuntu-latest
needs: [build-images, build-cli]
outputs:
tagname: ${{ steps.create_tag.outputs.tagname }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create Tag
id: create_tag
uses: butlerlogic/action-autotag@stable
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
with:
tag_prefix: 'v'
publish-release:
runs-on: ubuntu-latest
needs: [create-tag]
needs: [create-tag, build-images, build-worker, build-cli]
outputs:
id: ${{ steps.create_release.outputs.id }}
steps:
@ -130,38 +144,26 @@ jobs:
name: cli
path: cli
- name: Rename CLI
run: |
mv cli/bin/cli-x64 ./runtipi-cli-linux-x64
mv cli/bin/cli-arm64 ./runtipi-cli-linux-arm64
- name: Create release
id: create_release
uses: actions/create-release@v1
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
body: |
**${{ needs.create-tag.outputs.tagname }}**
tag_name: ${{ needs.create-tag.outputs.tagname }}
release_name: ${{ needs.create-tag.outputs.tagname }}
name: ${{ needs.create-tag.outputs.tagname }}
draft: false
prerelease: true
- name: Upload X64 Linux CLI binary to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: cli/bin/cli-x64
asset_name: runtipi-cli-linux-x64
asset_content_type: application/octet-stream
- name: Upload ARM64 Linux CLI binary to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: cli/bin/cli-arm64
asset_name: runtipi-cli-linux-arm64
asset_content_type: application/octet-stream
files: |
runtipi-cli-linux-x64
runtipi-cli-linux-arm64
e2e-tests:
needs: [create-tag, publish-release]

View file

@ -1,4 +1,4 @@
ARG NODE_VERSION="18.16"
ARG NODE_VERSION="20.10"
ARG ALPINE_VERSION="3.18"
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS node_base

View file

@ -1,4 +1,4 @@
ARG NODE_VERSION="18.16"
ARG NODE_VERSION="20.10"
ARG ALPINE_VERSION="3.18"
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION}

View file

@ -1,7 +1,7 @@
# Tipi — A personal homeserver for everyone
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-40-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
[![License](https://img.shields.io/github/license/runtipi/runtipi)](https://github.com/runtipi/runtipi/blob/master/LICENSE)
@ -123,6 +123,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/itsrllyhim"><img src="https://avatars.githubusercontent.com/u/143047010?v=4?s=100" width="100px;" alt="him"/><br /><sub><b>him</b></sub></a><br /><a href="https://github.com/runtipi/runtipi/commits?author=itsrllyhim" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://cchalop1.com"><img src="https://avatars.githubusercontent.com/u/28163855?v=4?s=100" width="100px;" alt="CHALOPIN Clément"/><br /><sub><b>CHALOPIN Clément</b></sub></a><br /><a href="https://github.com/runtipi/runtipi/commits?author=cchalop1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geetansh"><img src="https://avatars.githubusercontent.com/u/9976198?v=4?s=100" width="100px;" alt="Geetansh Jindal"/><br /><sub><b>Geetansh Jindal</b></sub></a><br /><a href="https://github.com/runtipi/runtipi/commits?author=geetansh" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/0livier"><img src="https://avatars.githubusercontent.com/u/10607?v=4?s=100" width="100px;" alt="Olivier Garcia"/><br /><sub><b>Olivier Garcia</b></sub></a><br /><a href="https://github.com/runtipi/runtipi/commits?author=0livier" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/qcoudeyr"><img src="https://avatars.githubusercontent.com/u/124463277?v=4?s=100" width="100px;" alt="qcoudeyr"/><br /><sub><b>qcoudeyr</b></sub></a><br /><a href="https://github.com/runtipi/runtipi/commits?author=qcoudeyr" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View file

@ -60,7 +60,6 @@ services:
context: .
dockerfile: ./packages/worker/Dockerfile.dev
container_name: tipi-worker
user: root
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/healthcheck']
interval: 5s

View file

@ -1,6 +1,6 @@
{
"name": "runtipi",
"version": "2.2.0",
"version": "2.2.1",
"description": "A homeserver for everyone",
"scripts": {
"knip": "knip",
@ -45,13 +45,15 @@
"@runtipi/postgres-migrations": "^5.3.0",
"@runtipi/shared": "workspace:^",
"@tabler/core": "1.0.0-beta20",
"@tabler/icons-react": "^2.40.0",
"@tabler/icons-react": "^2.42.0",
"argon2": "^0.31.2",
"bullmq": "^4.13.0",
"clsx": "^2.0.0",
"connect-redis": "^7.1.0",
"drizzle-orm": "^0.28.6",
"fs-extra": "^11.1.1",
"geist": "^1.2.0",
"let-it-go": "^1.0.0",
"lodash.merge": "^4.6.2",
"next": "14.0.1",
"next-client-cookies": "^1.0.6",
@ -65,7 +67,7 @@
"react-hot-toast": "^2.4.1",
"react-markdown": "^9.0.0",
"react-select": "^5.8.0",
"react-tooltip": "^5.22.0",
"react-tooltip": "^5.25.0",
"redaxios": "^0.5.1",
"redis": "^4.6.10",
"rehype-raw": "^7.0.0",
@ -93,16 +95,16 @@
"@total-typescript/shoehorn": "^0.1.1",
"@total-typescript/ts-reset": "^0.5.1",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.7",
"@types/jest": "^29.5.11",
"@types/lodash.merge": "^4.6.8",
"@types/node": "20.8.10",
"@types/pg": "^8.10.7",
"@types/react": "18.2.34",
"@types/react": "18.2.39",
"@types/react-dom": "18.2.14",
"@types/semver": "^7.5.4",
"@types/uuid": "^9.0.6",
"@types/validator": "^13.11.5",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react": "^4.1.1",
"@vitest/coverage-v8": "^0.34.6",
@ -110,7 +112,7 @@
"eslint": "8.52.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-next": "14.0.1",
"eslint-config-next": "14.0.3",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.0",

View file

@ -21,7 +21,7 @@ services:
tipi-db:
container_name: tipi-db
image: postgres:14
restart: on-failure
restart: unless-stopped
stop_grace_period: 1m
ports:
- ${POSTGRES_PORT:-5432}:5432
@ -60,7 +60,6 @@ services:
container_name: tipi-worker
image: ghcr.io/runtipi/worker:${TIPI_VERSION}
restart: unless-stopped
user: root
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/healthcheck']
interval: 5s

View file

@ -8,7 +8,7 @@ async function bundle() {
entryPoints: ['./src/index.ts'],
outfile: './dist/index.js',
platform: 'node',
target: 'node18',
target: 'node20',
bundle: true,
color: true,
sourcemap: commandArgs.includes('--sourcemap'),

View file

@ -8,10 +8,10 @@
"test": "dotenv -e .env.test vitest -- --coverage --watch=false --passWithNoTests",
"test:watch": "dotenv -e .env.test vitest",
"package": "npm run build && pkg package.json && chmod +x dist/bin/cli-x64 && chmod +x dist/bin/cli-arm64",
"package:m1": "npm run build && pkg package.json -t node18-darwin-arm64",
"package:m1": "npm run build && pkg package.json -t node20-darwin-arm64",
"set-version": "node -e \"require('fs').writeFileSync('assets/VERSION', process.argv[1])\"",
"build": "node build.js",
"build:meta": "esbuild ./src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --metafile=meta.json --analyze",
"build:meta": "esbuild ./src/index.ts --bundle --platform=node --target=node20 --outfile=dist/index.js --metafile=meta.json --analyze",
"dev": "dotenv -e ../../.env nodemon",
"lint": "eslint . --ext .ts",
"tsc": "tsc --noEmit",
@ -20,8 +20,8 @@
"pkg": {
"assets": "assets/**/*",
"targets": [
"node18-linux-x64",
"node18-linux-arm64"
"node20-linux-x64",
"node20-linux-arm64"
],
"outputPath": "dist/bin"
},
@ -30,7 +30,7 @@
"license": "ISC",
"devDependencies": {
"@faker-js/faker": "^8.2.0",
"@types/cli-progress": "^3.11.4",
"@types/cli-progress": "^3.11.5",
"@types/node": "20.8.10",
"dotenv-cli": "^7.3.0",
"esbuild": "^0.19.4",

View file

@ -58,9 +58,17 @@ export const envSchema = z.object({
if (typeof value === 'boolean') return value;
return value === 'true';
}),
allowAutoThemes: z
.string()
.or(z.boolean())
.optional()
.transform((value) => {
if (typeof value === 'boolean') return value;
return value === 'true';
}),
});
export const settingsSchema = envSchema
.partial()
.pick({ dnsIp: true, internalIp: true, postgresPort: true, appsRepoUrl: true, domain: true, storagePath: true, localDomain: true, demoMode: true, guestDashboard: true })
.pick({ dnsIp: true, internalIp: true, postgresPort: true, appsRepoUrl: true, domain: true, storagePath: true, localDomain: true, demoMode: true, guestDashboard: true, allowAutoThemes: true })
.and(z.object({ port: z.number(), sslPort: z.number(), listenIp: z.string().ip().trim() }).partial());

View file

@ -1,4 +1,4 @@
ARG NODE_VERSION="18.16"
ARG NODE_VERSION="20.10"
ARG ALPINE_VERSION="3.18"
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS node_base

View file

@ -1,4 +1,4 @@
ARG NODE_VERSION="18.16"
ARG NODE_VERSION="20.10"
ARG ALPINE_VERSION="3.18"
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS node_base

View file

@ -8,7 +8,7 @@ async function bundle() {
entryPoints: ['./src/index.ts'],
outfile: './dist/index.js',
platform: 'node',
target: 'node18',
target: 'node20',
bundle: true,
color: true,
sourcemap: commandArgs.includes('--sourcemap'),

View file

@ -4,7 +4,7 @@ import path from 'node:path';
import Redis from 'ioredis';
import dotenv from 'dotenv';
import { Queue } from 'bullmq';
import { copySystemFiles, generateSystemEnvFile, generateTlsCertificates } from '@/lib/system';
import { copySystemFiles, ensureFilePermissions, generateSystemEnvFile, generateTlsCertificates } from '@/lib/system';
import { runPostgresMigrations } from '@/lib/migrations';
import { startWorker } from './watcher/watcher';
import { logger } from '@/lib/logger';
@ -30,6 +30,9 @@ const main = async () => {
logger.info('Generating TLS certificates...');
await generateTlsCertificates({ domain: envMap.get('LOCAL_DOMAIN') });
logger.info('Ensuring file permissions...');
await ensureFilePermissions();
logger.info('Starting queue...');
const queue = new Queue('events', { connection: { host: envMap.get('REDIS_HOST'), port: 6379, password: envMap.get('REDIS_PASSWORD') } });
logger.info('Obliterating queue...');

View file

@ -259,16 +259,7 @@ export const generateTlsCertificates = async (data: { domain?: string }) => {
};
export const ensureFilePermissions = async () => {
const filesAndFolders = [
path.join(ROOT_FOLDER, 'apps'),
path.join(ROOT_FOLDER, 'logs'),
path.join(ROOT_FOLDER, 'repos'),
path.join(ROOT_FOLDER, 'state'),
path.join(ROOT_FOLDER, 'traefik'),
path.join(ROOT_FOLDER, '.env'),
path.join(ROOT_FOLDER, 'VERSION'),
path.join(ROOT_FOLDER, 'docker-compose.yml'),
];
const filesAndFolders = [path.join(ROOT_FOLDER, 'state'), path.join(ROOT_FOLDER, 'traefik')];
const files600 = [path.join(ROOT_FOLDER, 'traefik', 'shared', 'acme.json')];

View file

@ -1,6 +1,6 @@
import path from 'path';
import { execAsync, pathExists } from '@runtipi/shared';
import { getRepoHash } from './repo.helpers';
import { getRepoHash, getRepoBaseUrlAndBranch } from './repo.helpers';
import { logger } from '@/lib/logger';
export class RepoExecutors {
@ -26,21 +26,32 @@ export class RepoExecutors {
/**
* Given a repo url, clone it to the repos folder if it doesn't exist
*
* @param {string} repoUrl
* @param {string} url
*/
public cloneRepo = async (repoUrl: string) => {
public cloneRepo = async (url: string) => {
try {
const repoHash = getRepoHash(repoUrl);
// We may have a potential branch computed in the hash (see getRepoBaseUrlAndBranch)
// so we do it here before splitting the url into repoUrl and branch
const repoHash = getRepoHash(url);
const repoPath = path.join('/app', 'repos', repoHash);
if (await pathExists(repoPath)) {
this.logger.info(`Repo ${repoUrl} already exists`);
this.logger.info(`Repo ${url} already exists`);
return { success: true, message: '' };
}
this.logger.info(`Cloning repo ${repoUrl} to ${repoPath}`);
const [repoUrl, branch] = getRepoBaseUrlAndBranch(url);
await execAsync(`git clone ${repoUrl} ${repoPath}`);
let cloneCommand;
if (branch) {
this.logger.info(`Cloning repo ${repoUrl} on branch ${branch} to ${repoPath}`);
cloneCommand = `git clone -b ${branch} ${repoUrl} ${repoPath}`;
} else {
this.logger.info(`Cloning repo ${repoUrl} to ${repoPath}`);
cloneCommand = `git clone ${repoUrl} ${repoPath}`;
}
await execAsync(cloneCommand);
this.logger.info(`Cloned repo ${repoUrl} to ${repoPath}`);
return { success: true, message: '' };

View file

@ -10,3 +10,18 @@ export const getRepoHash = (repoUrl: string) => {
hash.update(repoUrl);
return hash.digest('hex');
};
/**
* Extracts the base URL and branch from a repository URL.
* @param repoUrl The repository URL.
* @returns An array containing the base URL and branch, or just the base URL if no branch is found.
*/
export const getRepoBaseUrlAndBranch = (repoUrl: string) => {
const branchMatch = repoUrl.match(/^(.*)\/tree\/(.*)$/);
if (branchMatch) {
return [branchMatch[1], branchMatch[2]] ;
}
return [repoUrl, undefined] ;
};

File diff suppressed because it is too large Load diff

BIN
public/tipi-christmas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -121,16 +121,12 @@ if ! command -v docker >/dev/null; then
install_docker "${OS}"
docker_result=$?
if [[ docker_result -eq 0 ]]; then
echo "Docker installed"
else
if [[ docker_result -ne 0 ]]; then
echo "Your system ${OS} is not supported trying with sub_os ${SUB_OS}"
install_docker "${SUB_OS}"
docker_sub_result=$?
if [[ docker_sub_result -eq 0 ]]; then
echo "Docker installed"
else
if [[ docker_sub_result -ne 0 ]]; then
echo "Your system ${SUB_OS} is not supported please install docker manually"
exit 1
fi
@ -138,11 +134,15 @@ if ! command -v docker >/dev/null; then
# Make sure user is in docker group
if ! groups | grep -q '\bdocker\b'; then
echo "Adding user to docker group"
sudo usermod -aG docker "$USER"
echo "✓ Docker installed. Please re-run the installation script to continue with the installation. (curl -L https://setup.runtipi.io | bash)"
fi
# Reload user groups
newgrp docker
exit 0
fi
function check_dependency_and_install() {

View file

@ -1,10 +1,13 @@
import React from 'react';
import Image from 'next/image';
import { getCurrentLocale } from 'src/utils/getCurrentLocale';
import { getLogo } from '@/lib/themes';
import { getConfig } from '@/server/core/TipiConfig';
import { LanguageSelector } from '../components/LanguageSelector';
export default async function AuthLayout({ children }: { children: React.ReactNode }) {
const locale = getCurrentLocale();
const { allowAutoThemes } = getConfig();
return (
<div className="page page-center">
@ -15,7 +18,7 @@ export default async function AuthLayout({ children }: { children: React.ReactNo
<div className="text-center mb-4">
<Image
alt="Tipi logo"
src="/tipi.png"
src={getLogo(allowAutoThemes)}
height={50}
width={50}
style={{

View file

@ -94,11 +94,11 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app, localDomain }) => {
const updateMutation = useAction(updateAppAction, {
onSuccess: (data) => {
setCustomStatus(app.status);
if (!data.success) {
setCustomStatus(app.status);
toast.error(data.failure.reason);
} else {
setCustomStatus('stopped');
toast.success(t('apps.app-details.update-success'));
}
},

View file

@ -26,7 +26,7 @@ export default async function Page() {
if (app.info?.available)
return (
<Link href={`/apps/${app.id}`} className={clsx('col-sm-6 col-lg-4', styles.link)} passHref>
<Link key={app.id} href={`/apps/${app.id}`} className={clsx('col-sm-6 col-lg-4', styles.link)} passHref>
<AppTile key={app.id} app={app.info} status={app.status} updateAvailable={updateAvailable} />
</Link>
);

View file

@ -13,14 +13,16 @@ import { useAction } from 'next-safe-action/hook';
import { logoutAction } from '@/actions/logout/logout-action';
import Script from 'next/script';
import { useRouter } from 'next/navigation';
import { getLogo } from '@/lib/themes';
import { NavBar } from '../NavBar';
interface IProps {
isUpdateAvailable?: boolean;
authenticated?: boolean;
autoTheme: boolean;
}
export const Header: React.FC<IProps> = ({ isUpdateAvailable, authenticated = true }) => {
export const Header: React.FC<IProps> = ({ isUpdateAvailable, authenticated = true, autoTheme }) => {
const { setDarkMode } = useUIStore();
const t = useTranslations('header');
@ -55,7 +57,7 @@ export const Header: React.FC<IProps> = ({ isUpdateAvailable, authenticated = tr
className={clsx('navbar-brand-image me-3')}
width={100}
height={100}
src="/tipi.png"
src={getLogo(autoTheme)}
style={{
width: '30px',
maxWidth: '30px',

View file

@ -5,6 +5,7 @@ import { SystemServiceClass } from '@/server/services/system';
import semver from 'semver';
import clsx from 'clsx';
import { AppServiceClass } from '@/server/services/apps/apps.service';
import { getConfig } from '@/server/core/TipiConfig';
import { Header } from './components/Header';
import { PageTitle } from './components/PageTitle';
import styles from './layout.module.scss';
@ -12,6 +13,7 @@ import { LayoutActions } from './components/LayoutActions/LayoutActions';
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
const user = await getUserFromCookie();
const { allowAutoThemes } = getConfig();
const { apps } = await AppServiceClass.listApps();
@ -25,7 +27,7 @@ export default async function DashboardLayout({ children }: { children: React.Re
return (
<div className="page">
<Header isUpdateAvailable={!isLatest} />
<Header isUpdateAvailable={!isLatest} autoTheme={allowAutoThemes} />
<div className="page-wrapper">
<div className="page-header d-print-none">
<div className="container-xl">

View file

@ -6,6 +6,7 @@ import { useTranslations } from 'next-intl';
import { useAction } from 'next-safe-action/hook';
import { updateSettingsAction } from '@/actions/settings/update-settings';
import { Locale } from '@/shared/internationalization/locales';
import { useRouter } from 'next/navigation';
import { SettingsForm, SettingsFormValues } from '../SettingsForm';
type Props = {
@ -16,12 +17,15 @@ type Props = {
export const SettingsContainer = ({ initialValues, currentLocale }: Props) => {
const t = useTranslations();
const router = useRouter();
const updateSettingsMutation = useAction(updateSettingsAction, {
onSuccess: (data) => {
if (!data.success) {
toast.error(data.failure.reason);
} else {
toast.success(t('settings.settings.settings-updated'));
router.refresh();
}
},
});

View file

@ -19,6 +19,7 @@ export type SettingsFormValues = {
storagePath?: string;
localDomain?: string;
guestDashboard?: boolean;
allowAutoThemes?: boolean;
};
interface IProps {
@ -139,6 +140,29 @@ export const SettingsForm = (props: IProps) => {
)}
/>
</div>
<div className="mb-3">
<Controller
control={control}
name="allowAutoThemes"
defaultValue={false}
render={({ field: { onChange, value, ref, ...rest } }) => (
<Switch
className="mb-3"
ref={ref}
checked={value}
onCheckedChange={onChange}
{...rest}
label={
<>
{t('allow-auto-themes')}
<Tooltip anchorSelect=".allow-auto-themes-hint">{t('allow-auto-themes-hint')}</Tooltip>
<span className={clsx('ms-1 form-help allow-auto-themes-hint')}>?</span>
</>
}
/>
)}
/>
</div>
<div className="mb-3">
<Input
{...register('domain')}

View file

@ -8,12 +8,15 @@ type Props = {
children: React.ReactNode;
cookies: ComponentProps<typeof CookiesProvider>['value'];
initialTheme?: string;
allowAutoThemes: boolean;
};
export const ClientProviders = ({ children, initialTheme, cookies }: Props) => {
export const ClientProviders = ({ children, initialTheme, cookies, allowAutoThemes }: Props) => {
return (
<CookiesProvider value={cookies}>
<ThemeProvider initialTheme={initialTheme}>{children}</ThemeProvider>
<ThemeProvider allowAutoThemes={allowAutoThemes} initialTheme={initialTheme}>
{children}
</ThemeProvider>
</CookiesProvider>
);
};

View file

@ -3,14 +3,22 @@
import { useUIStore } from '@/client/state/uiStore';
import React, { useEffect } from 'react';
import { useCookies } from 'next-client-cookies';
import { getAutoTheme } from '@/lib/themes';
type Props = {
children: React.ReactNode;
allowAutoThemes: boolean;
initialTheme?: string;
};
const loadChristmasTheme = async () => {
const { default: LetItGo } = await import('let-it-go');
const snow = new LetItGo({ number: 50 });
snow.letItGoAgain();
};
export const ThemeProvider = (props: Props) => {
const { children, initialTheme } = props;
const { children, initialTheme, allowAutoThemes } = props;
const cookies = useCookies();
const { theme, setDarkMode } = useUIStore();
@ -30,5 +38,12 @@ export const ThemeProvider = (props: Props) => {
setDarkMode(cookieTheme === 'dark');
}, [cookies, initialTheme, setDarkMode, theme]);
useEffect(() => {
const autoTheme = getAutoTheme();
if (autoTheme === 'christmas' && allowAutoThemes && typeof window !== 'undefined') {
loadChristmasTheme();
}
}, [allowAutoThemes]);
return children;
};

View file

@ -2,9 +2,10 @@ import React from 'react';
import type { Metadata } from 'next';
import { cookies } from 'next/headers';
import { Inter } from 'next/font/google';
import { GeistSans } from 'geist/font/sans';
import merge from 'lodash.merge';
import { NextIntlClientProvider } from 'next-intl';
import { getConfig } from '@/server/core/TipiConfig';
import './global.css';
import clsx from 'clsx';
@ -12,11 +13,6 @@ import { Toaster } from 'react-hot-toast';
import { getCurrentLocale } from '../utils/getCurrentLocale';
import { ClientProviders } from './components/ClientProviders';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
});
export const metadata: Metadata = {
title: 'Tipi',
description: 'Tipi',
@ -31,10 +27,12 @@ export default async function RootLayout({ children }: { children: React.ReactNo
const theme = cookies().get('theme');
const { allowAutoThemes } = getConfig();
return (
<html lang={locale} className={clsx(inter.className, 'border-top-wide border-primary')}>
<html lang={locale} className={clsx(GeistSans.className, 'border-top-wide border-primary')}>
<NextIntlClientProvider locale={locale} messages={mergedMessages}>
<ClientProviders initialTheme={theme?.value} cookies={cookies().getAll()}>
<ClientProviders initialTheme={theme?.value} cookies={cookies().getAll()} allowAutoThemes={allowAutoThemes}>
<body data-bs-theme={theme?.value}>
{children}
<Toaster />

View file

@ -14,7 +14,7 @@ export const dynamic = 'force-dynamic';
export default async function RootPage() {
const appService = new AppServiceClass(db);
const { guestDashboard } = getConfig();
const { guestDashboard, allowAutoThemes } = getConfig();
const headersList = headers();
const host = headersList.get('host');
@ -24,7 +24,7 @@ export default async function RootPage() {
const apps = await appService.getGuestDashboardApps();
return (
<UnauthenticatedPage title="guest-dashboard" subtitle="runtipi">
<UnauthenticatedPage autoTheme={allowAutoThemes} title="guest-dashboard" subtitle="runtipi">
{apps.length === 0 ? (
<EmptyPage title="guest-dashboard-no-apps" subtitle="guest-dashboard-no-apps-subtitle" />
) : (

View file

@ -1,5 +1,6 @@
import Image from 'next/image';
import React from 'react';
import { getLogo } from '@/lib/themes';
import { Button } from '../ui/Button';
interface IProps {
@ -16,7 +17,7 @@ export const StatusScreen: React.FC<IProps> = ({ title, subtitle, onAction, acti
<Image
alt="Tipi log"
className="mb-3"
src="/tipi.png"
src={getLogo(false)}
height={50}
width={50}
style={{

View file

@ -10,15 +10,16 @@ type Props = {
children: React.ReactNode;
title: MessageKey;
subtitle?: MessageKey;
autoTheme: boolean;
};
export const UnauthenticatedPage = (props: Props) => {
const { children, title, subtitle } = props;
const { children, title, subtitle, autoTheme } = props;
const t = useTranslations();
return (
<div className="page">
<Header authenticated={false} />
<Header authenticated={false} autoTheme={autoTheme} />
<div className="page-wrapper">
<div className="page-header d-print-none">
<div className="container-xl">

View file

@ -251,6 +251,8 @@
"invalid-domain": "Invalid domain",
"guest-dashboard": "Enable guest dashboard",
"guest-dashboard-hint": "This will allow non-authenticated users to see a limited dashboard and easily access the running apps on your instance.",
"allow-auto-themes": "Allow auto themes",
"allow-auto-themes-hint": "Be surprised by themes that change automatically based on the time of the year.",
"domain-name": "Domain name",
"domain-name-hint": "Make sure this exact domain contains an A record pointing to your IP.",
"dns-ip": "DNS IP",

38
src/lib/themes.ts Normal file
View file

@ -0,0 +1,38 @@
export const THEMES = {
christmas: {
name: 'christmas',
month: 11,
day: 1,
durationInDays: 26,
},
};
export type Theme = keyof typeof THEMES | 'default';
export const getAutoTheme = (): Theme => {
const date = new Date();
const theme = Object.entries(THEMES).find(([, { month, day, durationInDays }]) => {
const startDate = new Date(date.getFullYear(), month, day);
const endDate = new Date(date.getFullYear(), month, day + durationInDays);
return startDate <= date && date <= endDate;
});
return theme ? (theme[0] as Theme) : 'default';
};
export const getLogo = (autoTheme: boolean) => {
if (!autoTheme) {
return '/tipi.png';
}
const theme = getAutoTheme();
switch (theme) {
case 'christmas':
return '/tipi-christmas.png';
default:
return '/tipi.png';
}
};

View file

@ -52,6 +52,7 @@ export class TipiConfig {
demoMode: conf.DEMO_MODE,
guestDashboard: conf.GUEST_DASHBOARD,
seePreReleaseVersions: false,
allowAutoThemes: true,
};
const parsedConfig = envSchema.safeParse({ ...envConfig, ...this.getFileConfig() });

View file

@ -477,7 +477,7 @@ describe('Update app', () => {
it('Should correctly update app', async () => {
// arrange
const appConfig = createAppConfig({});
await insertApp({ version: 12, config: { TEST_FIELD: 'test' } }, appConfig, db);
await insertApp({ version: 12, status: 'running', config: { TEST_FIELD: 'test' } }, appConfig, db);
// act
await updateApp(appConfig.id, { version: 0 }, db);
@ -487,7 +487,7 @@ describe('Update app', () => {
expect(app).toBeDefined();
expect(app?.config).toStrictEqual({ TEST_FIELD: 'test' });
expect(app?.version).toBe(appConfig.tipi_version);
expect(app?.status).toBe('stopped');
expect(app?.status).toBe('running');
});
it("Should throw if app doesn't exist", async () => {
@ -505,6 +505,16 @@ describe('Update app', () => {
const app = await getAppById(appConfig.id, db);
expect(app?.status).toBe('stopped');
});
it('Should comme back to the previous status before the update of the app', async () => {
// arrange
const appConfig = createAppConfig({});
await insertApp({ status: 'stopped' }, appConfig, db);
// act & assert
await updateApp(appConfig.id, { version: 0 }, db);
const app = await getAppById(appConfig.id, db);
expect(app?.status).toBe('stopped');
});
});
describe('installedApps', () => {

View file

@ -341,6 +341,7 @@ export class AppServiceClass {
*/
public updateApp = async (id: string) => {
const app = await this.queries.getApp(id);
const appStatusBeforeUpdate = app?.status;
if (!app) {
throw new TranslatedError('server-messages.errors.app-not-found', { id });
@ -355,14 +356,19 @@ export class AppServiceClass {
if (success) {
const appInfo = getAppInfo(app.id, app.status);
await this.queries.updateApp(id, { status: 'running', version: appInfo?.tipi_version });
await this.queries.updateApp(id, { version: appInfo?.tipi_version });
if (appStatusBeforeUpdate === 'running') {
await this.startApp(id);
} else {
await this.queries.updateApp(id, { status: appStatusBeforeUpdate });
}
} else {
await this.queries.updateApp(id, { status: 'stopped' });
Logger.error(`Failed to update app ${id}: ${stdout}`);
throw new TranslatedError('server-messages.errors.app-failed-to-update', { id });
}
const updatedApp = await this.queries.updateApp(id, { status: 'stopped' });
const updatedApp = await this.getApp(id);
return updatedApp;
};