Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c73f96525a | ||
|
|
510df192d8 | ||
|
|
2e7620f1eb | ||
|
|
93d4ee5241 | ||
|
|
1a7594be93 | ||
|
|
f464a7ee67 | ||
|
|
a1cd3d4609 | ||
|
|
358fb234a7 | ||
|
|
94fed58de3 | ||
|
|
49f452d9f2 | ||
|
|
40d07fe159 | ||
|
|
e150e6bede | ||
|
|
87a411f42d | ||
|
|
88f25b6743 | ||
|
|
bb0dc2e90f | ||
|
|
4a8939796e | ||
|
|
ba795ccd5c | ||
|
|
c9b5a677d3 | ||
|
|
affcb893fa | ||
|
|
e030807acb | ||
|
|
4ced1365d4 | ||
|
|
dcdbb7cd45 | ||
|
|
b3102f5f32 | ||
|
|
2a61cf1b51 | ||
|
|
0d7c9c4c33 | ||
|
|
6649aeaaf0 | ||
|
|
cb11dc0c4e | ||
|
|
4a4f2645df | ||
|
|
81a836df53 | ||
|
|
919ca52ca1 | ||
|
|
f7ecb88659 | ||
|
|
1a0dcf7579 | ||
|
|
0f3037b315 | ||
|
|
b234ef2917 | ||
|
|
1b344fb90c | ||
|
|
d164306f8b | ||
|
|
c61d23caf1 | ||
|
|
fcfbbe8809 | ||
|
|
1bb4aac32f | ||
|
|
d895eac14d | ||
|
|
5cce890ff6 | ||
|
|
387c1e375d |
4
.github/release-drafter.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
template: |
|
||||
## What’s Changed
|
||||
|
||||
$CHANGES
|
||||
14
.github/workflows/draft.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v5.11.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
3
.gitignore
vendored
@@ -3,6 +3,9 @@ __pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# JavaScript packages
|
||||
node_modules/
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
|
||||
14
Dockerfile
@@ -3,20 +3,22 @@ FROM python:3-alpine
|
||||
# Getting things ready
|
||||
WORKDIR /usr/src/shynet
|
||||
COPY Pipfile.lock Pipfile ./
|
||||
COPY package.json package-lock.json ../
|
||||
# Django expects node_modules to be in its parent directory.
|
||||
|
||||
# Install dependencies & configure machine
|
||||
ARG GF_UID="500"
|
||||
ARG GF_GID="500"
|
||||
RUN apk update && \
|
||||
apk add gettext curl bash && \
|
||||
# URL from https://github.com/shlinkio/shlink/issues/596 :)
|
||||
curl "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&license_key=G4Lm0C60yJsnkdPi&suffix=tar.gz" | tar -xvz -C /tmp && \
|
||||
curl "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=G4Lm0C60yJsnkdPi&suffix=tar.gz" | tar -xvz -C /tmp && \
|
||||
apk add gettext curl bash npm && \
|
||||
curl -m 180 "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&license_key=kKG1ebhL3iWVd0iv&suffix=tar.gz" | tar -xvz -C /tmp && \
|
||||
curl -m 180 "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=kKG1ebhL3iWVd0iv&suffix=tar.gz" | tar -xvz -C /tmp && \
|
||||
mv /tmp/GeoLite2*/*.mmdb /etc && \
|
||||
apk del curl && \
|
||||
apk add --no-cache postgresql-libs && \
|
||||
apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev && \
|
||||
pip install pipenv && \
|
||||
npm i -P --prefix .. && \
|
||||
pip install pipenv~=2020.6.2 && \
|
||||
pipenv install --system --deploy && \
|
||||
apk --purge del .build-deps && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
@@ -32,4 +34,4 @@ RUN python manage.py collectstatic --noinput && \
|
||||
# Launch
|
||||
USER appuser
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT [ "./entrypoint.sh" ]
|
||||
CMD [ "./entrypoint.sh" ]
|
||||
|
||||
38
GUIDE.md
@@ -3,6 +3,7 @@
|
||||
## Table of Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Heroku](#heroku)
|
||||
- [Updating Your Configuration](#updating-your-configuration)
|
||||
- [Enhancements](#enhancements)
|
||||
* [Installation with SSL](#installation-with-ssl)
|
||||
@@ -10,9 +11,11 @@
|
||||
+ [Cloudflare](#cloudflare)
|
||||
+ [Nginx](#nginx)
|
||||
+ [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Staying Updated
|
||||
**If you install Shynet, you should strongly consider enabling notifications when new versions are released.** You can do this under the "Watch" tab on GitHub (above). This will ensure that you are notified when new versions are available, some of which may be security updates. (Shynet will never automatically update itself.)
|
||||
|
||||
## Installation
|
||||
|
||||
Installation of Shynet is easy! Follow the [Basic Installation](#basic-installation) guide below if you'd like to run Shynet over HTTP or if you are going to be running it over HTTPS through a reverse proxy. If you'd like to run Shynet over HTTPS without a reverse proxy, skip ahead to [Installation with SSL](#installation-with-ssl) instead.
|
||||
@@ -29,29 +32,36 @@ Before continuing, please be sure to have the latest version of Docker installed
|
||||
|
||||
3. Configure an environment file for Shynet, using [this file](/TEMPLATE.env) as a template. (This file is typically named `.env`.) Make sure you set the database settings, or Shynet won't be able to run.
|
||||
|
||||
4. Launch the Shynet server by running `docker run --env-file=<your env file> milesmcc/shynet:latest`. Watch the output of the script; if it's the first run, you'll see a temporary password printed that you can use to log in. You may need to bind Docker's port 8080 (where Shynet runs) to your local port 80 (http); this can be done using the flag `-p 80:8080` after `run`.
|
||||
4. Launch the Shynet server for the first time by running `docker run --env-file=<your env file> milesmcc/shynet:latest`. Provided you're using the default environment information (i.e., `PERFORM_CHECKS_AND_SETUP` is `True`), you'll see a few warnings about not having an admin user or host setup; these are normal. Don't worry — we'll do this in the next step. You only need to stop if you see a stacktrace about being unable to connect to the database.
|
||||
|
||||
5. Visit your service's homepage, and verify everything looks right! You should see a login prompt. Log in with the credentials from step 4. You'll probably be prompted to "confirm your email"—if you haven't set up an email server, the confirmation email will be printed to the console instead.
|
||||
5. Create an admin user by running `docker run --env-file=<your env file> milesmcc/shynet:latest ./manage.py registeradmin <your email>`. A temporary password will be printed to the console.
|
||||
|
||||
6. Create a service by clicking "+ Create Service" in the top right hand corner. Fill out the options as appropriate. Once you're done, press "create" and you'll be redirected to your new service's analytics page.
|
||||
6. Set the hostname of your Shynet instance by running `docker run --env-file=<your env file> milesmcc/shynet:latest ./manage.py hostname <your public hostname>`, where `<your public hostname>` is the _publicly accessible hostname_ of your instance, including port. This setting affects the URL that the tracking script sends its results to, so make sure it's correct. (Example hostnames: `shynet.rmrm.io` or `example.com:8000`.)
|
||||
|
||||
7. Finally, click on "Manage" in the top right of the service's page to get the tracking script code. Inject this script on all pages you'd like the service to track.
|
||||
7. Set the whitelabel of your Shynet instance by running `docker run --env-file=<your env file> milesmcc/shynet:latest ./manage.py whitelabel <whitelabel>`. While this setting doesn't affect any core operations of Shynet, it lets you rename Shynet to whatever you want. (Example whitelabels: `"My Shynet Instance"` or `"Acme Analytics"`.)
|
||||
|
||||
## Updating Your Configuration
|
||||
8. Launch your webserver by running `docker run --env-file=<your env file> milesmcc/shynet:latest`. You may need to bind Docker's port 8080 (where Shynet runs) to your local port 80 (http); this can be done using the flag `-p 80:8080` after `run`. Visit your service's homepage, and verify everything looks right! You should see a login prompt. Log in with the credentials from step 5. You'll probably be prompted to "confirm your email"—if you haven't set up an email server, the confirmation email will be printed to the console instead.
|
||||
|
||||
When you first setup Shynet, you set a number of environment variables that determine first-run initialization settings (these variables start with `SHYNET_`). Once they're first set, though, changing them won't have any effect. Be sure to run the following commands in the same way that you deploy Shynet (i.e., linked to the same database).
|
||||
9. Create a service by clicking "+ Create Service" in the top right hand corner. Fill out the options as appropriate. Once you're done, press "create" and you'll be redirected to your new service's analytics page.
|
||||
|
||||
* Create an admin account by running `docker run --env-file=<your env file> milesmcc/shynet:latest python manage.py registeradmin <your email>`. The command will print a temporary password that you'll be able to use to log in.
|
||||
10. Finally, click on "Manage" in the top right of the service's page to get the tracking script code. Inject this script on all pages you'd like the service to track.
|
||||
|
||||
* Configure Shynet's hostname (e.g. `shynet.example.com` or `localhost:8000`) by running `docker run --env-file=<your env file> milesmcc/shynet:latest python manage.py hostname "<your hostname>"`. This doesn't affect Shynet's bind port; instead, it determines what hostname to inject into the tracking script. (So you'll want to use the "user-facing" hostname here.)
|
||||
## Heroku
|
||||
|
||||
* Name your Shynet instance by running `docker run --env-file=<your env file> milesmcc/shynet:latest python manage.py whitelabel "<your instance name>"`. This could be something like "My Shynet Server" or "Acme Analytics"—whatever suits you.
|
||||
You may wish to deploy Shynet on Heroku. Note that Heroku's free offerings (namely the free Postgres addon) are unlikely to support running any Shynet instance that records more than a few hundred requests per day — the database will quickly fill up. In most cases, the more cost-effective option for running Shynet is renting a VPS from a full cloud service provider. However, if you're sure Heroku is the right option for you, or you just want to try Shynet out, you can use the Quick Deploy button then follow the steps below.
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/milesmcc/shynet/tree/master)
|
||||
|
||||
Once you deploy, you'll need to setup an admin user, whitelabel, and hostname before you can use Shynet. Do that with the following commands:
|
||||
|
||||
1. `heroku run --app=<your app> ./manage.py registeradmin <your email>`
|
||||
2. `heroku run --app=<your app> ./manage.py hostname <the hostname where you will run Shynet>`
|
||||
3. `heroku run --app=<your app> ./manage.py whitelabel "<your Shynet instance's name>"`
|
||||
|
||||
---
|
||||
|
||||
## Enhancements
|
||||
|
||||
|
||||
### Installation with SSL
|
||||
|
||||
If you are going to be running Shynet through a reverse proxy, please see [Configuring a Reverse Proxy](#configuring-a-reverse-proxy) instead.
|
||||
@@ -184,4 +194,8 @@ Here are solutions for some common issues. If your situation isn't described her
|
||||
|
||||
#### I changed the `SHYNET_WHITELABEL`/`SHYNET_HOST` environment variable, but nothing happened!
|
||||
|
||||
* Those values only affect how your Shynet instance is setup on first run; once it's configured, they have no effect. See [updating your configuration](#updating-your-configuration) for help on how to update your configuration.
|
||||
* Those values only affect how your Shynet instance is setup on first run; once it's configured, they have no effect. See [updating your configuration](#updating-your-configuration) for help on how to update your configuration. (Note: these environment variables are not present in newer Shynet versions; they have been removed from the guide.)
|
||||
|
||||
#### Shynet can't connect to my database running on `localhost`/`127.0.0.1`
|
||||
|
||||
* The problem is likely that to Shynet, `localhost` points to the local network in the container itself, not on the host machine. Try adding the `--network='host'` option when you run Docker.
|
||||
48
Pipfile
@@ -3,28 +3,30 @@ name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
black = "*"
|
||||
|
||||
[packages]
|
||||
django = "*"
|
||||
django-allauth = "*"
|
||||
geoip2 = "*"
|
||||
whitenoise = "*"
|
||||
celery = "*"
|
||||
django-ipware = "*"
|
||||
pyyaml = "*"
|
||||
ua-parser = "*"
|
||||
user-agents = "*"
|
||||
emoji-country-flag = "*"
|
||||
rules = "*"
|
||||
gunicorn = "*"
|
||||
psycopg2-binary = "*"
|
||||
redis = "*"
|
||||
django-redis-cache = "*"
|
||||
pycountry = "*"
|
||||
ipaddress = "*"
|
||||
html2text = "*"
|
||||
|
||||
[pipenv]
|
||||
allow_prereleases = true
|
||||
|
||||
[packages]
|
||||
django = "~=3.0"
|
||||
django-allauth = "~=0.42.0"
|
||||
geoip2 = "~=3.0.0"
|
||||
whitenoise = "~=5.1.0"
|
||||
celery = "~=4.4.6"
|
||||
django-ipware = "~=2.1.0"
|
||||
pyyaml = "~=5.3.1"
|
||||
ua-parser = "~=0.10.0"
|
||||
user-agents = "~=2.1"
|
||||
emoji-country-flag = "~=1.2.1"
|
||||
rules = "~=2.2"
|
||||
gunicorn = "~=20.0.4"
|
||||
psycopg2-binary = "~=2.8.5"
|
||||
redis = "~=3.5.3"
|
||||
django-redis-cache = "~=2.1.1"
|
||||
pycountry = "~=19.8.18"
|
||||
ipaddress = "~=1.0.23"
|
||||
html2text = "~=2020.1.16"
|
||||
django-health-check = "~=3.12.1"
|
||||
django-npm = "~=1.0.0"
|
||||
|
||||
[dev-packages]
|
||||
black = "*"
|
||||
|
||||
149
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "e66668cad32f92f11324d92caa0e3e83afcb3cafe471f3ba1f178fc090dd7b6b"
|
||||
"sha256": "ff989ac3413a6bd2253c9350c8f368a91942393221f1e47e5e39e60e457cc590"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -16,17 +16,17 @@
|
||||
"default": {
|
||||
"amqp": {
|
||||
"hashes": [
|
||||
"sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8",
|
||||
"sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d"
|
||||
"sha256:24dbaff8ce4f30566bb88976b398e8c4e77637171af3af6f1b9650f48890e60b",
|
||||
"sha256:bb68f8d2bced8f93ccfd07d96c689b716b3227720add971be980accfc2952139"
|
||||
],
|
||||
"version": "==2.5.2"
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"asgiref": {
|
||||
"hashes": [
|
||||
"sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5",
|
||||
"sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"
|
||||
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
|
||||
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
|
||||
],
|
||||
"version": "==3.2.7"
|
||||
"version": "==3.2.10"
|
||||
},
|
||||
"billiard": {
|
||||
"hashes": [
|
||||
@@ -37,18 +37,18 @@
|
||||
},
|
||||
"celery": {
|
||||
"hashes": [
|
||||
"sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f",
|
||||
"sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a"
|
||||
"sha256:ef17d7dffde7fc73ecab3a3b6389d93d3213bac53fa7f28e68e33647ad50b916",
|
||||
"sha256:fd77e4248bb1b7af5f7922dd8e81156f540306e3a5c4b1c24167c1f5f06025da"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.4.2"
|
||||
"version": "==4.4.6"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
|
||||
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
|
||||
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
|
||||
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
|
||||
],
|
||||
"version": "==2020.4.5.1"
|
||||
"version": "==2020.6.20"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -66,18 +66,26 @@
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:051ba55d42daa3eeda3944a8e4df2bc96d4c62f94316dea217248a22563c3621",
|
||||
"sha256:9aaa6a09678e1b8f0d98a948c56482eac3e3dd2ddbfb8de70a868135ef3b5e01"
|
||||
"sha256:045be31d68dfed684831e39ab1d9e77a595f1a393935cb43b6c5451d2e78c8a4",
|
||||
"sha256:ccf6c208424c0e1b0eaffd36efe12618a9ab4d0037e26f6ffceaa5277af985d7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.6"
|
||||
"version": "==3.1b1"
|
||||
},
|
||||
"django-allauth": {
|
||||
"hashes": [
|
||||
"sha256:7ab91485b80d231da191d5c7999ba93170ef1bf14ab6487d886598a1ad03e1d8"
|
||||
"sha256:f17209410b7f87da0a84639fd79d3771b596a6d3fc1a8e48ce50dabc7f441d30"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.41.0"
|
||||
"version": "==0.42.0"
|
||||
},
|
||||
"django-health-check": {
|
||||
"hashes": [
|
||||
"sha256:0563827e003d25fd4d9ebbd7467dea5f390435628d645aaa63f8889deaded73a",
|
||||
"sha256:9e6b7d93d4902901474efd4e25d31b5aaea7563b570c0260adce52cd3c3a9e36"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.12.1"
|
||||
},
|
||||
"django-ipware": {
|
||||
"hashes": [
|
||||
@@ -86,6 +94,13 @@
|
||||
"index": "pypi",
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"django-npm": {
|
||||
"hashes": [
|
||||
"sha256:2e6bba65e728fa18b9db3c8dc0d4490b70cb7f43bacf60eb3654d7dcb6424272"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"django-redis-cache": {
|
||||
"hashes": [
|
||||
"sha256:06d4e48545243883f88dc9263dda6c8a0012cb7d0cee2d8758d8917eca92cece",
|
||||
@@ -102,6 +117,12 @@
|
||||
"index": "pypi",
|
||||
"version": "==1.2.1"
|
||||
},
|
||||
"future": {
|
||||
"hashes": [
|
||||
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
|
||||
],
|
||||
"version": "==0.18.2"
|
||||
},
|
||||
"geoip2": {
|
||||
"hashes": [
|
||||
"sha256:5869e987bc54c0d707264fec4710661332cc38d2dca5a7f9bb5362d0308e2ce0",
|
||||
@@ -128,10 +149,10 @@
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
||||
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"version": "==2.9"
|
||||
"version": "==2.10"
|
||||
},
|
||||
"ipaddress": {
|
||||
"hashes": [
|
||||
@@ -143,10 +164,10 @@
|
||||
},
|
||||
"kombu": {
|
||||
"hashes": [
|
||||
"sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76",
|
||||
"sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2"
|
||||
"sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a",
|
||||
"sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74"
|
||||
],
|
||||
"version": "==4.6.8"
|
||||
"version": "==4.6.11"
|
||||
},
|
||||
"maxminddb": {
|
||||
"hashes": [
|
||||
@@ -237,18 +258,18 @@
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
"sha256:174101a3ce04560d716616290bb40e0a2af45d5844c8bd474c23fc5c52e7a46a",
|
||||
"sha256:7378105cd8ea20c4edc49f028581e830c01ad5f00be851def0f4bc616a83cd89"
|
||||
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
|
||||
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.0"
|
||||
"version": "==3.5.3"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
|
||||
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
|
||||
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
||||
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
|
||||
],
|
||||
"version": "==2.23.0"
|
||||
"version": "==2.24.0"
|
||||
},
|
||||
"requests-oauthlib": {
|
||||
"hashes": [
|
||||
@@ -266,10 +287,10 @@
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"version": "==1.14.0"
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"sqlparse": {
|
||||
"hashes": [
|
||||
@@ -310,20 +331,20 @@
|
||||
},
|
||||
"whitenoise": {
|
||||
"hashes": [
|
||||
"sha256:0f9137f74bd95fa54329ace88d8dc695fbe895369a632e35f7a136e003e41d73",
|
||||
"sha256:62556265ec1011bd87113fb81b7516f52688887b7a010ee899ff1fd18fd22700"
|
||||
"sha256:60154b976a13901414a25b0273a841145f77eb34a141f9ae032a0ace3e4d5b27",
|
||||
"sha256:6dd26bfda3af29177d8ab7333a0c7b7642eb615ce83764f4d15a9aecda3201c4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.0.1"
|
||||
"version": "==5.1.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
||||
"sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
|
||||
"sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
|
||||
],
|
||||
"version": "==1.4.3"
|
||||
"version": "==1.4.4"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
@@ -356,36 +377,36 @@
|
||||
},
|
||||
"regex": {
|
||||
"hashes": [
|
||||
"sha256:021a0ae4d2baeeb60a3014805a2096cb329bd6d9f30669b7ad0da51a9cb73349",
|
||||
"sha256:04d6e948ef34d3eac133bedc0098364a9e635a7914f050edb61272d2ddae3608",
|
||||
"sha256:099568b372bda492be09c4f291b398475587d49937c659824f891182df728cdf",
|
||||
"sha256:0ff50843535593ee93acab662663cb2f52af8e31c3f525f630f1dc6156247938",
|
||||
"sha256:1b17bf37c2aefc4cac8436971fe6ee52542ae4225cfc7762017f7e97a63ca998",
|
||||
"sha256:1e2255ae938a36e9bd7db3b93618796d90c07e5f64dd6a6750c55f51f8b76918",
|
||||
"sha256:2bc6a17a7fa8afd33c02d51b6f417fc271538990297167f68a98cae1c9e5c945",
|
||||
"sha256:3ab5e41c4ed7cd4fa426c50add2892eb0f04ae4e73162155cd668257d02259dd",
|
||||
"sha256:3b059e2476b327b9794c792c855aa05531a3f3044737e455d283c7539bd7534d",
|
||||
"sha256:4df91094ced6f53e71f695c909d9bad1cca8761d96fd9f23db12245b5521136e",
|
||||
"sha256:5493a02c1882d2acaaf17be81a3b65408ff541c922bfd002535c5f148aa29f74",
|
||||
"sha256:5b741ecc3ad3e463d2ba32dce512b412c319993c1bb3d999be49e6092a769fb2",
|
||||
"sha256:652ab4836cd5531d64a34403c00ada4077bb91112e8bcdae933e2eae232cf4a8",
|
||||
"sha256:669a8d46764a09f198f2e91fc0d5acdac8e6b620376757a04682846ae28879c4",
|
||||
"sha256:73a10404867b835f1b8a64253e4621908f0d71150eb4e97ab2e7e441b53e9451",
|
||||
"sha256:7ce4a213a96d6c25eeae2f7d60d4dad89ac2b8134ec3e69db9bc522e2c0f9388",
|
||||
"sha256:8127ca2bf9539d6a64d03686fd9e789e8c194fc19af49b69b081f8c7e6ecb1bc",
|
||||
"sha256:b5b5b2e95f761a88d4c93691716ce01dc55f288a153face1654f868a8034f494",
|
||||
"sha256:b7c9f65524ff06bf70c945cd8d8d1fd90853e27ccf86026af2afb4d9a63d06b1",
|
||||
"sha256:f7f2f4226db6acd1da228adf433c5c3792858474e49d80668ea82ac87cf74a03",
|
||||
"sha256:fa09da4af4e5b15c0e8b4986a083f3fd159302ea115a6cc0649cd163435538b8"
|
||||
"sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a",
|
||||
"sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938",
|
||||
"sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29",
|
||||
"sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae",
|
||||
"sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387",
|
||||
"sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a",
|
||||
"sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf",
|
||||
"sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610",
|
||||
"sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9",
|
||||
"sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5",
|
||||
"sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3",
|
||||
"sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89",
|
||||
"sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded",
|
||||
"sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754",
|
||||
"sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f",
|
||||
"sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868",
|
||||
"sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd",
|
||||
"sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910",
|
||||
"sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3",
|
||||
"sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac",
|
||||
"sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"
|
||||
],
|
||||
"version": "==2020.5.7"
|
||||
"version": "==2020.6.8"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
||||
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
|
||||
"sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
|
||||
],
|
||||
"version": "==0.10.0"
|
||||
"version": "==0.10.1"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
|
||||
@@ -93,8 +93,7 @@ Shynet is pretty simple, but there are a few key terms you need to know in order
|
||||
|
||||
## Installation
|
||||
|
||||
You can find intructions on getting started and usage in the [Usage Guide](GUIDE.md#installation). Out of the box, we support deploying via a simple
|
||||
Docker container, docker-compose, or Kubernetes (see [kubernetes](/kubernetes)).
|
||||
You can find intructions on getting started and usage in the [Usage Guide](GUIDE.md#installation). Out of the box, we support deploying via a simple Docker container, docker-compose, Heroku, or Kubernetes (see [kubernetes](/kubernetes)).
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
22
TEMPLATE.env
@@ -13,6 +13,7 @@ DB_PORT=5432
|
||||
EMAIL_HOST_USER=example
|
||||
EMAIL_HOST_PASSWORD=example_password
|
||||
EMAIL_HOST=smtp.example.com
|
||||
EMAIL_PORT=465
|
||||
SERVER_EMAIL=<Shynet> noreply@shynet.example.com
|
||||
|
||||
# General Django settings
|
||||
@@ -22,7 +23,11 @@ DJANGO_SECRET_KEY=random_string
|
||||
ALLOWED_HOSTS=*
|
||||
|
||||
# Set to True (capitalized) if you want people to be able to sign up for your Shynet instance (not recommended)
|
||||
SIGNUPS_ENABLED=False
|
||||
ACCOUNT_SIGNUPS_ENABLED=False
|
||||
|
||||
# Should user email addresses be verified? Only set this to `required` if you've setup the email settings and allow
|
||||
# public sign-ups; otherwise, it's unnecessary.
|
||||
ACCOUNT_EMAIL_VERIFICATION=none
|
||||
|
||||
# The timezone of the admin panel. Affects how dates are displayed.
|
||||
TIME_ZONE=America/New_York
|
||||
@@ -47,22 +52,15 @@ ONLY_SUPERUSERS_CREATE=True
|
||||
# Will skip only if value is False.
|
||||
PERFORM_CHECKS_AND_SETUP=True
|
||||
|
||||
# Your admin user's email. A temporary password will be printed
|
||||
# to the console on first run.
|
||||
SHYNET_ADMIN_EMAIL=you@example.com
|
||||
|
||||
# The domain on which you'll be hosting Shynet.
|
||||
SHYNET_HOST=shynet.example.com
|
||||
|
||||
# What you'd like to call your Shynet instance.
|
||||
SHYNET_WHITELABEL=My Shynet Instance
|
||||
# The port that Shynet should bind to. Don't set this if you're deploying on Heroku.
|
||||
PORT=8080
|
||||
|
||||
# Redis, queue, and parellization settings; not necessary for single-instance deployments.
|
||||
# Don't uncomment these unless you know what you are doing!
|
||||
# NUM_WORKERS=1
|
||||
# Make sure you set a REDIS_CACHE_LOCATION if you have more than one frontend worker/instance.
|
||||
# REDIS_CACHE_LOCATION=redis://redis.default.svc.cluster.local/0
|
||||
# REDIS_CACHE_LOCATION=redis://redis.default.svc.cluster.local/0
|
||||
# If CELERY_BROKER_URL is set, make sure CELERY_TASK_ALWAYS_EAGER is False and
|
||||
# that you have a separate queue consumer running somewhere via `celeryworker.sh`.
|
||||
# CELERY_TASK_ALWAYS_EAGER=False
|
||||
# CELERY_BROKER_URL=redis://redis.default.svc.cluster.local/1
|
||||
# CELERY_BROKER_URL=redis://redis.default.svc.cluster.local/1
|
||||
|
||||
122
app.json
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"name": "Shynet",
|
||||
"description":"Modern, privacy-friendly, and detailed web analytics that works without cookies or JS.",
|
||||
"keywords":[
|
||||
"app.json",
|
||||
"shynet",
|
||||
"heroku",
|
||||
"analytics",
|
||||
"privacy",
|
||||
"friendly"
|
||||
],
|
||||
"website": "https://github.com/milesmcc/shynet",
|
||||
"repository": "https://github.com/milesmcc/shynet",
|
||||
"logo": "https://github.com/milesmcc/shynet/raw/master/images/slogo.png",
|
||||
"success_url": "/",
|
||||
"stack": "container",
|
||||
"addons": [
|
||||
"heroku-postgresql:hobby-dev"
|
||||
],
|
||||
"formation": {
|
||||
"web": {
|
||||
"quantity": 1,
|
||||
"size": "free"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"DB_NAME": {
|
||||
"description": "Postgres database name (not required if using Postgres addon)",
|
||||
"value": "shynet",
|
||||
"required": false
|
||||
},
|
||||
"DB_USER": {
|
||||
"description": "Postgres database username (not required if using Postgres addon)",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"DB_PASSWORD": {
|
||||
"description": "Postgres database password (not required if using Postgres addon)",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"DB_HOST": {
|
||||
"description": "Postgres database hostname (not required if using Postgres addon)",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"DB_PORT": {
|
||||
"description": "Postgres database port (not required if using Postgres addon)",
|
||||
"value": "5432",
|
||||
"required": false
|
||||
},
|
||||
"EMAIL_HOST": {
|
||||
"description": "SMTP server hostname (for sending emails)",
|
||||
"value": "smtp.gmail.com",
|
||||
"required": false
|
||||
},
|
||||
"EMAIL_PORT": {
|
||||
"description": "SMTP server port (for sending emails)",
|
||||
"value": "465",
|
||||
"required": false
|
||||
},
|
||||
"EMAIL_HOST_USER": {
|
||||
"description": "SMTP server username (for sending emails)",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"EMAIL_HOST_PASSWORD": {
|
||||
"description": "SMTP server password (for sending emails)",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"SERVER_EMAIL": {
|
||||
"description": "Email address (for sending emails)",
|
||||
"value": "<Shynet> noreply@shynet.example.com",
|
||||
"required": false
|
||||
},
|
||||
"DJANGO_SECRET_KEY": {
|
||||
"description": "Django secret key",
|
||||
"generator": "secret"
|
||||
},
|
||||
"ALLOWED_HOSTS": {
|
||||
"description": "For better security, set this to your deployment's domain. (Where you will actually host, not embed, Shynet.) Set to '*' to allow serving all domains.",
|
||||
"value": "*",
|
||||
"required": false
|
||||
},
|
||||
"ACCOUNT_SIGNUPS_ENABLED": {
|
||||
"description": "Set to True (capitalized) if you want people to be able to sign up for your Shynet instance (not recommended).",
|
||||
"value": "False",
|
||||
"required": false
|
||||
},
|
||||
"TIME_ZONE": {
|
||||
"description": "The timezone of the admin panel. Affects how dates are displayed.",
|
||||
"value": "America/New_York",
|
||||
"required": false
|
||||
},
|
||||
"SCRIPT_USE_HTTPS": {
|
||||
"description": "Set to 'False' if you will not be serving Shynet over HTTPS.",
|
||||
"value": "True",
|
||||
"required": false
|
||||
},
|
||||
"SCRIPT_HEARTBEAT_FREQUENCY": {
|
||||
"description": "How frequently should the monitoring script 'phone home' (in ms)?",
|
||||
"value": "5000",
|
||||
"required": false
|
||||
},
|
||||
"SESSION_MEMORY_TIMEOUT": {
|
||||
"description": "How much time can elapse between requests from the same user before a new session is created, in seconds?",
|
||||
"value": "1800",
|
||||
"required": false
|
||||
},
|
||||
"ONLY_SUPERUSERS_CREATE": {
|
||||
"description": "Should only superusers (admins) be able to create tracked services?",
|
||||
"value": "True",
|
||||
"required": false
|
||||
},
|
||||
"PERFORM_CHECKS_AND_SETUP": {
|
||||
"description": "Whether to perform checks and setup at startup. Recommended value is 'True' for Heroku users.",
|
||||
"value": "True",
|
||||
"required": false
|
||||
}
|
||||
}
|
||||
}
|
||||
3
heroku.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
build:
|
||||
docker:
|
||||
web: Dockerfile
|
||||
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 215 KiB |
BIN
images/logo.png
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 589 KiB After Width: | Height: | Size: 360 KiB |
BIN
images/slogo.png
Normal file
|
After Width: | Height: | Size: 931 B |
@@ -8,7 +8,7 @@ stringData:
|
||||
DEBUG: "False"
|
||||
ALLOWED_HOSTS: "*" # For better security, set this to your deployment's domain. Comma separated.
|
||||
DJANGO_SECRET_KEY: ""
|
||||
SIGNUPS_ENABLED: "False"
|
||||
ACCOUNT_SIGNUPS_ENABLED: "False"
|
||||
TIME_ZONE: "America/New_York"
|
||||
|
||||
# Redis configuration (if you use the default Kubernetes config, this will work)
|
||||
|
||||
3032
package-lock.json
generated
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "shynet",
|
||||
"description": "Modern, privacy-friendly, and cookie-free web analytics.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/milesmcc/shynet.git"
|
||||
},
|
||||
"keywords": [
|
||||
"privacy",
|
||||
"analytics",
|
||||
"self-host"
|
||||
],
|
||||
"author": "R. Miles McCain <shynet@sendmiles.email>",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/milesmcc/shynet/issues"
|
||||
},
|
||||
"homepage": "https://github.com/milesmcc/shynet#readme",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.13.1",
|
||||
"a17t": "^0.1.4",
|
||||
"apexcharts": "^3.19.3",
|
||||
"inter-ui": "^3.13.1",
|
||||
"litepicker": "^1.5.7",
|
||||
"tailwindcss": "^1.4.6",
|
||||
"turbolinks": "^5.2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/a17t@0.1.3/dist/a17t.css">
|
||||
<script async src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
{% load static %}
|
||||
|
||||
<link rel="stylesheet" href="{% static 'a17t/dist/a17t.css' %}">
|
||||
<script async src="{% static '@fortawesome/fontawesome-free/js/all.min.js' %}" data-mutate-approach="sync"></script>
|
||||
<link href="{% static 'tailwindcss/dist/tailwind.min.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'inter-ui/Inter (web)/inter.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--family-primary: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
|
||||
@@ -92,4 +92,6 @@ def is_file(field):
|
||||
def add_class(field, css_class):
|
||||
if len(field.errors) > 0:
|
||||
css_class += " ~critical"
|
||||
if field.field.widget.attrs.get("class") != None:
|
||||
css_class += " " + field.field.widget.attrs["class"]
|
||||
return field.as_widget(attrs={"class": field.css_classes(extra_classes=css_class)})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
from hashlib import sha1
|
||||
from hashlib import sha256
|
||||
|
||||
import geoip2.database
|
||||
import user_agents
|
||||
@@ -73,7 +73,7 @@ def ingress_request(
|
||||
if payload.get("loadTime", 1) <= 0:
|
||||
payload["loadTime"] = None
|
||||
|
||||
association_id_hash = sha1()
|
||||
association_id_hash = sha256()
|
||||
association_id_hash.update(str(ip).encode("utf-8"))
|
||||
association_id_hash.update(str(user_agent).encode("utf-8"))
|
||||
session_cache_path = (
|
||||
@@ -110,6 +110,8 @@ def ingress_request(
|
||||
device_type = "TABLET"
|
||||
elif ua.is_pc:
|
||||
device_type = "DESKTOP"
|
||||
if device_type == "ROBOT" and service.ignore_robots:
|
||||
return
|
||||
session = Session.objects.create(
|
||||
service=service,
|
||||
ip=ip if service.collect_ips else None,
|
||||
@@ -119,11 +121,11 @@ def ingress_request(
|
||||
device=ua.device.family or ua.device.model or "",
|
||||
device_type=device_type,
|
||||
os=ua.os.family or "",
|
||||
asn=ip_data.get("asn", ""),
|
||||
country=ip_data.get("country", ""),
|
||||
asn=ip_data.get("asn") or "",
|
||||
country=ip_data.get("country") or "",
|
||||
longitude=ip_data.get("longitude"),
|
||||
latitude=ip_data.get("latitude"),
|
||||
time_zone=ip_data.get("time_zone", ""),
|
||||
time_zone=ip_data.get("time_zone") or "",
|
||||
)
|
||||
cache.set(
|
||||
session_cache_path, session.pk, timeout=settings.SESSION_MEMORY_TIMEOUT
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
window.onload = function () {
|
||||
var idempotency =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
function sendUpdate() {
|
||||
// This is a lightweight and privacy-friendly analytics script from Shynet, a self-hosted
|
||||
// analytics tool. To give you full visibility into how your data is being monitored, this
|
||||
// file is intentionally not minified or obfuscated. To learn more about Shynet (and to view
|
||||
// its source code), visit <https://github.com/milesmcc/shynet>.
|
||||
//
|
||||
// This script only sends the current URL, the referrer URL, and the page load time. That's it!
|
||||
|
||||
var Shynet = {
|
||||
idempotency: null,
|
||||
heartbeatTaskId: null,
|
||||
sendHeartbeat: function () {
|
||||
try {
|
||||
if (document.hidden) {
|
||||
return;
|
||||
@@ -16,7 +22,7 @@ window.onload = function () {
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
idempotency: idempotency,
|
||||
idempotency: Shynet.idempotency,
|
||||
referrer: document.referrer,
|
||||
location: window.location.href,
|
||||
loadTime:
|
||||
@@ -24,8 +30,25 @@ window.onload = function () {
|
||||
window.performance.timing.navigationStart,
|
||||
})
|
||||
);
|
||||
} catch { }
|
||||
} catch (e) { }
|
||||
},
|
||||
newPageLoad: function () {
|
||||
if (Shynet.heartbeatTaskId != null) {
|
||||
clearInterval(Shynet.heartbeatTaskId);
|
||||
}
|
||||
Shynet.idempotency = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
Shynet.heartbeatTaskId = setInterval(Shynet.sendHeartbeat, parseInt("{{heartbeat_frequency}}"));
|
||||
Shynet.sendHeartbeat();
|
||||
}
|
||||
setInterval(sendUpdate, parseInt("{{heartbeat_frequency}}"));
|
||||
sendUpdate();
|
||||
};
|
||||
|
||||
window.addEventListener("load", Shynet.newPageLoad);
|
||||
|
||||
{% if script_inject %}
|
||||
// The following is script is not part of Shynet, and was instead
|
||||
// provided by this site's administrator.
|
||||
//
|
||||
// -- START --
|
||||
{{script_inject|safe}}
|
||||
// -- END --
|
||||
{% endif %}
|
||||
@@ -1,5 +1,6 @@
|
||||
import base64
|
||||
import json
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
@@ -49,7 +50,15 @@ class ValidateServiceOriginsMixin:
|
||||
cache.set(f"service_origins_{service_uuid}", origins, timeout=3600)
|
||||
|
||||
resp = super().dispatch(request, *args, **kwargs)
|
||||
resp["Access-Control-Allow-Origin"] = origins
|
||||
|
||||
if origins != "*":
|
||||
remote_origin = request.META.get("HTTP_ORIGIN")
|
||||
origins = [origin.strip() for origin in origins.split(",")]
|
||||
if remote_origin in origins:
|
||||
resp["Access-Control-Allow-Origin"] = remote_origin
|
||||
else:
|
||||
resp["Access-Control-Allow-Origin"] = "*"
|
||||
|
||||
resp["Access-Control-Allow-Methods"] = "GET,HEAD,OPTIONS,POST"
|
||||
resp[
|
||||
"Access-Control-Allow-Headers"
|
||||
@@ -105,11 +114,14 @@ class ScriptView(ValidateServiceOriginsMixin, View):
|
||||
return render(
|
||||
self.request,
|
||||
"analytics/scripts/page.js",
|
||||
context={
|
||||
"endpoint": endpoint,
|
||||
"protocol": protocol,
|
||||
"heartbeat_frequency": heartbeat_frequency,
|
||||
},
|
||||
context=dict(
|
||||
{
|
||||
"endpoint": endpoint,
|
||||
"protocol": protocol,
|
||||
"heartbeat_frequency": heartbeat_frequency,
|
||||
"script_inject": self.get_script_inject(),
|
||||
}
|
||||
),
|
||||
content_type="application/javascript",
|
||||
)
|
||||
|
||||
@@ -125,3 +137,12 @@ class ScriptView(ValidateServiceOriginsMixin, View):
|
||||
return HttpResponse(
|
||||
json.dumps({"status": "OK"}), content_type="application/json"
|
||||
)
|
||||
|
||||
def get_script_inject(self):
|
||||
service_uuid = self.kwargs.get("service_uuid")
|
||||
script_inject = cache.get(f"script_inject_{service_uuid}")
|
||||
if script_inject == None:
|
||||
service = Service.objects.get(uuid=service_uuid)
|
||||
script_inject = service.script_inject
|
||||
cache.set(f"script_inject_{service_uuid}", script_inject, timeout=3600)
|
||||
return script_inject
|
||||
|
||||
18
shynet/core/migrations/0007_service_ignore_robots.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.6 on 2020-06-15 16:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("core", "0006_service_hide_referrer_regex"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="service",
|
||||
name="ignore_robots",
|
||||
field=models.BooleanField(default=False),
|
||||
)
|
||||
]
|
||||
23
shynet/core/migrations/0008_auto_20200628_1403.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.1b1 on 2020-06-28 18:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0007_service_ignore_robots'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='service',
|
||||
name='script_inject',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='first_name',
|
||||
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
|
||||
),
|
||||
]
|
||||
@@ -65,6 +65,7 @@ class Service(models.Model):
|
||||
max_length=2, choices=SERVICE_STATUSES, default=ACTIVE, db_index=True
|
||||
)
|
||||
respect_dnt = models.BooleanField(default=True)
|
||||
ignore_robots = models.BooleanField(default=False)
|
||||
collect_ips = models.BooleanField(default=True)
|
||||
ignored_ips = models.TextField(
|
||||
default="", blank=True, validators=[_validate_network_list]
|
||||
@@ -72,6 +73,7 @@ class Service(models.Model):
|
||||
hide_referrer_regex = models.TextField(
|
||||
default="", blank=True, validators=[_validate_regex]
|
||||
)
|
||||
script_inject = models.TextField(default="", blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["name", "uuid"]
|
||||
@@ -233,7 +235,9 @@ class Service(models.Model):
|
||||
"session_chart_data": json.dumps(
|
||||
[
|
||||
{"x": str(key), "y": value}
|
||||
for key, value in session_chart_data.items()
|
||||
for key, value in sorted(
|
||||
session_chart_data.items(), key=lambda k: k[0]
|
||||
)
|
||||
]
|
||||
),
|
||||
"online": True,
|
||||
|
||||
@@ -14,9 +14,11 @@ class ServiceForm(forms.ModelForm):
|
||||
"respect_dnt",
|
||||
"collect_ips",
|
||||
"ignored_ips",
|
||||
"ignore_robots",
|
||||
"hide_referrer_regex",
|
||||
"origins",
|
||||
"collaborators",
|
||||
"script_inject"
|
||||
]
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
@@ -24,14 +26,18 @@ class ServiceForm(forms.ModelForm):
|
||||
"ignored_ips": forms.TextInput(),
|
||||
"respect_dnt": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
|
||||
"collect_ips": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
|
||||
"ignore_robots": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
|
||||
"hide_referrer_regex": forms.TextInput(),
|
||||
"script_inject": forms.Textarea(attrs={'class':'font-mono', 'rows': 5})
|
||||
}
|
||||
labels = {
|
||||
"origins": "Allowed Hostnames",
|
||||
"respect_dnt": "Respect DNT",
|
||||
"collect_ips": "Collect IP addresses",
|
||||
"ignored_ips": "Ignored IP addresses",
|
||||
"ignore_robots": "Ignore robots",
|
||||
"hide_referrer_regex": "Hide specific referrers",
|
||||
"script_inject": "Additional injected JS",
|
||||
}
|
||||
help_texts = {
|
||||
"name": _("What should the service be called?"),
|
||||
@@ -42,11 +48,13 @@ class ServiceForm(forms.ModelForm):
|
||||
"respect_dnt": "Should visitors who have enabled <a href='https://en.wikipedia.org/wiki/Do_Not_Track'>Do Not Track</a> be excluded from all data?",
|
||||
"collect_ips": "Should individual IP addresses be collected? IP metadata (location, host, etc) will still be collected.",
|
||||
"ignored_ips": "A comma-separated list of IP addresses or IP ranges (IPv4 and IPv6) to exclude from tracking (e.g., '192.168.0.2, 127.0.0.1/32').",
|
||||
"ignore_robots": "Should sessions generated by bots be excluded from tracking?",
|
||||
"hide_referrer_regex": "Any referrers that match this <a href='https://regexr.com/'>RegEx</a> will not be listed in the referrer summary. Sessions will still be tracked normally. No effect if left blank.",
|
||||
"script_inject": "Optional additional JavaScript to inject at the end of the Shynet script. This code will be injected on every page where this service is installed.",
|
||||
}
|
||||
|
||||
collaborators = forms.CharField(
|
||||
help_text="Which users should have read-only access to this service? (Comma separated list of emails.)",
|
||||
help_text="Which users on this Shynet instance should have read-only access to this service? (Comma separated list of emails.)",
|
||||
required=False,
|
||||
)
|
||||
|
||||
|
||||
0
shynet/dashboard/static/dashboard/js/base.js
Normal file
@@ -8,10 +8,10 @@
|
||||
<meta name="robots" content="noindex">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% include 'a17t/head.html' %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/litepicker@1.2.0/dist/js/main.js"
|
||||
integrity="sha256-mOlCEHUNWZPYIrc5OFL4Ab2rsJGzIPld3cy1ok7Cfx0=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/apexcharts@3.18.1/dist/apexcharts.min.js"
|
||||
integrity="sha256-RalQXBZdisB04aaBsm+6YZ0b/iRYjX1MZn90m19AnCY=" crossorigin="anonymous"></script>
|
||||
<script src="{% static 'apexcharts/dist/apexcharts.min.js'%}"></script>
|
||||
<script src="{% static 'litepicker/dist/js/main.js' %}"></script>
|
||||
<script src="{% static 'turbolinks/dist/turbolinks.js' %}"></script>
|
||||
<script src="{% static 'dashboard/js/base.js' %}"></script>
|
||||
<link rel="stylesheet" href="{% static 'dashboard/css/global.css' %}">
|
||||
{% block extra_head %}
|
||||
{% endblock %}
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
{% if user.is_superuser %}
|
||||
{% url 'admin:index' as url %}
|
||||
{% include 'dashboard/includes/sidebar_portal.html' with label="Admin" url=url %}
|
||||
{% include 'dashboard/includes/sidebar_portal.html' with label="Admin" disable_turbolinks=True url=url %}
|
||||
{% endif %}
|
||||
|
||||
{% url 'account_email' as url %}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
{{form.respect_dnt|a17t}}
|
||||
{{form.collect_ips|a17t}}
|
||||
{{form.ignored_ips|a17t}}
|
||||
{{form.ignore_robots|a17t}}
|
||||
{{form.hide_referrer_regex|a17t}}
|
||||
{{form.origins|a17t}}
|
||||
{{form.script_inject|a17t}}
|
||||
</details>
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
<div>
|
||||
<a class="portal !low {% if request.get_full_path|startswith:url %}~urge active bg-gray-100{% endif %}"
|
||||
href="{{url}}">{{label}}</a>
|
||||
{% if disable_turbolinks %}data-turbolinks="false"{% endif %} href="{{url}}">{{label}}</a>
|
||||
</div>
|
||||
@@ -84,6 +84,9 @@ class ServiceUpdateView(
|
||||
cache.set(
|
||||
f"service_origins_{self.object.uuid}", self.object.origins, timeout=3600
|
||||
)
|
||||
cache.set(
|
||||
f"script_inject_{self.object.uuid}", self.object.script_inject, timeout=3600
|
||||
)
|
||||
return resp
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ ! $PERFORM_CHECKS_AND_SETUP == False ]]; then
|
||||
./startup_checks.sh
|
||||
fi
|
||||
|
||||
./webserver.sh
|
||||
./startup_checks.sh && exec ./webserver.sh
|
||||
else
|
||||
exec ./webserver.sh
|
||||
fi
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -10,11 +10,15 @@ https://docs.djangoproject.com/en/2.2/ref/settings/
|
||||
|
||||
import os
|
||||
|
||||
# import module sys to get the type of exception
|
||||
import sys
|
||||
import urllib.parse as urlparse
|
||||
|
||||
# Messages
|
||||
from django.contrib.messages import constants as messages
|
||||
|
||||
# Increment on new releases
|
||||
VERSION = "v0.4.1"
|
||||
VERSION = "v0.6.0"
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -43,6 +47,9 @@ INSTALLED_APPS = [
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.sites",
|
||||
"django.contrib.humanize",
|
||||
"health_check",
|
||||
"health_check.db",
|
||||
"health_check.cache",
|
||||
"rules.apps.AutodiscoverRulesConfig",
|
||||
"a17t",
|
||||
"core",
|
||||
@@ -105,9 +112,31 @@ else:
|
||||
"PASSWORD": os.environ.get("DB_PASSWORD"),
|
||||
"HOST": os.environ.get("DB_HOST"),
|
||||
"PORT": os.environ.get("DB_PORT"),
|
||||
"OPTIONS": {"connect_timeout": 5},
|
||||
}
|
||||
}
|
||||
|
||||
# Solution to removal of Heroku DB Injection
|
||||
if "DATABASE_URL" in os.environ:
|
||||
if "DATABASES" not in locals():
|
||||
DATABASES = {}
|
||||
url = urlparse.urlparse(os.environ["DATABASE_URL"])
|
||||
|
||||
# Ensure default database exists.
|
||||
DATABASES["default"] = DATABASES.get("default", {})
|
||||
|
||||
# Update with environment configuration.
|
||||
DATABASES["default"].update(
|
||||
{
|
||||
"NAME": url.path[1:],
|
||||
"USER": url.username,
|
||||
"PASSWORD": url.password,
|
||||
"HOST": url.hostname,
|
||||
"PORT": url.port,
|
||||
}
|
||||
)
|
||||
if url.scheme == "postgres":
|
||||
DATABASES["default"]["ENGINE"] = "django.db.backends.postgresql_psycopg2"
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
|
||||
@@ -176,6 +205,11 @@ USE_TZ = True
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = "compiledstatic/"
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
STATICFILES_FINDERS = [
|
||||
"npm.finders.NpmFinder",
|
||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||
]
|
||||
|
||||
# Redis
|
||||
if not DEBUG and os.getenv("REDIS_CACHE_LOCATION") is not None:
|
||||
@@ -207,6 +241,7 @@ ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||
ACCOUNT_EMAIL_SUBJECT_PREFIX = ""
|
||||
ACCOUNT_USER_DISPLAY = lambda k: k.email
|
||||
ACCOUNT_SIGNUPS_ENABLED = os.getenv("ACCOUNT_SIGNUPS_ENABLED", "False") == "True"
|
||||
ACCOUNT_EMAIL_VERIFICATION = os.getenv("ACCOUNT_EMAIL_VERIFICATION", "none")
|
||||
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
|
||||
@@ -246,6 +281,21 @@ else:
|
||||
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD")
|
||||
EMAIL_USE_SSL = True
|
||||
|
||||
# NPM
|
||||
|
||||
NPM_ROOT_PATH = "../"
|
||||
|
||||
NPM_FILE_PATTERNS = {
|
||||
"a17t": ["dist/a17t.css"],
|
||||
"@fortawesome/fontawesome-free": ["js/all.min.js"],
|
||||
"tailwindcss": ["dist/tailwind.min.css"],
|
||||
"apexcharts": ["dist/apexcharts.min.js"],
|
||||
"litepicker": ["dist/js/main.js"],
|
||||
"turbolinks": ["dist/turbolinks.js"],
|
||||
"stimulus": ["dist/stimulus.umd.js"],
|
||||
"inter-ui": ["Inter (web)/*"],
|
||||
}
|
||||
|
||||
# Shynet
|
||||
|
||||
# Can everyone create services, or only superusers?
|
||||
|
||||
@@ -21,5 +21,6 @@ urlpatterns = [
|
||||
path("accounts/", include("allauth.urls")),
|
||||
path("ingress/", include(("analytics.ingress_urls", "ingress")), name="ingress"),
|
||||
path("dashboard/", include(("dashboard.urls", "dashboard"), namespace="dashboard")),
|
||||
path("healthz/", include("health_check.urls")),
|
||||
path("", include(("core.urls", "core"), namespace="core")),
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Start Gunicorn processes
|
||||
echo Launching Shynet web server...
|
||||
exec gunicorn shynet.wsgi:application \
|
||||
--bind 0.0.0.0:8080 \
|
||||
--bind 0.0.0.0:${PORT:-8080} \
|
||||
--workers ${NUM_WORKERS:-1} \
|
||||
--timeout 100 \
|
||||
--certfile=cert.pem \
|
||||
|
||||
@@ -12,34 +12,13 @@ if [[ ${sanity_results[0]} == True ]]; then
|
||||
else
|
||||
echo "Database is ready to go."
|
||||
fi
|
||||
if [[ -n $SHYNET_ADMIN_EMAIL && ${sanity_results[1]} == True ]]; then
|
||||
echo "Creating an admin user..."
|
||||
{
|
||||
temppwd=$( ./manage.py registeradmin $SHYNET_ADMIN_EMAIL ) && echo "Admin user ($SHYNET_ADMIN_EMAIL) created! Password: $temppwd"
|
||||
} || {
|
||||
echo "Failed to create admin, exiting" & exit 1
|
||||
}
|
||||
else
|
||||
echo "Making no changes to admin user."
|
||||
if [[ ${sanity_results[1]} == True ]]; then
|
||||
echo "Warning: no admin user available. Consult docs for instructions."
|
||||
fi
|
||||
if [[ -n $SHYNET_HOST && ${sanity_results[2]} == True ]]; then
|
||||
echo "Setting hostname..."
|
||||
{
|
||||
./manage.py hostname $SHYNET_HOST && echo "Hostname set to $SHYNET_HOST!"
|
||||
} || {
|
||||
echo "Failed setting hostname, exiting" & exit 1
|
||||
}
|
||||
else
|
||||
echo "Making no changes to hostname."
|
||||
if [[ ${sanity_results[2]} == True ]]; then
|
||||
echo "Warning: Shynet's hostname is not set. The script won't work correctly. Consult docs for instructions."
|
||||
fi
|
||||
if [[ -n $SHYNET_WHITELABEL && ${sanity_results[3]} == True ]]; then
|
||||
echo "Setting whitelabel..."
|
||||
{
|
||||
./manage.py whitelabel "$SHYNET_WHITELABEL" && echo "Whitelabel set! Whitelabel: $SHYNET_WHITELABEL"
|
||||
} || {
|
||||
echo "Failed to set whitelabel, exiting" & exit 1
|
||||
}
|
||||
else
|
||||
echo "Making no changes to whitelabel."
|
||||
if [[ ${sanity_results[3]} == True ]]; then
|
||||
echo "Warning: Shynet's whitelabel is not set. Consult docs for instructions."
|
||||
fi
|
||||
echo "Startup checks complete!"
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
# Start Gunicorn processes
|
||||
echo Launching Shynet web server...
|
||||
exec gunicorn shynet.wsgi:application \
|
||||
--bind 0.0.0.0:8080 \
|
||||
--bind 0.0.0.0:${PORT:-8080} \
|
||||
--workers ${NUM_WORKERS:-1} \
|
||||
--timeout 100
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript><img src="//localhost:8000/ingress/test_uuid/pixel.gif"></noscript>
|
||||
<script src="//localhost:8000/ingress/66015ce4-c69d-40fb-be8f-5535538d795e/script.js"></script>
|
||||
<noscript><img src="http://localhost:8000/ingress/9b2c4e2f-8d29-4418-82d4-b68e06795025/pixel.gif"></noscript>
|
||||
<script src="http://localhost:8000/ingress/9b2c4e2f-8d29-4418-82d4-b68e06795025/script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||