generated from compsoc-edinburgh/deployment
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d215556
Showing
16 changed files
with
1,093 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
SUBDOMAIN=example |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
name: Docker | ||
|
||
on: | ||
workflow_dispatch: | ||
push: | ||
# Publish `main` as Docker `latest` image. | ||
branches: | ||
- main | ||
|
||
jobs: | ||
# Push image to GitHub Packages. | ||
# See also https://docs.docker.com/docker-hub/builds/ | ||
push: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- name: Build image | ||
run: | | ||
source .env | ||
docker build . --file Dockerfile --tag service-$SUBDOMAIN | ||
- name: Log into registry | ||
run: echo "${{ secrets.PAT }}" | docker login ghcr.io -u compsoc-service --password-stdin | ||
|
||
- name: Push image | ||
run: | | ||
source .env | ||
IMAGE_ID=ghcr.io/compsoc-edinburgh/service-$SUBDOMAIN | ||
docker tag service-$SUBDOMAIN $IMAGE_ID:latest | ||
docker tag service-$SUBDOMAIN $IMAGE_ID:${{ github.sha }} | ||
docker push $IMAGE_ID:${{ github.sha }} | ||
docker push $IMAGE_ID:latest | ||
- name: Trigger update | ||
run: | | ||
curl -H "Token: ${{ secrets.WATCHTOWER_TOKEN }}" https://watchtower.dev.comp-soc.com/v1/update |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
scripts | ||
.secrets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
FROM node:15.0.1-alpine | ||
|
||
WORKDIR /app | ||
|
||
COPY package*.json ./ | ||
|
||
RUN npm install | ||
|
||
COPY . . | ||
|
||
EXPOSE ${PORT} | ||
|
||
CMD [ "npm", "run", "start" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# Deploying a CompSoc service | ||
|
||
So you want to run a CompSoc service? Wonderful! You'll need to be part of the `SigWeb` team on GitHub for this to work, and will need to have added your SSH keys to GitHub. An implementation detail here is that keys are synced at most every 30 minutes, so you may need to wait that long after being added to the team. If you're not part of the team, but would _like_ to be, file an issue on the template repository. | ||
|
||
It's pretty simple to get started with a new service, just follow these steps: | ||
|
||
1. Use this template repository to create a template within `compsoc-edinburgh` where your app will live. | ||
2. Clone it | ||
3. Should your app be committee only? That's the default behaviour, but if you want it to be public delete line `28` in the `makefile` | ||
4. Modify `.env` with your app's options | ||
|
||
- `SUBDOMAIN=...`: Which subdomain of `dev.comp-soc.com` should this app run on. This should be a valid subdomain string. | ||
|
||
5. Add, commit, and push | ||
6. You'll need to enable GitHub Actions for the repository, and you'll need to trigger a manual run of the `Deploy` action | ||
7. Run `make initialise` | ||
8. And you're done! This template presumes a NodeJS app, but you'd be able to use any technologies to build your app, as long as: | ||
1. You can package it in a _single_ docker container | ||
2. It listens on at most _one_ port, specified by the `$PORT` environment variable | ||
|
||
Your app is now setup with deployment on push, automatic HTTPS, and (unless you diabled it) is behind CompSoc's GSuite authentication layer. | ||
|
||
# FAQ | ||
|
||
_I mean, these aren't frequently asked **yet**, but they may be in future_ | ||
|
||
## Can I use cool stuff like databases? | ||
|
||
Absolutely! Postgres is automatically supported (check the `$DATABASE_URL` environment variable for the connection string), and other can be supported as and when needed (just file an issue in the template repository) | ||
|
||
## What about secrets? I don't want those in `git`! | ||
|
||
You absolutely don't, which is why secrets management is built in. When you run `make initialise`, a directory `.secrets` will be created, which isn't checked in to git. Everything in that directory is available to your running application at the path `/secrets`. To re-sync secrets after you've changed the contents of `.secrets`, just run `make sync-secrets`. You can stick anything in here, from `json` config files to private signing keys. | ||
|
||
## Object storage support? | ||
|
||
Yep, check the `$FILE_UPLOAD` environment variable. It contains a URL that you can send a `GET` request to, with a `Content-Type` header set to indicate your file's type. The response will be: | ||
|
||
```json | ||
{ | ||
"upload_url": "https://...", // A signed upload URL that you can use yourself, or pass to a client | ||
"accessible_at": "https://cdn.comp-soc.com/..." // The URL at which your blob will be accessible once uploaded — to be stored in your database | ||
} | ||
``` | ||
|
||
## What if my app crashes in production? | ||
|
||
No worries, you can sort it — CompSoc believes in you! Just run `make tail` to stream the current production logs from your app into your terminal, or `make logs` to get a dump of all your app's logs since initialisation (note, this can be a _lot_ of data, so use `make tail` where you can). | ||
|
||
## I have another question? | ||
|
||
File an issue in the template repository, and we'll get to it as soon as possible. | ||
|
||
## I'd like to develop locally | ||
|
||
That's a good shout. You will need to have `docker` and `docker-compose` installed, but after that a simple `make dev` should get you running locally. Well, it will at _some_ point anyway — it doesn't quite yet... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
var createError = require('http-errors'); | ||
var express = require('express'); | ||
var path = require('path'); | ||
var cookieParser = require('cookie-parser'); | ||
var logger = require('morgan'); | ||
|
||
var indexRouter = require('./routes/index'); | ||
var usersRouter = require('./routes/users'); | ||
|
||
var app = express(); | ||
|
||
// view engine setup | ||
app.set('views', path.join(__dirname, 'views')); | ||
app.set('view engine', 'jade'); | ||
|
||
app.use(logger('dev')); | ||
app.use(express.json()); | ||
app.use(express.urlencoded({ extended: false })); | ||
app.use(cookieParser()); | ||
app.use(express.static(path.join(__dirname, 'public'))); | ||
|
||
app.use('/', indexRouter); | ||
app.use('/users', usersRouter); | ||
|
||
// catch 404 and forward to error handler | ||
app.use(function(req, res, next) { | ||
next(createError(404)); | ||
}); | ||
|
||
// error handler | ||
app.use(function(err, req, res, next) { | ||
// set locals, only providing error in development | ||
res.locals.message = err.message; | ||
res.locals.error = req.app.get('env') === 'development' ? err : {}; | ||
|
||
// render the error page | ||
res.status(err.status || 500); | ||
res.render('error'); | ||
}); | ||
|
||
module.exports = app; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#!/usr/bin/env node | ||
|
||
/** | ||
* Module dependencies. | ||
*/ | ||
|
||
var app = require('../app'); | ||
var debug = require('debug')('deploy-bot:server'); | ||
var http = require('http'); | ||
|
||
/** | ||
* Get port from environment and store in Express. | ||
*/ | ||
|
||
var port = normalizePort(process.env.PORT || '3000'); | ||
app.set('port', port); | ||
|
||
/** | ||
* Create HTTP server. | ||
*/ | ||
|
||
var server = http.createServer(app); | ||
|
||
/** | ||
* Listen on provided port, on all network interfaces. | ||
*/ | ||
|
||
server.listen(port); | ||
server.on('error', onError); | ||
server.on('listening', onListening); | ||
|
||
/** | ||
* Normalize a port into a number, string, or false. | ||
*/ | ||
|
||
function normalizePort(val) { | ||
var port = parseInt(val, 10); | ||
|
||
if (isNaN(port)) { | ||
// named pipe | ||
return val; | ||
} | ||
|
||
if (port >= 0) { | ||
// port number | ||
return port; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Event listener for HTTP server "error" event. | ||
*/ | ||
|
||
function onError(error) { | ||
if (error.syscall !== 'listen') { | ||
throw error; | ||
} | ||
|
||
var bind = typeof port === 'string' | ||
? 'Pipe ' + port | ||
: 'Port ' + port; | ||
|
||
// handle specific listen errors with friendly messages | ||
switch (error.code) { | ||
case 'EACCES': | ||
console.error(bind + ' requires elevated privileges'); | ||
process.exit(1); | ||
break; | ||
case 'EADDRINUSE': | ||
console.error(bind + ' is already in use'); | ||
process.exit(1); | ||
break; | ||
default: | ||
throw error; | ||
} | ||
} | ||
|
||
/** | ||
* Event listener for HTTP server "listening" event. | ||
*/ | ||
|
||
function onListening() { | ||
var addr = server.address(); | ||
var bind = typeof addr === 'string' | ||
? 'pipe ' + addr | ||
: 'port ' + addr.port; | ||
debug('Listening on ' + bind); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
include .env | ||
|
||
REMOTE[email protected] | ||
|
||
# .SILENT: | ||
|
||
tail: | ||
ssh ${REMOTE} 'docker logs -f --tail 0 service-${SUBDOMAIN}' | ||
|
||
logs: | ||
ssh ${REMOTE} 'docker logs service-${SUBDOMAIN}' | ||
|
||
sync-secrets: | ||
rsync -r ./.secrets/ ${REMOTE}:/secrets/service-${SUBDOMAIN} | ||
|
||
generate-port: | ||
ssh ${REMOTE} 'ruby -e "require \"socket\"; puts Addrinfo.tcp(\"\", 0).bind {|s| s.local_address.ip_port }"' | tr -d '[:space:]' > .open-port | ||
|
||
PORT = $(shell cat .open-port) | ||
|
||
# Only run once, at service initialisation. All other deployment will be through github actions | ||
initialise: generate-port | ||
mkdir -p .secrets | ||
ssh ${REMOTE} "mkdir -p /secrets/service-${SUBDOMAIN}" | ||
# _Definitely_ prone to race conditions, but this won't be called anywhere near frequently enough for that to matter | ||
ssh ${REMOTE} 'docker exec postgres createdb -U postgres service-db-${SUBDOMAIN}' | ||
ssh ${REMOTE} 'docker run -d --name service-${SUBDOMAIN} \ | ||
--network traefik-net \ | ||
--label "traefik.enable=true" \ | ||
-p ${PORT}:${PORT} \ | ||
-e PORT=${PORT} \ | ||
-v /secrets/service-${SUBDOMAIN}:/secrets \ | ||
-e "DATABASE_URL=postgresql://postgres:mysecretpassword@postgres:5432/service-db-${SUBDOMAIN}" \ | ||
-e "FILE_UPLOAD=https://service-simple-storage:3456/${SUBDOMAIN}" \ | ||
--label "com.centurylinklabs.watchtower.enable=true" \ | ||
--label "traefik.http.routers.service-${SUBDOMAIN}.rule=Host(\`${SUBDOMAIN}.dev.comp-soc.com\`)" \ | ||
--label "traefik.http.routers.service-${SUBDOMAIN}.middlewares=traefik-forward-auth" \ | ||
ghcr.io/compsoc-edinburgh/service-${SUBDOMAIN}' | ||
rm .open-port | ||
|
||
teardown-db: | ||
ssh ${REMOTE} 'docker exec postgres dropdb -U postgres service-db-${SUBDOMAIN}' | ||
|
||
teardown: teardown-db | ||
ssh ${REMOTE} 'docker stop service-${SUBDOMAIN}' | ||
ssh ${REMOTE} 'docker rm service-${SUBDOMAIN}' |
Oops, something went wrong.