diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 45f95ad0..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM ghcr.io/xtruder/nix-devcontainer:v1 - -# cache /nix -VOLUME /nix diff --git a/.devcontainer/compose.yml b/.devcontainer/compose.yml deleted file mode 100644 index e2780791..00000000 --- a/.devcontainer/compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: '3' -services: - dev: - build: - dockerfile: ./Dockerfile - args: - USER_UID: ${UID:-1000} - USER_GID: ${GID:-1000} - context: ./ - volumes: - - ..:/workspace:cached - - nix:/nix - security_opt: - - label:disable - network_mode: "bridge" -volumes: - nix: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6e898201..af68f308 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,29 +3,24 @@ { "name": "Nix", - "dockerComposeFile": "compose.yml", - "service": "dev", - "workspaceFolder": "/workspace", - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, + "build": { + "context": "..", + "dockerfile": "../packaging/docker/Dockerfile-dev" + }, + "overrideCommand": true, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", - "overrideCommand": false, - "userEnvProbe": "loginShell", - "updateRemoteUserUID": false, + "remoteUser": "ubuntu", "portsAttributes": { "5173": { - "label": "Transcribee", + "label": "transcribee", "onAutoForward": "notify" } }, - "onCreateCommand": "nix-shell shell.nix --command ./packaging/install_dependencies.sh", - // Configure tool-specific properties. "customizations": { "vscode": { @@ -34,15 +29,4 @@ ] } } - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" - - // This is purely informative: https://containers.dev/implementors/json_reference/#min-host-reqs - // "hostRequirements": { - // "cpus": 4, - // "memory": "8gb", - // "storage": "20gb", - // "gpu": true - // } } diff --git a/.dockerignore b/.dockerignore index d50998e7..e636ca63 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,7 @@ backend/storage backend/.venv worker/.venv frontend/node_modules/ +frontend/dist/ frontend/testData .git packaging/Dockerfile diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml new file mode 100644 index 00000000..e3f3469d --- /dev/null +++ b/.github/workflows/build-docker.yml @@ -0,0 +1,37 @@ +name: Build docker images + +on: + pull_request: + push: + branches: [main] + merge_group: + +env: + REGISTRY: ghcr.io + +jobs: + build-backend: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Log in to the Container registry + uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build + uses: docker/build-push-action@v7 + with: + context: . + pull: true # always pull dependency images + file: packaging/docker/Dockerfile + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/docker.yml b/.github/workflows/publish-docker.yml similarity index 76% rename from .github/workflows/docker.yml rename to .github/workflows/publish-docker.yml index f19b3cf6..eb8ef52d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/publish-docker.yml @@ -1,4 +1,4 @@ -name: Create and publish a Docker image +name: Publish docker images on: push: @@ -17,21 +17,21 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: actions/checkout@v6 - name: Log in to the Container registry - uses: docker/login-action@v2 + uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -42,13 +42,14 @@ jobs: # set latest tag for main branch type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} - - name: Build and push Docker image - uses: docker/build-push-action@v4 + - name: Build and push + uses: docker/build-push-action@v7 with: context: . push: true + pull: true # always pull dependency images tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - file: packaging/Dockerfile + file: packaging/docker/Dockerfile cache-from: type=gha cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 92b27930..962fe356 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .direnv +.DS_Store diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..e4fba218 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/.vscode/settings.json b/.vscode/settings.json index ba02f62e..5940f5fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,14 @@ { "python.autoComplete.extraPaths": [ - "backend/.venv/lib/python3.11/site-packages", - "worker/.venv/lib/python3.11/site-packages" + "backend/.venv/lib/python3.12/site-packages", + "worker/.venv/lib/python3.12/site-packages" ], "python.analysis.extraPaths": [ - "backend/.venv/lib/python3.11/site-packages", - "worker/.venv/lib/python3.11/site-packages" + "backend/.venv/lib/python3.12/site-packages", + "worker/.venv/lib/python3.12/site-packages" ], "python.analysis.typeCheckingMode": "basic", - "nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix", + "nixEnvSelector.nixFile": "${workspaceFolder}/flake.nix", + "nixEnvSelector.useFlakes": true, + "nixEnvSelector.flakeShell": "default", } diff --git a/backend/.python-version b/backend/.python-version new file mode 120000 index 00000000..0f0852e2 --- /dev/null +++ b/backend/.python-version @@ -0,0 +1 @@ +../.python-version \ No newline at end of file diff --git a/backend/openapi-schema.yml b/backend/openapi-schema.yml index a01dd4c7..7e43287b 100644 --- a/backend/openapi-schema.yml +++ b/backend/openapi-schema.yml @@ -752,16 +752,6 @@ info: version: 0.1.0 openapi: 3.1.0 paths: - /: - get: - operationId: root__get - responses: - '200': - content: - application/json: - schema: {} - description: Successful Response - summary: Root /api/v1/config/: get: operationId: get_config_api_v1_config__get diff --git a/backend/public/.gitkeep b/backend/public/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8cc41f0a..22de54a1 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -25,6 +25,7 @@ dependencies = [ "prometheus-fastapi-instrumentator~=6.1", "pydantic~=2.2", "pydantic-settings>=2.7", + "poethepoet>=0.46.0", ] requires-python = "~=3.12.0" readme = "./README.md" diff --git a/backend/transcribee_backend/main.py b/backend/transcribee_backend/main.py index 422a878c..89d84997 100644 --- a/backend/transcribee_backend/main.py +++ b/backend/transcribee_backend/main.py @@ -15,6 +15,7 @@ from transcribee_backend.routers.task import task_router from transcribee_backend.routers.user import user_router from transcribee_backend.routers.worker import worker_router +from transcribee_backend.spa_static_files import SPAStaticFiles from .media_storage import serve_media @@ -61,10 +62,17 @@ async def lifespan(_: FastAPI): app.include_router(page_router, prefix="/api/v1/page") app.include_router(worker_router, prefix="/api/v1/worker") - -@app.get("/") -async def root(): - return {"message": "🎤🐝: *taps mic* bzzp bzzp"} +app.get("/media/{file}")(serve_media) -app.get("/media/{file}")(serve_media) +app.mount( + "/", + SPAStaticFiles( + directory="public", + html=True, + no_cache_paths=[".", "index.html"], + assets_prefix="assets/", + assets_cache_header="public, max-age=86400, immutable", # aggressively cache for 1 day + ), + name="static-files", +) diff --git a/backend/transcribee_backend/spa_static_files.py b/backend/transcribee_backend/spa_static_files.py new file mode 100644 index 00000000..5720467f --- /dev/null +++ b/backend/transcribee_backend/spa_static_files.py @@ -0,0 +1,41 @@ +from fastapi import HTTPException +from fastapi.staticfiles import StaticFiles +from starlette.exceptions import HTTPException as StarletteHTTPException + + +class SPAStaticFiles(StaticFiles): + def __init__( + self, + *args, + no_cache_paths: list[str], + assets_prefix: str, + assets_cache_header: str | None, + **kwargs, + ): + super().__init__(*args, **kwargs) + self.no_cache_paths = no_cache_paths + self.assets_prefix = assets_prefix + self.assets_cache_header = assets_cache_header + + async def get_response(self, path: str, scope): + try: + res = await super().get_response(path, scope) + if path in self.no_cache_paths: + res.headers["cache-control"] = "no-cache" + elif self.assets_cache_header is not None and path.startswith( + self.assets_prefix + ): + res.headers["cache-control"] = self.assets_cache_header + return res + except (HTTPException, StarletteHTTPException) as ex: + if ex.status_code == 404: + if path.startswith(self.assets_prefix): + # Return 404 for missing assets + raise ex + else: + res = await super().get_response("index.html", scope) + if "index.html" in self.no_cache_paths: + res.headers["cache-control"] = "no-cache" + return res + else: + raise ex diff --git a/backend/uv.lock b/backend/uv.lock index fe8ddade..26efe13f 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -1086,6 +1086,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" }, ] +[[package]] +name = "pastel" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/f1/4594f5e0fcddb6953e5b8fe00da8c317b8b41b547e2b3ae2da7512943c62/pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d", size = 7555, upload-time = "2020-09-16T19:21:12.43Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955, upload-time = "2020-09-16T19:21:11.409Z" }, +] + [[package]] name = "pexpect" version = "4.9.0" @@ -1135,6 +1144,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] +[[package]] +name = "poethepoet" +version = "0.46.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pastel" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/f5/d501fcb67e450fd3fae9db06050420c0c6043758cfa8c30ba40278211265/poethepoet-0.46.0.tar.gz", hash = "sha256:daf8469031879ef59ef0b34fdba83574d65e41eb9186e20cd0f7c89ce479b030", size = 117276, upload-time = "2026-05-15T15:52:02.548Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/01/a9d6ea30351919298d3dc237172ae6a60ce0224ecaaee289b671ab979c13/poethepoet-0.46.0-py3-none-any.whl", hash = "sha256:dc6d770a14792d124abac9066c5a707876027d1878ac9ca26cf57e9b2a96dc89", size = 150581, upload-time = "2026-05-15T15:52:01.118Z" }, +] + [[package]] name = "prometheus-client" version = "0.21.1" @@ -1423,19 +1445,20 @@ wheels = [ [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] [[package]] @@ -1729,6 +1752,7 @@ dependencies = [ { name = "alembic" }, { name = "fastapi" }, { name = "filetype" }, + { name = "poethepoet" }, { name = "prometheus-fastapi-instrumentator" }, { name = "psycopg2" }, { name = "pydantic" }, @@ -1764,6 +1788,7 @@ requires-dist = [ { name = "alembic", specifier = "~=1.0" }, { name = "fastapi", specifier = "~=0.115" }, { name = "filetype", specifier = "~=1.2" }, + { name = "poethepoet", specifier = ">=0.46.0" }, { name = "prometheus-fastapi-instrumentator", specifier = "~=6.1" }, { name = "psycopg2", specifier = "~=2.9" }, { name = "pydantic", specifier = "~=2.2" }, diff --git a/compose.dev.yml b/compose.dev.yml deleted file mode 100644 index 0d3c84ae..00000000 --- a/compose.dev.yml +++ /dev/null @@ -1,4 +0,0 @@ -services: - transcribee: - volumes: - - ".:/app" diff --git a/compose.yml b/compose.yml index 4ffdb2b0..e10795c4 100644 --- a/compose.yml +++ b/compose.yml @@ -1,16 +1,22 @@ +name: transcribee + services: - transcribee: + backend: build: - dockerfile: packaging/Dockerfile - args: - DATABASE_URL: postgresql://transcribee:transcribee@db:5432/transcribee + dockerfile: ./packaging/docker/Dockerfile + environment: + TRANSCRIBEE_BACKEND_DATABASE_URL: postgresql://transcribee:transcribee@db:5432/transcribee + REDIS_HOST: valkey + TESTUSER_PASSWORD: test ports: - - "5173:5173" + - "8000:8000" depends_on: db: condition: service_healthy + volumes: + - postgres-data:/var/lib/postgresql db: - image: postgres + image: postgres:18 restart: always environment: POSTGRES_PASSWORD: transcribee @@ -20,3 +26,9 @@ services: interval: 1s timeout: 5s retries: 10 + valkey: + image: valkey/valkey:9 + restart: always + +volumes: + postgres-data: diff --git a/packaging/dev.sh b/dev.sh similarity index 100% rename from packaging/dev.sh rename to dev.sh diff --git a/doc/development_setup.md b/doc/development_setup.md index 2dbde555..b40dffbe 100644 --- a/doc/development_setup.md +++ b/doc/development_setup.md @@ -18,7 +18,7 @@ If you just want to try out `transcribee`, you need to go through the following dependencies listed in [`flake.nix`](../flake.nix) and in the files under [`pkgs`](../pkgs) file by hand (but that might be more inconvenient). 2. Run the dev script (this might take a long time as it downloads / compiles all the dependencies): - execute `./packaging/dev.sh` in the root folder of the `transcribee` repo. + execute `./dev.sh` in the root folder of the `transcribee` repo. 3. Profit! You can now point your browser to [http://localhost:5173/](http://localhost:5173/) and interact with the running transcribee instance. An admin user with the username "test" and the password "test" is created for you. diff --git a/docker-devshell.sh b/docker-devshell.sh new file mode 100755 index 00000000..3b16a53d --- /dev/null +++ b/docker-devshell.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -ouxe pipefail +docker build . -f packaging/docker/Dockerfile-dev --tag transcribee-dev +docker run --rm -ti -v $(pwd):/workspace -p 5173:5173 transcribee-dev diff --git a/flake.nix b/flake.nix index 944fc566..0852d3a8 100644 --- a/flake.nix +++ b/flake.nix @@ -147,8 +147,8 @@ pkgs.cargo # Our database - pkgs.postgresql_14 - pkgs.postgresql_14.pg_config + pkgs.postgresql_18 + pkgs.postgresql_18.pg_config # Our database2 ? pkgs.redis diff --git a/frontend/src/openapi-schema.ts b/frontend/src/openapi-schema.ts index 191ba9c4..84852b56 100644 --- a/frontend/src/openapi-schema.ts +++ b/frontend/src/openapi-schema.ts @@ -5,10 +5,6 @@ export interface paths { - "/": { - /** Root */ - get: operations["root__get"]; - }; "/api/v1/config/": { /** Get Config */ get: operations["get_config_api_v1_config__get"]; @@ -591,17 +587,6 @@ export type external = Record; export interface operations { - /** Root */ - root__get: { - responses: { - /** @description Successful Response */ - 200: { - content: { - "application/json": unknown; - }; - }; - }; - }; /** Get Config */ get_config_api_v1_config__get: { responses: { diff --git a/packaging/Dockerfile b/packaging/Dockerfile deleted file mode 100644 index bc4923d7..00000000 --- a/packaging/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM nixos/nix - -RUN nix-channel --update - -RUN mkdir -p /app - -COPY flake.nix /app - -WORKDIR /app - -COPY . /app/ - -ENV MEDIA_URL_BASE=http://localhost:5173/ - -RUN nix --extra-experimental-features 'nix-command flakes' develop --command ./packaging/install_dependencies.sh - -EXPOSE 5173 - -ARG DATABASE_URL -ENV TRANSCRIBEE_BACKEND_DATABASE_URL=$DATABASE_URL - -RUN mkdir -p /app/backend/db -RUN mkdir -p /app/backend/storage - -ENTRYPOINT ["nix", "--extra-experimental-features", "nix-command flakes", "develop", "path:.", "--command" ] - -CMD ./packaging/install_dependencies.sh; overmind start -f packaging/Procfile -l backend,frontend,redis,worker diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile new file mode 100644 index 00000000..4cf61b4d --- /dev/null +++ b/packaging/docker/Dockerfile @@ -0,0 +1,52 @@ +# ===== frontend build ===== +FROM node:24 AS frontend-builder + +WORKDIR /app/frontend + +# enable caching dependencies in separate layer +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci + +COPY frontend ./ +RUN npm run build + +# ===== backend build ===== +FROM astral/uv:trixie AS backend-builder + +ENV UV_NO_DEV=1 +ENV UV_NO_EDITABLE=1 +ENV UV_LINK_MODE=copy +ENV UV_MANAGED_PYTHON=1 +ENV UV_VENV_RELOCATABLE=1 +ENV UV_PYTHON_INSTALL_DIR=/opt + +WORKDIR /app/backend + +RUN apt-get update && apt-get install -y libpq5 && rm -rf /var/lib/apt/lists/* + +# enable caching dependencies in separate layer +COPY backend/uv.lock backend/pyproject.toml backend/.python-version ./ +COPY proto /app/proto +COPY .python-version /app/.python-version + +RUN uv sync --frozen --no-install-workspace + +COPY backend ./ +RUN uv sync --locked + +# ===== runtime ===== +FROM debian:trixie-slim + +WORKDIR /app/backend + +RUN apt-get update && apt-get install -y libpq5 file && rm -rf /var/lib/apt/lists/* + +COPY --from=backend-builder /opt /opt +COPY --from=backend-builder /app/backend ./ +COPY --from=frontend-builder /app/frontend/dist ./public + +COPY packaging/docker/run.sh /run.sh + +ENV PATH="/app/backend/.venv/bin:$PATH" + +CMD [ "/run.sh" ] diff --git a/packaging/docker/Dockerfile-dev b/packaging/docker/Dockerfile-dev new file mode 100644 index 00000000..819201c5 --- /dev/null +++ b/packaging/docker/Dockerfile-dev @@ -0,0 +1,22 @@ +FROM ubuntu + +RUN apt-get update && apt-get install -y xz-utils curl systemd sudo git +RUN echo "%ubuntu ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +USER ubuntu +WORKDIR /home/ubuntu + +# install nix +RUN curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install > install-nix.sh +RUN sh install-nix.sh --no-daemon --yes +RUN mkdir -p ~/.config/nix/ +RUN echo "experimental-features = nix-command flakes" > .config/nix/nix.conf +ENV PATH=$PATH:/home/ubuntu/.nix-profile/bin + +WORKDIR /workspace + +# prefill nix store +COPY --chown=ubuntu flake.nix /workspace/flake.nix +RUN nix develop + +CMD ["nix", "develop"] diff --git a/packaging/docker/run.sh b/packaging/docker/run.sh new file mode 100755 index 00000000..78273fea --- /dev/null +++ b/packaging/docker/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -euo pipefail + +# migrate db before starting +poe migrate + +if [ ! -z "$TESTUSER_PASSWORD" ]; then + poe admin create_user --user test --pass "$TESTUSER_PASSWORD" +fi + +uvicorn transcribee_backend.main:app \ + --host "0.0.0.0" \ + --ws websockets \ + --workers 1 # TODO: fix backend for more than one worker diff --git a/worker/.python-version b/worker/.python-version new file mode 120000 index 00000000..0f0852e2 --- /dev/null +++ b/worker/.python-version @@ -0,0 +1 @@ +../.python-version \ No newline at end of file