Bladeren bron

cleaning up models, add ssl cert for cname, prepare for forking from ui

tags/v0.1.8
Jonathan Cobb 4 jaren geleden
bovenliggende
commit
851b1b295b
23 gewijzigde bestanden met toevoegingen van 114 en 62 verwijderingen
  1. +1
    -0
      automation/roles/bubble/files/bubble_role.json
  2. +1
    -1
      automation/roles/bubble/templates/bubble.env.j2
  3. +1
    -0
      automation/roles/nginx/files/bubble_role.json
  4. +5
    -0
      automation/roles/nginx/files/certbot_renew.sh
  5. +2
    -1
      automation/roles/nginx/files/init_certbot.sh
  6. +9
    -1
      automation/roles/nginx/tasks/main.yml
  7. +1
    -1
      automation/roles/nginx/templates/site.conf.j2
  8. +5
    -0
      bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java
  9. +3
    -0
      bubble-server/src/main/java/bubble/dao/bill/BubblePlanDAO.java
  10. +6
    -0
      bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java
  11. +0
    -1
      bubble-server/src/main/java/bubble/model/account/AccountSshKey.java
  12. +0
    -1
      bubble-server/src/main/java/bubble/model/account/HasAccount.java
  13. +8
    -3
      bubble-server/src/main/java/bubble/model/bill/AccountPlan.java
  14. +13
    -9
      bubble-server/src/main/java/bubble/model/bill/BubblePlan.java
  15. +27
    -1
      bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java
  16. +1
    -1
      bubble-server/src/main/java/bubble/model/cloud/CloudService.java
  17. +12
    -0
      bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java
  18. +13
    -3
      bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties
  19. +2
    -0
      bubble-server/src/main/resources/models/defaults/bubblePlan.json
  20. +1
    -36
      bubble-server/src/test/resources/models/system/bubblePlan.json
  21. +1
    -1
      bubble-web
  22. +1
    -1
      utils/cobbzilla-utils
  23. +1
    -1
      utils/cobbzilla-wizard

+ 1
- 0
automation/roles/bubble/files/bubble_role.json Bestand weergeven

@@ -12,6 +12,7 @@
{"name": "default_locale", "value": "[[network.locale]]"},
{"name": "bubble_version", "value": "[[configuration.version]]"},
{"name": "bubble_host", "value": "[[node.fqdn]]"},
{"name": "bubble_cname", "value": "[[node.networkDomain]]"},
{"name": "admin_user", "value": "[[node.user]]"},
{"name": "db_encoding", "value": "UTF-8"},
{"name": "db_locale", "value": "en_US"},


+ 1
- 1
automation/roles/bubble/templates/bubble.env.j2 Bestand weergeven

@@ -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 SAGE_NODE={{ sage_node }}
export LETSENCRYPT_EMAIL={{ letsencrypt_email }}


+ 1
- 0
automation/roles/nginx/files/bubble_role.json Bestand weergeven

