@@ -12,6 +12,7 @@ | |||||
{"name": "default_locale", "value": "[[network.locale]]"}, | {"name": "default_locale", "value": "[[network.locale]]"}, | ||||
{"name": "bubble_version", "value": "[[configuration.version]]"}, | {"name": "bubble_version", "value": "[[configuration.version]]"}, | ||||
{"name": "bubble_host", "value": "[[node.fqdn]]"}, | {"name": "bubble_host", "value": "[[node.fqdn]]"}, | ||||
{"name": "bubble_cname", "value": "[[node.networkDomain]]"}, | |||||
{"name": "admin_user", "value": "[[node.user]]"}, | {"name": "admin_user", "value": "[[node.user]]"}, | ||||
{"name": "db_encoding", "value": "UTF-8"}, | {"name": "db_encoding", "value": "UTF-8"}, | ||||
{"name": "db_locale", "value": "en_US"}, | {"name": "db_locale", "value": "en_US"}, | ||||
@@ -1,4 +1,4 @@ | |||||
export PUBLIC_BASE_URI=https://{{ bubble_host }}:{{ ssl_port }} | |||||
export PUBLIC_BASE_URI=https://{{ bubble_cname }}:{{ ssl_port }} | |||||
export SELF_NODE={{ node_uuid }} | export SELF_NODE={{ node_uuid }} | ||||
export SAGE_NODE={{ sage_node }} | export SAGE_NODE={{ sage_node }} | ||||
export LETSENCRYPT_EMAIL={{ letsencrypt_email }} | export LETSENCRYPT_EMAIL={{ letsencrypt_email }} | ||||
@@ -4,6 +4,7 @@ | |||||
"template": true, | "template": true, | ||||
"config": [ | "config": [ | ||||
{"name": "server_name", "value": "[[node.fqdn]]"}, | {"name": "server_name", "value": "[[node.fqdn]]"}, | ||||
{"name": "server_alias", "value": "[[node.networkDomain]]"}, | |||||
{"name": "letsencrypt_email", "value": "[[configuration.letsencryptEmail]]"}, | {"name": "letsencrypt_email", "value": "[[configuration.letsencryptEmail]]"}, | ||||
{"name": "ssl_port", "value": "[[configuration.nginxPort]]"}, | {"name": "ssl_port", "value": "[[configuration.nginxPort]]"}, | ||||
{"name": "admin_port", "value": "[[node.adminPort]]"} | {"name": "admin_port", "value": "[[node.adminPort]]"} | ||||
@@ -0,0 +1,5 @@ | |||||
#!/bin/bash | |||||
service mitmproxy stop && service nginx stop && certbot renew --standalone --non-interactive || echo "Error updating SSL certificates" | |||||
service mitmproxy restart | |||||
service nginx restart |
@@ -2,12 +2,13 @@ | |||||
LE_EMAIL="${1}" | LE_EMAIL="${1}" | ||||
SERVER_NAME="${2}" | SERVER_NAME="${2}" | ||||
SERVER_ALIAS="${3}" | |||||
if [[ $(find /etc/letsencrypt/accounts -type f -name regr.json | xargs grep -l \"${LE_EMAIL}\" | wc -l | tr -d ' ') -eq 0 ]] ; then | if [[ $(find /etc/letsencrypt/accounts -type f -name regr.json | xargs grep -l \"${LE_EMAIL}\" | wc -l | tr -d ' ') -eq 0 ]] ; then | ||||
certbot register --agree-tos -m ${LE_EMAIL} --non-interactive | certbot register --agree-tos -m ${LE_EMAIL} --non-interactive | ||||
fi | fi | ||||
if [[ ! -f /etc/letsencrypt/live/${SERVER_NAME}/fullchain.pem ]] ; then | if [[ ! -f /etc/letsencrypt/live/${SERVER_NAME}/fullchain.pem ]] ; then | ||||
certbot certonly --standalone --non-interactive -d ${SERVER_NAME} | |||||
certbot certonly --standalone --non-interactive -d ${SERVER_NAME} -d ${SERVER_ALIAS} | |||||
else | else | ||||
certbot renew --standalone --non-interactive | certbot renew --standalone --non-interactive | ||||
fi | fi |
@@ -48,7 +48,15 @@ | |||||
mode: 0555 | mode: 0555 | ||||
- name: Init certbot | - name: Init certbot | ||||
shell: init_certbot.sh {{ letsencrypt_email }} {{ server_name }} | |||||
shell: init_certbot.sh {{ letsencrypt_email }} {{ server_name }} {{ server_alias }} | |||||
- name: Install certbot_renew.sh weekly cron job | |||||
copy: | |||||
src: "certbot_renew.sh" | |||||
dest: /etc/cron.weekly/certbot_renew.sh | |||||
owner: root | |||||
group: root | |||||
mode: 0755 | |||||
# see https://weakdh.org/sysadmin.html | # see https://weakdh.org/sysadmin.html | ||||
- name: Create a strong dhparam.pem | - name: Create a strong dhparam.pem | ||||
@@ -1,6 +1,6 @@ | |||||
server { | server { | ||||
server_name {{ server_name }}; | |||||
server_name {{ server_name }} {{ server_alias }}; | |||||
listen {{ ssl_port }} ssl http2; | listen {{ ssl_port }} ssl http2; | ||||
ssl_certificate /etc/letsencrypt/live/{{ server_name }}/fullchain.pem; | ssl_certificate /etc/letsencrypt/live/{{ server_name }}/fullchain.pem; | ||||
@@ -15,11 +15,13 @@ import bubble.server.BubbleConfiguration; | |||||
import bubble.service.bill.RefundService; | import bubble.service.bill.RefundService; | ||||
import bubble.service.cloud.NetworkService; | import bubble.service.cloud.NetworkService; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.wizard.validation.ValidationResult; | |||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import org.springframework.stereotype.Repository; | import org.springframework.stereotype.Repository; | ||||
import java.util.List; | import java.util.List; | ||||
import static bubble.model.cloud.BubbleNetwork.validateHostname; | |||||
import static java.util.concurrent.TimeUnit.SECONDS; | import static java.util.concurrent.TimeUnit.SECONDS; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.background; | import static org.cobbzilla.util.daemon.ZillaRuntime.background; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | import static org.cobbzilla.util.daemon.ZillaRuntime.now; | ||||
@@ -69,6 +71,9 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO<AccountPlan> { | |||||
} | } | ||||
@Override public Object preCreate(AccountPlan accountPlan) { | @Override public Object preCreate(AccountPlan accountPlan) { | ||||
final ValidationResult errors = validateHostname(accountPlan); | |||||
if (errors.isInvalid()) throw invalidEx(errors); | |||||
if (configuration.paymentsEnabled()) { | if (configuration.paymentsEnabled()) { | ||||
if (!accountPlan.hasPaymentMethodObject()) throw invalidEx("err.paymentMethod.required"); | if (!accountPlan.hasPaymentMethodObject()) throw invalidEx("err.paymentMethod.required"); | ||||
if (!accountPlan.getPaymentMethodObject().hasUuid()) throw invalidEx("err.paymentMethod.required"); | if (!accountPlan.getPaymentMethodObject().hasUuid()) throw invalidEx("err.paymentMethod.required"); | ||||
@@ -4,6 +4,7 @@ import bubble.dao.account.AccountOwnedEntityDAO; | |||||
import bubble.model.bill.BubblePlan; | import bubble.model.bill.BubblePlan; | ||||
import bubble.model.cloud.BubbleNode; | import bubble.model.cloud.BubbleNode; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import org.hibernate.criterion.Order; | |||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import org.springframework.stereotype.Repository; | import org.springframework.stereotype.Repository; | ||||
@@ -14,6 +15,8 @@ public class BubblePlanDAO extends AccountOwnedEntityDAO<BubblePlan> { | |||||
@Override public boolean dbFilterIncludeAll() { return true; } | @Override public boolean dbFilterIncludeAll() { return true; } | ||||
@Override public Order getDefaultSortOrder() { return Order.asc("priority"); } | |||||
@Override public BubblePlan findByUuid(String uuid) { | @Override public BubblePlan findByUuid(String uuid) { | ||||
final BubblePlan plan = super.findByUuid(uuid); | final BubblePlan plan = super.findByUuid(uuid); | ||||
if (plan != null) return plan; | if (plan != null) return plan; | ||||
@@ -12,6 +12,7 @@ import bubble.service.boot.SelfNodeService; | |||||
import bubble.service.cloud.NetworkService; | import bubble.service.cloud.NetworkService; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.wizard.model.IdentifiableBase; | import org.cobbzilla.wizard.model.IdentifiableBase; | ||||
import org.cobbzilla.wizard.validation.ValidationResult; | |||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import org.springframework.stereotype.Repository; | import org.springframework.stereotype.Repository; | ||||
@@ -19,7 +20,9 @@ import java.io.IOException; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import static bubble.model.cloud.BubbleNetwork.validateHostname; | |||||
import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE; | import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE; | ||||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||||
@Repository @Slf4j | @Repository @Slf4j | ||||
public class BubbleNetworkDAO extends AccountOwnedEntityDAO<BubbleNetwork> { | public class BubbleNetworkDAO extends AccountOwnedEntityDAO<BubbleNetwork> { | ||||
@@ -34,6 +37,9 @@ public class BubbleNetworkDAO extends AccountOwnedEntityDAO<BubbleNetwork> { | |||||
@Autowired private BubbleConfiguration configuration; | @Autowired private BubbleConfiguration configuration; | ||||
@Override public Object preCreate(BubbleNetwork network) { | @Override public Object preCreate(BubbleNetwork network) { | ||||
final ValidationResult errors = validateHostname(network); | |||||
if (errors.isInvalid()) throw invalidEx(errors); | |||||
if (!network.hasLocale()) network.setLocale(getDEFAULT_LOCALE()); | if (!network.hasLocale()) network.setLocale(getDEFAULT_LOCALE()); | ||||
return super.preCreate(network); | return super.preCreate(network); | ||||
} | } | ||||
@@ -42,7 +42,6 @@ public class AccountSshKey extends IdentifiableBase implements HasAccount { | |||||
@HasValue(message="err.name.required") | @HasValue(message="err.name.required") | ||||
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=100) | @ECIndex(unique=true) @Column(nullable=false, updatable=false, length=100) | ||||
@Getter @Setter private String name; | @Getter @Setter private String name; | ||||
public boolean hasName () { return !empty(name); } | |||||
@ECSearchable @ECField(index=20) | @ECSearchable @ECField(index=20) | ||||
@ECForeignKey(entity=Account.class) | @ECForeignKey(entity=Account.class) | ||||
@@ -10,6 +10,5 @@ public interface HasAccount extends Identifiable, NamedEntity, SqlViewSearchResu | |||||
<E> E setAccount (String account); | <E> E setAccount (String account); | ||||
default boolean hasAccount () { return getAccount() != null; } | default boolean hasAccount () { return getAccount() != null; } | ||||
String getName(); | String getName(); | ||||
default boolean hasName() { return getName() != null; } | |||||
} | } |
@@ -22,6 +22,7 @@ import javax.persistence.Transient; | |||||
import javax.validation.constraints.Size; | import javax.validation.constraints.Size; | ||||
import static bubble.model.bill.BillPeriod.BILL_START_END_FORMAT; | import static bubble.model.bill.BillPeriod.BILL_START_END_FORMAT; | ||||
import static bubble.model.cloud.BubbleNetwork.NETWORK_NAME_MAXLEN; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.bool; | import static org.cobbzilla.util.daemon.ZillaRuntime.bool; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | ||||
@@ -50,8 +51,8 @@ public class AccountPlan extends IdentifiableBase implements HasAccount { | |||||
// mirrors network name | // mirrors network name | ||||
@ECSearchable(filter=true) @ECField(index=10) | @ECSearchable(filter=true) @ECField(index=10) | ||||
@Size(max=100, message="err.name.length") | |||||
@Column(length=100, nullable=false) | |||||
@Size(max=NETWORK_NAME_MAXLEN, message="err.name.length") | |||||
@Column(length=NETWORK_NAME_MAXLEN, nullable=false) | |||||
@Getter @Setter private String name; | @Getter @Setter private String name; | ||||
@ECSearchable @ECField(index=20) | @ECSearchable @ECField(index=20) | ||||
@@ -138,6 +139,9 @@ public class AccountPlan extends IdentifiableBase implements HasAccount { | |||||
@Transient @Getter @Setter private transient AccountPaymentMethod paymentMethodObject = null; | @Transient @Getter @Setter private transient AccountPaymentMethod paymentMethodObject = null; | ||||
public boolean hasPaymentMethodObject () { return paymentMethodObject != null; } | public boolean hasPaymentMethodObject () { return paymentMethodObject != null; } | ||||
@Transient @Getter @Setter private transient String forkHost = null; | |||||
public boolean hasForkHost () { return !empty(forkHost); } | |||||
public BubbleNetwork bubbleNetwork(Account account, | public BubbleNetwork bubbleNetwork(Account account, | ||||
BubbleDomain domain, | BubbleDomain domain, | ||||
BubblePlan plan, | BubblePlan plan, | ||||
@@ -153,7 +157,8 @@ public class AccountPlan extends IdentifiableBase implements HasAccount { | |||||
.setDomainName(domain.getName()) | .setDomainName(domain.getName()) | ||||
.setFootprint(getFootprint()) | .setFootprint(getFootprint()) | ||||
.setComputeSizeType(plan.getComputeSizeType()) | .setComputeSizeType(plan.getComputeSizeType()) | ||||
.setStorage(storage.getUuid()); | |||||
.setStorage(storage.getUuid()) | |||||
.setForkHost(hasForkHost() ? getForkHost() : null); | |||||
} | } | ||||
} | } |
@@ -8,6 +8,7 @@ import lombok.Getter; | |||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
import org.cobbzilla.util.collection.HasPriority; | |||||
import org.cobbzilla.wizard.model.IdentifiableBase; | import org.cobbzilla.wizard.model.IdentifiableBase; | ||||
import org.cobbzilla.wizard.model.entityconfig.annotations.*; | import org.cobbzilla.wizard.model.entityconfig.annotations.*; | ||||
import org.cobbzilla.wizard.validation.HasValue; | import org.cobbzilla.wizard.validation.HasValue; | ||||
@@ -32,13 +33,13 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||||
}) | }) | ||||
@Entity @NoArgsConstructor @Accessors(chain=true) | @Entity @NoArgsConstructor @Accessors(chain=true) | ||||
@ECIndexes({ @ECIndex(unique=true, of={"account", "name"}) }) | @ECIndexes({ @ECIndex(unique=true, of={"account", "name"}) }) | ||||
public class BubblePlan extends IdentifiableBase implements HasAccount { | |||||
public class BubblePlan extends IdentifiableBase implements HasAccount, HasPriority { | |||||
public static final int MAX_CHARGENAME_LEN = 12; | public static final int MAX_CHARGENAME_LEN = 12; | ||||
public static final String[] CREATE_FIELDS = { | public static final String[] CREATE_FIELDS = { | ||||
"name", "chargeName", "enabled", "price", "period", "computeSizeType", | |||||
"nodesIncluded", "additionalPerNodePrice", | |||||
"name", "chargeName", "enabled", "priority", "price", "period", | |||||
"computeSizeType", "nodesIncluded", "additionalPerNodePrice", | |||||
"storageGbIncluded", "additionalStoragePerGbPrice", | "storageGbIncluded", "additionalStoragePerGbPrice", | ||||
"bandwidthGbIncluded", "additionalBandwidthPerGbPrice" | "bandwidthGbIncluded", "additionalBandwidthPerGbPrice" | ||||
}; | }; | ||||
@@ -77,27 +78,30 @@ public class BubblePlan extends IdentifiableBase implements HasAccount { | |||||
@Getter @Setter private Boolean enabled = true; | @Getter @Setter private Boolean enabled = true; | ||||
public boolean enabled () { return enabled == null || enabled; } | public boolean enabled () { return enabled == null || enabled; } | ||||
@ECSearchable @ECField(index=50) | |||||
@ECSearchable @ECField(index=50) @Column(nullable=false) | |||||
@ECIndex @Getter @Setter private Integer priority = 1; | |||||
@ECSearchable @ECField(index=60) | |||||
@ECIndex @Column(nullable=false) | @ECIndex @Column(nullable=false) | ||||
@Getter @Setter private Long price; | @Getter @Setter private Long price; | ||||
@ECSearchable @ECField(index=60) | |||||
@ECSearchable @ECField(index=70) | |||||
@ECIndex @Column(nullable=false, length=10) | @ECIndex @Column(nullable=false, length=10) | ||||
@Getter @Setter private String currency = "USD"; | @Getter @Setter private String currency = "USD"; | ||||
@ECSearchable @ECField(index=70) | |||||
@ECSearchable @ECField(index=80) | |||||
@Enumerated(EnumType.STRING) @Column(nullable=false, updatable=false, length=20) | @Enumerated(EnumType.STRING) @Column(nullable=false, updatable=false, length=20) | ||||
@Getter @Setter private BillPeriod period = BillPeriod.monthly; | @Getter @Setter private BillPeriod period = BillPeriod.monthly; | ||||
@ECSearchable @ECField(index=80) | |||||
@ECSearchable @ECField(index=90) | |||||
@ECIndex @Column(nullable=false, updatable=false, length=20) | @ECIndex @Column(nullable=false, updatable=false, length=20) | ||||
@Enumerated(EnumType.STRING) @Getter @Setter private ComputeNodeSizeType computeSizeType; | @Enumerated(EnumType.STRING) @Getter @Setter private ComputeNodeSizeType computeSizeType; | ||||
@ECSearchable @ECField(index=90) | |||||
@ECSearchable @ECField(index=100) | |||||
@Column(nullable=false, updatable=false) | @Column(nullable=false, updatable=false) | ||||
@Getter @Setter private Integer nodesIncluded; | @Getter @Setter private Integer nodesIncluded; | ||||
@ECSearchable @ECField(index=100) | |||||
@ECSearchable @ECField(index=110) | |||||
@Column(nullable=false, updatable=false) | @Column(nullable=false, updatable=false) | ||||
@Getter @Setter private Integer additionalPerNodePrice; | @Getter @Setter private Integer additionalPerNodePrice; | ||||
@@ -14,9 +14,11 @@ import lombok.experimental.Accessors; | |||||
import org.cobbzilla.util.collection.ArrayUtil; | import org.cobbzilla.util.collection.ArrayUtil; | ||||
import org.cobbzilla.wizard.model.Identifiable; | import org.cobbzilla.wizard.model.Identifiable; | ||||
import org.cobbzilla.wizard.model.IdentifiableBase; | import org.cobbzilla.wizard.model.IdentifiableBase; | ||||
import org.cobbzilla.wizard.model.NamedEntity; | |||||
import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; | import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; | ||||
import org.cobbzilla.wizard.model.entityconfig.annotations.*; | import org.cobbzilla.wizard.model.entityconfig.annotations.*; | ||||
import org.cobbzilla.wizard.validation.HasValue; | import org.cobbzilla.wizard.validation.HasValue; | ||||
import org.cobbzilla.wizard.validation.ValidationResult; | |||||
import org.hibernate.annotations.Type; | import org.hibernate.annotations.Type; | ||||
import javax.persistence.*; | import javax.persistence.*; | ||||
@@ -33,6 +35,8 @@ import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | import static org.cobbzilla.util.daemon.ZillaRuntime.die; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | ||||
import static org.cobbzilla.util.string.ValidationRegexes.HOST_PART_PATTERN; | |||||
import static org.cobbzilla.util.string.ValidationRegexes.validateRegexMatches; | |||||
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; | import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; | ||||
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | ||||
@@ -67,9 +71,13 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||||
@Transient @JsonIgnore public String getNetwork () { return getUuid(); } | @Transient @JsonIgnore public String getNetwork () { return getUuid(); } | ||||
public static final int NETWORK_NAME_MAXLEN = 100; | |||||
public static final int NETWORK_NAME_MINLEN = 4; | |||||
@ECSearchable @ECField(index=10) | @ECSearchable @ECField(index=10) | ||||
@HasValue(message="err.name.required") | @HasValue(message="err.name.required") | ||||
@ECIndex @Column(nullable=false, updatable=false, length=200) | |||||
@Size(min=NETWORK_NAME_MINLEN, max=NETWORK_NAME_MAXLEN, message="err.name.length") | |||||
@ECIndex @Column(nullable=false, updatable=false, length=NETWORK_NAME_MAXLEN) | |||||
@Getter @Setter private String name; | @Getter @Setter private String name; | ||||
@ECSearchable @ECField(index=20) | @ECSearchable @ECField(index=20) | ||||
@@ -143,4 +151,22 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||||
} | } | ||||
return die("hostFromFqdn("+fqdn+"): expected suffix ."+getNetworkDomain()); | return die("hostFromFqdn("+fqdn+"): expected suffix ."+getNetworkDomain()); | ||||
} | } | ||||
public static ValidationResult validateHostname(NamedEntity request) { | |||||
return validateHostname(request, new ValidationResult()); | |||||
} | |||||
public static ValidationResult validateHostname(NamedEntity request, ValidationResult errors) { | |||||
if (!request.hasName()) { | |||||
errors.addViolation("err.name.required"); | |||||
} else if (!validateRegexMatches(HOST_PART_PATTERN, request.getName())) { | |||||
errors.addViolation("err.name.invalid"); | |||||
} else if (request.getName().length() > NETWORK_NAME_MAXLEN) { | |||||
errors.addViolation("err.name.length"); | |||||
} else if (request.getName().length() < NETWORK_NAME_MINLEN) { | |||||
errors.addViolation("err.name.tooShort"); | |||||
} | |||||
return errors; | |||||
} | |||||
} | } |
@@ -102,7 +102,7 @@ public class CloudService extends IdentifiableBaseParentEntity implements Accoun | |||||
@ECIndex @Column(nullable=false, updatable=false, length=20) | @ECIndex @Column(nullable=false, updatable=false, length=20) | ||||
@Getter @Setter private CloudServiceType type; | @Getter @Setter private CloudServiceType type; | ||||
@ECSearchable @ECField(index=50) | |||||
@ECSearchable @ECField(index=50) @Column(nullable=false) | |||||
@ECIndex @Getter @Setter private Integer priority = 1; | @ECIndex @Getter @Setter private Integer priority = 1; | ||||
@ECSearchable @ECField(index=60) | @ECSearchable @ECField(index=60) | ||||
@@ -38,6 +38,8 @@ import java.util.List; | |||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import static bubble.ApiConstants.*; | import static bubble.ApiConstants.*; | ||||
import static org.cobbzilla.util.string.ValidationRegexes.HOST_PART_PATTERN; | |||||
import static org.cobbzilla.util.string.ValidationRegexes.validateRegexMatches; | |||||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | import static org.cobbzilla.wizard.resources.ResourceUtil.*; | ||||
@Slf4j | @Slf4j | ||||
@@ -115,6 +117,16 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco | |||||
request.setSshKey(null); // if it's an empty string, make it null (see simple_network test) | request.setSshKey(null); // if it's an empty string, make it null (see simple_network test) | ||||
} | } | ||||
if (request.hasForkHost()) { | |||||
if (!configuration.isSageLauncher()) { | |||||
errors.addViolation("err.forkHost.notAllowed"); | |||||
} else if (!validateRegexMatches(HOST_PART_PATTERN, request.getForkHost())) { | |||||
errors.addViolation("err.forkHost.invalid"); | |||||
} | |||||
} else { | |||||
BubbleNetwork.validateHostname(request, errors); | |||||
} | |||||
final BubbleDomain domain = domainDAO.findByAccountAndId(caller.getUuid(), request.getDomain()); | final BubbleDomain domain = domainDAO.findByAccountAndId(caller.getUuid(), request.getDomain()); | ||||
if (domain == null) { | if (domain == null) { | ||||
log.info("setReferences: domain not found: "+request.getDomain()+" for caller: "+caller.getUuid()); | log.info("setReferences: domain not found: "+request.getDomain()+" for caller: "+caller.getUuid()); | ||||
@@ -186,6 +186,11 @@ message_no_verified_contacts_subtext=Before creating your first Bubble, please v | |||||
form_title_new_network=New Bubble | form_title_new_network=New Bubble | ||||
field_label_network_name=Name | field_label_network_name=Name | ||||
field_label_network_type=Bubble Type | |||||
field_label_network_type_regular=Regular | |||||
field_label_network_type_fork=Fork | |||||
field_label_network_fork_host=Fork Host | |||||
field_description_network_fork_host=The current Sage Launcher will be forked onto this host. | |||||
field_label_network_domain=Domain | field_label_network_domain=Domain | ||||
field_label_plan=Plan | field_label_plan=Plan | ||||
field_label_show_advanced_plan_options=Show All Options | field_label_show_advanced_plan_options=Show All Options | ||||
@@ -222,11 +227,13 @@ link_network_action_delete_description=Delete this Bubble and all backups. You w | |||||
# Bubble Plans | # Bubble Plans | ||||
plan_name_bubble=Bubble Standard | plan_name_bubble=Bubble Standard | ||||
plan_description_bubble=Try this one first. Most users probably don't need anything more. | |||||
plan_description_bubble=Try this one first. Most users probably don't need anything more | |||||
plan_name_bubble_plus=Bubble Plus | plan_name_bubble_plus=Bubble Plus | ||||
plan_description_bubble_plus=Enhanced for faster performance. | |||||
plan_description_bubble_plus=Enhanced for faster performance | |||||
plan_name_bubble_big=Bubble Big | |||||
plan_description_bubble_big=A truly powerful bubble node | |||||
plan_name_bubble_pro=Bubble Pro | plan_name_bubble_pro=Bubble Pro | ||||
plan_description_bubble_pro=Two bubble nodes operate in concert to improve overall reliability. | |||||
plan_description_bubble_pro=Two bubble nodes operate in concert to improve overall reliability | |||||
# Footprints | # Footprints | ||||
footprint_name_US=United States | footprint_name_US=United States | ||||
@@ -418,6 +425,8 @@ err.email.invalid=Email address is invalid | |||||
err.email.required=Email address is required | err.email.required=Email address is required | ||||
err.expiration.cannotCreateSshKeyAlreadyExpired=Expiration date has already passed | err.expiration.cannotCreateSshKeyAlreadyExpired=Expiration date has already passed | ||||
err.footprint.required=Footprint is required | err.footprint.required=Footprint is required | ||||
err.forkHost.notAllowed=Fork Host is not allowed | |||||
err.forkHost.invalid=Fork Host is not a valid hostname | |||||
err.fqdn.domain.invalid=Domain not found for FQDN | err.fqdn.domain.invalid=Domain not found for FQDN | ||||
err.fqdn.length=FQDN is too long | err.fqdn.length=FQDN is too long | ||||
err.fqdn.network.invalid=network not found for FQDN | err.fqdn.network.invalid=network not found for FQDN | ||||
@@ -442,6 +451,7 @@ err.nodeOperationTimeout.required=Node operation timeout is required | |||||
err.nodeOperationTimeout.tooLong=Node operation timeout cannot be longer than 3 days | err.nodeOperationTimeout.tooLong=Node operation timeout cannot be longer than 3 days | ||||
err.nodeOperationTimeout.tooShort=Node operation timeout cannot be shorter than 1 minute | err.nodeOperationTimeout.tooShort=Node operation timeout cannot be shorter than 1 minute | ||||
err.name.length=Name is too long | err.name.length=Name is too long | ||||
err.name.tooShort=Name is too short | |||||
err.name.mismatch=Name mismatch | err.name.mismatch=Name mismatch | ||||
err.name.reserved=Name is reserved | err.name.reserved=Name is reserved | ||||
err.netlocation.invalid=Must specify both cloud and region, or neither | err.netlocation.invalid=Must specify both cloud and region, or neither | ||||
@@ -1,5 +1,6 @@ | |||||
[{ | [{ | ||||
"name": "bubble", | "name": "bubble", | ||||
"priority": 1000, | |||||
"chargeName": "BubbleVPN", | "chargeName": "BubbleVPN", | ||||
"computeSizeType": "small", | "computeSizeType": "small", | ||||
"nodesIncluded": 1, | "nodesIncluded": 1, | ||||
@@ -11,6 +12,7 @@ | |||||
"additionalBandwidthPerGbPrice": 0 | "additionalBandwidthPerGbPrice": 0 | ||||
}, { | }, { | ||||
"name": "bubble_plus", | "name": "bubble_plus", | ||||
"priority": 2000, | |||||
"chargeName": "BubblePlus", | "chargeName": "BubblePlus", | ||||
"computeSizeType": "medium", | "computeSizeType": "medium", | ||||
"nodesIncluded": 1, | "nodesIncluded": 1, | ||||
@@ -20,39 +20,4 @@ | |||||
"additionalStoragePerGbPrice": 2, | "additionalStoragePerGbPrice": 2, | ||||
"bandwidthGbIncluded": 1000, | "bandwidthGbIncluded": 1000, | ||||
"additionalBandwidthPerGbPrice": 2 | "additionalBandwidthPerGbPrice": 2 | ||||
}, { | |||||
"name": "bubble_big", | |||||
"chargeName": "BubbleBig", | |||||
"computeSizeType": "large", | |||||
"nodesIncluded": 1, | |||||
"additionalPerNodePrice": 2800, | |||||
"price": 2800, | |||||
"storageGbIncluded": 70, | |||||
"additionalStoragePerGbPrice": 2, | |||||
"bandwidthGbIncluded": 4000, | |||||
"additionalBandwidthPerGbPrice": 2 | |||||
}//, | |||||
// { | |||||
// "name": "bubble_pro", | |||||
// "chargeName": "BubblePro", | |||||
// "computeSizeType": "small", | |||||
// "nodesIncluded": 2, | |||||
// "additionalPerNodePrice": 1200, | |||||
// "price": 2500, | |||||
// "storageGbIncluded": 25, | |||||
// "additionalStoragePerGbPrice": 2, | |||||
// "bandwidthGbIncluded": 1000, | |||||
// "additionalBandwidthPerGbPrice": 2 | |||||
// }, | |||||
// { | |||||
// "name": "bubble_power_user", | |||||
// "computeSizeType": "medium", | |||||
// "nodesIncluded": 3, | |||||
// "additionalPerNodePrice": 2500, | |||||
// "price": 7500, | |||||
// "storageGbIncluded": 50, | |||||
// "additionalStoragePerGbPrice": 2, | |||||
// "bandwidthGbIncluded": 2000, | |||||
// "additionalBandwidthPerGbPrice": 2 | |||||
// } | |||||
] | |||||
}] |
@@ -1 +1 @@ | |||||
Subproject commit 10a000cac0bda587ddd16c402672cc464689c372 | |||||
Subproject commit a45c053235fc63154b27f6833958a3be7f73fac0 |
@@ -1 +1 @@ | |||||
Subproject commit 7e349b52203f4f8a49b6539f83804bcb1a9bc905 | |||||
Subproject commit 31dde8a290f6339c34866b1e15e17ef0041034e9 |
@@ -1 +1 @@ | |||||
Subproject commit b59b6ec756aa38bfeb358c3431c5dc5b1bf594b8 | |||||
Subproject commit b171791b38e47716ff299b99b8941ce60e9ee4d9 |