Skip to content

Commit

Permalink
feat: install headless satellite by extracting electron builds (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian authored Feb 22, 2024
1 parent feaa5e5 commit 2a60e94
Show file tree
Hide file tree
Showing 12 changed files with 929 additions and 34 deletions.
32 changes: 19 additions & 13 deletions .github/workflows/node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ jobs:
runs-on: ubuntu-latest
name: build image

# only run for main
if: github.ref == 'refs/heads/main'

needs:
- Linux-arm64

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
Expand All @@ -309,6 +315,18 @@ jobs:
with:
fetch-depth: 0

- name: Determine files to upload
id: filenames
shell: bash
run: |
HASH=$(git rev-parse --short HEAD)
COUNT=$(git rev-list --count HEAD)
VERSION=$(node -e "console.log(require('./package.json').version)")
echo "sourcename=pi-image/output-satellitepi/image.gz" >> $GITHUB_OUTPUT
echo "targetname=companion-satellite-pi-${COUNT}-${HASH}.img.gz" >> $GITHUB_OUTPUT
echo "longversion=${VERSION}+${COUNT}-${HASH}" >> $GITHUB_OUTPUT
- name: install packer
run: |
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
Expand All @@ -319,7 +337,7 @@ jobs:
run: |
cd pi-image
sudo packer init satellitepi.pkr.hcl
sudo packer build --var branch=${GITHUB_REF_NAME} satellitepi.pkr.hcl
sudo packer build --var branch=${GITHUB_REF_NAME} --var "build=${{ steps.filenames.outputs.longversion }}" satellitepi.pkr.hcl
env:
PACKER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand All @@ -335,18 +353,6 @@ jobs:
sudo gzip -n image
- name: Determine files to upload
id: filenames
shell: bash
run: |
HASH=$(git rev-parse --short HEAD)
COUNT=$(git rev-list --count HEAD)
VERSION=$(node -e "console.log(require('./package.json').version)")
echo "sourcename=pi-image/output-satellitepi/image.gz" >> $GITHUB_OUTPUT
echo "targetname=companion-satellite-pi-${COUNT}-${HASH}.img.gz" >> $GITHUB_OUTPUT
echo "longversion=${VERSION}+${COUNT}-${HASH}" >> $GITHUB_OUTPUT
- name: Upload build
uses: bitfocus/actions/upload-and-notify@main
with:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ No images are provided for this, but the process has been written to be a single
As root, run the following:

```
curl https://raw.githubusercontent.com/bitfocus/companion-satellite/main/pi-image/install.sh | sh
curl https://raw.githubusercontent.com/bitfocus/companion-satellite/main/pi-image/install.sh | bash
```

After this, you can use `sudo satellite-update` to change the version it has installed. Note: this is currently not fully implemented.
Expand Down
3 changes: 2 additions & 1 deletion pi-image/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
output-*
output-*
*/.yarn/
30 changes: 29 additions & 1 deletion pi-image/install.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
#!/usr/bin/env bash
set -e

if [ ! "$BASH_VERSION" ] ; then
echo "You must use bash to run this script. If running this script from curl, make sure the final word is 'bash'" 1>&2
exit 1
fi

CURRENT_ARCH=$(dpkg --print-architecture)
if [[ "$CURRENT_ARCH" != "x64" && "$CURRENT_ARCH" != "amd64" && "$CURRENT_ARCH" != "arm64" ]]; then
echo "$CURRENT_ARCH is not a supported cpu architecture for running Companion Satellite."
echo "If you are running on an arm device (such as a Raspberry Pi), make sure to use an arm64 image."
exit 1
fi

echo "This will attempt to install Companion Satellite as a system service on this device."
echo "It is designed to be run on headless servers, but can be used on desktop machines if you are happy to not have the tray icon."
echo "A user called 'satellite' will be created to run the service, and various scripts will be installed to manage the service"
Expand All @@ -10,6 +22,10 @@ if [ $(/usr/bin/id -u) -ne 0 ]; then
exit 1
fi

# Install a specific stable build. It is advised to not use this, as attempting to install a build that doesn't
# exist can leave your system in a broken state that needs fixing manually
SATELLITE_BUILD="${SATELLITE_BUILD:-beta}"
# Development only: Allow building using a testing branch of this updater
SATELLITE_BRANCH="${SATELLITE_BRANCH:-main}"

# add a system user
Expand All @@ -36,8 +52,13 @@ cd /usr/local/src/companion-satellite
# configure git for future updates
git config --global pull.rebase false


# run the update script
./pi-image/update.sh $SATELLITE_BRANCH
if [ "$SATELLITE_BRANCH" == "main" ]; then
./pi-image/update.sh beta "$SATELLITE_BUILD"
else
./pi-image/update.sh stable "$SATELLITE_BUILD"
fi

# enable start on boot
systemctl enable satellite
Expand All @@ -49,5 +70,12 @@ cp ./pi-image/satellite-config /boot/satellite-config
# TODO - verify permissions
echo "export PATH=/opt/fnm/aliases/default/bin:\$PATH" >> /home/satellite/.bashrc

# check that a build of satellite was installed
if [ ! -d "/opt/companion-satellite" ]
then
echo "No Companion Satellite build was installed!\nIt should be possible to recover from this with \"sudo satellite-update\""
exit 9999 # die with error code 9999
fi

echo "Companion Satellite is installed!"
echo "You should edit the configuration file at \"/boot/satellite-config\" then can start it with \"sudo systemctl start satellite\" or \"sudo satellite-update\""
6 changes: 3 additions & 3 deletions pi-image/satellite.service
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Wants=network-online.target
[Service]
Type=simple
User=satellite
WorkingDirectory=/usr/local/src/companion-satellite
ExecStartPre=+/opt/fnm/aliases/default/bin/node /usr/local/src/companion-satellite/satellite/dist/fixup-pi-config.js /home/satellite/satellite-config.json
ExecStart=/opt/fnm/aliases/default/bin/node /usr/local/src/companion-satellite/satellite/dist/main.js /home/satellite/satellite-config.json
WorkingDirectory=/opt/companion-satellite/satellite
ExecStartPre=+/opt/fnm/aliases/default/bin/node /opt/companion-satellite/satellite/dist/fixup-pi-config.js /home/satellite/satellite-config.json
ExecStart=/opt/fnm/aliases/default/bin/node /opt/companion-satellite/satellite/dist/main.js /home/satellite/satellite-config.json
Restart=on-failure
KillSignal=SIGINT
TimeoutStopSec=60
Expand Down
6 changes: 6 additions & 0 deletions pi-image/satellitepi.pkr.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ variable "branch" {
default = "main"
}

variable "build" {
type = string
default = "beta"
}

source "arm-image" "satellitepi" {
iso_checksum = "sha256:9ce5e2c8c6c7637cd2227fdaaf0e34633e6ebedf05f1c88e00f833cbb644db4b"
iso_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"
Expand Down Expand Up @@ -48,6 +53,7 @@ build {

# run the script
"export SATELLITE_BRANCH=${var.branch}",
"export SATELLITE_BUILD=${var.build}",
"chmod +x /tmp/install.sh",
"/tmp/install.sh"
]
Expand Down
1 change: 1 addition & 0 deletions pi-image/update-prompt/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
176 changes: 176 additions & 0 deletions pi-image/update-prompt/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// @ts-check
import semver from 'semver'
import inquirer from 'inquirer'
import fs from 'fs'

const ALLOWED_VERSIONS = '^1.5.0'

let currentVersion
try {
currentVersion = fs.readFileSync('/opt/companion-satellite/BUILD').toString().trim()
} catch (e) {
// Assume none installed
}

async function getLatestBuildsForBranch(branch, targetCount) {
targetCount *= 10 // HACK until the api changes
// eslint-disable-next-line no-undef
const data = await fetch(
`https://api.bitfocus.io/v1/product/companion-satellite/packages?branch=${branch}&limit=${targetCount}`,
)
const jsonData = await data.json()

// TODO - make sure this is durable
let target = `${process.platform}-${process.arch}-tgz`
if (target === 'linux-x64-tgz') target = 'linux-tgz'

// console.log('searching for', target, 'in', data.data.packages)

// assume the builds are sorted by date already
const result = []
for (const pkg of jsonData.packages) {
if (pkg.target === target) {
try {
if (semver.satisfies(pkg.version, ALLOWED_VERSIONS, { includePrerelease: true })) {
result.push({
name: pkg.version,
uri: pkg.uri,
published: new Date(pkg.published),
})
}
} catch (e) {
// Not a semver tag, so ignore
}
}
}

return result
}

async function selectBuildOfType(type, targetBuild) {
const candidates = await getLatestBuildsForBranch(type, 1)
const selectedBuild = targetBuild ? candidates.find((c) => c.name == targetBuild) : candidates[0]
if (selectedBuild) {
if (selectedBuild.name === currentVersion) {
console.log(`The latest build of ${type} (${selectedBuild.name}) is already installed`)
} else {
console.log(`Selected ${type}: ${selectedBuild.name}`)
fs.writeFileSync('/tmp/satellite-version-selection', selectedBuild.uri)
fs.writeFileSync('/tmp/satellite-version-selection-name', selectedBuild.name)
}
} else {
console.error(`No matching ${type} build was found!`)
}
}
async function chooseOfType(type) {
const candidates = await getLatestBuildsForBranch(type, 10)

if (candidates.length === 0) {
console.error(`No ${type} build was found!`)
} else {
const selectedBuild = await inquirer.prompt([
{
type: 'list',
name: 'ref',
message: 'Which version do you want? ',
choices: [...candidates.map((c) => c.name), 'cancel'],
},
])

if (selectedBuild.ref && selectedBuild.ref !== 'cancel') {
if (selectedBuild.ref === currentVersion) {
const confirm = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `Build "${currentVersion}" is already installed. Do you wish to reinstall it?`,
},
])
if (!confirm.confirm) {
return
}
}

const build = candidates.find((c) => c.name === selectedBuild.ref)
if (build) {
console.log(`Selected ${type}: ${build.name}`)
fs.writeFileSync('/tmp/satellite-version-selection', build.uri)
fs.writeFileSync('/tmp/satellite-version-selection-name', build.name)
} else {
console.error('Invalid selection!')
}
} else {
console.error('No version was selected!')
}
}
}

