This is part 4 of this series. Go here for part 1, here for part 2 and, here for part 3.
In part 3 of this series I went over the architecture of the api and how it all works. I’m now going to set this up an AWS lightsail instance to run this as well as adding it to systemd so that it starts automatically if the system reboots and restarts in the event of a failure, add in some ssl love with LetsEncrypt and, set up log rotation because I did enable logging with echo in case we encounter errors I can troubleshoot.
Preface
I ended up purchasing a domain for this project and have Cloudflare in front of it as a proxy for some analytics and a bit of security. The domain was ~$24 for 2 years and I’m only utilizing Cloudflares free tier for this. I’ll tally up the total cost for this project at the end.
The Infrastructure
As I mentioned in an earlier post I’ll be running this on an AWS Lightsail instance because it’s the easiest choice and setting one up is easier than setting up an ec2 instance. There’s just a couple of things that need to be done once it’s provisioned.
- Give it a static ip. This is free in AWS as long as the ip is attached to a lightsail instance.
- Open a few ports on the firewall. In this case 80 and 443 for http/s traffic. 80 won’t actually be used though as we’ll redirect via Cloudflare.
Other than that the server is ready to dive into and get configured.
The Hiccup
So now we just run go build woin-api
and get the binary onto our vps right? Well yes but actually no. Architecture matters, specifically system architecture. I wrote the code, and this blog, on a macbook with an arm processor so running go build
results in a binary that works only on arm64
and of course that isn’t what our lightsail instance is. Running uname -m
on the server spits out x86_64
so how do we fix this?
Well we could just clone the repo locally onto the server and build it there. That’s pretty easy to do. But what if this API goes viral (0% chance) and is used by a ton of people? I’d need to provision a larger server and maybe it won’t be x86_64
so should I settle for always having to clone a repo and build locally? That seems annoying.
So how can we cover all architectures especially in an automated fashion? Well my personal code is in Github and we can use actions, their CI/CD tooling, to automatically build the binary when we push a new tag up to the main branch. This is achieved easily enough with GoReleaser
The result is great. All we ever need to do now is just wget
the binary from the releases page.
SSL Time
I want my site to have SSL and you should too. The easiest answer here is getting a LetsEncrypt cert using CertBot. Following the instructions is easy enough. The auto-renewal requires port 80 to be open on your host though. If you’re not comfortable leaving it open then it’s on you to remeber to renew the cert every 90 days and will still require you to open port 80 to do so even if the process takes all of 30 seconds.
As a part of the cert request you’ll need to prove you own your domain and the easiest thing to do is create a TXT record in the same place your A record is, in this case it’s Cloudflare for me.
How Do We Tell Echo to Use the Cert?
Echo docs to the rescue. I did try their AutoTLS Recipe but couldn’t get it to work correctly so opted for the first link.
Logging and Log Management
Now’s a good time to discuss how we’ll handle logs and recovery in the event of a fatal error.
Echo has built in logging features of course and they’re pretty configurable. I utilize this so that I get information that I care about seeing. Note that everything in this section lives in my main function in the api main.go
.
// Middleware
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "time=${time_rfc3339_nano}, remote_ip=${remote_ip}, host=${host}, method=${method}, status=${status}, uri=${uri}, user_agent=${user_agent}, error=${error}, latency=${latency_human}\n",
Output: f,
}))
e.Use(middleware.Recover())
The formatting is pretty self-explanatory in what I’m actually logging. The e.Use(middleware.Recover())
is another part of the echo middleware and I use it here simply to recover from panics should they happen though it is customizable.
What may seem out of place if the f
variable in the Output
part of the logging declaration.
Above the middleware logging declaration I set up a writer to write to a log file as seen here.
// Writer for Echo Logging
f, err := os.OpenFile("/var/log/woin/server.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
panic(fmt.Sprintf("Error opening file: %v", err))
}
defer f.Close()
Here I’m just using os.OpenFile
, the f
mentioned above, from the standard library and handling any error that may come up when opening the file though it shouldn’t happen because I’m telling os.OpenFile
that it can r/w, create and append to the file so it “should” no matter what but it’s always best to handle any potential issues anyway.
Managing Your Logs
Log files will grow over time and while it’s unlikely to chew up all our disk space in this instance it’s always a good idea to plan for the possability of this happening. Enter logrotate.
I’m using a simple config here to make sure my log files don’t run amok. The below config file is pretty self explanatory but all options listed are explained in detail in the logrotate link above.
/var/log/woin/*.log {
weekly
missingok
rotate 12
compress
notifempty
create 0666 root root
sharedscripts
postrotate
systemctl reload woin-api
endscript
}
Systemd
If you’re not aware of what systemd is you can find out more here. I’ll be using it to add the api binary as a system service on my lightsail instance so that it automatically starts at boot and attempts to restart if it crashes.
First I’ll get my service file in place, touch /etc/systemd/system/woin-api.service
with the below contents…
[Unit]
Description=WOIN API
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=10
User=root
ExecStart=/home/woin-api
WorkingDirectory=/home
[Install]
WantedBy=multi-user.target
With the configuration file in place we run the following to get it enabled in systemd…
sudo systemctl daemon-reload && \
sudo systemctl enable woin-api && \
sudo service woin-api start
And that’s it. Now our api is running and will continue to even if the system reboots. Further, you can see it’s status by running sudo service woin-api status
. You should see something like this…
Conclusion
That’s it. Our api is up and running. If we curl our server endpoint we receive the expected response…
curl https://foo.bar/v1/species/Borian
{"status":200,"message":"success","data":{"species":{"_id":"6305358afc82b7f384fda793","name":"Borian","str":0,"agi":0,"end":1,"int":1,"log":0,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"carousing, hardy, [crafting], engineering, appraisal","exploits":"Darksight, Iron Constitution, Tinkerer, Long-lived, Personable, Small","age_multiplier":3,"damage":"1d6","size":"small","source":"NEW"}}}
And curling the whole collection of species works too…
curl https://foo.bar/v1/species
{"status":200,"message":"success","data":{"data":[{"_id":"6305358afc82b7f384fda75e","name":"Tauran","str":2,"agi":0,"end":1,"int":2,"log":0,"wil":0,"cha":0,"rep":1,"luc":0,"psi_mag_chi":0,"skill_choices":"astrogation, piloting, brawling, axes, tracking, scent","exploits":"Horns, Charge, Direction Sense, Stubborn, Stoic","age_multiplier":1,"damage":"2d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda781","name":"Fornian","str":1,"agi":1,"end":1,"int":0,"log":1,"wil":2,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[scientific]","exploits":"Plant, Spores, Scent Messages, Immune To Poison, Ambulation, Darksight, Underground Dwellers","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda761","name":"Deva","str":0,"agi":0,"end":0,"int":1,"log":0,"wil":0,"cha":2,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"history, local knowledge, [artistic], insight, religion","exploits":"Varied, Ageless, Reincarnated, Deathless Calm, Memory Of Past Lifetimes","age_multiplier":1,"damage":"1d6","size":"medium","source":"ZPG"},{"_id":"6305358afc82b7f384fda76d","name":"Houseki","str":2,"agi":1,"end":1,"int":0,"log":1,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":1,"skill_choices":"[technical], brawling, zero-g, mining, hardy","exploits":"Crystalline, Always Growing, Crystal Healing, Jagged Crystal, Slow \u0026 Careful, Implacable, Long-lived","age_multiplier":3,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda776","name":"Spartan","str":1,"agi":1,"end":1,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[combat], intimidate, carousing","exploits":"Berserker, Redundant Organs, Warlike","age_multiplier":1,"damage":"1d6","size":"medium","source":"NEW"},{"_id":"6305358afc82b7f384fda77b","name":"Sylvan Elf","str":0,"agi":2,"end":2,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":2,"skill_choices":"animal handling, bows, climbing, herbalism, nature, running, stealth, survival, tracking","exploits":"Fey, Healthy, Nature Affinity, Trance, Tree-Dwellers, Unimpeded","age_multiplier":5,"damage":"1d6","size":"medium","source":"OLD"},{"_id":"6305358afc82b7f384fda797","name":"Werewolf","str":1,"agi":0,"end":2,"int":2,"log":0,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"scent, tracking, running, hardy","exploits":"Transformation, Involuntary Transformation, Darksight, Silver Vulnerability","age_multiplier":1,"damage":"1d6","size":"medium","source":"DD"},{"_id":"6305358afc82b7f384fda799","name":"Clockman","str":1,"agi":0,"end":2,"int":0,"log":2,"wil":2,"cha":0,"rep":1,"luc":0,"psi_mag_chi":0,"skill_choices":"engineering, astronomy, law, history, medicine, astrogation","exploits":"Mechanoid, Repair, Wind-up, Internal Clock, Attachments, Durable","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda75f","name":"Garga","str":1,"agi":2,"end":0,"int":1,"log":0,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[crafting], [artistic], mathematics","exploits":"Amphibian, Great Leap, Webbed Limbs, Long Tongue, Chameleon","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda77c","name":"Jovian","str":1,"agi":0,"end":2,"int":0,"log":0,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"hardy, carrying, mining, high-g, computers, [technical]","exploits":"Sturdy Frame, Crushing Environment, Set In Their Ways, Hard To Move, Inexhaustible","age_multiplier":1,"damage":"1d6","size":"medium","source":"XEN"},{"_id":"6305358afc82b7f384fda753","name":"Human","str":0,"agi":0,"end":0,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":2,"psi_mag_chi":0,"skill_choices":"[any]","exploits":"Varied, Learners, Enduring","age_multiplier":1,"damage":"1d6","size":"medium","source":"ALL"},{"_id":"6305358afc82b7f384fda796","name":"Gorilla","str":2,"agi":1,"end":0,"int":1,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"brawling, carrying, interrogation, intimidation","exploits":"Mighty Load, Thick Fur, Roar, I Am Kang!","age_multiplier":1,"damage":"2d6","size":"large","source":"JDW"},{"_id":"6305358afc82b7f384fda77f","name":"Warped","str":0,"agi":0,"end":0,"int":2,"log":0,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":4,"skill_choices":"perception, insight, foresight, astrogation, [psionic]","exploits":"Crazed Minds, Warped Psionics, See Ghosts","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda78f","name":"Bragi","str":0,"agi":1,"end":0,"int":2,"log":1,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"piloting, astrogation, perception, swimming, art","exploits":"Aquatic, Astrogator Adaptive Gills, Suckers, Perceptive, Long-lived","age_multiplier":2,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda795","name":"Grey","str":1,"agi":1,"end":0,"int":2,"log":1,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":3,"skill_choices":"telepathy, negotiation, linguistics, insight, astrogation","exploits":"Empathy, Telepathic Message, Telekinesis, Long-lived, Telekinetic Shield","age_multiplier":2,"damage":"1d6","size":"small","source":"GSC"},{"_id":"6305358afc82b7f384fda752","name":"Saurian","str":0,"agi":0,"end":3,"int":0,"log":0,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"survival, climbing, running, spears","exploits":"Reptilian, Always On The Move, Balancing Tail, Inexhaustible, Poison Breath, Nomad","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda77e","name":"Drahzik","str":1,"agi":0,"end":1,"int":1,"log":0,"wil":0,"cha":0,"rep":1,"luc":0,"psi_mag_chi":0,"skill_choices":"hunting, tracking, stealth, rifles, spears, climbing","exploits":"Reptilian, Scent, Fast Healing, Cold-blooded, Infravision, Traditional Gear","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda764","name":"Belter","str":0,"agi":2,"end":0,"int":2,"log":0,"wil":0,"cha":0,"rep":0,"luc":1,"psi_mag_chi":0,"skill_choices":"acrobatics, low-g, zero-g, jumping, astronomy, carousing, appraisal, mining, engineering, zero-g sports, gambling","exploits":"Frail Form, Flexible, Hard-To-Hit, Hold Breath","age_multiplier":1,"damage":"1d6","size":"medium","source":"XEN"},{"_id":"6305358afc82b7f384fda76b","name":"Ogron","str":2,"agi":0,"end":2,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"carrying, hardy, bravery, intimidate","exploits":"Dull-witted, Smelly, Brawny, Large, Strong, Stronger With Age","age_multiplier":1,"damage":"2d6","size":"large","source":"NEW"},{"_id":"6305358afc82b7f384fda78b","name":"Augmented","str":0,"agi":0,"end":2,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"hardy, perception, reactions, [crafting], [trivia], [gaming], [scientific], [technical]","exploits":"Alteration, Adaptive, Inert","age_multiplier":1,"damage":"1d6","size":"medium","source":"NOW"},{"_id":"6305358afc82b7f384fda78e","name":"Grand Elf","str":0,"agi":2,"end":0,"int":0,"log":2,"wil":0,"cha":0,"rep":0,"luc":2,"psi_mag_chi":3,"skill_choices":"muskets, pistols, swords, alchemy, law, intimidate, leadership, sailing, [musical]","exploits":"Fey, Magic Sense, Meditation, Naturally Magical, Cultural Weapon, Long-lived, Magical","age_multiplier":5,"damage":"1d6","size":"medium","source":"OLD"},{"_id":"6305358afc82b7f384fda793","name":"Borian","str":0,"agi":0,"end":1,"int":1,"log":0,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"carousing, hardy, [crafting], engineering, appraisal","exploits":"Darksight, Iron Constitution, Tinkerer, Long-lived, Personable, Small","age_multiplier":3,"damage":"1d6","size":"small","source":"NEW"},{"_id":"6305358afc82b7f384fda757","name":"Trantor","str":2,"agi":1,"end":0,"int":0,"log":2,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"hardy, carrying, concentration, history, insight","exploits":"Tusks, Trunk, Never Forget, Slow To Move, Lasting Minds","age_multiplier":1,"damage":"3d6","size":"large","source":"GSC"},{"_id":"6305358afc82b7f384fda765","name":"Smallfolk","str":0,"agi":2,"end":0,"int":0,"log":0,"wil":0,"cha":2,"rep":0,"luc":2,"psi_mag_chi":1,"skill_choices":"[crafting], bluffing, brewing, cooking, diplomacy, farming, fishing, slings, stealth, thievery","exploits":"Stubborn, Evasion","age_multiplier":1,"damage":"1d6","size":"small","source":"OLD"},{"_id":"6305358afc82b7f384fda783","name":"Argon","str":2,"agi":1,"end":0,"int":1,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"nature, diplomacy, perception, reactions, carrying, intimidation","exploits":"Mighty Load, Commune With Nature, Pacifist, Thick Fur, Roar","age_multiplier":1,"damage":"2d6","size":"large","source":"GSC"},{"_id":"6305358afc82b7f384fda794","name":"Clone","str":1,"agi":1,"end":1,"int":1,"log":1,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[developmental], [physical]","exploits":"Ingrained Skill Package, Fast Healing, Slow Aging","age_multiplier":1,"damage":"1d6","size":"medium","source":"XEN"},{"_id":"6305358afc82b7f384fda79a","name":"Pagozan","str":0,"agi":2,"end":0,"int":0,"log":0,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"climbing, computers, piloting, stealth","exploits":"Reptilian, Camouflage, Motion-sight, Tail, Regenerative","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda759","name":"Kanid","str":0,"agi":1,"end":1,"int":2,"log":0,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"perception, scent, insight, tracking, running, [social]","exploits":"Pack-bonding, Fur Coat, Chasers, Scent, Alcohol Weakness, Social","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda75b","name":"Changeling","str":0,"agi":3,"end":0,"int":0,"log":0,"wil":1,"cha":2,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"bluffing, insight, stealth","exploits":"Changeling, Regeneration, Morphed Weapon, Acid Vulnerability, Ageless","age_multiplier":1,"damage":"2d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda771","name":"Raptor","str":2,"agi":3,"end":0,"int":3,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"tracking, engineering, intimidation, running, perception","exploits":"Reptilian, Fierce Jaws, Pounce","age_multiplier":1,"damage":"3d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda775","name":"Half-ghost","str":0,"agi":2,"end":0,"int":2,"log":0,"wil":2,"cha":0,"rep":0,"luc":0,"psi_mag_chi":3,"skill_choices":"[any]","exploits":"Semi-corporeal, No Biology, Manifest, Darksight","age_multiplier":5,"damage":"1d6","size":"medium","source":"SOM"},{"_id":"6305358afc82b7f384fda78d","name":"Dhampir","str":2,"agi":2,"end":0,"int":2,"log":0,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"history, stealth, disguise, bluffing","exploits":"Vampiric Bite, Spider-climb, Sunlight Sensitivity, Biological Immunity","age_multiplier":1,"damage":"1d6","size":"medium","source":"DD"},{"_id":"6305358afc82b7f384fda791","name":"Ogre","str":3,"agi":0,"end":3,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"brawling, carrying, hardy, bravery, intimidate, [melee weapon]","exploits":"Thick Hide, Smelly, Darksight, Acid Blood, Strong","age_multiplier":3,"damage":"2d6","size":"large","source":"OLD"},{"_id":"6305358afc82b7f384fda792","name":"Ginean","str":0,"agi":2,"end":1,"int":0,"log":1,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"climbing, computers, piloting, [artistic], [academic]","exploits":"Reptilian, Camouflage, Motion-sight, Regenerative, Long-lived","age_multiplier":3,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda75d","name":"Kithik","str":0,"agi":2,"end":0,"int":1,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":1,"skill_choices":"survival, astrogation, polearms","exploits":"Insectoid, Venomous Bite, Four Arms, Short-lived","damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda756","name":"Chorax","str":0,"agi":3,"end":1,"int":0,"log":1,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"biology, xenology, stealth, medicine, hunting","exploits":"Insectoid, Eight-legged, Webspinner, Revolting, Poison Immunity, Darksight","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda754","name":"Roden","str":0,"agi":2,"end":0,"int":0,"log":0,"wil":0,"cha":1,"rep":0,"luc":2,"psi_mag_chi":0,"skill_choices":"[subterfuge], appraisal, perception","exploits":"Gnaw Away, Tail, Light Sensitivity, Navigators, Scamper","age_multiplier":1,"damage":"1d6","size":"small","source":"GSC"},{"_id":"6305358afc82b7f384fda75a","name":"Adraxi","str":0,"agi":2,"end":0,"int":2,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"tracking, stealth, hunting, brawling, climbing","exploits":"Avian, Reptilian, Serrated Beak, Perch, Leathery Wings","age_multiplier":1,"damage":"2d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda767","name":"Pajak","str":0,"agi":2,"end":1,"int":2,"log":0,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[subterfuge], [scientific], bureaucracy, hypnotism","exploits":"Insectoid, Compound Eyes, Thin Bones, Scrawny, Bite, Short-lived","damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda786","name":"Yakan","str":2,"agi":1,"end":1,"int":0,"log":0,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"nature, diplomacy, perception, reactions, carrying, intimidation","exploits":"Mighty Load, Thick Fur, Roar, Cannot Speak","age_multiplier":1,"damage":"2d6","size":"large","source":"GSC"},{"_id":"6305358afc82b7f384fda75c","name":"Orangutan","str":1,"agi":3,"end":0,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"acrobatics, carousing, gambling, reactions","exploits":"King Of The Swingers, Great Leap, Fast","age_multiplier":1,"damage":"1d6","size":"medium","source":"JDW"},{"_id":"6305358afc82b7f384fda76e","name":"Torbanite","str":0,"agi":0,"end":4,"int":0,"log":0,"wil":2,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[medical], [scientific]","exploits":"Crsystalline, No Pain, Limited Motion, Enduring","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda777","name":"Mutant","str":0,"agi":0,"end":0,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":1,"skill_choices":"hardy, intimidate, resistance, survival, [crafting], [trivia], [gaming], stealth, disguise","exploits":"Mutation","age_multiplier":1,"damage":"1d6","size":"medium","source":"NOW"},{"_id":"6305358afc82b7f384fda77d","name":"Solurial","str":2,"agi":1,"end":1,"int":0,"log":0,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[outdoor], [artistic]","exploits":"Plant, Lashing Vines, Ponderous, Photosynthesis, Extended Families, Camouflage, Roots, Long-lived","age_multiplier":7,"damage":"2d6","size":"large","source":"GSC"},{"_id":"6305358afc82b7f384fda763","name":"Gris","str":1,"agi":0,"end":0,"int":1,"log":2,"wil":2,"cha":1,"rep":0,"luc":1,"psi_mag_chi":0,"skill_choices":"[artistic], [academic], [scientific]","exploits":"Eat Anything, Poor Sight, Philosophical, Scholastic, Advice","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda769","name":"Chimp","str":1,"agi":2,"end":0,"int":0,"log":0,"wil":1,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"acrobatics, brawling, carousing, movies, gambling","exploits":"Natural Climbers, Zero-G, Great Leap, Throwers, Agile, Weak-willed","age_multiplier":1,"damage":"1d6","size":"small","source":"JDW"},{"_id":"6305358afc82b7f384fda76f","name":"Zetan","str":0,"agi":2,"end":0,"int":2,"log":0,"wil":1,"cha":0,"rep":0,"luc":1,"psi_mag_chi":0,"skill_choices":"[artistic]","exploits":"Blur, Speed Dodge, Weak-willed, Unfocused, Unstoppable, Short-lived","damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda77a","name":"Mutant","str":0,"agi":0,"end":2,"int":0,"log":0,"wil":2,"cha":0,"rep":1,"luc":0,"psi_mag_chi":0,"skill_choices":"bluffing, disguise, running, stealth","exploits":"Radiation Resistance, Mutation","age_multiplier":1,"damage":"1d6","size":"medium","source":"JDW"},{"_id":"6305358afc82b7f384fda782","name":"Felan","str":0,"agi":2,"end":0,"int":1,"log":0,"wil":0,"cha":1,"rep":0,"luc":1,"psi_mag_chi":0,"skill_choices":"acrobatics, climbing, jumping, [unarmed fighting], reactions, appraisal, bluffing, stealth, negotiating","exploits":"Agile, Fast, Land On Your Feet, Claws","age_multiplier":1,"damage":"2d6","size":"medium","source":"NEW"},{"_id":"6305358afc82b7f384fda760","name":"Zouklan","str":1,"agi":3,"end":0,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[outdoor], spears, rifles, riding","exploits":"Climbers, Great Leap","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda774","name":"Hologram","str":0,"agi":0,"end":0,"int":2,"log":2,"wil":2,"cha":2,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[scientific], [social]","exploits":"Mechanoid, Intangible, Free Movement, Immortal, Limited Range, Ageless","age_multiplier":1,"damage":"0","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda785","name":"Orc","str":2,"agi":2,"end":0,"int":3,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[combat], blacksmithing, carousing, hunting, intimidation, mining, running, tracking","exploits":"Glory, Darksight, Bloodlust","age_multiplier":1,"damage":"2d6","size":"medium","source":"OLD"},{"_id":"6305358afc82b7f384fda758","name":"Synthetic","str":3,"agi":1,"end":0,"int":0,"log":2,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"computers, engineering, medicine, piloting, [artistic], [academic], [scientific]","exploits":"Mindless, Deterministic, Electronic Vulnerability, Automaton, Skill Package, Factory Specs","age_multiplier":1,"damage":"1d6","size":"medium","source":"XEN"},{"_id":"6305358afc82b7f384fda762","name":"Venetian","str":0,"agi":1,"end":0,"int":0,"log":2,"wil":0,"cha":0,"rep":0,"luc":2,"psi_mag_chi":3,"skill_choices":"reactions, acrobatics, perception, concentration, religion, [scientific]","exploits":"Naturally Psionic, Acute Hearing, Learned, Disciplined, Long-lived, Logical","age_multiplier":5,"damage":"2d6","size":"medium","source":"NEW"},{"_id":"6305358afc82b7f384fda76a","name":"Jamila","str":0,"agi":0,"end":0,"int":2,"log":0,"wil":0,"cha":2,"rep":0,"luc":0,"psi_mag_chi":1,"skill_choices":"[social], [subterfuge], [artistic]","exploits":"Pheromones, Draining Life Force, Diplomats","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda772","name":"Treem","str":0,"agi":1,"end":2,"int":0,"log":0,"wil":2,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[outdoor], [social], [artistic]","exploits":"Plant, Seeds, Immovable, Cannot Jump","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda778","name":"Betrux","str":0,"agi":1,"end":1,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"engineering, computers, electronics, climbing, perception","exploits":"Insectoid, Thick Shell, Antennae, Blind, Slow, Mandibles, Subspecies","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda784","name":"Android","str":2,"agi":0,"end":0,"int":0,"log":2,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"computers, engineering, running, linguistics, [technical]","exploits":"Mechanoid, Ageless, Modification","age_multiplier":1,"damage":"1d6","size":"any","source":"NEW"},{"_id":"6305358afc82b7f384fda78c","name":"Neron","str":0,"agi":1,"end":0,"int":0,"log":2,"wil":0,"cha":2,"rep":0,"luc":1,"psi_mag_chi":0,"skill_choices":"[medical], insight, linguistics, oceanography, polearms","exploits":"Aquatic, Water-breathers, Medical Marvel, Marine Friends, Darksight, Water Healing, Water Bolt","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda790","name":"Vouki","str":0,"agi":2,"end":0,"int":1,"log":0,"wil":0,"cha":2,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[social], hypnotism","exploits":"Reptilian, Hypnotism, Climb, Venomous Bite","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda768","name":"Chosen","str":1,"agi":0,"end":0,"int":0,"log":0,"wil":0,"cha":1,"rep":2,"luc":0,"psi_mag_chi":3,"skill_choices":"negotiating, tactics, [crafting]. [trivia], [gaming], [scientific]","exploits":"Fast-healing, Skill Focus, Destiny","age_multiplier":1,"damage":"1d6","size":"medium","source":"NOW"},{"_id":"6305358afc82b7f384fda76c","name":"Charon","str":0,"agi":1,"end":2,"int":0,"log":0,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"survival, hunting, engineering, [melee weapon], tracking, running, intimidation","exploits":"Irradiated, Grotesque, Mutation","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda770","name":"Clone","str":1,"agi":1,"end":1,"int":1,"log":1,"wil":1,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"perception, insight, concentration, meditation, bravery, resistance, hardy, climbing, swimming, jumping, carrying, running, acrobatics, zero-g, throwing","exploits":"Ingrained Skill Package, Fast Healing","age_multiplier":1,"damage":"1d6","size":"medium","source":"JDW"},{"_id":"6305358afc82b7f384fda780","name":"Robot","str":2,"agi":0,"end":2,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"computers, engineering, running, linguistics, [technical]","exploits":"Mindless, Deterministic, Electronic Vulnerability, Automaton, Modification Choice","age_multiplier":1,"damage":"1d6","size":"any","source":"JDW"},{"_id":"6305358afc82b7f384fda789","name":"Eladrin","str":0,"agi":0,"end":0,"int":2,"log":1,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":3,"skill_choices":"[lore], [outdoor], [magical]","exploits":"Step Through The Dreaming, Cold Iron","age_multiplier":1,"damage":"1d6","size":"medium","source":"ZPG"},{"_id":"6305358afc82b7f384fda798","name":"Acorax","str":0,"agi":2,"end":1,"int":2,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":1,"skill_choices":"acrobatics, perception, stealth, knives, bluffing, disguise, chemistry","exploits":"Avian, Hollow-boned, Beak, Mimickry","age_multiplier":1,"damage":"1d6","size":"small","source":"GSC"},{"_id":"6305358afc82b7f384fda755","name":"Laerrek","str":0,"agi":0,"end":0,"int":3,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":1,"skill_choices":"perception, insight, foresight, reactions, clairvoyance","exploits":"Truesight, Foresight, Insight, Long-lived","age_multiplier":2,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda766","name":"Gobber","str":0,"agi":2,"end":0,"int":0,"log":0,"wil":0,"cha":1,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"thievery, climbing, stealth, survival, knives","exploits":"Pack Attack, Darksight, Bright Light, Natural Cunning, Scavengers, Snatch, Between The Legs","age_multiplier":1,"damage":"1d6","size":"small","source":"GSC"},{"_id":"6305358afc82b7f384fda773","name":"Mountain Dwarf","str":0,"agi":0,"end":2,"int":0,"log":0,"wil":2,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[crafting], alchemy, axes, hammers, carousing, hammers, mining","exploits":"Darksight, Iron Constitution, Sturdy, Earthy, Long-lived, Stubborn","age_multiplier":3,"damage":"1d6","size":"small","source":"OLD"},{"_id":"6305358afc82b7f384fda779","name":"Hellion","str":0,"agi":1,"end":0,"int":0,"log":2,"wil":0,"cha":2,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"bluffing, engineering, rapiers, pistols","exploits":"Natural Weapons, Superior Darksight, Fire Resistance, Antipsyker","age_multiplier":1,"damage":"2d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda787","name":"Molluk","str":2,"agi":1,"end":2,"int":0,"log":0,"wil":0,"cha":0,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"hardy, carrying, meditation, concentration, history","exploits":"Crsytalline, Stone Fist, Bulky, Natural Camouflage, Long-lived","age_multiplier":10,"damage":"3d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda788","name":"Simp","str":1,"agi":2,"end":0,"int":0,"log":0,"wil":1,"cha":0,"rep":0,"luc":2,"psi_mag_chi":0,"skill_choices":"jumping, acrobatics, throwing, engineering, gambling","exploits":"Natural Climbers, Zero-G, Great Leap, Throwers, Agile, Weak-willed","age_multiplier":1,"damage":"1d6","size":"medium","source":"GSC"},{"_id":"6305358afc82b7f384fda78a","name":"Deepling","str":0,"agi":2,"end":0,"int":0,"log":2,"wil":0,"cha":2,"rep":0,"luc":0,"psi_mag_chi":0,"skill_choices":"[crafting], bluffing, engineering, sailing, rapiers, pistols, rifles","exploits":"Natural Weapons, Superior Darksight, Fire Resistance","age_multiplier":1,"damage":"2d6","size":"medium","source":"ZPG"}]}}