├── .github └── workflows │ └── enforce-license-compliance.yml ├── .gitignore ├── LICENSE.md ├── config └── codecov.yml ├── docker-compose.yml ├── readme.md └── scripts ├── bump-version.sh └── license.py /.github/workflows/enforce-license-compliance.yml: -------------------------------------------------------------------------------- 1 | name: Enforce License Compliance 2 | 3 | on: 4 | pull_request: 5 | branches: [main, master] 6 | 7 | jobs: 8 | enforce-license-compliance: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: 'Enforce License Compliance' 12 | uses: getsentry/action-enforce-license-compliance@57ba820387a1a9315a46115ee276b2968da51f3d # main 13 | with: 14 | fossa_api_key: ${{ secrets.FOSSA_API_KEY }} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | tmp 3 | .env -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Functional Source License, Version 1.1, Apache 2.0 Future License 2 | 3 | ## Abbreviation 4 | 5 | FSL-1.1-Apache-2.0 6 | 7 | ## Notice 8 | 9 | Copyright 2023-2024 Functional Software, Inc. dba Sentry 10 | 11 | ## Terms and Conditions 12 | 13 | ### Licensor ("We") 14 | 15 | The party offering the Software under these Terms and Conditions. 16 | 17 | ### The Software 18 | 19 | The "Software" is each version of the software that we make available under 20 | these Terms and Conditions, as indicated by our inclusion of these Terms and 21 | Conditions with the Software. 22 | 23 | ### License Grant 24 | 25 | Subject to your compliance with this License Grant and the Patents, 26 | Redistribution and Trademark clauses below, we hereby grant you the right to 27 | use, copy, modify, create derivative works, publicly perform, publicly display 28 | and redistribute the Software for any Permitted Purpose identified below. 29 | 30 | ### Permitted Purpose 31 | 32 | A Permitted Purpose is any purpose other than a Competing Use. A Competing Use 33 | means making the Software available to others in a commercial product or 34 | service that: 35 | 36 | 1. substitutes for the Software; 37 | 38 | 2. substitutes for any other product or service we offer using the Software 39 | that exists as of the date we make the Software available; or 40 | 41 | 3. offers the same or substantially similar functionality as the Software. 42 | 43 | Permitted Purposes specifically include using the Software: 44 | 45 | 1. for your internal use and access; 46 | 47 | 2. for non-commercial education; 48 | 49 | 3. for non-commercial research; and 50 | 51 | 4. in connection with professional services that you provide to a licensee 52 | using the Software in accordance with these Terms and Conditions. 53 | 54 | ### Patents 55 | 56 | To the extent your use for a Permitted Purpose would necessarily infringe our 57 | patents, the license grant above includes a license under our patents. If you 58 | make a claim against any party that the Software infringes or contributes to 59 | the infringement of any patent, then your patent license to the Software ends 60 | immediately. 61 | 62 | ### Redistribution 63 | 64 | The Terms and Conditions apply to all copies, modifications and derivatives of 65 | the Software. 66 | 67 | If you redistribute any copies, modifications or derivatives of the Software, 68 | you must include a copy of or a link to these Terms and Conditions and not 69 | remove any copyright notices provided in or with the Software. 70 | 71 | ### Disclaimer 72 | 73 | THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR 74 | IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR 75 | PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT. 76 | 77 | IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE 78 | SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, 79 | EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE. 80 | 81 | ### Trademarks 82 | 83 | Except for displaying the License Details and identifying us as the origin of 84 | the Software, you have no right under these Terms and Conditions to use our 85 | trademarks, trade names, service marks or product names. 86 | 87 | ## Grant of Future License 88 | 89 | We hereby irrevocably grant you an additional license to use the Software under 90 | the Apache License, Version 2.0 that is effective on the second anniversary of 91 | the date we make the Software available. On or after that date, you may use the 92 | Software under the Apache License, Version 2.0, in which case the following 93 | will apply: 94 | 95 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 96 | this file except in compliance with the License. 97 | 98 | You may obtain a copy of the License at 99 | 100 | http://www.apache.org/licenses/LICENSE-2.0 101 | 102 | Unless required by applicable law or agreed to in writing, software distributed 103 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 104 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 105 | specific language governing permissions and limitations under the License. 106 | -------------------------------------------------------------------------------- /config/codecov.yml: -------------------------------------------------------------------------------- 1 | setup: 2 | # Replace with the http location of your Codecov 3 | # https://docs.codecov.io/docs/configuration#section-codecov-url 4 | codecov_url: http://localhost:8080 5 | #codecov_api_url: # this defaults to and is designed to work out of the box like this 6 | #api_allowed_hosts: [] # this defaults to and is designed to work out of the box like this 7 | # Replace with your Codecov Enterprise License key. This is required for the containers to function. 8 | # https://docs.codecov.io/docs/configuration#section-enterprise-license 9 | enterprise_license: "F5O0Fu5ASFTPtWXM51BK8YQlq7IM2s+8TBGULrf9Um7wHjfPwI+Z3E4PfF/dPs6Uc5A+MLti+2etHq5dnFEfZgoiIVCLZ8x+0BVmUSWwPS42vJXnf1veY9Bglang4mDIhmfWfp5l6AT6cxmAVFpGrwobiK6OcN9pjWx4iWabazmsOiF9LM++v0WtuHNvhgzRcKmnJPgqahEB7qqF6KQ1hg==" 10 | # https://docs.codecov.com/docs/configuration#instance-wide-admins 11 | admins: 12 | - service: github 13 | username: "" 14 | # Replace with a random string 15 | # https://docs.codecov.io/docs/configuration#section-cookie-secret 16 | http: 17 | cookie_secret: "" 18 | cookies_domain: localhost 19 | timeseries: 20 | enabled: true 21 | github: 22 | client_id: "" 23 | client_secret: "" 24 | global_upload_token: "" 25 | services: 26 | redis_url: "redis://redis:6379" 27 | database_url: "postgres://postgres:testpassword@postgres:5432/postgres" 28 | timeseries_database_url: "postgres://postgres:testpassword@timescale:5432/postgres" 29 | minio: 30 | host: minio 31 | port: 9000 32 | auto_create_bucket: true 33 | # If using external storage. Comment above and uncomment below 34 | # host: s3.amazonaws.com or storage.googleapis.com if using GCS 35 | # bucket: 36 | # region: 37 | # verify_ssl: true 38 | # port: 443 39 | # access_key_id: # or if using GCS 40 | # secret_access_key: # or if using GCS 41 | # iam_auth: # set to true in AWS to attempt to authenticate via Instance role 42 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | gateway: 5 | image: codecov/self-hosted-gateway:latest-calver #note: this is for setup purposes only, be sure to pin to the latest release from our changelog: https://docs.codecov.io/changelog 6 | volumes: 7 | - ./config:/config 8 | #- ${CODECOV_SSL_CERT-/cert/codecov.crt}:/etc/codecov/ssl/certs/cert.crt:ro # uncomment if using ssl 9 | ports: 10 | - "${CODECOV_PORT-8080}:8080" 11 | #- "${CODECOV_SSL_PORT-8443}:8443" # uncomment if using ssl 12 | environment: 13 | - CODECOV_GATEWAY_MINIO_ENABLED=true 14 | #- CODECOV_GATEWAY_SSL_ENABLED=true # uncomment if using ssl 15 | networks: 16 | - codecov 17 | depends_on: 18 | - api 19 | - frontend 20 | 21 | frontend: 22 | image: codecov/self-hosted-frontend:latest-calver #note: this is for setup purposes only, be sure to pin to the latest release from our changelog: https://docs.codecov.io/changelog 23 | environment: 24 | - CODECOV_BASE_HOST=localhost:8080 25 | - CODECOV_API_HOST=localhost:8080 26 | - CODECOV_IA_HOST=localhost:8080 27 | - CODECOV_SCHEME=http 28 | volumes: 29 | - ./config:/config 30 | ports: 31 | - "8080" 32 | networks: 33 | - codecov 34 | 35 | api: 36 | image: codecov/self-hosted-api:latest-calver #note: this is for setup purposes only, be sure to pin to the latest release from our changelog: https://docs.codecov.io/changelog 37 | volumes: 38 | - ./config:/config 39 | networks: 40 | - codecov 41 | depends_on: 42 | - minio 43 | - timescale 44 | - postgres 45 | - redis 46 | 47 | worker: 48 | image: codecov/self-hosted-worker:latest-calver #note: this is for setup purposes only, be sure to pin to the latest release from our changelog: https://docs.codecov.io/changelog 49 | environment: 50 | - RUN_ENV=ENTERPRISE 51 | volumes: 52 | - ./config:/config 53 | - archive-volume:/archive 54 | networks: 55 | - codecov 56 | depends_on: 57 | - minio 58 | - redis 59 | - postgres 60 | - timescale 61 | 62 | redis: 63 | image: redis:6.2-alpine 64 | volumes: 65 | - redis-volume:/data 66 | networks: 67 | - codecov 68 | 69 | postgres: 70 | image: postgres:14-alpine 71 | environment: 72 | - POSTGRES_PASSWORD=testpassword 73 | - POSTGRES_USER=postgres 74 | - POSTGRES_DB=postgres 75 | volumes: 76 | - postgres-volume:/var/lib/postgresql/data 77 | networks: 78 | - codecov 79 | 80 | timescale: 81 | image: timescale/timescaledb:latest-pg14 82 | environment: 83 | - POSTGRES_PASSWORD=testpassword 84 | - POSTGRES_USER=postgres 85 | - POSTGRES_DB=postgres 86 | volumes: 87 | - timescale-volume:/var/lib/postgresql/data 88 | networks: 89 | - codecov 90 | 91 | minio: 92 | image: minio/minio:RELEASE.2020-04-15T00-39-01Z 93 | command: server /export 94 | ports: 95 | - "${CODECOV_MINIO_PORT-9000}:9000" 96 | environment: 97 | - MINIO_ACCESS_KEY=codecov-default-key 98 | - MINIO_SECRET_KEY=codecov-default-secret 99 | volumes: 100 | - archive-volume:/export 101 | networks: 102 | - codecov 103 | 104 | volumes: 105 | postgres-volume: 106 | timescale-volume: 107 | redis-volume: 108 | archive-volume: 109 | 110 | networks: 111 | codecov: 112 | driver: bridge 113 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Self-Hosted Codecov 2 | > Code coverage done right.® Empower your developers with Codecov to improve code quality. 3 | 4 | > We believe that everyone should have access to quality software (like Sentry), that’s why we have always offered Codecov for free to open source maintainers. 5 | > 6 | > By open sourcing Codecov, we’re not only joining the community that’s supported us from the start — but also want to make sure that every developer can contribute to and build on the Codecov experience. 7 | 8 | Testing software is crucial for deploying healthy code. Codecov provides metrics and insight in the results of tests through code coverage reports. Coverage reports are used to determine what lines of code were tested and what lines were missed entirely. These reports are uploaded to Codecov to be analyzed and stored historically. This information helps your developers save time tracking down bugs and commit stronger code that is well tested, to increase code coverage to 100%. 9 | 10 | ### Features 11 | 12 | Commit statuses and pull request comments are key to maintaining a healthy product. Codecov provides valuable metrics in the pull request feed and promotes healthy development. 13 | 14 | Our unique Coverage Diff® will describe the commit in relation to its impact on tests which significantly improves code reviews. 15 | 16 | ### Usage 17 | 18 | This repo is meant for a quick POC that can be ran locally. It is recommended to use Helm or Terraform to deploy Codecov in production. 19 | 20 | Codecov currently requires your team to use one or more of the following: GitHub, GitHub Enterprise, GitLab, Gitlab CE, GitLab Enterprise, Bitbucket and Bitbucket Server. You will need to configure one (or more) of these providers for your self-hosted install of Codecov to function correctly. 21 | 22 | We recommend using our [Self-Hosted Configuration Guide](https://docs.codecov.com/docs/configuration) to ensure that your self-hosted install is properly configured. 23 | 24 | ### Disclaimer 25 | 26 | As mentioned above, this is meant for a quick POC or example of how to run Codecov. This configuration is not hardened for security and any usage is at your own risk. Default database credentials are included in the docker compose config. At a minimum, this should be updated at a minimum before wider usage. The minio bucket is also exposed via the docker compose. This is needed for Codecov to function locally. While no secret data is stored in this bucket, it is still recommended to make this private and interface with storage via presigned urls. This necessitates using a [storage backend such as S3 or GCS](https://docs.codecov.com/v5.0/docs/archiving-reports#configuration-ii-minio-with-s3). 27 | 28 | ### License Generation 29 | 30 | Codecov self-hosted is based on Codecov's Enterprise On-Premises offering which is now deprecated. As a result, this software requires a license to run properly. **This is purely a technical requirement of the software at this time** and you will never be asked to purchase a license from Codecov or any other entity in order to use Codecov self-hosted. 31 | 32 | The installation comes with a general license with a 50 user seat limit. We chose 50 seats as we believe this is the maximum number of users a Docker Compose based PoC can reliably support out of the box, but your mileage may vary depending on how you plan to use Codecov. 33 | 34 | If you require more seats, a `license.py` script has been added that you can use from the command line to generate a license. From the scripts directory, run the following command: 35 | 36 | ``` 37 | python3 license.py new --expires=2030-12-25 --company=company-name --users=50 38 | ``` 39 | You can set `expires`, `company`, and `users` to whatever future date, name, and user count you wish respectively, but those arguments are required for the script to function properly. 40 | 41 | Note that this script requires pycryptodome to be installed on your system. You can install it with: 42 | 43 | ``` 44 | pip install pycryptodome 45 | ``` 46 | -------------------------------------------------------------------------------- /scripts/bump-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | NEW_VERSION="$1" 5 | 6 | sed -i -e "s/^\# Self-Hosted Codecov .*/# Self-Hosted Codecov $NEW_VERSION/" README.md 7 | 8 | echo "New version: $NEW_VERSION" 9 | -------------------------------------------------------------------------------- /scripts/license.py: -------------------------------------------------------------------------------- 1 | """ 2 | Note: Script requires use of pycryptodome. pip install pycryptodome 3 | """ 4 | from json import dumps, loads 5 | from Crypto import Random 6 | from base64 import b64encode, b64decode 7 | from Crypto.Cipher import AES 8 | from datetime import datetime 9 | import argparse 10 | 11 | KEY = bytes([0xfb, 0xe9, 0x1b, 0x34, 0x60, 0xff, 0xe2, 0xa1, 0xfa, 0xe3, 0xd0, 0xf9, 0x8d, 0xa6, 0x25, 0x7f]) 12 | BS = 16 13 | 14 | 15 | def pad(s): 16 | padding_length = BS - len(s) % BS 17 | padding = bytes([padding_length] * padding_length) 18 | return s + padding 19 | 20 | def unpad(s): 21 | return s[:-s[-1]] 22 | 23 | 24 | def create_license(expires, company, seats=None, repos=None, url=None, trial=True, pr=True): 25 | date = expires 26 | datetime.strptime(date, '%Y-%m-%d') 27 | users = seats 28 | if users: 29 | users = int(users) 30 | if repos: 31 | repos = int(repos) 32 | res = { 33 | 'company': company, 34 | 'expires': f'{date} 00:00:00', 35 | 'url': url, 36 | 'trial': trial, 37 | 'users': users, 38 | 'repos': repos, 39 | 'pr_billing': pr 40 | } 41 | iv = Random.new().read(AES.block_size) 42 | des = AES.new(KEY, AES.MODE_CBC, iv) 43 | cipher_text = b64encode(iv + des.encrypt(pad(dumps(res).encode('utf-8')))).decode('utf-8') 44 | print(f'''{cipher_text} 45 | 46 | License expires on {date} 47 | 48 | ---- Internal purposes only ---- 49 | {res} 50 | ''') 51 | 52 | 53 | def decode_license(license): 54 | license = b64decode(license) 55 | iv = license[:16] 56 | cipher = AES.new(KEY, AES.MODE_CBC, iv) 57 | print(loads(unpad(cipher.decrypt(license[16:])))) 58 | 59 | 60 | def main(args=None): 61 | expires = None 62 | company = None 63 | seats = None 64 | repos = None 65 | license = None 66 | url = None 67 | trial = True 68 | 69 | parser = argparse.ArgumentParser(prog='codecov key gen', add_help=True, 70 | formatter_class=argparse.RawDescriptionHelpFormatter) 71 | subparsers = parser.add_subparsers(title='Commands') 72 | 73 | make = subparsers.add_parser('new') 74 | make.add_argument('--expires', action="store", required=True, 75 | help="expires in YYYY-MM-DD") 76 | make.add_argument('--trial', action="store_true", 77 | default=False, help="set to true if trial is needed") 78 | make.add_argument('--pr', action="store_true", 79 | default=False, help="set to true if this license requires PR Author Billing") 80 | make.add_argument('--company', action="store", required=True, help="Company name") 81 | make.add_argument('--url', action="store", help="(Optional) Company url endpoint") 82 | make.add_argument('--users', action="store", dest="seats", nargs="?", 83 | default=None, help="(Optional) Number of seats") 84 | make.add_argument('--repos', action="store", dest="repos", nargs="?", 85 | default=None, help="(Optional) Number of repos") 86 | 87 | check = subparsers.add_parser('check') 88 | check.add_argument('--license', nargs=1, required=True) 89 | 90 | pref = parser.parse_args(args) 91 | 92 | if getattr(pref, 'license', None): 93 | decode_license(pref.license[0]) 94 | else: 95 | create_license(pref.expires, pref.company, seats=pref.seats, 96 | repos=pref.repos, url=pref.url, trial=pref.trial, pr=pref.pr) 97 | 98 | 99 | if __name__ == "__main__": 100 | main() --------------------------------------------------------------------------------