@@ -4,6 +4,7 @@
"template": true,
"config": [
{"name": "server_name", "value": "[[node.fqdn]]"},
{"name": "server_alias", "value": "[[node.networkDomain]]"},
{"name": "letsencrypt_email", "value": "[[configuration.letsencryptEmail]]"},
{"name": "ssl_port", "value": "[[configuration.nginxPort]]"},
{"name": "admin_port", "value": "[[node.adminPort]]"}


+ 5
- 0
automation/roles/nginx/files/certbot_renew.sh Bestand weergeven

@@ -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
- 1
automation/roles/nginx/files/init_certbot.sh Bestand weergeven

@@ -2,12 +2,13 @@

LE_EMAIL="${1}"
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
certbot register --agree-tos -m ${LE_EMAIL} --non-interactive
fi

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
certbot renew --standalone --non-interactive
fi

+ 9
- 1
automation/roles/nginx/tasks/main.yml Bestand weergeven

@@ -48,7 +48,15 @@
mode: 0555

- 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
- name: Create a strong dhparam.pem


+ 1
- 1
automation/roles/nginx/templates/site.conf.j2 Bestand weergeven

@@ -1,6 +1,6 @@

server {
server_name {{ server_name }};
server_name {{ server_name }} {{ server_alias }};
listen {{ ssl_port }} ssl http2;

ssl_certificate /etc/letsencrypt/live/{{ server_name }}/fullchain.pem;


+ 5
- 0
bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java Bestand weergeven

@@ -15,11 +15,13 @@ import bubble.server.BubbleConfiguration;
import bubble.service.bill.RefundService;
import bubble.service.cloud.NetworkService;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.wizard.validation.ValidationResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.List;

import static bubble.model.cloud.BubbleNetwork.validateHostname;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.cobbzilla.util.daemon.ZillaRuntime.background;
import static org.cobbzilla.util.daemon.ZillaRuntime.now;
@@ -69,6 +71,9 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO<AccountPlan> {
}

@Override public Object preCreate(AccountPlan accountPlan) {
final ValidationResult errors = validateHostname(accountPlan);
if (errors.isInvalid()) throw invalidEx(errors);

if (configuration.paymentsEnabled()) {
if (!accountPlan.hasPaymentMethodObject()) throw invalidEx("err.paymentMethod.required");
if (!accountPlan.getPaymentMethodObject().hasUuid()) throw invalidEx("err.paymentMethod.required");


+ 3
- 0
bubble-server/src/main/java/bubble/dao/bill/BubblePlanDAO.java Bestand weergeven

@@ -4,6 +4,7 @@ import bubble.dao.account.AccountOwnedEntityDAO;
import bubble.model.bill.BubblePlan;
import bubble.model.cloud.BubbleNode;
import bubble.server.BubbleConfiguration;
import org.hibernate.criterion.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@@ -14,6 +15,8 @@ public class BubblePlanDAO extends AccountOwnedEntityDAO<BubblePlan> {

@Override public boolean dbFilterIncludeAll() { return true; }

@Override public Order getDefaultSortOrder() { return Order.asc("priority"); }

@Override public BubblePlan findByUuid(String uuid) {
final BubblePlan plan = super.findByUuid(uuid);
if (plan != null) return plan;


+ 6
- 0
bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java Bestand weergeven

@@ -12,6 +12,7 @@ import bubble.service.boot.SelfNodeService;
import bubble.service.cloud.NetworkService;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.wizard.model.IdentifiableBase;
import org.cobbzilla.wizard.validation.ValidationResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@@ -19,7 +20,9 @@ import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

import static bubble.model.cloud.BubbleNetwork.validateHostname;
import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE;
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;

@Repository @Slf4j
public class BubbleNetworkDAO extends AccountOwnedEntityDAO<BubbleNetwork> {
@@ -34,6 +37,9 @@ public class BubbleNetworkDAO extends AccountOwnedEntityDAO<BubbleNetwork> {
@Autowired private BubbleConfiguration configuration;

@Override public Object preCreate(BubbleNetwork network) {
final ValidationResult errors = validateHostname(network);
if (errors.isInvalid()) throw invalidEx(errors);

if (!network.hasLocale()) network.setLocale(getDEFAULT_LOCALE());
return super.preCreate(network);
}


+ 0
- 1
bubble-server/src/main/java/bubble/model/account/AccountSshKey.java Bestand weergeven

@@ -42,7 +42,6 @@ public class AccountSshKey extends IdentifiableBase implements HasAccount {
@HasValue(message="err.name.required")
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=100)
@Getter @Setter private String name;
public boolean hasName () { return !empty(name); }

@ECSearchable @ECField(index=20)
@ECForeignKey(entity=Account.class)


+ 0
- 1
bubble-server/src/main/java/bubble/model/account/HasAccount.java Bestand weergeven

@@ -10,6 +10,5 @@ public interface HasAccount extends Identifiable, NamedEntity, SqlViewSearchResu
<E> E setAccount (String account);
default boolean hasAccount () { return getAccount() != null; }
String getName();
default boolean hasName() { return getName() != null; }

}

+ 8
- 3
bubble-server/src/main/java/bubble/model/bill/AccountPlan.java Bestand weergeven

@@ -22,6 +22,7 @@ import javax.persistence.Transient;
import javax.validation.constraints.Size;

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.empty;
import static org.cobbzilla.util.reflect.ReflectionUtil.copy;
@@ -50,8 +51,8 @@ public class AccountPlan extends IdentifiableBase implements HasAccount {

// mirrors network name
@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;

@ECSearchable @ECField(index=20)
@@ -138,6 +139,9 @@ public class AccountPlan extends IdentifiableBase implements HasAccount {
@Transient @Getter @Setter private transient AccountPaymentMethod 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,
BubbleDomain domain,
BubblePlan plan,
@@ -153,7 +157,8 @@ public class AccountPlan extends IdentifiableBase implements HasAccount {
.setDomainName(domain.getName())
.setFootprint(getFootprint())
.setComputeSizeType(plan.getComputeSizeType())
.setStorage(storage.getUuid());
.setStorage(storage.getUuid())
.setForkHost(hasForkHost() ? getForkHost() : null);
}

}

+ 13
- 9
bubble-server/src/main/java/bubble/model/bill/BubblePlan.java Bestand weergeven

@@ -8,6 +8,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.cobbzilla.util.collection.HasPriority;
import org.cobbzilla.wizard.model.IdentifiableBase;
import org.cobbzilla.wizard.model.entityconfig.annotations.*;
import org.cobbzilla.wizard.validation.HasValue;
@@ -32,13 +33,13 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy;
})
@Entity @NoArgsConstructor @Accessors(chain=true)
@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 String[] CREATE_FIELDS = {
"name", "chargeName", "enabled", "price", "period", "computeSizeType",
"nodesIncluded", "additionalPerNodePrice",
"name", "chargeName", "enabled", "priority", "price", "period",
"computeSizeType", "nodesIncluded", "additionalPerNodePrice",
"storageGbIncluded", "additionalStoragePerGbPrice",
"bandwidthGbIncluded", "additionalBandwidthPerGbPrice"
};
@@ -77,27 +78,30 @@ public class BubblePlan extends IdentifiableBase implements HasAccount {
@Getter @Setter private Boolean enabled = true;
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)
@Getter @Setter private Long price;

@ECSearchable @ECField(index=60)
@ECSearchable @ECField(index=70)
@ECIndex @Column(nullable=false, length=10)
@Getter @Setter private String currency = "USD";

@ECSearchable @ECField(index=70)
@ECSearchable @ECField(index=80)
@Enumerated(EnumType.STRING) @Column(nullable=false, updatable=false, length=20)
@Getter @Setter private BillPeriod period = BillPeriod.monthly;

@ECSearchable @ECField(index=80)
@ECSearchable @ECField(index=90)
@ECIndex @Column(nullable=false, updatable=false, length=20)
@Enumerated(EnumType.STRING) @Getter @Setter private ComputeNodeSizeType computeSizeType;

@ECSearchable @ECField(index=90)
@ECSearchable @ECField(index=100)
@Column(nullable=false, updatable=false)
@Getter @Setter private Integer nodesIncluded;

@ECSearchable @ECField(index=100)
@ECSearchable @ECField(index=110)
@Column(nullable=false, updatable=false)
@Getter @Setter private Integer additionalPerNodePrice;



+ 27
- 1
bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java Bestand weergeven

@@ -14,9 +14,11 @@ import lombok.experimental.Accessors;
import org.cobbzilla.util.collection.ArrayUtil;
import org.cobbzilla.wizard.model.Identifiable;
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.annotations.*;
import org.cobbzilla.wizard.validation.HasValue;
import org.cobbzilla.wizard.validation.ValidationResult;
import org.hibernate.annotations.Type;

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.empty;
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.ENC_PAD;

@@ -67,9 +71,13 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu

@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)
@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;

@ECSearchable @ECField(index=20)
@@ -143,4 +151,22 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu
}
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;
}

}

+ 1
- 1
bubble-server/src/main/java/bubble/model/cloud/CloudService.java Bestand weergeven

@@ -102,7 +102,7 @@ public class CloudService extends IdentifiableBaseParentEntity implements Accoun
@ECIndex @Column(nullable=false, updatable=false, length=20)
@Getter @Setter private CloudServiceType type;

@ECSearchable @ECField(index=50)
@ECSearchable @ECField(index=50) @Column(nullable=false)
@ECIndex @Getter @Setter private Integer priority = 1;

@ECSearchable @ECField(index=60)


+ 12
- 0
bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java Bestand weergeven

@@ -38,6 +38,8 @@ import java.util.List;
import java.util.stream.Collectors;

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.*;

@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)
}

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());
if (domain == null) {
log.info("setReferences: domain not found: "+request.getDomain()+" for caller: "+caller.getUuid());


+ 13
- 3
bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties Bestand weergeven

@@ -186,6 +186,11 @@ message_no_verified_contacts_subtext=Before creating your first Bubble, please v

form_title_new_network=New Bubble
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_plan=Plan
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
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_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_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
footprint_name_US=United States
@@ -418,6 +425,8 @@ err.email.invalid=Email address is invalid
err.email.required=Email address is required
err.expiration.cannotCreateSshKeyAlreadyExpired=Expiration date has already passed
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.length=FQDN is too long
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.tooShort=Node operation timeout cannot be shorter than 1 minute
err.name.length=Name is too long
err.name.tooShort=Name is too short
err.name.mismatch=Name mismatch
err.name.reserved=Name is reserved
err.netlocation.invalid=Must specify both cloud and region, or neither


+ 2
- 0
bubble-server/src/main/resources/models/defaults/bubblePlan.json Bestand weergeven

@@ -1,5 +1,6 @@
[{
"name": "bubble",
"priority": 1000,
"chargeName": "BubbleVPN",
"computeSizeType": "small",
"nodesIncluded": 1,
@@ -11,6 +12,7 @@
"additionalBandwidthPerGbPrice": 0
}, {
"name": "bubble_plus",
"priority": 2000,
"chargeName": "BubblePlus",
"computeSizeType": "medium",
"nodesIncluded": 1,


+ 1
- 36
bubble-server/src/test/resources/models/system/bubblePlan.json Bestand weergeven

@@ -20,39 +20,4 @@
"additionalStoragePerGbPrice": 2,
"bandwidthGbIncluded": 1000,
"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
bubble-web

@@ -1 +1 @@
Subproject commit 10a000cac0bda587ddd16c402672cc464689c372
Subproject commit a45c053235fc63154b27f6833958a3be7f73fac0

+ 1
- 1
utils/cobbzilla-utils

@@ -1 +1 @@
Subproject commit 7e349b52203f4f8a49b6539f83804bcb1a9bc905
Subproject commit 31dde8a290f6339c34866b1e15e17ef0041034e9

+ 1
- 1
utils/cobbzilla-wizard

@@ -1 +1 @@
Subproject commit b59b6ec756aa38bfeb358c3431c5dc5b1bf594b8
Subproject commit b171791b38e47716ff299b99b8941ce60e9ee4d9

Laden…
Annuleren
Opslaan