ci: automerge minor and patch app upgrades (#720)
* fix: app version mismatchs between config and docker-compose * ci: create automerge workflow wip
This commit is contained in:
		
							parent
							
								
									c354f1f094
								
							
						
					
					
						commit
						e495cc1293
					
				
							
								
								
									
										28
									
								
								.github/workflows/auto-merge.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/auto-merge.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
name: automerge
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    types:
 | 
			
		||||
      - labeled
 | 
			
		||||
      - unlabeled
 | 
			
		||||
      - synchronize
 | 
			
		||||
      - opened
 | 
			
		||||
      - edited
 | 
			
		||||
      - ready_for_review
 | 
			
		||||
      - reopened
 | 
			
		||||
      - unlocked
 | 
			
		||||
  pull_request_review:
 | 
			
		||||
    types:
 | 
			
		||||
      - submitted
 | 
			
		||||
  check_suite:
 | 
			
		||||
    types:
 | 
			
		||||
      - completed
 | 
			
		||||
  status: {}
 | 
			
		||||
jobs:
 | 
			
		||||
  automerge:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - id: automerge
 | 
			
		||||
        name: automerge
 | 
			
		||||
        uses: "pascalgn/automerge-action@v0.15.6"
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
 | 
			
		||||
							
								
								
									
										72
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										72
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,9 +1,8 @@
 | 
			
		|||
name: Tipi CI
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    types: [opened, synchronize, reopened, ready_for_review]
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  ci:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +25,7 @@ jobs:
 | 
			
		|||
      - name: Get pnpm store directory
 | 
			
		||||
        id: pnpm-cache
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
 | 
			
		||||
          echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - uses: actions/cache@v3
 | 
			
		||||
        name: Setup pnpm cache
 | 
			
		||||
| 
						 | 
				
			
			@ -44,3 +43,70 @@ jobs:
 | 
			
		|||
 | 
			
		||||
      - name: Run linter
 | 
			
		||||
        run: pnpm run lint
 | 
			
		||||
 | 
			
		||||
      - name: Check bumped version
 | 
			
		||||
        id: check-bumped-version
 | 
			
		||||
        uses: actions/github-script@v4
 | 
			
		||||
        with:
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          script: |
 | 
			
		||||
            const semver = require('semver')
 | 
			
		||||
            const { data } = await github.pulls.listFiles({
 | 
			
		||||
              owner: context.repo.owner,
 | 
			
		||||
              repo: context.repo.repo,
 | 
			
		||||
              pull_number: context.issue.number
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const modifiedFiles = data.map(file => file.filename);
 | 
			
		||||
            const filesInAppsFolder = modifiedFiles.filter(file => file.includes('apps/'))
 | 
			
		||||
 | 
			
		||||
            if (filesInAppsFolder.length < modifiedFiles.length) {
 | 
			
		||||
              console.log('Not all files are in apps folder, skipping automerge')
 | 
			
		||||
              core.setOutput('major_bump', 'true')
 | 
			
		||||
              return
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const configs = modifiedFiles.filter(file => file.includes('config.json'))
 | 
			
		||||
            let majorBump = 'false'
 | 
			
		||||
 | 
			
		||||
            for (const configFile of configs) {
 | 
			
		||||
              const baseContent = await github.repos.getContent({
 | 
			
		||||
                owner: context.repo.owner,
 | 
			
		||||
                repo: context.repo.repo,
 | 
			
		||||
                path: configFile,
 | 
			
		||||
                ref: context.payload.pull_request.base.ref
 | 
			
		||||
              });
 | 
			
		||||
              const baseConfig = JSON.parse(Buffer.from(baseContent.data.content, 'base64').toString('utf-8'))
 | 
			
		||||
 | 
			
		||||
              const currentContent = await github.repos.getContent({
 | 
			
		||||
                owner: context.repo.owner,
 | 
			
		||||
                repo: context.repo.repo,
 | 
			
		||||
                path: configFile,
 | 
			
		||||
                ref: context.payload.pull_request.head.ref
 | 
			
		||||
              });
 | 
			
		||||
              const currentConfig = JSON.parse(Buffer.from(currentContent.data.content, 'base64').toString('utf-8'))
 | 
			
		||||
 | 
			
		||||
              const baseVersion = semver.coerce(baseConfig.version)
 | 
			
		||||
              const currentVersion = semver.coerce(currentConfig.version)
 | 
			
		||||
 | 
			
		||||
              if (currentVersion?.major > baseVersion?.major) {
 | 
			
		||||
                console.log('Major bump detected, skipping automerge')
 | 
			
		||||
                majorBump = 'true'
 | 
			
		||||
                break
 | 
			
		||||
              } 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            core.setOutput('major_bump', majorBump)
 | 
			
		||||
 | 
			
		||||
      - name: Label this PR as "automerge" if major_bump is false
 | 
			
		||||
        if: steps.check-bumped-version.outputs.major_bump == 'false'
 | 
			
		||||
        uses: actions/github-script@v4
 | 
			
		||||
        with:
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          script: |
 | 
			
		||||
            github.issues.addLabels({
 | 
			
		||||
              issue_number: context.issue.number,
 | 
			
		||||
              owner: context.repo.owner,
 | 
			
		||||
              repo: context.repo.repo,
 | 
			
		||||
              labels: ["automerge"]
 | 
			
		||||
            })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								.github/workflows/renovate-app-version.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/renovate-app-version.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -21,7 +21,7 @@ jobs:
 | 
			
		|||
      - name: Get list of updated files by the last commit in this branch separated by space
 | 
			
		||||
        id: updated-files
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::set-output name=files::$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | tr '\n' ' ')"
 | 
			
		||||
          echo "files=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | tr '\n' ' ')" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - name: Run renovate-app-version.sh on updated files
 | 
			
		||||
        run: |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
import fs from 'fs';
 | 
			
		||||
import jsyaml from 'js-yaml';
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import semver from "semver";
 | 
			
		||||
import jsyaml from "js-yaml";
 | 
			
		||||
 | 
			
		||||
interface AppConfig {
 | 
			
		||||
  id: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -18,11 +19,18 @@ interface AppConfig {
 | 
			
		|||
  available: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const networkExceptions = ['pihole', 'tailscale', 'homeassistant', 'plex', 'zerotier', 'gladys'];
 | 
			
		||||
const networkExceptions = [
 | 
			
		||||
  "pihole",
 | 
			
		||||
  "tailscale",
 | 
			
		||||
  "homeassistant",
 | 
			
		||||
  "plex",
 | 
			
		||||
  "zerotier",
 | 
			
		||||
  "gladys",
 | 
			
		||||
];
 | 
			
		||||
const getAppConfigs = (): AppConfig[] => {
 | 
			
		||||
  const apps: AppConfig[] = [];
 | 
			
		||||
 | 
			
		||||
  const appsDir = fs.readdirSync('./apps');
 | 
			
		||||
  const appsDir = fs.readdirSync("./apps");
 | 
			
		||||
 | 
			
		||||
  appsDir.forEach((app: string) => {
 | 
			
		||||
    const path = `./apps/${app}/config.json`;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +44,7 @@ const getAppConfigs = (): AppConfig[] => {
 | 
			
		|||
          apps.push(config);
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        console.error('Error parsing config file', app);
 | 
			
		||||
        console.error("Error parsing config file", app);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			@ -44,152 +52,198 @@ const getAppConfigs = (): AppConfig[] => {
 | 
			
		|||
  return apps;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('App configs', () => {
 | 
			
		||||
  it('Get app config should return at least one app', () => {
 | 
			
		||||
describe("App configs", () => {
 | 
			
		||||
  it("Get app config should return at least one app", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    expect(apps.length).toBeGreaterThan(0);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have an id', () => {
 | 
			
		||||
  describe("Each app should have an id", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      expect(app.id).toBeDefined();
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        expect(app.id).toBeDefined();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a md description', () => {
 | 
			
		||||
  describe("Each app should have a md description", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      const path = `./apps/${app.id}/metadata/description.md`;
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        const path = `./apps/${app.id}/metadata/description.md`;
 | 
			
		||||
 | 
			
		||||
      if (fs.existsSync(path)) {
 | 
			
		||||
        const description = fs.readFileSync(path).toString();
 | 
			
		||||
        expect(description).toBeDefined();
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error(`Missing description for app ${app.id}`);
 | 
			
		||||
        expect(true).toBe(false);
 | 
			
		||||
      }
 | 
			
		||||
        if (fs.existsSync(path)) {
 | 
			
		||||
          const description = fs.readFileSync(path).toString();
 | 
			
		||||
          expect(description).toBeDefined();
 | 
			
		||||
        } else {
 | 
			
		||||
          expect(true).toBe(false);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have categories defined as an array', () => {
 | 
			
		||||
  describe("Each app should have categories defined as an array", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      expect(app.categories).toBeDefined();
 | 
			
		||||
      expect(app.categories).toBeInstanceOf(Array);
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        expect(app.categories).toBeDefined();
 | 
			
		||||
        expect(app.categories).toBeInstanceOf(Array);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a name', () => {
 | 
			
		||||
  describe("Each app should have a name", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      expect(app.name).toBeDefined();
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        expect(app.name).toBeDefined();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a description', () => {
 | 
			
		||||
  describe("Each app should have a description", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      expect(app.description).toBeDefined();
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        expect(app.description).toBeDefined();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a port', () => {
 | 
			
		||||
  describe("Each app should have a port", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      expect(app.port).toBeDefined();
 | 
			
		||||
      expect(app.port).toBeGreaterThan(999);
 | 
			
		||||
      expect(app.port).toBeLessThan(65535);
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        expect(app.port).toBeDefined();
 | 
			
		||||
        expect(app.port).toBeGreaterThan(999);
 | 
			
		||||
        expect(app.port).toBeLessThan(65535);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a different port', () => {
 | 
			
		||||
  test("Each app should have a different port", () => {
 | 
			
		||||
    const appConfigs = getAppConfigs();
 | 
			
		||||
    const ports = appConfigs.map((app) => app.port);
 | 
			
		||||
    expect(new Set(ports).size).toBe(appConfigs.length);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a unique id', () => {
 | 
			
		||||
  test("Each app should have a unique id", () => {
 | 
			
		||||
    const appConfigs = getAppConfigs();
 | 
			
		||||
    const ids = appConfigs.map((app) => app.id);
 | 
			
		||||
    expect(new Set(ids).size).toBe(appConfigs.length);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a version', () => {
 | 
			
		||||
  describe("Each app should have a version", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      expect(app.tipi_version).toBeDefined();
 | 
			
		||||
      expect(app.tipi_version).toBeGreaterThan(0);
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        expect(app.version).toBeDefined();
 | 
			
		||||
        expect(app.tipi_version).toBeDefined();
 | 
			
		||||
        expect(app.tipi_version).toBeGreaterThan(0);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a docker-compose file beside it', () => {
 | 
			
		||||
  describe("Each app should have a docker-compose file beside it", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      expect(fs.existsSync(`./apps/${app.id}/docker-compose.yml`)).toBe(true);
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        expect(fs.existsSync(`./apps/${app.id}/docker-compose.yml`)).toBe(true);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a metadata folder beside it', () => {
 | 
			
		||||
  describe("Each app should have a metadata folder beside it", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      expect(fs.existsSync(`./apps/${app.id}/metadata`)).toBe(true);
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        expect(fs.existsSync(`./apps/${app.id}/metadata`)).toBe(true);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a file named logo.jpg in the metadata folder', () => {
 | 
			
		||||
  describe("Each app should have a file named logo.jpg in the metadata folder", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      expect(fs.existsSync(`./apps/${app.id}/metadata/logo.jpg`)).toBe(true);
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        expect(fs.existsSync(`./apps/${app.id}/metadata/logo.jpg`)).toBe(true);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have a container name equals to its id', () => {
 | 
			
		||||
  describe("Each app should have a container name equals to its id", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      const dockerComposeFile = fs.readFileSync(`./apps/${app.id}/docker-compose.yml`).toString();
 | 
			
		||||
 | 
			
		||||
      const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | 
			
		||||
 | 
			
		||||
      if (!dockerCompose.services[app.id]) {
 | 
			
		||||
        console.error(app.id);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      expect(dockerCompose.services[app.id]).toBeDefined();
 | 
			
		||||
      expect(dockerCompose.services[app.id].container_name).toBe(app.id);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Each app should have network tipi_main_network', () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      if (!networkExceptions.includes(app.id)) {
 | 
			
		||||
        const dockerComposeFile = fs.readFileSync(`./apps/${app.id}/docker-compose.yml`).toString();
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        const dockerComposeFile = fs
 | 
			
		||||
          .readFileSync(`./apps/${app.id}/docker-compose.yml`)
 | 
			
		||||
          .toString();
 | 
			
		||||
 | 
			
		||||
        const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | 
			
		||||
 | 
			
		||||
        expect(dockerCompose.services[app.id]).toBeDefined();
 | 
			
		||||
        expect(dockerCompose.services[app.id].container_name).toBe(app.id);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
        if (!dockerCompose.services[app.id].networks) {
 | 
			
		||||
          console.error(app.id);
 | 
			
		||||
  describe("Each app should have the same version in config.json and docker-compose.yml", () => {
 | 
			
		||||
    const exceptions = ["revolt"];
 | 
			
		||||
    const apps = getAppConfigs().filter((app) => !exceptions.includes(app.id));
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        const dockerComposeFile = fs
 | 
			
		||||
          .readFileSync(`./apps/${app.id}/docker-compose.yml`)
 | 
			
		||||
          .toString();
 | 
			
		||||
 | 
			
		||||
        const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | 
			
		||||
 | 
			
		||||
        expect(dockerCompose.services[app.id]).toBeDefined();
 | 
			
		||||
        expect(dockerCompose.services[app.id].image).toBeDefined();
 | 
			
		||||
 | 
			
		||||
        const dockerImage = dockerCompose.services[app.id].image;
 | 
			
		||||
 | 
			
		||||
        const version = dockerImage.split(":")[1];
 | 
			
		||||
 | 
			
		||||
        expect(version).toContain(app.version);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe("Each app should have network tipi_main_network", () => {
 | 
			
		||||
    const apps = getAppConfigs();
 | 
			
		||||
 | 
			
		||||
    apps.forEach((app) => {
 | 
			
		||||
      test(app.id, () => {
 | 
			
		||||
        if (!networkExceptions.includes(app.id)) {
 | 
			
		||||
          const dockerComposeFile = fs
 | 
			
		||||
            .readFileSync(`./apps/${app.id}/docker-compose.yml`)
 | 
			
		||||
            .toString();
 | 
			
		||||
 | 
			
		||||
          const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | 
			
		||||
 | 
			
		||||
          expect(dockerCompose.services[app.id]).toBeDefined();
 | 
			
		||||
 | 
			
		||||
          expect(dockerCompose.services[app.id].networks).toBeDefined();
 | 
			
		||||
          expect(dockerCompose.services[app.id].networks).toStrictEqual([
 | 
			
		||||
            "tipi_main_network",
 | 
			
		||||
          ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expect(dockerCompose.services[app.id].networks).toBeDefined();
 | 
			
		||||
        expect(dockerCompose.services[app.id].networks).toStrictEqual(['tipi_main_network']);
 | 
			
		||||
      }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
  "available": true,
 | 
			
		||||
  "exposable": true,
 | 
			
		||||
  "tipi_version": 11,
 | 
			
		||||
  "version": "0.107.32",
 | 
			
		||||
  "version": "v0.107.32",
 | 
			
		||||
  "port": 8104,
 | 
			
		||||
  "id": "adguard",
 | 
			
		||||
  "categories": [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,10 @@
 | 
			
		|||
  "exposable": true,
 | 
			
		||||
  "id": "autobrr",
 | 
			
		||||
  "tipi_version": 6,
 | 
			
		||||
  "version": "1.26.1",
 | 
			
		||||
  "categories": ["media"],
 | 
			
		||||
  "version": "v1.26.2",
 | 
			
		||||
  "categories": [
 | 
			
		||||
    "media"
 | 
			
		||||
  ],
 | 
			
		||||
  "description": "autobrr is the modern download automation tool for torrents. With inspiration and ideas from tools like trackarr, autodl-irssi and flexget we built one tool that can do it all, and then some.",
 | 
			
		||||
  "short_desc": "Automation for downloads.",
 | 
			
		||||
  "author": "autobrr",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,13 +6,17 @@
 | 
			
		|||
  "exposable": true,
 | 
			
		||||
  "id": "chatgpt-ui",
 | 
			
		||||
  "tipi_version": 9,
 | 
			
		||||
  "version": "2.5.5",
 | 
			
		||||
  "categories": ["ai"],
 | 
			
		||||
  "version": "v2.5.5",
 | 
			
		||||
  "categories": [
 | 
			
		||||
    "ai"
 | 
			
		||||
  ],
 | 
			
		||||
  "description": "A ChatGPT web client that supports multiple users, multiple languages, and multiple database connections for persistent data storage",
 | 
			
		||||
  "short_desc": "A ChatGPT web client that supports multiple users, multiple languages, and multiple database connections for persistent data storage",
 | 
			
		||||
  "author": "https://github.com/WongSaang",
 | 
			
		||||
  "source": "https://github.com/WongSaang/chatgpt-ui",
 | 
			
		||||
  "supported_architectures": ["amd64"],
 | 
			
		||||
  "supported_architectures": [
 | 
			
		||||
    "amd64"
 | 
			
		||||
  ],
 | 
			
		||||
  "form_fields": [
 | 
			
		||||
    {
 | 
			
		||||
      "type": "random",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ version: "3"
 | 
			
		|||
services:
 | 
			
		||||
  deemix:
 | 
			
		||||
    container_name: deemix
 | 
			
		||||
    image: registry.gitlab.com/bockiii/deemix-docker
 | 
			
		||||
    image: registry.gitlab.com/bockiii/deemix-docker:latest
 | 
			
		||||
    ports:
 | 
			
		||||
      - ${APP_PORT}:6595
 | 
			
		||||
    volumes:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,10 @@
 | 
			
		|||
  "port": 8000,
 | 
			
		||||
  "id": "hello-world",
 | 
			
		||||
  "tipi_version": 2,
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "categories": ["utilities"],
 | 
			
		||||
  "version": "latest",
 | 
			
		||||
  "categories": [
 | 
			
		||||
    "utilities"
 | 
			
		||||
  ],
 | 
			
		||||
  "description": "Hello World web server in under 2 MB",
 | 
			
		||||
  "short_desc": "Hello World web server in under 2 MB",
 | 
			
		||||
  "author": "crccheck",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ version: "3.7"
 | 
			
		|||
services:
 | 
			
		||||
  hello-world:
 | 
			
		||||
    container_name: hello-world
 | 
			
		||||
    image: crccheck/hello-world
 | 
			
		||||
    image: crccheck/hello-world:latest
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    ports:
 | 
			
		||||
      - ${APP_PORT}:8000
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ version: "3.7"
 | 
			
		|||
services:
 | 
			
		||||
  minecraft-server:
 | 
			
		||||
    container_name: minecraft-server
 | 
			
		||||
    image: itzg/minecraft-server
 | 
			
		||||
    image: itzg/minecraft-server:latest
 | 
			
		||||
    ports:
 | 
			
		||||
      - ${APP_PORT}:25565
 | 
			
		||||
    environment:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,13 +6,18 @@
 | 
			
		|||
  "exposable": true,
 | 
			
		||||
  "id": "mixpost-pro",
 | 
			
		||||
  "tipi_version": 6,
 | 
			
		||||
  "version": "0.7",
 | 
			
		||||
  "categories": ["social", "utilities"],
 | 
			
		||||
  "version": "latest",
 | 
			
		||||
  "categories": [
 | 
			
		||||
    "social",
 | 
			
		||||
    "utilities"
 | 
			
		||||
  ],
 | 
			
		||||
  "description": "Mixpost it's the coolest Self-hosted social media management software.",
 | 
			
		||||
  "short_desc": "Self-hosted social media management. Schedule and organize your social content. ",
 | 
			
		||||
  "author": "Inovector",
 | 
			
		||||
  "source": "https://github.com/inovector/mixpost",
 | 
			
		||||
  "supported_architectures": ["amd64"],
 | 
			
		||||
  "supported_architectures": [
 | 
			
		||||
    "amd64"
 | 
			
		||||
  ],
 | 
			
		||||
  "form_fields": [
 | 
			
		||||
    {
 | 
			
		||||
      "type": "random",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,8 +7,11 @@
 | 
			
		|||
  "id": "openbooks",
 | 
			
		||||
  "tipi_version": 3,
 | 
			
		||||
  "url_suffix": "/openbooks/",
 | 
			
		||||
  "version": "v4.5.0",
 | 
			
		||||
  "categories": ["media", "books"],
 | 
			
		||||
  "version": "4.5.0",
 | 
			
		||||
  "categories": [
 | 
			
		||||
    "media",
 | 
			
		||||
    "books"
 | 
			
		||||
  ],
 | 
			
		||||
  "description": "Openbooks allows you to download ebooks from irc.irchighway.net quickly and easily.  ",
 | 
			
		||||
  "short_desc": "Search and Download eBooks",
 | 
			
		||||
  "author": "https://github.com/evan-buss",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,8 @@
 | 
			
		|||
    "social"
 | 
			
		||||
  ],
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "tipi_version": 3,
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "tipi_version": 4,
 | 
			
		||||
  "version": "2.4.9.2",
 | 
			
		||||
  "short_desc": "Open source alternative frontend for TikTok made using PHP ",
 | 
			
		||||
  "author": "pablouser1",
 | 
			
		||||
  "source": "https://github.com/pablouser1/ProxiTok",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ services:
 | 
			
		|||
 | 
			
		||||
  proxitok:
 | 
			
		||||
    container_name: proxitok
 | 
			
		||||
    image: ghcr.io/pablouser1/proxitok:master
 | 
			
		||||
    image: ghcr.io/pablouser1/proxitok:v2.4.9.2
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    ports:
 | 
			
		||||
      - ${APP_PORT}:80
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,10 @@
 | 
			
		|||
version: '3.7'
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  vikunja-frontend:
 | 
			
		||||
  vikunja:
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - vikunja-api
 | 
			
		||||
    container_name: vikunja-frontend
 | 
			
		||||
    container_name: vikunja
 | 
			
		||||
    image: vikunja/frontend:0.20.5
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    networks:
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +46,7 @@ services:
 | 
			
		|||
    networks:
 | 
			
		||||
      - tipi_main_network
 | 
			
		||||
  
 | 
			
		||||
  vikunja:
 | 
			
		||||
  vikunja-proxy:
 | 
			
		||||
    container_name: vikunja
 | 
			
		||||
    image: nginx
 | 
			
		||||
    ports:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,12 +5,9 @@
 | 
			
		|||
  "exposable": true,
 | 
			
		||||
  "port": 8103,
 | 
			
		||||
  "id": "your-spotify",
 | 
			
		||||
  "tipi_version": 3,
 | 
			
		||||
  "version": "latest",
 | 
			
		||||
  "categories": [
 | 
			
		||||
    "music",
 | 
			
		||||
    "utilities"
 | 
			
		||||
  ],
 | 
			
		||||
  "tipi_version": 4,
 | 
			
		||||
  "version": "1.6.0",
 | 
			
		||||
  "categories": ["music", "utilities"],
 | 
			
		||||
  "description": "Self hosted Spotify tracking dashboard.",
 | 
			
		||||
  "short_desc": "Self hosted Spotify tracking dashboard.",
 | 
			
		||||
  "author": "Yooooomi",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ version: "3"
 | 
			
		|||
services:
 | 
			
		||||
  your-spotify:
 | 
			
		||||
    container_name: your-spotify
 | 
			
		||||
    image: yooooomi/your_spotify_client
 | 
			
		||||
    image: yooooomi/your_spotify_client:1.6.0
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - your-spotify-server
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,7 @@
 | 
			
		|||
    "@types/jest": "^28.1.6",
 | 
			
		||||
    "@types/js-yaml": "^4.0.5",
 | 
			
		||||
    "@types/node": "^18.6.2",
 | 
			
		||||
    "@types/semver": "^7.5.0",
 | 
			
		||||
    "commitizen": "^4.2.5",
 | 
			
		||||
    "eslint": "^8.22.0",
 | 
			
		||||
    "eslint-plugin-json-schema-validator": "^4.0.1",
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +34,8 @@
 | 
			
		|||
    "husky": "^8.0.1",
 | 
			
		||||
    "jest": "^28.1.3",
 | 
			
		||||
    "js-yaml": "^4.1.0",
 | 
			
		||||
    "prettier": "^2.8.8",
 | 
			
		||||
    "semver": "^7.5.2",
 | 
			
		||||
    "ts-jest": "^28.0.7",
 | 
			
		||||
    "typescript": "^4.7.4"
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1237
									
								
								pnpm-lock.yaml
									
									
									
									
									
								
							
							
						
						
									
										1237
									
								
								pnpm-lock.yaml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user