├── .gitignore ├── README.md ├── docker-compose.yml └── owncast.sh /.gitignore: -------------------------------------------------------------------------------- 1 | files_owncast 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Owncast 2 | 3 | Short story long. My daughter's gym needed to live stream her competition. I ended up using OBS and YouTube to some success. However since YouTube doesn't allow fair use for music they kept "copyright claim" blocking the videos. One stream they even stopped the stream in the middle of it. The workaround is to use stream using 2 hour blocks. This is not ideal when you have people watching from all over the world. The solution is [Owncast](https://owncast.online/). Host my own service! 4 | 5 | ## My Setup 6 | 7 | Fairly simple: 8 | 9 | * Stand up VM 10 | * Any OS. Deploy Docker 11 | * Check out my script [owncast.sh](https://github.com/clemenko/owncast/blob/main/owncast.sh) 12 | * Deploy Owncast 13 | * Deploy along side [Traefik](https://traefik.io). Traefik will use Let's Encrypt for certs. 14 | * Here is a docker-compose that I use [docker-compose.yml](https://github.com/clemenko/owncast/blob/main/docker-compose.yml). 15 | * Configure Owncast with S3 16 | * Orginially I set everything up with Digital Ocean Spaces. But the CDN they provide wouldn't work with Safari browsers. So I switched to [Wasabi](https://wasabi.com/). Here are the owncast [docs for Wasabi](https://owncast.online/docs/storage/wasabi/). 17 | * Configure OBS 18 | * There are a LOT of settings. Here are the highlights. Twitch has a [page for the details](https://stream.twitch.tv/encoding/). 19 | * MAX resolution 1080p at 60fps 20 | * MAX bitrate 6000kps 21 | * Keyframe internval 2 22 | * Audio Sample rate 44.1kHz 23 | * Pro TIPS 24 | * NO WIFI. Which every computer you are using to stream from. IT MUST USE ethernet. We have streamed about a dozen times and all but the last one was wifi. Wifi has always dropped packets. Ethernet never dropped a single packet. NO MOAR WIFI! 25 | * Profit 26 | 27 | ## Lessons Learned 28 | 29 | No wifi is the pro tip for the month. The difference was night and day. 30 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | services: 3 | traefik: 4 | image: traefik 5 | container_name: "traefik" 6 | restart: unless-stopped 7 | security_opt: 8 | - no-new-privileges:true 9 | command: 10 | - "--api=true" 11 | - "--providers.docker=true" 12 | - "--providers.docker.exposedbydefault=true" 13 | - "--metrics.prometheus=true" 14 | - "--metrics.prometheus.addEntryPointsLabels=true" 15 | - "--metrics.prometheus.addServicesLabels=true" 16 | - "--accesslog=true" 17 | - "--accesslog.filepath=/opt/traefik/access.log" 18 | - "--global.sendAnonymousUsage=false" 19 | - "--entryPoints.http.address=:80" 20 | - "--entryPoints.https.address=:443" 21 | - "--certificatesResolvers.mytlschallenge.acme.httpChallenge=true" 22 | - "--certificatesResolvers.mytlschallenge.acme.httpChallenge.entryPoint=http" 23 | - "--certificatesresolvers.mytlschallenge.acme.email=clemenko@gmail.com" 24 | - "--certificatesresolvers.mytlschallenge.acme.storage=/opt/traefik/acme.json" 25 | labels: 26 | # Dashboard 27 | - "traefik.http.routers.dashboard.rule=Host(`ingress.ieacro.com`)" 28 | - "traefik.http.routers.dashboard.entrypoints=https" 29 | - "traefik.http.routers.dashboard.service=api@internal" 30 | - "traefik.http.routers.dashboard.tls.certresolver=mytlschallenge" 31 | - "traefik.http.routers.dashboard.middlewares=https-auth" 32 | 33 | # global redirect HTTPS 34 | - "traefik.http.routers.http-catchall.rule=HostRegexp(`{any:.+}`)" 35 | - "traefik.http.routers.http-catchall.entrypoints=http" 36 | - "traefik.http.routers.http-catchall.middlewares=redirect-to-https@docker" 37 | 38 | # middleware: Redirect HTTP->HTTPS 39 | - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" 40 | - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true" 41 | 42 | # sts middlewares 43 | - "traefik.http.middlewares.servicestls.headers.stsincludesubdomains=false" 44 | - "traefik.http.middlewares.servicestls.headers.stspreload=true" 45 | - "traefik.http.middlewares.servicestls.headers.stsseconds=15552001" 46 | - "traefik.http.middlewares.servicestls.headers.isdevelopment=false" 47 | 48 | # middleware: Basic Auth 49 | - "traefik.http.middlewares.https-auth.basicauth.users=admin:$$apr1$$VkNnnQVA$$nvOkZfQiOtK9XHgm3lvCD1" 50 | 51 | # https proto 52 | - "traefik.http.middlewares.testHeader.headers.customrequestheaders.X-Forwarded-Proto=https" 53 | - "traefik.http.middlewares.testHeader.headers.framedeny=true" 54 | - "traefik.http.middlewares.testHeader.headers.sslredirect=true" 55 | 56 | ports: 57 | - "80:80" 58 | - "443:443" 59 | volumes: 60 | - "/var/run/docker.sock:/var/run/docker.sock:ro" 61 | - "/opt/traefik/:/opt/traefik/" 62 | 63 | owncast: 64 | image: gabekangas/owncast #ghcr.io/owncast/owncast:nightly 65 | container_name: "owncast" 66 | restart: unless-stopped 67 | security_opt: 68 | - no-new-privileges:true 69 | ports: 70 | - "1935:1935" 71 | labels: 72 | - "traefik.http.routers.owncast.rule=Host(`stream.ieacro.com`)" 73 | - "traefik.http.routers.owncast.entrypoints=https" 74 | - "traefik.http.routers.owncast.tls.certresolver=mytlschallenge" 75 | - "traefik.http.services.owncast.loadbalancer.server.port=8080" 76 | volumes: 77 | - "/opt/owncast:/app/data" 78 | -------------------------------------------------------------------------------- /owncast.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # https://docs.docker.com/engine/install/ubuntu/ 3 | 4 | ################################### 5 | # edit vars 6 | ################################### 7 | set -e 8 | password=Pa22word 9 | zone=nyc3 10 | size=s-4vcpu-8gb-amd 11 | key=30:98:4f:c5:47:c2:88:28:fe:3c:23:cd:52:49:51:01 12 | domain=ieacro.com 13 | 14 | image=ubuntu-21-10-x64 15 | 16 | ###### NO MOAR EDITS ####### 17 | RED=$(tput setaf 1) 18 | GREEN=$(tput setaf 2) 19 | NORMAL=$(tput sgr0) 20 | BLUE=$(tput setaf 4) 21 | 22 | #better error checking 23 | command -v doctl >/dev/null 2>&1 || { echo "$RED" " ** Doctl was not found. Please install. ** " "$NORMAL" >&2; exit 1; } 24 | 25 | ################################# up ################################ 26 | function up () { 27 | 28 | #build VMS 29 | echo -n " building stream.ieacro.com" 30 | doctl compute droplet create stream.ieacro.com --region $zone --image $image --size $size --ssh-keys $key --wait > /dev/null 2>&1 31 | export ip=$(doctl compute droplet list --no-header|grep stream|awk '{print $3}') 32 | echo "$GREEN" "ok" "$NORMAL" 33 | 34 | #check for SSH 35 | echo -n " checking for ssh" 36 | until [ $(ssh -o ConnectTimeout=1 root@$ip 'exit' 2>&1 | grep 'timed out\|refused' | wc -l) = 0 ]; do echo -n "." ; sleep 5; done 37 | echo "$GREEN" "ok" "$NORMAL" 38 | 39 | #update DNS 40 | echo -n " updating dns" 41 | doctl compute domain records create $domain --record-type A --record-name stream --record-ttl 300 --record-data $ip > /dev/null 2>&1 42 | doctl compute domain records create $domain --record-type CNAME --record-name "ingress" --record-ttl 150 --record-data stream.$domain. > /dev/null 2>&1 43 | echo "$GREEN" "ok" "$NORMAL" 44 | 45 | #host modifications and Docker install 46 | echo -n " adding os packages" 47 | ssh root@$ip 'export DEBIAN_FRONTEND=noninteractive && apt update && curl -fsSL https://get.docker.com | bash && apt upgrade -y; apt autoremove -y && \ 48 | cat << EOF >> /etc/sysctl.conf 49 | # SWAP settings 50 | vm.swappiness=0 51 | vm.overcommit_memory=1 52 | 53 | # Have a larger connection range available 54 | net.ipv4.ip_local_port_range=1024 65000 55 | 56 | # Increase max connection 57 | net.core.somaxconn=10000 58 | 59 | # Reuse closed sockets faster 60 | net.ipv4.tcp_tw_reuse=1 61 | net.ipv4.tcp_fin_timeout=15 62 | 63 | # The maximum number of "backlogged sockets". Default is 128. 64 | net.core.somaxconn=4096 65 | net.core.netdev_max_backlog=4096 66 | 67 | # 16MB per socket - which sounds like a lot, 68 | # but will virtually never consume that much. 69 | net.core.rmem_max=16777216 70 | net.core.wmem_max=16777216 71 | 72 | # Various network tunables 73 | net.ipv4.tcp_max_syn_backlog=20480 74 | net.ipv4.tcp_max_tw_buckets=400000 75 | net.ipv4.tcp_no_metrics_save=1 76 | net.ipv4.tcp_rmem=4096 87380 16777216 77 | net.ipv4.tcp_syn_retries=2 78 | net.ipv4.tcp_synack_retries=2 79 | net.ipv4.tcp_wmem=4096 65536 16777216 80 | 81 | # ARP cache settings for a highly loaded docker swarm 82 | net.ipv4.neigh.default.gc_thresh1=8096 83 | net.ipv4.neigh.default.gc_thresh2=12288 84 | net.ipv4.neigh.default.gc_thresh3=16384 85 | 86 | # ip_forward and tcp keepalive for iptables 87 | net.ipv4.tcp_keepalive_time=600 88 | net.ipv4.ip_forward=1 89 | 90 | # monitor file system events 91 | fs.inotify.max_user_instances=8192 92 | fs.inotify.max_user_watches=1048576 93 | EOF 94 | sysctl -p && \ 95 | systemctl enable docker && systemctl start docker && mkdir -p /opt/{owncast,traefik}' > /dev/null 2>&1 96 | echo "$GREEN" "ok" "$NORMAL" 97 | 98 | echo -n " - deploying owncast & traefik " 99 | rsync -avP docker-compose.yml root@"$ip":/opt/ > /dev/null 2>&1 100 | rsync -avP files_owncast/* root@"$ip":/opt/owncast > /dev/null 2>&1 101 | ssh root@$ip 'curl -Ls "https://github.com/docker/compose/releases/download/v2.4.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod 755 /usr/local/bin/docker-compose && \ 102 | cd /opt && docker-compose up -d' > /dev/null 2>&1 103 | echo "$GREEN" "ok" "$NORMAL" 104 | } 105 | 106 | ############################## kill ################################ 107 | #remove the vms 108 | function kill () { 109 | 110 | export ip=$(doctl compute droplet list --no-header|grep stream|awk '{print $3}') 111 | 112 | echo -n " killing it all " 113 | doctl compute droplet delete --force stream.$domain 114 | doctl compute domain records delete -f $domain $(doctl compute domain records list $domain|grep 'stream'|awk '{print $1}') 115 | echo "$GREEN" "ok" "$NORMAL" 116 | } 117 | 118 | ############################# usage ################################ 119 | function usage () { 120 | echo "" 121 | echo "-------------------------------------------------" 122 | echo "" 123 | echo " Usage: $0 {up|kill}" 124 | echo "" 125 | echo " ./$0 up # build the vms " 126 | echo " ./$0 kill # kill the vms" 127 | echo "" 128 | echo "-------------------------------------------------" 129 | echo "" 130 | exit 1 131 | } 132 | 133 | case "$1" in 134 | up) up;; 135 | kill) kill;; 136 | *) usage;; 137 | esac 138 | --------------------------------------------------------------------------------