mirror of
https://github.com/tiny-pilot/tinypilot.git
synced 2023-10-01 22:58:29 +03:00
Update overhaul community release. (#1046)
* Delegate quick-install's duties to get-tinypilot.sh (#1038) * Replace quick-install with get-tinypilot.sh Fixes #1023 Fixes #1025 * Tweak architecture explanation * Read local version from `VERSION` file. (#1040) * Read local version from version file. * Change get version response example. * Use a class-wide `_is_debug` patch. * Mock version file to point to a non-existent file. * Add test for empty version file. * Add test for version file has UTF-8 encoding. * Rename FileError to VersionFileError. * Split version file error catching. * Move debug mode version file tests to own class. * Request latest version from Gatekeeper API. (#1043) * Request latest version from Gatekeeper API. * Remove git module. * Remove debug behavior. * Improve version docstring. * Set an explicit encoding when reading response. * Rename non-obvious exception name. * Rename test. * Fix formatting. * Validate response data. * Rename response_json to response_dict. * Add `sudo` to install instructions. (#1045) * Specify custom TMPDIR for use with `mktemp`. (#1048) * Specify custom TMPDIR for use with mktemp. * Avoid exporting TMPDIR variable. * Revert "Add `sudo` to install instructions. (#1045)" (#1057) This reverts commit706f78d759. * Build bundle `ansible-role-tinypilot` version. (#1076) * Bundle a variable version of ansible role. * Only clone a single branch * Test bundle build. * Preserve array ordering. * Add comment about Ansible role dependencies. * Remove .git url suffix. * Revert "Test bundle build." This reverts commit33602757b1. * Build bundle ansible-role-tinypilot version. * Upgrade to Ansible 2.10.7. (#1086) * Use ansible version 2.10.7. * Test bundle build. * Set remote_user. * Revert "Test bundle build." This reverts commita9051bdffa. * Set default remote_user via ansible.cfg. * Test bundle build. * Update Ansible indirect dependencies. * Revert "Test bundle build." This reverts commitc8890a2fc0. Co-authored-by: Michael Lynch <mtlynch@users.noreply.github.com> Co-authored-by: Jan Heuermann <jan@jotaen.net>
This commit is contained in:
@@ -64,6 +64,6 @@ As of Feb. 2021, uStreamer's maintainer is working on a H264 option, expected to
|
||||
|
||||
## Installation
|
||||
|
||||
TinyPilot's installation process is somewhat unusual in that it depends on Ansible. The [`quick-install`](./quick-install) script bootstraps an Ansible environment on a Raspberry Pi and then uses [`ansible-role-tinypilot`](https://github.com/tiny-pilot/ansible-role-tinypilot) to install itself locally. `ansible-role-tinypilot` transitively includes other roles that TinyPilot depends on such as [`ansible-role-ustreamer`](https://github.com/mtlynch/ansible-role-ustreamer) and [`ansible-role-nginx`](https://github.com/geerlingguy/ansible-role-nginx).
|
||||
TinyPilot's installation process is somewhat unusual in that it depends on Ansible. The [`get-tinypilot.sh`](./get-tinypilot.sh) script downloads an tarball, unpacks it, and executes a script called `install` from that package. The `install` script bootstraps an Ansible environment on a Raspberry Pi and then uses [`ansible-role-tinypilot`](https://github.com/tiny-pilot/ansible-role-tinypilot) to install itself locally. `ansible-role-tinypilot` transitively includes other roles that TinyPilot depends on such as [`ansible-role-ustreamer`](https://github.com/mtlynch/ansible-role-ustreamer) and [`ansible-role-nginx`](https://github.com/geerlingguy/ansible-role-nginx).
|
||||
|
||||
The `quick-install` script is also responsible for version-to-version updates and configuration changes.
|
||||
The `get-tinypilot.sh` script is also responsible for version-to-version updates and configuration changes.
|
||||
|
||||
@@ -65,7 +65,7 @@ You can install TinyPilot on a compatible Raspberry Pi in just two commands.
|
||||
curl \
|
||||
--silent \
|
||||
--show-error \
|
||||
https://raw.githubusercontent.com/tiny-pilot/tinypilot/master/quick-install | \
|
||||
https://raw.githubusercontent.com/tiny-pilot/tinypilot/master/get-tinypilot.sh | \
|
||||
bash - && \
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
@@ -135,7 +135,7 @@ def version_get():
|
||||
|
||||
Example:
|
||||
{
|
||||
"version": "bf07bfe72941457cf068ca0a44c6b0d62dd9ef05",
|
||||
"version": "bf07bfe",
|
||||
}
|
||||
|
||||
Returns error object on failure.
|
||||
|
||||
63
app/git.py
63
app/git.py
@@ -1,63 +0,0 @@
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GitFailedError(Error):
|
||||
pass
|
||||
|
||||
|
||||
def local_head_commit_id():
|
||||
"""Gets the commit ID from the HEAD of the local git repository.
|
||||
|
||||
Returns:
|
||||
A str containing a git commit ID from the local HEAD.
|
||||
Example: 'bf07bfe72941457cf068ca0a44c6b0d62dd9ef05'
|
||||
|
||||
Raises:
|
||||
Error: The git command failed.
|
||||
"""
|
||||
logger.info('Getting local HEAD commit ID')
|
||||
commit_id = _run(['git', 'rev-parse', 'HEAD']).stdout.strip()
|
||||
logger.info('Local HEAD commit ID: %s', commit_id)
|
||||
return commit_id
|
||||
|
||||
|
||||
def remote_head_commit_id():
|
||||
"""Gets the commit ID from the HEAD of the remote git repository.
|
||||
|
||||
It needs to fetch git updates before retrieving the remote commit ID. It
|
||||
might cause a delay but it should be very minimal.
|
||||
|
||||
Returns:
|
||||
A str containing a git commit ID from the origin/master HEAD.
|
||||
Example: 'bf07bfe72941457cf068ca0a44c6b0d62dd9ef05'
|
||||
|
||||
Raises:
|
||||
Error: The git command failed.
|
||||
"""
|
||||
logger.info('Getting remote HEAD commit ID')
|
||||
_fetch()
|
||||
commit_id = _run(['git', 'rev-parse', 'origin/master']).stdout.strip()
|
||||
logger.info('Remote HEAD commit ID: %s', commit_id)
|
||||
return commit_id
|
||||
|
||||
|
||||
def _fetch():
|
||||
logger.info('Performing git fetch')
|
||||
_run(['git', 'fetch', '--force'])
|
||||
logger.info('git fetch complete')
|
||||
|
||||
|
||||
def _run(cmd):
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
||||
try:
|
||||
result.check_returncode()
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise GitFailedError(result.stderr.strip()) from e
|
||||
return result
|
||||
@@ -182,29 +182,35 @@ _LICENSE_METADATA = [
|
||||
name='Ansible',
|
||||
homepage_url='https://www.ansible.com',
|
||||
license_url=
|
||||
'https://raw.githubusercontent.com/ansible/ansible/v2.9.10/COPYING'),
|
||||
'https://raw.githubusercontent.com/ansible/ansible/v2.10.7/COPYING'),
|
||||
LicenseMetadata(
|
||||
name='cffi',
|
||||
homepage_url='http://cffi.readthedocs.org/',
|
||||
license_url='https://foss.heptapod.net/pypy/cffi/-/raw/v1.14.1/LICENSE'
|
||||
license_url='https://foss.heptapod.net/pypy/cffi/-/raw/v1.15.1/LICENSE'
|
||||
),
|
||||
LicenseMetadata(
|
||||
name='cryptography',
|
||||
homepage_url='https://cryptography.io',
|
||||
license_url=
|
||||
'https://raw.githubusercontent.com/pyca/cryptography/35.0.0/LICENSE.BSD'
|
||||
'https://raw.githubusercontent.com/pyca/cryptography/37.0.4/LICENSE.BSD'
|
||||
),
|
||||
LicenseMetadata(
|
||||
name='packaging',
|
||||
homepage_url='https://github.com/pypa/packaging',
|
||||
license_url=
|
||||
'https://raw.githubusercontent.com/pypa/packaging/21.3/LICENSE.BSD'),
|
||||
LicenseMetadata(
|
||||
name='pycparser',
|
||||
homepage_url='https://github.com/eliben/pycparser',
|
||||
license_url=
|
||||
'https://raw.githubusercontent.com/eliben/pycparser/release_v2.20/LICENSE'
|
||||
'https://raw.githubusercontent.com/eliben/pycparser/release_v2.21/LICENSE'
|
||||
),
|
||||
LicenseMetadata(
|
||||
name='pyOpenSSL',
|
||||
homepage_url='https://pyopenssl.org',
|
||||
name='pyparsing',
|
||||
homepage_url='https://github.com/pyparsing/pyparsing/',
|
||||
license_url=
|
||||
'https://raw.githubusercontent.com/pyca/pyopenssl/20.0.1/LICENSE'),
|
||||
'https://raw.githubusercontent.com/pyparsing/pyparsing/pyparsing_3.0.9/LICENSE'
|
||||
),
|
||||
|
||||
# Indirect dependencies through Janus.
|
||||
LicenseMetadata(
|
||||
|
||||
@@ -1,24 +1,98 @@
|
||||
import git
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GitError(Error):
|
||||
class VersionFileError(Error):
|
||||
pass
|
||||
|
||||
|
||||
class VersionRequestError(Error):
|
||||
pass
|
||||
|
||||
|
||||
_VERSION_FILE = './VERSION'
|
||||
|
||||
|
||||
def _is_debug():
|
||||
return flask.current_app.debug
|
||||
|
||||
|
||||
def local_version():
|
||||
"""Read the current local version string from the version file.
|
||||
|
||||
If run locally, in development, where a version file is not present then a
|
||||
dummy version string is returned.
|
||||
|
||||
Returns:
|
||||
A version string (e.g., "abc1234" or "1.2.3").
|
||||
|
||||
Raises:
|
||||
VersionFileError: If an error occurred while accessing the version file.
|
||||
"""
|
||||
if _is_debug():
|
||||
return '0000000'
|
||||
|
||||
try:
|
||||
return git.local_head_commit_id()
|
||||
except git.Error as e:
|
||||
raise GitError('Failed to check local version: %s' % str(e)) from e
|
||||
with open(_VERSION_FILE, encoding='utf-8') as file:
|
||||
version = file.read().strip()
|
||||
except UnicodeDecodeError as e:
|
||||
raise VersionFileError(
|
||||
'The local version file must only contain UTF-8 characters.') from e
|
||||
except IOError as e:
|
||||
raise VersionFileError('Failed to check local version: %s' %
|
||||
str(e)) from e
|
||||
if version == '':
|
||||
raise VersionFileError('The local version file cannot be empty.')
|
||||
return version
|
||||
|
||||
|
||||
def latest_version():
|
||||
"""Requests the latest version from the TinyPilot Gatekeeper REST API.
|
||||
|
||||
Returns:
|
||||
A version string (e.g., "abc1234" or "1.2.3").
|
||||
|
||||
Raises:
|
||||
VersionRequestError: If an error occurred while making an HTTP request
|
||||
to the Gatekeeper API.
|
||||
"""
|
||||
try:
|
||||
return git.remote_head_commit_id()
|
||||
except git.Error as e:
|
||||
raise GitError('Failed to check latest available version: %s' %
|
||||
str(e)) from e
|
||||
with urllib.request.urlopen(
|
||||
'https://gk.tinypilotkvm.com/community/available-update',
|
||||
timeout=10) as response:
|
||||
response_bytes = response.read()
|
||||
except urllib.error.URLError as e:
|
||||
raise VersionRequestError(
|
||||
'Failed to request latest available version: %s' % str(e)) from e
|
||||
|
||||
try:
|
||||
response_text = response_bytes.decode('utf-8')
|
||||
except UnicodeDecodeError as e:
|
||||
raise VersionRequestError(
|
||||
'Failed to decode latest available version response body as UTF-8'
|
||||
' characters.') from e
|
||||
|
||||
try:
|
||||
response_dict = json.loads(response_text)
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
raise VersionRequestError(
|
||||
'Failed to decode latest available version response body as JSON.'
|
||||
) from e
|
||||
|
||||
if not isinstance(response_dict, dict):
|
||||
raise VersionRequestError(
|
||||
'Failed to decode latest available version response body as a JSON'
|
||||
' dictionary.')
|
||||
|
||||
if 'version' not in response_dict:
|
||||
raise VersionRequestError(
|
||||
'Failed to get latest available version because of a missing field:'
|
||||
' version')
|
||||
|
||||
return response_dict['version']
|
||||
|
||||
134
app/version_test.py
Normal file
134
app/version_test.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import json
|
||||
import tempfile
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from unittest import TestCase
|
||||
from unittest import mock
|
||||
|
||||
import version
|
||||
|
||||
|
||||
class VersionTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# Run all unit tests with debug mode disabled.
|
||||
is_debug_patch = mock.patch.object(version,
|
||||
'_is_debug',
|
||||
return_value=False)
|
||||
self.addCleanup(is_debug_patch.stop)
|
||||
is_debug_patch.start()
|
||||
|
||||
def test_local_version_when_file_exists(self):
|
||||
with tempfile.NamedTemporaryFile('w',
|
||||
encoding='utf-8') as mock_version_file:
|
||||
mock_version_file.write('1234567')
|
||||
mock_version_file.flush()
|
||||
|
||||
with mock.patch.object(version, '_VERSION_FILE',
|
||||
mock_version_file.name):
|
||||
self.assertEqual('1234567', version.local_version())
|
||||
|
||||
def test_local_version_strips_leading_trailing_whitespace(self):
|
||||
with tempfile.NamedTemporaryFile('w',
|
||||
encoding='utf-8') as mock_version_file:
|
||||
mock_version_file.write(' 1234567 \n')
|
||||
mock_version_file.flush()
|
||||
|
||||
with mock.patch.object(version, '_VERSION_FILE',
|
||||
mock_version_file.name):
|
||||
self.assertEqual('1234567', version.local_version())
|
||||
|
||||
def test_local_version_raises_file_error_when_file_doesnt_exist(self):
|
||||
with mock.patch.object(version, '_VERSION_FILE',
|
||||
'non-existent-file.txt'):
|
||||
with self.assertRaises(version.VersionFileError):
|
||||
version.local_version()
|
||||
|
||||
def test_local_version_raises_file_error_when_file_is_empty(self):
|
||||
with tempfile.NamedTemporaryFile('w',
|
||||
encoding='utf-8') as mock_version_file:
|
||||
|
||||
with mock.patch.object(version, '_VERSION_FILE',
|
||||
mock_version_file.name):
|
||||
with self.assertRaises(version.VersionFileError):
|
||||
version.local_version()
|
||||
|
||||
def test_local_version_raises_file_error_when_file_is_not_utf_8(self):
|
||||
with tempfile.NamedTemporaryFile() as mock_version_file:
|
||||
mock_version_file.write(b'\xff')
|
||||
mock_version_file.flush()
|
||||
|
||||
with mock.patch.object(version, '_VERSION_FILE',
|
||||
mock_version_file.name):
|
||||
with self.assertRaises(version.VersionFileError):
|
||||
version.local_version()
|
||||
|
||||
@mock.patch.object(urllib.request, 'urlopen')
|
||||
def test_latest_version_when_request_is_successful(self, mock_urlopen):
|
||||
mock_response = mock.Mock()
|
||||
mock_response.read.return_value = json.dumps({
|
||||
'version': '1234567'
|
||||
}).encode('utf-8')
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
self.assertEqual('1234567', version.latest_version())
|
||||
|
||||
@mock.patch.object(urllib.request, 'urlopen')
|
||||
def test_latest_version_raises_request_error_when_response_is_not_utf_8(
|
||||
self, mock_urlopen):
|
||||
mock_response = mock.Mock()
|
||||
mock_response.read.return_value = b'\xff'
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
with self.assertRaises(version.VersionRequestError):
|
||||
version.latest_version()
|
||||
|
||||
@mock.patch.object(urllib.request, 'urlopen')
|
||||
def test_latest_version_raises_request_error_when_response_is_not_json(
|
||||
self, mock_urlopen):
|
||||
mock_response = mock.Mock()
|
||||
mock_response.read.return_value = 'plain text'.encode('utf-8')
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
with self.assertRaises(version.VersionRequestError):
|
||||
version.latest_version()
|
||||
|
||||
@mock.patch.object(urllib.request, 'urlopen')
|
||||
def test_latest_version_raises_request_error_when_response_is_not_json_dict(
|
||||
self, mock_urlopen):
|
||||
mock_response = mock.Mock()
|
||||
mock_response.read.return_value = json.dumps(
|
||||
'json encoded string').encode('utf-8')
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
with self.assertRaises(version.VersionRequestError):
|
||||
version.latest_version()
|
||||
|
||||
@mock.patch.object(urllib.request, 'urlopen')
|
||||
def test_latest_version_raises_request_error_when_response_missing_field(
|
||||
self, mock_urlopen):
|
||||
mock_response = mock.Mock()
|
||||
mock_response.read.return_value = json.dumps({
|
||||
'wrong_field_name': 'wrong_field_value'
|
||||
}).encode('utf-8')
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
with self.assertRaises(version.VersionRequestError):
|
||||
version.latest_version()
|
||||
|
||||
@mock.patch.object(urllib.request, 'urlopen')
|
||||
def test_latest_version_raises_request_error_when_request_fails(
|
||||
self, mock_urlopen):
|
||||
mock_urlopen.side_effect = urllib.error.URLError(
|
||||
'dummy error from gatekeeper', None)
|
||||
|
||||
with self.assertRaises(version.VersionRequestError):
|
||||
version.latest_version()
|
||||
|
||||
|
||||
class DebugModeVersionTest(TestCase):
|
||||
|
||||
def test_local_version_returns_dummy_version_when_in_debug_mode(self):
|
||||
# Enable debug mode.
|
||||
with mock.patch.object(version, '_is_debug', return_value=True):
|
||||
self.assertEqual('0000000', version.local_version())
|
||||
@@ -1,3 +1,4 @@
|
||||
[defaults]
|
||||
remote_user = root
|
||||
roles_path = ./roles
|
||||
interpreter_python = /usr/bin/python3
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# Minimal set of dependencies to bootstrap an Ansible virtualenv.
|
||||
# When modifying dependencies, update the credit in /app/license_notice.py
|
||||
|
||||
ansible==2.9.10
|
||||
cffi==1.14.4
|
||||
cryptography==35.0.0
|
||||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
pycparser==2.20
|
||||
pyOpenSSL==20.0.1
|
||||
PyYAML==5.3.1
|
||||
six==1.15.0
|
||||
# Direct dependencies.
|
||||
ansible==2.10.7
|
||||
|
||||
# Indirect dependencies.
|
||||
ansible-base==2.10.17
|
||||
cffi==1.15.1
|
||||
cryptography==37.0.4
|
||||
Jinja2==3.1.2
|
||||
MarkupSafe==2.1.1
|
||||
packaging==21.3
|
||||
pycparser==2.21
|
||||
pyparsing==3.0.9
|
||||
PyYAML==6.0
|
||||
|
||||
@@ -15,7 +15,7 @@ set -u
|
||||
set -x
|
||||
|
||||
readonly ANSIBLE_ROLE_TINYPILOT_REPO='https://github.com/tiny-pilot/ansible-role-tinypilot'
|
||||
readonly ANSIBLE_ROLE_TINYPILOT_VERSION='master'
|
||||
readonly ANSIBLE_ROLE_TINYPILOT_VERSION='update-overhaul'
|
||||
|
||||
readonly BUNDLE_DIR='bundle'
|
||||
readonly OUTPUT_DIR='dist'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mtlynch/debian10-ansible:2.9.13
|
||||
FROM geerlingguy/docker-debian10-ansible
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install \
|
||||
|
||||
@@ -32,6 +32,6 @@ docker run \
|
||||
--name "$CONTAINER_NAME" \
|
||||
"$IMAGE_NAME"
|
||||
|
||||
docker exec "$CONTAINER_NAME" ./quick-install
|
||||
docker exec "$CONTAINER_NAME" ./get-tinypilot.sh
|
||||
|
||||
docker exec "$CONTAINER_NAME" ./e2e/test
|
||||
|
||||
203
quick-install
203
quick-install
@@ -1,199 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
{ # Prevent the script from executing until the client downloads the full file.
|
||||
echo "This script is now DEPRECATED." >&2
|
||||
echo "Please switch to https://raw.githubusercontent.com/tiny-pilot/tinypilot/master/get-tinypilot.sh" >&2
|
||||
|
||||
# Exit on first error.
|
||||
set -e
|
||||
|
||||
# Echo commands to stdout.
|
||||
set -x
|
||||
|
||||
#######################################
|
||||
# Adds a setting to the YAML settings file, if it's not yet defined.
|
||||
# Globals:
|
||||
# TINYPILOT_SETTINGS_FILE, a path.
|
||||
# Arguments:
|
||||
# Key to define.
|
||||
# Value to set.
|
||||
# Outputs:
|
||||
# The line appended to the settings file, if the variable wasn't yet defined.
|
||||
#######################################
|
||||
add_setting_if_undefined() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
if ! grep --silent "^${key}:" "${TINYPILOT_SETTINGS_FILE}"; then
|
||||
echo "${key}: ${value}" | tee --append "${TINYPILOT_SETTINGS_FILE}"
|
||||
fi
|
||||
}
|
||||
|
||||
readonly DEFAULT_TINYPILOT_SETTINGS_FILE="/home/tinypilot/settings.yml"
|
||||
|
||||
if [[ -n "${TINYPILOT_INSTALL_VARS}" ]]; then
|
||||
echo "TINYPILOT_INSTALL_VARS is no longer supported." >&2
|
||||
echo "Please specify extra settings via the ${DEFAULT_TINYPILOT_SETTINGS_FILE} file." >&2
|
||||
exit 255
|
||||
fi
|
||||
|
||||
# Treat undefined environment variables as errors.
|
||||
set -u
|
||||
|
||||
# Prevent installation on the 64-bit version of Raspberry Pi OS.
|
||||
# https://github.com/tiny-pilot/tinypilot/issues/929
|
||||
if [[ "$(uname -m)" == 'aarch64' && "$(lsb_release --id --short)" == 'Debian' ]]; then
|
||||
echo '64-bit Raspberry Pi OS is not yet supported.' >&2
|
||||
echo 'Please use the 32-bit version of Raspberry Pi OS.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create a temporary settings file that will be used throughout this script to
|
||||
# avoid repeatedly using sudo.
|
||||
# HACK: If we let mktemp use the default /tmp directory, the system purges the file
|
||||
# before the end of the script for some reason. We use /var/tmp as a workaround.
|
||||
readonly TINYPILOT_SETTINGS_FILE="$(mktemp --tmpdir="/var/tmp" --suffix ".yml")"
|
||||
# Check if there's already a settings file with extra installation settings.
|
||||
if [[ -f "${DEFAULT_TINYPILOT_SETTINGS_FILE}" ]]; then
|
||||
echo "Using settings file at: ${DEFAULT_TINYPILOT_SETTINGS_FILE}"
|
||||
sudo cp "${DEFAULT_TINYPILOT_SETTINGS_FILE}" "${TINYPILOT_SETTINGS_FILE}"
|
||||
else
|
||||
echo "No pre-existing settings file found at: ${DEFAULT_TINYPILOT_SETTINGS_FILE}"
|
||||
fi
|
||||
readonly EXTRA_VARS_PATH="@${TINYPILOT_SETTINGS_FILE}"
|
||||
|
||||
# Set default installation settings
|
||||
add_setting_if_undefined "ustreamer_port" "8001"
|
||||
add_setting_if_undefined "ustreamer_persistent" "true"
|
||||
|
||||
# Check if this system uses the TC358743 HDMI to CSI capture bridge.
|
||||
USE_TC358743_DEFAULTS=''
|
||||
if grep --silent "^ustreamer_capture_device:" "${TINYPILOT_SETTINGS_FILE}"; then
|
||||
if grep --silent "^ustreamer_capture_device: tc358743$" "${TINYPILOT_SETTINGS_FILE}"; then
|
||||
USE_TC358743_DEFAULTS='y'
|
||||
fi
|
||||
# Only check the existing config file if user has not set
|
||||
# ustreamer_capture_device install variable.
|
||||
elif [ -f /home/ustreamer/config.yml ] && grep --silent 'capture_device: "tc358743"' /home/ustreamer/config.yml; then
|
||||
USE_TC358743_DEFAULTS='y'
|
||||
fi
|
||||
|
||||
if [[ "$USE_TC358743_DEFAULTS" == 'y' ]]; then
|
||||
add_setting_if_undefined "ustreamer_encoder" "omx"
|
||||
add_setting_if_undefined "ustreamer_format" "uyvy"
|
||||
add_setting_if_undefined "ustreamer_workers" "3"
|
||||
add_setting_if_undefined "ustreamer_use_dv_timings" "true"
|
||||
add_setting_if_undefined "ustreamer_drop_same_frames" "30"
|
||||
else
|
||||
# If this system does not use a TC358743 capture chip, assume defaults for a
|
||||
# MacroSilicon MS2109-based HDMI-to-USB capture dongle.
|
||||
add_setting_if_undefined "ustreamer_encoder" "hw"
|
||||
add_setting_if_undefined "ustreamer_format" "jpeg"
|
||||
add_setting_if_undefined "ustreamer_resolution" "1920x1080"
|
||||
fi
|
||||
|
||||
echo "Final install settings:"
|
||||
cat "${TINYPILOT_SETTINGS_FILE}"
|
||||
|
||||
# Check if the user is accidentally downgrading from TinyPilot Pro.
|
||||
HAS_PRO_INSTALLED=0
|
||||
|
||||
SCRIPT_DIR="$(dirname "$0")"
|
||||
# If they're piping this script in from stdin, guess that TinyPilot is
|
||||
# in the default location.
|
||||
if [ "$SCRIPT_DIR" = "." ]; then
|
||||
SCRIPT_DIR="/opt/tinypilot"
|
||||
fi
|
||||
|
||||
# Detect TinyPilot Pro if the README file has a TinyPilot Pro header.
|
||||
TINYPILOT_README="${SCRIPT_DIR}/README.md"
|
||||
if [ -f "$TINYPILOT_README" ]; then
|
||||
if [ "$(head -n 1 $TINYPILOT_README)" = "# TinyPilot Pro" ]; then
|
||||
HAS_PRO_INSTALLED=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$HAS_PRO_INSTALLED" = 1 ]; then
|
||||
set +u # Don't exit if FORCE_DOWNGRADE is unset.
|
||||
if [ "$FORCE_DOWNGRADE" = 1 ]; then
|
||||
echo "Downgrading from TinyPilot Pro to TinyPilot Community Edition"
|
||||
set -u
|
||||
else
|
||||
set +x
|
||||
printf "You are trying to downgrade from TinyPilot Pro to TinyPilot "
|
||||
printf "Community Edition.\n\n"
|
||||
printf "You probably want to update to the latest version of TinyPilot "
|
||||
printf "Pro instead:\n\n"
|
||||
printf " /opt/tinypilot/scripts/upgrade && sudo reboot\n"
|
||||
printf "\n"
|
||||
printf "If you *really* want to downgrade to TinyPilot Community Edition, "
|
||||
printf "type the following:\n\n"
|
||||
printf " export FORCE_DOWNGRADE=1\n\n"
|
||||
printf "And then run your previous command again.\n"
|
||||
exit 255
|
||||
fi
|
||||
fi
|
||||
|
||||
sudo apt-get update --allow-releaseinfo-change-suite
|
||||
sudo apt-get install -y \
|
||||
git \
|
||||
libffi-dev \
|
||||
libssl-dev \
|
||||
python3-dev \
|
||||
python3-venv
|
||||
|
||||
INSTALLER_DIR="/opt/tinypilot-updater"
|
||||
sudo mkdir -p "$INSTALLER_DIR"
|
||||
sudo chown "$(whoami):$(whoami)" --recursive "$INSTALLER_DIR"
|
||||
pushd "$INSTALLER_DIR"
|
||||
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
# Ensure we're using a version of pip that can use binary wheels where available
|
||||
# instead of building the packages locally.
|
||||
pip install "pip>=21.3.1"
|
||||
|
||||
# When modifying dependencies, update the credit in app/license_notice.py.
|
||||
echo 'ansible==2.9.10
|
||||
cffi==1.14.4
|
||||
cryptography==35.0.0
|
||||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
pycparser==2.20
|
||||
pyOpenSSL==20.0.1
|
||||
PyYAML==5.3.1
|
||||
six==1.15.0' > requirements.txt
|
||||
pip install -r requirements.txt
|
||||
echo "[defaults]
|
||||
roles_path = $PWD
|
||||
interpreter_python = /usr/bin/python3
|
||||
" > ansible.cfg
|
||||
|
||||
TINYPILOT_ROLE_NAME="ansible-role-tinypilot"
|
||||
if [ -d "$TINYPILOT_ROLE_NAME" ]; then
|
||||
pushd "$TINYPILOT_ROLE_NAME"
|
||||
git pull origin master
|
||||
popd
|
||||
else
|
||||
TINYPILOT_ROLE_REPO="https://github.com/tiny-pilot/ansible-role-tinypilot.git"
|
||||
git clone "$TINYPILOT_ROLE_REPO" "$TINYPILOT_ROLE_NAME"
|
||||
fi
|
||||
|
||||
ansible-galaxy install \
|
||||
--force \
|
||||
--role-file "${TINYPILOT_ROLE_NAME}/requirements.yml"
|
||||
|
||||
echo "- hosts: localhost
|
||||
connection: local
|
||||
become: true
|
||||
become_method: sudo
|
||||
roles:
|
||||
- role: $TINYPILOT_ROLE_NAME" > install.yml
|
||||
ansible-playbook -i localhost, install.yml \
|
||||
--extra-vars "${EXTRA_VARS_PATH}"
|
||||
|
||||
# Copy the final install settings used in this installation back to the default
|
||||
# settings location.
|
||||
chmod +r "${TINYPILOT_SETTINGS_FILE}"
|
||||
sudo cp "${TINYPILOT_SETTINGS_FILE}" "${DEFAULT_TINYPILOT_SETTINGS_FILE}"
|
||||
sudo chown tinypilot:tinypilot "${DEFAULT_TINYPILOT_SETTINGS_FILE}"
|
||||
|
||||
|
||||
} # Prevent the script from executing until the client downloads the full file.
|
||||
curl \
|
||||
--silent \
|
||||
--show-error \
|
||||
https://raw.githubusercontent.com/tiny-pilot/tinypilot/master/get-tinypilot.sh | \
|
||||
bash -
|
||||
|
||||
Reference in New Issue
Block a user