1) Do you have two separate codebases for free and paid? Or do you have some sort of standard workflow where you have two different branches? 2) How do you handle updates to your product? 3) How about employee access?
Thanks in advance!
I've been running this set up for about a year [1] and it's working well. Having a single code base was a requirement before I made the project fair source [2]; the fair source ELv2 license lets me add feature gates to facilitate this, while protecting me from forks giving away EE features for free (while still allowing forks).
Updates are pushed to the SaaS offering daily. I cut self-hosted releases bi-annually unless it's for a critical fix.
[0]: https://github.com/keygen-sh/keygen-api
[1]: https://keygen.sh/blog/all-your-licensing-are-belong-to-you/
[2]: http://fair.io/
My SaaS version of https://datasette.io is the open source version plus 76 plugins. Almost all of those plugins are themselves open source, with just a few that aren't for features that are unique to the SaaS product - things like showing how much disk space the user has used up already.
Here's what that custom plugin looks like, it's pretty thin: https://gist.github.com/simonw/114131fd9c1826f3629cc3b3dcb84...
This approach has really made the private fork much more maintanable.
There is currently no difference between the self-hosted and the SaaS version, but I am planning two things:
1) An env variable `IS_SELF_HOSTED` which, when set to `false`, toggles certain features like billing (currently enabled via a separate env variable and theoretically available to self-hosters) and includes hard-coded stuff like a footer with links to the official project website and our ToS.
2) Add a registration feature for self-hosters who make a donation. I haven’t fully planned out this feature, but if a self-hosted instance is registered by a paid supporter, it will most likely remove a call for becoming a supporter (that is yet to be added) or give them a supporter badge.
Choosing the AGPLv3 has been partially inspired by Plausible’s very successful model [2]. They’re also using a `SELFHOST` env variable to differentiate between their "Enterprise Edition" and the "Community Edition" [3].
[2] https://plausible.io/blog/open-source-licenses
[3] https://github.com/plausible/analytics/blob/baa99652f612f50b...
- 2 git repos, one public, one private
- Custom tooling inspired by other existing tools out there like copybara to move commits in a unidirectional flow from the private repo to the public one
The private repo had the contents of the OSS repo in a subfolder, and a unidirectional workflow where all commits flowed from the private to the public. The way the OSS repo accepted contributions was going through the normal pull request workflow on the OSS repo, then on approval the custom tooling copied the pull request into the private repository where it would go through a second round where it was tested against the private code. On merge in the private repo the commit sha of the original commit along with the original author would get injected into the new commit message and then the custom tooling would then automatically push commits in the private repo subfolder to the public OSS repo, which essentially served as a read only mirror on the main branch.
It actually worked quite well, though obviously this approach comes with some tradeoffs. Here are some of the benefits and drawbacks off the top of my head
Benefits:
- no mixing of free and proprietary code. Public users just check out the OSS repo and it’s completely free
- proprietary code is not visible to the public
Drawbacks:
- workflow slightly more complex for both end users and employees of the enterprise
- more work to mainline code by the enterprise: contributions essentially need to be reviewed twice
- it is not possible to have signed commits by OSS contributors with this workflow
Overall it ended up being a great solution for that organization but this kind of approach can only work for low traffic or easy to review repositories. Anything with a decent amount of traffic or lots of complex contributions has a risk of becoming too much overhead for the organization to double build and test pull requests
And each plugin has a concept called install constraints. They consist of app, tier, and the role of the user provisioning the account.
Based on the right combination, certain plugins get auto installed and the user can enable/disable optional plugins/features.
Upon installation, the plug-in module has a “lifecycle” module with callbacks for install, uninstall, after_install, etc.
In those, we insert db metadata (pages, folders, menu items, etc) related to that plugin.
All is written in elixir.
No. Sentry has one codebase between free and paid. We have a Django monolith plus some services where all our features are implemented:
https://github.com/getsentry/sentry
https://github.com/getsentry/relay
https://github.com/getsentry/snuba
https://github.com/getsentry/vroom
etc.
We have a Docker Compose wrapper in a separate repo:
https://github.com/getsentry/self-hosted
We have a private repo called getsentry/getsentry that is another Django app that imports the public one and uses Django signals (iirc) to meter usage for billing. That is what we deploy to SaaS. All product features are implemented in the public repos.
3) How about employee access?
Employee access is managed through UI, I honestly don't know whether it lives in getsentry/sentry (public) or getsentry/getsentry (private). Probably the latter?
2) How do you handle updates to your product?
Employees work in public on GitHub. We ship more or less continuously to SaaS. It's possible to deploy approximately continuously on self-hosted as well, though we also bless monthly snapshots for a more relaxed cadence. Does that answer this question?
In this example as I recall most of the enterprise code was just activation code, so a properly motivated individual could probably figure out how to turn on some of the enterprise features, but most people won't bother to figure out how to do their own builds, and it's more work then finding the secret env variables in an open repo.
It's a little bit painful at times because changes that cover both code bases need two commits, and it isn't uncommon for someone to forget or commit the wrong submodule pointer. But depending on the team, it can work.
Not for everyone, just including it as an option I haven't seen mentioned in other comments.
We started with open-source first, and added SaaS offering after ~2 years. The code base split was a natural choice. At first, I didn't want to add SaaS, because managing servers is a lot of work. But, we have a lot of requests for such service, and it makes really easy to deploy notebook online (with few clicks you have unique domain and notebook running). I'm happy with this code base split.
If you include open source and proprietary components in the same repo I will be annoyed. Especially if it is not very clear how the licensing is split, or if the proprietary stuff is split in multiple folders.
If you don't have instructions, or it is otherwise unreasonably difficult to build the open-source only version, I will be very annoyed.
If your "open source" code is directly linked to the proprietary parts, and it requires non-trivial changes to your code in order to build without the proprietary components, I don't consider your project open source, no matter how much you plaster "open source" on your marketing materials. And it's even worse if the license is unclear on what the licensing situation is if you use a release that includes all these proprietary components, but they are turned off because you don't have a license key.
Projects use different licenses for enterprise and regular features. Cal does that for example.
Novu doesn’t commit their enterprise features to their codebase. And they symlink those features in. Their project is all MIT because of this.
It calls Stripe to check the plan for example.
I keep a copy of it in another folder to avoid accidentally committing it and use `rsync` to patch it in at deployment time.
Our “git” is Plane[1], which is by design fairly unopinionated and general. Our “github” is Jamsocket[2], which is more opinionated about deployment, observability, etc.
We use the open source version of Plane in Jamsocket, and build on top of it in a separate (proprietary) codebase.
We made the core part open source and our paid offering takes up the rest of the product as addons! So, we maintain only one codebase and avoid the hassle of drifts.
Happy to offer more insight.
We had started off by using a series of private repos for our non-OSS features. At build time, we had a pretty complicated suite of build tooling that took all of the repos and integrated them and ran the integration tests. The biggest reason why Elasticsearch went to single repo with all of the enterprise software included (knowing that the OSS-focused community was going to backlash on it) was just how much of a hassle that was. It bifurcated everything and the results were that paying customers were upset the most.
For example: have an issue with the product? You could file a GitHub issue in public and it would get looked at quickly, but if you were a paying customer with an issue with one of the enterprise features, you had to file an issue with the support team because the GitHub repo was separate/private, and then support would file an issue in the private GitHub repo, and you'd lose complete visibility with it. It was even worse for free/trial users: they had to file an issue on our forums (since they didn't have an established support contact), after which they lost visibility. Integration tests were also hellish.
I later worked at another open core company, which at first maintained a separate codebase for its paid offering, and synchronized changes over from the open source product periodically, and things were much worse: a ton of bugs kept popping up and getting the paid offering out the door was always extremely slow because you were always playing catch up on the OSS offering.
You can go down the submodule route, but you end up relying on pretty complicated build tooling. If that's in your team's realm of capabilities, it's not a bad route to go. If you have no need for "OSS," then you can choose a non-OSS license that provides whatever restrictions you care about and bundle it up together, which is easier, but might dissuade certain users.
Regardless, licensing enforcement isn't really an issue if you're working on a B2B product: there are certain demographics that won't pay for your software, but they won't pay if you have licensing enforcement either. B2C can be a different story.
public DoFoo(){
if(CurrentUser.IsFreeSku && CurrentUser.FoosThisMonth >= 10) return Error('Free users are only allowed to do 10 foos each month);
FooService.DoFoo();
CurrentUser.FoosThisMonth ++
}
Similar in the UI you may hide or show things based on the current user/tenant and their sub level.You would still have server code enforce the subscription level rules because a malicious user could circumvent any client-based rules. How exactly you do both will depend on the framework your saas uses.
For instance, a Git-like DAG for a config service [1] that supports history and eventually forking and merging on top of PostgreSQL. This was broken out of the larger Go codebase and is currently AGPL3 as a placeholder while I research better licenses to use.
The overall idea is the "nuts and bolts" of a small startup/SaaS app, including metrics, IAM/auth, and subscriptions via a single API and React/JS SDKs.
I hope to do a Show HN on one or both in the near future.
Mapzy: A simple storefinder (https://github.com/mapzy/mapzy)
Fugue: Privacy-friendly product analytics (https://github.com/shafy/fugu)
Most of the Cusommers didn't care self-hosting, if your SaaS price is decent.
I strongly do not recommend to have two copies of the codebase, because it increase the sync time (like bug fixing portings, for instance).
Sad to say, consider protecting you from AWS/GCP/etc reselling as a service
I'm using different implementations of interfaces for self-hosted vs commercially (my variant) hosted.
What implementations get chosen depends on environment variables.
The code is available as GPL