async function runPrompt() {
console.log('Warning: Downgrading to an older version can cause issues with the database not being compatible')

let isOnBeta = true

console.log(`You are currently on "${currentVersion || 'Unknown'}"`)

// TODO - restore this
// if (currentBranch) {
// console.log(`You are currently on branch: ${currentBranch}`)
// } else if (currentTag) {
// console.log(`You are currently on release: ${currentTag}`)
// } else {
// console.log('Unable to determine your current version')
// }

const answer = await inquirer.prompt([
{
type: 'list',
name: 'ref',
message: 'What version do you want? ',
choices: ['latest stable', 'latest beta', 'specific stable', 'specific beta', 'custom-url', 'cancel'],
default: isOnBeta ? 'latest beta' : 'latest stable',
},
])

if (answer.ref === 'custom-url') {
console.log(
'Warning: This must be an linux build of Companion for the correct architecture, or companion will not be able to launch afterwards',
)
const answer = await inquirer.prompt([
{
type: 'input',
name: 'url',
message: 'What build url?',
},
])

const confirm = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `Are you sure you to download the build "${answer.url}"?\nMake sure you trust the source.\nIf you don't know what you are doing you could break your SatellitePi installation`,
},
])
if (!confirm.confirm) {
return runPrompt()
} else {
fs.writeFileSync('/tmp/satellite-version-selection', answer.url)
fs.writeFileSync('/tmp/satellite-version-selection-name', '')
}
} else if (!answer.ref || answer.ref === 'cancel') {
console.error('No version was selected!')
} else if (answer.ref === 'latest beta') {
selectBuildOfType('beta')
} else if (answer.ref === 'latest stable') {
selectBuildOfType('stable')
} else if (answer.ref === 'specific beta') {
chooseOfType('beta')
} else if (answer.ref === 'specific stable') {
chooseOfType('stable')
}
}

if (process.argv[2]) {
selectBuildOfType(process.argv[2], process.argv[3])
} else {
runPrompt()
}
15 changes: 15 additions & 0 deletions pi-image/update-prompt/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "update-prompt",
"version": "1.0.0",
"main": "main.js",
"type": "module",
"license": "MIT",
"dependencies": {
"inquirer": "^9.2.14",
"semver": "^7.6.0"
},
"packageManager": "[email protected]",
"engines": {
"node": ">=18.18"
}
}
Loading

0 comments on commit 2a60e94

Please sign in to comment.