diff --git a/.dockerignore b/.dockerignore index b96afe2a7..29b8ca6ad 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,12 @@ +/target/ /.rustwide /.rustwide-docker +/Justfile +/LICENSE +/README.md +/docker-compose.yml +/docs/ /ignored -**/target +/mcps +/triagebot.toml +/clippy.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64b7285cc..dde64ba72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,13 +42,8 @@ jobs: - name: Launch postgres run: | - cp .env.sample .env - mkdir -p ${DOCSRS_PREFIX}/public-html - docker compose up -d db - # Give the database enough time to start up - sleep 5 - # Make sure the database is actually working - psql "${DOCSRS_DATABASE_URL}" + touch .docker.env + docker compose up --wait --wait-timeout 30 db - name: install SQLX CLI run: cargo install sqlx-cli --no-default-features --features postgres @@ -66,9 +61,6 @@ jobs: --database-url $DOCSRS_DATABASE_URL \ --target-version 0 - - name: Clean up the database - run: docker compose down --volumes - test: env: SQLX_OFFLINE: 1 @@ -91,13 +83,8 @@ jobs: - name: Launch postgres and min.io run: | - cp .env.sample .env - mkdir -p ${DOCSRS_PREFIX}/public-html - docker compose up -d db s3 - # Give the database enough time to start up - sleep 5 - # Make sure the database is actually working - psql "${DOCSRS_DATABASE_URL}" + touch .docker.env + docker compose up --wait --wait-timeout 30 db s3 - name: run workspace tests run: | @@ -109,9 +96,6 @@ jobs: run: | cargo test --locked -- --ignored --test-threads=1 - - name: Clean up the database - run: docker compose down --volumes - GUI_test: runs-on: ubuntu-latest steps: @@ -129,19 +113,12 @@ jobs: - name: Launch postgres and min.io run: | - cp .env.sample .env - mkdir -p ${DOCSRS_PREFIX}/public-html - docker compose up -d db s3 - # Give the database enough time to start up - sleep 5 - # Make sure the database is actually working - psql "${DOCSRS_DATABASE_URL}" + touch .docker.env + docker compose up --wait --wait-timeout 30 db s3 - name: Run GUI tests run: ./dockerfiles/run-gui-tests.sh - - name: Clean up the database - run: docker compose down --volumes fmt: name: Rustfmt diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 84ae05b95..1fd4cb261 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,5 +9,7 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Build the Docker image - run: docker build -t docs-rs -f dockerfiles/Dockerfile . + - run: docker build --target web-server -f dockerfiles/Dockerfile . + - run: docker build --target build-server -f dockerfiles/Dockerfile . + - run: docker build --target registry-watcher -f dockerfiles/Dockerfile . + - run: docker build --target cli -f dockerfiles/Dockerfile . diff --git a/.gitignore b/.gitignore index 99643b163..9d9ef06ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /ignored /.env +/.docker.env /src/web/badge/Cargo.lock target *.css diff --git a/Justfile b/Justfile index bf243dd7f..9dc2d6f1a 100644 --- a/Justfile +++ b/Justfile @@ -16,3 +16,30 @@ lint: lint-js: deno run -A npm:eslint@9 static templates gui-tests eslint.config.js + +# Initialize the docker compose database +[group('compose')] +compose-migrate: + docker compose run --build --rm cli database migrate + +# Update last seen reference to the current index head, to only build newly published crates +[group('compose')] +compose-queue-head: + docker compose run --build --rm cli queue set-last-seen-reference --head + +# Launch base docker services, ensuring the database is migrated +[group('compose')] +compose-up: + just compose-migrate + docker compose up --build -d + +# Launch base docker services and registry watcher, ensuring the database is migrated +[group('compose')] +compose-up-watch: + just compose-migrate + docker compose --profile watch up --build -d + +# Shutdown docker services and cleanup all temporary volumes +[group('compose')] +compose-down: + docker compose --profile all down --volumes --remove-orphans diff --git a/README.md b/README.md index 219caaf32..df535ace6 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,7 @@ mkdir -p ignored/cratesfyi-prefix/crates.io-index # Builds the docs.rs binary SQLX_OFFLINE=1 cargo build # Start the external services. -# It may be `docker compose` in newer versions -docker-compose up -d db s3 +docker compose up --wait db s3 # anything that doesn't run via docker-compose needs the settings defined in # .env. Either via `. ./.env` as below, or via any dotenv shell integration. . ./.env @@ -117,23 +116,57 @@ can take a look at its [documentation](https://github.com/GuillaumeGomez/browser ### Pure docker-compose -If you have trouble with the above commands, consider using `docker-compose up --build`, +If you have trouble with the above commands, consider using `docker compose up --build`, which uses docker-compose for the web server as well. This will not cache dependencies - in particular, you'll have to rebuild all 400 whenever the lockfile changes - but makes sure that you're in a known environment so you should have fewer problems getting started. -You can also use the `web` container to run builds on systems which don't support running builds directly (mostly on Mac OS or Windows): +You'll need to `touch .docker.env` first, this file can have any environment +variable overrides you want to use in docker containers. Then run the migrations +before launching the main services: + +```sh +docker compose run --build --rm cli database migrate +docker compose up --build -d +``` + +You can also use the `builder-a` container to run builds on systems which don't support running builds directly (mostly on Mac OS or Windows): + ```sh +# update the toolchain +docker compose run --rm builder-a build update-toolchain # run a build for a single crate -docker-compose run web build crate regex 1.3.1 -# or build essential files -docker-compose run web build add-essential-files -# rebuild the web container when you changed code. -docker-compose build web +docker compose run --rm builder-a build crate regex 1.3.1 +# rebuild containers when you changed code. +docker compose up --wait --build +``` + +You can also run other non-build commands like the setup steps above, or queueing crates for the background builders from within the `cli` container: + +```sh +docker compose run --rm cli database migrate +docker compose run --rm cli queue add regex 1.3.1 +``` + +If you want to run the registry watcher, you'll need to first set the "last seen +reference" from the registry index, e.g. to set it to the current head so only +newly published crates are built: + +```sh +docker compose run --rm cli queue set-last-seen-reference --head +``` + +Then enable the docker-compose profile that includes the watcher: + +```sh +docker compose --profile watch up --build -d ``` Note that running tests is not supported when using pure docker-compose. +Some of the above commands are included in the `Justfile` for ease of use, +check the `[compose]` group in `just --list`. + Please file bugs for any trouble you have running docs.rs! ### Docker-Compose @@ -143,22 +176,22 @@ Three services are defined: | name | access | credentials | description | |------|-------------------------------------------------|----------------------------|----------------------------------------| -| web | http://localhost:3000 | N/A | A container running the docs.rs binary | -| db | postgresql://cratesfyi:password@localhost:15432 | - | Postgres database used by web | -| s3 | http://localhost:9000 | `cratesfyi` - `secret_key` | MinIO (simulates AWS S3) used by web | +| web | http://0.0.0.0:3000 | N/A | A container running the docs.rs binary | +| db | postgresql://cratesfyi:password@127.0.0.1:15432 | - | Postgres database used by web | +| s3 | http://127.0.0.1:9000 | `cratesfyi` - `secret_key` | MinIO (simulates AWS S3) used by web | [docker-compose.yml]: ./docker-compose.yml #### Rebuilding Containers -To rebuild the site, run `docker-compose build`. +To rebuild the site, run `docker compose --profile all build`. Note that docker-compose caches the build even if you change the source code, so this will be necessary anytime you make changes. If you want to completely clean up the database, don't forget to remove the volumes too: ```sh -$ docker-compose down --volumes +$ docker compose down --volumes ``` #### FAQ @@ -172,7 +205,7 @@ This is probably because you have `git.autocrlf` set to true, ##### I see the error `/opt/rustwide/cargo-home/bin/cargo: cannot execute binary file: Exec format error` when running builds. -You are most likely not on a Linux platform. Running builds directly is only supported on `x86_64-unknown-linux-gnu`. On other platforms you can use the `docker-compose run web build [...]` workaround described above. +You are most likely not on a Linux platform. Running builds directly is only supported on `x86_64-unknown-linux-gnu`. On other platforms you can use the `docker compose run --rm builder-a build [...]` workaround described above. See [rustwide#41](https://github.com/rust-lang/rustwide/issues/41) for more details about supporting more platforms directly. @@ -200,11 +233,11 @@ cargo run -- start-web-server ```sh # Builds and adds it into database # This is the main command to build and add a documentation into docs.rs. -# For example, `docker-compose run web build crate regex 1.1.6` +# For example, `docker compose run --rm builder-a build crate regex 1.1.6` cargo run -- build crate -# alternatively, via the web container -docker-compose run web build crate +# alternatively, within docker-compose containers +docker compose run --rm builder-a build crate # Builds every crate on crates.io and adds them into database # (beware: this may take months to finish) diff --git a/docker-compose.yml b/docker-compose.yml index 78ad3cbad..935f58048 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,44 +1,126 @@ -version: "3" +x-healthcheck: &healthcheck-interval + interval: 10s + timeout: 1s + start_interval: 1s + start_period: 10s + +x-environment: &environment + RUST_BACKTRACE: true + + DOCSRS_PREFIX: /opt/docsrs/prefix + DOCSRS_COMPILER_METRICS_PATH: /opt/docsrs/prefix/metrics + + DOCSRS_DATABASE_URL: postgresql://cratesfyi:password@db + DOCSRS_MIN_POOL_IDLE: 1 + DOCSRS_MIN_POOL_SIZE: 2 + DOCSRS_MAX_POOL_SIZE: 10 + DOCSRS_MAX_LEGACY_POOL_SIZE: 10 + + DOCSRS_STORAGE_BACKEND: s3 + + S3_ENDPOINT: http://s3:9000 + AWS_ACCESS_KEY_ID: cratesfyi + AWS_SECRET_ACCESS_KEY: secret_key + + DOCSRS_RENDER_THREADS: 2 + + DOCSRS_RUSTWIDE_WORKSPACE: /opt/docsrs/rustwide + DOCSRS_DOCKER: true + DOCSRS_DOCKER_IMAGE: ghcr.io/rust-lang/crates-build-env/linux-micro + DOCSRS_BUILD_CPU_LIMIT: 2 + DOCSRS_INCLUDE_DEFAULT_TARGETS: false + +x-build: &build + context: . + dockerfile: ./dockerfiles/Dockerfile + args: + PROFILE: dev + PROFILE_DIR: debug + +x-builder: &builder + build: + <<: *build + target: build-server + platform: "linux/amd64" + depends_on: + - db + - s3 + environment: *environment + env_file: + - .docker.env + healthcheck: + <<: *healthcheck-interval + test: curl --silent --fail localhost:3000/about/metrics + services: web: build: - context: . - dockerfile: ./dockerfiles/Dockerfile - args: - PROFILE: dev - PROFILE_DIR: debug + <<: *build + target: web-server platform: "linux/amd64" depends_on: - db - s3 ports: - - "3000:3000" - # for metrics - expose: ["3000"] + - "3000:80" + environment: *environment + env_file: + - .docker.env + healthcheck: + <<: *healthcheck-interval + test: curl --silent --fail localhost:80/about/metrics + + # Include the registry watcher with + # `docker compose --profile watch up --build --wait` + registry-watcher: + build: + <<: *build + target: registry-watcher + platform: "linux/amd64" + depends_on: + - db volumes: - - "/var/run/docker.sock:/var/run/docker.sock" - - ".rustwide-docker:/opt/docsrs/rustwide" - "cratesio-index:/opt/docsrs/prefix/crates.io-index" - - "./ignored/cratesfyi-prefix/metrics:/opt/docsrs/prefix/metrics" - "./static:/opt/docsrs/static:ro" - environment: - DOCSRS_RUSTWIDE_WORKSPACE: /opt/docsrs/rustwide - DOCSRS_COMPILER_METRICS_PATH: /opt/docsrs/prefix/metrics - DOCSRS_DATABASE_URL: postgresql://cratesfyi:password@db - DOCSRS_STORAGE_BACKEND: s3 - S3_ENDPOINT: http://s3:9000 - AWS_ACCESS_KEY_ID: cratesfyi - AWS_SECRET_ACCESS_KEY: secret_key - DOCSRS_MAX_LEGACY_POOL_SIZE: 10 - DOCSRS_MAX_POOL_SIZE: 10 - DOCSRS_MIN_POOL_IDLE: 1 + environment: *environment env_file: - - .env + - .docker.env + profiles: + - watch + - all healthcheck: - test: ["CMD", "curl", "--silent", "--fail", "localhost:3000"] - interval: 10s - timeout: 5s - retries: 10 + <<: *healthcheck-interval + test: curl --silent --fail localhost:3000/about/metrics + + builder-a: + <<: *builder + volumes: + - ".rustwide-docker/builder-a:/opt/docsrs/rustwide" + - "./ignored/cratesfyi-prefix/metrics:/opt/docsrs/prefix/metrics" + - "/var/run/docker.sock:/var/run/docker.sock" + + builder-b: + <<: *builder + volumes: + - ".rustwide-docker/builder-b:/opt/docsrs/rustwide" + - "./ignored/cratesfyi-prefix/metrics:/opt/docsrs/prefix/metrics" + - "/var/run/docker.sock:/var/run/docker.sock" + + cli: + build: + <<: *build + target: cli + platform: "linux/amd64" + depends_on: + - db + - s3 + volumes: + - "cratesio-index:/opt/docsrs/prefix/crates.io-index" + environment: *environment + env_file: + - .docker.env + profiles: + - all db: build: @@ -51,12 +133,10 @@ services: POSTGRES_PASSWORD: password ports: # Use a non-standard port on the host to avoid conflicting with existing postgres servers - - "15432:5432" + - "127.0.0.1:15432:5432" healthcheck: - test: ["CMD", "pg_isready", "--username", "cratesfyi"] - interval: 10s - timeout: 5s - retries: 10 + <<: *healthcheck-interval + test: pg_isready --username cratesfyi s3: image: minio/minio @@ -66,38 +146,26 @@ services: minio server /data --console-address ":9001"; " ports: - - "9000:9000" - - "9001:9001" + - "127.0.0.1:9000:9000" + - "127.0.0.1:9001:9001" volumes: - minio-data:/data environment: MINIO_ROOT_USER: cratesfyi MINIO_ROOT_PASSWORD: secret_key healthcheck: - test: - [ - "CMD", - "curl", - "--silent", - "--fail", - "localhost:9000/minio/health/ready", - ] - interval: 10s - timeout: 5s - retries: 10 + <<: *healthcheck-interval + test: mc ready local prometheus: build: context: ./dockerfiles dockerfile: ./Dockerfile-prometheus ports: - - "9090:9090" + - "127.0.0.1:9090:9090" healthcheck: - test: - ["CMD", "curl", "--silent", "--fail", "localhost:9090/-/ready"] - interval: 10s - timeout: 5s - retries: 10 + <<: *healthcheck-interval + test: promtool check healthy gui_tests: build: @@ -108,6 +176,8 @@ services: - "host.docker.internal:host-gateway" volumes: - "${PWD}:/build/out" + profiles: + - all volumes: postgres-data: {} diff --git a/dockerfiles/Dockerfile b/dockerfiles/Dockerfile index cce7ccf16..d49df8c80 100644 --- a/dockerfiles/Dockerfile +++ b/dockerfiles/Dockerfile @@ -73,44 +73,84 @@ RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \ && DEBIAN_FRONTEND=noninteractive apt-get install -y \ ca-certificates \ + curl \ tini \ && rm -rf /var/lib/apt/lists/* +WORKDIR /srv/docsrs + +# Tini is a small init binary to properly handle signals +ENTRYPOINT ["/usr/bin/tini", "/usr/local/bin/cratesfyi", "--"] +CMD ["start-web-server", "0.0.0.0:80"] + ARG PROFILE_DIR=release COPY --from=build /build/target/$PROFILE_DIR/cratesfyi /usr/local/bin COPY static /srv/docsrs/static COPY templates /srv/docsrs/templates COPY vendor /srv/docsrs/vendor -WORKDIR /srv/docsrs +######################## +# Build server stage # +######################## + +FROM ubuntu:22.04 AS build-server + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates \ + tini \ + curl \ + docker.io \ + build-essential \ + gcc \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + # Tini is a small init binary to properly handle signals -CMD ["/usr/bin/tini", "/usr/local/bin/cratesfyi", "start-web-server", "0.0.0.0:80"] +ENTRYPOINT ["/usr/bin/tini", "/usr/local/bin/cratesfyi", "--"] +CMD ["start-build-server"] -################## -# Output stage # -################## +ARG PROFILE_DIR=release +COPY --from=build /build/target/$PROFILE_DIR/cratesfyi /usr/local/bin + +############################ +# Registry watcher stage # +############################ -FROM ubuntu:22.04 AS output +FROM ubuntu:22.04 AS registry-watcher -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ - git \ - libmagic1 \ - docker.io \ - ca-certificates \ - build-essential \ - gcc \ - pkg-config \ - libssl-dev +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates \ + tini \ + curl \ + git \ + && rm -rf /var/lib/apt/lists/* + +# Tini is a small init binary to properly handle signals +ENTRYPOINT ["/usr/bin/tini", "/usr/local/bin/cratesfyi", "--"] +CMD ["start-registry-watcher", "--repository-stats-updater=enabled", "--cdn-invalidator=enabled"] + +ARG PROFILE_DIR=release +COPY --from=build /build/target/$PROFILE_DIR/cratesfyi /usr/local/bin + +############### +# CLI stage # +############### + +FROM ubuntu:22.04 AS cli + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates \ + tini \ + && rm -rf /var/lib/apt/lists/* -RUN mkdir -p /opt/docsrs/prefix +ENTRYPOINT ["/usr/bin/tini", "/usr/local/bin/cratesfyi", "--"] ARG PROFILE_DIR=release COPY --from=build /build/target/$PROFILE_DIR/cratesfyi /usr/local/bin -COPY static /opt/docsrs/static -COPY templates /opt/docsrs/templates -COPY dockerfiles/entrypoint.sh /opt/docsrs/ -COPY vendor /opt/docsrs/vendor - -WORKDIR /opt/docsrs -ENTRYPOINT ["/opt/docsrs/entrypoint.sh"] -CMD ["daemon", "--registry-watcher=disabled"] diff --git a/dockerfiles/entrypoint.sh b/dockerfiles/entrypoint.sh deleted file mode 100755 index b8fdc61aa..000000000 --- a/dockerfiles/entrypoint.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -set -euv - -export DOCSRS_PREFIX=/opt/docsrs/prefix -export DOCSRS_DOCKER=true -export DOCSRS_LOG=${DOCSRS_LOG-"docs-rs,rustwide=info"} -export PATH="$PATH:/build/target/release" - -# Try migrating the database multiple times if it fails -# This avoids the docker container crashing the first time it's started with -# docker-compose, as PostgreSQL needs some time to initialize. -set +e -failed=0 -while true; do - if ! cratesfyi database migrate; then - ((failed=failed + 1)) - if [ "${failed}" -eq 5 ]; then - exit 1 - fi - echo "failed to migrate the database" - echo "waiting 1 second..." - sleep 1 - else - break - fi -done -set -e - -if ! [ -d "${DOCSRS_PREFIX}/crates.io-index/.git" ]; then - git clone ${REGISTRY_URL:-https://github.com/rust-lang/crates.io-index} "${DOCSRS_PREFIX}/crates.io-index" - # Prevent new crates built before the container creation to be built - git --git-dir="$DOCSRS_PREFIX/crates.io-index/.git" branch crates-index-diff_last-seen -fi - -cratesfyi build update-toolchain --only-first-time - -cratesfyi "$@" diff --git a/dockerfiles/run-gui-tests.sh b/dockerfiles/run-gui-tests.sh index 2c1963342..a76cee160 100755 --- a/dockerfiles/run-gui-tests.sh +++ b/dockerfiles/run-gui-tests.sh @@ -5,7 +5,7 @@ set -e # Just in case it's running, we stop the web server. docker compose stop web -docker compose up -d db s3 +docker compose up --wait --wait-timeout 30 db s3 # If we have a .env file, we need to temporarily move it so # it doesn't make sqlx fail compilation.