├── .dockerignore ├── .gitattributes ├── .github ├── imgs │ ├── .gitattributes │ ├── crate-view-dark.webp │ └── crate-view-light.webp └── workflows │ ├── audit.yml │ ├── book.yml │ ├── ci.yml │ ├── docker-publish.yml │ └── ui.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile-git ├── Dockerfile-web ├── LICENSE ├── README.md ├── book ├── .gitignore ├── CNAME ├── book.toml └── src │ ├── SUMMARY.md │ ├── getting-started │ ├── index.md │ ├── installation.md │ └── user-guide.md │ ├── guide │ ├── config-reference.md │ └── index.md │ └── introduction.md ├── chartered-db ├── Cargo.toml ├── README.md └── src │ ├── crates.rs │ ├── lib.rs │ ├── organisations.rs │ ├── permissions.rs │ ├── schema.rs │ ├── server_private_key.rs │ ├── users.rs │ └── uuid.rs ├── chartered-frontend ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── Dockerfile ├── README.md ├── package-lock.json ├── package.json ├── playwright.config.ts ├── postcss.config.cjs ├── src │ ├── app.d.ts │ ├── app.html │ ├── app.pcss │ ├── components │ │ ├── ErrorAlert.svelte │ │ ├── FeaturedCrate.svelte │ │ ├── Heatmap.svelte │ │ ├── Icon.svelte │ │ ├── Nav.svelte │ │ ├── NavItem.svelte │ │ ├── RelativeTime.svelte │ │ └── Spinner.svelte │ ├── img │ │ └── rust.svg │ ├── routes │ │ ├── (authed) │ │ │ ├── +layout.svelte │ │ │ ├── +page.svelte │ │ │ ├── crates │ │ │ │ └── [organisation] │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── +page.ts │ │ │ │ │ ├── AddMember.svelte │ │ │ │ │ ├── Member.svelte │ │ │ │ │ └── [crate] │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── +page.ts │ │ │ │ │ ├── Dependency.svelte │ │ │ │ │ ├── DependencyDefinition.svelte │ │ │ │ │ ├── MemberTab.svelte │ │ │ │ │ ├── RegistryDefinition.svelte │ │ │ │ │ └── VersionTab.svelte │ │ │ ├── organisations │ │ │ │ ├── +page.svelte │ │ │ │ ├── create │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── +page.ts │ │ │ │ └── list │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── +page.ts │ │ │ ├── search │ │ │ │ ├── +page.svelte │ │ │ │ └── +page.ts │ │ │ ├── sessions │ │ │ │ └── list │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── +page.ts │ │ │ │ │ └── DeleteSessionModal.svelte │ │ │ ├── ssh-keys │ │ │ │ ├── +page.svelte │ │ │ │ ├── +page.ts │ │ │ │ ├── CreateKeyForm.svelte │ │ │ │ ├── DeleteSshKeyModal.svelte │ │ │ │ └── SingleSshKey.svelte │ │ │ └── users │ │ │ │ └── [uuid] │ │ │ │ ├── +page.svelte │ │ │ │ └── +page.ts │ │ ├── (unauthed) │ │ │ ├── +layout.svelte │ │ │ ├── auth │ │ │ │ ├── login │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── +page.ts │ │ │ │ └── register │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── +page.ts │ │ │ └── login │ │ │ │ └── oauth │ │ │ │ └── +page.svelte │ │ ├── +error.svelte │ │ ├── +layout.svelte │ │ └── +layout.ts │ ├── stores │ │ └── auth.ts │ ├── types │ │ ├── crate.ts │ │ ├── featured_crate.ts │ │ ├── organisations.ts │ │ ├── sessions.ts │ │ ├── ssh_keys.ts │ │ └── user.ts │ └── util.ts ├── static │ └── favicon.png ├── svelte.config.js ├── tailwind.config.cjs ├── tests │ └── test.ts ├── tsconfig.json └── vite.config.ts ├── chartered-fs ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── chartered-git ├── .gitignore ├── Cargo.toml ├── README.md ├── config-example.toml └── src │ ├── command_handlers │ ├── fetch.rs │ ├── ls_refs.rs │ └── mod.rs │ ├── config.rs │ ├── generators.rs │ ├── main.rs │ └── tree.rs ├── chartered-types ├── Cargo.toml ├── README.md └── src │ ├── cargo.rs │ └── lib.rs ├── chartered-web ├── .gitignore ├── Cargo.toml ├── README.md ├── config-example.toml └── src │ ├── config.rs │ ├── endpoints │ ├── cargo_api │ │ ├── download.rs │ │ ├── mod.rs │ │ ├── owners.rs │ │ ├── publish.rs │ │ └── yank.rs │ ├── mod.rs │ └── web_api │ │ ├── auth │ │ ├── extend.rs │ │ ├── logout.rs │ │ ├── mod.rs │ │ ├── openid.rs │ │ └── password.rs │ │ ├── crates │ │ ├── info.rs │ │ ├── members.rs │ │ ├── mod.rs │ │ ├── most_downloaded.rs │ │ ├── recently_created.rs │ │ ├── recently_updated.rs │ │ └── search.rs │ │ ├── mod.rs │ │ ├── organisations │ │ ├── crud.rs │ │ ├── info.rs │ │ ├── list.rs │ │ ├── members.rs │ │ └── mod.rs │ │ ├── sessions │ │ ├── delete.rs │ │ ├── list.rs │ │ └── mod.rs │ │ ├── ssh_key.rs │ │ └── users │ │ ├── heatmap.rs │ │ ├── info.rs │ │ ├── mod.rs │ │ └── search.rs │ ├── main.rs │ └── middleware │ ├── cargo_auth.rs │ ├── ip.rs │ ├── logging.rs │ ├── mod.rs │ ├── rate_limit.rs │ └── web_auth.rs ├── diesel.toml └── migrations ├── .gitkeep ├── postgres ├── 2021-08-31-214501_create_crates_table │ ├── down.sql │ └── up.sql └── 2021-11-08-005634_update_sessions_add_uuid │ ├── down.sql │ └── up.sql └── sqlite ├── 2021-08-31-214501_create_crates_table ├── down.sql └── up.sql └── 2021-11-08-005634_update_sessions_add_uuid ├── down.sql └── up.sql /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile-git 2 | Dockerfile-web 3 | .dockerfile 4 | target/ 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .github/imgs/*.png filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.github/imgs/.gitattributes: -------------------------------------------------------------------------------- 1 | crate-view-dark.webp filter=lfs diff=lfs merge=lfs -text 2 | crate-view-light.webp filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.github/imgs/crate-view-dark.webp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a8ed7c544cbfa6c6100a138727cc669aa00bd66b4a9bb77a5d1b86c428676c51 3 | size 114538 4 | -------------------------------------------------------------------------------- /.github/imgs/crate-view-light.webp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e28a5bd23a5785a15c4d73f999a4757ab3a6f3317647fb2f8783910c13f5764e 3 | size 140938 4 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | jobs: 6 | audit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions-rs/audit-check@v1 11 | with: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/book.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Setup mdBook 16 | uses: peaceiris/actions-mdbook@v1 17 | with: 18 | mdbook-version: 'latest' 19 | - run: | 20 | cd book/ 21 | mdbook build 22 | cp CNAME book/ 23 | - name: Deploy 24 | uses: peaceiris/actions-gh-pages@v3 25 | if: ${{ github.ref == 'refs/heads/main' }} 26 | with: 27 | github_token: ${{ secrets.GITHUB_TOKEN }} 28 | publish_dir: ./book/book 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | args: --features sqlite 20 | 21 | test: 22 | name: Test Suite 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: stable 30 | override: true 31 | - uses: actions-rs/cargo@v1 32 | with: 33 | command: test 34 | args: --features sqlite 35 | 36 | fmt: 37 | name: Rustfmt 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v2 41 | - uses: actions-rs/toolchain@v1 42 | with: 43 | profile: minimal 44 | toolchain: stable 45 | override: true 46 | - run: rustup component add rustfmt 47 | - uses: actions-rs/cargo@v1 48 | with: 49 | command: fmt 50 | args: --all -- --check 51 | 52 | clippy: 53 | name: Clippy 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v2 57 | - uses: actions-rs/toolchain@v1 58 | with: 59 | profile: minimal 60 | toolchain: stable 61 | override: true 62 | - run: rustup component add clippy 63 | - uses: actions-rs/cargo@v1 64 | with: 65 | command: clippy 66 | args: --features sqlite -- -D warnings 67 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Create and publish Docker images 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | tags: [ 'v*.*.*' ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | env: 11 | REGISTRY: ghcr.io 12 | 13 | jobs: 14 | build-and-push-chartered-web: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read 18 | packages: write 19 | 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v3 23 | 24 | # Workaround: https://github.com/docker/build-push-action/issues/461 25 | - name: Setup Docker buildx 26 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf 27 | 28 | # Login against a Docker registry except on PR 29 | # https://github.com/docker/login-action 30 | - name: Log into registry ${{ env.REGISTRY }} 31 | if: github.event_name != 'pull_request' 32 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 33 | with: 34 | registry: ${{ env.REGISTRY }} 35 | username: ${{ github.actor }} 36 | password: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | # Extract metadata (tags, labels) for Docker 39 | # https://github.com/docker/metadata-action 40 | - name: Extract Docker metadata 41 | id: meta 42 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 43 | with: 44 | images: ${{ env.REGISTRY }}/w4/chartered-web 45 | 46 | # Build and push Docker image with Buildx (don't push on PR) 47 | # https://github.com/docker/build-push-action 48 | - name: Build and push chartered-web 49 | id: build-and-push 50 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 51 | with: 52 | context: . 53 | file: ./Dockerfile-web 54 | push: ${{ github.event_name != 'pull_request' }} 55 | tags: ${{ steps.meta.outputs.tags }} 56 | labels: ${{ steps.meta.outputs.labels }} 57 | 58 | build-and-push-chartered-git: 59 | runs-on: ubuntu-latest 60 | permissions: 61 | contents: read 62 | packages: write 63 | 64 | steps: 65 | - name: Checkout repository 66 | uses: actions/checkout@v3 67 | 68 | # Workaround: https://github.com/docker/build-push-action/issues/461 69 | - name: Setup Docker buildx 70 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf 71 | 72 | # Login against a Docker registry except on PR 73 | # https://github.com/docker/login-action 74 | - name: Log into registry ${{ env.REGISTRY }} 75 | if: github.event_name != 'pull_request' 76 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 77 | with: 78 | registry: ${{ env.REGISTRY }} 79 | username: ${{ github.actor }} 80 | password: ${{ secrets.GITHUB_TOKEN }} 81 | 82 | # Extract metadata (tags, labels) for Docker 83 | # https://github.com/docker/metadata-action 84 | - name: Extract Docker metadata 85 | id: meta 86 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 87 | with: 88 | images: ${{ env.REGISTRY }}/w4/chartered-git 89 | 90 | # Build and push Docker image with Buildx (don't push on PR) 91 | # https://github.com/docker/build-push-action 92 | - name: Build and push chartered-git 93 | id: build-and-push 94 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 95 | with: 96 | context: . 97 | file: ./Dockerfile-git 98 | push: ${{ github.event_name != 'pull_request' }} 99 | tags: ${{ steps.meta.outputs.tags }} 100 | labels: ${{ steps.meta.outputs.labels }} 101 | -------------------------------------------------------------------------------- /.github/workflows/ui.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: UI CI 4 | 5 | jobs: 6 | e2e-ui-test: 7 | name: End-to-end UI tests 8 | runs-on: ubuntu-22.04 # needed for recent sqlite version 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: '18' 19 | - uses: actions-rs/cargo@v1 20 | with: 21 | command: build 22 | args: -p chartered-web -p chartered-db --features sqlite 23 | - name: Install dependencies 24 | working-directory: ./chartered-frontend 25 | run: npm ci 26 | - name: Install Playwright 27 | working-directory: ./chartered-frontend 28 | run: npx playwright install --with-deps 29 | - name: Run tests 30 | working-directory: ./chartered-frontend 31 | run: | 32 | ../target/debug/chartered-web -c ../chartered-web/config-example.toml & 33 | sleep 5 34 | npm test 35 | - name: Upload test results 36 | if: always() 37 | uses: actions/upload-artifact@v2 38 | with: 39 | name: playwright-report 40 | path: playwright-report 41 | 42 | lint: 43 | name: Prettier formatting 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v3 47 | - uses: actions/setup-node@v2 48 | with: 49 | node-version: '18' 50 | - name: Install dependencies 51 | working-directory: ./chartered-frontend 52 | run: npm ci 53 | - name: Run tests 54 | working-directory: ./chartered-frontend 55 | run: | 56 | npm run lint 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea/ 3 | /chartered.db 4 | .DS_Store 5 | .env 6 | 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "chartered-git", 4 | "chartered-web", 5 | "chartered-fs", 6 | "chartered-db", 7 | "chartered-types", 8 | ] 9 | -------------------------------------------------------------------------------- /Dockerfile-git: -------------------------------------------------------------------------------- 1 | FROM lukemathwalker/cargo-chef:latest-rust-slim-bullseye AS chef 2 | WORKDIR /app 3 | 4 | FROM chef AS planner 5 | COPY . /app 6 | RUN cargo chef prepare --recipe-path recipe.json 7 | 8 | FROM chef AS builder 9 | RUN apt update && apt install -y pkg-config make libpq-dev libssl-dev 10 | ARG DEBIAN_FRONTEND=noninteractive 11 | COPY --from=planner /app/recipe.json recipe.json 12 | RUN cargo chef cook --release --recipe-path recipe.json 13 | COPY . /app 14 | RUN cargo build --release --features postgres --bin chartered-git 15 | 16 | FROM debian:bullseye-slim 17 | LABEL org.opencontainers.image.source https://github.com/w4/chartered 18 | WORKDIR /app 19 | ARG DEBIAN_FRONTEND=noninteractive 20 | RUN apt update && apt install -y libpq-dev libssl-dev ca-certificates && rm -rf /var/lib/apt/lists/* 21 | COPY --from=builder /app/target/release/chartered-git /app/chartered-git 22 | ENTRYPOINT ["/app/chartered-git"] 23 | -------------------------------------------------------------------------------- /Dockerfile-web: -------------------------------------------------------------------------------- 1 | FROM lukemathwalker/cargo-chef:latest-rust-slim-bullseye AS chef 2 | WORKDIR /app 3 | 4 | FROM chef AS planner 5 | COPY . /app 6 | RUN cargo chef prepare --recipe-path recipe.json 7 | 8 | FROM chef AS builder 9 | RUN apt update && apt install -y pkg-config make libpq-dev libssl-dev 10 | ARG DEBIAN_FRONTEND=noninteractive 11 | COPY --from=planner /app/recipe.json recipe.json 12 | RUN cargo chef cook --release --recipe-path recipe.json 13 | COPY . /app 14 | RUN cargo build --release --features postgres --bin chartered-web 15 | 16 | FROM debian:bullseye-slim 17 | LABEL org.opencontainers.image.source https://github.com/w4/chartered 18 | WORKDIR /app 19 | ARG DEBIAN_FRONTEND=noninteractive 20 | RUN apt update && apt install -y libpq-dev libssl-dev ca-certificates && rm -rf /var/lib/apt/lists/* 21 | COPY --from=builder /app/target/release/chartered-web /app/chartered-web 22 | ENTRYPOINT ["/app/chartered-web"] 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chartered 2 | 3 | a little dig at creating a private cargo repository with authenticated downloads, the plan is to have cargo connect to 4 | a git server we setup that can serve a fake index generated just for the authenticated user that we can embed temporary 5 | authentication credentials into. 6 | 7 | learn more at https://book.chart.rs/ 8 | 9 | designed to be easily morphable into a first-class authenticated registry-provider once [one][1] [of][2] the cargo RFCs go 10 | through. 11 | 12 | [1]: https://github.com/rust-lang/rfcs/pull/2719 13 | [2]: https://github.com/rust-lang/rfcs/pull/3139 14 | 15 | [open tasks](https://github.com/w4/chartered/issues) 16 | 17 | #### fine grained permissions per user per crate 18 | 19 | - VISIBLE 20 | - PUBLISH_VERSION 21 | - YANK_VERSION 22 | - MANAGE_USERS 23 | 24 | #### organisation support 25 | 26 | crates are required to be under an organisation, the organisation can be specified when declaring the custom registry 27 | in `.cargo/config.toml` like so: 28 | 29 | ``` 30 | [registries] 31 | my-org = { index = "ssh://chart.rs:22/my-org" } 32 | my-other-org = { index = "ssh://chart.rs:22/my-other-org" } 33 | ``` 34 | 35 | #### screen shots 36 | 37 | crate view (light) 38 | crate view (dark) 39 | -------------------------------------------------------------------------------- /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /book/CNAME: -------------------------------------------------------------------------------- 1 | book.chart.rs 2 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Jordan Doyle"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Chartered" 7 | 8 | [output.html] 9 | git-repository-url = "https://github.com/w4/chartered/tree/main/book" 10 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](./introduction.md) 4 | 5 | - [Getting Started](./getting-started/index.md) 6 | - [Server Installation](./getting-started/installation.md) 7 | - [User Guide](./getting-started/user-guide.md) 8 | - [Chartered Guide](./guide/index.md) 9 | - [Configuration Reference](./guide/config-reference.md) -------------------------------------------------------------------------------- /book/src/getting-started/index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | To get started with Chartered, run the server and upload your first crate. 4 | 5 | - [Server installation](./installation.md) 6 | - [User guide](./user-guide.md) 7 | -------------------------------------------------------------------------------- /book/src/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | # Server Installation 2 | 3 | Chartered's server comes in 3 parts: 4 | 5 | - **chartered-git**: hosts the git server which clients grab the crate index from, along with 6 | their credentials to grab the crates from the next service, 7 | - **chartered-web**: hosts the API portion of chartered, which serves the crates themselves 8 | (or a redirect to them, depending on which storage backend you're using) and hosts the "web 9 | API" which is consumed by our final service, 10 | - **chartered-frontend**: a React-based [crates.io](https://crates.io/)-like web UI for viewing 11 | crates, managing organisations and viewing AAA data. 12 | 13 | Each of these services are hosted separately from one another, and could technically be swapped 14 | out for other implementations - the only shared layer between the three of them is database 15 | storage for crate lookups and authentication credential vending. All of the services have the 16 | ability to be clustered with no extra configuration. 17 | 18 | ### Backend Services 19 | 20 | `chartered-git` and `chartered-web`'s setups are similar, first they need a database set up - 21 | either SQLite or PostgreSQL, PostgreSQL is recommended anywhere outside of development/local 22 | use for obvious reasons. 23 | 24 | Both the aformentioned services have sane defaults for development and can be ran simply by 25 | running the binary, the services will bind to `127.0.0.1:8899` and `127.0.0.1:8080` respectively 26 | and store crate files in `/tmp/chartered`, configuration away from these defaults is simple. 27 | 28 | Using the recommended setup, S3 & PostgreSQL: 29 | 30 | 31 | #### `chartered-web` config 32 | 33 | ```toml 34 | bind_address = "127.0.0.1:8080" 35 | database_uri = "postgres://user:password@localhost/chartered" 36 | storage_uri = "s3://s3-eu-west-1.amazonaws.com/my-cool-crate-store/" 37 | frontend_base_uri = "http://localhost:5173/" 38 | 39 | [auth.password] 40 | enabled = true 41 | 42 | # openid connect provider 43 | [auth.gitlab] 44 | enabled = true 45 | discovery_uri = "https://gitlab.com/" 46 | client_id = "[client-id]" 47 | client_secret = "[client-secret]" 48 | ``` 49 | 50 | #### `chartered-git` config 51 | 52 | ```toml 53 | bind_address = "127.0.0.1:2233" 54 | database_uri = "postgres://user:password@localhost/chartered" # can also be `sqlite://` 55 | web_base_uri = "http://localhost:8888/" 56 | 57 | [committer] 58 | name = "Chartered" 59 | email = "noreply@chart.rs" 60 | message = "Updated crates!" 61 | ``` 62 | 63 | These configuration files can be passed into each binary using the `-c` CLI argument. 64 | 65 | Alternative crate stores will be considered, please consider contributing or 66 | [create an issue on GitHub][gh-issue]. MySQL support, however, is a no-go. 67 | 68 | `chartered-web` & `chartered-git` can be built from source easily or ran using the 69 | Dockerfile: 70 | 71 | ``` 72 | $ docker build https://github.com/w4/chartered.git#main \ 73 | --target chartered-web \ 74 | -t chartered-web:master 75 | $ docker build https://github.com/w4/chartered.git#main \ 76 | --target chartered-git \ 77 | -t chartered-git:master 78 | $ docker -v $PWD/web-config.toml:/config.toml run -d chartered-web --config /config.toml 79 | $ docker -v $PWD/git-config.toml:/config.toml run -d chartered-git --config /config.toml 80 | ``` 81 | 82 | [gh-issue]: https://github.com/w4/chartered/issues 83 | 84 | ### Frontend 85 | 86 | The frontend only needs to be configured to point to the `chartered-web` service. This can be 87 | done by changing the bundled `config.json`. This can then be hosted in S3/minio/your preferred 88 | static hosting platform, a Dockerfile can also be built which uses [`static-web-server`][sws] 89 | to run on your own server without another way of hosting static content: 90 | 91 | ```sh 92 | # buildkit doesn't yet support subdirectories for git repositories 93 | $ DOCKER_BUILDKIT=0 docker build \ 94 | https://github.com/w4/chartered.git#main:chartered-frontend \ 95 | --build-arg BASE_URL=https://api.my.instance.chart.rs \ 96 | -t chartered-frontend:master 97 | $ docker run -d -p 8080:80 chartered-frontend:master 98 | $ curl http://127.0.0.1:8080 99 | ... 100 | ``` 101 | 102 | Where `BASE_URL` points to the `chartered-web` instance. 103 | 104 | [sws]: https://github.com/joseluisq/static-web-server 105 | -------------------------------------------------------------------------------- /book/src/getting-started/user-guide.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | Using chartered as a user is actually pretty simple, it's very much like your 4 | standard Cargo registry except with two extra concepts: organisations and 5 | permissions. 6 | 7 | Organisations are very much like the organisations you'll likely have used on 8 | your preferred SCM host, a group of users that have group-level permissions, 9 | in Chartered case the permissions these users may have are: 10 | 11 | ### Permissions 12 | 13 | - `VISIBLE` 14 | - Essentially the base-level permission meaning the user belongs to the group, 15 | if the user doesn't have this permission they're not in the group, this 16 | permission at the crate-level means the user can download the crate and see 17 | it in the WebUI. 18 | - `PUBLISH_VERSION` 19 | - Gives the ability to publish a new version for crates belonging to the group. 20 | - `YANK_VERSION` 21 | - Gives the ability to yank (and unyank) published versions for crates belonging 22 | to the group. 23 | - `MANAGE_USERS` 24 | - Gives the ability to add (and remove) users from the group, and crates belonging 25 | to the organisation. 26 | - `CREATE_CRATE` 27 | - Gives the ability to create a new crate under the organisation. 28 | 29 | All these permissions, with the exception of `CREATE_CRATE`, can also be used at the 30 | crate-level for giving extra permissions to org members for a particular crate - or 31 | even users outside of the org. Bare in mind, however, these permissions are _additive_ - 32 | it is not possible for permissions to be _subtracted_ from a user at the crate-level 33 | if they have been granted them by the organisation. 34 | 35 | ### Publishing your first crate 36 | 37 | With all this in mind, it's about time you started publishing your first crate! 38 | 39 | Chartered has excellent integration with Cargo's [alternative registry][arp] 40 | implementation and is used very much like a standard alternative registry. The only 41 | prerequisites for publishing your crate are: 42 | 43 | 1. Have an SSH key added to your Chartered account, which you can do via the WebUI 44 | 2. Belong to an organisation you have the `PUBLISH_VERSION` permission for, anyone can 45 | create a new organisation if you don't already belong to one. 46 | 47 | And once you're ready, you can add the organisation's registry to your `.cargo/config.toml` 48 | like so: 49 | 50 | ```toml 51 | [registries] 52 | my-organisation = { index = "ssh://ssh.chart.rs/my-organisation" } 53 | ``` 54 | 55 | (You should create this file if it doesn't already exist) 56 | 57 | You can now publish the crate using cargo as you normally would, except with the 58 | registry specified: 59 | 60 | ```sh 61 | $ cargo publish --registry my-organisation --token "" 62 | ``` 63 | 64 | Note: the token is purposefully empty, as the token will be vended by the index based 65 | on your SSH key. 66 | 67 | [arp]: https://doc.rust-lang.org/cargo/reference/registries.html 68 | 69 | ### Pulling in dependencies 70 | 71 | Again, not too dissimilar from using [crates.io][cio], you can declare your dependencies 72 | as normal with the exception that you need to specify the organisation the crate should 73 | be pulled from provided you've declared the organisation in `.cargo/config.toml` as shown 74 | in the previous section of this guide. 75 | 76 | ```toml 77 | [dependencies] 78 | my-other-crate = { version = "0.1", registry = "my-organisation" } 79 | ``` 80 | 81 | Your other Cargo dependencies from [crates.io][cio] can be declared as normal alongside 82 | organisation dependencies. 83 | 84 | [cio]: https://crates.io/ 85 | -------------------------------------------------------------------------------- /book/src/guide/index.md: -------------------------------------------------------------------------------- 1 | # Chartered Guide 2 | 3 | - [Configuration Reference](./config-reference.md) -------------------------------------------------------------------------------- /book/src/introduction.md: -------------------------------------------------------------------------------- 1 | # chartered ✈️ 2 | 3 | Chartered is an [alternative registry][arp] implementation that goes a little 4 | bit futher than your bog-standard registry, providing AAA (authentication, 5 | authorisation & accounting) guarentees for each of your crates. 6 | 7 | [arp]: https://doc.rust-lang.org/cargo/reference/registries.html 8 | 9 | ### Sections 10 | 11 | **[Getting Started](getting-started/index.md)** 12 | 13 | To get started with Chartered, running the servers & uploading your first 14 | crate. 15 | 16 | **[Chartered Guide](guide/index.md)** 17 | 18 | The guide will give you the run down on all things chartered. 19 | 20 | **Appendices:** 21 | * [Glossary](appendix/glossary.md) 22 | 23 | **Other documentation:** 24 | * [Changelog](https://github.com/w4/chartered/releases) 25 | * [Cargo book](https://doc.rust-lang.org/cargo/index.html) 26 | -------------------------------------------------------------------------------- /chartered-db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chartered-db" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | chartered-fs = { path = "../chartered-fs" } 10 | chartered-types = { path = "../chartered-types" } 11 | 12 | base64 = "0.13" 13 | bitflags = "1" 14 | chrono = "0.4" 15 | diesel = { version = "=1.4.8", features = ["r2d2", "chrono"] } 16 | diesel_migrations = "1.4" 17 | diesel-tracing = "0.1" 18 | displaydoc = "0.2" 19 | hex = "0.4" 20 | http = "0.2" 21 | itertools = "0.10" 22 | option_set = "0.1" 23 | rand = "0.8" 24 | reqwest = "0.11" 25 | serde = { version = "1", features = ["derive"] } 26 | serde_json = "1" 27 | thiserror = "1" 28 | tracing = "0.1" 29 | tokio = "1" 30 | uuid = "1" 31 | dotenv = "0.15" 32 | thrussh-keys = "0.21" 33 | 34 | [features] 35 | sqlite = ["diesel/sqlite", "diesel-tracing/sqlite"] 36 | postgres = ["diesel/postgres", "diesel-tracing/postgres"] 37 | 38 | -------------------------------------------------------------------------------- /chartered-db/README.md: -------------------------------------------------------------------------------- 1 | # chartered-db (library) 2 | 3 | Defines the database schema and corresponding queries for chartered. 4 | -------------------------------------------------------------------------------- /chartered-db/src/permissions.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use option_set::{option_set, OptionSet}; 3 | 4 | option_set! { 5 | #[derive(FromSqlRow, AsExpression)] 6 | pub struct UserPermission: Identity + i32 { 7 | const VISIBLE = 0b0000_0000_0000_0000_0000_0000_0000_0001; 8 | const PUBLISH_VERSION = 0b0000_0000_0000_0000_0000_0000_0000_0010; 9 | const YANK_VERSION = 0b0000_0000_0000_0000_0000_0000_0000_0100; 10 | const MANAGE_USERS = 0b0000_0000_0000_0000_0000_0000_0000_1000; 11 | const CREATE_CRATE = 0b0000_0000_0000_0000_0000_0000_0001_0000; 12 | } 13 | } 14 | 15 | impl UserPermission { 16 | #[must_use] 17 | pub fn names() -> &'static [&'static str] { 18 | Self::NAMES 19 | } 20 | 21 | /// Returns a list of permissions that implied by setting other permissions, 22 | /// those these can be overridden if the user adding the permssion explicitly 23 | /// removes the permission. 24 | #[must_use] 25 | pub fn implications() -> &'static [[UserPermission; 2]] { 26 | &[ 27 | // original => implied 28 | [Self::CREATE_CRATE, Self::PUBLISH_VERSION], 29 | ] 30 | } 31 | } 32 | 33 | impl diesel::deserialize::FromSql 34 | for UserPermission 35 | where 36 | i32: diesel::deserialize::FromSql, 37 | { 38 | fn from_sql( 39 | bytes: Option<&B::RawValue>, 40 | ) -> std::result::Result> { 41 | let val = i32::from_sql(bytes)?; 42 | Ok(UserPermission::from_bits_truncate(val)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chartered-db/src/schema.rs: -------------------------------------------------------------------------------- 1 | table! { 2 | crate_versions (id) { 3 | id -> Integer, 4 | crate_id -> Integer, 5 | version -> Text, 6 | filesystem_object -> Text, 7 | size -> Integer, 8 | yanked -> Bool, 9 | checksum -> Text, 10 | dependencies -> Binary, 11 | features -> Binary, 12 | links -> Nullable, 13 | user_id -> Integer, 14 | created_at -> Timestamp, 15 | } 16 | } 17 | 18 | table! { 19 | crates (id) { 20 | id -> Integer, 21 | name -> Text, 22 | organisation_id -> Integer, 23 | readme -> Nullable, 24 | description -> Nullable, 25 | repository -> Nullable, 26 | homepage -> Nullable, 27 | documentation -> Nullable, 28 | downloads -> Integer, 29 | created_at -> Timestamp, 30 | } 31 | } 32 | 33 | table! { 34 | organisations (id) { 35 | id -> Integer, 36 | uuid -> Binary, 37 | name -> Text, 38 | description -> Text, 39 | public -> Bool, 40 | } 41 | } 42 | 43 | table! { 44 | server_private_keys (id) { 45 | id -> Integer, 46 | ssh_key_type -> Text, 47 | ssh_private_key -> Binary, 48 | } 49 | } 50 | 51 | table! { 52 | user_crate_permissions (id) { 53 | id -> Integer, 54 | user_id -> Integer, 55 | crate_id -> Integer, 56 | permissions -> Integer, 57 | } 58 | } 59 | 60 | table! { 61 | user_organisation_permissions (id) { 62 | id -> Integer, 63 | user_id -> Integer, 64 | organisation_id -> Integer, 65 | permissions -> Integer, 66 | } 67 | } 68 | 69 | table! { 70 | user_sessions (id) { 71 | id -> Integer, 72 | user_id -> Integer, 73 | session_key -> Text, 74 | user_ssh_key_id -> Nullable, 75 | expires_at -> Nullable, 76 | user_agent -> Nullable, 77 | ip -> Nullable, 78 | uuid -> Binary, 79 | } 80 | } 81 | 82 | table! { 83 | user_ssh_keys (id) { 84 | id -> Integer, 85 | uuid -> Binary, 86 | name -> Text, 87 | user_id -> Integer, 88 | ssh_key -> Binary, 89 | created_at -> Timestamp, 90 | last_used_at -> Nullable, 91 | } 92 | } 93 | 94 | table! { 95 | users (id) { 96 | id -> Integer, 97 | uuid -> Binary, 98 | username -> Text, 99 | password -> Nullable, 100 | name -> Nullable, 101 | nick -> Nullable, 102 | email -> Nullable, 103 | external_profile_url -> Nullable, 104 | picture_url -> Nullable, 105 | } 106 | } 107 | 108 | joinable!(crate_versions -> crates (crate_id)); 109 | joinable!(crate_versions -> users (user_id)); 110 | joinable!(crates -> organisations (organisation_id)); 111 | joinable!(user_crate_permissions -> crates (crate_id)); 112 | joinable!(user_crate_permissions -> users (user_id)); 113 | joinable!(user_organisation_permissions -> organisations (organisation_id)); 114 | joinable!(user_organisation_permissions -> users (user_id)); 115 | joinable!(user_sessions -> user_ssh_keys (user_ssh_key_id)); 116 | joinable!(user_sessions -> users (user_id)); 117 | joinable!(user_ssh_keys -> users (user_id)); 118 | 119 | allow_tables_to_appear_in_same_query!( 120 | crate_versions, 121 | crates, 122 | organisations, 123 | server_private_keys, 124 | user_crate_permissions, 125 | user_organisation_permissions, 126 | user_sessions, 127 | user_ssh_keys, 128 | users, 129 | ); 130 | -------------------------------------------------------------------------------- /chartered-db/src/uuid.rs: -------------------------------------------------------------------------------- 1 | use diesel::sql_types::Binary; 2 | use std::fmt; 3 | use std::fmt::{Display, Formatter}; 4 | use std::io::prelude::*; 5 | pub use uuid::Uuid; 6 | 7 | #[derive(Debug, Clone, Copy, FromSqlRow, AsExpression, Hash, Eq, PartialEq)] 8 | #[sql_type = "Binary"] 9 | pub struct SqlUuid(pub uuid::Uuid); 10 | 11 | impl SqlUuid { 12 | #[must_use] 13 | pub fn random() -> Self { 14 | Self(uuid::Uuid::new_v4()) 15 | } 16 | } 17 | 18 | impl From for uuid::Uuid { 19 | fn from(s: SqlUuid) -> Self { 20 | s.0 21 | } 22 | } 23 | 24 | impl Display for SqlUuid { 25 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 26 | write!(f, "{}", self.0) 27 | } 28 | } 29 | 30 | impl diesel::deserialize::FromSql for SqlUuid 31 | where 32 | Vec: diesel::deserialize::FromSql, 33 | { 34 | fn from_sql(bytes: Option<&B::RawValue>) -> diesel::deserialize::Result { 35 | let value = >::from_sql(bytes)?; 36 | uuid::Uuid::from_slice(&value) 37 | .map(SqlUuid) 38 | .map_err(Into::into) 39 | } 40 | } 41 | 42 | impl diesel::serialize::ToSql for SqlUuid 43 | where 44 | [u8]: diesel::serialize::ToSql, 45 | { 46 | fn to_sql( 47 | &self, 48 | out: &mut diesel::serialize::Output<'_, W, B>, 49 | ) -> diesel::serialize::Result { 50 | out.write_all(self.0.as_bytes()) 51 | .map(|_| diesel::serialize::IsNull::No) 52 | .map_err(Into::into) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /chartered-frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /chartered-frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript'), 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020, 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /chartered-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | /.vite 10 | -------------------------------------------------------------------------------- /chartered-frontend/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /chartered-frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /chartered-frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "tabWidth": 4, 4 | "singleQuote": true, 5 | "trailingComma": "all", 6 | "printWidth": 120, 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /chartered-frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node AS builder 2 | ARG VITE_CHARTERED_WEB_URL 3 | ARG VITE_CHARTERED_SSH_URL 4 | RUN ["/bin/bash", "-c", ": ${VITE_CHARTERED_WEB_URL:?VITE_CHARTERED_WEB_URL must be set to the public URL that chartered-web can be reached by passing --build-arg to docker build.}"] 5 | RUN ["/bin/bash", "-c", ": ${VITE_CHARTERED_SSH_URL:?VITE_CHARTERED_SSH_URL must be set to the SSH URL that chartered-git can be reached by passing --build-arg to docker build.}"] 6 | WORKDIR /app 7 | COPY . /app 8 | RUN npm install -D && npm run build 9 | RUN echo '{"type": "module"}' > /app/build/package.json 10 | ENTRYPOINT [ "/bin/sh" ] 11 | 12 | FROM joseluisq/static-web-server 13 | ENV SERVER_LOG_LEVEL=info SERVER_ERROR_PAGE_404=./index.html SERVER_ROOT=. SERVER_SECURITY_HEADERS=true 14 | WORKDIR /app 15 | COPY --from=builder /app/build . 16 | -------------------------------------------------------------------------------- /chartered-frontend/README.md: -------------------------------------------------------------------------------- 1 | # chartered-frontend 2 | 3 | Everything Chartered frontend lives here, this is what users will typically 4 | interact with on a day-to-day basis on the web. 5 | 6 | ## Developing 7 | 8 | Once you've installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 9 | 10 | ```bash 11 | npm run dev 12 | 13 | # or start the server and open the app in a new browser tab 14 | npm run dev -- --open 15 | ``` 16 | 17 | ## Building 18 | 19 | To create a production version of chartered-frontend: 20 | 21 | ```bash 22 | docker build . --build-arg VITE_CHARTERED_WEB_URL=http://127.0.0.1:3000 23 | ``` 24 | -------------------------------------------------------------------------------- /chartered-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chartered-frontend-svelte", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "test": "playwright test", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 12 | "lint": "prettier --check . && eslint . && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 13 | "format": "prettier --write ." 14 | }, 15 | "devDependencies": { 16 | "@playwright/test": "^1.25.0", 17 | "@sveltejs/adapter-static": "^1.0.0-next.42", 18 | "@sveltejs/kit": "next", 19 | "@tailwindcss/forms": "^0.5.3", 20 | "@tailwindcss/typography": "^0.5.7", 21 | "@types/feather-icons": "^4.7.0", 22 | "@types/lodash": "^4.14.184", 23 | "@types/luxon": "^3.0.1", 24 | "@typescript-eslint/eslint-plugin": "^5.27.0", 25 | "@typescript-eslint/parser": "^5.27.0", 26 | "autoprefixer": "^10.4.8", 27 | "eslint": "^8.16.0", 28 | "eslint-config-prettier": "^8.3.0", 29 | "eslint-plugin-svelte3": "^4.0.0", 30 | "feather-icons": "^4.29.0", 31 | "luxon": "^3.0.3", 32 | "postcss": "^8.4.16", 33 | "prettier": "^2.6.2", 34 | "prettier-plugin-svelte": "^2.7.0", 35 | "svelte": "^3.44.0", 36 | "svelte-check": "^2.7.1", 37 | "svelte-preprocess": "^4.10.6", 38 | "tailwindcss": "^3.1.8", 39 | "tslib": "^2.3.1", 40 | "typescript": "^4.7.4", 41 | "vite": "^3.1.0-beta.1" 42 | }, 43 | "type": "module", 44 | "dependencies": { 45 | "lodash": "^4.17.21", 46 | "svelte-heatmap": "^1.0.2", 47 | "svelte-markdown": "^0.2.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /chartered-frontend/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173, 7 | }, 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /chartered-frontend/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /chartered-frontend/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | // and what to do when importing types 4 | declare namespace App { 5 | // interface Locals {} 6 | interface PageData { 7 | title?: string; 8 | } 9 | // interface Platform {} 10 | } 11 | -------------------------------------------------------------------------------- /chartered-frontend/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /chartered-frontend/src/app.pcss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | @apply bg-blue-100 dark:bg-slate-800 text-black dark:text-slate-400; 7 | } 8 | 9 | .heatmap svg { 10 | @apply overflow-visible mt-2; 11 | } 12 | 13 | @layer components { 14 | .card { 15 | @apply block p-6 bg-white rounded-lg border border-gray-200 dark:border-gray-700 shadow-md dark:bg-transparent; 16 | } 17 | 18 | .card-hr { 19 | @apply border-gray-200 dark:border-gray-700; 20 | } 21 | 22 | .card-divide { 23 | @apply divide-gray-200 dark:divide-gray-700; 24 | } 25 | 26 | a.card, 27 | button.card { 28 | @apply hover:bg-gray-100 dark:hover:bg-gray-700; 29 | } 30 | 31 | .card-header { 32 | @apply mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white; 33 | } 34 | 35 | .card-body { 36 | @apply font-normal text-gray-700 dark:text-gray-400; 37 | } 38 | 39 | header { 40 | @apply dark:bg-gradient-to-b dark:from-gray-900 dark:to-slate-800 text-black dark:text-inherit; 41 | } 42 | 43 | .btn-green { 44 | @apply w-fit mt-3 text-white border border-green-700 bg-green-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 focus:outline-none dark:focus:ring-blue-800 transition duration-150 ease-out; 45 | } 46 | 47 | .btn-red { 48 | @apply w-fit mt-3 text-white border border-red-700 bg-red-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 focus:outline-none dark:focus:ring-blue-800 transition duration-150 ease-out; 49 | } 50 | 51 | .btn-blue { 52 | @apply w-fit mt-3 text-white border border-blue-700 bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 focus:outline-none dark:focus:ring-blue-800 transition duration-150 ease-out; 53 | } 54 | 55 | .btn-blue-outline { 56 | @apply w-fit mt-3 text-blue-700 dark:text-inherit hover:text-white border border-blue-700 hover:bg-blue-700 dark:bg-transparent focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:border-blue-600 dark:hover:bg-blue-600 focus:outline-none dark:focus:ring-blue-800 transition duration-150 ease-out; 57 | } 58 | 59 | .btn-skeleton-outline { 60 | @apply w-fit mt-3 border border-slate-700 bg-transparent font-medium rounded-lg text-sm px-5 py-2.5 pointer-events-none; 61 | } 62 | } 63 | 64 | .text-highlight { 65 | @apply text-blue-700 dark:text-blue-600; 66 | } 67 | 68 | .skeleton { 69 | @apply h-2 bg-slate-700 rounded animate-pulse; 70 | } 71 | 72 | .skeleton-highlight { 73 | @apply h-2 bg-blue-700 rounded animate-pulse; 74 | } 75 | -------------------------------------------------------------------------------- /chartered-frontend/src/components/ErrorAlert.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 32 | -------------------------------------------------------------------------------- /chartered-frontend/src/components/FeaturedCrate.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | {#if crate} 12 | 13 |
14 |
15 | {crate.organisation}/{crate.name} 16 |
17 | 18 |
19 | 20 |
21 |
22 | 23 | 24 |
25 | {:else} 26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 | {/if} 39 | -------------------------------------------------------------------------------- /chartered-frontend/src/components/Heatmap.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 |
25 | 36 |
37 | 38 | 39 |
43 | 54 |
55 |
56 | 57 | 62 | -------------------------------------------------------------------------------- /chartered-frontend/src/components/Icon.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 | {#if icon} 44 | 45 | 46 | {@html icon.contents} 47 | 48 | 49 | {/if} 50 | 51 | 59 | -------------------------------------------------------------------------------- /chartered-frontend/src/components/NavItem.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 |
  • 29 | {#if active} 30 | 35 | 36 | 37 | {:else} 38 | 42 | 43 | 44 | {/if} 45 |
  • 46 | -------------------------------------------------------------------------------- /chartered-frontend/src/components/RelativeTime.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /chartered-frontend/src/components/Spinner.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 | 15 | 16 | 21 | 22 |
    23 | -------------------------------------------------------------------------------- /chartered-frontend/src/img/rust.svg: -------------------------------------------------------------------------------- 1 | 2 | 61 | 62 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/+layout.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | {#if $auth} 16 | 21 | 22 | 23 | {/if} 24 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/crates/[organisation]/+page.ts: -------------------------------------------------------------------------------- 1 | interface Params { 2 | organisation: string; 3 | } 4 | 5 | export function load({ params }: { params: Params }): App.PageData { 6 | return { 7 | title: params.organisation, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/crates/[organisation]/AddMember.svelte: -------------------------------------------------------------------------------- 1 | 77 | 78 |
    79 | { 82 | searchResults = []; 83 | onInput(e); 84 | }} 85 | type="text" 86 | class="border border-gray-200 dark:border-gray-700 bg-transparent text-sm rounded-lg block p-2.5 ring-blue-500 w-[100%] lg:w-[25%] focus:border-blue-500 mr-1" 87 | /> 88 | 89 | {#if loading} 90 |
    91 | 92 |
    93 | {/if} 94 |
    95 | 96 | {#if searchResults.length !== 0} 97 |
    98 | {#each searchResults as result} 99 | {#if !hideUuids.includes(result.user_uuid)} 100 | 117 | {/if} 118 | {/each} 119 |
    120 | {/if} 121 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/+page.ts: -------------------------------------------------------------------------------- 1 | interface Params { 2 | organisation: string; 3 | crate: string; 4 | } 5 | 6 | export function load({ params }: { params: Params }): App.PageData { 7 | return { 8 | title: `${params.organisation}/${params.crate}`, 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/Dependency.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 |
    28 | {#if dependency.registry === 'https://github.com/rust-lang/crates.io-index'} 29 | 30 | {dependency.name} 31 | 32 | {:else if dependency.registry?.indexOf('ssh://') === 0} 33 | 34 | {dependency.name} 35 | 36 | {:else} 37 | {dependency.name} 38 | {/if} 39 | = "{dependency.req}" 40 |
    41 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/DependencyDefinition.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | [dependencies] 16 | {$page.params.crate} = {`{ version = "${version}", registry = "${$page.params.organisation}" }`} 17 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/MemberTab.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 | {#await membersPromise then members} 29 |
    30 | {#each members.members as member} 31 | 39 | {/each} 40 | 41 | {#if newMember} 42 | 51 | {/if} 52 | 53 |
    54 | v.uuid)} 56 | on:new={(member) => { 57 | member.detail.permissions = []; 58 | member.detail.uuid = member.detail.user_uuid; 59 | newMember = member.detail; 60 | }} 61 | /> 62 |
    63 |
    64 | {/await} 65 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/RegistryDefinition.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | [registries] 9 | {$page.params.organisation} = {`{ index = "${indexUri}" }`} 10 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/VersionTab.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 |
    29 |
    {version.vers}
    30 | 31 |
    32 |
    33 | By 34 | 35 | {#if version.uploader.picture_url} 36 | {version.uploader.display_name} 41 | {:else} 42 |
    43 | 44 |
    45 | {/if} 46 | 47 | 51 | {version.uploader.display_name} 52 | 53 |
    54 | 55 |
    56 | 57 | 58 |
    59 | 60 |
    61 | 62 | {humanFileSize(version.size)} 63 |
    64 | 65 |
    66 | 67 | {Object.keys(version.features).length} features 68 |
    69 |
    70 |
    71 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/organisations/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/organisations/create/+page.ts: -------------------------------------------------------------------------------- 1 | export function load(): App.PageData { 2 | return { 3 | title: 'Create Organisation', 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/organisations/list/+page.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
    12 |
    13 |
    14 |

    15 | Your Organisations. 16 |

    17 |

    18 | Organisations and permissions are the heart of Chartered. All crates belong to an Organisation and to 19 | download a crate a user must have the VISIBLE permission for it. 20 |

    21 | 22 | Learn More 23 |
    24 |
    25 |
    26 | 27 |
    28 | {#await organisationsPromise} 29 |
    30 | {#each [1, 2, 3] as _} 31 |
    32 |
    33 |
    34 |
    35 |
    36 | 37 |
    38 |
    39 |
    40 |
    41 |
    42 |
    43 |
    44 |
    45 |
    46 | 47 |
    48 | Placeholder 49 |
    50 |
    51 | {/each} 52 |
    53 | {:then organisations} 54 | 84 | 85 | {#if organisations.organisations.length === 0} 86 |
    87 | You currently belong to no organisations, please create one or ask someone to add you to an existing 88 | one. 89 |
    90 | {/if} 91 | {:catch e} 92 | {e} 93 | {/await} 94 | 95 | + Create 96 |
    97 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/organisations/list/+page.ts: -------------------------------------------------------------------------------- 1 | export function load(): App.PageData { 2 | return { 3 | title: 'Organisations', 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/search/+page.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 |
    15 |
    16 |

    17 | Search results for "{$page.url.searchParams.get('q')}". 18 |

    19 |
    20 |
    21 |
    22 | 23 | {#await searchPromise} 24 |
    25 | 26 |
    27 | {:then results} 28 |
    29 | {#each results.crates as crate} 30 |
    31 |
    32 | 33 | {crate.organisation}/{crate.name} 34 | 35 | 36 | {crate.version} 37 |
    38 | 39 |
    40 | {crate.description} 41 |
    42 | 43 |
    44 | {#if crate.homepage} 45 | Homepage 46 | {/if} 47 | 48 | {#if crate.repository} 49 | Repository 50 | {/if} 51 |
    52 |
    53 | {/each} 54 |
    55 | {/await} 56 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/search/+page.ts: -------------------------------------------------------------------------------- 1 | export function load(): App.PageData { 2 | return { 3 | title: 'Search', 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/sessions/list/+page.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 |
    30 |
    31 |
    32 |

    Active Sessions

    33 |
    34 |
    35 |
    36 | 37 |
    38 |
    39 | {#await sessionPromise} 40 |
    41 | 42 |
    43 | {:then sessions} 44 |
    45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | {#each sessions.sessions as session} 58 | 59 | 60 | 61 | 62 | 69 | 74 | 75 | {/each} 76 | 77 |
    IP AddressUser AgentSSH Key FingerprintExpires 53 |
    {session.ip}{session.user_agent || 'n/a'}{session.ssh_key_fingerprint || 'n/a'} 63 | {#if session.expires_at} 64 | 65 | {:else} 66 | n/a 67 | {/if} 68 | 70 | 73 |
    78 |
    79 | {/await} 80 |
    81 |
    82 | 83 | {#if deleting} 84 | (deleting = null)} /> 85 | {/if} 86 | 87 | 105 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/sessions/list/+page.ts: -------------------------------------------------------------------------------- 1 | export function load(): App.PageData { 2 | return { 3 | title: 'Sessions', 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/ssh-keys/+page.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 |
    33 |
    34 |
    35 |

    36 | Manage your SSH Keys. 37 |

    38 |

    SSH keys are how Chartered identifies your account when called via Cargo.

    39 | 40 | Learn More 41 |
    42 |
    43 |
    44 | 45 |
    46 | {#await sshKeysPromise} 47 |
    48 |
    49 |
    50 | 51 |
    52 |
    53 |
    54 | {:then sshKeys} 55 |
    56 |
    57 |
    58 | {#each sshKeys.keys as sshKey, i} 59 | {#if i > 0}
    {/if} 60 | 61 | (deleting = sshKey)} /> 62 | {/each} 63 |
    64 |
    65 |
    66 | {:catch e} 67 | {e} 68 | {/await} 69 | 70 | 71 |
    72 | 73 | {#if deleting} 74 | (deleting = null)} /> 75 | {/if} 76 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/ssh-keys/+page.ts: -------------------------------------------------------------------------------- 1 | export function load(): App.PageData { 2 | return { 3 | title: 'SSH Keys', 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /chartered-frontend/src/routes/(authed)/ssh-keys/CreateKeyForm.svelte: -------------------------------------------------------------------------------- 1 | 71 | 72 | {#if error} 73 | (error = null)}>{error} 74 | {/if} 75 | 76 |
    77 |
    78 |
    79 | 80 |